diff --git a/backend/app/api/routes/concept.py b/backend/app/api/routes/concept.py index d7db0ae..c75135c 100644 --- a/backend/app/api/routes/concept.py +++ b/backend/app/api/routes/concept.py @@ -1,6 +1,6 @@ from typing import Any -from fastapi import APIRouter, Body, Depends +from fastapi import APIRouter, Body, Depends, HTTPException from odmantic import AIOEngine from app import schemas @@ -16,7 +16,14 @@ async def create_concept( engine: AIOEngine = Depends(deps.engine_generator), concept_in: schemas.ConceptCreate, ) -> Any: - concept = await crud_concept.create(engine, obj_in=concept_in) + # Check for duplicate concept name + duplicate = await crud_concept.get_by_name(engine, concept_in.name) + if duplicate: + raise HTTPException( + status_code=400, detail="Concept with this name already exists" + ) + + concept = await crud_concept.concept.create(engine, obj_in=concept_in) return concept @@ -36,6 +43,12 @@ async def update_concept( engine: AIOEngine = Depends(deps.engine_generator), concept_in: schemas.ConceptUpdate, ) -> Any: + # Check for duplicate concept name + duplicate = await crud_concept.get_by_name(engine, concept_in.name) + if duplicate and duplicate.id != concept_in.id: + raise HTTPException( + status_code=400, detail="Another concept with this name already exists." + ) db_obj = await crud_concept.get(engine, concept_in.id) concept = await crud_concept.update(engine, db_obj=db_obj, obj_in=concept_in) return concept diff --git a/backend/app/api/routes/document.py b/backend/app/api/routes/document.py index 21e52aa..5ee6054 100644 --- a/backend/app/api/routes/document.py +++ b/backend/app/api/routes/document.py @@ -81,6 +81,21 @@ async def delete_document( return {"msg": "File deleted successfully."} +@router.post("/update", response_model=schemas.DocumentBase) +async def update_document( + *, + engine: AIOEngine = Depends(deps.engine_generator), + document_in: schemas.DocumentUpdate, +) -> Any: + document = await crud_document.get(engine, document_in.id) + if not document: + raise HTTPException(status_code=404, detail="Document not found") + updated_document = await crud_document.update( + engine, db_obj=document, obj_in=document_in + ) + return updated_document + + @router.post("/process", response_model=schemas.Msg) async def process_document( *, @@ -212,14 +227,11 @@ async def get_document_metadata( engine: AIOEngine = Depends(deps.engine_generator), ) -> Any: """Get document metadata including annotations and concepts""" - document, annotations, concepts = await crud_document.get_with_related( - engine, document_id - ) + document, annotations = await crud_document.get_with_related(engine, document_id) if document is None: raise HTTPException(status_code=404, detail="Document not found") return { "document": document, "annotations": annotations, - "concepts": concepts, } diff --git a/backend/app/crud/crud_concept.py b/backend/app/crud/crud_concept.py index 49fb735..5bb47e1 100644 --- a/backend/app/crud/crud_concept.py +++ b/backend/app/crud/crud_concept.py @@ -7,6 +7,10 @@ class CRUDConcept(CRUDBase[Concept, ConceptCreate, ConceptUpdate]): + async def get_by_name(self, engine: AIOEngine, name: str) -> Concept | None: + # Check if a concept with the given name already exists + return await engine.find_one(Concept, Concept.name == name) + async def delete(self, engine: AIOEngine, id: str) -> Concept: concept = await super().delete(engine, id=id) diff --git a/backend/app/crud/crud_document.py b/backend/app/crud/crud_document.py index ac0fcf9..e20dd27 100644 --- a/backend/app/crud/crud_document.py +++ b/backend/app/crud/crud_document.py @@ -52,7 +52,7 @@ async def create(self, engine: AIOEngine, obj_in: DocumentCreate) -> Document: async def get_with_related( self, engine: AIOEngine, id: str - ) -> tuple[Document | None, list[Annotation], list[Concept]]: + ) -> tuple[Document | None, list[Annotation]]: """ Retrieves a document by its file_id along with its associated annotations and concepts. """ @@ -64,14 +64,7 @@ async def get_with_related( # Retrieve annotations associated with this document using get_multi for consistency. annotations = await crud_annotation.get_multi(engine, {"file_id": id}) - # Extract annotation IDs. - annotation_ids = [annotation.id for annotation in annotations] - - # Retrieve concepts linked to the annotations using get_multi. - concepts = await crud_concept.get_multi( - engine, {"annotation_ids": {"$in": annotation_ids}} - ) - return document, annotations, concepts + return document, annotations async def delete(self, engine: AIOEngine, id: str) -> Document: document = await super().delete(engine, id=id) diff --git a/backend/app/schemas/document.py b/backend/app/schemas/document.py index f001a0a..80a41e3 100644 --- a/backend/app/schemas/document.py +++ b/backend/app/schemas/document.py @@ -13,6 +13,7 @@ class DocumentCreate(BaseModel): class DocumentUpdate(BaseModel): + id: str name: str = None metadata: dict[str, Any] | None = None updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) diff --git a/frontend/electron/main/index.ts b/frontend/electron/main/index.ts index cb29401..c852609 100644 --- a/frontend/electron/main/index.ts +++ b/frontend/electron/main/index.ts @@ -103,7 +103,7 @@ app.on('activate', () => { } else { createWindow() } -}) +}); // New window example arg: new windows url ipcMain.handle('open-win', (_, arg) => { diff --git a/frontend/public/pdf.js/external/.gitignore b/frontend/public/pdf.js/external/.gitignore new file mode 100644 index 0000000..33113d1 --- /dev/null +++ b/frontend/public/pdf.js/external/.gitignore @@ -0,0 +1 @@ +cmaps/ diff --git a/frontend/public/pdf.js/external/bcmaps/.gitattributes b/frontend/public/pdf.js/external/bcmaps/.gitattributes new file mode 100644 index 0000000..556f8c8 --- /dev/null +++ b/frontend/public/pdf.js/external/bcmaps/.gitattributes @@ -0,0 +1 @@ +* binary diff --git a/frontend/public/pdf.js/external/bcmaps/78-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/78-EUC-H.bcmap new file mode 100644 index 0000000..2655fc7 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/78-EUC-V.bcmap new file mode 100644 index 0000000..f1ed853 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78-H.bcmap b/frontend/public/pdf.js/external/bcmaps/78-H.bcmap new file mode 100644 index 0000000..39e89d3 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/78-RKSJ-H.bcmap new file mode 100644 index 0000000..e4167cb Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/78-RKSJ-V.bcmap new file mode 100644 index 0000000..50b1646 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78-V.bcmap b/frontend/public/pdf.js/external/bcmaps/78-V.bcmap new file mode 100644 index 0000000..d7af99b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78ms-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/78ms-RKSJ-H.bcmap new file mode 100644 index 0000000..37077d0 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78ms-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/78ms-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/78ms-RKSJ-V.bcmap new file mode 100644 index 0000000..acf2323 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/78ms-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/83pv-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/83pv-RKSJ-H.bcmap new file mode 100644 index 0000000..2359bc5 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/83pv-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/90ms-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/90ms-RKSJ-H.bcmap new file mode 100644 index 0000000..af82938 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/90ms-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/90ms-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/90ms-RKSJ-V.bcmap new file mode 100644 index 0000000..780549d Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/90ms-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/90msp-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/90msp-RKSJ-H.bcmap new file mode 100644 index 0000000..bfd3119 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/90msp-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/90msp-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/90msp-RKSJ-V.bcmap new file mode 100644 index 0000000..25ef14a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/90msp-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/90pv-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/90pv-RKSJ-H.bcmap new file mode 100644 index 0000000..02f713b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/90pv-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/90pv-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/90pv-RKSJ-V.bcmap new file mode 100644 index 0000000..d08e0cc Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/90pv-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Add-H.bcmap b/frontend/public/pdf.js/external/bcmaps/Add-H.bcmap new file mode 100644 index 0000000..59442ac Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Add-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Add-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/Add-RKSJ-H.bcmap new file mode 100644 index 0000000..a3065e4 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Add-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Add-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/Add-RKSJ-V.bcmap new file mode 100644 index 0000000..040014c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Add-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Add-V.bcmap b/frontend/public/pdf.js/external/bcmaps/Add-V.bcmap new file mode 100644 index 0000000..2f816d3 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Add-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-0.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-0.bcmap new file mode 100644 index 0000000..88ec04a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-0.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-1.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-1.bcmap new file mode 100644 index 0000000..03a5014 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-1.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-2.bcmap new file mode 100644 index 0000000..2aa9514 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-3.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-3.bcmap new file mode 100644 index 0000000..86d8b8c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-3.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-4.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-4.bcmap new file mode 100644 index 0000000..f50fc6c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-4.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-5.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-5.bcmap new file mode 100644 index 0000000..6caf4a8 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-5.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-6.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-6.bcmap new file mode 100644 index 0000000..b77fb07 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-6.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-UCS2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-UCS2.bcmap new file mode 100644 index 0000000..69d79a2 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-CNS1-UCS2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-0.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-0.bcmap new file mode 100644 index 0000000..3610108 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-0.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-1.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-1.bcmap new file mode 100644 index 0000000..707bb10 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-1.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-2.bcmap new file mode 100644 index 0000000..f7648cc Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-3.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-3.bcmap new file mode 100644 index 0000000..8521458 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-3.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-4.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-4.bcmap new file mode 100644 index 0000000..e40c63a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-4.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-5.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-5.bcmap new file mode 100644 index 0000000..d7623b5 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-5.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-UCS2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-UCS2.bcmap new file mode 100644 index 0000000..7586525 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-GB1-UCS2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-0.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-0.bcmap new file mode 100644 index 0000000..f0e94ec Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-0.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-1.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-1.bcmap new file mode 100644 index 0000000..dad42c5 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-1.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-2.bcmap new file mode 100644 index 0000000..090819a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-3.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-3.bcmap new file mode 100644 index 0000000..087dfc1 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-3.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-4.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-4.bcmap new file mode 100644 index 0000000..46aa9bf Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-4.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-5.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-5.bcmap new file mode 100644 index 0000000..5b4b65c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-5.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-6.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-6.bcmap new file mode 100644 index 0000000..e77d699 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-6.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-UCS2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-UCS2.bcmap new file mode 100644 index 0000000..128a141 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Japan1-UCS2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-0.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-0.bcmap new file mode 100644 index 0000000..cef1a99 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-0.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-1.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-1.bcmap new file mode 100644 index 0000000..11ffa36 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-1.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-2.bcmap new file mode 100644 index 0000000..3172308 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-UCS2.bcmap b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-UCS2.bcmap new file mode 100644 index 0000000..f3371c0 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Adobe-Korea1-UCS2.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/B5-H.bcmap new file mode 100644 index 0000000..beb4d22 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/B5-V.bcmap new file mode 100644 index 0000000..2d4f87d Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/B5pc-H.bcmap b/frontend/public/pdf.js/external/bcmaps/B5pc-H.bcmap new file mode 100644 index 0000000..ce00131 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/B5pc-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/B5pc-V.bcmap b/frontend/public/pdf.js/external/bcmaps/B5pc-V.bcmap new file mode 100644 index 0000000..73b99ff Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/B5pc-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/CNS-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/CNS-EUC-H.bcmap new file mode 100644 index 0000000..61d1d0c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/CNS-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/CNS-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/CNS-EUC-V.bcmap new file mode 100644 index 0000000..1a393a5 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/CNS-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/CNS1-H.bcmap b/frontend/public/pdf.js/external/bcmaps/CNS1-H.bcmap new file mode 100644 index 0000000..f738e21 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/CNS1-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/CNS1-V.bcmap b/frontend/public/pdf.js/external/bcmaps/CNS1-V.bcmap new file mode 100644 index 0000000..9c3169f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/CNS1-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/CNS2-H.bcmap b/frontend/public/pdf.js/external/bcmaps/CNS2-H.bcmap new file mode 100644 index 0000000..c89b352 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/CNS2-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/CNS2-V.bcmap b/frontend/public/pdf.js/external/bcmaps/CNS2-V.bcmap new file mode 100644 index 0000000..7588cec --- /dev/null +++ b/frontend/public/pdf.js/external/bcmaps/CNS2-V.bcmap @@ -0,0 +1,3 @@ +เRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. +See ./LICENSEแCNS2-H \ No newline at end of file diff --git a/frontend/public/pdf.js/external/bcmaps/ETHK-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/ETHK-B5-H.bcmap new file mode 100644 index 0000000..cb29415 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/ETHK-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/ETHK-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/ETHK-B5-V.bcmap new file mode 100644 index 0000000..f09aec6 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/ETHK-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/ETen-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/ETen-B5-H.bcmap new file mode 100644 index 0000000..c2d7746 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/ETen-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/ETen-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/ETen-B5-V.bcmap new file mode 100644 index 0000000..89bff15 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/ETen-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/ETenms-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/ETenms-B5-H.bcmap new file mode 100644 index 0000000..a7d69db --- /dev/null +++ b/frontend/public/pdf.js/external/bcmaps/ETenms-B5-H.bcmap @@ -0,0 +1,3 @@ +เRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. +See ./LICENSEแ ETen-B5-H` ^ \ No newline at end of file diff --git a/frontend/public/pdf.js/external/bcmaps/ETenms-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/ETenms-B5-V.bcmap new file mode 100644 index 0000000..adc5d61 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/ETenms-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/EUC-H.bcmap new file mode 100644 index 0000000..e92ea5b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/EUC-V.bcmap new file mode 100644 index 0000000..7a7c183 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Ext-H.bcmap b/frontend/public/pdf.js/external/bcmaps/Ext-H.bcmap new file mode 100644 index 0000000..3b5cde4 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Ext-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Ext-RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/Ext-RKSJ-H.bcmap new file mode 100644 index 0000000..ea4d2d9 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Ext-RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Ext-RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/Ext-RKSJ-V.bcmap new file mode 100644 index 0000000..3457c27 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Ext-RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Ext-V.bcmap b/frontend/public/pdf.js/external/bcmaps/Ext-V.bcmap new file mode 100644 index 0000000..4999ca4 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Ext-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GB-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GB-EUC-H.bcmap new file mode 100644 index 0000000..e39908b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GB-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GB-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GB-EUC-V.bcmap new file mode 100644 index 0000000..d5be544 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GB-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GB-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GB-H.bcmap new file mode 100644 index 0000000..39189c5 --- /dev/null +++ b/frontend/public/pdf.js/external/bcmaps/GB-H.bcmap @@ -0,0 +1,4 @@ +เRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. +See ./LICENSE!!บ]aX!!]`21> p z$]‚"R‚d-Uƒ7*„ 4„%+ „Z „{/…%…<9K…b1]†."‡ ‰`]‡,"]ˆ +"]ˆh"]‰F"]Š$"]‹"]‹`"]Œ>"]"]z"]ŽX"]6"]"]r"]‘P"]’."]“ "]“j"]”H"]•&"]–"]–b"]—@"]˜"]˜|"]™Z"]š8"]›"]›t"]œR"]0"]ž"]žl"]ŸJ"] ("]ก"]กd"]ขB"]ฃ "Xฃ~']คW"]ฅ5"]ฆ"]ฆq"]งO"]จ-"]ฉ "]ฉi"]ชG"]ซ%"]ฌ"]ฌa"]ญ?"]ฎ"]ฎ{"]ฏY"]ฐ7"]ฑ"]ฑs"]ฒQ"]ณ/"]ด "]ดk"]ตI"]ถ'"]ท"]ทc"]ธA"]น"]น}"]บ["]ป9 \ No newline at end of file diff --git a/frontend/public/pdf.js/external/bcmaps/GB-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GB-V.bcmap new file mode 100644 index 0000000..3108345 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GB-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBK-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBK-EUC-H.bcmap new file mode 100644 index 0000000..05fff7e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBK-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBK-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBK-EUC-V.bcmap new file mode 100644 index 0000000..0cdf6be Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBK-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBK2K-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBK2K-H.bcmap new file mode 100644 index 0000000..46f6ba5 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBK2K-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBK2K-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBK2K-V.bcmap new file mode 100644 index 0000000..d9a9479 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBK2K-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBKp-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBKp-EUC-H.bcmap new file mode 100644 index 0000000..5cb0af6 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBKp-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBKp-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBKp-EUC-V.bcmap new file mode 100644 index 0000000..bca93b8 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBKp-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBT-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBT-EUC-H.bcmap new file mode 100644 index 0000000..4b4e2d3 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBT-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBT-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBT-EUC-V.bcmap new file mode 100644 index 0000000..38f7066 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBT-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBT-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBT-H.bcmap new file mode 100644 index 0000000..8437ac3 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBT-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBT-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBT-V.bcmap new file mode 100644 index 0000000..697ab4a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBT-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBTpc-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBTpc-EUC-H.bcmap new file mode 100644 index 0000000..f6e50e8 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBTpc-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBTpc-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBTpc-EUC-V.bcmap new file mode 100644 index 0000000..6c0d71a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBTpc-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBpc-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/GBpc-EUC-H.bcmap new file mode 100644 index 0000000..c9edf67 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBpc-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/GBpc-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/GBpc-EUC-V.bcmap new file mode 100644 index 0000000..31450c9 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/GBpc-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/H.bcmap b/frontend/public/pdf.js/external/bcmaps/H.bcmap new file mode 100644 index 0000000..7b24ea4 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKdla-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/HKdla-B5-H.bcmap new file mode 100644 index 0000000..7d30c05 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKdla-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKdla-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/HKdla-B5-V.bcmap new file mode 100644 index 0000000..7894694 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKdla-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKdlb-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/HKdlb-B5-H.bcmap new file mode 100644 index 0000000..d829a23 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKdlb-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKdlb-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/HKdlb-B5-V.bcmap new file mode 100644 index 0000000..2b572b5 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKdlb-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKgccs-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/HKgccs-B5-H.bcmap new file mode 100644 index 0000000..971a4f2 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKgccs-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKgccs-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/HKgccs-B5-V.bcmap new file mode 100644 index 0000000..d353ca2 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKgccs-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKm314-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/HKm314-B5-H.bcmap new file mode 100644 index 0000000..576dc01 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKm314-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKm314-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/HKm314-B5-V.bcmap new file mode 100644 index 0000000..0e96d0e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKm314-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKm471-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/HKm471-B5-H.bcmap new file mode 100644 index 0000000..11d170c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKm471-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKm471-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/HKm471-B5-V.bcmap new file mode 100644 index 0000000..54959bf Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKm471-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKscs-B5-H.bcmap b/frontend/public/pdf.js/external/bcmaps/HKscs-B5-H.bcmap new file mode 100644 index 0000000..6ef7857 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKscs-B5-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/HKscs-B5-V.bcmap b/frontend/public/pdf.js/external/bcmaps/HKscs-B5-V.bcmap new file mode 100644 index 0000000..1fb2fa2 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/HKscs-B5-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Hankaku.bcmap b/frontend/public/pdf.js/external/bcmaps/Hankaku.bcmap new file mode 100644 index 0000000..4b8ec7f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Hankaku.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Hiragana.bcmap b/frontend/public/pdf.js/external/bcmaps/Hiragana.bcmap new file mode 100644 index 0000000..17e983e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Hiragana.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSC-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/KSC-EUC-H.bcmap new file mode 100644 index 0000000..a45c65f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSC-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSC-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/KSC-EUC-V.bcmap new file mode 100644 index 0000000..0e7b21f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSC-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/KSC-H.bcmap new file mode 100644 index 0000000..b9b22b6 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSC-Johab-H.bcmap b/frontend/public/pdf.js/external/bcmaps/KSC-Johab-H.bcmap new file mode 100644 index 0000000..2531ffc Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSC-Johab-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSC-Johab-V.bcmap b/frontend/public/pdf.js/external/bcmaps/KSC-Johab-V.bcmap new file mode 100644 index 0000000..367ceb2 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSC-Johab-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/KSC-V.bcmap new file mode 100644 index 0000000..6ae2f0b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-H.bcmap new file mode 100644 index 0000000..a8d4240 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-HW-H.bcmap b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-HW-H.bcmap new file mode 100644 index 0000000..8b4ae18 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-HW-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-HW-V.bcmap b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-HW-V.bcmap new file mode 100644 index 0000000..b655dbc Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-HW-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-V.bcmap new file mode 100644 index 0000000..21f97f6 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSCms-UHC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSCpc-EUC-H.bcmap b/frontend/public/pdf.js/external/bcmaps/KSCpc-EUC-H.bcmap new file mode 100644 index 0000000..e06f361 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSCpc-EUC-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/KSCpc-EUC-V.bcmap b/frontend/public/pdf.js/external/bcmaps/KSCpc-EUC-V.bcmap new file mode 100644 index 0000000..f3c9113 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/KSCpc-EUC-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Katakana.bcmap b/frontend/public/pdf.js/external/bcmaps/Katakana.bcmap new file mode 100644 index 0000000..524303c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Katakana.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/LICENSE b/frontend/public/pdf.js/external/bcmaps/LICENSE new file mode 100644 index 0000000..b1ad168 --- /dev/null +++ b/frontend/public/pdf.js/external/bcmaps/LICENSE @@ -0,0 +1,36 @@ +%%Copyright: ----------------------------------------------------------- +%%Copyright: Copyright 1990-2009 Adobe Systems Incorporated. +%%Copyright: All rights reserved. +%%Copyright: +%%Copyright: Redistribution and use in source and binary forms, with or +%%Copyright: without modification, are permitted provided that the +%%Copyright: following conditions are met: +%%Copyright: +%%Copyright: Redistributions of source code must retain the above +%%Copyright: copyright notice, this list of conditions and the following +%%Copyright: disclaimer. +%%Copyright: +%%Copyright: Redistributions in binary form must reproduce the above +%%Copyright: copyright notice, this list of conditions and the following +%%Copyright: disclaimer in the documentation and/or other materials +%%Copyright: provided with the distribution. +%%Copyright: +%%Copyright: Neither the name of Adobe Systems Incorporated nor the names +%%Copyright: of its contributors may be used to endorse or promote +%%Copyright: products derived from this software without specific prior +%%Copyright: written permission. +%%Copyright: +%%Copyright: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +%%Copyright: CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +%%Copyright: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +%%Copyright: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +%%Copyright: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +%%Copyright: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +%%Copyright: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +%%Copyright: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +%%Copyright: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +%%Copyright: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +%%Copyright: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +%%Copyright: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +%%Copyright: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +%%Copyright: ----------------------------------------------------------- diff --git a/frontend/public/pdf.js/external/bcmaps/NWP-H.bcmap b/frontend/public/pdf.js/external/bcmaps/NWP-H.bcmap new file mode 100644 index 0000000..afc5e4b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/NWP-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/NWP-V.bcmap b/frontend/public/pdf.js/external/bcmaps/NWP-V.bcmap new file mode 100644 index 0000000..bb5785e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/NWP-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/RKSJ-H.bcmap b/frontend/public/pdf.js/external/bcmaps/RKSJ-H.bcmap new file mode 100644 index 0000000..fb8d298 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/RKSJ-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/RKSJ-V.bcmap b/frontend/public/pdf.js/external/bcmaps/RKSJ-V.bcmap new file mode 100644 index 0000000..a2555a6 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/RKSJ-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/Roman.bcmap b/frontend/public/pdf.js/external/bcmaps/Roman.bcmap new file mode 100644 index 0000000..f896dcf Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/Roman.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UCS2-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UCS2-H.bcmap new file mode 100644 index 0000000..d5db27c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UCS2-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UCS2-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UCS2-V.bcmap new file mode 100644 index 0000000..1dc9b7a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UCS2-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF16-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF16-H.bcmap new file mode 100644 index 0000000..961afef Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF16-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF16-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF16-V.bcmap new file mode 100644 index 0000000..df0cffe Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF16-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF32-H.bcmap new file mode 100644 index 0000000..1ab18a1 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF32-V.bcmap new file mode 100644 index 0000000..ad14662 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF8-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF8-H.bcmap new file mode 100644 index 0000000..83c6bd7 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF8-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF8-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF8-V.bcmap new file mode 100644 index 0000000..22a27e4 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniCNS-UTF8-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UCS2-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UCS2-H.bcmap new file mode 100644 index 0000000..5bd6228 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UCS2-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UCS2-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UCS2-V.bcmap new file mode 100644 index 0000000..53c534b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UCS2-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UTF16-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF16-H.bcmap new file mode 100644 index 0000000..b95045b Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF16-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UTF16-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF16-V.bcmap new file mode 100644 index 0000000..51f023e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF16-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF32-H.bcmap new file mode 100644 index 0000000..f0dbd14 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF32-V.bcmap new file mode 100644 index 0000000..ce9c30a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UTF8-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF8-H.bcmap new file mode 100644 index 0000000..982ca46 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF8-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniGB-UTF8-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF8-V.bcmap new file mode 100644 index 0000000..f78020d Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniGB-UTF8-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-H.bcmap new file mode 100644 index 0000000..7daf56a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-HW-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-HW-H.bcmap new file mode 100644 index 0000000..ac9975c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-HW-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-HW-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-HW-V.bcmap new file mode 100644 index 0000000..3da0a1c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-HW-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-V.bcmap new file mode 100644 index 0000000..c50b9dd Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UCS2-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF16-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF16-H.bcmap new file mode 100644 index 0000000..6761344 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF16-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF16-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF16-V.bcmap new file mode 100644 index 0000000..70bf90c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF16-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF32-H.bcmap new file mode 100644 index 0000000..7a83d53 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF32-V.bcmap new file mode 100644 index 0000000..7a87135 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF8-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF8-H.bcmap new file mode 100644 index 0000000..9f0334c Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF8-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF8-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF8-V.bcmap new file mode 100644 index 0000000..808a94f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS-UTF8-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF16-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF16-H.bcmap new file mode 100644 index 0000000..d768bf8 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF16-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF16-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF16-V.bcmap new file mode 100644 index 0000000..3d5bf6f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF16-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF32-H.bcmap new file mode 100644 index 0000000..09eee10 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF32-V.bcmap new file mode 100644 index 0000000..6c54600 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF8-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF8-H.bcmap new file mode 100644 index 0000000..1b1a64f Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF8-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF8-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF8-V.bcmap new file mode 100644 index 0000000..994aa9e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJIS2004-UTF8-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISPro-UCS2-HW-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISPro-UCS2-HW-V.bcmap new file mode 100644 index 0000000..643f921 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISPro-UCS2-HW-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISPro-UCS2-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISPro-UCS2-V.bcmap new file mode 100644 index 0000000..c148f67 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISPro-UCS2-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISPro-UTF8-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISPro-UTF8-V.bcmap new file mode 100644 index 0000000..1849d80 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISPro-UTF8-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISX0213-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISX0213-UTF32-H.bcmap new file mode 100644 index 0000000..a83a677 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISX0213-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISX0213-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISX0213-UTF32-V.bcmap new file mode 100644 index 0000000..f527248 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISX0213-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISX02132004-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISX02132004-UTF32-H.bcmap new file mode 100644 index 0000000..e1a988d Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISX02132004-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniJISX02132004-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniJISX02132004-UTF32-V.bcmap new file mode 100644 index 0000000..47e054a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniJISX02132004-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UCS2-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UCS2-H.bcmap new file mode 100644 index 0000000..b5b9485 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UCS2-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UCS2-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UCS2-V.bcmap new file mode 100644 index 0000000..026adca Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UCS2-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UTF16-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF16-H.bcmap new file mode 100644 index 0000000..fd4e66e Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF16-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UTF16-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF16-V.bcmap new file mode 100644 index 0000000..075efb7 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF16-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UTF32-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF32-H.bcmap new file mode 100644 index 0000000..769d214 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF32-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UTF32-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF32-V.bcmap new file mode 100644 index 0000000..bdab208 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF32-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UTF8-H.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF8-H.bcmap new file mode 100644 index 0000000..6ff8674 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF8-H.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/UniKS-UTF8-V.bcmap b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF8-V.bcmap new file mode 100644 index 0000000..8dfa76a Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/UniKS-UTF8-V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/V.bcmap b/frontend/public/pdf.js/external/bcmaps/V.bcmap new file mode 100644 index 0000000..fdec990 Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/V.bcmap differ diff --git a/frontend/public/pdf.js/external/bcmaps/WP-Symbol.bcmap b/frontend/public/pdf.js/external/bcmaps/WP-Symbol.bcmap new file mode 100644 index 0000000..46729bb Binary files /dev/null and b/frontend/public/pdf.js/external/bcmaps/WP-Symbol.bcmap differ diff --git a/frontend/public/pdf.js/external/builder/babel-plugin-pdfjs-preprocessor.mjs b/frontend/public/pdf.js/external/builder/babel-plugin-pdfjs-preprocessor.mjs new file mode 100644 index 0000000..ff92a7a --- /dev/null +++ b/frontend/public/pdf.js/external/builder/babel-plugin-pdfjs-preprocessor.mjs @@ -0,0 +1,257 @@ +import { types as t, transformSync } from "@babel/core"; +import vm from "vm"; + +const PDFJS_PREPROCESSOR_NAME = "PDFJSDev"; + +function isPDFJSPreprocessor(obj) { + return obj.type === "Identifier" && obj.name === PDFJS_PREPROCESSOR_NAME; +} + +function evalWithDefines(code, defines) { + if (!code?.trim()) { + throw new Error("No JavaScript expression given"); + } + return vm.runInNewContext(code, defines, { displayErrors: false }); +} + +function handlePreprocessorAction(ctx, actionName, args, path) { + try { + const arg = args[0]; + switch (actionName) { + case "test": + if (!t.isStringLiteral(arg)) { + throw new Error("No code for testing is given"); + } + return !!evalWithDefines(arg.value, ctx.defines); + case "eval": + if (!t.isStringLiteral(arg)) { + throw new Error("No code for eval is given"); + } + const result = evalWithDefines(arg.value, ctx.defines); + if ( + typeof result === "boolean" || + typeof result === "string" || + typeof result === "number" || + typeof result === "object" + ) { + return result; + } + break; + } + throw new Error("Unsupported action"); + } catch (e) { + throw path.buildCodeFrameError( + `Could not process ${PDFJS_PREPROCESSOR_NAME}.${actionName}: ${e.message}` + ); + } +} + +function babelPluginPDFJSPreprocessor(babel, ctx) { + return { + name: "babel-plugin-pdfjs-preprocessor", + manipulateOptions({ parserOpts }) { + parserOpts.attachComment = false; + }, + visitor: { + "ExportNamedDeclaration|ImportDeclaration": ({ node }) => { + if (node.source && ctx.map?.[node.source.value]) { + node.source.value = ctx.map[node.source.value]; + } + }, + "IfStatement|ConditionalExpression": { + exit(path) { + const { node } = path; + if (t.isBooleanLiteral(node.test)) { + // if (true) stmt1; => stmt1 + // if (false) stmt1; else stmt2; => stmt2 + if (node.test.value === true) { + path.replaceWith(node.consequent); + } else if (node.alternate) { + path.replaceWith(node.alternate); + } else { + path.remove(node); + } + } + }, + }, + UnaryExpression: { + exit(path) { + const { node } = path; + if ( + node.operator === "typeof" && + isPDFJSPreprocessor(node.argument) + ) { + // typeof PDFJSDev => 'object' + path.replaceWith(t.stringLiteral("object")); + return; + } + if (node.operator === "!" && t.isBooleanLiteral(node.argument)) { + // !true => false, !false => true + path.replaceWith(t.booleanLiteral(!node.argument.value)); + } + }, + }, + LogicalExpression: { + exit(path) { + const { node } = path; + if (!t.isBooleanLiteral(node.left)) { + return; + } + + switch (node.operator) { + case "&&": + // true && expr => expr + // false && expr => false + path.replaceWith( + node.left.value === true ? node.right : node.left + ); + break; + case "||": + // true || expr => true + // false || expr => expr + path.replaceWith( + node.left.value === true ? node.left : node.right + ); + break; + } + }, + }, + BinaryExpression: { + exit(path) { + const { node } = path; + switch (node.operator) { + case "==": + case "===": + case "!=": + case "!==": + if (t.isLiteral(node.left) && t.isLiteral(node.right)) { + // folding == and != check that can be statically evaluated + const { confident, value } = path.evaluate(); + if (confident) { + path.replaceWith(t.booleanLiteral(value)); + } + } + } + }, + }, + CallExpression(path) { + const { node } = path; + if ( + t.isMemberExpression(node.callee) && + isPDFJSPreprocessor(node.callee.object) && + t.isIdentifier(node.callee.property) && + !node.callee.computed + ) { + // PDFJSDev.xxxx(arg1, arg2, ...) => transform + const action = node.callee.property.name; + const result = handlePreprocessorAction( + ctx, + action, + node.arguments, + path + ); + path.replaceWith(t.inherits(t.valueToNode(result), path.node)); + } + + if (t.isIdentifier(node.callee, { name: "__non_webpack_import__" })) { + if (node.arguments.length !== 1) { + throw new Error("Invalid `__non_webpack_import__` usage."); + } + // Replace it with a standard `import`-call and + // ensure that Webpack will leave it alone. + const source = node.arguments[0]; + source.leadingComments = [ + { + type: "CommentBlock", + value: "webpackIgnore: true", + }, + ]; + path.replaceWith(t.importExpression(source)); + } + }, + "BlockStatement|StaticBlock": { + // Visit node in post-order so that recursive flattening + // of blocks works correctly. + exit(path) { + const { node } = path; + + let subExpressionIndex = 0; + while (subExpressionIndex < node.body.length) { + switch (node.body[subExpressionIndex].type) { + case "EmptyStatement": + // Removing empty statements from the blocks. + node.body.splice(subExpressionIndex, 1); + continue; + case "BlockStatement": + // Block statements inside a block are flattened + // into the parent one. + const subChildren = node.body[subExpressionIndex].body; + node.body.splice(subExpressionIndex, 1, ...subChildren); + subExpressionIndex += Math.max(subChildren.length - 1, 0); + continue; + case "ReturnStatement": + case "ThrowStatement": + // Removing dead code after return or throw. + node.body.splice( + subExpressionIndex + 1, + node.body.length - subExpressionIndex - 1 + ); + break; + } + subExpressionIndex++; + } + + if (node.type === "StaticBlock" && node.body.length === 0) { + path.remove(); + } + }, + }, + Function: { + exit(path) { + if (!t.isBlockStatement(path.node.body)) { + // Arrow function with expression body + return; + } + + const { body } = path.node.body; + if ( + body.length > 0 && + t.isReturnStatement(body.at(-1), { argument: null }) + ) { + // Function body ends with return without arg -- removing it. + body.pop(); + } + }, + }, + ClassMethod: { + exit(path) { + const { + node, + parentPath: { parent: classNode }, + } = path; + if ( + // Remove empty constructors. We only do this for + // base classes, as the default constructor of derived + // classes is not empty (and an empty constructor + // must throw at runtime when constructed). + node.kind === "constructor" && + node.body.body.length === 0 && + node.params.every(p => p.type === "Identifier") && + !classNode.superClass + ) { + path.remove(); + } + }, + }, + }, + }; +} + +function preprocessPDFJSCode(ctx, content) { + return transformSync(content, { + configFile: false, + plugins: [[babelPluginPDFJSPreprocessor, ctx]], + }).code; +} + +export { babelPluginPDFJSPreprocessor, preprocessPDFJSCode }; diff --git a/frontend/public/pdf.js/external/builder/builder.mjs b/frontend/public/pdf.js/external/builder/builder.mjs new file mode 100644 index 0000000..929a9cf --- /dev/null +++ b/frontend/public/pdf.js/external/builder/builder.mjs @@ -0,0 +1,232 @@ +import fs from "fs"; +import path from "path"; +import vm from "vm"; + +const AllWhitespaceRegexp = /^\s+$/g; + +/** + * A simple preprocessor that is based on the Firefox preprocessor + * (https://dxr.mozilla.org/mozilla-central/source/build/docs/preprocessor.rst). + * The main difference is that this supports a subset of the commands and it + * supports preprocessor commands in HTML-style comments. + * + * Currently supported commands: + * - if + * - elif + * - else + * - endif + * - include + * - expand + * - error + * + * Every #if must be closed with an #endif. Nested conditions are supported. + * + * Within an #if or #else block, one level of comment tokens is stripped. This + * allows us to write code that can run even without preprocessing. For example: + * + * //#if SOME_RARE_CONDITION + * // // Decrement by one + * // --i; + * //#else + * // // Increment by one. + * ++i; + * //#endif + */ +function preprocess(inFilename, outFilename, defines) { + let lineNumber = 0; + function loc() { + return fs.realpathSync(inFilename) + ":" + lineNumber; + } + + function expandCssImports(content, baseUrl) { + return content.replaceAll( + /^\s*@import\s+url\(([^)]+)\);\s*$/gm, + function (all, url) { + if (defines.GECKOVIEW) { + switch (url) { + case "annotation_editor_layer_builder.css": + return ""; + } + } + const file = path.join(path.dirname(baseUrl), url); + const imported = fs.readFileSync(file, "utf8").toString(); + return expandCssImports(imported, file); + } + ); + } + + // TODO make this really read line by line. + let content = fs.readFileSync(inFilename, "utf8").toString(); + // Handle CSS-imports first, when necessary. + if (/\.css$/i.test(inFilename)) { + content = expandCssImports(content, inFilename); + } + const lines = content.split("\n"), + totalLines = lines.length; + const out = []; + let i = 0; + function readLine() { + if (i < totalLines) { + return lines[i++]; + } + return null; + } + const writeLine = + typeof outFilename === "function" + ? outFilename + : function (line) { + if (!line || AllWhitespaceRegexp.test(line)) { + const prevLine = out.at(-1); + if (!prevLine || AllWhitespaceRegexp.test(prevLine)) { + return; // Avoid adding consecutive blank lines. + } + } + out.push(line); + }; + function evaluateCondition(code) { + if (!code?.trim()) { + throw new Error("No JavaScript expression given at " + loc()); + } + try { + return vm.runInNewContext(code, defines, { displayErrors: false }); + } catch (e) { + throw new Error( + 'Could not evaluate "' + + code + + '" at ' + + loc() + + "\n" + + e.name + + ": " + + e.message + ); + } + } + function include(file) { + const realPath = fs.realpathSync(inFilename); + const dir = path.dirname(realPath); + try { + let fullpath; + if (file.indexOf("$ROOT/") === 0) { + fullpath = path.join( + __dirname, + "../..", + file.substring("$ROOT/".length) + ); + } else { + fullpath = path.join(dir, file); + } + preprocess(fullpath, writeLine, defines); + } catch (e) { + if (e.code === "ENOENT") { + throw new Error('Failed to include "' + file + '" at ' + loc()); + } + throw e; // Some other error + } + } + function expand(line) { + line = line.replaceAll(/__[\w]+__/g, function (variable) { + variable = variable.substring(2, variable.length - 2); + if (variable in defines) { + return defines[variable]; + } + return ""; + }); + writeLine(line); + } + + // not inside if or else (process lines) + const STATE_NONE = 0; + // inside if, condition false (ignore until #else or #endif) + const STATE_IF_FALSE = 1; + // inside else, #if was false, so #else is true (process lines until #endif) + const STATE_ELSE_TRUE = 2; + // inside if, condition true (process lines until #else or #endif) + const STATE_IF_TRUE = 3; + // inside else or elif, #if/#elif was true, so following #else or #elif is + // false (ignore lines until #endif) + const STATE_ELSE_FALSE = 4; + + let line; + let state = STATE_NONE; + const stack = []; + const control = + /^(?:\/\/|\s*\/\*|)?$)?/; + + while ((line = readLine()) !== null) { + ++lineNumber; + const m = control.exec(line); + if (m) { + switch (m[1]) { + case "if": + stack.push(state); + state = evaluateCondition(m[2]) ? STATE_IF_TRUE : STATE_IF_FALSE; + break; + case "elif": + if (state === STATE_IF_TRUE || state === STATE_ELSE_FALSE) { + state = STATE_ELSE_FALSE; + } else if (state === STATE_IF_FALSE) { + state = evaluateCondition(m[2]) ? STATE_IF_TRUE : STATE_IF_FALSE; + } else if (state === STATE_ELSE_TRUE) { + throw new Error("Found #elif after #else at " + loc()); + } else { + throw new Error("Found #elif without matching #if at " + loc()); + } + break; + case "else": + if (state === STATE_IF_TRUE || state === STATE_ELSE_FALSE) { + state = STATE_ELSE_FALSE; + } else if (state === STATE_IF_FALSE) { + state = STATE_ELSE_TRUE; + } else { + throw new Error("Found #else without matching #if at " + loc()); + } + break; + case "endif": + if (state === STATE_NONE) { + throw new Error("Found #endif without #if at " + loc()); + } + state = stack.pop(); + break; + case "expand": + if (state !== STATE_IF_FALSE && state !== STATE_ELSE_FALSE) { + expand(m[2]); + } + break; + case "include": + if (state !== STATE_IF_FALSE && state !== STATE_ELSE_FALSE) { + include(m[2]); + } + break; + case "error": + if (state !== STATE_IF_FALSE && state !== STATE_ELSE_FALSE) { + throw new Error("Found #error " + m[2] + " at " + loc()); + } + break; + } + } else if (state === STATE_NONE) { + writeLine(line); + } else if ( + (state === STATE_IF_TRUE || state === STATE_ELSE_TRUE) && + !stack.includes(STATE_IF_FALSE) && + !stack.includes(STATE_ELSE_FALSE) + ) { + writeLine( + line + .replaceAll(/^\/\/|^$/g, "") + ); + } + } + if (state !== STATE_NONE || stack.length !== 0) { + throw new Error( + "Missing #endif in preprocessor for " + fs.realpathSync(inFilename) + ); + } + if (typeof outFilename !== "function") { + fs.writeFileSync(outFilename, out.join("\n")); + } +} + +export { preprocess }; diff --git a/frontend/public/pdf.js/external/builder/fixtures/confusing-comment-expected.js b/frontend/public/pdf.js/external/builder/fixtures/confusing-comment-expected.js new file mode 100644 index 0000000..018f084 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/confusing-comment-expected.js @@ -0,0 +1,4 @@ +'use strict'; +var i = 0; +while(i-->0) { +} diff --git a/frontend/public/pdf.js/external/builder/fixtures/confusing-comment.js b/frontend/public/pdf.js/external/builder/fixtures/confusing-comment.js new file mode 100644 index 0000000..ac626b9 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/confusing-comment.js @@ -0,0 +1,6 @@ +'use strict'; +//#if TRUE +var i = 0; +while(i-->0) { +} +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/css-comment-expected.css b/frontend/public/pdf.js/external/builder/fixtures/css-comment-expected.css new file mode 100644 index 0000000..7972ddd --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/css-comment-expected.css @@ -0,0 +1,5 @@ +/* Comment here... */ + div { + margin: 0; + padding: 0; + } diff --git a/frontend/public/pdf.js/external/builder/fixtures/css-comment.css b/frontend/public/pdf.js/external/builder/fixtures/css-comment.css new file mode 100644 index 0000000..bdfe64c --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/css-comment.css @@ -0,0 +1,7 @@ +/* Comment here... */ +/*#if TRUE*/ +/*div {*/ +/* margin: 0;*/ + /*padding: 0;*/ +/*}*/ +/*#endif*/ diff --git a/frontend/public/pdf.js/external/builder/fixtures/elif-expected.js b/frontend/public/pdf.js/external/builder/fixtures/elif-expected.js new file mode 100644 index 0000000..c2ef779 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/elif-expected.js @@ -0,0 +1 @@ +//Error: Found #elif without matching #if at __filename:2 diff --git a/frontend/public/pdf.js/external/builder/fixtures/elif.js b/frontend/public/pdf.js/external/builder/fixtures/elif.js new file mode 100644 index 0000000..3900de2 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/elif.js @@ -0,0 +1,4 @@ +'use strict'; +//#elif TRUE +var a; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/else-expected.js b/frontend/public/pdf.js/external/builder/fixtures/else-expected.js new file mode 100644 index 0000000..a623ae5 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/else-expected.js @@ -0,0 +1 @@ +//Error: Found #else without matching #if at __filename:2 diff --git a/frontend/public/pdf.js/external/builder/fixtures/else.js b/frontend/public/pdf.js/external/builder/fixtures/else.js new file mode 100644 index 0000000..f28b328 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/else.js @@ -0,0 +1,3 @@ +'use strict'; +//#else +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/error-expected.js b/frontend/public/pdf.js/external/builder/fixtures/error-expected.js new file mode 100644 index 0000000..7e061ad --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/error-expected.js @@ -0,0 +1 @@ +//Error: Found #error "Some Error" at __filename:3 diff --git a/frontend/public/pdf.js/external/builder/fixtures/error-false-expected.js b/frontend/public/pdf.js/external/builder/fixtures/error-false-expected.js new file mode 100644 index 0000000..0220a00 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/error-false-expected.js @@ -0,0 +1,2 @@ +'use strict'; +var a; diff --git a/frontend/public/pdf.js/external/builder/fixtures/error-false.js b/frontend/public/pdf.js/external/builder/fixtures/error-false.js new file mode 100644 index 0000000..e5b8153 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/error-false.js @@ -0,0 +1,5 @@ +'use strict'; +//#if FALSE +//#error "Some Error" +//#endif +var a; diff --git a/frontend/public/pdf.js/external/builder/fixtures/error.js b/frontend/public/pdf.js/external/builder/fixtures/error.js new file mode 100644 index 0000000..facd0e8 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/error.js @@ -0,0 +1,5 @@ +'use strict'; +//#if TRUE +//#error "Some Error" +//#endif +var b; diff --git a/frontend/public/pdf.js/external/builder/fixtures/expand-expected.html b/frontend/public/pdf.js/external/builder/fixtures/expand-expected.html new file mode 100644 index 0000000..89cc081 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/expand-expected.html @@ -0,0 +1 @@ +prefixtruesuffix diff --git a/frontend/public/pdf.js/external/builder/fixtures/expand.html b/frontend/public/pdf.js/external/builder/fixtures/expand.html new file mode 100644 index 0000000..a2247ea --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/expand.html @@ -0,0 +1 @@ + diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-empty-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-empty-expected.js new file mode 100644 index 0000000..5dd4f66 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-empty-expected.js @@ -0,0 +1 @@ +//Error: No JavaScript expression given at __filename:2 diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-empty.js b/frontend/public/pdf.js/external/builder/fixtures/if-empty.js new file mode 100644 index 0000000..81ad761 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-empty.js @@ -0,0 +1,6 @@ +'use strict'; +//#if +var a; +//#else +var b; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-false-else-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-false-else-expected.js new file mode 100644 index 0000000..724f9bd --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-false-else-expected.js @@ -0,0 +1,2 @@ +'use strict'; +var c; diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-false-else.js b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-false-else.js new file mode 100644 index 0000000..e3f6aca --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-false-else.js @@ -0,0 +1,8 @@ +'use strict'; +//#if FALSE +var a; +//#elif FALSE +var b; +//#else +var c; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-true-else-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-true-else-expected.js new file mode 100644 index 0000000..fe04f49 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-true-else-expected.js @@ -0,0 +1,2 @@ +'use strict'; +var b; diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-true-else.js b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-true-else.js new file mode 100644 index 0000000..2a37d2f --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-false-elif-true-else.js @@ -0,0 +1,8 @@ +'use strict'; +//#if FALSE +var a; +//#elif TRUE +var b; +//#else +var c; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-false-else-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-false-else-expected.js new file mode 100644 index 0000000..fe04f49 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-false-else-expected.js @@ -0,0 +1,2 @@ +'use strict'; +var b; diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-false-else.js b/frontend/public/pdf.js/external/builder/fixtures/if-false-else.js new file mode 100644 index 0000000..831ca99 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-false-else.js @@ -0,0 +1,6 @@ +'use strict'; +//#if FALSE +var a; +//#else +var b; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-nested-expected.css b/frontend/public/pdf.js/external/builder/fixtures/if-nested-expected.css new file mode 100644 index 0000000..e3859d7 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-nested-expected.css @@ -0,0 +1,3 @@ +div { + margin: 0; +} diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-nested-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-nested-expected.js new file mode 100644 index 0000000..7dd77df --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-nested-expected.js @@ -0,0 +1,6 @@ +'use strict'; +var a; + +var b; + +var d; diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-nested.css b/frontend/public/pdf.js/external/builder/fixtures/if-nested.css new file mode 100644 index 0000000..67ae1b3 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-nested.css @@ -0,0 +1,14 @@ +/*#if TRUE*/ +div { + margin: 0; + /*#if FALSE*/ + padding: 0; + /*#endif*/ +} +/*#endif*/ + +/*#if FALSE*/ +p { + margin: 0; +} +/*#endif*/ diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-nested.js b/frontend/public/pdf.js/external/builder/fixtures/if-nested.js new file mode 100644 index 0000000..d49180a --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-nested.js @@ -0,0 +1,19 @@ +'use strict'; +//#if TRUE +var a; + +//#if TRUE +var b; +//#else +var c; +//#endif + +var d; +//#else +var e; +//#if TRUE +var f; +//#endif + +var g; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-true-elif-false-else-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-true-elif-false-else-expected.js new file mode 100644 index 0000000..0220a00 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-true-elif-false-else-expected.js @@ -0,0 +1,2 @@ +'use strict'; +var a; diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-true-elif-false-else.js b/frontend/public/pdf.js/external/builder/fixtures/if-true-elif-false-else.js new file mode 100644 index 0000000..f00de95 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-true-elif-false-else.js @@ -0,0 +1,8 @@ +'use strict'; +//#if TRUE +var a; +//#elif FALSE +var b; +//#else +var c; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-true-else-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-true-else-expected.js new file mode 100644 index 0000000..0220a00 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-true-else-expected.js @@ -0,0 +1,2 @@ +'use strict'; +var a; diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-true-else.js b/frontend/public/pdf.js/external/builder/fixtures/if-true-else.js new file mode 100644 index 0000000..c4ed6cb --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-true-else.js @@ -0,0 +1,6 @@ +'use strict'; +//#if TRUE +var a; +//#else +var b; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-unclosed-expected.js b/frontend/public/pdf.js/external/builder/fixtures/if-unclosed-expected.js new file mode 100644 index 0000000..0ad0210 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-unclosed-expected.js @@ -0,0 +1 @@ +//Error: Missing #endif in preprocessor for __filename diff --git a/frontend/public/pdf.js/external/builder/fixtures/if-unclosed.js b/frontend/public/pdf.js/external/builder/fixtures/if-unclosed.js new file mode 100644 index 0000000..b74a4fb --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/if-unclosed.js @@ -0,0 +1,3 @@ +'use strict'; +//#if TRUE +var a; diff --git a/frontend/public/pdf.js/external/builder/fixtures/include-expected.html b/frontend/public/pdf.js/external/builder/fixtures/include-expected.html new file mode 100644 index 0000000..0c0f019 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/include-expected.html @@ -0,0 +1,5 @@ + diff --git a/frontend/public/pdf.js/external/builder/fixtures/include-non-existent-expected.html b/frontend/public/pdf.js/external/builder/fixtures/include-non-existent-expected.html new file mode 100644 index 0000000..c9a0b5b --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/include-non-existent-expected.html @@ -0,0 +1 @@ +//Error: Failed to include "some file that does not exist" at __filename:2 diff --git a/frontend/public/pdf.js/external/builder/fixtures/include-non-existent.html b/frontend/public/pdf.js/external/builder/fixtures/include-non-existent.html new file mode 100644 index 0000000..9b892ab --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/include-non-existent.html @@ -0,0 +1,2 @@ + + diff --git a/frontend/public/pdf.js/external/builder/fixtures/include.html b/frontend/public/pdf.js/external/builder/fixtures/include.html new file mode 100644 index 0000000..44352b5 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/include.html @@ -0,0 +1,5 @@ + diff --git a/frontend/public/pdf.js/external/builder/fixtures/js-comment-expected.js b/frontend/public/pdf.js/external/builder/fixtures/js-comment-expected.js new file mode 100644 index 0000000..a0b6ada --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/js-comment-expected.js @@ -0,0 +1,4 @@ +'use strict'; + //var a; + var b; +var c; diff --git a/frontend/public/pdf.js/external/builder/fixtures/js-comment.js b/frontend/public/pdf.js/external/builder/fixtures/js-comment.js new file mode 100644 index 0000000..361f9e8 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/js-comment.js @@ -0,0 +1,6 @@ +'use strict'; +//#if TRUE +////var a; +//var b; +var c; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/undefined-define-expected.js b/frontend/public/pdf.js/external/builder/fixtures/undefined-define-expected.js new file mode 100644 index 0000000..67bfbca --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/undefined-define-expected.js @@ -0,0 +1,2 @@ +//Error: Could not evaluate "notdefined" at __filename:2 +//ReferenceError: notdefined is not defined diff --git a/frontend/public/pdf.js/external/builder/fixtures/undefined-define.js b/frontend/public/pdf.js/external/builder/fixtures/undefined-define.js new file mode 100644 index 0000000..a59f416 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/undefined-define.js @@ -0,0 +1,6 @@ +'use strict'; +//#if notdefined +var a; +//#else +var b; +//#endif diff --git a/frontend/public/pdf.js/external/builder/fixtures/unsupported-ifdef-expected.js b/frontend/public/pdf.js/external/builder/fixtures/unsupported-ifdef-expected.js new file mode 100644 index 0000000..7b9907b --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/unsupported-ifdef-expected.js @@ -0,0 +1 @@ +//Error: Found #endif without #if at __filename:4 diff --git a/frontend/public/pdf.js/external/builder/fixtures/unsupported-ifdef.js b/frontend/public/pdf.js/external/builder/fixtures/unsupported-ifdef.js new file mode 100644 index 0000000..b3c1d81 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures/unsupported-ifdef.js @@ -0,0 +1,5 @@ +'use strict'; +//#ifdef TRUE +//ifdef should not be recognized +//#endif +var a; diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/blocks-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/blocks-expected.js new file mode 100644 index 0000000..22e7b74 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/blocks-expected.js @@ -0,0 +1,10 @@ +function test() { + "test"; + "1"; + "2"; + "3"; + if ("test") { + "5"; + } + "4"; +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/blocks.js b/frontend/public/pdf.js/external/builder/fixtures_babel/blocks.js new file mode 100644 index 0000000..a7f3c36 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/blocks.js @@ -0,0 +1,19 @@ +function test() { + {;} + ; + "test"; + { + "1"; + if (true) { + "2"; + } + ; + { + "3"; + if ("test") { + "5"; + } + } + "4"; + } +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/comments-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/comments-expected.js new file mode 100644 index 0000000..24488a4 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/comments-expected.js @@ -0,0 +1,17 @@ +function f1() { + "1"; + "2"; +} +function f2() { + "1"; + "2"; +} +function f3() { + if ("1") { + "1"; + } + "2"; + if ("3") { + "4"; + } +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/comments.js b/frontend/public/pdf.js/external/builder/fixtures_babel/comments.js new file mode 100644 index 0000000..5c7758c --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/comments.js @@ -0,0 +1,26 @@ +/* globals f0 */ +function f1() { + /* head */ + "1"; + /* mid */ + "2"; + /* tail */ +} + +function f2() { + // head + "1"; + // mid + "2"; + // tail +} + +function f3() { + if ("1") { // begin block + "1"; + } + "2"; // trailing + if (/* s */"3"/*e*/) { + "4"; + } +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/constants-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/constants-expected.js new file mode 100644 index 0000000..e3a5d6c --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/constants-expected.js @@ -0,0 +1,15 @@ +var a = true; +var b = false; +var c = '1'; +var d = false; +var e = true; +var f = '0'; +var g = '1'; +var h = '0'; +var i = true; +var j = false; +var k = false; +var l = true; +var m = false; +var n = false; +var o = true; diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/constants.js b/frontend/public/pdf.js/external/builder/fixtures_babel/constants.js new file mode 100644 index 0000000..db2ff6e --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/constants.js @@ -0,0 +1,15 @@ +var a = true; +var b = false; +var c = true && '1'; +var d = false && '0'; +var e = true || '1'; +var f = false || '0'; +var g = true ? '1' : '0'; +var h = false ? '1' : '0'; +var i = 'test' === 'test'; +var j = 'test' !== 'test'; +var k = 'test' === 'test2'; +var l = 'test' !== 'test2'; +var m = '1' === true; +var n = !true; +var o = !false; diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/constructors-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/constructors-expected.js new file mode 100644 index 0000000..841e538 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/constructors-expected.js @@ -0,0 +1,23 @@ +class A { + constructor() { + console.log("Hi!"); + } +} +class B { + constructor(x = console.log("Hi!")) {} +} +class C { + constructor({ + x + }) {} +} +class D {} +class E extends A { + constructor() {} +} +class F { + constructor() { + var a = 0; + } +} +class G {} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/constructors.js b/frontend/public/pdf.js/external/builder/fixtures_babel/constructors.js new file mode 100644 index 0000000..8d7f412 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/constructors.js @@ -0,0 +1,37 @@ +class A { + constructor() { + console.log("Hi!"); + } +} + +class B { + constructor(x = console.log("Hi!")) {} +} + +class C { + constructor({ x }) {} +} + +class D { + constructor(x, y, z) {} +} + +class E extends A { + constructor() {} +} + +class F { + constructor() { + if (PDFJSDev.test('TRUE')) { + var a = 0; + } + } +} + +class G { + constructor() { + if (PDFJSDev.test('FALSE')) { + var a = 0; + } + } +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/deadcode-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/deadcode-expected.js new file mode 100644 index 0000000..c948aca --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/deadcode-expected.js @@ -0,0 +1,21 @@ +function f1() {} +function f2() { + return 1; +} +function f3() { + var i = 0; + throw "test"; +} +function f4() { + var i = 0; +} +var obj = { + method1() {}, + method2() {} +}; +class C { + method1() {} + method2() {} +} +var arrow1 = () => {}; +var arrow2 = () => {}; diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/deadcode.js b/frontend/public/pdf.js/external/builder/fixtures_babel/deadcode.js new file mode 100644 index 0000000..c1d8c4f --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/deadcode.js @@ -0,0 +1,37 @@ +function f1() { + return; + var i = 0; +} + +function f2() { + return 1; + var i = 0; +} + +function f3() { + var i = 0; + throw "test"; + var j = 0; +} + +function f4() { + var i = 0; + if (true) { + return; + } + throw "test"; + var j = 0; +} + +var obj = { + method1() { return; var i = 0; }, + method2() { return; }, +}; + +class C { + method1() { return; var i = 0; } + method2() { return; } +} + +var arrow1 = () => { return; var i = 0; }; +var arrow2 = () => { return; }; diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/evals-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/evals-expected.js new file mode 100644 index 0000000..71eee73 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/evals-expected.js @@ -0,0 +1,18 @@ +var a = false; +var b = true; +var c = true; +var d = false; +var e = true; +var f = "text"; +var g = { + obj: { + i: 1 + }, + j: 2 +}; +var i = '0'; +var j = { + i: 1 +}; +var k = false; +var l = true; diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/evals.js b/frontend/public/pdf.js/external/builder/fixtures_babel/evals.js new file mode 100644 index 0000000..cabeb07 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/evals.js @@ -0,0 +1,11 @@ +var a = typeof PDFJSDev === 'undefined'; +var b = typeof PDFJSDev !== 'undefined'; +var c = PDFJSDev.test('TRUE'); +var d = PDFJSDev.test('FALSE'); +var e = PDFJSDev.eval('TRUE'); +var f = PDFJSDev.eval('TEXT'); +var g = PDFJSDev.eval('OBJ'); +var i = typeof PDFJSDev === 'undefined' ? PDFJSDev.eval('FALSE') : '0'; +var j = typeof PDFJSDev !== 'undefined' ? PDFJSDev.eval('OBJ.obj') : '0'; +var k = !PDFJSDev.test('TRUE'); +var l = !PDFJSDev.test('FALSE'); diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/ifs-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/ifs-expected.js new file mode 100644 index 0000000..db9f4af --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/ifs-expected.js @@ -0,0 +1,18 @@ +if ('test') { + "1"; +} +{ + "1"; +} +{ + "1"; +} +{ + "2"; +} +if ('1') { + "1"; +} +function f1() { + "1"; +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/ifs.js b/frontend/public/pdf.js/external/builder/fixtures_babel/ifs.js new file mode 100644 index 0000000..187c7f9 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/ifs.js @@ -0,0 +1,34 @@ +if ('test') { + "1"; +} +if (true) { + "1"; +} +if (true) { + "1"; +} else { + "2"; +} +if (false) { + "1"; +} +if (false) { + "1"; +} else { + "2"; +} +if (true && false) { + "1"; +} +if (true && false || '1') { + "1"; +} + +function f1() { + if (true) { + "1"; + } + if (false) { + "2"; + } +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/importalias-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/importalias-expected.js new file mode 100644 index 0000000..6c270ad --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/importalias-expected.js @@ -0,0 +1,4 @@ +import { Test } from "import-name"; +import { Test2 } from './non-alias'; +export { Test3 } from "import-name"; +await import(/*webpackIgnore: true*/"./non-alias"); diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/importalias.js b/frontend/public/pdf.js/external/builder/fixtures_babel/importalias.js new file mode 100644 index 0000000..955b67e --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/importalias.js @@ -0,0 +1,4 @@ +import { Test } from 'import-alias'; +import { Test2 } from './non-alias'; +export { Test3 } from 'import-alias'; +await __non_webpack_import__("./non-alias"); diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/staticblock-expected.js b/frontend/public/pdf.js/external/builder/fixtures_babel/staticblock-expected.js new file mode 100644 index 0000000..7edf5a8 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/staticblock-expected.js @@ -0,0 +1,8 @@ +class A { + static { + foo(); + } + static { + var a = 0; + } +} diff --git a/frontend/public/pdf.js/external/builder/fixtures_babel/staticblock.js b/frontend/public/pdf.js/external/builder/fixtures_babel/staticblock.js new file mode 100644 index 0000000..161a5d7 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/fixtures_babel/staticblock.js @@ -0,0 +1,20 @@ +class A { + static {} + static { + { foo() } + } + static { + {;} + } + static { + if (PDFJSDev.test('TRUE')) { + var a = 0; + } + } + + static { + if (PDFJSDev.test('FALSE')) { + var a = 1; + } + } +} diff --git a/frontend/public/pdf.js/external/builder/test-fixtures.mjs b/frontend/public/pdf.js/external/builder/test-fixtures.mjs new file mode 100644 index 0000000..4fb0833 --- /dev/null +++ b/frontend/public/pdf.js/external/builder/test-fixtures.mjs @@ -0,0 +1,69 @@ +import * as builder from "./builder.mjs"; +import { fileURLToPath } from "url"; +import fs from "fs"; +import path from "path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +let errors = 0; + +const baseDir = path.join(__dirname, "fixtures"); +const files = fs + .readdirSync(baseDir) + .filter(function (name) { + return /-expected\./.test(name); + }) + .map(function (name) { + return path.join(baseDir, name); + }); +files.forEach(function (expectationFilename) { + const inFilename = expectationFilename.replace("-expected", ""); + const expectation = fs + .readFileSync(expectationFilename) + .toString() + .trim() + .replaceAll("__filename", fs.realpathSync(inFilename)); + const outLines = []; + + const outFilename = function (line) { + outLines.push(line); + }; + const defines = { + TRUE: true, + FALSE: false, + }; + let out; + try { + builder.preprocess(inFilename, outFilename, defines); + out = outLines.join("\n").trim(); + } catch (e) { + out = ("Error: " + e.message).replaceAll(/^/gm, "//"); + } + if (out !== expectation) { + errors++; + + // Allow regenerating the expected output using + // OVERWRITE=true node ./external/builder/test-fixtures.mjs + if (process.env.OVERWRITE) { + fs.writeFileSync(expectationFilename, out + "\n"); + } + + console.log("Assertion failed for " + inFilename); + console.log("--------------------------------------------------"); + console.log("EXPECTED:"); + console.log(expectation); + console.log("--------------------------------------------------"); + console.log("ACTUAL"); + console.log(out); + console.log("--------------------------------------------------"); + console.log(); + } +}); + +if (errors) { + console.error("Found " + errors + " expectation failures."); + process.exit(1); +} else { + console.log("All tests completed without errors."); + process.exit(0); +} diff --git a/frontend/public/pdf.js/external/builder/test-fixtures_babel.mjs b/frontend/public/pdf.js/external/builder/test-fixtures_babel.mjs new file mode 100644 index 0000000..e7afa4d --- /dev/null +++ b/frontend/public/pdf.js/external/builder/test-fixtures_babel.mjs @@ -0,0 +1,75 @@ +import { fileURLToPath } from "url"; +import fs from "fs"; +import path from "path"; +import { preprocessPDFJSCode } from "./babel-plugin-pdfjs-preprocessor.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +let errors = 0; + +const baseDir = path.join(__dirname, "fixtures_babel"); +const files = fs + .readdirSync(baseDir) + .filter(function (name) { + return /-expected\./.test(name); + }) + .map(function (name) { + return path.join(baseDir, name); + }); +files.forEach(function (expectationFilename) { + const inFilename = expectationFilename.replace("-expected", ""); + const expectation = fs + .readFileSync(expectationFilename) + .toString() + .trim() + .replaceAll("__filename", fs.realpathSync(inFilename)); + const input = fs.readFileSync(inFilename).toString(); + + const defines = { + TRUE: true, + FALSE: false, + OBJ: { obj: { i: 1 }, j: 2 }, + TEXT: "text", + }; + const map = { + "import-alias": "import-name", + }; + const ctx = { + defines, + map, + rootPath: __dirname + "/../..", + }; + let out; + try { + out = preprocessPDFJSCode(ctx, input); + } catch (e) { + out = ("Error: " + e.message).replaceAll(/^/gm, "//"); + } + if (out !== expectation) { + errors++; + + // Allow regenerating the expected output using + // OVERWRITE=true node ./external/builder/test-fixtures_babel.mjs + if (process.env.OVERWRITE) { + fs.writeFileSync(expectationFilename, out + "\n"); + } + + console.log("Assertion failed for " + inFilename); + console.log("--------------------------------------------------"); + console.log("EXPECTED:"); + console.log(expectation); + console.log("--------------------------------------------------"); + console.log("ACTUAL"); + console.log(out); + console.log("--------------------------------------------------"); + console.log(); + } +}); + +if (errors) { + console.error("Found " + errors + " expectation failures."); + process.exit(1); +} else { + console.log("All tests completed without errors."); + process.exit(0); +} diff --git a/frontend/public/pdf.js/external/cmapscompress/README.md b/frontend/public/pdf.js/external/cmapscompress/README.md new file mode 100644 index 0000000..3d80a18 --- /dev/null +++ b/frontend/public/pdf.js/external/cmapscompress/README.md @@ -0,0 +1,171 @@ +# Quick notes about binary CMap format (bcmap) + +The format is designed to package some information from the CMap files located at external/cmap. Please notice for size optimization reasons, the original information blocks can be changed (split or joined) and items in the blocks can be swapped. + +The data stored in binary format in network byte order (big-endian). + +# Data primitives + +The following primitives used during encoding of the file: + - byte (B) โ€“ a byte, bits are numbered from 0 (less significant) to 7 (most significant) + - bytes block (B[n]) โ€“ a sequence of n bytes + - unsigned number (UN) โ€“ the number is encoded as sequence of bytes, bit 7 is flag to continue decoding the byte, bits 6-0 store number information, e.g. bytes 0x818407 will represent 16903 (0x4207). Limited to the 32 bit. + - signed number (SN) โ€“ the number is encoded as sequence of bytes, as UN, however shall be transformed before encoding: if n < 0, the n shall be encoded as (-2*n-1) using UN encoding, other n shall be encoded as (2*n) using UN encoding. So the lowest bit of the number indicates the sign of the initial number + - unsigned fixed number (UB[n]) โ€“ similar to the UN, but it represents an unsigned number that is stored in B[n] + - signed fixed number (SB[n]) โ€“ similar to the SN, but it represents a signed number that is stored in B[n] + - string (S) โ€“ the string is encoded as sequence of bytes. First comes length is characters encoded as UN, when UTF16 characters encoded as UN. + +# File structure + +The first byte is a header: + - bits 2-1 โ€“ indicate a CMapType. Valid values are 1 and 2 + - bit 0 โ€“ indicate WMode. Valid values are 0 and 1. + +Then records follow. The records starts from the record header encoded as B, where bits 7-5 indicate record type (see description of other bits below): + - 0 โ€“ codespacerange + - 1 โ€“ notdefrange + - 2 โ€“ cidchar + - 3 โ€“ cidrange + - 4 โ€“ bfchar + - 5 โ€“ bfrange + - 6 โ€“ reserved + - 7 โ€“ metadata + +## Metadata record + +The metadata record header bit 4-0 contain id of the metadata: + - 0 โ€“ comment, body of the record is encoded comment string (S) + - 1 โ€“ UseCMap, body of the record is usecmap id string (S) + +## Data records + +The records that have types 0 โ€“ 5, have the following fields in the header: + - bit 4 โ€“ indicate the char or start/end entries are stored in a sequence in this block + - bits 3-0 โ€“ contain length of the data size minus 1 in this block (dataSize) + +The amount of entries encoded as UN follows the header. The items records follow (see below). + + +### codespacerange (0) + +Represents the following CMap block: + + n begincodespacerange + + endcodespacerange + +First record format is: + + - start as B[dataSize] + - endDelta as UB[dataSize], end is calculated as (start + endDelta) + +Next record format is: + + - startDelta as UB[dataSize], start = end + startDelta + - endDelta as UB[dataSize], end = start + endDelta + + +### notdefrange (1) + +Represents the following CMap block: + + n beginnotdefrange + code + endnotdefrange + +First record format is: + + - start as B[dataSize] + - endDelta as UB[dataSize], end is calculated as (start + endDelta) + - code as UN + +Next record format is: + + - startDelta as UB[dataSize], start = end + startDelta + - endDelta as UB[dataSize], end = start + endDelta + - code as UN + + +### cidchar (2) + +Represents the following CMap block: + + n begincidchar + code + endcidchar + +First record format is: + + - char as B[dataSize] + - code as UN + +Next record format is: + + - if sequence = 0, charDelta as UB[dataSize], char = char + charDelta + 1 + - if sequence = 1, char = char + 1 + - codeDelta as SN, code = code + codeDelta + + +### cidrange (3) + +Represents the following CMap block: + + n begincidrange + code + endcidrange + +First record format is: + + - start as B[dataSize] + - endDelta as UN[dataSize], end is calculated as (start + endDelta) + - code as UN + +Next record format is: + + - if sequence = 0, startDelta as UB[dataSize], start = end + startDelta + 1 + - if sequence = 1, start = end + 1 + - endDelta as UN[dataSize], end = start + endDelta + - code as UN + + +### bfchar (4) + +Represents the following CMap block: + + n beginbfchar + + endbfchar + +First record format is: + + - char as B[ucs2Size], where ucs2Size = 2 (here and below) + - code as B[dataSize] + +Next record format is: + + - if sequence = 0, charDelta as UN[ucs2Size], char = charDelta + charDelta + 1 + - if sequence = 1, char = char + 1 + - codeDelta as SB[dataSize], code = code + codeDelta + + +### bfrange (5) + +Represents the following CMap block: + + n beginbfrange + + endbfrange + +First record format is: + + - start as B[ucs2Size] + - endDelta as UB[ucs2Size], end is calculated as (start + endDelta) + - code as B[dataSize] + +Next record format is: + + - if sequence = 0, startDelta as UB[ucs2Size], start = end + startDelta + 1 + - if sequence = 1, start = end + 1 + - endDelta as UB[ucs2Size], end = start + endDelta + - code as B[dataSize] + diff --git a/frontend/public/pdf.js/external/cmapscompress/compress.mjs b/frontend/public/pdf.js/external/cmapscompress/compress.mjs new file mode 100644 index 0000000..06a46c4 --- /dev/null +++ b/frontend/public/pdf.js/external/cmapscompress/compress.mjs @@ -0,0 +1,494 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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 fs from "fs"; +import { optimizeCMap } from "./optimize.mjs"; +import { parseAdobeCMap } from "./parse.mjs"; +import path from "path"; + +function compressCmap(srcPath, destPath, verify) { + const content = fs.readFileSync(srcPath).toString(); + const inputData = parseAdobeCMap(content); + optimizeCMap(inputData); + + let out = writeByte((inputData.type << 1) | inputData.wmode); + if (inputData.comment) { + out += writeByte(0xe0) + writeString(inputData.comment); + } + if (inputData.usecmap) { + out += writeByte(0xe1) + writeString(inputData.usecmap); + } + let i = 0; + while (i < inputData.body.length) { + const item = inputData.body[i++], + subitems = item.items; + const first = item.items[0]; + const sequence = item.sequence === true; + const flags = (item.type << 5) | (sequence ? 0x10 : 0); + let nextStart, nextCode; + switch (item.type) { + case 0: + out += + writeByte(flags | getHexSize(first.start)) + + writeNumber(subitems.length); + out += first.start + writeNumber(subHex(first.end, first.start)); + nextStart = incHex(first.end); + for (let j = 1; j < subitems.length; j++) { + out += + writeNumber(subHex(subitems[j].start, nextStart)) + + writeNumber(subHex(subitems[j].end, subitems[j].start)); + nextStart = incHex(subitems[j].end); + } + break; + case 1: + out += + writeByte(flags | getHexSize(first.start)) + + writeNumber(subitems.length); + out += + first.start + + writeNumber(subHex(first.end, first.start)) + + writeNumber(first.code); + nextStart = incHex(first.end); + for (let j = 1; j < subitems.length; j++) { + out += + writeNumber(subHex(subitems[j].start, nextStart)) + + writeNumber(subHex(subitems[j].end, subitems[j].start)) + + writeNumber(subitems[j].code); + nextStart = incHex(subitems[j].end); + } + break; + case 2: + out += + writeByte(flags | getHexSize(first.char)) + + writeNumber(subitems.length); + out += first.char + writeNumber(first.code); + nextStart = incHex(first.char); + nextCode = first.code + 1; + for (let j = 1; j < subitems.length; j++) { + out += + (sequence ? "" : writeNumber(subHex(subitems[j].char, nextStart))) + + writeSigned(subitems[j].code - nextCode); + nextStart = incHex(subitems[j].char); + nextCode = item.items[j].code + 1; + } + break; + case 3: + out += + writeByte(flags | getHexSize(first.start)) + + writeNumber(subitems.length); + out += + first.start + + writeNumber(subHex(first.end, first.start)) + + writeNumber(first.code); + nextStart = incHex(first.end); + for (let j = 1; j < subitems.length; j++) { + out += + (sequence + ? "" + : writeNumber(subHex(subitems[j].start, nextStart))) + + writeNumber(subHex(subitems[j].end, subitems[j].start)) + + writeNumber(subitems[j].code); + nextStart = incHex(subitems[j].end); + } + break; + case 4: + out += + writeByte(flags | getHexSize(first.code)) + + writeNumber(subitems.length); + out += first.char + first.code; + nextStart = incHex(first.char); + nextCode = incHex(first.code); + for (let j = 1; j < subitems.length; j++) { + out += + (sequence ? "" : writeNumber(subHex(subitems[j].char, nextStart))) + + writeSigned(subHex(subitems[j].code, nextCode)); + nextStart = incHex(subitems[j].char); + nextCode = incHex(subitems[j].code); + } + break; + case 5: + out += + writeByte(flags | getHexSize(first.code)) + + writeNumber(subitems.length); + out += + first.start + + writeNumber(subHex(first.end, first.start)) + + first.code; + nextStart = incHex(first.end); + for (let j = 1; j < subitems.length; j++) { + out += + (sequence + ? "" + : writeNumber(subHex(subitems[j].start, nextStart))) + + writeNumber(subHex(subitems[j].end, subitems[j].start)) + + subitems[j].code; + nextStart = incHex(subitems[j].end); + } + break; + } + } + + fs.writeFileSync(destPath, Buffer.from(out, "hex")); + + if (verify) { + const result2 = parseCMap(out); + const isGood = JSON.stringify(inputData) === JSON.stringify(result2); + if (!isGood) { + throw new Error("Extracted data does not match the expected result"); + } + } + + return { + orig: fs.statSync(srcPath).size, + packed: out.length >> 1, + }; +} + +function parseCMap(binaryData) { + const reader = { + buffer: binaryData, + pos: 0, + end: binaryData.length, + readByte() { + if (this.pos >= this.end) { + return -1; + } + const d1 = fromHexDigit(this.buffer[this.pos]); + const d2 = fromHexDigit(this.buffer[this.pos + 1]); + this.pos += 2; + return (d1 << 4) | d2; + }, + readNumber() { + let n = 0; + let last; + do { + const b = this.readByte(); + last = !(b & 0x80); + n = (n << 7) | (b & 0x7f); + } while (!last); + return n; + }, + readSigned() { + const n = this.readNumber(); + return n & 1 ? -(n >>> 1) - 1 : n >>> 1; + }, + readHex(size) { + const lengthInChars = (size + 1) << 1; + const s = this.buffer.substring(this.pos, this.pos + lengthInChars); + this.pos += lengthInChars; + return s; + }, + readHexNumber(size) { + const lengthInChars = (size + 1) << 1, + stack = []; + let last; + do { + const b = this.readByte(); + last = !(b & 0x80); + stack.push(b & 0x7f); + } while (!last); + let s = "", + buffer = 0, + bufferSize = 0; + while (s.length < lengthInChars) { + while (bufferSize < 4 && stack.length > 0) { + buffer |= stack.pop() << bufferSize; + bufferSize += 7; + } + s = toHexDigit(buffer & 15) + s; + buffer >>= 4; + bufferSize -= 4; + } + return s; + }, + readHexSigned(size) { + const num = this.readHexNumber(size); + const sign = fromHexDigit(num.at(-1)) & 1 ? 15 : 0; + let c = 0; + let result = ""; + for (const digit of num) { + c = (c << 4) | fromHexDigit(digit); + result += toHexDigit(sign ? (c >> 1) ^ sign : c >> 1); + c &= 1; + } + return result; + }, + readString() { + const len = this.readNumber(); + let s = ""; + for (let i = 0; i < len; i++) { + s += String.fromCharCode(this.readNumber()); + } + return s; + }, + }; + + const header = reader.readByte(); + const result = { + type: header >> 1, + wmode: header & 1, + comment: null, + usecmap: null, + body: [], + }; + + let b; + while ((b = reader.readByte()) >= 0) { + const type = b >> 5; + if (type === 7) { + switch (b & 0x1f) { + case 0: + result.comment = reader.readString(); + break; + case 1: + result.usecmap = reader.readString(); + break; + } + continue; + } + const sequence = !!(b & 0x10); + const dataSize = b & 15; + const subitems = []; + const item = { + type, + items: subitems, + }; + if (sequence) { + item.sequence = true; + } + const ucs2DataSize = 1; + const subitemsCount = reader.readNumber(); + let start, end, code, char; + switch (type) { + case 0: + start = reader.readHex(dataSize); + end = addHex(reader.readHexNumber(dataSize), start); + subitems.push({ start, end }); + for (let i = 1; i < subitemsCount; i++) { + start = addHex(reader.readHexNumber(dataSize), incHex(end)); + end = addHex(reader.readHexNumber(dataSize), start); + subitems.push({ start, end }); + } + break; + case 1: + start = reader.readHex(dataSize); + end = addHex(reader.readHexNumber(dataSize), start); + code = reader.readNumber(); + subitems.push({ start, end, code }); + for (let i = 1; i < subitemsCount; i++) { + start = addHex(reader.readHexNumber(dataSize), incHex(end)); + end = addHex(reader.readHexNumber(dataSize), start); + code = reader.readNumber(); + subitems.push({ start, end, code }); + } + break; + case 2: + char = reader.readHex(dataSize); + code = reader.readNumber(); + subitems.push({ char, code }); + for (let i = 1; i < subitemsCount; i++) { + char = sequence + ? incHex(char) + : addHex(reader.readHexNumber(dataSize), incHex(char)); + code = reader.readSigned() + (code + 1); + subitems.push({ char, code }); + } + break; + case 3: + start = reader.readHex(dataSize); + end = addHex(reader.readHexNumber(dataSize), start); + code = reader.readNumber(); + subitems.push({ start, end, code }); + for (let i = 1; i < subitemsCount; i++) { + start = sequence + ? incHex(end) + : addHex(reader.readHexNumber(dataSize), incHex(end)); + end = addHex(reader.readHexNumber(dataSize), start); + code = reader.readNumber(); + subitems.push({ start, end, code }); + } + break; + case 4: + char = reader.readHex(ucs2DataSize); + code = reader.readHex(dataSize); + subitems.push({ char, code }); + for (let i = 1; i < subitemsCount; i++) { + char = sequence + ? incHex(char) + : addHex(reader.readHexNumber(ucs2DataSize), incHex(char)); + code = addHex(reader.readHexSigned(dataSize), incHex(code)); + subitems.push({ char, code }); + } + break; + case 5: + start = reader.readHex(ucs2DataSize); + end = addHex(reader.readHexNumber(ucs2DataSize), start); + code = reader.readHex(dataSize); + subitems.push({ start, end, code }); + for (let i = 1; i < subitemsCount; i++) { + start = sequence + ? incHex(end) + : addHex(reader.readHexNumber(ucs2DataSize), incHex(end)); + end = addHex(reader.readHexNumber(ucs2DataSize), start); + code = reader.readHex(dataSize); + subitems.push({ start, end, code }); + } + break; + default: + throw new Error("Unknown type: " + type); + } + result.body.push(item); + } + + return result; +} + +function toHexDigit(n) { + return n.toString(16); +} +function fromHexDigit(s) { + return parseInt(s, 16); +} +function getHexSize(s) { + return (s.length >> 1) - 1; +} +function writeByte(b) { + return toHexDigit((b >> 4) & 15) + toHexDigit(b & 15); +} +function writeNumber(n) { + if (typeof n === "string") { + let s = "", + buffer = 0, + bufferSize = 0; + let i = n.length; + while (i > 0) { + --i; + buffer |= fromHexDigit(n[i]) << bufferSize; + bufferSize += 4; + if (bufferSize >= 7) { + s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s; + buffer >>>= 7; + bufferSize -= 7; + } + } + if (buffer > 0) { + s = writeByte((buffer & 0x7f) | (s.length > 0 ? 0x80 : 0)) + s; + } + while (s.indexOf("80") === 0) { + s = s.substring(2); + } + return s; + } + let s = writeByte(n & 0x7f); + n >>>= 7; + while (n > 0) { + s = writeByte((n & 0x7f) | 0x80) + s; + n >>>= 7; + } + return s; +} +function writeSigned(n) { + if (typeof n === "string") { + let t = ""; + let c = fromHexDigit(n[0]); + const neg = c >= 8; + c = neg ? c ^ 15 : c; + for (let i = 1; i < n.length; i++) { + const d = fromHexDigit(n[i]); + c = (c << 4) | (neg ? d ^ 15 : d); + t += toHexDigit(c >> 3); + c &= 7; + } + t += toHexDigit((c << 1) | (neg ? 1 : 0)); + return writeNumber(t); + } + return n < 0 ? writeNumber(-2 * n - 1) : writeNumber(2 * n); +} +function writeString(s) { + let t = writeNumber(s.length); + for (let i = 0; i < s.length; i++) { + t += writeNumber(s.charCodeAt(i)); + } + return t; +} +function addHex(a, b) { + let c = 0, + s = ""; + for (let i = a.length - 1; i >= 0; i--) { + c += fromHexDigit(a[i]) + fromHexDigit(b[i]); + if (c >= 16) { + s = toHexDigit(c - 16) + s; + c = 1; + } else { + s = toHexDigit(c) + s; + c = 0; + } + } + return s; +} +function subHex(a, b) { + let c = 0, + s = ""; + for (let i = a.length - 1; i >= 0; i--) { + c += fromHexDigit(a[i]) - fromHexDigit(b[i]); + if (c < 0) { + s = toHexDigit(c + 16) + s; + c = -1; + } else { + s = toHexDigit(c) + s; + c = 0; + } + } + return s; +} +function incHex(a) { + let c = 1, + s = ""; + for (let i = a.length - 1; i >= 0; i--) { + c += fromHexDigit(a[i]); + if (c >= 16) { + s = toHexDigit(c - 16) + s; + c = 1; + } else { + s = toHexDigit(c) + s; + c = 0; + } + } + return s; +} + +function compressCmaps(src, dest, verify) { + const files = fs.readdirSync(src).filter(function (fn) { + return !fn.includes("."); // skipping files with the extension + }); + files.forEach(function (fn) { + const srcPath = path.join(src, fn); + const destPath = path.join(dest, fn + ".bcmap"); + const stats = compressCmap(srcPath, destPath, verify); + console.log( + "Compressing " + + fn + + ": " + + stats.orig + + " vs " + + stats.packed + + " " + + ((stats.packed / stats.orig) * 100).toFixed(1) + + "%" + ); + }); +} + +export { compressCmaps }; diff --git a/frontend/public/pdf.js/external/cmapscompress/optimize.mjs b/frontend/public/pdf.js/external/cmapscompress/optimize.mjs new file mode 100644 index 0000000..97b2c1a --- /dev/null +++ b/frontend/public/pdf.js/external/cmapscompress/optimize.mjs @@ -0,0 +1,227 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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. + */ + +function optimizeCMap(data) { + let i = 1; + while (i < data.body.length) { + if (data.body[i - 1].type === data.body[i].type) { + data.body[i - 1].items = data.body[i - 1].items.concat( + data.body[i].items + ); + data.body.splice(i, 1); + } else { + i++; + } + } + // split into groups with different lengths + i = 0; + while (i < data.body.length) { + const item = data.body[i]; + const keys = Object.keys(item.items[0]).filter(function (val) { + return typeof item.items[0][val] === "string"; + }); + let j = 1; + while (j < item.items.length) { + let different = false; + for (let q = 0; q < keys.length && !different; q++) { + different = + item.items[j - 1][keys[q]].length !== item.items[j][keys[q]].length; + } + if (different) { + break; + } + j++; + } + if (j < item.items.length) { + data.body.splice(i + 1, 0, { + type: item.type, + items: item.items.splice(j, item.items.length - j), + }); + } + i++; + } + // find sequences of single char ranges + i = 0; + while (i < data.body.length) { + const item = data.body[i]; + if (item.type === 3 || item.type === 5) { + let j = 0; + while (j < item.items.length) { + const q = j; + while ( + j < item.items.length && + item.items[j].start === item.items[j].end + ) { + j++; + } + if (j - q >= 9) { + if (j < item.items.length) { + data.body.splice(i + 1, 0, { + type: item.type, + items: item.items.splice(j, item.items.length - j), + }); + } + if (q > 0) { + data.body.splice(i + 1, 0, { + type: item.type - 1, + items: item.items.splice(q, j - q).map(function (val) { + return { char: val.start, code: val.code }; + }), + }); + i++; + } else { + item.type -= 1; + item.items = item.items.map(function (val) { + return { char: val.start, code: val.code }; + }); + } + continue; + } + j++; + } + } + i++; + } + + // find sequences of increasing code/ranges order + i = 0; + while (i < data.body.length) { + const item = data.body[i]; + if (item.type >= 2 && item.type <= 5) { + let j = 1; + const startProp = item.type === 2 || item.type === 4 ? "char" : "start"; + const endProp = item.type === 2 || item.type === 4 ? "char" : "end"; + while (j < item.items.length) { + const q = j - 1; + while ( + j < item.items.length && + incHex(item.items[j - 1][endProp]) === item.items[j][startProp] + ) { + j++; + } + if (j - q >= 9) { + if (j < item.items.length) { + data.body.splice(i + 1, 0, { + type: item.type, + items: item.items.splice(j, item.items.length - j), + }); + } + if (q > 0) { + data.body.splice(i + 1, 0, { + type: item.type, + items: item.items.splice(q, j - q), + sequence: true, + }); + i++; + } else { + item.sequence = true; + } + continue; + } + j++; + } + } + i++; + } + + // split non-sequences two groups where codes are close + i = 0; + while (i < data.body.length) { + const item = data.body[i]; + if (!item.sequence && (item.type === 2 || item.type === 3)) { + const subitems = item.items; + const codes = subitems.map(function (val) { + return val.code; + }); + codes.sort(function (a, b) { + return a - b; + }); + const maxDistance = 100, + minItems = 10, + itemsPerBucket = 50; + if (subitems.length > minItems && codes.at(-1) - codes[0] > maxDistance) { + const gapsCount = Math.max(2, (subitems.length / itemsPerBucket) | 0); + const gaps = []; + for (let q = 0; q < gapsCount; q++) { + gaps.push({ length: 0 }); + } + for (let j = 1; j < codes.length; j++) { + const gapLength = codes[j] - codes[j - 1]; + let q = 0; + while (q < gaps.length && gaps[q].length > gapLength) { + q++; + } + if (q >= gaps.length) { + continue; + } + let q0 = q; + while (q < gaps.length) { + if (gaps[q].length < gaps[q0].length) { + q0 = q; + } + q++; + } + gaps[q0] = { length: gapLength, boundary: codes[j] }; + } + const groups = gaps + .filter(function (g) { + return g.length >= maxDistance; + }) + .map(function (g) { + return g.boundary; + }); + groups.sort(function (a, b) { + return a - b; + }); + if (groups.length > 1) { + const buckets = [(item.items = [])]; + for (let j = 0; j < groups.length; j++) { + const newItem = { type: item.type, items: [] }; + buckets.push(newItem.items); + i++; + data.body.splice(i, 0, newItem); + } + for (const subitem of subitems) { + const { code } = subitem; + let q = 0; + while (q < groups.length && groups[q] <= code) { + q++; + } + buckets[q].push(subitem); + } + } + } + } + i++; + } +} + +function incHex(a) { + let c = 1, + s = ""; + for (let i = a.length - 1; i >= 0; i--) { + c += parseInt(a[i], 16); + if (c >= 16) { + s = "0" + s; + c = 1; + } else { + s = c.toString(16) + s; + c = 0; + } + } + return s; +} + +export { optimizeCMap }; diff --git a/frontend/public/pdf.js/external/cmapscompress/parse.mjs b/frontend/public/pdf.js/external/cmapscompress/parse.mjs new file mode 100644 index 0000000..47ec49c --- /dev/null +++ b/frontend/public/pdf.js/external/cmapscompress/parse.mjs @@ -0,0 +1,105 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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. + */ + +function parseAdobeCMap(content) { + let m = /(\bbegincmap\b[\s\S]*?)\bendcmap\b/.exec(content); + if (!m) { + throw new Error("cmap was not found"); + } + + const body = m[1].replaceAll(/\r\n?/g, "\n"); + const result = { + type: 1, + wmode: 0, + comment: + "Copyright 1990-2009 Adobe Systems Incorporated.\nAll rights reserved.\nSee ./LICENSE", + usecmap: null, + body: [], + }; + m = /\/CMapType\s+(\d+)\s+def\b/.exec(body); + result.type = +m[1]; + m = /\/WMode\s+(\d+)\s+def\b/.exec(body); + result.wmode = +m[1]; + m = /\/([\w-]+)\s+usecmap\b/.exec(body); + if (m) { + result.usecmap = m[1]; + } + const re = + /(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g; + while ((m = re.exec(body))) { + const lines = m[3].toLowerCase().split("\n"); + + switch (m[2]) { + case "begincodespacerange": + result.body.push({ + type: 0, + items: lines.map(function (line) { + const m2 = /<(\w+)>\s+<(\w+)>/.exec(line); + return { start: m2[1], end: m2[2] }; + }), + }); + break; + case "beginnotdefrange": + result.body.push({ + type: 1, + items: lines.map(function (line) { + const m2 = /<(\w+)>\s+<(\w+)>\s+(\d+)/.exec(line); + return { start: m2[1], end: m2[2], code: +m2[3] }; + }), + }); + break; + case "begincidchar": + result.body.push({ + type: 2, + items: lines.map(function (line) { + const m2 = /<(\w+)>\s+(\d+)/.exec(line); + return { char: m2[1], code: +m2[2] }; + }), + }); + break; + case "begincidrange": + result.body.push({ + type: 3, + items: lines.map(function (line) { + const m2 = /<(\w+)>\s+<(\w+)>\s+(\d+)/.exec(line); + return { start: m2[1], end: m2[2], code: +m2[3] }; + }), + }); + break; + case "beginbfchar": + result.body.push({ + type: 4, + items: lines.map(function (line) { + const m2 = /<(\w+)>\s+<(\w+)>/.exec(line); + return { char: m2[1], code: m2[2] }; + }), + }); + break; + case "beginbfrange": + result.body.push({ + type: 5, + items: lines.map(function (line) { + const m2 = /<(\w+)>\s+<(\w+)>\s+<(\w+)>/.exec(line); + return { start: m2[1], end: m2[2], code: m2[3] }; + }), + }); + break; + } + } + + return result; +} + +export { parseAdobeCMap }; diff --git a/frontend/public/pdf.js/external/importL10n/locales.mjs b/frontend/public/pdf.js/external/importL10n/locales.mjs new file mode 100644 index 0000000..0809d7e --- /dev/null +++ b/frontend/public/pdf.js/external/importL10n/locales.mjs @@ -0,0 +1,116 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 fs from "fs"; +import path from "path"; + +// Fetches all languages that have an *active* translation in mozilla-central. +// This is used in gulpfile.js for the `importl10n` command. + +const DEFAULT_LOCALE = "en-US"; + +const EXCLUDE_LANG_CODES = new Set(["ca-valencia", "ja-JP-mac"]); + +function normalizeText(s) { + return s.replaceAll(/\r\n?/g, "\n").replaceAll("\uFEFF", ""); +} + +async function downloadLanguageCodes() { + console.log("Downloading language codes...\n"); + + const ALL_LOCALES = + "https://raw.githubusercontent.com/mozilla/gecko-dev/master/browser/locales/all-locales"; + + const response = await fetch(ALL_LOCALES); + if (!response.ok) { + throw new Error(response.statusText); + } + const content = await response.text(); + + // Remove any leading/trailing white-space. + const langCodes = normalizeText(content.trim()).split("\n"); + // Remove all locales that we don't want to download below. + return langCodes.filter( + langCode => langCode !== DEFAULT_LOCALE && !EXCLUDE_LANG_CODES.has(langCode) + ); +} + +async function downloadLanguageFiles(root, langCode) { + console.log(`Downloading ${langCode}...`); + + // Constants for constructing the URLs. Translations are taken from the + // Nightly channel as those are the most recent ones. + const MOZ_CENTRAL_ROOT = + "https://raw.githubusercontent.com/mozilla-l10n/firefox-l10n/main/"; + const MOZ_CENTRAL_PDFJS_DIR = "/toolkit/toolkit/pdfviewer/"; + + // Defines which files to download for each language. + const files = ["viewer.ftl"]; + + const outputDir = path.join(root, langCode); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir); + } + + // Download the necessary files for this language. + for (const fileName of files) { + const outputPath = path.join(outputDir, fileName); + const url = MOZ_CENTRAL_ROOT + langCode + MOZ_CENTRAL_PDFJS_DIR + fileName; + + const response = await fetch(url); + if (!response.ok) { + // Not all files exist for each language. Files without translations + // have been removed (https://bugzilla.mozilla.org/show_bug.cgi?id=1443175). + continue; + } + const content = await response.text(); + + fs.writeFileSync(outputPath, normalizeText(content), "utf8"); + } +} + +async function downloadL10n(root) { + const langCodes = await downloadLanguageCodes(); + + for (const langCode of langCodes) { + if (!langCode) { + continue; + } + await downloadLanguageFiles(root, langCode); + } + + const removeCodes = []; + for (const entry of fs.readdirSync(root)) { + const dirPath = path.join(root, entry), + stat = fs.lstatSync(dirPath); + + if ( + stat.isDirectory() && + entry !== DEFAULT_LOCALE && + !langCodes.includes(entry) + ) { + removeCodes.push(entry); + } + } + if (removeCodes.length) { + console.log( + "\nConsider removing the following unmaintained locales:\n" + + removeCodes.join(", ") + + "\n" + ); + } +} + +export { downloadL10n }; diff --git a/frontend/public/pdf.js/external/openjpeg/LICENSE_OPENJPEG b/frontend/public/pdf.js/external/openjpeg/LICENSE_OPENJPEG new file mode 100644 index 0000000..e8fa410 --- /dev/null +++ b/frontend/public/pdf.js/external/openjpeg/LICENSE_OPENJPEG @@ -0,0 +1,39 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium + * Copyright (c) 2002-2014, Professor Benoit Macq + * Copyright (c) 2003-2014, Antonin Descampe + * Copyright (c) 2003-2009, Francois-Olivier Devaux + * Copyright (c) 2005, Herve Drolon, FreeImage Team + * Copyright (c) 2002-2003, Yannick Verschueren + * Copyright (c) 2001-2003, David Janssens + * Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France + * Copyright (c) 2012, CS Systemes d'Information, France + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/frontend/public/pdf.js/external/openjpeg/LICENSE_PDFJS_OPENJPEG b/frontend/public/pdf.js/external/openjpeg/LICENSE_PDFJS_OPENJPEG new file mode 100644 index 0000000..623929b --- /dev/null +++ b/frontend/public/pdf.js/external/openjpeg/LICENSE_PDFJS_OPENJPEG @@ -0,0 +1,22 @@ +Copyright (c) 2024, Mozilla Foundation + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/frontend/public/pdf.js/external/openjpeg/README.md b/frontend/public/pdf.js/external/openjpeg/README.md new file mode 100644 index 0000000..10b6d0f --- /dev/null +++ b/frontend/public/pdf.js/external/openjpeg/README.md @@ -0,0 +1,12 @@ +## Build + +In order to generate the file `openjpeg.js`: +* git clone https://github.com/mozilla/pdf.js.openjpeg/ +* the build requires to have a [Docker](https://www.docker.com/) setup and then: + * `node build.js -C` to build the Docker image + * `node build.js -co /pdf.js/external/openjpeg/` to compile the decoder + +## Licensing + +[OpenJPEG](https://www.openjpeg.org/) is under [BSD 2-clause "Simplified" License](https://github.com/uclouvain/openjpeg/blob/master/LICENSE) +and [pdf.js.openjpeg](https://github.com/mozilla/pdf.js.openjpeg/) is released under [BSD 2-clause](https://github.com/mozilla/pdf.js.openjpeg/blob/main/LICENSE) license so `openjpeg.js` is released under [BSD 2-clause](https://github.com/mozilla/pdf.js.openjpeg/blob/main/LICENSE) license too. diff --git a/frontend/public/pdf.js/external/openjpeg/openjpeg.js b/frontend/public/pdf.js/external/openjpeg/openjpeg.js new file mode 100644 index 0000000..cfdedd4 --- /dev/null +++ b/frontend/public/pdf.js/external/openjpeg/openjpeg.js @@ -0,0 +1,15 @@ +var OpenJPEG = (() => { + var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined; + + return ( +async function(moduleArg = {}) { + var moduleRtn; + +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;var runtimeInitialized=false;var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var wasmBinaryFile;function findWasmBinary(){var f="openjpeg.wasm";if(!isDataURI(f)){return locateFile(f)}return f}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){return{a:wasmImports}}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["s"];updateMemoryViews();addOnInit(wasmExports["t"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}wasmBinaryFile??=findWasmBinary();try{var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}catch(e){readyPromiseReject(e);return Promise.reject(e)}}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var noExitRuntime=Module["noExitRuntime"]||true;var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{func();maybeExit()}catch(e){handleException(e)}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};function _copy_pixels_1(compG_ptr,nb_pixels){compG_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels);const compG=Module.HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);imageData.set(compG)}function _copy_pixels_3(compR_ptr,compG_ptr,compB_ptr,nb_pixels){compR_ptr>>=2;compG_ptr>>=2;compB_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*3);const compR=Module.HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=Module.HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=Module.HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);for(let i=0;i>=2;compG_ptr>>=2;compB_ptr>>=2;compA_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compR=Module.HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=Module.HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=Module.HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);const compA=Module.HEAP32.subarray(compA_ptr,compA_ptr+nb_pixels);for(let i=0;i2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToAscii=(str,buffer)=>{for(var i=0;i{var bufSize=0;getEnvStrings().forEach((string,i)=>{var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(string=>bufSize+=string.length+1);HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var _fd_close=fd=>52;var INT53_MAX=9007199254740992;var INT53_MIN=-9007199254740992;var bigintToI53Checked=num=>numINT53_MAX?NaN:Number(num);function _fd_seek(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);return 70}var printCharBuffers=[null,[],[]];var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder:undefined;var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead=NaN)=>{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};function _gray_to_rgba(compG_ptr,nb_pixels){compG_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compG=Module.HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);for(let i=0;i>=2;compA_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compG=Module.HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compA=Module.HEAP32.subarray(compA_ptr,compA_ptr+nb_pixels);for(let i=0;i>=2;compG_ptr>>=2;compB_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compR=Module.HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=Module.HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=Module.HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);for(let i=0;i0){dependenciesFulfilled=run;return}preRun();if(runDependencies>0){dependenciesFulfilled=run;return}function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise; + + + return moduleRtn; +} +); +})(); +export default OpenJPEG; diff --git a/frontend/public/pdf.js/external/openjpeg/openjpeg.wasm b/frontend/public/pdf.js/external/openjpeg/openjpeg.wasm new file mode 100644 index 0000000..c4bdd4d Binary files /dev/null and b/frontend/public/pdf.js/external/openjpeg/openjpeg.wasm differ diff --git a/frontend/public/pdf.js/external/quickjs/README.md b/frontend/public/pdf.js/external/quickjs/README.md new file mode 100644 index 0000000..9877673 --- /dev/null +++ b/frontend/public/pdf.js/external/quickjs/README.md @@ -0,0 +1,11 @@ +## Build + +In order to generate the file `quickjs-eval.js`: +* git clone https://github.com/mozilla/pdf.js.quickjs/ +* the build requires to have a [Docker](https://www.docker.com/) setup and then: + * `node build.js -C` to build the Docker image + * `node build.js -co /pdf.js/external/quickjs/` to compile the sandbox + +## Licensing + +[Quickjs](https://bellard.org/quickjs/) and [pdf.js.quickjs](https://github.com/mozilla/pdf.js.quickjs/) are released under [MIT](https://opensource.org/licenses/MIT) license so `quickjs-eval.js` is released under [MIT](https://opensource.org/licenses/MIT) license too. diff --git a/frontend/public/pdf.js/external/quickjs/quickjs-eval.js b/frontend/public/pdf.js/external/quickjs/quickjs-eval.js new file mode 100644 index 0000000..ab93ae4 --- /dev/null +++ b/frontend/public/pdf.js/external/quickjs/quickjs-eval.js @@ -0,0 +1,31 @@ + +var Module = (() => { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + + return ( +function(moduleArg = {}) { + +var d=moduleArg,k,n;d.ready=new Promise((a,b)=>{k=a;n=b});var p=Object.assign({},d),q="";"undefined"!=typeof document&&document.currentScript&&(q=document.currentScript.src);_scriptDir&&(q=_scriptDir);q.startsWith("blob:")?q="":q=q.substr(0,q.replace(/[?#].*/,"").lastIndexOf("/")+1);var aa=d.print||console.log.bind(console),u=d.printErr||console.error.bind(console);Object.assign(d,p);p=null;var v;d.wasmBinary&&(v=d.wasmBinary);"object"!=typeof WebAssembly&&w("no native wasm support detected"); +var x,y=!1,z,A,B,C;function D(){var a=x.buffer;d.HEAP8=z=new Int8Array(a);d.HEAP16=new Int16Array(a);d.HEAPU8=A=new Uint8Array(a);d.HEAPU16=new Uint16Array(a);d.HEAP32=B=new Int32Array(a);d.HEAPU32=C=new Uint32Array(a);d.HEAPF32=new Float32Array(a);d.HEAPF64=new Float64Array(a)}var E=[],F=[],G=[];function ba(){var a=d.preRun.shift();E.unshift(a)}var H=0,I=null,J=null; +function w(a){d.onAbort?.(a);a="Aborted("+a+")";u(a);y=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");n(a);throw a;}var K=a=>a.startsWith("data:application/octet-stream;base64,"),L;L="data:application/octet-stream;base64,AGFzbQEAAAABzgZtYAJ/fwBgA39/fwF/YAR/fn9/AX5gAn9/AX9gBX9+f39/AX5gAX8Bf2ADf39/AGAEf39/fwF/YAJ/fgF+YAF/AGABfAF8YAV/f39/fwF/YAJ/fgBgAn9+AX9gAn9/AX5gA39/fgF/YAN/fn8BfmAGf35/f39/AX5gA39+fwBgA39+fwF/YAZ/f39/f38Bf2AEf39/fwBgBn9+fn9/fwF+YAR/f35/AX9gA39+fgF+YAN/f38BfmADf35+AX9gBH9/f38BfmAFf35+fn4AYAJ8fAF8YAF/AX5gBH9/f34Bf2AFf35+f38BfmAFf39/f38AYAd/fn9+fn5/AX9gBX9/f35+AX9gB39/f39/f38Bf2AAAGAFf35/fn8Bf2AEf35+fwBgBH9+fn8BfmAFf35+fn8Bf2AFf39/f38BfmAEf39+fgF/YAF+AX9gBH9+f34BfmAEf35/fwBgBH9+fn8Bf2AJf39/f39/f39/AX9gCH9/f39/f39/AX9gA39+fgBgBH9+f38Bf2AGf35/fn5/AX9gBX9+fn9/AGABfgF+YAd/fn9/f39/AX5gAX8BfGADf39+AGAEf35/fgF/YAV/f35/fwF/YAR/fn5+AX9gBn9/f39/fwF+YAN+f38Bf2AHf39/f39/fwBgAnx/AXxgA39/fgF+YAJ+fwF/YAN8fH8BfGAEf39+fwBgBH9+fn4BfmAAAX9gBn98f39/fwF/YAABfGAFf35/fn8BfmAGf39+fn5+AX9gAn5/AGACf3wAYAV/f39/fgF+YAR/f35/AX5gBH9+f34AYAd/fn5+f39/AX5gBH5+fn4Bf2AKf39/f39/f39/fwF/YAd/f39/f39+AX5gBX9+f39/AGAHfH9/f39/fwBgBX98f39/AX5gAXwBf2AFf39+f38AYAZ/fn5+fn8Bf2AGf35/f39/AX9gBH98f38Bf2AGf39/f39/AGAEf39/fgF+YAV/fn9/fwF/YAV/fn5+fgF/YAJ/fwF8YAV/fn5/fwF/YAV/f35+fgF+YAV/f35+fwF/YAJ8fwF/YAJ8fAF/YAh/fn5+fn9+fgF+YAN/fnwBfmAAAX5gB39/f35+fn8Bf2ACfn4BfGADfn5+AX9gA39/fAACSQwBYQFhABUBYQFiACUBYQFjAAcBYQFkAAYBYQFlAEgBYQFmAAABYQFn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IAILLQEBfwJAIAAoAgAiAUUNACAAKAIQIgBFDQAgASgCACAAQQAgASgCBBEBABoLCzEAIAFBAE4EQCAAQbYBEA0gACABEDggACgCQCIAKAKkAiABQRRsaiAAKAKEAjYCBAsLJwEBfyMAQRBrIgIkACACIAE2AgwgACACQQxqQQQQchogAkEQaiQACxcAIAAgASACQoCAgIAwIAMgBEECENIBCxgBAX4gASkDACEDIAEgAjcDACAAIAMQDAszAQF/IAIEQCAAIQMDQCADIAEtAAA6AAAgA0EBaiEDIAFBAWohASACQQFrIgINAAsLIAALwQUCAn4GfyMAQeAAayIJJAAgA0EAIANBAEobIQsDQCAKIAtHBEAgACACIApBBHRqIgMoAgAQsAUhBiADLQAEIQdCgICAgDAhBAJAAkACQAJAAkACQAJAAkACQAJAIAMtAAUOCgECAgUHAwQIBQAGCyAAIAMoAggQsAUhCAJ+AkACQAJAIAMoAgxBAWoOAwIAAQkLIAAgACkDwAEiBCAIIARBABARDAILIAAgACgCKCkDECIEIAggBEEAEBEMAQsgACABIAggAUEAEBELIQQgACAIEBAgBkHLAUYEQEEBIQcMCAsgBkHUAUcNB0EAIQcMBwsCQCAGQcsBRgRAQQEhBwwBCyAGQdQBRw0AQQAhBwsgACABIAZBAiADIAcQgAMaDAcLIAAgASAGQoCAgIAwIAMoAggEfiAJIAMoAgA2AhAgCUEgaiIIQcAAQeEqIAlBEGoQSBogACADKAIIIAhBAEEKQQggAy0ABUECRhsgAy4BBhCCAQVCgICAgDALIgQgAygCDAR+IAkgAygCADYCACAJQSBqIghBwABB2iogCRBIGiAAIAMoAgwgCEEBQQtBCSADLQAFQQJGGyADLgEGEIIBBUKAgICAMAsiBSAHQYA6chBqGiAAIAQQDCAAIAUQDAwGCyADKQMIIgRCgICAgAh8Qv////8PWARAIARC/////w+DIQQMBQtCgICAgMB+IAS5vSIEQoCAgIDAgYD8/wB9IARC////////////AINCgICAgICAgPj/AFYbIQQMBAtCgICAgMB+IAMpAwgiBEKAgICAwIGA/P8AfSAEQv///////////wCDQoCAgICAgID4/wBWGyEEDAMLIAAgASAGQQIgAyAHEIADGgwDCxABAAsgAzUCCCEECyAAIAEgBiAEIAcQFRoLIAAgBhAQIApBAWohCgwBCwsgCUHgAGokAAuMAgICfgF/AkACQAJAAkACQAJAAkACQAJAQQcgAUIgiKciBCAEQQdrQW5JG0EKag4SAgAGBAAAAAAAAQMFAAAAAAEDAAsgAEGbHkEAEBJCgICAgOAADwsgBEF1SQ0GIAGnIgAgACgCAEEBajYCAAwGCyAAQSEQhgEhAgwECyAAQQQQhgEhAgwDCyAAIABBBRCGASICQTAgAacpAgRC/////weDQQAQFRoMAgsgAEEGEIYBIQIMAQsgAEEHEIYBIQILQoCAgIDgACEDIAJCgICAgHCDQoCAgIDgAFIEfiAEQXVPBEAgAaciBCAEKAIAQQFqNgIACyAAIAIgARC9ASACBUKAgICA4AALDwsgAQsyAQF/AkAgAUIgiKdBdUkNACABpyICIAIoAgAiAkEBazYCACACQQFKDQAgACABEJcFCwsLACAAQeweQQAQEgujBAELfyAAKAIAIQUjAEEQayIIIAI2AgxBfyEJAkADQAJAIAggAiIDQQRqIgI2AgwgAygCACIHQX9GDQAgACgCBCEKA0AgASIEIApODQMgBCAEIAVqIgwtAAAiBkECdEHgrgFqIg0tAABqIgEgCkoNAyAGQcYBRgRAIAwoAAEhCQwBCwsgBiAHRwRAIAdBGHYgBkYgBiAHQRB2Qf8BcUZyIAYgB0H/AXFGckUgBiAHQQh2Qf8BcUdxIAZFIAdBgAJJcnINAyAAIAY2AhALIARBAWohBAJAAkACQAJAAkACQAJAAkAgDS0AA0EFaw4YAAkACQkBCQkBCQkBAQECAgICBAUGBwkDCQsgBCAFai0AACEEIAggA0EIaiICNgIMIAMoAgQiA0F/RgRAIAAgBDYCFAwJCyADIARGDQgMCQsgBCAFai8AACEEIAggA0EIaiICNgIMIAMoAgQiA0F/RgRAIAAgBDYCFAwICyADIARGDQcMCAsgACAEIAVqKAAANgIYDAYLIAAgBCAFaiIDKAAANgIYIAAgAy8ABDYCHAwFCyAAIAQgBWooAAA2AiAMBAsgACAEIAVqIgMoAAA2AiAgACADLQAENgIcDAMLIAAgBCAFaiIDKAAANgIgIAAgAy8ABDYCHAwCCyAAIAQgBWoiAygAADYCICAAIAMoAAQ2AhggACADLQAINgIcDAELCyAAIAk2AgwgACABNgIIQQEhCwsgCwskAQF/IAAoAhAiAkEQaiABIAIoAgARAwAiAUUEQCAAEHALIAELCwAgACABQQAQjQQLJwEBfyMAQRBrIgIkACACIAE7AQ4gACACQQ5qQQIQchogAkEQaiQAC9QBAgR/An5BfyECAkACQAJAAkACQAJAAkAgAUIgiKciA0EKag4RAwUFAgUFBQUFBAABAQEFBQYFCyABp0EARw8LIAGnDwsgAacpAgQhByAAIAEQDCAHQv////8Hg0IAUg8LIAGnKAIMIQQgACABEAwgBEH/////B2pBfkkPCyABpywABSEFIAAgARAMIAVBAE4PCyADQQdrQW1NBEAgAUKAgICAwIGA/P8AfEL///////////8Ag0IBfUKAgICAgICA+P8AVA8LIAAgARAMQQEhAgsgAgs/AQJ/IwBBEGsiAiQAAn8gASAAKAIQRwRAIAIgATYCACAAQcyQASACEBNBfwwBCyAAEA8LIQMgAkEQaiQAIAMLCwAgACABQQEQ6QULGQAgAEEAEFAaIABCgICAgPD/////ADcCBAvDCgIFfxF+IwBB4ABrIgUkACAEQv///////z+DIQwgAiAEhUKAgICAgICAgIB/gyEKIAJC////////P4MiDUIgiCEOIARCMIinQf//AXEhBwJAAkAgAkIwiKdB//8BcSIJQf//AWtBgoB+TwRAIAdB//8Ba0GBgH5LDQELIAFQIAJC////////////AIMiC0KAgICAgIDA//8AVCALQoCAgICAgMD//wBRG0UEQCACQoCAgICAgCCEIQoMAgsgA1AgBEL///////////8AgyICQoCAgICAgMD//wBUIAJCgICAgICAwP//AFEbRQRAIARCgICAgICAIIQhCiADIQEMAgsgASALQoCAgICAgMD//wCFhFAEQCACIAOEUARAQoCAgICAgOD//wAhCkIAIQEMAwsgCkKAgICAgIDA//8AhCEKQgAhAQwCCyADIAJCgICAgICAwP//AIWEUARAIAEgC4QhGUIAIQEgGVAEQEKAgICAgIDg//8AIQoMAwsgCkKAgICAgIDA//8AhCEKDAILIAEgC4RQBEBCACEBDAILIAIgA4RQBEBCACEBDAILIAtC////////P1gEQCAFQdAAaiABIA0gASANIA1QIgYbeSAG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////B4OENwIEIABBADYCBCABrUKAgICAkH+ECwsPACAAKAJAQYACaiABEBsLEwAgACABIAIgAyABQYCAARDQAQsNACAAIAEgAkEGEMgCCx8BAX8gACgCJCIBIAEoAgBBAWo2AgAgACABQQIQ5wULZwECfwJ/IAAoAggiAiAAKAIMTgRAQX8gACACQQFqIAEQxAINARoLIAAgACgCCCIDQQFqNgIIIAAoAgRBEGohAgJAIAAoAhAEQCACIANBAXRqIAE7AQAMAQsgAiADaiABOgAAC0EACwt6AQN/AkACQCAAIgFBA3FFDQAgAS0AAEUEQEEADwsDQCABQQFqIgFBA3FFDQEgAS0AAA0ACwwBCwNAIAEiAkEEaiEBIAIoAgAiA0F/cyADQYGChAhrcUGAgYKEeHFFDQALA0AgAiIBQQFqIQIgAS0AAA0ACwsgASAAawsNACAAIAEgAkEAEJkDCywBAX8jAEEQayIDJAAgAyACNgIMIABB3ABqQYABIAEgAhDJAhogA0EQaiQAC+gDAQl/IwBBIGsiBSQAIAEgAiABKAIMIAIoAgxJIgYbIgcoAgQgAiABIAYbIggoAgRzIQoCQAJAIAcoAgwiAkUEQAJAIAgoAggiAUH/////B0cEQCAHKAIIIgJB/////wdHDQELIAAQKkEAIQIMAwsgAUH+////B0cgAkH+////B0dxRQRAAkAgAUH+////B0YEQCACQYCAgIB4Rg0BDAQLIAFBgICAgHhHIAJB/v///wdHcg0DCyAAECpBASECDAMLIAAgChCAAUEAIQIMAgsgCCgCDCILIQkgAiEGIARBB3FBBkYEQCACIANBIWpBBXYiASABIAJKGyEGIAsgASABIAtKGyEJCwJ/AkAgACAIRg0AIAAgB0YNACAADAELIAAoAgAhASAFQgA3AhggBUKAgICAgICAgIB/NwIQIAUgATYCDCAAIQwgBUEMagshASAHKAIQIQAgCCgCECENAn8gASAGIAlqEFAEQCABECpBIAwBCyABKAIQIA0gC0ECdGogCUECdGsgCSAAIAJBAnRqIAZBAnRrIAYQ8AEgASAKNgIEIAEgBygCCCAIKAIIajYCCCABIAMgBBCbAgshAiABIAVBDGoiAEcNASAMIAAQvwQMAQsgACAKEH9BACECCyAFQSBqJAAgAgsKACAAIAFBARBHCygB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ajAAAEL/////D4MPCyAEKAIoIANNDQggBCgCJCADajEAAA8LIAQoAiggA00NByAEKAIkIANBAXRqMgEAQv////8Pgw8LIAQoAiggA00NBiAEKAIkIANBAXRqMwEADwsgBCgCKCADTQ0FIAQoAiQgA0ECdGo1AgAPCyAEKAIoIANNDQQgBCgCJCADQQJ0aigCACIAQQBOBEAgAK0PC0KAgICAwH4gALi9IgFCgICAgMCBgPz/AH0gAUKAgICAgICA+P8AVhsPCyAEKAIoIANNDQMgACAEKAIkIANBA3RqKQMAEL8CDwsgBCgCKCADTQ0CIAAgBCgCJCADQQN0aikDABCIBA8LIAQoAiggA00NAUKAgICAwH4gBCgCJCADQQJ0aioCALu9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsPCyAEKAIoIANNDQBCgICAgMB+IAQoAiQgA0EDdGopAwAiAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGw8LIAAgAhAwIQMgACACEAwgA0UEQEKAgICA4AAPCyAAIAEgAyABQQAQESEBIAAgAxAQCyABC4IDAgR/An4CQCAAKQNwIgVQRSAFIAApA3ggACgCBCIBIAAoAiwiAmusfCIGV3FFBEAjAEEQayICJABBfyEBAkACfyAAIAAoAkgiA0EBayADcjYCSCAAKAIUIAAoAhxHBEAgAEEAQQAgACgCJBEBABoLIABBADYCHCAAQgA3AxAgACgCACIDQQRxBEAgACADQSByNgIAQX8MAQsgACAAKAIsIAAoAjBqIgQ2AgggACAENgIEIANBG3RBH3ULDQAgACACQQ9qQQEgACgCIBEBAEEBRw0AIAItAA8hAQsgAkEQaiQAIAEiA0EATg0BIAAoAgQhASAAKAIsIQILIABCfzcDcCAAIAE2AmggACAGIAIgAWusfDcDeEF/DwsgBkIBfCEGIAAoAgQhASAAKAIIIQICQCAAKQNwIgVQDQAgBSAGfSIFIAIgAWusWQ0AIAEgBadqIQILIAAgAjYCaCAAIAYgACgCLCIAIAFrrHw3A3ggACABTwRAIAFBAWsgAzoAAAsgAwtPAQF/An9BACAAKAIMIAFGDQAaIAAoAgAiAigCACAAKAIQIAFBAnQgAigCBBEBACECIAEEQEF/IAJFDQEaCyAAIAE2AgwgACACNgIQQQALC9EBAQZ/IABBAWohBQJAAkAgAC0AACIDwCIHQQBOBEAgBSEBDAELQX8hBCAHQUBrQf8BcSIDQT1LDQEgA0ECdEGU9gFqKAIAIgYgAU4NASAGQQFrIQggACAGakEBaiEBIAcgBkHz9QFqLQAAcSEDQQAhAANAIAAgBkcEQCAFLAAAIgRBv39KBEBBfw8FIARBP3EgA0EGdHIhAyAAQQFqIQAgBUEBaiEFDAILAAsLQX8hBCADIAhBAnRBgPYBaigCAEkNAQsgAiABNgIAIAMhBAsgBAsLACAAIAFBABDpBQsJACAAQQEQrQELugEBAn8CQAJAIAJC/////wdYBEAgACABIAKnQYCAgIB4chBuIgRBAEwNASAAIAEgAhBOIgJCgICAgHCDQoCAgIDgAFINAkF/IQQMAgsgACACEIsDIgVFBEBBfyEEDAELAkAgACABIAUQbiIEQQBMBEBCgICAgDAhAgwBCyAAIAEgBSABQQAQESICQoCAgIBwg0KAgICA4ABSDQBBfyEECyAAIAUQEAwBC0KAgICAMCECCyADIAI3AwAgBAsbAQF/IAAgARA1BH9BAAUgAEH7OUEAEBJBfwsLYwEBfyACQiCIp0F1TwRAIAKnIgUgBSgCAEEBajYCAAsCQCAAIAEgAhDTBSIFDQACQCABKAIAIgBBAEgEQCAAIARqIgBBACAAQQBKGyEDDAELIAAgA0wNAQsgASADNgIACyAFCxgAIAAtAABBIHFFBEAgASACIAAQlwQaCwsPACAAKAJAQYACaiABEA4LrgIAAkACQAJAAkAgAkEDTARAAkACQAJAAkACQAJAAkACQAJAIAFB2ABrDgkAAQIDBAUGBwgKCyAAIAJBO2tB/wFxEA4PCyAAIAJBN2tB/wFxEA4PCyAAIAJBM2tB/wFxEA4PCyAAIAJBL2tB/wFxEA4PCyAAIAJBK2tB/wFxEA4PCyAAIAJBJ2tB/wFxEA4PCyAAIAJBI2tB/wFxEA4PCyAAIAJBH2tB/wFxEA4PCyAAIAJBG2tB/wFxEA4PCyACQf8BSw0BAkACQAJAIAFB2ABrDgMAAQIECyAAQcIBEA4MBQsgAEHDARAODAQLIABBxAEQDgwDCyABQSJGDQELIAAgAUH/AXEQDiAAIAJB//8DcRAmDwsgACACQRJrQf8BcRAODwsgACACQf8BcRAOCzgBAX8CQAJAIAFCgICAgHBUDQAgAaciAy8BBiACRw0AIAMoAiAiAw0BCyAAIAIQigNBACEDCyADC0EBAX8gAQRAA0AgAiADRkUEQCAAIAEgA0EDdGooAgQQECADQQFqIQMMAQsLIAAoAhAiAEEQaiABIAAoAgQRAAALCywBAX8gACgCECICQRBqIAEgAigCABEDACICBEAgAkEAIAEQLA8LIAAQcCACC20BAX8jAEGAAmsiBSQAIARBgMAEcSACIANMckUEQCAFIAFB/wFxIAIgA2siA0GAAiADQYACSSIBGxAsGiABRQRAA0AgACAFQYACEFcgA0GAAmsiA0H/AUsNAAsLIAAgBSADEFcLIAVBgAJqJAALvgECAn4BfwJAAkAgAUKAgICAcINCgICAgDBRBEAgACgCKCACQQN0aikDACIDQiCIp0F0Sw0BDAILIAAgAUE8IAFBABARIgNCgICAgHCDQoCAgIDgAFEEQCADDwsgA0L/////b1YNASAAIAMQDCAAIAEQ/AIiBUUEQEKAgICA4AAPCyAFKAIoIAJBA3RqKQMAIgNCIIinQXVJDQELIAOnIgUgBSgCAEEBajYCAAsgACADIAIQRyEEIAAgAxAMIAQLDAAgAEHZ6gBBABASCw0AIAAgASABED0Q6gELdQEBfiAAIAEgBH4gAiADfnwgA0IgiCICIAFCIIgiBH58IANC/////w+DIgMgAUL/////D4MiAX4iBUIgiCADIAR+fCIDQiCIfCABIAJ+IANC/////w+DfCIBQiCIfDcDCCAAIAVC/////w+DIAFCIIaENwMAC1ABAX4CQCADQcAAcQRAIAEgA0FAaq2GIQJCACEBDAELIANFDQAgAiADrSIEhiABQcAAIANrrYiEIQIgASAEhiEBCyAAIAE3AwAgACACNwMIC2QAAkACQCABQQBIDQAgACgCrAIgAUwNACAAKAKkAiABQRRsaiIAIAAoAgAgAmoiADYCACAAQQBIDQEgAA8LQdwXQajsAEHzpwFBr8MAEAAAC0HphQFBqOwAQfanAUGvwwAQAAALcAECfyAEIAMoAgBKBH8jAEEQayIFJAAgACABKAIAIAQgAygCAEEDbEECbSIAIAAgBEgbIgAgAmwgBUEMahCnASIEBH8gAyAFKAIMIAJuIABqNgIAIAEgBDYCAEEABUF/CyEGIAVBEGokACAGBUEACwsLACAAIAFBARDaBQtjAQF/IAJCIIinQXVPBEAgAqciBiAGKAIAQQFqNgIACwJAIAAgASACENIFIgANACABKQMAIgJCAFMEQCABIAIgBXwiAjcDAAsgAiADWQRAIAQiAyACWQ0BCyABIAM3AwALIAALYAAgACABIAJCgICAgAh8Qv////8PWAR+IAJC/////w+DBUKAgICAwH4gArm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLIANBh4ABEJQBC0MBA38CQCACRQ0AA0AgAC0AACIEIAEtAAAiBUYEQCABQQFqIQEgAEEBaiEAIAJBAWsiAg0BDAILCyAEIAVrIQMLIAMLaQECfwJ/IAAoAgAiA0ECaiIEIAAoAgRKBEBBfyAAIAQQ0QINARogACgCACEDCyAAIANBAWo2AgAgACgCCCIEIANBAnRqIAE2AgAgACAAKAIAIgBBAWo2AgAgBCAAQQJ0aiACNgIAQQALC60QAgx/AX4jAEEQayIKJAACQAJAIAFC/////29YBEAgABAiDAELIAZBgDBxIg5FIAYgBkEIdiIQcSAQQX9zckEHcSIRQQdGcSESIAZBgMAAcSEMIAJB/////wdxIQ0gAachCQJAAkACQAJAAkADQCAJKAIQIgdBMGohCCAHIAcoAhggAnFBf3NBAnRqKAIAIQcCQANAIAdFDQEgAiAIIAdBAWtBA3QiC2oiBygCBEcEQCAHKAIAQf///x9xIQcMAQsLIAkoAhQgC2ohCCAKIAc2AgwgDEUgBygCACILQYCAgIACcUVyRQRAIANCIIinQXVPBEAgA6ciByAHKAIAQQFqNgIACyAAIApBCGogA0EAEL4CDQgCfiAKKAIIIgdBAE4EQCAHrQwBC0KAgICAwH4gB7i9IgNCgICAgMCBgPz/AH0gA0KAgICAgICA+P8AVhsLIQMgCSgCECIHQTBqIQggByAHKAIYIAJxQX9zQQJ0aigCACEHAkADQCAHBEAgCCAHQQFrQQN0IgtqIgcoAgQgAkYNAiAHKAIAQf///x9xIQcMAQsLQdj1AEGo7ABB58YAQasLEAAACyAJKAIUIAtqIQggCiAHNgIMIAcoAgAhCwsgC0EadiIPIAYQjwNFDQYgD0EwcSIPQTBGBEAgACAJIAIgCCAHEMECRQ0CDAgLIAZBgPQAcUUNBSAOBEAgBKciDUEAIAAgBBA1GyECIAWnIg5BACAAIAUQNRshDAJAIAtBgICAgHxxQYCAgIAERwRAQX8hByAAIAkgCkEMahDTAQ0LAkAgCigCDCgCAEGAgICAfHFBgICAgHhGBEAgACgCECAIKAIAEOUBDAELIAAgCCkDABAMCyAKKAIMIgcgBygCAEH///+/AXFBgICAgARyNgIAIAhCADcDAAwBCyALQYCAgCBxDQAgBkGAEHEEQCACIAgoAgBHDQkLIAZBgCBxRQ0AIAwgCCgCBEcNCAsgBkGAEHEEQCAIKAIAIgcEQCAAIAetQoCAgIBwhBAMCyACRSAEQiCIp0F1SXJFBEAgDSANKAIAQQFqNgIACyAIIAI2AgALIAZBgCBxRQ0GIAgoAgQiAgRAIAAgAq1CgICAgHCEEAwLIAxFIAVCIIinQXVJckUEQCAOIA4oAgBBAWo2AgALIAggDDYCBAwGCyAPQSBGDQQgD0EQRgRAQX8hByAAIAkgCkEMahDTAQ0JIAgoAgAiAgRAIAAgAq1CgICAgHCEEAwLIAgoAgQiAgRAIAAgAq1CgICAgHCEEAwLIAooAgwiAiACKAIAQf///78DcTYCACAIQoCAgIAwNwMAIAooAgwoAgAhCwwFCyAMRSALQYCAgOAAcXINBEEBIQcgACADIAgpAwAQTUUNBgwICyAKQQA2AgwgCS0ABUEIcUUNAiAJLwEGIgdBAkcNASACQQBODQIgDSAJKAIoTw0CIBJFBEAgACAJEI4DRQ0BDAcLC0EBIQcgDEUNBiAJKAIkIA1BA3RqIQIgA0IgiKdBdU8EQCADpyIGIAYoAgBBAWo2AgALIAAgAiADEB0MBgsgB0EVa0H//wNxQQpLDQACQAJAIAJBAE4EQCAAIAIQ3wUiAUKAgICAcIMiE0KAgICAMFENA0F/IQcgE0KAgICA4ABRDQggACABENkFIgJBAEgEQCAAIAEQDAwJCyACRQRAIAAgARAMIAAgBkGaDRB8IQcMCQtBACEHAkBBByABQiCIpyICIAJBB2tBbkkbIgJBdkcEQCACQQdHBEAgAg0CIAFCgICAgAiDQh+IpyEHDAILIAFCgICAgMCBgPz/AHxCP4inIQcMAQsgAaciAigCCEUNACACKAIMQYCAgIB4RyEHCyAAIAEQDCAHRQ0BIAAgBkG7DRB8IQcMCAsgDSAJKAIoSQ0BCyAAIAZB2Q0QfCEHDAYLIA5FIBFBB0ZxRQRAIAAgBkHBJhB8IQcMBgtBASEHIAxFDQUgA0IgiKdBdU8EQCADpyICIAIoAgBBAWo2AgALIAAgASANrSADIAYQzwEhBwwFCyAAIAkgAiADIAQgBSAGEN0FIQcMBAsgC0GAgICAfHFBgICAgHhGBEACQCAMRQ0AIAgoAgAoAhAhAiAJLwEGQQtGBEAgACADIAIpAwAQTUUNBAwBCyADQiCIp0F1TwRAIAOnIgcgBygCAEEBajYCAAsgACACIAMQHQsgBkGCBHFBgARHDQEgCS8BBkELRgRAIAAgBkGc0QAQfCEHDAULQX8hByAAIAkgCkEMahDTAQ0EIAgoAgAiBygCECkDACIBQiCIp0F1TwRAIAGnIgIgAigCAEEBajYCACAIKAIAIQcLIAAoAhAgBxDlASAIIAE3AwAgCigCDCICIAIoAgBB////vwNxNgIADAELIAtBgICAgAJxBEBBASECIAwEQCADQiCIp0F1TwRAIAOnIgIgAigCAEEBajYCAAsgACAJIAMgBhDeBSECCyAGQYIEcUGABEYEQCAKIAkoAhAiBkEwajYCDEF/IQcgACAJIApBDGogBigCMEEadkE9cRCNAw0FCyACIQcMBAsgDARAIAAgCCkDABAMIANCIIinQXVPBEAgA6ciAiACKAIAQQFqNgIACyAIIAM3AwALIAZBgARxRQ0AQX8hByAAIAkgCkEMaiAKKAIMKAIAQRp2QT1xIAZBAnFyEI0DDQMLQX9BASAAIAkgCkEMaiAQQQVxIgBBf3MgCigCDCgCAEEadnEgACAGcXIQjQMbIQcMAgsgACAGQe/YABB8IQcMAQtBfyEHCyAKQRBqJAAgBwtpAQN/IwBBEGsiAyQAAkACQCABQoCAgIBwVA0AIAGnIgQvAQYhBSACBEAgBUEgRw0BDAILIAVBFWtB//8DcUELSQ0BCyADQbgRQa4OIAIbNgIAIABB8iogAxASQQAhBAsgA0EQaiQAIAQLRgIBfwF+IAJC/////wdYBEAgACABIAIQTg8LIAAgAhCLAyIDRQRAQoCAgIDgAA8LIAAgASADIAFBABARIQQgACADEBAgBAv8AQICfwF8IwBBEGsiBCQAAkAgAkIgiKciA0ECTQRAIAEgAqe3OQMAQQAhAwwBCyADQQdrQW1NBEAgASACQoCAgIDAgYD8/wB8NwMAQQAhAwwBCwJ/IAAgAhCWASICQoCAgIBwg0KAgICA4ABRBEBEAAAAAAAA+H8hBUF/DAELAnwCQAJAQQcgAkIgiKciAyADQQdrQW5JGyIDQXZHBEAgA0EHRg0CIAMNASACp7cMAwsgAqdBBGogBEEIahCxBCAAIAIQDCAEKwMIIQVBAAwDCxABAAsgAkKAgICAwIGA/P8AfL8LIQVBAAshAyABIAU5AwALIARBEGokACADC90BAQN/AkAgAUKAgICAcFoEQCABpyEDA0ACQCADLQAFQQRxRQ0AIAAoAhAoAkQgAy8BBkEYbGooAhQiBEUNACAEKAIQIgRFDQAgAyADKAIAQQFqNgIAIAAgA61CgICAgHCEIgEgAiAEERMAIQUgACABEAwgBQ8LIAMgAygCAEEBajYCACAAQQAgAyACEEMhBCAAIAOtQoCAgIBwhBAMIAQNAgJAIAMvAQZBFWtB//8DcUEKSw0AIAAgAhCTAyIERQ0AIARBH3UPCyADKAIQKAIsIgMNAAsLQQAhBAsgBAvNCQIEfwV+IwBB8ABrIgYkACAEQv///////////wCDIQkCQAJAIAFQIgUgAkL///////////8AgyIKQoCAgICAgMD//wB9QoCAgICAgMCAgH9UIApQG0UEQCADQgBSIAlCgICAgICAwP//AH0iC0KAgICAgIDAgIB/ViALQoCAgICAgMCAgH9RGw0BCyAFIApCgICAgICAwP//AFQgCkKAgICAgIDA//8AURtFBEAgAkKAgICAgIAghCEEIAEhAwwCCyADUCAJQoCAgICAgMD//wBUIAlCgICAgICAwP//AFEbRQRAIARCgICAgICAIIQhBAwCCyABIApCgICAgICAwP//AIWEUARAQoCAgICAgOD//wAgAiABIAOFIAIgBIVCgICAgICAgICAf4WEUCIFGyEEQgAgASAFGyEDDAILIAMgCUKAgICAgIDA//8AhYRQDQEgASAKhFAEQCADIAmEQgBSDQIgASADgyEDIAIgBIMhBAwCCyADIAmEUEUNACABIQMgAiEEDAELIAMgASABIANUIAkgClYgCSAKURsiCBshCiAEIAIgCBsiDEL/////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/wBWGwsQTgtTAQF/IAAoAhAiBEEQaiABIAIgBCgCCBEBACIBIAJFckUEQCAAEHAgAQ8LIAMEQCADIAEgACgCECgCDBEFACIAIAJrIgJBACAAIAJPGzYCAAsgAQsNACAAQQAgAUEAEJoDC/kBAgN+An8jAEEQayIFJAACfiABvSIEQv///////////wCDIgJCgICAgICAgAh9Qv/////////v/wBYBEAgAkI8hiEDIAJCBIhCgICAgICAgIA8fAwBCyACQoCAgICAgID4/wBaBEAgBEI8hiEDIARCBIhCgICAgICAwP//AIQMAQsgAlAEQEIADAELIAUgAkIAIAKnZ0EgaiACQiCIp2cgAkKAgICAEFQbIgZBMWoQYiAFKQMAIQMgBSkDCEKAgICAgIDAAIVBjPgAIAZrrUIwhoQLIQIgACADNwMAIAAgAiAEQoCAgICAgICAgH+DhDcDCCAFQRBqJAALKgEBfyMAQRBrIgMkACADIAI2AgwgACABIAJBpANBABCUBBogA0EQaiQAC0cAIAAgAUkEQCAAIAEgAhAeGg8LIAIEQCAAIAJqIQAgASACaiEBA0AgAEEBayIAIAFBAWsiAS0AADoAACACQQFrIgINAAsLCyABAX4gACAAIAIgASADQQRBABCCASIFIAEgBBC/ASAFC4sMAQZ/IwBBIGsiAyQAAkACQAJAAkACQAJ/IAAoAhAiAkGDf0cEQEEAIAJBV0cNARogACgCQCIELQBsQQFxRQRAIABBl+AAQQAQEwwDCyAEKAJkRQRAIABB6jtBABATDAMLQX8hBSAAEA8NBQJAAkACQAJAIAAoAhAiBEEpaw4EAgEBAgALIARB3QBGIARBOmtBAklyIARB/QBGcg0BCyAAKAIwDQBBACECIARBKkYEQCAAEA8NCEEBIQILIAAgARCtAUUNAQwHCyAAQQYQDUEAIQILIAAoAkAtAGwhASACBEAgABAtIQUgABAtIQIgAEGAAUH/ACABQQNGGxANIABBDhANIABBBhANIABBBhANIAAgBRAaIABBhgEQDSABQQNHIgdFBEAgAEGMARANCyAAQYMBEA0gAEHCABANIABB6gAQFyAAQesAQX8QGCEGIAAgAhAaQYoBIQQgACAHBH9BigEFIABBwQAQDSAAQcEAEBdBiwELEA0gAEEREA0gAEHrAEF/EBghBCAA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AqcQvAJFIQQMBgsgAacgAqdGIAVBeEZxIQQMBQsgBUF/Rw0EIAGnIAKnRiEEDAQLIAGntyEIIAVBB0cEQCAFDQQgAqe3DAILIAJCgICAgMCBgPz/AHy/DAELIAFCgICAgMCBgPz/AHy/IQggBQRAIAVBB0cNAyACQoCAgIDAgYD8/wB8vwwBCyACp7cLIQkCQCADBEACQAJAIAi9IgFC////////////AIMiAkKBgICAgICA+P8AWgRAIAm9Qv///////////wCDQoGAgICAgID4/wBUIQQMAQsgCb0iCkL///////////8Ag0KBgICAgICA+P8AVA0BCyAEIAJCgICAgICAgPj/AFZzIQQMBQsgA0ECRw0BCyAIIAlhIQQMAwsgASAKUSEEDAILIAVBdkcNACAAIAdBHGoiBiABEK0CIgMgACAHQQhqIAIQrQIiBRC9AiEEIAMgBkYEQCAGEBkLIAUgB0EIaiIDRw0AIAMQGQsgACABEAwgACACEAwLIAdBMGokACAECy8BAX8jAEHQAGsiAyQAIAMgACADQRBqIAEQgQE2AgAgACACIAMQEiADQdAAaiQACw0AIAAgASABED0QnQMLHQEBfyAAIAFB/wFxEA4gACgCBCEDIAAgAhAbIAMLEgAgACABIAIgAyAEQZQDELEDC1IBAX8gACgCDCIDRQRAQQAPCyAAIAAoAghB/////wNBgYCAgHwgASABQYGAgIB8TBsiASABQf////8DThtqNgIIIABB/////wMgAiADQQAQ3AILHwEBfyAAKAIMIgNFBEBBAA8LIAAgASACIANBABDcAgsgACABQgA3AgwgAUKAgICAgICAgIB/NwIEIAEgADYCAAtmAQF/An9BACAAKAIIIgIgAU8NABpBfyAAKAIMDQAaIAAoAhQgACgCACACQQNsQQF2IgIgASABIAJJGyIBIAAoAhARAQAiAkUEQCAAQQE2AgxBfw8LIAAgATYCCCAAIAI2AgBBAAsLZwECfwJAIAFCgICAgHBUDQAgAaciAy8BBkEEayIEQR1LQQEgBHRBz4CAgAJxRXINACAAIAMpAyAQDCADIAI3AyAPCyAAIAIQDCABQoCAgIBwg0KAgICA4ABSBEAgAEHu0gBBABASCwshAQF/IAAgASAAIAIQtgEiAiADIAQQFSEFIAAgAhAQIAULRwIBfgF/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PEdxRQRAIAAgBigCNBCeAQsgACAEEBoLIAAgBigCMCAGKAIsIAYoAjQgBigCPEEBIAwQwQELIAAoAhBB3QBGDQAgB0Glf0YEQEGQ0wAhCAwECyAAQSwQKEUNAQwFCwsgAEGFARANIAAoAkAiASABKAKwAigCADYCsAIgABAPDQMLAkAgBUUNACAAKAIQQT1HDQBBfyEHIABB7ABBfxAYIQEgABAPDQQgACAKEBogAwRAIABBDhANCyAAEFMNBCAAQewAIAsQGBogACABEBpBASEHDAQLIANFBEAgAEHDPUEAEBMMAwsgACgCQCgCgAIgDWpBswEgDiANaxAsGiAAKAJAKAKkAiAKQRRsaiIAIAAoAgBBAWs2AgBBACEHDAMLIAAgCEEAEBMMAQsgACgCACAGKAI0EBALQX8hBwsgBkFAayQAIAcLKwAgACgCQCgCpAFBAE4EQCAAQQYQDSAAQdkAEA0gACAAKAJALwGkARAUCwsTACAAIAEgAiADIARBAEEAEN0BC6YBAQF/IwBBEGsiAyQAIAMgAjcDCAJAIAAgAUGHASABQQAQESICQoCAgIBwg0KAgICA4ABRDQAgACACEDUEQCAAIAIgAUEBIANBCGoQNiICQv////9vViACQoCAgICwf4NCgICAgCBRcg0BIAAgAhAMIABBpcEAQQAQEkKAgICA4AAhAgwBCyAAIAIQDCAAIAEgACADQQhqEIoFIQILIANBEGokACACC6MBAgN/AX4gAEEQaiECIAEoAgAiBEEBaiEDAkAgACkCBCIFQoCAgIAIg1BFBEAgAiAEQQF0ai8BACIAQYD4A3FBgLADRyADIAWnQf////8HcU5yDQEgAiADQQF0ai8BACICQYD4A3FBgLgDRw0BIABBCnRBgPg/cSACQf8HcXJBgIAEaiEAIARBAmohAwwBCyACIARqLQAAIQALIAEgAzYCACAACxIAIAFB2AFOBEAgACABEIYFCwthACAAIAEgAkKAgICACHxC/////w9YBH4gAkL/////D4MFQoCAgIDAfiACub0iAkKAgICAwIGA/P8AfSACQv///////////wCDQoCAgICAgID4/wBWGwsgAyAEQQdyEJQBCzkAIABB/wBNBEAgAEEDdkH8////AXFBsP8BaigCACAAdkEBcQ8LIABBfnFBjMAARiAAEJYGQQBHcgs1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QfD//wFxayIYJAAgBSEVIAYEQCAHIAQgByAIGyIIQQAgCEEAShsiCGsiCUEAIAcgCU8bIREDQAJAIAggCkYEQANAIAggEUYNAiAYIAhBA3RqQoCAgIAwNwMAIAhBAWohCAwACwALIAUgCkEDdCIJaikDACIBQiCIp0F1TwRAIAGnIhUgFSgCAEEBajYCAAsgCSAYaiABNwMAIBFBAWohESAKQQFqIQoMAQsLIA4gBzYCVCAYIRULIA4gFTYCQCAOIBggBkEDdGoiETYCREEAIQgDQCAIIBdHBEAgESAIQQN0akKAgICAMDcDACAIQQFqIQgMAQsLIBIoAhQhBiAOIBYoAowBNgIwIBYgDkEwaiIUNgKMASASKAIwIQkgESAXQQN0aiIIIRcLQQAMAQtBAQshBwNAAkACQAJAAkAgB0UEQCAEQQN0ISMgA0KAgICAcIMhOyARQQhqIRogEUEQaiEbIBFBGGohHCAVQQhqIR0gFUEQaiEeIBVBGGohHyAUQRhqISQgAkIgiKciIEF+cSElIANCIIinISYgBK0hOiADpyEhIA5BMGohJyAOQegAaiEiIAghBwJAA0ACQCAGQQFqIQxCgICAgDAhNQJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJA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AAsgCCABNwMQIAhBGGohBwzeAQsgCCAIQQhrIgYpAwA3AwAgCEEQaykDACIBQiCIp0F1TwRAIAGnIgcgBygCAEEBajYCAAsgBiABNwMAIAhBCGohBwzdAQsgCCAIQQhrIgYpAwAiATcDACAGIAhBEGsiBikDADcDACABQiCIp0F1TwRAIAGnIgcgBygCAEEBajYCAAsgBiABNwMAIAhBCGohBwzcAQsgCCAIQQhrIgYpAwAiATcDACAIQRBrIgcpAwAhNSAHIAhBGGsiBykDADcDACAGIDU3AwAgAUIgiKdBdU8EQCABpyIGIAYoAgBBAWo2AgALIAcgATcDACAIQQhqIQcM2wELIAggCEEIayIGKQMAIgE3AwAgCEEQayIHKQMAITUgByAIQRhrIgcpAwA3AwAgBiA1NwMAIAcgCEEgayIGKQMANwMAIAFCIIinQXVPBEAgAaciByAHKAIAQQFqNgIACyAGIAE3AwAgCEEIaiEHDNoBCyAIQRBrIgYpAwAhASAGIAhBGGsiBikDADcDACAGIAE3AwAM0wELIAhBGGsiBikDACEBIAYgCEEQayIGKQMANwMAIAhBCGsiBykDACE1IAcgATcDACAGIDU3AwAM0gELIAhBIGsiBikDACEBIAYgCEEYayIGKQMANwMAIAhBEGsiBykDACE1IAcgCEEIayIHKQMANwMAIAYgNTcDACAHIAE3AwAM0QELIAhBKGsiBikDACEBIAYgCEEgayIGKQMANwMAIAhBGGsiBykDACE1IAcgCEEQayIHKQMANwMAIAYgNTcDACAHIAhBCGsiBikDADcDACAGIAE3AwAM0AELIAhBCGsiBikDACEBIAYgCEEQayIGKQMANwMAIAhBGGsiBykDACE1IAcgATcDACAGIDU3AwAMzwELIAhBEGsiBikDACEBIAYgCEEYayIGKQMANwMAIAhBIGsiBykDACE1IAcgATcDACAGIDU3AwAMzgELIAhBEGsiBikDACEBIAYgCEEYayIGKQMANwMAIAhBIGsiBykDACE1IAcgCEEoayIHKQMANwMAIAYgNTcDACAHIAE3AwAMzQELIAhBCGsiBikDACEBIAYgCEEQayIGKQMANwMAIAYgATcDAAzMAQsgCEEgayIGKQMAIQEgBiAIQRBrIgYpAwA3AwAgCEEIayIHKQMAITUgByAIQRhr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xH4iAUIAUg0EIA0gD3JBAE4NBSALQoCAgIDA/v8DNwMADA0LIAtCgICAgMB+IA+3IA23o70iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGzcDAAwMCyANQQBKIA9BAE5xRQRAIAsCfiAPtyANtxCZBCI8vSIBAn8gPJlEAAAAAAAA4EFjBEAgPKoMAQtBgICAgHgLIgq3vVEEQCAKrQwBC0KAgICAwH4gAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGws3AwAMDAsgDyANcK0hAQwCCyAPtyE8IAsCfgJ8IA23Ij29QoCAgICAgID4/wCDQoCAgICAgID4/wBRBEBEAAAAAAAA+H8gPJlEAAAAAAAA8D9hDQEaCyA8ID0QowMLIjy9IgECfyA8mUQAAAAAAADgQWMEQCA8qgwBC0GAgICAeAsiCre9UQRAIAqtDAELQoCAgIDAfiABQoCAgIDAgYD8/wB9IAFC////////////AINCgICAgICAgPj/AFYbCzcDAAwKCyA1xCABxH0hAQsgAUKAgICACHxC/////w9WDQEgASE2CyA2Qv////8PgwwBC0KAgICAwH4gAbm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLNwMADAULIBBBdkcgD0F2R3FFBEAgCSAKIAsgNSABIAkoAhAoAqwCESMADQcMBQsgCSAHQQhqIDUQbQ0FIAkgByABEG0NBgsCQAJAAkACQCAKQZsBaw4GAAECBAUDBAsgBysDCCAHKwMAogwFCyAHKwMIIAcrAwCjDAQLIAcrAwggBysDABCZBAwDCyAHKwMIITwgBysDACI9vUKAgICAgICA+P8Ag0KAgICAgICA+P8AUQRARAAAAAAAAPh/IDyZRAAAAAAAAPA/YQ0DGgsgPCA9EKMDDAILEAEACyAHKwMIIAcrAwChCyE8IAtCgICAgMB+IDy9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhs3AwALQQAMAgsgCSABEAwLIAtCgICAgDA3AwAgDUKAgICAMDcDAEF/CyEwIAdBEGokACAwDUsgCEEIayEHDEcLIAhBBGsoAgAiB0UgB0EHa0FuSXINQCAIIQcgCSAIQY4BEOEBRQ1GDEoLAkACfCAIQQhrIgcpAwAiAUIgiKciCkUEQEQAAAAAAAAAgCABpyIGRQ0BGkQAAAAAAADgQSAGQYCAgIB4Rg0BGiAHQgAgAX1C/////w+DNwMADEILIApBB2tBbUsNASABQoCAgIDA/v8Dfb8LITwgB0KAgICAwH4gPL0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGzcDAAxACyAIIQcgCSAIQY0BEOEBRQ1FDEkLIAhBCGsiBykDACIBQv////8PViABQv////8HUXJFBEAgByABQgF8Qv////8PgzcDAAw/CyAIIQcgCSAIQZABEOEBRQ1EDEgLIAhBCGsiBykDACIBQv////8PViABQoCAgIAIUXJFBEAgByABQgF9Qv////8PgzcDAAw+CyAIIQcgCSAIQY8BEOEBRQ1DDEcLIAkgCEEIayIHKQMAEGUiAUKAgICAcINCgICAgOAAUQRAIAdCgICAgDA3AwAMRwsgByABNwMAIAFCIIinQXVPBEAgAaciByAHKAIAQQFqNgIACyAIIAE3AwAgCSAIQQhqIgcgCkECaxDhAUUNQgxGCyAGQQJqIQwgESAGLQABQQN0aiIHKQMAIgFC/////w9WIAFC/////wdRckUEQCAHIAFCAXxC/////w+DNwMADDwLIAFCIIinQXVPBEAgAaciCiAKKAIAQQFqNgIACyAOIAE3A2AgCSAiQZABEOEBDUUgCSAHIA4pA2AQHQw7CyAGQQJqIQwgESAGLQABQQN0aiIHKQMAIgFC/////w9WIAFCgICAgAhRckUEQCAHIAFCAX1C/////w+DNwMADDsLIAFCIIinQXVPBEAgAaciCiAKKAIAQQFqNgIACyAOIAE3A2AgCSAiQY8BEOEBDUQgCSAHIA4pA2AQHQw6CyAIQQhrIgcpAwAiAUL/////D1gEQCAHIAFC/////w+FNwMADDoLIAghByMAQRBrIgokAAJ/AkACQCAJIAhBCGsiCykDABBlIgFCgICAgHCDIjVCgICAgOAAUQ0AIDVCgICAgOB+UQRAIAkgC0GWASABIAkoAhAoAqgCER8ADQEMAgsgCSAKQQxqIAEQlQENACALIAo1AgxC/////w+FNwMADAELIAtCgICAgDA3AwBBfwwBC0EACyExIApBEGok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//8AgyIGQoCAgICAgMD//wBWIAZCgICAgICAwP//AFEbDQAgACAChCAFIAaEhFAEQEEADwsgASADg0IAWQRAQX8hBCAAIAJUIAEgA1MgASADURsNASAAIAKFIAEgA4WEQgBSDwtBfyEEIAAgAlYgASADVSABIANRGw0AIAAgAoUgASADhYRCAFIhBAsgBAuhAgEFfwNAAkACQAJAAkACfyACIAdMIgkgBCAGTHJFBEAgASAHQQJ0aigCACIIIAMgBkECdGooAgAiCUkEQCAIDAILIAggCUcNAyAGQQFqIQYgB0EBaiEHIAghCQwECyAJDQEgASAHQQJ0aigCAAshCSAHQQFqIQcMAgsgBCAGTA0CIAMgBkECdGooAgAhCQsgBkEBaiEGCwJ/AkACQAJAAkAgBQ4DAwABAgsgBiAHcUEBcQwDCyAGIAdzQQFxDAILEAEACyAGIAdyQQFxCyAAKAIAIghBAXFGDQEgACgCBCAITARAIAAgCEEBahDRAgRAQX8PCyAAKAIAIQgLIAAgCEEBajYCACAAKAIIIAhBAnRqIAk2AgAMAQsLIAAQmAZBAAvmAQAgAAJ/IAEoAggiAEH+////B04EQEEAIAJBAXENARpB/////wcgAEH+////B0cNARogASgCBEH/////B2oMAQtBACAAQQBMDQAaIABBH00EQEEAIAEoAhAgASgCDEECdGpBBGsoAgBBICAAa3YiAGsgACABKAIEGwwBCyACQQFxRQRAQf////8HIAEoAgRFDQEaQYCAgIB4IABBIEcNARogASgCECABKAIMQQJ0akEEaygCABpBgICAgHgMAQtBACABKAIQIAEoAgwiAiACQQV0IABrEHEiAGsgACABKAIEGws2AgALEgAgACABIAIgAyAEQZMDELEDCw4AIABBACABQRByELoBC58BAgR/An4gAzUCACEJA0AgAiAFRkUEQCAAIAVBAnQiB2ogBq0gASAHajUCACAJfnwiCj4CACAFQQFqIQUgCkIgiKchBgwBCwsgACACQQJ0IgdqIAY2AgBBASAEIARBAU0bIQRBASEFA0AgBCAFRkUEQCAAIAVBAnQiBmoiCCAHaiAIIAEgAiADIAZqKAIAEL0ENgIAIAVBAWohBQwBCwsLWgEFfyADQQAgA0EAShshBgNAIAUgBkcEQCAAIAVBAnQiA2ogASAD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gICAgOAAUg0AIAEhAgwBCwJAIAKnIgUpAgQiCkL/////B4NQDQAgAaciAykCBCELAkAgAygCAEEBRyAKIAuFQoCAgIAIg0IAUnINACADIAAoAhAoAgwRBQAgBSkCBCIKpyIEQf////8HcSIHIAMpAgQiC6ciBkH/////B3EiCGogBEEfdnQgBkEfdiIJQRFzakkNACAFQRBqIQYgA0EQaiEEIAkEQCAEIAhBAXRqIAYgB0EBdBAeGiADIAMpAgQiCiAFKQIEfEL/////B4MgCkKAgICAeIOENwIEDAILIAQgCGogBiAHEB4aIAMgAykCBCIKIAUpAgR8Qv////8HgyILIApCgICAgHiDhDcCBCAEIAunakEAOgAADAELAn4CQAJAIAunQf////8HcSAKp0H/////B3FqIgdBgICAgARPBEAgAEHkyABBABA6DAELIAAgByAKIAuEpyIGQR92EOkBIggNAQtCgICAgOAADAELIAhBEGohBAJAIAZBAE4EQCAEIANBEGogAygCBEH/////B3EQHiIEIAMoAgRB/////wdxaiAFQRBqIAUoAgRB/////wdxEB4aIAQgB2pBADoAAAwBCyAEIAMgAygCBEH/////B3EQkwUgBCADKAIEQQF0aiAFIAUoAgRB/////wdxEJMFCyAIrUKAgICAkH+ECyEKIAAgARAMDAELIAEhCgsgACACEAwgCgsPACAAIAFCgICAgDAQggILCwAgAEGfCUEAEBILjgIBA38jAEEQayIFJAAgBSAAOQMIIAUgAUEBayIHNgIAIAZBgAFB9t8AIAUQSBogAyAGLQAAQS1GNgIAIAQgBi0AAToAACABQQJOBEAgBEEBaiAGQQNqIAcQHhoLIAEgBGpBADoAACACIQkgASAGaiABQQFKakECaiEBA0AgASICQQFqIQEgAiwAACIDEI8GDQALQQEhBAJAAkACQCADQf8BcUEraw4DAQIAAgtBACEECyABLAAAIQMgASECC0EAIQEgA0EwayIDQQlNBEADQCABQQpsIANrIQEgAiwAASEIIAJBAWohAiAIQTBrIgNBCkkNAAsLIAlBACABayABIAQbQQFqNgIAIAVBEGokAAuADAIHfwV+IwBBoANrIgUkAAJAIAG9IgxCgICAgICAgPj/AINCgICAgICAgPj/AFEEQCAMQv//////////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QCAHqwwBC0EACyIDNgIcIAcgA7hhDQMMAQsgAwRAQX8hAyAAIAIQlgEiAkKAgICAcINCgICAgOAAUQ0EIAAgBEEcaiACQQEQvgJFDQMMBAsgACAEQRxqIAIQdQRAIAAgAhAMDAILQX8hAyAAIAIQlgEiAkKAgICAcINCgICAgOAAUQ0DIAAgBEEEaiACQQAQvgINAyAEKAIEIAQoAhxGDQILIABBiscAQQAQRAtBfyEDDAELIAEgBCgCHDYCAEEAIQMLIARBIGokACADC0ABAX4gABDiASICQoCAgIBwg0KAgICA4ABSBEAgAqdBBGogARCcAkUEQCACDwsgACACEAwgABBwC0KAgICA4AALMgEBfyMAQdAAayICJAAgAiAAIAJBEGogARCBATYCACAAQbzpACACEMMCIAJB0ABqJAALoAECAX8BfiMAQRBrIgUkACAFIAQ2AgxBfyEEIAAgASAFQQxqENMBRQRAIAMoAgAiAEF8cSABIAIgAygCBCAAQQNxQQJ0QYS3AWooAgARGwAhBiADEOAFIAUoAgwiACAAKAIAQf////8DcTYCACADQoCAgIAwIAYgBkKAgICAcINCgICAgOAAUSIAGzcDAEF/QQAgABshBAsgBUEQaiQAIAQLFQECfiAAIAEQ6AEhAyAAIAEQDCADCw0AIAAgASACQQIQyAIL1QEBA38jAEEQayIFJABBfyEDAkAgACgCFA0AAkACQCABQYCAgIAETgRAIAAoAgBB5MgAQQAQOgwBCyABIAAoAgxBA2xBAm0iBCABIARKGyEBIAAoAhAiBCACQYACSHJFBEAgACABEOADIQMMAwsgACgCACAAKAIEIAEgBHQgBGtBEWogBUEMahCnASICDQELIAAQ9wIMAQsgBSgCDCEDIAAgAjYCBCAAQf////8DIAMgACgCEHYgAWoiACAAQf////8DThs2AgxBACEDCyAFQRBqJAAgAwsqAQF/IAAoAhAiA0EQaiABIAIgAygCCBEBACIBIAJFckUEQCAAEHALIAELgQECAn8BfgJAIAEpAgQiBEL//////////79/VgRAIAEoAgwhAAwBCyAAKAI0IARCIIinIAAoAiRBAWtxQQJ0aiECIAAoAjghAwNAIAMgAigCACIAQQJ0aigCACICIAFGDQEgAkEMaiECIAANAAtBxocBQajsAEH/FEH4DhAAAAsgAAupBwIJfwF+AkACQAJAAn8gAkECTARAIAIgASkCBCIMQj6Ip0YEQCAAIAEQxgIiBEHXAUoNBSABIAEoAgBBAWs2AgAgBA8LIAAoAjQgACgCJEEBayABIAIQ6wVB/////wNxIgdxIgpBAnRqIQMgDKdB/////wdxIQUDQCACIAMoAgAiBEUNAhoCQCAAKAI4IARBAnRqKAIAIgMpAgQiDKdB/////wdxIAVHIAxCPoinIAJHciAMQiCIp0H/////A3EgB0dyDQAgAyABIAUQ6gUNACAEQdgBSA0EIAMgAygCAEEBajYCAAwECyADQQxqIQMMAAsACyACQQNHIQdBAwshBQJAIAAoAjwNAEEAIQQgAEEQaiILIAAoAjhB0wEgACgCLEEDbEECbSICIAJB0wFMGyICQQJ0IAAoAggRAQAiCEUNASAAKAIsIgkhAyAJRQRAIAtBECAAKAIAEQMAIgZFBEAgCyAIIAAoAgQRAAAMAwsgBkKAgICAgICAgEA3AgQgBkEBNgIAIAZBADYADCAIIAY2AgAgACAAKAIoQQFqNgIoQQEhAwsgACADNgI8IAAgCDYCOCAAIAI2AiwgCSACIAIgCUkbIQQgAkEBayEGA0AgAyAERg0BIAAoAjggA0ECdGpBASADQQFqIgJBAXRBAXIgAyAGRhs2AgAgAiEDDAALAAsCQCABBEAgASkCBCIMQv//////////P1gEQCABIAwgBa1CPoaENwIEDAILIABBEGogDKciAkEfdSACQf////8HcSACQR92dGpBEWogACgCABEDACICRQRAQQAhBAwECyACQQE2AgAgAiACKQIEQv////93gyABKQIEQoCAgIAIg4QiDDcCBCACIAxCgICAgHiDIAEpAgRC/////weDhDcCBCACQRBqIAFBEGogASgCBCIDQf////8HcSADQR92dCADQX9zQR92ahAeGiAAIAEQkAQgAiEBDAELIABBEGpBECAAKAIAEQMAIgFFBEBBAA8LIAFCgYCAgICAgICAfzcCAAsgACAAKAI4IAAoAjwiBEECdGoiAigCAEEBdjYCPCACIAE2AgAgASAENgIMIAEgATUCBCAHrUIghoQgBa1CPoaENwIEIAAgACgCKEEBajYCKCAFQQNGDQIgASAAKAI0IApBAnRqIgEoAgA2AgwgASAENgIAIAAo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QQAQEgwBCwJAIARFIAJBAExyRQRAIAAgBSAHQgAgAUF/EPMCDQIMAQsgASEICyACQQAgAkEAShutIQlCACEBA0AgASAJUgRAIAMgAadBA3RqKQMAIgdCIIinQXVPBEAgB6ciAiACKAIAQQFqNgIACyABIAh8IQogAUIBfCEBIAAgBSAKIAcQe0EATg0BDAILCyAAIAVBMCAGQoCAgIAIfCIIQv////8PWAR+IAZC/////w+DBUKAgICAwH4gBrm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLEDlBAEgNACAAIAUQDCAGQv////8PgyAIQv////8PWA0BGkKAgICAwH4gBrm9IgFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsMAQsgACAFEAxCgICAgOAACyELIAxBEGokACALCxUBAn4gACABEIcFIQMgACABEAwgAwv5DgIKfgR/IwBBEGsiECQAIBAgAjcDCAJAAkACfgJAAkACQAJAAkACQAJAAkACQAJAQQcgAkIgiKciDiAOQQdrQW5JGyIOQQdqDg8EAwMDAwMABQUFAwMDAwECCwJAAkACQAJAIAKnIg4vAQYiD0EEaw4DAQACAwtCgICAgDAhByAAIAIQNCICQoCAgIBwg0KAgICA4ABRDQsgACACEO4DIgJCgICAgHCDQoCAgIDgAFENCyABKAIoIAIQhAEhDgwOC0KAgICAMCEHIAAgAhCWASICQoCAgIBwg0KAgICA4ABRDQogASgCKCACEIQBIQ4MDQsgASgCKCAOKQMgEI0BIQ4gACACEAwMDAsgD0EhRg0HQoCAgIAwIQYgACABKQMIQQEgEEEIahDxAyIEQoCAgIDwAINCgICAgOAAUQ0GIAAgBBAnBEAgAEHJ3wBBABASDAcLIANCIIinQXVPBEAgA6ciDiAOKAIAQQFqNgIACyABKQMYIgRCIIinQXVPBEAgBKciDiAOKAIAQQFqNgIACwJAAkACQAJAIAAgAyAEELYCIghCgICAgHCDQoCAgIDgAFEEQEKAgICAMCEHDAELIAEpAxgiBEKAgICAcINCgICAgJB/UQRAIASnKAIEQf////8HcUUNAwsgCEIgiKdBdU8EQCAIpyIOIA4oAgBBAWo2AgALIABB65YBIAhB7JYB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BEAgAiAANgIAC0EBC4cDAgN+BH8CQCABKAIIIgZB/v///wdOBEBBASEHIAJBAXENAUL///////////8AIQMgBkH+////B0cNASABNAIEQv///////////wB8IQMMAQsgBkEATARADAELIAZBP00EQCABKAIQIAEoAgwiCEECdGoiCUEEaygCACECQgAgBkEgTQR+IAJBICAGa3atBSAIQQJPBH4gCUEIazUCAAVCAAsgAq1CIIaEQcAAIAZrrYgLIgN9IAMgASgCBBshAwwBCyACQQFxRQRAIAEoAgRFBEBC////////////ACEDQQEhBwwCC0KAgICAgICAgIB/IQNBASEHIAZBwABHDQEgASgCECABKAIMIgFBAnRqIgJBBGs1AgBCIIYhBCABQQJPBH4gAkEIazUCAAVCAAsgBIRCgICAgICAgICAf1IhBwwBC0IAIAEoAhAiCCABKAIMIgIgAkEFdCAGayIGEHGtIAggAiAGQSBqEHGtQiCGhCIDfSADIAEoAgQbIQMLIAAgAzcDACAHC60CAgJ/An4jAEEgayICJAACQCAAKAIIQf////8HRgRAQoCAgICAgID8/wAhBAwBCyAAKAIAIQMgAkIANwIYIAJCgICAgICAgICAfzcCECACIAM2AgwgAkEMaiIDIAAQSRoCfiACKAIUIgBB/f///wdMBEAgA0E1QcgEELoBGiACKAIUIQALQoCAgICAgID4/wAgAEH+////B0YNABpCACAAQYCAgIB4Rg0AGiACKAIcIQMCfiACKAIYQQJGBEAgAykCAAwBCyADNQIAQiCGCyEEIABBgnhMBEAgBEGOeCAAa62IIQRCAAwBCyAEQguIQv////////8HgyEEIABB/gdqrUI0hgshBSAEIAWEIAI1AhBCP4aEIQQgAkEMahAZCyABIAQ3AwAgAkEgaiQACw0AIAAgASACQQIQsAMLIwACQAJAAkAgAg4CAAECCyAAIAFyDwsgACABcw8LIAAgAXEL4QgBEX8gAigCBCAFcyIFIAEoAgQiBnMhDQJAIAEgAhDyASIIIA1Fcg0AIAEoAghB/f///wdKDQAgACAEQQdxQQJGEIABQQAPCyAFIAYgCEEASCIGGyEFIAEgAiAGGyEKAkACQAJAIAIgASAGGyIIKAIMIgcEQCAKKAIMIgsNAQsgCCgCCCIBQf7///8H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CyAAIAsQDCAAIAgQDCAAIAoQDCAAIAEQDAwBCyABIQ0LIAVBIGokACANC0sBAn8gACABRwRAIAAoAhAiAgRAIAAoAgAiAygCACACQQAgAygCBBEBABoLIAAgASkCADcCACAAIAEoAhA2AhAgACABKQIINwIICwv0AQIDfgF/AkAgAykDACIEQoCAgIBwWgRAIAMpAwgiBUL/////b1YNAQsgABAiQoCAgIDgAA8LQoCAgIDgACEGIABCgICAgCBBLBBHIgFCgICAgHCDQoCAgIDgAFIEfiAAQRgQJCICRQRAIAAgARAMQoCAgIDgAA8LIASnIgMgAygCAEEBajYCACACIAQ3AwAgBaciByAHKAIAQQFqNgIAIAIgBTcDCCAAIAQQNSEAIAJBADoAESACIAA6ABAgAUKAgICAcFoEQCABpyIAIAI2AiAgACAALQAFQe8BcSADLQAFQRBxcjoABQsgAQVCgICAgOAACwsbACAAEBkgAEIANwIQIABCADcCCCAAQgA3AgALCQAgASACEPgFCxMAIABBEGogASACIAAoAggRAQALqAECAX8CfiAAvSIEQv///////////wCDQoGAgICAgID4/wBaBEAgAb1C////////////AINCgYCAgICAgPj/AFQPC0F/IQICQCAAIAFjDQAgAb0iA0L///////////8Ag0KAgICAgICA+P8AVg0AQQEhAiAAIAFkDQBBACECIABEAAAAAAAAAABiDQAgBEIAUwRAIANCP4enQX9zDwsgA0I/iKchAgsgAgvKBQIFfwN+IwBBMGsiAiQAIAIgATcDECACQQA2AgwgAiAANgIIIAIgAykDACIKNwMYAkACQCAKQoCAgIBwgyILQoCAgIAwUgRAQoCAgIDgACEJIAAgChBVDQELQoCAgIDgACEJIAAgARCKASIFQQBIDQACQCAFQQJJDQAgAaciAy8BBkEVayIEQf//A3FBC08NAiACIARBAnRB/P8PcSIEQZz1AWooAgA2AiBBASADLwEGQcqeAWotAAAiBnQhCCADKAIkIQcgC0KAgICAMFIEQCAAIAVBAnQQJCIERQ0CQQAhAwNAIAMgBUZFBEAgBCADQQJ0aiADNgIAIANBAWohAwwBCwsgAiAINgIoIAIgBzYCJCAEIAVBBEHLACACQQhqENcBAkACQAJAAkAgAigCDA4CAAEDCyAAIAUgBnQiAxAkIgYNAQsgACgCECIAQRBqIAQgACgCBBEAAAwECyAGIAcgAxAeIQZBACEDAkACQAJAAkACQCAIQQFrDggAAQkCCQkJAwkLA0AgAyAFRg0EIAMgB2ogBiAEIANBAnRqKAIAai0AADoAACADQQFqIQMMAAsACwNAIAMgBUYNAyAHIANBAXRqIAYgBCADQQJ0aigCAEEBdGovAQA7AQAgA0EBaiEDDAALAAsDQCADIAVGDQIgByADQQJ0IghqIAYgBCAIaigCAEECdGooAgA2AgAgA0EBaiEDDAALAAsDQCADIAVGDQEgByADQQN0aiAGIAQgA0ECdGooAgBBA3RqKQMANwMAIANBAWohAwwACwALIAAoAhAiA0EQaiAGIAMoAgQRAAALIAAoAhAiAEEQaiAEIAAoAgQRAAAMAQsgByAFIAggBEHI9QFqKAIAIAJBCGoQ1wEgAigCDA0BCyABQiCIp0F1TwRAIAGnIgAgACgCAEEBajYCAAsgASEJCyACQTBqJAAgCQ8LEAEAC+cCAQF+IAAgARCKASICQQBIBEBCgICAgOAADwsCQCACRQ0AAkACQAJAAkACQCABpyIALwEGQcqeAWotAAAOBAABAgMECyAAKAIkIgAgAmohAgNAIAAgAkEBayICTw0FIAAtAAAhAyAAIAItAAA6AAAgAiADOgAAIABBAWohAAwACwALIAAoAiQiACACQQF0aiECA0AgACACQQJrIgJPDQQgAC8BACEDIAAgAi8BADsBACACIAM7AQAgAEECaiEADAALAAsgACgCJCIAIAJBAnRqIQIDQCAAIAJBBGsiAk8NAyAAKAIAIQMgACACKAIANgIAIAIgAzYCACAAQQRqIQAMAAsACyAAKAIkIgAgAkEDdGohAgNAIAAgAkEIayICTw0CIAApAwAhBCAAIAIpAwA3AwAgAiAENwMAIABBCGohAAwACwALEAEACyABQiCIp0F1TwRAIAGnIgAgACgCAEEBajYCAAsgAQtRAgF/AX5CgICAgOAAIQQgACABIAIQayIDBH4gAygCICIDKAIMKAIgLQAEBEAgAkUEQEIADwsgABBfQoCAgIDgAA8LIAM1AhAFQoCAgIDgAAsLNwAgACABIAIQayIARQRAQoCAgIDgAA8LIAAoAiAoAgwiACAAKAIAQQFqNgIAIACtQoCAgIBwhAsMACAAKAIQIAEQ5wML2gEBAn4CQAJAIAJFBEAgAUKAgICAcIMhBSAAQS8QKSEEDAELAn4gAUKAgICAcIMiBUKAgICAMFIgAykDACIEQoCAgIBwg0KAgICAgH9SckUEQCAAQbmMASAAIAAoAhAgBKcQxgIQKUGrjAEQsgEMAQsgACAEECULIgRCgICAgHCDQoCAgIDgAFENAQsgBUKAgICAMFENACAAIAFBBRBeIgFCgICAgHCDQoCAgIDgAFIEQCAAIAEgBBC9ASAAIAFBMCAEpykCBEL/////B4NBABAVGgsgASEECyAEC0YBAX8CQCAAKAIIIAJqIgMgACgCDEoEQCAAIAMgARDEAg0BCwNAIAJBAEwEQEEADwsgAkEBayECIAAgARCHAUUNAAsLQX8LlQECBX8BfiABKQIEIginQf////8HcSIDRQRAIAIPCyAAKAIEQf////8HcSEHAn8gCEKAgICACINQRQRAIAEvARAMAQsgAS0AEAshBSADQQFrIQYgByADayEEAkADQCACIARKDQEgACAFIAIQoAEiA0EASCADIARKcg0BIAAgASADQQFqIgJBASAGELwDDQALIAMPC0F/C6cBAgN/AX4CQAJAIAAgARD2AyIDQQBIDQAgA0UNAUGbHiECIAAgACABQe4AIAFBABARIgVCgICAgHCDIgFCgICAgCBRIAFCgICAgDBRcgR/QZseBSABQoCAgIDgAFENASAAIAUQNCIBQoCAgIBwg0KAgICA4ABRDQFBACECIAGnQecAQQAQoAEhBCAAIAEQDCAEQQBODQJB2ssAC0EAEBILQX8hAgsgAguhAQIDfwF+AkACQCAAKQIEIgRCgICAgAiDUA0AIABBEGohAiAEp0H/////B3EhA0EAIQADQCAAIANODQECQCACIABBAXRqLwEAIgFBgPADcUGAsANHBEAgACEBDAELIAFB/7cDSw0DIABBAWoiASADTg0DIAIgAUEBdGovAQBBgEBrQf//A3FBgPgDSQ0DCyABQQFqIQAMAAsAC0F/IQALIAALVQEBfwJAAkACQCABQiCIp0EBag4DAAECAQsgAaciAi8BBkEGRw0AIAIpAyAiAUKAgICAcINCgICAgBBRDQELIABBlMAAQQAQEkKAgICA4AAhAQsgAQsQAEHOkQEgAEELEJICQQBHC4kBAgN/AX5BwZEBIQMCQAJAIAEpAgQiBqdB/////wdxIgUgAkwNACABQRBqIQQCfyAGQoCAgIAIg1BFBEAgBCACQQF0ai8BAAwBCyACIARqLQAAC0ElRw0AQcMbIQMgAkECaiAFTg0AIAEgAkEBakECEL0DIgJBAE4NAQsgACADEL4DQX8hAgsgAgtWAQF+IwBBEGsiAiQAIAAgAkEIaiADKQMAEEIEfkKAgICA4AAFIAIpAwhCgICAgICAgPj/AINCgICAgICAgPj/AFKtQoCAgIAQhAshBCACQRBqJAAgBAtWAQF+IwBBEGsiAiQAIAAgAkEIaiADKQMAEEIEfkKAgICA4AAFIAIpAwhC////////////AINCgICAgICAgPj/AFatQoCAgIAQhAshBCACQRBqJAAgBAvBAwIDfwR+IwBBMGsiCCQAIANCACADQgBVGyENIAVBAWshCiAGQoCAgIBwgyEOIAVBAEwhBUIAIQMDQAJAIAMgDVEEQCAEIQwMAQtCfyEMIAAgAiADIAhBKGoQVCIJQQBIDQACQCAJRQ0AIA5CgICAgDBSBEAgCCAIKQMoNwMAIAMhCyAIIAI3AxAgCCADQoCAgIAIWgR+QoCAgIDAfiADub0iC0KAgICAwIGA/P8AfSALQv///////////wCDQoCAgICAgID4/wBWGwUgCws3AwggCCAAIAYgB0EDIAgQHCILNwMoIAAgCCkDABAMIAAgCCkDCBAMIAtCgICAgHCDQoCAgIDgAFENAgsCQAJAAkAgBQ0AIAAgCCkDKCILEMwBIglBAEgNASAJRQ0AIAAgCEEgaiALEC9BAEgNASAAIAEgCyAIKQMgIAQgCkKAgICAMEKAgICAMBDUBCIEQgBTDQEgACALEAwMAwsgBEL/////////D1MNASAAQdXIAEEAEBIgCCkDKCELCyAAIAsQDAwCCyAAIAEgBCAIKQMoEGdBAEgNASAEQgF8IQQLIANCAXwhAwwBCwsgCEEwaiQAIAwLtQUCBH4GfyMAQTBrIggkACAIQgA3AhwgCCAANgIYIAggAykDACIENwMoQoCAgIAwIQYCQAJAAn8gBEKAgICAcINCgICAgDBSBEBBACECQQAgACAEEFUNARogCEEBNgIgC0EAIQICQCAAIAhBEGogACABECAiBhAvBEAMAQtCACEEA0AgCCkDECAFVQRAIAkgCk8EQCAAIAIgCiAKQQF2akEfakFwcSIKQRhsIAhBDGoQpwEiA0UNAyAIKAIMQRhuIApqIQogAyECC0EAIAAgBiAFIAIgCUEYbGoiCxBUIgNBAEgNAxoCQCADRQ0AIAs1AgRCIIZCgICAgDBRBEAgBEIBfCEEDAELIAsgBTcDECALQQA2AgggCUEBaiEJCyAFQgF8IQUMAQsLIAIgCUEYQcoAIAhBGGoQ1wFBACAIKAIcDQEaIAQgBEI/h0J/hYMhBCAJrSEBQgAhBQNAAkAgASAFUgRAIAIgBaciCkEYbGoiAygCCCILBEAgACALrUKAgICAkH+EEAwLIAMpAwAhByAFIAMpAxBRBEAgACAHEAwMAgsgACAGIAUgBxB7QQBODQEgCkEBagwECyAAKAIQIgNBEGogAiADKAIEEQAAA0AgASAEUQRAIAgpAxAhAQNAIAEgBFcNCCAAIAYgBBCFAiEMIARCAXwhBCAMQQBODQALDAYLIAAgBiABQoCAgIAwEHshDSABQgF8IQEgDUEATg0ACwwECyAEQgF8IQQgBUIBfCEFDAALAAtBAAshAyAJIAMgAyAJSRshCQNAIAMgCUcEQCAAIAIgA0EYbGoiCikDABAMIAooAggiCgRAIAAgCq1CgICAgJB/hBAMCyADQQFqIQMMAQsLIAAoAhAiA0EQaiACIAMoAgQRAAALIAAgBhAMQoCAgIDgACEGCyAIQTBqJAAgBgswACABQoCAgIAQhEKAgICAcINCgICAgDBRBEAgACABEDQPCyAAIAFBOUEAQQAQpwILmQIBAX4CQAJAAkAgAUKAgICAcIMiBEKAgICAMFIEQCAEQoCAgIAgUg0BIABBxMIAEGAhBAwCCyAAQYvpABBgIQQMAQsgACABECAiAUKAgICAcINCgICAgOAAUQ0BIAAgARDMASIDQQBIBEAgACABEAxCgICAgOAADwsCf0GTASADDQAaQZ0BIAAgARA1DQAaQZIBIAGnLwEGIgNBEktBASADdEH4jhBxRXINABogACgCECgCRCADQRhsaigCBAshAiAAIAFB0gEgAUEAEBEhBCAAIAEQDCAEQoCAgIBwgyIBQoCAgICQf1ENACABQoCAgIDgAFENASAAIAQQDCAAIAIQKSEECyAAQeeRASAEQa3wABCyASEBCyABC48EAQJ+IwBBIGsiAiQAIAMpAwAhBQJAAkACQCAEBEAgBUL/////b1gEQCAAECIMAwsgBaciBCAEKAIAQQFqNgIADAELIAAgBRAgIgUhASAFQoCAgIBwg0KAgICA4ABRDQILAkAgACADKQMIEDAiA0UNAEKAgICAMCEBAkACQCAFQoCAgIBwVA0AIAAgAiAFpyADEEMiBEEASA0CIARFDQAgABAzIgFCgICAgHCDQoCAgIDgAFENAQJAIAItAABBEHEEQCACKQMQIgZCIIinQXVPBEAgBqciBCAEKAIAQQFqNgIACyAAIAFBwgAgBkGHgAEQFUEASA0DIAIpAxgiBkIgiKdBdU8EQCAGpyIEIAQoAgBBAWo2AgALIAAgAUHDACAGQYeAARAVQQBODQEMAwsgAikDCCIGQiCIp0F1TwRAIAanIgQgBCgCAEEBajYCAAsgACABQcEAIAZBh4ABEBVBAEgNAiAAIAFBPyACNQIAQgGIQgGDQoCAgIAQhEGHgAEQFUEASA0CCyAAIAFBwAAgAjUCAEICiEIBg0KAgICAEIRBh4ABEBVBAEgNASAAIAFBPiACNQIAQgGDQoCAgIAQhEGHgAEQFUEASA0BIAAgAhBGCyAAIAMQECAAIAUQDAwDCyAAIAIQRiAAIAEQDAsgACADEBAgACAFEAwLQoCAgIDgACEBCyACQSBqJAAgAQtVAQF/IwBBIGsiBSQAAkAgACAFIAMQhAVBAEgEQEF/IQQMAQsgACABIAIgBSkDCCAFKQMQIAUpAxggBSgCACAEchBqIQQgACAFEEYLIAVBIGokACAEC4MCAgZ/AX4jAEEQayIEJAACQCABQv////9vWARAIAAQIkF/IQMMAQtBfyEDIAAgAhAgIglCgICAgHCDQoCAgIDgAFENACAAIARBDGogBEEIaiAJp0ETEH0hA0KAgICAMCECIAQoAgghBiAEKAIMIQcCQAJAIANBAEgNAANAIAUgBkYEQEEAIQMMAwsgACACEAwgACAJIAcgBUEDdGoiCCgCBCAJQQAQESICQoCAgIBwg0KAgICA4ABRDQFBfyEDIAVBAWohBSAAIAEgCCgCBCACQYCAARDZBEEATg0ACwwBC0F/IQMLIAAgByAGEFsgACAJEAwgACACEAwLIARBEGokACADC0gBAn8jAEEQayICJABBfyEDAkAgACACQQxqIAEQswENACACKAIMIgNBJWtBXEsNACAAQYSBAUEAEERBfyEDCyACQRBqJAAgAwt1AQF/AkAgAUKAgICAcINCgICAgOB+UQRADAELAkAgAUKAgICAcFQNACABpyICLwEGQSFHDQAgAikDICIBQoCAgIBwg0KAgICA4H5SDQAMAQsgAEGTGkEAEBJCgICAgOAADwsgAaciACAAKAIAQQFqNgIAIAELvwEBAX8gASADai0AAEE8RgRAIAAgBEH/AXEQDiAAIAVB//8DcRAmIANBAWohAwsgASACKAIEIgBBBWsiAmoiBi0AAEG2AUYEQCAAIAFqLQAAQRZGBEAgBkEROgAAIABBBGshAgsgAEECaiEAIAEgAmoiBiAFOwABIAYgBEEBajoAACACQQNqIQIDQCAAIAJMRQRAIAEgAmpBswE6AAAgAkEBaiECDAELCyADDwtBvMMAQajsAEGz6gFBiM0AEAAAC84CAgd/AX4jAEEwayICJAACQAJAIAMpAwAiAUL/////b1gEQCABQiCIp0F1SQ0BIAGnIgAgACgCAEEBajYCAAwBC0KAgICA4AAhDCAAIAEQigQiA0EASA0BIANFBEAgAEHt0ABBABASDAILIAAgAkEsaiACQShqIAGnIgZBAxB9DQEgAigCLCEHIAIoAighCEEAIQMCQANAIAMgCEcEQCAHIANBA3RqKAIEIQlBgIIBIQUCQCAERQ0AIAAgAkEIaiIKIAYgCRBDIgtBAEgNAyALRQ0AIAIoAgghBSAAIAoQRkGAhgFBgIIBIAVBAnEbIQULIAAgASAJQoCAgIAwQoCAgIAwQoCAgIAwIAUQakEASA0CIANBAWohAwwBCwsgACAHIAgQWyAGIAYoAgBBAWo2AgAMAQsgACAHIAgQWwwBCyABIQwLIAJBMGokACAMC0IBAX8CQCAAIAFqIgAtAAFBPUcNAEEBIQICQAJAIAAtAAAiAEEWaw4EAgEBAgALIABBswFGDQELIABBHUYhAgsgAguzAQEBf0F/IQMCQCABKAJMRQ0AAkACQAJAAkAgAkHyAGsOAwIBAAMLIAEoArQBIgNBAE4NAyABIAAgAUH0ABBMIgA2ArQBIAAPCyABKAKwASIDQQBODQIgASAAIAFB8wAQTCIANgKwASAADwsgASgCrAEiA0EATg0BIAEgACABQfIAEEwiADYCrAEgAA8LIAJBCEcNACABKAKoASIDQQBODQAgASAAIAEQ0wMiAzYCqAELIAMLSwEBfyAAIAEoAgA2AkAgAEEpEA0gACAAKAJAKAIENgJAIABCgICAgCAQxwMhAiABKAIAIAI2AgggAEEDEA0gACACEDggAEHQABANC8gBAgN/AX4jAEEQayIDJAAgACABECkiBkKAgICAcINCgICAgOAAUgRAAkACQCAAIANBDGogBhDfASIBRQ0AIAAgAhA9IgQgAygCDGpBAWoQJCIFRQ0AIAUgASADKAIMEB4iBSADKAIMaiACIAQQHhogBSADKAIMaiAEakEAOgAAIAAgBSADKAIMIARqEJ0DIQQgACgCECICQRBqIAUgAigCBBEAACAAIAEQMQwBCyAAIAEQMUEAIQQLIAAgBhAMCyADQRBqJAAgBAunAQEFfyMAQRBrIgMkACABpyIEKAIQIgJBMGohBSACIAIoAhhBf3NBAnRBvH5yaigCACECAkACQANAIAJFDQEgBSACQQN0aiIGQQhrIQIgBkEEaygCAEEwRwRAIAIoAgBB////H3EhAgwBCwsgAyACNgIMIAJFDQAgACAEIANBDGogAigCAEEadkE8cRCNAw0BCyAEIAQtAAVB/gFxOgAFCyADQRBqJAALsAUCCX8DfiMAQTBrIgQkACAAKAIAIQVCgICAgDAhDkKAgICAMCENAkAgAQRAQX8hAyAFEDsiDUKAgICAcINCgICAgOAAUQ0BIAAgDUEAEMABIQkgBSANEAwgCQ0BIAUQOyIOQoCAgIBwg0KAgICA4ABRDQEgBSANQfEAIA5BgIABEBVBAEgNAQsgAEEQaiEGQQAhAwJAAkADQCAGKAIAQYJ/RgRAIAAoAhghCiAEIAYpAxg3AyggBCAGKQMQNwMgIAQgBikDCDcDGCAEIAYpAwA3AxAgCkEBaiEHIAApAyAhDAJAAkACQCABBEAgDEIgiKdBdU8EQCAMpyIIIAgoAgBBAWo2AgALIAUgDiADIAxBhIABEJMBQQBIDQIgBSANIAMCfiAAQeAAQQAgByAEQRBqIARBDGoQ/wJFBEAgBCkDIAwBCyAEQoCAgIAwNwMgQoCAgIAwC0GEgAEQkwFBAEgNAiAAKAIoQeAARw0BIAUgDhDjBCAFIA0Q4wQgAiADQQFqNgIADAcLIAUgDBAMIABCgICAgDA3AyAgAEHgAEEBIAcgBEEQaiAEQQxqEP8CDQECQCAEKQMgIgynKAIEQf////8HcUEBIAMbBEAgACAMQQEQwAEhCyAAKAIAIAwQDCALDQMgA0UEQCAAKAIoQeAARg0JIABBwgAQDSAAQd0AEBcLIANBAWohAwwBCyAAKAIAIAwQDAsgACgCKEHgAEYNBQsgABAPDQAgABCLAQ0AIAYoAgBB/QBHBEAgAEHsPUEAEBMMAQsgACAGEIECIABBADYCMCAAIAAoAhQ2AgQgACAAKAI4EM0DRQ0BC0F/IQMMBQsgA0EBaiEDDAELCyAAQYJ/ECghAwwCCyAAQSQQDSAAIANBAWtB//8DcRAUCyAAEA8hAwsgBEEwaiQAIAMLbwEBfyAAQSYQDSAAQQAQFCAAQQEQDSAAQQAQOCAAIAAQLSICEBogAEGCARANIAAgAUECakH/AXEQWCAAQesAQX8QGCEBIABB0QAQDSAAQZABEA0gAEHsACACEBgaIAAgARAaIABBDhANIABBDhANC50BAQd/IAAoAkAiBCgCiAEiA0EAIANBAEobIQMCQANAAkAgAiADRgRAQQAhAyAEKAJ8IgJBACACQQBKGyEFQQAhAgNAIAIgBUYNBCACQQR0IQcgAkEBaiECIAcgBCgCdGooAgAgAUcNAAsMAQsgAkEEdCEIIAJBAWohAiAIIAQoAoABaigCACABRw0BCwsgAEG2E0EAEBNBfyEDCyADC4oFAgh/AX4jAEFAaiIBJAAgACgCOCECQX8hCAJAIAAoAgAgAUEoakEgED4NAAJAIAAoAgAgAUEQakEBED4NACACQQFqIQNBACECAkADQCADIgUgACgCPE8NASACIQZBASECIANBAWohAwJAAkACQAJAAkACQAJAAkAgBS0AACIEQdsAaw4DBgMBAAsgBEEvRwRAIARBCmsOBAcCAgcCC0EvIQQgBg0FA0AgASADQQFqNgIMAkAgAywAACICQQBOBEAgAkH/AXEhAgwBCyADQQYgAUEMahBRIgJBgIDEAE8NBgsgAhDJAQRAIAFBEGogAhCxAQ0LIAEoAgwhAwwBCwsgAEGEfzYCECAAIAFBKGoQNzcDICABQRBqEDchCSAAIAM2AjggACAJNwMoQQAhCAwKC0HdACEEQQAhAgwECyAEwEEATg0BIAVBBiABQQhqEFEiBEGAgMQATw0CIARB/v//AHFBqMAARg0EIAEoAgghAwwBCyABQShqQdwAEDwNBiAFQQJqIQcCQCAFLQABIgQEQCAEQQprDgQFAQEFAQtBACEEIAYhAiAHIgMgACgCPE8NBgwDCyAEwEEATgRAIAYhAiAHIQMMAwtBB0EGQQAgA0EGIAFBDGoQUSIEQf7//wBxQajAAEYbIARB///DAEsiAhsiA0UEQCAHIAEoAgwgAhshAwwBCyADQQZrDgIDAQcLIAYhAgwBCyAAQbLfAEEAEBMMBAsgAUEoaiAEELEBRQ0BDAMLCyAAQa02QQAQEwwBCyAAQdI2QQAQEwsgASgCKCgCECIAQRBqIAEoAiwgACgCBBEAACABKAIQKAIQIgBBEGogASgCFCAAKAIEEQAACyABQUBrJAAgCAszAQF/A0ACQCABQQBOBH8gASACRw0BQQEFQQALDwsgACgCzAEgAUEDdGooAgAhAQwACwALQwECfyAAKAKIASECQX8hAwJAA0AgAkEATA0BIAAoAoABIAJBAWsiAkEEdGooAgAgAUcNAAsgAkGAgICAAnIhAwsgAwuDAwEGfyABKAI4IQMCQAJAAkAgAS0AbkEBcQRAIANFBEBB7TAhAyABKAJADQMLQYDdACEDIAJBO0YgAkHOAEZyDQJBACECIAEoAogBIgNBACADQQBKGyEEA0AgAiAERg0CQdvcACEDIAEoAoABIAJBBHRqKAIAIgZBO0YgBkHOAEZyDQMgAkEBaiECDAALAAsgA0UNACABLwFsIgJBggxGDQAgAkEIdkEDaw4EAAICAAILQQAhBCABKAKIASICQQAgAkEAShshCEEAIQMDQCADIAhGDQJBACECAkAgASgCgAEiBSADQQR0aigCACIGRQ0AA0ACQCACIANGBEBBACECIAEoAnwiBUEAIAVBAEobIQUDQCACIAVGDQQgBiABKAJ0IAJBBHRqIgcoAgBGBEAgBygCBEUNAwsgAkEBaiECDAALAAsgAkEEdCEHIAJBAWohAiAFIAdqKAIAIAZHDQELC0GBEyEDDAILIANBAWohAwwACwALIAAgA0EAEBNBfyEECyAEC2EBAX8gAEG4ARANIABB9wAQFyAAIAAoAkAvAbwBEBQgAEEREA0gAEHqAEF/EBghASAAQbgBEA0gAEEIEBcgAEEAEBQgAEEbEA0gAEEkEA0gAEEAEBQgACABEBogAEEOEA0LUQECf0F/IQJBASEDA0ACQCAAIAEQrQENACADRQRAIAAoAkBBfzYCmAILIAAoAhBBLEcEQEEAIQIMAQsgABAPDQAgAEEOEA1BACEDDAELCyACC5sdAgR+BX8CfwJAIABBEGoiB0H4ASAAKAIAEQMAIgVFDQAgBUEFakEAQfMBECwaIAVBBToABCAFQQE2AgAgACgCUCIIIAVBCGoiCTYCBCAFIABB0ABqNgIMIAUgCDYCCCAAIAk2AlAgBSAHIAAoAkBBA3QgACgCABEDACIINgIoIAhFBEAgByAFIAAoAgQRAAAMAQsgBSAANgIQIAAoAkgiByAFQRRqIgk2AgQgBSAAQcgAajYCGCAFIAc2AhQgACAJNgJIIAUgAEHkAWo2AtgBIAAoAkAiAEEAIABBAEobIQADQCAAIAZHBEAgCCAGQQN0akKAgICAIDcDACAGQQFqIQYMAQsLIAVCgICAgCA3A1AgBUKAgICAIDcDSCAFQoCAgIAgNwNAIAUgBUHgAWoiADYC5AEgBSAANgLgASAFQoCAgIAgEEEhASAFKAIoIAE3AwhBACEGIAUgBUEQQeyWAUEAQQBBACABEPwBIgE3AzAgAUIgiKdBdU8EQCABpyIAIAAoAgBBAWo2AgALIAUoAiggATcDaCAFEDMhASAFKAIoIAE3AxggBSABQZDKAUEDEB8gBUHYAGohBwNAIAUoAighACAGQQhHBEAgBkECdEHAnQFqKAIAIQggBSAFIAApAxgQQSIBQTcgBSAIEPsEQQMQFRogBSABQTMgBUEvEClBAxAVGiAHIAZBA3RqIAE3AwAgBkEBaiEGDAELCyAFIAApAwhBAhBHIQEgBSgCKCABNwMQIAUgBSABp0EAIAFC/////29WG0EBEPIENgIkIAUgBUEkakEAQTBBChDuBBogBQwBC0EACyIFBEBBACEGIwBBgAFrIgckACAFIgAgAEESQQBBABDnAjcDsAEgAEETQQBBABDnAiEBIAAgACkDMEHQAEKAgICAMCABIAApA7ABQYEyEGoaIAAgACkDMEHOAEKAgICAMCABIAApA7ABQYEyEGoaIAAgARAMIAAgACABIAAgAEGwAWpBARDeBBAMIAAgABAzNwPAASAAIABCgICAgCAQQTcDyAEgACAAQbofQRRBASAAKAIoKQMIEKwBQcDKAUEYEB8gACAAKAIoKQMIQcDNAUELEB8gACAAKQMwQfDOAUEHEB8gACAAQRVB1DpBAUEFQQAQggEiATcDOCABQiCIp0F1TwRAIAGnIgggCCgCAEEBajYCAAsgACABQdQ6IAApAzAQvwEgACAAQRZBty5BAUEFQX8QggEiAUG3LiAAKAIoKQMYEL8BIABB2ABqIQgDQCAGQQhHBEAgACAAQRYgBkECdEHAnQFqKAIAIglBAkEBIAZBB0YbQQUgBiABEPwBIAkgCCAGQQN0aikDABC/ASAGQQFqIQYMAQsLIAAgABAzIgE3A5gBIAAgAUHgzwFBARAfIAAgACgCKCkDEEHwzwFBJxAfIABBsw5BF0EBIAAoAigpAxAQrAEiAUIgiKdBdU8EQCABpyIGIAYoAgBBAWo2AgALIAAgATcDQCAAIAFB4NQBQQQQHyAHQeCdAUH/ABAeIgchBkHjACEIIABCgICAgCAQQSEBA0AgCARAIAAgASAGQoGAgIAQQQcQvgEaIAYQPSAGakEBaiIGLQAAIQgMAQsLIAAgACgCKCkDEEHWASABQQEQFRogACAAIAAoAigpAxAiAUHsACABQQAQETcDqAEgACAAKQOYARBBIQEgACgCKCABNwPAAiAAIAFBoNUBQQIQHyAAIAApA8ABQcDVAUEPEB8gACAAKAIoKQMIQQQQRyEBIAAoAiggATcDICAAIAFCABC9ASAAIAAoAigpAyBBgNgBQQYQHyAAIABBvDVBGEEBIAAoAigpAyAQrAFB4NgBQQ4QHyAAIAAoAigpAwhBBhBHIQEgACgCKCABNwMwIAAgAUKAgICAEBC9ASAAIAAoAigpAzBBwNoBQQIQHyAAQaLAAEEZQQEgACgCKCkDMBCsARogACAAKAIoKQMIQQUQRyEBIAAoAiggATcDKCAAIAEgAEEvECkQvQEgACAAQfTKAEEaQQEgACgCKCkDKBCsAUHg2gFBAxAfIAAgACgCKCkDKEGQ2wFBNBAfIAAgACkDmAEQQSEBIAAoAiggATcDyAIgACABQcDiAUECEB8gBxCNBiAAQgEgBzQCCCAHKQMAQsCEPX58IgEgAUIBWBs3A9ABIAAgACkDwAFB4OIBQQEQHyAAIAApA8ABQbDoAUEBEB8gABAzIQEgACgCKCABNwM4IAAgAUGg6gFBBRAfIAAgAEGewQBBG0EAIAAoAigpAzgQrAEiAUHw6gFBAhAfQcsBIQYDQCAGQdgBRwRAIAAgASAAIAcgBhCBASIIQS4QnwMiCUEBaiAIIAkbIAAgBhBSQQAQvgEaIAZBAWohBgwBCwsgACAAKQOYARBBIQEgACgCKCABNwPYAiAAIAFBkOsBQQQQHyAAIAApAzAQQSEBIAAoAiggATcDgAEgAEEVQag6QQFBBUEBEIIBIQEgACAAKAIoKQOAAUHQ6wFBARAfIAAgACgCKCIGKQOAASAGKQPYAkEBQQEQ9AEgACABIAAoAigpA4ABQQBBARD0ASAAIAEQDCAAIABBHEHUwwBBARDnAiIBNwO4ASAAKQPAASECIAFCIIinQXVPBEAgAaciBiAGKAIAQQFqNgIACyAAIAJBOyABQQMQFRogACkDwAEiAUIgiKdBdU8EQCABpyIGIAYoAgBBAWo2AgALIAAgAUGMASABQQMQFRogB0GAAWokACAAEDMhASAAKAIoIAE3A1AgACABQZDCAUEvEB8gACAAQdrQAEEdQQcgACgCKCkDUBCsAUGAyQFBAxAfIABBETYC7AEgACAAKAIoKQMoQaC3AUEBEB8gAEEeNgLoASAAEDMhASAAKAIoIAE3A5ABIAAgAUGwtwFBEhAfIABB6zZBH0ECIAAoAigpA5ABEKwBIgFCIIinQXVPBEAgAaciBiAGKAIAQQFqNgIACyAAIAE3A0ggACABQdC5AUEBEB8gACAAKQOYARBBIQEgACgCKCABNwPQAiAAIAFB4LkBQQIQHyAAIAApA8ABQYC6AUEBEB8CQCAAKAIQIgYoAkBBLU8EQCAGKAJEKAKgCA0BCyAGQYicAUEsQQEQgQQaIAYoAkQiBkEgNgKwCCAGQZScATYCtAgLIABBIUGtCUECQQJBABCCASIBQoCAgIBwWgRAIAGnIgYgBi0ABUEQcjoABQsgACABQcC6AUEBEB8gACAAKQPAAUGtCSABQQMQvgEaQQAhBiMAQUBqIgckAANAAkAgBkEERgRAQQAhBgNAIAZBAkYNAiAAIAApA5gBEEEhASAAKAIoIAZBA3RqIAE3A7ACIAAgASAGQQJ0QcCcAWooAgAgBkHMnAFqLQAAEB8gBkEBaiEGDAALAAsgACAHIAZBsAFyEIEBIQggABAzIQEgBkEiakEDdCIJIAAoAihqIAE3AwAgACABIAZBAnRBsJwBaigCACAGQcicAWotAAAQHyAAQSIgCEEAQQMgBhCCASEBIAZBAU0EQCAAIAFBkL8BQQIQHwsgACABIAggACgCKCAJaikDABC/ASAGQQFqIQYMAQsLIAdBQGskACMAQUBqIgckACAAEDMhASAAKAIoIAE3A5gBIAAgAUHg6wFBAxAfIAAgAEHfNEEjIAAoAigpA5gBELIDQZDsAUECEB8gABAzIQEgACgCKCABNwOgASAAIAFBsOwBQQMQHyAAIABBuDRBJCAAKAIoKQOgARCyA0Hg7AFBARAfIAAgABAzIgFB8OwBQSQQHyAAIAFBOCAAIAAoAigpAxAiAkE4IAJBABARQQMQFRogACAAQSVBrg5BABDnAiICQbDxAUEDEB8gACACIAEQrARBFSEGA0AgBkEgRwRAIAAgARBBIQMgBkEDdCIIIAAoAihqIAM3AwAgACADQf/xAEEBIAZByp4Bai0AAHStIgNBABC+ARogACAAQSYgACAHIAZBjgFqEIEBIglBA0EDIAYgAhD8ASIEIAkgACgCKCAIaikDABC/ASAAIARB//EAIANBABC+ARogBkEBaiEGDAELCyAAIAEQDCAAIAIQDCAAEDMhASAAKAIoIAE3A4ACIAAgAUHg8QFBGBAfIABBuBFBJyAAKAIoKQOAAhCyAxogB0FAayQAAkAgACgCECIAKAJAQS5PBEAgACgCRCgCuAgNAQsgAEHQnAFBLUEJEIEEGiAAKAJEIgBBKDYC8AkgAEEpNgLACSAAQSk2AqgJIABBKjYCkAkgAEErNgL4CCAAQSs2AuAICyAFEDMhASAFKAIoIAE3A+gCIAUgAUGwvwFBBBAfIAVBLEHO0QBBAUECQQAQggEiAUIgiKdBdU8EQCABpyIAIAAoAgBBAWo2AgALIAUgATcDUCAFIAFB8L8BQQgQHyAFIAFBztEAIAUoAigpA+gCEL8BIAUgBSkDMBBBIQEgBSgCKCABNwOAAyAFQRVBzzpBAUEFQQIgBSkDOBD8ASEBIAUgBSgCKCkDgANB8MABQQEQHyAFIAEgBSgCKCkDgANBAEEBEPQBIAUgARAMIAUgBRAzIgE3A6ABIAUgAUGAwQFBARAfIAUgBSkDoAEQQSEBIAUoAiggATcDmAMgBSABQZDBAUEDEB8gBSAFKQOgARBBIQEgBSgCKCABNwOoAyAFIAFBwMEBQQQQHyAFIAUpAzAQQSEBIAUoAiggATcDoAMgBUEVQaM6QQFBBUEDIAUpAzgQ/AEhASAFIAUoAigpA6ADQYDCAUEBEB8gBSAFKAIoIgApA6ADIAApA6gDQQFBARD0ASAFIAEgBSgCKCkDoANBAEEBEPQBIAUgARAMIAUoAhAiAEEtNgKwAiAAQS42AqwCIABBLzYCqAIgAEEwNgKkAiAAQTE2AqACIAUQMyEBIAUoAiggATcDiAIgBSABQcDJAUEDEB8gBSAFQZsbQTJBASAFKAIoKQOIAhCsAUHwyQFBAhAfCyAFC5YCAQR/IAAoAhAhBiABKAIAIgUtABAEfyAGIAUQgwQgBSgCFCADakGBgNzxeWwgBGpBgYDc8XlsBUEACyEHAn8gBSgCICIIIAUoAhxOBEAgACABIAIgCEEBahDWBQRAQX8gBS0AEEUNAhogBiAFEIwDQX8PCyABKAIAIQULIAUtABAEQCAFIAc2AhQgBiAFEIwDCyAFIAUoAiAiAUEBajYCICAFIAFBA3RqIgEgACADEBYiADYCNCABIAEoAjBB////H3EgBEEadHI2AjAgBSAFLQARIABBH3ZyOgARIAEgASgCMEGAgIBgcSAFIAAgBSgCGHFBf3NBAnRqIgAoAgBB////H3FyNgIwIAAgBSgCIDYCAEEACwvoAQEDfwJAAkAgACgCICICQSVJDQAgAkEtTQRAIAAoAkAiAS0AbkEBcQ0BIAJBLUcNAiABLwFsIgNBAXENASADQYD+A3FBgAZHDQIgASgCZA0CIAEoAgQiAUUNAiABLQBsQQFxDQEMAgsgAkEuRw0BIAAoAkQNACAAKAJAIgEvAWwiA0ECcQ0AAkAgA0EIdkEDaw4FAAICAgECCyABKAJkDQEgASgCBCIBRQ0BIAEvAWwiAUECcQ0AIAFBgP4DcUGADkcNAQsgAAJ/IAAoAiQEQCAAQQE2AihBg38MAQsgAkHWAGsLNgIQCwvkAgEFfyMAQaABayIFJAAgASgCACEHIAVBgAE2AgggBSAFQRBqNgIMIAQEfyAFQSM6ABBBAQVBAAshBAJ/AkADQCAFIAc2ApwBAn8gA0H/AEwEQCAFKAIMIgYgBGogAzoAACAEQQFqDAELIAUoAgwiBiAEaiADEN0CIARqCyEEIAUgBSgCnAEiAyIIQQFqNgKcAQJAIAMtAAAiA0HcAEYEQEHcACEDIAgtAAFB9QBHDQEgBUGcAWpBARCXAiEDIAJBATYCAAwBCyADwEEATg0AIAdBBiAFQZwBahBRIQMLIAMQyQFFDQEgBSgCnAEhByAEIAUoAghBBmtJDQAgACgCACAFQQxqIAVBCGogBUEQahCvBUUNAAsgBSgCDCEGQQAMAQsgACgCACAGIAQQnQMLIQkgBUEQaiAGRwRAIAAoAgAoAhAiAEEQaiAGIAAoAgQRAAALIAEgBzYCACAFQaABaiQAIAkLRQAgACgCzAEgAUEDdGpBBGohAQNAIAEoAgAiAUEASEUEQCAAKAJ0IAFBBHRqIgEgASgCDEEEcjYCDCABQQhqIQEMAQsLC6kDAQx/AkAgACgCECIEKALcAUEBdEECaiAEKALYAUwNACAEQRBqIglBBCAEKALUASIDQQFqIgh0IgUgBCgCABEDACIHRQ0AQQEgCHQhCiAHQQAgBRAsIQcgBCgC2AEiBUEAIAVBAEobIQtBHyADayEMA0AgBCgC4AEhAyAGIAtGRQRAIAMgBkECdGooAgAhAwNAIAMEQCADKAIoIQ4gAyAHIAMoAhQgDHZBAnRqIg0oAgA2AiggDSADNgIAIA4hAwwBCwsgBkEBaiEGDAELCyAJIAMgBCgCBBEAACAEIAc2AuABIAQgCjYC2AEgBCAINgLUAQsgACACQQN0QUBrECQiA0UEQEEADwsgA0ECOgAUIANBATYCECAEKAJQIgUgA0EYaiIGNgIEIAMgBEHQAGo2AhwgAyAFNgIYIAQgBjYCUCABBEAgASABKAIAQQFqNgIACyADQgA3AgAgAyABNgI8IANCADcCMCADIAI2AiwgA0EDNgIoIANBATsBICADQgA3AgggAyABQYGA3PF5bEH//6OOBms2AiQgACgCECADQRBqIgAQjAMgAAsNACAAIAFB6/8AEOIEC+8CAQZ/QQEhCSADIQcCQANAIAcoAswBIAVBA3RqQQRqIQYCQAJAA0AgBigCACIFQQBIDQEgBygCdCIIIAVBBHQiCmoiC0EIaiEGIAsoAgAgBEcNAAsgCCAKaigCDEEEdkEPcSEIQQEhBiAJBEBBACEGDAILIAAgAyAHQQAgBSAEQQFBAUEAEJ8BIgVBAE4NAQwDCyAHKAIEIgZFBEACQCAHKAIgRQ0AQQAhBSAHKALAAiIGQQAgBkEAShshBgNAIAUgBkYNASAEIAcoAsgCIAVBA3RqIggoAgRGBEAgCC0AACIJQQR2IQggAyAHRgRAQQEhBgwFC0EBIQYgACADIAdBACAJQQF2QQFxIAUgBCAJQQJ2QQFxIAlBA3ZBAXEgCBD7ASIFQQBIDQYMBAUgBUEBaiEFDAELAAsACyAAIARBn48BEIEDDAMLIAcoAgwhBUEAIQkgBiEHDAELCyABIAY2AgAgAiAINgIAIAUPC0F/C4gYAQh/IwBBEGsiDCQAIAxBfzYCDCACQQhGIgkgAkHyAGtBA0kiC3IhDSABKALMASADQQN0akEEaiEDAkACQAJAAkACQAJAA0AgAygCACIDQQBOBEAgAiABKAJ0IANBBHRqIgooAgAiDkYEQCAEQX1xQbkBRwRAIAMhCQwECyADIQkgCi0ADEEBcUUNAyAFQTAQDiAFIAAgAhAWEBsgBUEAEA4MBwsgCSAOQdUARyALcnJFBEAgBUHYABAOIAUgA0H//wNxECYgACABIAIgBCAFIAxBDGpBARDYAQsgCkEIaiEDDAELC0F/IQkgA0F+RwRAIAEgAhD3ASEJCyANRSAJQQBOckUEQCAAIAEgAhDgBCEJCwJAIAJBzgBHIAlBAE5yRQRAIAEoAkhFDQEgACABEPACIQkLIAlBAE4NAQsCQCABKAIsBEAgASgCcCACRg0BCyADQX5HDQMMBAsgACABIAIQ7wIiCUEASA0BCwJAAkACQAJAIARBtwFrDggCAgADAAECAgcLAkAgCUGAgICAAnEiAw0AIAEoAnQgCUEEdGotAAxBAXFFDQAgBUEwEA4gBSAAIAIQFhAbIAVBABAODAcLAkAgBEG5AWsOAwIDAAcLAkAgAw0AIAEoAnQgCUEEdGooAgxB8AFxQcAARw0AIAVBCxAOIAVB2AAQDiAFIAlB//8DcRAmIAVBzAAQDiAFIAAgAhAWIgIQGyAFQQQQDiAFIAAgAhAWEBsMBwsCQCAMKAIMQX9HDQAgBiAHKAIEEN8ERQ0AIAUgBiAHIAgCfyADBEAgCUGAgICAAmshCUHbAAwBC0HiAEHYACABKAJ0IAlBBHRqLQAMQQJxGwsgCRDdBCEIDAcLIAMEQCAFQfsAEA4gBSAAIAIQFhAbIAUgCUH//wNxECYMBwsgBUH6ABAOIAUgACACEBYQGyAFIAlB//8DcRAmDAYLIAVBBhAOCyAJQYCAgIACcQRAIAVB3ABB3ABB2wAgBEG9AUYbIARBuQFGGxAOIAUgCUH//wNxECYMBQsgBQJ/AkACQCAEQbkBaw4FAAEBAQABC0HZACABKAJ0IAlBBHRqLQAMQQJxRQ0BGkHjAEHkAEHZACACQQhGGyAEQb0BRxsMAQtB2AAgASgCdCAJQQR0ai0ADEECcUUNABpB5QBB4gAgBEG+AUYbCxAOIAUgCUH//wNxECYMBAsgBUEJEA4MAwsgA0F+Rg0BCyABKAKQAUEASCACQfIAa0EDSXIgAkEIRnINACAFQdgAEA4gBSABLwGQARAmIAAgASACIAQgBSAMQQxqQQAQ2AELIAEoApQBQQBIIAJB8gBrQQNJciACQQhGckUEQCAFQdgAEA4gBSABLwGUARAmIAAgASACIAQgBSAMQQxqQQAQ2AELIAJB8gBrQQNJIQsgAkEIRiEOIAJBzgBHIQ8gASEKAkACQAJAAkADQCAKIgMoAgQiCkUEQCADIQoMAgsgCigCzAEgAygCDEEDdGpBBGohAwNAIAMoAgAiA0EATgRAIAIgCigCdCADQQR0aiINKAIAIhBGBEAgBEF9cUG5AUcEQCADIQkMBgsgAyEJIA0tAAxBAXFFDQUgBUEwEA4gBSAAIAIQFhAbIAVBABAODAgFAkAgDiAQQdUARyALcnINACANIA0oAgxBBHI2AgwgACABIApBACADQdUAQQBBAEEAEJ8BIgNBAEgNACAFQd4AEA4gBSADQf//A3EQJiAAIAEgAiAEIAUgDEEMakEBENgBCyANQQhqIQMMAgsACwsgCUEATg0CIANBfkYiA0UEQCAKIAIQ9wEiCUEATg0DCyALRSACQQhHcUUEQCAAIAogAhDgBCIJQQBODQMLAkACQCAPDQAgCigCSEUNACAAIAoQ8AIhCQwBCwJAIAooAixFDQAgCigCcCACRw0AIAAgCiACEO8CIQkMAQsCQCADDQAgDiAKKAKQASIDQQBIIAtycg0AIAooAnQgA0EEdGoiAyADKAIMQQRyNgIMIAAgASAKQQAgCigCkAEgAygCAEEAQQBBABCfASEDIAVB3gAQDiAFIANB//8DcRAmIAAgASACIAQgBSAMQQxqQQAQ2AELIA4gCigClAEiA0EASCALcnJFBEAgCigCdCADQQR0aiIDIAMoAgxBBHI2AgwgACABIApBACAKKAKUASADKAIAQQBBAEEAEJ8BIQMgBUHeABAOIAUgA0H//wNxECYgACABIAIgBCAFIAxBDGpBABDYAQsgCigCIEUNAQwCCwsgCUEATg0BCyAKKAIgRQ0CIAJB8gBrQQNJIQ4gAkEIRiEQQQAhAwNAAkACQCAKKALAAiADSgRAIAIgCigCyAIgA0EDdGoiDygCBCINRgRAIAEgCkYNBiAAIAEgCkEAIA8tAAAiCUEBdkEBcSADIAIgCUECdkEBcSAJQQN2QQFxIAlBBHYQ+wEhAwwGCyANQdMAa0ECTwRAIA1B1QBHIA5yDQMMAgsgDkUNAQwCCyAJQQBIDQUMAwsgEA0AIAMhCyABIApHBEAgACABIApBACAPLQAAQQF2QQFxIAMgDUEAQQBBABD7ASELCyAFQd4AEA4gBSALQf//A3EQJiAAIAEgAiAEIAUgDEEMaiANQdUARhDYAQsgA0EBaiEDDAALAAsCfyAJQYCAgIACcQRAIAooAoABIAlBgICAgAJrIgNBBHRqIgkgCSgCDEEEcjYCDCAAIAEgCkEBIAMgAkEAQQBBABCfAQwBCyAJQQR0IgMgCigCdGoiCyALKAIMQQRyNgIMIAAgASAKQQAgCSACIAooAnQgA2ooAgwiA0EBcSADQQF2QQFxIANBBHZBD3EQnwELIgNBAEgNAQsCQCAFAn8CQAJAAkACQAJAIARBtwFrDgcBAQAGAAMBCAsgASgCyAIgA0EDdGotAAAiCUEEcQRAIAVBMBAOIAUgACACEBYQGyAFQQAQDgwIC0EAIQoCQCAEQbkBaw4DAgYACAsgCUHwAXFBwABGBEAgBUELEA4gBUHeABAOIAUgA0H//wNxECYgBUHMABAOIAUgACACEBYiAhAbIAVBBBAOIAUgACACEBYQGwwICwJAIAwoAgxBf0cNACAGIAcoAgQQ3wRFDQAgBSAGIAcgCEHmAEHeACAJQQhxGyADEN0EIQgMCAsgBUH8ABAOIAUgACACEBYQGyAFIANB//8DcRAmDAcLIARBvQFGIQogBEG5AWsOBQACAgIAAgtB3wAgASgCyAIgA0EDdGotAABBCHFFDQIaQegAQd8AIAJBCEYbQecAIAobDAILIAVBBhAOC0HmAEHeACABKALIAiADQQN0ai0AAEEIcRsLEA4gBSADQf//A3EQJgwCCyAFQQkQDgwBCwJAAkACQAJAAkAgBEG3AWsOBwICAgQAAQMFCwJAIAwoAgxBf0cNACAGIAcoAgRqIgMtAAFBPUcNAAJAAkAgAy0AACIDQRlrDgUBAgICAQALIANBswFGDQAgA0EWRw0BCyABLQBuQQFxIgkEQCAFQTYQDiAFIAAgAhAWEBsLIAYgCGotAABBPEYEQCAFQTgQDiAFIAAgAhAWEBsgCEEBaiEICyAGIAcoAgQiB0EFayIDaiILLQAAQbYBRw0GIAYgB2otAAAhBAJAAkAgCQRAQTshCgJAAkACQAJAIARBGWsOBQIBAQEDAAtBFSEJIARBFkYNBCAEQbMBRg0FCxABAAtBGCEJDAILQRshCQwBC0E5IQpBESEJIARBFkcNAQsgCyAJOgAAIAdBBGshAwsgB0ECaiEEIAMgBmoiByAKOgAAIAcgACACEBY2AAEgA0EFaiEDA0AgAyAETg0GIAMgBmpBswE6AAAgA0EBaiEDDAALAAsgBUH9ABAOIAUgACACEBYQGwwECyAFQQYQDiAFQTgQDiAFIAAgAhAWEBsMAwsgBSAEQYABc0H/AXEQDiAFIAAgAhAWEBsMAgsgBUE6EA4gBSAAIAIQFhAbDAELIAVBmgEQDiAFIAAgAhAWEBsLIAwoAgwiAEEATgRAIAVBtgEQDiAFIAAQGyABKAKkAiAAQRRsaiAFKAIENgIICyAMQRBqJAAgCA8LQbzDAEGo7ABB5OoBQcrMABAAAAshACAAQpADgVCtQu4CQu0CIABCA4NQGyAAQuQAgVCtfXwLWQEBfiAAQu0CfiAAQrEPfUICh3wgAELtDn0iASABQuQAgSIBfSABQj+HQpx/g3xCnH9/fCAAQsEMfSIAIABCkAOBIgB9IABCP4dC8HyDfEKQA398QsrxK30LiwIDBX8BfAF+IwBB4ABrIgUkAEKAgICA4AAhCwJAIAAgASAFQRBqIARBD3EiCCAEQQh2QQ9xIgdFENUDIgZBAEgNACACIARBBHZBD3EgB2siBCACIARIGyIEQQAgBEEAShshCUEAIQQDQCAEIAlHBEAgACAFQQhqIAMgBEEDdGopAwAQQg0CIAVBEGogBCAHakEDdGogBSsDCCIKnTkDACAGQQAgCr1CgICAgICAgPj/AINCgICAgICAgPj/AFIbIQYgBEEBaiEEDAELC0QAAAAAAAD4fyEKIAAgASAGRSACQQBMcgR8RAAAAAAAAPh/BSAFQRBqIAgQ6wMLEPkEIQsLIAVB4ABqJAAgCwvHAQEBfwJAAkAgAUKAgICAcFQNACABpyIDLwEGQQpHDQAgACADKQMgEAwgAwJ+IAK9IgECfyACmUQAAAAAAADgQWMEQCACqgwBC0GAgICAeAsiALe9UQRAIACtDAELQoCAgIDAfiABQoCAgIDAgYD8/wB9IAFC////////////AINCgICAgICAgPj/AFYbCyIBNwMgIAFCIIinQXVJDQEgAaciACAAKAIAQQFqNgIAIAEPCyAAQZkfQQAQEkKAgICA4AAhAQsgAQuaAQEDfyMAQRBrIgQkACAEIAI3AwggASgCECIBKAIAIgUgASgCBCIGNgIEIAYgBTYCACABQgA3AgAgACAAIAFBIGogA0EDdGopAwBCgICAgDBBASAEQQhqEBwQDCAAIAEpAxAQDCAAIAEpAxgQDCAAIAEpAyAQDCAAIAEpAygQDCAAKAIQIgBBEGogASAAKAIEEQAAIARBEGokAAspAQJ+IAAgARC2ASIBRQRAQoCAgIDgAA8LIAAgARApIQMgACABEBAgAwuNAQEDfyMAQRBrIgQkACAEIAE3AwggA0EBdCEGQQAhAwNAAkACQCADQQJGDQAgAEHJAEEBIAMgBnJBASAEQQhqEIUBIgFCgICAgHCDQoCAgIDgAFINAUF/IQUgA0EBRw0AIAAgAikDABAMCyAEQRBqJAAgBQ8LIAIgA0EDdGogATcDACADQQFqIQMMAAsAC7oHAgZ/An4jAEEwayIDJAAgAUEMaiEGAkACQAJAAkADQCABKAIQIgIgBkYNAwJAAn8CQAJAAkACQAJAIAEoAgQiBA4GAQMDAAoCCAsgASgCCCECDAULIAIoAghFBEAgASgCCCECDAMLIAAgARDXAwwFCwJAAkAgAigCCA4CCAABCyABQQQ2AgQgAyACKQMQNwMoIAAgACkDUCACIANBKGpBABDeASIIQoCAgIBwg0KAgICA4ABRBEAgACgCECICKQOAASEIIAJCgICAgCA3A4ABIAMgCDcDECAAIAApA1AgAiADQRBqQQEQ3gEhCCAAIAMpAxAQDCAIQoCAgIBwg0KAgICA4ABRDQkLIAAgATUCAEKAgICAcIQgA0EBEPwERQRAIANCgICAgDA3AxggA0KAgICAMDcDECAAIAggAyADQRBqEKkCGiAAIAMpAwAQDCAAIAMpAwgQDAsgACAIEAwMCAsgACABIAIpAxAQ1gMMBwsgAikDECIIQiCIp0F1TwRAIAinIgUgBSgCAEEBajYCAAsgBEEBRyACKAIIIgVBAkdyRQRAIAAgCBCYASABKAIIIQJBAQwCCyABKAIIIgIoAmQiBCAFrTcDACAEQQhrIAg3AwAgAiAEQQhqNgJkC0EACyEEIAIgBDYCHCABQQM2AgQLA0AgACACELECIQggASgCCCICKAIgBEAgCEKAgICAcINCgICAgOAAUQRAIAAoAhAiAikDgAEhCCACQoCAgIAgNwOAASAAIAEQ1wMgACABIAgQ1gMgACAIEAwMAwsgACABENcDIAAgASAIQQEQ8QIgACAIEAwMAgsgCEKAgICAEFoNBSACKAJkQQhrIgIpAwAhCSACQoCAgIAwNwMAAkACQCAIpyICDgMBAAAECyABIAI2AgQgACABIAlBABDxAiAAIAkQDAwCCyADIAk3AygCQAJAIAAgACkDUCACIANBKGpBABDeASIIQoCAgIBwg0KAgICA4ABRDQAgACABNQIAQoCAgIBwhCADQRBqQQAQ/AQEQCAAIAgQDAwBCyADQoCAgIAwNwMIIANCgICAgDA3AwAgACAIIANBEGogAxCpAiEHIAAgCBAMQQAhAgNAIAJBAkZFBEAgACADQRBqIAJBA3RqKQMAEAwgAkEBaiECDAELCyAHRQ0BCyAAIAkQDCABKAIIIgJBATYCHAwBCwsLIAAgCRAMDAILEAEACyAAIAFCgICAgDBBARDxAgsgA0EwaiQADwtB1vEAQajsAEGgmAFB1hQQAAALUQIBfgF/IAAgACkDkAFBAxBHIgJCgICAgHCDQoCAgIDgAFIEQCABQiCIp0F1TwRAIAGnIgMgAygCAEEBajYCAAsgACACQTUgAUEDEBUaCyACCygBAX8gASABKAIAQQFrIgI2AgAgAkUEQCAAQRBqIAEgACgCBBEAAAsLwgEBAn8gAigCBEUEQCACKAIYIgMgAigCHCIENgIEIAQgAzYCACACQgA3AhgCQCABKAIABEAgAhCcBQwBCyAAIAIpAyAQIQsgACACKQMoECEgAiACKAIAQQFrIgM2AgACQCADRQRAIAIoAhAiAyACKAIUIgQ2AgQgBCADNgIAIAJCADcCECAAQRBqIAIgACgCBBEAAAwBCyACQoCAgIAwNwMoIAJCgICAgDA3AyAgAkEBNgIECyABIAEoAgxBAWs2AgwLC4YBACAAIAEgBEEiahBaIgJFBEBCgICAgOAADwsgACACIAMpAwAiAUIAIAFCIIinQQdrQW5PGyABIAFCgICAgMCBgPz/AHxC////////////AINQGxDyAiIARQRAQoCAgIAwDwsgACkDKCIBQiCIp0F1TwRAIAGnIgAgACgCAEEBajYCAAsgAQu7BQIDfgd/IwBBEGsiCyQAQoCAgIDgACEHAkAgACABIARBImoQWiICRQ0AIAIoAgBFIAMpAwAiBUIAIAVCIIinQQdrQW5PGyAFIAVCgICAgMCBgPz/AHxC////////////AINQGyIFQv////9vVnJFBEAgABAiDAELQoCAgIAwIQYgBEEBcUUEQCADKQMIIQYLAkAgACACIAUQ8gIiAwRAIAAgAykDKBAMDAELIABBMBAkIgNFDQEgAyACNgIIIANCATcDAAJAIAIoAgAEQCADIAWnIgQoAhg2AgwgBCADNgIYDAELIAVCIIinQXVJDQAgBaciBCAEKAIAQQFqNgIACyADIAU3AyAgAigCECIJIAIoAhQiBEEBayAFENkDcUEDdGoiCCgCACIKIANBGGoiDDYCBCADIAg2AhwgAyAKNgIYIAggDDYCACACKAIEIgggA0EQaiIKNgIEIAMgAkEEaiIMNgIUIAMgCDYCECACIAo2AgQgAiACKAIMQQFqIgg2AgwgCCACKAIYSQ0AIAAgCUEEIARBAXQgBEEBRhsiAEEDdCALQQxqEKcBIghFDQAgCygCDEEDdiAAaiEEQQAhAANAIAAgBEZFBEAgCCAAQQN0aiIJIAk2AgQgCSAJNgIAIABBAWohAAwBCwsgBEEBayEKIAJBCGohAANAIAwgACgCACIARwRAIABBDGsoAgBFBEAgCCAAKQMQENkDIApxQQN0aiIJKAIAIg0gAEEIaiIONgIEIAAgCTYCDCAAIA02AgggCSAONgIACyAAQQRqIQAMAQsLIAIgBDYCFCACIAg2AhAgAiAEQQF0NgIYCyAGQiCIp0F1TwRAIAanIgAgACgCAEEBajYCAAsgAyAGNwMoIAFCIIinQXVPBEAgAaciACAAKAIAQQFqNgIACyABIQcLIAtBEGok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AmoQI0UNDAJAIAMoAqgGIgVBAEgEQCADKALYBiEFDAELIAMgBTYC2AYLIAEgAygCxAYgBRAuIANBwAZqQfIBEA4gAygCrAZBAXMhBQwSCyADQX82AogDIANCw/aAgOABNwOAAyADQZwGaiAEIANBgANqECNFDQsCQCADKAKoBiICQQBIBEAgAygC2AYhAgwBCyADIAI2AtgGCyABIAMoAsQGIAIQLiADQcAGaiICIAMtAKwGEA4gAiADKAK8BhAbDBALIANBfzYCuAMgA0LZuP2CcDcDsAMgA0GcBmogBCADQbADahAjRQ0KIAMoAqgGIgJBAE4EQCADIAI2AtgGCyADQo6AgIBwNwOgAyADKAKsBiIFQQFqIQYCQCADQZwGaiADKAKkBiICIANBoANqECMEfyADKAKoBiICQQBOBEAgAyACNgLYBgsgAyADKAKwBjYClANBfyEEIANBfzYCmAMgAyAFQQFrNgKQAyADQZwGaiADKAKkBiICIANBkANqECNFDQEgAygCpAYhAiADKAKoBgVBfwshBCAGIQULIAEgAygCxAYgAygC2AYQLiADQcAGaiAFIAMoArAGEFkgBEEASA0TIAMgBDYC2AYMEwsgBi8AASICQf8BSw0JIANCjoCAgHA3AswEIAMgAjYCyAQgA0KRpYKAkAs3A8AEAkAgA0GcBmoiBiAEIANBwARqECNFBEAgA0KOgICAcDcDsAQgAyACNgKsBCADQdkANgKoBCADQo+hgoCQAjcDoAQgBiAEIANBoARqECNFDQELAkAgAygCqAYiBUEASARAIAMoAtgGIQUMAQsgAyAFNgLYBgsgASADKALEBiAFEC4gA0HABmoiBEGUAUGTASADKAKsBkF9cUGQAUYbEA4gBCACQf8BcRAODA8LIANCjoCAgHA3ApQEIAMgAjYCkAQgA0KRgICAkAs3A4gEIANChICAgOATNwOABCADQZwGaiAEIANBgARqECMEQAJAIAMoAqgGIgVBAEgEQCADKALYBiEFDAELIAMgBTYC2AYLIAEgAygCxAYgBRAuAkAgAygCvAZBL0YEQCADQcAGakHBARAODAELIANBwAZqIgRBBBAOIAQgAygCvAYQGwsgA0HABmoiBEGVARAOIAQgAkH/AXEQDgwPCyADQo6AgIBwNwL0AyADIAI2AvADIANCkYCAgJALNwPoAyADQoGAgIDgEzcD4AMgA0GcBmogBCADQeADahAjBEACQCADKAKoBiIFQQBIBEAgAygC2AYhBQwBCyADIAU2AtgGCyABIAMoAsQGIAUQLiADQcAGaiIEIAMoArQGEM4DIARBlQEQDiAEIAJB/wFxEA4MDwsgA0KOgICAcDcD2AMgAyACNgLUAyADQdkANgLQAyADQp6BgICQAjcDyAMgA0LYtvmCcDcDwAMgA0GcBmogBCADQcADahAjBEACQCADKAKoBiIFQQBIBEAgAygC2AYhBQwBCyADIAU2AtgGCyABIAMoAsQGIAUQLiADQcAGaiIEIAMoAqwGIAMoArAGEFkgBEGVARAOIAQgAkH/AXEQDgwPCyABIAMoAsQGIAMoAtgGEC4gA0HABmpB2AAgAhBZIAQhAgwSCyAGLwABIQIgASADKALEBiADKALYBhAuIANBwAZqIAUgAhBZIAQhAgwRCyADIAYvAAEiAjYC5AQgA0F/NgLoBCADIAVBAWs2AuAEIANBnAZqIAQgA0HgBGoQIwRAAkAgAygCqAYiBEEASARAIAMoAtgGIQQMAQsgAyAENgLYBgsgASADKALEBiAEEC4gA0HABmogBUEBaiACEFkMDQsgASADKALEBiADKALYBhAuIANBwAZqIAUgAhBZIAQhAgwQCyABIAogCCAEIANB2AZqEKUCIQQMBgsgASgC1AIhCyABKALMAiEGQQAhCUEAIQgDQAJAIAkgC0gEQEEDIQogBigCACICQeoAa0EDTwRAIAJB7QFHDQJBASEKCwJAIAEoAqQCIAYoAgxBFGxqKAIMIAYoAggiBWsiBEGAf0ggBCAKQf8AakpyRQRAIAZBATYCBCACQe0BRgRAQewBIQIgBkHsATYCAAwCCyAGIAJBgAFqIgI2AgAMAQsgAkHsAEcgBEGAgAJqQf//A0tyDQIgBkLtgYCAIDcCAEECIQpB7QEhAgsgAygCwAYgBWpBAWsgAjoAACAGKAIEIgIgAygCwAYgBWpqIgQgBCAKaiADKALEBiAFIApqIAJqaxCrASADIAMoAsQGIAprNgLEBkEAIQQgASgCrAIiAkEAIAJBAEobIQcgASgCpAIhAgNAIAQgB0YEQCABKALUAiELIAYhByAJIQQDQAJAIAsgBEEBaiIETARAQQAhAiABKALgAiIEQQAgBEEAShshBANAIAIgBEYNAiAFIAEoAtgCIAJBA3RqIgcoAgAiDEkEQCAHIAwgCms2AgALIAJBAWohAgwACwALIAciAkEQaiEHIAIoAhgiDCAFTA0BIAIgDCAKazYCGAwBCwsgCEEBaiEIDAMLIAUgAigCDCILSARAIAIgCyAKazYCDAsgAkEUaiECIARBAWohBAwACwALIAEoAswCIQIgCARAQQAhBQNAIAUgC0gEQCABKAKkAiACKAIMQRRsaigCDCACKAIIIgRrIQYCQAJAAkACQCACKAIEQQFrDgQAAQMCAwsgAygCwAYgBGogBjoAACABKALUAiELDAILIAMoAsAGIARqIAY7AAAMAQsgAygCwAYgBGogBjYAAAsgAkEQaiECIAVBAWohBQwBCwsgASgCzAIhAgsgACgCECIEQRBqIAIgBCgCBBEAACABQQA2AswCIAAoAhAiAkEQaiABKAKkAiACKAIEEQAAIAFBADYCpAIgASgC2AIhAgJAIAEtAG5BAnEEQCACIQUMAQtBACEFIAJFDQAgASgC8AIhByABKAIAIAFB9AJqIggQgwJBACECQQAhCgNAIAEoAtgCIQUgAiABKALgAk4NAQJAIAUgAkEDdGoiBigCBCIEQQBIIAQgB0ZyDQAgBigCACIGIAprIgVBAEgNAAJAIAQgB2siB0EBaiIKQQRLIAVBMktyRQRAIAggCiAFQQVsakEBakH/AXEQDgwBCyAIQQAQDiAIIAUQsQUgCCAHQQF0IAdBH3VzELEFCyAGIQogBCEHCyACQQFqIQIMAAsACyAAKAIQIgJBEGogBSACKAIEEQAAIAFBADYC2AIgDRCJASANIAMpAtAGNwIQIA0gAykCyAY3AgggDSADKQLABjcCACABQQE2AqACIAEoAowCBEAgABBwDCALIAEoAoACIQcgAyABKAKEAiIENgKcBiADIAAgBEEBdBAkIgY2AqQGIAZFDR9BACECIARBACAEQQBKGyEFA0AgAiAFRwRAIAYgAkEBdGpB//8DOwEAIAJBAWohAgwBCwsgA0EANgKsBiADIAAgBEECdBAkIgI2AqgGAkAgAkUNACADQgA3ArAGIANBADYCoAYgACADQZwGakEAQQBBAEF/ELABDQADQCADKAKsBiECAkACQAJAIAMoArAGIgRBAEoEQCADIARBAWsiBDYCsAYgByACIARBAnRq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IAIgCTYCSCACIAo2AkQgAkHBAEHQACAHQQxIGzYCTCACIAdBC2pBDG9BAWo2AkAgAkGQAmogA2pBwAAgA2tB5vQAIAJBQGsQSCADaiEDCyAAIAJBkAJqIAMQ6gEhEQsgAkHQAmokACARCzcCA38BfiMAQRBrIgAkACAAEI0GIAApAwAhAyAAKAIIIQIgAEEQaiQAIAJB6AdtrCADQugHfnwLhwEBAXwgACADKQMAEKgBIgJFBEBCgICAgOAADwsgAhAHIQQgACACEDEgBL0iAQJ/IASZRAAAAAAAAOBBYwRAIASqDAELQYCAgIB4CyIAt71RBEAgAK0PC0KAgICAwH4gAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwvxAQIGfwF+IABBCBAkIgRFBEBBfw8LIARCATcCACACpyEGIAJCIIinQXVJIQgDQAJAAkAgA0ECRg0AIAAgACkDMCADQS5yEEciCUKAgICAcINCgICAgOAAUgRAIABBEBAkIgUNAiAAIAkQDAtBfyEHIANFDQAgACABKQMAEAwLIAAoAhAgBBD/BCAHDwsgBCAEKAIAQQFqNgIAIAUgBDYCCCAIRQRAIAYgBigCAEEBajYCAAsgBSACNwMAIAlCgICAgHBaBEAgCacgBTYCIAsgACAJQS9BARCYAyABIANBA3RqIAk3AwAgA0EBaiEDDAALAAt/AQV/IABBEGohBCABQQxqIQUgASgCECECA0AgAiAFRkUEQCACKAIEIQYgACACKQMQECEgACACKQMYECEgACACKQMgECEgACACKQMoECEgBCACIAAoAgQRAAAgBiECDAELCyABKAIIIgMEQCAAIAMQzgELIAQgASAAKAIEEQAAC+EDAgR/An4jAEFAaiICJAAgAiAAIAEQsQIiBjcDOAJAAkAgASgCIARAIAZCgICAgHCDQoCAgIDgAFENASAAIAEpAyhCgICAgDBBASACQThqEBwhBiAAIAIpAzgQDCAAIAYQDAwCCyACIAEoAmRBCGsiAykDADcDKCADQoCAgIAwNwMAIAAgBhAMQQAhAyAAIAApA1AgACACQShqQQAQ3gEhBiAAIAIpAygQDCAGQoCAgIBwg0KAgICA4ABRDQADQAJAIANBAkcEQCACQRBqIANBA3RqIAAgACkDMCADQTFqEEciBzcD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aiIGKAIEIAJBABARIgNCgICAgHCDQoCAgIDgAFENAyAGKAIEIQYCfyAEBEAgACABIAYgAxA5DAELIAAgASAGIANBBxAVC0EASA0DCyAFQQFqIQUMAQsLIAAgCCALEFtBACEJDAELIAAgCCALEFsLIAdBMGokACAJC6YBAQF+AkACQAJ+IARBBHEEQEEpIQIgACABEEoMAQtBKCECIAAgARAgCyIBQoCAgIBwg0KAgICA4ABRDQAgACACEIYBIgVCgICAgHCDQoCAgIDgAFENACAAQRAQJCICBEAgAkEANgIMIAIgBEEDcTYCCCACIAE3AwAgBUKAgICAcFQNAiAFpyACNgIgDAILIAAgBRAMCyAAIAEQDEKAgICA4AAPCyAFC8QBAQR/IAGnIgUgAjYCICAFQgA3AiQCQCACKAI8IgZFDQACQCAAIAZBAnQQXCIIRQ0AIAUgCDYCJEEAIQUDQCAFIAIoAjxODQIgAigCJCAFQQN0aiIHLwECIQYCQCAHLQAAIgdBAXEEQCAAIAQgBiAHQQF2QQFxEP8DIgYNAQwDCyADIAZBAnRqKAIAIgYgBigCAEEBajYCAAsgCCAFQQJ0aiAGNgIAIAVBAWohBQwACwALIAAgARAMQoCAgIDgACEBCyABC4IBAQJ+IAAgARApIQICQCABQQBIDQAgACgCECgCOCABQQJ0aigCACkCBCIDp0GAgICAeEYgA0KAgICA8P///z+DUCADQv//////////v39WcSADQoCAgICAgICAQINCgICAgICAgICAf1FyRXINACAAQa/wACACQa3wABCyASECCyACC2QBAn8CQAJAIAFCgICAgHBUDQAgARCUBQ0AQX8hAyAAIAIQMCIERQ0BIAAgBBDEBSECIAAgBBAQIAJCgICAgHCDQoCAgIDgAFENASAAIAFBNyACQQEQFUEASA0BC0EAIQMLIAMLNQACQCACRSABQoCAgIBwVHINACABEJQFDQAgACABQTcgACACEClBARAVQQBODQBBfw8LQQALDAAgACABQbYVELUBC2gCAX8BfgJAIAAgAUHqACABQQAQESIEQoCAgIBwg0KAgICA4ABSBEAgACAEECchAyAAIAFBwQAgAUEAEBEiAUKAgICAcINCgICAgOAAUg0BC0EAIQNCgICAgOAAIQELIAIgAzYCACABCxQBAn4gACABECAhAyAAIAEQDCADC/sBAgR/AX4gACgCyAEiBSgCECIEQTBqIQYgBCAEKAIYIAFxQX9zQQJ0aigCACEEAkADQCAERQ0BIAEgBiAEQQFrIgdBA3RqIgQoAgRHBEAgBCgCAEH///8fcSEEDAELCyAFKAIUIAdBA3RqIQUCQCADQQFGDQAgBTUCBEIghkKAgICAwABRBEAgACACEAwgACAEKAIEENEBQX8PCyAELQADQQhxDQAgACACEAwgAEGAgAEgARDnAQ8LIAAgBSACEB1BAA8LIAAgACkDwAEiCCABIAIgCAJ/IAAoAhAoAowBIgMEQEGAgAYgAygCKEEBcQ0BGgtBgIACCxDQAQuKAQEBfwJAIAJCgICAgHCDQoCAgICQf1EgA0KAgICAcINCgICAgJB/UXFFBEAgAEGl5gBBABASDAELIAAgAUESEF4iAUKAgICAcINCgICAgOAAUQ0AIAGnIgQgAz4CJCAEIAI+AiAgACABQdYAQgBBAhAVGiABDwsgACADEAwgACACEAxCgICAgOAACw0AIAAgAUHMjQEQgQMLZwEBfwJAIAFBAE4EQCAAKAIQIgIoAiwgAU0NASACKAI4IAFBAnRqKAIAIgEgASgCAEEBajYCACAAIAFBBBDmAw8LQYaJAUGo7ABB1RdBycAAEAAAC0GQzgBBqOwAQdYXQcnAABAAAAuxAgEEfwJAAkACQAJAIAJCgICAgHBUDQAgAqciAy8BBhDgAUUNACADKAIoIgRFDQAgBCgCECIDQTBqIQUgAyADKAIYQX9zQQJ0QdR5cmooAgAhAwNAIANFDQMgBSADQQFrIgNBA3RqIgYoAgRBygFHBEAgBigCAEH///8fcSEDDAELCyABQoCAgIBwVA0AIAQoAhQgA0EDdGopAwAiAkKAgICAcINCgICAgIB/UQ0BCyAAECIMAgsgACACEIgCIQMgAacoAhAiAEEwaiEEIAAgAyAAKAIYcUF/c0ECdGooAgAhAANAIABFBEBBAA8LIAQgAEEDdGoiBUEIayEAIAMgBUEEaygCAEYEQCAAQQBHDwUgACgCAEH///8fcSEADAELAAsACyAAQZ3kAEEAEBILQX8LRAEBfyAAQeQBaiECIABB4AFqIQADfyAAIAIoAgAiAkYEQEEADwsgASACQQRrKAIARgR/IAJBCGsFIAJBBGohAgwBCwsLiQECA38BfgJAIAAoAhAoAowBIgJFDQADQCABQQBMBEADQCACKQMIIgRCgICAgHBUDQMgBKciAS8BBhDgAUUNAyABKAIgIgEvABEiA0GAwABxRQRAIANBgAhxRQ0EIAAgASgCQBAWDwsgAigCACICDQAMAwsACyABQQFrIQEgAigCACICDQALC0EACykBAX8gAkIgiKdBdU8EQCACpyIDIAMoAgBBAWo2AgALIAAgASACEIUEC/QBAwF+An8BfANAAkBBfyEEAkACQAJAQQcgAkIgiKciBSAFQQdrQW5JGw4IAAAAAAICAwECCyACxCEDQQAhBAwCC0EAIQQgAkKAgICAwIGA/P8AfCICQv///////////wCDQoCAgICAgID4/wBWDQFCgICAgICAgICAfyEDIAK/IgZEAAAAAAAA4MNjDQFC////////////ACEDIAZEAAAAAAAA4ENkDQEgBplEAAAAAAAA4ENjBEAgBrAhAwwCC0KAgICAgICAgIB/IQMMAQsgACACEJYBIgJCgICAgHCDQoCAgIDgAFINAQsLIAEgAzcDACAEC+YBAgN/AXwDQAJAQX8hBAJAAkACQEEHIAJCIIinIgUgBUEHa0FuSRsOCAAAAAACAgMBAgsgAqchA0EAIQQMAgtBACEEIAJCgICAgMCBgPz/AHwiAkL///////////8Ag0KAgICAgICA+P8AVgRADAILQYCAgIB4IQMgAr8iBkQAAAAAAADgwWMNAUH/////ByEDIAZEAADA////30FkDQEgBplEAAAAAAAA4EFjBEAgBqohAwwCC0GAgICAeCEDDAELIAAgAhCWASICQoCAgIBwg0KAgICA4ABSDQELCyABIAM2AgAgBAttAAJAAkACQAJAAkAgAkEEdkEDcUEBaw4DAAECAwsgASgCACICBEAgACACrUKAgICAcIQQIQsgASgCBCIBRQ0DIAAgAa1CgICAgHCEECEPCyAAIAEoAgAQ5QEPCyABEOAFDwsgACABKQMAECELC/UBAQl/QX8hAiABIAFBAWtxRQRAIABBEGoiCCABQQJ0IgMgACgCABEDACIFBH8gBUEAIAMQLCEGIAFB/////wNqQf////8DcSEJIAAoAjQhBwNAIAQgACgCJE9FBEAgByAEQQJ0aigCACECA0AgAgRAIAAoAjggAkECdGooAgAiAygCDCEKIAMgBiAJIAMoAghxQQJ0aiIDKAIANgIMIAMgAjYCACAKIQIMAQsLIARBAWohBAwBCwsgCCAHIAAoAgQRAAAgACABQQF0NgIwIAAgATYCJCAAIAY2AjRBAAVBfwsPC0GbhwFBqOwAQYcUQe3HABAAAAu0AwEHfyADIAEoAgAiBSgCHEEDbEECbSIEIAMgBEobIQcCQCACBEAgACACKAIUIAdBA3QQxQIiA0UNASACIAM2AhQLIAUoAhhBAWohAwNAIAMiAkEBdCEDIAIgB0kNAAsgACACQQJ0IgYgB0EDdGpBMGoQJCIIRQ0AIAUoAggiAyAFKAIMIgQ2AgQgBCADNgIAIAVCADcCCCAGIAhqIAUgBSgCIEEDdEEwahAeIQQgACgCECIDKAJQIgkgBEEIaiIKNgIEIAQgA0HQAGo2AgwgBCAJNgIIIAMgCjYCUAJAIAQoAhhBAWogAkcEQCAEIAJBAWsiCTYCGEEAIQMgCEEAIAYQLBogBEEwaiECA0AgAyAEKAIgTw0CAkAgAigCBCIGRQRAIANBAWohAwwBCyACIAIoAgBBgICAYHEgBCAGIAlxQX9zQQJ0aiIGKAIAQf///x9xcjYCACAGIANBAWoiAzYCAAsgAkEIaiECDAALAAsgCCAFIAJBAnRrIAYQHhoLIAAoAhAiAEEQaiAFIAUoAhhBf3NBAnRqIAAoAgQRAAAgASAENgIAIAQgBzYCHEEADwtBfwvbAQEDfwJAIAAgASgCGEEBakECdCICIAEoAhxBA3RqQTBqIgMQJCIERQRAQQAhAgwBCyAEIAEgASgCGEF/c0ECdGogAxAeIAJqIgJBATYCACAAKAIQIQEgAkECOgAEIAEoAlAiAyACQQhqIgQ2AgQgAiABQdAAajYCDCACIAM2AgggASAENgJQQQAhASACQQA6ABAgAigCLCIDBEAgAyADKAIAQQFqNgIACyACQTBqIQMDQCABIAIoAiBPDQEgACADKAIEEBYaIANBCGohAyABQQFqIQEMAAsACyACC2YBA38jAEEQayIDJAAgACABKAIkIAIgASgCIEEDbEEBdiIAIAAgAkgbIgBBA3QgA0EMahCnASICBH8gAygCDCEEIAEgAjYCJCABIARBA3YgAGo2AiBBAAVBfwshBSADQRBqJAAgBQtsAgN/AXwjAEEQayICJAACfyABQiCIpyIDBEBBACADQQtqQRJJDQEaC0F/IAAgAkEIaiABEEINABogAisDCCIFvUKAgICAgICA+P8Ag0KAgICAgICA+P8AUiAFnCAFYXELIQQgAkEQaiQAIAQL9QICA38BfiMAQRBrIgMkAAJAAkACQAJAAkADQAJAQoCAgIDAfiEGAkACQAJAQQcgAUIgiKciBCAEQQdrQW5JG0EKag4SAAYFAwYGBgYGAgcBAQkGBgcHBgsgAkEBRg0GIAAgARAMIABB6zRBABASDAcLIAFC/////w+DIQYMBwtCgICAgOAAIQYgACABQQEQkgEiAUKAgICAcINCgICAgOAAUg0BDAYLCyAAIANBCGogARDfASECIAAgARAMIAJFDQMgAyACIAIQ/gEiBGoiBTYCDEIAIQYCQCAEIAMoAghGDQAgACAFIANBDGpBAEEEEIACIgZCgICAgHCDQoCAgIDgAFENACADIAMoAgwQ/gEgAygCDGoiBDYCDCADKAIIIAQgAmtGDQAgACAGEAxCgICAgMB+IQYLIAAgAhAxDAQLIAAgARAMIABBizVBABASDAILIAAgARAMDAILIAEhBgwBC0KAgICA4AAhBgsgA0EQaiQAIAYLsgEBAX8CQANAAkACQAJAAkACQEEHIAJCIIinIgMgA0EHa0FuSRsiA0EKag4EAQQEAgALAkAgA0EBag4DAwQABAsgACgC2AEgARC7ASABIALEEJwCGiABDwsgAqdBBGoPCyAAIAIQnwUiAkKAgICAcINCgICAgOAAUg0CDAMLIAAgAkEBEJIBIgJCgICAgHCDQoCAgIDgAFINAQwCCwsgACACEAwgAEHdGUEAEBJBAA8LQQAL7gEBAXwgAQJ/AkADQAJAAkACQEEHIAJCIIinIgEgAUEHa0FuSRsOCAAAAAACAgIBAgtBACEAQf8BIAKnIgEgAUH/AU4bIgFBACABQQBKGwwEC0EAIQAgAkKAgICAwIGA/P8AfCICQv///////////wCDQoCAgICAgID4/wBWDQIgAr8iA0QAAAAAAAAAAGMNAkH/ASADRAAAAAAA4G9AZA0DGgJ/IAOeIgOZRAAAAAAAAOBBYwRAIAOqDAELQYCAgIB4CwwDCyAAIAIQlgEiAkKAgICAcINCgICAgOAAUg0AC0F/IQALQQALNgIAIAALiQYCA38BfiMAQRBrIggkAAJAAkACQAJAAkAgAS0ABSIHQQRxRQ0AIAEvAQYiCUECRgRAAkAgB0EIcQRAAkAgAkEASARAIAggAkH/////B3EiCTYCDCAJIAEoAihHDQEgB0EBcUUNBiAGQYAwcSAGIAZBCHZxQQdxQQdHcg0BIANCIIinQXVPBEAgA6ciAiACKAIAQQFqNgIACyAAIAEgAyAGEIYEIQcMCQsgACAIQQxqIAIQpQFFDQQLQX8hByAAIAEQjgNFDQEMBwsgACAIQQxqIAIQpQFFDQILIAAgCEEIaiABKAIUIgkpAwAQdRogCCgCDEEBaiIHIAgoAghNDQEgASgCEC0AM0EIcUUEQCAAIAZBMBDnASEHDAYLIAggBzYCCCAAIAkgB0EATgR+IAetBUKAgICAwH4gB7i9IgpCgICAgMCBgPz/AH0gCkKAgICAgICA+P8AVhsLEB0MAQsgCUEVa0H//wNxQQpNBEAgACACEJMDIgdFDQEgB0EASA0EIAAgBkH7DRB8IQcMBQsgBkGAgAhxDQAgACgCECgCRCAJQRhsaigCFCIHRQ0AIAGtQoCAgIBwhCEKIAcoAgwiBwRAIAAgCiACIAMgBCAFIAYgBxEiACEHDAULIAAgChCXASIHQQBIDQMgB0UNAQsgAS0ABUEBcQ0BCyAAIAZBhdgAEHwhBwwCCyAAIAEgAiAGQQVxQRByIAZBB3EgBkGAMHEiAhsQdyIBRQ0AIAIEQCABQQA2AgACQCAGQYAQcUUNACAAIAQQNUUNACAEpyECIARCIIinQXVPBEAgAiACKAIAQQFqNgIACyABIAI2AgALIAFBADYCBEEBIQcgBkGAIHFFDQIgACAFEDVFDQIgBachACAFQiCIp0F1TwRAIAAgACgCAEEBajYCAAsgASAANgIEDAILAkAgBkGAwABxBEAgA0IgiKdBdU8EQCADpyIAIAAoAgBBAWo2AgALIAEgAzcDAAwBCyABQoCAgIAwNwMAC0EBIQcMAQtBfyEHCyAIQRBqJAAgBwu2BQEKfyMAQRBrIgUkAAJ/QX8gACAFQQxqIAJBABC+Ag0AGiABKAIQLQAzQQhxRQRAIAAgA0EwEOcBDAELIAEtAAVBCHEEQCAFKAIMIgMgASgCKCIHSQRAIAMhBANAIAQgB0ZFBEAgACABKAIkIARBA3RqKQMAEAwgBEEBaiEEDAELCyABIAM2AigLIAEoAhQgA0EATgR+IAOtBUKAgICAwH4gA7i9IgJCgICAgMCBgPz/AH0gAkKAgICAgICA+P8AVhsLNwMAQQEMAQsgACAFQQRqIAEoAhQpAwAQdRoCQAJAAkACQCAFKAIEIgYgBSgCDCIHSwRAIAEoAhAiCigCICIEIAYgB2tPBEAgBSgCBCEEA0AgBiAHTQ0FIAAgASAAIAZBAWsQ7AUiBhCEBCEMIAAgBhAQIAxFDQMgBEEBayIEIQYMAAsACyAFIAc2AgQgByEJIApBMGoiBiEIA0AgBCALTARAIAUgCTYCBEEAIQgDQCAEIAhMDQUCQCAGKAIEIgRFDQAgACAFQQhqIAQQpQFFDQAgBSgCCCAJSQ0AIAAgASAGKAIEEIQEGiABKAIQIgogCEEDdGpBMGohBgsgBkEIaiEGIAhBAWohCCAKKAIgIQQMAAsABQJAIAgoAgQiBEUNACAAIAVBCGogBBClAUUNACAFKAIIIgQgCUkNACAJIARBAWogCC0AA0EEcRshCQsgCEEIaiEIIAtBAWohCyAKKAIgIQQMAQsACwALIAUgBzYCBCAHIQYMAwsgBSAENgIECyAFKAIEIQYMAQsgBSAENgIECyAAIAEoAhQgBkEATgR+IAatBUKAgICAwH4gBri9IgJCgICAgMCBgPz/AH0gAkKAgICAgICA+P8AVhsLEB1BASAFKAIEIAdNDQAaIAAgA0H72AAQfAshDSAFQRBqJAAgDQu5BAIFfwJ+IwBBEGsiBSQAAkAgAUEASARAIAFB/////wdxrSEHDAELAkAgASAAKAIQIgIoAixJBEACQCACKAI4IAFBAnRqKAIAIgEpAgQiB0KAgICAgICAgECDQoCAgICAgICAwABSDQAgB6dB/////wdxIQQCQCAHQoCAgIAIg1BFBEAgBEUNAgJAIAEvARAiAkEtRwRAIAFBEGohAwwBCyABQRJqIQMgAS8BEiECIARBAkcNAEKAgICAwP7/AyEHIAJBMEYNBgsgAkE6a0F1Sw0BIAVB+QA7AQ4gBUHpgNADNgEKIAVC7oCYg5CNgDc3AQIgAkHJAEcgASAEQQF0akEQaiADa0EQR3INAiADQQJqIAVBAmpBDhBoRQ0BDAILIARFDQECQCABLQAQIgJBLUcEQCABQRBqIQMMAQsgAUERaiEDIAEtABEhAiAEQQJHDQBCgICAgMD+/wMhByACQf8BcUEwRg0FCyACQf8BcSICQTprQXVLDQAgAkHJAEcgASAEakEQaiADa0EIR3INASADQQFqQdILQQcQaA0BCyABIAEoAgBBAWo2AgAgACABrUKAgICAkH+EEJYBIghCgICAgHCDQoCAgIDgAFENAiAAIAgQJSIHQoCAgIBwg0KAgICA4ABRBEAgACAIEAwMBAsgASAHpxC8AiEGIAAgBxAMIAZFDQIgACAIEAwLQoCAgIAwIQcMAgtBps4AQajsAEHgGEGTgwEQAAALIAghBwsgBUEQaiQAIAcLDQAgACgCAEF8cRCeAwufAgIEfwF+AkAgACACEDVFDQAgAqciBS8BBkEORgRAIAAgASAFKAIgKQMAEOIFDwsgAUKAgICAcFQNAAJAIAAgAkE8IAJBABARIgdC/////29YBEBBfyEEIAdCgICAgHCDQoCAgIDgAFENASAAQcweQQAQEgwBCyABpyEDIAenIQYDQAJAIAMoAhAoAiwiBUUEQCADLwEGQSxHDQMgAyADKAIAQQFqNgIAIAOtQoCAgIBwhCEBAkADQCAAIAEQwgIiAUKAgICAcIMiAkKAgICAIFENBSACQoCAgIDgAFENASABpyAGRgRAIAAgARAMDAQLIAAQdkUNAAsgACABEAwLQX8hBAwDCyAFIgMgBkcNAQsLQQEhBAsgACAHEAwLIAQLowECAn8CfiMAQRBrIgMkACADIAE3AwgCfwJAIAJCgICAgHBaBEAgACACQdQBIAJBABARIgZCgICAgHCDIgVCgICAgCBRIAVCgICAgDBRckUEQEF/IAVCgICAgOAAUQ0DGiAAIAAgBiACQQEgA0EIahA2ECcMAwsgACACEDUNAQsgAEH84gBBABASQX8MAQsgACABIAIQ4QULIQQgA0EQaiQAIAQLmgUBCX8jAEEQayICJAAgAkEANgIMIAJCADcDACACQX82AggCQAJAIAJBwAJByJsBKAIAEQMAIgQEQCAEQQBBwAIQLCIAQdCbASkCADcCCCAAQcibASkCADcCACAAKAIMRQRAIABBATYCDAsgACACKQMANwMQIAAgAikDCDcDGCAAQYCAEDYCbCAAQeQBaiIBQQhqQQBBNBAsGiABIAA2AgAgAUECNgIEIABBAzYCuAIgAEEENgK0AiAAQQU2AqwCIABBBjYCqAIgAEEHNgKkAiAAQQg2AqACIAAgAEGgAWoiATYCpAEgACABNgKgASAAQQA6AGggACAAQdgAaiIBNgJcIAAgATYCWCAAIABB0ABqIgE2AlQgACABNgJQIAAgAEHIAGoiATYCTCAAIAE2AkggAEEANgI0IABBADYCJCAAQQA2AjwgAEIANwMoAkAgAEGAAhDVBQ0AIABBEGohCEHwngEhA0EBIQEDQCABQdgBRwRAIAAgAxA9IgVBABDoBSIGBH8gBkEQaiADIAUQHiAFakEAOgAAIAAgBkEEQQNBASABQcoBSxsgAUHKAUYbEMcCBUEAC0UNAiABQQFqIQEgAyAFakEBaiEDDAELCyAAQfCWAUEBQSsQgQRBAEgNACAAKAJEIgFBCTYC+AIgAUEKNgKwAiABQaybATYCnAIgAUGQmwE2AowBIAFB9JoBNgLUASABQQs2ApADIAFBDDYC4AIgAEEANgLcASAAQoSAgICAAjcC1AEgCEHAACAAKAIAEQMAIgENAiAAQQA2AuABCyAAEMAFC0EAIQQMAQsgAUEAQcAAECwhASAAQoCAgIAgNwOAASAAQYCAcDYCeCAAQoCAEDcDcCAAIAE2AuABCyACQRBqJAAgBAuBAQIBfgF/IwBBgAJrIgYkACAGQYACIAIgAxDJAhoCQCAAIAAgAUEDdGopA1hBAxBHIgVCgICAgHCDQoCAgIDgAFEEQEKAgICAICEFDAELIAAgBUEzIAAgBhBgQQMQFRoLIAQEQCAAIAVBAEEAQQAQtAILIAAgBRCYASAGQYACaiQAC54DAgR/AX4jAEEQayIGJAACQAJAAkACQCACQQBIBEAgBiACQf////8HcTYCACABQcAAQcURIAYQSBoMAQsgACgCLCACTQ0CIAJFBEAgAUGhgAEoAAA2AAMgAUGegAEoAAA2AAAMAQsgACgCOCACQQJ0aigCACIEQQFxDQMgASECAkAgBEUNACAEKQIEIgdCgICAgAiDUARAIARBEGohAyAHpyEFQQAhAkEAIQADQCACIAVGRQRAIAAgAiADai0AAHIhACACQQFqIQIMAQsLIABBgAFIDQMLIARBEGohBUEAIQAgASECA0AgACAHp0H/////B3FPDQECfyAHQoCAgIAIg1BFBEAgBSAAQQF0ai8BAAwBCyAAIAVqLQAACyEDIAIgAWtBOUoNAQJ/IANB/wBNBEAgAiADOgAAIAJBAWoMAQsgAiADEN0CIAJqCyECIABBAWohACAEKQIEIQcMAAsACyACQQA6AAALIAEhAwsgBkEQaiQAIAMPC0GmzgBBqOwAQeYXQbLxABAAAAtBo4kBQajsAEHwF0Gy8QAQAAALVAECfyAAQQE6AGggAEHYAGohAgJAA0AgAiAAKAJcIgFHBEAgAUEIayIBKAIADQIgACABEIsFDAELCyAAQQA6AGgPC0GkhgFBqOwAQfEqQegWEAAAC8QDAQJ/IAAoAhAiAygCFEEwaiADKAJsSwRAIAMQnQUgAyADKAIUIgNBAXYgA2o2AmwLAkAgAEEwECQiAwRAIANBADYCICADQQA2AhggA0EBOgAFIAMgAjsBBiADIAE2AhAgAyAAIAEoAhxBA3QQJCIENgIUIAQNASAAKAIQIgJBEGogAyACKAIEEQAACyAAKAIQIAEQjAJCgICAgOAADwsCQAJAAkACQAJAAkACQAJAIAJBAWsOIQcABgQEBAQCBgQGAQYGBgYGBQYGAgICAgICAgICAgIDBAYLIANBADYCKCADQgA3AyAgAyADLQAFQQxyOgAFIAEgACgCJEcEfyAAIANBMEEKEHcFIAQLQgA3AwAMBgsgBEKAgICAMDcDAAwFCyADQgA3AiQgAyADLQAFQQxyOgAFDAQLIANCADcCJAwDCyADQoCAgIAwNwMgDAELIANCADcDIAsgACgCECgCRCACQRhsaigCFEUNACADIAMtAAVBBHI6AAULIANBATYCACAAKAIQIQAgA0EAOgAEIAAoAlAiASADQQhqIgI2AgQgAyAAQdAAajYCDCADIAE2AgggACACNgJQIAOtQoCAgIBwhAtEACAAQRBqIAEgAnQgAmtBEWogACgCABEDACIABEAgAEEANgIMIABBATYCACAAIAFB/////wdxIAJBH3RyrTcCBAsgAAv1AQIBfwJ+IwBB0ABrIgMkAAJAAn4gAUEASARAIAMgAUH/////B3E2AgAgA0EQaiIBQcAAQcURIAMQSBogACABEGAMAQsgACgCECIAKAIsIAFNDQECQAJAIAAoAjgiACABQQJ0aigCACIBKQIEIgRCgICAgICAgIBAg0KAgICAgICAgMAAUQ0AIAJFDQEgBKdBgICAgHhHDQAgACgCvAEhAQsgASABKAIAQQFqNgIAIAGtQoCAgICQf4QMAQsgASABKAIAQQFqNgIAIAGtQoCAgICAf4QLIQUgA0HQAGokACAFDwtBps4AQajsAEGfGEH8zwAQAAALqwECAX4CfyABKQIEQoCAgIAIgyEDIAAtAAdBgAFxRQRAIANQBEAgAEEQaiABQRBqIAIQaA8LQQAgAUEQaiAAQRBqIAIQmgVrDwsgAUEQaiEEIABBEGohACADUARAIAAgBCACEJoFDwsgAkEAIAJBAEobIQVBACEBA0AgASAFRgRAQQAPCyABQQF0IQIgAUEBaiEBIAAgAmovAQAgAiAEai8BAGsiAkUNAAsgAgtsAgJ/AX4gAEEQaiECIAApAgQiBKchAAJAIARCgICAgAiDUEUEQCAAQf////8HcSEDQQAhAANAIAAgA0YNAiACIABBAXRqLwEAIAFBhwJsaiEBIABBAWohAAwACwALIAIgACABEO4FIQELIAELcAICfwF+IwBBEGsiAiQAAkAgAUEATgRAIAFBgICAgHhyIQMMAQsgAiABNgIAIAJBBWoiAUELQcURIAIQSBogACABEGAiBEKAgICAcINCgICAgOAAUQ0AIAAoAhAgBKdBARDHAiEDCyACQRBqJAAgAwvTAQIFfwF+AkAgASkCBCIHp0H/////B3EiBEELa0F2SQ0AAn8gB0KAgICACINQIgZFBEAgAS8BEAwBCyABLQAQCyIDQTBrIgJBCUsNAAJ/AkAgA0EwRwRAIAFBEGohBUEBIQEDQCABIARGDQICfyAGRQRAIAUgAUEBdGovAQAMAQsgASAFai0AAAtBMGsiA0EJSw0EIAFBAWohASADrSACrUIKfnwiB6chAiAHQoCAgIAQVA0ACwwDC0EAIgIgBEEBRw0BGgsgACACNgIAQQELDwtBAAssAQF/A0AgASADRkUEQCAAIANqLQAAIAJBhwJsaiECIANBAWohAwwBCwsgAgteAQF/AkAgAUKAgICAcFQNACABpyIELwEGIANHDQAgBCgCICIERQ0AIAQpAwAiAUKAgICAYFoEQCAAIAGnIAIRAAALIAQpAwgiAUKAgICAYFQNACAAIAGnIAIRAAALC0oBAX8CQCABQoCAgIBwVA0AIAGnIgMvAQYgAkcNACADKAIgIgNFDQAgACADKQMAECEgACADKQMIECEgAEEQaiADIAAoAgQRAAALCzgBAX8gAEEwayIEQQpPBH8gAEHBAGsgA00EQCAAQTdrDwsgAiAAQdcAayAAQeEAayABTxsFIAQLC6IDAQJ/IAAgASgCBBAQA0AgASgCECEDIAIgASgCFE5FBEAgACADIAJBA3RqKAIAEBAgAkEBaiECDAELCyAAKAIQIgJBEGogAyACKAIEEQAAQQAhAgNAAkAgASgCHCEDIAIgASgCIE4NACADIAJBFGxqIgMoAghFBEAgACgCECADKAIEEOUBCyAAIAMoAhAQECAAIAMoAgwQECACQQFqIQIMAQsLIAAoAhAiAkEQaiADIAIoAgQRAAAgACgCECICQRBqIAEoAiggAigCBBEAAEEAIQIDQCABKAI0IQMgAiABKAI4TkUEQCAAIAMgAkEMbGooAgQQECACQQFqIQIMAQsLIAAoAhAiAkEQaiADIAIoAgQRAAAgACgCECICQRBqIAEoAmQgAigCBBEAACAAIAEpA0AQDCAAIAEpA0gQDCAAIAEpA6gBEAwgACABKQOwARAMIAAgASkDiAEQDCAAIAEpA5ABEAwgACABKQOYARAMIAEoAggiAiABKAIMIgM2AgQgAyACNgIAIAFCADcCCCAAKAIQIgBBEGogASAAKAIEEQAAC9IDAgJ+An8jAEEgayIEJAACQCABQv///////////wCDIgNCgICAgICAwIA8fSADQoCAgICAgMD/wwB9VARAIAFCBIYgAEI8iIQhAyAAQv//////////D4MiAEKBgICAgICAgAhaBEAgA0KBgICAgICAgMAAfCECDAILIANCgICAgICAgIBAfSECIABCgICAgICAgIAIUg0BIAIgA0IBg3whAgwBCyAAUCADQoCAgICAgMD//wBUIANCgICAgICAwP//AFEbRQRAIAFCBIYgAEI8iIRC/////////wODQoCAgICAgID8/wCEIQIMAQtCgICAgICAgPj/ACECIANC////////v//DAFYNAEIAIQIgA0IwiKciBUGR9wBJDQAgBEEQaiAAIAFC////////P4NCgICAgICAwACEIgIgBUGB9wBrEGIgBCAAIAJBgfgAIAVrEI0CIAQpAwhCBIYgBCkDACIAQjyIhCECIAQpAxAgBCkDGIRCAFKtIABC//////////8Pg4QiAEKBgICAgICAgAhaBEAgAkIBfCECDAELIABCgICAgICAgIAIUg0AIAJCAYMgAnwhAgsgBEEgaiQAIAIgAUKAgICAgICAgIB/g4S/C6oPAgV/D34jAEHQAmsiBSQAIARC////////P4MhCiACQv///////z+DIQsgAiAEhUKAgICAgICAgIB/gyEMIARCMIinQf//AXEhCAJAAkAgAkIwiKdB//8BcSIJQf//AWtBgoB+TwRAIAhB//8Ba0GBgH5LDQELIAFQIAJC////////////AIMiDUKAgICAgIDA//8AVCANQoCAgICAgMD//wBRG0UEQCACQoCAgICAgCCEIQwMAgsgA1AgBEL///////////8AgyICQoCAgICAgMD//wBUIAJCgICAgICAwP//AFEbRQRAIARCgICAgICAIIQhDCADIQEMAgsgASANQoCAgICAgMD//wCFhFAEQCADIAJCgICAgICAwP//AIWEUARAQgAhAUKAgICAgIDg//8AIQwMAwsgDEKAgICAgIDA//8AhCEMQgAhAQwCCyADIAJCgICAgICAwP//AIWEUARAQgAhAQwCCyABIA2EUARAQoCAgICAgOD//wAgDCACIAOEUBshDEIAIQEMAgsgAiADhFAEQCAMQoCAgICAgMD//wCEIQxCACEBDAILIA1C////////P1gEQCAFQcACaiABIAsgASALIAtQIgYbeSAGQQZ0rXynIgZBD2sQYkEQIAZrIQYgBSkDyAIhCyAFKQPAAiEBCyACQv///////z9WDQAgBUGwAmogAyAKIAMgCiAKUCIHG3kgB0EGdK18pyIHQQ9rEGIgBiAHakEQayEGIAUpA7gCIQogBSkDsAIhAwsgBUGgAmogCkKAgICAgIDAAIQiEkIPhiADQjGIhCICQgBCgICAgLDmvIL1ACACfSIEQgAQYSAFQZACakIAIAUpA6gCfUIAIARCABBhIAVBgAJqIAUpA5gCQgGGIAUpA5ACQj+IhCIEQgAgAkIAEGEgBUHwAWogBEIAQgAgBSkDiAJ9QgAQYSAFQeABaiAFKQP4AUIBhiAFKQPwAUI/iIQiBEIAIAJCABBhIAVB0AFqIARCAEIAIAUpA+gBfUIAEGEgBUHAAWogBSkD2AFCAYYgBSkD0AFCP4iEIgRCACACQgAQYSAFQbABaiAEQgBCACAFKQPIAX1CABBhIAVBoAFqIAJCACAFKQO4AUIBhiAFKQOwAUI/iIRCAX0iAkIA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IAUgA0E5ShshAwJAIBdCB1cEQCADIAdBBHRqIQcMAQsgF0IcWARAIAJBMGogAxB4IAJBIGogHCAYQgBCgICAgICAwP0/ECsgAkEQaiACKQMwIAIpAzggAikDICIcIAIpAygiGBArIAIgAikDECACKQMYIBkgGxBvIAIpAwghGyACKQMAIRkMAQsgA0UgCHINACACQdAAaiAcIBhCAEKAgICAgICA/z8QKyACQUBrIAIpA1AgAikDWCAZIBsQbyACKQNIIRtBASEIIAIpA0AhGQsgF0IBfCEXQQEhCQsgACgCBCIDIAAoAmhHBH8gACADQQFqNgIEIAMtAAAFIAAQTwshAwwBCwsCfiAJRQRAIAApA3BCAFkEQAJAIAAgACgCBCIDQQFrNgIEIAAgA0ECazYCBCABRQ0AIAAgA0EDazYCBAsLIAJB4ABqIAS3RAAAAAAAAAAAohCpASACKQNgIRkgAikDaAwBCyAXQgdXBEAgFyEYA0AgB0EEdCEHIBhCAXwiGEIIUg0ACwsCQAJAAkAgA0FfcUHQAEYEQCAAEIEGIhhCgICAgICAgICAf1INAyAAKQNwQgBZDQEMAgtCACEYIAApA3BCAFMNAgsgACAAKAIEQQFrNgIEC0IAIRgLIAdFBEAgAkHwAGogBLdEAAAAAAAAAACiEKkBIAIpA3AhGSACKQN4DAELIBogFyABG0IChiAYfEIgfSIXQQAgD2utVQRAQcTUBEHEADYCACACQaABaiAEEHggAkGQAWogAikDoAEgAikDqAFCf0L///////+///8AECsgAkGAAWogAikDkAEgAikDmAFCf0L///////+///8AECsgAikDgAEhGSACKQOIAQwBCyAPQeIBa6wgF1cEQCAHQQBOBEADQCACQaADaiAZIBtCAEKAgICAgIDA/79/EG8gGSAbQoCAgICAgID/PxD1BSEAIAJBkANqIBkgGyACKQOgAyAZIABBAE4iABsgAikDqAMgGyAAGxBvIBdCAX0hFyACKQOYAyEbIAIpA5ADIRkgB0EBdCAAciIHQQBODQALCwJ+IBcgD6x9QiB8IhinIgBBACAAQQBKGyANIBggDa1TGyIAQfEATgRAIAJBgANqIAQQeCACKQOIAyEaIAIpA4ADIRxCAAwBCyACQeACakQAAAAAAADwP0GQASAAaxDVARCpASACQdAC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QQFqNgIEIAEtAAAMAQsgABBPC0Ewa0EKSQ0ACwsgACkDcEIAWQRAIAAgACgCBEEBazYCBAtCACAFfSAFIAMbIQUMAQtCgICAgICAgICAfyEFIAApA3BCAFMNACAAIAAoAgRBAWs2AgRCgICAgICAgICAfw8LIAULygYCBX8EfiMAQYABayIFJAACQAJAAkAgAyAEQgBCABDrAUUNAAJ/IARC////////P4MhCwJ/IARCMIinQf//AXEiBkH//wFHBEBBBCAGDQEaQQJBAyADIAuEUBsMAgsgAyALhFALCyEJIAJCMIinIghB//8BcSIHQf//AUYNACAJDQELIAVBEGogASACIAMgBBArIAUgBSkDECICIAUpAxgiASACIAEQ9AUgBSkDCCECIAUpAwAhBAwBCyABIAJC////////////AIMiCyADIARC////////////AIMiChDrAUEATARAIAEgCyADIAoQ6wEEQCABIQQMAgsgBUHwAGogASACQgBCABArIAUpA3ghAiAFKQNwIQQMAQsgBEIwiKdB//8BcSEGIAcEfiABBSAFQeAAaiABIAtCAEKAgICAgIDAu8AAECsgBSkDaCILQjCIp0H4AGshByAFKQNgCyEEIAZFBEAgBUHQAGogAyAKQgBCgICAgICAwLvAABArIAUpA1giCkIwiKdB+ABrIQYgBSkDUCEDCyAKQv///////z+DQoCAgICAgMAAhCEMIAtC////////P4NCgICAgICAwACEIQsgBiAHSARAA0ACfiALIAx9IAMgBFatfSIKQgBZBEAgCiAEIAN9IgSEUARAIAVBIGogASACQgBCABArIAUpAyghAiAFKQMgIQQMBQsgCkIBhiAEQj+IhAwBCyALQgGGIARCP4iECyELIARCAYYhBCAHQQFrIgcgBkoNAAsgBiEHCwJAIAsgDH0gAyAEVq19IgpCAFMEQCALIQoMAQsgCiAEIAN9IgSEQgBSDQAgBUEwaiABIAJCAEIAECsgBSkDOCECIAUpAzAhBAwBCyAKQv///////z9YBEADQCAEQj+IIQ0gB0EBayEHIARCAYYhBCANIApCAYaEIgpCgICAgICAwABUDQALCyAIQYCAAnEhBiAHQQBMBEAgBUFAayAEIApC////////P4MgB0H4AGogBnKtQjCGhEIAQoCAgICAgMDDPxAr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6e0AQdjsAEHUIUGzxAAQAAALCyAFQQxqEBkgBUEgaiQAQRALrwEBAn8jAEEgayIEJAAgACgCACEFIARBCGogA0EAEO0BIAAgASAEKAIIIgEgAUEfdSIBcyABayIBIAJBwAAgAUEBa2dBAXRrQQAgAUECTxtqQQhqIgJB4A8QrwMhASADKAIEBEAgBEIANwIYIARCgICAgICAgICAfzcCECAEIAU2AgwgBEEMaiIDQgEQMhogACADIAAgAkHgDxCIASABciEBIAMQGQsgBEEgaiQAIAELkAYCCH8BfiMAQfAAayIDJAAgACABRwRAIAAoAgAhBCADQgA3AmggA0KAgICAgICAgIB/NwJgIAMgBDYCXCADQdwAaiIFIAEQSRogA0IANwJUIANCgICAgICAgICAfzcCTCADIAQ2AkggAygCZCEGIANBADYCZCADQcgAaiIBQqrVqtUKEDIaIANBADYCUCAFIAEQrAIEQCADIAMoAmRBAWo2AmQgBkEBayEGCyADQcgAahAZIAJBAWpBAXYQuQQhBSADQgA3AlQgA0KAgICAgICAgIB/NwJMIAMgBDYCSCADQgA3AkAgA0KAgICAgICAgIB/NwI4IAMgBDYCNCADQdwAaiIBIAFCf0H/////A0EAEHoaIAVBACAFQQBKGyEJIAIgBWogAiAFQQF0bkEBaiIKQQF0akEgaiECQQAhAQNAIAEgCUZFBEAgA0HIAGoiByADQdwAaiIIQgEgAkEAEHoaIANBNGoiCyAHIAJBBhC1BCAHIAtCASACQQAQehogCCAIIAcgAkEAEIgBGiABQQFqIQEMAQsLIANCADcCLCADQoCAgICAgICAgH83AiQgAyAENgIgIANCADcCGCADQoCAgICAgICAgH83AhAgAyAENgIMIANBIGoiASADQdwAaiIEQgIgAkEAEHoaIAEgBCABIAJBABCIARogA0EMaiABIAEgAkEAEEAaIABCABAyGiAKrSEMA0AgDKdBAExFBEAgA0HIAGoiAUIBEDIaIANBNGoiBCAMQgGGQv7///8Pg0IBhBAyGiABIAEgBCACQQAQiAEaIAAgACABIAJBABC4ARogACAAIANBDGogAkEAEEAaIAxCAX0hDAwBCwsgACAAQgEgAkEAEHoaIAAgACADQSBqIgEgAkEAEEAaIAEQGSADQQxqEBkgA0E0ahAZ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C0KAgICAMCEFIAZCgICAgHCDQoCAgIAwUQ0DIAAgBkEBEJABGgwDC0KAgICAMCEJQoCAgIAwIQZCgICAgDAhBSAAIAQQICIHQoCAgIBwg0KAgICA4ABRDQILIAAgDUEIaiAHEC9BAEgNACANAn4gDSkDCCIEQoCAgIAIfEL/////D1gEQCAEQv////8PgwwBC0KAgICAwH4gBLm9IgVCgICAgMCBgPz/AH0gBUL///////////8Ag0KAgICAgICA+P8AVhsLIgg3AyAgACABQQEgDUEgahC/AyEFIAAgCBAMAkAgBUKAgICAcINCgICAgOAAUQ0AQgAhCCAEQgAgBEIAVRshDANAIAggDFENBCAAIAcgCBBsIgRCgICAgHCDQoCAgIDgAFENAQJAIA4EQCAEIQEMAQsgDSAENwMgIA0gCEL/////D4M3AyggACAKIAtBAiANQSBqEBwhASAAIAQQDCABQoCAgIBwg0KAgICA4ABRDQILIAAgBSAIIAEQeyERIAhCAXwhCCARQQBODQALCwwBC0KAgICAMCEFCyAAIAUQDEKAgICA4AAhBQsgACAHEAwgACAGEAwgACAJEAwgDUEwaiQAIAULDwAgACsDACABKwMAEMQECzkBAX5CgICAgMB+IAEpAwAiAkKAgICAwIGA/P8AfSACQv///////////wCDQoCAgICAgID4/wBWGwsRACAAKgIAuyABKgIAuxDEBAs7AQF+QoCAgIDAfiABKgIAu70iAkKAgICAwIGA/P8AfSACQv///////////wCDQoCAgICAgID4/wBWGwsZAQJ+IAEpAwAiAyAAKQMAIgRUIAMgBFZrCwwAIAAgASkDABCIBAsZAQJ+IAEpAwAiAyAAKQMAIgRTIAMgBFVrCwwAIAAgASkDABC/AgsXACABKAIAIgEgACgCACIASSAAIAFJaws9AQF+IAEoAgAiAEEATgRAIACtDwtCgICAgMB+IAC4vSICQoCAgIDAgYD8/wB9IAJCgICAgICAgPj/AFYbC9sFAwV/A34BfCMAQUBqIgUkAAJAAnwCQAJAAkACQAJAIAJBACABQoCAgIBwgyILQoCAgIAwUhsiAg4CAgABCwJAIAMpAwAiCUKAgICAcFQNACAJpyIELwEGQQpHDQAgBCkDICIKQiCIpyIEQQAgBEELakESSRsNACAAIAUg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IQULIAJBEGokACAFCx0AIAAgAUEAEGsiAEUEQEKAgICA4AAPCyAANQIoCz0BAX5CgICAgBAhASADKQMAIgRCgICAgHBaBH4gBKcvAQZBFWtB//8DcUEMSa1CgICAgBCEBUKAgICAEAsL7gMCBX4CfyMAQSBrIgokAEKAgICA4AAhBQJAIAAgASAEEFoiC0UNACALLQAEBEAgABBfDAELIAAgCkEYaiADKQMAQgAgCzQCACIGIAYQZg0AIAogBjcDECADKQMIIgdCgICAgHCDQoCAgIAwUgRAIAAgCkEQaiAHQgAgBiAGEGYNASAKKQMQIQYLIAopAxghCSAAIAFCgICAgDAQ/QEiB0KAgICAcIMiBUKAgICA4ABRBEAgByEFDAELIAYgCX0iBkIAIAZCAFUbIQgCQCAFQoCAgIAwUQRAIABCgICAgDAgCCAEEOUDIQUMAQsgCiAGQv////8HVwR+IAhC/////w+DBUKAgICAwH4gCLm9IgVCgICAgMCBgPz/AH0gBUL///////////8Ag0KAgICAgICA+P8AVhsLNwMIIAAgB0EBIApBCGoQowEhBSAAIAcQDCAAIAopAwgQDAsgBUKAgICAcINCgICAgOAAUQ0AAkAgACAFIAQQWiICRQ0AIAAgBSABEE0EQCAAQco0QQAQEgwBCwJAIAItAAQNACACNAIAIAhTBEAgAEHOwgBBABASDAILIAstAAQNACACKAIIIAsoAgggCadqIAinEB4aDAILIAAQXwsgACAFEAxCgICAgOAAIQULIApBIGokACAFC1EAIAAgASACEFoiAEUEQEKAgICA4AAPCyAAKAIAIgBBAE4EQCAArQ8LQoCAgIDAfiAAuL0iAUKAgICAwIGA/P8AfSABQoCAgICAgID4/wBWGwv/AwICfwF+AkACQAJAAkACQAJAIAFCgICAgHBaBEAgAaciAi8BBkErRg0BCyAEQQE2AgAMAQsgAigCICEGIARBATYCACAGDQELIABBsS1BABASDAELIAYoAgQhAgJAAkACQAJ/AkACQAJAAkAgBigCACIHQQFrDgQCAgcBAAsgBUUNAiAAIAYQyQQLQoCAgIAwIQEgBUEBaw4CAwQHCyADKQMAIgFCIIinQXVPBEAgAaciAyADKAIAQQFqNgIACwJAIAVBAkcNAEEBIQMgB0EBRw0AIAAgARCYASAGKAIEDAILIAIoAmQiAyAFrTcDACADQQhrIAE3AwAgAiADQQhqNgJkC0EAIQMgAgsiBSADNgIcIAZBAzYCACAAIAUQsQIhASAGQQE2AgAgBigCBCgCIARAIAAgBhDJBCABDwsgAUKAgICAEFoNBSACKAJkQQhrIgApAwAhCCAAQoCAgIAwNwMAIAFCAlEEQCAGQQI2AgAgBEECNgIAIAgPCyAEQQA2AgAgCA8LIAMpAwAiAUIgiKdBdUkNAyABpyIAIAAoAgBBAWo2AgAgAQ8LIAMpAwAiAUIgiKdBdU8EQCABpyICIAIoAgBBAWo2AgALIAAgARCYAQwBCyAAQY8tQQAQEgtCgICAgOAAIQELIAEPC0HW8QBBqOwAQaCUAUHEFBAAAAt3AQF+IAMpAwAiAUKAgICAcINCgICAgIB/UgRAIABBkcEAQQAQEkKAgICA4AAPC0KAgICAMCEEIAGnIgApAgRCgICAgICAgIBAg0KAgICAgICAgIB/UQR+IAAgACgCAEEBajYCACABQoCAgICQf4QFQoCAgIAwCws8AQF+QoCAgIDgACEBIAAgAykDABAlIgRCgICAgHCDQoCAgIDgAFIEfiAAIASnQQIQ5gMFQoCAgIDgAAsLVgIBfgF/IAAgARC7AyIBQoCAgIBwg0KAgICA4ABRBEAgAQ8LQoCAgIAwIQIgAaciAygCBEGAgICAeEcEQCAAIAAoAhAgAxDGAhApIQILIAAgARAMIAILCQAgACABELsDC1sBAX4jAEEQayICJAAgAiAAIAEQuwMiATcDCAJAIAFCgICAgHCDQoCAgIDgAFEEQCABIQQMAQsgAEKAgICAMEEBIAJBCGoQygQhBCAAIAEQDAsgAkEQaiQAIAQLLQBCgICAgOAAIAAgAykDACADKQMIQQAQiQIiAEEAR61CgICAgBCEIABBAEgbC6ABAQN+IAMpAwAiBSEEIAJBBE4EQCADKQMYIQQLIAVC/////29YBEAgABAiQoCAgIDgAA8LIAMpAxAhAUKAgICA4AAhBgJAIAAgAykDCBAwIgJFDQAgAUIgiKdBdU8EQCABpyIDIAMoAgBBAWo2AgALIAAgBSACIAEgBEEAENABIQMgACACEBAgA0EASA0AIANBAEetQoCAgIAQhCEGCyAGCyoAIAMpAwAiAUL/////b1gEQCAAECJCgICAgOAADwsgACABQQNBABCyAgtjAQF+IAMpAwAiBEL/////b1gEQCAAECJCgICAgOAADwtCgICAgOAAIQECQCAAIAMpAwgQMCICRQ0AIAAgBCACEG4hAyAAIAIQECADQQBIDQAgA0EAR61CgICAgBCEIQELIAELYwEDfgJAAkAgAykDACIBQv////9vWARAIAAQIgwBCyADKQMIIQUgASEEIAJBA04EQCADKQMQIQQLIAAgBRAwIgINAQtCgICAgOAADwsgACABIAIgBEEAEBEhBiAAIAIQECAGC2YBAX4gAykDACIEQv////9vWARAIAAQIkKAgICA4AAPC0KAgICA4AAhAQJAIAAgAykDCBAwIgJFDQAgACAEIAJBABDNASEDIAAgAhAQIANBAEgNACADQQBHrUKAgICAEIQhAQsgAQuaAQIBfwJ+IwBBEGsiBCQAIAMpAwghBSADKQMAIgYhAQJAAkACQAJAIAJBA0gNACADKQMQIgFCgICAgHBaBEAgAactAAVBEHENAQsgAEGdLEEAEBIMAQsgACAEQQxqIAUQ/QMiAg0BC0KAgICA4AAhAQwBCyAAIAYgASAEKAIMIgMgAhD+AiEBIAAgAiADEIYDCyAEQRBqJAAgAQt5AQF/IAFCgICAgHCDQoCAgIAwUgRAIABBnSxBABASQoCAgIDgAA8LAn4CQCACRQ0AIAMpAwAiAUKAgICAcINCgICAgDBRDQBCgICAgOAAIAAgARAlIgFCgICAgHCDQoCAgIDgAFENARogAachBAsgACAEQQMQ5gMLCxUAIAAgAykDACADIANBCGpBAhCIAws3ACMAQRBrIgIkACAAIAJBDGogAykDABB1IQAgAigCDCEDIAJBEGokAEKAgICA4AAgA2etIAAbC04AIwBBEGsiAiQAQoCAgIDgACEBAkAgACACQQxqIAMpAwAQdQ0AIAAgAkEIaiADKQMIEHUNACACKAIIIAIoAgxsrSEBCyACQRBqJAAgAQsGACAAtrsLfwAgACAAKQPQASIBQgyIIAGFIgFCGYYgAYUiAUIbiCABhSIBNwPQAUKAgICAwH4gAUKdurP7lJL9oiV+QgyIQoCAgICAgID4P4S/RAAAAAAAAPC/oL0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwuIBAMFfAV/AX4jAEEQayIKJAAgCkIANwMIAkACQCACQQBMDQBCgICAgOAAIQEgACAKQQhqIAMpAwAQQg0BQQEhCyAKKwMIIQQgAkEBRwRAA0AgAiALRg0CIAAgCiADIAtBA3RqKQMAEEINAyALQQFqIQsgCisDACEFIwBBIGsiCSQAAkAgBJkiByAFmSIGIAe9IAa9VCIMGyIEvSIOQjSIpyINQf8PRg0AIAYgByAMGyEFAkAgDlANACAFvUI0iKciDEH/D0YNACAMIA1rQcEATgRAIAcgBqAhBAwCCwJ8IAxB/gtPBEAgBEQAAAAAAAAwFKIhBCAFRAAAAAAAADAUoiEFRAAAAAAAALBrDAELRAAAAAAAAPA/IA1BvARLDQAaIAREAAAAAAAAsGuiIQQgBUQAAAAAAACwa6IhBUQAAAAAAAAwFAshCCAJQRhqIAlBEGogBRCIBiAJQQhqIAkgBBCIBiAIIAkrAwAgCSsDEKAgCSsDCKAgCSsDGKCfoiEEDAELIAUhBAsgCUEgaiQADAALAAsgBJkhBAsgBL0iAQJ/IASZRAAAAAAAAOBBYwRAIASqDAELQYCAgIB4CyIAt71RBEAgAK0hAQwBC0KAgICAwH4gAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGyEBCyAKQRBqJAAgAQtOACAAIABEAAAAAAAA8L9EAAAAAAAA8D8gAEQAAAAAAAAAAGMbIAC9Qv///////////wCDQoCAgICAgID4/wBWGyAARAAAAAAAAAAAYRsLgwECAn4BfyAAvSIBQjSIp0H/D3EiA0H+B00EQCABQoCAgICAgICAgH+DIQIgA0H+B0cgAUKAgICAgICA8L9/UXJFBEAgAkKAgICAgICA+D+Evw8LIAK/DwsgA0GyCE0EfCABQj+HIAF8QgFBswggA2uthiIBQgGIfEIAIAF9g78FIAALC4IFAwJ8BX8CfiMAQRBrIgkkAAJ+QoCAgIDA/v/7/wBCgICAgMD+/3sgBBsgAkUNABoCfCADKQMAIgFC/////w9YBEBBASACIAJBAUwbIQogAachCEEBIQcDQCAHIApHBEAgCLcgAyAHQQN0aikDACIBQoCAgIAQWg0DGiAIIAGnIgsgCCALShsgCCALIAggC0gbIAQbIQggB0EBaiEHDAELCyAIrQwCC0KAgICA4AAgACAJQQhqIAEQQg0BGkEBIQcgCSsDCAshBSAHIAIgAiAHSBshAgNAIAIgB0cEQEKAgICA4AAgACAJIAMgB0EDdGopAwAQQg0CGgJAIAW9IgxC////////////AINCgICAgICAgPj/AFYNACAJKwMAIga9IgFC////////////AINCgICAgICAgPj/AFYEQCAGIQUMAQsgBUQAAAAAAAAAAGEgBkQAAAAAAAAAAGFxIQogBARAIAoEQCABIAyDvyEFDAILIAUgBSAGpSAGvUL///////////8Ag0KAgICAgICA+P8AVhsgBiAFvUL///////////8Ag0KAgICAgICA+P8AWBshBQwBCyAKBEAgASAMhL8hBQwBCyAFIAUgBqQgBr1C////////////AINCgICAgICAgPj/AFYbIAYgBb1C////////////AINCgICAgICAgPj/AFgbIQULIAdBAWohBwwBCwsgBb0iAQJ/IAWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyIAt71RBEAgAK0MAQtCgICAgMB+IAFCgICAgMCBgPz/AH0gAUL///////////8Ag0KAgICAgICA+P8AVhsLIQ0gCUEQaiQAIA0L4wECAX4CfyMAQRBrIgIkAAJAIAAgAUEpEFoiA0UEQCAEQQA2AgBCgICAgOAAIQEMAQtCgICAgDAhAQJAIAMpAwAiBkKAgICAcINCgICAgDBSBEAgAiADKAIMIgU2AgwgBSAGpyIHKAIEQf////8HcUkNASAAIAYQDCADQoCAgIAwNwMACyAEQQE2AgAMAQsgByACQQxqEMYBIQggAyACKAIMNgIMIARBADYCACAIQf//A00EQCAAIAhB//8DcRCUAyEBDAELIAAgByAFQQF0akEQakECEJIDIQELIAJBEGokACABC5EDAgN/An4jAEEgayICJABCgICAgOAAIQgCQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRDQAgACACQQhqIgVBBxA+GiAFQTwQPBogBSAEQQN0IgRB0OEBaigCACIGEIMBGiAEQdThAWooAgAiBARAIAVBIBA8GiAFIAQQgwEaIAVB2pEBEIMBGiAAIAMpAwAQSiIJQoCAgIBwg0KAgICA4ABRBEAgACABEAwgAigCCCgCECIAQRBqIAIoAgwgACgCBBEAAAwCCyAJpyIH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QdAAaiQAIAYLuAICA38DfiMAQSBrIgIkAEKAgICA4AAhBwJAAkACQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRDQAgACACIAMpAwAQ4wENACACKQMAIghCgICAgAhaBEAgAEHTGEEAEEQMAQsgCKciA0EBRg0BIAGnIgQpAgQiCaciBkH/////B3EiBUUNASAJQv////8HgyAIfkKAgICABFoEQCAAQeTIAEEAEDoMAQsgACACQQhqIAMgBWwgBkEfdhCZAw0AAkAgBUEBRwRAA0AgA0EATA0CIAJBCGogBEEAIAUQSxogA0EBayEDDAALAAsgAkEIagJ/IAQtAAdBgAFxBEAgBC8BEAwBCyAELQAQCyADEMsEGgsgACABEAwgAkEIahA3IQcMAgsgACABEAwMAQsgASEHCyACQSBqJAAgBwtYAQF+IAAgAykDABDkAUEAR61CgICAgBCEIQQgAUKAgICAcINCgICAgDBRBEAgBA8LIAAgAUEGEF4iAUKAgICAcINCgICAgOAAUgRAIAAgASAEEL0BCyABC8EBAgJ/An4jAEEQayIEJABCgICAgOAAIQYCQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRBEAgASEGDAELAkAgACAEQQxqIAMpAwAgAaciBSgCBEH/////B3EiAiACEFYNACAEIAI2AgggAykDCCIHQoCAgIBwg0KAgICAMFIEQCAAIARBCGogByACIAIQVg0BIAQoAgghAgsgACAFIAQoAgwiAyACIAMgAiADShsQjgEhBgsgACABEAwLIARBEGokACAGC8ABAgN/An4jAEEQayICJABCgICAgOAAIQcCQCAAIAEQSiIBQoCAgIBwg0KAgICA4ABRBEAgASEHDAELAkAgACACQQxqIAMpAwAgAaciBigCBEH/////B3EiBCAEEFYNACACIAQgAigCDCIFayIENgIIIAAgBiAFIAMpAwgiCEKAgICAcINCgICAgDBSBH8gACACQQhqIAggBEEAEFYNASACKAIIBSAECyAFahCOASEHCyAAIAEQDAsgAkEQaiQAIAcL0wECAn8CfiMAQRBrIgIkAEKAgICA4AAhBgJAIAAgARBKIgFCgICAgHCDQoCAgIDgAFEEQCABIQYMAQsCQCAAIAJBDGogAykDACABpyIFKAIEQf////8HcUEAEFYNACACIAUo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ABogAy0AAEEBcUUEQCADKAI4IQAgAygCNCIBBEAgASAANgI4CyAABEAgACABNgI0CyADQZjVBCgCAEYEQEGY1QQgADYCAAsgAygCYBDUASADENQBCyAMEAogDBDUAQsgB0HQAWokAAsJACAAIAEQzwQLLAAgACABEM8EIgFCgICAgHCDQoCAgIDgAFIEfiAAQQNBAiABpxsQKQUgAQsLkAECAXwBfiMAQRBrIgIkAAJ+IAMpAwAiAUIgiKciAwRAQoCAgIAQIANBC2pBEkkNARoLQoCAgIDgACAAIAJBCGogARBCDQAaIAIrAwgiBJlE////////P0NlIAS9QoCAgICAgID4/wCDQoCAgICAgID4/wBSIAScIARhcXGtQoCAgIAQhAshBSACQRBqJAAgBQsmAEKAgICA4AAgACADKQMAENkFIgBBAEetQoCAgIAQhCAAQQBIGwsvAQF+An4gAygCBCICBEBCgICAgBAiBCACQQtqQRJJDQEaCyAAIAQgAyADENIECwsvAQF+An4gAygCBCICBEBCgICAgBAiBCACQQtqQRJJDQEaCyAAIAQgAyADENMECwsJACAAIAEQngILowECAn4BfyMAQRBrIgIkAAJ+IAAgARCeAiIFQoCAgIBwg0KAgICA4ABRBEAgBQwBC0EKIQcCQAJAIAQNACADKQMAIgFCgICAgHCDQoCAgIAwUQ0AIAAgARDbBCIHQQBIDQELQoCAgIDgACAAIAJBCGogBRBtDQEaIAAgAisDCCAHQQBBABC6AgwBCyAAIAUQDEKAgICA4AALIQYgAkEQaiQAIAYLkAICAX4BfCMAQRBrIgIkAEKAgICA4AAhBAJAIAAgARCeAiIBQoCAgIBwg0KAgICA4ABRBEAgASEEDAELIAAgAiABEG0NAAJAAkAgAykDACIBQoCAgIBwg0KAgICAMFEEQCACKwMAIgW9IQEMAQsgACACQQxqIAEQswENAiACKwMAIgW9IgFCgICAgICAgPj/AINCgICAgICAgPj/AFINAQsgAEKAgICAwH4gAUKAgICAwIGA/P8AfSAFvUL///////////8Ag0KAgICAgICA+P8AVhsQNCEEDAELIAIoAgwiA0HlAGtBm39NBEAgAEHrIUEAEEQMAQsgACAFQQogA0EBELoCIQQLIAJBEGokACAEC80BAgF+AnwjAEEQ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IAQgCjcDACAEQQhqIQQgCEIBfCEIDAELCyAJIAx8IQgDQCAIIAtZDQIgBiAIp0EDdGopAwAiCUIgiKdBdU8EQCAJpyIDIAMoAgBBAWo2AgALIAQgCTcDACAEQQhqIQQgCEIBfCEIDAALAAsDQAJAIAkgClEEQCADQRBqIQNCACEJA0AgCSAPUQ0CIAMgCadBA3RqKQMAIhBCIIinQXVPBEAgEKciBiAGKAIAQQFqNgIACyAEIBA3AwAgBEEIaiEEIAlCAXwhCQwACwALIAAgDiAJIAQQVEF/Rg0DIARBCGohBCAJQgF8IQkMAQsLIAogDHwhCQNAIAkgC1kNASAAIA4gCSAEEFRBf0YNAiAEQQhqIQQgCUIBfCEJDAALAAsgAiAERgRAIAFCgICAgDAgACABQTAgDUKAgICACFoEfkKAgICAwH4gDbm9IghCgICAgMCBgPz/AH0gCEL///////////8Ag0KAgICAgICA+P8AVhsFIA0LEDlBAEgiAxshC0KAgICA4AAgASADGyEIIAIhBAwDC0GJFkGo7ABB4rkCQfHqABAAAAsgASELDAELQQAhAkKAgICAMCELCwNAIAIgBEZFBEAgBEKAgICAMDcDACAEQQhqIQQMAQsLIAAgCxAMIAAgDhAMIAVBMGokACAIC8gIAgl+A38jAEEwayIOJABCgICAgDAhBQJAAkAgACAOQSBqIAAgARAgIgoQLw0AIAAgDkEYaiADKQMAQgAgDikDICIGIAYQZg0AAkAgBARAAkACQAJAIAIOAgIAAQsgBiAOKQMYfSEHQQAhAgwBCyAAIA5BEGogAykDCEIAIAYgDikDGH1CABBmDQMgAkECayECIA4pAxAhBwsgBiACrXwgB31CgICAgICAgBBTDQEgAEH0yABBABASDAILIA4gBjcDECAGIQEgAykDCCINQoCAgIBwg0KAgICAMFIEfiAAIA5BEGogDUIAIAEgARBmDQIgDikDEAUgAQsgDikDGH0iAUIAIAFCAFUbIQdBACECCyAAIAogB0KAgICACHxC/////w9YBH4gB0L/////D4MFQoCAgIDAfiAHub0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwsiBRCfAiEBIAAgBRAMAkAgAUKAgICAcINCgICAgOAAUQ0AIA4pAxgiDSAH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AiAAIAFBNyABQQAQESIBQoCAgIBwg0KAgICAMFEEfiAAQS8QKQUgAQtBnggQsgELC+EFAwN+B38DfAJAIAAgARBVDQAgACAAKQMwQQ4QRyIFQoCAgIBwg0KAgICA4ABRDQAgBaciCSABQoCAgIBwWgR/IAGnLQAFQRBxBUEACyAJLQAFQe8BcXI6AAUCQCAAQQEgAiACQQFMGyIKQQFrIghBA3RBGGoQJCIHRQ0AIAFCIIinQXVPBEAgAaciAiACKAIAQQFqNgIACyAHIAE3AwAgAykDACIEQiCIp0F1TwRAIASnIgIgAigCAEEBajYCAAsgByAINgIQIAcgBDcDCCAHQRhqIQtBACECA0AgAiAIRwRAIAMgAkEBaiIMQQN0aikDACIEQiCIp0F1TwRAIASnIg0gDSgCAEEBajYCAAsgCyACQQN0aiAENwMAIAwhAgwBCwsgCSAHNgIgAn8gAUL/////b1gEQCAAECJBfwwBCyAAQQAgAadBMBBDCyICQQBIDQACQCACRQ0AIAAgAUEwIAFBABARIgRCgICAgHCDQoCAgIDgAFENASAEQv////8PWARAIASnIgIgCGtBACACIApOG60hBgwBCyAEQiCIp0EHa0FtTQRAAkAgBEKAgICAwIGA/P8AfCIEQv///////////wCDQoCAgICAgID4/wBWDQAgBL+dIg8gCLciEGUNACAPIBChIQ4LIA69IgQCfyAOmUQAAAAAAADgQWMEQCAOqgwBC0GAgICAeAsiAre9UQRAIAKtIQYMAgtCgICAgMB+IARCgICAgMCBgPz/AH0gBEL///////////8Ag0KAgICAgICA+P8AVhshBgwBCyAAIAQQDAsgACAFQTAgBkEBEBUaIABBgJIBIAAgAUE3IAFBABARIgFCgICAgHCDIgRCgICAgJB/UgR+IARCgICAgOAAUQ0BIAAgARAMIABBLxApBSABC0HslgEQsgEiAUKAgICAcINCgICAgOAAUQ0AIAAgBUE3IAFBARAVGiAFDwsgACAFEAwLQoCAgIDgAAswACACQQBMBEAgACABQoCAgIAwQQBBABAcDwsgACABIAMpAwAgAkEBayADQQhqEBwLgwICAX4BfyMAQSBrIgIkAEKAgICA4AAhBQJAAkAgACABECAiAUKAgICAcINCgICAgOAAUQ0AIAAgAykDABAw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ARAgCzYAIAMpAwAiAUIgiKciAkF/RiAERSACQX5xQQJHcXJFBEAgABAiQoCAgIDgAA8LIAAgARDoAQuJAQEBfiADKQMAIgFC/////29WIAFCgICAgHCDQoCAgIAgUXJFBEAgAEHe0gBBABASQoCAgIDgAA8LAkAgACABEEEiAUKAgICAcINCgICAgOAAUgRAIAMpAwgiBEKAgICAcINCgICAgDBRDQEgACABIAQQ2gRFDQEgACABEAwLQoCAgIDgAA8LIAELnwIBA34gAUL/////b1gEQCAAECJCgICAgOAADwtCgICAgOAAIQUCfiAAIAFBNyABQQAQESIEQoCAgIBwg0KAgICAMFEEQCAAQZQBECkMAQsgACAEEDQLIgRCgICAgHCDIgZCgICAgOAAUgR+An4gACABQTMgAUEAEBEiAUKAgICAcINCgICAgDBRBEAgAEEvECkMAQsgACABEDQLIgFCgICAgHCDIgVCgICAgOAAUQRAIAAgBBAMQoCAgIDgAA8LAkAgBkKAgICAkH9RBEAgBKcoAgRB/////wdxRQ0BCyAFQoCAgICQf1EEQCABpygCBEH/////B3FFDQELIABB7JYBIARBpJIBELIBIQQLIAAgBCABELYCBUKAgICA4AALC5UCAgF+An8jAEEwayICJABCgICAgOAAIQECQCAAIAJBKGogAykDABCkAQ0AIAAQ4gEiBUKAgICAcINCgICAgOAAUQ0AIAAgAkEUaiADKQMIEK4CIgZFBEAgACAFEAwMAQsgACgC2AEgAhC7ASACQgEQMhogAiACKQMoIgGnIgdBARC5ARogAiACQn9B/////wNBARB6GiAFp0EEaiIDIAYgAhCyBBoCQCAERSABUHINACACQgEQMhogAiAHQQFrQQEQuQEaIAMgAhDyAUEASA0AIAJCARAyGiACIAdBARC5ARogAyADIAJB/////wNBARDuARoLIAIQGSAAIAYgAkEUahDmASAFEK8CIQELIAJBMGokACABCwkAIAAgARDcBAt0AgJ+AX8gACABENwEIgFCgICAgHCDQoCAgIDgAFEEQCABDwtBCiEGAn4CQCACRQ0AIAMpAwAiBEKAgICAcINCgICAgDBRDQAgACAEENsEIgZBAE4NAEKAgICA4AAMAQsgACABIAYQogULIQUgACABEAwgBQvOAQIBfwJ+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/////////wCDQoCAgICAgID4/wBWGwvvAQEDfiMAQRBrIgIkAEKAgICA4AAhBAJAIAAgACABECAiAUEBELsCIgVCgICAgHCDQoCAgIDgAFENACAFQiCIpyIDQQAgA0ELakESSRtFBEAgACACQQhqIAUQQkEASA0BQoCAgIAgIQQgAikDCEKAgICAgICA+P8Ag0KAgICAgICA+P8AUQ0BC0KAgICA4AAhBCAAIAFB48oAEIcCIgZCgICAgHCDQoCAgIDgAFENACAAIAYQNUUEQCAAQergAEEAEBIgACAGEAwMAQsgACAGIAFBAEEAEDYhBAsgACABEAwgACAFEAwgAkEQaiQAIAQLjAIDAXwBfgF/IwBBEGsiAiQAQoCAgIDgACEFAkAgACACQQhqIgYgARCmAg0AIAAgBiADKQMAEEINACACAn4gAisDCCIEvUKAgICAgICA+P8Ag0KAgICAgICA+P8AUgRAIASdIgREAAAAAACwnUCgIAQgBEQAAAAAAABZQGMbIAQgBEQAAAAAAAAAAGYbIQQLIAS9IgUCfyAEmUQAAAAAAADgQWMEQCAEqgwBC0GAgICAeAsiA7e9UQRAIAOtDAELQoCAgIDAfiAFQoCAgIDAgYD8/wB9IAVC////////////AINCgICAgICAgPj/AFYbCzcDACAAIAFBASACQREQ+AQhBQsgAkEQaiQAIAULigEDAX4BfAF/IwBBEGsiAiQAQoCAgIDgACEEAkAgACACQQhqIgYgARCmAg0AIAAgBiADKQMAEEINACAAIAEgAisDCCIFnUQAAAAAAAAAAKBEAAAAAAAA+H8gBUQAANzCCLI+Q2UbRAAAAAAAAPh/IAVEAADcwgiyPsNmGxD5BCEECyACQRBqJAAgBAvZAQIBfAF+IwBB0ABrIgIkAAJ+QoCAgIDgACAAIAEgAiAEQQ9xQQAQ1QMiAEEASA0AGkKAgICAwH4gAEUNABogBEGAAnEEQCACIAIrAwBEAAAAAACwncCgOQMACyACIARBBHZBD3FBA3RqKwMAIgW9IgECfyAFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAsiBLe9UQRAIAStDAELQoCAgIDAfiABQoCAgIDAgYD8/wB9IAFC////////////AINCgICAgICAgPj/AFYbCyEGIAJB0ABqJAAgBguHAQIBfAF+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IAMpAxAiBUIgiKdBdU8EQCAFpyIJIAkoAgBBAWo2AgALIAggBTcDCAJAIAQNACADKQMYIgVCIIinQXVJDQAgBaciCSAJKAIAQQFqNgIACyAIIAE3AxAgCCAFNwMAIAAgByAGQQMgCBAcIQUgACAIKQMAEAwgBEUEQCAAIAgpAwgQDAsgAygCBCEDIAAoAhAgAhDiAyAFQoCAgIBwg0KAgICA4ABRDQIgACAFEAwLDAALAAsgCEEgaiQAIAULVAAgACABIAJBImoQWiIARQRAQoCAgIDgAA8LIAAoAgwiAEEATgRAIACtDwtCgICAgMB+IAC4vSIBQoCAgIDAgYD8/wB9IAFCgICAgICAgPj/AFYbC1kBAX8gACABIARBImoQWiICRQRAQoCAgIDgAA8LIAJBBGohAyACKAIIIQQDfiADIARGBH5CgICAgDAFIARBEGshBSAEKAIEIQQgACgCECACIAUQgAUMAQsLC3UAIAAgASAEQSJqEFoiAkUEQEKAgICA4AAPCyAAIAIgAykDACIBQgAgAUIgiKdBB2tBbk8bIAEgAUKAgICAwIGA/P8AfEL///////////8Ag1AbEPICIgNFBEBCgICAgBAPCyAAKAIQIAIgAxCABUKBgICAEAthACAAIAEgBEEiahBaIgJFBEBCgICAgOAADwsgACACIAMpAwAiAUIAIAFCIIinQQdrQW5PGyABIAFCgICAgMCBgPz/AHxC////////////AINQGxDyAkEAR61CgICAgBCECwgAQoCAgIAwC0oAAkAgBSkDACIBQoCAgIBwVA0AIAGnIgIvAQZBLEcNACACKAIgIgJFDQAgAkEBOgARIAAgARAMIAVCgICAgCA3AwALQoCAgIAwC88BAQN+IwBBEGsiAiQAQoCAgIDgACEFAkACQAJ+QoCAgIAwIABCgICAgDAgACADEMAEIgRCgICAgHCDQoCAgIDgAFENABogAiAENwMIQoCAgIDgACAAQcMAQQBBAEEBIAJBCGoQhQEiBkKAgICAcINCgICAgOAAUQ0AGiAAEDMiAUKAgICAcINCgICAgOAAUg0BIAYLIQEgACAEEAwgACABEAwMAQsgACABQYQBIARBBxAVGiAAIAFBhQEgBkEHEBUaIAEhBQsgAkEQaiQAIAULswMCA38CfiMAQdAAayIGJABBfyEHAkAgACAG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QoCAgIBwg0KAgICA4ABRcg0AIAAgAykDCCIGEDVFDQBCgICAgOAAIQQCQCAAEDMiBUKAgICAcINCgICAgOAAUQRAIAEhBQwBCyAAIAVBLyABQQcQFUEASA0AIAAgBUEvIAYQhQUhBAsgACAFEAwMAQsgASEECyAHQRBqJAAgBAsQACAAIAMpAwBBESAEELICC6UDAQR+IwBBEGsiAyQAIAQCfwJAAkACQAJAIAAgAUEqEFoiAkUEQEKAgICAMCEBDAELIAIoAhgEQEKAgICAMCEBQQEMBQsgACACKQMAIgggAikDCCIGEMUBIgFCgICAgHCDIgdCgICAgOAAUg0BC0KAgICAMCEHDAELIAdCgICAgCBRBEAgAkEBNgIYQoCAgIAwIQFBAQwDCyACKAIQBEAgACAAIAFCABBOEDQiB0KAgICAcIMiCUKAgICA4ABRDQECQCAJQoCAgICQf1INACAHpygCBEH/////B3ENACAAIANBCGogACAIQdYAIAhBABAREKEBQQBIDQIgACAIQdYAAn4gBqcgAykDCCACKAIUEPQCIgZCgICAgAh8Qv////8PWARAIAZC/////w+DDAELQoCAgIDAfiAGub0iBkKAgICAwIGA/P8AfSAGQv///////////wCDQoCAgICAgID4/wBWGwsQOUEASA0CCyAAIAcQDAwCCyACQQE2AhgMAQsgACABEAwgACAHEAxCgICAgOAAIQELQQALNgIAIANBEGokACABCyAAIAFCIIinQXVPBEAgAaciACAAKAIAQQFqNgIACyABC/EHAgR/C34jAEEwayIEJAACQCABQv////9vWARAIAAQIkKAgICA4AAhAQwBC0KAgICAMCEIAkACQCAAIAMpAwAQJSIPQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhDEKAgICAMCEBQoCAgIAwIQ1CgICAgDAhEAwBCyAAIAEgACkDSBD9ASIQQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhDEKAgICAMCEBQoCAgIAwIQ0MAQsCQAJAIAAgACABQe4AIAFBABAREDQiDUKAgICAcINCgICAgOAAUQ0AIA2nIgJB9QBBABCgASEGIAJB+QBBABCgAUEASARAIABB7JYBIA1B0A4QsgEiDUKAgICAcINCgICAgOAAUQ0BCyAEIA03AyggBCABNwMgIAAgEEECIARBIGoQowEiDEKAgICAcINCgICAgOAAUQ0BIAAQOyIBQoCAgIBwg0KAgICA4ABRBEBCgICAgOAAIQEMAwtBfyECAkAgAykDCCIJQoCAgIBwg0KAgICAMFENACAAIARBHGogCRB1QQBIDQMgBCgCHCICDQAMBAsCQAJAIA+nIgcpAgQiCKdB/////wdxIgUEQCAGQX9zQR92IQYgCEL/////B4MhESACrSESQgAhCUKAgICAMCEIQQAhAgNAIAKtIQogAiEDA0AgAyAFTw0DIAAgDEHWACADrSIOEDlBAEgNByAAIAgQDAJAIAAgDCAPEMUBIghCgICAgHCDIgtCgICAgCBRDQAgC0KAgICA4ABRDQggACAEQRBqIAAgDEHWACAMQQAQERChAQ0IIAQgBCkDECILIBEgCyARUxsiCzcDECAKIAtRDQAgACAHIAIgAxCOASIKQoCAgIBwg0KAgICA4ABRDQggACABIAkgChBnQQBIDQggCUIBfCIKIBJRDQkgACAEQQhqIAgQLw0IIAunIQJCASELIAlCASAEKQMIIg4gDkIBVxt8IQkDQCAJIApRDQMgACAAIAggCxBsEDQiDkKAgICAcINCgICAgOAAUQ0JIAAgASAKIA4QZ0EASA0JIAtCAXwhCyAKQgF8IgogElINAAsMCQsgByAOIAYQ9AKnIQMMAAsACwALIAAgDCAPEMUBIghCgICAgHCDIglCgICAgCBSDQFCACEJQQAhAgsgACAHIAIgBSACIAVJGyAFEI4BIgpCgICAgHCDQoCAgIDgAFENAyAAIAEgCSAKEGdBAEgNAwwECyAJQoCAgIDgAFINAwwCC0KAgICAMCEMC0KAgICAMCEBCyAAIAEQDEKAgICA4AAhAQsgACAPEAwgACAQEAwgACAMEAwgACANEAwgACAIEAwLIARBMGokACABC+ACAQd+IAFC/////29YBEAgABAiQoCAgIDgAA8LQoCAgIDgACEIQoCAgIAwIQYCQAJAAkAgACADKQMAECUiB0KAgICAcINCgICAgOAAUQRAQoCAgIAwIQQMAQsgACABQdYAIAFBABARIgRCgICAgHCDQoCAgIDgAFENACAAIARCABBNRQRAIAAgAUHWAEIAEDlBAEgNAQsgACABIAcQxQEiBUKAgICAcIMiCUKAgICA4ABRDQEgACABQdYAIAFBABARIgZCgICAgHCDQoCAgIDgAFENAQJAIAAgBiAEEE0EQCAAIAQQDAwBCyAAIAFB1gAgBBA5QQBODQBCgICAgDAhBAwCCyAAIAcQDCAAIAYQDEL/////DyEIIAlCgICAgCBRDQIgACAFQdgAIAVBABARIQogACAFEAwgCg8LQoCAgIAwIQULIAAgBRAMIAAgBxAMIAAgBhAMIAAgBBAMCyAIC84EAgZ+AX8jAEEgayICJAACQCABQv////9vWARAIAAQIkKAgICA4AAhBwwBC0KAgICA4AAhB0KAgICAMCEIAkAgACADKQMAECUiCUKAgICAcINCgICAgOAAUQRAQoCAgIAwIQRCgICAgDAhBUKAgICAMCEGDAELAkACQCAAIAEgACkDSBD9ASIGQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhBAwBCyAAIAAgAUHuACABQQAQERA0IgRCgICAgHCDQoCAgIDgAFINAQtCgICAgDAhBQwBCyACIAQ3AxggAiABNwMQIAAgBkECIAJBEGoQowEiBUKAgICAcINCgICAgOAAUQ0AIAAgAkEIaiAAIAFB1gAgAUEAEBEQoQENACAAIAVB1gACfiACKQMIIgFCgICAgAh8Qv////8PWARAIAFC/////w+DDAELQoCAgIDAfiABub0iAUKAgICAwIGA/P8AfSABQv///////////wCDQoCAgICAgID4/wBWGwsQOUEASA0AQoCAgIDgACEIIABBKhCGASIBQoCAgIBwg0KAgICA4ABRDQAgAEEgECQiA0UEQCABIQgMAQsgAyAJNwMIIAMgBTcDACADIASnIgpB5wBBABCgAUF/c0EfdjYCECAKQfUAQQAQoAEhCiADQQA2AhggAyAKQX9zQR92NgIUIAFCgICAgHBaBEAgAacgAzYCIAsgACAGEAwgACAEEAwgASEHDAELIAAgCRAMIAAgBhAMIAAgBBAMIAAgBRAMIAAgCBAMCyACQSBqJAAgBwv+BAIIfgJ/IwBBEGsiAiQAAkAgAUL/////b1gEQCAAECJCgICAgOAAIQcMAQtCgICAgOAAIQdCgICAgDAhBQJAAkACQCAAIAMpAwAQJSIJQoCAgIBwg0KAgICA4ABRBEBCgICAgDAhCAwBCyAAIAFB7gAgAUEAEBEiCEKAgICAcINCgICAgOAAUQ0AIAAgCBA0IghCgICAgHCDQoCAgIDgAFENACAIp0HnAEEAEKABQX9GBEAgACABIAkQxQEhBwwDCyAAIAAgAUHwACABQQAQERAnIgxBAEgNACAAIAFB1gBCABA5QQBIDQAgABA7IgZCgICAgHCDQoCAgIDgAFEEQEKAgICA4AAhBgwCCyAJpyENA0ACQCAAIAUQDCAAIAEgCRDFASIFQoCAgIBwgyIEQoCAgIAgUQ0AIARCgICAgOAAUQ0DAkAgACAAIAVCABBOEDQiBEKAgICAcIMiC0KAgICAkH9SBEBBACEDIAtCgICAgOAAUQ0FDAELIASnKAIEQf////8HcUUhAwsgACAGIAogBBB7QQBIDQMgCkIBfCEKIANFDQEgACACQQhqIAAgAUHWACABQQAQERChAUEASA0DIAAgAUHWAAJ+IA0gAikDCCAMEPQCIgRCgICAgAh8Qv////8PWARAIARC/////w+DDAELQoCAgIDAfiAEub0iBEKAgICAwIGA/P8AfSAEQv///////////wCDQoCAgICAgID4/wBWGwsQOUEATg0BDAMLCyAKpwRAIAYhBwwDCyAAIAYQDEKAgICAICEHDAILQoCAgIAwIQYLIAAgBhAMCyAAIAUQDCAAIAgQDCAAIAkQDAsgAkEQaiQAIAcLjgEBAn8gASgCACICQQBKBEAgASACQQFrIgI2AgACQCACDQAgAS0ABEHwAXFBEEcNACABKAIIIgIgASgCDCIDNgIEIAMgAjYCACABQQA2AgggACgCYCICIAFBCGoiAzYCBCABIABB4ABqNgIMIAEgAjYCCCAAIAM2AmALDwtB5oQBQajsAEHWLEHN4wAQAAAL8BQCDn8OfiMAQZABayIEJAACQCABQv////9vWARAIAAQIkKAgICA4AAhFQwBCyADKQMIIR4gACAEQThqQQAQPhogBEEANgIwIARCgICAgMAANwMoIAQgADYCACAEIARBCGoiBzYCBEKAgICA4AAhFUKAgICAMCEWQoCAgIAwIRdCgICAgDAhE0KAgICAMCEUQoCAgIAwIR1CgICAgDAhHAJAAkAgACADKQMAECUiGEKAgICAcINCgICAgOAAUQ0AIAAgHhA1IglFBEAgACAeECUiHUKAgICAcINCgICAgOAAUQ0BIB2nIQULAkACQCAAIAFB7gAgAUEAEBEiHEKAgICA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QQRqIQMMAAsACwuaAQEHfyABpygCICIDBEAgAEEQaiEEIANBBGohBiADKAIIIQIDQCACIAZHBEAgAigCBCEIIAJBEGshBSACQQxrKAIARQRAAkAgAygCAARAIAUQnAUMAQsgACACKQMQECELIAAgAikDGBAhCyAEIAUgACgCBBEAACAIIQIMAQsLIAQgAygCECAAKAIEEQAAIAQgAyAAKAIEEQAACwsbAQF/IAGnKAIgIgMEQCAAIAMoAgwgAhEAAAsLUgEDfyABpygCICICBEAgAigCBCIDBEAgAigCACIEIAM2AgQgAyAENgIAIAJCADcCAAsgACACNQIMQoCAgIBwhBAhIABBEGogAiAAKAIEEQAACwupAQEGfyABpygCICIDBEAgA0EMaiEFIAMoAhAhAgNAIAIgBUcEQCACKAIEIQcgAkIANwIAIAIoAgghBCAHIQIgBC8BBkEgRg0BIARCADcCJAwBCwsCQAJAIAMtAAVFDQAgACgCyAEiAkUNACAAKALQASADKAIIIAIRAAAMAQsgAygCGCICRQ0AIAAgAygCFCADKAIIIAIRBgALIABBEGogAyAAKAIEEQAACwspAQF/IAAgAaciAjUCJEKAgICAkH+EECEgACACNQIgQoCAgICQf4QQIQshACABpygCICkDACIBQoCAgIBgWgRAIAAgAacgAhEAAAsLaQEDfyAAIAGnKAIgIgIpAwAQISACLQARRQRAA0AgAigCFCEEIAMgAigCDE9FBEAgACAEIANBA3RqKAIEEMcBIANBAWohAwwBCwsgAEEQaiAEIAAoAgQRAAALIABBEGogAiAAKAIEEQAAC2wBA38CQCABQoCAgIBwVA0AIAGnIgMvAQZBD0cNACADKAIgIgRFDQAgBEEIaiEFQQAhAwNAIAMgBC0ABU8NASAFIANBA3RqKQMAIgFCgICAgGBaBEAgACABpyACEQAACyADQQFqIQMMAAsACwtqAQN/AkAgAUKAgICAcFQNACABpyICLwEGQQ9HDQAgAigCICIDRQ0AIANBCGohBEEAIQIDQCACIAMtAAVPRQRAIAAgBCACQQN0aikDABAhIAJBAWohAgwBCwsgAEEQaiADIAAoAgQRAAALC38BA38gAacoAiAiBCkDACIBQoCAgIBgWgRAIAAgAacgAhEAAAsgBCkDCCIBQoCAgIBgWgRAIAAgAacgAhEAAAsgBEEYaiEFA0AgBCgCECADSgRAIAUgA0EDdGopAwAiAUKAgICAYFoEQCAAIAGnIAIRAAALIANBAWohAwwBCwsLWQEDfyAAIAGnKAIgIgIpAwAQISAAIAIpAwgQISACQRhqIQQDQCADIAIoAhBORQRAIAAgBCADQQN0aikDABAhIANBAWohAwwBCwsgAEEQaiACIAAoAgQRAAALcgEEfyABpyIDKAIgIQQgAygCJCEFIAMoAigiAwRAIAAgAyACEQAACyAEBEACQCAFRQ0AQQAhAwNAIAMgBCgCPE4NASAFIANBAnRqKAIAIgYEQCAAIAYgAhEAAAsgA0EBaiEDDAALAAsgACAEIAIRAAALC3wBA38gAaciAigCKCIDBEAgACADrUKAgICAcIQQIQsgAigCICIDBEAgAigCJCIEBEBBACECA0AgAiADKAI8TkUEQCAAIAQgAkECdGooAgAQ5QEgAkEBaiECDAELCyAAQRBqIAQgACgCBBEAAAsgACADrUKAgICAYIQQIQsLEgAgAacoAiAiAARAIAAQngMLCx4AIAGnKQMgIgFCgICAgGBaBEAgACABpyACEQAACwsZACAAIAGnIgApAyAQISAAQoCAgIAwNwMgC0QBAn8gAachBANAIAQoAiggA0sEQCAEKAIkIANBA3RqKQMAIgFCgICAgGBaBEAgACABpyACEQAACyADQQFqIQMMAQsLC0YBA38gAachAwNAIAMoAiQhBCACIAMoAihPRQRAIAAgBCACQQN0aikDABAhIAJBAWohAgwBCwsgAEEQaiAEIAAoAgQRAAALEQAgAEEQaiACIAAoAgQRAAAL2wECAX8CfiMAQSBrIgMkACABQQNGBEAgAikDECEEIAIpAwghBQJAIAAgA0EQaiACKQMAEKoFQQBIBEBCgICAgOAAIQQMAQsgACAEIAVBAiADQRBqEBwiBEKAgICAcINCgICAgOAAUQRAIAAoAhAiASkDgAEhBCABQoCAgIAgNwOAASADIAQ3AwggACADKQMYQoCAgIAwQQEgA0EIahAcIQQgACADKQMIEAwLIAAgAykDEBAMIAAgAykDGBAMCyADQSBqJAAgBA8LQZuCAUGo7ABBy/UCQaDtABAAAAuIAQIBfgF/QQAhAkKAgICAMCEBA0ACQCACQQJHBH4gBSACQQN0IgRq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Y29kZV0KfQBjYW5ub3QgbWl4ID8/IHdpdGggJiYgb3IgfHwAcHJveHk6IHByb3BlcnR5IG5vdCBwcmVzZW50IGluIHRhcmdldCB3ZXJlIHJldHVybmVkIGJ5IG5vbiBleHRlbnNpYmxlIHByb3h5AHJldm9rZWQgcHJveHkAUHJveHkAYWRkX3Byb3BlcnR5AHByb3h5OiBjYW5ub3Qgc2V0IHByb3BlcnR5AG5vIHNldHRlciBmb3IgcHJvcGVydHkAdmFsdWUgaGFzIG5vIHByb3BlcnR5AGNvdWxkIG5vdCBkZWxldGUgcHJvcGVydHkAcHJveHk6IGR1cGxpY2F0ZSBwcm9wZXJ0eQBKU19EZWZpbmVBdXRvSW5pdFByb3BlcnR5AGhhc093blByb3BlcnR5AHByb3h5OiBpbmNvbnNpc3RlbnQgZGVsZXRlUHJvcGVydHkAcHJveHk6IGluY29uc2lzdGVudCBkZWZpbmVQcm9wZXJ0eQBKU19EZWZpbmVQcm9wZXJ0eQAhbXItPmVtcHR5AGluZmluaXR5AEluZmluaXR5AG91dCBvZiBtZW1vcnkAdW5rbm93biB1bmljb2RlIGdlbmVyYWwgY2F0ZWdvcnkAR2VuZXJhbF9DYXRlZ29yeQBldmVyeQBhbnkAYXBwbHkAJyVzJyBpcyByZWFkLW9ubHkAZXhwZWN0aW5nIGNhdGNoIG9yIGZpbmFsbHkAc3RpY2t5AGJpZ2ludCBhcmUgZm9yYmlkZGVuIGluIEpTT04uc3RyaW5naWZ5AHN1YmFycmF5AGVtcHR5IGFycmF5AG5vbiBpbnRlZ2VyIGluZGV4IGluIHR5cGVkIGFycmF5AG5lZ2F0aXZlIGluZGV4IGluIHR5cGVkIGFycmF5AG91dC1vZi1ib3VuZCBpbmRleCBpbiB0eXBlZCBhcnJheQBjYW5ub3QgY3JlYXRlIG51bWVyaWMgaW5kZXggaW4gdHlwZWQgYXJyYXkAaXNBcnJheQBUeXBlZEFycmF5AGdldERheQBnZXRVVENEYXkAZ3JvdXBCeQBtLT5kZnNfYW5jZXN0b3JfaW5kZXggPD0gbS0+ZGZzX2luZGV4AGpzX2dldF9hdG9tX2luZGV4AGludmFsaWQgYXJyYXkgaW5kZXgASlNfQXRvbUlzQXJyYXlJbmRleABmaW5kTGFzdEluZGV4AGZpbmRJbmRleABpbnZhbGlkIGV4cG9ydCBzeW50YXgAaW52YWxpZCBhc3NpZ25tZW50IHN5bnRheABtYXgAXHUlMDR4AGludmFsaWQgb3Bjb2RlOiBwYz0ldSBvcGNvZGU9MHglMDJ4AC0rICAgMFgweAAtMFgrMFggMFgtMHgrMHggMHgAbGluZSB0ZXJtaW5hdG9yIG5vdCBhbGxvd2VkIGFmdGVyIHRocm93AGJmX3BvdwBub3cAaW50ZWdlciBvdmVyZmxvdwBzdGFjayBvdmVyZmxvdwBtdXN0IGJlIGNhbGxlZCB3aXRoIG5ldwBpc1ZpZXcARGF0YVZpZXcAcmF3ACV1AGNsYXNzIGRlY2xhcmF0aW9ucyBjYW4ndCBhcHBlYXIgaW4gc2luZ2xlLXN0YXRlbWVudCBjb250ZXh0AGZ1bmN0aW9uIGRlY2xhcmF0aW9ucyBjYW4ndCBhcHBlYXIgaW4gc2luZ2xlLXN0YXRlbWVudCBjb250ZXh0AGxleGljYWwgZGVjbGFyYXRpb25zIGNhbid0IGFwcGVhciBpbiBzaW5nbGUtc3RhdGVtZW50IGNvbnRleHQAZHVwbGljYXRlIGFyZ3VtZW50IG5hbWVzIG5vdCBhbGxvd2VkIGluIHRoaXMgY29udGV4dABkdXBsaWNhdGUgcGFyYW1ldGVyIG5hbWVzIG5vdCBhbGxvd2VkIGluIHRoaXMgY29udGV4dABpbXBvcnQubWV0YSBub3Qgc3VwcG9ydGVkIGluIHRoaXMgY29udGV4dABKU19GcmVlQ29udGV4dABKU0NvbnRleHQAanNfbWFwX2l0ZXJhdG9yX25leHQAanNfZ2VuZXJhdG9yX25leHQAanNfYXN5bmNfZ2VuZXJhdG9yX3Jlc3VtZV9uZXh0AHVuZXhwZWN0ZWQgZW5kIG9mIGlucHV0AHR0AGV4cG9ydGVkIHZhcmlhYmxlICclcycgZG9lcyBub3QgZXhpc3QAcHJpdmF0ZSBjbGFzcyBmaWVsZCAnJXMnIGRvZXMgbm90IGV4aXN0AHRlc3QAYXNzaWdubWVudCByZXN0IHByb3BlcnR5IG11c3QgYmUgbGFzdABwdmFsID09IGxhc3QAZmluZExhc3QAYmZfc3FydABzb3J0AGNicnQAdHJpbVN0YXJ0AHBhZFN0YXJ0AHVua25vd24gdW5pY29kZSBzY3JpcHQAU2NyaXB0AGh5cG90AGZyZWVfemVyb19yZWZjb3VudABzdHJfaW5kZXggPT0gbnVtX2tleXNfY291bnQgKyBzdHJfa2V5c19jb3VudABudW1faW5kZXggPT0gbnVtX2tleXNfY291bnQAc3ltX2luZGV4ID09IGF0b21fY291bnQAbGFiZWwgPj0gMCAmJiBsYWJlbCA8IHMtPmxhYmVsX2NvdW50AGxhYjEgPj0gMCAmJiBsYWIxIDwgcy0+bGFiZWxfY291bnQAdmFsIDwgcy0+Y2FwdHVyZV9jb3VudAB2YWwyIDwgcy0+Y2FwdHVyZV9jb3VudABpbnZhbGlkIHJlcGVhdCBjb3VudABpbnZhbGlkIHJlcGV0aXRpb24gY291bnQAZm9udABpbnZhbGlkIGNvZGUgcG9pbnQAZnJvbUNvZGVQb2ludABpbnZhbGlkIGhpbnQAY2Fubm90IGNvbnZlcnQgTmFOIG9yIEluZmluaXR5IHRvIGJpZ2ludABjYW5ub3QgY29udmVydCB0byBiaWdpbnQAYm90aCBvcGVyYW5kcyBtdXN0IGJlIGJpZ2ludABub3QgYSBiaWdpbnQAcHJpdmF0ZSBtZXRob2QgaXMgYWxyZWFkeSBwcmVzZW50AGVuY29kZVVSSUNvbXBvbmVudABkZWNvZGVVUklDb21wb25lbnQAdW5leHBlY3RlZCBlbmQgb2YgY29tbWVudABpbnZhbGlkIHN3aXRjaCBzdGF0ZW1lbnQAQmlnSW50AHBhcnNlSW50AGR1cGxpY2F0ZSBkZWZhdWx0AHNwbGl0AGV4cGVjdGluZyBoZXggZGlnaXQAdHJpbVJpZ2h0AHJlZHVjZVJpZ2h0AHVuc2hpZnQAdHJpbUxlZnQAaW52YWxpZCBvZmZzZXQAaW52YWxpZCBieXRlT2Zmc2V0AGdldFRpbWV6b25lT2Zmc2V0AHJlc29sdmluZyBmdW5jdGlvbiBhbHJlYWR5IHNldABwcm94eTogaW5jb25zaXN0ZW50IHNldABmaW5kX2p1bXBfdGFyZ2V0AGV4cGVjdGluZyB0YXJnZXQAaW52YWxpZCBkZXN0cnVjdHVyaW5nIHRhcmdldABwcm94eTogaW5jb25zaXN0ZW50IGdldABXZWFrU2V0AGNvbnN0cnVjdABKU19GcmVlQXRvbVN0cnVjdAB1c2Ugc3RyaWN0AFJlZmxlY3QAcmVqZWN0AG5vdCBhbiBBc3luY0dlbmVyYXRvciBvYmplY3QAY2Fubm90IGNvbnZlcnQgdG8gb2JqZWN0AGludmFsaWQgYnJhbmQgb24gb2JqZWN0AG9wZXJhbmQgJ3Byb3RvdHlwZScgcHJvcGVydHkgaXMgbm90IGFuIG9iamVjdABpdGVyYXRvciBtdXN0IHJldHVybiBhbiBvYmplY3QAbm90IGEgRGF0ZSBvYmplY3QAbm90IGEgb2JqZWN0AEpTT2JqZWN0AHBhcnNlRmxvYXQAZmxhdABub3RoaW5nIHRvIHJlcGVhdABjb25jYXQAY29kZVBvaW50QXQAY2hhckF0AGNoYXJDb2RlQXQAa2V5cwBwcm94eTogdGFyZ2V0IHByb3BlcnR5IG11c3QgYmUgcHJlc2VudCBpbiBwcm94eSBvd25LZXlzACAgZmFzdCBhcnJheXMAZXhwb3J0ICclcycgaW4gbW9kdWxlICclcycgaXMgYW1iaWd1b3VzAHByaXZhdGUgY2xhc3MgZmllbGQgJyVzJyBhbHJlYWR5IGV4aXN0cwB0b28gbWFueSBhcmd1bWVudHMAVG9vIG1hbnkgY2FsbCBhcmd1bWVudHMAdG9vIG1hbnkgZWxlbWVudHMAICBlbGVtZW50cwBpbnZhbGlkIG51bWJlciBvZiBkaWdpdHMAYmluYXJ5IG9iamVjdHMAaW52YWxpZCBwcm9wZXJ0eSBhY2Nlc3MAanNfb3BfZGVmaW5lX2NsYXNzAGZkLT5ieXRlX2NvZGUuYnVmW2RlZmluZV9jbGFzc19wb3NdID09IE9QX2RlZmluZV9jbGFzcwBfX2dldENsYXNzAHNldEhvdXJzAGdldEhvdXJzAHNldFVUQ0hvdXJzAGdldFVUQ0hvdXJzAGdhdGhlcl9hdmFpbGFibGVfYW5jZXN0b3JzAGdldE93blByb3BlcnR5RGVzY3JpcHRvcnMAd2l0aFJlc29sdmVycwB0b28gbWFueSBpbWJyaWNhdGVkIHF1YW50aWZpZXJzAHVuaWNvZGVfcHJvcF9vcHMAYWNvcwBmb3IgYXdhaXQgaXMgb25seSB2YWxpZCBpbiBhc3luY2hyb25vdXMgZnVuY3Rpb25zAG5ldy50YXJnZXQgb25seSBhbGxvd2VkIHdpdGhpbiBmdW5jdGlvbnMAYnl0ZWNvZGUgZnVuY3Rpb25zAEMgZnVuY3Rpb25zAHByb3h5OiBpbmNvbnNpc3RlbnQgcHJldmVudEV4dGVuc2lvbnMAU2NyaXB0X0V4dGVuc2lvbnMAYXRvbXMAcHJveHk6IHByb3BlcnRpZXMgbXVzdCBiZSBzdHJpbmdzIG9yIHN5bWJvbHMAZ2V0T3duUHJvcGVydHlTeW1ib2xzAHJlc29sdmVfbGFiZWxzAEpTX0V2YWxUaGlzAHN0cmluZ3MAaW52YWxpZCBkZXNjcmlwdG9yIGZsYWdzAGludmFsaWQgcmVndWxhciBleHByZXNzaW9uIGZsYWdzAHZhbHVlcwBzZXRNaW51dGVzAGdldE1pbnV0ZXMAc2V0VVRDTWludXRlcwBnZXRVVENNaW51dGVzAHRvbyBtYW55IGNhcHR1cmVzACAgc2hhcGVzAGdldE93blByb3BlcnR5TmFtZXMAZ2NfZnJlZV9jeWNsZXMAYWRkX2V2YWxfdmFyaWFibGVzAHJlc29sdmVfdmFyaWFibGVzAHRvbyBtYW55IGxvY2FsIHZhcmlhYmxlcwB0b28gbWFueSBjbG9zdXJlIHZhcmlhYmxlcwBjb21wYWN0X3Byb3BlcnRpZXMAICBwcm9wZXJ0aWVzAGRlZmluZVByb3BlcnRpZXMAZW50cmllcwBmcm9tRW50cmllcwB0b28gbWFueSByYW5nZXMAaW5jbHVkZXMAaGFzSW5kaWNlcwBzZXRNaWxsaXNlY29uZHMAZ2V0TWlsbGlzZWNvbmRzAHNldFVUQ01pbGxpc2Vjb25kcwBnZXRVVENNaWxsaXNlY29uZHMAc2V0U2Vjb25kcwBnZXRTZWNvbmRzAHNldFVUQ1NlY29uZHMAZ2V0VVRDU2Vjb25kcwBpdGFsaWNzAGFicwBwcm94eTogaW5jb25zaXN0ZW50IGhhcwAlLipzACAoJXMAc2V0ICVzAGdldCAlcwAgICAgYXQgJXMAbm90IGEgJXMAdW5zdXBwb3J0ZWQga2V5d29yZDogJXMAc3Vic3RyAHByb3h5OiBpbmNvbnNpc3RlbnQgZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yAHN1cGVyKCkgaXMgb25seSB2YWxpZCBpbiBhIGRlcml2ZWQgY2xhc3MgY29uc3RydWN0b3IAcGFyZW50IGNsYXNzIG11c3QgYmUgY29uc3RydWN0b3IAbm90IGEgY29uc3RydWN0b3IAQXJyYXkgSXRlcmF0b3IAU2V0IEl0ZXJhdG9yAE1hcCBJdGVyYXRvcgBSZWdFeHAgU3RyaW5nIEl0ZXJhdG9yAG5vdCBhbiBBc3luYy1mcm9tLVN5bmMgSXRlcmF0b3IAY2Fubm90IGludm9rZSBhIHJ1bm5pbmcgZ2VuZXJhdG9yAG5vdCBhIGdlbmVyYXRvcgBBc3luY0dlbmVyYXRvcgBzeW50YXggZXJyb3IAU3ludGF4RXJyb3IARXZhbEVycm9yAEludGVybmFsRXJyb3IAQWdncmVnYXRlRXJyb3IAVHlwZUVycm9yAFJhbmdlRXJyb3IAUmVmZXJlbmNlRXJyb3IAVVJJRXJyb3IAZmxvb3IAZm9udGNvbG9yAGFuY2hvcgBmb3IAa2V5Rm9yAGV4cGVjdGluZyBzdXJyb2dhdGUgcGFpcgBhIGRlY2xhcmF0aW9uIGluIHRoZSBoZWFkIG9mIGEgZm9yLSVzIGxvb3AgY2FuJ3QgaGF2ZSBhbiBpbml0aWFsaXplcgAnYXJndW1lbnRzJyBpZGVudGlmaWVyIGlzIG5vdCBhbGxvd2VkIGluIGNsYXNzIGZpZWxkIGluaXRpYWxpemVyAGludmFsaWQgbnVtYmVyIG9mIGFyZ3VtZW50cyBmb3IgZ2V0dGVyIG9yIHNldHRlcgBpbnZhbGlkIHNldHRlcgBpbnZhbGlkIGdldHRlcgBmaWx0ZXIAbWlzc2luZyBmb3JtYWwgcGFyYW1ldGVyACJ1c2Ugc3RyaWN0IiBub3QgYWxsb3dlZCBpbiBmdW5jdGlvbiB3aXRoIGRlZmF1bHQgb3IgZGVzdHJ1Y3R1cmluZyBwYXJhbWV0ZXIAaW52YWxpZCBjaGFyYWN0ZXIAdW5leHBlY3RlZCBjaGFyYWN0ZXIAcHJpdmF0ZSBjbGFzcyBmaWVsZCBmb3JiaWRkZW4gYWZ0ZXIgc3VwZXIAaW52YWxpZCByZWRlZmluaXRpb24gb2YgbGV4aWNhbCBpZGVudGlmaWVyACdsZXQnIGlzIG5vdCBhIHZhbGlkIGxleGljYWwgaWRlbnRpZmllcgBpbnZhbGlkIHJlZGVmaW5pdGlvbiBvZiBnbG9iYWwgaWRlbnRpZmllcgB5aWVsZCBpcyBhIHJlc2VydmVkIGlkZW50aWZpZXIAJyVzJyBpcyBhIHJlc2VydmVkIGlkZW50aWZpZXIAb3RoZXIAYXRvbTFfaXNfaW50ZWdlciAmJiBhdG9tMl9pc19pbnRlZ2VyAGNhbm5vdCBjb252ZXJ0IHRvIGJpZ2ludDogbm90IGFuIGludGVnZXIAaXNJbnRlZ2VyAGlzU2FmZUludGVnZXIAYnVmZmVyAFNoYXJlZEFycmF5QnVmZmVyAGNhbm5vdCB1c2UgaWRlbnRpY2FsIEFycmF5QnVmZmVyAGNhbm5vdCBjb252ZXJ0IGJpZ2ludCB0byBudW1iZXIAY2Fubm90IGNvbnZlcnQgc3ltYm9sIHRvIG51bWJlcgBub3QgYSBudW1iZXIAbGluZU51bWJlcgBtYWxmb3JtZWQgdW5pY29kZSBjaGFyAGNsZWFyAHNldFllYXIAZ2V0WWVhcgBzZXRGdWxsWWVhcgBnZXRGdWxsWWVhcgBzZXRVVENGdWxsWWVhcgBnZXRVVENGdWxsWWVhcgBxICE9IHIAdW5leHBlY3RlZCBsaW5lIHRlcm1pbmF0b3IgaW4gcmVnZXhwAHVuZXhwZWN0ZWQgZW5kIG9mIHJlZ2V4cABSZWdFeHAAc3VwAGludmFsaWQgZ3JvdXAAcG9wAGNvbnRpbnVlIG11c3QgYmUgaW5zaWRlIGxvb3AAYmZfbG9naWNfb3AAZHVtcABudW1fa2V5c19jbXAAdXNlIHN0cmlwAG1hcABmbGF0TWFwAFdlYWtNYXAAZXhwZWN0aW5nICd7JyBhZnRlciBccABsb2cxcABkaXZpc2lvbiBieSB6ZXJvADBvAGhhc093bgByZXR1cm4AcHJvbWlzZSBzZWxmIHJlc29sdXRpb24Ab3V0IG9mIG1lbW9yeSBpbiByZWdleHAgZXhlY3V0aW9uAGRlc2NyaXB0aW9uACFtLT5ldmFsX2hhc19leGNlcHRpb24AIW1vZHVsZS0+ZXZhbF9oYXNfZXhjZXB0aW9uAHByb3h5OiBkZWZpbmVQcm9wZXJ0eSBleGNlcHRpb24AanNfYXN5bmNfZ2VuZXJhdG9yX3Jlc29sdmVfZnVuY3Rpb24AanNfY3JlYXRlX2Z1bmN0aW9uAHNldC9hZGQgaXMgbm90IGEgZnVuY3Rpb24AcmV0dXJuIG5vdCBpbiBhIGZ1bmN0aW9uAEFzeW5jR2VuZXJhdG9yRnVuY3Rpb24AY2FsbEV4dGVybmFsRnVuY3Rpb24AQXN5bmNGdW5jdGlvbgBqc19pbm5lcl9tb2R1bGVfZXZhbHVhdGlvbgAhbS0+YXN5bmNfZXZhbHVhdGlvbgBtb2R1bGUtPmFzeW5jX2V2YWx1YXRpb24AaW52YWxpZCBvcGVyYXRpb24AdW5zdXBwb3J0ZWQgb3BlcmF0aW9uAGF3YWl0IGluIGRlZmF1bHQgZXhwcmVzc2lvbgB5aWVsZCBpbiBkZWZhdWx0IGV4cHJlc3Npb24AaW52YWxpZCBkZWNpbWFsIGVzY2FwZSBpbiByZWd1bGFyIGV4cHJlc3Npb24AYmFjayByZWZlcmVuY2Ugb3V0IG9mIHJhbmdlIGluIHJlZ3VsYXIgZXhwcmVzc2lvbgBpbnZhbGlkIGVzY2FwZSBzZXF1ZW5jZSBpbiByZWd1bGFyIGV4cHJlc3Npb24AZXhwZWN0ZWQgJ29mJyBvciAnaW4nIGluIGZvciBjb250cm9sIGV4cHJlc3Npb24AdG9vIGNvbXBsaWNhdGVkIGRlc3RydWN0dXJpbmcgZXhwcmVzc2lvbgBleHBlY3RlZCAnfScgYWZ0ZXIgdGVtcGxhdGUgZXhwcmVzc2lvbgB0b1ByZWNpc2lvbgBhc2luAGpvaW4AbWluAGNvcHlXaXRoaW4AdGVtcGxhdGUgbGl0ZXJhbCBjYW5ub3QgYXBwZWFyIGluIGFuIG9wdGlvbmFsIGNoYWluAGNpcmN1bGFyIHByb3RvdHlwZSBjaGFpbgBhc3NpZ24AIXktPnNpZ24AaXNGcm96ZW4AKHBvcyArIGxlbikgPD0gYmNfYnVmX2xlbgB1bmV4cGVjdGVkIGVsbGlwc2lzIHRva2VuAHRoZW4Ac2V0dGVyIGlzIGZvcmJpZGRlbgBudWxsIG9yIHVuZGVmaW5lZCBhcmUgZm9yYmlkZGVuAGF0YW4AbmFuAG5vdCBhIGJvb2xlYW4AQm9vbGVhbgBnY19zY2FuAGJhZCBub3JtYWxpemF0aW9uIGZvcm0ASlNfTmV3U3ltYm9sRnJvbUF0b20AZnJvbQByYW5kb20AdHJpbQBiZl9kaXZyZW0AbS0+Y3ljbGVfcm9vdCA9PSBtAGltdWwAbm90IGEgc3ltYm9sAFN5bWJvbABSZWdFeHAgZXhlYyBtZXRob2QgbXVzdCByZXR1cm4gYW4gb2JqZWN0IG9yIG51bGwAcGFyZW50IHByb3RvdHlwZSBtdXN0IGJlIGFuIG9iamVjdCBvciBudWxsAGNhbm5vdCBzZXQgcHJvcGVydHkgJyVzJyBvZiBudWxsAGNhbm5vdCByZWFkIHByb3BlcnR5ICclcycgb2YgbnVsbABOdWxsAGZpbGwAbmV3IEFycmF5QnVmZmVyIGlzIHRvbyBzbWFsbABUeXBlZEFycmF5IGxlbmd0aCBpcyB0b28gc21hbGwAY2FsbABkb3RBbGwAbWF0Y2hBbGwAcmVwbGFjZUFsbABjZWlsAHVwZGF0ZV9sYWJlbABiY19idWZbcG9zXSA9PSBPUF9sYWJlbABldmFsAGludmFsaWQgYmlnaW50IGxpdGVyYWwAaW52YWxpZCBudW1iZXIgbGl0ZXJhbABtYWxmb3JtZWQgZXNjYXBlIHNlcXVlbmNlIGluIHN0cmluZyBsaXRlcmFsAGJmX2V4cF9pbnRlcm5hbABiZl9sb2dfaW50ZXJuYWwAYmZfZnRvYV9pbnRlcm5hbABKU19TZXRQcm9wZXJ0eUludGVybmFsAEpTX0dldE93blByb3BlcnR5TmFtZXNJbnRlcm5hbABfX0pTX0V2YWxJbnRlcm5hbAB0b0V4cG9uZW50aWFsAHNlYWwAZ2xvYmFsAGJsaW5rAHJldHVybiBpbiBhIHN0YXRpYyBpbml0aWFsaXplciBibG9jawBzdGFjawBscmVfZXhlY19iYWNrdHJhY2sAcy0+aXNfd2VhawBiZl9wb3dfdWkAc2V0TW9udGgAZ2V0TW9udGgAc2V0VVRDTW9udGgAZ2V0VVRDTW9udGgAaW52YWxpZCBrZXl3b3JkOiB3aXRoAHN0YXJ0c1dpdGgAZW5kc1dpdGgAcHJvcCA9PSBKU19BVE9NX2xlbmd0aABpbnZhbGlkIGFycmF5IGxlbmd0aABpbnZhbGlkIGFycmF5IGJ1ZmZlciBsZW5ndGgAaW52YWxpZCBsZW5ndGgAaW52YWxpZCBieXRlTGVuZ3RoAE1hdGgAcHVzaABhY29zaABKU19SZXNpemVBdG9tSGFzaABhc2luaABhdGFuaABicmVhayBtdXN0IGJlIGluc2lkZSBsb29wIG9yIHN3aXRjaABtYXRjaABuaXBfY2F0Y2gAc2VhcmNoAGZvckVhY2gAYmZfbG9nAEFycmF5IHRvbyBsb25nAHN0cmluZyB0b28gbG9uZwBBcnJheSBsb28gbG9uZwBzdWJzdHJpbmcAY2Fubm90IGNvbnZlcnQgc3ltYm9sIHRvIHN0cmluZwB1bmV4cGVjdGVkIGVuZCBvZiBzdHJpbmcAbm90IGEgc3RyaW5nAGludmFsaWQgY2hhcmFjdGVyIGluIGEgSlNPTiBzdHJpbmcAdG9TdHJpbmcAdG9EYXRlU3RyaW5nAHRvTG9jYWxlRGF0ZVN0cmluZwB0b1RpbWVTdHJpbmcAdG9Mb2NhbGVUaW1lU3RyaW5nAHRvTG9jYWxlU3RyaW5nAHRvR01UU3RyaW5nAEpTU3RyaW5nAHRvSVNPU3RyaW5nAHRvVVRDU3RyaW5nAGpzX2lubmVyX21vZHVsZV9saW5raW5nAGR1cGxpY2F0ZSBpbXBvcnQgYmluZGluZwBpbnZhbGlkIGltcG9ydCBiaW5kaW5nAHByb21pc2UgaXMgcGVuZGluZwBiaWcAcmVnZXhwIG11c3QgaGF2ZSB0aGUgJ2cnIGZsYWcAb2YAaW5mAEluZgBkaWZmID09IChpbnQ4X3QpZGlmZgBkaWZmID09IChpbnQxNl90KWRpZmYAaHJlZgBnY19kZWNyZWYAZnJlZV92YXJfcmVmAG9wdGltaXplX3Njb3BlX21ha2VfZ2xvYmFsX3JlZgByZXNldF93ZWFrX3JlZgBkZWxldGVfd2Vha19yZWYAb3B0aW1pemVfc2NvcGVfbWFrZV9yZWYAaW5kZXhPZgBsYXN0SW5kZXhPZgB2YWx1ZU9mAHNldFByb3RvdHlwZU9mAGdldFByb3RvdHlwZU9mAGlzUHJvdG90eXBlT2YAJS4qZgBmb250c2l6ZQBuZXdfc2l6ZSA8PSBzaC0+cHJvcF9zaXplAGRlc2NyIDwgcnQtPmF0b21fc2l6ZQBhdG9tIDwgcnQtPmF0b21fc2l6ZQBjb21wdXRlX3N0YWNrX3NpemUAbiA8IGJ1Zl9zaXplAG5vcm1hbGl6ZQBjcl9yZWdleHBfY2Fub25pY2FsaXplAGZyZWV6ZQByZXNvbHZlAHRvUHJpbWl0aXZlAHB1dF9sdmFsdWUAdW5rbm93biB1bmljb2RlIHByb3BlcnR5IHZhbHVlAHJlc3QgZWxlbWVudCBjYW5ub3QgaGF2ZSBhIGRlZmF1bHQgdmFsdWUAaW52YWxpZCByZXQgdmFsdWUAX19KU19BdG9tVG9WYWx1ZQBfX3F1b3RlAGlzRmluaXRlAGRlbGV0ZQBjcmVhdGUAc2V0RGF0ZQBnZXREYXRlAHNldFVUQ0RhdGUAZ2V0VVRDRGF0ZQBJbnZhbGlkIERhdGUAcmV2ZXJzZQBwYXJzZQBwcm94eSBwcmV2ZW50RXh0ZW5zaW9ucyBoYW5kbGVyIHJldHVybmVkIGZhbHNlAG1vZHVsZSBuYW1lc3BhY2UgcHJvcGVydGllcyBoYXZlIHdyaXRhYmxlID0gZmFsc2UAUHJvbWlzZQB0b0xvd2VyQ2FzZQB0b0xvY2FsZUxvd2VyQ2FzZQB0b1VwcGVyQ2FzZQB0b0xvY2FsZVVwcGVyQ2FzZQBpZ25vcmVDYXNlAGxvY2FsZUNvbXBhcmUAcHJveHk6IGluY29uc2lzdGVudCBwcm90b3R5cGUAcHJveHk6IGJhZCBwcm90b3R5cGUAbm90IGEgcHJvdG90eXBlAGludmFsaWQgb2JqZWN0IHR5cGUAdW5lc2NhcGUAbm9uZQByZXN0IGVsZW1lbnQgbXVzdCBiZSB0aGUgbGFzdCBvbmUAbXVsdGlsaW5lACAgcGMybGluZQBhc3luY19mdW5jX3Jlc3VtZQBzb21lAEpTX0ZyZWVSdW50aW1lAEpTUnVudGltZQBzZXRUaW1lAGdldFRpbWUAYXN5bmNfZnVuY19mcmVlX2ZyYW1lAHNldF9vYmplY3RfbmFtZQBleHBlY3RpbmcgcHJvcGVydHkgbmFtZQB1bmtub3duIHVuaWNvZGUgcHJvcGVydHkgbmFtZQBpbnZhbGlkIHByb3BlcnR5IG5hbWUAZHVwbGljYXRlIF9fcHJvdG9fXyBwcm9wZXJ0eSBuYW1lAGludmFsaWQgcmVkZWZpbml0aW9uIG9mIHBhcmFtZXRlciBuYW1lAGV4cGVjdGluZyBncm91cCBuYW1lAGR1cGxpY2F0ZSBncm91cCBuYW1lAGludmFsaWQgZ3JvdXAgbmFtZQBkdXBsaWNhdGUgbGFiZWwgbmFtZQBpbnZhbGlkIGZpcnN0IGNoYXJhY3RlciBvZiBwcml2YXRlIG5hbWUAaW52YWxpZCBsZXhpY2FsIHZhcmlhYmxlIG5hbWUAaW52YWxpZCBtZXRob2QgbmFtZQBleHBlY3RpbmcgZmllbGQgbmFtZQBpbnZhbGlkIGZpZWxkIG5hbWUAY2xhc3Mgc3RhdGVtZW50IHJlcXVpcmVzIGEgbmFtZQBmaWxlTmFtZQBqc19saW5rX21vZHVsZQBqc19ldmFsdWF0ZV9tb2R1bGUAbW9kdWxlLT5jeWNsZV9yb290ID09IG1vZHVsZQBjb21waWxlAG9iamVjdCBpcyBub3QgZXh0ZW5zaWJsZQBwcm94eTogaW5jb25zaXN0ZW50IGlzRXh0ZW5zaWJsZQBjYW5ub3QgaGF2ZSBzZXR0ZXIvZ2V0dGVyIGFuZCB2YWx1ZSBvciB3cml0YWJsZQBwcm9wZXJ0eSBpcyBub3QgY29uZmlndXJhYmxlAHZhbHVlIGlzIG5vdCBpdGVyYWJsZQBwcm9wZXJ0eUlzRW51bWVyYWJsZQBtaXNzaW5nIGluaXRpYWxpemVyIGZvciBjb25zdCB2YXJpYWJsZQBsZXhpY2FsIHZhcmlhYmxlAGludmFsaWQgcmVkZWZpbml0aW9uIG9mIGEgdmFyaWFibGUAcmV2b2NhYmxlAHN0cmlrZQBtcF9kaXZub3JtX2xhcmdlAGludmFsaWQgY2xhc3MgcmFuZ2UAbWVzc2FnZQBpbnZhbGlkIGx2YWx1ZSBpbiBzdHJpY3QgbW9kZQBpbnZhbGlkIHZhcmlhYmxlIG5hbWUgaW4gc3RyaWN0IG1vZGUAY2Fubm90IGRlbGV0ZSBhIGRpcmVjdCByZWZlcmVuY2UgaW4gc3RyaWN0IG1vZGUAb2N0YWwgZXNjYXBlIHNlcXVlbmNlcyBhcmUgbm90IGFsbG93ZWQgaW4gc3RyaWN0IG1vZGUAb2N0YWwgbGl0ZXJhbHMgYXJlIGRlcHJlY2F0ZWQgaW4gc3RyaWN0IG1vZGUAdW5pY29kZQAgIGJ5dGVjb2RlAEpTRnVuY3Rpb25CeXRlY29kZQBza2lwX2RlYWRfY29kZQBpbnZhbGlkIGFyZ3VtZW50IG5hbWUgaW4gc3RyaWN0IGNvZGUAaW52YWxpZCBmdW5jdGlvbiBuYW1lIGluIHN0cmljdCBjb2RlAGludmFsaWQgcmVkZWZpbml0aW9uIG9mIGdsb2JhbCBpZGVudGlmaWVyIGluIG1vZHVsZSBjb2RlAGltcG9ydC5tZXRhIG9ubHkgdmFsaWQgaW4gbW9kdWxlIGNvZGUAZnJvbUNoYXJDb2RlAGludmFsaWQgZm9yIGluL29mIGxlZnQgaGFuZC1zaWRlAGludmFsaWQgYXNzaWdubWVudCBsZWZ0LWhhbmQgc2lkZQByZWR1Y2UAc291cmNlACd0aGlzJyBjYW4gYmUgaW5pdGlhbGl6ZWQgb25seSBvbmNlAHByb3BlcnR5IGNvbnN0cnVjdG9yIGFwcGVhcnMgbW9yZSB0aGFuIG9uY2UAaW52YWxpZCBVVEYtOCBzZXF1ZW5jZQBjaXJjdWxhciByZWZlcmVuY2UAc2xpY2UAc3BsaWNlAHJhY2UAcmVwbGFjZQAlKy4qZQB1bmV4cGVjdGVkICdhd2FpdCcga2V5d29yZAB1bmV4cGVjdGVkICd5aWVsZCcga2V5d29yZABtYXBfZGVjcmVmX3JlY29yZABpdGVyYXRvciBkb2VzIG5vdCBoYXZlIGEgdGhyb3cgbWV0aG9kAG9iamVjdCBuZWVkcyB0b0lTT1N0cmluZyBtZXRob2QAJ3N1cGVyJyBpcyBvbmx5IHZhbGlkIGluIGEgbWV0aG9kAGZyb3VuZABfX2JmX3JvdW5kAGJyZWFrL2NvbnRpbnVlIGxhYmVsIG5vdCBmb3VuZABvdXQgb2YgYm91bmQAZmluZABiaW5kAGludmFsaWQgaW5kZXggZm9yIGFwcGVuZABleHRyYW5lb3VzIGNoYXJhY3RlcnMgYXQgdGhlIGVuZAB1bmV4cGVjdGVkIGRhdGEgYXQgdGhlIGVuZAB1bmV4cGVjdGVkIGVuZABpbnZhbGlkIGluY3JlbWVudC9kZWNyZW1lbnQgb3BlcmFuZABpbnZhbGlkICdpbnN0YW5jZW9mJyByaWdodCBvcGVyYW5kAGludmFsaWQgJ2luJyBvcGVyYW5kAHRyaW1FbmQAcGFkRW5kAGJvbGQAJWxsZABnY19kZWNyZWZfY2hpbGQAcmVzb2x2ZV9zY29wZV9wcml2YXRlX2ZpZWxkAGNhbm5vdCBkZWxldGUgYSBwcml2YXRlIGNsYXNzIGZpZWxkAGV4cGVjdGluZyA8YnJhbmQ+IHByaXZhdGUgZmllbGQAJXMgaXMgbm90IGluaXRpYWxpemVkAGZpeGVkAHRvRml4ZWQAc2V0X29iamVjdF9uYW1lX2NvbXB1dGVkAHJlZ2V4IG5vdCBzdXBwb3J0ZWQAZXZhbCBpcyBub3Qgc3VwcG9ydGVkAFJlZ0V4cCBhcmUgbm90IHN1cHBvcnRlZAB0b1NvcnRlZABpbnRlcnJ1cHRlZAAhcy0+aXNfY29tcGxldGVkACVzIG9iamVjdCBleHBlY3RlZABpZGVudGlmaWVyIGV4cGVjdGVkAGJ5dGVjb2RlIGZ1bmN0aW9uIGV4cGVjdGVkAHN0cmluZyBleHBlY3RlZABmcm9tIGNsYXVzZSBleHBlY3RlZABmdW5jdGlvbiBuYW1lIGV4cGVjdGVkAHZhcmlhYmxlIG5hbWUgZXhwZWN0ZWQAbWV0YSBleHBlY3RlZABqc19hc3luY19tb2R1bGVfZXhlY3V0aW9uX3JlamVjdGVkAGpzX3NldF9tb2R1bGVfZXZhbHVhdGVkAG1lbW9yeSBhbGxvY2F0ZWQAbWVtb3J5IHVzZWQAdG9SZXZlcnNlZABkZXJpdmVkIGNsYXNzIGNvbnN0cnVjdG9yIG11c3QgcmV0dXJuIGFuIG9iamVjdCBvciB1bmRlZmluZWQAY2Fubm90IHNldCBwcm9wZXJ0eSAnJXMnIG9mIHVuZGVmaW5lZABjYW5ub3QgcmVhZCBwcm9wZXJ0eSAnJXMnIG9mIHVuZGVmaW5lZABmbGFncyBtdXN0IGJlIHVuZGVmaW5lZABVbmRlZmluZWQAcHJpdmF0ZSBjbGFzcyBmaWVsZCBpcyBhbHJlYWR5IGRlZmluZWQAJyVzJyBpcyBub3QgZGVmaW5lZABncm91cCBuYW1lIG5vdCBkZWZpbmVkAGlzV2VsbEZvcm1lZAB0b1dlbGxGb3JtZWQAYWxsU2V0dGxlZABqc19hc3luY19tb2R1bGVfZXhlY3V0aW9uX2Z1bGZpbGxlZABjYW5ub3QgYmUgY2FsbGVkAGlzU2VhbGVkACFzaC0+aXNfaGFzaGVkAEFycmF5QnVmZmVyIGlzIGRldGFjaGVkAGpzX2FycmF5X3RvU3BsaWNlZABhZGQAJSswN2QAJTA0ZAAlMDJkJTAyZABwJStkACVjJStkACUwMmQvJTAyZC8lMCpkACUuM3MgJS4zcyAlMDJkICUwKmQAcCVkACVjJWQAOiVkAGludmFsaWQgdGhyb3cgdmFyIHR5cGUgJWQAc2MAanNfZGVmX21hbGxvYwB0cnVuYwBnYwBleGVjAGJmX2ludGVnZXJfdG9fcmFkaXhfcmVjAC90bXAvcXVpY2tqcy9xdWlja2pzLmMAL3RtcC9xdWlja2pzL2xpYnJlZ2V4cC5jAC90bXAvcXVpY2tqcy9saWJiZi5jAC90bXAvcXVpY2tqcy9saWJ1bmljb2RlLmMAc3ViAHByb21pc2VfcmVhY3Rpb25fam9iAGpzX3Byb21pc2VfcmVzb2x2ZV90aGVuYWJsZV9qb2IAMGIAciAhPSBhICYmIHIgIT0gYgBxICE9IGEgJiYgcSAhPSBiAHJ3YQByICE9IGEAX19sb29rdXBTZXR0ZXJfXwBfX2RlZmluZVNldHRlcl9fAF9fbG9va3VwR2V0dGVyX18AX19kZWZpbmVHZXR0ZXJfXwBfX3Byb3RvX18AW1N5bWJvbC5zcGxpdF0AW1N5bWJvbC5zcGVjaWVzXQBbU3ltYm9sLml0ZXJhdG9yXQBbU3ltYm9sLmFzeW5jSXRlcmF0b3JdAFtTeW1ib2wubWF0Y2hBbGxdAFtTeW1ib2wubWF0Y2hdAFtTeW1ib2wuc2VhcmNoXQBbU3ltYm9sLnRvU3RyaW5nVGFnXQBbU3ltYm9sLnRvUHJpbWl0aXZlXQBbdW5zdXBwb3J0ZWQgdHlwZV0AW2Z1bmN0aW9uIGJ5dGVjb2RlXQBbU3ltYm9sLmhhc0luc3RhbmNlXQBbU3ltYm9sLnJlcGxhY2VdAFsAJTAyZDolMDJkOiUwMmQuJTAzZFoAUE9TSVRJVkVfSU5GSU5JVFkATkVHQVRJVkVfSU5GSU5JVFkAcC0+Y2xhc3NfaWQgPT0gSlNfQ0xBU1NfQVJSQVkAc3RhY2tfbGVuIDwgUE9QX1NUQUNLX0xFTl9NQVgALSUwMmQtJTAyZFQASlNfQXRvbUdldFN0clJUAG9wY29kZSA8IFJFT1BfQ09VTlQASlNfVkFMVUVfR0VUX1RBRyhmdW5jX3JldCkgPT0gSlNfVEFHX0lOVABCWVRFU19QRVJfRUxFTUVOVAAlMDJkOiUwMmQ6JTAyZCBHTVQASlNfVkFMVUVfR0VUX1RBRyhzZi0+Y3VyX2Z1bmMpID09IEpTX1RBR19PQkpFQ1QAdmFyX2tpbmQgPT0gSlNfVkFSX1BSSVZBVEVfU0VUVEVSAE1BWF9TQUZFX0lOVEVHRVIATUlOX1NBRkVfSU5URUdFUgBhc1VpbnROAGFzSW50TgBpc05hTgBEYXRlIHZhbHVlIGlzIE5hTgB0b0pTT04ARVBTSUxPTgBwLT5nY19vYmpfdHlwZSA9PSBKU19HQ19PQkpfVFlQRV9KU19PQkpFQ1QgfHwgcC0+Z2Nfb2JqX3R5cGUgPT0gSlNfR0NfT0JKX1RZUEVfRlVOQ1RJT05fQllURUNPREUgfHwgcC0+Z2Nfb2JqX3R5cGUgPT0gSlNfR0NfT0JKX1RZUEVfQVNZTkNfRlVOQ1RJT04ATkFOACUwMmQ6JTAyZDolMDJkICVjTQBzdGFja190b3AgPT0gTlVMTABzLT5sYWJlbF9zbG90c1tsYWJlbF0uZmlyc3RfcmVsb2MgPT0gTlVMTABsYWJlbF9zbG90c1tpXS5maXJzdF9yZWxvYyA9PSBOVUxMAHBycyAhPSBOVUxMAHNmLT5jdXJfc3AgIT0gTlVMTABzZiAhPSBOVUxMAG1yMSAhPSBOVUxMAHZhcl9raW5kICE9IEpTX1ZBUl9OT1JNQUwAYi0+ZnVuY19raW5kID09IEpTX0ZVTkNfTk9STUFMAGVuY29kZVVSSQBkZWNvZGVVUkkAUEkAc3BlY2lhbCA9PSBQVVRfTFZBTFVFX05PS0VFUCB8fCBzcGVjaWFsID09IFBVVF9MVkFMVUVfTk9LRUVQX0RFUFRIAHMtPnN0YXRlID09IEpTX0FTWU5DX0dFTkVSQVRPUl9TVEFURV9FWEVDVVRJTkcAbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0VWQUxVQVRJTkcAbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktJTkcAcHJlYyAhPSBCRl9QUkVDX0lORgBwcmVjMSAhPSBCRl9QUkVDX0lORgAwMTIzNDU2Nzg5QUJDREVGAFNJWkUATUFYX1ZBTFVFAE1JTl9WQUxVRQBOQU1FAGV2YWxfdHlwZSA9PSBKU19FVkFMX1RZUEVfR0xPQkFMIHx8IGV2YWxfdHlwZSA9PSBKU19FVkFMX1RZUEVfTU9EVUxFAExPRzJFAExPRzEwRQBzLT5zdGF0ZSA9PSBKU19BU1lOQ19HRU5FUkFUT1JfU1RBVEVfQVdBSVRJTkdfUkVUVVJOIHx8IHMtPnN0YXRlID09IEpTX0FTWU5DX0dFTkVSQVRPUl9TVEFURV9DT01QTEVURUQAbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfVU5MSU5LRUQgfHwgbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfTElOS0VEIHx8IG0tPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0VWQUxVQVRJTkdfQVNZTkMgfHwgbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVEVEAG0xLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19FVkFMVUFUSU5HIHx8IG0xLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19FVkFMVUFUSU5HX0FTWU5DIHx8IG0xLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19FVkFMVUFURUQAbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktJTkcgfHwgbTEtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktFRCB8fCBtMS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVElOR19BU1lOQyB8fCBtMS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVEVEAG0tPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0xJTktFRABtLT5zdGF0dXMgPT0gSlNfTU9EVUxFX1NUQVRVU19VTkxJTktFRABVVEMAbS0+c3RhdHVzID09IEpTX01PRFVMRV9TVEFUVVNfRVZBTFVBVElOR19BU1lOQwBtb2R1bGUtPnN0YXR1cyA9PSBKU19NT0RVTEVfU1RBVFVTX0VWQUxVQVRJTkdfQVNZTkMAPGlucHV0PgA8aW5pdFNjcmlwdD4APGV2YWxTY3JpcHQ+ADxzZXQ+ADxhbm9ueW1vdXM+ADxjb21tRnVuPgA8Y2FsbEV4dGVybmFsRnVuY3Rpb24+ADxudWxsPgBiaWdpbnQgb3BlcmFuZHMgYXJlIGZvcmJpZGRlbiBmb3IgPj4+ACZxdW90OwBzZXRVaW50OABnZXRVaW50OABzZXRJbnQ4AGdldEludDgAbWFsZm9ybWVkIFVURi04AHJhZGl4IG11c3QgYmUgYmV0d2VlbiAyIGFuZCAzNgBzZXRVaW50MTYAZ2V0VWludDE2AHNldEludDE2AGdldEludDE2AGFyZ2MgPT0gNQBzZXRCaWdVaW50NjQAZ2V0QmlnVWludDY0AHNldEJpZ0ludDY0AGdldEJpZ0ludDY0AHNldEZsb2F0NjQAZ2V0RmxvYXQ2NABhcmdjID09IDMAYXRhbjIAbG9nMgBTUVJUMV8yAFNRUlQyAExOMgBjbHozMgBzZXRVaW50MzIAZ2V0VWludDMyAHNldEludDMyAGdldEludDMyAHNldEZsb2F0MzIAZ2V0RmxvYXQzMgBzdGFja19sZW4gPj0gMgBKU19BdG9tSXNOdW1lcmljSW5kZXgxAGpzX2ZjdnQxAEpTX0NvbXBhY3RCaWdJbnQxAGV4cG0xAHIgIT0gYTEgJiYgciAhPSBiMQBscy0+YWRkciA9PSAtMQBucSA+PSAxAHN0YWNrX2xlbiA+PSAxAHAtPmhlYWRlci5yZWZfY291bnQgPT0gMQBwLT5zaGFwZS0+aGVhZGVyLnJlZl9jb3VudCA9PSAxAHN0YWNrX2xlbiA9PSAxAGpzX2ZyZWVfc2hhcGUwAGxvZzEwAExOMTAAcC0+cmVmX2NvdW50ID4gMAB2YXJfcmVmLT5oZWFkZXIucmVmX2NvdW50ID4gMABtLT5wZW5kaW5nX2FzeW5jX2RlcGVuZGVuY2llcyA+IDAAc3RhY2tfc2l6ZSA+IDAAY3Bvb2xfaWR4ID49IDAAcnQtPmF0b21fY291bnQgPj0gMABscy0+cmVmX2NvdW50ID49IDAAcy0+aXNfZXZhbCB8fCBzLT5jbG9zdXJlX3Zhcl9jb3VudCA9PSAwAHAtPnJlZl9jb3VudCA9PSAwAGN0eC0+aGVhZGVyLnJlZl9jb3VudCA9PSAwAHNoLT5oZWFkZXIucmVmX2NvdW50ID09IDAAcC0+bWFyayA9PSAwAChwci0+dS5pbml0LnJlYWxtX2FuZF9pZCAmIDMpID09IDAAKG5ld19oYXNoX3NpemUgJiAobmV3X2hhc2hfc2l6ZSAtIDEpKSA9PSAwAGkgIT0gMABzaXplICE9IDAAXiRcLiorPygpW117fXwvADwvADAuAG1pc3NpbmcgYmluZGluZyBwYXR0ZXJuLi4uAGJpZ2ludCBhcmd1bWVudCB3aXRoIHVuYXJ5ICsAYXN5bmMgZnVuY3Rpb24gKgAKfSkAbGlzdF9lbXB0eSgmcnQtPmdjX29ial9saXN0KQBqID09IChzaC0+cHJvcF9jb3VudCAtIHNoLT5kZWxldGVkX3Byb3BfY291bnQpACFfX0pTX0F0b21Jc1RhZ2dlZEludChkZXNjcikAIWF0b21faXNfZnJlZShwKQAobnVsbCkAIChuYXRpdmUpAGpzX2NsYXNzX2hhc19ieXRlY29kZShwLT5jbGFzc19pZCkAbmlwX2NhdGNoOiBubyBjYXRjaCBvcCAocGM9JWQpAGluY29uc2lzdGVudCBjYXRjaCBwb3NpdGlvbjogJWQgJWQgKHBjPSVkKQBpbmNvbnNpc3RlbnQgc3RhY2sgc2l6ZTogJWQgJWQgKHBjPSVkKQBieXRlY29kZSBidWZmZXIgb3ZlcmZsb3cgKG9wPSVkLCBwYz0lZCkAc3RhY2sgb3ZlcmZsb3cgKG9wPSVkLCBwYz0lZCkAc3RhY2sgdW5kZXJmbG93IChvcD0lZCwgcGM9JWQpAGludmFsaWQgb3Bjb2RlIChvcD0lZCwgcGM9JWQpACg/OikAaWR4IDwgY291bnRvZihjYXNlX2NvbnZfdGFibGUxKQBubyBmdW5jdGlvbiBmaWxlbmFtZSBmb3IgaW1wb3J0KCkALV8uIX4qJygpACBhbm9ueW1vdXMoAFN5bWJvbCgAZXhwZWN0aW5nICd9JwBjbGFzcyBjb25zdHJ1Y3RvcnMgbXVzdCBiZSBpbnZva2VkIHdpdGggJ25ldycAZXhwZWN0aW5nICdhcycAdW5leHBlY3RlZCB0b2tlbiBpbiBleHByZXNzaW9uOiAnJS4qcycAdW5leHBlY3RlZCB0b2tlbjogJyUuKnMnAHJlZGVjbGFyYXRpb24gb2YgJyVzJwBkdXBsaWNhdGUgZXhwb3J0ZWQgbmFtZSAnJXMnAGNpcmN1bGFyIHJlZmVyZW5jZSB3aGVuIGxvb2tpbmcgZm9yIGV4cG9ydCAnJXMnIGluIG1vZHVsZSAnJXMnAENvdWxkIG5vdCBmaW5kIGV4cG9ydCAnJXMnIGluIG1vZHVsZSAnJXMnAGNvdWxkIG5vdCBsb2FkIG1vZHVsZSAnJXMnAGNhbm5vdCBkZWZpbmUgdmFyaWFibGUgJyVzJwB1bmRlZmluZWQgcHJpdmF0ZSBmaWVsZCAnJXMnAHVuc3VwcG9ydGVkIHJlZmVyZW5jZSB0byAnc3VwZXInAGludmFsaWQgdXNlIG9mICdzdXBlcicAJ2ZvciBhd2FpdCcgbG9vcCBzaG91bGQgYmUgdXNlZCB3aXRoICdvZicAJ2ZvciBvZicgZXhwcmVzc2lvbiBjYW5ub3Qgc3RhcnQgd2l0aCAnYXN5bmMnAGV4cGVjdGluZyAnJWMnAHVucGFyZW50aGVzaXplZCB1bmFyeSBleHByZXNzaW9uIGNhbid0IGFwcGVhciBvbiB0aGUgbGVmdC1oYW5kIHNpZGUgb2YgJyoqJwBpbnZhbGlkIHVzZSBvZiAnaW1wb3J0KCknAGV4cGVjdGluZyAlJQA7Lz86QCY9KyQsIwA9IgBzZXQgAGdldCAAW29iamVjdCAAYXN5bmMgZnVuY3Rpb24gAGJvdW5kIAAlLjNzLCAlMDJkICUuM3MgJTAqZCAAYXN5bmMgADogACAgICAgICAgICAACikgewoACkpTT2JqZWN0IGNsYXNzZXMKACUtMjBzICU4cyAlOHMKACAgJTVkICAlMi4wZCAlcwoAICAlM3UgKyAlLTJ1ICAlcwoAICBtYWxsb2NfdXNhYmxlX3NpemUgdW5hdmFpbGFibGUKACUtMjBzICU4bGxkCgAlLTIwcyAlOGxsZCAlOGxsZAoAX19KU19GcmVlVmFsdWU6IHVua25vd24gdGFnPSVkCgAlLTIwcyAlOGxsZCAlOGxsZCAgKCUwLjFmIHBlciBmYXN0IGFycmF5KQoAJS0yMHMgJThsbGQgJThsbGQgICglMC4xZiBwZXIgb2JqZWN0KQoAJS0yMHMgJThsbGQgJThsbGQgICglMC4xZiBwZXIgZnVuY3Rpb24pCgAlLTIwcyAlOGxsZCAlOGxsZCAgKCUwLjFmIHBlciBhdG9tKQoAJS0yMHMgJThsbGQgJThsbGQgICglMC4xZiBwZXIgYmxvY2spCgAlLTIwcyAlOGxsZCAlOGxsZCAgKCVkIG92ZXJoZWFkLCAlMC4xZiBhdmVyYWdlIHNsYWNrKQoAJS0yMHMgJThsbGQgJThsbGQgICglMC4xZiBwZXIgc3RyaW5nKQoAJS0yMHMgJThsbGQgJThsbGQgICglMC4xZiBwZXIgc2hhcGUpCgBRdWlja0pTIG1lbW9yeSB1c2FnZSAtLSAxLjAuMCB2ZXJzaW9uLCAlZC1iaXQsIG1hbGxvYyBsaW1pdDogJWxsZAoKAAAAAJIAQfyWAQsNkwAAAEwAAABNAAAAlABBlJcBCz2VAAAATgAAAE8AAACWAAAATgAAAE8AAACXAAAATgAAAE8AAACYAAAATgAAAE8AAACZAAAATAAAAE0AAACZAEHclwELDZwAAABOAAAATwAAAJIAQfSXAQv9Ap0AAABQAAAAUQAAAJ0AAABSAAAAUwAAAJ0AAABUAAAAVQAAAJ0AAABWAAAAVwAAAJ4AAABSAAAAUwAAAJ8AAABYAAAAWQAAAKAAAABaAAAAAAAAAKEAAABbAAAAAAAAAKIAAABbAAAAAAAAAKMAAABcAAAAXQAAAKQAAABcAAAAXQAAAKUAAABcAAAAXQAAAKYAAABcAAAAXQAAAKcAAABcAAAAXQAAAKgAAABcAAAAXQAAAKkAAABcAAAAXQAAAKoAAABcAAAAXQAAAKsAAABcAAAAXQAAAKwAAABcAAAAXQAAAK0AAABcAAAAXQAAAK4AAABcAAAAXQAAAK8AAABOAAAATwAAALAAAABeAAAAXwAAALEAAABeAAAAXwAAALIAAABeAAAAXwAAALMAAABeAAAAXwAAALQAAABgAAAAYQAAALUAAABgAAAAYQAAALYAAABiAAAAYwAAALcAAABiAAAAYwAAALgAAABkAAAAZQAAALkAAABmAAAAZwBBgJsBCwFoAEGQmwELDWkAAAAAAAAAagAAAGsAQbybAQsBbABByJsBCw1tAAAAbgAAAG8AAABwAEHgmwELtxvsKQAAQAEAACUKAAD4AAAAuA8AADAAAABaJQAAEAAAADkuAABYAAAAkgAAAHEAAAByAAAAcwAAAHQAAAB1AAAAdgAAAHcAAAB4AAAAeQAAAFBdAAAQXgAAwF4AABBfAABQXwAAcF8AAAwLBQQCAgAAuwAAAHoAAAB7AAAAvAAAAHwAAAB9AAAAvQAAAHwAAAB9AAAAvgAAAFIAAABTAAAAvwAAAH4AAAB/AAAAwAAAAH4AAAB/AAAALwAAAIAAAACBAAAAwQAAAFIAAABTAAAAwgAAAIIAAACDAAAAAAAAAOkWAAAaFwAAJRcAAN0WAAAQFwAANBcAAPMWAAABFwAAY29weVdpdGhpbgBlbnRyaWVzAGZpbGwAZmluZABmaW5kSW5kZXgAZmluZExhc3QAZmluZExhc3RJbmRleABmbGF0AGZsYXRNYXAAaW5jbHVkZXMAa2V5cwB0b1JldmVyc2VkAHRvU29ydGVkAHRvU3BsaWNlZAB2YWx1ZXMAAAAAAAEBAgIDAwIDAAAAAAAAbnVsbABmYWxzZQB0cnVlAGlmAGVsc2UAcmV0dXJuAHZhcgB0aGlzAGRlbGV0ZQB2b2lkAHR5cGVvZgBuZXcAaW4AaW5zdGFuY2VvZgBkbwB3aGlsZQBmb3IAYnJlYWsAY29udGludWUAc3dpdGNoAGNhc2UAZGVmYXVsdAB0aHJvdwB0cnkAY2F0Y2gAZmluYWxseQBmdW5jdGlvbgBkZWJ1Z2dlcgB3aXRoAGNsYXNzAGNvbnN0AGVudW0AZXhwb3J0AGV4dGVuZHMAaW1wb3J0AHN1cGVyAGltcGxlbWVudHMAaW50ZXJmYWNlAGxldABwYWNrYWdlAHByaXZhdGUAcHJvdGVjdGVkAHB1YmxpYwBzdGF0aWMAeWllbGQAYXdhaXQAAGxlbmd0aABmaWxlTmFtZQBsaW5lTnVtYmVyAG1lc3NhZ2UAY2F1c2UAZXJyb3JzAHN0YWNrAG5hbWUAdG9TdHJpbmcAdG9Mb2NhbGVTdHJpbmcAdmFsdWVPZgBldmFsAHByb3RvdHlwZQBjb25zdHJ1Y3RvcgBjb25maWd1cmFibGUAd3JpdGFibGUAZW51bWVyYWJsZQB2YWx1ZQBnZXQAc2V0AG9mAF9fcHJvdG9fXwB1bmRlZmluZWQAbnVtYmVyAGJvb2xlYW4Ac3RyaW5nAG9iamVjdABzeW1ib2wAaW50ZWdlcgB1bmtub3duAGFyZ3VtZW50cwBjYWxsZWUAY2FsbGVyADxldmFsPgA8cmV0PgA8dmFyPgA8YXJnX3Zhcj4APHdpdGg+AGxhc3RJbmRleAB0YXJnZXQAaW5kZXgAaW5wdXQAZGVmaW5lUHJvcGVydGllcwBhcHBseQBqb2luAGNvbmNhdABzcGxpdABjb25zdHJ1Y3QAZ2V0UHJvdG90eXBlT2YAc2V0UHJvdG90eXBlT2YAaXNFeHRlbnNpYmxlAHByZXZlbnRFeHRlbnNpb25zAGhhcwBkZWxldGVQcm9wZXJ0eQBkZWZpbmVQcm9wZXJ0eQBnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IAb3duS2V5cwBhZGQAZG9uZQBuZXh0AHZhbHVlcwBzb3VyY2UAZmxhZ3MAZ2xvYmFsAHVuaWNvZGUAcmF3AG5ldy50YXJnZXQAdGhpcy5hY3RpdmVfZnVuYwA8aG9tZV9vYmplY3Q+ADxjb21wdXRlZF9maWVsZD4APHN0YXRpY19jb21wdXRlZF9maWVsZD4APGNsYXNzX2ZpZWxkc19pbml0PgA8YnJhbmQ+ACNjb25zdHJ1Y3RvcgBhcwBmcm9tAG1ldGEAKmRlZmF1bHQqACoATW9kdWxlAHRoZW4AcmVzb2x2ZQByZWplY3QAcHJvbWlzZQBwcm94eQByZXZva2UAYXN5bmMAZXhlYwBncm91cHMAaW5kaWNlcwBzdGF0dXMAcmVhc29uAGdsb2JhbFRoaXMAYmlnaW50AG5vdC1lcXVhbAB0aW1lZC1vdXQAb2sAdG9KU09OAE9iamVjdABBcnJheQBFcnJvcgBOdW1iZXIAU3RyaW5nAEJvb2xlYW4AU3ltYm9sAEFyZ3VtZW50cwBNYXRoAEpTT04ARGF0ZQBGdW5jdGlvbgBHZW5lcmF0b3JGdW5jdGlvbgBGb3JJbkl0ZXJhdG9yAFJlZ0V4cABBcnJheUJ1ZmZlcgBTaGFyZWRBcnJheUJ1ZmZlcgBVaW50OENsYW1wZWRBcnJheQBJbnQ4QXJyYXkAVWludDhBcnJheQBJbnQxNkFycmF5AFVpbnQxNkFycmF5AEludDMyQXJyYXkAVWludDMyQXJyYXkAQmlnSW50NjRBcnJheQBCaWdVaW50NjRBcnJheQBGbG9hdDMyQXJyYXkARmxvYXQ2NEFycmF5AERhdGFWaWV3AEJpZ0ludABNYXAAU2V0AFdlYWtNYXAAV2Vha1NldABNYXAgSXRlcmF0b3IAU2V0IEl0ZXJhdG9yAEFycmF5IEl0ZXJhdG9yAFN0cmluZyBJdGVyYXRvcgBSZWdFeHAgU3RyaW5nIEl0ZXJhdG9yAEdlbmVyYXRvcgBQcm94eQBQcm9taXNlAFByb21pc2VSZXNvbHZlRnVuY3Rpb24AUHJvbWlzZVJlamVjdEZ1bmN0aW9uAEFzeW5jRnVuY3Rpb24AQXN5bmNGdW5jdGlvblJlc29sdmUAQXN5bmNGdW5jdGlvblJlamVjdABBc3luY0dlbmVyYXRvckZ1bmN0aW9uAEFzeW5jR2VuZXJhdG9yAEV2YWxFcnJvcgBSYW5nZUVycm9yAFJlZmVyZW5jZUVycm9yAFN5bnRheEVycm9yAFR5cGVFcnJvcgBVUklFcnJvcgBJbnRlcm5hbEVycm9yADxicmFuZD4AU3ltYm9sLnRvUHJpbWl0aXZlAFN5bWJvbC5pdGVyYXRvcgBTeW1ib2wubWF0Y2gAU3ltYm9sLm1hdGNoQWxsAFN5bWJvbC5yZXBsYWNlAFN5bWJvbC5zZWFyY2gAU3ltYm9sLnNwbGl0AFN5bWJvbC50b1N0cmluZ1RhZwBTeW1ib2wuaXNDb25jYXRTcHJlYWRhYmxlAFN5bWJvbC5oYXNJbnN0YW5jZQBTeW1ib2wuc3BlY2llcwBTeW1ib2wudW5zY29wYWJsZXMAU3ltYm9sLmFzeW5jSXRlcmF0b3IAAAAAAAEAAAAFAAEUBQABFQUAARUFAAEXBQABFwEAAQABAAEAAQABAAEAAQABAAEAAQABAAIAAQUDAAEKAQEAAAECAQABAwIAAQECAAECAwABAgQAAQMGAAECAwABAwQAAQQFAAEDAwABBAQAAQUFAAECAgABBAQAAQMDAAEDAwABBAQAAQUFAAMCAQ0DAQENAwEADQMCAQ0DAgANAwABDQMDAQoBAQAAAQAAAAEBAgABAAAAAQICAAECAAABAQAAAQEAAAYAABgFAQEPAwIBCgECAQABAQEAAQEBAAUAARcFAAEXBQABFwUBABcFAQAXBQIAFwECAwABAwAABgAAGAYAABgGAQAYBQEBFwUBAhcFAgAXAQIBAAEDAAABAwEAAQIBAAECAgABAwAAAQMBAAEEAAAFAgEXBQEBFwECAgABAgEAAQICAAEDAgABAwIAAgMDBQYCARgCAwEFBgICGAYDAxgDAAEQAwEAEAMBARAD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BOOAQJ+GiIlBY4C8jUHxjUPVhuw0iVKViWwFBUDvAEGAowILhBP6BgBwCQDwCkBXDADwDWDHDyDqF0AFGwBBIAAMqIA3qiBQ/iA6DSF0EQFaFCFEGYFaHaH1aiFF0kGv4iHwAQ4AQWRsYW0sQWRsbQBBaG9tLEFob20AQW5hdG9saWFuX0hpZXJvZ2x5cGhzLEhsdXcAQXJhYmljLEFyYWIAQXJtZW5pYW4sQXJtbgBBdmVzdGFuLEF2c3QAQmFsaW5lc2UsQmFsaQBCYW11bSxCYW11AEJhc3NhX1ZhaCxCYXNzAEJhdGFrLEJhdGsAQmVuZ2FsaSxCZW5nAEJoYWlrc3VraSxCaGtzAEJvcG9tb2ZvLEJvcG8AQnJhaG1pLEJyYWgAQnJhaWxsZSxCcmFpAEJ1Z2luZXNlLEJ1Z2kAQnVoaWQsQnVoZABDYW5hZGlhbl9BYm9yaWdpbmFsLENhbnMAQ2FyaWFuLENhcmkAQ2F1Y2FzaWFuX0FsYmFuaWFuLEFnaGIAQ2hha21hLENha20AQ2hhbSxDaGFtAENoZXJva2VlLENoZXIAQ2hvcmFzbWlhbixDaHJzAENvbW1vbixaeXl5AENvcHRpYyxDb3B0LFFhYWMAQ3VuZWlmb3JtLFhzdXgAQ3lwcmlvdCxDcHJ0AEN5cmlsbGljLEN5cmwAQ3lwcm9fTWlub2FuLENwbW4ARGVzZXJldCxEc3J0AERldmFuYWdhcmksRGV2YQBEaXZlc19Ba3VydSxEaWFrAERvZ3JhLERvZ3IARHVwbG95YW4sRHVwbABFZ3lwdGlhbl9IaWVyb2dseXBocyxFZ3lwAEVsYmFzYW4sRWxiYQBFbHltYWljLEVseW0ARXRoaW9waWMsRXRoaQBHZW9yZ2lhbixHZW9yAEdsYWdvbGl0aWMsR2xhZwBHb3RoaWMsR290aABHcmFudGhhLEdyYW4AR3JlZWssR3JlawBHdWphcmF0aSxHdWpyAEd1bmphbGFfR29uZGksR29uZwBHdXJtdWtoaSxHdXJ1AEhhbixIYW5pAEhhbmd1bCxIYW5nAEhhbmlmaV9Sb2hpbmd5YSxSb2hnAEhhbnVub28sSGFubwBIYXRyYW4sSGF0cgBIZWJyZXcsSGVicgBIaXJhZ2FuYSxIaXJhAEltcGVyaWFsX0FyYW1haWMsQXJtaQBJbmhlcml0ZWQsWmluaCxRYWFpAEluc2NyaXB0aW9uYWxfUGFobGF2aSxQaGxpAEluc2NyaXB0aW9uYWxfUGFydGhpYW4sUHJ0aQBKYXZhbmVzZSxKYXZhAEthaXRoaSxLdGhpAEthbm5hZGEsS25kYQBLYXRha2FuYSxLYW5hAEthd2ksS2F3aQBLYXlhaF9MaSxLYWxpAEtoYXJvc2h0aGksS2hhcgBLaG1lcixLaG1yAEtob2praSxLaG9qAEtoaXRhbl9TbWFsbF9TY3JpcHQsS2l0cwBLaHVkYXdhZGksU2luZABMYW8sTGFvbwBMYXRpbixMYXRuAExlcGNoYSxMZXBjAExpbWJ1LExpbWIATGluZWFyX0EsTGluYQBMaW5lYXJfQixMaW5iAExpc3UsTGlzdQBMeWNpYW4sTHljaQBMeWRpYW4sTHlkaQBNYWthc2FyLE1ha2EATWFoYWphbmksTWFoagBNYWxheWFsYW0sTWx5bQBNYW5kYWljLE1hbmQATWFuaWNoYWVhbixNYW5pAE1hcmNoZW4sTWFyYwBNYXNhcmFtX0dvbmRpLEdvbm0ATWVkZWZhaWRyaW4sTWVkZgBNZWV0ZWlfTWF5ZWssTXRlaQBNZW5kZV9LaWtha3VpLE1lbmQATWVyb2l0aWNfQ3Vyc2l2ZSxNZXJjAE1lcm9pdGljX0hpZXJvZ2x5cGhzLE1lcm8ATWlhbyxQbHJkAE1vZGksTW9kaQBNb25nb2xpYW4sTW9uZwBNcm8sTXJvbwBNdWx0YW5pLE11bHQATXlhbm1hcixNeW1yAE5hYmF0YWVhbixOYmF0AE5hZ19NdW5kYXJpLE5hZ20ATmFuZGluYWdhcmksTmFuZABOZXdfVGFpX0x1ZSxUYWx1AE5ld2EsTmV3YQBOa28sTmtvbwBOdXNodSxOc2h1AE55aWFrZW5nX1B1YWNodWVfSG1vbmcsSG1ucABPZ2hhbSxPZ2FtAE9sX0NoaWtpLE9sY2sAT2xkX0h1bmdhcmlhbixIdW5nAE9sZF9JdGFsaWMsSXRhbABPbGRfTm9ydGhfQXJhYmlhbixOYXJiAE9sZF9QZXJtaWMsUGVybQBPbGRfUGVyc2lhbixYcGVvAE9sZF9Tb2dkaWFuLFNvZ28AT2xkX1NvdXRoX0FyYWJpYW4sU2FyYgBPbGRfVHVya2ljLE9ya2gAT2xkX1V5Z2h1cixPdWdyAE9yaXlhLE9yeWEAT3NhZ2UsT3NnZQBPc21hbnlhLE9zbWEAUGFoYXdoX0htb25nLEhtbmcAUGFsbXlyZW5lLFBhbG0AUGF1X0Npbl9IYXUsUGF1YwBQaGFnc19QYSxQaGFnAFBob2VuaWNpYW4sUGhueABQc2FsdGVyX1BhaGxhdmksUGhscABSZWphbmcsUmpuZwBSdW5pYyxSdW5yAFNhbWFyaXRhbixTYW1yAFNhdXJhc2h0cmEsU2F1cgBTaGFyYWRhLFNocmQAU2hhdmlhbixTaGF3AFNpZGRoYW0sU2lkZABTaWduV3JpdGluZyxTZ253AFNpbmhhbGEsU2luaABTb2dkaWFuLFNvZ2QAU29yYV9Tb21wZW5nLFNvcmEAU295b21ibyxTb3lvAFN1bmRhbmVzZSxTdW5kAFN5bG90aV9OYWdyaSxTeWxvAFN5cmlhYyxTeXJjAFRhZ2Fsb2csVGdsZwBUYWdiYW53YSxUYWdiAFRhaV9MZSxUYWxlAFRhaV9UaGFtLExhbmEAVGFpX1ZpZXQsVGF2dABUYWtyaSxUYWtyAFRhbWlsLFRhbWwAVGFuZ3V0LFRhbmcAVGVsdWd1LFRlbHUAVGhhYW5hLFRoYWEAVGhhaSxUaGFpAFRpYmV0YW4sVGlidABUaWZpbmFnaCxUZm5nAFRpcmh1dGEsVGlyaABUYW5nc2EsVG5zYQBUb3RvLFRvdG8AVWdhcml0aWMsVWdhcgBWYWksVmFpaQBWaXRoa3VxaSxWaXRoAFdhbmNobyxXY2hvAFdhcmFuZ19DaXRpLFdhcmEAWWV6aWRpLFllemkAWWksWWlpaQBaYW5hYmF6YXJfU3F1YXJlLFphbmIAQZC2AgvyIMAZmUeFGZlHrhmAR44ZgEeEGZZHgBmeR4AZ4WBHphmER4QZgQ2TGeAPOIMsgBmCLAGDLIAZgCwDgCyAGYAsgBmCLACALACTLAC+LI0ajyzgJB2BOOBIHQClBQGxBQGCBQC2NQeaNQOFNQqEBIAZhQSAGY0EgBmCBIAZnwSAGYkEijiZBIA44AsEgBmhBI2LALuLAYKLrwSxlQ26ZgGCZq1/AY5/AJtSAYBSAIqLBJ4EAIEEBckEgBmcBNAggziOIIEZmSCDCwCHCwGBCwGVCwCGCwCACwKDCwGI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eW1ib2wAU28sT3RoZXJfU3ltYm9sAFBjLENvbm5lY3Rvcl9QdW5jdHVhdGlvbgBQZCxEYXNoX1B1bmN0dWF0aW9uAFBzLE9wZW5fUHVuY3R1YXRpb24AUGUsQ2xvc2VfUHVuY3R1YXRpb24AUGksSW5pdGlhbF9QdW5jdHVhdGlvbgBQZixGaW5hbF9QdW5jdHVhdGlvbgBQbyxPdGhlcl9QdW5jdHVhdGlvbgBacyxTcGFjZV9TZXBhcmF0b3IAWmwsTGluZV9TZXBhcmF0b3IAWnAsUGFyYWdyYXBoX1NlcGFyYXRvcgBDYyxDb250cm9sLGNudHJsAENmLEZvcm1hdABDcyxTdXJyb2dhdGUAQ28sUHJpdmF0ZV9Vc2UATEMsQ2FzZWRfTGV0dGVyAEwsTGV0dGVyAE0sTWFyayxDb21iaW5pbmdfTWFyawBOLE51bWJlcgBTLFN5bWJvbABQLFB1bmN0dWF0aW9uLHB1bmN0AFosU2VwYXJhdG9yAEMsT3RoZXIAQZDXAguwCA4AAAA+AAAAwAEAAAAOAAAA8AAAAAB/AAAAgAMBAAA8QVNDSUlfSGV4X0RpZ2l0LEFIZXgAQmlkaV9Db250cm9sLEJpZGlfQwBEYXNoAERlcHJlY2F0ZWQsRGVwAERpYWNyaXRpYyxEaWEARXh0ZW5kZXIsRXh0AEhleF9EaWdpdCxIZXgASURTX0JpbmFyeV9PcGVyYXRvcixJRFNCAElEU19UcmluYXJ5X09wZXJhdG9yLElEU1QASWRlb2dyYXBoaWMsSWRlbwBKb2luX0NvbnRyb2wsSm9pbl9DAExvZ2ljYWxfT3JkZXJfRXhjZXB0aW9uLExPRQBOb25jaGFyYWN0ZXJfQ29kZV9Qb2ludCxOQ2hhcgBQYXR0ZXJuX1N5bnRheCxQYXRfU3luAFBhdHRlcm5fV2hpdGVfU3BhY2UsUGF0X1dTAFF1b3RhdGlvbl9NYXJrLFFNYXJrAFJhZGljYWwAUmVnaW9uYWxfSW5kaWNhdG9yLFJJAFNlbnRlbmNlX1Rlcm1pbmFsLFNUZXJtAFNvZnRfRG90dGVkLFNEAFRlcm1pbmFsX1B1bmN0dWF0aW9uLFRlcm0AVW5pZmllZF9JZGVvZ3JhcGgsVUlkZW8AVmFyaWF0aW9uX1NlbGVjdG9yLFZTAFdoaXRlX1NwYWNlLHNwYWNlAEJpZGlfTWlycm9yZWQsQmlkaV9NAEVtb2ppAEVtb2ppX0NvbXBvbmVudCxFQ29tcABFbW9qaV9Nb2RpZmllcixFTW9kAEVtb2ppX01vZGlmaWVyX0Jhc2UsRUJhc2UARW1vamlfUHJlc2VudGF0aW9uLEVQcmVzAEV4dGVuZGVkX1BpY3RvZ3JhcGhpYyxFeHRQaWN0AERlZmF1bHRfSWdub3JhYmxlX0NvZGVfUG9pbnQsREkASURfU3RhcnQsSURTAENhc2VfSWdub3JhYmxlLENJAEFTQ0lJAEFscGhhYmV0aWMsQWxwaGEAQW55AEFzc2lnbmVkAENhc2VkAENoYW5nZXNfV2hlbl9DYXNlZm9sZGVkLENXQ0YAQ2hhbmdlc19XaGVuX0Nhc2VtYXBwZWQsQ1dDTQBDaGFuZ2VzX1doZW5fTG93ZXJjYXNlZCxDV0wAQ2hhbmdlc19XaGVuX05GS0NfQ2FzZWZvbGRlZCxDV0tDRgBDaGFuZ2VzX1doZW5fVGl0bGVjYXNlZCxDV1QAQ2hhbmdlc19XaGVuX1VwcGVyY2FzZWQsQ1dVAEdyYXBoZW1lX0Jhc2UsR3JfQmFzZQBHcmFwaGVtZV9FeHRlbmQsR3JfRXh0AElEX0NvbnRpbnVlLElEQwBMb3dlcmNhc2UsTG93ZXIATWF0aABVcHBlcmNhc2UsVXBwZXIAWElEX0NvbnRpbnVlLFhJREMAWElEX1N0YXJ0LFhJRFMAQdDfAgvyAgEAnAYHTQMEEACPCwAAEQAIAFNKUQBSAFMAOlRVAFdZP11cAEZhY0JkAGYAaABqAGwAbgAAQAAAAAAaAJMAACA1ACcAIQAkIioAE2ttACYkJxQWGBscPh4/Hzk9IiFBHkAlJSYoICpILEMuSzBMMkRCmQAAlY99foOEEoCCdncSe6N8eHmKkpimoIUAmqGTdTOVAI4AdJmYl5YAAJ4AnAChoBUuLzC0tU+qqRIUHiEiIio0NaanNh9JAACXAVraHTYFAMTDxsXIx8rJzMvE1UXWQtdG2M7Q0tTa2e72/g4HD4CfACGAo+0AwEDGYOfb5pnAAAAGYNwp/RUSBhb43QYVEoQIxhb/3wPAQABGYN7gbTc4ORUUFxYAGhkcGwBft2VERwBPYk5QAABIAAAAo6SlAAAAAAC2AABaAEcAW1ZYYF5waW9OADtnuAAAAABFqIqLjKusWFivlLBvsl1cX15hYGZnaGliY2Rla2ptbG9ucXAAQdDiAgtzmQMIAwEDpQMTAwADQgORA5cDqQNGAEkATABTAGkABwO8Ak4ASgAMAzUFUgVIADEDVABXAAoDWQBBAL4CCB+AHygfkB9oH6Afuh+GA7Mfyh+JA8MfoQP6H48D8x9EBUYFOwVOBT0FuANiBEqmYB7JA2sA5QBB0OMCC+YggQAoAJcAKgCBgCoAl8ArABWBLACXAC0AgUAtAJcALgAVQS4AmQEvABYgMABCCEAAQopEAEIESgCWAEwAF4FMAEICTQBCQ04AL8FPAELDUAC/QFIAQgNTAEIJVQBCCFoAlgBeAEJDXgCBwF8AQgFoAELBawCFAXEAF8NxAERIcwBEg3cAQoN5AL4CewCXQXwAQgF9AEQEfgBCDoAAQoGHAESHiQCDBKwAFwO2AIMCuAAUAtAAlgDRAIAA3QCXgN4AgIDfAJcA4QA+QeEAgMDhAL4E4gCug+oAroLyAK0B9AAuwfQAA0H1AAMD/ACBQP4APgIAAb7AAQG+AQMBvkAGAb5ADgE+AhQBvsAVAb4BFwFEgR0BREEwAUQCNAFEgTUBRIM2AUSDOAFEhjoBRAE+AYXAYQGugogBL0KdAYQBsAGEwLQBhEBKAoRATAKEAE0CLgRWAi7BcgIgAXcChMB3AoTAjAKEgI0CrkGWAoSAlwKEANICLsHSAiAB1wKEAOUCroHyAoQAEgOEADADIsExAy6BMgOugVIDhIB2A64BdwOFwIwDhcCsAy8BtwOBAMMDhMDQA4RA0wOEgNQDhMDVA4QA1wOEQNoDhMDcAy5B3QOFwN0DhADeA4VA3gOEQOADhMDkA4RA5wOEgOgDhMDpA4QA6wOEQO4DhIAJBIEAPwSEhMEGhIDEBoTBzgYgAdAGhMDQBoMDSwcfxEwHgxdPB4EAXgeD0mYHRB2AB0KJjgdEGJMHQg2fBxaCpQeFgKYHvsCmB0QNqAdEoK4HIgHAB0SDwAciAcIHRIPCByIBxAdEgsQHIgHGB0SCxgc+EcgHRILQByIB0gdEgtIHIgHUB0SD1Ac+TNYHgEDcB76A3AeAwNwHvgDdB4BA3Qe+gN0HgMDdB74A3geAQN4HvoDeB4DA3ge+AN8HgEDfByAI4AcgCOQHIAjoB74F7AeAwO4HvgDvB5dA7weAgO8HF8HvBz5E8AeAQPIHvoDyB4DA8ge+A/MHgMD0B66C9QeAwPYHPkP3B4DA+AeuA/kHgMD6Bz4B+wcCgfsHvoP8B4BA/ge+gP4HgMD+B74A/weAQP8Hl4D/Bx4BAAiVhAAIgUAECJfABQiBAAkIl0AJCJmACQiBwAsIhcAMCLEADQiFgA0IscANCJcBDwiXwREIs8AVCIHAFwiVBRwIgcAeCBUCHwgfBSAIg4UiCBVEJQiXACoIGQFACIGAQAi/wEAIGUFBCIHAQQi/QEIILYVCCIFARQiXgEUIlUJGCJcASAiZQEgIl4BICIEASQiAgEkIgQBKCAKBSgiVBEsIH0JNCIFATgiZwE4IgwJPCJVCUQgZAVQIm4BUCBnGVAiXwFcIgQBYCJdAWAiZgFgIl8BYCIEAWQiXQFkImYBZCJvAWQiXAFoIgUBaCJeAWgiZwFoIlQJbCJdAXAiZgFwIl8BcCIEAXQiXQF0ImYBdCJvAXQiXAF4IgUBeCJeAXgiZwF4IFQJfCJlAYgg+gWYIvoBrCL5Bcwi+AIEIvkCCCL4Agwi+AYkIhQCLCLFAiwiFwIsIsQCMCL5AkAi+AJEIvsGRCL4BmAi+QpsIRAGdCEQBnghEAaAIRAGhCEQBogg+AqsIRAK4CCCCuggeQcoInwQYCSNFGgmXwBwJpQQdCStFHwmbwCEJoQQiCSVFJAmZwCYJJQ0nCR+NLQkfDTQJgYA6CbMAgwqZAJ0Kl0CdCpmAnQq+ALcKFQEfC4HAWwuBwKcLgcC8C60EwAutRMILrYTEC4PzxgstheALAx3jCy2I8QuBAAAMg4INDIQLEwyEQhkMIgEcDCLBHAwigR0MIkEeDCIBHwyEACUMI8EmDISAJwyFwCcMhAsrDIRCMQwiATQMIsE0DCKBNQwiQTYMIgE3DIQAPQwgwj0MhIA/DIXAPwwtSkwMH0VRDJ/KUwytFVkMA4dkDEEHgAyJgIMMKcGDDKlBhAyJAIUMKUGFDKnChQyJAIcMj0CHDI2AhwxBEogMAwKRDJkAlAyjRJQMI4OWDC0HmAyvhJsMocKdDLUAnwyzQJ8MhYCfDIMYoAwjQqwMI0WtDJfArwyhBLAMpUGyDJcAswyZQLMMl4CzDJnAswytF7QMhcC/DLMBwAyxwMAMswDBDDFBwQy1wMEMswDCDLFBwgwzAcMMMYHDDIUAxAyxQMQMM4HEDIUAxQy1QMUMt4DFDLXAxQyxAMYMNUHGDLPAxgyxAccMs8DHDLUAyAyzQMgMsYHIDC9CyQwxQcoMtcDKDLEAywyzQMsMtYDLDLHAywwvAcwMtYDMDLPAzAy1AM0MsUDNDLWAzQyFwM0MsQLODLNAzwyxgM8MhcDPDLEB0AyzwNAMsQHRDLXA0QyzANIMhUDSDLWA0gyFwNIMMwHTDLGB0wyzQNQMhYDUDLHA1AyzANUMhUDVDLWA1QyxwNUMIQXWDCWF2AylAtsMmUDcDBeB3AyZAN0Ml0HdDCcB3gyFgt4MicDfDD8E4AyZAOIMm0DiDL+D4gwZQuQMBULlDD9D5gwxwecMhUDoDLGB6AyFQOkMB4HpDIkA6gyXQOoMGYLqDJ2A6wyNwOsMPwjsDAUB8AybgPAMl8HwDJuA8QyZwPEMFwXyDJmA9AwXwfQMGUH1DJfA9QybAPYMmUD2DBeC9gwZgfcMoQT4DCVF+gwlxfwMJUH/DJnA/wwDAacpgQDcKZWB/CkDAf4pAwLXKoFA2iqCFEA+gn9KPoI/aj4CoYo+EAGbPoIvnD6QxbM+lwHAPhnBwD4/QcE+r8LEPoRBxz6tBMg+gUDKPgSDyj6gA8w+oALOPoSAzz4gAdA+IMHQPq6E0T6FwNM+LTHUPq3L9D4vifo+LQL/Pi8vAD+lghc/scAYP68HGT+v/xw/pYE8P69kPT8xIFQ/MZtkPzEBfD+zg3w/sUB+P72Afj+7wH4/swB/PwMFhD+tAYw/FcOMPy1Gjj8DzJE/lcaXP68BnD+FAJ0/L4WdP606oD8vRL0/H2/APx/B1z+tX9g/gQDoPx9P6D8fg/A/H4PyPx+D9D+fgfY/gwf4P4NN4EGRD+dBkoEmRJLAKkQSgUtEEsHSRBLCLkUSgW5FkgBORpKDV3QSw250Hw0AdR+NBnUfDQ11n4MTdR+JFXUfDRp1H40gdRUQJ3WfQy91n0UxdR8NNHUfjTp1lQNBdR9EQ3Wfg0V1H41HdZUHTnWfg1J1H41UdR8NW3UfjWF1Hw1odR+NbnUfDXV1H417dR8NgnUfjYh1Hw2PdR+NlXUfDZx1H42idQMBqXWfCKp1gUCudZ+DrnWBQLB1n4ywdYHAtnUtA7d1n4i4dYHAvHWfA711gcC+dZ8Mv3WBQMV1LYPFdZ8Ix3WBQMt1n4PLdYFAzXWfjM11gcDTdS0D1HWfiNV1gcDZdZ8D2nWBwNt1nwzcdYFA4nUtg+J1nwjkdYFA6HWfg+h1gUDqdZ+M6nWBwPB1LQTxdR+F83UfBfZ1H4X4dR8F+3Ufhf11nwQMeJ9BDnifBQ94A8IReK3QEngDARt4LQKAe61NgXsDQoh7gcCJey1FinsDBI17gYCQewPckXstBaB7rciie4NEqHutyKp7lwBAfCFFQHwlDUR8h4BKfBXBSnwXQUt8Hw1MfBeCUnyZgFN8l8BTfJeBWnyXAGR8LwGAfIGAgHwDFoR8wQSQfAMBlHwfBfx+rAEAvhDRAL6sRwm+EDkNviyHKb4sAi2+kDcuvpD/Sb4QvGm+AAAAACAAAABhAAIABAAGALwDCAAKAAwAFQCVAKUAuQDBAMMAxwDLANEA1wDdAOAA5gD4AAgBCgFzABABEgEUASABLAFEAU0BUwFiAWgBagF2AZIBlAGpAbsBxwHRAdUBuQLXATsA2QHbAbcA4QH8AQwCGAIdAiMCJwKjAzMCPwJCAksCTgJRAl0CYAJpAmwCbwJ1AngCgQKKApwCnwKjAq8CuQLFAskCzQLRAtUC5wLtAvEC9QL5Av0CBQMJAw0DEwMXAxsDIwMnAysDLwM1Az0DQQNJA00DUQMLD1cDWwNfA2MDZwNrA28DcwN5A30DgQOFA4kDjQORA5UDmQOdA6ED3BClA8kDzQPZA90D4QPvA/EDPQRPBJkE8AQCBUoFZAVsBXAFcwWaBfoF/gUHBgsGFAYYBh4GIgYoBo4GlAaYBp4GogarBqwD8watA/YGrgP5Bq8D/AbMA/8GzQMCB84DBQcJBw0HEQeGAzIHNQe5AzcHOweIA1MHiQNWB5ADaweKA3cHsAOJB44DmQefB6MHjAO4B48Duwe0AL4HwAfCBxAgywcuAM0HzwcgANIH1gfbB98H5AfqB/AHIAD2BxIiAQgFCAcIHQglCCcIQwAtCDAIkAE2CDkITgBFCEcITAhOCFEIWgCpA1oAUwhXCGAIaQBiCGUIbwh0CHoIfgiiCEkApAimCKkIVgCrCK0IsAi0CFgAtgi4CLsIwAjCCMUIdgDHCMkIzAjQCHgA0gjUCNcI2wjeCOQI5wjwCPMI9gj5CAIJBgkLCQ8JFAkXCRoJIwksCTsJPglBCUQJRwlKCVYJXAlgCWIJZAloCWoJcAl4CXwJgAmGCYkJjwmRCTAAkwmZCZwJngmhCaQJYS3Na5+fpgmxCbwJxwmVCqEKFQsgACcLMQuNC6ELpQupC60LsQu1C7kLvQvBC8ULIQw1DDkMPQxBDEUMSQxNDFEMVQxZDG8McQxzDKAMvAzcDOQM7Az0DPwMBA0MDRQNIg0uDXoNgg2FDYkNjQ2dDbENtQ28DcINxg0oDiwOMA4yDjYOPA4+DkEOQw5GDncOew6JDo4OlA6cDqMOqQ60Dr4Oxg7KDs8O2Q7dDuQO7A7zDvgOBA8KDxUPGw8iDygPMw89D0UPTA9RD1cPXg9jD2kPcA92D30Pgg+JD40Png+kD6kPrQ+4D74PyQ/QD9YP2g/hD+UP7w/6DwAQBBAJEA8QExAaEB8QIxApEC8QMhA2EDkQPxBFEFkQYRB5EHwQgBCVEKEQsRDDEMsQzxDaEN4Q6hDyEPQQABEFERERQRFJEU0RUxFXEVoRbhFxEXURexF9EYERhBGMEZIRlhGcEaIRqBGrEW+nrxGyEbYRjQK+ERASDhMMFJAUlRRTFWwVchV4FX4VihWWFSsAoRW5Fb0VwRXFFckVzRXhFeUVSRZiFogWjhZMF1IXVxd3F3cYfRgRGdMZdxp/Gp0aohq2GsAaxhraGt8a5RrzGiMbMBs4GzwbUhvJG9sb3RvfG2QxIBwiHCQcJhwoHCocSBx+HMQc0hzXHOAc6Rz7HAQdCR0pHUQdRh1IHUodTB1OHVAdUh1yHXQddh14HXodgR2DHYUdhx2WHZgdmh2cHZ4doB2iHaQdph2oHaodrB2uHbAdsh22HfQDuB0HIrodAiK8HcQd9APGHQciyB0CIsod0h30A9QdByLWHQIi2B3gHfQD4h0HIuQdAiLmHe4d9APwHQci8h0CIvQd/h0AHgIeBB4GHggeCh4MHg4eFh45Hj0eQx5gHi0GaB50HiwGhB70HgAfEx8lHzgfOh8+H0QfSh9MH1AfUh9aH10fXx9lH2cftTBtH8Uf2x/fH+Ef5h8zIEQgRSFVIVshVSJzIwBBwIQDC4ZJIIgghDIzIIEgpzFvMdA0MdAyM9A0QYBBgUGCQYNBiEGKAABDp0WARYFFgkWISYBJgUmCSYgAAE6DT4BPgU+CT4NPiAAAAABVgFWBVYJViFmBAAAAAGGAYYFhgmGDYYhhigAAY6dlgGWBZYJliGmAaYFpgmmIAABug2+Ab4Fvgm+Db4gAAAAAdYB1gXWCdYh5gQAAeYhBhEGGQahDgUOCQ4dDjESMRYRFhkWHRahFjEeCR4ZHh0enSIJJg0mESYZJqEmHSUppakqCS6dMgUynTIxMAABrIGtOgU6nToy8Am5PhE+GT4tSgVKnUoxTgVOCU6dTjFSnVIxVg1WEVYZVilWLVahXglmCWYhagVqHWoxPm1WbRAB9AUQAfgFkAH4BTEpMamxqTkpOam5qQQCMSQCMTwCMVQCM3ACE3ACB3ACM3ACAxACEJgKExgCER4xLjE+o6gGE6wGEtwGMkgKMagCMRFpEemR6R4FOAIDFAIHGAIHYAIFBj0GRRY9FkUmPSZFPj0+RUo9SkVWPVZFTplSmSIxBAIdFAKfWAITVAIRPAIcuAoRZAIRoAGYCagByAHkCewKBAncAeQAghiCHIIogqCCDIItjAmwAcwB4AJUCgIEAk4iBIMUggagAgZEDgZUDgZcDgZkDgQAAAJ8DgQAAAKUDgakDgcoDgQEDmAekB7AAtAC2ALgAygABA7gHxAe+AMQAyAClAw0TAAED0QDRB8YDwAO6A8EDwgMAAJgDtQMVBIAVBIgAAAATBIEGBIgaBIEYBIAjBIYYBIY4BIY1BIA1BIgAAAAzBIFWBIg6BIE4BIBDBIZ0BI8WBIYQBIYQBIgVBIbYBIgWBIgXBIgYBIQYBIgeBIjoBIgtBIgjBIQjBIgjBIsnBIgrBIhlBYIFJwYALAAtIS0ALiMtJwYATSFNoE0jTdUGVAYAAAAAwQZUBtIGVAYoCTwJMAk8CTMJPAkVCQAnAScCJwcnDCcNJxYnGie+CQkACRmhCbwJrwm8CTIKPAo4CjwKFgoAJgEmBiYrCjwKRwtWCz4LCQAJGSELPAuSC9cLvgsIAAkACBlGDFYMvwzVDMYM1QzCDAQACBM+DQgACQAIGdkNyg3KDQ8FEgAPFU0OMg7NDrIOmQ4SABIIQg+3D0wPtw9RD7cPVg+3D1sPtw9AD7UPcQ9yD3EPAANBD7IPgQ+zD4APsw+BD3EPgA+SD7cPnA+3D6EPtw+mD7cPqw+3D5APtQ8lEC4QBRs1GwAAAAAHGzUbAAAAAAkbNRsAAAAACxs1GwAAAAANGzUbERs1GzobNRsAAAAAPBs1Gz4bNRtCGzUbQQDGAEIAAABEAEUAjgFHAE8AIgJQAFIAVABVAFcAYQBQAlECAh1iAGQAZQBZAlsCXAJnAAAAawBtAEsBbwBUAhYdFx1wAHQAdQAdHW8CdgAlHbIDswO0A8YDxwNpAHIAdQB2ALIDswPBA8YDxwNSAmMAVQLwAFwCZgBfAmECZQJoAmkCagJ7HZ0CbQKFHZ8CcQJwAnICcwJ0AnUCeAKCAoMCqwGJAooCHB2LAowCegCQApECkgK4A0EApUIAh0IAo0IAsccAgUQAh0QAo0QAsUQAp0QArRIBgBIBgUUArUUAsCgChkYAh0cAhEgAh0gAo0gAiEgAp0gArkkAsM8AgUsAgUsAo0sAsUwAozYehEyxTK1NgU2HTaNOh06jTrFOrdUAgdUAiEwBgEwBgVAAgVAAh1IAh1IAo1oehFIAsVMAh1MAo1oBh2ABh2Ieh1QAh1QAo1QAsVQArVUApFUAsFUArWgBgWoBiFaDVqNXgFeBV4hXh1ejWIdYiFmHWoJao1qxaLF0iHeKeYphAL4CfwGHQQCjQQCJwgCBwgCAwgCJwgCDoB6CAgGBAgGAAgGJAgGDoB6GRQCjRQCJRQCDygCBygCAygCJygCDuB6CSQCJSQCjTwCjTwCJ1ACB1ACA1ACJ1ACDzB6CoAGBoAGAoAGJoAGDoAGjVQCjVQCJrwGBrwGArwGJrwGDrwGjWQCAWQCjWQCJWQCDsQMTAwAfgAAfgQAfwpEDEwMIH4AIH4EIH8K1AxMDEB+AEB+BlQMTAxgfgBgfgbcDk7cDlCAfgCEfgCAfgSEfgSAfwiEfwpcDk5cDlCgfgCkfgCgfgSkfgSgfwikfwrkDk7kDlDAfgDEfgDAfgTEfgTAfwjEfwpkDk5kDlDgfgDkfgDgfgTkfgTgfwjkfwr8Dk78DlEAfgEAfgZ8DEwNIH4BIH4HFAxMDUB+AUB+BUB/CpQOUAAAAWR+AAAAAWR+BAAAAWR/CyQOTyQOUYB+AYR+AYB+BYR+BYB/CYR/CqQOTqQOUaB+AaR+AaB+BaR+BaB/CaR/CsQOAtQOAtwOAuQOAvwOAxQOAyQOAAB9FAyAfRQNgH0UDsQOGsQOEcB/FsQPFrAPFAAAAsQPCth/FkQOGkQOEkQOAkQPFIJMgkyDCqADCdB/FtwPFrgPFAAAAtwPCxh/FlQOAlwOAlwPFvx+Avx+Bvx/CuQOGuQOEygOAAAO5QspCmQaZBJkA/h+A/h+B/h/CxQOGxQOEywOAAAPBE8EUxULLQqUGpQSlAKEDlKgAgIUDYAB8H8XJA8XOA8UAAADJA8L2H8WfA4CpA4CpA8UglAIgICAgICAgICAgILMuLi4uLjIgMiAyIAAAADUgNSA1IAAAACEhAAAghT8/PyEhPzIgAAAAADBpAAA0NTY3ODkrPSgpbjAAKwASIj0AKAApAAAAYQBlAG8AeABZAmhrbG1ucHN0UnNhL2NhL3OwAENjL29jL3WwAEZIAB8AAAAg3wEBBCROb1BRUlJSU01URUxUTUsAxQBCQwBlRUYATW/QBUZBWMADswOTA6ADESJEZGVpajHQNzHQOTHQMTAx0DMy0DMx0DUy0DUz0DU00DUx0DY10DYx0Dgz0Dg10Dg30Dgx0ElJSUlJSVZWSVZJSVZJSUlJWFhJWElJTENETWlpaWlpaWl2dml2aWl2aWlpaXh4aXhpaWxjZG0w0DOQIbiSIbiUIbjQIbjUIbjSIbgDIrgIIrgLIrgjIrgAAAAlIrgrIisiKyIAAAAuIi4iLiIAAAA8IrhDIrhFIrgAAABIIrg9ALgAAABhIrhNIrg8ALg+ALhkIrhlIrhyIrh2Irh6IriCIriGIriiIrioIripIrirIrh8IriRIriyIjgDCDAxADEAMAAyMCgAMQApACgAMQAwACkAKDIwKTEALgAxADAALgAyMC4oAGEAKQBBAGEAKyIAAAAAOjo9PT09PT3dKrhqVgBOACg2P1mFjKC6P1EAJixDV2yhtsGbUgBeen+dpsHO57ZTyFPjU9dWH1frWAJZClkVWSdZc1lQW4Bb+FsPXCJcOFxuXHFc213lXfFd/l1yXnpef170Xv5eC18TX1BfYV9zX8NfCGI2YktiL2U0ZYdll2WkZbll4GXlZfBmCGcoZyBrYmt5a7Nry2vUa9trD2wUbDRsa3AqcjZyO3I/ckdyWXJbcqxyhHOJc9x05nQYdR91KHUwdYt1knV2dn12rna/du5223fid/N3Onm4eb55dHrLevl6c3z4fDZ/UX+Kf71/AYAMgBKAM4B/gImA44EABxAZKTg8i4+VTYZrhkCITIhjiH6Ji4nSiQCKN4xGjFWMeIydjGSNcI2zjauOyo6bj7CPtY+RkEmRxpHMkdGRd5WAlRyWtpa5luiWUZdel2KXaZfLl+2X85cBmKiY25jfmJaZmZmsmaia2JrfmiWbL5symzybWpvlnHWef56lngAWHigsVFhpbnuWpa3o9/sSMAAAQVNEU0VTSzCZMAAAAABNMJkwAAAAAE8wmTAAAAAAUTCZMAAAAABTMJkwAAAAAFUwmTAAAAAAVzCZMAAAAABZMJkwAAAAAFswmTAAAAAAXTCZMAAAAABfMJkwAAAAAGEwmTBkMJkwAAAAAGYwmTAAAAAAaDCZMG8wmTByMJkwdTCZMHgwmTB7MJkwRjCZMCAAmTCdMJkwiDCKMKswmTAAAAAArTCZMAAAAACvMJkwAAAAALEwmTAAAAAAszCZMAAAAAC1MJkwAAAAALcwmTAAAAAAuTCZMAAAAAC7MJkwAAAAAL0wmTAAAAAAvzCZMAAAAADBMJkwxDCZMAAAAADGMJkwAAAAAMgwmTDPMJkw0jCZMNUwmTDYMJkw2zCZMKYwmTDvMJkw/TCZMLMwyDAAEQABqgKsrQMEBbCxsrO0tRoGBwghCRFhERQRTAABs7S4ur/DxQjJywkKDA4PExUXGBkaGx4iLDM43d5DREVwcXR9foCKjQBOjE4JTttWCk4tTgtOMnVZThlOAU4pWTBXuk4oACkAABECEQMRBREGEQcRCRELEQwRDhEPERARERESESgAABFhESkAKAACEWERKQAoAAURYREpACgACRFhESkAKAALEWERKQAoAA4RYREpACgADBFuESkAKAALEWkRDBFlEasRKQAoAAsRaRESEW4RKQAoACkAAE6MTglO21aUTm1RA05rUV1OQVMIZ2twNGwoZ9GRH1flZSpoCWc+eQ1UeXKhjF15tFLjTnxUZlvjdgFPx4xUU215EU/qgfOBT1V8Xodlj3tQVEUyADEAMwAwAAARAAIDBQYHCQsMDg8QERIAEQBhAmEDYQVhBmEHYQlhC2EMYQ4RYREAEQ5htwBpCxEBYwBpCxFuEQBOjE4JTttWlE5tUQNOa1FdTkFTCGdrcDRsKGfRkR9X5WUqaAlnPnkNVHlyoYxdebRS2Hk3dXNZaZAqUXBT6GwFmBFPmVFjawpOLU4LTuZd81M7U5dbZlvjdgFPx4xUUxxZMwA2ADQAMAA1MDEACGcxADAACGdIZ2VyZ2VWTFREojAAAgQGCAkLDQ8RExUXGRsdHyIkJigpKissLTAzNjk8PT4/QEJERkdISUpLTU5PUOROjFShMAEwWycBSjQAAVI5AaIwAFpJpDAAJ08MpDAATx0CBU+oMAARB1QhqDAAVANUpDAGTxUGWDwHAEarMAA+GB0AQj9RrDAAQUcARzKuMKwwrjAAHU6tMAA4PU8BPhNPrTDtMK0wAEADPDOtMABANE8bPq0wAEBCFhuwMAA5MKQwDEU8JE8LRxgASa8wAD5NHrEwAEsIAjoZAksspDARAAtHtTAAPgxHK7AwBzpDALkwAjoIAjoPB0MAtzAQABI0ETwTF6QwKh8kKwAguzAWQQA4DcQwDTgA0DAALBwbojAyABcmSa8wJQA8szAhACA4oTA0AEgiKKMwMgBZJacwLxwQAETVMAAUHq8wKQAQTTzaML0wuDAiExogMwwiOwEiRAAhRAekMDkATyTIMBQjANsw8zDJMBQqABIzIhIzKqQwOgALSaQwOgBHOh8rOkcLtzAnPAAwPK8wMAA+RN8w6jDQMA8aACwb4TCsMKwwNQAcRzVQHD+iMEJaJ0JaSUQAUcMwJwAFKOow6TDUMBcAKNYwFSYAFeww4DCyMDpBFgBBwzAsAAUwALlwMQAwALlwMgAwALlwaFBhZGFBVWJhcm9WcGNkbWQAbQCyAEkAVQBzXhBiLWaMVCdZY2sOZrtsKmgPXxpPPnlwAEFuAEG8A0FtAEFrAEFLAEJNAEJHAEJjYWxrY2FscABGbgBGvANGvANnbQBnawBnSAB6a0h6TUh6R0h6VEh6vAMTIW0AEyFkABMhawATIWYAbW4AbbwDbW0AbWMAbWsAbWMACgpPAApPbQCyAGMACApPCgpQAApQbQCzAGsAbQCzAG0AFSJzAG0AFSJzALIAUGFrUGFNUGFHUGFyYWRyYWTRc3IAYQBkABUicwCyAHAAc24Ac7wDc20Ac3AAVm4AVrwDVm0AVmsAVk0AVnAAV24AV7wDV20AV2sAV00AV2sAqQNNAKkDYS5tLkJxY2NjZEPRa2dDby5kQkd5aGFIUGluS0tLTWt0bG1sbmxvZ2x4bWJtaWxtb2xQSHAubS5QUE1QUnNyU3ZXYlbRbUHRbTEA5WUxADAA5WUyADAA5WUzADAA5WVnYWxKBEwEQ0ZRJgFTASenN6trAlKrSIz0ZsqOyIzRbjJO5VOcn5yfUVnRkYdVSFn2YWl2hX8/hrqH+IiPkAJqG23ZcN5zPYRqkfGZgk51UwRrG3Ithh6eUF3rb82FZInJYtiBH4jKXhdnam38cs6Qhk+3Ud5SxGTTahBy53YBgAaGXIbvjTKXb5v6nYx4f3mgfcmDBJN/ntaK31gEX2B8foBicsp4woz3lthYYlwTatptD28vfTd+S5bSUouA3FHMURx6vn3xg3WWgIvPYgJq/oo5TudbEmCHc3B1F1P7eL9PqV8NTsxseGUifcNTXlgBd0mEqoq6a7CPiGz+YuWCoGNlda5OaVHJUYFo53xvgtKKz5H1UkJUc1nsXsVl/m8qea2VapqXns6em1LGZndrYo90XpBhAGKaZCNvSXGJdMp59H1vgCaP7oQjkEqTF1KjUr1UyHDCiKqKyV71X3tjrms+fHVz5E75Vudbul0cYLJzaXSaf0aANJL2lkiXGJiLT655tJG4luFghk7aUO5bP1yZZQJqznFCdvyEfJCNn4hmLpaJUntn82dBbZxuCXRZdWt4EH1emG1RLmJ4litQGV3qbSqPi19EYRdoh3OGlilSD1RlXBNmTmeoaOVsBnTidXl/z4jhiMyR4pY/U7puHVTQcZh0+oWjllecn56XZ8tt6IHLeiB7knzAcplwWIvATjaDOlIHUqZe02LWfIVbHm20ZjuPTIhNlouJ015AUcBVAAAAAFpYAAB0ZgAAAADeUSpzynY8eV55ZXmPeVaXvny9fwAAEoYAAPiKAAAAADiQ/ZDvmPyYKJm0nd6Qt5auT+dQTVHJUuRSUVOdVQZWaFZAWKhYZFxuXJRgaGGOYfJhT2XiZZFmhWh3bRpuIm9ucStyInSReD55SXlIeVB5VnldeY15jnlAeoF6wHv0fQl+QX5yfwWA7YF5gnmCV4QQiZaJAYs5i9OMCI22jziQ45b/lzuYdWDuQhiCAiZOtVFoUYBPRVGAUcdS+lKdVVVVmVXiVVpYs1hEWVRZYlooW9Je2V5pX61f2GBOYQhhjmFgYfJhNGLEYxxkUmRWZXRmF2cbZ1ZneWu6a0Ft227LbiJvHnBucad3NXKvcipzcXQGdTt1HXYfdsp223b0dkp3QHfMeLF6wHt7fFt99H0+fwWAUoPvg3mHQYmGiZaJv4r4isuKAYv+iu2KOYuKiwiNOI9ykJmRdpJ8luOWVpfbl/+XC5g7mBKbnJ9KKEQo1TOdOxhAOUBJUtBc035Dn46fKqACZmZmaWZsZmZpZmZsfwF0cwB0ZQUPEQ8ADwYZEQ8I2QW0BQAAAADyBbcF0AUSAAMECwwNGBrpBcEF6QXCBUn7wQVJ+8IF0AW3BdAFuAXQBbwF2AW8Bd4FvAXgBbwF4wW8BbkFLQMuAy8DMAMxAxwAGAYiBisG0AXcBXEGAAAKCgoKDQ0NDQ8PDw8JCQkJDg4ODggICAgzMzMzNTU1NRMTExMSEhISFRUVFRYWFhYcHBsbHR0XFycnICA4ODg4Pj4+PkJCQkJAQEBASUlKSkpKT09QUFBQTU1NTWFhYmJJBmRkZGR+fn19f38ugoJ8fICAh4eHhwAAJgYAAQABAK8ArwAiACIAoQChAKAAoACiAKIAqgCqAKoAIwAjACPMBgAAAAAmBgAGAAcAHwAjACQCBgIHAggCHwIjAiQEBgQHBAgEHwQjBCQFBgUfBSMFJAYHBh8HBgcfCAYIBwgfDQYNBw0IDR8PBw8fEAYQBxAIEB8RBxEfEh8TBhMfFAYUHxsGGwcbCBsfGyMbJBwHHB8cIxwkHQEdBh0HHQgdHh0fHSMdJB4GHgceCB4fHiMeJB8GHwcfCB8fHyMfJCAGIAcgCCAfICMgJCEGIR8hIyEkJAYkByQIJB8kIyQkCkoLSiNKIABMBlEGUQb/AB8mBgALAAwAHwAgACMAJAILAgwCHwIgAiMCJAQLBAwEHyYGBCAEIwQkBQsFDAUfBSAFIwUkGyMbJBwjHCQdAR0eHR8dIx0kHh8eIx4kHwEfHyALIAwgHyAgICMgJCNKJAskDCQfJCAkIyQkAAYABwAIAB8AIQIGAgcCCAIfAiEEBgQHBAgEHwQhBR8GBwYfBwYHHwgGCB8NBg0HDQgNHw8HDwgPHxAGEAcQCBAfEQcSHxMGEx8UBhQfGwYbBxsIGx8cBxwfHQYdBx0IHR4dHx4GHgceCB4fHiEfBh8HHwgfHyAGIAcgCCAfICEhBiEfIUokBiQHJAgkHyQhAB8AIQIfAiEEHwQhBR8FIQ0fDSEOHw4hHR4dHx4fIB8gISQfJCFABk4GUQYnBhAiECMSIhIjEyITIwwiDCMNIg0jBiIGIwUiBSMHIgcjDiIOIw8iDyMNBQ0GDQcNHg0KDAoOCg8KECIQIxIiEiMTIhMjDCIMIw0iDSMGIgYjBSIFIwciByMOIg4jDyIPIw0FDQYNBw0eDQoMCg4KDwoNBQ0GDQcNHgwgDSAQHgwFDAYMBw0FDQYNBxAeER4AJAAkKgYAAhsAAwIAAwIAAxsABBsAGwIAGwMAGwQCGwMCGwMDGyADGx8JAwIJAgMJAh8JGwMJGwMJGwIJGxsJGxsLAwMLAwMLGxsKAxsKAxsKAiAKGwQKGwQKGxsKGxsMAx8MBBsMBBsNGwMNGwMNGxsNGyAPAhsPGxsPGxsPGx8QGxsQGyAQGx8XBBsXBBsYGwMYGxsaAxsaAyAaAx8aAgIaAgIaBBsaBBsaGwMaGwMbAwIbAxsbAyAbAgMbAhsbBAIbBBsoBh0EBh8dBB8dHR4FHR4FIR4EHR4EHR4EIR4dIh4dISIdHSIdHQAGIgIEIgIEIQIGIgIGIQIdIgIdIQQdIgQFIQQdIQsGIQ0FIgwFIg4FIhwEIhwdIiIFIiIEIiIdIh0dIhodIh4FIhodBRwFHREdIhsdIh4EBR0GIhwEHRsdHRwEHR4EBQQFIgUEIh0EIhkdIgAFIhsdHREEHQ0dHQsGIh4EIjUGAA+dDQ+dJwYAHR0gABwBCh4GHggOHRIeCgwhHRIdIyAhDB0eNQYADxQnBg4dIv8AHR0g/xIdIyD/IQwdHicGBR3/BR0AHSAnBgqlAB0sAAEwAjA6ADsAIQA/ABYwFzAmIBMgEgEAX18oKXt9CDAMDQgJAgMAAQQFBgdbAF0APiA+ID4gPiBfAF8AXwAsAAEwLgAAADsAOgA/ACEAFCAoACkAewB9ABQwFTAjJiorLTw+PQBcJCVAQAb/CwAL/wwgAE0GQAb/DgAO/w8AD/8QABD/EQAR/xIAEiEGAAEBAgIDAwQEBQUFBQYGBwcHBwgICQkJCQoKCgoLCwsLDAwMDA0NDQ0ODg8PEBARERISEhITExMTFBQUFBUVFRUWFhYWFxcXFxgYGBgZGRkZICAgICEhISEiIiIiIyMjIyQkJCQlJSUlJiYmJicnKCgpKSkpIgYiACIAIgEiASIDIgMiBSIFIQCFKQEwAQsMAPrxoKKkpqji5ObC+6GjpaepqqyusLK0tri6vL7Aw8XHycrLzM3O0dTX2t3e3+Dh4+Xn6Onq6+zu8piZMTFPMVUxWzFhMaIAowCsAK8ApgClAKkgAAACJZAhkSGSIZMhoCXLJdAC0QLmAJkCUwIAAKMCZqulAqQCVgJXApEdWAJeAqkCZAJiAmACmwInAZwCZwKEAqoCqwJsAgTfjqduAgXfjgIG3/gAdgJ3AnEAegII330CfgKAAqgCpgJnq6cCiAJxLAAAjwKhAqICmALAAcEBwgEK3x7fQQRAAAAAABSZELoQAAAAAJsQuhAFBaUQuhAFMREnETIRJxFVRxM+E0cTVxNVuRS6FLkUsBQAAAAAuRS9FFVQuBWvFbkVrxVVNRkwGQVX0WXRWNFl0V/RbtFf0W/RX9Fw0V/RcdFf0XLRVVVVBbnRZdG60WXRu9Fu0bzRbtG70W/RvNFv0VVVVUEAYQBBAGEAaQBBAGEAQQBDRAAARwAASksAAE5PUFEAU1RVVldYWVphYmNkAGZoAHAAQQBhAEFCAERFRkdKAFMAYQBBQgBERUZHAElKS0xNAE9TAGEAQQBhAEEAYQBBAGEAQQBhAEEAYQBBAGEAMQE3ApEDowOxA9EDJAAfBCAFkQOjA7ED0QMkAB8EIAWRA6MDsQPRAyQAHwQgBZEDowOxA9EDJAAfBCAFkQOjA7ED0QMkAB8EIAULDDAAMAAwADAAMAAwBDoEPgRLBE0ETgSJpjAEqSYouX+fAAECAwQFBgcICgsODxETFBUWFxgaG2EmJS97UaaxBCcGAAEFCCoGHggDDSAZGhscCQ8XCxgHCgABBAYMDhBEkHdFKAYsBgAARwYzBhcQERITAAYOAg80BioGKwYuBgAANgYAADoGLQYAAEoGAABEBgAARgYzBjkGAAA1BkIGAAA0BgAAAAAuBgAANgYAADoGAAC6BgAAbwYAACgGLAYAAEcGAAAAAC0GNwZKBkMGAABFBkYGMwY5BkEGNQZCBgAANAYqBisGLgYAADYGOAY6Bm4GAAChBicGAAEFCCAhCwYQIyoGGhscCQ8XCxgHCgABBAYMDhAoBiwGLwYAAEgGMgYtBjcGSgYqBhobHAkPFwsYBwoAAQQGDA4QMC4wACwAKABBACkAFDBTABUwQ1JDRFdaQQBIVk1WU0RTU1BQVldDTUNNRE1SREpLMDAAaGhLYldbzFPHMIxOGlnjiSlZpE4gZiFxmWVNUoxfjVGwZR1SQn0fdamM8Fg5VBRvlWJVYwBOCU5KkOZdLU7zUwdjcI1TYoF5enoIVIBuCWcIZzN1clK2VU2RFDAVMCxnCU6MTolbuXBTYtd23VJXZZdf71MwADhOBQAJIgFgT65Pu08CUHpQmVDnUM9QnjQ6Bk1RVFFkUXdRHAW5NGdRjVFLBZdRpFHMTqxRtVHfkfVRA1LfNDtSRlJyUndSFTUCACCAgAAIAADHUgACHTM+P1CCipOstri4uCwKcHDKU99TYwvrU/FTBlSeVDhUSFRoVKJU9lQQVVNVY1WEVYRVmVWrVbNVwlUWVwZWF1dRVnRWB1LuWM5X9FcNWItXMlgxWKxY5BTyWPdYBlkaWSJZYlmoFuoW7FkbWida2FlmWu42/DYIWz5bPlvIGcNb2FvnW/NbGBv/WwZcU18iXIE3YFxuXMBcjVzkHUNd5h1uXWtdfF3hXeJdLzj9XShePV5pXmI4gyF8OLBes162XspekqP+XjEjMSMBgiJfIl/HOLgy2mFiX2tf4ziaX81f11/5X4FgOjkcOZRg1CbHYAICAAAAAAAAAAgACgAAAggAgAgAAAiAKIACAAACSGEABAYEMkZqXGeWqq7I011iAFR38wwrPWP8Ymhjg2PkY/ErImTFY6ljLjppZH5knWR3ZGw6T2VsZQow42X4ZklmGTuRZgg75DqSUZVRAGecZq2A2UMXZxtnIWdeZ1NnwzNJO/pnhWdSaIVobTSOaB9oFGmdO0Jpo2nqaahqozbbahg8IWunOFRrTjxya59rumu7a406Cx36Ok5svDy/bM1sZ2wWbT5td21BbWlteG2FbR49NG0vbm5uMz3Lbsdu0T75bW5vXj+OP8ZvOXAecBtwlj1KcH1wd3CtcCUFRXFjQpxxq0MocjVyUHIIRoBylXI1RwIgAAAgAAAAAAiAAAACAoCKAAAgAAgKAICIgCAUSHpzi3OsPqVzuD64Pkd0XHRxdIV0ynQbPyR1Nkw+dZJMcHWfIRB2oU+4T0RQ/D8IQPR281DyUBlRM1Eedx93H3dKdzlAi3dGQJZAHVROeIx4zHjjQCZWVnmaVsVWj3nreS9BQHpKek96fFmnWqda7noCQqtbxnvJeydCgFzSfKBC6HzjfAB9hl9jfQFDx30CfkV+NEMoYkdiWUPZYnp/PmOVf/p/BYDaZCNlYICoZXCAXzPVQ7KAA4ELRD6BtVqnZ7VnkzOcMwGCBIKej2tEkYKLgp2Cs1KxgrOCvYLmgjxr5YIdg2ODrYMjg72D54NXhFODyoPMg9yDNmxrbQIAACAiKqAKACCAKACoICAAAoAiAooIAKoAAAACAAAo1WwrRfGE84QWhcpzZIUsb11FYUWxb9Jwa0VQhlyGZ4ZphqmGiIYOh+KGeYcoh2uHhofXReGHAYj5RWCIY4hndteI3og1RvqIuzSueGZ5vkbHRqCK7YqKi1WMqHyrjMGMG413jS9/BAjLjbyN8I3eCNSOOI/She2FlJDxkBGRLocbkTiS15LYknyS+ZMVlPqLi5WVSbeVd43mScOWsl0jl0WRGpJuSnZK4JcKlLJKlpQLmAuYKZi2leKYM0spmaeZwpn+mc5LMJsSm0Cc/ZzOTO1MZ53OoPhMBaEOopGiu55WTfme/p4Fnw+fFp87nwCmAoigAAAAAIAAKAAIoICggACAgAAKiIAAgAAgKgCAAEQgFSIAQdDNAwtXTQMAlwUgxgUA5wYARQcAnAgATQkAPAsAPQ0ANg8AOBAgOhkAyxog0xwAzx0A4iAALjAgK6kg7asAOQoBUQ8BcxEBdRMBKxchPxwhnrwhCOABROkBS+kBAEGwzgMLgweyz9QA6APcAOgA2ATcAcoD3AHKCtwEAQPcxwDwwALcwgHcgMID3MAA6AHcwEHpAOpB6QDqAOnMsOLEsNgA3MMA3MIA3gDcxQXcwQDcwQDeAOTASQpDE4AAF4BBGIDAANyAABKwF8dCHq9HG8EB3MQA3MEA3I8AI7A0xoHDANzAgcGAANzBANyiACSdwADcwQDcwQLcwAHcwADcwgDcwADcwADcwADcwbBvxgDcwIgA3JfDgMiAwoDEqgLcsAvAAtzDqcQE3M2AANzBANzBANzCAtxCG8IA3MEB3MSwCwAHjwAJgsAA3MGwNgAHjwAJr8CwDAAHjwAJsD0AB48ACbA9AAePAAmwTgAJsD0AB48ACYYAVABbsDQAB48ACbA8AQmPAAmwSwAJsDwBZwAJjANrsDsBdgAJjAN6sBsB3JoA3IAA3IAA2LAGQYGAAISEA4KBAIKAwQAJgMGwDQDcsD8AB4ABCbAhANyynsKzgwEJnQAJsGwACYnAsJoA5LBeAN7AANywqsAA3LAWAAmTx4EA3K/EBdzBANyAAdzBAdzEANzDsDQAB44ACaXAANzGsAUBCbAJAAeKAQmwEgAHsGfCQQAE3MED3MBBAAUBgwDchcCCwbCVwQDcxgDcwQDqANYA3ADK5ADoAeQA3ADawADpANzAANyyn8EBAcMCAcGDwIIBAcAA3MABAQPcwLgDzcKwXAAJsC/fsfkA2gDkAOgA3gHgsDgBCLhto8CDyZ/BsB/BsOMACaQACbBmAAma0bAIAtykAAmwLgAHiwAJsL7AgMEA3IHBhMGAwLADAAmwxQAJuEb/ABqy0MYG3MGznADcsLEA3LBkxLZhANyAwKfAAAEA3IMACbB0wADcsgzDsVLBsB8C3LAVAdzCANzAA9ywAMAA3MAA3LCPAAmoAAmNAAmwCAAJAAewFMKvAQmwDQAHsBsACYgAB7A5AAkAB7CBAAcACbAfAQePAAmXxoLEsJwACYIAB5bAsDIACQAHsMoACQAHsE0ACbBFAAkAB7BCAAmw3AAJAAew0QEJgwAHsGsACbAiAAmRAAmwIAAJsXQACbDRAAeAAQmwIAAJsXgBCbhDfAQBsArGtIgBBrhEewABuAyVAdgCAYIA4gTYhwfcgcQB3J3DsGPCuAWKxoDQgcaAwYDEsDPAsG/GsUbAsAzDscsB6ADcwLOvBtywPMUABwBBwNUDC+IOAUrASQJKgAKBAoICgwLAAsICAAqEAkIkhQLAB4AJgglAJIAixAKCIoQihiLGAsgCygLMAocCiiLOAowikCKSIo4iiAKJAooCgiQAAwIDBAOLAoAkCAOECYYJWCQCCgYDmCKaIp4iAAkKA6AiDAMOA0AIEAMSA6IipiLACaQiqCKqIowCjQKOAkADQgNEA4ADjwKOJMIHiAmKCZAkRgOsIgAEsCJCCLIiAgS0IkAERAS2IkIEwiLAIsQixiLIIkAJwASRAsoixATMIsIE0CLOIpICkwKUApUCQAVCBQgKlgKUJEQFxAeMCY4JwAaSJEQICCMKI4AFDCOEBZAJkgkOI4IFEiOGBYgFFCOMBRYjmAmKBR4jkAUgI5oJjgUkIyIjmQKaApsCwAXCBcQFnAKsJMYFyAXGB5QJlgkAB6okJiPKBSojKCNAI0IjRCNGI8wFSiNII0wjTiNQI7gknQLOBb4kDApSIwAGvCS6JEAGVCNCBkQGViNYI6ACoQKiAqMCwQLDAgEKpAJDJKUCwQeBCYMJQSSBIsUCgyKFIocixwLJAssCzQKnAosizwKNIpEikyKPIqgCqQKqAoMkAQMDAwUDqwKBJAkDhQmHCVkkAwoHA5kimyKfIgEJCwOhIg0DDwNBCBEDEwOjIqciwQmlIqkiqyKAI6wCrQKuAkEDQwNFA68CjyTDB4kJiwmRJEcDrSIBBIQIsSJDCLMiAwS1IkEERQS3IkMEwyLBIsUixyLJIkEJwQSxAssixQTNIsME0SLPIrICswK0ArUCQQVDBQkKtgKVJEUFxQeNCY8JwQaTJEUICSMLI4EFDSOFBZEJkwkPI4MFEyOHBYkFFSONBRcjmQmLBR8jgSORBSEjmwmPBSUjIyO5AroCuwLBBcMFxQW8Aq0kxwXJBccHlQmXCQEHqyQnI8sFKyMpI0EjQyNFI0cjzQVLI0kjgiNNI08jUSO5JL0CzwW/JA0KUyO/Ar0kgyO7JEEGVSNDBkUGVyNZIwExgAwALkYkRCRKJEgkAAhCCUQJBAiIIoYkhCSKJIgkriKYJJYknCSaJAAjBgoCIwQKRgnOB8oHyAfMB0ckRSRLJEkkAQhDCUUJBQiJIockhSSLJIkkryKZJJcknSSbJAEjBwoDIwUKRwnPB8sHyQfNB1AkTiRUJFIkUSRPJFUkUySUIpYilSKXIgQjBiMFIwcjGCMZIxojGyMsIy0jLiMvIwAkoiSgJKYkpCSoJKMkoSSnJKUkqSSwJK4ktCSyJLYksSSvJLUksyS3JIIIgAiBCAIIAwicIp0iCgoLCoMIQAuKLIEMiSyILEAlQSUALQcuAA1AJkEmgC4BDcgmySYAL4QvAg2DL4IvQA3YJtkmhjEEDUAnQScAMYYwBg2FMIQwQQ1AKAAyBw1PKFAogDKELAMuVyhCDYEsgCzAJMEkhiyDLMAoQw3AJcElQClEDcAmwSYFLgIuwClFDQUvBC+ADdAm0SaAL0Aqgg3gJuEmgDCBMMAqgw0EMAMwgQ3AJ8EngjBAK4QNRyhIKIQxgTEGLwgNgS8FMEYNgzCCMQAOAQ5AD4ARghEDDwAPwBEBD0ARAhIEEoEPQBLAD0ISgA9EEoQSgg+GEogSihLAEoISgRGDEUMQQBDBEUEQQREDEgUSwRBBEgAQQxLAEEUShRLCEIcSiRKLEsESgxKAEAARAREAEgESgBKBEkATQRNDE0ITRBPCEwAUwBNAFIAUwBRAFUEVQBcAF0EXwBcAGAIYARhAGIAYABnAGMEYARlAGUIZQRmAGcAZwhnBGYAcwBzAHYAfACACIAQgBiAIIEAggCCCIMAgwSAAIbgiuSIQIxEjHCMdI0wkViRNJFckjCSNJJ4knyQAJQIlBCXAKwElAyUFJcErwivDK8QrxSvGK8crgCWCJYQlyCuBJYMlhSXJK8oryyvMK80rzivPKwAmAiYBJgMmgCaCJoEmgybCJsQmxiYALMMmxSbHJgEsAiwDLAQsBSwGLAcsyibMJs4mCCzLJs0mzyYJLAosCywMLA0sDiwPLNIm1CbWJtMm1SbXJtom3CbeJtsm3SbfJgAnAicBJwMngCeCJ4EngycAKAIoBCgBKAMoBShCKEQoRihJKEsoTShALEooTChOKEEsQixDLEQsRSxGLEcsUShTKFUoSCxSKFQoVihJLEosSyxMLE0sTixPLIIsAS6AMYcsAS8CLwMvBi6FMQAwATACMEBGQUaARsBGwkbBRgBHQEeAR8BHwkcASUBJgEmCSQBKwkkDSgRKQEpBSoBKgUrASsFKwEvBSwBLAUtAS0FLwkvDS4BLgUuCS4NLAEwBTAJMA0wAVkBUQlREVEZUSFRKVExUTlRQVFJUVFRWVIBUglSEVMBUwVQAVQFVQFVBVYBVgVXAVcFVgFbAWABXAlcEVwZXCFcKVwxXDlcQVxJXFFcWV0BXQldEV4BXgVfAV8FXAFgBWEBYQViAWIFYAFkBWQJZA1lAWUCPQo+Aj8CPwY8AkAGQQZBAkEOQgJCBkMCQAEGw5AMLtiD6GBdWDVYSExYMFhE26QI2TDbhEhIWEw4QDuISEgwTDPoZFxZtDxYODwUUDBsPDg8MKw4CNg4LBRVLFuEPDMHiEAziAP8wAv8IAv8nvyIhAl9fISJhAiECQUIhAiECn38CX18hAl8/AgU/ImUBAwIBAwIBAwL/CAL/CgIBAwJfIQL/MqIhAiEiX0EC/wDiPAXiE+QKbuQE7gaEzgQOBO4J5mh/BA4/IARCFgFgLgEWQQABACEC4QkA4QHiGz8CQUL/EGI/DF8/AuEr4ij/Gg+GKP8v/wYC/1gA4R4gBLbiIRYRIC8NAOYlEQYWJhYmFgbgAOUTYGU24AO7TDYNNi/mAxYbVuUYBOUC5g3pAnYlBuVbFgXGGw+mJCYPZiXpAkUvBfYGABsFBuUW5hMg5VHmAwXgBukC5RnmASQPVgQgBi3lDmYE5gEERgSGIPYHAOURRiAWAOUDgOUQDqUAO6DmAOUhBOYQG+YYB+UuBgcGBUfmAGcGJwXG5QImNukCFgTlBwYnAOUAICUg5Q4AxQAFQGUgBgVHZiAnICcGBeAAB2AlAEUmIOkCJS2rDw0FFgYgJgcApWAlIOUOAMUAJQAlACUgBgBHJmAmIEZABsBlAAXA6QImRQYW4AImBwDlAQBFAOUOAMUAJQCFIAYFR4YAJgcAJwYgBeAHJSYg6QIWDcAFpgAGJwDlACAlIOUOAMUAJQCFIAYFBwYHZiAnICcGwCYHYCUARSYg6QIPBavgAgYFAKVARQBlQCUABQAlQCVARUDlBGAnBidARwBHBiAFoAfgBukCS68ND4AGRwblAABFAOUPAOUIIAYFRmcARgBmwCYARSAFICUmIOkCwBbLDwUGJxblAABFAOUPAOUCAIUgBgUHBocABicAJybAJ6AlACUmIOkCACUH4AQmJ+UBAEUA5SEmBUdmAEcARwYFD2BFB8tFJiDpAusBD6UABicA5QpA5RAA5QEABSDFQAZgR0YABgDnAKDpAiAnFuAE5SgGJcZgDaUE5gAW6QI24B0lAAUAhQDlEAAFAOUCBiXmAQUghQAEAMYA6QIgZeAYBU/2Bw8WTyav6QLrAg8GDwYPBhITEhMn5QAA5Rxg5gYHhhYmheYDAOYcAO8ABq8AL5ZvNuAd5SMnZgemByYnJgXpAralJyZlRgVHJcdFZuUFBicmpwYFB+kCRwYv4R4AAYABIOIjFgRC5YDBAGUgxQAFAGUg5SEAZSDlGQBlIMUABQBlIOUHAOUxAGUg5TsgRvYB6wxA5QjvAqDhTiCiIBHlgeQPFuUJF+USEhNA5UNWSuUAwOUKRgfgAeULJgc24AHlCibgBOUFAEUAJuAE5SwmB8bnAAYn5gNWBFYNBQYg6QKg6wKgthF2RhsG6QKg5RsE5S3AhSblGgYFgOU+4ALlFwBGZyZHYCcGp0ZgD0A26QLlFiCF4APlJGDlEqDpAgtA7xrlDyYnBiA25S0HBgfGAAYHBifmAKfmAiAG6QKg6QKg1gS2IOYGCOYI4ClmB+UnBgeGBwaHBiflAEDpAtbvAuYB7wE2ACYH5RYHZicmB0Yl6QLlJAYHJkcGB0Yn4AB25RznAOYAJyZAlukCQEXpAuUWpDbiAcDhIyBB9gDgAEYW5gUHxmUGpQYlByYFgOIk5DfiBQTiGuQd5jj/gA7iAP9a4gDhAKIgoSDiAOEA4gDhAKIgoSDiAAABAAEAAQA/wuEA4gYg4gDjAOIA4wDiAOMAggAiYQMOAk5CACJhA05iICJhAE7iAIFOIEIAImEDLgD3A5uxNhQVEjQVEhT2ABgZmxf2ARQVdjBWDBIT9gMMFhD2AhebAPsCCwQgq0wSEwTrAkwSEwDkBUDtGeAH5gVoBkjmBOAHLwFvAS8CQSJBAg8BLwyBrwEPAQ8BD2EPAmECZQIvIiGMP0IPDC8CD+sI6hs/agsvYIyPLG8MLwwvDM8M7xcsLwwPDO8X7ICE7wASExIT7wwszxIT70kM7xbsEe8grO894BHvA+AN6zTvRusO74AvDO8BDO8u7ADvZwzvgHASExITEhMSExITEhMSE+sW7ySMEhPsFxITEhMSExITEhPsCO+AeOx7EhMSExITEhMSExITEhMSExITEhMSE+w3EhMSE+wYEhPsgHrvKOwNL6zvHyDvGADvYeEo4ihfISLfQQI/Aj+CJEEC/1oCr39GP4B2CzbiHgACgAIg5TDABBbgBgblD+ABxQDFAMUAxQDFAMUAxQDFAOYYNhQVFBVWFBUWFBX2ARE2ERYUFTYUFRITEhMSExITlgT2AjF2ERYS9gUvVhITEhMSExITEeAa7xIA71HgBO+ATuAS7wRgF1YPBAUKEhMSExITEhMSEy8SExITEhMSExESMw/qAWYnEYQvSgQFFi8A5U4gJi4kBRHlUhZEBYDlIwDlVgAva+8C5RjvHOAE5QjvFwDrAu8W6wAP6wfvGOsC7x/rB++AuOWZOO845cARjQTlg+9A7y/gAeUgpDblgIQEVuUI6QIl4Az/JgUGSBbmAhYE/xQkJuU+6gImtuAA7g/kAS7/BiL/NgTiAJ//AgQufwV/Iv8NYQKBAv8HQQI/gD8AAgACf+AQRD8FJALFBkUGZQblDycmB28GQKsvDQ+g5Sx24AAn5SrnCCbgADbpAqDmCqVWBRYlBukC5RTmADblD+YDJ+ADFuUVQEYH5ScGJ2YnJkf2BQAE6QJgNoUGBOUB6QKFAOUhpicmJybgAUUG5QAGByDpAiB25QgEpU8FBwYH5SoGBUYlJoUmBQYF4BAlBDblAwcmJzYFJAcG4AKlIKUgpeABxQDFAOIjDmTiAQQuYOJI5RsnBicGJxYHBiDpAqDlqxzgBOUPYOUpYPyHeP2YeOWA5iDlYuAewuAEgoAFBuUCDOUFAIUABQAlACUA5WTuCeAI5YDjExLvCOU4IOUuwA/gGOUEDU/mCNYSExag5ggWMTASExITEhMSExITEhMSExITNhITdlBWAHYREhMSExITVgwRTAAWDTZghQDlfyAbAFYNVhITFgwWETbpAjZMNuESEhYTDhAO4hISDBMMEhMWEhM25QIE5SUk5RdApSClIKUgRUAtDA4PLQAPbC/gAlsvIOUEAOUSAOULACUA5Qcg5QbgGuVzgFZg6yVA7wHqLWvvCStPAO8FQA/gJ+8lBuB65RVA5SngBwbrE2DlGGvgAeUMCuUACoDlHoaA5RYAFuUcYOUAForgIuEg4iDlRiDpAqDhHGDiHGDlIOAA5SzgAxbhAwDhBwDBACEA4gMA4gcAwgAi4DvlgK/gAeUO4ALlAOAQpADkIgDkAeA9pSAFAOUkACVABSDlDwAW6wDlDy/L5RfgAOsB4CjlCwAlgIvlDqtAFuUSgBbgOOUwYCsl6wgg6yYFRgAmgGZlAEUA5RUgRmAG6wHA9gHA5RUrFuUVS+AY5QAP5RQmYIvW4AHlLkDW5Q4g6wDlC4DrAOUKwHbgBMvgSOVB4C/hK+AF4ivAq+UcZuAA6QLggJ7rFwDlIgAmESAl4ENG5RXrAgXgAOUO5gNrluAO5QpmduAe5Q3L4AzlD+ABBwYH5S3mB9Zg6wzpAgYlJgXgAUYH5SVHZicmNht2BuACGyDlEcDpAqBG5RyGB+YAAOkCdgUnBeAA5RsGNgXgASYH5ShH5gEnZXZmFgcG6QIFFgVWAOsM4APlCgDlEUdGJwYHJrYGJQbgNsUABQBlAOUHAOUCFqDlJwZH5gCA6QKgJicA5QAgJSDlDgDFACUAhQAmBScGZyAnIEcgBaAHgIUnIMZAhuCAA+UtR+YAJ0YHBmWW6QI2ABYGReAW5ShHpgcGZyYHJiUWBeAA6QLggB7lJ0dmIGcmByb2D2Um4BrlKEfmACcGByZWBeAD6QKg9gXgC+UjBgcGJ6YHBgUWoOkC4C7lEyBGJ2YHhmDpAitWD8XggDHlJEfmAQcmFuBc4RjiGOkC6wHgBOUAIAUg5QAAJQDlEKcAJyAmBwYFBwUHBlbgAekC4D7lACDlH0dmICZnBgUWBQfgEwXmAuUgpgcFZvYABuAABaYnRuUm5gUHJlYFluAF5UHA9gLggG7lAQDlHQfGAKYHBgWW4ALpAusLQDblFiDmDgAHxgcmBybgQcUAJQDlHqZABgAmAMYFBuAA6QKgpQAlAOUYhwAmACcGBwYFwOkC4ICu5QsmJzbAJgUH5QUA5RonhkAnBgcG9gXpAuBOBeAH6w3vAG3vCeAFFuWDEuBe6mcAluAD5YA84InE5Vk24AXlg6j7CAal5gfgjyLlgb/goTHlgbHA5RcA6QJgNuVHAOkCoOUWIIYW4ALlKMaWb2QWD+AC6QIAywDlDYDlC+CCKOEY4hjrD3bgXeVDYAYF5y/AZuQF4DgkFgQG4AMn4Abll3DgAOWETuAi5QHgol9kAMQAJADlgJvgBwXgFUUgBeAGZeAA5YEE4Ih85WOA5QVA5QHA5QIgDyYWe+CR1OYmIOYP4AHvbOA074Bu4ALvHyDvNCdGT6f7AOYAL8bvFmbvNeAN7zpGD+By6wzgBOsM4ATvT+AB6xHgf+ES4hLhEsIA4grhEuISAQAhIAEgISBhAOEAYgACAMIA4gPhEuISIQBhIOEAAMEA4hIhAGEAgQABQMEA4hLhEuIS4RLiEuES4hLhEuIS4RLiEuES4hQg4REM4hEMouERDOIRDKLhEQziEQyi4REM4hEMouERDOIRDKI/IOkq74F45i9v5irvAAbvBgYvluAHhgDmB+CDyOICBeIMoKLggE3GAOYJIMYAJgCGgOQ24BkG4GjlJUDGxCDpAmAFD+CAuOUWBuAJ5SRm6QKADeCBSOUTBGbpAuCCXsUAZQAlAOUHAOWAPSDrAcbgIeEa4hrGBGDpAmA24IKJ6zMPSw1r4ETrJQ/rB+CAOmUA5RMAJQAFIAUA5QIAZQAFAAWgBWAFAAUABQBFACUABSAFAAUABQAFAAUAJQAFIGUAxQBlAGUABQDlAgDlCYBFAIUA5QngLCzggIbvJGDvXOAE7wcg7wcA7wcA7x3gAusF74AZ4DDvFeAF7yRg7wHAL+AGr+CAEu+Ac47vglBg7wlA7wVA729g71eg7wRgD+AH7wRg7zDgAO8CoO8g4ADvFiAv4EbvgMzgBO8GIO8FQO8BwO8mAM/gAO8GYO8BwO8BwO+ACwDvL+Ad6QLgg37lwGZY4Bjlj7Kg5YBWIOWV+uAG5Zyp4IuX5YGW4IVa5ZLDgOWP2ODKm8kb4Bb7WOB45oBo4MC9iP3Av3Yg/cC/diAAAAAA4AIBAAADAQDQAwEAgAUBAMUFAQDgBQEAMAYBAFAGAQBbBgEAcAYBAOCOAACQBgEAsAYBANAGAQDwBgEAEAcBAM8IAQDUCAEA4AgBACAJAQBACQEA0AoBACwLAQA4CwEAPQsBAFALAQCVCwEAmQsBALALAQAADAEAOgwBAFAMAQBvDAEAeAwBAIAMAQBQDQEAoA0BAKAOAQDNDgEA4A4BAAAPAQCwDwEAoBABALwQAQDAEAEAEBEBALARAQBQEgEAIIoAAOCGAEHwhAQLZBwAyACsAUUADwBBACAACwAMABMAlAIfABcAFgAdAL8BBQAKADcAFwCPAVwADAAFAAQARQAEAA8ARwA6AAsAHwAJAAQAxABPAPgALQANABYArQDvABwABABHAJEAnAAzAEwE4QIAQeCFBAv0BayA/oBE24BSeoBICIFOBIBC4oBgzWaAQKiA1oAAAAAA3YBDcBGAmQmBXB+AmoKKgJ+Dl4GNgcCMGBEckQMBiQAUKBEJAgUTJMohGAgIACELC5EJAAYAKUEhg0CnCICXgJCAQbyBi4gkIQkUjQABhZeBuACAnIOIgUFVgZ6JQZKVvoOfgWDUYgADgEDSAIBg1MDUgMYBCAkLgIsABoDAAw8GgJsDBAAWgEFTgZiAmICegJiAnoCYgJ6AmICegJgHgbFV/xiaAQAIgIkDAAAoGAAAAgEACAAAAAABAAsGAwMAgImAkCIEgJAAAAAAAAAAAENEgEJpjQABAQDHiq+MBo+A5DMZC4CigJ2P5YrkCogCA0CmixaFk7UJjgEiiYGcgrkxCYGJgImBnIK5IwkLgJ0KgIqCuTgQgZSBlROCuTEJgYiBiYGdgLoiEIKJgKeEuDAQF4GKgZyCuTAQF4GKgY6Ai4O5MBCCiYCJgZyCyigAh5GBvAGGkYDiASiBj4BAopKIioCj7YsAC5YbEBEyg4yLAImDRnOBnYGdgZ2BwZJAu4GhgPWLg4hA3YS4iYGTyYGKgrCEr467gp2ICbiKsZJBr41GwLNI9Z9geHOHoYFBYQeAloTXgbGPALiApYSbi6yDr4ukgMKNiweBrIKxABEMgKskgEDsh2BPMoBIVoRGhRAMg0MTg0GCgUFSgrSNrIGKgqyIiIC8gqOLkYG4gq+MjYHbiAgoCECciZaDuTEJgYmAiYFA0IwC6ZFA7DGGnIHRjgDpiuaNQQCMQPYoCQoAgECNMSuAm4mpIIORiq2NQZY4htKVgI35KgAIEAKAwSAIg0Fbg4gIgK8ygmBQDQC2M9yBYEyrgGAjYDCQDgEE44BItoBH55mFmYWZAAAAAECpgI6AQfSIMZ2E34CzgE2AgEwuvoyAoaRCsICMgI+MQNKPQ0+ZR5GBYHodgUDRgECAEoFDYYOIgGBcFQEQqYCIYNh0vWAhX49DRZlhzF+ZhZmFmQBB4IsEC0FJvYCXgEFlgJeA5YCXgEDpgJGB5oCXgPaAjoBNVIBE1YBQIIFgz22BU52Al4BBV4CLgEDwgEN/gGC4MweEbC6s3wBBsIwECzdDToBODoFGUoFIroBQ/YBgzjqAzohtAAYAnd//QO9OD1iEgUiQgJSAT2uBQLaAQs6AT+CIRmeAAEHwjAQLE0X/hUDWgLCAQX+Bz4BhB9mAjoAAQZCNBAs3Q3mASreA/oBgIeaBYMvAhUGVgfMAAAAAAAAAgEEegQBDeYBgLR+BYMvAhUGVgfMAAAAAAAAAgABB0I0ECxZBwwgIgaSBTtyqCk6HPz+Hi4COgK6AAEHwjQQLpwRB74BBnoCegFrkg0C1AAAAgN4GBoCKCYGJEIGNgAAAAECfBgABAAESEILzgIuAQIQBAYCiAYBAu4ieKYTaCIGJgKMEAgQIB4CegKCCnIBCKIDXg0Leh/sIgNIBgKERgED8gULUgP6Ap4GtgLWAiAMDA4CLgIgAJoCQgIgDAwOAi4BBQYDhgUZSgdSERRsQioCRgJuMgKGkQNWDQLUAAACAmQAAAAAAAIC3BQATBRECDBEAAAwVBQiPACCLEioICwAHgowGkoGagIyKgNYYEIoBDAoAEBECBgUchY+Pj4iAQKEIgUD3gUE01ZmaRSCA5oLkgEGegUDwgEEugNKAi0DVqYC0AILfCYDegLDdgo3fnoCnh66AQX9gcpuBQNGAQIASgUNhg4iAYE2VQQ0IAIGJAAAJgsOB6cIAlwQAAQGA66BBapG/gbWnjIKZlZSBi4CSAxoAgECGCICfmUCDFQ0NChYGgIhHhyCpgIhgtOSDVLmGjYe/hUI+1IDGAQgJC4CLAAaAwAMPBoCbAwQAFoBBU4FBI4GxSC+9TZEYmgEACICJAwAAKBgAAAIBAAgAAAAAAQALBgMDAICJgJAiBICQQkOKhJ6An5mCooDugoyrg4gxSZ2JYPwFQh1rBeFP/6+JNZmFRhuAWfCBmYS2gwAArIBFW4CygE5AgEQEgEgIhbyApoCOgEGFgEwDAYCeC4CbgEG9gJKA7oBgzY+BpICJgECogE+egABBoJIECxdBSIBFKIBJAgCASCiBSMSFQriBbdzVgABBwJIEC4EE3QCAxgUDAYFB9kCeByWQC4CIgUD8hEDQgLaQgJoAAQBAhTuBQIULCoLCmtqKuYqhgf2HqImPm7yAjwKDm4DJgI+A7YCPgO2Aj4CugruAjwaA9oDtgI+A7YCPgOyBj4D7gPsogOqAjITKgZoAAAOBwRCBvYDvAIGnC4SYMICJgULAgkOzgUCyioiAQVqCQTg5gK+OgYrngI6ApYi1gUCJgb+F0ZgYKAqxvtiLpIpBvACCioKMgoyCjIFM74JBPIBB+YXog96AYHVxgIsIgJuB0YGNoeWC7IFAyYCakbiDo4DegIuAo4BAlILAg7KA44SIgv+BYE8vgEMAj0ENAICugKyBwoBC+4BEniipgIhDKYFCOoVB1ILFirCDQL+AqIDHgfeBvYDLgIiC54FAsYHQgI+AlzKEQMwCgPqBQPqB/YD1gfKAQQyBQQELgECbgNKAkYDQgEGkgEEBAIHQgFaujmA2mYS6hkRXkM+BYD/9GDCBXwCtgZZCHxIvOYadg06BvUDBhkF2gLyDRd+G7BCCAEC2gEIXgUNtgEG4gENZgELvgP6ASUKAt4BCYoBBjYDDgFOIgKqE5oHcgmBvFYBF9YBDwYCVgECIgOuAlIFgVHqASA+BS9mAQmeCRM6AYFCogUSbCIBgcVeBSAWCr4k1mYVg/qiJNZmFYC/vCYdgL/GBAEHQlgQLpwFgMAWBmIiNgkPEWb+/YFH/YFj/QW2B6WB1CYCaV/eHRNWpiGAkZkGLYE0DYKbfn1A5hUDdgVaBjV0wTB5CHUXhU0qEUF9gIAuBTj+E+oRK7xGAYJD5CQCBAAAAAAAAAABg/c+fQg2BYP/9gWD//YFg//2BYP/9gWD//YFg//2BYP/9gWD//YFg//2BYP/9gWD//YFg//2BYP/9gWD//YFg//2BYP/9gQBBgJgEC0WgjomGmRiAmYOhMAAIAAsDAoCWgJ6AXxeXh46BkoCJQTBCz0CfQnWdRGtB//9BgBOYjoBgzQyBQQSBiISRgOOAX4eBl4EAQdCYBAv0AaEDgECCgI6AX1uHmIFOBoBByIOMgmDOIINAvAOA2YFgLn+ZgNiLQNVh8eWZAAAAAKCAi4CPgEVIgECSgkCzgKqCQPWAvAACgUEkgUbjgUMVA4FDBIBAxYFAywSAQTmBQWGDQK0JgZyBQLuBwIFDu4GIgk3jgIyAlYFBrIBgdPuAQQ2BQOICgEF9gdWB3oBAl4FAkoJAj4FA+IBgUmUCgUCogIuAj4DAgErzgUT8hKuDQLyB9IP+gkCADYCPgdcIgeuAQaCBQXQMjuiBQPiCQgQAgED6gdaBQaOBQrOByYFgSyiBQISAwIGKgENSgGBOBYBd54AAQdCaBAumA+iBQMOAQRiAnYCzgJOAQT+A4QCAWQiAsoCMAoBAg4BAnIBBpIBA1YFLMYBhp6SBsYGxgbGBsYGxgbGBsYGxgbGBsYGxgbGBSIWAQTCBmYAAoICJAICKCoBDPQeAQgCAuIDHgI0AgkCzgKqKAEDqgbWOnoBBBIFE84FAqwOFQTaBQxSHQwSA+4LGgUCcEoCmGYFBOYFBYYNArQiCnIFAu4S9gUO7gYiCTeOAjAOAiQAKgUGrgWB0+oFBDIJA4oRBfYHVgd6AQJaCQJKC/oCPgUD4gGBSYxCDQKiAiQCAigqAwAGARDmAr4BEhYBAxoBBNYFAl4XDhdiDQ7eEq4NAvIbvg/6CQIANgI+B14TrgEGggouBQWUajuiBQPiCQgQAgED6gdYLgUGdgqyAQoSByYFFKoRgRfiBQISAwIKJgENRgWBOBYBd5oMAAAAAAAAAAGAz/1m/v2BR/2BaDQgAgYkAAAmCYQXVYKbfn1A5hUDdgVaBjV0wVB5TSoRQX1gKEIBg5e+PbQLvQO8AAAAAAACIhJGA44CZgFXegEl+ipwMgK6AT5+AAEGAngQLhwSngZEAgJsAgJwAgKyAjoBOfYNHXIFJm4GJgbWBjYFAsIBAvxoqAgoYGAADiCCAkSOICAA5ngsgiAmSIYghC5eBjzuTDoFEPI3JARgIFBwSjUGSlQ2AjTg1EBwBDBgCCYkpgYuSAwgACAMhKpeBigsYCQuqD4CnIAAUIhgUAED/gEICGgiBjQmJqodBqokPYM48LIFAoYGRAICbAICcAAAIgWDXdoC4gLiAuIC4gAAAAKIFBInuA4BfjICLgEDXgJWA2YWOgUFugYuAQKWAmIoaQMaAQOaBiYCIgLkYhIgBAQkDAQAJAgIPFAAEi4oJAAiAkQGBkSgACgwBC4GKDAkECACBkwwoGQMBASgBAAAFAgWAiYGOAQMAAxCAioGvgoiAjYCNgEFzgUHOgpKBsgOARNmAi4BCWACAYb1pgEDJgECfgYuBjQGJypkBloCTAYiUgUCtoYHvCQKB0gqAQQaAvooolzEPiwEZA4GMCQeBiASCixcRAAMFAgXVr8UnCoOJEAEQgYlA4osYQRqugImAQLjvjIKIhq0Gh42DiIaIAKIFBIlf0oBA1IBg3SqAYPPVmUH6hEWvg2wGa99h8/qEYCYcgEDagI+DYcx2gLsRAYL0CYqUkhAaAjAAl4BAyAuAlAOBQK0ShNKAj4KIgIqAQj4BBz2AiIkKt4C8CAiAkBCMQOSCqYgAQZCiBAuRAWAjGYFAzBoBgEIIgZSBsYuqgJKAjAeBkAwPBICUBggDAQYDgZuAogADEIC8gpeAjYBDWoGyA4BhxK2AQMmAQL0BicqZAJeAkwEggpSBQK2gi4iAxYCVi6oci5AQgsYAgEC6gb6MGJeRgJmBjIDV1K/FKBIKG4oOiEDiixhBGq6AiYBAuO+MgoiGrQaHjYOIhogAQbCjBAvTAUCoA4BfjICLgEDXgJWA2YWOgUFugYuA3oDFgJiKGkDGgEDmgYmAiIC5GCiLgPGJ9YGKAAAoECiJgY4BAwADEICKhKyCiICNgI2AQXOBQc6CkoGyA4BE2YCLgEJYAIBhvWVA/4yCnoC7hYuBjQGJkbiajomAkwGIA4hBsYRBPYdBCa//84vUqouDt4eJhaeHndGLroCJgEG4QP9D/QAAAABArIBCoIBCy4BLQYFGUoHUhEf6hJmEsI9Q84BgzJqPQO6AQJ+AzohgvKaDVM6HbC6ET/8AQZalBAsa8D8AAAAAAAD4PwAAAAAAAAAABtDPQ+v9TD4AQbulBAtlQAO44j9Pu2EFZ6zdPxgtRFT7Iek/m/aB0gtz7z8YLURU+yH5P+JlLyJ/K3o8B1wUMyamgTy9y/B6iAdwPAdcFDMmppE8GC1EVPsh6T8YLURU+yHpv9IhM3982QJA0iEzf3zZAsAAQa+mBAvoFYAYLURU+yEJQBgtRFT7IQnAAwAAAAQAAAAEAAAABgAAAIP5ogBETm4A/CkVANFXJwDdNPUAYtvAADyZlQBBkEMAY1H+ALveqwC3YcUAOm4kANJNQgBJBuAACeouAByS0QDrHf4AKbEcAOg+pwD1NYIARLsuAJzphAC0JnAAQX5fANaROQBTgzkAnPQ5AItfhAAo+b0A+B87AN7/lwAPmAUAES/vAApaiwBtH20Az342AAnLJwBGT7cAnmY/AC3qXwC6J3UA5evHAD178QD3OQcAklKKAPtr6gAfsV8ACF2NADADVgB7/EYA8KtrACC8zwA29JoA46kdAF5hkQAIG+YAhZllAKAUXwCNQGgAgNj/ACdzTQAGBjEAylYVAMmocwB74mAAa4zAABnERwDNZ8MACejcAFmDKgCLdsQAphyWAESv3QAZV9EApT4FAAUH/wAzfj8AwjLoAJhP3gC7fTIAJj3DAB5r7wCf+F4ANR86AH/yygDxhx0AfJAhAGokfADVbvoAMC13ABU7QwC1FMYAwxmdAK3EwgAsTUEADABdAIZ9RgDjcS0Am8aaADNiAAC00nwAtKeXADdV1QDXPvYAoxAYAE12/ABknSoAcNerAGN8+AB6sFcAFxXnAMBJVgA71tkAp4Q4ACQjywDWincAWlQjAAAfuQDxChsAGc7fAJ8x/wBmHmoAmVdhAKz7RwB+f9gAImW3ADLoiQDmv2AA78TNAGw2CQBdP9QAFt7XAFg73gDem5IA0iIoACiG6ADiWE0AxsoyAAjjFgDgfcsAF8BQAPMdpwAY4FsALhM0AIMSYgCDSAEA9Y5bAK2wfwAe6fIASEpDABBn0wCq3dgArl9CAGphzgAKKKQA05m0AAam8gBcd38Ao8KDAGE8iACKc3gAr4xaAG/XvQAtpmMA9L/LAI2B7wAmwWcAVcpFAMrZNgAoqNIAwmGNABLJdwAEJhQAEkabAMRZxADIxUQATbKRAAAX8wDUQ60AKUnlAP3VEAAAvvwAHpTMAHDO7gATPvUA7PGAALPnwwDH+CgAkwWUAMFxPgAuCbMAC0XzAIgSnACrIHsALrWfAEeSwgB7Mi8ADFVtAHKnkABr5x8AMcuWAHkWSgBBeeIA9N+JAOiUlwDi5oQAmTGXAIjtawBfXzYAu/0OAEiatABnpGwAcXJCAI1dMgCfFbgAvOUJAI0xJQD3dDkAMAUcAA0MAQBLCGgALO5YAEeqkAB05wIAvdYkAPd9pgBuSHIAnxbvAI6UpgC0kfYA0VNRAM8K8gAgmDMA9Ut+ALJjaADdPl8AQF0DAIWJfwBVUikAN2TAAG3YEAAySDIAW0x1AE5x1ABFVG4ACwnBACr1aQAUZtUAJwedAF0EUAC0O9sA6nbFAIf5FwBJa30AHSe6AJZpKQDGzKwArRRUAJDiagCI2YkALHJQAASkvgB3B5QA8zBwAAD8JwDqcagAZsJJAGTgPQCX3YMAoz+XAEOU/QANhowAMUHeAJI5nQDdcIwAF7fnAAjfOwAVNysAXICgAFqAkwAQEZIAD+jYAGyArwDb/0sAOJAPAFkYdgBipRUAYcu7AMeJuQAQQL0A0vIEAEl1JwDrtvYA2yK7AAoUqgCJJi8AZIN2AAk7MwAOlBoAUTqqAB2jwgCv7a4AXCYSAG3CTQAtepwAwFaXAAM/gwAJ8PYAK0CMAG0xmQA5tAcADCAVANjDWwD1ksQAxq1LAE7KpQCnN80A5qk2AKuSlADdQmgAGWPeAHaM7wBoi1IA/Ns3AK6hqwDfFTEAAK6hAAz72gBkTWYA7QW3ACllMABXVr8AR/86AGr5uQB1vvMAKJPfAKuAMABmjPYABMsVAPoiBgDZ5B0APbOkAFcbjwA2zQkATkLpABO+pAAzI7UA8KoaAE9lqADSwaUACz8PAFt4zQAj+XYAe4sEAIkXcgDGplMAb27iAO/rAACbSlgAxNq3AKpmugB2z88A0QIdALHxLQCMmcEAw613AIZI2gD3XaAAxoD0AKzwLwDd7JoAP1y8ANDebQCQxx8AKtu2AKMlOgAAr5oArVOTALZXBAApLbQAS4B+ANoHpwB2qg4Ae1mhABYSKgDcty0A+uX9AInb/gCJvv0A5HZsAAap/AA+gHAAhW4VAP2H/wAoPgcAYWczACoYhgBNveoAs+evAI9tbgCVZzkAMb9bAITXSAAw3xYAxy1DACVhNQDJcM4AMMu4AL9s/QCkAKIABWzkAFrdoAAhb0cAYhLSALlchABwYUkAa1bgAJlSAQBQVTcAHtW3ADPxxAATbl8AXTDkAIUuqQAdssMAoTI2AAi3pADqsdQAFvchAI9p5AAn/3cADAOAAI1ALQBPzaAAIKWZALOi0wAvXQoAtPlCABHaywB9vtAAm9vBAKsXvQDKooEACGpcAC5VFwAnAFUAfxTwAOEHhgAUC2QAlkGNAIe+3gDa/SoAayW2AHuJNAAF8/4Aub+eAGhqTwBKKqgAT8RaAC34vADXWpgA9MeVAA1NjQAgOqYApFdfABQ/sQCAOJUAzCABAHHdhgDJ3rYAv2D1AE1lEQABB2sAjLCsALLA0ABRVUgAHvsOAJVywwCjBjsAwEA1AAbcewDgRcwATin6ANbKyADo80EAfGTeAJtk2ADZvjEApJfDAHdY1ABp48UA8NoTALo6PABGGEYAVXVfANK99QBuksYArC5dAA5E7QAcPkIAYcSHACn96QDn1vMAInzKAG+RNQAI4MUA/9eNAG5q4gCw/cYAkwjBAHxddABrrbIAzW6dAD5yewDGEWoA98+pAClz3wC1yboAtwBRAOKyDQB0uiQA5X1gAHTYigANFSwAgRgMAH5mlAABKRYAn3p2AP39vgBWRe8A2X42AOzZEwCLurkAxJf8ADGoJwDxbsMAlMU2ANioVgC0qLUAz8wOABKJLQBvVzQALFaJAJnO4wDWILkAa16qAD4qnAARX8wA/QtKAOH0+wCOO20A4oYsAOnUhAD8tKkA7+7RAC41yQAvOWEAOCFEABvZyACB/AoA+0pqAC8c2ABTtIQATpmMAFQizAAqVdwAwMbWAAsZlgAacLgAaZVkACZaYAA/Uu4AfxEPAPS1EQD8y/UANLwtADS87gDoXcwA3V5gAGeOmwCSM+8AyRe4AGFYmwDhV7wAUYPGANg+EADdcUgALRzdAK8YoQAhLEYAWfPXANl6mACeVMAAT4b6AFYG/ADlea4AiSI2ADitIgBnk9wAVeiqAIImOADK55sAUQ2kAJkzsQCp1w4AaQVIAGWy8AB/iKcAiEyXAPnRNgAhkrMAe4JKAJjPIQBAn9wA3EdVAOF0OgBn60IA/p3fAF7UXwB7Z6QAuqx6AFX2ogAriCMAQbpVAFluCAAhKoYAOUeDAInj5gDlntQASftAAP9W6QAcD8oAxVmKAJT6KwDTwcUAD8XPANtargBHxYYAhUNiACGGOwAseZQAEGGHACpMewCALBoAQ78SAIgmkAB4PIkAqMTkAOXbewDEOsIAJvTqAPdnigANkr8AZaMrAD2TsQC9fAsApFHcACfdYwBp4d0AmpQZAKgplQBozigACe20AESfIABOmMoAcIJjAH58IwAPuTIAp/WOABRW5wAh8QgAtZ0qAG9+TQClGVEAtfmrAILf1gCW3WEAFjYCAMQ6nwCDoqEAcu1tADmNegCCuKkAazJcAEYnWwAANO0A0gB3APz0VQABWU0A4HGAAEGjvAQLrQFA+yH5PwAAAAAtRHQ+AAAAgJhG+DwAAABgUcx4OwAAAICDG/A5AAAAQCAlejgAAACAIoLjNgAAAAAd82k1/oIrZUcVZ0AAAAAAAAA4QwAA+v5CLna/OjuevJr3DL29/f/////fPzxUVVVVVcU/kSsXz1VVpT8X0KRnERGBPwAAAAAAAMhC7zn6/kIu5j8kxIL/vb/OP7X0DNcIa6w/zFBG0quygz+EOk6b4NdVPwBB3r0EC4MR8D9uv4gaTzubPDUz+6k99u8/XdzYnBNgcbxhgHc+muzvP9FmhxB6XpC8hX9u6BXj7z8T9mc1UtKMPHSFFdOw2e8/+o75I4DOi7ze9t0pa9DvP2HI5mFO92A8yJt1GEXH7z+Z0zNb5KOQPIPzxso+vu8/bXuDXaaalzwPiflsWLXvP/zv/ZIatY4890dyK5Ks7z/RnC9wPb4+PKLR0zLso+8/C26QiTQDarwb0/6vZpvvPw69LypSVpW8UVsS0AGT7z9V6k6M74BQvMwxbMC9iu8/FvTVuSPJkbzgLamumoLvP69VXOnj04A8UY6lyJh67z9Ik6XqFRuAvHtRfTy4cu8/PTLeVfAfj7zqjYw4+WrvP79TEz+MiYs8dctv61tj7z8m6xF2nNmWvNRcBITgW+8/YC86PvfsmjyquWgxh1TvP504hsuC54+8Hdn8IlBN7z+Nw6ZEQW+KPNaMYog7Ru8/fQTksAV6gDyW3H2RST/vP5SoqOP9jpY8OGJ1bno47z99SHTyGF6HPD+msk/OMe8/8ucfmCtHgDzdfOJlRSvvP14IcT97uJa8gWP14d8k7z8xqwlt4feCPOHeH/WdHu8/+r9vGpshPbyQ2drQfxjvP7QKDHKCN4s8CwPkpoUS7z+Py86JkhRuPFYvPqmvDO8/tquwTXVNgzwVtzEK/gbvP0x0rOIBQoY8MdhM/HAB7z9K+NNdOd2PPP8WZLII/O4/BFuOO4Cjhrzxn5JfxfbuP2hQS8ztSpK8y6k6N6fx7j+OLVEb+AeZvGbYBW2u7O4/0jaUPujRcbz3n+U02+fuPxUbzrMZGZm85agTwy3j7j9tTCqnSJ+FPCI0Ekym3u4/imkoemASk7wcgKwERdruP1uJF0iPp1i8Ki73IQrW7j8bmklnmyx8vJeoUNn10e4/EazCYO1jQzwtiWFgCM7uP+9kBjsJZpY8VwAd7UHK7j95A6Ha4cxuPNA8wbWixu4/MBIPP47/kzze09fwKsPuP7CvervOkHY8Jyo21dq/7j934FTrvR2TPA3d/ZmyvO4/jqNxADSUj7ynLJ12srnuP0mjk9zM3oe8QmbPotq27j9fOA+9xt54vIJPnVYrtO4/9lx77EYShrwPkl3KpLHuP47X/RgFNZM82ie1Nkev7j8Fm4ovt5h7PP3Hl9QSre4/CVQc4uFjkDwpVEjdB6vuP+rGGVCFxzQ8t0ZZiiap7j81wGQr5jKUPEghrRVvp+4/n3aZYUrkjLwJ3Ha54aXuP6hN7zvFM4y8hVU6sH6k7j+u6SuJeFOEvCDDzDRGo+4/WFhWeN3Ok7wlIlWCOKLuP2QZfoCqEFc8c6lM1FWh7j8oIl6/77OTvM07f2aeoO4/grk0h60Sary/2gt1EqDuP+6pbbjvZ2O8LxplPLKf7j9RiOBUPdyAvISUUfl9n+4/zz5afmQfeLx0X+zodZ/uP7B9i8BK7oa8dIGlSJqf7j+K5lUeMhmGvMlnQlbrn+4/09QJXsuckDw/Xd5PaaDuPx2lTbncMnu8hwHrcxSh7j9rwGdU/eyUPDLBMAHtoe4/VWzWq+HrZTxiTs8286LuP0LPsy/FoYi8Eho+VCek7j80NzvxtmmTvBPOTJmJpe4/Hv8ZOoRegLytxyNGGqfuP25XcthQ1JS87ZJEm9mo7j8Aig5bZ62QPJlmitnHqu4/tOrwwS+3jTzboCpC5azuP//nxZxgtmW8jES1FjKv7j9EX/NZg/Z7PDZ3FZmuse4/gz0epx8Jk7zG/5ELW7TuPykebIu4qV285cXNsDe37j9ZuZB8+SNsvA9SyMtEuu4/qvn0IkNDkrxQTt6fgr3uP0uOZtdsyoW8ugfKcPHA7j8nzpEr/K9xPJDwo4KRxO4/u3MK4TXSbTwjI+MZY8juP2MiYiIExYe8ZeVde2bM7j/VMeLjhhyLPDMtSuyb0O4/Fbu809G7kbxdJT6yA9XuP9Ix7pwxzJA8WLMwE57Z7j+zWnNuhGmEPL/9eVVr3u4/tJ2Ol83fgrx689O/a+PuP4czy5J3Gow8rdNamZ/o7j/62dFKj3uQvGa2jSkH7u4/uq7cVtnDVbz7FU+4ovPuP0D2pj0OpJC8OlnljXL57j80k6049NZovEde+/J2/+4/NYpYa+LukbxKBqEwsAXvP83dXwrX/3Q80sFLkB4M7z+smJL6+72RvAke11vCEu8/swyvMK5uczycUoXdmxnvP5T9n1wy4448etD/X6sg7z+sWQnRj+CEPEvRVy7xJ+8/ZxpOOK/NYzy15waUbS/vP2gZkmwsa2c8aZDv3CA37z/StcyDGIqAvPrDXVULP+8/b/r/P12tj7x8iQdKLUfvP0mpdTiuDZC88okNCIdP7z+nBz2mhaN0PIek+9wYWO8/DyJAIJ6RgryYg8kW42DvP6ySwdVQWo48hTLbA+Zp7z9LawGsWTqEPGC0AfMhc+8/Hz60ByHVgrxfm3szl3zvP8kNRzu5Kom8KaH1FEaG7z/TiDpgBLZ0PPY/i+cukO8/cXKdUezFgzyDTMf7UZrvP/CR048S94+82pCkoq+k7z99dCPimK6NvPFnji1Ir+8/CCCqQbzDjjwnWmHuG7rvPzLrqcOUK4Q8l7prNyvF7z/uhdExqWSKPEBFblt20O8/7eM75Lo3jrwUvpyt/dvvP53NkU07iXc82JCegcHn7z+JzGBBwQVTPPFxjyvC8+8/0XSeAFedvSqAcFIP//8+JwoAAABkAAAA6AMAABAnAACghgEAQEIPAICWmAAA4fUFGAAAADUAAABxAAAAa////877//+Sv///AAAAAAAAAAAZAAoAGRkZAAAAAAUAAAAAAAAJAAAAAAsAAAAAAAAAABkAEQoZGRkDCgcAAQAJCxgAAAkGCwAACwAGGQAAABkZGQBB8c4ECyEOAAAAAAAAAAAZAAoNGRkZAA0AAAIACQ4AAAAJAA4AAA4AQavPBAsBDABBt88ECxUTAAAAABMAAAAACQwAAAAAAAwAAAwAQeXPBAsBEABB8c8ECxUPAAAABA8AAAAACRAAAAAAABAAABAAQZ/QBAsBEgBBq9AECx4RAAAAABEAAAAACRIAAAAAABIAABIAABoAAAAaGhoAQeLQBAsOGgAAABoaGgAAAAAAAAkAQZPRBAsBFABBn9EECxUXAAAAABcAAAAACRQAAAAAABQAABQAQc3RBAsBFgBB2dEECycVAAAAABUAAAAACRYAAAAAABYAABYAADAxMjM0NTY3ODlBQkNERUYAQaTSBAsCpgEAQczSBAsI//////////8AQZDTBAsBBQBBnNMECwKhAQBBtNMECw6iAQAAowEAACgrAQAABABBzNMECwEBAEHc0wQLBf////8KAEGg1AQLB5ApAQBAMQI=";if(!K(L)){var M=L;L=d.locateFile?d.locateFile(M,q):q+M} +function ca(){var a=L;return Promise.resolve().then(()=>{if(a==L&&v)var b=new Uint8Array(v);else{if(K(a)){b=atob(a.slice(37));for(var c=new Uint8Array(b.length),e=0;eWebAssembly.instantiate(c,a)).then(c=>c).then(b,c=>{u(`failed to asynchronously prepare wasm: ${c}`);w(c)})}function ea(a,b){return da(a,b)} +var N=a=>{for(;0{for(var c=b+NaN,e=b;a[e]&&!(e>=c);)++e;if(16f?c+=String.fromCharCode(f):(f-=65536,c+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else c+= +String.fromCharCode(f)}return c},fa=[0,31,60,91,121,152,182,213,244,274,305,335],ha=[0,31,59,90,120,151,181,212,243,273,304,334],Q=a=>{for(var b=0,c=0;c=e?b++:2047>=e?b+=2:55296<=e&&57343>=e?(b+=4,++c):b+=3}return b},R=(a,b,c)=>{var e=A;if(0=g){var l=a.charCodeAt(++f);g=65536+((g&1023)<<10)|l&1023}if(127>=g){if(b>=c)break;e[b++]=g}else{if(2047>=g){if(b+1>=c)break;e[b++]=192|g>> +6}else{if(65535>=g){if(b+2>=c)break;e[b++]=224|g>>12}else{if(b+3>=c)break;e[b++]=240|g>>18;e[b++]=128|g>>12&63}e[b++]=128|g>>6&63}e[b++]=128|g&63}}e[b]=0}},T=a=>{var b=Q(a)+1,c=S(b);c&&R(a,c,b);return c};function U(){} +var ia=[null,[],[]],ka=(a,b,c,e)=>{var f={string:h=>{var r=0;if(null!==h&&void 0!==h&&0!==h){r=Q(h)+1;var Y=V(r);R(h,Y,r);r=Y}return r},array:h=>{var r=V(h.length);z.set(h,r);return r}};a=d["_"+a];var g=[],l=0;if(e)for(var m=0;m{a=a?P(A,a):"";b=null!==b?JSON.parse(b?P(A,b):""):[];try{const e=d.externalCall(a,b);return e?T(e):null}catch(e){return d.HEAPU8[c]=1,T(e.message)}}; +var la={a:(a,b,c,e)=>{w(`Assertion failed: ${a?P(A,a):""}, at: `+[b?b?P(A,b):"":"unknown filename",c,e?e?P(A,e):"":"unknown function"])},i:function(a,b,c){a=new Date(1E3*(b+2097152>>>0<4194305-!!a?(a>>>0)+4294967296*b:NaN));B[c>>2]=a.getSeconds();B[c+4>>2]=a.getMinutes();B[c+8>>2]=a.getHours();B[c+12>>2]=a.getDate();B[c+16>>2]=a.getMonth();B[c+20>>2]=a.getFullYear()-1900;B[c+24>>2]=a.getDay();b=a.getFullYear();B[c+28>>2]=(0!==b%4||0===b%100&&0!==b%400?ha:fa)[a.getMonth()]+a.getDate()-1|0;B[c+36>> +2]=-(60*a.getTimezoneOffset());b=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();var e=(new Date(a.getFullYear(),0,1)).getTimezoneOffset();B[c+32>>2]=(b!=e&&a.getTimezoneOffset()==Math.min(e,b))|0},d:(a,b,c)=>{function e(t){return(t=t.toTimeString().match(/\(([A-Za-z ]+)\)$/))?t[1]:"GMT"}var f=(new Date).getFullYear(),g=new Date(f,0,1),l=new Date(f,6,1);f=g.getTimezoneOffset();var m=l.getTimezoneOffset();C[a>>2]=60*Math.max(f,m);B[b>>2]=Number(f!=m);a=e(g);b=e(l);a=T(a);b=T(b);m>2]=a, +C[c+4>>2]=b):(C[c>>2]=b,C[c+4>>2]=a)},b:()=>{w("")},g:U,f:function(a,b){a=a?P(A,a):"";let c;try{c=window.JSON.parse(a)}catch(e){c=a}0!==b?window.alert(a):window.console.log("DUMP",c)},e:()=>Date.now(),j:a=>{var b=A.length;a>>>=0;if(2147483648=c;c*=2){var e=b*(1+.2/c);e=Math.min(e,a+100663296);var f=Math;e=Math.max(a,e);a:{f=(f.min.call(f,2147483648,e+(65536-e%65536)%65536)-x.buffer.byteLength+65535)/65536;try{x.grow(f);D();var g=1;break a}catch(l){}g=void 0}if(g)return!0}return!1}, +c:(a,b,c,e)=>{for(var f=0,g=0;g>2],m=C[b+4>>2];b+=8;for(var t=0;t>2]=f;return 0},k:function(a){a=a?P(A,a):"";window.console.log(a)},h:function(a){a=a?P(A,a):"";return Date.parse(a)},l:function(a,b,c,e){a=a?P(A,a):"";b=b?P(A,b):"";c=c?P(A,c):"";c=`Quickjs -- ${a}: ${b}\n${c}`;0!==e?window.alert(c):window.console.error(c)}},X=function(){function a(c){X=c.exports;x=X.m;D();F.unshift(X.n); +H--;d.monitorRunDependencies?.(H);0==H&&(null!==I&&(clearInterval(I),I=null),J&&(c=J,J=null,c()));return X}var b={a:la};H++;d.monitorRunDependencies?.(H);if(d.instantiateWasm)try{return d.instantiateWasm(b,a)}catch(c){u(`Module.instantiateWasm callback failed with error: ${c}`),n(c)}ea(b,function(c){a(c.instance)}).catch(n);return{}}();d._evalInSandbox=(a,b)=>(d._evalInSandbox=X.o)(a,b);d._nukeSandbox=()=>(d._nukeSandbox=X.p)();d._init=(a,b)=>(d._init=X.q)(a,b); +d._commFun=(a,b)=>(d._commFun=X.r)(a,b);d._dumpMemoryUse=()=>(d._dumpMemoryUse=X.s)();var S=a=>(S=X.t)(a);d._free=a=>(d._free=X.u)(a);var W=()=>(W=X.w)(),ja=a=>(ja=X.x)(a),V=a=>(V=X.y)(a);d.ccall=ka;d.cwrap=(a,b,c,e)=>{var f=!c||c.every(g=>"number"===g||"boolean"===g);return"string"!==b&&f&&!e?d["_"+a]:function(){return ka(a,b,c,arguments,e)}};d.stringToNewUTF8=T;var Z;J=function ma(){Z||na();Z||(J=ma)}; +function na(){function a(){if(!Z&&(Z=!0,d.calledRun=!0,!y)){N(F);k(d);if(d.onRuntimeInitialized)d.onRuntimeInitialized();if(d.postRun)for("function"==typeof d.postRun&&(d.postRun=[d.postRun]);d.postRun.length;){var b=d.postRun.shift();G.unshift(b)}N(G)}}if(!(0=20" + }, + "license": "Apache-2.0" +} diff --git a/frontend/public/pdf.js/src/core/annotation.js b/frontend/public/pdf.js/src/core/annotation.js new file mode 100644 index 0000000..23d383e --- /dev/null +++ b/frontend/public/pdf.js/src/core/annotation.js @@ -0,0 +1,5151 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + AnnotationActionEventType, + AnnotationBorderStyleType, + AnnotationEditorType, + AnnotationFieldFlag, + AnnotationFlag, + AnnotationReplyType, + AnnotationType, + assert, + BASELINE_FACTOR, + FeatureTest, + getModificationDate, + IDENTITY_MATRIX, + info, + isArrayEqual, + LINE_DESCENT_FACTOR, + LINE_FACTOR, + OPS, + RenderingIntentFlag, + shadow, + stringToPDFString, + unreachable, + Util, + warn, +} from "../shared/util.js"; +import { + collectActions, + escapeString, + getInheritableProperty, + getParentToUpdate, + getRotationMatrix, + isNumberArray, + lookupMatrix, + lookupNormalRect, + lookupRect, + numberToString, + stringToAsciiOrUTF16BE, + stringToUTF16String, +} from "./core_utils.js"; +import { + createDefaultAppearance, + FakeUnicodeFont, + getPdfColor, + parseAppearanceStream, + parseDefaultAppearance, +} from "./default_appearance.js"; +import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js"; +import { Stream, StringStream } from "./stream.js"; +import { BaseStream } from "./base_stream.js"; +import { bidi } from "./bidi.js"; +import { Catalog } from "./catalog.js"; +import { ColorSpace } from "./colorspace.js"; +import { FileSpec } from "./file_spec.js"; +import { JpegStream } from "./jpeg_stream.js"; +import { ObjectLoader } from "./object_loader.js"; +import { OperatorList } from "./operator_list.js"; +import { XFAFactory } from "./xfa/factory.js"; + +class AnnotationFactory { + static createGlobals(pdfManager) { + return Promise.all([ + pdfManager.ensureCatalog("acroForm"), + pdfManager.ensureDoc("xfaDatasets"), + pdfManager.ensureCatalog("structTreeRoot"), + // Only necessary to prevent the `Catalog.baseUrl`-getter, used + // with some Annotations, from throwing and thus breaking parsing: + pdfManager.ensureCatalog("baseUrl"), + // Only necessary to prevent the `Catalog.attachments`-getter, used + // with "GoToE" actions, from throwing and thus breaking parsing: + pdfManager.ensureCatalog("attachments"), + ]).then( + ([acroForm, xfaDatasets, structTreeRoot, baseUrl, attachments]) => ({ + pdfManager, + acroForm: acroForm instanceof Dict ? acroForm : Dict.empty, + xfaDatasets, + structTreeRoot, + baseUrl, + attachments, + }), + reason => { + warn(`createGlobals: "${reason}".`); + return null; + } + ); + } + + /** + * Create an `Annotation` object of the correct type for the given reference + * to an annotation dictionary. This yields a promise that is resolved when + * the `Annotation` object is constructed. + * + * @param {XRef} xref + * @param {Object} ref + * @params {Object} annotationGlobals + * @param {Object} idFactory + * @param {boolean} [collectFields] + * @param {Object} [orphanFields] + * @param {Object} [pageRef] + * @returns {Promise} A promise that is resolved with an {Annotation} + * instance. + */ + static async create( + xref, + ref, + annotationGlobals, + idFactory, + collectFields, + orphanFields, + pageRef + ) { + const pageIndex = collectFields + ? await this._getPageIndex(xref, ref, annotationGlobals.pdfManager) + : null; + + return annotationGlobals.pdfManager.ensure(this, "_create", [ + xref, + ref, + annotationGlobals, + idFactory, + collectFields, + orphanFields, + pageIndex, + pageRef, + ]); + } + + /** + * @private + */ + static _create( + xref, + ref, + annotationGlobals, + idFactory, + collectFields = false, + orphanFields = null, + pageIndex = null, + pageRef = null + ) { + const dict = xref.fetchIfRef(ref); + if (!(dict instanceof Dict)) { + return undefined; + } + + const { acroForm, pdfManager } = annotationGlobals; + const id = + ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`; + + // Determine the annotation's subtype. + let subtype = dict.get("Subtype"); + subtype = subtype instanceof Name ? subtype.name : null; + + // Return the right annotation object based on the subtype and field type. + const parameters = { + xref, + ref, + dict, + subtype, + id, + annotationGlobals, + collectFields, + orphanFields, + needAppearances: + !collectFields && acroForm.get("NeedAppearances") === true, + pageIndex, + evaluatorOptions: pdfManager.evaluatorOptions, + pageRef, + }; + + switch (subtype) { + case "Link": + return new LinkAnnotation(parameters); + + case "Text": + return new TextAnnotation(parameters); + + case "Widget": + let fieldType = getInheritableProperty({ dict, key: "FT" }); + fieldType = fieldType instanceof Name ? fieldType.name : null; + + switch (fieldType) { + case "Tx": + return new TextWidgetAnnotation(parameters); + case "Btn": + return new ButtonWidgetAnnotation(parameters); + case "Ch": + return new ChoiceWidgetAnnotation(parameters); + case "Sig": + return new SignatureWidgetAnnotation(parameters); + } + warn( + `Unimplemented widget field type "${fieldType}", ` + + "falling back to base field type." + ); + return new WidgetAnnotation(parameters); + + case "Popup": + return new PopupAnnotation(parameters); + + case "FreeText": + return new FreeTextAnnotation(parameters); + + case "Line": + return new LineAnnotation(parameters); + + case "Square": + return new SquareAnnotation(parameters); + + case "Circle": + return new CircleAnnotation(parameters); + + case "PolyLine": + return new PolylineAnnotation(parameters); + + case "Polygon": + return new PolygonAnnotation(parameters); + + case "Caret": + return new CaretAnnotation(parameters); + + case "Ink": + return new InkAnnotation(parameters); + + case "Highlight": + return new HighlightAnnotation(parameters); + + case "Underline": + return new UnderlineAnnotation(parameters); + + case "Squiggly": + return new SquigglyAnnotation(parameters); + + case "StrikeOut": + return new StrikeOutAnnotation(parameters); + + case "Stamp": + return new StampAnnotation(parameters); + + case "FileAttachment": + return new FileAttachmentAnnotation(parameters); + + default: + if (!collectFields) { + if (!subtype) { + warn("Annotation is missing the required /Subtype."); + } else { + warn( + `Unimplemented annotation type "${subtype}", ` + + "falling back to base annotation." + ); + } + } + return new Annotation(parameters); + } + } + + static async _getPageIndex(xref, ref, pdfManager) { + try { + const annotDict = await xref.fetchIfRefAsync(ref); + if (!(annotDict instanceof Dict)) { + return -1; + } + const pageRef = annotDict.getRaw("P"); + if (pageRef instanceof Ref) { + try { + const pageIndex = await pdfManager.ensureCatalog("getPageIndex", [ + pageRef, + ]); + return pageIndex; + } catch (ex) { + info(`_getPageIndex -- not a valid page reference: "${ex}".`); + } + } + if (annotDict.has("Kids")) { + return -1; // Not an annotation reference. + } + // Fallback to, potentially, checking the annotations of all pages. + // PLEASE NOTE: This could force the *entire* PDF document to load, + // hence it absolutely cannot be done unconditionally. + const numPages = await pdfManager.ensureDoc("numPages"); + + for (let pageIndex = 0; pageIndex < numPages; pageIndex++) { + const page = await pdfManager.getPage(pageIndex); + const annotations = await pdfManager.ensure(page, "annotations"); + + for (const annotRef of annotations) { + if (annotRef instanceof Ref && isRefsEqual(annotRef, ref)) { + return pageIndex; + } + } + } + } catch (ex) { + warn(`_getPageIndex: "${ex}".`); + } + return -1; + } + + static generateImages(annotations, xref, isOffscreenCanvasSupported) { + if (!isOffscreenCanvasSupported) { + warn( + "generateImages: OffscreenCanvas is not supported, cannot save or print some annotations with images." + ); + return null; + } + let imagePromises; + for (const { bitmapId, bitmap } of annotations) { + if (!bitmap) { + continue; + } + imagePromises ||= new Map(); + imagePromises.set(bitmapId, StampAnnotation.createImage(bitmap, xref)); + } + + return imagePromises; + } + + static async saveNewAnnotations( + evaluator, + task, + annotations, + imagePromises, + changes + ) { + const xref = evaluator.xref; + let baseFontRef; + const promises = []; + const { isOffscreenCanvasSupported } = evaluator.options; + + for (const annotation of annotations) { + if (annotation.deleted) { + continue; + } + switch (annotation.annotationType) { + case AnnotationEditorType.FREETEXT: + if (!baseFontRef) { + const baseFont = new Dict(xref); + baseFont.set("BaseFont", Name.get("Helvetica")); + baseFont.set("Type", Name.get("Font")); + baseFont.set("Subtype", Name.get("Type1")); + baseFont.set("Encoding", Name.get("WinAnsiEncoding")); + baseFontRef = xref.getNewTemporaryRef(); + changes.put(baseFontRef, { + data: baseFont, + }); + } + promises.push( + FreeTextAnnotation.createNewAnnotation(xref, annotation, changes, { + evaluator, + task, + baseFontRef, + }) + ); + break; + case AnnotationEditorType.HIGHLIGHT: + if (annotation.quadPoints) { + promises.push( + HighlightAnnotation.createNewAnnotation(xref, annotation, changes) + ); + } else { + promises.push( + InkAnnotation.createNewAnnotation(xref, annotation, changes) + ); + } + break; + case AnnotationEditorType.INK: + promises.push( + InkAnnotation.createNewAnnotation(xref, annotation, changes) + ); + break; + case AnnotationEditorType.STAMP: + const image = isOffscreenCanvasSupported + ? await imagePromises?.get(annotation.bitmapId) + : null; + if (image?.imageStream) { + const { imageStream, smaskStream } = image; + if (smaskStream) { + const smaskRef = xref.getNewTemporaryRef(); + changes.put(smaskRef, { + data: smaskStream, + }); + imageStream.dict.set("SMask", smaskRef); + } + const imageRef = (image.imageRef = xref.getNewTemporaryRef()); + changes.put(imageRef, { + data: imageStream, + }); + image.imageStream = image.smaskStream = null; + } + promises.push( + StampAnnotation.createNewAnnotation(xref, annotation, changes, { + image, + }) + ); + break; + case AnnotationEditorType.SIGNATURE: + promises.push( + StampAnnotation.createNewAnnotation(xref, annotation, changes, {}) + ); + break; + } + } + + return { + annotations: await Promise.all(promises), + }; + } + + static async printNewAnnotations( + annotationGlobals, + evaluator, + task, + annotations, + imagePromises + ) { + if (!annotations) { + return null; + } + + const { options, xref } = evaluator; + const promises = []; + for (const annotation of annotations) { + if (annotation.deleted) { + continue; + } + switch (annotation.annotationType) { + case AnnotationEditorType.FREETEXT: + promises.push( + FreeTextAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + evaluator, + task, + evaluatorOptions: options, + } + ) + ); + break; + case AnnotationEditorType.HIGHLIGHT: + if (annotation.quadPoints) { + promises.push( + HighlightAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + evaluatorOptions: options, + } + ) + ); + } else { + promises.push( + InkAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + evaluatorOptions: options, + } + ) + ); + } + break; + case AnnotationEditorType.INK: + promises.push( + InkAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + evaluatorOptions: options, + } + ) + ); + break; + case AnnotationEditorType.STAMP: + const image = options.isOffscreenCanvasSupported + ? await imagePromises?.get(annotation.bitmapId) + : null; + if (image?.imageStream) { + const { imageStream, smaskStream } = image; + if (smaskStream) { + imageStream.dict.set("SMask", smaskStream); + } + image.imageRef = new JpegStream(imageStream, imageStream.length); + image.imageStream = image.smaskStream = null; + } + promises.push( + StampAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + image, + evaluatorOptions: options, + } + ) + ); + break; + case AnnotationEditorType.SIGNATURE: + promises.push( + StampAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + evaluatorOptions: options, + } + ) + ); + break; + } + } + + return Promise.all(promises); + } +} + +function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) { + if (!Array.isArray(color)) { + return defaultColor; + } + + const rgbColor = defaultColor || new Uint8ClampedArray(3); + switch (color.length) { + case 0: // Transparent, which we indicate with a null value + return null; + + case 1: // Convert grayscale to RGB + ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0); + return rgbColor; + + case 3: // Convert RGB percentages to RGB + ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0); + return rgbColor; + + case 4: // Convert CMYK to RGB + ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0); + return rgbColor; + + default: + return defaultColor; + } +} + +function getPdfColorArray(color) { + return Array.from(color, c => c / 255); +} + +function getQuadPoints(dict, rect) { + // The region is described as a number of quadrilaterals. + // Each quadrilateral must consist of eight coordinates. + const quadPoints = dict.getArray("QuadPoints"); + if ( + !isNumberArray(quadPoints, null) || + quadPoints.length === 0 || + quadPoints.length % 8 > 0 + ) { + return null; + } + + const newQuadPoints = new Float32Array(quadPoints.length); + for (let i = 0, ii = quadPoints.length; i < ii; i += 8) { + // Each series of eight numbers represents the coordinates for one + // quadrilateral in the order [x1, y1, x2, y2, x3, y3, x4, y4]. + // Convert this to an array of objects with x and y coordinates. + const [x1, y1, x2, y2, x3, y3, x4, y4] = quadPoints.slice(i, i + 8); + const minX = Math.min(x1, x2, x3, x4); + const maxX = Math.max(x1, x2, x3, x4); + const minY = Math.min(y1, y2, y3, y4); + const maxY = Math.max(y1, y2, y3, y4); + // The quadpoints should be ignored if any coordinate in the array + // lies outside the region specified by the rectangle. The rectangle + // can be `null` for markup annotations since their rectangle may be + // incorrect (fixes bug 1538111). + if ( + rect !== null && + (minX < rect[0] || maxX > rect[2] || minY < rect[1] || maxY > rect[3]) + ) { + return null; + } + // The PDF specification states in section 12.5.6.10 (figure 64) that the + // order of the quadpoints should be bottom left, bottom right, top right + // and top left. However, in practice PDF files use a different order, + // namely bottom left, bottom right, top left and top right (this is also + // mentioned on https://github.com/highkite/pdfAnnotate#QuadPoints), so + // this is the actual order we should work with. However, the situation is + // even worse since Adobe's own applications and other applications violate + // the specification and create annotations with other orders, namely top + // left, top right, bottom left and bottom right or even top left, + // top right, bottom right and bottom left. To avoid inconsistency and + // broken rendering, we normalize all lists to put the quadpoints in the + // same standard order (see https://stackoverflow.com/a/10729881). + newQuadPoints.set([minX, maxY, maxX, maxY, minX, minY, maxX, minY], i); + } + return newQuadPoints; +} + +function getTransformMatrix(rect, bbox, matrix) { + // 12.5.5: Algorithm: Appearance streams + const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox( + bbox, + matrix + ); + if (minX === maxX || minY === maxY) { + // From real-life file, bbox was [0, 0, 0, 0]. In this case, + // just apply the transform for rect + return [1, 0, 0, 1, rect[0], rect[1]]; + } + + const xRatio = (rect[2] - rect[0]) / (maxX - minX); + const yRatio = (rect[3] - rect[1]) / (maxY - minY); + return [ + xRatio, + 0, + 0, + yRatio, + rect[0] - minX * xRatio, + rect[1] - minY * yRatio, + ]; +} + +class Annotation { + constructor(params) { + const { dict, xref, annotationGlobals, ref, orphanFields } = params; + const parentRef = orphanFields?.get(ref); + if (parentRef) { + dict.set("Parent", parentRef); + } + + this.setTitle(dict.get("T")); + this.setContents(dict.get("Contents")); + this.setModificationDate(dict.get("M")); + this.setFlags(dict.get("F")); + this.setRectangle(dict.getArray("Rect")); + this.setColor(dict.getArray("C")); + this.setBorderStyle(dict); + this.setAppearance(dict); + this.setOptionalContent(dict); + + const MK = dict.get("MK"); + this.setBorderAndBackgroundColors(MK); + this.setRotation(MK, dict); + this.ref = params.ref instanceof Ref ? params.ref : null; + + this._streams = []; + if (this.appearance) { + this._streams.push(this.appearance); + } + + // The annotation cannot be changed (neither its position/visibility nor its + // contents), hence we can just display its appearance and don't generate + // a HTML element for it. + const isLocked = !!(this.flags & AnnotationFlag.LOCKED); + const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS); + + // Expose public properties using a data object. + this.data = { + annotationFlags: this.flags, + borderStyle: this.borderStyle, + color: this.color, + backgroundColor: this.backgroundColor, + borderColor: this.borderColor, + rotation: this.rotation, + contentsObj: this._contents, + hasAppearance: !!this.appearance, + id: params.id, + modificationDate: this.modificationDate, + rect: this.rectangle, + subtype: params.subtype, + hasOwnCanvas: false, + noRotate: !!(this.flags & AnnotationFlag.NOROTATE), + noHTML: isLocked && isContentLocked, + isEditable: false, + structParent: -1, + }; + + if (annotationGlobals.structTreeRoot) { + let structParent = dict.get("StructParent"); + this.data.structParent = structParent = + Number.isInteger(structParent) && structParent >= 0 ? structParent : -1; + + annotationGlobals.structTreeRoot.addAnnotationIdToPage( + params.pageRef, + structParent + ); + } + + if (params.collectFields) { + // Fields can act as container for other fields and have + // some actions even if no Annotation inherit from them. + // Those fields can be referenced by CO (calculation order). + const kids = dict.get("Kids"); + if (Array.isArray(kids)) { + const kidIds = []; + for (const kid of kids) { + if (kid instanceof Ref) { + kidIds.push(kid.toString()); + } + } + if (kidIds.length !== 0) { + this.data.kidIds = kidIds; + } + } + + this.data.actions = collectActions(xref, dict, AnnotationActionEventType); + this.data.fieldName = this._constructFieldName(dict); + this.data.pageIndex = params.pageIndex; + } + + const it = dict.get("IT"); + if (it instanceof Name) { + this.data.it = it.name; + } + + this._isOffscreenCanvasSupported = + params.evaluatorOptions.isOffscreenCanvasSupported; + this._fallbackFontDict = null; + this._needAppearances = false; + } + + /** + * @private + */ + _hasFlag(flags, flag) { + return !!(flags & flag); + } + + _buildFlags(noView, noPrint) { + let { flags } = this; + if (noView === undefined) { + if (noPrint === undefined) { + return undefined; + } + if (noPrint) { + return flags & ~AnnotationFlag.PRINT; + } + return (flags & ~AnnotationFlag.HIDDEN) | AnnotationFlag.PRINT; + } + + if (noView) { + flags |= AnnotationFlag.PRINT; + if (noPrint) { + // display === 1. + return (flags & ~AnnotationFlag.NOVIEW) | AnnotationFlag.HIDDEN; + } + // display === 3. + return (flags & ~AnnotationFlag.HIDDEN) | AnnotationFlag.NOVIEW; + } + + flags &= ~(AnnotationFlag.HIDDEN | AnnotationFlag.NOVIEW); + if (noPrint) { + // display === 2. + return flags & ~AnnotationFlag.PRINT; + } + + // display === 0. + return flags | AnnotationFlag.PRINT; + } + + /** + * @private + */ + _isViewable(flags) { + return ( + !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && + !this._hasFlag(flags, AnnotationFlag.NOVIEW) + ); + } + + /** + * @private + */ + _isPrintable(flags) { + // In Acrobat, hidden flag cancels the print one + // (see annotation_hidden_print.pdf). + return ( + this._hasFlag(flags, AnnotationFlag.PRINT) && + !this._hasFlag(flags, AnnotationFlag.HIDDEN) && + !this._hasFlag(flags, AnnotationFlag.INVISIBLE) + ); + } + + /** + * Check if the annotation must be displayed by taking into account + * the value found in the annotationStorage which may have been set + * through JS. + * + * @public + * @memberof Annotation + * @param {AnnotationStorage} [annotationStorage] - Storage for annotation + * @param {boolean} [_renderForms] - if true widgets are rendered thanks to + * the annotation layer. + */ + mustBeViewed(annotationStorage, _renderForms) { + const noView = annotationStorage?.get(this.data.id)?.noView; + if (noView !== undefined) { + return !noView; + } + return this.viewable && !this._hasFlag(this.flags, AnnotationFlag.HIDDEN); + } + + /** + * Check if the annotation must be printed by taking into account + * the value found in the annotationStorage which may have been set + * through JS. + * + * @public + * @memberof Annotation + * @param {AnnotationStorage} [annotationStorage] - Storage for annotation + */ + mustBePrinted(annotationStorage) { + const noPrint = annotationStorage?.get(this.data.id)?.noPrint; + if (noPrint !== undefined) { + return !noPrint; + } + return this.printable; + } + + mustBeViewedWhenEditing(isEditing, modifiedIds = null) { + return isEditing ? !this.data.isEditable : !modifiedIds?.has(this.data.id); + } + + /** + * @type {boolean} + */ + get viewable() { + if (this.data.quadPoints === null) { + return false; + } + if (this.flags === 0) { + return true; + } + return this._isViewable(this.flags); + } + + /** + * @type {boolean} + */ + get printable() { + if (this.data.quadPoints === null) { + return false; + } + if (this.flags === 0) { + return false; + } + return this._isPrintable(this.flags); + } + + /** + * @private + */ + _parseStringHelper(data) { + const str = typeof data === "string" ? stringToPDFString(data) : ""; + const dir = str && bidi(str).dir === "rtl" ? "rtl" : "ltr"; + + return { str, dir }; + } + + setDefaultAppearance(params) { + const { dict, annotationGlobals } = params; + + const defaultAppearance = + getInheritableProperty({ dict, key: "DA" }) || + annotationGlobals.acroForm.get("DA"); + this._defaultAppearance = + typeof defaultAppearance === "string" ? defaultAppearance : ""; + this.data.defaultAppearanceData = parseDefaultAppearance( + this._defaultAppearance + ); + } + + /** + * Set the title. + * + * @param {string} title - The title of the annotation, used e.g. with + * PopupAnnotations. + */ + setTitle(title) { + this._title = this._parseStringHelper(title); + } + + /** + * Set the contents. + * + * @param {string} contents - Text to display for the annotation or, if the + * type of annotation does not display text, a + * description of the annotation's contents + */ + setContents(contents) { + this._contents = this._parseStringHelper(contents); + } + + /** + * Set the modification date. + * + * @public + * @memberof Annotation + * @param {string} modificationDate - PDF date string that indicates when the + * annotation was last modified + */ + setModificationDate(modificationDate) { + this.modificationDate = + typeof modificationDate === "string" ? modificationDate : null; + } + + /** + * Set the flags. + * + * @public + * @memberof Annotation + * @param {number} flags - Unsigned 32-bit integer specifying annotation + * characteristics + * @see {@link shared/util.js} + */ + setFlags(flags) { + this.flags = Number.isInteger(flags) && flags > 0 ? flags : 0; + if ( + this.flags & AnnotationFlag.INVISIBLE && + this.constructor.name !== "Annotation" + ) { + // From the pdf spec v1.7, section 12.5.3 (Annotation Flags): + // If set, do not display the annotation if it does not belong to one of + // the standard annotation types and no annotation handler is available. + // + // So we can remove the flag in case we have a known annotation type. + this.flags ^= AnnotationFlag.INVISIBLE; + } + } + + /** + * Check if a provided flag is set. + * + * @public + * @memberof Annotation + * @param {number} flag - Hexadecimal representation for an annotation + * characteristic + * @returns {boolean} + * @see {@link shared/util.js} + */ + hasFlag(flag) { + return this._hasFlag(this.flags, flag); + } + + /** + * Set the rectangle. + * + * @public + * @memberof Annotation + * @param {Array} rectangle - The rectangle array with exactly four entries + */ + setRectangle(rectangle) { + this.rectangle = lookupNormalRect(rectangle, [0, 0, 0, 0]); + } + + /** + * Set the color and take care of color space conversion. + * The default value is black, in RGB color space. + * + * @public + * @memberof Annotation + * @param {Array} color - The color array containing either 0 + * (transparent), 1 (grayscale), 3 (RGB) or + * 4 (CMYK) elements + */ + setColor(color) { + this.color = getRgbColor(color); + } + + /** + * Set the line endings; should only be used with specific annotation types. + * @param {Array} lineEndings - The line endings array. + */ + setLineEndings(lineEndings) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + throw new Error("Not implemented: setLineEndings"); + } + this.lineEndings = ["None", "None"]; // The default values. + + if (Array.isArray(lineEndings) && lineEndings.length === 2) { + for (let i = 0; i < 2; i++) { + const obj = lineEndings[i]; + + if (obj instanceof Name) { + switch (obj.name) { + case "None": + continue; + case "Square": + case "Circle": + case "Diamond": + case "OpenArrow": + case "ClosedArrow": + case "Butt": + case "ROpenArrow": + case "RClosedArrow": + case "Slash": + this.lineEndings[i] = obj.name; + continue; + } + } + warn(`Ignoring invalid lineEnding: ${obj}`); + } + } + } + + setRotation(mk, dict) { + this.rotation = 0; + let angle = mk instanceof Dict ? mk.get("R") || 0 : dict.get("Rotate") || 0; + if (Number.isInteger(angle) && angle !== 0) { + angle %= 360; + if (angle < 0) { + angle += 360; + } + if (angle % 90 === 0) { + this.rotation = angle; + } + } + } + + /** + * Set the color for background and border if any. + * The default values are transparent. + * + * @public + * @memberof Annotation + * @param {Dict} mk - The MK dictionary + */ + setBorderAndBackgroundColors(mk) { + if (mk instanceof Dict) { + this.borderColor = getRgbColor(mk.getArray("BC"), null); + this.backgroundColor = getRgbColor(mk.getArray("BG"), null); + } else { + this.borderColor = this.backgroundColor = null; + } + } + + /** + * Set the border style (as AnnotationBorderStyle object). + * + * @public + * @memberof Annotation + * @param {Dict} borderStyle - The border style dictionary + */ + setBorderStyle(borderStyle) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert(this.rectangle, "setRectangle must have been called previously."); + } + + this.borderStyle = new AnnotationBorderStyle(); + if (!(borderStyle instanceof Dict)) { + return; + } + if (borderStyle.has("BS")) { + const dict = borderStyle.get("BS"); + + if (dict instanceof Dict) { + const dictType = dict.get("Type"); + + if (!dictType || isName(dictType, "Border")) { + this.borderStyle.setWidth(dict.get("W"), this.rectangle); + this.borderStyle.setStyle(dict.get("S")); + this.borderStyle.setDashArray(dict.getArray("D")); + } + } + } else if (borderStyle.has("Border")) { + const array = borderStyle.getArray("Border"); + if (Array.isArray(array) && array.length >= 3) { + this.borderStyle.setHorizontalCornerRadius(array[0]); + this.borderStyle.setVerticalCornerRadius(array[1]); + this.borderStyle.setWidth(array[2], this.rectangle); + + if (array.length === 4) { + // Dash array available + this.borderStyle.setDashArray(array[3], /* forceStyle = */ true); + } + } + } else { + // There are no border entries in the dictionary. According to the + // specification, we should draw a solid border of width 1 in that + // case, but Adobe Reader did not implement that part of the + // specification and instead draws no border at all, so we do the same. + // See also https://github.com/mozilla/pdf.js/issues/6179. + this.borderStyle.setWidth(0); + } + } + + /** + * Set the (normal) appearance. + * + * @public + * @memberof Annotation + * @param {Dict} dict - The annotation's data dictionary + */ + setAppearance(dict) { + this.appearance = null; + + const appearanceStates = dict.get("AP"); + if (!(appearanceStates instanceof Dict)) { + return; + } + + // In case the normal appearance is a stream, then it is used directly. + const normalAppearanceState = appearanceStates.get("N"); + if (normalAppearanceState instanceof BaseStream) { + this.appearance = normalAppearanceState; + return; + } + if (!(normalAppearanceState instanceof Dict)) { + return; + } + + // In case the normal appearance is a dictionary, the `AS` entry provides + // the key of the stream in this dictionary. + const as = dict.get("AS"); + if (!(as instanceof Name) || !normalAppearanceState.has(as.name)) { + return; + } + const appearance = normalAppearanceState.get(as.name); + if (appearance instanceof BaseStream) { + this.appearance = appearance; + } + } + + setOptionalContent(dict) { + this.oc = null; + + const oc = dict.get("OC"); + if (oc instanceof Name) { + warn("setOptionalContent: Support for /Name-entry is not implemented."); + } else if (oc instanceof Dict) { + this.oc = oc; + } + } + + loadResources(keys, appearance) { + return appearance.dict.getAsync("Resources").then(resources => { + if (!resources) { + return undefined; + } + + const objectLoader = new ObjectLoader(resources, keys, resources.xref); + return objectLoader.load().then(function () { + return resources; + }); + }); + } + + async getOperatorList(evaluator, task, intent, annotationStorage) { + const { hasOwnCanvas, id, rect } = this.data; + let appearance = this.appearance; + const isUsingOwnCanvas = !!( + hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY + ); + if (isUsingOwnCanvas && (this.width === 0 || this.height === 0)) { + // Empty annotation, don't draw anything. + this.data.hasOwnCanvas = false; + return { + opList: new OperatorList(), + separateForm: false, + separateCanvas: false, + }; + } + if (!appearance) { + if (!isUsingOwnCanvas) { + return { + opList: new OperatorList(), + separateForm: false, + separateCanvas: false, + }; + } + appearance = new StringStream(""); + appearance.dict = new Dict(); + } + + const appearanceDict = appearance.dict; + const resources = await this.loadResources( + ["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], + appearance + ); + const bbox = lookupRect(appearanceDict.getArray("BBox"), [0, 0, 1, 1]); + const matrix = lookupMatrix( + appearanceDict.getArray("Matrix"), + IDENTITY_MATRIX + ); + const transform = getTransformMatrix(rect, bbox, matrix); + + const opList = new OperatorList(); + + let optionalContent; + if (this.oc) { + optionalContent = await evaluator.parseMarkedContentProps( + this.oc, + /* resources = */ null + ); + } + if (optionalContent !== undefined) { + opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); + } + + opList.addOp(OPS.beginAnnotation, [ + id, + rect, + transform, + matrix, + isUsingOwnCanvas, + ]); + + await evaluator.getOperatorList({ + stream: appearance, + task, + resources, + operatorList: opList, + fallbackFontDict: this._fallbackFontDict, + }); + opList.addOp(OPS.endAnnotation, []); + + if (optionalContent !== undefined) { + opList.addOp(OPS.endMarkedContent, []); + } + this.reset(); + return { opList, separateForm: false, separateCanvas: isUsingOwnCanvas }; + } + + async save(evaluator, task, annotationStorage, changes) { + return null; + } + + get hasTextContent() { + return false; + } + + async extractTextContent(evaluator, task, viewBox) { + if (!this.appearance) { + return; + } + + const resources = await this.loadResources( + ["ExtGState", "Font", "Properties", "XObject"], + this.appearance + ); + + const text = []; + const buffer = []; + let firstPosition = null; + const sink = { + desiredSize: Math.Infinity, + ready: true, + + enqueue(chunk, size) { + for (const item of chunk.items) { + if (item.str === undefined) { + continue; + } + firstPosition ||= item.transform.slice(-2); + buffer.push(item.str); + if (item.hasEOL) { + text.push(buffer.join("").trimEnd()); + buffer.length = 0; + } + } + }, + }; + + await evaluator.getTextContent({ + stream: this.appearance, + task, + resources, + includeMarkedContent: true, + keepWhiteSpace: true, + sink, + viewBox, + }); + this.reset(); + + if (buffer.length) { + text.push(buffer.join("").trimEnd()); + } + + if (text.length > 1 || text[0]) { + const appearanceDict = this.appearance.dict; + const bbox = lookupRect(appearanceDict.getArray("BBox"), null); + const matrix = lookupMatrix(appearanceDict.getArray("Matrix"), null); + + this.data.textPosition = this._transformPoint( + firstPosition, + bbox, + matrix + ); + this.data.textContent = text; + } + } + + _transformPoint(coords, bbox, matrix) { + const { rect } = this.data; + bbox ||= [0, 0, 1, 1]; + matrix ||= [1, 0, 0, 1, 0, 0]; + const transform = getTransformMatrix(rect, bbox, matrix); + transform[4] -= rect[0]; + transform[5] -= rect[1]; + coords = Util.applyTransform(coords, transform); + return Util.applyTransform(coords, matrix); + } + + /** + * Get field data for usage in JS sandbox. + * + * Field object is defined here: + * https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/js_api_reference.pdf#page=16 + * + * @public + * @memberof Annotation + * @returns {Object | null} + */ + getFieldObject() { + if (this.data.kidIds) { + return { + id: this.data.id, + actions: this.data.actions, + name: this.data.fieldName, + strokeColor: this.data.borderColor, + fillColor: this.data.backgroundColor, + type: "", + kidIds: this.data.kidIds, + page: this.data.pageIndex, + rotation: this.rotation, + }; + } + return null; + } + + /** + * Reset the annotation. + * + * This involves resetting the various streams that are either cached on the + * annotation instance or created during its construction. + * + * @public + * @memberof Annotation + */ + reset() { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.appearance && + !this._streams.includes(this.appearance) + ) { + unreachable("The appearance stream should always be reset."); + } + + for (const stream of this._streams) { + stream.reset(); + } + } + + /** + * Construct the (fully qualified) field name from the (partial) field + * names of the field and its ancestors. + * + * @private + * @memberof Annotation + * @param {Dict} dict - Complete widget annotation dictionary + * @returns {string} + */ + _constructFieldName(dict) { + // Both the `Parent` and `T` fields are optional. While at least one of + // them should be provided, bad PDF generators may fail to do so. + if (!dict.has("T") && !dict.has("Parent")) { + warn("Unknown field name, falling back to empty field name."); + return ""; + } + + // If no parent exists, the partial and fully qualified names are equal. + if (!dict.has("Parent")) { + return stringToPDFString(dict.get("T")); + } + + // Form the fully qualified field name by appending the partial name to + // the parent's fully qualified name, separated by a period. + const fieldName = []; + if (dict.has("T")) { + fieldName.unshift(stringToPDFString(dict.get("T"))); + } + + let loopDict = dict; + const visited = new RefSet(); + if (dict.objId) { + visited.put(dict.objId); + } + while (loopDict.has("Parent")) { + loopDict = loopDict.get("Parent"); + if ( + !(loopDict instanceof Dict) || + (loopDict.objId && visited.has(loopDict.objId)) + ) { + // Even though it is not allowed according to the PDF specification, + // bad PDF generators may provide a `Parent` entry that is not a + // dictionary, but `null` for example (issue 8143). + // + // If parent has been already visited, it means that we're + // in an infinite loop. + break; + } + if (loopDict.objId) { + visited.put(loopDict.objId); + } + + if (loopDict.has("T")) { + fieldName.unshift(stringToPDFString(loopDict.get("T"))); + } + } + return fieldName.join("."); + } + + get width() { + return this.data.rect[2] - this.data.rect[0]; + } + + get height() { + return this.data.rect[3] - this.data.rect[1]; + } +} + +/** + * Contains all data regarding an annotation's border style. + */ +class AnnotationBorderStyle { + constructor() { + this.width = 1; + this.rawWidth = 1; + this.style = AnnotationBorderStyleType.SOLID; + this.dashArray = [3]; + this.horizontalCornerRadius = 0; + this.verticalCornerRadius = 0; + } + + /** + * Set the width. + * + * @public + * @memberof AnnotationBorderStyle + * @param {number} width - The width. + * @param {Array} rect - The annotation `Rect` entry. + */ + setWidth(width, rect = [0, 0, 0, 0]) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + isNumberArray(rect, 4), + "A valid `rect` parameter must be provided." + ); + } + + // Some corrupt PDF generators may provide the width as a `Name`, + // rather than as a number (fixes issue 10385). + if (width instanceof Name) { + this.width = 0; // This is consistent with the behaviour in Adobe Reader. + return; + } + if (typeof width === "number") { + if (width > 0) { + this.rawWidth = width; + const maxWidth = (rect[2] - rect[0]) / 2; + const maxHeight = (rect[3] - rect[1]) / 2; + + // Ignore large `width`s, since they lead to the Annotation overflowing + // the size set by the `Rect` entry thus causing the `annotationLayer` + // to render it over the surrounding document (fixes bug1552113.pdf). + if ( + maxWidth > 0 && + maxHeight > 0 && + (width > maxWidth || width > maxHeight) + ) { + warn(`AnnotationBorderStyle.setWidth - ignoring width: ${width}`); + width = 1; + } + } + this.width = width; + } + } + + /** + * Set the style. + * + * @public + * @memberof AnnotationBorderStyle + * @param {Name} style - The annotation style. + * @see {@link shared/util.js} + */ + setStyle(style) { + if (!(style instanceof Name)) { + return; + } + switch (style.name) { + case "S": + this.style = AnnotationBorderStyleType.SOLID; + break; + + case "D": + this.style = AnnotationBorderStyleType.DASHED; + break; + + case "B": + this.style = AnnotationBorderStyleType.BEVELED; + break; + + case "I": + this.style = AnnotationBorderStyleType.INSET; + break; + + case "U": + this.style = AnnotationBorderStyleType.UNDERLINE; + break; + + default: + break; + } + } + + /** + * Set the dash array. + * + * @public + * @memberof AnnotationBorderStyle + * @param {Array} dashArray - The dash array with at least one element + * @param {boolean} [forceStyle] + */ + setDashArray(dashArray, forceStyle = false) { + // We validate the dash array, but we do not use it because CSS does not + // allow us to change spacing of dashes. For more information, visit + // http://www.w3.org/TR/css3-background/#the-border-style. + if (Array.isArray(dashArray)) { + // The PDF specification states that elements in the dash array, if + // present, must be non-negative numbers and must not all equal zero. + let isValid = true; + let allZeros = true; + for (const element of dashArray) { + const validNumber = +element >= 0; + if (!validNumber) { + isValid = false; + break; + } else if (element > 0) { + allZeros = false; + } + } + if (dashArray.length === 0 || (isValid && !allZeros)) { + this.dashArray = dashArray; + + if (forceStyle) { + // Even though we cannot use the dash array in the display layer, + // at least ensure that we use the correct border-style. + this.setStyle(Name.get("D")); + } + } else { + this.width = 0; // Adobe behavior when the array is invalid. + } + } else if (dashArray) { + this.width = 0; // Adobe behavior when the array is invalid. + } + } + + /** + * Set the horizontal corner radius (from a Border dictionary). + * + * @public + * @memberof AnnotationBorderStyle + * @param {number} radius - The horizontal corner radius. + */ + setHorizontalCornerRadius(radius) { + if (Number.isInteger(radius)) { + this.horizontalCornerRadius = radius; + } + } + + /** + * Set the vertical corner radius (from a Border dictionary). + * + * @public + * @memberof AnnotationBorderStyle + * @param {number} radius - The vertical corner radius. + */ + setVerticalCornerRadius(radius) { + if (Number.isInteger(radius)) { + this.verticalCornerRadius = radius; + } + } +} + +class MarkupAnnotation extends Annotation { + constructor(params) { + super(params); + + const { dict } = params; + + if (dict.has("IRT")) { + const rawIRT = dict.getRaw("IRT"); + this.data.inReplyTo = rawIRT instanceof Ref ? rawIRT.toString() : null; + + const rt = dict.get("RT"); + this.data.replyType = + rt instanceof Name ? rt.name : AnnotationReplyType.REPLY; + } + let popupRef = null; + + if (this.data.replyType === AnnotationReplyType.GROUP) { + // Subordinate annotations in a group should inherit + // the group attributes from the primary annotation. + const parent = dict.get("IRT"); + + this.setTitle(parent.get("T")); + this.data.titleObj = this._title; + + this.setContents(parent.get("Contents")); + this.data.contentsObj = this._contents; + + if (!parent.has("CreationDate")) { + this.data.creationDate = null; + } else { + this.setCreationDate(parent.get("CreationDate")); + this.data.creationDate = this.creationDate; + } + + if (!parent.has("M")) { + this.data.modificationDate = null; + } else { + this.setModificationDate(parent.get("M")); + this.data.modificationDate = this.modificationDate; + } + + popupRef = parent.getRaw("Popup"); + + if (!parent.has("C")) { + // Fall back to the default background color. + this.data.color = null; + } else { + this.setColor(parent.getArray("C")); + this.data.color = this.color; + } + } else { + this.data.titleObj = this._title; + + this.setCreationDate(dict.get("CreationDate")); + this.data.creationDate = this.creationDate; + + popupRef = dict.getRaw("Popup"); + + if (!dict.has("C")) { + // Fall back to the default background color. + this.data.color = null; + } + } + + this.data.popupRef = popupRef instanceof Ref ? popupRef.toString() : null; + + if (dict.has("RC")) { + this.data.richText = XFAFactory.getRichTextAsHtml(dict.get("RC")); + } + } + + /** + * Set the creation date. + * + * @public + * @memberof MarkupAnnotation + * @param {string} creationDate - PDF date string that indicates when the + * annotation was originally created + */ + setCreationDate(creationDate) { + this.creationDate = typeof creationDate === "string" ? creationDate : null; + } + + _setDefaultAppearance({ + xref, + extra, + strokeColor, + fillColor, + blendMode, + strokeAlpha, + fillAlpha, + pointsCallback, + }) { + let minX = Number.MAX_VALUE; + let minY = Number.MAX_VALUE; + let maxX = Number.MIN_VALUE; + let maxY = Number.MIN_VALUE; + + const buffer = ["q"]; + if (extra) { + buffer.push(extra); + } + if (strokeColor) { + buffer.push(`${strokeColor[0]} ${strokeColor[1]} ${strokeColor[2]} RG`); + } + if (fillColor) { + buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`); + } + + // If there are no quadpoints, the rectangle should be used instead. + // Convert the rectangle definition to a points array similar to how the + // quadpoints are defined. + const pointsArray = + this.data.quadPoints || + Float32Array.from([ + this.rectangle[0], + this.rectangle[3], + this.rectangle[2], + this.rectangle[3], + this.rectangle[0], + this.rectangle[1], + this.rectangle[2], + this.rectangle[1], + ]); + + for (let i = 0, ii = pointsArray.length; i < ii; i += 8) { + const [mX, MX, mY, MY] = pointsCallback( + buffer, + pointsArray.subarray(i, i + 8) + ); + minX = Math.min(minX, mX); + maxX = Math.max(maxX, MX); + minY = Math.min(minY, mY); + maxY = Math.max(maxY, MY); + } + buffer.push("Q"); + + const formDict = new Dict(xref); + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("Subtype", Name.get("Form")); + + const appearanceStream = new StringStream(buffer.join(" ")); + appearanceStream.dict = appearanceStreamDict; + formDict.set("Fm0", appearanceStream); + + const gsDict = new Dict(xref); + if (blendMode) { + gsDict.set("BM", Name.get(blendMode)); + } + if (typeof strokeAlpha === "number") { + gsDict.set("CA", strokeAlpha); + } + if (typeof fillAlpha === "number") { + gsDict.set("ca", fillAlpha); + } + + const stateDict = new Dict(xref); + stateDict.set("GS0", gsDict); + + const resources = new Dict(xref); + resources.set("ExtGState", stateDict); + resources.set("XObject", formDict); + + const appearanceDict = new Dict(xref); + appearanceDict.set("Resources", resources); + const bbox = (this.data.rect = [minX, minY, maxX, maxY]); + appearanceDict.set("BBox", bbox); + + this.appearance = new StringStream("/GS0 gs /Fm0 Do"); + this.appearance.dict = appearanceDict; + + // This method is only called if there is no appearance for the annotation, + // so `this.appearance` is not pushed yet in the `Annotation` constructor. + this._streams.push(this.appearance, appearanceStream); + } + + static async createNewAnnotation(xref, annotation, changes, params) { + const annotationRef = (annotation.ref ||= xref.getNewTemporaryRef()); + + const ap = await this.createNewAppearanceStream(annotation, xref, params); + let annotationDict; + + if (ap) { + const apRef = xref.getNewTemporaryRef(); + annotationDict = this.createNewDict(annotation, xref, { + apRef, + }); + changes.put(apRef, { + data: ap, + }); + } else { + annotationDict = this.createNewDict(annotation, xref, {}); + } + if (Number.isInteger(annotation.parentTreeId)) { + annotationDict.set("StructParent", annotation.parentTreeId); + } + + changes.put(annotationRef, { + data: annotationDict, + }); + + return { ref: annotationRef }; + } + + static async createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + params + ) { + const ap = await this.createNewAppearanceStream(annotation, xref, params); + const annotationDict = this.createNewDict( + annotation, + xref, + ap ? { ap } : {} + ); + + const newAnnotation = new this.prototype.constructor({ + dict: annotationDict, + xref, + annotationGlobals, + evaluatorOptions: params.evaluatorOptions, + }); + + if (annotation.ref) { + newAnnotation.ref = newAnnotation.refToReplace = annotation.ref; + } + + return newAnnotation; + } +} + +class WidgetAnnotation extends Annotation { + constructor(params) { + super(params); + + const { dict, xref, annotationGlobals } = params; + const data = this.data; + this._needAppearances = params.needAppearances; + + data.annotationType = AnnotationType.WIDGET; + if (data.fieldName === undefined) { + data.fieldName = this._constructFieldName(dict); + } + + if (data.actions === undefined) { + data.actions = collectActions(xref, dict, AnnotationActionEventType); + } + + let fieldValue = getInheritableProperty({ + dict, + key: "V", + getArray: true, + }); + data.fieldValue = this._decodeFormValue(fieldValue); + + const defaultFieldValue = getInheritableProperty({ + dict, + key: "DV", + getArray: true, + }); + data.defaultFieldValue = this._decodeFormValue(defaultFieldValue); + + if (fieldValue === undefined && annotationGlobals.xfaDatasets) { + // Try to figure out if we have something in the xfa dataset. + const path = this._title.str; + if (path) { + this._hasValueFromXFA = true; + data.fieldValue = fieldValue = + annotationGlobals.xfaDatasets.getValue(path); + } + } + + // When no "V" entry exists, let the fieldValue fallback to the "DV" entry + // (fixes issue13823.pdf). + if (fieldValue === undefined && data.defaultFieldValue !== null) { + data.fieldValue = data.defaultFieldValue; + } + + data.alternativeText = stringToPDFString(dict.get("TU") || ""); + + this.setDefaultAppearance(params); + + data.hasAppearance ||= + this._needAppearances && + data.fieldValue !== undefined && + data.fieldValue !== null; + + const fieldType = getInheritableProperty({ dict, key: "FT" }); + data.fieldType = fieldType instanceof Name ? fieldType.name : null; + + const localResources = getInheritableProperty({ dict, key: "DR" }); + const acroFormResources = annotationGlobals.acroForm.get("DR"); + const appearanceResources = this.appearance?.dict.get("Resources"); + + this._fieldResources = { + localResources, + acroFormResources, + appearanceResources, + mergedResources: Dict.merge({ + xref, + dictArray: [localResources, appearanceResources, acroFormResources], + mergeSubDicts: true, + }), + }; + + data.fieldFlags = getInheritableProperty({ dict, key: "Ff" }); + if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) { + data.fieldFlags = 0; + } + + data.password = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD); + data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY); + data.required = this.hasFieldFlag(AnnotationFieldFlag.REQUIRED); + data.hidden = + this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN) || + this._hasFlag(data.annotationFlags, AnnotationFlag.NOVIEW); + } + + /** + * Decode the given form value. + * + * @private + * @memberof WidgetAnnotation + * @param {Array|Name|string} formValue - The (possibly encoded) + * form value. + * @returns {Array|string|null} + */ + _decodeFormValue(formValue) { + if (Array.isArray(formValue)) { + return formValue + .filter(item => typeof item === "string") + .map(item => stringToPDFString(item)); + } else if (formValue instanceof Name) { + return stringToPDFString(formValue.name); + } else if (typeof formValue === "string") { + return stringToPDFString(formValue); + } + return null; + } + + /** + * Check if a provided field flag is set. + * + * @public + * @memberof WidgetAnnotation + * @param {number} flag - Hexadecimal representation for an annotation + * field characteristic + * @returns {boolean} + * @see {@link shared/util.js} + */ + hasFieldFlag(flag) { + return !!(this.data.fieldFlags & flag); + } + + /** @inheritdoc */ + _isViewable(flags) { + // We don't take into account the `NOVIEW` or `HIDDEN` flags here, + // since the visibility can be changed by js code, hence in case + // it's made viewable, we should render it (with visibility set to + // hidden). + // We don't take into account the `INVISIBLE` flag here, since we've a known + // annotation type. + return true; + } + + /** @inheritdoc */ + mustBeViewed(annotationStorage, renderForms) { + if (renderForms) { + return this.viewable; + } + return ( + super.mustBeViewed(annotationStorage, renderForms) && + !this._hasFlag(this.flags, AnnotationFlag.NOVIEW) + ); + } + + getRotationMatrix(annotationStorage) { + let rotation = annotationStorage?.get(this.data.id)?.rotation; + if (rotation === undefined) { + rotation = this.rotation; + } + + return rotation === 0 + ? IDENTITY_MATRIX + : getRotationMatrix(rotation, this.width, this.height); + } + + getBorderAndBackgroundAppearances(annotationStorage) { + let rotation = annotationStorage?.get(this.data.id)?.rotation; + if (rotation === undefined) { + rotation = this.rotation; + } + + if (!this.backgroundColor && !this.borderColor) { + return ""; + } + const rect = + rotation === 0 || rotation === 180 + ? `0 0 ${this.width} ${this.height} re` + : `0 0 ${this.height} ${this.width} re`; + + let str = ""; + if (this.backgroundColor) { + str = `${getPdfColor( + this.backgroundColor, + /* isFill */ true + )} ${rect} f `; + } + + if (this.borderColor) { + const borderWidth = this.borderStyle.width || 1; + str += `${borderWidth} w ${getPdfColor( + this.borderColor, + /* isFill */ false + )} ${rect} S `; + } + + return str; + } + + async getOperatorList(evaluator, task, intent, annotationStorage) { + // Do not render form elements on the canvas when interactive forms are + // enabled. The display layer is responsible for rendering them instead. + if ( + intent & RenderingIntentFlag.ANNOTATIONS_FORMS && + !(this instanceof SignatureWidgetAnnotation) && + !this.data.noHTML && + !this.data.hasOwnCanvas + ) { + return { + opList: new OperatorList(), + separateForm: true, + separateCanvas: false, + }; + } + + if (!this._hasText) { + return super.getOperatorList(evaluator, task, intent, annotationStorage); + } + + const content = await this._getAppearance( + evaluator, + task, + intent, + annotationStorage + ); + if (this.appearance && content === null) { + return super.getOperatorList(evaluator, task, intent, annotationStorage); + } + + const opList = new OperatorList(); + + // Even if there is an appearance stream, ignore it. This is the + // behaviour used by Adobe Reader. + if (!this._defaultAppearance || content === null) { + return { opList, separateForm: false, separateCanvas: false }; + } + + const isUsingOwnCanvas = !!( + this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY + ); + + const matrix = [1, 0, 0, 1, 0, 0]; + const bbox = [0, 0, this.width, this.height]; + const transform = getTransformMatrix(this.data.rect, bbox, matrix); + + let optionalContent; + if (this.oc) { + optionalContent = await evaluator.parseMarkedContentProps( + this.oc, + /* resources = */ null + ); + } + if (optionalContent !== undefined) { + opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); + } + + opList.addOp(OPS.beginAnnotation, [ + this.data.id, + this.data.rect, + transform, + this.getRotationMatrix(annotationStorage), + isUsingOwnCanvas, + ]); + + const stream = new StringStream(content); + await evaluator.getOperatorList({ + stream, + task, + resources: this._fieldResources.mergedResources, + operatorList: opList, + }); + opList.addOp(OPS.endAnnotation, []); + + if (optionalContent !== undefined) { + opList.addOp(OPS.endMarkedContent, []); + } + return { opList, separateForm: false, separateCanvas: isUsingOwnCanvas }; + } + + _getMKDict(rotation) { + const mk = new Dict(null); + if (rotation) { + mk.set("R", rotation); + } + if (this.borderColor) { + mk.set("BC", getPdfColorArray(this.borderColor)); + } + if (this.backgroundColor) { + mk.set("BG", getPdfColorArray(this.backgroundColor)); + } + return mk.size > 0 ? mk : null; + } + + amendSavedDict(annotationStorage, dict) {} + + setValue(dict, value, xref, changes) { + const { dict: parentDict, ref: parentRef } = getParentToUpdate( + dict, + this.ref, + xref + ); + if (!parentDict) { + dict.set("V", value); + } else if (!changes.has(parentRef)) { + const newParentDict = parentDict.clone(); + newParentDict.set("V", value); + changes.put(parentRef, { data: newParentDict }); + return newParentDict; + } + + return null; + } + + async save(evaluator, task, annotationStorage, changes) { + const storageEntry = annotationStorage?.get(this.data.id); + const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint); + let value = storageEntry?.value, + rotation = storageEntry?.rotation; + if (value === this.data.fieldValue || value === undefined) { + if ( + !this._hasValueFromXFA && + rotation === undefined && + flags === undefined + ) { + return; + } + value ||= this.data.fieldValue; + } + + // Value can be an array (with choice list and multiple selections) + if ( + rotation === undefined && + !this._hasValueFromXFA && + Array.isArray(value) && + Array.isArray(this.data.fieldValue) && + isArrayEqual(value, this.data.fieldValue) && + flags === undefined + ) { + return; + } + + if (rotation === undefined) { + rotation = this.rotation; + } + + let appearance = null; + if (!this._needAppearances) { + appearance = await this._getAppearance( + evaluator, + task, + RenderingIntentFlag.SAVE, + annotationStorage + ); + if (appearance === null && flags === undefined) { + // Appearance didn't change. + return; + } + } else { + // No need to create an appearance: the pdf has the flag /NeedAppearances + // which means that it's up to the reader to produce an appearance. + } + + let needAppearances = false; + if (appearance?.needAppearances) { + needAppearances = true; + appearance = null; + } + + const { xref } = evaluator; + + const originalDict = xref.fetchIfRef(this.ref); + if (!(originalDict instanceof Dict)) { + return; + } + + const dict = new Dict(xref); + for (const key of originalDict.getKeys()) { + if (key !== "AP") { + dict.set(key, originalDict.getRaw(key)); + } + } + if (flags !== undefined) { + dict.set("F", flags); + if (appearance === null && !needAppearances) { + const ap = originalDict.getRaw("AP"); + if (ap) { + dict.set("AP", ap); + } + } + } + + const xfa = { + path: this.data.fieldName, + value, + }; + + const newParentDict = this.setValue( + dict, + Array.isArray(value) + ? value.map(stringToAsciiOrUTF16BE) + : stringToAsciiOrUTF16BE(value), + xref, + changes + ); + this.amendSavedDict(annotationStorage, newParentDict || dict); + + const maybeMK = this._getMKDict(rotation); + if (maybeMK) { + dict.set("MK", maybeMK); + } + + changes.put(this.ref, { + data: dict, + xfa, + needAppearances, + }); + if (appearance !== null) { + const newRef = xref.getNewTemporaryRef(); + const AP = new Dict(xref); + dict.set("AP", AP); + AP.set("N", newRef); + + const resources = this._getSaveFieldResources(xref); + const appearanceStream = new StringStream(appearance); + const appearanceDict = (appearanceStream.dict = new Dict(xref)); + appearanceDict.set("Subtype", Name.get("Form")); + appearanceDict.set("Resources", resources); + appearanceDict.set("BBox", [0, 0, this.width, this.height]); + + const rotationMatrix = this.getRotationMatrix(annotationStorage); + if (rotationMatrix !== IDENTITY_MATRIX) { + // The matrix isn't the identity one. + appearanceDict.set("Matrix", rotationMatrix); + } + + changes.put(newRef, { + data: appearanceStream, + xfa: null, + needAppearances: false, + }); + } + + dict.set("M", `D:${getModificationDate()}`); + } + + async _getAppearance(evaluator, task, intent, annotationStorage) { + if (this.data.password) { + return null; + } + const storageEntry = annotationStorage?.get(this.data.id); + let value, rotation; + if (storageEntry) { + value = storageEntry.formattedValue || storageEntry.value; + rotation = storageEntry.rotation; + } + + if ( + rotation === undefined && + value === undefined && + !this._needAppearances + ) { + if (!this._hasValueFromXFA || this.appearance) { + // The annotation hasn't been rendered so use the appearance. + return null; + } + } + + // Empty or it has a trailing whitespace. + const colors = this.getBorderAndBackgroundAppearances(annotationStorage); + + if (value === undefined) { + // The annotation has its value in XFA datasets but not in the V field. + value = this.data.fieldValue; + if (!value) { + return `/Tx BMC q ${colors}Q EMC`; + } + } + + if (Array.isArray(value) && value.length === 1) { + value = value[0]; + } + + assert(typeof value === "string", "Expected `value` to be a string."); + value = value.trimEnd(); + + if (this.data.combo) { + // The value can be one of the exportValue or any other values. + const option = this.data.options.find( + ({ exportValue }) => value === exportValue + ); + value = option?.displayValue || value; + } + + if (value === "") { + // the field is empty: nothing to render + return `/Tx BMC q ${colors}Q EMC`; + } + + if (rotation === undefined) { + rotation = this.rotation; + } + + let lineCount = -1; + let lines; + + // We could have a text containing for example some sequences of chars and + // their diacritics (e.g. "รฉ".normalize("NFKD") shows 1 char when it's 2). + // Positioning diacritics is really something we don't want to do here. + // So if a font has a glyph for a acute accent and one for "e" then we won't + // get any encoding issues but we'll render "e" and then "ยด". + // It's why we normalize the string. We use NFC to preserve the initial + // string, (e.g. "ยฒ".normalize("NFC") === "ยฒ" + // but "ยฒ".normalize("NFKC") === "2"). + // + // TODO: it isn't a perfect solution, some chars like "แบนฬ" will be + // decomposed into two chars ("แบน" and "ยด"), so we should detect such + // situations and then use either FakeUnicodeFont or set the + // /NeedAppearances flag. + if (this.data.multiLine) { + lines = value.split(/\r\n?|\n/).map(line => line.normalize("NFC")); + lineCount = lines.length; + } else { + lines = [value.replace(/\r\n?|\n/, "").normalize("NFC")]; + } + + const defaultPadding = 1; + const defaultHPadding = 2; + let { width: totalWidth, height: totalHeight } = this; + + if (rotation === 90 || rotation === 270) { + [totalWidth, totalHeight] = [totalHeight, totalWidth]; + } + + if (!this._defaultAppearance) { + // The DA is required and must be a string. + // If there is no font named Helvetica in the resource dictionary, + // the evaluator will fall back to a default font. + // Doing so prevents exceptions and allows saving/printing + // the file as expected. + this.data.defaultAppearanceData = parseDefaultAppearance( + (this._defaultAppearance = "/Helvetica 0 Tf 0 g") + ); + } + + let font = await WidgetAnnotation._getFontData( + evaluator, + task, + this.data.defaultAppearanceData, + this._fieldResources.mergedResources + ); + + let defaultAppearance, fontSize, lineHeight; + const encodedLines = []; + let encodingError = false; + for (const line of lines) { + const encodedString = font.encodeString(line); + if (encodedString.length > 1) { + encodingError = true; + } + encodedLines.push(encodedString.join("")); + } + + if (encodingError && intent & RenderingIntentFlag.SAVE) { + // We don't have a way to render the field, so we just rely on the + // /NeedAppearances trick to let the different sofware correctly render + // this pdf. + return { needAppearances: true }; + } + + // We check that the font is able to encode the string. + if (encodingError && this._isOffscreenCanvasSupported) { + // If it can't then we fallback on fake unicode font (mapped to sans-serif + // for the rendering). + // It means that a printed form can be rendered differently (it depends on + // the sans-serif font) but at least we've something to render. + // In an ideal world the associated font should correctly handle the + // possible chars but a user can add a smiley or whatever. + // We could try to embed a font but it means that we must have access + // to the raw font file. + const fontFamily = this.data.comb ? "monospace" : "sans-serif"; + const fakeUnicodeFont = new FakeUnicodeFont(evaluator.xref, fontFamily); + const resources = fakeUnicodeFont.createFontResources(lines.join("")); + const newFont = resources.getRaw("Font"); + + if (this._fieldResources.mergedResources.has("Font")) { + const oldFont = this._fieldResources.mergedResources.get("Font"); + for (const key of newFont.getKeys()) { + oldFont.set(key, newFont.getRaw(key)); + } + } else { + this._fieldResources.mergedResources.set("Font", newFont); + } + + const fontName = fakeUnicodeFont.fontName.name; + font = await WidgetAnnotation._getFontData( + evaluator, + task, + { fontName, fontSize: 0 }, + resources + ); + + for (let i = 0, ii = encodedLines.length; i < ii; i++) { + encodedLines[i] = stringToUTF16String(lines[i]); + } + + const savedDefaultAppearance = Object.assign( + Object.create(null), + this.data.defaultAppearanceData + ); + this.data.defaultAppearanceData.fontSize = 0; + this.data.defaultAppearanceData.fontName = fontName; + + [defaultAppearance, fontSize, lineHeight] = this._computeFontSize( + totalHeight - 2 * defaultPadding, + totalWidth - 2 * defaultHPadding, + value, + font, + lineCount + ); + + this.data.defaultAppearanceData = savedDefaultAppearance; + } else { + if (!this._isOffscreenCanvasSupported) { + warn( + "_getAppearance: OffscreenCanvas is not supported, annotation may not render correctly." + ); + } + + [defaultAppearance, fontSize, lineHeight] = this._computeFontSize( + totalHeight - 2 * defaultPadding, + totalWidth - 2 * defaultHPadding, + value, + font, + lineCount + ); + } + + let descent = font.descent; + if (isNaN(descent)) { + descent = BASELINE_FACTOR * lineHeight; + } else { + descent = Math.max( + BASELINE_FACTOR * lineHeight, + Math.abs(descent) * fontSize + ); + } + + // Take into account the space we have to compute the default vertical + // padding. + const defaultVPadding = Math.min( + Math.floor((totalHeight - fontSize) / 2), + defaultPadding + ); + const alignment = this.data.textAlignment; + + if (this.data.multiLine) { + return this._getMultilineAppearance( + defaultAppearance, + encodedLines, + font, + fontSize, + totalWidth, + totalHeight, + alignment, + defaultHPadding, + defaultVPadding, + descent, + lineHeight, + annotationStorage + ); + } + + if (this.data.comb) { + return this._getCombAppearance( + defaultAppearance, + font, + encodedLines[0], + fontSize, + totalWidth, + totalHeight, + defaultHPadding, + defaultVPadding, + descent, + lineHeight, + annotationStorage + ); + } + + const bottomPadding = defaultVPadding + descent; + if (alignment === 0 || alignment > 2) { + // Left alignment: nothing to do + return ( + `/Tx BMC q ${colors}BT ` + + defaultAppearance + + ` 1 0 0 1 ${numberToString(defaultHPadding)} ${numberToString( + bottomPadding + )} Tm (${escapeString(encodedLines[0])}) Tj` + + " ET Q EMC" + ); + } + + const prevInfo = { shift: 0 }; + const renderedText = this._renderText( + encodedLines[0], + font, + fontSize, + totalWidth, + alignment, + prevInfo, + defaultHPadding, + bottomPadding + ); + return ( + `/Tx BMC q ${colors}BT ` + + defaultAppearance + + ` 1 0 0 1 0 0 Tm ${renderedText}` + + " ET Q EMC" + ); + } + + static async _getFontData(evaluator, task, appearanceData, resources) { + const operatorList = new OperatorList(); + const initialState = { + font: null, + clone() { + return this; + }, + }; + + const { fontName, fontSize } = appearanceData; + await evaluator.handleSetFont( + resources, + [fontName && Name.get(fontName), fontSize], + /* fontRef = */ null, + operatorList, + task, + initialState, + /* fallbackFontDict = */ null + ); + + return initialState.font; + } + + _getTextWidth(text, font) { + return ( + font + .charsToGlyphs(text) + .reduce((width, glyph) => width + glyph.width, 0) / 1000 + ); + } + + _computeFontSize(height, width, text, font, lineCount) { + let { fontSize } = this.data.defaultAppearanceData; + let lineHeight = (fontSize || 12) * LINE_FACTOR, + numberOfLines = Math.round(height / lineHeight); + + if (!fontSize) { + // A zero value for size means that the font shall be auto-sized: + // its size shall be computed as a function of the height of the + // annotation rectangle (see 12.7.3.3). + + const roundWithTwoDigits = x => Math.floor(x * 100) / 100; + + if (lineCount === -1) { + // Get the text width for a font size of 1. + const textWidth = this._getTextWidth(text, font); + + // width / textWidth is the max font size to fit the width. + // height / LINE_FACTOR is the max font size to fit the height. + fontSize = roundWithTwoDigits( + Math.min(height / LINE_FACTOR, width / textWidth) + ); + numberOfLines = 1; + } else { + const lines = text.split(/\r\n?|\n/); + const cachedLines = []; + for (const line of lines) { + const encoded = font.encodeString(line).join(""); + const glyphs = font.charsToGlyphs(encoded); + const positions = font.getCharPositions(encoded); + cachedLines.push({ + line: encoded, + glyphs, + positions, + }); + } + + const isTooBig = fsize => { + // Return true when the text doesn't fit the given height. + let totalHeight = 0; + for (const cache of cachedLines) { + const chunks = this._splitLine(null, font, fsize, width, cache); + totalHeight += chunks.length * fsize; + if (totalHeight > height) { + return true; + } + } + return false; + }; + + // Hard to guess how many lines there are. + // The field may have been sized to have 10 lines + // and the user entered only 1 so if we get font size from + // height and number of lines then we'll get something too big. + // So we compute a fake number of lines based on height and + // a font size equal to 12 (this is the default font size in + // Acrobat). + // Then we'll adjust font size to what we have really. + numberOfLines = Math.max(numberOfLines, lineCount); + + while (true) { + lineHeight = height / numberOfLines; + fontSize = roundWithTwoDigits(lineHeight / LINE_FACTOR); + + if (isTooBig(fontSize)) { + numberOfLines++; + continue; + } + + break; + } + } + + const { fontName, fontColor } = this.data.defaultAppearanceData; + this._defaultAppearance = createDefaultAppearance({ + fontSize, + fontName, + fontColor, + }); + } + + return [this._defaultAppearance, fontSize, height / numberOfLines]; + } + + _renderText( + text, + font, + fontSize, + totalWidth, + alignment, + prevInfo, + hPadding, + vPadding + ) { + // TODO: we need to take into account (if possible) how the text + // is rendered. For example in arabic, the cumulated width of some + // glyphs isn't equal to the width of the rendered glyphs because + // of ligatures. + let shift; + if (alignment === 1) { + // Center + const width = this._getTextWidth(text, font) * fontSize; + shift = (totalWidth - width) / 2; + } else if (alignment === 2) { + // Right + const width = this._getTextWidth(text, font) * fontSize; + shift = totalWidth - width - hPadding; + } else { + shift = hPadding; + } + const shiftStr = numberToString(shift - prevInfo.shift); + prevInfo.shift = shift; + vPadding = numberToString(vPadding); + + return `${shiftStr} ${vPadding} Td (${escapeString(text)}) Tj`; + } + + /** + * @private + */ + _getSaveFieldResources(xref) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + this.data.defaultAppearanceData, + "Expected `_defaultAppearanceData` to have been set." + ); + } + const { localResources, appearanceResources, acroFormResources } = + this._fieldResources; + + const fontName = this.data.defaultAppearanceData?.fontName; + if (!fontName) { + return localResources || Dict.empty; + } + + for (const resources of [localResources, appearanceResources]) { + if (resources instanceof Dict) { + const localFont = resources.get("Font"); + if (localFont instanceof Dict && localFont.has(fontName)) { + return resources; + } + } + } + if (acroFormResources instanceof Dict) { + const acroFormFont = acroFormResources.get("Font"); + if (acroFormFont instanceof Dict && acroFormFont.has(fontName)) { + const subFontDict = new Dict(xref); + subFontDict.set(fontName, acroFormFont.getRaw(fontName)); + + const subResourcesDict = new Dict(xref); + subResourcesDict.set("Font", subFontDict); + + return Dict.merge({ + xref, + dictArray: [subResourcesDict, localResources], + mergeSubDicts: true, + }); + } + } + return localResources || Dict.empty; + } + + getFieldObject() { + return null; + } +} + +class TextWidgetAnnotation extends WidgetAnnotation { + constructor(params) { + super(params); + + const { dict } = params; + + if (dict.has("PMD")) { + // It's used to display a barcode but it isn't specified so we just hide + // it to avoid any confusion. + this.flags |= AnnotationFlag.HIDDEN; + this.data.hidden = true; + warn("Barcodes are not supported"); + } + + this.data.hasOwnCanvas = this.data.readOnly && !this.data.noHTML; + this._hasText = true; + + // The field value is always a string. + if (typeof this.data.fieldValue !== "string") { + this.data.fieldValue = ""; + } + + // Determine the alignment of text in the field. + let alignment = getInheritableProperty({ dict, key: "Q" }); + if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) { + alignment = null; + } + this.data.textAlignment = alignment; + + // Determine the maximum length of text in the field. + let maximumLength = getInheritableProperty({ dict, key: "MaxLen" }); + if (!Number.isInteger(maximumLength) || maximumLength < 0) { + maximumLength = 0; + } + this.data.maxLen = maximumLength; + + // Process field flags for the display layer. + this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE); + this.data.comb = + this.hasFieldFlag(AnnotationFieldFlag.COMB) && + !this.data.multiLine && + !this.data.password && + !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && + this.data.maxLen !== 0; + this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL); + } + + get hasTextContent() { + return !!this.appearance && !this._needAppearances; + } + + _getCombAppearance( + defaultAppearance, + font, + text, + fontSize, + width, + height, + hPadding, + vPadding, + descent, + lineHeight, + annotationStorage + ) { + const combWidth = width / this.data.maxLen; + // Empty or it has a trailing whitespace. + const colors = this.getBorderAndBackgroundAppearances(annotationStorage); + + const buf = []; + const positions = font.getCharPositions(text); + for (const [start, end] of positions) { + buf.push(`(${escapeString(text.substring(start, end))}) Tj`); + } + + const renderedComb = buf.join(` ${numberToString(combWidth)} 0 Td `); + return ( + `/Tx BMC q ${colors}BT ` + + defaultAppearance + + ` 1 0 0 1 ${numberToString(hPadding)} ${numberToString( + vPadding + descent + )} Tm ${renderedComb}` + + " ET Q EMC" + ); + } + + _getMultilineAppearance( + defaultAppearance, + lines, + font, + fontSize, + width, + height, + alignment, + hPadding, + vPadding, + descent, + lineHeight, + annotationStorage + ) { + const buf = []; + const totalWidth = width - 2 * hPadding; + const prevInfo = { shift: 0 }; + for (let i = 0, ii = lines.length; i < ii; i++) { + const line = lines[i]; + const chunks = this._splitLine(line, font, fontSize, totalWidth); + for (let j = 0, jj = chunks.length; j < jj; j++) { + const chunk = chunks[j]; + const vShift = + i === 0 && j === 0 ? -vPadding - (lineHeight - descent) : -lineHeight; + buf.push( + this._renderText( + chunk, + font, + fontSize, + width, + alignment, + prevInfo, + hPadding, + vShift + ) + ); + } + } + + // Empty or it has a trailing whitespace. + const colors = this.getBorderAndBackgroundAppearances(annotationStorage); + const renderedText = buf.join("\n"); + + return ( + `/Tx BMC q ${colors}BT ` + + defaultAppearance + + ` 1 0 0 1 0 ${numberToString(height)} Tm ${renderedText}` + + " ET Q EMC" + ); + } + + _splitLine(line, font, fontSize, width, cache = {}) { + line = cache.line || line; + + const glyphs = cache.glyphs || font.charsToGlyphs(line); + + if (glyphs.length <= 1) { + // Nothing to split + return [line]; + } + + const positions = cache.positions || font.getCharPositions(line); + const scale = fontSize / 1000; + const chunks = []; + + let lastSpacePosInStringStart = -1, + lastSpacePosInStringEnd = -1, + lastSpacePos = -1, + startChunk = 0, + currentWidth = 0; + + for (let i = 0, ii = glyphs.length; i < ii; i++) { + const [start, end] = positions[i]; + const glyph = glyphs[i]; + const glyphWidth = glyph.width * scale; + if (glyph.unicode === " ") { + if (currentWidth + glyphWidth > width) { + // We can break here + chunks.push(line.substring(startChunk, start)); + startChunk = start; + currentWidth = glyphWidth; + lastSpacePosInStringStart = -1; + lastSpacePos = -1; + } else { + currentWidth += glyphWidth; + lastSpacePosInStringStart = start; + lastSpacePosInStringEnd = end; + lastSpacePos = i; + } + } else if (currentWidth + glyphWidth > width) { + // We must break to the last white position (if available) + if (lastSpacePosInStringStart !== -1) { + chunks.push(line.substring(startChunk, lastSpacePosInStringEnd)); + startChunk = lastSpacePosInStringEnd; + i = lastSpacePos + 1; + lastSpacePosInStringStart = -1; + currentWidth = 0; + } else { + // Just break in the middle of the word + chunks.push(line.substring(startChunk, start)); + startChunk = start; + currentWidth = glyphWidth; + } + } else { + currentWidth += glyphWidth; + } + } + + if (startChunk < line.length) { + chunks.push(line.substring(startChunk, line.length)); + } + + return chunks; + } + + async extractTextContent(evaluator, task, viewBox) { + await super.extractTextContent(evaluator, task, viewBox); + const text = this.data.textContent; + if (!text) { + return; + } + + // The text extractor doesn't handle empty lines correctly, so if the + // content we get is more or less (modulo whitespaces) the same as the + // field value we just ignore it. + const allText = text.join("\n"); + if (allText === this.data.fieldValue) { + return; + } + const regex = allText.replaceAll(/([.*+?^${}()|[\]\\])|(\s+)/g, (_m, p1) => + p1 ? `\\${p1}` : "\\s+" + ); + if (new RegExp(`^\\s*${regex}\\s*$`).test(this.data.fieldValue)) { + this.data.textContent = this.data.fieldValue.split("\n"); + } + } + + getFieldObject() { + return { + id: this.data.id, + value: this.data.fieldValue, + defaultValue: this.data.defaultFieldValue || "", + multiline: this.data.multiLine, + password: this.data.password, + charLimit: this.data.maxLen, + comb: this.data.comb, + editable: !this.data.readOnly, + hidden: this.data.hidden, + name: this.data.fieldName, + rect: this.data.rect, + actions: this.data.actions, + page: this.data.pageIndex, + strokeColor: this.data.borderColor, + fillColor: this.data.backgroundColor, + rotation: this.rotation, + type: "text", + }; + } +} + +class ButtonWidgetAnnotation extends WidgetAnnotation { + constructor(params) { + super(params); + + this.checkedAppearance = null; + this.uncheckedAppearance = null; + + const isRadio = this.hasFieldFlag(AnnotationFieldFlag.RADIO), + isPushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON); + + this.data.checkBox = !isRadio && !isPushButton; + this.data.radioButton = isRadio && !isPushButton; + this.data.pushButton = isPushButton; + this.data.isTooltipOnly = false; + + if (this.data.checkBox) { + this._processCheckBox(params); + } else if (this.data.radioButton) { + this._processRadioButton(params); + } else if (this.data.pushButton) { + this.data.hasOwnCanvas = true; + this.data.noHTML = false; + this._processPushButton(params); + } else { + warn("Invalid field flags for button widget annotation"); + } + } + + async getOperatorList(evaluator, task, intent, annotationStorage) { + if (this.data.pushButton) { + return super.getOperatorList( + evaluator, + task, + intent, + false, // we use normalAppearance to render the button + annotationStorage + ); + } + + let value = null; + let rotation = null; + if (annotationStorage) { + const storageEntry = annotationStorage.get(this.data.id); + value = storageEntry ? storageEntry.value : null; + rotation = storageEntry ? storageEntry.rotation : null; + } + + if (value === null && this.appearance) { + // Nothing in the annotationStorage. + // But we've a default appearance so use it. + return super.getOperatorList(evaluator, task, intent, annotationStorage); + } + + if (value === null || value === undefined) { + // There is no default appearance so use the one derived + // from the field value. + value = this.data.checkBox + ? this.data.fieldValue === this.data.exportValue + : this.data.fieldValue === this.data.buttonValue; + } + + const appearance = value + ? this.checkedAppearance + : this.uncheckedAppearance; + if (appearance) { + const savedAppearance = this.appearance; + const savedMatrix = lookupMatrix( + appearance.dict.getArray("Matrix"), + IDENTITY_MATRIX + ); + + if (rotation) { + appearance.dict.set( + "Matrix", + this.getRotationMatrix(annotationStorage) + ); + } + + this.appearance = appearance; + const operatorList = super.getOperatorList( + evaluator, + task, + intent, + annotationStorage + ); + this.appearance = savedAppearance; + appearance.dict.set("Matrix", savedMatrix); + return operatorList; + } + + // No appearance + return { + opList: new OperatorList(), + separateForm: false, + separateCanvas: false, + }; + } + + async save(evaluator, task, annotationStorage, changes) { + if (this.data.checkBox) { + this._saveCheckbox(evaluator, task, annotationStorage, changes); + return; + } + + if (this.data.radioButton) { + this._saveRadioButton(evaluator, task, annotationStorage, changes); + } + } + + async _saveCheckbox(evaluator, task, annotationStorage, changes) { + if (!annotationStorage) { + return; + } + const storageEntry = annotationStorage.get(this.data.id); + const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint); + let rotation = storageEntry?.rotation, + value = storageEntry?.value; + + if (rotation === undefined && flags === undefined) { + if (value === undefined) { + return; + } + + const defaultValue = this.data.fieldValue === this.data.exportValue; + if (defaultValue === value) { + return; + } + } + + let dict = evaluator.xref.fetchIfRef(this.ref); + if (!(dict instanceof Dict)) { + return; + } + dict = dict.clone(); + + if (rotation === undefined) { + rotation = this.rotation; + } + if (value === undefined) { + value = this.data.fieldValue === this.data.exportValue; + } + + const xfa = { + path: this.data.fieldName, + value: value ? this.data.exportValue : "", + }; + + const name = Name.get(value ? this.data.exportValue : "Off"); + this.setValue(dict, name, evaluator.xref, changes); + + dict.set("AS", name); + dict.set("M", `D:${getModificationDate()}`); + if (flags !== undefined) { + dict.set("F", flags); + } + + const maybeMK = this._getMKDict(rotation); + if (maybeMK) { + dict.set("MK", maybeMK); + } + + changes.put(this.ref, { + data: dict, + xfa, + needAppearances: false, + }); + } + + async _saveRadioButton(evaluator, task, annotationStorage, changes) { + if (!annotationStorage) { + return; + } + const storageEntry = annotationStorage.get(this.data.id); + const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint); + let rotation = storageEntry?.rotation, + value = storageEntry?.value; + + if (rotation === undefined && flags === undefined) { + if (value === undefined) { + return; + } + + const defaultValue = this.data.fieldValue === this.data.buttonValue; + if (defaultValue === value) { + return; + } + } + + let dict = evaluator.xref.fetchIfRef(this.ref); + if (!(dict instanceof Dict)) { + return; + } + dict = dict.clone(); + + if (value === undefined) { + value = this.data.fieldValue === this.data.buttonValue; + } + + if (rotation === undefined) { + rotation = this.rotation; + } + + const xfa = { + path: this.data.fieldName, + value: value ? this.data.buttonValue : "", + }; + + const name = Name.get(value ? this.data.buttonValue : "Off"); + if (value) { + this.setValue(dict, name, evaluator.xref, changes); + } + + dict.set("AS", name); + dict.set("M", `D:${getModificationDate()}`); + if (flags !== undefined) { + dict.set("F", flags); + } + + const maybeMK = this._getMKDict(rotation); + if (maybeMK) { + dict.set("MK", maybeMK); + } + + changes.put(this.ref, { + data: dict, + xfa, + needAppearances: false, + }); + } + + _getDefaultCheckedAppearance(params, type) { + const { width, height } = this; + const bbox = [0, 0, width, height]; + + // Ratio used to have a mark slightly smaller than the bbox. + const FONT_RATIO = 0.8; + const fontSize = Math.min(width, height) * FONT_RATIO; + + // Char Metrics + // Widths came from widths for ZapfDingbats. + // Heights are guessed with Fontforge and FoxitDingbats.pfb. + let metrics, char; + if (type === "check") { + // Char 33 (2713 in unicode) + metrics = { + width: 0.755 * fontSize, + height: 0.705 * fontSize, + }; + char = "\x33"; + } else if (type === "disc") { + // Char 6C (25CF in unicode) + metrics = { + width: 0.791 * fontSize, + height: 0.705 * fontSize, + }; + char = "\x6C"; + } else { + unreachable(`_getDefaultCheckedAppearance - unsupported type: ${type}`); + } + + // Values to center the glyph in the bbox. + const xShift = numberToString((width - metrics.width) / 2); + const yShift = numberToString((height - metrics.height) / 2); + + const appearance = `q BT /PdfJsZaDb ${fontSize} Tf 0 g ${xShift} ${yShift} Td (${char}) Tj ET Q`; + + const appearanceStreamDict = new Dict(params.xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", bbox); + appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]); + appearanceStreamDict.set("Length", appearance.length); + + const resources = new Dict(params.xref); + const font = new Dict(params.xref); + font.set("PdfJsZaDb", this.fallbackFontDict); + resources.set("Font", font); + + appearanceStreamDict.set("Resources", resources); + + this.checkedAppearance = new StringStream(appearance); + this.checkedAppearance.dict = appearanceStreamDict; + + this._streams.push(this.checkedAppearance); + } + + _processCheckBox(params) { + const customAppearance = params.dict.get("AP"); + if (!(customAppearance instanceof Dict)) { + return; + } + + const normalAppearance = customAppearance.get("N"); + if (!(normalAppearance instanceof Dict)) { + return; + } + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1722036. + // If we've an AS and a V then take AS. + const asValue = this._decodeFormValue(params.dict.get("AS")); + if (typeof asValue === "string") { + this.data.fieldValue = asValue; + } + + const yes = + this.data.fieldValue !== null && this.data.fieldValue !== "Off" + ? this.data.fieldValue + : "Yes"; + + const exportValues = normalAppearance.getKeys(); + if (exportValues.length === 0) { + exportValues.push("Off", yes); + } else if (exportValues.length === 1) { + if (exportValues[0] === "Off") { + exportValues.push(yes); + } else { + exportValues.unshift("Off"); + } + } else if (exportValues.includes(yes)) { + exportValues.length = 0; + exportValues.push("Off", yes); + } else { + const otherYes = exportValues.find(v => v !== "Off"); + exportValues.length = 0; + exportValues.push("Off", otherYes); + } + + // Don't use a "V" entry pointing to a non-existent appearance state, + // see e.g. bug1720411.pdf where it's an *empty* Name-instance. + if (!exportValues.includes(this.data.fieldValue)) { + this.data.fieldValue = "Off"; + } + + this.data.exportValue = exportValues[1]; + + const checkedAppearance = normalAppearance.get(this.data.exportValue); + this.checkedAppearance = + checkedAppearance instanceof BaseStream ? checkedAppearance : null; + const uncheckedAppearance = normalAppearance.get("Off"); + this.uncheckedAppearance = + uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null; + + if (this.checkedAppearance) { + this._streams.push(this.checkedAppearance); + } else { + this._getDefaultCheckedAppearance(params, "check"); + } + if (this.uncheckedAppearance) { + this._streams.push(this.uncheckedAppearance); + } + this._fallbackFontDict = this.fallbackFontDict; + if (this.data.defaultFieldValue === null) { + this.data.defaultFieldValue = "Off"; + } + } + + _processRadioButton(params) { + this.data.buttonValue = null; + + // The parent field's `V` entry holds a `Name` object with the appearance + // state of whichever child field is currently in the "on" state. + const fieldParent = params.dict.get("Parent"); + if (fieldParent instanceof Dict) { + this.parent = params.dict.getRaw("Parent"); + const fieldParentValue = fieldParent.get("V"); + if (fieldParentValue instanceof Name) { + this.data.fieldValue = this._decodeFormValue(fieldParentValue); + } + } + + // The button's value corresponds to its appearance state. + const appearanceStates = params.dict.get("AP"); + if (!(appearanceStates instanceof Dict)) { + return; + } + const normalAppearance = appearanceStates.get("N"); + if (!(normalAppearance instanceof Dict)) { + return; + } + for (const key of normalAppearance.getKeys()) { + if (key !== "Off") { + this.data.buttonValue = this._decodeFormValue(key); + break; + } + } + + const checkedAppearance = normalAppearance.get(this.data.buttonValue); + this.checkedAppearance = + checkedAppearance instanceof BaseStream ? checkedAppearance : null; + const uncheckedAppearance = normalAppearance.get("Off"); + this.uncheckedAppearance = + uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null; + + if (this.checkedAppearance) { + this._streams.push(this.checkedAppearance); + } else { + this._getDefaultCheckedAppearance(params, "disc"); + } + if (this.uncheckedAppearance) { + this._streams.push(this.uncheckedAppearance); + } + this._fallbackFontDict = this.fallbackFontDict; + if (this.data.defaultFieldValue === null) { + this.data.defaultFieldValue = "Off"; + } + } + + _processPushButton(params) { + const { dict, annotationGlobals } = params; + + if (!dict.has("A") && !dict.has("AA") && !this.data.alternativeText) { + warn("Push buttons without action dictionaries are not supported"); + return; + } + + this.data.isTooltipOnly = !dict.has("A") && !dict.has("AA"); + + Catalog.parseDestDictionary({ + destDict: dict, + resultObj: this.data, + docBaseUrl: annotationGlobals.baseUrl, + docAttachments: annotationGlobals.attachments, + }); + } + + getFieldObject() { + let type = "button"; + let exportValues; + if (this.data.checkBox) { + type = "checkbox"; + exportValues = this.data.exportValue; + } else if (this.data.radioButton) { + type = "radiobutton"; + exportValues = this.data.buttonValue; + } + return { + id: this.data.id, + value: this.data.fieldValue || "Off", + defaultValue: this.data.defaultFieldValue, + exportValues, + editable: !this.data.readOnly, + name: this.data.fieldName, + rect: this.data.rect, + hidden: this.data.hidden, + actions: this.data.actions, + page: this.data.pageIndex, + strokeColor: this.data.borderColor, + fillColor: this.data.backgroundColor, + rotation: this.rotation, + type, + }; + } + + get fallbackFontDict() { + const dict = new Dict(); + dict.set("BaseFont", Name.get("ZapfDingbats")); + dict.set("Type", Name.get("FallbackType")); + dict.set("Subtype", Name.get("FallbackType")); + dict.set("Encoding", Name.get("ZapfDingbatsEncoding")); + + return shadow(this, "fallbackFontDict", dict); + } +} + +class ChoiceWidgetAnnotation extends WidgetAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + + this.indices = dict.getArray("I"); + this.hasIndices = Array.isArray(this.indices) && this.indices.length > 0; + + // Determine the options. The options array may consist of strings or + // arrays. If the array consists of arrays, then the first element of + // each array is the export value and the second element of each array is + // the display value. If the array consists of strings, then these + // represent both the export and display value. In this case, we convert + // it to an array of arrays as well for convenience in the display layer. + // Note that the specification does not state that the `Opt` field is + // inheritable, but in practice PDF generators do make annotations + // inherit the options from a parent annotation (issue 8094). + this.data.options = []; + + const options = getInheritableProperty({ dict, key: "Opt" }); + if (Array.isArray(options)) { + for (let i = 0, ii = options.length; i < ii; i++) { + const option = xref.fetchIfRef(options[i]); + const isOptionArray = Array.isArray(option); + + this.data.options[i] = { + exportValue: this._decodeFormValue( + isOptionArray ? xref.fetchIfRef(option[0]) : option + ), + displayValue: this._decodeFormValue( + isOptionArray ? xref.fetchIfRef(option[1]) : option + ), + }; + } + } + + if (!this.hasIndices) { + // The field value can be `null` if no item is selected, a string if one + // item is selected or an array of strings if multiple items are selected. + // For consistency in the API and convenience in the display layer, we + // always make the field value an array with zero, one or multiple items. + if (typeof this.data.fieldValue === "string") { + this.data.fieldValue = [this.data.fieldValue]; + } else if (!this.data.fieldValue) { + this.data.fieldValue = []; + } + } else { + // The specs say that we should have an indices array only with + // multiselectable Choice and the "V" entry should have the + // precedence, but Acrobat itself is using it whatever the + // the "V" entry is (see bug 1770750). + this.data.fieldValue = []; + const ii = this.data.options.length; + for (const i of this.indices) { + if (Number.isInteger(i) && i >= 0 && i < ii) { + this.data.fieldValue.push(this.data.options[i].exportValue); + } + } + } + + // It's a workaround for the issue #19083. + // Normally a choice widget is a mix of a text field and a listbox, + // So in the case where the V entry isn't an option we should just set it + // as the text field value. + if (this.data.options.length === 0 && this.data.fieldValue.length > 0) { + // If there are no options, then the field value is the only option. + this.data.options = this.data.fieldValue.map(value => ({ + exportValue: value, + displayValue: value, + })); + } + + // Process field flags for the display layer. + this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO); + this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT); + this._hasText = true; + } + + getFieldObject() { + const type = this.data.combo ? "combobox" : "listbox"; + const value = + this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null; + return { + id: this.data.id, + value, + defaultValue: this.data.defaultFieldValue, + editable: !this.data.readOnly, + name: this.data.fieldName, + rect: this.data.rect, + numItems: this.data.fieldValue.length, + multipleSelection: this.data.multiSelect, + hidden: this.data.hidden, + actions: this.data.actions, + items: this.data.options, + page: this.data.pageIndex, + strokeColor: this.data.borderColor, + fillColor: this.data.backgroundColor, + rotation: this.rotation, + type, + }; + } + + amendSavedDict(annotationStorage, dict) { + if (!this.hasIndices) { + return; + } + let values = annotationStorage?.get(this.data.id)?.value; + if (!Array.isArray(values)) { + values = [values]; + } + const indices = []; + const { options } = this.data; + for (let i = 0, j = 0, ii = options.length; i < ii; i++) { + if (options[i].exportValue === values[j]) { + indices.push(i); + j += 1; + } + } + dict.set("I", indices); + } + + async _getAppearance(evaluator, task, intent, annotationStorage) { + if (this.data.combo) { + return super._getAppearance(evaluator, task, intent, annotationStorage); + } + + let exportedValue, rotation; + const storageEntry = annotationStorage?.get(this.data.id); + if (storageEntry) { + rotation = storageEntry.rotation; + exportedValue = storageEntry.value; + } + + if ( + rotation === undefined && + exportedValue === undefined && + !this._needAppearances + ) { + // The annotation hasn't been rendered so use the appearance + return null; + } + + if (exportedValue === undefined) { + exportedValue = this.data.fieldValue; + } else if (!Array.isArray(exportedValue)) { + exportedValue = [exportedValue]; + } + + const defaultPadding = 1; + const defaultHPadding = 2; + let { width: totalWidth, height: totalHeight } = this; + + if (rotation === 90 || rotation === 270) { + [totalWidth, totalHeight] = [totalHeight, totalWidth]; + } + + const lineCount = this.data.options.length; + const valueIndices = []; + for (let i = 0; i < lineCount; i++) { + const { exportValue } = this.data.options[i]; + if (exportedValue.includes(exportValue)) { + valueIndices.push(i); + } + } + + if (!this._defaultAppearance) { + // The DA is required and must be a string. + // If there is no font named Helvetica in the resource dictionary, + // the evaluator will fall back to a default font. + // Doing so prevents exceptions and allows saving/printing + // the file as expected. + this.data.defaultAppearanceData = parseDefaultAppearance( + (this._defaultAppearance = "/Helvetica 0 Tf 0 g") + ); + } + + const font = await WidgetAnnotation._getFontData( + evaluator, + task, + this.data.defaultAppearanceData, + this._fieldResources.mergedResources + ); + + let defaultAppearance; + let { fontSize } = this.data.defaultAppearanceData; + if (!fontSize) { + const lineHeight = (totalHeight - defaultPadding) / lineCount; + let lineWidth = -1; + let value; + for (const { displayValue } of this.data.options) { + const width = this._getTextWidth(displayValue, font); + if (width > lineWidth) { + lineWidth = width; + value = displayValue; + } + } + + [defaultAppearance, fontSize] = this._computeFontSize( + lineHeight, + totalWidth - 2 * defaultHPadding, + value, + font, + -1 + ); + } else { + defaultAppearance = this._defaultAppearance; + } + + const lineHeight = fontSize * LINE_FACTOR; + const vPadding = (lineHeight - fontSize) / 2; + const numberOfVisibleLines = Math.floor(totalHeight / lineHeight); + + let firstIndex = 0; + if (valueIndices.length > 0) { + const minIndex = Math.min(...valueIndices); + const maxIndex = Math.max(...valueIndices); + + firstIndex = Math.max(0, maxIndex - numberOfVisibleLines + 1); + if (firstIndex > minIndex) { + firstIndex = minIndex; + } + } + const end = Math.min(firstIndex + numberOfVisibleLines + 1, lineCount); + + const buf = ["/Tx BMC q", `1 1 ${totalWidth} ${totalHeight} re W n`]; + + if (valueIndices.length) { + // This value has been copied/pasted from annotation-choice-widget.pdf. + // It corresponds to rgb(153, 193, 218). + buf.push("0.600006 0.756866 0.854904 rg"); + + // Highlight the lines in filling a blue rectangle at the selected + // positions. + for (const index of valueIndices) { + if (firstIndex <= index && index < end) { + buf.push( + `1 ${ + totalHeight - (index - firstIndex + 1) * lineHeight + } ${totalWidth} ${lineHeight} re f` + ); + } + } + } + buf.push("BT", defaultAppearance, `1 0 0 1 0 ${totalHeight} Tm`); + + const prevInfo = { shift: 0 }; + for (let i = firstIndex; i < end; i++) { + const { displayValue } = this.data.options[i]; + const vpadding = i === firstIndex ? vPadding : 0; + buf.push( + this._renderText( + displayValue, + font, + fontSize, + totalWidth, + 0, + prevInfo, + defaultHPadding, + -lineHeight + vpadding + ) + ); + } + + buf.push("ET Q EMC"); + + return buf.join("\n"); + } +} + +class SignatureWidgetAnnotation extends WidgetAnnotation { + constructor(params) { + super(params); + + // Unset the fieldValue since it's (most likely) a `Dict` which is + // non-serializable and will thus cause errors when sending annotations + // to the main-thread (issue 10347). + this.data.fieldValue = null; + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = !this.data.hasOwnCanvas; + } + + getFieldObject() { + return { + id: this.data.id, + value: null, + page: this.data.pageIndex, + type: "signature", + }; + } +} + +class TextAnnotation extends MarkupAnnotation { + constructor(params) { + const DEFAULT_ICON_SIZE = 22; // px + + super(params); + + // No rotation for Text (see 12.5.6.4). + this.data.noRotate = true; + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = false; + + const { dict } = params; + this.data.annotationType = AnnotationType.TEXT; + + if (this.data.hasAppearance) { + this.data.name = "NoIcon"; + } else { + this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE; + this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE; + this.data.name = dict.has("Name") ? dict.get("Name").name : "Note"; + } + + if (dict.has("State")) { + this.data.state = dict.get("State") || null; + this.data.stateModel = dict.get("StateModel") || null; + } else { + this.data.state = null; + this.data.stateModel = null; + } + } +} + +class LinkAnnotation extends Annotation { + constructor(params) { + super(params); + + const { dict, annotationGlobals } = params; + this.data.annotationType = AnnotationType.LINK; + + // A link is never rendered on the main canvas so we must render its HTML + // version. + this.data.noHTML = false; + + const quadPoints = getQuadPoints(dict, this.rectangle); + if (quadPoints) { + this.data.quadPoints = quadPoints; + } + + // The color entry for a link annotation is the color of the border. + this.data.borderColor ||= this.data.color; + + Catalog.parseDestDictionary({ + destDict: dict, + resultObj: this.data, + docBaseUrl: annotationGlobals.baseUrl, + docAttachments: annotationGlobals.attachments, + }); + } +} + +class PopupAnnotation extends Annotation { + constructor(params) { + super(params); + + const { dict } = params; + this.data.annotationType = AnnotationType.POPUP; + + // A pop-up is never rendered on the main canvas so we must render its HTML + // version. + this.data.noHTML = false; + + if (this.width === 0 || this.height === 0) { + this.data.rect = null; + } + + let parentItem = dict.get("Parent"); + if (!parentItem) { + warn("Popup annotation has a missing or invalid parent annotation."); + return; + } + this.data.parentRect = lookupNormalRect(parentItem.getArray("Rect"), null); + + const rt = parentItem.get("RT"); + if (isName(rt, AnnotationReplyType.GROUP)) { + // Subordinate annotations in a group should inherit + // the group attributes from the primary annotation. + parentItem = parentItem.get("IRT"); + } + + if (!parentItem.has("M")) { + this.data.modificationDate = null; + } else { + this.setModificationDate(parentItem.get("M")); + this.data.modificationDate = this.modificationDate; + } + + if (!parentItem.has("C")) { + // Fall back to the default background color. + this.data.color = null; + } else { + this.setColor(parentItem.getArray("C")); + this.data.color = this.color; + } + + // If the Popup annotation is not viewable, but the parent annotation is, + // that is most likely a bug. Fallback to inherit the flags from the parent + // annotation (this is consistent with the behaviour in Adobe Reader). + if (!this.viewable) { + const parentFlags = parentItem.get("F"); + if (this._isViewable(parentFlags)) { + this.setFlags(parentFlags); + } + } + + this.setTitle(parentItem.get("T")); + this.data.titleObj = this._title; + + this.setContents(parentItem.get("Contents")); + this.data.contentsObj = this._contents; + + if (parentItem.has("RC")) { + this.data.richText = XFAFactory.getRichTextAsHtml(parentItem.get("RC")); + } + + this.data.open = !!dict.get("Open"); + } +} + +class FreeTextAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + // It uses its own canvas in order to be hidden if edited. + // But if it has the noHTML flag, it means that we don't want to be able + // to modify it so we can just draw it on the main canvas. + this.data.hasOwnCanvas = this.data.noRotate; + this.data.isEditable = !this.data.noHTML; + // We want to be able to add mouse listeners to the annotation. + this.data.noHTML = false; + + const { evaluatorOptions, xref } = params; + this.data.annotationType = AnnotationType.FREETEXT; + this.setDefaultAppearance(params); + this._hasAppearance = !!this.appearance; + + if (this._hasAppearance) { + const { fontColor, fontSize } = parseAppearanceStream( + this.appearance, + evaluatorOptions, + xref + ); + this.data.defaultAppearanceData.fontColor = fontColor; + this.data.defaultAppearanceData.fontSize = fontSize || 10; + } else { + this.data.defaultAppearanceData.fontSize ||= 10; + const { fontColor, fontSize } = this.data.defaultAppearanceData; + if (this._contents.str) { + this.data.textContent = this._contents.str + .split(/\r\n?|\n/) + .map(line => line.trimEnd()); + const { coords, bbox, matrix } = FakeUnicodeFont.getFirstPositionInfo( + this.rectangle, + this.rotation, + fontSize + ); + this.data.textPosition = this._transformPoint(coords, bbox, matrix); + } + if (this._isOffscreenCanvasSupported) { + const strokeAlpha = params.dict.get("CA"); + const fakeUnicodeFont = new FakeUnicodeFont(xref, "sans-serif"); + this.appearance = fakeUnicodeFont.createAppearance( + this._contents.str, + this.rectangle, + this.rotation, + fontSize, + fontColor, + strokeAlpha + ); + this._streams.push(this.appearance); + } else { + warn( + "FreeTextAnnotation: OffscreenCanvas is not supported, annotation may not render correctly." + ); + } + } + } + + get hasTextContent() { + return this._hasAppearance; + } + + static createNewDict(annotation, xref, { apRef, ap }) { + const { color, fontSize, oldAnnotation, rect, rotation, user, value } = + annotation; + const freetext = oldAnnotation || new Dict(xref); + freetext.set("Type", Name.get("Annot")); + freetext.set("Subtype", Name.get("FreeText")); + if (oldAnnotation) { + freetext.set("M", `D:${getModificationDate()}`); + // TODO: We should try to generate a new RC from the content we've. + // For now we can just remove it to avoid any issues. + freetext.delete("RC"); + } else { + freetext.set("CreationDate", `D:${getModificationDate()}`); + } + freetext.set("Rect", rect); + const da = `/Helv ${fontSize} Tf ${getPdfColor(color, /* isFill */ true)}`; + freetext.set("DA", da); + freetext.set("Contents", stringToAsciiOrUTF16BE(value)); + freetext.set("F", 4); + freetext.set("Border", [0, 0, 0]); + freetext.set("Rotate", rotation); + + if (user) { + freetext.set("T", stringToAsciiOrUTF16BE(user)); + } + + if (apRef || ap) { + const n = new Dict(xref); + freetext.set("AP", n); + + if (apRef) { + n.set("N", apRef); + } else { + n.set("N", ap); + } + } + + return freetext; + } + + static async createNewAppearanceStream(annotation, xref, params) { + const { baseFontRef, evaluator, task } = params; + const { color, fontSize, rect, rotation, value } = annotation; + + const resources = new Dict(xref); + const font = new Dict(xref); + + if (baseFontRef) { + font.set("Helv", baseFontRef); + } else { + const baseFont = new Dict(xref); + baseFont.set("BaseFont", Name.get("Helvetica")); + baseFont.set("Type", Name.get("Font")); + baseFont.set("Subtype", Name.get("Type1")); + baseFont.set("Encoding", Name.get("WinAnsiEncoding")); + font.set("Helv", baseFont); + } + resources.set("Font", font); + + const helv = await WidgetAnnotation._getFontData( + evaluator, + task, + { + fontName: "Helv", + fontSize, + }, + resources + ); + + const [x1, y1, x2, y2] = rect; + let w = x2 - x1; + let h = y2 - y1; + + if (rotation % 180 !== 0) { + [w, h] = [h, w]; + } + + const lines = value.split("\n"); + const scale = fontSize / 1000; + let totalWidth = -Infinity; + const encodedLines = []; + for (let line of lines) { + const encoded = helv.encodeString(line); + if (encoded.length > 1) { + // The font doesn't contain all the chars. + return null; + } + line = encoded.join(""); + encodedLines.push(line); + let lineWidth = 0; + const glyphs = helv.charsToGlyphs(line); + for (const glyph of glyphs) { + lineWidth += glyph.width * scale; + } + totalWidth = Math.max(totalWidth, lineWidth); + } + + let hscale = 1; + if (totalWidth > w) { + hscale = w / totalWidth; + } + let vscale = 1; + const lineHeight = LINE_FACTOR * fontSize; + const lineAscent = (LINE_FACTOR - LINE_DESCENT_FACTOR) * fontSize; + const totalHeight = lineHeight * lines.length; + if (totalHeight > h) { + vscale = h / totalHeight; + } + const fscale = Math.min(hscale, vscale); + const newFontSize = fontSize * fscale; + let firstPoint, clipBox, matrix; + switch (rotation) { + case 0: + matrix = [1, 0, 0, 1]; + clipBox = [rect[0], rect[1], w, h]; + firstPoint = [rect[0], rect[3] - lineAscent]; + break; + case 90: + matrix = [0, 1, -1, 0]; + clipBox = [rect[1], -rect[2], w, h]; + firstPoint = [rect[1], -rect[0] - lineAscent]; + break; + case 180: + matrix = [-1, 0, 0, -1]; + clipBox = [-rect[2], -rect[3], w, h]; + firstPoint = [-rect[2], -rect[1] - lineAscent]; + break; + case 270: + matrix = [0, -1, 1, 0]; + clipBox = [-rect[3], rect[0], w, h]; + firstPoint = [-rect[3], rect[2] - lineAscent]; + break; + } + + const buffer = [ + "q", + `${matrix.join(" ")} 0 0 cm`, + `${clipBox.join(" ")} re W n`, + `BT`, + `${getPdfColor(color, /* isFill */ true)}`, + `0 Tc /Helv ${numberToString(newFontSize)} Tf`, + ]; + + buffer.push( + `${firstPoint.join(" ")} Td (${escapeString(encodedLines[0])}) Tj` + ); + const vShift = numberToString(lineHeight); + for (let i = 1, ii = encodedLines.length; i < ii; i++) { + const line = encodedLines[i]; + buffer.push(`0 -${vShift} Td (${escapeString(line)}) Tj`); + } + buffer.push("ET", "Q"); + const appearance = buffer.join("\n"); + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", rect); + appearanceStreamDict.set("Resources", resources); + appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]); + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } +} + +class LineAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.LINE; + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = false; + + const lineCoordinates = lookupRect(dict.getArray("L"), [0, 0, 0, 0]); + this.data.lineCoordinates = Util.normalizeRect(lineCoordinates); + + if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { + this.setLineEndings(dict.getArray("LE")); + this.data.lineEndings = this.lineEndings; + } + + if (!this.appearance) { + // The default stroke color is black. + const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + const interiorColor = getRgbColor(dict.getArray("IC"), null); + // The default fill color is transparent. Setting the fill colour is + // necessary if/when we want to add support for non-default line endings. + const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; + const fillAlpha = fillColor ? strokeAlpha : null; + + const borderWidth = this.borderStyle.width || 1, + borderAdjust = 2 * borderWidth; + + // If the /Rect-entry is empty/wrong, create a fallback rectangle so that + // we get similar rendering/highlighting behaviour as in Adobe Reader. + const bbox = [ + this.data.lineCoordinates[0] - borderAdjust, + this.data.lineCoordinates[1] - borderAdjust, + this.data.lineCoordinates[2] + borderAdjust, + this.data.lineCoordinates[3] + borderAdjust, + ]; + if (!Util.intersect(this.rectangle, bbox)) { + this.rectangle = bbox; + } + + this._setDefaultAppearance({ + xref, + extra: `${borderWidth} w`, + strokeColor, + fillColor, + strokeAlpha, + fillAlpha, + pointsCallback: (buffer, points) => { + buffer.push( + `${lineCoordinates[0]} ${lineCoordinates[1]} m`, + `${lineCoordinates[2]} ${lineCoordinates[3]} l`, + "S" + ); + return [ + points[0] - borderWidth, + points[2] + borderWidth, + points[7] - borderWidth, + points[3] + borderWidth, + ]; + }, + }); + } + } +} + +class SquareAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.SQUARE; + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = false; + + if (!this.appearance) { + // The default stroke color is black. + const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + const interiorColor = getRgbColor(dict.getArray("IC"), null); + // The default fill color is transparent. + const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; + const fillAlpha = fillColor ? strokeAlpha : null; + + if (this.borderStyle.width === 0 && !fillColor) { + // Prevent rendering a "hairline" border (fixes issue14164.pdf). + return; + } + + this._setDefaultAppearance({ + xref, + extra: `${this.borderStyle.width} w`, + strokeColor, + fillColor, + strokeAlpha, + fillAlpha, + pointsCallback: (buffer, points) => { + const x = points[4] + this.borderStyle.width / 2; + const y = points[5] + this.borderStyle.width / 2; + const width = points[6] - points[4] - this.borderStyle.width; + const height = points[3] - points[7] - this.borderStyle.width; + buffer.push(`${x} ${y} ${width} ${height} re`); + if (fillColor) { + buffer.push("B"); + } else { + buffer.push("S"); + } + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } +} + +class CircleAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.CIRCLE; + + if (!this.appearance) { + // The default stroke color is black. + const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + const interiorColor = getRgbColor(dict.getArray("IC"), null); + // The default fill color is transparent. + const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null; + const fillAlpha = fillColor ? strokeAlpha : null; + + if (this.borderStyle.width === 0 && !fillColor) { + // Prevent rendering a "hairline" border (fixes issue14164.pdf). + return; + } + + // Circles are approximated by Bรฉzier curves with four segments since + // there is no circle primitive in the PDF specification. For the control + // points distance, see https://stackoverflow.com/a/27863181. + const controlPointsDistance = (4 / 3) * Math.tan(Math.PI / (2 * 4)); + + this._setDefaultAppearance({ + xref, + extra: `${this.borderStyle.width} w`, + strokeColor, + fillColor, + strokeAlpha, + fillAlpha, + pointsCallback: (buffer, points) => { + const x0 = points[0] + this.borderStyle.width / 2; + const y0 = points[1] - this.borderStyle.width / 2; + const x1 = points[6] - this.borderStyle.width / 2; + const y1 = points[7] + this.borderStyle.width / 2; + const xMid = x0 + (x1 - x0) / 2; + const yMid = y0 + (y1 - y0) / 2; + const xOffset = ((x1 - x0) / 2) * controlPointsDistance; + const yOffset = ((y1 - y0) / 2) * controlPointsDistance; + + buffer.push( + `${xMid} ${y1} m`, + `${xMid + xOffset} ${y1} ${x1} ${yMid + yOffset} ${x1} ${yMid} c`, + `${x1} ${yMid - yOffset} ${xMid + xOffset} ${y0} ${xMid} ${y0} c`, + `${xMid - xOffset} ${y0} ${x0} ${yMid - yOffset} ${x0} ${yMid} c`, + `${x0} ${yMid + yOffset} ${xMid - xOffset} ${y1} ${xMid} ${y1} c`, + "h" + ); + if (fillColor) { + buffer.push("B"); + } else { + buffer.push("S"); + } + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } +} + +class PolylineAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.POLYLINE; + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = false; + this.data.vertices = null; + + if ( + (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) && + !(this instanceof PolygonAnnotation) + ) { + // Only meaningful for polyline annotations. + this.setLineEndings(dict.getArray("LE")); + this.data.lineEndings = this.lineEndings; + } + + // The vertices array is an array of numbers representing the alternating + // horizontal and vertical coordinates, respectively, of each vertex. + // Convert this to an array of objects with x and y coordinates. + const rawVertices = dict.getArray("Vertices"); + if (!isNumberArray(rawVertices, null)) { + return; + } + const vertices = (this.data.vertices = Float32Array.from(rawVertices)); + + if (!this.appearance) { + // The default stroke color is black. + const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + const borderWidth = this.borderStyle.width || 1, + borderAdjust = 2 * borderWidth; + + // If the /Rect-entry is empty/wrong, create a fallback rectangle so that + // we get similar rendering/highlighting behaviour as in Adobe Reader. + const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + for (let i = 0, ii = vertices.length; i < ii; i += 2) { + bbox[0] = Math.min(bbox[0], vertices[i] - borderAdjust); + bbox[1] = Math.min(bbox[1], vertices[i + 1] - borderAdjust); + bbox[2] = Math.max(bbox[2], vertices[i] + borderAdjust); + bbox[3] = Math.max(bbox[3], vertices[i + 1] + borderAdjust); + } + if (!Util.intersect(this.rectangle, bbox)) { + this.rectangle = bbox; + } + + this._setDefaultAppearance({ + xref, + extra: `${borderWidth} w`, + strokeColor, + strokeAlpha, + pointsCallback: (buffer, points) => { + for (let i = 0, ii = vertices.length; i < ii; i += 2) { + buffer.push( + `${vertices[i]} ${vertices[i + 1]} ${i === 0 ? "m" : "l"}` + ); + } + buffer.push("S"); + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } +} + +class PolygonAnnotation extends PolylineAnnotation { + constructor(params) { + // Polygons are specific forms of polylines, so reuse their logic. + super(params); + + this.data.annotationType = AnnotationType.POLYGON; + } +} + +class CaretAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + this.data.annotationType = AnnotationType.CARET; + } +} + +class InkAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = false; + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.INK; + this.data.inkLists = []; + this.data.isEditable = !this.data.noHTML; + // We want to be able to add mouse listeners to the annotation. + this.data.noHTML = false; + this.data.opacity = dict.get("CA") || 1; + + const rawInkLists = dict.getArray("InkList"); + if (!Array.isArray(rawInkLists)) { + return; + } + for (let i = 0, ii = rawInkLists.length; i < ii; ++i) { + // The raw ink lists array contains arrays of numbers representing + // the alternating horizontal and vertical coordinates, respectively, + // of each vertex. Convert this to an array of objects with x and y + // coordinates. + if (!Array.isArray(rawInkLists[i])) { + continue; + } + const inkList = new Float32Array(rawInkLists[i].length); + this.data.inkLists.push(inkList); + for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) { + const x = xref.fetchIfRef(rawInkLists[i][j]), + y = xref.fetchIfRef(rawInkLists[i][j + 1]); + if (typeof x === "number" && typeof y === "number") { + inkList[j] = x; + inkList[j + 1] = y; + } + } + } + + if (!this.appearance) { + // The default stroke color is black. + const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + const borderWidth = this.borderStyle.width || 1, + borderAdjust = 2 * borderWidth; + + // If the /Rect-entry is empty/wrong, create a fallback rectangle so that + // we get similar rendering/highlighting behaviour as in Adobe Reader. + const bbox = [Infinity, Infinity, -Infinity, -Infinity]; + for (const inkList of this.data.inkLists) { + for (let i = 0, ii = inkList.length; i < ii; i += 2) { + bbox[0] = Math.min(bbox[0], inkList[i] - borderAdjust); + bbox[1] = Math.min(bbox[1], inkList[i + 1] - borderAdjust); + bbox[2] = Math.max(bbox[2], inkList[i] + borderAdjust); + bbox[3] = Math.max(bbox[3], inkList[i + 1] + borderAdjust); + } + } + if (!Util.intersect(this.rectangle, bbox)) { + this.rectangle = bbox; + } + + this._setDefaultAppearance({ + xref, + extra: `${borderWidth} w`, + strokeColor, + strokeAlpha, + pointsCallback: (buffer, points) => { + // According to the specification, see "12.5.6.13 Ink Annotations": + // When drawn, the points shall be connected by straight lines or + // curves in an implementation-dependent way. + // In order to simplify things, we utilize straight lines for now. + for (const inkList of this.data.inkLists) { + for (let i = 0, ii = inkList.length; i < ii; i += 2) { + buffer.push( + `${inkList[i]} ${inkList[i + 1]} ${i === 0 ? "m" : "l"}` + ); + } + buffer.push("S"); + } + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } + + static createNewDict(annotation, xref, { apRef, ap }) { + const { + oldAnnotation, + color, + opacity, + paths, + outlines, + rect, + rotation, + thickness, + user, + } = annotation; + const ink = oldAnnotation || new Dict(xref); + ink.set("Type", Name.get("Annot")); + ink.set("Subtype", Name.get("Ink")); + ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`); + ink.set("Rect", rect); + ink.set("InkList", outlines?.points || paths.points); + ink.set("F", 4); + ink.set("Rotate", rotation); + + if (user) { + ink.set("T", stringToAsciiOrUTF16BE(user)); + } + + if (outlines) { + // Free highlight. + // There's nothing about this in the spec, but it's used when highlighting + // in Edge's viewer. Acrobat takes into account this parameter to indicate + // that the Ink is used for highlighting. + ink.set("IT", Name.get("InkHighlight")); + } + + // Line thickness. + const bs = new Dict(xref); + ink.set("BS", bs); + bs.set("W", thickness); + + // Color. + ink.set("C", getPdfColorArray(color)); + + // Opacity. + ink.set("CA", opacity); + + const n = new Dict(xref); + ink.set("AP", n); + + if (apRef) { + n.set("N", apRef); + } else { + n.set("N", ap); + } + + return ink; + } + + static async createNewAppearanceStream(annotation, xref, params) { + if (annotation.outlines) { + return this.createNewAppearanceStreamForHighlight( + annotation, + xref, + params + ); + } + const { color, rect, paths, thickness, opacity } = annotation; + + const appearanceBuffer = [ + `${thickness} w 1 J 1 j`, + `${getPdfColor(color, /* isFill */ false)}`, + ]; + + if (opacity !== 1) { + appearanceBuffer.push("/R0 gs"); + } + + for (const outline of paths.lines) { + appearanceBuffer.push( + `${numberToString(outline[4])} ${numberToString(outline[5])} m` + ); + for (let i = 6, ii = outline.length; i < ii; i += 6) { + if (isNaN(outline[i])) { + appearanceBuffer.push( + `${numberToString(outline[i + 4])} ${numberToString( + outline[i + 5] + )} l` + ); + } else { + const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6); + appearanceBuffer.push( + [c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c" + ); + } + } + if (outline.length === 6) { + appearanceBuffer.push( + `${numberToString(outline[4])} ${numberToString(outline[5])} l` + ); + } + } + appearanceBuffer.push("S"); + + const appearance = appearanceBuffer.join("\n"); + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", rect); + appearanceStreamDict.set("Length", appearance.length); + + if (opacity !== 1) { + const resources = new Dict(xref); + const extGState = new Dict(xref); + const r0 = new Dict(xref); + r0.set("CA", opacity); + r0.set("Type", Name.get("ExtGState")); + extGState.set("R0", r0); + resources.set("ExtGState", extGState); + appearanceStreamDict.set("Resources", resources); + } + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } + + static async createNewAppearanceStreamForHighlight(annotation, xref, params) { + const { + color, + rect, + outlines: { outline }, + opacity, + } = annotation; + const appearanceBuffer = [ + `${getPdfColor(color, /* isFill */ true)}`, + "/R0 gs", + ]; + + appearanceBuffer.push( + `${numberToString(outline[4])} ${numberToString(outline[5])} m` + ); + for (let i = 6, ii = outline.length; i < ii; i += 6) { + if (isNaN(outline[i])) { + appearanceBuffer.push( + `${numberToString(outline[i + 4])} ${numberToString( + outline[i + 5] + )} l` + ); + } else { + const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6); + appearanceBuffer.push( + [c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c" + ); + } + } + appearanceBuffer.push("h f"); + const appearance = appearanceBuffer.join("\n"); + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", rect); + appearanceStreamDict.set("Length", appearance.length); + + const resources = new Dict(xref); + const extGState = new Dict(xref); + resources.set("ExtGState", extGState); + appearanceStreamDict.set("Resources", resources); + const r0 = new Dict(xref); + extGState.set("R0", r0); + r0.set("BM", Name.get("Multiply")); + + if (opacity !== 1) { + r0.set("ca", opacity); + r0.set("Type", Name.get("ExtGState")); + } + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } +} + +class HighlightAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.HIGHLIGHT; + this.data.isEditable = !this.data.noHTML; + // We want to be able to add mouse listeners to the annotation. + this.data.noHTML = false; + this.data.opacity = dict.get("CA") || 1; + + const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null)); + if (quadPoints) { + const resources = this.appearance?.dict.get("Resources"); + + if (!this.appearance || !resources?.has("ExtGState")) { + if (this.appearance) { + // Workaround for cases where there's no /ExtGState-entry directly + // available, e.g. when the appearance stream contains a /XObject of + // the /Form-type, since that causes the highlighting to completely + // obscure the PDF content below it (fixes issue13242.pdf). + warn("HighlightAnnotation - ignoring built-in appearance stream."); + } + // Default color is yellow in Acrobat Reader + const fillColor = this.color ? getPdfColorArray(this.color) : [1, 1, 0]; + const fillAlpha = dict.get("CA"); + + this._setDefaultAppearance({ + xref, + fillColor, + blendMode: "Multiply", + fillAlpha, + pointsCallback: (buffer, points) => { + buffer.push( + `${points[0]} ${points[1]} m`, + `${points[2]} ${points[3]} l`, + `${points[6]} ${points[7]} l`, + `${points[4]} ${points[5]} l`, + "f" + ); + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } else { + this.data.popupRef = null; + } + } + + static createNewDict(annotation, xref, { apRef, ap }) { + const { color, oldAnnotation, opacity, rect, rotation, user, quadPoints } = + annotation; + const highlight = oldAnnotation || new Dict(xref); + highlight.set("Type", Name.get("Annot")); + highlight.set("Subtype", Name.get("Highlight")); + highlight.set( + oldAnnotation ? "M" : "CreationDate", + `D:${getModificationDate()}` + ); + highlight.set("CreationDate", `D:${getModificationDate()}`); + highlight.set("Rect", rect); + highlight.set("F", 4); + highlight.set("Border", [0, 0, 0]); + highlight.set("Rotate", rotation); + highlight.set("QuadPoints", quadPoints); + + // Color. + highlight.set("C", getPdfColorArray(color)); + + // Opacity. + highlight.set("CA", opacity); + + if (user) { + highlight.set("T", stringToAsciiOrUTF16BE(user)); + } + + if (apRef || ap) { + const n = new Dict(xref); + highlight.set("AP", n); + n.set("N", apRef || ap); + } + + return highlight; + } + + static async createNewAppearanceStream(annotation, xref, params) { + const { color, rect, outlines, opacity } = annotation; + + const appearanceBuffer = [ + `${getPdfColor(color, /* isFill */ true)}`, + "/R0 gs", + ]; + + const buffer = []; + for (const outline of outlines) { + buffer.length = 0; + buffer.push( + `${numberToString(outline[0])} ${numberToString(outline[1])} m` + ); + for (let i = 2, ii = outline.length; i < ii; i += 2) { + buffer.push( + `${numberToString(outline[i])} ${numberToString(outline[i + 1])} l` + ); + } + buffer.push("h"); + appearanceBuffer.push(buffer.join("\n")); + } + appearanceBuffer.push("f*"); + const appearance = appearanceBuffer.join("\n"); + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", rect); + appearanceStreamDict.set("Length", appearance.length); + + const resources = new Dict(xref); + const extGState = new Dict(xref); + resources.set("ExtGState", extGState); + appearanceStreamDict.set("Resources", resources); + const r0 = new Dict(xref); + extGState.set("R0", r0); + r0.set("BM", Name.get("Multiply")); + + if (opacity !== 1) { + r0.set("ca", opacity); + r0.set("Type", Name.get("ExtGState")); + } + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } +} + +class UnderlineAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.UNDERLINE; + + const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null)); + if (quadPoints) { + if (!this.appearance) { + // Default color is black + const strokeColor = this.color + ? getPdfColorArray(this.color) + : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + // The values 0.571 and 1.3 below corresponds to what Acrobat is doing. + this._setDefaultAppearance({ + xref, + extra: "[] 0 d 0.571 w", + strokeColor, + strokeAlpha, + pointsCallback: (buffer, points) => { + buffer.push( + `${points[4]} ${points[5] + 1.3} m`, + `${points[6]} ${points[7] + 1.3} l`, + "S" + ); + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } else { + this.data.popupRef = null; + } + } +} + +class SquigglyAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.SQUIGGLY; + + const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null)); + if (quadPoints) { + if (!this.appearance) { + // Default color is black + const strokeColor = this.color + ? getPdfColorArray(this.color) + : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + this._setDefaultAppearance({ + xref, + extra: "[] 0 d 1 w", + strokeColor, + strokeAlpha, + pointsCallback: (buffer, points) => { + const dy = (points[1] - points[5]) / 6; + let shift = dy; + let x = points[4]; + const y = points[5]; + const xEnd = points[6]; + buffer.push(`${x} ${y + shift} m`); + do { + x += 2; + shift = shift === 0 ? dy : 0; + buffer.push(`${x} ${y + shift} l`); + } while (x < xEnd); + buffer.push("S"); + return [points[4], xEnd, y - 2 * dy, y + 2 * dy]; + }, + }); + } + } else { + this.data.popupRef = null; + } + } +} + +class StrikeOutAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + this.data.annotationType = AnnotationType.STRIKEOUT; + + const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null)); + if (quadPoints) { + if (!this.appearance) { + // Default color is black + const strokeColor = this.color + ? getPdfColorArray(this.color) + : [0, 0, 0]; + const strokeAlpha = dict.get("CA"); + + this._setDefaultAppearance({ + xref, + extra: "[] 0 d 1 w", + strokeColor, + strokeAlpha, + pointsCallback: (buffer, points) => { + buffer.push( + `${(points[0] + points[4]) / 2} ` + + `${(points[1] + points[5]) / 2} m`, + `${(points[2] + points[6]) / 2} ` + + `${(points[3] + points[7]) / 2} l`, + "S" + ); + return [points[0], points[2], points[7], points[3]]; + }, + }); + } + } else { + this.data.popupRef = null; + } + } +} + +class StampAnnotation extends MarkupAnnotation { + #savedHasOwnCanvas; + + constructor(params) { + super(params); + + this.data.annotationType = AnnotationType.STAMP; + this.#savedHasOwnCanvas = this.data.hasOwnCanvas = this.data.noRotate; + this.data.isEditable = !this.data.noHTML; + // We want to be able to add mouse listeners to the annotation. + this.data.noHTML = false; + } + + mustBeViewedWhenEditing(isEditing, modifiedIds = null) { + if (isEditing) { + if (!this.data.isEditable) { + return false; + } + // When we're editing, we want to ensure that the stamp annotation is + // drawn on a canvas in order to use it in the annotation editor layer. + this.#savedHasOwnCanvas = this.data.hasOwnCanvas; + this.data.hasOwnCanvas = true; + return true; + } + this.data.hasOwnCanvas = this.#savedHasOwnCanvas; + + return !modifiedIds?.has(this.data.id); + } + + static async createImage(bitmap, xref) { + // TODO: when printing, we could have a specific internal colorspace + // (e.g. something like DeviceRGBA) in order avoid any conversion (i.e. no + // jpeg, no rgba to rgb conversion, etc...) + + const { width, height } = bitmap; + const canvas = new OffscreenCanvas(width, height); + const ctx = canvas.getContext("2d", { alpha: true }); + + // Draw the image and get the data in order to extract the transparency. + ctx.drawImage(bitmap, 0, 0); + const data = ctx.getImageData(0, 0, width, height).data; + const buf32 = new Uint32Array(data.buffer); + const hasAlpha = buf32.some( + FeatureTest.isLittleEndian + ? x => x >>> 24 !== 0xff + : x => (x & 0xff) !== 0xff + ); + + if (hasAlpha) { + // Redraw the image on a white background in order to remove the thin gray + // line which can appear when exporting to jpeg. + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, width, height); + ctx.drawImage(bitmap, 0, 0); + } + + const jpegBufferPromise = canvas + .convertToBlob({ type: "image/jpeg", quality: 1 }) + .then(blob => blob.arrayBuffer()); + + const xobjectName = Name.get("XObject"); + const imageName = Name.get("Image"); + const image = new Dict(xref); + image.set("Type", xobjectName); + image.set("Subtype", imageName); + image.set("BitsPerComponent", 8); + image.set("ColorSpace", Name.get("DeviceRGB")); + image.set("Filter", Name.get("DCTDecode")); + image.set("BBox", [0, 0, width, height]); + image.set("Width", width); + image.set("Height", height); + + let smaskStream = null; + if (hasAlpha) { + const alphaBuffer = new Uint8Array(buf32.length); + if (FeatureTest.isLittleEndian) { + for (let i = 0, ii = buf32.length; i < ii; i++) { + alphaBuffer[i] = buf32[i] >>> 24; + } + } else { + for (let i = 0, ii = buf32.length; i < ii; i++) { + alphaBuffer[i] = buf32[i] & 0xff; + } + } + + const smask = new Dict(xref); + smask.set("Type", xobjectName); + smask.set("Subtype", imageName); + smask.set("BitsPerComponent", 8); + smask.set("ColorSpace", Name.get("DeviceGray")); + smask.set("Width", width); + smask.set("Height", height); + + smaskStream = new Stream(alphaBuffer, 0, 0, smask); + } + const imageStream = new Stream(await jpegBufferPromise, 0, 0, image); + + return { + imageStream, + smaskStream, + width, + height, + }; + } + + static createNewDict(annotation, xref, { apRef, ap }) { + const { oldAnnotation, rect, rotation, user } = annotation; + const stamp = oldAnnotation || new Dict(xref); + stamp.set("Type", Name.get("Annot")); + stamp.set("Subtype", Name.get("Stamp")); + stamp.set( + oldAnnotation ? "M" : "CreationDate", + `D:${getModificationDate()}` + ); + stamp.set("Rect", rect); + stamp.set("F", 4); + stamp.set("Border", [0, 0, 0]); + stamp.set("Rotate", rotation); + + if (user) { + stamp.set("T", stringToAsciiOrUTF16BE(user)); + } + + if (apRef || ap) { + const n = new Dict(xref); + stamp.set("AP", n); + + if (apRef) { + n.set("N", apRef); + } else { + n.set("N", ap); + } + } + + return stamp; + } + + static async #createNewAppearanceStreamForDrawing(annotation, xref) { + const { areContours, color, rect, lines, thickness } = annotation; + + const appearanceBuffer = [ + `${thickness} w 1 J 1 j`, + `${getPdfColor(color, /* isFill */ areContours)}`, + ]; + + for (const line of lines) { + appearanceBuffer.push( + `${numberToString(line[4])} ${numberToString(line[5])} m` + ); + for (let i = 6, ii = line.length; i < ii; i += 6) { + if (isNaN(line[i])) { + appearanceBuffer.push( + `${numberToString(line[i + 4])} ${numberToString(line[i + 5])} l` + ); + } else { + const [c1x, c1y, c2x, c2y, x, y] = line.slice(i, i + 6); + appearanceBuffer.push( + [c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c" + ); + } + } + if (line.length === 6) { + appearanceBuffer.push( + `${numberToString(line[4])} ${numberToString(line[5])} l` + ); + } + } + appearanceBuffer.push(areContours ? "F" : "S"); + + const appearance = appearanceBuffer.join("\n"); + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", rect); + appearanceStreamDict.set("Length", appearance.length); + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } + + static async createNewAppearanceStream(annotation, xref, params) { + if (annotation.oldAnnotation) { + // We'll use the AP we already have. + return null; + } + if (annotation.isSignature) { + return this.#createNewAppearanceStreamForDrawing(annotation, xref); + } + + const { rotation } = annotation; + const { imageRef, width, height } = params.image; + const resources = new Dict(xref); + const xobject = new Dict(xref); + resources.set("XObject", xobject); + xobject.set("Im0", imageRef); + const appearance = `q ${width} 0 0 ${height} 0 0 cm /Im0 Do Q`; + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", [0, 0, width, height]); + appearanceStreamDict.set("Resources", resources); + + if (rotation) { + const matrix = getRotationMatrix(rotation, width, height); + appearanceStreamDict.set("Matrix", matrix); + } + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } +} + +class FileAttachmentAnnotation extends MarkupAnnotation { + constructor(params) { + super(params); + + const { dict, xref } = params; + const file = new FileSpec(dict.get("FS"), xref); + + this.data.annotationType = AnnotationType.FILEATTACHMENT; + this.data.hasOwnCanvas = this.data.noRotate; + this.data.noHTML = false; + this.data.file = file.serializable; + + const name = dict.get("Name"); + this.data.name = + name instanceof Name ? stringToPDFString(name.name) : "PushPin"; + + const fillAlpha = dict.get("ca"); + this.data.fillAlpha = + typeof fillAlpha === "number" && fillAlpha >= 0 && fillAlpha <= 1 + ? fillAlpha + : null; + } +} + +export { + Annotation, + AnnotationBorderStyle, + AnnotationFactory, + getQuadPoints, + MarkupAnnotation, + PopupAnnotation, + WidgetAnnotation, +}; diff --git a/frontend/public/pdf.js/src/core/arithmetic_decoder.js b/frontend/public/pdf.js/src/core/arithmetic_decoder.js new file mode 100644 index 0000000..bb481ac --- /dev/null +++ b/frontend/public/pdf.js/src/core/arithmetic_decoder.js @@ -0,0 +1,182 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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. + */ + +// Table C-2 +const QeTable = [ + { qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1 }, + { qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0 }, + { qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0 }, + { qe: 0x0ac1, nmps: 4, nlps: 12, switchFlag: 0 }, + { qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0 }, + { qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0 }, + { qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1 }, + { qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0 }, + { qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0 }, + { qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0 }, + { qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0 }, + { qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0 }, + { qe: 0x1c01, nmps: 13, nlps: 20, switchFlag: 0 }, + { qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0 }, + { qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1 }, + { qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0 }, + { qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0 }, + { qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0 }, + { qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0 }, + { qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0 }, + { qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0 }, + { qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0 }, + { qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0 }, + { qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0 }, + { qe: 0x1c01, nmps: 25, nlps: 22, switchFlag: 0 }, + { qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0 }, + { qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0 }, + { qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0 }, + { qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0 }, + { qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0 }, + { qe: 0x0ac1, nmps: 31, nlps: 28, switchFlag: 0 }, + { qe: 0x09c1, nmps: 32, nlps: 29, switchFlag: 0 }, + { qe: 0x08a1, nmps: 33, nlps: 30, switchFlag: 0 }, + { qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0 }, + { qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0 }, + { qe: 0x02a1, nmps: 36, nlps: 33, switchFlag: 0 }, + { qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0 }, + { qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0 }, + { qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0 }, + { qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0 }, + { qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0 }, + { qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0 }, + { qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0 }, + { qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0 }, + { qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0 }, + { qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0 }, + { qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0 }, +]; + +/** + * This class implements the QM Coder decoding as defined in + * JPEG 2000 Part I Final Committee Draft Version 1.0 + * Annex C.3 Arithmetic decoding procedure + * available at http://www.jpeg.org/public/fcd15444-1.pdf + * + * The arithmetic decoder is used in conjunction with context models to decode + * JPEG2000 and JBIG2 streams. + */ +class ArithmeticDecoder { + // C.3.5 Initialisation of the decoder (INITDEC) + constructor(data, start, end) { + this.data = data; + this.bp = start; + this.dataEnd = end; + + this.chigh = data[start]; + this.clow = 0; + + this.byteIn(); + + this.chigh = ((this.chigh << 7) & 0xffff) | ((this.clow >> 9) & 0x7f); + this.clow = (this.clow << 7) & 0xffff; + this.ct -= 7; + this.a = 0x8000; + } + + // C.3.4 Compressed data input (BYTEIN) + byteIn() { + const data = this.data; + let bp = this.bp; + + if (data[bp] === 0xff) { + if (data[bp + 1] > 0x8f) { + this.clow += 0xff00; + this.ct = 8; + } else { + bp++; + this.clow += data[bp] << 9; + this.ct = 7; + this.bp = bp; + } + } else { + bp++; + this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xff00; + this.ct = 8; + this.bp = bp; + } + if (this.clow > 0xffff) { + this.chigh += this.clow >> 16; + this.clow &= 0xffff; + } + } + + // C.3.2 Decoding a decision (DECODE) + readBit(contexts, pos) { + // Contexts are packed into 1 byte: + // highest 7 bits carry cx.index, lowest bit carries cx.mps + let cx_index = contexts[pos] >> 1, + cx_mps = contexts[pos] & 1; + const qeTableIcx = QeTable[cx_index]; + const qeIcx = qeTableIcx.qe; + let d; + let a = this.a - qeIcx; + + if (this.chigh < qeIcx) { + // exchangeLps + if (a < qeIcx) { + a = qeIcx; + d = cx_mps; + cx_index = qeTableIcx.nmps; + } else { + a = qeIcx; + d = 1 ^ cx_mps; + if (qeTableIcx.switchFlag === 1) { + cx_mps = d; + } + cx_index = qeTableIcx.nlps; + } + } else { + this.chigh -= qeIcx; + if ((a & 0x8000) !== 0) { + this.a = a; + return cx_mps; + } + // exchangeMps + if (a < qeIcx) { + d = 1 ^ cx_mps; + if (qeTableIcx.switchFlag === 1) { + cx_mps = d; + } + cx_index = qeTableIcx.nlps; + } else { + d = cx_mps; + cx_index = qeTableIcx.nmps; + } + } + // C.3.3 renormD; + do { + if (this.ct === 0) { + this.byteIn(); + } + + a <<= 1; + this.chigh = ((this.chigh << 1) & 0xffff) | ((this.clow >> 15) & 1); + this.clow = (this.clow << 1) & 0xffff; + this.ct--; + } while ((a & 0x8000) === 0); + this.a = a; + + contexts[pos] = (cx_index << 1) | cx_mps; + return d; + } +} + +export { ArithmeticDecoder }; diff --git a/frontend/public/pdf.js/src/core/ascii_85_stream.js b/frontend/public/pdf.js/src/core/ascii_85_stream.js new file mode 100644 index 0000000..9cabbc4 --- /dev/null +++ b/frontend/public/pdf.js/src/core/ascii_85_stream.js @@ -0,0 +1,98 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { DecodeStream } from "./decode_stream.js"; +import { isWhiteSpace } from "./core_utils.js"; + +class Ascii85Stream extends DecodeStream { + constructor(str, maybeLength) { + // Most streams increase in size when decoded, but Ascii85 streams + // typically shrink by ~20%. + if (maybeLength) { + maybeLength *= 0.8; + } + super(maybeLength); + + this.str = str; + this.dict = str.dict; + this.input = new Uint8Array(5); + } + + readBlock() { + const TILDA_CHAR = 0x7e; // '~' + const Z_LOWER_CHAR = 0x7a; // 'z' + const EOF = -1; + + const str = this.str; + + let c = str.getByte(); + while (isWhiteSpace(c)) { + c = str.getByte(); + } + + if (c === EOF || c === TILDA_CHAR) { + this.eof = true; + return; + } + + const bufferLength = this.bufferLength; + let buffer, i; + + // special code for z + if (c === Z_LOWER_CHAR) { + buffer = this.ensureBuffer(bufferLength + 4); + for (i = 0; i < 4; ++i) { + buffer[bufferLength + i] = 0; + } + this.bufferLength += 4; + } else { + const input = this.input; + input[0] = c; + for (i = 1; i < 5; ++i) { + c = str.getByte(); + while (isWhiteSpace(c)) { + c = str.getByte(); + } + + input[i] = c; + + if (c === EOF || c === TILDA_CHAR) { + break; + } + } + buffer = this.ensureBuffer(bufferLength + i - 1); + this.bufferLength += i - 1; + + // partial ending; + if (i < 5) { + for (; i < 5; ++i) { + input[i] = 0x21 + 84; + } + this.eof = true; + } + let t = 0; + for (i = 0; i < 5; ++i) { + t = t * 85 + (input[i] - 0x21); + } + + for (i = 3; i >= 0; --i) { + buffer[bufferLength + i] = t & 0xff; + t >>= 8; + } + } + } +} + +export { Ascii85Stream }; diff --git a/frontend/public/pdf.js/src/core/ascii_hex_stream.js b/frontend/public/pdf.js/src/core/ascii_hex_stream.js new file mode 100644 index 0000000..60fe007 --- /dev/null +++ b/frontend/public/pdf.js/src/core/ascii_hex_stream.js @@ -0,0 +1,79 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { DecodeStream } from "./decode_stream.js"; + +class AsciiHexStream extends DecodeStream { + constructor(str, maybeLength) { + // Most streams increase in size when decoded, but AsciiHex streams shrink + // by 50%. + if (maybeLength) { + maybeLength *= 0.5; + } + super(maybeLength); + + this.str = str; + this.dict = str.dict; + + this.firstDigit = -1; + } + + readBlock() { + const UPSTREAM_BLOCK_SIZE = 8000; + const bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE); + if (!bytes.length) { + this.eof = true; + return; + } + + const maxDecodeLength = (bytes.length + 1) >> 1; + const buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength); + let bufferLength = this.bufferLength; + + let firstDigit = this.firstDigit; + for (const ch of bytes) { + let digit; + if (ch >= /* '0' = */ 0x30 && ch <= /* '9' = */ 0x39) { + digit = ch & 0x0f; + } else if ( + (ch >= /* 'A' = */ 0x41 && ch <= /* 'Z' = */ 0x46) || + (ch >= /* 'a' = */ 0x61 && ch <= /* 'z' = */ 0x66) + ) { + digit = (ch & 0x0f) + 9; + } else if (ch === /* '>' = */ 0x3e) { + this.eof = true; + break; + } else { + // Probably whitespace, ignoring. + continue; + } + if (firstDigit < 0) { + firstDigit = digit; + } else { + buffer[bufferLength++] = (firstDigit << 4) | digit; + firstDigit = -1; + } + } + if (firstDigit >= 0 && this.eof) { + // incomplete byte + buffer[bufferLength++] = firstDigit << 4; + firstDigit = -1; + } + this.firstDigit = firstDigit; + this.bufferLength = bufferLength; + } +} + +export { AsciiHexStream }; diff --git a/frontend/public/pdf.js/src/core/base_stream.js b/frontend/public/pdf.js/src/core/base_stream.js new file mode 100644 index 0000000..0266449 --- /dev/null +++ b/frontend/public/pdf.js/src/core/base_stream.js @@ -0,0 +1,142 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { bytesToString, shadow, unreachable } from "../shared/util.js"; + +class BaseStream { + constructor() { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === BaseStream + ) { + unreachable("Cannot initialize BaseStream."); + } + } + + // eslint-disable-next-line getter-return + get length() { + unreachable("Abstract getter `length` accessed"); + } + + // eslint-disable-next-line getter-return + get isEmpty() { + unreachable("Abstract getter `isEmpty` accessed"); + } + + get isDataLoaded() { + return shadow(this, "isDataLoaded", true); + } + + getByte() { + unreachable("Abstract method `getByte` called"); + } + + getBytes(length) { + unreachable("Abstract method `getBytes` called"); + } + + /** + * NOTE: This method can only be used to get image-data that is guaranteed + * to be fully loaded, since otherwise intermittent errors may occur; + * note the `ObjectLoader` class. + */ + async getImageData(length, decoderOptions) { + return this.getBytes(length, decoderOptions); + } + + async asyncGetBytes() { + unreachable("Abstract method `asyncGetBytes` called"); + } + + get isAsync() { + return false; + } + + get isAsyncDecoder() { + return false; + } + + get canAsyncDecodeImageFromBuffer() { + return false; + } + + async getTransferableImage() { + return null; + } + + peekByte() { + const peekedByte = this.getByte(); + if (peekedByte !== -1) { + this.pos--; + } + return peekedByte; + } + + peekBytes(length) { + const bytes = this.getBytes(length); + this.pos -= bytes.length; + return bytes; + } + + getUint16() { + const b0 = this.getByte(); + const b1 = this.getByte(); + if (b0 === -1 || b1 === -1) { + return -1; + } + return (b0 << 8) + b1; + } + + getInt32() { + const b0 = this.getByte(); + const b1 = this.getByte(); + const b2 = this.getByte(); + const b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + } + + getByteRange(begin, end) { + unreachable("Abstract method `getByteRange` called"); + } + + getString(length) { + return bytesToString(this.getBytes(length)); + } + + skip(n) { + this.pos += n || 1; + } + + reset() { + unreachable("Abstract method `reset` called"); + } + + moveStart() { + unreachable("Abstract method `moveStart` called"); + } + + makeSubStream(start, length, dict = null) { + unreachable("Abstract method `makeSubStream` called"); + } + + /** + * @returns {Array | null} + */ + getBaseStreams() { + return null; + } +} + +export { BaseStream }; diff --git a/frontend/public/pdf.js/src/core/bidi.js b/frontend/public/pdf.js/src/core/bidi.js new file mode 100644 index 0000000..d8e7fd7 --- /dev/null +++ b/frontend/public/pdf.js/src/core/bidi.js @@ -0,0 +1,440 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { warn } from "../shared/util.js"; + +// Character types for symbols from 0000 to 00FF. +// Source: ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt +// prettier-ignore +const baseTypes = [ + "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "S", "B", "S", + "WS", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", + "BN", "BN", "BN", "BN", "B", "B", "B", "S", "WS", "ON", "ON", "ET", + "ET", "ET", "ON", "ON", "ON", "ON", "ON", "ES", "CS", "ES", "CS", "CS", + "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "CS", "ON", + "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "L", "L", "L", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", + "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", + "BN", "BN", "BN", "BN", "BN", "BN", "B", "BN", "BN", "BN", "BN", "BN", + "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", + "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "CS", "ON", "ET", + "ET", "ET", "ET", "ON", "ON", "ON", "ON", "L", "ON", "ON", "BN", "ON", + "ON", "ET", "ET", "EN", "EN", "ON", "L", "ON", "ON", "ON", "EN", "L", + "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", + "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L" +]; + +// Character types for symbols from 0600 to 06FF. +// Source: ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt +// Note that 061D does not exist in the Unicode standard (see +// http://unicode.org/charts/PDF/U0600.pdf), so we replace it with an +// empty string and issue a warning if we encounter this character. The +// empty string is required to properly index the items after it. +// prettier-ignore +const arabicTypes = [ + "AN", "AN", "AN", "AN", "AN", "AN", "ON", "ON", "AL", "ET", "ET", "AL", + "CS", "AL", "ON", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", + "NSM", "NSM", "NSM", "NSM", "AL", "AL", "", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", + "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", + "NSM", "NSM", "NSM", "NSM", "AN", "AN", "AN", "AN", "AN", "AN", "AN", + "AN", "AN", "AN", "ET", "AN", "AN", "AL", "AL", "AL", "NSM", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", + "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", + "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "NSM", "NSM", + "ON", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "EN", "EN", "EN", "EN", + "EN", "EN", "EN", "EN", "EN", "EN", "AL", "AL", "AL", "AL", "AL", "AL" +]; + +function isOdd(i) { + return (i & 1) !== 0; +} + +function isEven(i) { + return (i & 1) === 0; +} + +function findUnequal(arr, start, value) { + let j, jj; + for (j = start, jj = arr.length; j < jj; ++j) { + if (arr[j] !== value) { + return j; + } + } + return j; +} + +function setValues(arr, start, end, value) { + for (let j = start; j < end; ++j) { + arr[j] = value; + } +} + +function reverseValues(arr, start, end) { + for (let i = start, j = end - 1; i < j; ++i, --j) { + const temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } +} + +function createBidiText(str, isLTR, vertical = false) { + let dir = "ltr"; + if (vertical) { + dir = "ttb"; + } else if (!isLTR) { + dir = "rtl"; + } + return { str, dir }; +} + +// These are used in bidi(), which is called frequently. We re-use them on +// each call to avoid unnecessary allocations. +const chars = []; +const types = []; + +function bidi(str, startLevel = -1, vertical = false) { + let isLTR = true; + const strLength = str.length; + if (strLength === 0 || vertical) { + return createBidiText(str, isLTR, vertical); + } + + // Get types and fill arrays + chars.length = strLength; + types.length = strLength; + let numBidi = 0; + + let i, ii; + for (i = 0; i < strLength; ++i) { + chars[i] = str.charAt(i); + + const charCode = str.charCodeAt(i); + let charType = "L"; + if (charCode <= 0x00ff) { + charType = baseTypes[charCode]; + } else if (0x0590 <= charCode && charCode <= 0x05f4) { + charType = "R"; + } else if (0x0600 <= charCode && charCode <= 0x06ff) { + charType = arabicTypes[charCode & 0xff]; + if (!charType) { + warn("Bidi: invalid Unicode character " + charCode.toString(16)); + } + } else if ( + (0x0700 <= charCode && charCode <= 0x08ac) || + (0xfb50 <= charCode && charCode <= 0xfdff) || + (0xfe70 <= charCode && charCode <= 0xfeff) + ) { + charType = "AL"; + } + if (charType === "R" || charType === "AL" || charType === "AN") { + numBidi++; + } + types[i] = charType; + } + + // Detect the bidi method + // - If there are no rtl characters then no bidi needed + // - If less than 30% chars are rtl then string is primarily ltr, + // unless the string is very short. + // - If more than 30% chars are rtl then string is primarily rtl + if (numBidi === 0) { + isLTR = true; + return createBidiText(str, isLTR); + } + + if (startLevel === -1) { + if (numBidi / strLength < 0.3 && strLength > 4) { + isLTR = true; + startLevel = 0; + } else { + isLTR = false; + startLevel = 1; + } + } + + const levels = []; + for (i = 0; i < strLength; ++i) { + levels[i] = startLevel; + } + + /* + X1-X10: skip most of this, since we are NOT doing the embeddings. + */ + const e = isOdd(startLevel) ? "R" : "L"; + const sor = e; + const eor = sor; + + /* + W1. Examine each non-spacing mark (NSM) in the level run, and change the + type of the NSM to the type of the previous character. If the NSM is at the + start of the level run, it will get the type of sor. + */ + let lastType = sor; + for (i = 0; i < strLength; ++i) { + if (types[i] === "NSM") { + types[i] = lastType; + } else { + lastType = types[i]; + } + } + + /* + W2. Search backwards from each instance of a European number until the + first strong type (R, L, AL, or sor) is found. If an AL is found, change + the type of the European number to Arabic number. + */ + lastType = sor; + let t; + for (i = 0; i < strLength; ++i) { + t = types[i]; + if (t === "EN") { + types[i] = lastType === "AL" ? "AN" : "EN"; + } else if (t === "R" || t === "L" || t === "AL") { + lastType = t; + } + } + + /* + W3. Change all ALs to R. + */ + for (i = 0; i < strLength; ++i) { + t = types[i]; + if (t === "AL") { + types[i] = "R"; + } + } + + /* + W4. A single European separator between two European numbers changes to a + European number. A single common separator between two numbers of the same + type changes to that type: + */ + for (i = 1; i < strLength - 1; ++i) { + if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") { + types[i] = "EN"; + } + if ( + types[i] === "CS" && + (types[i - 1] === "EN" || types[i - 1] === "AN") && + types[i + 1] === types[i - 1] + ) { + types[i] = types[i - 1]; + } + } + + /* + W5. A sequence of European terminators adjacent to European numbers changes + to all European numbers: + */ + for (i = 0; i < strLength; ++i) { + if (types[i] === "EN") { + // do before + for (let j = i - 1; j >= 0; --j) { + if (types[j] !== "ET") { + break; + } + types[j] = "EN"; + } + // do after + for (let j = i + 1; j < strLength; ++j) { + if (types[j] !== "ET") { + break; + } + types[j] = "EN"; + } + } + } + + /* + W6. Otherwise, separators and terminators change to Other Neutral: + */ + for (i = 0; i < strLength; ++i) { + t = types[i]; + if (t === "WS" || t === "ES" || t === "ET" || t === "CS") { + types[i] = "ON"; + } + } + + /* + W7. Search backwards from each instance of a European number until the + first strong type (R, L, or sor) is found. If an L is found, then change + the type of the European number to L. + */ + lastType = sor; + for (i = 0; i < strLength; ++i) { + t = types[i]; + if (t === "EN") { + types[i] = lastType === "L" ? "L" : "EN"; + } else if (t === "R" || t === "L") { + lastType = t; + } + } + + /* + N1. A sequence of neutrals takes the direction of the surrounding strong + text if the text on both sides has the same direction. European and Arabic + numbers are treated as though they were R. Start-of-level-run (sor) and + end-of-level-run (eor) are used at level run boundaries. + */ + for (i = 0; i < strLength; ++i) { + if (types[i] === "ON") { + const end = findUnequal(types, i + 1, "ON"); + let before = sor; + if (i > 0) { + before = types[i - 1]; + } + + let after = eor; + if (end + 1 < strLength) { + after = types[end + 1]; + } + if (before !== "L") { + before = "R"; + } + if (after !== "L") { + after = "R"; + } + if (before === after) { + setValues(types, i, end, before); + } + i = end - 1; // reset to end (-1 so next iteration is ok) + } + } + + /* + N2. Any remaining neutrals take the embedding direction. + */ + for (i = 0; i < strLength; ++i) { + if (types[i] === "ON") { + types[i] = e; + } + } + + /* + I1. For all characters with an even (left-to-right) embedding direction, + those of type R go up one level and those of type AN or EN go up two + levels. + I2. For all characters with an odd (right-to-left) embedding direction, + those of type L, EN or AN go up one level. + */ + for (i = 0; i < strLength; ++i) { + t = types[i]; + if (isEven(levels[i])) { + if (t === "R") { + levels[i] += 1; + } else if (t === "AN" || t === "EN") { + levels[i] += 2; + } + } else if (/* isOdd && */ t === "L" || t === "AN" || t === "EN") { + levels[i] += 1; + } + } + + /* + L1. On each line, reset the embedding level of the following characters to + the paragraph embedding level: + + segment separators, + paragraph separators, + any sequence of whitespace characters preceding a segment separator or + paragraph separator, and any sequence of white space characters at the end + of the line. + */ + + // don't bother as text is only single line + + /* + L2. From the highest level found in the text to the lowest odd level on + each line, reverse any contiguous sequence of characters that are at that + level or higher. + */ + + // find highest level & lowest odd level + let highestLevel = -1; + let lowestOddLevel = 99; + let level; + for (i = 0, ii = levels.length; i < ii; ++i) { + level = levels[i]; + if (highestLevel < level) { + highestLevel = level; + } + if (lowestOddLevel > level && isOdd(level)) { + lowestOddLevel = level; + } + } + + // now reverse between those limits + for (level = highestLevel; level >= lowestOddLevel; --level) { + // find segments to reverse + let start = -1; + for (i = 0, ii = levels.length; i < ii; ++i) { + if (levels[i] < level) { + if (start >= 0) { + reverseValues(chars, start, i); + start = -1; + } + } else if (start < 0) { + start = i; + } + } + if (start >= 0) { + reverseValues(chars, start, levels.length); + } + } + + /* + L3. Combining marks applied to a right-to-left base character will at this + point precede their base character. If the rendering engine expects them to + follow the base characters in the final display process, then the ordering + of the marks and the base character must be reversed. + */ + + // don't bother for now + + /* + L4. A character that possesses the mirrored property as specified by + Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved + directionality of that character is R. + */ + + // don't mirror as characters are already mirrored in the pdf + + // Finally, return string + for (i = 0, ii = chars.length; i < ii; ++i) { + const ch = chars[i]; + if (ch === "<" || ch === ">") { + chars[i] = ""; + } + } + return createBidiText(chars.join(""), isLTR); +} + +export { bidi }; diff --git a/frontend/public/pdf.js/src/core/binary_cmap.js b/frontend/public/pdf.js/src/core/binary_cmap.js new file mode 100644 index 0000000..b34f00f --- /dev/null +++ b/frontend/public/pdf.js/src/core/binary_cmap.js @@ -0,0 +1,326 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { FormatError } from "../shared/util.js"; + +function hexToInt(a, size) { + let n = 0; + for (let i = 0; i <= size; i++) { + n = (n << 8) | a[i]; + } + return n >>> 0; +} + +function hexToStr(a, size) { + // This code is hot. Special-case some common values to avoid creating an + // object with subarray(). + if (size === 1) { + return String.fromCharCode(a[0], a[1]); + } + if (size === 3) { + return String.fromCharCode(a[0], a[1], a[2], a[3]); + } + return String.fromCharCode(...a.subarray(0, size + 1)); +} + +function addHex(a, b, size) { + let c = 0; + for (let i = size; i >= 0; i--) { + c += a[i] + b[i]; + a[i] = c & 255; + c >>= 8; + } +} + +function incHex(a, size) { + let c = 1; + for (let i = size; i >= 0 && c > 0; i--) { + c += a[i]; + a[i] = c & 255; + c >>= 8; + } +} + +const MAX_NUM_SIZE = 16; +const MAX_ENCODED_NUM_SIZE = 19; // ceil(MAX_NUM_SIZE * 7 / 8) + +class BinaryCMapStream { + constructor(data) { + this.buffer = data; + this.pos = 0; + this.end = data.length; + this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE); + } + + readByte() { + if (this.pos >= this.end) { + return -1; + } + return this.buffer[this.pos++]; + } + + readNumber() { + let n = 0; + let last; + do { + const b = this.readByte(); + if (b < 0) { + throw new FormatError("unexpected EOF in bcmap"); + } + last = !(b & 0x80); + n = (n << 7) | (b & 0x7f); + } while (!last); + return n; + } + + readSigned() { + const n = this.readNumber(); + return n & 1 ? ~(n >>> 1) : n >>> 1; + } + + readHex(num, size) { + num.set(this.buffer.subarray(this.pos, this.pos + size + 1)); + this.pos += size + 1; + } + + readHexNumber(num, size) { + let last; + const stack = this.tmpBuf; + let sp = 0; + do { + const b = this.readByte(); + if (b < 0) { + throw new FormatError("unexpected EOF in bcmap"); + } + last = !(b & 0x80); + stack[sp++] = b & 0x7f; + } while (!last); + let i = size, + buffer = 0, + bufferSize = 0; + while (i >= 0) { + while (bufferSize < 8 && stack.length > 0) { + buffer |= stack[--sp] << bufferSize; + bufferSize += 7; + } + num[i] = buffer & 255; + i--; + buffer >>= 8; + bufferSize -= 8; + } + } + + readHexSigned(num, size) { + this.readHexNumber(num, size); + const sign = num[size] & 1 ? 255 : 0; + let c = 0; + for (let i = 0; i <= size; i++) { + c = ((c & 1) << 8) | num[i]; + num[i] = (c >> 1) ^ sign; + } + } + + readString() { + const len = this.readNumber(), + buf = new Array(len); + for (let i = 0; i < len; i++) { + buf[i] = this.readNumber(); + } + return String.fromCharCode(...buf); + } +} + +class BinaryCMapReader { + async process(data, cMap, extend) { + const stream = new BinaryCMapStream(data); + const header = stream.readByte(); + cMap.vertical = !!(header & 1); + + let useCMap = null; + const start = new Uint8Array(MAX_NUM_SIZE); + const end = new Uint8Array(MAX_NUM_SIZE); + const char = new Uint8Array(MAX_NUM_SIZE); + const charCode = new Uint8Array(MAX_NUM_SIZE); + const tmp = new Uint8Array(MAX_NUM_SIZE); + let code; + + let b; + while ((b = stream.readByte()) >= 0) { + const type = b >> 5; + if (type === 7) { + // metadata, e.g. comment or usecmap + switch (b & 0x1f) { + case 0: + stream.readString(); // skipping comment + break; + case 1: + useCMap = stream.readString(); + break; + } + continue; + } + const sequence = !!(b & 0x10); + const dataSize = b & 15; + + if (dataSize + 1 > MAX_NUM_SIZE) { + throw new Error("BinaryCMapReader.process: Invalid dataSize."); + } + + const ucs2DataSize = 1; + const subitemsCount = stream.readNumber(); + switch (type) { + case 0: // codespacerange + stream.readHex(start, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + cMap.addCodespaceRange( + dataSize + 1, + hexToInt(start, dataSize), + hexToInt(end, dataSize) + ); + for (let i = 1; i < subitemsCount; i++) { + incHex(end, dataSize); + stream.readHexNumber(start, dataSize); + addHex(start, end, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + cMap.addCodespaceRange( + dataSize + 1, + hexToInt(start, dataSize), + hexToInt(end, dataSize) + ); + } + break; + case 1: // notdefrange + stream.readHex(start, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + stream.readNumber(); // code + // undefined range, skipping + for (let i = 1; i < subitemsCount; i++) { + incHex(end, dataSize); + stream.readHexNumber(start, dataSize); + addHex(start, end, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + stream.readNumber(); // code + // nop + } + break; + case 2: // cidchar + stream.readHex(char, dataSize); + code = stream.readNumber(); + cMap.mapOne(hexToInt(char, dataSize), code); + for (let i = 1; i < subitemsCount; i++) { + incHex(char, dataSize); + if (!sequence) { + stream.readHexNumber(tmp, dataSize); + addHex(char, tmp, dataSize); + } + code = stream.readSigned() + (code + 1); + cMap.mapOne(hexToInt(char, dataSize), code); + } + break; + case 3: // cidrange + stream.readHex(start, dataSize); + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + code = stream.readNumber(); + cMap.mapCidRange( + hexToInt(start, dataSize), + hexToInt(end, dataSize), + code + ); + for (let i = 1; i < subitemsCount; i++) { + incHex(end, dataSize); + if (!sequence) { + stream.readHexNumber(start, dataSize); + addHex(start, end, dataSize); + } else { + start.set(end); + } + stream.readHexNumber(end, dataSize); + addHex(end, start, dataSize); + code = stream.readNumber(); + cMap.mapCidRange( + hexToInt(start, dataSize), + hexToInt(end, dataSize), + code + ); + } + break; + case 4: // bfchar + stream.readHex(char, ucs2DataSize); + stream.readHex(charCode, dataSize); + cMap.mapOne( + hexToInt(char, ucs2DataSize), + hexToStr(charCode, dataSize) + ); + for (let i = 1; i < subitemsCount; i++) { + incHex(char, ucs2DataSize); + if (!sequence) { + stream.readHexNumber(tmp, ucs2DataSize); + addHex(char, tmp, ucs2DataSize); + } + incHex(charCode, dataSize); + stream.readHexSigned(tmp, dataSize); + addHex(charCode, tmp, dataSize); + cMap.mapOne( + hexToInt(char, ucs2DataSize), + hexToStr(charCode, dataSize) + ); + } + break; + case 5: // bfrange + stream.readHex(start, ucs2DataSize); + stream.readHexNumber(end, ucs2DataSize); + addHex(end, start, ucs2DataSize); + stream.readHex(charCode, dataSize); + cMap.mapBfRange( + hexToInt(start, ucs2DataSize), + hexToInt(end, ucs2DataSize), + hexToStr(charCode, dataSize) + ); + for (let i = 1; i < subitemsCount; i++) { + incHex(end, ucs2DataSize); + if (!sequence) { + stream.readHexNumber(start, ucs2DataSize); + addHex(start, end, ucs2DataSize); + } else { + start.set(end); + } + stream.readHexNumber(end, ucs2DataSize); + addHex(end, start, ucs2DataSize); + stream.readHex(charCode, dataSize); + cMap.mapBfRange( + hexToInt(start, ucs2DataSize), + hexToInt(end, ucs2DataSize), + hexToStr(charCode, dataSize) + ); + } + break; + default: + throw new Error(`BinaryCMapReader.process - unknown type: ${type}`); + } + } + + if (useCMap) { + return extend(useCMap); + } + return cMap; + } +} + +export { BinaryCMapReader }; diff --git a/frontend/public/pdf.js/src/core/calibri_factors.js b/frontend/public/pdf.js/src/core/calibri_factors.js new file mode 100644 index 0000000..c1140c4 --- /dev/null +++ b/frontend/public/pdf.js/src/core/calibri_factors.js @@ -0,0 +1,309 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +// Factors to rescale LiberationSans-Bold.ttf to have the same +// metrics as calibrib.ttf. +const CalibriBoldFactors = [ + 1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, + 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.54657, + 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, + 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, + 0.92138, 0.83908, 0.7762, 0.73293, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, + 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.9121, 0.86943, 0.79795, + 0.88198, 0.77958, 0.70864, 0.81055, 0.90399, 0.88653, 0.96017, 0.82577, + 0.77892, 0.78257, 0.97507, 1.54657, 0.97507, 0.85284, 0.89552, 0.90176, + 0.88762, 0.8785, 0.75241, 0.8785, 0.90518, 0.95015, 0.77618, 0.8785, 0.88401, + 0.91916, 0.86304, 0.88401, 0.91488, 0.8785, 0.8801, 0.8785, 0.8785, 0.91343, + 0.7173, 1.04106, 0.8785, 0.85075, 0.95794, 0.82616, 0.85162, 0.79492, 0.88331, + 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, + 1.7801, 0.89552, 1.24487, 1.13254, 1.12401, 0.96839, 0.85284, 0.68787, + 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, + 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, + 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.73293, 0.73133, + 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, + 0.9121, 0.86943, 0.86943, 0.86943, 0.86943, 0.86943, 0.85284, 0.87508, + 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.88762, + 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.8715, 0.75241, 0.90518, + 0.90518, 0.90518, 0.90518, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.8785, + 0.8801, 0.8801, 0.8801, 0.8801, 0.8801, 0.90747, 0.89049, 0.8785, 0.8785, + 0.8785, 0.8785, 0.85162, 0.8785, 0.85162, 0.83908, 0.88762, 0.83908, 0.88762, + 0.83908, 0.88762, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, + 0.73293, 0.75241, 0.87289, 0.83016, 0.88506, 0.93125, 0.73133, 0.90518, + 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, + 0.81921, 0.77618, 0.81921, 0.77618, 0.81921, 0.77618, 1, 1, 0.87356, 0.8785, + 0.91075, 0.89608, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, + 0.95958, 0.88401, 0.95958, 0.88401, 0.76229, 0.90167, 0.59526, 0.91916, 1, 1, + 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.79468, 0.91926, 0.88175, 0.70823, + 0.94903, 0.9121, 0.8785, 1, 1, 0.9121, 0.8785, 0.87802, 0.88656, 0.8785, + 0.86943, 0.8801, 0.86943, 0.8801, 0.86943, 0.8801, 0.87402, 0.89291, 0.77958, + 0.91343, 1, 1, 0.77958, 0.91343, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, + 0.7173, 0.70864, 0.7173, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, + 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, + 0.90399, 0.8785, 0.96017, 0.95794, 0.77892, 0.85162, 0.77892, 0.78257, + 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, + 0.88762, 0.77539, 0.8715, 0.87508, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, + 1.20528, 1, 1.15543, 0.70674, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, + 1.06303, 0.83908, 0.80352, 0.57184, 0.6965, 0.56289, 0.82001, 0.56029, + 0.81235, 1.02988, 0.83908, 0.7762, 0.68156, 0.80367, 0.73133, 0.78257, + 0.87356, 0.86943, 0.95958, 0.75727, 0.89019, 1.04924, 0.9121, 0.7648, 0.86943, + 0.87356, 0.79795, 0.78275, 0.81055, 0.77892, 0.9762, 0.82577, 0.99819, + 0.84896, 0.95958, 0.77892, 0.96108, 1.01407, 0.89049, 1.02988, 0.94211, + 0.96108, 0.8936, 0.84021, 0.87842, 0.96399, 0.79109, 0.89049, 1.00813, + 1.02988, 0.86077, 0.87445, 0.92099, 0.84723, 0.86513, 0.8801, 0.75638, + 0.85714, 0.78216, 0.79586, 0.87965, 0.94211, 0.97747, 0.78287, 0.97926, + 0.84971, 1.02988, 0.94211, 0.8801, 0.94211, 0.84971, 0.73133, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0.90264, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0.90518, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90548, 1, 1, 1, 1, 1, 1, + 0.96017, 0.95794, 0.96017, 0.95794, 0.96017, 0.95794, 0.77892, 0.85162, 1, 1, + 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.92794, 0.87012, + 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71143, 1.06152, 1, 1, 1.03372, + 1.03372, 0.97171, 1.4956, 2.2807, 0.93835, 0.83406, 0.91133, 0.84107, 0.91133, + 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90527, + 1.81055, 0.90527, 1.81055, 1.31006, 1.53711, 0.94434, 1.08696, 1, 0.95018, + 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, + 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, + 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, + 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const CalibriBoldMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; + +// Factors to rescale LiberationSans-BoldItalic.ttf to have the same +// metrics as calibriz.ttf. +const CalibriBoldItalicFactors = [ + 1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, + 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.56239, + 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, + 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, + 0.92138, 0.83908, 0.7762, 0.71805, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, + 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.90872, 0.85938, 0.79795, + 0.87068, 0.77958, 0.69766, 0.81055, 0.90399, 0.88653, 0.96068, 0.82577, + 0.77892, 0.78257, 0.97507, 1.529, 0.97507, 0.85284, 0.89552, 0.90176, 0.94908, + 0.86411, 0.74012, 0.86411, 0.88323, 0.95015, 0.86411, 0.86331, 0.88401, + 0.91916, 0.86304, 0.88401, 0.9039, 0.86331, 0.86331, 0.86411, 0.86411, + 0.90464, 0.70852, 1.04106, 0.86331, 0.84372, 0.95794, 0.82616, 0.84548, + 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, + 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.19129, 0.96839, + 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, + 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, + 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, + 0.71805, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, + 0.95958, 0.88506, 0.90872, 0.85938, 0.85938, 0.85938, 0.85938, 0.85938, + 0.85284, 0.87068, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, + 0.90807, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.85887, + 0.74012, 0.88323, 0.88323, 0.88323, 0.88323, 0.88401, 0.88401, 0.88401, + 0.88401, 0.8785, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, + 0.90747, 0.89049, 0.86331, 0.86331, 0.86331, 0.86331, 0.84548, 0.86411, + 0.84548, 0.83908, 0.94908, 0.83908, 0.94908, 0.83908, 0.94908, 0.71805, + 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.87289, + 0.79538, 0.88506, 0.92726, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, + 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.81921, 0.86411, 0.81921, + 0.86411, 0.81921, 0.86411, 1, 1, 0.87356, 0.86331, 0.91075, 0.8777, 0.95958, + 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, + 0.88401, 0.76467, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, + 1, 1, 0.70424, 0.77312, 0.91926, 0.88175, 0.70823, 0.94903, 0.90872, 0.86331, + 1, 1, 0.90872, 0.86331, 0.86906, 0.88116, 0.86331, 0.85938, 0.86331, 0.85938, + 0.86331, 0.85938, 0.86331, 0.87402, 0.86549, 0.77958, 0.90464, 1, 1, 0.77958, + 0.90464, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, + 0.70852, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.86331, 0.90399, + 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, + 0.86331, 0.96068, 0.95794, 0.77892, 0.84548, 0.77892, 0.78257, 0.79492, + 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.94908, + 0.77539, 0.85887, 0.87068, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, + 1, 1.15543, 0.70088, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.48387, + 0.83908, 0.80352, 0.57118, 0.6965, 0.56347, 0.79179, 0.55853, 0.80346, + 1.02988, 0.83908, 0.7762, 0.67174, 0.86036, 0.73133, 0.78257, 0.87356, + 0.86441, 0.95958, 0.75727, 0.89019, 1.04924, 0.90872, 0.74889, 0.85938, + 0.87891, 0.79795, 0.7957, 0.81055, 0.77892, 0.97447, 0.82577, 0.97466, + 0.87179, 0.95958, 0.77892, 0.94252, 0.95612, 0.8753, 1.02988, 0.92733, + 0.94252, 0.87411, 0.84021, 0.8728, 0.95612, 0.74081, 0.8753, 1.02189, 1.02988, + 0.84814, 0.87445, 0.91822, 0.84723, 0.85668, 0.86331, 0.81344, 0.87581, + 0.76422, 0.82046, 0.96057, 0.92733, 0.99375, 0.78022, 0.95452, 0.86015, + 1.02988, 0.92733, 0.86331, 0.92733, 0.86015, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0.90631, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88323, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85174, 1, 1, 1, 1, 1, 1, 0.96068, 0.95794, + 0.96068, 0.95794, 0.96068, 0.95794, 0.77892, 0.84548, 1, 1, 0.89552, 0.90527, + 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.89807, 0.87012, 0.87012, 0.87012, + 0.89552, 0.89552, 1.42259, 0.71094, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, + 1.4956, 2.2807, 0.92972, 0.83406, 0.91133, 0.83326, 0.91133, 1, 1, 1, 0.72021, + 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90616, 1.81055, 0.90527, + 1.81055, 1.3107, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, + 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, + 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, + 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +]; +const CalibriBoldItalicMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; + +// Factors to rescale LiberationSans-Italic.ttf to have the same +// metrics as calibrii.ttf. +const CalibriItalicFactors = [ + 1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, + 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39543, 0.91133, + 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, + 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, + 0.8675, 0.81552, 0.72346, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, + 0.6377, 0.77892, 0.75593, 1.02638, 0.89249, 0.84118, 0.77452, 0.85374, + 0.75186, 0.67789, 0.79776, 0.88844, 0.85066, 0.94309, 0.77818, 0.7306, + 0.76659, 1.10369, 1.38313, 1.10369, 1.06139, 0.89552, 0.8739, 0.9245, 0.9245, + 0.83203, 0.9245, 0.85865, 1.09842, 0.9245, 0.9245, 1.03297, 1.07692, 0.90918, + 1.03297, 0.94959, 0.9245, 0.92274, 0.9245, 0.9245, 1.02933, 0.77832, 1.20562, + 0.9245, 0.8916, 0.98986, 0.86621, 0.89453, 0.79004, 0.94152, 1.77256, 0.94152, + 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, + 1.17889, 1.13254, 1.16359, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, + 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, + 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, + 0.8675, 0.8675, 0.76318, 0.72346, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, + 0.90685, 0.90685, 0.90685, 0.86477, 0.89249, 0.84118, 0.84118, 0.84118, + 0.84118, 0.84118, 0.85284, 0.84557, 0.88844, 0.88844, 0.88844, 0.88844, + 0.7306, 0.77452, 0.86331, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, + 0.84843, 0.83203, 0.85865, 0.85865, 0.85865, 0.85865, 0.82601, 0.82601, + 0.82601, 0.82601, 0.94469, 0.9245, 0.92274, 0.92274, 0.92274, 0.92274, + 0.92274, 0.90747, 0.86651, 0.9245, 0.9245, 0.9245, 0.9245, 0.89453, 0.9245, + 0.89453, 0.8675, 0.9245, 0.8675, 0.9245, 0.8675, 0.9245, 0.72346, 0.83203, + 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.85193, 0.8875, + 0.86477, 0.99034, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, + 0.73206, 0.85865, 0.73206, 0.85865, 0.81105, 0.9245, 0.81105, 0.9245, 0.81105, + 0.9245, 1, 1, 0.86275, 0.9245, 0.90872, 0.93591, 0.90685, 0.82601, 0.90685, + 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77896, + 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, + 0.9375, 0.98156, 0.93407, 0.77261, 1.11429, 0.89249, 0.9245, 1, 1, 0.89249, + 0.9245, 0.92534, 0.86698, 0.9245, 0.84118, 0.92274, 0.84118, 0.92274, 0.84118, + 0.92274, 0.8667, 0.86291, 0.75186, 1.02933, 1, 1, 0.75186, 1.02933, 0.67789, + 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 1, 1, 0.79776, + 0.97655, 0.79776, 1.23023, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, + 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.94309, 0.98986, 0.7306, + 0.89453, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, + 1.09231, 0.54873, 0.8675, 0.9245, 0.76318, 0.84843, 0.84557, 0.86651, 1, 1, + 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67009, 0.96334, 0.93695, + 1.35191, 1.40909, 0.95161, 1.48387, 0.8675, 0.90861, 0.6192, 0.7363, 0.64824, + 0.82411, 0.56321, 0.85696, 1.23516, 0.8675, 0.81552, 0.7286, 0.84134, 0.73206, + 0.76659, 0.86275, 0.84369, 0.90685, 0.77892, 0.85871, 1.02638, 0.89249, + 0.75828, 0.84118, 0.85984, 0.77452, 0.76466, 0.79776, 0.7306, 0.90782, + 0.77818, 0.903, 0.87291, 0.90685, 0.7306, 0.99058, 1.03667, 0.94635, 1.23516, + 0.9849, 0.99058, 0.92393, 0.8916, 0.942, 1.03667, 0.75026, 0.94635, 1.0297, + 1.23516, 0.90918, 0.94048, 0.98217, 0.89746, 0.84153, 0.92274, 0.82507, + 0.88832, 0.84438, 0.88178, 1.03525, 0.9849, 1.00225, 0.78086, 0.97248, + 0.89404, 1.23516, 0.9849, 0.92274, 0.9849, 0.89404, 0.73206, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0.89693, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85865, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90933, 1, 1, 1, 1, 1, 1, 0.94309, + 0.98986, 0.94309, 0.98986, 0.94309, 0.98986, 0.7306, 0.89453, 1, 1, 0.89552, + 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, + 1.2566, 0.89552, 0.89552, 1.42259, 0.68994, 1.03809, 1, 1, 1.0176, 1.0176, + 1.11523, 1.4956, 2.01462, 0.97858, 0.82616, 0.91133, 0.83437, 0.91133, 1, 1, + 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90572, 1.81055, + 0.90749, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, + 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, + 0.85284, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, + 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, + 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const CalibriItalicMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; + +// Factors to rescale LiberationSans-Regular.ttf to have the same +// metrics as calibri.ttf. +const CalibriRegularFactors = [ + 1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, + 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39016, 0.91133, + 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, + 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, + 0.8675, 0.81552, 0.73834, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, + 0.6377, 0.77892, 0.75593, 1.02638, 0.89385, 0.85122, 0.77452, 0.86503, + 0.75186, 0.68887, 0.79776, 0.88844, 0.85066, 0.94258, 0.77818, 0.7306, + 0.76659, 1.10369, 1.39016, 1.10369, 1.06139, 0.89552, 0.8739, 0.86128, + 0.94469, 0.8457, 0.94469, 0.89464, 1.09842, 0.84636, 0.94469, 1.03297, + 1.07692, 0.90918, 1.03297, 0.95897, 0.94469, 0.9482, 0.94469, 0.94469, + 1.04692, 0.78223, 1.20562, 0.94469, 0.90332, 0.98986, 0.86621, 0.90527, + 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, + 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.08707, 0.92098, + 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, + 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, + 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.73834, + 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, + 0.86477, 0.89385, 0.85122, 0.85122, 0.85122, 0.85122, 0.85122, 0.85284, + 0.85311, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, + 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.8693, 0.8457, 0.89464, + 0.89464, 0.89464, 0.89464, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, + 0.94469, 0.9482, 0.9482, 0.9482, 0.9482, 0.9482, 0.90747, 0.86651, 0.94469, + 0.94469, 0.94469, 0.94469, 0.90527, 0.94469, 0.90527, 0.8675, 0.86128, 0.8675, + 0.86128, 0.8675, 0.86128, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, + 0.73834, 0.8457, 0.85193, 0.92454, 0.86477, 0.9921, 0.73206, 0.89464, 0.73206, + 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.81105, + 0.84636, 0.81105, 0.84636, 0.81105, 0.84636, 1, 1, 0.86275, 0.94469, 0.90872, + 0.95786, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, + 1.03297, 0.90685, 0.82601, 0.77741, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, + 0.75593, 1.03297, 1, 1, 0.76032, 0.90452, 0.98156, 1.11842, 0.77261, 1.11429, + 0.89385, 0.94469, 1, 1, 0.89385, 0.94469, 0.95877, 0.86901, 0.94469, 0.85122, + 0.9482, 0.85122, 0.9482, 0.85122, 0.9482, 0.8667, 0.90016, 0.75186, 1.04692, + 1, 1, 0.75186, 1.04692, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, + 0.68887, 0.78223, 1, 1, 0.79776, 0.92188, 0.79776, 1.23023, 0.88844, 0.94469, + 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, + 0.88844, 0.94469, 0.94258, 0.98986, 0.7306, 0.90527, 0.7306, 0.76659, 0.79004, + 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.86128, + 0.76318, 0.8693, 0.85311, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, + 1, 1.1437, 0.67742, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, + 0.86686, 0.90861, 0.62267, 0.74359, 0.65649, 0.85498, 0.56963, 0.88254, + 1.23516, 0.8675, 0.81552, 0.75443, 0.84503, 0.73206, 0.76659, 0.86275, + 0.85122, 0.90685, 0.77892, 0.85746, 1.02638, 0.89385, 0.75657, 0.85122, + 0.86275, 0.77452, 0.74171, 0.79776, 0.7306, 0.95165, 0.77818, 0.89772, + 0.88831, 0.90685, 0.7306, 0.98142, 1.02191, 0.96576, 1.23516, 0.99018, + 0.98142, 0.9236, 0.89258, 0.94035, 1.02191, 0.78848, 0.96576, 0.9561, 1.23516, + 0.90918, 0.92578, 0.95424, 0.89746, 0.83969, 0.9482, 0.80113, 0.89442, + 0.85208, 0.86155, 0.98022, 0.99018, 1.00452, 0.81209, 0.99247, 0.89181, + 1.23516, 0.99018, 0.9482, 0.99018, 0.89181, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0.88844, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89464, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96766, 1, 1, 1, 1, 1, 1, 0.94258, 0.98986, + 0.94258, 0.98986, 0.94258, 0.98986, 0.7306, 0.90527, 1, 1, 0.89552, 0.90527, + 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, + 0.89552, 0.89552, 1.42259, 0.69043, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, + 1.4956, 2.01462, 0.99331, 0.82616, 0.91133, 0.84286, 0.91133, 1, 1, 1, + 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90527, 1.81055, + 0.90527, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, + 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, + 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, + 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, + 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, + 1.07185, 0.99413, 0.96334, 1.08065, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const CalibriRegularMetrics = { lineHeight: 1.2207, lineGap: 0.2207 }; + +export { + CalibriBoldFactors, + CalibriBoldItalicFactors, + CalibriBoldItalicMetrics, + CalibriBoldMetrics, + CalibriItalicFactors, + CalibriItalicMetrics, + CalibriRegularFactors, + CalibriRegularMetrics, +}; diff --git a/frontend/public/pdf.js/src/core/catalog.js b/frontend/public/pdf.js/src/core/catalog.js new file mode 100644 index 0000000..1274f6f --- /dev/null +++ b/frontend/public/pdf.js/src/core/catalog.js @@ -0,0 +1,1798 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + collectActions, + isNumberArray, + MissingDataException, + PDF_VERSION_REGEXP, + recoverJsURL, + toRomanNumerals, + XRefEntryException, +} from "./core_utils.js"; +import { + createValidAbsoluteUrl, + DocumentActionEventType, + FormatError, + info, + objectSize, + PermissionFlag, + shadow, + stringToPDFString, + stringToUTF8String, + warn, +} from "../shared/util.js"; +import { + Dict, + isDict, + isName, + isRefsEqual, + Name, + Ref, + RefSet, + RefSetCache, +} from "./primitives.js"; +import { NameTree, NumberTree } from "./name_number_tree.js"; +import { BaseStream } from "./base_stream.js"; +import { clearGlobalCaches } from "./cleanup_helper.js"; +import { ColorSpace } from "./colorspace.js"; +import { FileSpec } from "./file_spec.js"; +import { GlobalImageCache } from "./image_utils.js"; +import { MetadataParser } from "./metadata_parser.js"; +import { StructTreeRoot } from "./struct_tree.js"; + +function isValidExplicitDest(dest) { + if (!Array.isArray(dest) || dest.length < 2) { + return false; + } + const [page, zoom, ...args] = dest; + if (!(page instanceof Ref) && !Number.isInteger(page)) { + return false; + } + if (!(zoom instanceof Name)) { + return false; + } + const argsLen = args.length; + let allowNull = true; + switch (zoom.name) { + case "XYZ": + if (argsLen < 2 || argsLen > 3) { + return false; + } + break; + case "Fit": + case "FitB": + return argsLen === 0; + case "FitH": + case "FitBH": + case "FitV": + case "FitBV": + if (argsLen > 1) { + return false; + } + break; + case "FitR": + if (argsLen !== 4) { + return false; + } + allowNull = false; + break; + default: + return false; + } + for (const arg of args) { + if (!(typeof arg === "number" || (allowNull && arg === null))) { + return false; + } + } + return true; +} + +function fetchDest(dest) { + if (dest instanceof Dict) { + dest = dest.get("D"); + } + return isValidExplicitDest(dest) ? dest : null; +} + +function fetchRemoteDest(action) { + let dest = action.get("D"); + if (dest) { + if (dest instanceof Name) { + dest = dest.name; + } + if (typeof dest === "string") { + return stringToPDFString(dest); + } else if (isValidExplicitDest(dest)) { + return JSON.stringify(dest); + } + } + return null; +} + +class Catalog { + constructor(pdfManager, xref) { + this.pdfManager = pdfManager; + this.xref = xref; + + this._catDict = xref.getCatalogObj(); + if (!(this._catDict instanceof Dict)) { + throw new FormatError("Catalog object is not a dictionary."); + } + // Given that `XRef.parse` will both fetch *and* validate the /Pages-entry, + // the following call must always succeed here: + this.toplevelPagesDict; // eslint-disable-line no-unused-expressions + + this._actualNumPages = null; + + this.fontCache = new RefSetCache(); + this.builtInCMapCache = new Map(); + this.standardFontDataCache = new Map(); + this.globalImageCache = new GlobalImageCache(); + this.pageKidsCountCache = new RefSetCache(); + this.pageIndexCache = new RefSetCache(); + this.pageDictCache = new RefSetCache(); + this.nonBlendModesSet = new RefSet(); + this.systemFontCache = new Map(); + } + + cloneDict() { + return this._catDict.clone(); + } + + get version() { + const version = this._catDict.get("Version"); + if (version instanceof Name) { + if (PDF_VERSION_REGEXP.test(version.name)) { + return shadow(this, "version", version.name); + } + warn(`Invalid PDF catalog version: ${version.name}`); + } + return shadow(this, "version", null); + } + + get lang() { + const lang = this._catDict.get("Lang"); + return shadow( + this, + "lang", + lang && typeof lang === "string" ? stringToPDFString(lang) : null + ); + } + + /** + * @type {boolean} `true` for pure XFA documents, + * `false` for XFA Foreground documents. + */ + get needsRendering() { + const needsRendering = this._catDict.get("NeedsRendering"); + return shadow( + this, + "needsRendering", + typeof needsRendering === "boolean" ? needsRendering : false + ); + } + + get collection() { + let collection = null; + try { + const obj = this._catDict.get("Collection"); + if (obj instanceof Dict && obj.size > 0) { + collection = obj; + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + info("Cannot fetch Collection entry; assuming no collection is present."); + } + return shadow(this, "collection", collection); + } + + get acroForm() { + let acroForm = null; + try { + const obj = this._catDict.get("AcroForm"); + if (obj instanceof Dict && obj.size > 0) { + acroForm = obj; + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + info("Cannot fetch AcroForm entry; assuming no forms are present."); + } + return shadow(this, "acroForm", acroForm); + } + + get acroFormRef() { + const value = this._catDict.getRaw("AcroForm"); + return shadow(this, "acroFormRef", value instanceof Ref ? value : null); + } + + get metadata() { + const streamRef = this._catDict.getRaw("Metadata"); + if (!(streamRef instanceof Ref)) { + return shadow(this, "metadata", null); + } + + let metadata = null; + try { + const stream = this.xref.fetch( + streamRef, + /* suppressEncryption = */ !this.xref.encrypt?.encryptMetadata + ); + + if (stream instanceof BaseStream && stream.dict instanceof Dict) { + const type = stream.dict.get("Type"); + const subtype = stream.dict.get("Subtype"); + + if (isName(type, "Metadata") && isName(subtype, "XML")) { + // XXX: This should examine the charset the XML document defines, + // however since there are currently no real means to decode arbitrary + // charsets, let's just hope that the author of the PDF was reasonable + // enough to stick with the XML default charset, which is UTF-8. + const data = stringToUTF8String(stream.getString()); + if (data) { + metadata = new MetadataParser(data).serializable; + } + } + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + info(`Skipping invalid Metadata: "${ex}".`); + } + return shadow(this, "metadata", metadata); + } + + get markInfo() { + let markInfo = null; + try { + markInfo = this._readMarkInfo(); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn("Unable to read mark info."); + } + return shadow(this, "markInfo", markInfo); + } + + /** + * @private + */ + _readMarkInfo() { + const obj = this._catDict.get("MarkInfo"); + if (!(obj instanceof Dict)) { + return null; + } + + const markInfo = { + Marked: false, + UserProperties: false, + Suspects: false, + }; + for (const key in markInfo) { + const value = obj.get(key); + if (typeof value === "boolean") { + markInfo[key] = value; + } + } + + return markInfo; + } + + get structTreeRoot() { + let structTree = null; + try { + structTree = this._readStructTreeRoot(); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn("Unable read to structTreeRoot info."); + } + return shadow(this, "structTreeRoot", structTree); + } + + /** + * @private + */ + _readStructTreeRoot() { + const rawObj = this._catDict.getRaw("StructTreeRoot"); + const obj = this.xref.fetchIfRef(rawObj); + if (!(obj instanceof Dict)) { + return null; + } + + const root = new StructTreeRoot(obj, rawObj); + root.init(); + return root; + } + + get toplevelPagesDict() { + const pagesObj = this._catDict.get("Pages"); + if (!(pagesObj instanceof Dict)) { + throw new FormatError("Invalid top-level pages dictionary."); + } + return shadow(this, "toplevelPagesDict", pagesObj); + } + + get documentOutline() { + let obj = null; + try { + obj = this._readDocumentOutline(); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn("Unable to read document outline."); + } + return shadow(this, "documentOutline", obj); + } + + /** + * @private + */ + _readDocumentOutline() { + let obj = this._catDict.get("Outlines"); + if (!(obj instanceof Dict)) { + return null; + } + obj = obj.getRaw("First"); + if (!(obj instanceof Ref)) { + return null; + } + + const root = { items: [] }; + const queue = [{ obj, parent: root }]; + // To avoid recursion, keep track of the already processed items. + const processed = new RefSet(); + processed.put(obj); + const xref = this.xref, + blackColor = new Uint8ClampedArray(3); + + while (queue.length > 0) { + const i = queue.shift(); + const outlineDict = xref.fetchIfRef(i.obj); + if (outlineDict === null) { + continue; + } + if (!outlineDict.has("Title")) { + warn("Invalid outline item encountered."); + } + + const data = { url: null, dest: null, action: null }; + Catalog.parseDestDictionary({ + destDict: outlineDict, + resultObj: data, + docBaseUrl: this.baseUrl, + docAttachments: this.attachments, + }); + const title = outlineDict.get("Title"); + const flags = outlineDict.get("F") || 0; + const color = outlineDict.getArray("C"); + const count = outlineDict.get("Count"); + let rgbColor = blackColor; + + // We only need to parse the color when it's valid, and non-default. + if ( + isNumberArray(color, 3) && + (color[0] !== 0 || color[1] !== 0 || color[2] !== 0) + ) { + rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); + } + + const outlineItem = { + action: data.action, + attachment: data.attachment, + dest: data.dest, + url: data.url, + unsafeUrl: data.unsafeUrl, + newWindow: data.newWindow, + setOCGState: data.setOCGState, + title: typeof title === "string" ? stringToPDFString(title) : "", + color: rgbColor, + count: Number.isInteger(count) ? count : undefined, + bold: !!(flags & 2), + italic: !!(flags & 1), + items: [], + }; + + i.parent.items.push(outlineItem); + obj = outlineDict.getRaw("First"); + if (obj instanceof Ref && !processed.has(obj)) { + queue.push({ obj, parent: outlineItem }); + processed.put(obj); + } + obj = outlineDict.getRaw("Next"); + if (obj instanceof Ref && !processed.has(obj)) { + queue.push({ obj, parent: i.parent }); + processed.put(obj); + } + } + return root.items.length > 0 ? root.items : null; + } + + get permissions() { + let permissions = null; + try { + permissions = this._readPermissions(); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn("Unable to read permissions."); + } + return shadow(this, "permissions", permissions); + } + + /** + * @private + */ + _readPermissions() { + const encrypt = this.xref.trailer.get("Encrypt"); + if (!(encrypt instanceof Dict)) { + return null; + } + + let flags = encrypt.get("P"); + if (typeof flags !== "number") { + return null; + } + + // PDF integer objects are represented internally in signed 2's complement + // form. Therefore, convert the signed decimal integer to a signed 2's + // complement binary integer so we can use regular bitwise operations on it. + flags += 2 ** 32; + + const permissions = []; + for (const key in PermissionFlag) { + const value = PermissionFlag[key]; + if (flags & value) { + permissions.push(value); + } + } + return permissions; + } + + get optionalContentConfig() { + let config = null; + try { + const properties = this._catDict.get("OCProperties"); + if (!properties) { + return shadow(this, "optionalContentConfig", null); + } + const defaultConfig = properties.get("D"); + if (!defaultConfig) { + return shadow(this, "optionalContentConfig", null); + } + const groupsData = properties.get("OCGs"); + if (!Array.isArray(groupsData)) { + return shadow(this, "optionalContentConfig", null); + } + const groupRefCache = new RefSetCache(); + // Ensure all the optional content groups are valid. + for (const groupRef of groupsData) { + if (!(groupRef instanceof Ref) || groupRefCache.has(groupRef)) { + continue; + } + groupRefCache.put(groupRef, this.#readOptionalContentGroup(groupRef)); + } + config = this.#readOptionalContentConfig(defaultConfig, groupRefCache); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(`Unable to read optional content config: ${ex}`); + } + return shadow(this, "optionalContentConfig", config); + } + + #readOptionalContentGroup(groupRef) { + const group = this.xref.fetch(groupRef); + const obj = { + id: groupRef.toString(), + name: null, + intent: null, + usage: { + print: null, + view: null, + }, + rbGroups: [], + }; + + const name = group.get("Name"); + if (typeof name === "string") { + obj.name = stringToPDFString(name); + } + + let intent = group.getArray("Intent"); + if (!Array.isArray(intent)) { + intent = [intent]; + } + if (intent.every(i => i instanceof Name)) { + obj.intent = intent.map(i => i.name); + } + + const usage = group.get("Usage"); + if (!(usage instanceof Dict)) { + return obj; + } + const usageObj = obj.usage; + + const print = usage.get("Print"); + if (print instanceof Dict) { + const printState = print.get("PrintState"); + if (printState instanceof Name) { + switch (printState.name) { + case "ON": + case "OFF": + usageObj.print = { printState: printState.name }; + } + } + } + + const view = usage.get("View"); + if (view instanceof Dict) { + const viewState = view.get("ViewState"); + if (viewState instanceof Name) { + switch (viewState.name) { + case "ON": + case "OFF": + usageObj.view = { viewState: viewState.name }; + } + } + } + + return obj; + } + + #readOptionalContentConfig(config, groupRefCache) { + function parseOnOff(refs) { + const onParsed = []; + if (Array.isArray(refs)) { + for (const value of refs) { + if (value instanceof Ref && groupRefCache.has(value)) { + onParsed.push(value.toString()); + } + } + } + return onParsed; + } + + function parseOrder(refs, nestedLevels = 0) { + if (!Array.isArray(refs)) { + return null; + } + const order = []; + + for (const value of refs) { + if (value instanceof Ref && groupRefCache.has(value)) { + parsedOrderRefs.put(value); // Handle "hidden" groups, see below. + + order.push(value.toString()); + continue; + } + // Handle nested /Order arrays (see e.g. issue 9462 and bug 1240641). + const nestedOrder = parseNestedOrder(value, nestedLevels); + if (nestedOrder) { + order.push(nestedOrder); + } + } + + if (nestedLevels > 0) { + return order; + } + const hiddenGroups = []; + for (const [groupRef] of groupRefCache.items()) { + if (parsedOrderRefs.has(groupRef)) { + continue; + } + hiddenGroups.push(groupRef.toString()); + } + if (hiddenGroups.length) { + order.push({ name: null, order: hiddenGroups }); + } + + return order; + } + + function parseNestedOrder(ref, nestedLevels) { + if (++nestedLevels > MAX_NESTED_LEVELS) { + warn("parseNestedOrder - reached MAX_NESTED_LEVELS."); + return null; + } + const value = xref.fetchIfRef(ref); + if (!Array.isArray(value)) { + return null; + } + const nestedName = xref.fetchIfRef(value[0]); + if (typeof nestedName !== "string") { + return null; + } + const nestedOrder = parseOrder(value.slice(1), nestedLevels); + if (!nestedOrder?.length) { + return null; + } + return { name: stringToPDFString(nestedName), order: nestedOrder }; + } + + function parseRBGroups(rbGroups) { + if (!Array.isArray(rbGroups)) { + return; + } + + for (const value of rbGroups) { + const rbGroup = xref.fetchIfRef(value); + if (!Array.isArray(rbGroup) || !rbGroup.length) { + continue; + } + const parsedRbGroup = new Set(); + + for (const ref of rbGroup) { + if ( + ref instanceof Ref && + groupRefCache.has(ref) && + !parsedRbGroup.has(ref.toString()) + ) { + parsedRbGroup.add(ref.toString()); + // Keep a record of which RB groups the current OCG belongs to. + groupRefCache.get(ref).rbGroups.push(parsedRbGroup); + } + } + } + } + + const xref = this.xref, + parsedOrderRefs = new RefSet(), + MAX_NESTED_LEVELS = 10; + + parseRBGroups(config.get("RBGroups")); + + return { + name: + typeof config.get("Name") === "string" + ? stringToPDFString(config.get("Name")) + : null, + creator: + typeof config.get("Creator") === "string" + ? stringToPDFString(config.get("Creator")) + : null, + baseState: + config.get("BaseState") instanceof Name + ? config.get("BaseState").name + : null, + on: parseOnOff(config.get("ON")), + off: parseOnOff(config.get("OFF")), + order: parseOrder(config.get("Order")), + groups: [...groupRefCache], + }; + } + + setActualNumPages(num = null) { + this._actualNumPages = num; + } + + get hasActualNumPages() { + return this._actualNumPages !== null; + } + + get _pagesCount() { + const obj = this.toplevelPagesDict.get("Count"); + if (!Number.isInteger(obj)) { + throw new FormatError( + "Page count in top-level pages dictionary is not an integer." + ); + } + return shadow(this, "_pagesCount", obj); + } + + get numPages() { + return this.hasActualNumPages ? this._actualNumPages : this._pagesCount; + } + + get destinations() { + const rawDests = this.#readDests(), + dests = Object.create(null); + for (const obj of rawDests) { + if (obj instanceof NameTree) { + for (const [key, value] of obj.getAll()) { + const dest = fetchDest(value); + if (dest) { + dests[stringToPDFString(key)] = dest; + } + } + } else if (obj instanceof Dict) { + for (const [key, value] of obj) { + const dest = fetchDest(value); + if (dest) { + // Always let the NameTree take precedence. + dests[key] ||= dest; + } + } + } + } + return shadow(this, "destinations", dests); + } + + getDestination(id) { + const rawDests = this.#readDests(); + for (const obj of rawDests) { + if (obj instanceof NameTree || obj instanceof Dict) { + const dest = fetchDest(obj.get(id)); + if (dest) { + return dest; + } + } + } + + if (rawDests[0] instanceof NameTree) { + // Fallback to checking the *entire* NameTree, in an attempt to handle + // corrupt PDF documents with out-of-order NameTrees (fixes issue 10272). + const dest = this.destinations[id]; + if (dest) { + warn(`Found "${id}" at an incorrect position in the NameTree.`); + return dest; + } + } + return null; + } + + #readDests() { + const obj = this._catDict.get("Names"); + const rawDests = []; + if (obj?.has("Dests")) { + rawDests.push(new NameTree(obj.getRaw("Dests"), this.xref)); + } + if (this._catDict.has("Dests")) { + // Simple destination dictionary. + rawDests.push(this._catDict.get("Dests")); + } + return rawDests; + } + + get pageLabels() { + let obj = null; + try { + obj = this._readPageLabels(); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn("Unable to read page labels."); + } + return shadow(this, "pageLabels", obj); + } + + /** + * @private + */ + _readPageLabels() { + const obj = this._catDict.getRaw("PageLabels"); + if (!obj) { + return null; + } + + const pageLabels = new Array(this.numPages); + let style = null, + prefix = ""; + + const numberTree = new NumberTree(obj, this.xref); + const nums = numberTree.getAll(); + let currentLabel = "", + currentIndex = 1; + + for (let i = 0, ii = this.numPages; i < ii; i++) { + const labelDict = nums.get(i); + + if (labelDict !== undefined) { + if (!(labelDict instanceof Dict)) { + throw new FormatError("PageLabel is not a dictionary."); + } + + if ( + labelDict.has("Type") && + !isName(labelDict.get("Type"), "PageLabel") + ) { + throw new FormatError("Invalid type in PageLabel dictionary."); + } + + if (labelDict.has("S")) { + const s = labelDict.get("S"); + if (!(s instanceof Name)) { + throw new FormatError("Invalid style in PageLabel dictionary."); + } + style = s.name; + } else { + style = null; + } + + if (labelDict.has("P")) { + const p = labelDict.get("P"); + if (typeof p !== "string") { + throw new FormatError("Invalid prefix in PageLabel dictionary."); + } + prefix = stringToPDFString(p); + } else { + prefix = ""; + } + + if (labelDict.has("St")) { + const st = labelDict.get("St"); + if (!(Number.isInteger(st) && st >= 1)) { + throw new FormatError("Invalid start in PageLabel dictionary."); + } + currentIndex = st; + } else { + currentIndex = 1; + } + } + + switch (style) { + case "D": + currentLabel = currentIndex; + break; + case "R": + case "r": + currentLabel = toRomanNumerals(currentIndex, style === "r"); + break; + case "A": + case "a": + const LIMIT = 26; // Use only the characters A-Z, or a-z. + const A_UPPER_CASE = 0x41, + A_LOWER_CASE = 0x61; + + const baseCharCode = style === "a" ? A_LOWER_CASE : A_UPPER_CASE; + const letterIndex = currentIndex - 1; + const character = String.fromCharCode( + baseCharCode + (letterIndex % LIMIT) + ); + currentLabel = character.repeat(Math.floor(letterIndex / LIMIT) + 1); + break; + default: + if (style) { + throw new FormatError( + `Invalid style "${style}" in PageLabel dictionary.` + ); + } + currentLabel = ""; + } + + pageLabels[i] = prefix + currentLabel; + currentIndex++; + } + return pageLabels; + } + + get pageLayout() { + const obj = this._catDict.get("PageLayout"); + // Purposely use a non-standard default value, rather than 'SinglePage', to + // allow differentiating between `undefined` and /SinglePage since that does + // affect the Scroll mode (continuous/non-continuous) used in Adobe Reader. + let pageLayout = ""; + + if (obj instanceof Name) { + switch (obj.name) { + case "SinglePage": + case "OneColumn": + case "TwoColumnLeft": + case "TwoColumnRight": + case "TwoPageLeft": + case "TwoPageRight": + pageLayout = obj.name; + } + } + return shadow(this, "pageLayout", pageLayout); + } + + get pageMode() { + const obj = this._catDict.get("PageMode"); + let pageMode = "UseNone"; // Default value. + + if (obj instanceof Name) { + switch (obj.name) { + case "UseNone": + case "UseOutlines": + case "UseThumbs": + case "FullScreen": + case "UseOC": + case "UseAttachments": + pageMode = obj.name; + } + } + return shadow(this, "pageMode", pageMode); + } + + get viewerPreferences() { + const obj = this._catDict.get("ViewerPreferences"); + if (!(obj instanceof Dict)) { + return shadow(this, "viewerPreferences", null); + } + let prefs = null; + + for (const [key, value] of obj) { + let prefValue; + + switch (key) { + case "HideToolbar": + case "HideMenubar": + case "HideWindowUI": + case "FitWindow": + case "CenterWindow": + case "DisplayDocTitle": + case "PickTrayByPDFSize": + if (typeof value === "boolean") { + prefValue = value; + } + break; + case "NonFullScreenPageMode": + if (value instanceof Name) { + switch (value.name) { + case "UseNone": + case "UseOutlines": + case "UseThumbs": + case "UseOC": + prefValue = value.name; + break; + default: + prefValue = "UseNone"; + } + } + break; + case "Direction": + if (value instanceof Name) { + switch (value.name) { + case "L2R": + case "R2L": + prefValue = value.name; + break; + default: + prefValue = "L2R"; + } + } + break; + case "ViewArea": + case "ViewClip": + case "PrintArea": + case "PrintClip": + if (value instanceof Name) { + switch (value.name) { + case "MediaBox": + case "CropBox": + case "BleedBox": + case "TrimBox": + case "ArtBox": + prefValue = value.name; + break; + default: + prefValue = "CropBox"; + } + } + break; + case "PrintScaling": + if (value instanceof Name) { + switch (value.name) { + case "None": + case "AppDefault": + prefValue = value.name; + break; + default: + prefValue = "AppDefault"; + } + } + break; + case "Duplex": + if (value instanceof Name) { + switch (value.name) { + case "Simplex": + case "DuplexFlipShortEdge": + case "DuplexFlipLongEdge": + prefValue = value.name; + break; + default: + prefValue = "None"; + } + } + break; + case "PrintPageRange": + // The number of elements must be even. + if (Array.isArray(value) && value.length % 2 === 0) { + const isValid = value.every( + (page, i, arr) => + Number.isInteger(page) && + page > 0 && + (i === 0 || page >= arr[i - 1]) && + page <= this.numPages + ); + if (isValid) { + prefValue = value; + } + } + break; + case "NumCopies": + if (Number.isInteger(value) && value > 0) { + prefValue = value; + } + break; + default: + warn(`Ignoring non-standard key in ViewerPreferences: ${key}.`); + continue; + } + + if (prefValue === undefined) { + warn(`Bad value, for key "${key}", in ViewerPreferences: ${value}.`); + continue; + } + if (!prefs) { + prefs = Object.create(null); + } + prefs[key] = prefValue; + } + return shadow(this, "viewerPreferences", prefs); + } + + get openAction() { + const obj = this._catDict.get("OpenAction"); + const openAction = Object.create(null); + + if (obj instanceof Dict) { + // Convert the OpenAction dictionary into a format that works with + // `parseDestDictionary`, to avoid having to re-implement those checks. + const destDict = new Dict(this.xref); + destDict.set("A", obj); + + const resultObj = { url: null, dest: null, action: null }; + Catalog.parseDestDictionary({ destDict, resultObj }); + + if (Array.isArray(resultObj.dest)) { + openAction.dest = resultObj.dest; + } else if (resultObj.action) { + openAction.action = resultObj.action; + } + } else if (Array.isArray(obj)) { + openAction.dest = obj; + } + return shadow( + this, + "openAction", + objectSize(openAction) > 0 ? openAction : null + ); + } + + get attachments() { + const obj = this._catDict.get("Names"); + let attachments = null; + + if (obj instanceof Dict && obj.has("EmbeddedFiles")) { + const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref); + for (const [key, value] of nameTree.getAll()) { + const fs = new FileSpec(value, this.xref); + if (!attachments) { + attachments = Object.create(null); + } + attachments[stringToPDFString(key)] = fs.serializable; + } + } + return shadow(this, "attachments", attachments); + } + + get xfaImages() { + const obj = this._catDict.get("Names"); + let xfaImages = null; + + if (obj instanceof Dict && obj.has("XFAImages")) { + const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref); + for (const [key, value] of nameTree.getAll()) { + if (!xfaImages) { + xfaImages = new Dict(this.xref); + } + xfaImages.set(stringToPDFString(key), value); + } + } + return shadow(this, "xfaImages", xfaImages); + } + + _collectJavaScript() { + const obj = this._catDict.get("Names"); + let javaScript = null; + + function appendIfJavaScriptDict(name, jsDict) { + if (!(jsDict instanceof Dict)) { + return; + } + if (!isName(jsDict.get("S"), "JavaScript")) { + return; + } + + let js = jsDict.get("JS"); + if (js instanceof BaseStream) { + js = js.getString(); + } else if (typeof js !== "string") { + return; + } + js = stringToPDFString(js).replaceAll("\x00", ""); + // Skip empty entries, similar to the `_collectJS` function. + if (js) { + (javaScript ||= new Map()).set(name, js); + } + } + + if (obj instanceof Dict && obj.has("JavaScript")) { + const nameTree = new NameTree(obj.getRaw("JavaScript"), this.xref); + for (const [key, value] of nameTree.getAll()) { + appendIfJavaScriptDict(stringToPDFString(key), value); + } + } + // Append OpenAction "JavaScript" actions, if any, to the JavaScript map. + const openAction = this._catDict.get("OpenAction"); + if (openAction) { + appendIfJavaScriptDict("OpenAction", openAction); + } + + return javaScript; + } + + get jsActions() { + const javaScript = this._collectJavaScript(); + let actions = collectActions( + this.xref, + this._catDict, + DocumentActionEventType + ); + + if (javaScript) { + actions ||= Object.create(null); + + for (const [key, val] of javaScript) { + if (key in actions) { + actions[key].push(val); + } else { + actions[key] = [val]; + } + } + } + return shadow(this, "jsActions", actions); + } + + async fontFallback(id, handler) { + const translatedFonts = await Promise.all(this.fontCache); + + for (const translatedFont of translatedFonts) { + if (translatedFont.loadedName === id) { + translatedFont.fallback(handler); + return; + } + } + } + + async cleanup(manuallyTriggered = false) { + clearGlobalCaches(); + this.globalImageCache.clear(/* onlyData = */ manuallyTriggered); + this.pageKidsCountCache.clear(); + this.pageIndexCache.clear(); + this.pageDictCache.clear(); + this.nonBlendModesSet.clear(); + + const translatedFonts = await Promise.all(this.fontCache); + + for (const { dict } of translatedFonts) { + delete dict.cacheKey; + } + this.fontCache.clear(); + this.builtInCMapCache.clear(); + this.standardFontDataCache.clear(); + this.systemFontCache.clear(); + } + + async getPageDict(pageIndex) { + const nodesToVisit = [this.toplevelPagesDict]; + const visitedNodes = new RefSet(); + + const pagesRef = this._catDict.getRaw("Pages"); + if (pagesRef instanceof Ref) { + visitedNodes.put(pagesRef); + } + const xref = this.xref, + pageKidsCountCache = this.pageKidsCountCache, + pageIndexCache = this.pageIndexCache, + pageDictCache = this.pageDictCache; + let currentPageIndex = 0; + + while (nodesToVisit.length) { + const currentNode = nodesToVisit.pop(); + + if (currentNode instanceof Ref) { + const count = pageKidsCountCache.get(currentNode); + // Skip nodes where the page can't be. + if (count >= 0 && currentPageIndex + count <= pageIndex) { + currentPageIndex += count; + continue; + } + // Prevent circular references in the /Pages tree. + if (visitedNodes.has(currentNode)) { + throw new FormatError("Pages tree contains circular reference."); + } + visitedNodes.put(currentNode); + + const obj = await (pageDictCache.get(currentNode) || + xref.fetchAsync(currentNode)); + if (obj instanceof Dict) { + let type = obj.getRaw("Type"); + if (type instanceof Ref) { + type = await xref.fetchAsync(type); + } + if (isName(type, "Page") || !obj.has("Kids")) { + // Cache the Page reference, since it can *greatly* improve + // performance by reducing redundant lookups in long documents + // where all nodes are found at *one* level of the tree. + if (!pageKidsCountCache.has(currentNode)) { + pageKidsCountCache.put(currentNode, 1); + } + // Help improve performance of the `getPageIndex` method. + if (!pageIndexCache.has(currentNode)) { + pageIndexCache.put(currentNode, currentPageIndex); + } + + if (currentPageIndex === pageIndex) { + return [obj, currentNode]; + } + currentPageIndex++; + continue; + } + } + nodesToVisit.push(obj); + continue; + } + + // Must be a child page dictionary. + if (!(currentNode instanceof Dict)) { + throw new FormatError( + "Page dictionary kid reference points to wrong type of object." + ); + } + const { objId } = currentNode; + + let count = currentNode.getRaw("Count"); + if (count instanceof Ref) { + count = await xref.fetchAsync(count); + } + if (Number.isInteger(count) && count >= 0) { + // Cache the Kids count, since it can reduce redundant lookups in + // documents where all nodes are found at *one* level of the tree. + if (objId && !pageKidsCountCache.has(objId)) { + pageKidsCountCache.put(objId, count); + } + + // Skip nodes where the page can't be. + if (currentPageIndex + count <= pageIndex) { + currentPageIndex += count; + continue; + } + } + + let kids = currentNode.getRaw("Kids"); + if (kids instanceof Ref) { + kids = await xref.fetchAsync(kids); + } + if (!Array.isArray(kids)) { + // Prevent errors in corrupt PDF documents that violate the + // specification by *inlining* Page dicts directly in the Kids + // array, rather than using indirect objects (fixes issue9540.pdf). + let type = currentNode.getRaw("Type"); + if (type instanceof Ref) { + type = await xref.fetchAsync(type); + } + if (isName(type, "Page") || !currentNode.has("Kids")) { + if (currentPageIndex === pageIndex) { + return [currentNode, null]; + } + currentPageIndex++; + continue; + } + + throw new FormatError("Page dictionary kids object is not an array."); + } + + // Always check all `Kids` nodes, to avoid getting stuck in an empty + // node further down in the tree (see issue5644.pdf, issue8088.pdf), + // and to ensure that we actually find the correct `Page` dict. + for (let last = kids.length - 1; last >= 0; last--) { + const lastKid = kids[last]; + nodesToVisit.push(lastKid); + + // Launch all requests in parallel so we don't wait for each one in turn + // when looking for a page near the end, if all the pages are top level. + if ( + currentNode === this.toplevelPagesDict && + lastKid instanceof Ref && + !pageDictCache.has(lastKid) + ) { + pageDictCache.put(lastKid, xref.fetchAsync(lastKid)); + } + } + } + + throw new Error(`Page index ${pageIndex} not found.`); + } + + /** + * Eagerly fetches the entire /Pages-tree; should ONLY be used as a fallback. + * @returns {Promise} + */ + async getAllPageDicts(recoveryMode = false) { + const { ignoreErrors } = this.pdfManager.evaluatorOptions; + + const queue = [{ currentNode: this.toplevelPagesDict, posInKids: 0 }]; + const visitedNodes = new RefSet(); + + const pagesRef = this._catDict.getRaw("Pages"); + if (pagesRef instanceof Ref) { + visitedNodes.put(pagesRef); + } + const map = new Map(), + xref = this.xref, + pageIndexCache = this.pageIndexCache; + let pageIndex = 0; + + function addPageDict(pageDict, pageRef) { + // Help improve performance of the `getPageIndex` method. + if (pageRef && !pageIndexCache.has(pageRef)) { + pageIndexCache.put(pageRef, pageIndex); + } + + map.set(pageIndex++, [pageDict, pageRef]); + } + function addPageError(error) { + if (error instanceof XRefEntryException && !recoveryMode) { + throw error; + } + if (recoveryMode && ignoreErrors && pageIndex === 0) { + // Ensure that the viewer will always load (fixes issue15590.pdf). + warn(`getAllPageDicts - Skipping invalid first page: "${error}".`); + error = Dict.empty; + } + + map.set(pageIndex++, [error, null]); + } + + while (queue.length > 0) { + const queueItem = queue.at(-1); + const { currentNode, posInKids } = queueItem; + + let kids = currentNode.getRaw("Kids"); + if (kids instanceof Ref) { + try { + kids = await xref.fetchAsync(kids); + } catch (ex) { + addPageError(ex); + break; + } + } + if (!Array.isArray(kids)) { + addPageError( + new FormatError("Page dictionary kids object is not an array.") + ); + break; + } + + if (posInKids >= kids.length) { + queue.pop(); + continue; + } + + const kidObj = kids[posInKids]; + let obj; + if (kidObj instanceof Ref) { + // Prevent circular references in the /Pages tree. + if (visitedNodes.has(kidObj)) { + addPageError( + new FormatError("Pages tree contains circular reference.") + ); + break; + } + visitedNodes.put(kidObj); + + try { + obj = await xref.fetchAsync(kidObj); + } catch (ex) { + addPageError(ex); + break; + } + } else { + // Prevent errors in corrupt PDF documents that violate the + // specification by *inlining* Page dicts directly in the Kids + // array, rather than using indirect objects (see issue9540.pdf). + obj = kidObj; + } + if (!(obj instanceof Dict)) { + addPageError( + new FormatError( + "Page dictionary kid reference points to wrong type of object." + ) + ); + break; + } + + let type = obj.getRaw("Type"); + if (type instanceof Ref) { + try { + type = await xref.fetchAsync(type); + } catch (ex) { + addPageError(ex); + break; + } + } + if (isName(type, "Page") || !obj.has("Kids")) { + addPageDict(obj, kidObj instanceof Ref ? kidObj : null); + } else { + queue.push({ currentNode: obj, posInKids: 0 }); + } + queueItem.posInKids++; + } + return map; + } + + getPageIndex(pageRef) { + const cachedPageIndex = this.pageIndexCache.get(pageRef); + if (cachedPageIndex !== undefined) { + return Promise.resolve(cachedPageIndex); + } + + // The page tree nodes have the count of all the leaves below them. To get + // how many pages are before we just have to walk up the tree and keep + // adding the count of siblings to the left of the node. + const xref = this.xref; + + function pagesBeforeRef(kidRef) { + let total = 0, + parentRef; + + return xref + .fetchAsync(kidRef) + .then(function (node) { + if ( + isRefsEqual(kidRef, pageRef) && + !isDict(node, "Page") && + !(node instanceof Dict && !node.has("Type") && node.has("Contents")) + ) { + throw new FormatError( + "The reference does not point to a /Page dictionary." + ); + } + if (!node) { + return null; + } + if (!(node instanceof Dict)) { + throw new FormatError("Node must be a dictionary."); + } + parentRef = node.getRaw("Parent"); + return node.getAsync("Parent"); + }) + .then(function (parent) { + if (!parent) { + return null; + } + if (!(parent instanceof Dict)) { + throw new FormatError("Parent must be a dictionary."); + } + return parent.getAsync("Kids"); + }) + .then(function (kids) { + if (!kids) { + return null; + } + + const kidPromises = []; + let found = false; + for (const kid of kids) { + if (!(kid instanceof Ref)) { + throw new FormatError("Kid must be a reference."); + } + if (isRefsEqual(kid, kidRef)) { + found = true; + break; + } + kidPromises.push( + xref.fetchAsync(kid).then(function (obj) { + if (!(obj instanceof Dict)) { + throw new FormatError("Kid node must be a dictionary."); + } + if (obj.has("Count")) { + total += obj.get("Count"); + } else { + // Page leaf node. + total++; + } + }) + ); + } + if (!found) { + throw new FormatError("Kid reference not found in parent's kids."); + } + return Promise.all(kidPromises).then(function () { + return [total, parentRef]; + }); + }); + } + + let total = 0; + const next = ref => + pagesBeforeRef(ref).then(args => { + if (!args) { + this.pageIndexCache.put(pageRef, total); + return total; + } + const [count, parentRef] = args; + total += count; + return next(parentRef); + }); + + return next(pageRef); + } + + get baseUrl() { + const uri = this._catDict.get("URI"); + if (uri instanceof Dict) { + const base = uri.get("Base"); + if (typeof base === "string") { + const absoluteUrl = createValidAbsoluteUrl(base, null, { + tryConvertEncoding: true, + }); + if (absoluteUrl) { + return shadow(this, "baseUrl", absoluteUrl.href); + } + } + } + return shadow(this, "baseUrl", this.pdfManager.docBaseUrl); + } + + /** + * @typedef {Object} ParseDestDictionaryParameters + * @property {Dict} destDict - The dictionary containing the destination. + * @property {Object} resultObj - The object where the parsed destination + * properties will be placed. + * @property {string} [docBaseUrl] - The document base URL that is used when + * attempting to recover valid absolute URLs from relative ones. + * @property {Object} [docAttachments] - The document attachments (may not + * exist in most PDF documents). + */ + + /** + * Helper function used to parse the contents of destination dictionaries. + * @param {ParseDestDictionaryParameters} params + */ + static parseDestDictionary({ + destDict, + resultObj, + docBaseUrl = null, + docAttachments = null, + }) { + if (!(destDict instanceof Dict)) { + warn("parseDestDictionary: `destDict` must be a dictionary."); + return; + } + + let action = destDict.get("A"), + url, + dest; + if (!(action instanceof Dict)) { + if (destDict.has("Dest")) { + // A /Dest entry should *only* contain a Name or an Array, but some bad + // PDF generators ignore that and treat it as an /A entry. + action = destDict.get("Dest"); + } else { + action = destDict.get("AA"); + if (action instanceof Dict) { + if (action.has("D")) { + // MouseDown + action = action.get("D"); + } else if (action.has("U")) { + // MouseUp + action = action.get("U"); + } + } + } + } + + if (action instanceof Dict) { + const actionType = action.get("S"); + if (!(actionType instanceof Name)) { + warn("parseDestDictionary: Invalid type in Action dictionary."); + return; + } + const actionName = actionType.name; + + switch (actionName) { + case "ResetForm": + const flags = action.get("Flags"); + const include = ((typeof flags === "number" ? flags : 0) & 1) === 0; + const fields = []; + const refs = []; + for (const obj of action.get("Fields") || []) { + if (obj instanceof Ref) { + refs.push(obj.toString()); + } else if (typeof obj === "string") { + fields.push(stringToPDFString(obj)); + } + } + resultObj.resetForm = { fields, refs, include }; + break; + case "URI": + url = action.get("URI"); + if (url instanceof Name) { + // Some bad PDFs do not put parentheses around relative URLs. + url = "/" + url.name; + } + break; + + case "GoTo": + dest = action.get("D"); + break; + + case "Launch": + // We neither want, nor can, support arbitrary 'Launch' actions. + // However, in practice they are mostly used for linking to other PDF + // files, which we thus attempt to support (utilizing `docBaseUrl`). + /* falls through */ + + case "GoToR": + const urlDict = action.get("F"); + if (urlDict instanceof Dict) { + const fs = new FileSpec( + urlDict, + /* xref = */ null, + /* skipContent = */ true + ); + const { rawFilename } = fs.serializable; + url = rawFilename; + } else if (typeof urlDict === "string") { + url = urlDict; + } + + // NOTE: the destination is relative to the *remote* document. + const remoteDest = fetchRemoteDest(action); + if (remoteDest && typeof url === "string") { + url = /* baseUrl = */ url.split("#", 1)[0] + "#" + remoteDest; + } + // The 'NewWindow' property, equal to `LinkTarget.BLANK`. + const newWindow = action.get("NewWindow"); + if (typeof newWindow === "boolean") { + resultObj.newWindow = newWindow; + } + break; + + case "GoToE": + const target = action.get("T"); + let attachment; + + if (docAttachments && target instanceof Dict) { + const relationship = target.get("R"); + const name = target.get("N"); + + if (isName(relationship, "C") && typeof name === "string") { + attachment = docAttachments[stringToPDFString(name)]; + } + } + + if (attachment) { + resultObj.attachment = attachment; + + // NOTE: the destination is relative to the *attachment*. + const attachmentDest = fetchRemoteDest(action); + if (attachmentDest) { + resultObj.attachmentDest = attachmentDest; + } + } else { + warn(`parseDestDictionary - unimplemented "GoToE" action.`); + } + break; + + case "Named": + const namedAction = action.get("N"); + if (namedAction instanceof Name) { + resultObj.action = namedAction.name; + } + break; + + case "SetOCGState": + const state = action.get("State"); + const preserveRB = action.get("PreserveRB"); + + if (!Array.isArray(state) || state.length === 0) { + break; + } + const stateArr = []; + + for (const elem of state) { + if (elem instanceof Name) { + switch (elem.name) { + case "ON": + case "OFF": + case "Toggle": + stateArr.push(elem.name); + break; + } + } else if (elem instanceof Ref) { + stateArr.push(elem.toString()); + } + } + + if (stateArr.length !== state.length) { + break; // Some of the original entries are not valid. + } + resultObj.setOCGState = { + state: stateArr, + preserveRB: typeof preserveRB === "boolean" ? preserveRB : true, + }; + break; + + case "JavaScript": + const jsAction = action.get("JS"); + let js; + + if (jsAction instanceof BaseStream) { + js = jsAction.getString(); + } else if (typeof jsAction === "string") { + js = jsAction; + } + + const jsURL = js && recoverJsURL(stringToPDFString(js)); + if (jsURL) { + url = jsURL.url; + resultObj.newWindow = jsURL.newWindow; + break; + } + /* falls through */ + default: + if (actionName === "JavaScript" || actionName === "SubmitForm") { + // Don't bother the user with a warning for actions that require + // scripting support, since those will be handled separately. + break; + } + warn(`parseDestDictionary - unsupported action: "${actionName}".`); + break; + } + } else if (destDict.has("Dest")) { + // Simple destination. + dest = destDict.get("Dest"); + } + + if (typeof url === "string") { + const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl, { + addDefaultProtocol: true, + tryConvertEncoding: true, + }); + if (absoluteUrl) { + resultObj.url = absoluteUrl.href; + } + resultObj.unsafeUrl = url; + } + if (dest) { + if (dest instanceof Name) { + dest = dest.name; + } + if (typeof dest === "string") { + resultObj.dest = stringToPDFString(dest); + } else if (isValidExplicitDest(dest)) { + resultObj.dest = dest; + } + } + } +} + +export { Catalog }; diff --git a/frontend/public/pdf.js/src/core/ccitt.js b/frontend/public/pdf.js/src/core/ccitt.js new file mode 100644 index 0000000..8a57a5d --- /dev/null +++ b/frontend/public/pdf.js/src/core/ccitt.js @@ -0,0 +1,1059 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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. + */ +/* Copyright 1996-2003 Glyph & Cog, LLC + * + * The CCITT stream implementation contained in this file is a JavaScript port + * of XPDF's implementation, made available under the Apache 2.0 open source + * license. + */ + +import { FormatError, info } from "../shared/util.js"; + +/** + * @typedef {Object} CCITTFaxDecoderSource + * @property {function} next - Method that return one byte of data for decoding, + * or -1 when EOF is reached. + */ + +const ccittEOL = -2; +const ccittEOF = -1; +const twoDimPass = 0; +const twoDimHoriz = 1; +const twoDimVert0 = 2; +const twoDimVertR1 = 3; +const twoDimVertL1 = 4; +const twoDimVertR2 = 5; +const twoDimVertL2 = 6; +const twoDimVertR3 = 7; +const twoDimVertL3 = 8; + +// prettier-ignore +const twoDimTable = [ + [-1, -1], [-1, -1], // 000000x + [7, twoDimVertL3], // 0000010 + [7, twoDimVertR3], // 0000011 + [6, twoDimVertL2], [6, twoDimVertL2], // 000010x + [6, twoDimVertR2], [6, twoDimVertR2], // 000011x + [4, twoDimPass], [4, twoDimPass], // 0001xxx + [4, twoDimPass], [4, twoDimPass], + [4, twoDimPass], [4, twoDimPass], + [4, twoDimPass], [4, twoDimPass], + [3, twoDimHoriz], [3, twoDimHoriz], // 001xxxx + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimVertL1], [3, twoDimVertL1], // 010xxxx + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertR1], [3, twoDimVertR1], // 011xxxx + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [1, twoDimVert0], [1, twoDimVert0], // 1xxxxxx + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0] +]; + +// prettier-ignore +const whiteTable1 = [ + [-1, -1], // 00000 + [12, ccittEOL], // 00001 + [-1, -1], [-1, -1], // 0001x + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 001xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 010xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 011xx + [11, 1792], [11, 1792], // 1000x + [12, 1984], // 10010 + [12, 2048], // 10011 + [12, 2112], // 10100 + [12, 2176], // 10101 + [12, 2240], // 10110 + [12, 2304], // 10111 + [11, 1856], [11, 1856], // 1100x + [11, 1920], [11, 1920], // 1101x + [12, 2368], // 11100 + [12, 2432], // 11101 + [12, 2496], // 11110 + [12, 2560] // 11111 +]; + +// prettier-ignore +const whiteTable2 = [ + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx + [8, 29], [8, 29], // 00000010x + [8, 30], [8, 30], // 00000011x + [8, 45], [8, 45], // 00000100x + [8, 46], [8, 46], // 00000101x + [7, 22], [7, 22], [7, 22], [7, 22], // 0000011xx + [7, 23], [7, 23], [7, 23], [7, 23], // 0000100xx + [8, 47], [8, 47], // 00001010x + [8, 48], [8, 48], // 00001011x + [6, 13], [6, 13], [6, 13], [6, 13], // 000011xxx + [6, 13], [6, 13], [6, 13], [6, 13], + [7, 20], [7, 20], [7, 20], [7, 20], // 0001000xx + [8, 33], [8, 33], // 00010010x + [8, 34], [8, 34], // 00010011x + [8, 35], [8, 35], // 00010100x + [8, 36], [8, 36], // 00010101x + [8, 37], [8, 37], // 00010110x + [8, 38], [8, 38], // 00010111x + [7, 19], [7, 19], [7, 19], [7, 19], // 0001100xx + [8, 31], [8, 31], // 00011010x + [8, 32], [8, 32], // 00011011x + [6, 1], [6, 1], [6, 1], [6, 1], // 000111xxx + [6, 1], [6, 1], [6, 1], [6, 1], + [6, 12], [6, 12], [6, 12], [6, 12], // 001000xxx + [6, 12], [6, 12], [6, 12], [6, 12], + [8, 53], [8, 53], // 00100100x + [8, 54], [8, 54], // 00100101x + [7, 26], [7, 26], [7, 26], [7, 26], // 0010011xx + [8, 39], [8, 39], // 00101000x + [8, 40], [8, 40], // 00101001x + [8, 41], [8, 41], // 00101010x + [8, 42], [8, 42], // 00101011x + [8, 43], [8, 43], // 00101100x + [8, 44], [8, 44], // 00101101x + [7, 21], [7, 21], [7, 21], [7, 21], // 0010111xx + [7, 28], [7, 28], [7, 28], [7, 28], // 0011000xx + [8, 61], [8, 61], // 00110010x + [8, 62], [8, 62], // 00110011x + [8, 63], [8, 63], // 00110100x + [8, 0], [8, 0], // 00110101x + [8, 320], [8, 320], // 00110110x + [8, 384], [8, 384], // 00110111x + [5, 10], [5, 10], [5, 10], [5, 10], // 00111xxxx + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 11], [5, 11], [5, 11], [5, 11], // 01000xxxx + [5, 11], [5, 11], [5, 11], [5, 11], + [5, 11], [5, 11], [5, 11], [5, 11], + [5, 11], [5, 11], [5, 11], [5, 11], + [7, 27], [7, 27], [7, 27], [7, 27], // 0100100xx + [8, 59], [8, 59], // 01001010x + [8, 60], [8, 60], // 01001011x + [9, 1472], // 010011000 + [9, 1536], // 010011001 + [9, 1600], // 010011010 + [9, 1728], // 010011011 + [7, 18], [7, 18], [7, 18], [7, 18], // 0100111xx + [7, 24], [7, 24], [7, 24], [7, 24], // 0101000xx + [8, 49], [8, 49], // 01010010x + [8, 50], [8, 50], // 01010011x + [8, 51], [8, 51], // 01010100x + [8, 52], [8, 52], // 01010101x + [7, 25], [7, 25], [7, 25], [7, 25], // 0101011xx + [8, 55], [8, 55], // 01011000x + [8, 56], [8, 56], // 01011001x + [8, 57], [8, 57], // 01011010x + [8, 58], [8, 58], // 01011011x + [6, 192], [6, 192], [6, 192], [6, 192], // 010111xxx + [6, 192], [6, 192], [6, 192], [6, 192], + [6, 1664], [6, 1664], [6, 1664], [6, 1664], // 011000xxx + [6, 1664], [6, 1664], [6, 1664], [6, 1664], + [8, 448], [8, 448], // 01100100x + [8, 512], [8, 512], // 01100101x + [9, 704], // 011001100 + [9, 768], // 011001101 + [8, 640], [8, 640], // 01100111x + [8, 576], [8, 576], // 01101000x + [9, 832], // 011010010 + [9, 896], // 011010011 + [9, 960], // 011010100 + [9, 1024], // 011010101 + [9, 1088], // 011010110 + [9, 1152], // 011010111 + [9, 1216], // 011011000 + [9, 1280], // 011011001 + [9, 1344], // 011011010 + [9, 1408], // 011011011 + [7, 256], [7, 256], [7, 256], [7, 256], // 0110111xx + [4, 2], [4, 2], [4, 2], [4, 2], // 0111xxxxx + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 3], [4, 3], [4, 3], [4, 3], // 1000xxxxx + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [5, 128], [5, 128], [5, 128], [5, 128], // 10010xxxx + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 8], [5, 8], [5, 8], [5, 8], // 10011xxxx + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 9], [5, 9], [5, 9], [5, 9], // 10100xxxx + [5, 9], [5, 9], [5, 9], [5, 9], + [5, 9], [5, 9], [5, 9], [5, 9], + [5, 9], [5, 9], [5, 9], [5, 9], + [6, 16], [6, 16], [6, 16], [6, 16], // 101010xxx + [6, 16], [6, 16], [6, 16], [6, 16], + [6, 17], [6, 17], [6, 17], [6, 17], // 101011xxx + [6, 17], [6, 17], [6, 17], [6, 17], + [4, 4], [4, 4], [4, 4], [4, 4], // 1011xxxxx + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 5], [4, 5], [4, 5], [4, 5], // 1100xxxxx + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [6, 14], [6, 14], [6, 14], [6, 14], // 110100xxx + [6, 14], [6, 14], [6, 14], [6, 14], + [6, 15], [6, 15], [6, 15], [6, 15], // 110101xxx + [6, 15], [6, 15], [6, 15], [6, 15], + [5, 64], [5, 64], [5, 64], [5, 64], // 11011xxxx + [5, 64], [5, 64], [5, 64], [5, 64], + [5, 64], [5, 64], [5, 64], [5, 64], + [5, 64], [5, 64], [5, 64], [5, 64], + [4, 6], [4, 6], [4, 6], [4, 6], // 1110xxxxx + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 7], [4, 7], [4, 7], [4, 7], // 1111xxxxx + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7] +]; + +// prettier-ignore +const blackTable1 = [ + [-1, -1], [-1, -1], // 000000000000x + [12, ccittEOL], [12, ccittEOL], // 000000000001x + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000010xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000011xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000100xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000101xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000110xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000111xx + [11, 1792], [11, 1792], [11, 1792], [11, 1792], // 00000001000xx + [12, 1984], [12, 1984], // 000000010010x + [12, 2048], [12, 2048], // 000000010011x + [12, 2112], [12, 2112], // 000000010100x + [12, 2176], [12, 2176], // 000000010101x + [12, 2240], [12, 2240], // 000000010110x + [12, 2304], [12, 2304], // 000000010111x + [11, 1856], [11, 1856], [11, 1856], [11, 1856], // 00000001100xx + [11, 1920], [11, 1920], [11, 1920], [11, 1920], // 00000001101xx + [12, 2368], [12, 2368], // 000000011100x + [12, 2432], [12, 2432], // 000000011101x + [12, 2496], [12, 2496], // 000000011110x + [12, 2560], [12, 2560], // 000000011111x + [10, 18], [10, 18], [10, 18], [10, 18], // 0000001000xxx + [10, 18], [10, 18], [10, 18], [10, 18], + [12, 52], [12, 52], // 000000100100x + [13, 640], // 0000001001010 + [13, 704], // 0000001001011 + [13, 768], // 0000001001100 + [13, 832], // 0000001001101 + [12, 55], [12, 55], // 000000100111x + [12, 56], [12, 56], // 000000101000x + [13, 1280], // 0000001010010 + [13, 1344], // 0000001010011 + [13, 1408], // 0000001010100 + [13, 1472], // 0000001010101 + [12, 59], [12, 59], // 000000101011x + [12, 60], [12, 60], // 000000101100x + [13, 1536], // 0000001011010 + [13, 1600], // 0000001011011 + [11, 24], [11, 24], [11, 24], [11, 24], // 00000010111xx + [11, 25], [11, 25], [11, 25], [11, 25], // 00000011000xx + [13, 1664], // 0000001100100 + [13, 1728], // 0000001100101 + [12, 320], [12, 320], // 000000110011x + [12, 384], [12, 384], // 000000110100x + [12, 448], [12, 448], // 000000110101x + [13, 512], // 0000001101100 + [13, 576], // 0000001101101 + [12, 53], [12, 53], // 000000110111x + [12, 54], [12, 54], // 000000111000x + [13, 896], // 0000001110010 + [13, 960], // 0000001110011 + [13, 1024], // 0000001110100 + [13, 1088], // 0000001110101 + [13, 1152], // 0000001110110 + [13, 1216], // 0000001110111 + [10, 64], [10, 64], [10, 64], [10, 64], // 0000001111xxx + [10, 64], [10, 64], [10, 64], [10, 64] +]; + +// prettier-ignore +const blackTable2 = [ + [8, 13], [8, 13], [8, 13], [8, 13], // 00000100xxxx + [8, 13], [8, 13], [8, 13], [8, 13], + [8, 13], [8, 13], [8, 13], [8, 13], + [8, 13], [8, 13], [8, 13], [8, 13], + [11, 23], [11, 23], // 00000101000x + [12, 50], // 000001010010 + [12, 51], // 000001010011 + [12, 44], // 000001010100 + [12, 45], // 000001010101 + [12, 46], // 000001010110 + [12, 47], // 000001010111 + [12, 57], // 000001011000 + [12, 58], // 000001011001 + [12, 61], // 000001011010 + [12, 256], // 000001011011 + [10, 16], [10, 16], [10, 16], [10, 16], // 0000010111xx + [10, 17], [10, 17], [10, 17], [10, 17], // 0000011000xx + [12, 48], // 000001100100 + [12, 49], // 000001100101 + [12, 62], // 000001100110 + [12, 63], // 000001100111 + [12, 30], // 000001101000 + [12, 31], // 000001101001 + [12, 32], // 000001101010 + [12, 33], // 000001101011 + [12, 40], // 000001101100 + [12, 41], // 000001101101 + [11, 22], [11, 22], // 00000110111x + [8, 14], [8, 14], [8, 14], [8, 14], // 00000111xxxx + [8, 14], [8, 14], [8, 14], [8, 14], + [8, 14], [8, 14], [8, 14], [8, 14], + [8, 14], [8, 14], [8, 14], [8, 14], + [7, 10], [7, 10], [7, 10], [7, 10], // 0000100xxxxx + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 11], [7, 11], [7, 11], [7, 11], // 0000101xxxxx + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [9, 15], [9, 15], [9, 15], [9, 15], // 000011000xxx + [9, 15], [9, 15], [9, 15], [9, 15], + [12, 128], // 000011001000 + [12, 192], // 000011001001 + [12, 26], // 000011001010 + [12, 27], // 000011001011 + [12, 28], // 000011001100 + [12, 29], // 000011001101 + [11, 19], [11, 19], // 00001100111x + [11, 20], [11, 20], // 00001101000x + [12, 34], // 000011010010 + [12, 35], // 000011010011 + [12, 36], // 000011010100 + [12, 37], // 000011010101 + [12, 38], // 000011010110 + [12, 39], // 000011010111 + [11, 21], [11, 21], // 00001101100x + [12, 42], // 000011011010 + [12, 43], // 000011011011 + [10, 0], [10, 0], [10, 0], [10, 0], // 0000110111xx + [7, 12], [7, 12], [7, 12], [7, 12], // 0000111xxxxx + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12] +]; + +// prettier-ignore +const blackTable3 = [ + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx + [6, 9], // 000100 + [6, 8], // 000101 + [5, 7], [5, 7], // 00011x + [4, 6], [4, 6], [4, 6], [4, 6], // 0010xx + [4, 5], [4, 5], [4, 5], [4, 5], // 0011xx + [3, 1], [3, 1], [3, 1], [3, 1], // 010xxx + [3, 1], [3, 1], [3, 1], [3, 1], + [3, 4], [3, 4], [3, 4], [3, 4], // 011xxx + [3, 4], [3, 4], [3, 4], [3, 4], + [2, 3], [2, 3], [2, 3], [2, 3], // 10xxxx + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 2], [2, 2], [2, 2], [2, 2], // 11xxxx + [2, 2], [2, 2], [2, 2], [2, 2], + [2, 2], [2, 2], [2, 2], [2, 2], + [2, 2], [2, 2], [2, 2], [2, 2] +]; + +/** + * @param {CCITTFaxDecoderSource} source - The data which should be decoded. + * @param {Object} [options] - Decoding options. + */ +class CCITTFaxDecoder { + constructor(source, options = {}) { + if (typeof source?.next !== "function") { + throw new Error('CCITTFaxDecoder - invalid "source" parameter.'); + } + this.source = source; + this.eof = false; + + this.encoding = options.K || 0; + this.eoline = options.EndOfLine || false; + this.byteAlign = options.EncodedByteAlign || false; + this.columns = options.Columns || 1728; + this.rows = options.Rows || 0; + this.eoblock = options.EndOfBlock ?? true; + this.black = options.BlackIs1 || false; + + this.codingLine = new Uint32Array(this.columns + 1); + this.refLine = new Uint32Array(this.columns + 2); + + this.codingLine[0] = this.columns; + this.codingPos = 0; + + this.row = 0; + this.nextLine2D = this.encoding < 0; + this.inputBits = 0; + this.inputBuf = 0; + this.outputBits = 0; + this.rowsDone = false; + + let code1; + while ((code1 = this._lookBits(12)) === 0) { + this._eatBits(1); + } + if (code1 === 1) { + this._eatBits(12); + } + if (this.encoding > 0) { + this.nextLine2D = !this._lookBits(1); + this._eatBits(1); + } + } + + readNextChar() { + if (this.eof) { + return -1; + } + const refLine = this.refLine; + const codingLine = this.codingLine; + const columns = this.columns; + + let refPos, blackPixels, bits, i; + + if (this.outputBits === 0) { + if (this.rowsDone) { + this.eof = true; + } + if (this.eof) { + return -1; + } + this.err = false; + + let code1, code2, code3; + if (this.nextLine2D) { + for (i = 0; codingLine[i] < columns; ++i) { + refLine[i] = codingLine[i]; + } + refLine[i++] = columns; + refLine[i] = columns; + codingLine[0] = 0; + this.codingPos = 0; + refPos = 0; + blackPixels = 0; + + while (codingLine[this.codingPos] < columns) { + code1 = this._getTwoDimCode(); + switch (code1) { + case twoDimPass: + this._addPixels(refLine[refPos + 1], blackPixels); + if (refLine[refPos + 1] < columns) { + refPos += 2; + } + break; + case twoDimHoriz: + code1 = code2 = 0; + if (blackPixels) { + do { + code1 += code3 = this._getBlackCode(); + } while (code3 >= 64); + do { + code2 += code3 = this._getWhiteCode(); + } while (code3 >= 64); + } else { + do { + code1 += code3 = this._getWhiteCode(); + } while (code3 >= 64); + do { + code2 += code3 = this._getBlackCode(); + } while (code3 >= 64); + } + this._addPixels(codingLine[this.codingPos] + code1, blackPixels); + if (codingLine[this.codingPos] < columns) { + this._addPixels( + codingLine[this.codingPos] + code2, + blackPixels ^ 1 + ); + } + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + break; + case twoDimVertR3: + this._addPixels(refLine[refPos] + 3, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case twoDimVertR2: + this._addPixels(refLine[refPos] + 2, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case twoDimVertR1: + this._addPixels(refLine[refPos] + 1, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case twoDimVert0: + this._addPixels(refLine[refPos], blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case twoDimVertL3: + this._addPixelsNeg(refLine[refPos] - 3, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) { + --refPos; + } else { + ++refPos; + } + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case twoDimVertL2: + this._addPixelsNeg(refLine[refPos] - 2, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) { + --refPos; + } else { + ++refPos; + } + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case twoDimVertL1: + this._addPixelsNeg(refLine[refPos] - 1, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) { + --refPos; + } else { + ++refPos; + } + while ( + refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns + ) { + refPos += 2; + } + } + break; + case ccittEOF: + this._addPixels(columns, 0); + this.eof = true; + break; + default: + info("bad 2d code"); + this._addPixels(columns, 0); + this.err = true; + } + } + } else { + codingLine[0] = 0; + this.codingPos = 0; + blackPixels = 0; + while (codingLine[this.codingPos] < columns) { + code1 = 0; + if (blackPixels) { + do { + code1 += code3 = this._getBlackCode(); + } while (code3 >= 64); + } else { + do { + code1 += code3 = this._getWhiteCode(); + } while (code3 >= 64); + } + this._addPixels(codingLine[this.codingPos] + code1, blackPixels); + blackPixels ^= 1; + } + } + + let gotEOL = false; + + if (this.byteAlign) { + this.inputBits &= ~7; + } + + if (!this.eoblock && this.row === this.rows - 1) { + this.rowsDone = true; + } else { + code1 = this._lookBits(12); + if (this.eoline) { + while (code1 !== ccittEOF && code1 !== 1) { + this._eatBits(1); + code1 = this._lookBits(12); + } + } else { + while (code1 === 0) { + this._eatBits(1); + code1 = this._lookBits(12); + } + } + if (code1 === 1) { + this._eatBits(12); + gotEOL = true; + } else if (code1 === ccittEOF) { + this.eof = true; + } + } + + if (!this.eof && this.encoding > 0 && !this.rowsDone) { + this.nextLine2D = !this._lookBits(1); + this._eatBits(1); + } + + if (this.eoblock && gotEOL && this.byteAlign) { + code1 = this._lookBits(12); + if (code1 === 1) { + this._eatBits(12); + if (this.encoding > 0) { + this._lookBits(1); + this._eatBits(1); + } + if (this.encoding >= 0) { + for (i = 0; i < 4; ++i) { + code1 = this._lookBits(12); + if (code1 !== 1) { + info("bad rtc code: " + code1); + } + this._eatBits(12); + if (this.encoding > 0) { + this._lookBits(1); + this._eatBits(1); + } + } + } + this.eof = true; + } + } else if (this.err && this.eoline) { + while (true) { + code1 = this._lookBits(13); + if (code1 === ccittEOF) { + this.eof = true; + return -1; + } + if (code1 >> 1 === 1) { + break; + } + this._eatBits(1); + } + this._eatBits(12); + if (this.encoding > 0) { + this._eatBits(1); + this.nextLine2D = !(code1 & 1); + } + } + + this.outputBits = + codingLine[0] > 0 + ? codingLine[(this.codingPos = 0)] + : codingLine[(this.codingPos = 1)]; + this.row++; + } + + let c; + if (this.outputBits >= 8) { + c = this.codingPos & 1 ? 0 : 0xff; + this.outputBits -= 8; + if (this.outputBits === 0 && codingLine[this.codingPos] < columns) { + this.codingPos++; + this.outputBits = + codingLine[this.codingPos] - codingLine[this.codingPos - 1]; + } + } else { + bits = 8; + c = 0; + do { + if (typeof this.outputBits !== "number") { + throw new FormatError( + 'Invalid /CCITTFaxDecode data, "outputBits" must be a number.' + ); + } + + if (this.outputBits > bits) { + c <<= bits; + if (!(this.codingPos & 1)) { + c |= 0xff >> (8 - bits); + } + this.outputBits -= bits; + bits = 0; + } else { + c <<= this.outputBits; + if (!(this.codingPos & 1)) { + c |= 0xff >> (8 - this.outputBits); + } + bits -= this.outputBits; + this.outputBits = 0; + if (codingLine[this.codingPos] < columns) { + this.codingPos++; + this.outputBits = + codingLine[this.codingPos] - codingLine[this.codingPos - 1]; + } else if (bits > 0) { + c <<= bits; + bits = 0; + } + } + } while (bits); + } + if (this.black) { + c ^= 0xff; + } + return c; + } + + /** + * @private + */ + _addPixels(a1, blackPixels) { + const codingLine = this.codingLine; + let codingPos = this.codingPos; + + if (a1 > codingLine[codingPos]) { + if (a1 > this.columns) { + info("row is wrong length"); + this.err = true; + a1 = this.columns; + } + if ((codingPos & 1) ^ blackPixels) { + ++codingPos; + } + + codingLine[codingPos] = a1; + } + this.codingPos = codingPos; + } + + /** + * @private + */ + _addPixelsNeg(a1, blackPixels) { + const codingLine = this.codingLine; + let codingPos = this.codingPos; + + if (a1 > codingLine[codingPos]) { + if (a1 > this.columns) { + info("row is wrong length"); + this.err = true; + a1 = this.columns; + } + if ((codingPos & 1) ^ blackPixels) { + ++codingPos; + } + + codingLine[codingPos] = a1; + } else if (a1 < codingLine[codingPos]) { + if (a1 < 0) { + info("invalid code"); + this.err = true; + a1 = 0; + } + while (codingPos > 0 && a1 < codingLine[codingPos - 1]) { + --codingPos; + } + codingLine[codingPos] = a1; + } + + this.codingPos = codingPos; + } + + /** + * This function returns the code found from the table. + * The start and end parameters set the boundaries for searching the table. + * The limit parameter is optional. Function returns an array with three + * values. The first array element indicates whether a valid code is being + * returned. The second array element is the actual code. The third array + * element indicates whether EOF was reached. + * @private + */ + _findTableCode(start, end, table, limit) { + const limitValue = limit || 0; + for (let i = start; i <= end; ++i) { + let code = this._lookBits(i); + if (code === ccittEOF) { + return [true, 1, false]; + } + if (i < end) { + code <<= end - i; + } + if (!limitValue || code >= limitValue) { + const p = table[code - limitValue]; + if (p[0] === i) { + this._eatBits(i); + return [true, p[1], true]; + } + } + } + return [false, 0, false]; + } + + /** + * @private + */ + _getTwoDimCode() { + let code = 0; + let p; + if (this.eoblock) { + code = this._lookBits(7); + p = twoDimTable[code]; + if (p?.[0] > 0) { + this._eatBits(p[0]); + return p[1]; + } + } else { + const result = this._findTableCode(1, 7, twoDimTable); + if (result[0] && result[2]) { + return result[1]; + } + } + info("Bad two dim code"); + return ccittEOF; + } + + /** + * @private + */ + _getWhiteCode() { + let code = 0; + let p; + if (this.eoblock) { + code = this._lookBits(12); + if (code === ccittEOF) { + return 1; + } + + p = code >> 5 === 0 ? whiteTable1[code] : whiteTable2[code >> 3]; + + if (p[0] > 0) { + this._eatBits(p[0]); + return p[1]; + } + } else { + let result = this._findTableCode(1, 9, whiteTable2); + if (result[0]) { + return result[1]; + } + + result = this._findTableCode(11, 12, whiteTable1); + if (result[0]) { + return result[1]; + } + } + info("bad white code"); + this._eatBits(1); + return 1; + } + + /** + * @private + */ + _getBlackCode() { + let code, p; + if (this.eoblock) { + code = this._lookBits(13); + if (code === ccittEOF) { + return 1; + } + if (code >> 7 === 0) { + p = blackTable1[code]; + } else if (code >> 9 === 0 && code >> 7 !== 0) { + p = blackTable2[(code >> 1) - 64]; + } else { + p = blackTable3[code >> 7]; + } + + if (p[0] > 0) { + this._eatBits(p[0]); + return p[1]; + } + } else { + let result = this._findTableCode(2, 6, blackTable3); + if (result[0]) { + return result[1]; + } + + result = this._findTableCode(7, 12, blackTable2, 64); + if (result[0]) { + return result[1]; + } + + result = this._findTableCode(10, 13, blackTable1); + if (result[0]) { + return result[1]; + } + } + info("bad black code"); + this._eatBits(1); + return 1; + } + + /** + * @private + */ + _lookBits(n) { + let c; + while (this.inputBits < n) { + if ((c = this.source.next()) === -1) { + if (this.inputBits === 0) { + return ccittEOF; + } + return (this.inputBuf << (n - this.inputBits)) & (0xffff >> (16 - n)); + } + this.inputBuf = (this.inputBuf << 8) | c; + this.inputBits += 8; + } + return (this.inputBuf >> (this.inputBits - n)) & (0xffff >> (16 - n)); + } + + /** + * @private + */ + _eatBits(n) { + if ((this.inputBits -= n) < 0) { + this.inputBits = 0; + } + } +} + +export { CCITTFaxDecoder }; diff --git a/frontend/public/pdf.js/src/core/ccitt_stream.js b/frontend/public/pdf.js/src/core/ccitt_stream.js new file mode 100644 index 0000000..0e3b1ce --- /dev/null +++ b/frontend/public/pdf.js/src/core/ccitt_stream.js @@ -0,0 +1,60 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { CCITTFaxDecoder } from "./ccitt.js"; +import { DecodeStream } from "./decode_stream.js"; +import { Dict } from "./primitives.js"; + +class CCITTFaxStream extends DecodeStream { + constructor(str, maybeLength, params) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + + if (!(params instanceof Dict)) { + params = Dict.empty; + } + + const source = { + next() { + return str.getByte(); + }, + }; + this.ccittFaxDecoder = new CCITTFaxDecoder(source, { + K: params.get("K"), + EndOfLine: params.get("EndOfLine"), + EncodedByteAlign: params.get("EncodedByteAlign"), + Columns: params.get("Columns"), + Rows: params.get("Rows"), + EndOfBlock: params.get("EndOfBlock"), + BlackIs1: params.get("BlackIs1"), + }); + } + + readBlock() { + while (!this.eof) { + const c = this.ccittFaxDecoder.readNextChar(); + if (c === -1) { + this.eof = true; + return; + } + this.ensureBuffer(this.bufferLength + 1); + this.buffer[this.bufferLength++] = c; + } + } +} + +export { CCITTFaxStream }; diff --git a/frontend/public/pdf.js/src/core/cff_font.js b/frontend/public/pdf.js/src/core/cff_font.js new file mode 100644 index 0000000..4066050 --- /dev/null +++ b/frontend/public/pdf.js/src/core/cff_font.js @@ -0,0 +1,139 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { CFFCompiler, CFFParser } from "./cff_parser.js"; +import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; +import { warn } from "../shared/util.js"; + +class CFFFont { + constructor(file, properties) { + this.properties = properties; + + const parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); + this.cff = parser.parse(); + this.cff.duplicateFirstGlyph(); + const compiler = new CFFCompiler(this.cff); + this.seacs = this.cff.seacs; + try { + this.data = compiler.compile(); + } catch { + warn("Failed to compile font " + properties.loadedName); + // There may have just been an issue with the compiler, set the data + // anyway and hope the font loaded. + this.data = file; + } + this._createBuiltInEncoding(); + } + + get numGlyphs() { + return this.cff.charStrings.count; + } + + getCharset() { + return this.cff.charset.charset; + } + + getGlyphMapping() { + const cff = this.cff; + const properties = this.properties; + const { cidToGidMap, cMap } = properties; + const charsets = cff.charset.charset; + let charCodeToGlyphId; + let glyphId; + + if (properties.composite) { + let invCidToGidMap; + if (cidToGidMap?.length > 0) { + invCidToGidMap = Object.create(null); + for (let i = 0, ii = cidToGidMap.length; i < ii; i++) { + const gid = cidToGidMap[i]; + if (gid !== undefined) { + invCidToGidMap[gid] = i; + } + } + } + + charCodeToGlyphId = Object.create(null); + let charCode; + if (cff.isCIDFont) { + // If the font is actually a CID font then we should use the charset + // to map CIDs to GIDs. + for (glyphId = 0; glyphId < charsets.length; glyphId++) { + const cid = charsets[glyphId]; + charCode = cMap.charCodeOf(cid); + + if (invCidToGidMap?.[charCode] !== undefined) { + // According to the PDF specification, see Table 117, it's not clear + // that a /CIDToGIDMap should be used with any non-TrueType fonts, + // however it's necessary to do so in order to fix issue 15559. + // + // It seems, in the CFF-case, that the /CIDToGIDMap needs to be used + // "inverted" compared to the TrueType-case. Here it thus seem to be + // a charCode mapping, rather than the normal CID to GID mapping. + charCode = invCidToGidMap[charCode]; + } + charCodeToGlyphId[charCode] = glyphId; + } + } else { + // If it is NOT actually a CID font then CIDs should be mapped + // directly to GIDs. + for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { + charCode = cMap.charCodeOf(glyphId); + charCodeToGlyphId[charCode] = glyphId; + } + } + return charCodeToGlyphId; + } + + let encoding = cff.encoding ? cff.encoding.encoding : null; + if (properties.isInternalFont) { + encoding = properties.defaultEncoding; + } + charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); + return charCodeToGlyphId; + } + + hasGlyphId(id) { + return this.cff.hasGlyphId(id); + } + + /** + * @private + */ + _createBuiltInEncoding() { + const { charset, encoding } = this.cff; + if (!charset || !encoding) { + return; + } + const charsets = charset.charset, + encodings = encoding.encoding; + const map = []; + + for (const charCode in encodings) { + const glyphId = encodings[charCode]; + if (glyphId >= 0) { + const glyphName = charsets[glyphId]; + if (glyphName) { + map[charCode] = glyphName; + } + } + } + if (map.length > 0) { + this.properties.builtInEncoding = map; + } + } +} + +export { CFFFont }; diff --git a/frontend/public/pdf.js/src/core/cff_parser.js b/frontend/public/pdf.js/src/core/cff_parser.js new file mode 100644 index 0000000..cdb6622 --- /dev/null +++ b/frontend/public/pdf.js/src/core/cff_parser.js @@ -0,0 +1,1937 @@ +/* Copyright 2016 Mozilla Foundation + * + * 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 { + bytesToString, + FormatError, + info, + shadow, + stringToBytes, + Util, + warn, +} from "../shared/util.js"; +import { + ExpertCharset, + ExpertSubsetCharset, + ISOAdobeCharset, +} from "./charsets.js"; +import { ExpertEncoding, StandardEncoding } from "./encodings.js"; +import { readInt16 } from "./core_utils.js"; + +// Maximum subroutine call depth of type 2 charstrings. Matches OTS. +const MAX_SUBR_NESTING = 10; + +/** + * The CFF class takes a Type1 file and wrap it into a + * 'Compact Font Format' which itself embed Type2 charstrings. + */ +// prettier-ignore +const CFFStandardStrings = [ + ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", + "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", + "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", + "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", + "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", + "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", + "sterling", "fraction", "yen", "florin", "section", "currency", + "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", + "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", + "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", + "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", + "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", + "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", + "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", + "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", + "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", + "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", + "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", + "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", + "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", + "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", + "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", + "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", + "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", + "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", + "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", + "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", + "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", + "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", + "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", + "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", + "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", + "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", + "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", + "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", + "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", + "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", + "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", + "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", + "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", + "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", + "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", + "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", + "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", + "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", + "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", + "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", + "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", + "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", + "oneinferior", "twoinferior", "threeinferior", "fourinferior", + "fiveinferior", "sixinferior", "seveninferior", "eightinferior", + "nineinferior", "centinferior", "dollarinferior", "periodinferior", + "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", + "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", + "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", + "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", + "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", + "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", + "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", + "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", + "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold" +]; + +const NUM_STANDARD_CFF_STRINGS = 391; + +const CharstringValidationData = [ + /* 0 */ null, + /* 1 */ { id: "hstem", min: 2, stackClearing: true, stem: true }, + /* 2 */ null, + /* 3 */ { id: "vstem", min: 2, stackClearing: true, stem: true }, + /* 4 */ { id: "vmoveto", min: 1, stackClearing: true }, + /* 5 */ { id: "rlineto", min: 2, resetStack: true }, + /* 6 */ { id: "hlineto", min: 1, resetStack: true }, + /* 7 */ { id: "vlineto", min: 1, resetStack: true }, + /* 8 */ { id: "rrcurveto", min: 6, resetStack: true }, + /* 9 */ null, + /* 10 */ { id: "callsubr", min: 1, undefStack: true }, + /* 11 */ { id: "return", min: 0, undefStack: true }, + /* 12 */ null, + /* 13 */ null, + /* 14 */ { id: "endchar", min: 0, stackClearing: true }, + /* 15 */ null, + /* 16 */ null, + /* 17 */ null, + /* 18 */ { id: "hstemhm", min: 2, stackClearing: true, stem: true }, + /* 19 */ { id: "hintmask", min: 0, stackClearing: true }, + /* 20 */ { id: "cntrmask", min: 0, stackClearing: true }, + /* 21 */ { id: "rmoveto", min: 2, stackClearing: true }, + /* 22 */ { id: "hmoveto", min: 1, stackClearing: true }, + /* 23 */ { id: "vstemhm", min: 2, stackClearing: true, stem: true }, + /* 24 */ { id: "rcurveline", min: 8, resetStack: true }, + /* 25 */ { id: "rlinecurve", min: 8, resetStack: true }, + /* 26 */ { id: "vvcurveto", min: 4, resetStack: true }, + /* 27 */ { id: "hhcurveto", min: 4, resetStack: true }, + /* 28 */ null, // shortint + /* 29 */ { id: "callgsubr", min: 1, undefStack: true }, + /* 30 */ { id: "vhcurveto", min: 4, resetStack: true }, + /* 31 */ { id: "hvcurveto", min: 4, resetStack: true }, +]; + +const CharstringValidationData12 = [ + null, + null, + null, + { id: "and", min: 2, stackDelta: -1 }, + { id: "or", min: 2, stackDelta: -1 }, + { id: "not", min: 1, stackDelta: 0 }, + null, + null, + null, + { id: "abs", min: 1, stackDelta: 0 }, + { + id: "add", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] + stack[index - 1]; + }, + }, + { + id: "sub", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] - stack[index - 1]; + }, + }, + { + id: "div", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] / stack[index - 1]; + }, + }, + null, + { + id: "neg", + min: 1, + stackDelta: 0, + stackFn(stack, index) { + stack[index - 1] = -stack[index - 1]; + }, + }, + { id: "eq", min: 2, stackDelta: -1 }, + null, + null, + { id: "drop", min: 1, stackDelta: -1 }, + null, + { id: "put", min: 2, stackDelta: -2 }, + { id: "get", min: 1, stackDelta: 0 }, + { id: "ifelse", min: 4, stackDelta: -3 }, + { id: "random", min: 0, stackDelta: 1 }, + { + id: "mul", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] * stack[index - 1]; + }, + }, + null, + { id: "sqrt", min: 1, stackDelta: 0 }, + { id: "dup", min: 1, stackDelta: 1 }, + { id: "exch", min: 2, stackDelta: 0 }, + { id: "index", min: 2, stackDelta: 0 }, + { id: "roll", min: 3, stackDelta: -2 }, + null, + null, + null, + { id: "hflex", min: 7, resetStack: true }, + { id: "flex", min: 13, resetStack: true }, + { id: "hflex1", min: 9, resetStack: true }, + { id: "flex1", min: 11, resetStack: true }, +]; + +class CFFParser { + constructor(file, properties, seacAnalysisEnabled) { + this.bytes = file.getBytes(); + this.properties = properties; + this.seacAnalysisEnabled = !!seacAnalysisEnabled; + } + + parse() { + const properties = this.properties; + const cff = new CFF(); + this.cff = cff; + + // The first five sections must be in order, all the others are reached + // via offsets contained in one of the below. + const header = this.parseHeader(); + const nameIndex = this.parseIndex(header.endPos); + const topDictIndex = this.parseIndex(nameIndex.endPos); + const stringIndex = this.parseIndex(topDictIndex.endPos); + const globalSubrIndex = this.parseIndex(stringIndex.endPos); + + const topDictParsed = this.parseDict(topDictIndex.obj.get(0)); + const topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings); + + cff.header = header.obj; + cff.names = this.parseNameIndex(nameIndex.obj); + cff.strings = this.parseStringIndex(stringIndex.obj); + cff.topDict = topDict; + cff.globalSubrIndex = globalSubrIndex.obj; + + this.parsePrivateDict(cff.topDict); + + cff.isCIDFont = topDict.hasName("ROS"); + + const charStringOffset = topDict.getByName("CharStrings"); + const charStringIndex = this.parseIndex(charStringOffset).obj; + + const fontMatrix = topDict.getByName("FontMatrix"); + if (fontMatrix) { + properties.fontMatrix = fontMatrix; + } + + const fontBBox = topDict.getByName("FontBBox"); + if (fontBBox) { + // adjusting ascent/descent + properties.ascent = Math.max(fontBBox[3], fontBBox[1]); + properties.descent = Math.min(fontBBox[1], fontBBox[3]); + properties.ascentScaled = true; + } + + let charset, encoding; + if (cff.isCIDFont) { + const fdArrayIndex = this.parseIndex(topDict.getByName("FDArray")).obj; + for (let i = 0, ii = fdArrayIndex.count; i < ii; ++i) { + const dictRaw = fdArrayIndex.get(i); + const fontDict = this.createDict( + CFFTopDict, + this.parseDict(dictRaw), + cff.strings + ); + this.parsePrivateDict(fontDict); + cff.fdArray.push(fontDict); + } + // cid fonts don't have an encoding + encoding = null; + charset = this.parseCharsets( + topDict.getByName("charset"), + charStringIndex.count, + cff.strings, + true + ); + cff.fdSelect = this.parseFDSelect( + topDict.getByName("FDSelect"), + charStringIndex.count + ); + } else { + charset = this.parseCharsets( + topDict.getByName("charset"), + charStringIndex.count, + cff.strings, + false + ); + encoding = this.parseEncoding( + topDict.getByName("Encoding"), + properties, + cff.strings, + charset.charset + ); + } + + cff.charset = charset; + cff.encoding = encoding; + + const charStringsAndSeacs = this.parseCharStrings({ + charStrings: charStringIndex, + localSubrIndex: topDict.privateDict.subrsIndex, + globalSubrIndex: globalSubrIndex.obj, + fdSelect: cff.fdSelect, + fdArray: cff.fdArray, + privateDict: topDict.privateDict, + }); + cff.charStrings = charStringsAndSeacs.charStrings; + cff.seacs = charStringsAndSeacs.seacs; + cff.widths = charStringsAndSeacs.widths; + + return cff; + } + + parseHeader() { + let bytes = this.bytes; + const bytesLength = bytes.length; + let offset = 0; + + // Prevent an infinite loop, by checking that the offset is within the + // bounds of the bytes array. Necessary in empty, or invalid, font files. + while (offset < bytesLength && bytes[offset] !== 1) { + ++offset; + } + if (offset >= bytesLength) { + throw new FormatError("Invalid CFF header"); + } + if (offset !== 0) { + info("cff data is shifted"); + bytes = bytes.subarray(offset); + this.bytes = bytes; + } + const major = bytes[0]; + const minor = bytes[1]; + const hdrSize = bytes[2]; + const offSize = bytes[3]; + const header = new CFFHeader(major, minor, hdrSize, offSize); + return { obj: header, endPos: hdrSize }; + } + + parseDict(dict) { + let pos = 0; + + function parseOperand() { + let value = dict[pos++]; + if (value === 30) { + return parseFloatOperand(); + } else if (value === 28) { + value = readInt16(dict, pos); + pos += 2; + return value; + } else if (value === 29) { + value = dict[pos++]; + value = (value << 8) | dict[pos++]; + value = (value << 8) | dict[pos++]; + value = (value << 8) | dict[pos++]; + return value; + } else if (value >= 32 && value <= 246) { + return value - 139; + } else if (value >= 247 && value <= 250) { + return (value - 247) * 256 + dict[pos++] + 108; + } else if (value >= 251 && value <= 254) { + return -((value - 251) * 256) - dict[pos++] - 108; + } + warn('CFFParser_parseDict: "' + value + '" is a reserved command.'); + return NaN; + } + + function parseFloatOperand() { + let str = ""; + const eof = 15; + // prettier-ignore + const lookup = ["0", "1", "2", "3", "4", "5", "6", "7", "8", + "9", ".", "E", "E-", null, "-"]; + const length = dict.length; + while (pos < length) { + const b = dict[pos++]; + const b1 = b >> 4; + const b2 = b & 15; + + if (b1 === eof) { + break; + } + str += lookup[b1]; + + if (b2 === eof) { + break; + } + str += lookup[b2]; + } + return parseFloat(str); + } + + let operands = []; + const entries = []; + + pos = 0; + const end = dict.length; + while (pos < end) { + let b = dict[pos]; + if (b <= 21) { + if (b === 12) { + b = (b << 8) | dict[++pos]; + } + entries.push([b, operands]); + operands = []; + ++pos; + } else { + operands.push(parseOperand()); + } + } + return entries; + } + + parseIndex(pos) { + const cffIndex = new CFFIndex(); + const bytes = this.bytes; + const count = (bytes[pos++] << 8) | bytes[pos++]; + const offsets = []; + let end = pos; + let i, ii; + + if (count !== 0) { + const offsetSize = bytes[pos++]; + // add 1 for offset to determine size of last object + const startPos = pos + (count + 1) * offsetSize - 1; + + for (i = 0, ii = count + 1; i < ii; ++i) { + let offset = 0; + for (let j = 0; j < offsetSize; ++j) { + offset <<= 8; + offset += bytes[pos++]; + } + offsets.push(startPos + offset); + } + end = offsets[count]; + } + for (i = 0, ii = offsets.length - 1; i < ii; ++i) { + const offsetStart = offsets[i]; + const offsetEnd = offsets[i + 1]; + cffIndex.add(bytes.subarray(offsetStart, offsetEnd)); + } + return { obj: cffIndex, endPos: end }; + } + + parseNameIndex(index) { + const names = []; + for (let i = 0, ii = index.count; i < ii; ++i) { + const name = index.get(i); + names.push(bytesToString(name)); + } + return names; + } + + parseStringIndex(index) { + const strings = new CFFStrings(); + for (let i = 0, ii = index.count; i < ii; ++i) { + const data = index.get(i); + strings.add(bytesToString(data)); + } + return strings; + } + + createDict(Type, dict, strings) { + const cffDict = new Type(strings); + for (const [key, value] of dict) { + cffDict.setByKey(key, value); + } + return cffDict; + } + + parseCharString(state, data, localSubrIndex, globalSubrIndex) { + if (!data || state.callDepth > MAX_SUBR_NESTING) { + return false; + } + let stackSize = state.stackSize; + const stack = state.stack; + + let length = data.length; + + for (let j = 0; j < length; ) { + const value = data[j++]; + let validationCommand = null; + if (value === 12) { + const q = data[j++]; + if (q === 0) { + // The CFF specification state that the 'dotsection' command + // (12, 0) is deprecated and treated as a no-op, but all Type2 + // charstrings processors should support them. Unfortunately + // the font sanitizer don't. As a workaround the sequence (12, 0) + // is replaced by a useless (0, hmoveto). + data[j - 2] = 139; + data[j - 1] = 22; + stackSize = 0; + } else { + validationCommand = CharstringValidationData12[q]; + } + } else if (value === 28) { + // number (16 bit) + stack[stackSize] = readInt16(data, j); + j += 2; + stackSize++; + } else if (value === 14) { + if (stackSize >= 4) { + stackSize -= 4; + if (this.seacAnalysisEnabled) { + state.seac = stack.slice(stackSize, stackSize + 4); + return false; + } + } + validationCommand = CharstringValidationData[value]; + } else if (value >= 32 && value <= 246) { + // number + stack[stackSize] = value - 139; + stackSize++; + } else if (value >= 247 && value <= 254) { + // number (+1 bytes) + stack[stackSize] = + value < 251 + ? ((value - 247) << 8) + data[j] + 108 + : -((value - 251) << 8) - data[j] - 108; + j++; + stackSize++; + } else if (value === 255) { + // number (32 bit) + stack[stackSize] = + ((data[j] << 24) | + (data[j + 1] << 16) | + (data[j + 2] << 8) | + data[j + 3]) / + 65536; + j += 4; + stackSize++; + } else if (value === 19 || value === 20) { + state.hints += stackSize >> 1; + if (state.hints === 0) { + // Not a valid value (see bug 1529502): just remove it. + data.copyWithin(j - 1, j, -1); + j -= 1; + length -= 1; + continue; + } + // skipping right amount of hints flag data + j += (state.hints + 7) >> 3; + stackSize %= 2; + validationCommand = CharstringValidationData[value]; + } else if (value === 10 || value === 29) { + const subrsIndex = value === 10 ? localSubrIndex : globalSubrIndex; + if (!subrsIndex) { + validationCommand = CharstringValidationData[value]; + warn("Missing subrsIndex for " + validationCommand.id); + return false; + } + let bias = 32768; + if (subrsIndex.count < 1240) { + bias = 107; + } else if (subrsIndex.count < 33900) { + bias = 1131; + } + const subrNumber = stack[--stackSize] + bias; + if ( + subrNumber < 0 || + subrNumber >= subrsIndex.count || + isNaN(subrNumber) + ) { + validationCommand = CharstringValidationData[value]; + warn("Out of bounds subrIndex for " + validationCommand.id); + return false; + } + state.stackSize = stackSize; + state.callDepth++; + const valid = this.parseCharString( + state, + subrsIndex.get(subrNumber), + localSubrIndex, + globalSubrIndex + ); + if (!valid) { + return false; + } + state.callDepth--; + stackSize = state.stackSize; + continue; + } else if (value === 11) { + state.stackSize = stackSize; + return true; + } else if (value === 0 && j === data.length) { + // Operator 0 is not used according to the current spec and + // it's the last char and consequently it's likely a terminator. + // So just replace it by endchar command to make OTS happy. + data[j - 1] = 14; + validationCommand = CharstringValidationData[14]; + } else if (value === 9) { + // Not a valid value. + data.copyWithin(j - 1, j, -1); + j -= 1; + length -= 1; + continue; + } else { + validationCommand = CharstringValidationData[value]; + } + if (validationCommand) { + if (validationCommand.stem) { + state.hints += stackSize >> 1; + if (value === 3 || value === 23) { + // vstem or vstemhm. + state.hasVStems = true; + } else if (state.hasVStems && (value === 1 || value === 18)) { + // Some browsers don't draw glyphs that specify vstems before + // hstems. As a workaround, replace hstem (1) and hstemhm (18) + // with a pointless vstem (3) or vstemhm (23). + warn("CFF stem hints are in wrong order"); + data[j - 1] = value === 1 ? 3 : 23; + } + } + if ("min" in validationCommand) { + if (!state.undefStack && stackSize < validationCommand.min) { + warn( + "Not enough parameters for " + + validationCommand.id + + "; actual: " + + stackSize + + ", expected: " + + validationCommand.min + ); + + if (stackSize === 0) { + // Just "fix" the outline in replacing command by a endchar: + // it could lead to wrong rendering of some glyphs or not. + // For example, the pdf in #6132 is well-rendered. + data[j - 1] = 14; + return true; + } + return false; + } + } + if (state.firstStackClearing && validationCommand.stackClearing) { + state.firstStackClearing = false; + // the optional character width can be found before the first + // stack-clearing command arguments + stackSize -= validationCommand.min; + if (stackSize >= 2 && validationCommand.stem) { + // there are even amount of arguments for stem commands + stackSize %= 2; + } else if (stackSize > 1) { + warn("Found too many parameters for stack-clearing command"); + } + if (stackSize > 0) { + // Width can be any number since its the difference + // from nominalWidthX. + state.width = stack[stackSize - 1]; + } + } + if ("stackDelta" in validationCommand) { + if ("stackFn" in validationCommand) { + validationCommand.stackFn(stack, stackSize); + } + stackSize += validationCommand.stackDelta; + } else if (validationCommand.stackClearing) { + stackSize = 0; + } else if (validationCommand.resetStack) { + stackSize = 0; + state.undefStack = false; + } else if (validationCommand.undefStack) { + stackSize = 0; + state.undefStack = true; + state.firstStackClearing = false; + } + } + } + if (length < data.length) { + data.fill(/* endchar = */ 14, length); + } + state.stackSize = stackSize; + return true; + } + + parseCharStrings({ + charStrings, + localSubrIndex, + globalSubrIndex, + fdSelect, + fdArray, + privateDict, + }) { + const seacs = []; + const widths = []; + const count = charStrings.count; + for (let i = 0; i < count; i++) { + const charstring = charStrings.get(i); + const state = { + callDepth: 0, + stackSize: 0, + stack: [], + undefStack: true, + hints: 0, + firstStackClearing: true, + seac: null, + width: null, + hasVStems: false, + }; + let valid = true; + let localSubrToUse = null; + let privateDictToUse = privateDict; + if (fdSelect && fdArray.length) { + const fdIndex = fdSelect.getFDIndex(i); + if (fdIndex === -1) { + warn("Glyph index is not in fd select."); + valid = false; + } + if (fdIndex >= fdArray.length) { + warn("Invalid fd index for glyph index."); + valid = false; + } + if (valid) { + privateDictToUse = fdArray[fdIndex].privateDict; + localSubrToUse = privateDictToUse.subrsIndex; + } + } else if (localSubrIndex) { + localSubrToUse = localSubrIndex; + } + if (valid) { + valid = this.parseCharString( + state, + charstring, + localSubrToUse, + globalSubrIndex + ); + } + if (state.width !== null) { + const nominalWidth = privateDictToUse.getByName("nominalWidthX"); + widths[i] = nominalWidth + state.width; + } else { + const defaultWidth = privateDictToUse.getByName("defaultWidthX"); + widths[i] = defaultWidth; + } + if (state.seac !== null) { + seacs[i] = state.seac; + } + if (!valid) { + // resetting invalid charstring to single 'endchar' + charStrings.set(i, new Uint8Array([14])); + } + } + return { charStrings, seacs, widths }; + } + + emptyPrivateDictionary(parentDict) { + const privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings); + parentDict.setByKey(18, [0, 0]); + parentDict.privateDict = privateDict; + } + + parsePrivateDict(parentDict) { + // no private dict, do nothing + if (!parentDict.hasName("Private")) { + this.emptyPrivateDictionary(parentDict); + return; + } + const privateOffset = parentDict.getByName("Private"); + // make sure the params are formatted correctly + if (!Array.isArray(privateOffset) || privateOffset.length !== 2) { + parentDict.removeByName("Private"); + return; + } + const size = privateOffset[0]; + const offset = privateOffset[1]; + // remove empty dicts or ones that refer to invalid location + if (size === 0 || offset >= this.bytes.length) { + this.emptyPrivateDictionary(parentDict); + return; + } + + const privateDictEnd = offset + size; + const dictData = this.bytes.subarray(offset, privateDictEnd); + const dict = this.parseDict(dictData); + const privateDict = this.createDict( + CFFPrivateDict, + dict, + parentDict.strings + ); + parentDict.privateDict = privateDict; + + if (privateDict.getByName("ExpansionFactor") === 0) { + // Firefox doesn't render correctly such a font on Windows (see issue + // 15289), hence we just reset it to its default value. + privateDict.setByName("ExpansionFactor", 0.06); + } + + // Parse the Subrs index also since it's relative to the private dict. + if (!privateDict.getByName("Subrs")) { + return; + } + const subrsOffset = privateDict.getByName("Subrs"); + const relativeOffset = offset + subrsOffset; + // Validate the offset. + if (subrsOffset === 0 || relativeOffset >= this.bytes.length) { + this.emptyPrivateDictionary(parentDict); + return; + } + const subrsIndex = this.parseIndex(relativeOffset); + privateDict.subrsIndex = subrsIndex.obj; + } + + parseCharsets(pos, length, strings, cid) { + if (pos === 0) { + return new CFFCharset( + true, + CFFCharsetPredefinedTypes.ISO_ADOBE, + ISOAdobeCharset + ); + } else if (pos === 1) { + return new CFFCharset( + true, + CFFCharsetPredefinedTypes.EXPERT, + ExpertCharset + ); + } else if (pos === 2) { + return new CFFCharset( + true, + CFFCharsetPredefinedTypes.EXPERT_SUBSET, + ExpertSubsetCharset + ); + } + + const bytes = this.bytes; + const start = pos; + const format = bytes[pos++]; + const charset = [cid ? 0 : ".notdef"]; + let id, count, i; + + // subtract 1 for the .notdef glyph + length -= 1; + + switch (format) { + case 0: + for (i = 0; i < length; i++) { + id = (bytes[pos++] << 8) | bytes[pos++]; + charset.push(cid ? id : strings.get(id)); + } + break; + case 1: + while (charset.length <= length) { + id = (bytes[pos++] << 8) | bytes[pos++]; + count = bytes[pos++]; + for (i = 0; i <= count; i++) { + charset.push(cid ? id++ : strings.get(id++)); + } + } + break; + case 2: + while (charset.length <= length) { + id = (bytes[pos++] << 8) | bytes[pos++]; + count = (bytes[pos++] << 8) | bytes[pos++]; + for (i = 0; i <= count; i++) { + charset.push(cid ? id++ : strings.get(id++)); + } + } + break; + default: + throw new FormatError("Unknown charset format"); + } + // Raw won't be needed if we actually compile the charset. + const end = pos; + const raw = bytes.subarray(start, end); + + return new CFFCharset(false, format, charset, raw); + } + + parseEncoding(pos, properties, strings, charset) { + const encoding = Object.create(null); + const bytes = this.bytes; + let predefined = false; + let format, i, ii; + let raw = null; + + function readSupplement() { + const supplementsCount = bytes[pos++]; + for (i = 0; i < supplementsCount; i++) { + const code = bytes[pos++]; + const sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); + encoding[code] = charset.indexOf(strings.get(sid)); + } + } + + if (pos === 0 || pos === 1) { + predefined = true; + format = pos; + const baseEncoding = pos ? ExpertEncoding : StandardEncoding; + for (i = 0, ii = charset.length; i < ii; i++) { + const index = baseEncoding.indexOf(charset[i]); + if (index !== -1) { + encoding[index] = i; + } + } + } else { + const dataStart = pos; + format = bytes[pos++]; + switch (format & 0x7f) { + case 0: + const glyphsCount = bytes[pos++]; + for (i = 1; i <= glyphsCount; i++) { + encoding[bytes[pos++]] = i; + } + break; + + case 1: + const rangesCount = bytes[pos++]; + let gid = 1; + for (i = 0; i < rangesCount; i++) { + const start = bytes[pos++]; + const left = bytes[pos++]; + for (let j = start; j <= start + left; j++) { + encoding[j] = gid++; + } + } + break; + + default: + throw new FormatError(`Unknown encoding format: ${format} in CFF`); + } + const dataEnd = pos; + if (format & 0x80) { + // hasSupplement + // The font sanitizer does not support CFF encoding with a + // supplement, since the encoding is not really used to map + // between gid to glyph, let's overwrite what is declared in + // the top dictionary to let the sanitizer think the font use + // StandardEncoding, that's a lie but that's ok. + bytes[dataStart] &= 0x7f; + readSupplement(); + } + raw = bytes.subarray(dataStart, dataEnd); + } + format &= 0x7f; + return new CFFEncoding(predefined, format, encoding, raw); + } + + parseFDSelect(pos, length) { + const bytes = this.bytes; + const format = bytes[pos++]; + const fdSelect = []; + let i; + + switch (format) { + case 0: + for (i = 0; i < length; ++i) { + const id = bytes[pos++]; + fdSelect.push(id); + } + break; + case 3: + const rangesCount = (bytes[pos++] << 8) | bytes[pos++]; + for (i = 0; i < rangesCount; ++i) { + let first = (bytes[pos++] << 8) | bytes[pos++]; + if (i === 0 && first !== 0) { + warn( + "parseFDSelect: The first range must have a first GID of 0" + + " -- trying to recover." + ); + first = 0; + } + const fdIndex = bytes[pos++]; + const next = (bytes[pos] << 8) | bytes[pos + 1]; + for (let j = first; j < next; ++j) { + fdSelect.push(fdIndex); + } + } + // Advance past the sentinel(next). + pos += 2; + break; + default: + throw new FormatError(`parseFDSelect: Unknown format "${format}".`); + } + if (fdSelect.length !== length) { + throw new FormatError("parseFDSelect: Invalid font data."); + } + + return new CFFFDSelect(format, fdSelect); + } +} + +// Compact Font Format +class CFF { + constructor() { + this.header = null; + this.names = []; + this.topDict = null; + this.strings = new CFFStrings(); + this.globalSubrIndex = null; + + // The following could really be per font, but since we only have one font + // store them here. + this.encoding = null; + this.charset = null; + this.charStrings = null; + this.fdArray = []; + this.fdSelect = null; + + this.isCIDFont = false; + } + + duplicateFirstGlyph() { + // Browsers will not display a glyph at position 0. Typically glyph 0 is + // notdef, but a number of fonts put a valid glyph there so it must be + // duplicated and appended. + if (this.charStrings.count >= 65535) { + warn("Not enough space in charstrings to duplicate first glyph."); + return; + } + const glyphZero = this.charStrings.get(0); + this.charStrings.add(glyphZero); + if (this.isCIDFont) { + this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0]); + } + } + + hasGlyphId(id) { + if (id < 0 || id >= this.charStrings.count) { + return false; + } + const glyph = this.charStrings.get(id); + return glyph.length > 0; + } +} + +class CFFHeader { + constructor(major, minor, hdrSize, offSize) { + this.major = major; + this.minor = minor; + this.hdrSize = hdrSize; + this.offSize = offSize; + } +} + +class CFFStrings { + constructor() { + this.strings = []; + } + + get(index) { + if (index >= 0 && index <= NUM_STANDARD_CFF_STRINGS - 1) { + return CFFStandardStrings[index]; + } + if (index - NUM_STANDARD_CFF_STRINGS <= this.strings.length) { + return this.strings[index - NUM_STANDARD_CFF_STRINGS]; + } + return CFFStandardStrings[0]; + } + + getSID(str) { + let index = CFFStandardStrings.indexOf(str); + if (index !== -1) { + return index; + } + index = this.strings.indexOf(str); + if (index !== -1) { + return index + NUM_STANDARD_CFF_STRINGS; + } + return -1; + } + + add(value) { + this.strings.push(value); + } + + get count() { + return this.strings.length; + } +} + +class CFFIndex { + constructor() { + this.objects = []; + this.length = 0; + } + + add(data) { + this.length += data.length; + this.objects.push(data); + } + + set(index, data) { + this.length += data.length - this.objects[index].length; + this.objects[index] = data; + } + + get(index) { + return this.objects[index]; + } + + get count() { + return this.objects.length; + } +} + +class CFFDict { + constructor(tables, strings) { + this.keyToNameMap = tables.keyToNameMap; + this.nameToKeyMap = tables.nameToKeyMap; + this.defaults = tables.defaults; + this.types = tables.types; + this.opcodes = tables.opcodes; + this.order = tables.order; + this.strings = strings; + this.values = Object.create(null); + } + + // value should always be an array + setByKey(key, value) { + if (!(key in this.keyToNameMap)) { + return false; + } + // ignore empty values + if (value.length === 0) { + return true; + } + // Ignore invalid values (fixes bug1068432.pdf and bug1308536.pdf). + for (const val of value) { + if (isNaN(val)) { + warn(`Invalid CFFDict value: "${value}" for key "${key}".`); + return true; + } + } + const type = this.types[key]; + // remove the array wrapping these types of values + if (type === "num" || type === "sid" || type === "offset") { + value = value[0]; + } + this.values[key] = value; + return true; + } + + setByName(name, value) { + if (!(name in this.nameToKeyMap)) { + throw new FormatError(`Invalid dictionary name "${name}"`); + } + this.values[this.nameToKeyMap[name]] = value; + } + + hasName(name) { + return this.nameToKeyMap[name] in this.values; + } + + getByName(name) { + if (!(name in this.nameToKeyMap)) { + throw new FormatError(`Invalid dictionary name ${name}"`); + } + const key = this.nameToKeyMap[name]; + if (!(key in this.values)) { + return this.defaults[key]; + } + return this.values[key]; + } + + removeByName(name) { + delete this.values[this.nameToKeyMap[name]]; + } + + static createTables(layout) { + const tables = { + keyToNameMap: {}, + nameToKeyMap: {}, + defaults: {}, + types: {}, + opcodes: {}, + order: [], + }; + for (const entry of layout) { + const key = Array.isArray(entry[0]) + ? (entry[0][0] << 8) + entry[0][1] + : entry[0]; + tables.keyToNameMap[key] = entry[1]; + tables.nameToKeyMap[entry[1]] = key; + tables.types[key] = entry[2]; + tables.defaults[key] = entry[3]; + tables.opcodes[key] = Array.isArray(entry[0]) ? entry[0] : [entry[0]]; + tables.order.push(key); + } + return tables; + } +} + +const CFFTopDictLayout = [ + [[12, 30], "ROS", ["sid", "sid", "num"], null], + [[12, 20], "SyntheticBase", "num", null], + [0, "version", "sid", null], + [1, "Notice", "sid", null], + [[12, 0], "Copyright", "sid", null], + [2, "FullName", "sid", null], + [3, "FamilyName", "sid", null], + [4, "Weight", "sid", null], + [[12, 1], "isFixedPitch", "num", 0], + [[12, 2], "ItalicAngle", "num", 0], + [[12, 3], "UnderlinePosition", "num", -100], + [[12, 4], "UnderlineThickness", "num", 50], + [[12, 5], "PaintType", "num", 0], + [[12, 6], "CharstringType", "num", 2], + // prettier-ignore + [[12, 7], "FontMatrix", ["num", "num", "num", "num", "num", "num"], + [0.001, 0, 0, 0.001, 0, 0]], + [13, "UniqueID", "num", null], + [5, "FontBBox", ["num", "num", "num", "num"], [0, 0, 0, 0]], + [[12, 8], "StrokeWidth", "num", 0], + [14, "XUID", "array", null], + [15, "charset", "offset", 0], + [16, "Encoding", "offset", 0], + [17, "CharStrings", "offset", 0], + [18, "Private", ["offset", "offset"], null], + [[12, 21], "PostScript", "sid", null], + [[12, 22], "BaseFontName", "sid", null], + [[12, 23], "BaseFontBlend", "delta", null], + [[12, 31], "CIDFontVersion", "num", 0], + [[12, 32], "CIDFontRevision", "num", 0], + [[12, 33], "CIDFontType", "num", 0], + [[12, 34], "CIDCount", "num", 8720], + [[12, 35], "UIDBase", "num", null], + // XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes + // before FDArray. + [[12, 37], "FDSelect", "offset", null], + [[12, 36], "FDArray", "offset", null], + [[12, 38], "FontName", "sid", null], +]; + +class CFFTopDict extends CFFDict { + static get tables() { + return shadow(this, "tables", this.createTables(CFFTopDictLayout)); + } + + constructor(strings) { + super(CFFTopDict.tables, strings); + this.privateDict = null; + } +} + +const CFFPrivateDictLayout = [ + [6, "BlueValues", "delta", null], + [7, "OtherBlues", "delta", null], + [8, "FamilyBlues", "delta", null], + [9, "FamilyOtherBlues", "delta", null], + [[12, 9], "BlueScale", "num", 0.039625], + [[12, 10], "BlueShift", "num", 7], + [[12, 11], "BlueFuzz", "num", 1], + [10, "StdHW", "num", null], + [11, "StdVW", "num", null], + [[12, 12], "StemSnapH", "delta", null], + [[12, 13], "StemSnapV", "delta", null], + [[12, 14], "ForceBold", "num", 0], + [[12, 17], "LanguageGroup", "num", 0], + [[12, 18], "ExpansionFactor", "num", 0.06], + [[12, 19], "initialRandomSeed", "num", 0], + [20, "defaultWidthX", "num", 0], + [21, "nominalWidthX", "num", 0], + [19, "Subrs", "offset", null], +]; + +class CFFPrivateDict extends CFFDict { + static get tables() { + return shadow(this, "tables", this.createTables(CFFPrivateDictLayout)); + } + + constructor(strings) { + super(CFFPrivateDict.tables, strings); + this.subrsIndex = null; + } +} + +const CFFCharsetPredefinedTypes = { + ISO_ADOBE: 0, + EXPERT: 1, + EXPERT_SUBSET: 2, +}; + +class CFFCharset { + constructor(predefined, format, charset, raw) { + this.predefined = predefined; + this.format = format; + this.charset = charset; + this.raw = raw; + } +} + +class CFFEncoding { + constructor(predefined, format, encoding, raw) { + this.predefined = predefined; + this.format = format; + this.encoding = encoding; + this.raw = raw; + } +} + +class CFFFDSelect { + constructor(format, fdSelect) { + this.format = format; + this.fdSelect = fdSelect; + } + + getFDIndex(glyphIndex) { + if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) { + return -1; + } + return this.fdSelect[glyphIndex]; + } +} + +// Helper class to keep track of where an offset is within the data and helps +// filling in that offset once it's known. +class CFFOffsetTracker { + constructor() { + this.offsets = Object.create(null); + } + + isTracking(key) { + return key in this.offsets; + } + + track(key, location) { + if (key in this.offsets) { + throw new FormatError(`Already tracking location of ${key}`); + } + this.offsets[key] = location; + } + + offset(value) { + for (const key in this.offsets) { + this.offsets[key] += value; + } + } + + setEntryLocation(key, values, output) { + if (!(key in this.offsets)) { + throw new FormatError(`Not tracking location of ${key}`); + } + const data = output.data; + const dataOffset = this.offsets[key]; + const size = 5; + for (let i = 0, ii = values.length; i < ii; ++i) { + const offset0 = i * size + dataOffset; + const offset1 = offset0 + 1; + const offset2 = offset0 + 2; + const offset3 = offset0 + 3; + const offset4 = offset0 + 4; + // It's easy to screw up offsets so perform this sanity check. + if ( + data[offset0] !== 0x1d || + data[offset1] !== 0 || + data[offset2] !== 0 || + data[offset3] !== 0 || + data[offset4] !== 0 + ) { + throw new FormatError("writing to an offset that is not empty"); + } + const value = values[i]; + data[offset0] = 0x1d; + data[offset1] = (value >> 24) & 0xff; + data[offset2] = (value >> 16) & 0xff; + data[offset3] = (value >> 8) & 0xff; + data[offset4] = value & 0xff; + } + } +} + +// Takes a CFF and converts it to the binary representation. +class CFFCompiler { + constructor(cff) { + this.cff = cff; + } + + compile() { + const cff = this.cff; + const output = { + data: [], + length: 0, + add(data) { + try { + // It's possible to exceed the call stack maximum size when trying + // to push too much elements. + // In case of failure, we fallback to the `concat` method. + this.data.push(...data); + } catch { + this.data = this.data.concat(data); + } + this.length = this.data.length; + }, + }; + + // Compile the five entries that must be in order. + const header = this.compileHeader(cff.header); + output.add(header); + + const nameIndex = this.compileNameIndex(cff.names); + output.add(nameIndex); + + if (cff.isCIDFont) { + // The spec is unclear on how font matrices should relate to each other + // when there is one in the main top dict and the sub top dicts. + // Windows handles this differently than linux and osx so we have to + // normalize to work on all. + // Rules based off of some mailing list discussions: + // - If main font has a matrix and subfont doesn't, use the main matrix. + // - If no main font matrix and there is a subfont matrix, use the + // subfont matrix. + // - If both have matrices, concat together. + // - If neither have matrices, use default. + // To make this work on all platforms we move the top matrix into each + // sub top dict and concat if necessary. + if (cff.topDict.hasName("FontMatrix")) { + const base = cff.topDict.getByName("FontMatrix"); + cff.topDict.removeByName("FontMatrix"); + for (const subDict of cff.fdArray) { + let matrix = base.slice(0); + if (subDict.hasName("FontMatrix")) { + matrix = Util.transform(matrix, subDict.getByName("FontMatrix")); + } + subDict.setByName("FontMatrix", matrix); + } + } + } + + const xuid = cff.topDict.getByName("XUID"); + if (xuid?.length > 16) { + // Length of XUID array must not be greater than 16 (issue #12399). + cff.topDict.removeByName("XUID"); + } + + cff.topDict.setByName("charset", 0); + let compiled = this.compileTopDicts( + [cff.topDict], + output.length, + cff.isCIDFont + ); + output.add(compiled.output); + const topDictTracker = compiled.trackers[0]; + + const stringIndex = this.compileStringIndex(cff.strings.strings); + output.add(stringIndex); + + const globalSubrIndex = this.compileIndex(cff.globalSubrIndex); + output.add(globalSubrIndex); + + // Now start on the other entries that have no specific order. + if (cff.encoding && cff.topDict.hasName("Encoding")) { + if (cff.encoding.predefined) { + topDictTracker.setEntryLocation( + "Encoding", + [cff.encoding.format], + output + ); + } else { + const encoding = this.compileEncoding(cff.encoding); + topDictTracker.setEntryLocation("Encoding", [output.length], output); + output.add(encoding); + } + } + const charset = this.compileCharset( + cff.charset, + cff.charStrings.count, + cff.strings, + cff.isCIDFont + ); + topDictTracker.setEntryLocation("charset", [output.length], output); + output.add(charset); + + const charStrings = this.compileCharStrings(cff.charStrings); + topDictTracker.setEntryLocation("CharStrings", [output.length], output); + output.add(charStrings); + + if (cff.isCIDFont) { + // For some reason FDSelect must be in front of FDArray on windows. OSX + // and linux don't seem to care. + topDictTracker.setEntryLocation("FDSelect", [output.length], output); + const fdSelect = this.compileFDSelect(cff.fdSelect); + output.add(fdSelect); + // It is unclear if the sub font dictionary can have CID related + // dictionary keys, but the sanitizer doesn't like them so remove them. + compiled = this.compileTopDicts(cff.fdArray, output.length, true); + topDictTracker.setEntryLocation("FDArray", [output.length], output); + output.add(compiled.output); + const fontDictTrackers = compiled.trackers; + + this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output); + } + + this.compilePrivateDicts([cff.topDict], [topDictTracker], output); + + // If the font data ends with INDEX whose object data is zero-length, + // the sanitizer will bail out. Add a dummy byte to avoid that. + output.add([0]); + + return output.data; + } + + encodeNumber(value) { + if (Number.isInteger(value)) { + return this.encodeInteger(value); + } + return this.encodeFloat(value); + } + + static get EncodeFloatRegExp() { + return shadow( + this, + "EncodeFloatRegExp", + /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/ + ); + } + + encodeFloat(num) { + let value = num.toString(); + + // Rounding inaccurate doubles. + const m = CFFCompiler.EncodeFloatRegExp.exec(value); + if (m) { + const epsilon = parseFloat("1e" + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(num * epsilon) / epsilon).toString(); + } + + let nibbles = ""; + let i, ii; + for (i = 0, ii = value.length; i < ii; ++i) { + const a = value[i]; + if (a === "e") { + nibbles += value[++i] === "-" ? "c" : "b"; + } else if (a === ".") { + nibbles += "a"; + } else if (a === "-") { + nibbles += "e"; + } else { + nibbles += a; + } + } + nibbles += nibbles.length & 1 ? "f" : "ff"; + const out = [30]; + for (i = 0, ii = nibbles.length; i < ii; i += 2) { + out.push(parseInt(nibbles.substring(i, i + 2), 16)); + } + return out; + } + + encodeInteger(value) { + let code; + if (value >= -107 && value <= 107) { + code = [value + 139]; + } else if (value >= 108 && value <= 1131) { + value -= 108; + code = [(value >> 8) + 247, value & 0xff]; + } else if (value >= -1131 && value <= -108) { + value = -value - 108; + code = [(value >> 8) + 251, value & 0xff]; + } else if (value >= -32768 && value <= 32767) { + code = [0x1c, (value >> 8) & 0xff, value & 0xff]; + } else { + code = [ + 0x1d, + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff, + ]; + } + return code; + } + + compileHeader(header) { + // `header.hdrSize` can be any value but we only write 4 values + // so header size is 4 (prevents OTS from rejecting the font). + return [header.major, header.minor, 4, header.offSize]; + } + + compileNameIndex(names) { + const nameIndex = new CFFIndex(); + for (const name of names) { + // OTS doesn't allow names to be over 127 characters. + const length = Math.min(name.length, 127); + let sanitizedName = new Array(length); + for (let j = 0; j < length; j++) { + // OTS requires chars to be between a range and not certain other + // chars. + let char = name[j]; + if ( + char < "!" || + char > "~" || + char === "[" || + char === "]" || + char === "(" || + char === ")" || + char === "{" || + char === "}" || + char === "<" || + char === ">" || + char === "/" || + char === "%" + ) { + char = "_"; + } + sanitizedName[j] = char; + } + sanitizedName = sanitizedName.join(""); + + if (sanitizedName === "") { + sanitizedName = "Bad_Font_Name"; + } + nameIndex.add(stringToBytes(sanitizedName)); + } + return this.compileIndex(nameIndex); + } + + compileTopDicts(dicts, length, removeCidKeys) { + const fontDictTrackers = []; + let fdArrayIndex = new CFFIndex(); + for (const fontDict of dicts) { + if (removeCidKeys) { + fontDict.removeByName("CIDFontVersion"); + fontDict.removeByName("CIDFontRevision"); + fontDict.removeByName("CIDFontType"); + fontDict.removeByName("CIDCount"); + fontDict.removeByName("UIDBase"); + } + const fontDictTracker = new CFFOffsetTracker(); + const fontDictData = this.compileDict(fontDict, fontDictTracker); + fontDictTrackers.push(fontDictTracker); + fdArrayIndex.add(fontDictData); + fontDictTracker.offset(length); + } + fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers); + return { + trackers: fontDictTrackers, + output: fdArrayIndex, + }; + } + + compilePrivateDicts(dicts, trackers, output) { + for (let i = 0, ii = dicts.length; i < ii; ++i) { + const fontDict = dicts[i]; + const privateDict = fontDict.privateDict; + if (!privateDict || !fontDict.hasName("Private")) { + throw new FormatError("There must be a private dictionary."); + } + const privateDictTracker = new CFFOffsetTracker(); + const privateDictData = this.compileDict(privateDict, privateDictTracker); + + let outputLength = output.length; + privateDictTracker.offset(outputLength); + if (!privateDictData.length) { + // The private dictionary was empty, set the output length to zero to + // ensure the offset length isn't out of bounds in the eyes of the + // sanitizer. + outputLength = 0; + } + + trackers[i].setEntryLocation( + "Private", + [privateDictData.length, outputLength], + output + ); + output.add(privateDictData); + + if (privateDict.subrsIndex && privateDict.hasName("Subrs")) { + const subrs = this.compileIndex(privateDict.subrsIndex); + privateDictTracker.setEntryLocation( + "Subrs", + [privateDictData.length], + output + ); + output.add(subrs); + } + } + } + + compileDict(dict, offsetTracker) { + const out = []; + // The dictionary keys must be in a certain order. + for (const key of dict.order) { + if (!(key in dict.values)) { + continue; + } + let values = dict.values[key]; + let types = dict.types[key]; + if (!Array.isArray(types)) { + types = [types]; + } + if (!Array.isArray(values)) { + values = [values]; + } + + // Remove any empty dict values. + if (values.length === 0) { + continue; + } + + for (let j = 0, jj = types.length; j < jj; ++j) { + const type = types[j]; + const value = values[j]; + switch (type) { + case "num": + case "sid": + out.push(...this.encodeNumber(value)); + break; + case "offset": + // For offsets we just insert a 32bit integer so we don't have to + // deal with figuring out the length of the offset when it gets + // replaced later on by the compiler. + const name = dict.keyToNameMap[key]; + // Some offsets have the offset and the length, so just record the + // position of the first one. + if (!offsetTracker.isTracking(name)) { + offsetTracker.track(name, out.length); + } + out.push(0x1d, 0, 0, 0, 0); + break; + case "array": + case "delta": + out.push(...this.encodeNumber(value)); + for (let k = 1, kk = values.length; k < kk; ++k) { + out.push(...this.encodeNumber(values[k])); + } + break; + default: + throw new FormatError(`Unknown data type of ${type}`); + } + } + out.push(...dict.opcodes[key]); + } + return out; + } + + compileStringIndex(strings) { + const stringIndex = new CFFIndex(); + for (const string of strings) { + stringIndex.add(stringToBytes(string)); + } + return this.compileIndex(stringIndex); + } + + compileCharStrings(charStrings) { + const charStringsIndex = new CFFIndex(); + for (let i = 0; i < charStrings.count; i++) { + const glyph = charStrings.get(i); + // If the CharString outline is empty, replace it with .notdef to + // prevent OTS from rejecting the font (fixes bug1252420.pdf). + if (glyph.length === 0) { + charStringsIndex.add(new Uint8Array([0x8b, 0x0e])); + continue; + } + charStringsIndex.add(glyph); + } + return this.compileIndex(charStringsIndex); + } + + compileCharset(charset, numGlyphs, strings, isCIDFont) { + // Freetype requires the number of charset strings be correct and MacOS + // requires a valid mapping for printing. + let out; + const numGlyphsLessNotDef = numGlyphs - 1; + if (isCIDFont) { + // In a CID font, the charset is a mapping of CIDs not SIDs so just + // create an identity mapping. + out = new Uint8Array([ + 2, // format + 0, // first CID upper byte + 0, // first CID lower byte + (numGlyphsLessNotDef >> 8) & 0xff, + numGlyphsLessNotDef & 0xff, + ]); + } else { + const length = 1 + numGlyphsLessNotDef * 2; + out = new Uint8Array(length); + out[0] = 0; // format 0 + let charsetIndex = 0; + const numCharsets = charset.charset.length; + let warned = false; + for (let i = 1; i < out.length; i += 2) { + let sid = 0; + if (charsetIndex < numCharsets) { + const name = charset.charset[charsetIndex++]; + sid = strings.getSID(name); + if (sid === -1) { + sid = 0; + if (!warned) { + warned = true; + warn(`Couldn't find ${name} in CFF strings`); + } + } + } + out[i] = (sid >> 8) & 0xff; + out[i + 1] = sid & 0xff; + } + } + return this.compileTypedArray(out); + } + + compileEncoding(encoding) { + return this.compileTypedArray(encoding.raw); + } + + compileFDSelect(fdSelect) { + const format = fdSelect.format; + let out, i; + switch (format) { + case 0: + out = new Uint8Array(1 + fdSelect.fdSelect.length); + out[0] = format; + for (i = 0; i < fdSelect.fdSelect.length; i++) { + out[i + 1] = fdSelect.fdSelect[i]; + } + break; + case 3: + const start = 0; + let lastFD = fdSelect.fdSelect[0]; + const ranges = [ + format, + 0, // nRanges place holder + 0, // nRanges place holder + (start >> 8) & 0xff, + start & 0xff, + lastFD, + ]; + for (i = 1; i < fdSelect.fdSelect.length; i++) { + const currentFD = fdSelect.fdSelect[i]; + if (currentFD !== lastFD) { + ranges.push((i >> 8) & 0xff, i & 0xff, currentFD); + lastFD = currentFD; + } + } + // 3 bytes are pushed for every range and there are 3 header bytes. + const numRanges = (ranges.length - 3) / 3; + ranges[1] = (numRanges >> 8) & 0xff; + ranges[2] = numRanges & 0xff; + // sentinel + ranges.push((i >> 8) & 0xff, i & 0xff); + out = new Uint8Array(ranges); + break; + } + return this.compileTypedArray(out); + } + + compileTypedArray(data) { + return Array.from(data); + } + + compileIndex(index, trackers = []) { + const objects = index.objects; + // First 2 bytes contains the number of objects contained into this index + const count = objects.length; + + // If there is no object, just create an index. + if (count === 0) { + return [0, 0]; + } + + const data = [(count >> 8) & 0xff, count & 0xff]; + + let lastOffset = 1, + i; + for (i = 0; i < count; ++i) { + lastOffset += objects[i].length; + } + + let offsetSize; + if (lastOffset < 0x100) { + offsetSize = 1; + } else if (lastOffset < 0x10000) { + offsetSize = 2; + } else if (lastOffset < 0x1000000) { + offsetSize = 3; + } else { + offsetSize = 4; + } + + // Next byte contains the offset size use to reference object in the file + data.push(offsetSize); + + // Add another offset after this one because we need a new offset + let relativeOffset = 1; + for (i = 0; i < count + 1; i++) { + if (offsetSize === 1) { + data.push(relativeOffset & 0xff); + } else if (offsetSize === 2) { + data.push((relativeOffset >> 8) & 0xff, relativeOffset & 0xff); + } else if (offsetSize === 3) { + data.push( + (relativeOffset >> 16) & 0xff, + (relativeOffset >> 8) & 0xff, + relativeOffset & 0xff + ); + } else { + data.push( + (relativeOffset >>> 24) & 0xff, + (relativeOffset >> 16) & 0xff, + (relativeOffset >> 8) & 0xff, + relativeOffset & 0xff + ); + } + + if (objects[i]) { + relativeOffset += objects[i].length; + } + } + + for (i = 0; i < count; i++) { + // Notify the tracker where the object will be offset in the data. + if (trackers[i]) { + trackers[i].offset(data.length); + } + data.push(...objects[i]); + } + return data; + } +} + +export { + CFF, + CFFCharset, + CFFCompiler, + CFFFDSelect, + CFFHeader, + CFFIndex, + CFFParser, + CFFPrivateDict, + CFFStandardStrings, + CFFStrings, + CFFTopDict, +}; diff --git a/frontend/public/pdf.js/src/core/charsets.js b/frontend/public/pdf.js/src/core/charsets.js new file mode 100644 index 0000000..504edfc --- /dev/null +++ b/frontend/public/pdf.js/src/core/charsets.js @@ -0,0 +1,119 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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. + */ + +// prettier-ignore +const ISOAdobeCharset = [ + ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", + "percent", "ampersand", "quoteright", "parenleft", "parenright", + "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", + "one", "two", "three", "four", "five", "six", "seven", "eight", + "nine", "colon", "semicolon", "less", "equal", "greater", "question", + "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", + "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", + "sterling", "fraction", "yen", "florin", "section", "currency", + "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", + "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", + "periodcentered", "paragraph", "bullet", "quotesinglbase", + "quotedblbase", "quotedblright", "guillemotright", "ellipsis", + "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", + "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", + "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", + "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", + "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", + "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", + "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", + "registered", "minus", "eth", "multiply", "threesuperior", "copyright", + "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", + "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", + "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", + "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", + "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", + "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", + "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", + "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", + "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", + "ugrave", "yacute", "ydieresis", "zcaron" +]; + +// prettier-ignore +const ExpertCharset = [ + ".notdef", "space", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", + "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", + "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", + "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", + "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", + "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", + "colon", "semicolon", "commasuperior", "threequartersemdash", + "periodsuperior", "questionsmall", "asuperior", "bsuperior", + "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", + "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", + "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", + "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", + "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", + "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", + "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", + "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", + "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", + "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", + "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", + "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", + "Cedillasmall", "onequarter", "onehalf", "threequarters", + "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", + "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", + "twosuperior", "threesuperior", "foursuperior", "fivesuperior", + "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", + "zeroinferior", "oneinferior", "twoinferior", "threeinferior", + "fourinferior", "fiveinferior", "sixinferior", "seveninferior", + "eightinferior", "nineinferior", "centinferior", "dollarinferior", + "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", + "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", + "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", + "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", + "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", + "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", + "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", + "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", + "Ydieresissmall" +]; + +// prettier-ignore +const ExpertSubsetCharset = [ + ".notdef", "space", "dollaroldstyle", "dollarsuperior", + "parenleftsuperior", "parenrightsuperior", "twodotenleader", + "onedotenleader", "comma", "hyphen", "period", "fraction", + "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", + "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", + "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", + "threequartersemdash", "periodsuperior", "asuperior", "bsuperior", + "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", + "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", + "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", + "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", + "rupiah", "centoldstyle", "figuredash", "hypheninferior", "onequarter", + "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", + "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", + "twosuperior", "threesuperior", "foursuperior", "fivesuperior", + "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", + "zeroinferior", "oneinferior", "twoinferior", "threeinferior", + "fourinferior", "fiveinferior", "sixinferior", "seveninferior", + "eightinferior", "nineinferior", "centinferior", "dollarinferior", + "periodinferior", "commainferior" +]; + +export { ExpertCharset, ExpertSubsetCharset, ISOAdobeCharset }; diff --git a/frontend/public/pdf.js/src/core/chunked_stream.js b/frontend/public/pdf.js/src/core/chunked_stream.js new file mode 100644 index 0000000..b4ebe5b --- /dev/null +++ b/frontend/public/pdf.js/src/core/chunked_stream.js @@ -0,0 +1,557 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { arrayBuffersToBytes, MissingDataException } from "./core_utils.js"; +import { assert } from "../shared/util.js"; +import { Stream } from "./stream.js"; + +class ChunkedStream extends Stream { + constructor(length, chunkSize, manager) { + super( + /* arrayBuffer = */ new Uint8Array(length), + /* start = */ 0, + /* length = */ length, + /* dict = */ null + ); + + this.chunkSize = chunkSize; + this._loadedChunks = new Set(); + this.numChunks = Math.ceil(length / chunkSize); + this.manager = manager; + this.progressiveDataLength = 0; + this.lastSuccessfulEnsureByteChunk = -1; // Single-entry cache + } + + // If a particular stream does not implement one or more of these methods, + // an error should be thrown. + getMissingChunks() { + const chunks = []; + for (let chunk = 0, n = this.numChunks; chunk < n; ++chunk) { + if (!this._loadedChunks.has(chunk)) { + chunks.push(chunk); + } + } + return chunks; + } + + get numChunksLoaded() { + return this._loadedChunks.size; + } + + get isDataLoaded() { + return this.numChunksLoaded === this.numChunks; + } + + onReceiveData(begin, chunk) { + const chunkSize = this.chunkSize; + if (begin % chunkSize !== 0) { + throw new Error(`Bad begin offset: ${begin}`); + } + + // Using `this.length` is inaccurate here since `this.start` can be moved + // (see the `moveStart` method). + const end = begin + chunk.byteLength; + if (end % chunkSize !== 0 && end !== this.bytes.length) { + throw new Error(`Bad end offset: ${end}`); + } + + this.bytes.set(new Uint8Array(chunk), begin); + const beginChunk = Math.floor(begin / chunkSize); + const endChunk = Math.floor((end - 1) / chunkSize) + 1; + + for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) { + // Since a value can only occur *once* in a `Set`, there's no need to + // manually check `Set.prototype.has()` before adding the value here. + this._loadedChunks.add(curChunk); + } + } + + onReceiveProgressiveData(data) { + let position = this.progressiveDataLength; + const beginChunk = Math.floor(position / this.chunkSize); + + this.bytes.set(new Uint8Array(data), position); + position += data.byteLength; + this.progressiveDataLength = position; + const endChunk = + position >= this.end + ? this.numChunks + : Math.floor(position / this.chunkSize); + + for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) { + // Since a value can only occur *once* in a `Set`, there's no need to + // manually check `Set.prototype.has()` before adding the value here. + this._loadedChunks.add(curChunk); + } + } + + ensureByte(pos) { + if (pos < this.progressiveDataLength) { + return; + } + + const chunk = Math.floor(pos / this.chunkSize); + if (chunk > this.numChunks) { + return; + } + if (chunk === this.lastSuccessfulEnsureByteChunk) { + return; + } + + if (!this._loadedChunks.has(chunk)) { + throw new MissingDataException(pos, pos + 1); + } + this.lastSuccessfulEnsureByteChunk = chunk; + } + + ensureRange(begin, end) { + if (begin >= end) { + return; + } + if (end <= this.progressiveDataLength) { + return; + } + + const beginChunk = Math.floor(begin / this.chunkSize); + if (beginChunk > this.numChunks) { + return; + } + const endChunk = Math.min( + Math.floor((end - 1) / this.chunkSize) + 1, + this.numChunks + ); + for (let chunk = beginChunk; chunk < endChunk; ++chunk) { + if (!this._loadedChunks.has(chunk)) { + throw new MissingDataException(begin, end); + } + } + } + + nextEmptyChunk(beginChunk) { + const numChunks = this.numChunks; + for (let i = 0; i < numChunks; ++i) { + const chunk = (beginChunk + i) % numChunks; // Wrap around to beginning. + if (!this._loadedChunks.has(chunk)) { + return chunk; + } + } + return null; + } + + hasChunk(chunk) { + return this._loadedChunks.has(chunk); + } + + getByte() { + const pos = this.pos; + if (pos >= this.end) { + return -1; + } + if (pos >= this.progressiveDataLength) { + this.ensureByte(pos); + } + return this.bytes[this.pos++]; + } + + getBytes(length) { + const bytes = this.bytes; + const pos = this.pos; + const strEnd = this.end; + + if (!length) { + if (strEnd > this.progressiveDataLength) { + this.ensureRange(pos, strEnd); + } + return bytes.subarray(pos, strEnd); + } + + let end = pos + length; + if (end > strEnd) { + end = strEnd; + } + if (end > this.progressiveDataLength) { + this.ensureRange(pos, end); + } + + this.pos = end; + return bytes.subarray(pos, end); + } + + getByteRange(begin, end) { + if (begin < 0) { + begin = 0; + } + if (end > this.end) { + end = this.end; + } + if (end > this.progressiveDataLength) { + this.ensureRange(begin, end); + } + return this.bytes.subarray(begin, end); + } + + makeSubStream(start, length, dict = null) { + if (length) { + if (start + length > this.progressiveDataLength) { + this.ensureRange(start, start + length); + } + } else if (start >= this.progressiveDataLength) { + // When the `length` is undefined you do *not*, under any circumstances, + // want to fallback on calling `this.ensureRange(start, this.end)` since + // that would force the *entire* PDF file to be loaded, thus completely + // breaking the whole purpose of using streaming and/or range requests. + // + // However, not doing any checking here could very easily lead to wasted + // time/resources during e.g. parsing, since `MissingDataException`s will + // require data to be re-parsed, which we attempt to minimize by at least + // checking that the *beginning* of the data is available here. + this.ensureByte(start); + } + + function ChunkedStreamSubstream() {} + ChunkedStreamSubstream.prototype = Object.create(this); + ChunkedStreamSubstream.prototype.getMissingChunks = function () { + const chunkSize = this.chunkSize; + const beginChunk = Math.floor(this.start / chunkSize); + const endChunk = Math.floor((this.end - 1) / chunkSize) + 1; + const missingChunks = []; + for (let chunk = beginChunk; chunk < endChunk; ++chunk) { + if (!this._loadedChunks.has(chunk)) { + missingChunks.push(chunk); + } + } + return missingChunks; + }; + Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", { + get() { + if (this.numChunksLoaded === this.numChunks) { + return true; + } + return this.getMissingChunks().length === 0; + }, + configurable: true, + }); + + const subStream = new ChunkedStreamSubstream(); + subStream.pos = subStream.start = start; + subStream.end = start + length || this.end; + subStream.dict = dict; + return subStream; + } + + getBaseStreams() { + return [this]; + } +} + +class ChunkedStreamManager { + constructor(pdfNetworkStream, args) { + this.length = args.length; + this.chunkSize = args.rangeChunkSize; + this.stream = new ChunkedStream(this.length, this.chunkSize, this); + this.pdfNetworkStream = pdfNetworkStream; + this.disableAutoFetch = args.disableAutoFetch; + this.msgHandler = args.msgHandler; + + this.currRequestId = 0; + + this._chunksNeededByRequest = new Map(); + this._requestsByChunk = new Map(); + this._promisesByRequest = new Map(); + this.progressiveDataLength = 0; + this.aborted = false; + + this._loadedStreamCapability = Promise.withResolvers(); + } + + sendRequest(begin, end) { + const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end); + if (!rangeReader.isStreamingSupported) { + rangeReader.onProgress = this.onProgress.bind(this); + } + + let chunks = [], + loaded = 0; + return new Promise((resolve, reject) => { + const readChunk = ({ value, done }) => { + try { + if (done) { + const chunkData = arrayBuffersToBytes(chunks); + chunks = null; + resolve(chunkData); + return; + } + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + value instanceof ArrayBuffer, + "readChunk (sendRequest) - expected an ArrayBuffer." + ); + } + loaded += value.byteLength; + + if (rangeReader.isStreamingSupported) { + this.onProgress({ loaded }); + } + + chunks.push(value); + rangeReader.read().then(readChunk, reject); + } catch (e) { + reject(e); + } + }; + rangeReader.read().then(readChunk, reject); + }).then(data => { + if (this.aborted) { + return; // Ignoring any data after abort. + } + this.onReceiveData({ chunk: data, begin }); + }); + } + + /** + * Get all the chunks that are not yet loaded and group them into + * contiguous ranges to load in as few requests as possible. + */ + requestAllChunks(noFetch = false) { + if (!noFetch) { + const missingChunks = this.stream.getMissingChunks(); + this._requestChunks(missingChunks); + } + return this._loadedStreamCapability.promise; + } + + _requestChunks(chunks) { + const requestId = this.currRequestId++; + + const chunksNeeded = new Set(); + this._chunksNeededByRequest.set(requestId, chunksNeeded); + for (const chunk of chunks) { + if (!this.stream.hasChunk(chunk)) { + chunksNeeded.add(chunk); + } + } + + if (chunksNeeded.size === 0) { + return Promise.resolve(); + } + + const capability = Promise.withResolvers(); + this._promisesByRequest.set(requestId, capability); + + const chunksToRequest = []; + for (const chunk of chunksNeeded) { + let requestIds = this._requestsByChunk.get(chunk); + if (!requestIds) { + requestIds = []; + this._requestsByChunk.set(chunk, requestIds); + + chunksToRequest.push(chunk); + } + requestIds.push(requestId); + } + + if (chunksToRequest.length > 0) { + const groupedChunksToRequest = this.groupChunks(chunksToRequest); + for (const groupedChunk of groupedChunksToRequest) { + const begin = groupedChunk.beginChunk * this.chunkSize; + const end = Math.min( + groupedChunk.endChunk * this.chunkSize, + this.length + ); + this.sendRequest(begin, end).catch(capability.reject); + } + } + + return capability.promise.catch(reason => { + if (this.aborted) { + return; // Ignoring any pending requests after abort. + } + throw reason; + }); + } + + getStream() { + return this.stream; + } + + /** + * Loads any chunks in the requested range that are not yet loaded. + */ + requestRange(begin, end) { + end = Math.min(end, this.length); + + const beginChunk = this.getBeginChunk(begin); + const endChunk = this.getEndChunk(end); + + const chunks = []; + for (let chunk = beginChunk; chunk < endChunk; ++chunk) { + chunks.push(chunk); + } + return this._requestChunks(chunks); + } + + requestRanges(ranges = []) { + const chunksToRequest = []; + for (const range of ranges) { + const beginChunk = this.getBeginChunk(range.begin); + const endChunk = this.getEndChunk(range.end); + for (let chunk = beginChunk; chunk < endChunk; ++chunk) { + if (!chunksToRequest.includes(chunk)) { + chunksToRequest.push(chunk); + } + } + } + + chunksToRequest.sort(function (a, b) { + return a - b; + }); + return this._requestChunks(chunksToRequest); + } + + /** + * Groups a sorted array of chunks into as few contiguous larger + * chunks as possible. + */ + groupChunks(chunks) { + const groupedChunks = []; + let beginChunk = -1; + let prevChunk = -1; + + for (let i = 0, ii = chunks.length; i < ii; ++i) { + const chunk = chunks[i]; + if (beginChunk < 0) { + beginChunk = chunk; + } + + if (prevChunk >= 0 && prevChunk + 1 !== chunk) { + groupedChunks.push({ beginChunk, endChunk: prevChunk + 1 }); + beginChunk = chunk; + } + if (i + 1 === chunks.length) { + groupedChunks.push({ beginChunk, endChunk: chunk + 1 }); + } + + prevChunk = chunk; + } + return groupedChunks; + } + + onProgress(args) { + this.msgHandler.send("DocProgress", { + loaded: this.stream.numChunksLoaded * this.chunkSize + args.loaded, + total: this.length, + }); + } + + onReceiveData(args) { + const chunk = args.chunk; + const isProgressive = args.begin === undefined; + const begin = isProgressive ? this.progressiveDataLength : args.begin; + const end = begin + chunk.byteLength; + + const beginChunk = Math.floor(begin / this.chunkSize); + const endChunk = + end < this.length + ? Math.floor(end / this.chunkSize) + : Math.ceil(end / this.chunkSize); + + if (isProgressive) { + this.stream.onReceiveProgressiveData(chunk); + this.progressiveDataLength = end; + } else { + this.stream.onReceiveData(begin, chunk); + } + + if (this.stream.isDataLoaded) { + this._loadedStreamCapability.resolve(this.stream); + } + + const loadedRequests = []; + for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) { + // The server might return more chunks than requested. + const requestIds = this._requestsByChunk.get(curChunk); + if (!requestIds) { + continue; + } + this._requestsByChunk.delete(curChunk); + + for (const requestId of requestIds) { + const chunksNeeded = this._chunksNeededByRequest.get(requestId); + if (chunksNeeded.has(curChunk)) { + chunksNeeded.delete(curChunk); + } + + if (chunksNeeded.size > 0) { + continue; + } + loadedRequests.push(requestId); + } + } + + // If there are no pending requests, automatically fetch the next + // unfetched chunk of the PDF file. + if (!this.disableAutoFetch && this._requestsByChunk.size === 0) { + let nextEmptyChunk; + if (this.stream.numChunksLoaded === 1) { + // This is a special optimization so that after fetching the first + // chunk, rather than fetching the second chunk, we fetch the last + // chunk. + const lastChunk = this.stream.numChunks - 1; + if (!this.stream.hasChunk(lastChunk)) { + nextEmptyChunk = lastChunk; + } + } else { + nextEmptyChunk = this.stream.nextEmptyChunk(endChunk); + } + if (Number.isInteger(nextEmptyChunk)) { + this._requestChunks([nextEmptyChunk]); + } + } + + for (const requestId of loadedRequests) { + const capability = this._promisesByRequest.get(requestId); + this._promisesByRequest.delete(requestId); + capability.resolve(); + } + + this.msgHandler.send("DocProgress", { + loaded: this.stream.numChunksLoaded * this.chunkSize, + total: this.length, + }); + } + + onError(err) { + this._loadedStreamCapability.reject(err); + } + + getBeginChunk(begin) { + return Math.floor(begin / this.chunkSize); + } + + getEndChunk(end) { + return Math.floor((end - 1) / this.chunkSize) + 1; + } + + abort(reason) { + this.aborted = true; + this.pdfNetworkStream?.cancelAllRequests(reason); + + for (const capability of this._promisesByRequest.values()) { + capability.reject(reason); + } + } +} + +export { ChunkedStream, ChunkedStreamManager }; diff --git a/frontend/public/pdf.js/src/core/cleanup_helper.js b/frontend/public/pdf.js/src/core/cleanup_helper.js new file mode 100644 index 0000000..5ada23c --- /dev/null +++ b/frontend/public/pdf.js/src/core/cleanup_helper.js @@ -0,0 +1,31 @@ +/* Copyright 2022 Mozilla Foundation + * + * 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 { clearPatternCaches } from "./pattern.js"; +import { clearPrimitiveCaches } from "./primitives.js"; +import { clearUnicodeCaches } from "./unicode.js"; +import { JpxImage } from "./jpx.js"; + +function clearGlobalCaches() { + clearPatternCaches(); + clearPrimitiveCaches(); + clearUnicodeCaches(); + + // Remove the global `JpxImage` instance, since it may hold a reference to + // the WebAssembly module. + JpxImage.cleanup(); +} + +export { clearGlobalCaches }; diff --git a/frontend/public/pdf.js/src/core/cmap.js b/frontend/public/pdf.js/src/core/cmap.js new file mode 100644 index 0000000..6c0cab6 --- /dev/null +++ b/frontend/public/pdf.js/src/core/cmap.js @@ -0,0 +1,718 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { Cmd, EOF, isCmd, Name } from "./primitives.js"; +import { FormatError, unreachable, warn } from "../shared/util.js"; +import { BaseStream } from "./base_stream.js"; +import { BinaryCMapReader } from "./binary_cmap.js"; +import { Lexer } from "./parser.js"; +import { MissingDataException } from "./core_utils.js"; +import { Stream } from "./stream.js"; + +const BUILT_IN_CMAPS = [ + // << Start unicode maps. + "Adobe-GB1-UCS2", + "Adobe-CNS1-UCS2", + "Adobe-Japan1-UCS2", + "Adobe-Korea1-UCS2", + // >> End unicode maps. + "78-EUC-H", + "78-EUC-V", + "78-H", + "78-RKSJ-H", + "78-RKSJ-V", + "78-V", + "78ms-RKSJ-H", + "78ms-RKSJ-V", + "83pv-RKSJ-H", + "90ms-RKSJ-H", + "90ms-RKSJ-V", + "90msp-RKSJ-H", + "90msp-RKSJ-V", + "90pv-RKSJ-H", + "90pv-RKSJ-V", + "Add-H", + "Add-RKSJ-H", + "Add-RKSJ-V", + "Add-V", + "Adobe-CNS1-0", + "Adobe-CNS1-1", + "Adobe-CNS1-2", + "Adobe-CNS1-3", + "Adobe-CNS1-4", + "Adobe-CNS1-5", + "Adobe-CNS1-6", + "Adobe-GB1-0", + "Adobe-GB1-1", + "Adobe-GB1-2", + "Adobe-GB1-3", + "Adobe-GB1-4", + "Adobe-GB1-5", + "Adobe-Japan1-0", + "Adobe-Japan1-1", + "Adobe-Japan1-2", + "Adobe-Japan1-3", + "Adobe-Japan1-4", + "Adobe-Japan1-5", + "Adobe-Japan1-6", + "Adobe-Korea1-0", + "Adobe-Korea1-1", + "Adobe-Korea1-2", + "B5-H", + "B5-V", + "B5pc-H", + "B5pc-V", + "CNS-EUC-H", + "CNS-EUC-V", + "CNS1-H", + "CNS1-V", + "CNS2-H", + "CNS2-V", + "ETHK-B5-H", + "ETHK-B5-V", + "ETen-B5-H", + "ETen-B5-V", + "ETenms-B5-H", + "ETenms-B5-V", + "EUC-H", + "EUC-V", + "Ext-H", + "Ext-RKSJ-H", + "Ext-RKSJ-V", + "Ext-V", + "GB-EUC-H", + "GB-EUC-V", + "GB-H", + "GB-V", + "GBK-EUC-H", + "GBK-EUC-V", + "GBK2K-H", + "GBK2K-V", + "GBKp-EUC-H", + "GBKp-EUC-V", + "GBT-EUC-H", + "GBT-EUC-V", + "GBT-H", + "GBT-V", + "GBTpc-EUC-H", + "GBTpc-EUC-V", + "GBpc-EUC-H", + "GBpc-EUC-V", + "H", + "HKdla-B5-H", + "HKdla-B5-V", + "HKdlb-B5-H", + "HKdlb-B5-V", + "HKgccs-B5-H", + "HKgccs-B5-V", + "HKm314-B5-H", + "HKm314-B5-V", + "HKm471-B5-H", + "HKm471-B5-V", + "HKscs-B5-H", + "HKscs-B5-V", + "Hankaku", + "Hiragana", + "KSC-EUC-H", + "KSC-EUC-V", + "KSC-H", + "KSC-Johab-H", + "KSC-Johab-V", + "KSC-V", + "KSCms-UHC-H", + "KSCms-UHC-HW-H", + "KSCms-UHC-HW-V", + "KSCms-UHC-V", + "KSCpc-EUC-H", + "KSCpc-EUC-V", + "Katakana", + "NWP-H", + "NWP-V", + "RKSJ-H", + "RKSJ-V", + "Roman", + "UniCNS-UCS2-H", + "UniCNS-UCS2-V", + "UniCNS-UTF16-H", + "UniCNS-UTF16-V", + "UniCNS-UTF32-H", + "UniCNS-UTF32-V", + "UniCNS-UTF8-H", + "UniCNS-UTF8-V", + "UniGB-UCS2-H", + "UniGB-UCS2-V", + "UniGB-UTF16-H", + "UniGB-UTF16-V", + "UniGB-UTF32-H", + "UniGB-UTF32-V", + "UniGB-UTF8-H", + "UniGB-UTF8-V", + "UniJIS-UCS2-H", + "UniJIS-UCS2-HW-H", + "UniJIS-UCS2-HW-V", + "UniJIS-UCS2-V", + "UniJIS-UTF16-H", + "UniJIS-UTF16-V", + "UniJIS-UTF32-H", + "UniJIS-UTF32-V", + "UniJIS-UTF8-H", + "UniJIS-UTF8-V", + "UniJIS2004-UTF16-H", + "UniJIS2004-UTF16-V", + "UniJIS2004-UTF32-H", + "UniJIS2004-UTF32-V", + "UniJIS2004-UTF8-H", + "UniJIS2004-UTF8-V", + "UniJISPro-UCS2-HW-V", + "UniJISPro-UCS2-V", + "UniJISPro-UTF8-V", + "UniJISX0213-UTF32-H", + "UniJISX0213-UTF32-V", + "UniJISX02132004-UTF32-H", + "UniJISX02132004-UTF32-V", + "UniKS-UCS2-H", + "UniKS-UCS2-V", + "UniKS-UTF16-H", + "UniKS-UTF16-V", + "UniKS-UTF32-H", + "UniKS-UTF32-V", + "UniKS-UTF8-H", + "UniKS-UTF8-V", + "V", + "WP-Symbol", +]; + +// Heuristic to avoid hanging the worker-thread for CMap data with ridiculously +// large ranges, such as e.g. 0xFFFFFFFF (fixes issue11922_reduced.pdf). +const MAX_MAP_RANGE = 2 ** 24 - 1; // = 0xFFFFFF + +// CMap, not to be confused with TrueType's cmap. +class CMap { + constructor(builtInCMap = false) { + // Codespace ranges are stored as follows: + // [[1BytePairs], [2BytePairs], [3BytePairs], [4BytePairs]] + // where nBytePairs are ranges e.g. [low1, high1, low2, high2, ...] + this.codespaceRanges = [[], [], [], []]; + this.numCodespaceRanges = 0; + // Map entries have one of two forms. + // - cid chars are 16-bit unsigned integers, stored as integers. + // - bf chars are variable-length byte sequences, stored as strings, with + // one byte per character. + this._map = []; + this.name = ""; + this.vertical = false; + this.useCMap = null; + this.builtInCMap = builtInCMap; + } + + addCodespaceRange(n, low, high) { + this.codespaceRanges[n - 1].push(low, high); + this.numCodespaceRanges++; + } + + mapCidRange(low, high, dstLow) { + if (high - low > MAX_MAP_RANGE) { + throw new Error("mapCidRange - ignoring data above MAX_MAP_RANGE."); + } + while (low <= high) { + this._map[low++] = dstLow++; + } + } + + mapBfRange(low, high, dstLow) { + if (high - low > MAX_MAP_RANGE) { + throw new Error("mapBfRange - ignoring data above MAX_MAP_RANGE."); + } + const lastByte = dstLow.length - 1; + while (low <= high) { + this._map[low++] = dstLow; + // Only the last byte has to be incremented (in the normal case). + const nextCharCode = dstLow.charCodeAt(lastByte) + 1; + if (nextCharCode > 0xff) { + dstLow = + dstLow.substring(0, lastByte - 1) + + String.fromCharCode(dstLow.charCodeAt(lastByte - 1) + 1) + + "\x00"; + continue; + } + dstLow = + dstLow.substring(0, lastByte) + String.fromCharCode(nextCharCode); + } + } + + mapBfRangeToArray(low, high, array) { + if (high - low > MAX_MAP_RANGE) { + throw new Error("mapBfRangeToArray - ignoring data above MAX_MAP_RANGE."); + } + const ii = array.length; + let i = 0; + while (low <= high && i < ii) { + this._map[low] = array[i++]; + ++low; + } + } + + // This is used for both bf and cid chars. + mapOne(src, dst) { + this._map[src] = dst; + } + + lookup(code) { + return this._map[code]; + } + + contains(code) { + return this._map[code] !== undefined; + } + + forEach(callback) { + // Most maps have fewer than 65536 entries, and for those we use normal + // array iteration. But really sparse tables are possible -- e.g. with + // indices in the *billions*. For such tables we use for..in, which isn't + // ideal because it stringifies the indices for all present elements, but + // it does avoid iterating over every undefined entry. + const map = this._map; + const length = map.length; + if (length <= 0x10000) { + for (let i = 0; i < length; i++) { + if (map[i] !== undefined) { + callback(i, map[i]); + } + } + } else { + for (const i in map) { + callback(i, map[i]); + } + } + } + + charCodeOf(value) { + // `Array.prototype.indexOf` is *extremely* inefficient for arrays which + // are both very sparse and very large (see issue8372.pdf). + const map = this._map; + if (map.length <= 0x10000) { + return map.indexOf(value); + } + for (const charCode in map) { + if (map[charCode] === value) { + return charCode | 0; + } + } + return -1; + } + + getMap() { + return this._map; + } + + readCharCode(str, offset, out) { + let c = 0; + const codespaceRanges = this.codespaceRanges; + // 9.7.6.2 CMap Mapping + // The code length is at most 4. + for (let n = 0, nn = codespaceRanges.length; n < nn; n++) { + c = ((c << 8) | str.charCodeAt(offset + n)) >>> 0; + // Check each codespace range to see if it falls within. + const codespaceRange = codespaceRanges[n]; + for (let k = 0, kk = codespaceRange.length; k < kk; ) { + const low = codespaceRange[k++]; + const high = codespaceRange[k++]; + if (c >= low && c <= high) { + out.charcode = c; + out.length = n + 1; + return; + } + } + } + out.charcode = 0; + out.length = 1; + } + + getCharCodeLength(charCode) { + const codespaceRanges = this.codespaceRanges; + for (let n = 0, nn = codespaceRanges.length; n < nn; n++) { + // Check each codespace range to see if it falls within. + const codespaceRange = codespaceRanges[n]; + for (let k = 0, kk = codespaceRange.length; k < kk; ) { + const low = codespaceRange[k++]; + const high = codespaceRange[k++]; + if (charCode >= low && charCode <= high) { + return n + 1; + } + } + } + return 1; + } + + get length() { + return this._map.length; + } + + get isIdentityCMap() { + if (!(this.name === "Identity-H" || this.name === "Identity-V")) { + return false; + } + if (this._map.length !== 0x10000) { + return false; + } + for (let i = 0; i < 0x10000; i++) { + if (this._map[i] !== i) { + return false; + } + } + return true; + } +} + +// A special case of CMap, where the _map array implicitly has a length of +// 65536 and each element is equal to its index. +class IdentityCMap extends CMap { + constructor(vertical, n) { + super(); + + this.vertical = vertical; + this.addCodespaceRange(n, 0, 0xffff); + } + + mapCidRange(low, high, dstLow) { + unreachable("should not call mapCidRange"); + } + + mapBfRange(low, high, dstLow) { + unreachable("should not call mapBfRange"); + } + + mapBfRangeToArray(low, high, array) { + unreachable("should not call mapBfRangeToArray"); + } + + mapOne(src, dst) { + unreachable("should not call mapCidOne"); + } + + lookup(code) { + return Number.isInteger(code) && code <= 0xffff ? code : undefined; + } + + contains(code) { + return Number.isInteger(code) && code <= 0xffff; + } + + forEach(callback) { + for (let i = 0; i <= 0xffff; i++) { + callback(i, i); + } + } + + charCodeOf(value) { + return Number.isInteger(value) && value <= 0xffff ? value : -1; + } + + getMap() { + // Sometimes identity maps must be instantiated, but it's rare. + const map = new Array(0x10000); + for (let i = 0; i <= 0xffff; i++) { + map[i] = i; + } + return map; + } + + get length() { + return 0x10000; + } + + // eslint-disable-next-line getter-return + get isIdentityCMap() { + unreachable("should not access .isIdentityCMap"); + } +} + +function strToInt(str) { + let a = 0; + for (let i = 0; i < str.length; i++) { + a = (a << 8) | str.charCodeAt(i); + } + return a >>> 0; +} + +function expectString(obj) { + if (typeof obj !== "string") { + throw new FormatError("Malformed CMap: expected string."); + } +} + +function expectInt(obj) { + if (!Number.isInteger(obj)) { + throw new FormatError("Malformed CMap: expected int."); + } +} + +function parseBfChar(cMap, lexer) { + while (true) { + let obj = lexer.getObj(); + if (obj === EOF) { + break; + } + if (isCmd(obj, "endbfchar")) { + return; + } + expectString(obj); + const src = strToInt(obj); + obj = lexer.getObj(); + // TODO are /dstName used? + expectString(obj); + const dst = obj; + cMap.mapOne(src, dst); + } +} + +function parseBfRange(cMap, lexer) { + while (true) { + let obj = lexer.getObj(); + if (obj === EOF) { + break; + } + if (isCmd(obj, "endbfrange")) { + return; + } + expectString(obj); + const low = strToInt(obj); + obj = lexer.getObj(); + expectString(obj); + const high = strToInt(obj); + obj = lexer.getObj(); + if (Number.isInteger(obj) || typeof obj === "string") { + const dstLow = Number.isInteger(obj) ? String.fromCharCode(obj) : obj; + cMap.mapBfRange(low, high, dstLow); + } else if (isCmd(obj, "[")) { + obj = lexer.getObj(); + const array = []; + while (!isCmd(obj, "]") && obj !== EOF) { + array.push(obj); + obj = lexer.getObj(); + } + cMap.mapBfRangeToArray(low, high, array); + } else { + break; + } + } + throw new FormatError("Invalid bf range."); +} + +function parseCidChar(cMap, lexer) { + while (true) { + let obj = lexer.getObj(); + if (obj === EOF) { + break; + } + if (isCmd(obj, "endcidchar")) { + return; + } + expectString(obj); + const src = strToInt(obj); + obj = lexer.getObj(); + expectInt(obj); + const dst = obj; + cMap.mapOne(src, dst); + } +} + +function parseCidRange(cMap, lexer) { + while (true) { + let obj = lexer.getObj(); + if (obj === EOF) { + break; + } + if (isCmd(obj, "endcidrange")) { + return; + } + expectString(obj); + const low = strToInt(obj); + obj = lexer.getObj(); + expectString(obj); + const high = strToInt(obj); + obj = lexer.getObj(); + expectInt(obj); + const dstLow = obj; + cMap.mapCidRange(low, high, dstLow); + } +} + +function parseCodespaceRange(cMap, lexer) { + while (true) { + let obj = lexer.getObj(); + if (obj === EOF) { + break; + } + if (isCmd(obj, "endcodespacerange")) { + return; + } + if (typeof obj !== "string") { + break; + } + const low = strToInt(obj); + obj = lexer.getObj(); + if (typeof obj !== "string") { + break; + } + const high = strToInt(obj); + cMap.addCodespaceRange(obj.length, low, high); + } + throw new FormatError("Invalid codespace range."); +} + +function parseWMode(cMap, lexer) { + const obj = lexer.getObj(); + if (Number.isInteger(obj)) { + cMap.vertical = !!obj; + } +} + +function parseCMapName(cMap, lexer) { + const obj = lexer.getObj(); + if (obj instanceof Name) { + cMap.name = obj.name; + } +} + +async function parseCMap(cMap, lexer, fetchBuiltInCMap, useCMap) { + let previous, embeddedUseCMap; + objLoop: while (true) { + try { + const obj = lexer.getObj(); + if (obj === EOF) { + break; + } else if (obj instanceof Name) { + if (obj.name === "WMode") { + parseWMode(cMap, lexer); + } else if (obj.name === "CMapName") { + parseCMapName(cMap, lexer); + } + previous = obj; + } else if (obj instanceof Cmd) { + switch (obj.cmd) { + case "endcmap": + break objLoop; + case "usecmap": + if (previous instanceof Name) { + embeddedUseCMap = previous.name; + } + break; + case "begincodespacerange": + parseCodespaceRange(cMap, lexer); + break; + case "beginbfchar": + parseBfChar(cMap, lexer); + break; + case "begincidchar": + parseCidChar(cMap, lexer); + break; + case "beginbfrange": + parseBfRange(cMap, lexer); + break; + case "begincidrange": + parseCidRange(cMap, lexer); + break; + } + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn("Invalid cMap data: " + ex); + continue; + } + } + + if (!useCMap && embeddedUseCMap) { + // Load the useCMap definition from the file only if there wasn't one + // specified. + useCMap = embeddedUseCMap; + } + if (useCMap) { + return extendCMap(cMap, fetchBuiltInCMap, useCMap); + } + return cMap; +} + +async function extendCMap(cMap, fetchBuiltInCMap, useCMap) { + cMap.useCMap = await createBuiltInCMap(useCMap, fetchBuiltInCMap); + // If there aren't any code space ranges defined clone all the parent ones + // into this cMap. + if (cMap.numCodespaceRanges === 0) { + const useCodespaceRanges = cMap.useCMap.codespaceRanges; + for (let i = 0; i < useCodespaceRanges.length; i++) { + cMap.codespaceRanges[i] = useCodespaceRanges[i].slice(); + } + cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges; + } + // Merge the map into the current one, making sure not to override + // any previously defined entries. + cMap.useCMap.forEach(function (key, value) { + if (!cMap.contains(key)) { + cMap.mapOne(key, value); + } + }); + + return cMap; +} + +async function createBuiltInCMap(name, fetchBuiltInCMap) { + if (name === "Identity-H") { + return new IdentityCMap(false, 2); + } else if (name === "Identity-V") { + return new IdentityCMap(true, 2); + } + if (!BUILT_IN_CMAPS.includes(name)) { + throw new Error("Unknown CMap name: " + name); + } + if (!fetchBuiltInCMap) { + throw new Error("Built-in CMap parameters are not provided."); + } + + const { cMapData, isCompressed } = await fetchBuiltInCMap(name); + const cMap = new CMap(true); + + if (isCompressed) { + return new BinaryCMapReader().process(cMapData, cMap, useCMap => + extendCMap(cMap, fetchBuiltInCMap, useCMap) + ); + } + const lexer = new Lexer(new Stream(cMapData)); + return parseCMap(cMap, lexer, fetchBuiltInCMap, null); +} + +class CMapFactory { + static async create({ encoding, fetchBuiltInCMap, useCMap }) { + if (encoding instanceof Name) { + return createBuiltInCMap(encoding.name, fetchBuiltInCMap); + } else if (encoding instanceof BaseStream) { + const parsedCMap = await parseCMap( + /* cMap = */ new CMap(), + /* lexer = */ new Lexer(encoding), + fetchBuiltInCMap, + useCMap + ); + + if (parsedCMap.isIdentityCMap) { + return createBuiltInCMap(parsedCMap.name, fetchBuiltInCMap); + } + return parsedCMap; + } + throw new Error("Encoding required."); + } +} + +export { CMap, CMapFactory, IdentityCMap }; diff --git a/frontend/public/pdf.js/src/core/colorspace.js b/frontend/public/pdf.js/src/core/colorspace.js new file mode 100644 index 0000000..9f5ce00 --- /dev/null +++ b/frontend/public/pdf.js/src/core/colorspace.js @@ -0,0 +1,1548 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + assert, + FeatureTest, + FormatError, + info, + shadow, + unreachable, + warn, +} from "../shared/util.js"; +import { Dict, Name, Ref } from "./primitives.js"; +import { BaseStream } from "./base_stream.js"; +import { MissingDataException } from "./core_utils.js"; + +/** + * Resizes an RGB image with 3 components. + * @param {TypedArray} src - The source buffer. + * @param {TypedArray} dest - The destination buffer. + * @param {number} w1 - Original width. + * @param {number} h1 - Original height. + * @param {number} w2 - New width. + * @param {number} h2 - New height. + * @param {number} alpha01 - Size reserved for the alpha channel. + */ +function resizeRgbImage(src, dest, w1, h1, w2, h2, alpha01) { + const COMPONENTS = 3; + alpha01 = alpha01 !== 1 ? 0 : alpha01; + const xRatio = w1 / w2; + const yRatio = h1 / h2; + let newIndex = 0, + oldIndex; + const xScaled = new Uint16Array(w2); + const w1Scanline = w1 * COMPONENTS; + + for (let i = 0; i < w2; i++) { + xScaled[i] = Math.floor(i * xRatio) * COMPONENTS; + } + for (let i = 0; i < h2; i++) { + const py = Math.floor(i * yRatio) * w1Scanline; + for (let j = 0; j < w2; j++) { + oldIndex = py + xScaled[j]; + dest[newIndex++] = src[oldIndex++]; + dest[newIndex++] = src[oldIndex++]; + dest[newIndex++] = src[oldIndex++]; + newIndex += alpha01; + } + } +} + +function resizeRgbaImage(src, dest, w1, h1, w2, h2, alpha01) { + const xRatio = w1 / w2; + const yRatio = h1 / h2; + let newIndex = 0; + const xScaled = new Uint16Array(w2); + + if (alpha01 === 1) { + for (let i = 0; i < w2; i++) { + xScaled[i] = Math.floor(i * xRatio); + } + const src32 = new Uint32Array(src.buffer); + const dest32 = new Uint32Array(dest.buffer); + const rgbMask = FeatureTest.isLittleEndian ? 0x00ffffff : 0xffffff00; + for (let i = 0; i < h2; i++) { + const buf = src32.subarray(Math.floor(i * yRatio) * w1); + for (let j = 0; j < w2; j++) { + dest32[newIndex++] |= buf[xScaled[j]] & rgbMask; + } + } + } else { + const COMPONENTS = 4; + const w1Scanline = w1 * COMPONENTS; + for (let i = 0; i < w2; i++) { + xScaled[i] = Math.floor(i * xRatio) * COMPONENTS; + } + for (let i = 0; i < h2; i++) { + const buf = src.subarray(Math.floor(i * yRatio) * w1Scanline); + for (let j = 0; j < w2; j++) { + const oldIndex = xScaled[j]; + dest[newIndex++] = buf[oldIndex]; + dest[newIndex++] = buf[oldIndex + 1]; + dest[newIndex++] = buf[oldIndex + 2]; + } + } + } +} + +function copyRgbaImage(src, dest, alpha01) { + if (alpha01 === 1) { + const src32 = new Uint32Array(src.buffer); + const dest32 = new Uint32Array(dest.buffer); + const rgbMask = FeatureTest.isLittleEndian ? 0x00ffffff : 0xffffff00; + for (let i = 0, ii = src32.length; i < ii; i++) { + dest32[i] |= src32[i] & rgbMask; + } + } else { + let j = 0; + for (let i = 0, ii = src.length; i < ii; i += 4) { + dest[j++] = src[i]; + dest[j++] = src[i + 1]; + dest[j++] = src[i + 2]; + } + } +} + +class ColorSpace { + constructor(name, numComps) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === ColorSpace + ) { + unreachable("Cannot initialize ColorSpace."); + } + this.name = name; + this.numComps = numComps; + } + + /** + * Converts the color value to the RGB color. The color components are + * located in the src array starting from the srcOffset. Returns the array + * of the rgb components, each value ranging from [0,255]. + */ + getRgb(src, srcOffset) { + const rgb = new Uint8ClampedArray(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + } + + /** + * Converts the color value to the RGB color, similar to the getRgb method. + * The result placed into the dest array starting from the destOffset. + */ + getRgbItem(src, srcOffset, dest, destOffset) { + unreachable("Should not call ColorSpace.getRgbItem"); + } + + /** + * Converts the specified number of the color values to the RGB colors. + * The colors are located in the src array starting from the srcOffset. + * The result is placed into the dest array starting from the destOffset. + * The src array items shall be in [0,2^bits) range, the dest array items + * will be in [0,255] range. alpha01 indicates how many alpha components + * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA + * array). + */ + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + unreachable("Should not call ColorSpace.getRgbBuffer"); + } + + /** + * Determines the number of bytes required to store the result of the + * conversion done by the getRgbBuffer method. As in getRgbBuffer, + * |alpha01| is either 0 (RGB output) or 1 (RGBA output). + */ + getOutputLength(inputLength, alpha01) { + unreachable("Should not call ColorSpace.getOutputLength"); + } + + /** + * Returns true if source data will be equal the result/output data. + */ + isPassthrough(bits) { + return false; + } + + /** + * Refer to the static `ColorSpace.isDefaultDecode` method below. + */ + isDefaultDecode(decodeMap, bpc) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + } + + /** + * Fills in the RGB colors in the destination buffer. alpha01 indicates + * how many alpha components there are in the dest array; it will be either + * 0 (RGB array) or 1 (RGBA array). + */ + fillRgb( + dest, + originalWidth, + originalHeight, + width, + height, + actualHeight, + bpc, + comps, + alpha01 + ) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'ColorSpace.fillRgb: Unsupported "dest" type.' + ); + } + const count = originalWidth * originalHeight; + let rgbBuf = null; + const numComponentColors = 1 << bpc; + const needsResizing = originalHeight !== height || originalWidth !== width; + + if (this.isPassthrough(bpc)) { + rgbBuf = comps; + } else if ( + this.numComps === 1 && + count > numComponentColors && + this.name !== "DeviceGray" && + this.name !== "DeviceRGB" + ) { + // Optimization: create a color map when there is just one component and + // we are converting more colors than the size of the color map. We + // don't build the map if the colorspace is gray or rgb since those + // methods are faster than building a map. This mainly offers big speed + // ups for indexed and alternate colorspaces. + // + // TODO it may be worth while to cache the color map. While running + // testing I never hit a cache so I will leave that out for now (perhaps + // we are reparsing colorspaces too much?). + const allColors = + bpc <= 8 + ? new Uint8Array(numComponentColors) + : new Uint16Array(numComponentColors); + for (let i = 0; i < numComponentColors; i++) { + allColors[i] = i; + } + const colorMap = new Uint8ClampedArray(numComponentColors * 3); + this.getRgbBuffer( + allColors, + 0, + numComponentColors, + colorMap, + 0, + bpc, + /* alpha01 = */ 0 + ); + + if (!needsResizing) { + // Fill in the RGB values directly into |dest|. + let destPos = 0; + for (let i = 0; i < count; ++i) { + const key = comps[i] * 3; + dest[destPos++] = colorMap[key]; + dest[destPos++] = colorMap[key + 1]; + dest[destPos++] = colorMap[key + 2]; + destPos += alpha01; + } + } else { + rgbBuf = new Uint8Array(count * 3); + let rgbPos = 0; + for (let i = 0; i < count; ++i) { + const key = comps[i] * 3; + rgbBuf[rgbPos++] = colorMap[key]; + rgbBuf[rgbPos++] = colorMap[key + 1]; + rgbBuf[rgbPos++] = colorMap[key + 2]; + } + } + } else if (!needsResizing) { + // Fill in the RGB values directly into |dest|. + this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, alpha01); + } else { + rgbBuf = new Uint8ClampedArray(count * 3); + this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, /* alpha01 = */ 0); + } + + if (rgbBuf) { + if (needsResizing) { + resizeRgbImage( + rgbBuf, + dest, + originalWidth, + originalHeight, + width, + height, + alpha01 + ); + } else { + let destPos = 0, + rgbPos = 0; + for (let i = 0, ii = width * actualHeight; i < ii; i++) { + dest[destPos++] = rgbBuf[rgbPos++]; + dest[destPos++] = rgbBuf[rgbPos++]; + dest[destPos++] = rgbBuf[rgbPos++]; + destPos += alpha01; + } + } + } + } + + /** + * True if the colorspace has components in the default range of [0, 1]. + * This should be true for all colorspaces except for lab color spaces + * which are [0,100], [-128, 127], [-128, 127]. + */ + get usesZeroToOneRange() { + return shadow(this, "usesZeroToOneRange", true); + } + + /** + * @private + */ + static _cache(cacheKey, xref, localColorSpaceCache, parsedColorSpace) { + if (!localColorSpaceCache) { + throw new Error( + 'ColorSpace._cache - expected "localColorSpaceCache" argument.' + ); + } + if (!parsedColorSpace) { + throw new Error( + 'ColorSpace._cache - expected "parsedColorSpace" argument.' + ); + } + let csName, csRef; + if (cacheKey instanceof Ref) { + csRef = cacheKey; + + // If parsing succeeded, we know that this call cannot throw. + cacheKey = xref.fetch(cacheKey); + } + if (cacheKey instanceof Name) { + csName = cacheKey.name; + } + if (csName || csRef) { + localColorSpaceCache.set(csName, csRef, parsedColorSpace); + } + } + + static getCached(cacheKey, xref, localColorSpaceCache) { + if (!localColorSpaceCache) { + throw new Error( + 'ColorSpace.getCached - expected "localColorSpaceCache" argument.' + ); + } + if (cacheKey instanceof Ref) { + const localColorSpace = localColorSpaceCache.getByRef(cacheKey); + if (localColorSpace) { + return localColorSpace; + } + + try { + cacheKey = xref.fetch(cacheKey); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + // Any errors should be handled during parsing, rather than here. + } + } + if (cacheKey instanceof Name) { + const localColorSpace = localColorSpaceCache.getByName(cacheKey.name); + if (localColorSpace) { + return localColorSpace; + } + } + return null; + } + + static async parseAsync({ + cs, + xref, + resources = null, + pdfFunctionFactory, + localColorSpaceCache, + }) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + !this.getCached(cs, xref, localColorSpaceCache), + "Expected `ColorSpace.getCached` to have been manually checked " + + "before calling `ColorSpace.parseAsync`." + ); + } + const parsedColorSpace = this._parse( + cs, + xref, + resources, + pdfFunctionFactory + ); + + // Attempt to cache the parsed ColorSpace, by name and/or reference. + this._cache(cs, xref, localColorSpaceCache, parsedColorSpace); + + return parsedColorSpace; + } + + static parse({ + cs, + xref, + resources = null, + pdfFunctionFactory, + localColorSpaceCache, + }) { + const cachedColorSpace = this.getCached(cs, xref, localColorSpaceCache); + if (cachedColorSpace) { + return cachedColorSpace; + } + const parsedColorSpace = this._parse( + cs, + xref, + resources, + pdfFunctionFactory + ); + + // Attempt to cache the parsed ColorSpace, by name and/or reference. + this._cache(cs, xref, localColorSpaceCache, parsedColorSpace); + + return parsedColorSpace; + } + + /** + * @private + */ + static _parse(cs, xref, resources = null, pdfFunctionFactory) { + cs = xref.fetchIfRef(cs); + if (cs instanceof Name) { + switch (cs.name) { + case "G": + case "DeviceGray": + return this.singletons.gray; + case "RGB": + case "DeviceRGB": + return this.singletons.rgb; + case "DeviceRGBA": + return this.singletons.rgba; + case "CMYK": + case "DeviceCMYK": + return this.singletons.cmyk; + case "Pattern": + return new PatternCS(/* baseCS = */ null); + default: + if (resources instanceof Dict) { + const colorSpaces = resources.get("ColorSpace"); + if (colorSpaces instanceof Dict) { + const resourcesCS = colorSpaces.get(cs.name); + if (resourcesCS) { + if (resourcesCS instanceof Name) { + return this._parse( + resourcesCS, + xref, + resources, + pdfFunctionFactory + ); + } + cs = resourcesCS; + break; + } + } + } + // Fallback to the default gray color space. + warn(`Unrecognized ColorSpace: ${cs.name}`); + return this.singletons.gray; + } + } + if (Array.isArray(cs)) { + const mode = xref.fetchIfRef(cs[0]).name; + let params, numComps, baseCS, whitePoint, blackPoint, gamma; + + switch (mode) { + case "G": + case "DeviceGray": + return this.singletons.gray; + case "RGB": + case "DeviceRGB": + return this.singletons.rgb; + case "CMYK": + case "DeviceCMYK": + return this.singletons.cmyk; + case "CalGray": + params = xref.fetchIfRef(cs[1]); + whitePoint = params.getArray("WhitePoint"); + blackPoint = params.getArray("BlackPoint"); + gamma = params.get("Gamma"); + return new CalGrayCS(whitePoint, blackPoint, gamma); + case "CalRGB": + params = xref.fetchIfRef(cs[1]); + whitePoint = params.getArray("WhitePoint"); + blackPoint = params.getArray("BlackPoint"); + gamma = params.getArray("Gamma"); + const matrix = params.getArray("Matrix"); + return new CalRGBCS(whitePoint, blackPoint, gamma, matrix); + case "ICCBased": + const stream = xref.fetchIfRef(cs[1]); + const dict = stream.dict; + numComps = dict.get("N"); + const alt = dict.get("Alternate"); + if (alt) { + const altCS = this._parse(alt, xref, resources, pdfFunctionFactory); + // Ensure that the number of components are correct, + // and also (indirectly) that it is not a PatternCS. + if (altCS.numComps === numComps) { + return altCS; + } + warn("ICCBased color space: Ignoring incorrect /Alternate entry."); + } + if (numComps === 1) { + return this.singletons.gray; + } else if (numComps === 3) { + return this.singletons.rgb; + } else if (numComps === 4) { + return this.singletons.cmyk; + } + break; + case "Pattern": + baseCS = cs[1] || null; + if (baseCS) { + baseCS = this._parse(baseCS, xref, resources, pdfFunctionFactory); + } + return new PatternCS(baseCS); + case "I": + case "Indexed": + baseCS = this._parse(cs[1], xref, resources, pdfFunctionFactory); + const hiVal = Math.max(0, Math.min(xref.fetchIfRef(cs[2]), 255)); + const lookup = xref.fetchIfRef(cs[3]); + return new IndexedCS(baseCS, hiVal, lookup); + case "Separation": + case "DeviceN": + const name = xref.fetchIfRef(cs[1]); + numComps = Array.isArray(name) ? name.length : 1; + baseCS = this._parse(cs[2], xref, resources, pdfFunctionFactory); + const tintFn = pdfFunctionFactory.create(cs[3]); + return new AlternateCS(numComps, baseCS, tintFn); + case "Lab": + params = xref.fetchIfRef(cs[1]); + whitePoint = params.getArray("WhitePoint"); + blackPoint = params.getArray("BlackPoint"); + const range = params.getArray("Range"); + return new LabCS(whitePoint, blackPoint, range); + default: + // Fallback to the default gray color space. + warn(`Unimplemented ColorSpace object: ${mode}`); + return this.singletons.gray; + } + } + // Fallback to the default gray color space. + warn(`Unrecognized ColorSpace object: ${cs}`); + return this.singletons.gray; + } + + /** + * Checks if a decode map matches the default decode map for a color space. + * This handles the general decode maps where there are two values per + * component, e.g. [0, 1, 0, 1, 0, 1] for a RGB color. + * This does not handle Lab, Indexed, or Pattern decode maps since they are + * slightly different. + * @param {Array} decode - Decode map (usually from an image). + * @param {number} numComps - Number of components the color space has. + */ + static isDefaultDecode(decode, numComps) { + if (!Array.isArray(decode)) { + return true; + } + if (numComps * 2 !== decode.length) { + warn("The decode map is not the correct length"); + return true; + } + for (let i = 0, ii = decode.length; i < ii; i += 2) { + if (decode[i] !== 0 || decode[i + 1] !== 1) { + return false; + } + } + return true; + } + + static get singletons() { + return shadow(this, "singletons", { + get gray() { + return shadow(this, "gray", new DeviceGrayCS()); + }, + get rgb() { + return shadow(this, "rgb", new DeviceRgbCS()); + }, + get rgba() { + return shadow(this, "rgba", new DeviceRgbaCS()); + }, + get cmyk() { + return shadow(this, "cmyk", new DeviceCmykCS()); + }, + }); + } +} + +/** + * Alternate color space handles both Separation and DeviceN color spaces. A + * Separation color space is actually just a DeviceN with one color component. + * Both color spaces use a tinting function to convert colors to a base color + * space. + * + * The default color is `new Float32Array(new Array(numComps).fill(1))`. + */ +class AlternateCS extends ColorSpace { + constructor(numComps, base, tintFn) { + super("Alternate", numComps); + this.base = base; + this.tintFn = tintFn; + this.tmpBuf = new Float32Array(base.numComps); + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'AlternateCS.getRgbItem: Unsupported "dest" type.' + ); + } + const tmpBuf = this.tmpBuf; + this.tintFn(src, srcOffset, tmpBuf, 0); + this.base.getRgbItem(tmpBuf, 0, dest, destOffset); + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'AlternateCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const tintFn = this.tintFn; + const base = this.base; + const scale = 1 / ((1 << bits) - 1); + const baseNumComps = base.numComps; + const usesZeroToOneRange = base.usesZeroToOneRange; + const isPassthrough = + (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0; + let pos = isPassthrough ? destOffset : 0; + const baseBuf = isPassthrough + ? dest + : new Uint8ClampedArray(baseNumComps * count); + const numComps = this.numComps; + + const scaled = new Float32Array(numComps); + const tinted = new Float32Array(baseNumComps); + let i, j; + + for (i = 0; i < count; i++) { + for (j = 0; j < numComps; j++) { + scaled[j] = src[srcOffset++] * scale; + } + tintFn(scaled, 0, tinted, 0); + if (usesZeroToOneRange) { + for (j = 0; j < baseNumComps; j++) { + baseBuf[pos++] = tinted[j] * 255; + } + } else { + base.getRgbItem(tinted, 0, baseBuf, pos); + pos += baseNumComps; + } + } + + if (!isPassthrough) { + base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01); + } + } + + getOutputLength(inputLength, alpha01) { + return this.base.getOutputLength( + (inputLength * this.base.numComps) / this.numComps, + alpha01 + ); + } +} + +class PatternCS extends ColorSpace { + constructor(baseCS) { + super("Pattern", null); + this.base = baseCS; + } + + isDefaultDecode(decodeMap, bpc) { + unreachable("Should not call PatternCS.isDefaultDecode"); + } +} + +/** + * The default color is `new Uint8Array([0])`. + */ +class IndexedCS extends ColorSpace { + constructor(base, highVal, lookup) { + super("Indexed", 1); + this.base = base; + + const length = base.numComps * (highVal + 1); + this.lookup = new Uint8Array(length); + + if (lookup instanceof BaseStream) { + const bytes = lookup.getBytes(length); + this.lookup.set(bytes); + } else if (typeof lookup === "string") { + for (let i = 0; i < length; ++i) { + this.lookup[i] = lookup.charCodeAt(i) & 0xff; + } + } else { + throw new FormatError(`IndexedCS - unrecognized lookup table: ${lookup}`); + } + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'IndexedCS.getRgbItem: Unsupported "dest" type.' + ); + } + const numComps = this.base.numComps; + const start = src[srcOffset] * numComps; + this.base.getRgbBuffer(this.lookup, start, 1, dest, destOffset, 8, 0); + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'IndexedCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const base = this.base; + const numComps = base.numComps; + const outputDelta = base.getOutputLength(numComps, alpha01); + const lookup = this.lookup; + + for (let i = 0; i < count; ++i) { + const lookupPos = src[srcOffset++] * numComps; + base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01); + destOffset += outputDelta; + } + } + + getOutputLength(inputLength, alpha01) { + return this.base.getOutputLength(inputLength * this.base.numComps, alpha01); + } + + isDefaultDecode(decodeMap, bpc) { + if (!Array.isArray(decodeMap)) { + return true; + } + if (decodeMap.length !== 2) { + warn("Decode map length is not correct"); + return true; + } + if (!Number.isInteger(bpc) || bpc < 1) { + warn("Bits per component is not correct"); + return true; + } + return decodeMap[0] === 0 && decodeMap[1] === (1 << bpc) - 1; + } +} + +/** + * The default color is `new Float32Array([0])`. + */ +class DeviceGrayCS extends ColorSpace { + constructor() { + super("DeviceGray", 1); + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceGrayCS.getRgbItem: Unsupported "dest" type.' + ); + } + const c = src[srcOffset] * 255; + dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceGrayCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const scale = 255 / ((1 << bits) - 1); + let j = srcOffset, + q = destOffset; + for (let i = 0; i < count; ++i) { + const c = scale * src[j++]; + dest[q++] = c; + dest[q++] = c; + dest[q++] = c; + q += alpha01; + } + } + + getOutputLength(inputLength, alpha01) { + return inputLength * (3 + alpha01); + } +} + +/** + * The default color is `new Float32Array([0, 0, 0])`. + */ +class DeviceRgbCS extends ColorSpace { + constructor() { + super("DeviceRGB", 3); + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceRgbCS.getRgbItem: Unsupported "dest" type.' + ); + } + dest[destOffset] = src[srcOffset] * 255; + dest[destOffset + 1] = src[srcOffset + 1] * 255; + dest[destOffset + 2] = src[srcOffset + 2] * 255; + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceRgbCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + if (bits === 8 && alpha01 === 0) { + dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset); + return; + } + const scale = 255 / ((1 << bits) - 1); + let j = srcOffset, + q = destOffset; + for (let i = 0; i < count; ++i) { + dest[q++] = scale * src[j++]; + dest[q++] = scale * src[j++]; + dest[q++] = scale * src[j++]; + q += alpha01; + } + } + + getOutputLength(inputLength, alpha01) { + return ((inputLength * (3 + alpha01)) / 3) | 0; + } + + isPassthrough(bits) { + return bits === 8; + } +} + +/** + * The default color is `new Float32Array([0, 0, 0, 1])`. + */ +class DeviceRgbaCS extends ColorSpace { + constructor() { + super("DeviceRGBA", 4); + } + + getOutputLength(inputLength, _alpha01) { + return inputLength * 4; + } + + isPassthrough(bits) { + return bits === 8; + } + + fillRgb( + dest, + originalWidth, + originalHeight, + width, + height, + actualHeight, + bpc, + comps, + alpha01 + ) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceRgbaCS.fillRgb: Unsupported "dest" type.' + ); + } + if (originalHeight !== height || originalWidth !== width) { + resizeRgbaImage( + comps, + dest, + originalWidth, + originalHeight, + width, + height, + alpha01 + ); + } else { + copyRgbaImage(comps, dest, alpha01); + } + } +} + +/** + * The default color is `new Float32Array([0, 0, 0, 1])`. + */ +class DeviceCmykCS extends ColorSpace { + constructor() { + super("DeviceCMYK", 4); + } + + // The coefficients below was found using numerical analysis: the method of + // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, + // where color_value is the tabular value from the table of sampled RGB colors + // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding + // CMYK color conversion using the estimation below: + // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 + #toRgb(src, srcOffset, srcScale, dest, destOffset) { + const c = src[srcOffset] * srcScale; + const m = src[srcOffset + 1] * srcScale; + const y = src[srcOffset + 2] * srcScale; + const k = src[srcOffset + 3] * srcScale; + + dest[destOffset] = + 255 + + c * + (-4.387332384609988 * c + + 54.48615194189176 * m + + 18.82290502165302 * y + + 212.25662451639585 * k + + -285.2331026137004) + + m * + (1.7149763477362134 * m - + 5.6096736904047315 * y + + -17.873870861415444 * k - + 5.497006427196366) + + y * + (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) + + k * (-21.86122147463605 * k - 189.48180835922747); + + dest[destOffset + 1] = + 255 + + c * + (8.841041422036149 * c + + 60.118027045597366 * m + + 6.871425592049007 * y + + 31.159100130055922 * k + + -79.2970844816548) + + m * + (-15.310361306967817 * m + + 17.575251261109482 * y + + 131.35250912493976 * k - + 190.9453302588951) + + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + + k * (-20.737325471181034 * k - 187.80453709719578); + + dest[destOffset + 2] = + 255 + + c * + (0.8842522430003296 * c + + 8.078677503112928 * m + + 30.89978309703729 * y - + 0.23883238689178934 * k + + -14.183576799673286) + + m * + (10.49593273432072 * m + + 63.02378494754052 * y + + 50.606957656360734 * k - + 112.23884253719248) + + y * + (0.03296041114873217 * y + + 115.60384449646641 * k + + -193.58209356861505) + + k * (-22.33816807309886 * k - 180.12613974708367); + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceCmykCS.getRgbItem: Unsupported "dest" type.' + ); + } + this.#toRgb(src, srcOffset, 1, dest, destOffset); + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'DeviceCmykCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const scale = 1 / ((1 << bits) - 1); + for (let i = 0; i < count; i++) { + this.#toRgb(src, srcOffset, scale, dest, destOffset); + srcOffset += 4; + destOffset += 3 + alpha01; + } + } + + getOutputLength(inputLength, alpha01) { + return ((inputLength / 4) * (3 + alpha01)) | 0; + } +} + +/** + * CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 + * + * The default color is `new Float32Array([0])`. + */ +class CalGrayCS extends ColorSpace { + constructor(whitePoint, blackPoint, gamma) { + super("CalGray", 1); + + if (!whitePoint) { + throw new FormatError( + "WhitePoint missing - required for color space CalGray" + ); + } + // Translate arguments to spec variables. + [this.XW, this.YW, this.ZW] = whitePoint; + [this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0]; + this.G = gamma || 1; + + // Validate variables as per spec. + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { + throw new FormatError( + `Invalid WhitePoint components for ${this.name}, no fallback available` + ); + } + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info(`Invalid BlackPoint for ${this.name}, falling back to default.`); + this.XB = this.YB = this.ZB = 0; + } + + if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { + warn( + `${this.name}, BlackPoint: XB: ${this.XB}, YB: ${this.YB}, ` + + `ZB: ${this.ZB}, only default values are supported.` + ); + } + + if (this.G < 1) { + info( + `Invalid Gamma: ${this.G} for ${this.name}, falling back to default.` + ); + this.G = 1; + } + } + + #toRgb(src, srcOffset, dest, destOffset, scale) { + // A represents a gray component of a calibrated gray space. + // A <---> AG in the spec + const A = src[srcOffset] * scale; + const AG = A ** this.G; + + // Computes L as per spec. ( = this.YW * AG ) + // Except if other than default BlackPoint values are used. + const L = this.YW * AG; + // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. + // Convert values to rgb range [0, 255]. + const val = Math.max(295.8 * L ** 0.3333333333333333 - 40.8, 0); + dest[destOffset] = val; + dest[destOffset + 1] = val; + dest[destOffset + 2] = val; + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'CalGrayCS.getRgbItem: Unsupported "dest" type.' + ); + } + this.#toRgb(src, srcOffset, dest, destOffset, 1); + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'CalGrayCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const scale = 1 / ((1 << bits) - 1); + + for (let i = 0; i < count; ++i) { + this.#toRgb(src, srcOffset, dest, destOffset, scale); + srcOffset += 1; + destOffset += 3 + alpha01; + } + } + + getOutputLength(inputLength, alpha01) { + return inputLength * (3 + alpha01); + } +} + +/** + * CalRGBCS: Based on "PDF Reference, Sixth Ed", p.247 + * + * The default color is `new Float32Array([0, 0, 0])`. + */ +class CalRGBCS extends ColorSpace { + // See http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html for these + // matrices. + // prettier-ignore + static #BRADFORD_SCALE_MATRIX = new Float32Array([ + 0.8951, 0.2664, -0.1614, + -0.7502, 1.7135, 0.0367, + 0.0389, -0.0685, 1.0296]); + + // prettier-ignore + static #BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([ + 0.9869929, -0.1470543, 0.1599627, + 0.4323053, 0.5183603, 0.0492912, + -0.0085287, 0.0400428, 0.9684867]); + + // See http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html. + // prettier-ignore + static #SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([ + 3.2404542, -1.5371385, -0.4985314, + -0.9692660, 1.8760108, 0.0415560, + 0.0556434, -0.2040259, 1.0572252]); + + static #FLAT_WHITEPOINT_MATRIX = new Float32Array([1, 1, 1]); + + static #tempNormalizeMatrix = new Float32Array(3); + + static #tempConvertMatrix1 = new Float32Array(3); + + static #tempConvertMatrix2 = new Float32Array(3); + + static #DECODE_L_CONSTANT = ((8 + 16) / 116) ** 3 / 8.0; + + constructor(whitePoint, blackPoint, gamma, matrix) { + super("CalRGB", 3); + + if (!whitePoint) { + throw new FormatError( + "WhitePoint missing - required for color space CalRGB" + ); + } + // Translate arguments to spec variables. + const [XW, YW, ZW] = (this.whitePoint = whitePoint); + const [XB, YB, ZB] = (this.blackPoint = blackPoint || new Float32Array(3)); + + [this.GR, this.GG, this.GB] = gamma || new Float32Array([1, 1, 1]); + [ + this.MXA, + this.MYA, + this.MZA, + this.MXB, + this.MYB, + this.MZB, + this.MXC, + this.MYC, + this.MZC, + ] = matrix || new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]); + + // Validate variables as per spec. + if (XW < 0 || ZW < 0 || YW !== 1) { + throw new FormatError( + `Invalid WhitePoint components for ${this.name}, no fallback available` + ); + } + + if (XB < 0 || YB < 0 || ZB < 0) { + info( + `Invalid BlackPoint for ${this.name} [${XB}, ${YB}, ${ZB}], ` + + "falling back to default." + ); + this.blackPoint = new Float32Array(3); + } + + if (this.GR < 0 || this.GG < 0 || this.GB < 0) { + info( + `Invalid Gamma [${this.GR}, ${this.GG}, ${this.GB}] for ` + + `${this.name}, falling back to default.` + ); + this.GR = this.GG = this.GB = 1; + } + } + + #matrixProduct(a, b, result) { + result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2]; + result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2]; + } + + #toFlat(sourceWhitePoint, LMS, result) { + result[0] = (LMS[0] * 1) / sourceWhitePoint[0]; + result[1] = (LMS[1] * 1) / sourceWhitePoint[1]; + result[2] = (LMS[2] * 1) / sourceWhitePoint[2]; + } + + #toD65(sourceWhitePoint, LMS, result) { + const D65X = 0.95047; + const D65Y = 1; + const D65Z = 1.08883; + + result[0] = (LMS[0] * D65X) / sourceWhitePoint[0]; + result[1] = (LMS[1] * D65Y) / sourceWhitePoint[1]; + result[2] = (LMS[2] * D65Z) / sourceWhitePoint[2]; + } + + #sRGBTransferFunction(color) { + // See http://en.wikipedia.org/wiki/SRGB. + if (color <= 0.0031308) { + return this.#adjustToRange(0, 1, 12.92 * color); + } + // Optimization: + // If color is close enough to 1, skip calling the following transform + // since calling Math.pow is expensive. If color is larger than + // the threshold, the final result is larger than 254.5 since + // ((1 + 0.055) * 0.99554525 ** (1 / 2.4) - 0.055) * 255 === + // 254.50000003134699 + if (color >= 0.99554525) { + return 1; + } + return this.#adjustToRange(0, 1, (1 + 0.055) * color ** (1 / 2.4) - 0.055); + } + + #adjustToRange(min, max, value) { + return Math.max(min, Math.min(max, value)); + } + + #decodeL(L) { + if (L < 0) { + return -this.#decodeL(-L); + } + if (L > 8.0) { + return ((L + 16) / 116) ** 3; + } + return L * CalRGBCS.#DECODE_L_CONSTANT; + } + + #compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) { + // In case the blackPoint is already the default blackPoint then there is + // no need to do compensation. + if ( + sourceBlackPoint[0] === 0 && + sourceBlackPoint[1] === 0 && + sourceBlackPoint[2] === 0 + ) { + result[0] = XYZ_Flat[0]; + result[1] = XYZ_Flat[1]; + result[2] = XYZ_Flat[2]; + return; + } + + // For the blackPoint calculation details, please see + // http://www.adobe.com/content/dam/Adobe/en/devnet/photoshop/sdk/ + // AdobeBPC.pdf. + // The destination blackPoint is the default blackPoint [0, 0, 0]. + const zeroDecodeL = this.#decodeL(0); + + const X_DST = zeroDecodeL; + const X_SRC = this.#decodeL(sourceBlackPoint[0]); + + const Y_DST = zeroDecodeL; + const Y_SRC = this.#decodeL(sourceBlackPoint[1]); + + const Z_DST = zeroDecodeL; + const Z_SRC = this.#decodeL(sourceBlackPoint[2]); + + const X_Scale = (1 - X_DST) / (1 - X_SRC); + const X_Offset = 1 - X_Scale; + + const Y_Scale = (1 - Y_DST) / (1 - Y_SRC); + const Y_Offset = 1 - Y_Scale; + + const Z_Scale = (1 - Z_DST) / (1 - Z_SRC); + const Z_Offset = 1 - Z_Scale; + + result[0] = XYZ_Flat[0] * X_Scale + X_Offset; + result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset; + result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset; + } + + #normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) { + // In case the whitePoint is already flat then there is no need to do + // normalization. + if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) { + result[0] = XYZ_In[0]; + result[1] = XYZ_In[1]; + result[2] = XYZ_In[2]; + return; + } + + const LMS = result; + this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS); + + const LMS_Flat = CalRGBCS.#tempNormalizeMatrix; + this.#toFlat(sourceWhitePoint, LMS, LMS_Flat); + + this.#matrixProduct( + CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, + LMS_Flat, + result + ); + } + + #normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) { + const LMS = result; + this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS); + + const LMS_D65 = CalRGBCS.#tempNormalizeMatrix; + this.#toD65(sourceWhitePoint, LMS, LMS_D65); + + this.#matrixProduct( + CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, + LMS_D65, + result + ); + } + + #toRgb(src, srcOffset, dest, destOffset, scale) { + // A, B and C represent a red, green and blue components of a calibrated + // rgb space. + const A = this.#adjustToRange(0, 1, src[srcOffset] * scale); + const B = this.#adjustToRange(0, 1, src[srcOffset + 1] * scale); + const C = this.#adjustToRange(0, 1, src[srcOffset + 2] * scale); + + // A <---> AGR in the spec + // B <---> BGG in the spec + // C <---> CGB in the spec + const AGR = A === 1 ? 1 : A ** this.GR; + const BGG = B === 1 ? 1 : B ** this.GG; + const CGB = C === 1 ? 1 : C ** this.GB; + + // Computes intermediate variables L, M, N as per spec. + // To decode X, Y, Z values map L, M, N directly to them. + const X = this.MXA * AGR + this.MXB * BGG + this.MXC * CGB; + const Y = this.MYA * AGR + this.MYB * BGG + this.MYC * CGB; + const Z = this.MZA * AGR + this.MZB * BGG + this.MZC * CGB; + + // The following calculations are based on this document: + // http://www.adobe.com/content/dam/Adobe/en/devnet/photoshop/sdk/ + // AdobeBPC.pdf. + const XYZ = CalRGBCS.#tempConvertMatrix1; + XYZ[0] = X; + XYZ[1] = Y; + XYZ[2] = Z; + const XYZ_Flat = CalRGBCS.#tempConvertMatrix2; + + this.#normalizeWhitePointToFlat(this.whitePoint, XYZ, XYZ_Flat); + + const XYZ_Black = CalRGBCS.#tempConvertMatrix1; + this.#compensateBlackPoint(this.blackPoint, XYZ_Flat, XYZ_Black); + + const XYZ_D65 = CalRGBCS.#tempConvertMatrix2; + this.#normalizeWhitePointToD65( + CalRGBCS.#FLAT_WHITEPOINT_MATRIX, + XYZ_Black, + XYZ_D65 + ); + + const SRGB = CalRGBCS.#tempConvertMatrix1; + this.#matrixProduct(CalRGBCS.#SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB); + + // Convert the values to rgb range [0, 255]. + dest[destOffset] = this.#sRGBTransferFunction(SRGB[0]) * 255; + dest[destOffset + 1] = this.#sRGBTransferFunction(SRGB[1]) * 255; + dest[destOffset + 2] = this.#sRGBTransferFunction(SRGB[2]) * 255; + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'CalRGBCS.getRgbItem: Unsupported "dest" type.' + ); + } + this.#toRgb(src, srcOffset, dest, destOffset, 1); + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'CalRGBCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const scale = 1 / ((1 << bits) - 1); + + for (let i = 0; i < count; ++i) { + this.#toRgb(src, srcOffset, dest, destOffset, scale); + srcOffset += 3; + destOffset += 3 + alpha01; + } + } + + getOutputLength(inputLength, alpha01) { + return ((inputLength * (3 + alpha01)) / 3) | 0; + } +} + +/** + * LabCS: Based on "PDF Reference, Sixth Ed", p.250 + * + * The default color is `new Float32Array([0, 0, 0])`. + */ +class LabCS extends ColorSpace { + constructor(whitePoint, blackPoint, range) { + super("Lab", 3); + + if (!whitePoint) { + throw new FormatError( + "WhitePoint missing - required for color space Lab" + ); + } + // Translate args to spec variables + [this.XW, this.YW, this.ZW] = whitePoint; + [this.amin, this.amax, this.bmin, this.bmax] = range || [ + -100, 100, -100, 100, + ]; + + // These are here just for completeness - the spec doesn't offer any + // formulas that use BlackPoint in Lab + [this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0]; + + // Validate vars as per spec + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { + throw new FormatError( + "Invalid WhitePoint components, no fallback available" + ); + } + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info("Invalid BlackPoint, falling back to default"); + this.XB = this.YB = this.ZB = 0; + } + + if (this.amin > this.amax || this.bmin > this.bmax) { + info("Invalid Range, falling back to defaults"); + this.amin = -100; + this.amax = 100; + this.bmin = -100; + this.bmax = 100; + } + } + + // Function g(x) from spec + #fn_g(x) { + return x >= 6 / 29 ? x ** 3 : (108 / 841) * (x - 4 / 29); + } + + #decode(value, high1, low2, high2) { + return low2 + (value * (high2 - low2)) / high1; + } + + // If decoding is needed maxVal should be 2^bits per component - 1. + #toRgb(src, srcOffset, maxVal, dest, destOffset) { + // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] + // not the usual [0, 1]. If a command like setFillColor is used the src + // values will already be within the correct range. However, if we are + // converting an image we have to map the values to the correct range given + // above. + // Ls,as,bs <---> L*,a*,b* in the spec + let Ls = src[srcOffset]; + let as = src[srcOffset + 1]; + let bs = src[srcOffset + 2]; + if (maxVal !== false) { + Ls = this.#decode(Ls, maxVal, 0, 100); + as = this.#decode(as, maxVal, this.amin, this.amax); + bs = this.#decode(bs, maxVal, this.bmin, this.bmax); + } + + // Adjust limits of 'as' and 'bs' + if (as > this.amax) { + as = this.amax; + } else if (as < this.amin) { + as = this.amin; + } + if (bs > this.bmax) { + bs = this.bmax; + } else if (bs < this.bmin) { + bs = this.bmin; + } + + // Computes intermediate variables X,Y,Z as per spec + const M = (Ls + 16) / 116; + const L = M + as / 500; + const N = M - bs / 200; + + const X = this.XW * this.#fn_g(L); + const Y = this.YW * this.#fn_g(M); + const Z = this.ZW * this.#fn_g(N); + + let r, g, b; + // Using different conversions for D50 and D65 white points, + // per http://www.color.org/srgb.pdf + if (this.ZW < 1) { + // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) + r = X * 3.1339 + Y * -1.617 + Z * -0.4906; + g = X * -0.9785 + Y * 1.916 + Z * 0.0333; + b = X * 0.072 + Y * -0.229 + Z * 1.4057; + } else { + // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) + r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; + g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; + b = X * 0.0557 + Y * -0.204 + Z * 1.057; + } + // Convert the color values to the [0,255] range (clamping is automatic). + dest[destOffset] = Math.sqrt(r) * 255; + dest[destOffset + 1] = Math.sqrt(g) * 255; + dest[destOffset + 2] = Math.sqrt(b) * 255; + } + + getRgbItem(src, srcOffset, dest, destOffset) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'LabCS.getRgbItem: Unsupported "dest" type.' + ); + } + this.#toRgb(src, srcOffset, false, dest, destOffset); + } + + getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + dest instanceof Uint8ClampedArray, + 'LabCS.getRgbBuffer: Unsupported "dest" type.' + ); + } + const maxVal = (1 << bits) - 1; + for (let i = 0; i < count; i++) { + this.#toRgb(src, srcOffset, maxVal, dest, destOffset); + srcOffset += 3; + destOffset += 3 + alpha01; + } + } + + getOutputLength(inputLength, alpha01) { + return ((inputLength * (3 + alpha01)) / 3) | 0; + } + + isDefaultDecode(decodeMap, bpc) { + // XXX: Decoding is handled with the lab conversion because of the strange + // ranges that are used. + return true; + } + + get usesZeroToOneRange() { + return shadow(this, "usesZeroToOneRange", false); + } +} + +export { ColorSpace }; diff --git a/frontend/public/pdf.js/src/core/core_utils.js b/frontend/public/pdf.js/src/core/core_utils.js new file mode 100644 index 0000000..f099f21 --- /dev/null +++ b/frontend/public/pdf.js/src/core/core_utils.js @@ -0,0 +1,753 @@ +/* Copyright 2019 Mozilla Foundation + * + * 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 { + AnnotationEditorPrefix, + assert, + BaseException, + hexNumbers, + objectSize, + stringToPDFString, + Util, + warn, +} from "../shared/util.js"; +import { Dict, isName, Ref, RefSet } from "./primitives.js"; +import { BaseStream } from "./base_stream.js"; + +const PDF_VERSION_REGEXP = /^[1-9]\.\d$/; +const MAX_INT_32 = 2 ** 31 - 1; +const MIN_INT_32 = -(2 ** 31); + +function getLookupTableFactory(initializer) { + let lookup; + return function () { + if (initializer) { + lookup = Object.create(null); + initializer(lookup); + initializer = null; + } + return lookup; + }; +} + +class MissingDataException extends BaseException { + constructor(begin, end) { + super(`Missing data [${begin}, ${end})`, "MissingDataException"); + this.begin = begin; + this.end = end; + } +} + +class ParserEOFException extends BaseException { + constructor(msg) { + super(msg, "ParserEOFException"); + } +} + +class XRefEntryException extends BaseException { + constructor(msg) { + super(msg, "XRefEntryException"); + } +} + +class XRefParseException extends BaseException { + constructor(msg) { + super(msg, "XRefParseException"); + } +} + +/** + * Combines multiple ArrayBuffers into a single Uint8Array. + * @param {Array} arr - An array of ArrayBuffers. + * @returns {Uint8Array} + */ +function arrayBuffersToBytes(arr) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + for (const item of arr) { + assert( + item instanceof ArrayBuffer, + "arrayBuffersToBytes - expected an ArrayBuffer." + ); + } + } + const length = arr.length; + if (length === 0) { + return new Uint8Array(0); + } + if (length === 1) { + return new Uint8Array(arr[0]); + } + let dataLength = 0; + for (let i = 0; i < length; i++) { + dataLength += arr[i].byteLength; + } + const data = new Uint8Array(dataLength); + let pos = 0; + for (let i = 0; i < length; i++) { + const item = new Uint8Array(arr[i]); + data.set(item, pos); + pos += item.byteLength; + } + return data; +} + +async function fetchBinaryData(url) { + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Failed to fetch file "${url}" with "${response.statusText}".` + ); + } + return new Uint8Array(await response.arrayBuffer()); +} + +/** + * Get the value of an inheritable property. + * + * If the PDF specification explicitly lists a property in a dictionary as + * inheritable, then the value of the property may be present in the dictionary + * itself or in one or more parents of the dictionary. + * + * If the key is not found in the tree, `undefined` is returned. Otherwise, + * the value for the key is returned or, if `stopWhenFound` is `false`, a list + * of values is returned. + * + * @param {Dict} dict - Dictionary from where to start the traversal. + * @param {string} key - The key of the property to find the value for. + * @param {boolean} getArray - Whether or not the value should be fetched as an + * array. The default value is `false`. + * @param {boolean} stopWhenFound - Whether or not to stop the traversal when + * the key is found. If set to `false`, we always walk up the entire parent + * chain, for example to be able to find `\Resources` placed on multiple + * levels of the tree. The default value is `true`. + */ +function getInheritableProperty({ + dict, + key, + getArray = false, + stopWhenFound = true, +}) { + let values; + const visited = new RefSet(); + + while (dict instanceof Dict && !(dict.objId && visited.has(dict.objId))) { + if (dict.objId) { + visited.put(dict.objId); + } + const value = getArray ? dict.getArray(key) : dict.get(key); + if (value !== undefined) { + if (stopWhenFound) { + return value; + } + (values ||= []).push(value); + } + dict = dict.get("Parent"); + } + return values; +} + +/** + * Get the parent dictionary to update when a property is set. + * + * @param {Dict} dict - Dictionary from where to start the traversal. + * @param {Ref} ref - The reference to the dictionary. + * @param {XRef} xref - The `XRef` instance. + */ +function getParentToUpdate(dict, ref, xref) { + const visited = new RefSet(); + const firstDict = dict; + const result = { dict: null, ref: null }; + + while (dict instanceof Dict && !visited.has(ref)) { + visited.put(ref); + if (dict.has("T")) { + break; + } + ref = dict.getRaw("Parent"); + if (!(ref instanceof Ref)) { + return result; + } + dict = xref.fetch(ref); + } + if (dict instanceof Dict && dict !== firstDict) { + result.dict = dict; + result.ref = ref; + } + return result; +} + +// prettier-ignore +const ROMAN_NUMBER_MAP = [ + "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", + "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", + "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" +]; + +/** + * Converts positive integers to (upper case) Roman numerals. + * @param {number} number - The number that should be converted. + * @param {boolean} lowerCase - Indicates if the result should be converted + * to lower case letters. The default value is `false`. + * @returns {string} The resulting Roman number. + */ +function toRomanNumerals(number, lowerCase = false) { + assert( + Number.isInteger(number) && number > 0, + "The number should be a positive integer." + ); + + const roman = + "M".repeat((number / 1000) | 0) + + ROMAN_NUMBER_MAP[((number % 1000) / 100) | 0] + + ROMAN_NUMBER_MAP[10 + (((number % 100) / 10) | 0)] + + ROMAN_NUMBER_MAP[20 + (number % 10)]; + return lowerCase ? roman.toLowerCase() : roman; +} + +// Calculate the base 2 logarithm of the number `x`. This differs from the +// native function in the sense that it returns the ceiling value and that it +// returns 0 instead of `Infinity`/`NaN` for `x` values smaller than/equal to 0. +function log2(x) { + return x > 0 ? Math.ceil(Math.log2(x)) : 0; +} + +function readInt8(data, offset) { + return (data[offset] << 24) >> 24; +} + +function readInt16(data, offset) { + return ((data[offset] << 24) | (data[offset + 1] << 16)) >> 16; +} + +function readUint16(data, offset) { + return (data[offset] << 8) | data[offset + 1]; +} + +function readUint32(data, offset) { + return ( + ((data[offset] << 24) | + (data[offset + 1] << 16) | + (data[offset + 2] << 8) | + data[offset + 3]) >>> + 0 + ); +} + +// Checks if ch is one of the following characters: SPACE, TAB, CR or LF. +function isWhiteSpace(ch) { + return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a; +} + +/** + * Checks if something is an Array containing only boolean values, + * and (optionally) checks its length. + * @param {any} arr + * @param {number | null} len + * @returns {boolean} + */ +function isBooleanArray(arr, len) { + return ( + Array.isArray(arr) && + (len === null || arr.length === len) && + arr.every(x => typeof x === "boolean") + ); +} + +/** + * Checks if something is an Array containing only numbers, + * and (optionally) checks its length. + * @param {any} arr + * @param {number | null} len + * @returns {boolean} + */ +function isNumberArray(arr, len) { + if (Array.isArray(arr)) { + return ( + (len === null || arr.length === len) && + arr.every(x => typeof x === "number") + ); + } + + // This check allows us to have typed arrays but not the + // BigInt64Array/BigUint64Array types (their elements aren't "number"). + return ( + ArrayBuffer.isView(arr) && + !(arr instanceof BigInt64Array || arr instanceof BigUint64Array) && + (len === null || arr.length === len) + ); +} + +// Returns the matrix, or the fallback value if it's invalid. +function lookupMatrix(arr, fallback) { + return isNumberArray(arr, 6) ? arr : fallback; +} + +// Returns the rectangle, or the fallback value if it's invalid. +function lookupRect(arr, fallback) { + return isNumberArray(arr, 4) ? arr : fallback; +} + +// Returns the normalized rectangle, or the fallback value if it's invalid. +function lookupNormalRect(arr, fallback) { + return isNumberArray(arr, 4) ? Util.normalizeRect(arr) : fallback; +} + +/** + * AcroForm field names use an array like notation to refer to + * repeated XFA elements e.g. foo.bar[nnn]. + * see: XFA Spec Chapter 3 - Repeated Elements + * + * @param {string} path - XFA path name. + * @returns {Array} - Array of Objects with the name and pos of + * each part of the path. + */ +function parseXFAPath(path) { + const positionPattern = /(.+)\[(\d+)\]$/; + return path.split(".").map(component => { + const m = component.match(positionPattern); + if (m) { + return { name: m[1], pos: parseInt(m[2], 10) }; + } + return { name: component, pos: 0 }; + }); +} + +function escapePDFName(str) { + const buffer = []; + let start = 0; + for (let i = 0, ii = str.length; i < ii; i++) { + const char = str.charCodeAt(i); + // Whitespace or delimiters aren't regular chars, so escape them. + if ( + char < 0x21 || + char > 0x7e || + char === 0x23 /* # */ || + char === 0x28 /* ( */ || + char === 0x29 /* ) */ || + char === 0x3c /* < */ || + char === 0x3e /* > */ || + char === 0x5b /* [ */ || + char === 0x5d /* ] */ || + char === 0x7b /* { */ || + char === 0x7d /* } */ || + char === 0x2f /* / */ || + char === 0x25 /* % */ + ) { + if (start < i) { + buffer.push(str.substring(start, i)); + } + buffer.push(`#${char.toString(16)}`); + start = i + 1; + } + } + + if (buffer.length === 0) { + return str; + } + + if (start < str.length) { + buffer.push(str.substring(start, str.length)); + } + + return buffer.join(""); +} + +// Replace "(", ")", "\n", "\r" and "\" by "\(", "\)", "\\n", "\\r" and "\\" +// in order to write it in a PDF file. +function escapeString(str) { + return str.replaceAll(/([()\\\n\r])/g, match => { + if (match === "\n") { + return "\\n"; + } else if (match === "\r") { + return "\\r"; + } + return `\\${match}`; + }); +} + +function _collectJS(entry, xref, list, parents) { + if (!entry) { + return; + } + + let parent = null; + if (entry instanceof Ref) { + if (parents.has(entry)) { + // If we've already found entry then we've a cycle. + return; + } + parent = entry; + parents.put(parent); + entry = xref.fetch(entry); + } + if (Array.isArray(entry)) { + for (const element of entry) { + _collectJS(element, xref, list, parents); + } + } else if (entry instanceof Dict) { + if (isName(entry.get("S"), "JavaScript")) { + const js = entry.get("JS"); + let code; + if (js instanceof BaseStream) { + code = js.getString(); + } else if (typeof js === "string") { + code = js; + } + code &&= stringToPDFString(code).replaceAll("\x00", ""); + if (code) { + list.push(code); + } + } + _collectJS(entry.getRaw("Next"), xref, list, parents); + } + + if (parent) { + parents.remove(parent); + } +} + +function collectActions(xref, dict, eventType) { + const actions = Object.create(null); + const additionalActionsDicts = getInheritableProperty({ + dict, + key: "AA", + stopWhenFound: false, + }); + if (additionalActionsDicts) { + // additionalActionsDicts contains dicts from ancestors + // as they're found in the tree from bottom to top. + // So the dicts are visited in reverse order to guarantee + // that actions from elder ancestors will be overwritten + // by ones from younger ancestors. + for (let i = additionalActionsDicts.length - 1; i >= 0; i--) { + const additionalActions = additionalActionsDicts[i]; + if (!(additionalActions instanceof Dict)) { + continue; + } + for (const key of additionalActions.getKeys()) { + const action = eventType[key]; + if (!action) { + continue; + } + const actionDict = additionalActions.getRaw(key); + const parents = new RefSet(); + const list = []; + _collectJS(actionDict, xref, list, parents); + if (list.length > 0) { + actions[action] = list; + } + } + } + } + // Collect the Action if any (we may have one on pushbutton). + if (dict.has("A")) { + const actionDict = dict.get("A"); + const parents = new RefSet(); + const list = []; + _collectJS(actionDict, xref, list, parents); + if (list.length > 0) { + actions.Action = list; + } + } + return objectSize(actions) > 0 ? actions : null; +} + +const XMLEntities = { + /* < */ 0x3c: "<", + /* > */ 0x3e: ">", + /* & */ 0x26: "&", + /* " */ 0x22: """, + /* ' */ 0x27: "'", +}; + +function* codePointIter(str) { + for (let i = 0, ii = str.length; i < ii; i++) { + const char = str.codePointAt(i); + if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) { + // char is represented by two u16 + i++; + } + yield char; + } +} + +function encodeToXmlString(str) { + const buffer = []; + let start = 0; + for (let i = 0, ii = str.length; i < ii; i++) { + const char = str.codePointAt(i); + if (0x20 <= char && char <= 0x7e) { + // ascii + const entity = XMLEntities[char]; + if (entity) { + if (start < i) { + buffer.push(str.substring(start, i)); + } + buffer.push(entity); + start = i + 1; + } + } else { + if (start < i) { + buffer.push(str.substring(start, i)); + } + buffer.push(`&#x${char.toString(16).toUpperCase()};`); + if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) { + // char is represented by two u16 + i++; + } + start = i + 1; + } + } + + if (buffer.length === 0) { + return str; + } + if (start < str.length) { + buffer.push(str.substring(start, str.length)); + } + return buffer.join(""); +} + +function validateFontName(fontFamily, mustWarn = false) { + // See https://developer.mozilla.org/en-US/docs/Web/CSS/string. + const m = /^("|').*("|')$/.exec(fontFamily); + if (m && m[1] === m[2]) { + const re = new RegExp(`[^\\\\]${m[1]}`); + if (re.test(fontFamily.slice(1, -1))) { + if (mustWarn) { + warn(`FontFamily contains unescaped ${m[1]}: ${fontFamily}.`); + } + return false; + } + } else { + // See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident. + for (const ident of fontFamily.split(/[ \t]+/)) { + if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) { + if (mustWarn) { + warn(`FontFamily contains invalid : ${fontFamily}.`); + } + return false; + } + } + } + return true; +} + +function validateCSSFont(cssFontInfo) { + // See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style. + const DEFAULT_CSS_FONT_OBLIQUE = "14"; + // See https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight. + const DEFAULT_CSS_FONT_WEIGHT = "400"; + const CSS_FONT_WEIGHT_VALUES = new Set([ + "100", + "200", + "300", + "400", + "500", + "600", + "700", + "800", + "900", + "1000", + "normal", + "bold", + "bolder", + "lighter", + ]); + + const { fontFamily, fontWeight, italicAngle } = cssFontInfo; + + if (!validateFontName(fontFamily, true)) { + return false; + } + + const weight = fontWeight ? fontWeight.toString() : ""; + cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight) + ? weight + : DEFAULT_CSS_FONT_WEIGHT; + + const angle = parseFloat(italicAngle); + cssFontInfo.italicAngle = + isNaN(angle) || angle < -90 || angle > 90 + ? DEFAULT_CSS_FONT_OBLIQUE + : italicAngle.toString(); + + return true; +} + +function recoverJsURL(str) { + // Attempt to recover valid URLs from `JS` entries with certain + // white-listed formats: + // - window.open('http://example.com') + // - app.launchURL('http://example.com', true) + // - xfa.host.gotoURL('http://example.com') + const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"]; + const regex = new RegExp( + "^\\s*(" + + URL_OPEN_METHODS.join("|").replaceAll(".", "\\.") + + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", + "i" + ); + + const jsUrl = regex.exec(str); + if (jsUrl?.[2]) { + return { + url: jsUrl[2], + newWindow: jsUrl[1] === "app.launchURL" && jsUrl[3] === "true", + }; + } + + return null; +} + +function numberToString(value) { + if (Number.isInteger(value)) { + return value.toString(); + } + + const roundedValue = Math.round(value * 100); + if (roundedValue % 100 === 0) { + return (roundedValue / 100).toString(); + } + + if (roundedValue % 10 === 0) { + return value.toFixed(1); + } + + return value.toFixed(2); +} + +function getNewAnnotationsMap(annotationStorage) { + if (!annotationStorage) { + return null; + } + const newAnnotationsByPage = new Map(); + // The concept of page in a XFA is very different, so + // editing is just not implemented. + for (const [key, value] of annotationStorage) { + if (!key.startsWith(AnnotationEditorPrefix)) { + continue; + } + let annotations = newAnnotationsByPage.get(value.pageIndex); + if (!annotations) { + annotations = []; + newAnnotationsByPage.set(value.pageIndex, annotations); + } + annotations.push(value); + } + return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null; +} + +function stringToAsciiOrUTF16BE(str) { + return isAscii(str) ? str : stringToUTF16String(str, /* bigEndian = */ true); +} + +function isAscii(str) { + return /^[\x00-\x7F]*$/.test(str); +} + +function stringToUTF16HexString(str) { + const buf = []; + for (let i = 0, ii = str.length; i < ii; i++) { + const char = str.charCodeAt(i); + buf.push(hexNumbers[(char >> 8) & 0xff], hexNumbers[char & 0xff]); + } + return buf.join(""); +} + +function stringToUTF16String(str, bigEndian = false) { + const buf = []; + if (bigEndian) { + buf.push("\xFE\xFF"); + } + for (let i = 0, ii = str.length; i < ii; i++) { + const char = str.charCodeAt(i); + buf.push( + String.fromCharCode((char >> 8) & 0xff), + String.fromCharCode(char & 0xff) + ); + } + return buf.join(""); +} + +function getRotationMatrix(rotation, width, height) { + switch (rotation) { + case 90: + return [0, 1, -1, 0, width, 0]; + case 180: + return [-1, 0, 0, -1, width, height]; + case 270: + return [0, -1, 1, 0, 0, height]; + default: + throw new Error("Invalid rotation"); + } +} + +/** + * Get the number of bytes to use to represent the given positive integer. + * If n is zero, the function returns 0 which means that we don't need to waste + * a byte to represent it. + * @param {number} x - a positive integer. + * @returns {number} + */ +function getSizeInBytes(x) { + // n bits are required for numbers up to 2^n - 1. + // So for a number x, we need ceil(log2(1 + x)) bits. + return Math.ceil(Math.ceil(Math.log2(1 + x)) / 8); +} + +export { + arrayBuffersToBytes, + codePointIter, + collectActions, + encodeToXmlString, + escapePDFName, + escapeString, + fetchBinaryData, + getInheritableProperty, + getLookupTableFactory, + getNewAnnotationsMap, + getParentToUpdate, + getRotationMatrix, + getSizeInBytes, + isAscii, + isBooleanArray, + isNumberArray, + isWhiteSpace, + log2, + lookupMatrix, + lookupNormalRect, + lookupRect, + MAX_INT_32, + MIN_INT_32, + MissingDataException, + numberToString, + ParserEOFException, + parseXFAPath, + PDF_VERSION_REGEXP, + readInt16, + readInt8, + readUint16, + readUint32, + recoverJsURL, + stringToAsciiOrUTF16BE, + stringToUTF16HexString, + stringToUTF16String, + toRomanNumerals, + validateCSSFont, + validateFontName, + XRefEntryException, + XRefParseException, +}; diff --git a/frontend/public/pdf.js/src/core/crypto.js b/frontend/public/pdf.js/src/core/crypto.js new file mode 100644 index 0000000..5c95a7b --- /dev/null +++ b/frontend/public/pdf.js/src/core/crypto.js @@ -0,0 +1,1852 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + bytesToString, + FormatError, + isArrayEqual, + PasswordException, + PasswordResponses, + stringToBytes, + unreachable, + utf8StringToString, + warn, +} from "../shared/util.js"; +import { Dict, isName, Name } from "./primitives.js"; +import { DecryptStream } from "./decrypt_stream.js"; + +class ARCFourCipher { + constructor(key) { + this.a = 0; + this.b = 0; + const s = new Uint8Array(256); + const keyLength = key.length; + + for (let i = 0; i < 256; ++i) { + s[i] = i; + } + for (let i = 0, j = 0; i < 256; ++i) { + const tmp = s[i]; + j = (j + tmp + key[i % keyLength]) & 0xff; + s[i] = s[j]; + s[j] = tmp; + } + this.s = s; + } + + encryptBlock(data) { + let a = this.a, + b = this.b; + const s = this.s; + const n = data.length; + const output = new Uint8Array(n); + for (let i = 0; i < n; ++i) { + a = (a + 1) & 0xff; + const tmp = s[a]; + b = (b + tmp) & 0xff; + const tmp2 = s[b]; + s[a] = tmp2; + s[b] = tmp; + output[i] = data[i] ^ s[(tmp + tmp2) & 0xff]; + } + this.a = a; + this.b = b; + return output; + } + + decryptBlock(data) { + return this.encryptBlock(data); + } + + encrypt(data) { + return this.encryptBlock(data); + } +} + +const calculateMD5 = (function calculateMD5Closure() { + const r = new Uint8Array([ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, + 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, + 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, + 15, 21, + ]); + + const k = new Int32Array([ + -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, + -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, + 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, + 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, + 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784, + 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556, + -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222, + -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, + -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606, + -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649, + -145523070, -1120210379, 718787259, -343485551, + ]); + + function hash(data, offset, length) { + let h0 = 1732584193, + h1 = -271733879, + h2 = -1732584194, + h3 = 271733878; + // pre-processing + const paddedLength = (length + 72) & ~63; // data + 9 extra bytes + const padded = new Uint8Array(paddedLength); + let i, j; + for (i = 0; i < length; ++i) { + padded[i] = data[offset++]; + } + padded[i++] = 0x80; + const n = paddedLength - 8; + while (i < n) { + padded[i++] = 0; + } + padded[i++] = (length << 3) & 0xff; + padded[i++] = (length >> 5) & 0xff; + padded[i++] = (length >> 13) & 0xff; + padded[i++] = (length >> 21) & 0xff; + padded[i++] = (length >>> 29) & 0xff; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + const w = new Int32Array(16); + for (i = 0; i < paddedLength; ) { + for (j = 0; j < 16; ++j, i += 4) { + w[j] = + padded[i] | + (padded[i + 1] << 8) | + (padded[i + 2] << 16) | + (padded[i + 3] << 24); + } + let a = h0, + b = h1, + c = h2, + d = h3, + f, + g; + for (j = 0; j < 64; ++j) { + if (j < 16) { + f = (b & c) | (~b & d); + g = j; + } else if (j < 32) { + f = (d & b) | (~d & c); + g = (5 * j + 1) & 15; + } else if (j < 48) { + f = b ^ c ^ d; + g = (3 * j + 5) & 15; + } else { + f = c ^ (b | ~d); + g = (7 * j) & 15; + } + const tmp = d, + rotateArg = (a + f + k[j] + w[g]) | 0, + rotate = r[j]; + d = c; + c = b; + b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0; + a = tmp; + } + h0 = (h0 + a) | 0; + h1 = (h1 + b) | 0; + h2 = (h2 + c) | 0; + h3 = (h3 + d) | 0; + } + // prettier-ignore + return new Uint8Array([ + h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF, + h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF, + h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF, + h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF + ]); + } + + return hash; +})(); + +class Word64 { + constructor(highInteger, lowInteger) { + this.high = highInteger | 0; + this.low = lowInteger | 0; + } + + and(word) { + this.high &= word.high; + this.low &= word.low; + } + + xor(word) { + this.high ^= word.high; + this.low ^= word.low; + } + + or(word) { + this.high |= word.high; + this.low |= word.low; + } + + shiftRight(places) { + if (places >= 32) { + this.low = (this.high >>> (places - 32)) | 0; + this.high = 0; + } else { + this.low = (this.low >>> places) | (this.high << (32 - places)); + this.high = (this.high >>> places) | 0; + } + } + + shiftLeft(places) { + if (places >= 32) { + this.high = this.low << (places - 32); + this.low = 0; + } else { + this.high = (this.high << places) | (this.low >>> (32 - places)); + this.low <<= places; + } + } + + rotateRight(places) { + let low, high; + if (places & 32) { + high = this.low; + low = this.high; + } else { + low = this.low; + high = this.high; + } + places &= 31; + this.low = (low >>> places) | (high << (32 - places)); + this.high = (high >>> places) | (low << (32 - places)); + } + + not() { + this.high = ~this.high; + this.low = ~this.low; + } + + add(word) { + const lowAdd = (this.low >>> 0) + (word.low >>> 0); + let highAdd = (this.high >>> 0) + (word.high >>> 0); + if (lowAdd > 0xffffffff) { + highAdd += 1; + } + this.low = lowAdd | 0; + this.high = highAdd | 0; + } + + copyTo(bytes, offset) { + bytes[offset] = (this.high >>> 24) & 0xff; + bytes[offset + 1] = (this.high >> 16) & 0xff; + bytes[offset + 2] = (this.high >> 8) & 0xff; + bytes[offset + 3] = this.high & 0xff; + bytes[offset + 4] = (this.low >>> 24) & 0xff; + bytes[offset + 5] = (this.low >> 16) & 0xff; + bytes[offset + 6] = (this.low >> 8) & 0xff; + bytes[offset + 7] = this.low & 0xff; + } + + assign(word) { + this.high = word.high; + this.low = word.low; + } +} + +const calculateSHA256 = (function calculateSHA256Closure() { + function rotr(x, n) { + return (x >>> n) | (x << (32 - n)); + } + + function ch(x, y, z) { + return (x & y) ^ (~x & z); + } + + function maj(x, y, z) { + return (x & y) ^ (x & z) ^ (y & z); + } + + function sigma(x) { + return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); + } + + function sigmaPrime(x) { + return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); + } + + function littleSigma(x) { + return rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3); + } + + function littleSigmaPrime(x) { + return rotr(x, 17) ^ rotr(x, 19) ^ (x >>> 10); + } + + const k = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + ]; + + function hash(data, offset, length) { + // initial hash values + let h0 = 0x6a09e667, + h1 = 0xbb67ae85, + h2 = 0x3c6ef372, + h3 = 0xa54ff53a, + h4 = 0x510e527f, + h5 = 0x9b05688c, + h6 = 0x1f83d9ab, + h7 = 0x5be0cd19; + // pre-processing + const paddedLength = Math.ceil((length + 9) / 64) * 64; + const padded = new Uint8Array(paddedLength); + let i, j; + for (i = 0; i < length; ++i) { + padded[i] = data[offset++]; + } + padded[i++] = 0x80; + const n = paddedLength - 8; + while (i < n) { + padded[i++] = 0; + } + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = (length >>> 29) & 0xff; + padded[i++] = (length >> 21) & 0xff; + padded[i++] = (length >> 13) & 0xff; + padded[i++] = (length >> 5) & 0xff; + padded[i++] = (length << 3) & 0xff; + const w = new Uint32Array(64); + // for each 512 bit block + for (i = 0; i < paddedLength; ) { + for (j = 0; j < 16; ++j) { + w[j] = + (padded[i] << 24) | + (padded[i + 1] << 16) | + (padded[i + 2] << 8) | + padded[i + 3]; + i += 4; + } + + for (j = 16; j < 64; ++j) { + w[j] = + (littleSigmaPrime(w[j - 2]) + + w[j - 7] + + littleSigma(w[j - 15]) + + w[j - 16]) | + 0; + } + let a = h0, + b = h1, + c = h2, + d = h3, + e = h4, + f = h5, + g = h6, + h = h7, + t1, + t2; + for (j = 0; j < 64; ++j) { + t1 = h + sigmaPrime(e) + ch(e, f, g) + k[j] + w[j]; + t2 = sigma(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + h0 = (h0 + a) | 0; + h1 = (h1 + b) | 0; + h2 = (h2 + c) | 0; + h3 = (h3 + d) | 0; + h4 = (h4 + e) | 0; + h5 = (h5 + f) | 0; + h6 = (h6 + g) | 0; + h7 = (h7 + h) | 0; + } + // prettier-ignore + return new Uint8Array([ + (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, (h0) & 0xFF, + (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, (h1) & 0xFF, + (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, (h2) & 0xFF, + (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, (h3) & 0xFF, + (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, (h4) & 0xFF, + (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, (h5) & 0xFF, + (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, (h6) & 0xFF, + (h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, (h7) & 0xFF + ]); + } + + return hash; +})(); + +const calculateSHA512 = (function calculateSHA512Closure() { + function ch(result, x, y, z, tmp) { + result.assign(x); + result.and(y); + tmp.assign(x); + tmp.not(); + tmp.and(z); + result.xor(tmp); + } + + function maj(result, x, y, z, tmp) { + result.assign(x); + result.and(y); + tmp.assign(x); + tmp.and(z); + result.xor(tmp); + tmp.assign(y); + tmp.and(z); + result.xor(tmp); + } + + function sigma(result, x, tmp) { + result.assign(x); + result.rotateRight(28); + tmp.assign(x); + tmp.rotateRight(34); + result.xor(tmp); + tmp.assign(x); + tmp.rotateRight(39); + result.xor(tmp); + } + + function sigmaPrime(result, x, tmp) { + result.assign(x); + result.rotateRight(14); + tmp.assign(x); + tmp.rotateRight(18); + result.xor(tmp); + tmp.assign(x); + tmp.rotateRight(41); + result.xor(tmp); + } + + function littleSigma(result, x, tmp) { + result.assign(x); + result.rotateRight(1); + tmp.assign(x); + tmp.rotateRight(8); + result.xor(tmp); + tmp.assign(x); + tmp.shiftRight(7); + result.xor(tmp); + } + + function littleSigmaPrime(result, x, tmp) { + result.assign(x); + result.rotateRight(19); + tmp.assign(x); + tmp.rotateRight(61); + result.xor(tmp); + tmp.assign(x); + tmp.shiftRight(6); + result.xor(tmp); + } + + // prettier-ignore + const k = [ + new Word64(0x428a2f98, 0xd728ae22), new Word64(0x71374491, 0x23ef65cd), + new Word64(0xb5c0fbcf, 0xec4d3b2f), new Word64(0xe9b5dba5, 0x8189dbbc), + new Word64(0x3956c25b, 0xf348b538), new Word64(0x59f111f1, 0xb605d019), + new Word64(0x923f82a4, 0xaf194f9b), new Word64(0xab1c5ed5, 0xda6d8118), + new Word64(0xd807aa98, 0xa3030242), new Word64(0x12835b01, 0x45706fbe), + new Word64(0x243185be, 0x4ee4b28c), new Word64(0x550c7dc3, 0xd5ffb4e2), + new Word64(0x72be5d74, 0xf27b896f), new Word64(0x80deb1fe, 0x3b1696b1), + new Word64(0x9bdc06a7, 0x25c71235), new Word64(0xc19bf174, 0xcf692694), + new Word64(0xe49b69c1, 0x9ef14ad2), new Word64(0xefbe4786, 0x384f25e3), + new Word64(0x0fc19dc6, 0x8b8cd5b5), new Word64(0x240ca1cc, 0x77ac9c65), + new Word64(0x2de92c6f, 0x592b0275), new Word64(0x4a7484aa, 0x6ea6e483), + new Word64(0x5cb0a9dc, 0xbd41fbd4), new Word64(0x76f988da, 0x831153b5), + new Word64(0x983e5152, 0xee66dfab), new Word64(0xa831c66d, 0x2db43210), + new Word64(0xb00327c8, 0x98fb213f), new Word64(0xbf597fc7, 0xbeef0ee4), + new Word64(0xc6e00bf3, 0x3da88fc2), new Word64(0xd5a79147, 0x930aa725), + new Word64(0x06ca6351, 0xe003826f), new Word64(0x14292967, 0x0a0e6e70), + new Word64(0x27b70a85, 0x46d22ffc), new Word64(0x2e1b2138, 0x5c26c926), + new Word64(0x4d2c6dfc, 0x5ac42aed), new Word64(0x53380d13, 0x9d95b3df), + new Word64(0x650a7354, 0x8baf63de), new Word64(0x766a0abb, 0x3c77b2a8), + new Word64(0x81c2c92e, 0x47edaee6), new Word64(0x92722c85, 0x1482353b), + new Word64(0xa2bfe8a1, 0x4cf10364), new Word64(0xa81a664b, 0xbc423001), + new Word64(0xc24b8b70, 0xd0f89791), new Word64(0xc76c51a3, 0x0654be30), + new Word64(0xd192e819, 0xd6ef5218), new Word64(0xd6990624, 0x5565a910), + new Word64(0xf40e3585, 0x5771202a), new Word64(0x106aa070, 0x32bbd1b8), + new Word64(0x19a4c116, 0xb8d2d0c8), new Word64(0x1e376c08, 0x5141ab53), + new Word64(0x2748774c, 0xdf8eeb99), new Word64(0x34b0bcb5, 0xe19b48a8), + new Word64(0x391c0cb3, 0xc5c95a63), new Word64(0x4ed8aa4a, 0xe3418acb), + new Word64(0x5b9cca4f, 0x7763e373), new Word64(0x682e6ff3, 0xd6b2b8a3), + new Word64(0x748f82ee, 0x5defb2fc), new Word64(0x78a5636f, 0x43172f60), + new Word64(0x84c87814, 0xa1f0ab72), new Word64(0x8cc70208, 0x1a6439ec), + new Word64(0x90befffa, 0x23631e28), new Word64(0xa4506ceb, 0xde82bde9), + new Word64(0xbef9a3f7, 0xb2c67915), new Word64(0xc67178f2, 0xe372532b), + new Word64(0xca273ece, 0xea26619c), new Word64(0xd186b8c7, 0x21c0c207), + new Word64(0xeada7dd6, 0xcde0eb1e), new Word64(0xf57d4f7f, 0xee6ed178), + new Word64(0x06f067aa, 0x72176fba), new Word64(0x0a637dc5, 0xa2c898a6), + new Word64(0x113f9804, 0xbef90dae), new Word64(0x1b710b35, 0x131c471b), + new Word64(0x28db77f5, 0x23047d84), new Word64(0x32caab7b, 0x40c72493), + new Word64(0x3c9ebe0a, 0x15c9bebc), new Word64(0x431d67c4, 0x9c100d4c), + new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a), + new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)]; + + function hash(data, offset, length, mode384 = false) { + // initial hash values + let h0, h1, h2, h3, h4, h5, h6, h7; + if (!mode384) { + h0 = new Word64(0x6a09e667, 0xf3bcc908); + h1 = new Word64(0xbb67ae85, 0x84caa73b); + h2 = new Word64(0x3c6ef372, 0xfe94f82b); + h3 = new Word64(0xa54ff53a, 0x5f1d36f1); + h4 = new Word64(0x510e527f, 0xade682d1); + h5 = new Word64(0x9b05688c, 0x2b3e6c1f); + h6 = new Word64(0x1f83d9ab, 0xfb41bd6b); + h7 = new Word64(0x5be0cd19, 0x137e2179); + } else { + // SHA384 is exactly the same + // except with different starting values and a trimmed result + h0 = new Word64(0xcbbb9d5d, 0xc1059ed8); + h1 = new Word64(0x629a292a, 0x367cd507); + h2 = new Word64(0x9159015a, 0x3070dd17); + h3 = new Word64(0x152fecd8, 0xf70e5939); + h4 = new Word64(0x67332667, 0xffc00b31); + h5 = new Word64(0x8eb44a87, 0x68581511); + h6 = new Word64(0xdb0c2e0d, 0x64f98fa7); + h7 = new Word64(0x47b5481d, 0xbefa4fa4); + } + + // pre-processing + const paddedLength = Math.ceil((length + 17) / 128) * 128; + const padded = new Uint8Array(paddedLength); + let i, j; + for (i = 0; i < length; ++i) { + padded[i] = data[offset++]; + } + padded[i++] = 0x80; + const n = paddedLength - 16; + while (i < n) { + padded[i++] = 0; + } + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = (length >>> 29) & 0xff; + padded[i++] = (length >> 21) & 0xff; + padded[i++] = (length >> 13) & 0xff; + padded[i++] = (length >> 5) & 0xff; + padded[i++] = (length << 3) & 0xff; + + const w = new Array(80); + for (i = 0; i < 80; i++) { + w[i] = new Word64(0, 0); + } + let a = new Word64(0, 0), + b = new Word64(0, 0), + c = new Word64(0, 0); + let d = new Word64(0, 0), + e = new Word64(0, 0), + f = new Word64(0, 0); + let g = new Word64(0, 0), + h = new Word64(0, 0); + const t1 = new Word64(0, 0), + t2 = new Word64(0, 0); + const tmp1 = new Word64(0, 0), + tmp2 = new Word64(0, 0); + let tmp3; + + // for each 1024 bit block + for (i = 0; i < paddedLength; ) { + for (j = 0; j < 16; ++j) { + w[j].high = + (padded[i] << 24) | + (padded[i + 1] << 16) | + (padded[i + 2] << 8) | + padded[i + 3]; + w[j].low = + (padded[i + 4] << 24) | + (padded[i + 5] << 16) | + (padded[i + 6] << 8) | + padded[i + 7]; + i += 8; + } + for (j = 16; j < 80; ++j) { + tmp3 = w[j]; + littleSigmaPrime(tmp3, w[j - 2], tmp2); + tmp3.add(w[j - 7]); + littleSigma(tmp1, w[j - 15], tmp2); + tmp3.add(tmp1); + tmp3.add(w[j - 16]); + } + + a.assign(h0); + b.assign(h1); + c.assign(h2); + d.assign(h3); + e.assign(h4); + f.assign(h5); + g.assign(h6); + h.assign(h7); + for (j = 0; j < 80; ++j) { + t1.assign(h); + sigmaPrime(tmp1, e, tmp2); + t1.add(tmp1); + ch(tmp1, e, f, g, tmp2); + t1.add(tmp1); + t1.add(k[j]); + t1.add(w[j]); + + sigma(t2, a, tmp2); + maj(tmp1, a, b, c, tmp2); + t2.add(tmp1); + + tmp3 = h; + h = g; + g = f; + f = e; + d.add(t1); + e = d; + d = c; + c = b; + b = a; + tmp3.assign(t1); + tmp3.add(t2); + a = tmp3; + } + h0.add(a); + h1.add(b); + h2.add(c); + h3.add(d); + h4.add(e); + h5.add(f); + h6.add(g); + h7.add(h); + } + + let result; + if (!mode384) { + result = new Uint8Array(64); + h0.copyTo(result, 0); + h1.copyTo(result, 8); + h2.copyTo(result, 16); + h3.copyTo(result, 24); + h4.copyTo(result, 32); + h5.copyTo(result, 40); + h6.copyTo(result, 48); + h7.copyTo(result, 56); + } else { + result = new Uint8Array(48); + h0.copyTo(result, 0); + h1.copyTo(result, 8); + h2.copyTo(result, 16); + h3.copyTo(result, 24); + h4.copyTo(result, 32); + h5.copyTo(result, 40); + } + return result; + } + + return hash; +})(); + +function calculateSHA384(data, offset, length) { + return calculateSHA512(data, offset, length, /* mode384 = */ true); +} + +class NullCipher { + decryptBlock(data) { + return data; + } + + encrypt(data) { + return data; + } +} + +class AESBaseCipher { + constructor() { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === AESBaseCipher + ) { + unreachable("Cannot initialize AESBaseCipher."); + } + + this._s = new Uint8Array([ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, + 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, + 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, + 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, + 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, + 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, + 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, + 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, + 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, + 0xb0, 0x54, 0xbb, 0x16, + ]); + + this._inv_s = new Uint8Array([ + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, + 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, + 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, + 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, + 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, + 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, + 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, + 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, + 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, + 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, + 0x55, 0x21, 0x0c, 0x7d, + ]); + + this._mix = new Uint32Array([ + 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, + 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, + 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, + 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, + 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, + 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, + 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, + 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, + 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, + 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, + 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, + 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, + 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, + 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, + 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, + 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, + 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, + 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, + 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, + 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, + 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, + 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, + 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, + 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, + 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, + 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, + 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, + 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, + 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, + 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, + 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, + 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, + 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, + 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, + 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, + 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, + 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, + 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, + 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, + 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, + 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, + 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, + 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3, + ]); + + this._mixCol = new Uint8Array(256); + for (let i = 0; i < 256; i++) { + this._mixCol[i] = i < 128 ? i << 1 : (i << 1) ^ 0x1b; + } + + this.buffer = new Uint8Array(16); + this.bufferPosition = 0; + } + + _expandKey(cipherKey) { + unreachable("Cannot call `_expandKey` on the base class"); + } + + _decrypt(input, key) { + let t, u, v; + const state = new Uint8Array(16); + state.set(input); + + // AddRoundKey + for (let j = 0, k = this._keySize; j < 16; ++j, ++k) { + state[j] ^= key[k]; + } + for (let i = this._cyclesOfRepetition - 1; i >= 1; --i) { + // InvShiftRows + t = state[13]; + state[13] = state[9]; + state[9] = state[5]; + state[5] = state[1]; + state[1] = t; + t = state[14]; + u = state[10]; + state[14] = state[6]; + state[10] = state[2]; + state[6] = t; + state[2] = u; + t = state[15]; + u = state[11]; + v = state[7]; + state[15] = state[3]; + state[11] = t; + state[7] = u; + state[3] = v; + // InvSubBytes + for (let j = 0; j < 16; ++j) { + state[j] = this._inv_s[state[j]]; + } + // AddRoundKey + for (let j = 0, k = i * 16; j < 16; ++j, ++k) { + state[j] ^= key[k]; + } + // InvMixColumns + for (let j = 0; j < 16; j += 4) { + const s0 = this._mix[state[j]]; + const s1 = this._mix[state[j + 1]]; + const s2 = this._mix[state[j + 2]]; + const s3 = this._mix[state[j + 3]]; + t = + s0 ^ + (s1 >>> 8) ^ + (s1 << 24) ^ + (s2 >>> 16) ^ + (s2 << 16) ^ + (s3 >>> 24) ^ + (s3 << 8); + state[j] = (t >>> 24) & 0xff; + state[j + 1] = (t >> 16) & 0xff; + state[j + 2] = (t >> 8) & 0xff; + state[j + 3] = t & 0xff; + } + } + // InvShiftRows + t = state[13]; + state[13] = state[9]; + state[9] = state[5]; + state[5] = state[1]; + state[1] = t; + t = state[14]; + u = state[10]; + state[14] = state[6]; + state[10] = state[2]; + state[6] = t; + state[2] = u; + t = state[15]; + u = state[11]; + v = state[7]; + state[15] = state[3]; + state[11] = t; + state[7] = u; + state[3] = v; + for (let j = 0; j < 16; ++j) { + // InvSubBytes + state[j] = this._inv_s[state[j]]; + // AddRoundKey + state[j] ^= key[j]; + } + return state; + } + + _encrypt(input, key) { + const s = this._s; + + let t, u, v; + const state = new Uint8Array(16); + state.set(input); + + for (let j = 0; j < 16; ++j) { + // AddRoundKey + state[j] ^= key[j]; + } + + for (let i = 1; i < this._cyclesOfRepetition; i++) { + // SubBytes + for (let j = 0; j < 16; ++j) { + state[j] = s[state[j]]; + } + // ShiftRows + v = state[1]; + state[1] = state[5]; + state[5] = state[9]; + state[9] = state[13]; + state[13] = v; + v = state[2]; + u = state[6]; + state[2] = state[10]; + state[6] = state[14]; + state[10] = v; + state[14] = u; + v = state[3]; + u = state[7]; + t = state[11]; + state[3] = state[15]; + state[7] = v; + state[11] = u; + state[15] = t; + // MixColumns + for (let j = 0; j < 16; j += 4) { + const s0 = state[j + 0]; + const s1 = state[j + 1]; + const s2 = state[j + 2]; + const s3 = state[j + 3]; + t = s0 ^ s1 ^ s2 ^ s3; + state[j + 0] ^= t ^ this._mixCol[s0 ^ s1]; + state[j + 1] ^= t ^ this._mixCol[s1 ^ s2]; + state[j + 2] ^= t ^ this._mixCol[s2 ^ s3]; + state[j + 3] ^= t ^ this._mixCol[s3 ^ s0]; + } + // AddRoundKey + for (let j = 0, k = i * 16; j < 16; ++j, ++k) { + state[j] ^= key[k]; + } + } + + // SubBytes + for (let j = 0; j < 16; ++j) { + state[j] = s[state[j]]; + } + // ShiftRows + v = state[1]; + state[1] = state[5]; + state[5] = state[9]; + state[9] = state[13]; + state[13] = v; + v = state[2]; + u = state[6]; + state[2] = state[10]; + state[6] = state[14]; + state[10] = v; + state[14] = u; + v = state[3]; + u = state[7]; + t = state[11]; + state[3] = state[15]; + state[7] = v; + state[11] = u; + state[15] = t; + // AddRoundKey + for (let j = 0, k = this._keySize; j < 16; ++j, ++k) { + state[j] ^= key[k]; + } + return state; + } + + _decryptBlock2(data, finalize) { + const sourceLength = data.length; + let buffer = this.buffer, + bufferLength = this.bufferPosition; + const result = []; + let iv = this.iv; + + for (let i = 0; i < sourceLength; ++i) { + buffer[bufferLength] = data[i]; + ++bufferLength; + if (bufferLength < 16) { + continue; + } + // buffer is full, decrypting + const plain = this._decrypt(buffer, this._key); + // xor-ing the IV vector to get plain text + for (let j = 0; j < 16; ++j) { + plain[j] ^= iv[j]; + } + iv = buffer; + result.push(plain); + buffer = new Uint8Array(16); + bufferLength = 0; + } + // saving incomplete buffer + this.buffer = buffer; + this.bufferLength = bufferLength; + this.iv = iv; + if (result.length === 0) { + return new Uint8Array(0); + } + // combining plain text blocks into one + let outputLength = 16 * result.length; + if (finalize) { + // undo a padding that is described in RFC 2898 + const lastBlock = result.at(-1); + let psLen = lastBlock[15]; + if (psLen <= 16) { + for (let i = 15, ii = 16 - psLen; i >= ii; --i) { + if (lastBlock[i] !== psLen) { + // Invalid padding, assume that the block has no padding. + psLen = 0; + break; + } + } + outputLength -= psLen; + result[result.length - 1] = lastBlock.subarray(0, 16 - psLen); + } + } + const output = new Uint8Array(outputLength); + for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) { + output.set(result[i], j); + } + return output; + } + + decryptBlock(data, finalize, iv = null) { + const sourceLength = data.length; + const buffer = this.buffer; + let bufferLength = this.bufferPosition; + // If an IV is not supplied, wait for IV values. They are at the start + // of the stream. + if (iv) { + this.iv = iv; + } else { + for ( + let i = 0; + bufferLength < 16 && i < sourceLength; + ++i, ++bufferLength + ) { + buffer[bufferLength] = data[i]; + } + if (bufferLength < 16) { + // Need more data. + this.bufferLength = bufferLength; + return new Uint8Array(0); + } + this.iv = buffer; + data = data.subarray(16); + } + this.buffer = new Uint8Array(16); + this.bufferLength = 0; + // starting decryption + this.decryptBlock = this._decryptBlock2; + return this.decryptBlock(data, finalize); + } + + encrypt(data, iv) { + const sourceLength = data.length; + let buffer = this.buffer, + bufferLength = this.bufferPosition; + const result = []; + + if (!iv) { + iv = new Uint8Array(16); + } + for (let i = 0; i < sourceLength; ++i) { + buffer[bufferLength] = data[i]; + ++bufferLength; + if (bufferLength < 16) { + continue; + } + + for (let j = 0; j < 16; ++j) { + buffer[j] ^= iv[j]; + } + + // buffer is full, encrypting + const cipher = this._encrypt(buffer, this._key); + iv = cipher; + result.push(cipher); + buffer = new Uint8Array(16); + bufferLength = 0; + } + // saving incomplete buffer + this.buffer = buffer; + this.bufferLength = bufferLength; + this.iv = iv; + if (result.length === 0) { + return new Uint8Array(0); + } + // combining plain text blocks into one + const outputLength = 16 * result.length; + const output = new Uint8Array(outputLength); + for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) { + output.set(result[i], j); + } + return output; + } +} + +class AES128Cipher extends AESBaseCipher { + constructor(key) { + super(); + + this._cyclesOfRepetition = 10; + this._keySize = 160; // bits + + this._rcon = new Uint8Array([ + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, + 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, + 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, + 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, + 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, + 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, + 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, + 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, + 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, + 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, + 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, + 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, + 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, + 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, + 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, + ]); + + this._key = this._expandKey(key); + } + + _expandKey(cipherKey) { + const b = 176; + const s = this._s; + const rcon = this._rcon; + + const result = new Uint8Array(b); + result.set(cipherKey); + + for (let j = 16, i = 1; j < b; ++i) { + // RotWord + let t1 = result[j - 3]; + let t2 = result[j - 2]; + let t3 = result[j - 1]; + let t4 = result[j - 4]; + // SubWord + t1 = s[t1]; + t2 = s[t2]; + t3 = s[t3]; + t4 = s[t4]; + // Rcon + t1 ^= rcon[i]; + for (let n = 0; n < 4; ++n) { + result[j] = t1 ^= result[j - 16]; + j++; + result[j] = t2 ^= result[j - 16]; + j++; + result[j] = t3 ^= result[j - 16]; + j++; + result[j] = t4 ^= result[j - 16]; + j++; + } + } + return result; + } +} + +class AES256Cipher extends AESBaseCipher { + constructor(key) { + super(); + + this._cyclesOfRepetition = 14; + this._keySize = 224; // bits + + this._key = this._expandKey(key); + } + + _expandKey(cipherKey) { + const b = 240; + const s = this._s; + + const result = new Uint8Array(b); + result.set(cipherKey); + + let r = 1; + let t1, t2, t3, t4; + for (let j = 32, i = 1; j < b; ++i) { + if (j % 32 === 16) { + t1 = s[t1]; + t2 = s[t2]; + t3 = s[t3]; + t4 = s[t4]; + } else if (j % 32 === 0) { + // RotWord + t1 = result[j - 3]; + t2 = result[j - 2]; + t3 = result[j - 1]; + t4 = result[j - 4]; + // SubWord + t1 = s[t1]; + t2 = s[t2]; + t3 = s[t3]; + t4 = s[t4]; + // Rcon + t1 ^= r; + if ((r <<= 1) >= 256) { + r = (r ^ 0x1b) & 0xff; + } + } + + for (let n = 0; n < 4; ++n) { + result[j] = t1 ^= result[j - 32]; + j++; + result[j] = t2 ^= result[j - 32]; + j++; + result[j] = t3 ^= result[j - 32]; + j++; + result[j] = t4 ^= result[j - 32]; + j++; + } + } + return result; + } +} + +class PDF17 { + checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) { + const hashData = new Uint8Array(password.length + 56); + hashData.set(password, 0); + hashData.set(ownerValidationSalt, password.length); + hashData.set(userBytes, password.length + ownerValidationSalt.length); + const result = calculateSHA256(hashData, 0, hashData.length); + return isArrayEqual(result, ownerPassword); + } + + checkUserPassword(password, userValidationSalt, userPassword) { + const hashData = new Uint8Array(password.length + 8); + hashData.set(password, 0); + hashData.set(userValidationSalt, password.length); + const result = calculateSHA256(hashData, 0, hashData.length); + return isArrayEqual(result, userPassword); + } + + getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) { + const hashData = new Uint8Array(password.length + 56); + hashData.set(password, 0); + hashData.set(ownerKeySalt, password.length); + hashData.set(userBytes, password.length + ownerKeySalt.length); + const key = calculateSHA256(hashData, 0, hashData.length); + const cipher = new AES256Cipher(key); + return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16)); + } + + getUserKey(password, userKeySalt, userEncryption) { + const hashData = new Uint8Array(password.length + 8); + hashData.set(password, 0); + hashData.set(userKeySalt, password.length); + // `key` is the decryption key for the UE string. + const key = calculateSHA256(hashData, 0, hashData.length); + const cipher = new AES256Cipher(key); + return cipher.decryptBlock(userEncryption, false, new Uint8Array(16)); + } +} + +class PDF20 { + _hash(password, input, userBytes) { + // This refers to Algorithm 2.B as defined in ISO 32000-2. + let k = calculateSHA256(input, 0, input.length).subarray(0, 32); + let e = [0]; + let i = 0; + while (i < 64 || e.at(-1) > i - 32) { + const combinedLength = password.length + k.length + userBytes.length, + combinedArray = new Uint8Array(combinedLength); + let writeOffset = 0; + combinedArray.set(password, writeOffset); + writeOffset += password.length; + combinedArray.set(k, writeOffset); + writeOffset += k.length; + combinedArray.set(userBytes, writeOffset); + + const k1 = new Uint8Array(combinedLength * 64); + for (let j = 0, pos = 0; j < 64; j++, pos += combinedLength) { + k1.set(combinedArray, pos); + } + // AES128 CBC NO PADDING with first 16 bytes of k as the key + // and the second 16 as the iv. + const cipher = new AES128Cipher(k.subarray(0, 16)); + e = cipher.encrypt(k1, k.subarray(16, 32)); + // Now we have to take the first 16 bytes of an unsigned big endian + // integer and compute the remainder modulo 3. That is a fairly large + // number and JavaScript isn't going to handle that well. + // The number is e0 + 256 * e1 + 256^2 * e2... and 256 % 3 === 1, hence + // the powers of 256 are === 1 modulo 3 and finally the number modulo 3 + // is equal to the remainder modulo 3 of the sum of the e_n. + const remainder = e.slice(0, 16).reduce((a, b) => a + b, 0) % 3; + if (remainder === 0) { + k = calculateSHA256(e, 0, e.length); + } else if (remainder === 1) { + k = calculateSHA384(e, 0, e.length); + } else if (remainder === 2) { + k = calculateSHA512(e, 0, e.length); + } + i++; + } + return k.subarray(0, 32); + } + + checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) { + const hashData = new Uint8Array(password.length + 56); + hashData.set(password, 0); + hashData.set(ownerValidationSalt, password.length); + hashData.set(userBytes, password.length + ownerValidationSalt.length); + const result = this._hash(password, hashData, userBytes); + return isArrayEqual(result, ownerPassword); + } + + checkUserPassword(password, userValidationSalt, userPassword) { + const hashData = new Uint8Array(password.length + 8); + hashData.set(password, 0); + hashData.set(userValidationSalt, password.length); + const result = this._hash(password, hashData, []); + return isArrayEqual(result, userPassword); + } + + getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) { + const hashData = new Uint8Array(password.length + 56); + hashData.set(password, 0); + hashData.set(ownerKeySalt, password.length); + hashData.set(userBytes, password.length + ownerKeySalt.length); + const key = this._hash(password, hashData, userBytes); + const cipher = new AES256Cipher(key); + return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16)); + } + + getUserKey(password, userKeySalt, userEncryption) { + const hashData = new Uint8Array(password.length + 8); + hashData.set(password, 0); + hashData.set(userKeySalt, password.length); + // `key` is the decryption key for the UE string. + const key = this._hash(password, hashData, []); + const cipher = new AES256Cipher(key); + return cipher.decryptBlock(userEncryption, false, new Uint8Array(16)); + } +} + +class CipherTransform { + constructor(stringCipherConstructor, streamCipherConstructor) { + this.StringCipherConstructor = stringCipherConstructor; + this.StreamCipherConstructor = streamCipherConstructor; + } + + createStream(stream, length) { + const cipher = new this.StreamCipherConstructor(); + return new DecryptStream( + stream, + length, + function cipherTransformDecryptStream(data, finalize) { + return cipher.decryptBlock(data, finalize); + } + ); + } + + decryptString(s) { + const cipher = new this.StringCipherConstructor(); + let data = stringToBytes(s); + data = cipher.decryptBlock(data, true); + return bytesToString(data); + } + + encryptString(s) { + const cipher = new this.StringCipherConstructor(); + if (cipher instanceof AESBaseCipher) { + // Append some chars equal to "16 - (M mod 16)" + // where M is the string length (see section 7.6.2 in PDF specification) + // to have a final string where the length is a multiple of 16. + // Special note: + // "Note that the pad is present when M is evenly divisible by 16; + // it contains 16 bytes of 0x10." + const strLen = s.length; + const pad = 16 - (strLen % 16); + s += String.fromCharCode(pad).repeat(pad); + + // Generate an initialization vector + const iv = new Uint8Array(16); + crypto.getRandomValues(iv); + + let data = stringToBytes(s); + data = cipher.encrypt(data, iv); + + const buf = new Uint8Array(16 + data.length); + buf.set(iv); + buf.set(data, 16); + + return bytesToString(buf); + } + + let data = stringToBytes(s); + data = cipher.encrypt(data); + return bytesToString(data); + } +} + +class CipherTransformFactory { + static #defaultPasswordBytes = new Uint8Array([ + 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, + 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, + 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a, + ]); + + #createEncryptionKey20( + revision, + password, + ownerPassword, + ownerValidationSalt, + ownerKeySalt, + uBytes, + userPassword, + userValidationSalt, + userKeySalt, + ownerEncryption, + userEncryption, + perms + ) { + if (password) { + const passwordLength = Math.min(127, password.length); + password = password.subarray(0, passwordLength); + } else { + password = []; + } + const pdfAlgorithm = revision === 6 ? new PDF20() : new PDF17(); + + if ( + pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword) + ) { + return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption); + } else if ( + password.length && + pdfAlgorithm.checkOwnerPassword( + password, + ownerValidationSalt, + uBytes, + ownerPassword + ) + ) { + return pdfAlgorithm.getOwnerKey( + password, + ownerKeySalt, + uBytes, + ownerEncryption + ); + } + + return null; + } + + #prepareKeyData( + fileId, + password, + ownerPassword, + userPassword, + flags, + revision, + keyLength, + encryptMetadata + ) { + const hashDataSize = 40 + ownerPassword.length + fileId.length; + const hashData = new Uint8Array(hashDataSize); + let i = 0, + j, + n; + if (password) { + n = Math.min(32, password.length); + for (; i < n; ++i) { + hashData[i] = password[i]; + } + } + j = 0; + while (i < 32) { + hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++]; + } + // as now the padded password in the hashData[0..i] + for (j = 0, n = ownerPassword.length; j < n; ++j) { + hashData[i++] = ownerPassword[j]; + } + hashData[i++] = flags & 0xff; + hashData[i++] = (flags >> 8) & 0xff; + hashData[i++] = (flags >> 16) & 0xff; + hashData[i++] = (flags >>> 24) & 0xff; + for (j = 0, n = fileId.length; j < n; ++j) { + hashData[i++] = fileId[j]; + } + if (revision >= 4 && !encryptMetadata) { + hashData[i++] = 0xff; + hashData[i++] = 0xff; + hashData[i++] = 0xff; + hashData[i++] = 0xff; + } + let hash = calculateMD5(hashData, 0, i); + const keyLengthInBytes = keyLength >> 3; + if (revision >= 3) { + for (j = 0; j < 50; ++j) { + hash = calculateMD5(hash, 0, keyLengthInBytes); + } + } + const encryptionKey = hash.subarray(0, keyLengthInBytes); + let cipher, checkData; + + if (revision >= 3) { + for (i = 0; i < 32; ++i) { + hashData[i] = CipherTransformFactory.#defaultPasswordBytes[i]; + } + for (j = 0, n = fileId.length; j < n; ++j) { + hashData[i++] = fileId[j]; + } + cipher = new ARCFourCipher(encryptionKey); + checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i)); + n = encryptionKey.length; + const derivedKey = new Uint8Array(n); + for (j = 1; j <= 19; ++j) { + for (let k = 0; k < n; ++k) { + derivedKey[k] = encryptionKey[k] ^ j; + } + cipher = new ARCFourCipher(derivedKey); + checkData = cipher.encryptBlock(checkData); + } + for (j = 0, n = checkData.length; j < n; ++j) { + if (userPassword[j] !== checkData[j]) { + return null; + } + } + } else { + cipher = new ARCFourCipher(encryptionKey); + checkData = cipher.encryptBlock( + CipherTransformFactory.#defaultPasswordBytes + ); + for (j = 0, n = checkData.length; j < n; ++j) { + if (userPassword[j] !== checkData[j]) { + return null; + } + } + } + return encryptionKey; + } + + #decodeUserPassword(password, ownerPassword, revision, keyLength) { + const hashData = new Uint8Array(32); + let i = 0; + const n = Math.min(32, password.length); + for (; i < n; ++i) { + hashData[i] = password[i]; + } + let j = 0; + while (i < 32) { + hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++]; + } + let hash = calculateMD5(hashData, 0, i); + const keyLengthInBytes = keyLength >> 3; + if (revision >= 3) { + for (j = 0; j < 50; ++j) { + hash = calculateMD5(hash, 0, hash.length); + } + } + + let cipher, userPassword; + if (revision >= 3) { + userPassword = ownerPassword; + const derivedKey = new Uint8Array(keyLengthInBytes); + for (j = 19; j >= 0; j--) { + for (let k = 0; k < keyLengthInBytes; ++k) { + derivedKey[k] = hash[k] ^ j; + } + cipher = new ARCFourCipher(derivedKey); + userPassword = cipher.encryptBlock(userPassword); + } + } else { + cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes)); + userPassword = cipher.encryptBlock(ownerPassword); + } + return userPassword; + } + + #buildObjectKey(num, gen, encryptionKey, isAes = false) { + const n = encryptionKey.length; + const key = new Uint8Array(n + 9); + key.set(encryptionKey); + let i = n; + key[i++] = num & 0xff; + key[i++] = (num >> 8) & 0xff; + key[i++] = (num >> 16) & 0xff; + key[i++] = gen & 0xff; + key[i++] = (gen >> 8) & 0xff; + if (isAes) { + key[i++] = 0x73; + key[i++] = 0x41; + key[i++] = 0x6c; + key[i++] = 0x54; + } + const hash = calculateMD5(key, 0, i); + return hash.subarray(0, Math.min(n + 5, 16)); + } + + #buildCipherConstructor(cf, name, num, gen, key) { + if (!(name instanceof Name)) { + throw new FormatError("Invalid crypt filter name."); + } + const self = this; + const cryptFilter = cf.get(name.name); + const cfm = cryptFilter?.get("CFM"); + + if (!cfm || cfm.name === "None") { + return function () { + return new NullCipher(); + }; + } + if (cfm.name === "V2") { + return function () { + return new ARCFourCipher( + self.#buildObjectKey(num, gen, key, /* isAes = */ false) + ); + }; + } + if (cfm.name === "AESV2") { + return function () { + return new AES128Cipher( + self.#buildObjectKey(num, gen, key, /* isAes = */ true) + ); + }; + } + if (cfm.name === "AESV3") { + return function () { + return new AES256Cipher(key); + }; + } + throw new FormatError("Unknown crypto method"); + } + + constructor(dict, fileId, password) { + const filter = dict.get("Filter"); + if (!isName(filter, "Standard")) { + throw new FormatError("unknown encryption method"); + } + this.filterName = filter.name; + this.dict = dict; + const algorithm = dict.get("V"); + if ( + !Number.isInteger(algorithm) || + (algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) + ) { + throw new FormatError("unsupported encryption algorithm"); + } + this.algorithm = algorithm; + let keyLength = dict.get("Length"); + if (!keyLength) { + // Spec asks to rely on encryption dictionary's Length entry, however + // some PDFs don't have it. Trying to recover. + if (algorithm <= 3) { + // For 1 and 2 it's fixed to 40-bit, for 3 40-bit is a minimal value. + keyLength = 40; + } else { + // Trying to find default handler -- it usually has Length. + const cfDict = dict.get("CF"); + const streamCryptoName = dict.get("StmF"); + if (cfDict instanceof Dict && streamCryptoName instanceof Name) { + cfDict.suppressEncryption = true; // See comment below. + const handlerDict = cfDict.get(streamCryptoName.name); + keyLength = handlerDict?.get("Length") || 128; + if (keyLength < 40) { + // Sometimes it's incorrect value of bits, generators specify + // bytes. + keyLength <<= 3; + } + } + } + } + if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) { + throw new FormatError("invalid key length"); + } + + const ownerBytes = stringToBytes(dict.get("O")), + userBytes = stringToBytes(dict.get("U")); + // prepare keys + const ownerPassword = ownerBytes.subarray(0, 32); + const userPassword = userBytes.subarray(0, 32); + const flags = dict.get("P"); + const revision = dict.get("R"); + // meaningful when V is 4 or 5 + const encryptMetadata = + (algorithm === 4 || algorithm === 5) && + dict.get("EncryptMetadata") !== false; + this.encryptMetadata = encryptMetadata; + + const fileIdBytes = stringToBytes(fileId); + let passwordBytes; + if (password) { + if (revision === 6) { + try { + password = utf8StringToString(password); + } catch { + warn( + "CipherTransformFactory: Unable to convert UTF8 encoded password." + ); + } + } + passwordBytes = stringToBytes(password); + } + + let encryptionKey; + if (algorithm !== 5) { + encryptionKey = this.#prepareKeyData( + fileIdBytes, + passwordBytes, + ownerPassword, + userPassword, + flags, + revision, + keyLength, + encryptMetadata + ); + } else { + const ownerValidationSalt = ownerBytes.subarray(32, 40); + const ownerKeySalt = ownerBytes.subarray(40, 48); + const uBytes = userBytes.subarray(0, 48); + const userValidationSalt = userBytes.subarray(32, 40); + const userKeySalt = userBytes.subarray(40, 48); + const ownerEncryption = stringToBytes(dict.get("OE")); + const userEncryption = stringToBytes(dict.get("UE")); + const perms = stringToBytes(dict.get("Perms")); + encryptionKey = this.#createEncryptionKey20( + revision, + passwordBytes, + ownerPassword, + ownerValidationSalt, + ownerKeySalt, + uBytes, + userPassword, + userValidationSalt, + userKeySalt, + ownerEncryption, + userEncryption, + perms + ); + } + if (!encryptionKey && !password) { + throw new PasswordException( + "No password given", + PasswordResponses.NEED_PASSWORD + ); + } else if (!encryptionKey && password) { + // Attempting use the password as an owner password + const decodedPassword = this.#decodeUserPassword( + passwordBytes, + ownerPassword, + revision, + keyLength + ); + encryptionKey = this.#prepareKeyData( + fileIdBytes, + decodedPassword, + ownerPassword, + userPassword, + flags, + revision, + keyLength, + encryptMetadata + ); + } + + if (!encryptionKey) { + throw new PasswordException( + "Incorrect Password", + PasswordResponses.INCORRECT_PASSWORD + ); + } + + this.encryptionKey = encryptionKey; + + if (algorithm >= 4) { + const cf = dict.get("CF"); + if (cf instanceof Dict) { + // The 'CF' dictionary itself should not be encrypted, and by setting + // `suppressEncryption` we can prevent an infinite loop inside of + // `XRef_fetchUncompressed` if the dictionary contains indirect + // objects (fixes issue7665.pdf). + cf.suppressEncryption = true; + } + this.cf = cf; + this.stmf = dict.get("StmF") || Name.get("Identity"); + this.strf = dict.get("StrF") || Name.get("Identity"); + this.eff = dict.get("EFF") || this.stmf; + } + } + + createCipherTransform(num, gen) { + if (this.algorithm === 4 || this.algorithm === 5) { + return new CipherTransform( + this.#buildCipherConstructor( + this.cf, + this.strf, + num, + gen, + this.encryptionKey + ), + this.#buildCipherConstructor( + this.cf, + this.stmf, + num, + gen, + this.encryptionKey + ) + ); + } + // algorithms 1 and 2 + const key = this.#buildObjectKey( + num, + gen, + this.encryptionKey, + /* isAes = */ false + ); + const cipherConstructor = function () { + return new ARCFourCipher(key); + }; + return new CipherTransform(cipherConstructor, cipherConstructor); + } +} + +export { + AES128Cipher, + AES256Cipher, + ARCFourCipher, + calculateMD5, + calculateSHA256, + calculateSHA384, + calculateSHA512, + CipherTransformFactory, + PDF17, + PDF20, +}; diff --git a/frontend/public/pdf.js/src/core/dataset_reader.js b/frontend/public/pdf.js/src/core/dataset_reader.js new file mode 100644 index 0000000..221bcac --- /dev/null +++ b/frontend/public/pdf.js/src/core/dataset_reader.js @@ -0,0 +1,80 @@ +/* Copyright 2022 Mozilla Foundation + * + * 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 { stringToUTF8String, warn } from "../shared/util.js"; +import { parseXFAPath } from "./core_utils.js"; +import { SimpleXMLParser } from "./xml_parser.js"; + +function decodeString(str) { + try { + return stringToUTF8String(str); + } catch (ex) { + warn(`UTF-8 decoding failed: "${ex}".`); + return str; + } +} + +class DatasetXMLParser extends SimpleXMLParser { + constructor(options) { + super(options); + this.node = null; + } + + onEndElement(name) { + const node = super.onEndElement(name); + if (node && name === "xfa:datasets") { + this.node = node; + + // We don't need anything else, so just kill the parser. + throw new Error("Aborting DatasetXMLParser."); + } + } +} + +class DatasetReader { + constructor(data) { + if (data.datasets) { + this.node = new SimpleXMLParser({ hasAttributes: true }).parseFromString( + data.datasets + ).documentElement; + } else { + const parser = new DatasetXMLParser({ hasAttributes: true }); + try { + parser.parseFromString(data["xdp:xdp"]); + } catch {} + this.node = parser.node; + } + } + + getValue(path) { + if (!this.node || !path) { + return ""; + } + const node = this.node.searchNode(parseXFAPath(path), 0); + + if (!node) { + return ""; + } + + const first = node.firstChild; + if (first?.nodeName === "value") { + return node.children.map(child => decodeString(child.textContent)); + } + + return decodeString(node.textContent); + } +} + +export { DatasetReader }; diff --git a/frontend/public/pdf.js/src/core/decode_stream.js b/frontend/public/pdf.js/src/core/decode_stream.js new file mode 100644 index 0000000..ef2bab4 --- /dev/null +++ b/frontend/public/pdf.js/src/core/decode_stream.js @@ -0,0 +1,189 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { BaseStream } from "./base_stream.js"; +import { Stream } from "./stream.js"; + +// Lots of DecodeStreams are created whose buffers are never used. For these +// we share a single empty buffer. This is (a) space-efficient and (b) avoids +// having special cases that would be required if we used |null| for an empty +// buffer. +const emptyBuffer = new Uint8Array(0); + +// Super class for the decoding streams. +class DecodeStream extends BaseStream { + constructor(maybeMinBufferLength) { + super(); + this._rawMinBufferLength = maybeMinBufferLength || 0; + + this.pos = 0; + this.bufferLength = 0; + this.eof = false; + this.buffer = emptyBuffer; + this.minBufferLength = 512; + if (maybeMinBufferLength) { + // Compute the first power of two that is as big as maybeMinBufferLength. + while (this.minBufferLength < maybeMinBufferLength) { + this.minBufferLength *= 2; + } + } + } + + get isEmpty() { + while (!this.eof && this.bufferLength === 0) { + this.readBlock(); + } + return this.bufferLength === 0; + } + + ensureBuffer(requested) { + const buffer = this.buffer; + if (requested <= buffer.byteLength) { + return buffer; + } + let size = this.minBufferLength; + while (size < requested) { + size *= 2; + } + const buffer2 = new Uint8Array(size); + buffer2.set(buffer); + return (this.buffer = buffer2); + } + + getByte() { + const pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) { + return -1; + } + this.readBlock(); + } + return this.buffer[this.pos++]; + } + + getBytes(length, decoderOptions = null) { + const pos = this.pos; + let end; + + if (length) { + this.ensureBuffer(pos + length); + end = pos + length; + + while (!this.eof && this.bufferLength < end) { + this.readBlock(decoderOptions); + } + const bufEnd = this.bufferLength; + if (end > bufEnd) { + end = bufEnd; + } + } else { + while (!this.eof) { + this.readBlock(decoderOptions); + } + end = this.bufferLength; + } + + this.pos = end; + return this.buffer.subarray(pos, end); + } + + async getImageData(length, decoderOptions) { + if (!this.canAsyncDecodeImageFromBuffer) { + if (this.isAsyncDecoder) { + return this.decodeImage(null, decoderOptions); + } + return this.getBytes(length, decoderOptions); + } + const data = await this.stream.asyncGetBytes(); + return this.decodeImage(data, decoderOptions); + } + + reset() { + this.pos = 0; + } + + makeSubStream(start, length, dict = null) { + if (length === undefined) { + while (!this.eof) { + this.readBlock(); + } + } else { + const end = start + length; + while (this.bufferLength <= end && !this.eof) { + this.readBlock(); + } + } + return new Stream(this.buffer, start, length, dict); + } + + getBaseStreams() { + return this.str ? this.str.getBaseStreams() : null; + } +} + +class StreamsSequenceStream extends DecodeStream { + constructor(streams, onError = null) { + streams = streams.filter(s => s instanceof BaseStream); + + let maybeLength = 0; + for (const stream of streams) { + maybeLength += + stream instanceof DecodeStream + ? stream._rawMinBufferLength + : stream.length; + } + super(maybeLength); + + this.streams = streams; + this._onError = onError; + } + + readBlock() { + const streams = this.streams; + if (streams.length === 0) { + this.eof = true; + return; + } + const stream = streams.shift(); + let chunk; + try { + chunk = stream.getBytes(); + } catch (reason) { + if (this._onError) { + this._onError(reason, stream.dict?.objId); + return; + } + throw reason; + } + const bufferLength = this.bufferLength; + const newLength = bufferLength + chunk.length; + const buffer = this.ensureBuffer(newLength); + buffer.set(chunk, bufferLength); + this.bufferLength = newLength; + } + + getBaseStreams() { + const baseStreamsBuf = []; + for (const stream of this.streams) { + const baseStreams = stream.getBaseStreams(); + if (baseStreams) { + baseStreamsBuf.push(...baseStreams); + } + } + return baseStreamsBuf.length > 0 ? baseStreamsBuf : null; + } +} + +export { DecodeStream, StreamsSequenceStream }; diff --git a/frontend/public/pdf.js/src/core/decrypt_stream.js b/frontend/public/pdf.js/src/core/decrypt_stream.js new file mode 100644 index 0000000..ee2fff2 --- /dev/null +++ b/frontend/public/pdf.js/src/core/decrypt_stream.js @@ -0,0 +1,57 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { DecodeStream } from "./decode_stream.js"; + +const chunkSize = 512; + +class DecryptStream extends DecodeStream { + constructor(str, maybeLength, decrypt) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + this.decrypt = decrypt; + this.nextChunk = null; + this.initialized = false; + } + + readBlock() { + let chunk; + if (this.initialized) { + chunk = this.nextChunk; + } else { + chunk = this.str.getBytes(chunkSize); + this.initialized = true; + } + if (!chunk?.length) { + this.eof = true; + return; + } + this.nextChunk = this.str.getBytes(chunkSize); + const hasMoreData = this.nextChunk?.length > 0; + + const decrypt = this.decrypt; + chunk = decrypt(chunk, !hasMoreData); + + const bufferLength = this.bufferLength, + newLength = bufferLength + chunk.length, + buffer = this.ensureBuffer(newLength); + buffer.set(chunk, bufferLength); + this.bufferLength = newLength; + } +} + +export { DecryptStream }; diff --git a/frontend/public/pdf.js/src/core/default_appearance.js b/frontend/public/pdf.js/src/core/default_appearance.js new file mode 100644 index 0000000..740a037 --- /dev/null +++ b/frontend/public/pdf.js/src/core/default_appearance.js @@ -0,0 +1,491 @@ +/* Copyright 2020 Mozilla Foundation + * + * 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 { + codePointIter, + escapePDFName, + getRotationMatrix, + numberToString, + stringToUTF16HexString, +} from "./core_utils.js"; +import { Dict, Name } from "./primitives.js"; +import { + LINE_DESCENT_FACTOR, + LINE_FACTOR, + OPS, + shadow, + warn, +} from "../shared/util.js"; +import { ColorSpace } from "./colorspace.js"; +import { EvaluatorPreprocessor } from "./evaluator.js"; +import { LocalColorSpaceCache } from "./image_utils.js"; +import { PDFFunctionFactory } from "./function.js"; +import { StringStream } from "./stream.js"; + +class DefaultAppearanceEvaluator extends EvaluatorPreprocessor { + constructor(str) { + super(new StringStream(str)); + } + + parse() { + const operation = { + fn: 0, + args: [], + }; + const result = { + fontSize: 0, + fontName: "", + fontColor: /* black = */ new Uint8ClampedArray(3), + }; + + try { + while (true) { + operation.args.length = 0; // Ensure that `args` it's always reset. + + if (!this.read(operation)) { + break; + } + if (this.savedStatesDepth !== 0) { + continue; // Don't get info in save/restore sections. + } + const { fn, args } = operation; + + switch (fn | 0) { + case OPS.setFont: + const [fontName, fontSize] = args; + if (fontName instanceof Name) { + result.fontName = fontName.name; + } + if (typeof fontSize === "number" && fontSize > 0) { + result.fontSize = fontSize; + } + break; + case OPS.setFillRGBColor: + ColorSpace.singletons.rgb.getRgbItem(args, 0, result.fontColor, 0); + break; + case OPS.setFillGray: + ColorSpace.singletons.gray.getRgbItem(args, 0, result.fontColor, 0); + break; + case OPS.setFillCMYKColor: + ColorSpace.singletons.cmyk.getRgbItem(args, 0, result.fontColor, 0); + break; + } + } + } catch (reason) { + warn(`parseDefaultAppearance - ignoring errors: "${reason}".`); + } + + return result; + } +} + +// Parse DA to extract font and color information. +function parseDefaultAppearance(str) { + return new DefaultAppearanceEvaluator(str).parse(); +} + +class AppearanceStreamEvaluator extends EvaluatorPreprocessor { + constructor(stream, evaluatorOptions, xref) { + super(stream); + this.stream = stream; + this.evaluatorOptions = evaluatorOptions; + this.xref = xref; + + this.resources = stream.dict?.get("Resources"); + } + + parse() { + const operation = { + fn: 0, + args: [], + }; + let result = { + scaleFactor: 1, + fontSize: 0, + fontName: "", + fontColor: /* black = */ new Uint8ClampedArray(3), + fillColorSpace: ColorSpace.singletons.gray, + }; + let breakLoop = false; + const stack = []; + + try { + while (true) { + operation.args.length = 0; // Ensure that `args` it's always reset. + + if (breakLoop || !this.read(operation)) { + break; + } + const { fn, args } = operation; + + switch (fn | 0) { + case OPS.save: + stack.push({ + scaleFactor: result.scaleFactor, + fontSize: result.fontSize, + fontName: result.fontName, + fontColor: result.fontColor.slice(), + fillColorSpace: result.fillColorSpace, + }); + break; + case OPS.restore: + result = stack.pop() || result; + break; + case OPS.setTextMatrix: + result.scaleFactor *= Math.hypot(args[0], args[1]); + break; + case OPS.setFont: + const [fontName, fontSize] = args; + if (fontName instanceof Name) { + result.fontName = fontName.name; + } + if (typeof fontSize === "number" && fontSize > 0) { + result.fontSize = fontSize * result.scaleFactor; + } + break; + case OPS.setFillColorSpace: + result.fillColorSpace = ColorSpace.parse({ + cs: args[0], + xref: this.xref, + resources: this.resources, + pdfFunctionFactory: this._pdfFunctionFactory, + localColorSpaceCache: this._localColorSpaceCache, + }); + break; + case OPS.setFillColor: + const cs = result.fillColorSpace; + cs.getRgbItem(args, 0, result.fontColor, 0); + break; + case OPS.setFillRGBColor: + ColorSpace.singletons.rgb.getRgbItem(args, 0, result.fontColor, 0); + break; + case OPS.setFillGray: + ColorSpace.singletons.gray.getRgbItem(args, 0, result.fontColor, 0); + break; + case OPS.setFillCMYKColor: + ColorSpace.singletons.cmyk.getRgbItem(args, 0, result.fontColor, 0); + break; + case OPS.showText: + case OPS.showSpacedText: + case OPS.nextLineShowText: + case OPS.nextLineSetSpacingShowText: + breakLoop = true; + break; + } + } + } catch (reason) { + warn(`parseAppearanceStream - ignoring errors: "${reason}".`); + } + this.stream.reset(); + delete result.scaleFactor; + delete result.fillColorSpace; + + return result; + } + + get _localColorSpaceCache() { + return shadow(this, "_localColorSpaceCache", new LocalColorSpaceCache()); + } + + get _pdfFunctionFactory() { + const pdfFunctionFactory = new PDFFunctionFactory({ + xref: this.xref, + isEvalSupported: this.evaluatorOptions.isEvalSupported, + }); + return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory); + } +} + +// Parse appearance stream to extract font and color information. +// It returns the font properties used to render the first text object. +function parseAppearanceStream(stream, evaluatorOptions, xref) { + return new AppearanceStreamEvaluator(stream, evaluatorOptions, xref).parse(); +} + +function getPdfColor(color, isFill) { + if (color[0] === color[1] && color[1] === color[2]) { + const gray = color[0] / 255; + return `${numberToString(gray)} ${isFill ? "g" : "G"}`; + } + return ( + Array.from(color, c => numberToString(c / 255)).join(" ") + + ` ${isFill ? "rg" : "RG"}` + ); +} + +// Create default appearance string from some information. +function createDefaultAppearance({ fontSize, fontName, fontColor }) { + return `/${escapePDFName(fontName)} ${fontSize} Tf ${getPdfColor( + fontColor, + /* isFill */ true + )}`; +} + +class FakeUnicodeFont { + constructor(xref, fontFamily) { + this.xref = xref; + this.widths = null; + this.firstChar = Infinity; + this.lastChar = -Infinity; + this.fontFamily = fontFamily; + + const canvas = new OffscreenCanvas(1, 1); + this.ctxMeasure = canvas.getContext("2d", { willReadFrequently: true }); + + if (!FakeUnicodeFont._fontNameId) { + FakeUnicodeFont._fontNameId = 1; + } + this.fontName = Name.get( + `InvalidPDFjsFont_${fontFamily}_${FakeUnicodeFont._fontNameId++}` + ); + } + + get fontDescriptorRef() { + if (!FakeUnicodeFont._fontDescriptorRef) { + const fontDescriptor = new Dict(this.xref); + fontDescriptor.set("Type", Name.get("FontDescriptor")); + fontDescriptor.set("FontName", this.fontName); + fontDescriptor.set("FontFamily", "MyriadPro Regular"); + fontDescriptor.set("FontBBox", [0, 0, 0, 0]); + fontDescriptor.set("FontStretch", Name.get("Normal")); + fontDescriptor.set("FontWeight", 400); + fontDescriptor.set("ItalicAngle", 0); + + FakeUnicodeFont._fontDescriptorRef = + this.xref.getNewPersistentRef(fontDescriptor); + } + + return FakeUnicodeFont._fontDescriptorRef; + } + + get descendantFontRef() { + const descendantFont = new Dict(this.xref); + descendantFont.set("BaseFont", this.fontName); + descendantFont.set("Type", Name.get("Font")); + descendantFont.set("Subtype", Name.get("CIDFontType0")); + descendantFont.set("CIDToGIDMap", Name.get("Identity")); + descendantFont.set("FirstChar", this.firstChar); + descendantFont.set("LastChar", this.lastChar); + descendantFont.set("FontDescriptor", this.fontDescriptorRef); + descendantFont.set("DW", 1000); + + const widths = []; + const chars = [...this.widths.entries()].sort(); + let currentChar = null; + let currentWidths = null; + for (const [char, width] of chars) { + if (!currentChar) { + currentChar = char; + currentWidths = [width]; + continue; + } + if (char === currentChar + currentWidths.length) { + currentWidths.push(width); + } else { + widths.push(currentChar, currentWidths); + currentChar = char; + currentWidths = [width]; + } + } + + if (currentChar) { + widths.push(currentChar, currentWidths); + } + + descendantFont.set("W", widths); + + const cidSystemInfo = new Dict(this.xref); + cidSystemInfo.set("Ordering", "Identity"); + cidSystemInfo.set("Registry", "Adobe"); + cidSystemInfo.set("Supplement", 0); + descendantFont.set("CIDSystemInfo", cidSystemInfo); + + return this.xref.getNewPersistentRef(descendantFont); + } + + get baseFontRef() { + const baseFont = new Dict(this.xref); + baseFont.set("BaseFont", this.fontName); + baseFont.set("Type", Name.get("Font")); + baseFont.set("Subtype", Name.get("Type0")); + baseFont.set("Encoding", Name.get("Identity-H")); + baseFont.set("DescendantFonts", [this.descendantFontRef]); + baseFont.set("ToUnicode", Name.get("Identity-H")); + + return this.xref.getNewPersistentRef(baseFont); + } + + get resources() { + const resources = new Dict(this.xref); + const font = new Dict(this.xref); + font.set(this.fontName.name, this.baseFontRef); + resources.set("Font", font); + + return resources; + } + + _createContext() { + this.widths = new Map(); + this.ctxMeasure.font = `1000px ${this.fontFamily}`; + + return this.ctxMeasure; + } + + createFontResources(text) { + const ctx = this._createContext(); + for (const line of text.split(/\r\n?|\n/)) { + for (const char of line.split("")) { + const code = char.charCodeAt(0); + if (this.widths.has(code)) { + continue; + } + const metrics = ctx.measureText(char); + const width = Math.ceil(metrics.width); + this.widths.set(code, width); + this.firstChar = Math.min(code, this.firstChar); + this.lastChar = Math.max(code, this.lastChar); + } + } + + return this.resources; + } + + static getFirstPositionInfo(rect, rotation, fontSize) { + // Get the position of the first char in the rect. + const [x1, y1, x2, y2] = rect; + let w = x2 - x1; + let h = y2 - y1; + + if (rotation % 180 !== 0) { + [w, h] = [h, w]; + } + const lineHeight = LINE_FACTOR * fontSize; + const lineDescent = LINE_DESCENT_FACTOR * fontSize; + + return { + coords: [0, h + lineDescent - lineHeight], + bbox: [0, 0, w, h], + matrix: + rotation !== 0 ? getRotationMatrix(rotation, h, lineHeight) : undefined, + }; + } + + createAppearance(text, rect, rotation, fontSize, bgColor, strokeAlpha) { + const ctx = this._createContext(); + const lines = []; + let maxWidth = -Infinity; + for (const line of text.split(/\r\n?|\n/)) { + lines.push(line); + // The line width isn't the sum of the char widths, because in some + // languages, like arabic, it'd be wrong because of ligatures. + const lineWidth = ctx.measureText(line).width; + maxWidth = Math.max(maxWidth, lineWidth); + for (const code of codePointIter(line)) { + const char = String.fromCodePoint(code); + let width = this.widths.get(code); + if (width === undefined) { + const metrics = ctx.measureText(char); + width = Math.ceil(metrics.width); + this.widths.set(code, width); + this.firstChar = Math.min(code, this.firstChar); + this.lastChar = Math.max(code, this.lastChar); + } + } + } + maxWidth *= fontSize / 1000; + + const [x1, y1, x2, y2] = rect; + let w = x2 - x1; + let h = y2 - y1; + + if (rotation % 180 !== 0) { + [w, h] = [h, w]; + } + + let hscale = 1; + if (maxWidth > w) { + hscale = w / maxWidth; + } + let vscale = 1; + const lineHeight = LINE_FACTOR * fontSize; + const lineDescent = LINE_DESCENT_FACTOR * fontSize; + const maxHeight = lineHeight * lines.length; + if (maxHeight > h) { + vscale = h / maxHeight; + } + const fscale = Math.min(hscale, vscale); + const newFontSize = fontSize * fscale; + + const buffer = [ + "q", + `0 0 ${numberToString(w)} ${numberToString(h)} re W n`, + `BT`, + `1 0 0 1 0 ${numberToString(h + lineDescent)} Tm 0 Tc ${getPdfColor( + bgColor, + /* isFill */ true + )}`, + `/${this.fontName.name} ${numberToString(newFontSize)} Tf`, + ]; + + const { resources } = this; + strokeAlpha = + typeof strokeAlpha === "number" && strokeAlpha >= 0 && strokeAlpha <= 1 + ? strokeAlpha + : 1; + + if (strokeAlpha !== 1) { + buffer.push("/R0 gs"); + const extGState = new Dict(this.xref); + const r0 = new Dict(this.xref); + r0.set("ca", strokeAlpha); + r0.set("CA", strokeAlpha); + r0.set("Type", Name.get("ExtGState")); + extGState.set("R0", r0); + resources.set("ExtGState", extGState); + } + + const vShift = numberToString(lineHeight); + for (const line of lines) { + buffer.push(`0 -${vShift} Td <${stringToUTF16HexString(line)}> Tj`); + } + buffer.push("ET", "Q"); + const appearance = buffer.join("\n"); + + const appearanceStreamDict = new Dict(this.xref); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", [0, 0, w, h]); + appearanceStreamDict.set("Length", appearance.length); + appearanceStreamDict.set("Resources", resources); + + if (rotation) { + const matrix = getRotationMatrix(rotation, w, h); + appearanceStreamDict.set("Matrix", matrix); + } + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } +} + +export { + createDefaultAppearance, + FakeUnicodeFont, + getPdfColor, + parseAppearanceStream, + parseDefaultAppearance, +}; diff --git a/frontend/public/pdf.js/src/core/document.js b/frontend/public/pdf.js/src/core/document.js new file mode 100644 index 0000000..b3a550c --- /dev/null +++ b/frontend/public/pdf.js/src/core/document.js @@ -0,0 +1,1959 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + AnnotationEditorPrefix, + assert, + FormatError, + info, + InvalidPDFException, + isArrayEqual, + objectSize, + PageActionEventType, + RenderingIntentFlag, + shadow, + stringToBytes, + stringToPDFString, + stringToUTF8String, + toHexUtil, + unreachable, + Util, + warn, +} from "../shared/util.js"; +import { + AnnotationFactory, + PopupAnnotation, + WidgetAnnotation, +} from "./annotation.js"; +import { + collectActions, + getInheritableProperty, + getNewAnnotationsMap, + isWhiteSpace, + lookupNormalRect, + MissingDataException, + PDF_VERSION_REGEXP, + validateCSSFont, + XRefEntryException, + XRefParseException, +} from "./core_utils.js"; +import { + Dict, + isName, + isRefsEqual, + Name, + Ref, + RefSet, + RefSetCache, +} from "./primitives.js"; +import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js"; +import { BaseStream } from "./base_stream.js"; +import { calculateMD5 } from "./crypto.js"; +import { Catalog } from "./catalog.js"; +import { clearGlobalCaches } from "./cleanup_helper.js"; +import { DatasetReader } from "./dataset_reader.js"; +import { Linearization } from "./parser.js"; +import { NullStream } from "./stream.js"; +import { ObjectLoader } from "./object_loader.js"; +import { OperatorList } from "./operator_list.js"; +import { PartialEvaluator } from "./evaluator.js"; +import { StreamsSequenceStream } from "./decode_stream.js"; +import { StructTreePage } from "./struct_tree.js"; +import { XFAFactory } from "./xfa/factory.js"; +import { XRef } from "./xref.js"; + +const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792]; + +class Page { + constructor({ + pdfManager, + xref, + pageIndex, + pageDict, + ref, + globalIdFactory, + fontCache, + builtInCMapCache, + standardFontDataCache, + globalImageCache, + systemFontCache, + nonBlendModesSet, + xfaFactory, + }) { + this.pdfManager = pdfManager; + this.pageIndex = pageIndex; + this.pageDict = pageDict; + this.xref = xref; + this.ref = ref; + this.fontCache = fontCache; + this.builtInCMapCache = builtInCMapCache; + this.standardFontDataCache = standardFontDataCache; + this.globalImageCache = globalImageCache; + this.systemFontCache = systemFontCache; + this.nonBlendModesSet = nonBlendModesSet; + this.evaluatorOptions = pdfManager.evaluatorOptions; + this.resourcesPromise = null; + this.xfaFactory = xfaFactory; + + const idCounters = { + obj: 0, + }; + this._localIdFactory = class extends globalIdFactory { + static createObjId() { + return `p${pageIndex}_${++idCounters.obj}`; + } + + static getPageObjId() { + return `p${ref.toString()}`; + } + }; + } + + /** + * @private + */ + _getInheritableProperty(key, getArray = false) { + const value = getInheritableProperty({ + dict: this.pageDict, + key, + getArray, + stopWhenFound: false, + }); + if (!Array.isArray(value)) { + return value; + } + if (value.length === 1 || !(value[0] instanceof Dict)) { + return value[0]; + } + return Dict.merge({ xref: this.xref, dictArray: value }); + } + + get content() { + return this.pageDict.getArray("Contents"); + } + + get resources() { + // For robustness: The spec states that a \Resources entry has to be + // present, but can be empty. Some documents still omit it; in this case + // we return an empty dictionary. + const resources = this._getInheritableProperty("Resources"); + + return shadow( + this, + "resources", + resources instanceof Dict ? resources : Dict.empty + ); + } + + _getBoundingBox(name) { + if (this.xfaData) { + return this.xfaData.bbox; + } + const box = lookupNormalRect( + this._getInheritableProperty(name, /* getArray = */ true), + null + ); + + if (box) { + if (box[2] - box[0] > 0 && box[3] - box[1] > 0) { + return box; + } + warn(`Empty, or invalid, /${name} entry.`); + } + return null; + } + + get mediaBox() { + // Reset invalid media box to letter size. + return shadow( + this, + "mediaBox", + this._getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX + ); + } + + get cropBox() { + // Reset invalid crop box to media box. + return shadow( + this, + "cropBox", + this._getBoundingBox("CropBox") || this.mediaBox + ); + } + + get userUnit() { + const obj = this.pageDict.get("UserUnit"); + return shadow( + this, + "userUnit", + typeof obj === "number" && obj > 0 ? obj : 1.0 + ); + } + + get view() { + // From the spec, 6th ed., p.963: + // "The crop, bleed, trim, and art boxes should not ordinarily + // extend beyond the boundaries of the media box. If they do, they are + // effectively reduced to their intersection with the media box." + const { cropBox, mediaBox } = this; + + if (cropBox !== mediaBox && !isArrayEqual(cropBox, mediaBox)) { + const box = Util.intersect(cropBox, mediaBox); + if (box && box[2] - box[0] > 0 && box[3] - box[1] > 0) { + return shadow(this, "view", box); + } + warn("Empty /CropBox and /MediaBox intersection."); + } + return shadow(this, "view", mediaBox); + } + + get rotate() { + let rotate = this._getInheritableProperty("Rotate") || 0; + + // Normalize rotation so it's a multiple of 90 and between 0 and 270. + if (rotate % 90 !== 0) { + rotate = 0; + } else if (rotate >= 360) { + rotate %= 360; + } else if (rotate < 0) { + // The spec doesn't cover negatives. Assume it's counterclockwise + // rotation. The following is the other implementation of modulo. + rotate = ((rotate % 360) + 360) % 360; + } + return shadow(this, "rotate", rotate); + } + + /** + * @private + */ + _onSubStreamError(reason, objId) { + if (this.evaluatorOptions.ignoreErrors) { + warn(`getContentStream - ignoring sub-stream (${objId}): "${reason}".`); + return; + } + throw reason; + } + + /** + * @returns {Promise} + */ + getContentStream() { + return this.pdfManager.ensure(this, "content").then(content => { + if (content instanceof BaseStream) { + return content; + } + if (Array.isArray(content)) { + return new StreamsSequenceStream( + content, + this._onSubStreamError.bind(this) + ); + } + // Replace non-existent page content with empty content. + return new NullStream(); + }); + } + + get xfaData() { + return shadow( + this, + "xfaData", + this.xfaFactory + ? { bbox: this.xfaFactory.getBoundingBox(this.pageIndex) } + : null + ); + } + + async #replaceIdByRef(annotations, deletedAnnotations, existingAnnotations) { + const promises = []; + 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, ref); + if (annotation.popupRef) { + const popupRef = Ref.fromString(annotation.popupRef); + if (popupRef) { + deletedAnnotations.put(popupRef, popupRef); + } + } + continue; + } + existingAnnotations?.put(ref); + annotation.ref = ref; + promises.push( + this.xref.fetchAsync(ref).then( + obj => { + if (obj instanceof Dict) { + annotation.oldAnnotation = obj.clone(); + } + }, + () => { + warn(`Cannot fetch \`oldAnnotation\` for: ${ref}.`); + } + ) + ); + delete annotation.id; + } + } + await Promise.all(promises); + } + + async saveNewAnnotations(handler, task, annotations, imagePromises, changes) { + if (this.xfaFactory) { + throw new Error("XFA: Cannot save new annotations."); + } + + const partialEvaluator = new PartialEvaluator({ + xref: this.xref, + handler, + pageIndex: this.pageIndex, + idFactory: this._localIdFactory, + fontCache: this.fontCache, + builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, + globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, + options: this.evaluatorOptions, + }); + + const deletedAnnotations = new RefSetCache(); + const existingAnnotations = new RefSet(); + await this.#replaceIdByRef( + annotations, + deletedAnnotations, + existingAnnotations + ); + + const pageDict = this.pageDict; + const annotationsArray = this.annotations.filter( + a => !(a instanceof Ref && deletedAnnotations.has(a)) + ); + const newData = await AnnotationFactory.saveNewAnnotations( + partialEvaluator, + task, + annotations, + imagePromises, + changes + ); + + for (const { ref } of newData.annotations) { + // Don't add an existing annotation ref to the annotations array. + if (ref instanceof Ref && !existingAnnotations.has(ref)) { + annotationsArray.push(ref); + } + } + + const dict = pageDict.clone(); + dict.set("Annots", annotationsArray); + changes.put(this.ref, { + data: dict, + }); + + for (const deletedRef of deletedAnnotations) { + changes.put(deletedRef, { + data: null, + }); + } + } + + save(handler, task, annotationStorage, changes) { + const partialEvaluator = new PartialEvaluator({ + xref: this.xref, + handler, + pageIndex: this.pageIndex, + idFactory: this._localIdFactory, + fontCache: this.fontCache, + builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, + globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, + options: this.evaluatorOptions, + }); + + // Fetch the page's annotations and save the content + // in case of interactive form fields. + return this._parsedAnnotations.then(function (annotations) { + const promises = []; + for (const annotation of annotations) { + promises.push( + annotation + .save(partialEvaluator, task, annotationStorage, changes) + .catch(function (reason) { + warn( + "save - ignoring annotation data during " + + `"${task.name}" task: "${reason}".` + ); + return null; + }) + ); + } + + return Promise.all(promises); + }); + } + + loadResources(keys) { + // TODO: add async `_getInheritableProperty` and remove this. + this.resourcesPromise ||= this.pdfManager.ensure(this, "resources"); + + return this.resourcesPromise.then(() => { + const objectLoader = new ObjectLoader(this.resources, keys, this.xref); + return objectLoader.load(); + }); + } + + getOperatorList({ + handler, + sink, + task, + intent, + cacheKey, + annotationStorage = null, + modifiedIds = null, + }) { + const contentStreamPromise = this.getContentStream(); + const resourcesPromise = this.loadResources([ + "ColorSpace", + "ExtGState", + "Font", + "Pattern", + "Properties", + "Shading", + "XObject", + ]); + + const partialEvaluator = new PartialEvaluator({ + xref: this.xref, + handler, + pageIndex: this.pageIndex, + idFactory: this._localIdFactory, + fontCache: this.fontCache, + builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, + globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, + options: this.evaluatorOptions, + }); + + const newAnnotsByPage = !this.xfaFactory + ? getNewAnnotationsMap(annotationStorage) + : null; + const newAnnots = newAnnotsByPage?.get(this.pageIndex); + let newAnnotationsPromise = Promise.resolve(null); + let deletedAnnotations = null; + + if (newAnnots) { + const annotationGlobalsPromise = + this.pdfManager.ensureDoc("annotationGlobals"); + let imagePromises; + + // An annotation can contain a reference to a bitmap, but this bitmap + // is defined in another annotation. So we need to find this annotation + // and generate the bitmap. + const missingBitmaps = new Set(); + for (const { bitmapId, bitmap } of newAnnots) { + if (bitmapId && !bitmap && !missingBitmaps.has(bitmapId)) { + missingBitmaps.add(bitmapId); + } + } + + const { isOffscreenCanvasSupported } = this.evaluatorOptions; + if (missingBitmaps.size > 0) { + const annotationWithBitmaps = newAnnots.slice(); + for (const [key, annotation] of annotationStorage) { + if (!key.startsWith(AnnotationEditorPrefix)) { + continue; + } + if (annotation.bitmap && missingBitmaps.has(annotation.bitmapId)) { + annotationWithBitmaps.push(annotation); + } + } + // The array annotationWithBitmaps cannot be empty: the check above + // makes sure to have at least one annotation containing the bitmap. + imagePromises = AnnotationFactory.generateImages( + annotationWithBitmaps, + this.xref, + isOffscreenCanvasSupported + ); + } else { + imagePromises = AnnotationFactory.generateImages( + newAnnots, + this.xref, + isOffscreenCanvasSupported + ); + } + + deletedAnnotations = new RefSet(); + + newAnnotationsPromise = Promise.all([ + annotationGlobalsPromise, + this.#replaceIdByRef(newAnnots, deletedAnnotations, null), + ]).then(([annotationGlobals]) => { + if (!annotationGlobals) { + return null; + } + + return AnnotationFactory.printNewAnnotations( + annotationGlobals, + partialEvaluator, + task, + newAnnots, + imagePromises + ); + }); + } + + const pageListPromise = Promise.all([ + contentStreamPromise, + resourcesPromise, + ]).then(([contentStream]) => { + const opList = new OperatorList(intent, sink); + + handler.send("StartRenderPage", { + transparency: partialEvaluator.hasBlendModes( + this.resources, + this.nonBlendModesSet + ), + pageIndex: this.pageIndex, + cacheKey, + }); + + return partialEvaluator + .getOperatorList({ + stream: contentStream, + task, + resources: this.resources, + operatorList: opList, + }) + .then(function () { + return opList; + }); + }); + + // Fetch the page's annotations and add their operator lists to the + // page's operator list to render them. + return Promise.all([ + pageListPromise, + this._parsedAnnotations, + 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 ( + annotations.length === 0 || + intent & RenderingIntentFlag.ANNOTATIONS_DISABLE + ) { + pageOpList.flush(/* lastChunk = */ true); + return { length: pageOpList.totalLength }; + } + const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS), + isEditing = !!(intent & RenderingIntentFlag.IS_EDITING), + intentAny = !!(intent & RenderingIntentFlag.ANY), + intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY), + intentPrint = !!(intent & RenderingIntentFlag.PRINT); + + // Collect the operator list promises for the annotations. Each promise + // is resolved with the complete operator list for a single annotation. + const opListPromises = []; + for (const annotation of annotations) { + if ( + intentAny || + (intentDisplay && + annotation.mustBeViewed(annotationStorage, renderForms) && + annotation.mustBeViewedWhenEditing(isEditing, modifiedIds)) || + (intentPrint && annotation.mustBePrinted(annotationStorage)) + ) { + opListPromises.push( + annotation + .getOperatorList( + partialEvaluator, + task, + intent, + annotationStorage + ) + .catch(function (reason) { + warn( + "getOperatorList - ignoring annotation data during " + + `"${task.name}" task: "${reason}".` + ); + return { + opList: null, + separateForm: false, + separateCanvas: false, + }; + }) + ); + } + } + + return Promise.all(opListPromises).then(function (opLists) { + let form = false, + canvas = false; + + for (const { opList, separateForm, separateCanvas } of opLists) { + pageOpList.addOpList(opList); + + form ||= separateForm; + canvas ||= separateCanvas; + } + pageOpList.flush( + /* lastChunk = */ true, + /* separateAnnots = */ { form, canvas } + ); + return { length: pageOpList.totalLength }; + }); + }); + } + + async extractTextContent({ + handler, + task, + includeMarkedContent, + disableNormalization, + sink, + }) { + const contentStreamPromise = this.getContentStream(); + const resourcesPromise = this.loadResources([ + "ExtGState", + "Font", + "Properties", + "XObject", + ]); + const langPromise = this.pdfManager.ensureCatalog("lang"); + + const [contentStream, , lang] = await Promise.all([ + contentStreamPromise, + resourcesPromise, + langPromise, + ]); + const partialEvaluator = new PartialEvaluator({ + xref: this.xref, + handler, + pageIndex: this.pageIndex, + idFactory: this._localIdFactory, + fontCache: this.fontCache, + builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, + globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, + options: this.evaluatorOptions, + }); + + return partialEvaluator.getTextContent({ + stream: contentStream, + task, + resources: this.resources, + includeMarkedContent, + disableNormalization, + sink, + viewBox: this.view, + lang, + }); + } + + async getStructTree() { + const structTreeRoot = + await this.pdfManager.ensureCatalog("structTreeRoot"); + if (!structTreeRoot) { + return null; + } + // Ensure that the structTree will contain the page's annotations. + await this._parsedAnnotations; + + const structTree = await this.pdfManager.ensure(this, "_parseStructTree", [ + structTreeRoot, + ]); + return this.pdfManager.ensure(structTree, "serializable"); + } + + /** + * @private + */ + _parseStructTree(structTreeRoot) { + const tree = new StructTreePage(structTreeRoot, this.pageDict); + tree.parse(this.ref); + return tree; + } + + async getAnnotationsData(handler, task, intent) { + const annotations = await this._parsedAnnotations; + if (annotations.length === 0) { + return annotations; + } + + const annotationsData = [], + textContentPromises = []; + let partialEvaluator; + + const intentAny = !!(intent & RenderingIntentFlag.ANY), + intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY), + intentPrint = !!(intent & RenderingIntentFlag.PRINT); + + for (const annotation of annotations) { + // Get the annotation even if it's hidden because + // JS can change its display. + const isVisible = intentAny || (intentDisplay && annotation.viewable); + if (isVisible || (intentPrint && annotation.printable)) { + annotationsData.push(annotation.data); + } + + if (annotation.hasTextContent && isVisible) { + partialEvaluator ||= new PartialEvaluator({ + xref: this.xref, + handler, + pageIndex: this.pageIndex, + idFactory: this._localIdFactory, + fontCache: this.fontCache, + builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, + globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, + options: this.evaluatorOptions, + }); + + textContentPromises.push( + annotation + .extractTextContent(partialEvaluator, task, [ + -Infinity, + -Infinity, + Infinity, + Infinity, + ]) + .catch(function (reason) { + warn( + `getAnnotationsData - ignoring textContent during "${task.name}" task: "${reason}".` + ); + }) + ); + } + } + + await Promise.all(textContentPromises); + return annotationsData; + } + + get annotations() { + const annots = this._getInheritableProperty("Annots"); + return shadow(this, "annotations", Array.isArray(annots) ? annots : []); + } + + get _parsedAnnotations() { + const promise = this.pdfManager + .ensure(this, "annotations") + .then(async annots => { + if (annots.length === 0) { + return annots; + } + + const [annotationGlobals, fieldObjects] = await Promise.all([ + this.pdfManager.ensureDoc("annotationGlobals"), + this.pdfManager.ensureDoc("fieldObjects"), + ]); + if (!annotationGlobals) { + return []; + } + + const orphanFields = fieldObjects?.orphanFields; + const annotationPromises = []; + for (const annotationRef of annots) { + annotationPromises.push( + AnnotationFactory.create( + this.xref, + annotationRef, + annotationGlobals, + this._localIdFactory, + /* collectFields */ false, + orphanFields, + this.ref + ).catch(function (reason) { + warn(`_parsedAnnotations: "${reason}".`); + return null; + }) + ); + } + + const sortedAnnotations = []; + let popupAnnotations, widgetAnnotations; + // Ensure that PopupAnnotations are handled last, since they depend on + // their parent Annotation in the display layer; fixes issue 11362. + for (const annotation of await Promise.all(annotationPromises)) { + if (!annotation) { + continue; + } + if (annotation instanceof WidgetAnnotation) { + (widgetAnnotations ||= []).push(annotation); + continue; + } + if (annotation instanceof PopupAnnotation) { + (popupAnnotations ||= []).push(annotation); + continue; + } + sortedAnnotations.push(annotation); + } + if (widgetAnnotations) { + sortedAnnotations.push(...widgetAnnotations); + } + if (popupAnnotations) { + sortedAnnotations.push(...popupAnnotations); + } + + return sortedAnnotations; + }); + + return shadow(this, "_parsedAnnotations", promise); + } + + get jsActions() { + const actions = collectActions( + this.xref, + this.pageDict, + PageActionEventType + ); + return shadow(this, "jsActions", actions); + } +} + +const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]); +const STARTXREF_SIGNATURE = new Uint8Array([ + 0x73, 0x74, 0x61, 0x72, 0x74, 0x78, 0x72, 0x65, 0x66, +]); +const ENDOBJ_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64, 0x6f, 0x62, 0x6a]); + +function find(stream, signature, limit = 1024, backwards = false) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert(limit > 0, 'The "limit" must be a positive integer.'); + } + const signatureLength = signature.length; + + const scanBytes = stream.peekBytes(limit); + const scanLength = scanBytes.length - signatureLength; + + if (scanLength <= 0) { + return false; + } + if (backwards) { + const signatureEnd = signatureLength - 1; + + let pos = scanBytes.length - 1; + while (pos >= signatureEnd) { + let j = 0; + while ( + j < signatureLength && + scanBytes[pos - j] === signature[signatureEnd - j] + ) { + j++; + } + if (j >= signatureLength) { + // `signature` found. + stream.pos += pos - signatureEnd; + return true; + } + pos--; + } + } else { + // forwards + let pos = 0; + while (pos <= scanLength) { + let j = 0; + while (j < signatureLength && scanBytes[pos + j] === signature[j]) { + j++; + } + if (j >= signatureLength) { + // `signature` found. + stream.pos += pos; + return true; + } + pos++; + } + } + return false; +} + +/** + * The `PDFDocument` class holds all the (worker-thread) data of the PDF file. + */ +class PDFDocument { + constructor(pdfManager, stream) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + stream instanceof BaseStream, + 'PDFDocument: Invalid "stream" argument.' + ); + } + if (stream.length <= 0) { + throw new InvalidPDFException( + "The PDF file is empty, i.e. its size is zero bytes." + ); + } + + this.pdfManager = pdfManager; + this.stream = stream; + this.xref = new XRef(stream, pdfManager); + this._pagePromises = new Map(); + this._version = null; + + const idCounters = { + font: 0, + }; + this._globalIdFactory = class { + static getDocId() { + return `g_${pdfManager.docId}`; + } + + static createFontId() { + return `f${++idCounters.font}`; + } + + static createObjId() { + unreachable("Abstract method `createObjId` called."); + } + + static getPageObjId() { + unreachable("Abstract method `getPageObjId` called."); + } + }; + } + + parse(recoveryMode) { + this.xref.parse(recoveryMode); + this.catalog = new Catalog(this.pdfManager, this.xref); + } + + get linearization() { + let linearization = null; + try { + linearization = Linearization.create(this.stream); + } catch (err) { + if (err instanceof MissingDataException) { + throw err; + } + info(err); + } + return shadow(this, "linearization", linearization); + } + + get startXRef() { + const stream = this.stream; + let startXRef = 0; + + if (this.linearization) { + // Find the end of the first object. + stream.reset(); + if (find(stream, ENDOBJ_SIGNATURE)) { + stream.skip(6); + + let ch = stream.peekByte(); + while (isWhiteSpace(ch)) { + stream.pos++; + ch = stream.peekByte(); + } + startXRef = stream.pos - stream.start; + } + } else { + // Find `startxref` by checking backwards from the end of the file. + const step = 1024; + const startXRefLength = STARTXREF_SIGNATURE.length; + let found = false, + pos = stream.end; + + while (!found && pos > 0) { + pos -= step - startXRefLength; + if (pos < 0) { + pos = 0; + } + stream.pos = pos; + found = find(stream, STARTXREF_SIGNATURE, step, true); + } + + if (found) { + stream.skip(9); + let ch; + do { + ch = stream.getByte(); + } while (isWhiteSpace(ch)); + let str = ""; + while (ch >= /* Space = */ 0x20 && ch <= /* '9' = */ 0x39) { + str += String.fromCharCode(ch); + ch = stream.getByte(); + } + startXRef = parseInt(str, 10); + if (isNaN(startXRef)) { + startXRef = 0; + } + } + } + return shadow(this, "startXRef", startXRef); + } + + // Find the header, get the PDF format version and setup the + // stream to start from the header. + checkHeader() { + const stream = this.stream; + stream.reset(); + + if (!find(stream, PDF_HEADER_SIGNATURE)) { + // May not be a PDF file, but don't throw an error and let + // parsing continue. + return; + } + stream.moveStart(); + + // Skip over the "%PDF-" prefix, since it was found above. + stream.skip(PDF_HEADER_SIGNATURE.length); + // Read the PDF format version. + let version = "", + ch; + while ( + (ch = stream.getByte()) > /* Space = */ 0x20 && + version.length < /* MAX_PDF_VERSION_LENGTH = */ 7 + ) { + version += String.fromCharCode(ch); + } + + if (PDF_VERSION_REGEXP.test(version)) { + this._version = version; + } else { + warn(`Invalid PDF header version: ${version}`); + } + } + + parseStartXRef() { + this.xref.setStartXRef(this.startXRef); + } + + get numPages() { + let num = 0; + if (this.catalog.hasActualNumPages) { + num = this.catalog.numPages; + } else if (this.xfaFactory) { + // num is a Promise. + num = this.xfaFactory.getNumPages(); + } else if (this.linearization) { + num = this.linearization.numPages; + } else { + num = this.catalog.numPages; + } + return shadow(this, "numPages", num); + } + + /** + * @private + */ + _hasOnlyDocumentSignatures(fields, recursionDepth = 0) { + const RECURSION_LIMIT = 10; + + if (!Array.isArray(fields)) { + return false; + } + return fields.every(field => { + field = this.xref.fetchIfRef(field); + if (!(field instanceof Dict)) { + return false; + } + if (field.has("Kids")) { + if (++recursionDepth > RECURSION_LIMIT) { + warn("_hasOnlyDocumentSignatures: maximum recursion depth reached"); + return false; + } + return this._hasOnlyDocumentSignatures( + field.get("Kids"), + recursionDepth + ); + } + const isSignature = isName(field.get("FT"), "Sig"); + const rectangle = field.get("Rect"); + const isInvisible = + Array.isArray(rectangle) && rectangle.every(value => value === 0); + return isSignature && isInvisible; + }); + } + + get _xfaStreams() { + const acroForm = this.catalog.acroForm; + if (!acroForm) { + return null; + } + + const xfa = acroForm.get("XFA"); + const entries = { + "xdp:xdp": "", + template: "", + datasets: "", + config: "", + connectionSet: "", + localeSet: "", + stylesheet: "", + "/xdp:xdp": "", + }; + if (xfa instanceof BaseStream && !xfa.isEmpty) { + entries["xdp:xdp"] = xfa; + return entries; + } + + if (!Array.isArray(xfa) || xfa.length === 0) { + return null; + } + + for (let i = 0, ii = xfa.length; i < ii; i += 2) { + let name; + if (i === 0) { + name = "xdp:xdp"; + } else if (i === ii - 2) { + name = "/xdp:xdp"; + } else { + name = xfa[i]; + } + + if (!entries.hasOwnProperty(name)) { + continue; + } + const data = this.xref.fetchIfRef(xfa[i + 1]); + if (!(data instanceof BaseStream) || data.isEmpty) { + continue; + } + entries[name] = data; + } + return entries; + } + + get xfaDatasets() { + const streams = this._xfaStreams; + if (!streams) { + return shadow(this, "xfaDatasets", null); + } + for (const key of ["datasets", "xdp:xdp"]) { + const stream = streams[key]; + if (!stream) { + continue; + } + try { + const str = stringToUTF8String(stream.getString()); + const data = { [key]: str }; + return shadow(this, "xfaDatasets", new DatasetReader(data)); + } catch { + warn("XFA - Invalid utf-8 string."); + break; + } + } + return shadow(this, "xfaDatasets", null); + } + + get xfaData() { + const streams = this._xfaStreams; + if (!streams) { + return null; + } + const data = Object.create(null); + for (const [key, stream] of Object.entries(streams)) { + if (!stream) { + continue; + } + try { + data[key] = stringToUTF8String(stream.getString()); + } catch { + warn("XFA - Invalid utf-8 string."); + return null; + } + } + return data; + } + + get xfaFactory() { + let data; + if ( + this.pdfManager.enableXfa && + this.catalog.needsRendering && + this.formInfo.hasXfa && + !this.formInfo.hasAcroForm + ) { + data = this.xfaData; + } + return shadow(this, "xfaFactory", data ? new XFAFactory(data) : null); + } + + get isPureXfa() { + return this.xfaFactory ? this.xfaFactory.isValid() : false; + } + + get htmlForXfa() { + return this.xfaFactory ? this.xfaFactory.getPages() : null; + } + + async loadXfaImages() { + const xfaImagesDict = await this.pdfManager.ensureCatalog("xfaImages"); + if (!xfaImagesDict) { + return; + } + + const keys = xfaImagesDict.getKeys(); + const objectLoader = new ObjectLoader(xfaImagesDict, keys, this.xref); + await objectLoader.load(); + + const xfaImages = new Map(); + for (const key of keys) { + const stream = xfaImagesDict.get(key); + if (stream instanceof BaseStream) { + xfaImages.set(key, stream.getBytes()); + } + } + + this.xfaFactory.setImages(xfaImages); + } + + async loadXfaFonts(handler, task) { + const acroForm = await this.pdfManager.ensureCatalog("acroForm"); + if (!acroForm) { + return; + } + const resources = await acroForm.getAsync("DR"); + if (!(resources instanceof Dict)) { + return; + } + const objectLoader = new ObjectLoader(resources, ["Font"], this.xref); + await objectLoader.load(); + + const fontRes = resources.get("Font"); + if (!(fontRes instanceof Dict)) { + return; + } + + const options = Object.assign( + Object.create(null), + this.pdfManager.evaluatorOptions + ); + options.useSystemFonts = false; + + const partialEvaluator = new PartialEvaluator({ + xref: this.xref, + handler, + pageIndex: -1, + idFactory: this._globalIdFactory, + fontCache: this.catalog.fontCache, + builtInCMapCache: this.catalog.builtInCMapCache, + standardFontDataCache: this.catalog.standardFontDataCache, + options, + }); + const operatorList = new OperatorList(); + const pdfFonts = []; + const initialState = { + get font() { + return pdfFonts.at(-1); + }, + set font(font) { + pdfFonts.push(font); + }, + clone() { + return this; + }, + }; + + const promises = []; + for (const [fontName, font] of fontRes) { + const descriptor = font.get("FontDescriptor"); + if (!(descriptor instanceof Dict)) { + continue; + } + let fontFamily = descriptor.get("FontFamily"); + // For example, "Wingdings 3" is not a valid font name in the css specs. + fontFamily = fontFamily.replaceAll(/[ ]+(\d)/g, "$1"); + const fontWeight = descriptor.get("FontWeight"); + + // Angle is expressed in degrees counterclockwise in PDF + // when it's clockwise in CSS + // (see https://drafts.csswg.org/css-fonts-4/#valdef-font-style-oblique-angle) + const italicAngle = -descriptor.get("ItalicAngle"); + const cssFontInfo = { fontFamily, fontWeight, italicAngle }; + + if (!validateCSSFont(cssFontInfo)) { + continue; + } + promises.push( + partialEvaluator + .handleSetFont( + resources, + [Name.get(fontName), 1], + /* fontRef = */ null, + operatorList, + task, + initialState, + /* fallbackFontDict = */ null, + /* cssFontInfo = */ cssFontInfo + ) + .catch(function (reason) { + warn(`loadXfaFonts: "${reason}".`); + return null; + }) + ); + } + + await Promise.all(promises); + const missingFonts = this.xfaFactory.setFonts(pdfFonts); + + if (!missingFonts) { + return; + } + + options.ignoreErrors = true; + promises.length = 0; + pdfFonts.length = 0; + + const reallyMissingFonts = new Set(); + for (const missing of missingFonts) { + if (!getXfaFontName(`${missing}-Regular`)) { + // No substitution available: we'll fallback on Myriad. + reallyMissingFonts.add(missing); + } + } + + if (reallyMissingFonts.size) { + missingFonts.push("PdfJS-Fallback"); + } + + for (const missing of missingFonts) { + if (reallyMissingFonts.has(missing)) { + continue; + } + for (const fontInfo of [ + { name: "Regular", fontWeight: 400, italicAngle: 0 }, + { name: "Bold", fontWeight: 700, italicAngle: 0 }, + { name: "Italic", fontWeight: 400, italicAngle: 12 }, + { name: "BoldItalic", fontWeight: 700, italicAngle: 12 }, + ]) { + const name = `${missing}-${fontInfo.name}`; + const dict = getXfaFontDict(name); + + promises.push( + partialEvaluator + .handleSetFont( + resources, + [Name.get(name), 1], + /* fontRef = */ null, + operatorList, + task, + initialState, + /* fallbackFontDict = */ dict, + /* cssFontInfo = */ { + fontFamily: missing, + fontWeight: fontInfo.fontWeight, + italicAngle: fontInfo.italicAngle, + } + ) + .catch(function (reason) { + warn(`loadXfaFonts: "${reason}".`); + return null; + }) + ); + } + } + + await Promise.all(promises); + this.xfaFactory.appendFonts(pdfFonts, reallyMissingFonts); + } + + async serializeXfaData(annotationStorage) { + return this.xfaFactory + ? this.xfaFactory.serializeData(annotationStorage) + : null; + } + + /** + * The specification states in section 7.5.2 that the version from + * the catalog, if present, should overwrite the version from the header. + */ + get version() { + return this.catalog.version || this._version; + } + + get formInfo() { + const formInfo = { + hasFields: false, + hasAcroForm: false, + hasXfa: false, + hasSignatures: false, + }; + const acroForm = this.catalog.acroForm; + if (!acroForm) { + return shadow(this, "formInfo", formInfo); + } + + try { + const fields = acroForm.get("Fields"); + const hasFields = Array.isArray(fields) && fields.length > 0; + formInfo.hasFields = hasFields; // Used by the `fieldObjects` getter. + + // The document contains XFA data if the `XFA` entry is a non-empty + // array or stream. + const xfa = acroForm.get("XFA"); + formInfo.hasXfa = + (Array.isArray(xfa) && xfa.length > 0) || + (xfa instanceof BaseStream && !xfa.isEmpty); + + // The document contains AcroForm data if the `Fields` entry is a + // non-empty array and it doesn't consist of only document signatures. + // This second check is required for files that don't actually contain + // AcroForm data (only XFA data), but that use the `Fields` entry to + // store (invisible) document signatures. This can be detected using + // the first bit of the `SigFlags` integer (see Table 219 in the + // specification). + const sigFlags = acroForm.get("SigFlags"); + const hasSignatures = !!(sigFlags & 0x1); + const hasOnlyDocumentSignatures = + hasSignatures && this._hasOnlyDocumentSignatures(fields); + formInfo.hasAcroForm = hasFields && !hasOnlyDocumentSignatures; + formInfo.hasSignatures = hasSignatures; + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(`Cannot fetch form information: "${ex}".`); + } + return shadow(this, "formInfo", formInfo); + } + + get documentInfo() { + const docInfo = { + PDFFormatVersion: this.version, + Language: this.catalog.lang, + EncryptFilterName: this.xref.encrypt + ? this.xref.encrypt.filterName + : null, + IsLinearized: !!this.linearization, + IsAcroFormPresent: this.formInfo.hasAcroForm, + IsXFAPresent: this.formInfo.hasXfa, + IsCollectionPresent: !!this.catalog.collection, + IsSignaturesPresent: this.formInfo.hasSignatures, + }; + + let infoDict; + try { + infoDict = this.xref.trailer.get("Info"); + } catch (err) { + if (err instanceof MissingDataException) { + throw err; + } + info("The document information dictionary is invalid."); + } + if (!(infoDict instanceof Dict)) { + return shadow(this, "documentInfo", docInfo); + } + + for (const [key, value] of infoDict) { + switch (key) { + case "Title": + case "Author": + case "Subject": + case "Keywords": + case "Creator": + case "Producer": + case "CreationDate": + case "ModDate": + if (typeof value === "string") { + docInfo[key] = stringToPDFString(value); + continue; + } + break; + case "Trapped": + if (value instanceof Name) { + docInfo[key] = value; + continue; + } + break; + default: + // For custom values, only accept white-listed types to prevent + // errors that would occur when trying to send non-serializable + // objects to the main-thread (for example `Dict` or `Stream`). + let customValue; + switch (typeof value) { + case "string": + customValue = stringToPDFString(value); + break; + case "number": + case "boolean": + customValue = value; + break; + default: + if (value instanceof Name) { + customValue = value; + } + break; + } + + if (customValue === undefined) { + warn(`Bad value, for custom key "${key}", in Info: ${value}.`); + continue; + } + if (!docInfo.Custom) { + docInfo.Custom = Object.create(null); + } + docInfo.Custom[key] = customValue; + continue; + } + warn(`Bad value, for key "${key}", in Info: ${value}.`); + } + return shadow(this, "documentInfo", docInfo); + } + + get fingerprints() { + const FINGERPRINT_FIRST_BYTES = 1024; + const EMPTY_FINGERPRINT = "\x00".repeat(16); + + function validate(data) { + return ( + typeof data === "string" && + data.length === 16 && + data !== EMPTY_FINGERPRINT + ); + } + + const id = this.xref.trailer.get("ID"); + let hashOriginal, hashModified; + if (Array.isArray(id) && validate(id[0])) { + hashOriginal = stringToBytes(id[0]); + + if (id[1] !== id[0] && validate(id[1])) { + hashModified = stringToBytes(id[1]); + } + } else { + hashOriginal = calculateMD5( + this.stream.getByteRange(0, FINGERPRINT_FIRST_BYTES), + 0, + FINGERPRINT_FIRST_BYTES + ); + } + + return shadow(this, "fingerprints", [ + toHexUtil(hashOriginal), + hashModified ? toHexUtil(hashModified) : null, + ]); + } + + async _getLinearizationPage(pageIndex) { + const { catalog, linearization, xref } = this; + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + linearization?.pageFirst === pageIndex, + "_getLinearizationPage - invalid pageIndex argument." + ); + } + + const ref = Ref.get(linearization.objectNumberFirst, 0); + try { + const obj = await xref.fetchAsync(ref); + // Ensure that the object that was found is actually a Page dictionary. + if (obj instanceof Dict) { + let type = obj.getRaw("Type"); + if (type instanceof Ref) { + type = await xref.fetchAsync(type); + } + if ( + isName(type, "Page") || + (!obj.has("Type") && !obj.has("Kids") && obj.has("Contents")) + ) { + if (!catalog.pageKidsCountCache.has(ref)) { + catalog.pageKidsCountCache.put(ref, 1); // Cache the Page reference. + } + // Help improve performance of the `Catalog.getPageIndex` method. + if (!catalog.pageIndexCache.has(ref)) { + catalog.pageIndexCache.put(ref, 0); + } + + return [obj, ref]; + } + } + throw new FormatError( + "The Linearization dictionary doesn't point to a valid Page dictionary." + ); + } catch (reason) { + warn(`_getLinearizationPage: "${reason.message}".`); + return catalog.getPageDict(pageIndex); + } + } + + getPage(pageIndex) { + const cachedPromise = this._pagePromises.get(pageIndex); + if (cachedPromise) { + return cachedPromise; + } + const { catalog, linearization, xfaFactory } = this; + + let promise; + if (xfaFactory) { + promise = Promise.resolve([Dict.empty, null]); + } else if (linearization?.pageFirst === pageIndex) { + promise = this._getLinearizationPage(pageIndex); + } else { + promise = catalog.getPageDict(pageIndex); + } + promise = promise.then( + ([pageDict, ref]) => + new Page({ + pdfManager: this.pdfManager, + xref: this.xref, + pageIndex, + pageDict, + ref, + globalIdFactory: this._globalIdFactory, + fontCache: catalog.fontCache, + builtInCMapCache: catalog.builtInCMapCache, + standardFontDataCache: catalog.standardFontDataCache, + globalImageCache: catalog.globalImageCache, + systemFontCache: catalog.systemFontCache, + nonBlendModesSet: catalog.nonBlendModesSet, + xfaFactory, + }) + ); + + this._pagePromises.set(pageIndex, promise); + return promise; + } + + async checkFirstPage(recoveryMode = false) { + if (recoveryMode) { + return; + } + try { + await this.getPage(0); + } catch (reason) { + if (reason instanceof XRefEntryException) { + // Clear out the various caches to ensure that we haven't stored any + // inconsistent and/or incorrect state, since that could easily break + // subsequent `this.getPage` calls. + this._pagePromises.delete(0); + await this.cleanup(); + + throw new XRefParseException(); + } + } + } + + async checkLastPage(recoveryMode = false) { + const { catalog, pdfManager } = this; + + catalog.setActualNumPages(); // Ensure that it's always reset. + let numPages; + + try { + await Promise.all([ + pdfManager.ensureDoc("xfaFactory"), + pdfManager.ensureDoc("linearization"), + pdfManager.ensureCatalog("numPages"), + ]); + + if (this.xfaFactory) { + return; // The Page count is always calculated for XFA-documents. + } else if (this.linearization) { + numPages = this.linearization.numPages; + } else { + numPages = catalog.numPages; + } + + if (!Number.isInteger(numPages)) { + throw new FormatError("Page count is not an integer."); + } else if (numPages <= 1) { + return; + } + await this.getPage(numPages - 1); + } catch (reason) { + // Clear out the various caches to ensure that we haven't stored any + // inconsistent and/or incorrect state, since that could easily break + // subsequent `this.getPage` calls. + this._pagePromises.delete(numPages - 1); + await this.cleanup(); + + if (reason instanceof XRefEntryException && !recoveryMode) { + throw new XRefParseException(); + } + warn(`checkLastPage - invalid /Pages tree /Count: ${numPages}.`); + + let pagesTree; + try { + pagesTree = await catalog.getAllPageDicts(recoveryMode); + } catch (reasonAll) { + if (reasonAll instanceof XRefEntryException && !recoveryMode) { + throw new XRefParseException(); + } + catalog.setActualNumPages(1); + return; + } + + for (const [pageIndex, [pageDict, ref]] of pagesTree) { + let promise; + if (pageDict instanceof Error) { + promise = Promise.reject(pageDict); + + // Prevent "uncaught exception: Object"-messages in the console. + promise.catch(() => {}); + } else { + promise = Promise.resolve( + new Page({ + pdfManager, + xref: this.xref, + pageIndex, + pageDict, + ref, + globalIdFactory: this._globalIdFactory, + fontCache: catalog.fontCache, + builtInCMapCache: catalog.builtInCMapCache, + standardFontDataCache: catalog.standardFontDataCache, + globalImageCache: catalog.globalImageCache, + systemFontCache: catalog.systemFontCache, + nonBlendModesSet: catalog.nonBlendModesSet, + xfaFactory: null, + }) + ); + } + + this._pagePromises.set(pageIndex, promise); + } + catalog.setActualNumPages(pagesTree.size); + } + } + + fontFallback(id, handler) { + return this.catalog.fontFallback(id, handler); + } + + async cleanup(manuallyTriggered = false) { + return this.catalog + ? this.catalog.cleanup(manuallyTriggered) + : clearGlobalCaches(); + } + + async #collectFieldObjects( + name, + parentRef, + fieldRef, + promises, + annotationGlobals, + visitedRefs, + orphanFields + ) { + const { xref } = this; + + if (!(fieldRef instanceof Ref) || visitedRefs.has(fieldRef)) { + return; + } + visitedRefs.put(fieldRef); + const field = await xref.fetchAsync(fieldRef); + if (!(field instanceof Dict)) { + return; + } + let subtype = await field.getAsync("Subtype"); + subtype = subtype instanceof Name ? subtype.name : null; + // Skip unrelated annotation types (see issue 19281). + switch (subtype) { + case "Link": + return; + } + if (field.has("T")) { + const partName = stringToPDFString(await field.getAsync("T")); + name = name === "" ? partName : `${name}.${partName}`; + } else { + let obj = field; + while (true) { + obj = obj.getRaw("Parent") || parentRef; + if (obj instanceof Ref) { + if (visitedRefs.has(obj)) { + break; + } + obj = await xref.fetchAsync(obj); + } + if (!(obj instanceof Dict)) { + break; + } + if (obj.has("T")) { + const partName = stringToPDFString(await obj.getAsync("T")); + name = name === "" ? partName : `${name}.${partName}`; + break; + } + } + } + + if ( + parentRef && + !field.has("Parent") && + isName(field.get("Subtype"), "Widget") + ) { + // We've a parent from the Fields array, but the field hasn't. + orphanFields.put(fieldRef, parentRef); + } + + if (!promises.has(name)) { + promises.set(name, []); + } + promises.get(name).push( + AnnotationFactory.create( + xref, + fieldRef, + annotationGlobals, + /* idFactory = */ null, + /* collectFields */ true, + orphanFields, + /* pageRef */ null + ) + .then(annotation => annotation?.getFieldObject()) + .catch(function (reason) { + warn(`#collectFieldObjects: "${reason}".`); + return null; + }) + ); + + if (!field.has("Kids")) { + return; + } + const kids = await field.getAsync("Kids"); + if (Array.isArray(kids)) { + for (const kid of kids) { + await this.#collectFieldObjects( + name, + fieldRef, + kid, + promises, + annotationGlobals, + visitedRefs, + orphanFields + ); + } + } + } + + get fieldObjects() { + const promise = this.pdfManager + .ensureDoc("formInfo") + .then(async formInfo => { + if (!formInfo.hasFields) { + return null; + } + + const [annotationGlobals, acroForm] = await Promise.all([ + this.pdfManager.ensureDoc("annotationGlobals"), + this.pdfManager.ensureCatalog("acroForm"), + ]); + if (!annotationGlobals) { + return null; + } + + const visitedRefs = new RefSet(); + const allFields = Object.create(null); + const fieldPromises = new Map(); + const orphanFields = new RefSetCache(); + for (const fieldRef of await acroForm.getAsync("Fields")) { + await this.#collectFieldObjects( + "", + null, + fieldRef, + fieldPromises, + annotationGlobals, + visitedRefs, + orphanFields + ); + } + + const allPromises = []; + for (const [name, promises] of fieldPromises) { + allPromises.push( + Promise.all(promises).then(fields => { + fields = fields.filter(field => !!field); + if (fields.length > 0) { + allFields[name] = fields; + } + }) + ); + } + await Promise.all(allPromises); + + return { + allFields: objectSize(allFields) > 0 ? allFields : null, + orphanFields, + }; + }); + + return shadow(this, "fieldObjects", promise); + } + + get hasJSActions() { + const promise = this.pdfManager.ensureDoc("_parseHasJSActions"); + return shadow(this, "hasJSActions", promise); + } + + /** + * @private + */ + async _parseHasJSActions() { + const [catalogJsActions, fieldObjects] = await Promise.all([ + this.pdfManager.ensureCatalog("jsActions"), + this.pdfManager.ensureDoc("fieldObjects"), + ]); + + if (catalogJsActions) { + return true; + } + if (fieldObjects?.allFields) { + return Object.values(fieldObjects.allFields).some(fieldObject => + fieldObject.some(object => object.actions !== null) + ); + } + return false; + } + + get calculationOrderIds() { + const calculationOrder = this.catalog.acroForm?.get("CO"); + if (!Array.isArray(calculationOrder) || calculationOrder.length === 0) { + return shadow(this, "calculationOrderIds", null); + } + + const ids = []; + for (const id of calculationOrder) { + if (id instanceof Ref) { + ids.push(id.toString()); + } + } + return shadow(this, "calculationOrderIds", ids.length ? ids : null); + } + + get annotationGlobals() { + return shadow( + this, + "annotationGlobals", + AnnotationFactory.createGlobals(this.pdfManager) + ); + } +} + +export { Page, PDFDocument }; diff --git a/frontend/public/pdf.js/src/core/encodings.js b/frontend/public/pdf.js/src/core/encodings.js new file mode 100644 index 0000000..8ebbd7b --- /dev/null +++ b/frontend/public/pdf.js/src/core/encodings.js @@ -0,0 +1,301 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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. + */ + +// prettier-ignore +const ExpertEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "exclamsmall", "Hungarumlautsmall", "", "dollaroldstyle", + "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", + "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", + "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", + "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", + "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", + "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", + "questionsmall", "", "asuperior", "bsuperior", "centsuperior", "dsuperior", + "esuperior", "", "", "", "isuperior", "", "", "lsuperior", "msuperior", + "nsuperior", "osuperior", "", "", "rsuperior", "ssuperior", "tsuperior", + "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", + "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", + "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", + "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", + "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", + "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", + "onefitted", "rupiah", "Tildesmall", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "exclamdownsmall", "centoldstyle", "Lslashsmall", + "", "", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", + "Caronsmall", "", "Dotaccentsmall", "", "", "Macronsmall", "", "", + "figuredash", "hypheninferior", "", "", "Ogoneksmall", "Ringsmall", + "Cedillasmall", "", "", "", "onequarter", "onehalf", "threequarters", + "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", + "seveneighths", "onethird", "twothirds", "", "", "zerosuperior", + "onesuperior", "twosuperior", "threesuperior", "foursuperior", + "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", + "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", + "threeinferior", "fourinferior", "fiveinferior", "sixinferior", + "seveninferior", "eightinferior", "nineinferior", "centinferior", + "dollarinferior", "periodinferior", "commainferior", "Agravesmall", + "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", + "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", + "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", + "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", + "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", + "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", + "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", + "Ydieresissmall"]; + +// prettier-ignore +const MacExpertEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "exclamsmall", "Hungarumlautsmall", "centoldstyle", + "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", + "parenleftsuperior", "parenrightsuperior", "twodotenleader", + "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", + "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", + "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", + "nineoldstyle", "colon", "semicolon", "", "threequartersemdash", "", + "questionsmall", "", "", "", "", "Ethsmall", "", "", "onequarter", + "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", + "seveneighths", "onethird", "twothirds", "", "", "", "", "", "", "ff", + "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", + "Circumflexsmall", "hypheninferior", "Gravesmall", "Asmall", "Bsmall", + "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", + "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", + "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", + "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", + "Tildesmall", "", "", "asuperior", "centsuperior", "", "", "", "", + "Aacutesmall", "Agravesmall", "Acircumflexsmall", "Adieresissmall", + "Atildesmall", "Aringsmall", "Ccedillasmall", "Eacutesmall", "Egravesmall", + "Ecircumflexsmall", "Edieresissmall", "Iacutesmall", "Igravesmall", + "Icircumflexsmall", "Idieresissmall", "Ntildesmall", "Oacutesmall", + "Ogravesmall", "Ocircumflexsmall", "Odieresissmall", "Otildesmall", + "Uacutesmall", "Ugravesmall", "Ucircumflexsmall", "Udieresissmall", "", + "eightsuperior", "fourinferior", "threeinferior", "sixinferior", + "eightinferior", "seveninferior", "Scaronsmall", "", "centinferior", + "twoinferior", "", "Dieresissmall", "", "Caronsmall", "osuperior", + "fiveinferior", "", "commainferior", "periodinferior", "Yacutesmall", "", + "dollarinferior", "", "", "Thornsmall", "", "nineinferior", "zeroinferior", + "Zcaronsmall", "AEsmall", "Oslashsmall", "questiondownsmall", + "oneinferior", "Lslashsmall", "", "", "", "", "", "", "Cedillasmall", "", + "", "", "", "", "OEsmall", "figuredash", "hyphensuperior", "", "", "", "", + "exclamdownsmall", "", "Ydieresissmall", "", "onesuperior", "twosuperior", + "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", + "sevensuperior", "ninesuperior", "zerosuperior", "", "esuperior", + "rsuperior", "tsuperior", "", "", "isuperior", "ssuperior", "dsuperior", + "", "", "", "", "", "lsuperior", "Ogoneksmall", "Brevesmall", + "Macronsmall", "bsuperior", "nsuperior", "msuperior", "commasuperior", + "periodsuperior", "Dotaccentsmall", "Ringsmall", "", "", "", ""]; + +// prettier-ignore +const MacRomanEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", + "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", + "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", + "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", + "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", + "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", + "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", + "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", + "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", + "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", + "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", + "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", + "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", + "section", "bullet", "paragraph", "germandbls", "registered", "copyright", + "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", + "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", + "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", + "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", + "radical", "florin", "approxequal", "Delta", "guillemotleft", + "guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde", "OE", + "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", + "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", + "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", + "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", + "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", + "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", + "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", + "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", + "ogonek", "caron"]; + +// prettier-ignore +const StandardEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", + "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", + "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", + "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", + "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", + "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", + "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdown", + "cent", "sterling", "fraction", "yen", "florin", "section", "currency", + "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", + "guilsinglright", "fi", "fl", "", "endash", "dagger", "daggerdbl", + "periodcentered", "", "paragraph", "bullet", "quotesinglbase", + "quotedblbase", "quotedblright", "guillemotright", "ellipsis", + "perthousand", "", "questiondown", "", "grave", "acute", "circumflex", + "tilde", "macron", "breve", "dotaccent", "dieresis", "", "ring", "cedilla", + "", "hungarumlaut", "ogonek", "caron", "emdash", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "AE", "", "ordfeminine", "", "", + "", "", "Lslash", "Oslash", "OE", "ordmasculine", "", "", "", "", "", "ae", + "", "", "", "dotlessi", "", "", "lslash", "oslash", "oe", "germandbls", "", + "", "", ""]; + +// prettier-ignore +const WinAnsiEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", + "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", + "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", + "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", + "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", + "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", + "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", + "bullet", "Euro", "bullet", "quotesinglbase", "florin", "quotedblbase", + "ellipsis", "dagger", "daggerdbl", "circumflex", "perthousand", "Scaron", + "guilsinglleft", "OE", "bullet", "Zcaron", "bullet", "bullet", "quoteleft", + "quoteright", "quotedblleft", "quotedblright", "bullet", "endash", + "emdash", "tilde", "trademark", "scaron", "guilsinglright", "oe", "bullet", + "zcaron", "Ydieresis", "space", "exclamdown", "cent", "sterling", + "currency", "yen", "brokenbar", "section", "dieresis", "copyright", + "ordfeminine", "guillemotleft", "logicalnot", "hyphen", "registered", + "macron", "degree", "plusminus", "twosuperior", "threesuperior", "acute", + "mu", "paragraph", "periodcentered", "cedilla", "onesuperior", + "ordmasculine", "guillemotright", "onequarter", "onehalf", "threequarters", + "questiondown", "Agrave", "Aacute", "Acircumflex", "Atilde", "Adieresis", + "Aring", "AE", "Ccedilla", "Egrave", "Eacute", "Ecircumflex", "Edieresis", + "Igrave", "Iacute", "Icircumflex", "Idieresis", "Eth", "Ntilde", "Ograve", + "Oacute", "Ocircumflex", "Otilde", "Odieresis", "multiply", "Oslash", + "Ugrave", "Uacute", "Ucircumflex", "Udieresis", "Yacute", "Thorn", + "germandbls", "agrave", "aacute", "acircumflex", "atilde", "adieresis", + "aring", "ae", "ccedilla", "egrave", "eacute", "ecircumflex", "edieresis", + "igrave", "iacute", "icircumflex", "idieresis", "eth", "ntilde", "ograve", + "oacute", "ocircumflex", "otilde", "odieresis", "divide", "oslash", + "ugrave", "uacute", "ucircumflex", "udieresis", "yacute", "thorn", + "ydieresis"]; + +// prettier-ignore +const SymbolSetEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "exclam", "universal", "numbersign", "existential", "percent", + "ampersand", "suchthat", "parenleft", "parenright", "asteriskmath", "plus", + "comma", "minus", "period", "slash", "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", + "equal", "greater", "question", "congruent", "Alpha", "Beta", "Chi", + "Delta", "Epsilon", "Phi", "Gamma", "Eta", "Iota", "theta1", "Kappa", + "Lambda", "Mu", "Nu", "Omicron", "Pi", "Theta", "Rho", "Sigma", "Tau", + "Upsilon", "sigma1", "Omega", "Xi", "Psi", "Zeta", "bracketleft", + "therefore", "bracketright", "perpendicular", "underscore", "radicalex", + "alpha", "beta", "chi", "delta", "epsilon", "phi", "gamma", "eta", "iota", + "phi1", "kappa", "lambda", "mu", "nu", "omicron", "pi", "theta", "rho", + "sigma", "tau", "upsilon", "omega1", "omega", "xi", "psi", "zeta", + "braceleft", "bar", "braceright", "similar", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "Euro", "Upsilon1", "minute", "lessequal", + "fraction", "infinity", "florin", "club", "diamond", "heart", "spade", + "arrowboth", "arrowleft", "arrowup", "arrowright", "arrowdown", "degree", + "plusminus", "second", "greaterequal", "multiply", "proportional", + "partialdiff", "bullet", "divide", "notequal", "equivalence", + "approxequal", "ellipsis", "arrowvertex", "arrowhorizex", "carriagereturn", + "aleph", "Ifraktur", "Rfraktur", "weierstrass", "circlemultiply", + "circleplus", "emptyset", "intersection", "union", "propersuperset", + "reflexsuperset", "notsubset", "propersubset", "reflexsubset", "element", + "notelement", "angle", "gradient", "registerserif", "copyrightserif", + "trademarkserif", "product", "radical", "dotmath", "logicalnot", + "logicaland", "logicalor", "arrowdblboth", "arrowdblleft", "arrowdblup", + "arrowdblright", "arrowdbldown", "lozenge", "angleleft", "registersans", + "copyrightsans", "trademarksans", "summation", "parenlefttp", + "parenleftex", "parenleftbt", "bracketlefttp", "bracketleftex", + "bracketleftbt", "bracelefttp", "braceleftmid", "braceleftbt", "braceex", + "", "angleright", "integral", "integraltp", "integralex", "integralbt", + "parenrighttp", "parenrightex", "parenrightbt", "bracketrighttp", + "bracketrightex", "bracketrightbt", "bracerighttp", "bracerightmid", + "bracerightbt", ""]; + +// prettier-ignore +const ZapfDingbatsEncoding = [ + "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "space", "a1", "a2", "a202", "a3", "a4", "a5", "a119", "a118", "a117", + "a11", "a12", "a13", "a14", "a15", "a16", "a105", "a17", "a18", "a19", + "a20", "a21", "a22", "a23", "a24", "a25", "a26", "a27", "a28", "a6", "a7", + "a8", "a9", "a10", "a29", "a30", "a31", "a32", "a33", "a34", "a35", "a36", + "a37", "a38", "a39", "a40", "a41", "a42", "a43", "a44", "a45", "a46", + "a47", "a48", "a49", "a50", "a51", "a52", "a53", "a54", "a55", "a56", + "a57", "a58", "a59", "a60", "a61", "a62", "a63", "a64", "a65", "a66", + "a67", "a68", "a69", "a70", "a71", "a72", "a73", "a74", "a203", "a75", + "a204", "a76", "a77", "a78", "a79", "a81", "a82", "a83", "a84", "a97", + "a98", "a99", "a100", "", "a89", "a90", "a93", "a94", "a91", "a92", "a205", + "a85", "a206", "a86", "a87", "a88", "a95", "a96", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "a101", "a102", "a103", + "a104", "a106", "a107", "a108", "a112", "a111", "a110", "a109", "a120", + "a121", "a122", "a123", "a124", "a125", "a126", "a127", "a128", "a129", + "a130", "a131", "a132", "a133", "a134", "a135", "a136", "a137", "a138", + "a139", "a140", "a141", "a142", "a143", "a144", "a145", "a146", "a147", + "a148", "a149", "a150", "a151", "a152", "a153", "a154", "a155", "a156", + "a157", "a158", "a159", "a160", "a161", "a163", "a164", "a196", "a165", + "a192", "a166", "a167", "a168", "a169", "a170", "a171", "a172", "a173", + "a162", "a174", "a175", "a176", "a177", "a178", "a179", "a193", "a180", + "a199", "a181", "a200", "a182", "", "a201", "a183", "a184", "a197", "a185", + "a194", "a198", "a186", "a195", "a187", "a188", "a189", "a190", "a191", ""]; + +function getEncoding(encodingName) { + switch (encodingName) { + case "WinAnsiEncoding": + return WinAnsiEncoding; + case "StandardEncoding": + return StandardEncoding; + case "MacRomanEncoding": + return MacRomanEncoding; + case "SymbolSetEncoding": + return SymbolSetEncoding; + case "ZapfDingbatsEncoding": + return ZapfDingbatsEncoding; + case "ExpertEncoding": + return ExpertEncoding; + case "MacExpertEncoding": + return MacExpertEncoding; + default: + return null; + } +} + +export { + ExpertEncoding, + getEncoding, + MacRomanEncoding, + StandardEncoding, + SymbolSetEncoding, + WinAnsiEncoding, + ZapfDingbatsEncoding, +}; diff --git a/frontend/public/pdf.js/src/core/evaluator.js b/frontend/public/pdf.js/src/core/evaluator.js new file mode 100644 index 0000000..484e167 --- /dev/null +++ b/frontend/public/pdf.js/src/core/evaluator.js @@ -0,0 +1,5211 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + AbortException, + assert, + FONT_IDENTITY_MATRIX, + FormatError, + IDENTITY_MATRIX, + info, + isArrayEqual, + normalizeUnicode, + OPS, + shadow, + stringToPDFString, + TextRenderingMode, + Util, + warn, +} from "../shared/util.js"; +import { CMapFactory, IdentityCMap } from "./cmap.js"; +import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js"; +import { ErrorFont, Font } from "./fonts.js"; +import { + fetchBinaryData, + isNumberArray, + lookupMatrix, + lookupNormalRect, +} from "./core_utils.js"; +import { + getEncoding, + MacRomanEncoding, + StandardEncoding, + SymbolSetEncoding, + WinAnsiEncoding, + ZapfDingbatsEncoding, +} from "./encodings.js"; +import { + getFontNameToFileMap, + getSerifFonts, + getStandardFontName, + getStdFontMap, + getSymbolsFonts, + isKnownFontName, +} from "./standard_fonts.js"; +import { getTilingPatternIR, Pattern } from "./pattern.js"; +import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js"; +import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js"; +import { isPDFFunction, PDFFunctionFactory } from "./function.js"; +import { Lexer, Parser } from "./parser.js"; +import { + LocalColorSpaceCache, + LocalGStateCache, + LocalImageCache, + LocalTilingPatternCache, + RegionalImageCache, +} from "./image_utils.js"; +import { BaseStream } from "./base_stream.js"; +import { bidi } from "./bidi.js"; +import { ColorSpace } from "./colorspace.js"; +import { DecodeStream } from "./decode_stream.js"; +import { FontFlags } from "./fonts_utils.js"; +import { getFontSubstitution } from "./font_substitutions.js"; +import { getGlyphsUnicode } from "./glyphlist.js"; +import { getMetrics } from "./metrics.js"; +import { getUnicodeForGlyph } from "./unicode.js"; +import { MurmurHash3_64 } from "../shared/murmurhash3.js"; +import { OperatorList } from "./operator_list.js"; +import { PDFImage } from "./image.js"; +import { Stream } from "./stream.js"; + +const DefaultPartialEvaluatorOptions = Object.freeze({ + maxImageSize: -1, + disableFontFace: false, + ignoreErrors: false, + isEvalSupported: true, + isOffscreenCanvasSupported: false, + isImageDecoderSupported: false, + canvasMaxAreaInBytes: -1, + fontExtraProperties: false, + useSystemFonts: true, + cMapUrl: null, + standardFontDataUrl: null, + wasmUrl: null, +}); + +const PatternType = { + TILING: 1, + SHADING: 2, +}; + +// Optionally avoid sending individual, or very few, text chunks to reduce +// `postMessage` overhead with ReadableStream (see issue 13962). +// +// PLEASE NOTE: This value should *not* be too large (it's used as a lower limit +// in `enqueueChunk`), since that would cause streaming of textContent to become +// essentially useless in practice by sending all (or most) chunks at once. +// Also, a too large value would (indirectly) affect the main-thread `textLayer` +// building negatively by forcing all textContent to be handled at once, which +// could easily end up hurting *overall* performance (e.g. rendering as well). +const TEXT_CHUNK_BATCH_SIZE = 10; + +const deferred = Promise.resolve(); + +// Convert PDF blend mode names to HTML5 blend mode names. +function normalizeBlendMode(value, parsingArray = false) { + if (Array.isArray(value)) { + // Use the first *supported* BM value in the Array (fixes issue11279.pdf). + for (const val of value) { + const maybeBM = normalizeBlendMode(val, /* parsingArray = */ true); + if (maybeBM) { + return maybeBM; + } + } + warn(`Unsupported blend mode Array: ${value}`); + return "source-over"; + } + + if (!(value instanceof Name)) { + if (parsingArray) { + return null; + } + return "source-over"; + } + switch (value.name) { + case "Normal": + case "Compatible": + return "source-over"; + case "Multiply": + return "multiply"; + case "Screen": + return "screen"; + case "Overlay": + return "overlay"; + case "Darken": + return "darken"; + case "Lighten": + return "lighten"; + case "ColorDodge": + return "color-dodge"; + case "ColorBurn": + return "color-burn"; + case "HardLight": + return "hard-light"; + case "SoftLight": + return "soft-light"; + case "Difference": + return "difference"; + case "Exclusion": + return "exclusion"; + case "Hue": + return "hue"; + case "Saturation": + return "saturation"; + case "Color": + return "color"; + case "Luminosity": + return "luminosity"; + } + if (parsingArray) { + return null; + } + warn(`Unsupported blend mode: ${value.name}`); + return "source-over"; +} + +function addLocallyCachedImageOps(opList, data) { + if (data.objId) { + opList.addDependency(data.objId); + } + opList.addImageOps(data.fn, data.args, data.optionalContent, data.hasMask); + + if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) { + data.args[0].count++; + } +} + +// Trying to minimize Date.now() usage and check every 100 time. +class TimeSlotManager { + static TIME_SLOT_DURATION_MS = 20; + + static CHECK_TIME_EVERY = 100; + + constructor() { + this.reset(); + } + + check() { + if (++this.checked < TimeSlotManager.CHECK_TIME_EVERY) { + return false; + } + this.checked = 0; + return this.endTime <= Date.now(); + } + + reset() { + this.endTime = Date.now() + TimeSlotManager.TIME_SLOT_DURATION_MS; + this.checked = 0; + } +} + +class PartialEvaluator { + constructor({ + xref, + handler, + pageIndex, + idFactory, + fontCache, + builtInCMapCache, + standardFontDataCache, + globalImageCache, + systemFontCache, + options = null, + }) { + this.xref = xref; + this.handler = handler; + this.pageIndex = pageIndex; + this.idFactory = idFactory; + this.fontCache = fontCache; + this.builtInCMapCache = builtInCMapCache; + this.standardFontDataCache = standardFontDataCache; + this.globalImageCache = globalImageCache; + this.systemFontCache = systemFontCache; + this.options = options || DefaultPartialEvaluatorOptions; + this.type3FontRefs = null; + + this._regionalImageCache = new RegionalImageCache(); + this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this); + } + + /** + * Since Functions are only cached (locally) by reference, we can share one + * `PDFFunctionFactory` instance within this `PartialEvaluator` instance. + */ + get _pdfFunctionFactory() { + const pdfFunctionFactory = new PDFFunctionFactory({ + xref: this.xref, + isEvalSupported: this.options.isEvalSupported, + }); + return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory); + } + + get parsingType3Font() { + return !!this.type3FontRefs; + } + + clone(newOptions = null) { + const newEvaluator = Object.create(this); + newEvaluator.options = Object.assign( + Object.create(null), + this.options, + newOptions + ); + return newEvaluator; + } + + hasBlendModes(resources, nonBlendModesSet) { + if (!(resources instanceof Dict)) { + return false; + } + if (resources.objId && nonBlendModesSet.has(resources.objId)) { + return false; + } + + const processed = new RefSet(nonBlendModesSet); + if (resources.objId) { + processed.put(resources.objId); + } + + const nodes = [resources], + xref = this.xref; + while (nodes.length) { + const node = nodes.shift(); + // First check the current resources for blend modes. + const graphicStates = node.get("ExtGState"); + if (graphicStates instanceof Dict) { + for (let graphicState of graphicStates.getRawValues()) { + if (graphicState instanceof Ref) { + if (processed.has(graphicState)) { + continue; // The ExtGState has already been processed. + } + try { + graphicState = xref.fetch(graphicState); + } catch (ex) { + // Avoid parsing a corrupt ExtGState more than once. + processed.put(graphicState); + + info(`hasBlendModes - ignoring ExtGState: "${ex}".`); + continue; + } + } + if (!(graphicState instanceof Dict)) { + continue; + } + if (graphicState.objId) { + processed.put(graphicState.objId); + } + + const bm = graphicState.get("BM"); + if (bm instanceof Name) { + if (bm.name !== "Normal") { + return true; + } + continue; + } + if (bm !== undefined && Array.isArray(bm)) { + for (const element of bm) { + if (element instanceof Name && element.name !== "Normal") { + return true; + } + } + } + } + } + // Descend into the XObjects to look for more resources and blend modes. + const xObjects = node.get("XObject"); + if (!(xObjects instanceof Dict)) { + continue; + } + for (let xObject of xObjects.getRawValues()) { + if (xObject instanceof Ref) { + if (processed.has(xObject)) { + // The XObject has already been processed, and by avoiding a + // redundant `xref.fetch` we can *significantly* reduce the load + // time for badly generated PDF files (fixes issue6961.pdf). + continue; + } + try { + xObject = xref.fetch(xObject); + } catch (ex) { + // Avoid parsing a corrupt XObject more than once. + processed.put(xObject); + + info(`hasBlendModes - ignoring XObject: "${ex}".`); + continue; + } + } + if (!(xObject instanceof BaseStream)) { + continue; + } + if (xObject.dict.objId) { + processed.put(xObject.dict.objId); + } + const xResources = xObject.dict.get("Resources"); + if (!(xResources instanceof Dict)) { + continue; + } + // Checking objId to detect an infinite loop. + if (xResources.objId && processed.has(xResources.objId)) { + continue; + } + + nodes.push(xResources); + if (xResources.objId) { + processed.put(xResources.objId); + } + } + } + + // When no blend modes exist, there's no need re-fetch/re-parse any of the + // processed `Ref`s again for subsequent pages. This helps reduce redundant + // `XRef.fetch` calls for some documents (e.g. issue6961.pdf). + for (const ref of processed) { + nonBlendModesSet.put(ref); + } + return false; + } + + async fetchBuiltInCMap(name) { + const cachedData = this.builtInCMapCache.get(name); + if (cachedData) { + return cachedData; + } + let data; + + if (this.options.cMapUrl !== null) { + // Only compressed CMaps are (currently) supported here. + data = { + cMapData: await fetchBinaryData(`${this.options.cMapUrl}${name}.bcmap`), + isCompressed: true, + }; + } else { + // Get the data on the main-thread instead. + data = await this.handler.sendWithPromise("FetchBinaryData", { + type: "cMapReaderFactory", + name, + }); + } + // Cache the CMap data, to avoid fetching it repeatedly. + this.builtInCMapCache.set(name, data); + + return data; + } + + async fetchStandardFontData(name) { + const cachedData = this.standardFontDataCache.get(name); + if (cachedData) { + return new Stream(cachedData); + } + + // The symbol fonts are not consistent across platforms, always load the + // standard font data for them. + if ( + this.options.useSystemFonts && + name !== "Symbol" && + name !== "ZapfDingbats" + ) { + return null; + } + + const standardFontNameToFileName = getFontNameToFileMap(), + filename = standardFontNameToFileName[name]; + let data; + + try { + if (this.options.standardFontDataUrl !== null) { + data = await fetchBinaryData( + `${this.options.standardFontDataUrl}${filename}` + ); + } else { + // Get the data on the main-thread instead. + data = await this.handler.sendWithPromise("FetchBinaryData", { + type: "standardFontDataFactory", + filename, + }); + } + } catch (ex) { + warn(ex); + return null; + } + // Cache the "raw" standard font data, to avoid fetching it repeatedly + // (see e.g. issue 11399). + this.standardFontDataCache.set(name, data); + + return new Stream(data); + } + + async buildFormXObject( + resources, + xobj, + smask, + operatorList, + task, + initialState, + localColorSpaceCache + ) { + const dict = xobj.dict; + const matrix = lookupMatrix(dict.getArray("Matrix"), null); + const bbox = lookupNormalRect(dict.getArray("BBox"), null); + + let optionalContent, groupOptions; + if (dict.has("OC")) { + optionalContent = await this.parseMarkedContentProps( + dict.get("OC"), + resources + ); + } + if (optionalContent !== undefined) { + operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); + } + const group = dict.get("Group"); + if (group) { + groupOptions = { + matrix, + bbox, + smask, + isolated: false, + knockout: false, + }; + + const groupSubtype = group.get("S"); + let colorSpace = null; + if (isName(groupSubtype, "Transparency")) { + groupOptions.isolated = group.get("I") || false; + groupOptions.knockout = group.get("K") || false; + if (group.has("CS")) { + const cs = group.getRaw("CS"); + + const cachedColorSpace = ColorSpace.getCached( + cs, + this.xref, + localColorSpaceCache + ); + if (cachedColorSpace) { + colorSpace = cachedColorSpace; + } else { + colorSpace = await this.parseColorSpace({ + cs, + resources, + localColorSpaceCache, + }); + } + } + } + + if (smask?.backdrop) { + colorSpace ||= ColorSpace.singletons.rgb; + smask.backdrop = colorSpace.getRgb(smask.backdrop, 0); + } + + operatorList.addOp(OPS.beginGroup, [groupOptions]); + } + + // If it's a group, a new canvas will be created that is the size of the + // bounding box and translated to the correct position so we don't need to + // apply the bounding box to it. + const args = group ? [matrix, null] : [matrix, bbox]; + operatorList.addOp(OPS.paintFormXObjectBegin, args); + + await this.getOperatorList({ + stream: xobj, + task, + resources: dict.get("Resources") || resources, + operatorList, + initialState, + }); + operatorList.addOp(OPS.paintFormXObjectEnd, []); + + if (group) { + operatorList.addOp(OPS.endGroup, [groupOptions]); + } + + if (optionalContent !== undefined) { + operatorList.addOp(OPS.endMarkedContent, []); + } + } + + _sendImgData(objId, imgData, cacheGlobally = false) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + imgData + ) { + assert(Number.isInteger(imgData.dataLen), "Expected dataLen to be set."); + } + const transfers = imgData ? [imgData.bitmap || imgData.data.buffer] : null; + + if (this.parsingType3Font || cacheGlobally) { + return this.handler.send( + "commonobj", + [objId, "Image", imgData], + transfers + ); + } + return this.handler.send( + "obj", + [objId, this.pageIndex, "Image", imgData], + transfers + ); + } + + async buildPaintImageXObject({ + resources, + image, + isInline = false, + operatorList, + cacheKey, + localImageCache, + localColorSpaceCache, + }) { + const dict = image.dict; + const imageRef = dict.objId; + const w = dict.get("W", "Width"); + const h = dict.get("H", "Height"); + + if (!(w && typeof w === "number") || !(h && typeof h === "number")) { + warn("Image dimensions are missing, or not numbers."); + return; + } + const maxImageSize = this.options.maxImageSize; + if (maxImageSize !== -1 && w * h > maxImageSize) { + const msg = "Image exceeded maximum allowed size and was removed."; + + if (this.options.ignoreErrors) { + warn(msg); + return; + } + throw new Error(msg); + } + + let optionalContent; + if (dict.has("OC")) { + optionalContent = await this.parseMarkedContentProps( + dict.get("OC"), + resources + ); + } + + const imageMask = dict.get("IM", "ImageMask") || false; + let imgData, args; + if (imageMask) { + // This depends on a tmpCanvas being filled with the + // current fillStyle, such that processing the pixel + // data can't be done here. Instead of creating a + // complete PDFImage, only read the information needed + // for later. + const interpolate = dict.get("I", "Interpolate"); + const bitStrideLength = (w + 7) >> 3; + const imgArray = image.getBytes(bitStrideLength * h); + const decode = dict.getArray("D", "Decode"); + + if (this.parsingType3Font) { + imgData = PDFImage.createRawMask({ + imgArray, + width: w, + height: h, + imageIsFromDecodeStream: image instanceof DecodeStream, + inverseDecode: decode?.[0] > 0, + interpolate, + }); + + imgData.cached = !!cacheKey; + args = [imgData]; + + operatorList.addImageOps( + OPS.paintImageMaskXObject, + args, + optionalContent + ); + + if (cacheKey) { + const cacheData = { + fn: OPS.paintImageMaskXObject, + args, + optionalContent, + }; + localImageCache.set(cacheKey, imageRef, cacheData); + + if (imageRef) { + this._regionalImageCache.set( + /* name = */ null, + imageRef, + cacheData + ); + } + } + return; + } + + imgData = await PDFImage.createMask({ + imgArray, + width: w, + height: h, + imageIsFromDecodeStream: image instanceof DecodeStream, + inverseDecode: decode?.[0] > 0, + interpolate, + isOffscreenCanvasSupported: this.options.isOffscreenCanvasSupported, + }); + + if (imgData.isSingleOpaquePixel) { + // Handles special case of mainly LaTeX documents which use image + // masks to draw lines with the current fill style. + operatorList.addImageOps( + OPS.paintSolidColorImageMask, + [], + optionalContent + ); + + if (cacheKey) { + const cacheData = { + fn: OPS.paintSolidColorImageMask, + args: [], + optionalContent, + }; + localImageCache.set(cacheKey, imageRef, cacheData); + + if (imageRef) { + this._regionalImageCache.set( + /* name = */ null, + imageRef, + cacheData + ); + } + } + return; + } + + const objId = `mask_${this.idFactory.createObjId()}`; + operatorList.addDependency(objId); + + imgData.dataLen = imgData.bitmap + ? imgData.width * imgData.height * 4 + : imgData.data.length; + this._sendImgData(objId, imgData); + + args = [ + { + data: objId, + width: imgData.width, + height: imgData.height, + interpolate: imgData.interpolate, + count: 1, + }, + ]; + operatorList.addImageOps( + OPS.paintImageMaskXObject, + args, + optionalContent + ); + + if (cacheKey) { + const cacheData = { + objId, + fn: OPS.paintImageMaskXObject, + args, + optionalContent, + }; + localImageCache.set(cacheKey, imageRef, cacheData); + + if (imageRef) { + this._regionalImageCache.set(/* name = */ null, imageRef, cacheData); + } + } + return; + } + + const SMALL_IMAGE_DIMENSIONS = 200; + const hasMask = dict.has("SMask") || dict.has("Mask"); + // Inlining small images into the queue as RGB data + if (isInline && w + h < SMALL_IMAGE_DIMENSIONS && !hasMask) { + try { + const imageObj = new PDFImage({ + xref: this.xref, + res: resources, + image, + isInline, + pdfFunctionFactory: this._pdfFunctionFactory, + localColorSpaceCache, + }); + // We force the use of RGBA_32BPP images here, because we can't handle + // any other kind. + imgData = await imageObj.createImageData( + /* forceRGBA = */ true, + /* isOffscreenCanvasSupported = */ false + ); + operatorList.isOffscreenCanvasSupported = + this.options.isOffscreenCanvasSupported; + operatorList.addImageOps( + OPS.paintInlineImageXObject, + [imgData], + optionalContent + ); + } catch (reason) { + const msg = `Unable to decode inline image: "${reason}".`; + + if (!this.options.ignoreErrors) { + throw new Error(msg); + } + warn(msg); + } + return; + } + + // If there is no imageMask, create the PDFImage and a lot + // of image processing can be done here. + let objId = `img_${this.idFactory.createObjId()}`, + cacheGlobally = false; + + if (this.parsingType3Font) { + objId = `${this.idFactory.getDocId()}_type3_${objId}`; + } else if (cacheKey && imageRef) { + cacheGlobally = this.globalImageCache.shouldCache( + imageRef, + this.pageIndex + ); + + if (cacheGlobally) { + assert(!isInline, "Cannot cache an inline image globally."); + + objId = `${this.idFactory.getDocId()}_${objId}`; + } + } + + // Ensure that the dependency is added before the image is decoded. + operatorList.addDependency(objId); + args = [objId, w, h]; + operatorList.addImageOps( + OPS.paintImageXObject, + args, + optionalContent, + hasMask + ); + + if (cacheGlobally) { + if (this.globalImageCache.hasDecodeFailed(imageRef)) { + this.globalImageCache.setData(imageRef, { + objId, + fn: OPS.paintImageXObject, + args, + optionalContent, + hasMask, + byteSize: 0, // Data is `null`, since decoding failed previously. + }); + + this._sendImgData(objId, /* imgData = */ null, cacheGlobally); + return; + } + + // For large (at least 500x500) or more complex images that we'll cache + // globally, check if the image is still cached locally on the main-thread + // to avoid having to re-parse the image (since that can be slow). + if (w * h > 250000 || hasMask) { + const localLength = await this.handler.sendWithPromise("commonobj", [ + objId, + "CopyLocalImage", + { imageRef }, + ]); + + if (localLength) { + this.globalImageCache.setData(imageRef, { + objId, + fn: OPS.paintImageXObject, + args, + optionalContent, + hasMask, + byteSize: 0, // Temporary entry, to avoid `setData` returning early. + }); + this.globalImageCache.addByteSize(imageRef, localLength); + return; + } + } + } + + PDFImage.buildImage({ + xref: this.xref, + res: resources, + image, + isInline, + pdfFunctionFactory: this._pdfFunctionFactory, + localColorSpaceCache, + }) + .then(async imageObj => { + imgData = await imageObj.createImageData( + /* forceRGBA = */ false, + /* isOffscreenCanvasSupported = */ this.options + .isOffscreenCanvasSupported + ); + imgData.dataLen = imgData.bitmap + ? imgData.width * imgData.height * 4 + : imgData.data.length; + imgData.ref = imageRef; + + if (cacheGlobally) { + this.globalImageCache.addByteSize(imageRef, imgData.dataLen); + } + return this._sendImgData(objId, imgData, cacheGlobally); + }) + .catch(reason => { + warn(`Unable to decode image "${objId}": "${reason}".`); + + if (imageRef) { + this.globalImageCache.addDecodeFailed(imageRef); + } + return this._sendImgData(objId, /* imgData = */ null, cacheGlobally); + }); + + if (cacheKey) { + const cacheData = { + objId, + fn: OPS.paintImageXObject, + args, + optionalContent, + hasMask, + }; + localImageCache.set(cacheKey, imageRef, cacheData); + + if (imageRef) { + this._regionalImageCache.set(/* name = */ null, imageRef, cacheData); + + if (cacheGlobally) { + this.globalImageCache.setData(imageRef, { + objId, + fn: OPS.paintImageXObject, + args, + optionalContent, + hasMask, + byteSize: 0, // Temporary entry, note `addByteSize` above. + }); + } + } + } + } + + handleSMask( + smask, + resources, + operatorList, + task, + stateManager, + localColorSpaceCache + ) { + const smaskContent = smask.get("G"); + const smaskOptions = { + subtype: smask.get("S").name, + backdrop: smask.get("BC"), + }; + + // The SMask might have a alpha/luminosity value transfer function -- + // we will build a map of integer values in range 0..255 to be fast. + const transferObj = smask.get("TR"); + if (isPDFFunction(transferObj)) { + const transferFn = this._pdfFunctionFactory.create(transferObj); + const transferMap = new Uint8Array(256); + const tmp = new Float32Array(1); + for (let i = 0; i < 256; i++) { + tmp[0] = i / 255; + transferFn(tmp, 0, tmp, 0); + transferMap[i] = (tmp[0] * 255) | 0; + } + smaskOptions.transferMap = transferMap; + } + + return this.buildFormXObject( + resources, + smaskContent, + smaskOptions, + operatorList, + task, + stateManager.state.clone(), + localColorSpaceCache + ); + } + + handleTransferFunction(tr) { + let transferArray; + if (Array.isArray(tr)) { + transferArray = tr; + } else if (isPDFFunction(tr)) { + transferArray = [tr]; + } else { + return null; // Not a valid transfer function entry. + } + + const transferMaps = []; + let numFns = 0, + numEffectfulFns = 0; + for (const entry of transferArray) { + const transferObj = this.xref.fetchIfRef(entry); + numFns++; + + if (isName(transferObj, "Identity")) { + transferMaps.push(null); + continue; + } else if (!isPDFFunction(transferObj)) { + return null; // Not a valid transfer function object. + } + + const transferFn = this._pdfFunctionFactory.create(transferObj); + const transferMap = new Uint8Array(256), + tmp = new Float32Array(1); + for (let j = 0; j < 256; j++) { + tmp[0] = j / 255; + transferFn(tmp, 0, tmp, 0); + transferMap[j] = (tmp[0] * 255) | 0; + } + transferMaps.push(transferMap); + numEffectfulFns++; + } + + if (!(numFns === 1 || numFns === 4)) { + return null; // Only 1 or 4 functions are supported, by the specification. + } + if (numEffectfulFns === 0) { + return null; // Only /Identity transfer functions found, which are no-ops. + } + return transferMaps; + } + + handleTilingType( + fn, + color, + resources, + pattern, + patternDict, + operatorList, + task, + localTilingPatternCache + ) { + // Create an IR of the pattern code. + const tilingOpList = new OperatorList(); + // Merge the available resources, to prevent issues when the patternDict + // is missing some /Resources entries (fixes issue6541.pdf). + const patternResources = Dict.merge({ + xref: this.xref, + dictArray: [patternDict.get("Resources"), resources], + }); + + return this.getOperatorList({ + stream: pattern, + task, + resources: patternResources, + operatorList: tilingOpList, + }) + .then(function () { + const operatorListIR = tilingOpList.getIR(); + const tilingPatternIR = getTilingPatternIR( + operatorListIR, + patternDict, + color + ); + // Add the dependencies to the parent operator list so they are + // resolved before the sub operator list is executed synchronously. + operatorList.addDependencies(tilingOpList.dependencies); + operatorList.addOp(fn, tilingPatternIR); + + if (patternDict.objId) { + localTilingPatternCache.set(/* name = */ null, patternDict.objId, { + operatorListIR, + dict: patternDict, + }); + } + }) + .catch(reason => { + if (reason instanceof AbortException) { + return; + } + if (this.options.ignoreErrors) { + warn(`handleTilingType - ignoring pattern: "${reason}".`); + return; + } + throw reason; + }); + } + + async handleSetFont( + resources, + fontArgs, + fontRef, + operatorList, + task, + state, + fallbackFontDict = null, + cssFontInfo = null + ) { + const fontName = fontArgs?.[0] instanceof Name ? fontArgs[0].name : null; + + let translated = await this.loadFont( + fontName, + fontRef, + resources, + fallbackFontDict, + cssFontInfo + ); + + if (translated.font.isType3Font) { + try { + await translated.loadType3Data(this, resources, task); + // Add the dependencies to the parent operatorList so they are + // resolved before Type3 operatorLists are executed synchronously. + operatorList.addDependencies(translated.type3Dependencies); + } catch (reason) { + translated = new TranslatedFont({ + loadedName: "g_font_error", + font: new ErrorFont(`Type3 font load error: ${reason}`), + dict: translated.font, + evaluatorOptions: this.options, + }); + } + } + + state.font = translated.font; + translated.send(this.handler); + return translated.loadedName; + } + + handleText(chars, state) { + const font = state.font; + const glyphs = font.charsToGlyphs(chars); + + if (font.data) { + const isAddToPathSet = !!( + state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG + ); + if ( + isAddToPathSet || + state.fillColorSpace.name === "Pattern" || + font.disableFontFace || + this.options.disableFontFace + ) { + PartialEvaluator.buildFontPaths( + font, + glyphs, + this.handler, + this.options + ); + } + } + return glyphs; + } + + ensureStateFont(state) { + if (state.font) { + return; + } + const reason = new FormatError( + "Missing setFont (Tf) operator before text rendering operator." + ); + + if (this.options.ignoreErrors) { + warn(`ensureStateFont: "${reason}".`); + return; + } + throw reason; + } + + async setGState({ + resources, + gState, + operatorList, + cacheKey, + task, + stateManager, + localGStateCache, + localColorSpaceCache, + }) { + const gStateRef = gState.objId; + let isSimpleGState = true; + // This array holds the converted/processed state data. + const gStateObj = []; + let promise = Promise.resolve(); + for (const [key, value] of gState) { + switch (key) { + case "Type": + break; + case "LW": + case "LC": + case "LJ": + case "ML": + case "D": + case "RI": + case "FL": + case "CA": + case "ca": + gStateObj.push([key, value]); + break; + case "Font": + isSimpleGState = false; + + promise = promise.then(() => + this.handleSetFont( + resources, + null, + value[0], + operatorList, + task, + stateManager.state + ).then(function (loadedName) { + operatorList.addDependency(loadedName); + gStateObj.push([key, [loadedName, value[1]]]); + }) + ); + break; + case "BM": + gStateObj.push([key, normalizeBlendMode(value)]); + break; + case "SMask": + if (isName(value, "None")) { + gStateObj.push([key, false]); + break; + } + if (value instanceof Dict) { + isSimpleGState = false; + + promise = promise.then(() => + this.handleSMask( + value, + resources, + operatorList, + task, + stateManager, + localColorSpaceCache + ) + ); + gStateObj.push([key, true]); + } else { + warn("Unsupported SMask type"); + } + break; + case "TR": + const transferMaps = this.handleTransferFunction(value); + gStateObj.push([key, transferMaps]); + break; + // Only generate info log messages for the following since + // they are unlikely to have a big impact on the rendering. + case "OP": + case "op": + case "OPM": + case "BG": + case "BG2": + case "UCR": + case "UCR2": + case "TR2": + case "HT": + case "SM": + case "SA": + case "AIS": + case "TK": + // TODO implement these operators. + info("graphic state operator " + key); + break; + default: + info("Unknown graphic state operator " + key); + break; + } + } + await promise; + + if (gStateObj.length > 0) { + operatorList.addOp(OPS.setGState, [gStateObj]); + } + + if (isSimpleGState) { + localGStateCache.set(cacheKey, gStateRef, gStateObj); + } + } + + loadFont( + fontName, + font, + resources, + fallbackFontDict = null, + cssFontInfo = null + ) { + const errorFont = async () => + new TranslatedFont({ + loadedName: "g_font_error", + font: new ErrorFont(`Font "${fontName}" is not available.`), + dict: font, + evaluatorOptions: this.options, + }); + + let fontRef; + if (font) { + // Loading by ref. + if (font instanceof Ref) { + fontRef = font; + } + } else { + // Loading by name. + const fontRes = resources.get("Font"); + if (fontRes) { + fontRef = fontRes.getRaw(fontName); + } + } + if (fontRef) { + if (this.type3FontRefs?.has(fontRef)) { + return errorFont(); + } + + if (this.fontCache.has(fontRef)) { + return this.fontCache.get(fontRef); + } + + try { + font = this.xref.fetchIfRef(fontRef); + } catch (ex) { + warn(`loadFont - lookup failed: "${ex}".`); + } + } + + if (!(font instanceof Dict)) { + if (!this.options.ignoreErrors && !this.parsingType3Font) { + warn(`Font "${fontName}" is not available.`); + return errorFont(); + } + warn( + `Font "${fontName}" is not available -- attempting to fallback to a default font.` + ); + + // Falling back to a default font to avoid completely broken rendering, + // but note that there're no guarantees that things will look "correct". + font = fallbackFontDict || PartialEvaluator.fallbackFontDict; + } + + // We are holding `font.cacheKey` references only for `fontRef`s that + // are not actually `Ref`s, but rather `Dict`s. See explanation below. + if (font.cacheKey && this.fontCache.has(font.cacheKey)) { + return this.fontCache.get(font.cacheKey); + } + + const { promise, resolve } = Promise.withResolvers(); + + let preEvaluatedFont; + try { + preEvaluatedFont = this.preEvaluateFont(font); + preEvaluatedFont.cssFontInfo = cssFontInfo; + } catch (reason) { + warn(`loadFont - preEvaluateFont failed: "${reason}".`); + return errorFont(); + } + const { descriptor, hash } = preEvaluatedFont; + + const fontRefIsRef = fontRef instanceof Ref; + let fontID; + + if (hash && descriptor instanceof Dict) { + const fontAliases = (descriptor.fontAliases ||= Object.create(null)); + + if (fontAliases[hash]) { + const aliasFontRef = fontAliases[hash].aliasRef; + if (fontRefIsRef && aliasFontRef && this.fontCache.has(aliasFontRef)) { + this.fontCache.putAlias(fontRef, aliasFontRef); + return this.fontCache.get(fontRef); + } + } else { + fontAliases[hash] = { + fontID: this.idFactory.createFontId(), + }; + } + + if (fontRefIsRef) { + fontAliases[hash].aliasRef = fontRef; + } + fontID = fontAliases[hash].fontID; + } else { + fontID = this.idFactory.createFontId(); + } + assert( + fontID?.startsWith("f"), + 'The "fontID" must be (correctly) defined.' + ); + + // Workaround for bad PDF generators that reference fonts incorrectly, + // where `fontRef` is a `Dict` rather than a `Ref` (fixes bug946506.pdf). + // In this case we cannot put the font into `this.fontCache` (which is + // a `RefSetCache`), since it's not possible to use a `Dict` as a key. + // + // However, if we don't cache the font it's not possible to remove it + // when `cleanup` is triggered from the API, which causes issues on + // subsequent rendering operations (see issue7403.pdf) and would force us + // to unnecessarily load the same fonts over and over. + // + // Instead, we cheat a bit by using a modified `fontID` as a key in + // `this.fontCache`, to allow the font to be cached. + // NOTE: This works because `RefSetCache` calls `toString()` on provided + // keys. Also, since `fontRef` is used when getting cached fonts, + // we'll not accidentally match fonts cached with the `fontID`. + if (fontRefIsRef) { + this.fontCache.put(fontRef, promise); + } else { + font.cacheKey = `cacheKey_${fontID}`; + this.fontCache.put(font.cacheKey, promise); + } + + // Keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page. + font.loadedName = `${this.idFactory.getDocId()}_${fontID}`; + + this.translateFont(preEvaluatedFont) + .then(translatedFont => { + resolve( + new TranslatedFont({ + loadedName: font.loadedName, + font: translatedFont, + dict: font, + evaluatorOptions: this.options, + }) + ); + }) + .catch(reason => { + // TODO reject? + warn(`loadFont - translateFont failed: "${reason}".`); + + resolve( + new TranslatedFont({ + loadedName: font.loadedName, + font: new ErrorFont( + reason instanceof Error ? reason.message : reason + ), + dict: font, + evaluatorOptions: this.options, + }) + ); + }); + return promise; + } + + buildPath(operatorList, fn, args, parsingText = false) { + const lastIndex = operatorList.length - 1; + if (!args) { + args = []; + } + if ( + lastIndex < 0 || + operatorList.fnArray[lastIndex] !== OPS.constructPath + ) { + // Handle corrupt PDF documents that contains path operators inside of + // text objects, which may shift subsequent text, by enclosing the path + // operator in save/restore operators (fixes issue10542_reduced.pdf). + // + // Note that this will effectively disable the optimization in the + // `else` branch below, but given that this type of corruption is + // *extremely* rare that shouldn't really matter much in practice. + if (parsingText) { + warn(`Encountered path operator "${fn}" inside of a text object.`); + operatorList.addOp(OPS.save, null); + } + + let minMax; + switch (fn) { + case OPS.rectangle: + const x = args[0] + args[2]; + const y = args[1] + args[3]; + minMax = [ + Math.min(args[0], x), + Math.min(args[1], y), + Math.max(args[0], x), + Math.max(args[1], y), + ]; + break; + case OPS.moveTo: + case OPS.lineTo: + minMax = [args[0], args[1], args[0], args[1]]; + break; + default: + minMax = [Infinity, Infinity, -Infinity, -Infinity]; + break; + } + operatorList.addOp(OPS.constructPath, [[fn], args, minMax]); + + if (parsingText) { + operatorList.addOp(OPS.restore, null); + } + } else { + const opArgs = operatorList.argsArray[lastIndex]; + opArgs[0].push(fn); + opArgs[1].push(...args); + const minMax = opArgs[2]; + + // Compute min/max in the worker instead of the main thread. + // If the current matrix (when drawing) is a scaling one + // then min/max can be easily computed in using those values. + // Only rectangle, lineTo and moveTo are handled here since + // Bezier stuff requires to have the starting point. + switch (fn) { + case OPS.rectangle: + const x = args[0] + args[2]; + const y = args[1] + args[3]; + minMax[0] = Math.min(minMax[0], args[0], x); + minMax[1] = Math.min(minMax[1], args[1], y); + minMax[2] = Math.max(minMax[2], args[0], x); + minMax[3] = Math.max(minMax[3], args[1], y); + break; + case OPS.moveTo: + case OPS.lineTo: + minMax[0] = Math.min(minMax[0], args[0]); + minMax[1] = Math.min(minMax[1], args[1]); + minMax[2] = Math.max(minMax[2], args[0]); + minMax[3] = Math.max(minMax[3], args[1]); + break; + } + } + } + + parseColorSpace({ cs, resources, localColorSpaceCache }) { + return ColorSpace.parseAsync({ + cs, + xref: this.xref, + resources, + pdfFunctionFactory: this._pdfFunctionFactory, + localColorSpaceCache, + }).catch(reason => { + if (reason instanceof AbortException) { + return null; + } + if (this.options.ignoreErrors) { + warn(`parseColorSpace - ignoring ColorSpace: "${reason}".`); + return null; + } + throw reason; + }); + } + + parseShading({ + shading, + resources, + localColorSpaceCache, + localShadingPatternCache, + }) { + // Shadings and patterns may be referenced by the same name but the resource + // dictionary could be different so we can't use the name for the cache key. + let id = localShadingPatternCache.get(shading); + if (id) { + return id; + } + let patternIR; + + try { + const shadingFill = Pattern.parseShading( + shading, + this.xref, + resources, + this._pdfFunctionFactory, + localColorSpaceCache + ); + patternIR = shadingFill.getIR(); + } catch (reason) { + if (reason instanceof AbortException) { + return null; + } + if (this.options.ignoreErrors) { + warn(`parseShading - ignoring shading: "${reason}".`); + + localShadingPatternCache.set(shading, null); + return null; + } + throw reason; + } + + id = `pattern_${this.idFactory.createObjId()}`; + if (this.parsingType3Font) { + id = `${this.idFactory.getDocId()}_type3_${id}`; + } + localShadingPatternCache.set(shading, id); + + if (this.parsingType3Font) { + this.handler.send("commonobj", [id, "Pattern", patternIR]); + } else { + this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]); + } + return id; + } + + handleColorN( + operatorList, + fn, + args, + cs, + patterns, + resources, + task, + localColorSpaceCache, + localTilingPatternCache, + localShadingPatternCache + ) { + // compile tiling patterns + const patternName = args.pop(); + // SCN/scn applies patterns along with normal colors + if (patternName instanceof Name) { + const rawPattern = patterns.getRaw(patternName.name); + + const localTilingPattern = + rawPattern instanceof Ref && + localTilingPatternCache.getByRef(rawPattern); + if (localTilingPattern) { + try { + const color = cs.base ? cs.base.getRgb(args, 0) : null; + const tilingPatternIR = getTilingPatternIR( + localTilingPattern.operatorListIR, + localTilingPattern.dict, + color + ); + operatorList.addOp(fn, tilingPatternIR); + return undefined; + } catch { + // Handle any errors during normal TilingPattern parsing. + } + } + + const pattern = this.xref.fetchIfRef(rawPattern); + if (pattern) { + const dict = pattern instanceof BaseStream ? pattern.dict : pattern; + const typeNum = dict.get("PatternType"); + + if (typeNum === PatternType.TILING) { + const color = cs.base ? cs.base.getRgb(args, 0) : null; + return this.handleTilingType( + fn, + color, + resources, + pattern, + dict, + operatorList, + task, + localTilingPatternCache + ); + } else if (typeNum === PatternType.SHADING) { + const shading = dict.get("Shading"); + const objId = this.parseShading({ + shading, + resources, + localColorSpaceCache, + localShadingPatternCache, + }); + if (objId) { + const matrix = lookupMatrix(dict.getArray("Matrix"), null); + operatorList.addOp(fn, ["Shading", objId, matrix]); + } + return undefined; + } + throw new FormatError(`Unknown PatternType: ${typeNum}`); + } + } + throw new FormatError(`Unknown PatternName: ${patternName}`); + } + + _parseVisibilityExpression(array, nestingCounter, currentResult) { + const MAX_NESTING = 10; + if (++nestingCounter > MAX_NESTING) { + warn("Visibility expression is too deeply nested"); + return; + } + const length = array.length; + const operator = this.xref.fetchIfRef(array[0]); + if (length < 2 || !(operator instanceof Name)) { + warn("Invalid visibility expression"); + return; + } + switch (operator.name) { + case "And": + case "Or": + case "Not": + currentResult.push(operator.name); + break; + default: + warn(`Invalid operator ${operator.name} in visibility expression`); + return; + } + for (let i = 1; i < length; i++) { + const raw = array[i]; + const object = this.xref.fetchIfRef(raw); + if (Array.isArray(object)) { + const nestedResult = []; + currentResult.push(nestedResult); + // Recursively parse a subarray. + this._parseVisibilityExpression(object, nestingCounter, nestedResult); + } else if (raw instanceof Ref) { + // Reference to an OCG dictionary. + currentResult.push(raw.toString()); + } + } + } + + async parseMarkedContentProps(contentProperties, resources) { + let optionalContent; + if (contentProperties instanceof Name) { + const properties = resources.get("Properties"); + optionalContent = properties.get(contentProperties.name); + } else if (contentProperties instanceof Dict) { + optionalContent = contentProperties; + } else { + throw new FormatError("Optional content properties malformed."); + } + + const optionalContentType = optionalContent.get("Type")?.name; + if (optionalContentType === "OCG") { + return { + type: optionalContentType, + id: optionalContent.objId, + }; + } else if (optionalContentType === "OCMD") { + const expression = optionalContent.get("VE"); + if (Array.isArray(expression)) { + const result = []; + this._parseVisibilityExpression(expression, 0, result); + if (result.length > 0) { + return { + type: "OCMD", + expression: result, + }; + } + } + + const optionalContentGroups = optionalContent.get("OCGs"); + if ( + Array.isArray(optionalContentGroups) || + optionalContentGroups instanceof Dict + ) { + const groupIds = []; + if (Array.isArray(optionalContentGroups)) { + for (const ocg of optionalContentGroups) { + groupIds.push(ocg.toString()); + } + } else { + // Dictionary, just use the obj id. + groupIds.push(optionalContentGroups.objId); + } + + return { + type: optionalContentType, + ids: groupIds, + policy: + optionalContent.get("P") instanceof Name + ? optionalContent.get("P").name + : null, + expression: null, + }; + } else if (optionalContentGroups instanceof Ref) { + return { + type: optionalContentType, + id: optionalContentGroups.toString(), + }; + } + } + return null; + } + + getOperatorList({ + stream, + task, + resources, + operatorList, + initialState = null, + fallbackFontDict = null, + }) { + // Ensure that `resources`/`initialState` is correctly initialized, + // even if the provided parameter is e.g. `null`. + resources ||= Dict.empty; + initialState ||= new EvalState(); + + if (!operatorList) { + throw new Error('getOperatorList: missing "operatorList" parameter'); + } + + const self = this; + const xref = this.xref; + let parsingText = false; + const localImageCache = new LocalImageCache(); + const localColorSpaceCache = new LocalColorSpaceCache(); + const localGStateCache = new LocalGStateCache(); + const localTilingPatternCache = new LocalTilingPatternCache(); + const localShadingPatternCache = new Map(); + + const xobjs = resources.get("XObject") || Dict.empty; + const patterns = resources.get("Pattern") || Dict.empty; + const stateManager = new StateManager(initialState); + const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); + const timeSlotManager = new TimeSlotManager(); + + function closePendingRestoreOPS(argument) { + for (let i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { + operatorList.addOp(OPS.restore, []); + } + } + + return new Promise(function promiseBody(resolve, reject) { + const next = function (promise) { + Promise.all([promise, operatorList.ready]).then(function () { + try { + promiseBody(resolve, reject); + } catch (ex) { + reject(ex); + } + }, reject); + }; + task.ensureNotTerminated(); + timeSlotManager.reset(); + + const operation = {}; + let stop, i, ii, cs, name, isValidName; + while (!(stop = timeSlotManager.check())) { + // The arguments parsed by read() are used beyond this loop, so we + // cannot reuse the same array on each iteration. Therefore we pass + // in |null| as the initial value (see the comment on + // EvaluatorPreprocessor_read() for why). + operation.args = null; + if (!preprocessor.read(operation)) { + break; + } + let args = operation.args; + let fn = operation.fn; + + switch (fn | 0) { + case OPS.paintXObject: + // eagerly compile XForm objects + isValidName = args[0] instanceof Name; + name = args[0].name; + + if (isValidName) { + const localImage = localImageCache.getByName(name); + if (localImage) { + addLocallyCachedImageOps(operatorList, localImage); + args = null; + continue; + } + } + + next( + new Promise(function (resolveXObject, rejectXObject) { + if (!isValidName) { + throw new FormatError("XObject must be referred to by name."); + } + + let xobj = xobjs.getRaw(name); + if (xobj instanceof Ref) { + const localImage = + localImageCache.getByRef(xobj) || + self._regionalImageCache.getByRef(xobj); + if (localImage) { + addLocallyCachedImageOps(operatorList, localImage); + resolveXObject(); + return; + } + + const globalImage = self.globalImageCache.getData( + xobj, + self.pageIndex + ); + if (globalImage) { + operatorList.addDependency(globalImage.objId); + operatorList.addImageOps( + globalImage.fn, + globalImage.args, + globalImage.optionalContent, + globalImage.hasMask + ); + + resolveXObject(); + return; + } + + xobj = xref.fetch(xobj); + } + + if (!(xobj instanceof BaseStream)) { + throw new FormatError("XObject should be a stream"); + } + + const type = xobj.dict.get("Subtype"); + if (!(type instanceof Name)) { + throw new FormatError("XObject should have a Name subtype"); + } + + if (type.name === "Form") { + stateManager.save(); + self + .buildFormXObject( + resources, + xobj, + null, + operatorList, + task, + stateManager.state.clone(), + localColorSpaceCache + ) + .then(function () { + stateManager.restore(); + resolveXObject(); + }, rejectXObject); + return; + } else if (type.name === "Image") { + self + .buildPaintImageXObject({ + resources, + image: xobj, + operatorList, + cacheKey: name, + localImageCache, + localColorSpaceCache, + }) + .then(resolveXObject, rejectXObject); + return; + } else if (type.name === "PS") { + // PostScript XObjects are unused when viewing documents. + // See section 4.7.1 of Adobe's PDF reference. + info("Ignored XObject subtype PS"); + } else { + throw new FormatError( + `Unhandled XObject subtype ${type.name}` + ); + } + resolveXObject(); + }).catch(function (reason) { + if (reason instanceof AbortException) { + return; + } + if (self.options.ignoreErrors) { + warn(`getOperatorList - ignoring XObject: "${reason}".`); + return; + } + throw reason; + }) + ); + return; + case OPS.setFont: + const fontSize = args[1]; + // eagerly collect all fonts + next( + self + .handleSetFont( + resources, + args, + null, + operatorList, + task, + stateManager.state, + fallbackFontDict + ) + .then(function (loadedName) { + operatorList.addDependency(loadedName); + operatorList.addOp(OPS.setFont, [loadedName, fontSize]); + }) + ); + return; + case OPS.beginText: + parsingText = true; + break; + case OPS.endText: + parsingText = false; + break; + case OPS.endInlineImage: + const cacheKey = args[0].cacheKey; + if (cacheKey) { + const localImage = localImageCache.getByName(cacheKey); + if (localImage) { + addLocallyCachedImageOps(operatorList, localImage); + args = null; + continue; + } + } + next( + self.buildPaintImageXObject({ + resources, + image: args[0], + isInline: true, + operatorList, + cacheKey, + localImageCache, + localColorSpaceCache, + }) + ); + return; + case OPS.showText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + args[0] = self.handleText(args[0], stateManager.state); + break; + case OPS.showSpacedText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + const combinedGlyphs = [], + state = stateManager.state; + for (const arrItem of args[0]) { + if (typeof arrItem === "string") { + combinedGlyphs.push(...self.handleText(arrItem, state)); + } else if (typeof arrItem === "number") { + combinedGlyphs.push(arrItem); + } + } + args[0] = combinedGlyphs; + fn = OPS.showText; + break; + case OPS.nextLineShowText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + operatorList.addOp(OPS.nextLine); + args[0] = self.handleText(args[0], stateManager.state); + fn = OPS.showText; + break; + case OPS.nextLineSetSpacingShowText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + operatorList.addOp(OPS.nextLine); + operatorList.addOp(OPS.setWordSpacing, [args.shift()]); + operatorList.addOp(OPS.setCharSpacing, [args.shift()]); + args[0] = self.handleText(args[0], stateManager.state); + fn = OPS.showText; + break; + case OPS.setTextRenderingMode: + stateManager.state.textRenderingMode = args[0]; + break; + + case OPS.setFillColorSpace: { + const cachedColorSpace = ColorSpace.getCached( + args[0], + xref, + localColorSpaceCache + ); + if (cachedColorSpace) { + stateManager.state.fillColorSpace = cachedColorSpace; + continue; + } + + next( + self + .parseColorSpace({ + cs: args[0], + resources, + localColorSpaceCache, + }) + .then(function (colorSpace) { + stateManager.state.fillColorSpace = + colorSpace || ColorSpace.singletons.gray; + }) + ); + return; + } + case OPS.setStrokeColorSpace: { + const cachedColorSpace = ColorSpace.getCached( + args[0], + xref, + localColorSpaceCache + ); + if (cachedColorSpace) { + stateManager.state.strokeColorSpace = cachedColorSpace; + continue; + } + + next( + self + .parseColorSpace({ + cs: args[0], + resources, + localColorSpaceCache, + }) + .then(function (colorSpace) { + stateManager.state.strokeColorSpace = + colorSpace || ColorSpace.singletons.gray; + }) + ); + return; + } + case OPS.setFillColor: + cs = stateManager.state.fillColorSpace; + args = cs.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeColor: + cs = stateManager.state.strokeColorSpace; + args = cs.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + case OPS.setFillGray: + stateManager.state.fillColorSpace = ColorSpace.singletons.gray; + args = ColorSpace.singletons.gray.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeGray: + stateManager.state.strokeColorSpace = ColorSpace.singletons.gray; + args = ColorSpace.singletons.gray.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + case OPS.setFillCMYKColor: + stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk; + args = ColorSpace.singletons.cmyk.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeCMYKColor: + stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk; + args = ColorSpace.singletons.cmyk.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + case OPS.setFillRGBColor: + stateManager.state.fillColorSpace = ColorSpace.singletons.rgb; + args = ColorSpace.singletons.rgb.getRgb(args, 0); + break; + case OPS.setStrokeRGBColor: + stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb; + args = ColorSpace.singletons.rgb.getRgb(args, 0); + break; + case OPS.setFillColorN: + cs = stateManager.state.patternFillColorSpace; + if (!cs) { + if (isNumberArray(args, null)) { + args = ColorSpace.singletons.gray.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + } + args = []; + fn = OPS.setFillTransparent; + break; + } + if (cs.name === "Pattern") { + next( + self.handleColorN( + operatorList, + OPS.setFillColorN, + args, + cs, + patterns, + resources, + task, + localColorSpaceCache, + localTilingPatternCache, + localShadingPatternCache + ) + ); + return; + } + args = cs.getRgb(args, 0); + fn = OPS.setFillRGBColor; + break; + case OPS.setStrokeColorN: + cs = stateManager.state.patternStrokeColorSpace; + if (!cs) { + if (isNumberArray(args, null)) { + args = ColorSpace.singletons.gray.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + } + args = []; + fn = OPS.setStrokeTransparent; + break; + } + if (cs.name === "Pattern") { + next( + self.handleColorN( + operatorList, + OPS.setStrokeColorN, + args, + cs, + patterns, + resources, + task, + localColorSpaceCache, + localTilingPatternCache, + localShadingPatternCache + ) + ); + return; + } + args = cs.getRgb(args, 0); + fn = OPS.setStrokeRGBColor; + break; + + case OPS.shadingFill: + let shading; + try { + const shadingRes = resources.get("Shading"); + if (!shadingRes) { + throw new FormatError("No shading resource found"); + } + + shading = shadingRes.get(args[0].name); + if (!shading) { + throw new FormatError("No shading object found"); + } + } catch (reason) { + if (reason instanceof AbortException) { + continue; + } + if (self.options.ignoreErrors) { + warn(`getOperatorList - ignoring Shading: "${reason}".`); + continue; + } + throw reason; + } + const patternId = self.parseShading({ + shading, + resources, + localColorSpaceCache, + localShadingPatternCache, + }); + if (!patternId) { + continue; + } + args = [patternId]; + fn = OPS.shadingFill; + break; + case OPS.setGState: + isValidName = args[0] instanceof Name; + name = args[0].name; + + if (isValidName) { + const localGStateObj = localGStateCache.getByName(name); + if (localGStateObj) { + if (localGStateObj.length > 0) { + operatorList.addOp(OPS.setGState, [localGStateObj]); + } + args = null; + continue; + } + } + + next( + new Promise(function (resolveGState, rejectGState) { + if (!isValidName) { + throw new FormatError("GState must be referred to by name."); + } + + const extGState = resources.get("ExtGState"); + if (!(extGState instanceof Dict)) { + throw new FormatError("ExtGState should be a dictionary."); + } + + const gState = extGState.get(name); + // TODO: Attempt to lookup cached GStates by reference as well, + // if and only if there are PDF documents where doing so + // would significantly improve performance. + if (!(gState instanceof Dict)) { + throw new FormatError("GState should be a dictionary."); + } + + self + .setGState({ + resources, + gState, + operatorList, + cacheKey: name, + task, + stateManager, + localGStateCache, + localColorSpaceCache, + }) + .then(resolveGState, rejectGState); + }).catch(function (reason) { + if (reason instanceof AbortException) { + return; + } + if (self.options.ignoreErrors) { + warn(`getOperatorList - ignoring ExtGState: "${reason}".`); + return; + } + throw reason; + }) + ); + return; + case OPS.moveTo: + case OPS.lineTo: + case OPS.curveTo: + case OPS.curveTo2: + case OPS.curveTo3: + case OPS.closePath: + case OPS.rectangle: + self.buildPath(operatorList, fn, args, parsingText); + continue; + case OPS.markPoint: + case OPS.markPointProps: + case OPS.beginCompat: + case OPS.endCompat: + // Ignore operators where the corresponding handlers are known to + // be no-op in CanvasGraphics (display/canvas.js). This prevents + // serialization errors and is also a bit more efficient. + // We could also try to serialize all objects in a general way, + // e.g. as done in https://github.com/mozilla/pdf.js/pull/6266, + // but doing so is meaningless without knowing the semantics. + continue; + case OPS.beginMarkedContentProps: + if (!(args[0] instanceof Name)) { + warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`); + operatorList.addOp(OPS.beginMarkedContentProps, ["OC", null]); + continue; + } + if (args[0].name === "OC") { + next( + self + .parseMarkedContentProps(args[1], resources) + .then(data => { + operatorList.addOp(OPS.beginMarkedContentProps, [ + "OC", + data, + ]); + }) + .catch(reason => { + if (reason instanceof AbortException) { + return; + } + if (self.options.ignoreErrors) { + warn( + `getOperatorList - ignoring beginMarkedContentProps: "${reason}".` + ); + operatorList.addOp(OPS.beginMarkedContentProps, [ + "OC", + null, + ]); + return; + } + throw reason; + }) + ); + return; + } + // Other marked content types aren't supported yet. + args = [ + args[0].name, + args[1] instanceof Dict ? args[1].get("MCID") : null, + ]; + + break; + case OPS.beginMarkedContent: + case OPS.endMarkedContent: + default: + // Note: Ignore the operator if it has `Dict` arguments, since + // those are non-serializable, otherwise postMessage will throw + // "An object could not be cloned.". + if (args !== null) { + for (i = 0, ii = args.length; i < ii; i++) { + if (args[i] instanceof Dict) { + break; + } + } + if (i < ii) { + warn("getOperatorList - ignoring operator: " + fn); + continue; + } + } + } + operatorList.addOp(fn, args); + } + if (stop) { + next(deferred); + return; + } + // Some PDFs don't close all restores inside object/form. + // Closing those for them. + closePendingRestoreOPS(); + resolve(); + }).catch(reason => { + if (reason instanceof AbortException) { + return; + } + if (this.options.ignoreErrors) { + warn( + `getOperatorList - ignoring errors during "${task.name}" ` + + `task: "${reason}".` + ); + + closePendingRestoreOPS(); + return; + } + throw reason; + }); + } + + getTextContent({ + stream, + task, + resources, + stateManager = null, + includeMarkedContent = false, + sink, + seenStyles = new Set(), + viewBox, + lang = null, + markedContentData = null, + disableNormalization = false, + keepWhiteSpace = false, + }) { + // Ensure that `resources`/`stateManager` is correctly initialized, + // even if the provided parameter is e.g. `null`. + resources ||= Dict.empty; + stateManager ||= new StateManager(new TextState()); + + if (includeMarkedContent) { + markedContentData ||= { level: 0 }; + } + + const textContent = { + items: [], + styles: Object.create(null), + lang, + }; + const textContentItem = { + initialized: false, + str: [], + totalWidth: 0, + totalHeight: 0, + width: 0, + height: 0, + vertical: false, + prevTransform: null, + textAdvanceScale: 0, + spaceInFlowMin: 0, + spaceInFlowMax: 0, + trackingSpaceMin: Infinity, + negativeSpaceMax: -Infinity, + notASpace: -Infinity, + transform: null, + fontName: null, + hasEOL: false, + }; + + // Use a circular buffer (length === 2) to save the last chars in the + // text stream. + // This implementation of the circular buffer is using a fixed array + // and the position of the next element: + // function addElement(x) { + // buffer[pos] = x; + // pos = (pos + 1) % buffer.length; + // } + // It's a way faster than: + // function addElement(x) { + // buffer.push(x); + // buffer.shift(); + // } + // + // It's useful to know when we need to add a whitespace in the + // text chunk. + const twoLastChars = [" ", " "]; + let twoLastCharsPos = 0; + + /** + * Save the last char. + * @param {string} char + * @returns {boolean} true when the two last chars before adding the new one + * are a non-whitespace followed by a whitespace. + */ + function saveLastChar(char) { + const nextPos = (twoLastCharsPos + 1) % 2; + const ret = + twoLastChars[twoLastCharsPos] !== " " && twoLastChars[nextPos] === " "; + twoLastChars[twoLastCharsPos] = char; + twoLastCharsPos = nextPos; + + return !keepWhiteSpace && ret; + } + + function shouldAddWhitepsace() { + return ( + !keepWhiteSpace && + twoLastChars[twoLastCharsPos] !== " " && + twoLastChars[(twoLastCharsPos + 1) % 2] === " " + ); + } + + function resetLastChars() { + twoLastChars[0] = twoLastChars[1] = " "; + twoLastCharsPos = 0; + } + + // Used in addFakeSpaces. + + // A white <= fontSize * TRACKING_SPACE_FACTOR is a tracking space + // so it doesn't count as a space. + const TRACKING_SPACE_FACTOR = 0.102; + + // When a white <= fontSize * NOT_A_SPACE_FACTOR, there is no space + // even if one is present in the text stream. + const NOT_A_SPACE_FACTOR = 0.03; + + // A negative white < fontSize * NEGATIVE_SPACE_FACTOR induces + // a break (a new chunk of text is created). + // It doesn't change anything when the text is copied but + // it improves potential mismatch between text layer and canvas. + const NEGATIVE_SPACE_FACTOR = -0.2; + + // A white with a width in [fontSize * MIN_FACTOR; fontSize * MAX_FACTOR] + // is a space which will be inserted in the current flow of words. + // If the width is outside of this range then the flow is broken + // (which means a new span in the text layer). + // It's useful to adjust the best as possible the span in the layer + // to what is displayed in the canvas. + const SPACE_IN_FLOW_MIN_FACTOR = 0.102; + const SPACE_IN_FLOW_MAX_FACTOR = 0.6; + + // If a char is too high/too low compared to the previous we just create + // a new chunk. + // If the advance isn't in the +/-VERTICAL_SHIFT_RATIO * height range then + // a new chunk is created. + const VERTICAL_SHIFT_RATIO = 0.25; + + const self = this; + const xref = this.xref; + const showSpacedTextBuffer = []; + + // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd. + let xobjs = null; + const emptyXObjectCache = new LocalImageCache(); + const emptyGStateCache = new LocalGStateCache(); + + const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager); + + let textState; + + function pushWhitespace({ + width = 0, + height = 0, + transform = textContentItem.prevTransform, + fontName = textContentItem.fontName, + }) { + textContent.items.push({ + str: " ", + dir: "ltr", + width, + height, + transform, + fontName, + hasEOL: false, + }); + } + + function getCurrentTextTransform() { + // 9.4.4 Text Space Details + const font = textState.font; + const tsm = [ + textState.fontSize * textState.textHScale, + 0, + 0, + textState.fontSize, + 0, + textState.textRise, + ]; + + if ( + font.isType3Font && + (textState.fontSize <= 1 || font.isCharBBox) && + !isArrayEqual(textState.fontMatrix, FONT_IDENTITY_MATRIX) + ) { + const glyphHeight = font.bbox[3] - font.bbox[1]; + if (glyphHeight > 0) { + tsm[3] *= glyphHeight * textState.fontMatrix[3]; + } + } + + return Util.transform( + textState.ctm, + Util.transform(textState.textMatrix, tsm) + ); + } + + function ensureTextContentItem() { + if (textContentItem.initialized) { + return textContentItem; + } + const { font, loadedName } = textState; + if (!seenStyles.has(loadedName)) { + seenStyles.add(loadedName); + textContent.styles[loadedName] = { + fontFamily: font.fallbackName, + ascent: font.ascent, + descent: font.descent, + vertical: font.vertical, + }; + if (self.options.fontExtraProperties && font.systemFontInfo) { + const style = textContent.styles[loadedName]; + style.fontSubstitution = font.systemFontInfo.css; + style.fontSubstitutionLoadedName = font.systemFontInfo.loadedName; + } + } + textContentItem.fontName = loadedName; + + const trm = (textContentItem.transform = getCurrentTextTransform()); + if (!font.vertical) { + textContentItem.width = textContentItem.totalWidth = 0; + textContentItem.height = textContentItem.totalHeight = Math.hypot( + trm[2], + trm[3] + ); + textContentItem.vertical = false; + } else { + textContentItem.width = textContentItem.totalWidth = Math.hypot( + trm[0], + trm[1] + ); + textContentItem.height = textContentItem.totalHeight = 0; + textContentItem.vertical = true; + } + + const scaleLineX = Math.hypot( + textState.textLineMatrix[0], + textState.textLineMatrix[1] + ); + const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]); + textContentItem.textAdvanceScale = scaleCtmX * scaleLineX; + + const { fontSize } = textState; + textContentItem.trackingSpaceMin = fontSize * TRACKING_SPACE_FACTOR; + textContentItem.notASpace = fontSize * NOT_A_SPACE_FACTOR; + textContentItem.negativeSpaceMax = fontSize * NEGATIVE_SPACE_FACTOR; + textContentItem.spaceInFlowMin = fontSize * SPACE_IN_FLOW_MIN_FACTOR; + textContentItem.spaceInFlowMax = fontSize * SPACE_IN_FLOW_MAX_FACTOR; + textContentItem.hasEOL = false; + + textContentItem.initialized = true; + return textContentItem; + } + + function updateAdvanceScale() { + if (!textContentItem.initialized) { + return; + } + + const scaleLineX = Math.hypot( + textState.textLineMatrix[0], + textState.textLineMatrix[1] + ); + const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]); + const scaleFactor = scaleCtmX * scaleLineX; + if (scaleFactor === textContentItem.textAdvanceScale) { + return; + } + + if (!textContentItem.vertical) { + textContentItem.totalWidth += + textContentItem.width * textContentItem.textAdvanceScale; + textContentItem.width = 0; + } else { + textContentItem.totalHeight += + textContentItem.height * textContentItem.textAdvanceScale; + textContentItem.height = 0; + } + + textContentItem.textAdvanceScale = scaleFactor; + } + + function runBidiTransform(textChunk) { + let text = textChunk.str.join(""); + if (!disableNormalization) { + text = normalizeUnicode(text); + } + const bidiResult = bidi(text, -1, textChunk.vertical); + return { + str: bidiResult.str, + dir: bidiResult.dir, + width: Math.abs(textChunk.totalWidth), + height: Math.abs(textChunk.totalHeight), + transform: textChunk.transform, + fontName: textChunk.fontName, + hasEOL: textChunk.hasEOL, + }; + } + + async function handleSetFont(fontName, fontRef) { + const translated = await self.loadFont(fontName, fontRef, resources); + + if (translated.font.isType3Font) { + try { + await translated.loadType3Data(self, resources, task); + } catch { + // Ignore Type3-parsing errors, since we only use `loadType3Data` + // here to ensure that we'll always obtain a useful /FontBBox. + } + } + + textState.loadedName = translated.loadedName; + textState.font = translated.font; + textState.fontMatrix = translated.font.fontMatrix || FONT_IDENTITY_MATRIX; + } + + function applyInverseRotation(x, y, matrix) { + const scale = Math.hypot(matrix[0], matrix[1]); + return [ + (matrix[0] * x + matrix[1] * y) / scale, + (matrix[2] * x + matrix[3] * y) / scale, + ]; + } + + function compareWithLastPosition(glyphWidth) { + const currentTransform = getCurrentTextTransform(); + let posX = currentTransform[4]; + let posY = currentTransform[5]; + + // Check if the glyph is in the viewbox. + if (textState.font?.vertical) { + if ( + posX < viewBox[0] || + posX > viewBox[2] || + posY + glyphWidth < viewBox[1] || + posY > viewBox[3] + ) { + return false; + } + } else if ( + posX + glyphWidth < viewBox[0] || + posX > viewBox[2] || + posY < viewBox[1] || + posY > viewBox[3] + ) { + return false; + } + + if (!textState.font || !textContentItem.prevTransform) { + return true; + } + + let lastPosX = textContentItem.prevTransform[4]; + let lastPosY = textContentItem.prevTransform[5]; + + if (lastPosX === posX && lastPosY === posY) { + return true; + } + + let rotate = -1; + // Take into account the rotation is the current transform. + if ( + currentTransform[0] && + currentTransform[1] === 0 && + currentTransform[2] === 0 + ) { + rotate = currentTransform[0] > 0 ? 0 : 180; + } else if ( + currentTransform[1] && + currentTransform[0] === 0 && + currentTransform[3] === 0 + ) { + rotate = currentTransform[1] > 0 ? 90 : 270; + } + + switch (rotate) { + case 0: + break; + case 90: + [posX, posY] = [posY, posX]; + [lastPosX, lastPosY] = [lastPosY, lastPosX]; + break; + case 180: + [posX, posY, lastPosX, lastPosY] = [ + -posX, + -posY, + -lastPosX, + -lastPosY, + ]; + break; + case 270: + [posX, posY] = [-posY, -posX]; + [lastPosX, lastPosY] = [-lastPosY, -lastPosX]; + break; + default: + // This is not a 0, 90, 180, 270 rotation so: + // - remove the scale factor from the matrix to get a rotation matrix + // - apply the inverse (which is the transposed) to the positions + // and we can then compare positions of the glyphes to detect + // a whitespace. + [posX, posY] = applyInverseRotation(posX, posY, currentTransform); + [lastPosX, lastPosY] = applyInverseRotation( + lastPosX, + lastPosY, + textContentItem.prevTransform + ); + } + + if (textState.font.vertical) { + const advanceY = (lastPosY - posY) / textContentItem.textAdvanceScale; + const advanceX = posX - lastPosX; + + // When the total height of the current chunk is negative + // then we're writing from bottom to top. + const textOrientation = Math.sign(textContentItem.height); + if (advanceY < textOrientation * textContentItem.negativeSpaceMax) { + if ( + Math.abs(advanceX) > + 0.5 * textContentItem.width /* not the same column */ + ) { + appendEOL(); + return true; + } + + resetLastChars(); + flushTextContentItem(); + return true; + } + + if (Math.abs(advanceX) > textContentItem.width) { + appendEOL(); + return true; + } + + if (advanceY <= textOrientation * textContentItem.notASpace) { + // The real spacing between 2 consecutive chars is thin enough to be + // considered a non-space. + resetLastChars(); + } + + if (advanceY <= textOrientation * textContentItem.trackingSpaceMin) { + if (shouldAddWhitepsace()) { + // The space is very thin, hence it deserves to have its own span in + // order to avoid too much shift between the canvas and the text + // layer. + resetLastChars(); + flushTextContentItem(); + pushWhitespace({ height: Math.abs(advanceY) }); + } else { + textContentItem.height += advanceY; + } + } else if ( + !addFakeSpaces( + advanceY, + textContentItem.prevTransform, + textOrientation + ) + ) { + if (textContentItem.str.length === 0) { + resetLastChars(); + pushWhitespace({ height: Math.abs(advanceY) }); + } else { + textContentItem.height += advanceY; + } + } + + if (Math.abs(advanceX) > textContentItem.width * VERTICAL_SHIFT_RATIO) { + flushTextContentItem(); + } + + return true; + } + + const advanceX = (posX - lastPosX) / textContentItem.textAdvanceScale; + const advanceY = posY - lastPosY; + + // When the total width of the current chunk is negative + // then we're writing from right to left. + const textOrientation = Math.sign(textContentItem.width); + if (advanceX < textOrientation * textContentItem.negativeSpaceMax) { + if ( + Math.abs(advanceY) > + 0.5 * textContentItem.height /* not the same line */ + ) { + appendEOL(); + return true; + } + + // We're moving back so in case the last char was a whitespace + // we cancel it: it doesn't make sense to insert it. + resetLastChars(); + flushTextContentItem(); + return true; + } + + if (Math.abs(advanceY) > textContentItem.height) { + appendEOL(); + return true; + } + + if (advanceX <= textOrientation * textContentItem.notASpace) { + // The real spacing between 2 consecutive chars is thin enough to be + // considered a non-space. + resetLastChars(); + } + + if (advanceX <= textOrientation * textContentItem.trackingSpaceMin) { + if (shouldAddWhitepsace()) { + // The space is very thin, hence it deserves to have its own span in + // order to avoid too much shift between the canvas and the text + // layer. + resetLastChars(); + flushTextContentItem(); + pushWhitespace({ width: Math.abs(advanceX) }); + } else { + textContentItem.width += advanceX; + } + } else if ( + !addFakeSpaces(advanceX, textContentItem.prevTransform, textOrientation) + ) { + if (textContentItem.str.length === 0) { + resetLastChars(); + pushWhitespace({ width: Math.abs(advanceX) }); + } else { + textContentItem.width += advanceX; + } + } + + if (Math.abs(advanceY) > textContentItem.height * VERTICAL_SHIFT_RATIO) { + flushTextContentItem(); + } + + return true; + } + + function buildTextContentItem({ chars, extraSpacing }) { + const font = textState.font; + if (!chars) { + // Just move according to the space we have. + const charSpacing = textState.charSpacing + extraSpacing; + if (charSpacing) { + if (!font.vertical) { + textState.translateTextMatrix( + charSpacing * textState.textHScale, + 0 + ); + } else { + textState.translateTextMatrix(0, -charSpacing); + } + } + + if (keepWhiteSpace) { + compareWithLastPosition(0); + } + + return; + } + + const glyphs = font.charsToGlyphs(chars); + const scale = textState.fontMatrix[0] * textState.fontSize; + + for (let i = 0, ii = glyphs.length; i < ii; i++) { + const glyph = glyphs[i]; + const { category } = glyph; + + if (category.isInvisibleFormatMark) { + continue; + } + let charSpacing = + textState.charSpacing + (i + 1 === ii ? extraSpacing : 0); + + let glyphWidth = glyph.width; + if (font.vertical) { + glyphWidth = glyph.vmetric ? glyph.vmetric[0] : -glyphWidth; + } + let scaledDim = glyphWidth * scale; + + if (!keepWhiteSpace && category.isWhitespace) { + // Don't push a " " in the textContentItem + // (except when it's between two non-spaces chars), + // it will be done (if required) in next call to + // compareWithLastPosition. + // This way we can merge real spaces and spaces due to cursor moves. + if (!font.vertical) { + charSpacing += scaledDim + textState.wordSpacing; + textState.translateTextMatrix( + charSpacing * textState.textHScale, + 0 + ); + } else { + charSpacing += -scaledDim + textState.wordSpacing; + textState.translateTextMatrix(0, -charSpacing); + } + saveLastChar(" "); + continue; + } + + if ( + !category.isZeroWidthDiacritic && + !compareWithLastPosition(scaledDim) + ) { + // The glyph is not in page so just skip it but move the cursor. + if (!font.vertical) { + textState.translateTextMatrix(scaledDim * textState.textHScale, 0); + } else { + textState.translateTextMatrix(0, scaledDim); + } + continue; + } + + // Must be called after compareWithLastPosition because + // the textContentItem could have been flushed. + const textChunk = ensureTextContentItem(); + if (category.isZeroWidthDiacritic) { + scaledDim = 0; + } + + if (!font.vertical) { + scaledDim *= textState.textHScale; + textState.translateTextMatrix(scaledDim, 0); + textChunk.width += scaledDim; + } else { + textState.translateTextMatrix(0, scaledDim); + scaledDim = Math.abs(scaledDim); + textChunk.height += scaledDim; + } + + if (scaledDim) { + // Save the position of the last visible character. + textChunk.prevTransform = getCurrentTextTransform(); + } + + const glyphUnicode = glyph.unicode; + if (saveLastChar(glyphUnicode)) { + // The two last chars are a non-whitespace followed by a whitespace + // and then this non-whitespace, so we insert a whitespace here. + // Replaces all whitespaces with standard spaces (0x20), to avoid + // alignment issues between the textLayer and the canvas if the text + // contains e.g. tabs (fixes issue6612.pdf). + textChunk.str.push(" "); + } + textChunk.str.push(glyphUnicode); + + if (charSpacing) { + if (!font.vertical) { + textState.translateTextMatrix( + charSpacing * textState.textHScale, + 0 + ); + } else { + textState.translateTextMatrix(0, -charSpacing); + } + } + } + } + + function appendEOL() { + resetLastChars(); + if (textContentItem.initialized) { + textContentItem.hasEOL = true; + flushTextContentItem(); + } else { + textContent.items.push({ + str: "", + dir: "ltr", + width: 0, + height: 0, + transform: getCurrentTextTransform(), + fontName: textState.loadedName, + hasEOL: true, + }); + } + } + + function addFakeSpaces(width, transf, textOrientation) { + if ( + textOrientation * textContentItem.spaceInFlowMin <= width && + width <= textOrientation * textContentItem.spaceInFlowMax + ) { + if (textContentItem.initialized) { + resetLastChars(); + textContentItem.str.push(" "); + } + return false; + } + + const fontName = textContentItem.fontName; + + let height = 0; + if (textContentItem.vertical) { + height = width; + width = 0; + } + + flushTextContentItem(); + resetLastChars(); + pushWhitespace({ + width: Math.abs(width), + height: Math.abs(height), + transform: transf || getCurrentTextTransform(), + fontName, + }); + + return true; + } + + function flushTextContentItem() { + if (!textContentItem.initialized || !textContentItem.str) { + return; + } + + // Do final text scaling. + if (!textContentItem.vertical) { + textContentItem.totalWidth += + textContentItem.width * textContentItem.textAdvanceScale; + } else { + textContentItem.totalHeight += + textContentItem.height * textContentItem.textAdvanceScale; + } + + textContent.items.push(runBidiTransform(textContentItem)); + textContentItem.initialized = false; + textContentItem.str.length = 0; + } + + function enqueueChunk(batch = false) { + const length = textContent.items.length; + if (length === 0) { + return; + } + if (batch && length < TEXT_CHUNK_BATCH_SIZE) { + return; + } + sink.enqueue(textContent, length); + textContent.items = []; + textContent.styles = Object.create(null); + } + + const timeSlotManager = new TimeSlotManager(); + + return new Promise(function promiseBody(resolve, reject) { + const next = function (promise) { + enqueueChunk(/* batch = */ true); + Promise.all([promise, sink.ready]).then(function () { + try { + promiseBody(resolve, reject); + } catch (ex) { + reject(ex); + } + }, reject); + }; + task.ensureNotTerminated(); + timeSlotManager.reset(); + + const operation = {}; + let stop, + name, + isValidName, + args = []; + while (!(stop = timeSlotManager.check())) { + // The arguments parsed by read() are not used beyond this loop, so + // we can reuse the same array on every iteration, thus avoiding + // unnecessary allocations. + args.length = 0; + operation.args = args; + if (!preprocessor.read(operation)) { + break; + } + + const previousState = textState; + textState = stateManager.state; + const fn = operation.fn; + args = operation.args; + + switch (fn | 0) { + case OPS.setFont: + // Optimization to ignore multiple identical Tf commands. + const fontNameArg = args[0].name, + fontSizeArg = args[1]; + if ( + textState.font && + fontNameArg === textState.fontName && + fontSizeArg === textState.fontSize + ) { + break; + } + + flushTextContentItem(); + textState.fontName = fontNameArg; + textState.fontSize = fontSizeArg; + next(handleSetFont(fontNameArg, null)); + return; + case OPS.setTextRise: + textState.textRise = args[0]; + break; + case OPS.setHScale: + textState.textHScale = args[0] / 100; + break; + case OPS.setLeading: + textState.leading = args[0]; + break; + case OPS.moveText: + textState.translateTextLineMatrix(args[0], args[1]); + textState.textMatrix = textState.textLineMatrix.slice(); + break; + case OPS.setLeadingMoveText: + textState.leading = -args[1]; + textState.translateTextLineMatrix(args[0], args[1]); + textState.textMatrix = textState.textLineMatrix.slice(); + break; + case OPS.nextLine: + textState.carriageReturn(); + break; + case OPS.setTextMatrix: + textState.setTextMatrix( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5] + ); + textState.setTextLineMatrix( + args[0], + args[1], + args[2], + args[3], + args[4], + args[5] + ); + updateAdvanceScale(); + break; + case OPS.setCharSpacing: + textState.charSpacing = args[0]; + break; + case OPS.setWordSpacing: + textState.wordSpacing = args[0]; + break; + case OPS.beginText: + textState.textMatrix = IDENTITY_MATRIX.slice(); + textState.textLineMatrix = IDENTITY_MATRIX.slice(); + break; + case OPS.showSpacedText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + + const spaceFactor = + ((textState.font.vertical ? 1 : -1) * textState.fontSize) / 1000; + const elements = args[0]; + for (let i = 0, ii = elements.length; i < ii; i++) { + const item = elements[i]; + if (typeof item === "string") { + showSpacedTextBuffer.push(item); + } else if (typeof item === "number" && item !== 0) { + // PDF Specification 5.3.2 states: + // The number is expressed in thousandths of a unit of text + // space. + // This amount is subtracted from the current horizontal or + // vertical coordinate, depending on the writing mode. + // In the default coordinate system, a positive adjustment + // has the effect of moving the next glyph painted either to + // the left or down by the given amount. + const str = showSpacedTextBuffer.join(""); + showSpacedTextBuffer.length = 0; + buildTextContentItem({ + chars: str, + extraSpacing: item * spaceFactor, + }); + } + } + + if (showSpacedTextBuffer.length > 0) { + const str = showSpacedTextBuffer.join(""); + showSpacedTextBuffer.length = 0; + buildTextContentItem({ + chars: str, + extraSpacing: 0, + }); + } + break; + case OPS.showText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + buildTextContentItem({ + chars: args[0], + extraSpacing: 0, + }); + break; + case OPS.nextLineShowText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + textState.carriageReturn(); + buildTextContentItem({ + chars: args[0], + extraSpacing: 0, + }); + break; + case OPS.nextLineSetSpacingShowText: + if (!stateManager.state.font) { + self.ensureStateFont(stateManager.state); + continue; + } + textState.wordSpacing = args[0]; + textState.charSpacing = args[1]; + textState.carriageReturn(); + buildTextContentItem({ + chars: args[2], + extraSpacing: 0, + }); + break; + case OPS.paintXObject: + flushTextContentItem(); + xobjs ??= resources.get("XObject") || Dict.empty; + + isValidName = args[0] instanceof Name; + name = args[0].name; + + if (isValidName && emptyXObjectCache.getByName(name)) { + break; + } + + next( + new Promise(function (resolveXObject, rejectXObject) { + if (!isValidName) { + throw new FormatError("XObject must be referred to by name."); + } + + let xobj = xobjs.getRaw(name); + if (xobj instanceof Ref) { + if (emptyXObjectCache.getByRef(xobj)) { + resolveXObject(); + return; + } + + const globalImage = self.globalImageCache.getData( + xobj, + self.pageIndex + ); + if (globalImage) { + resolveXObject(); + return; + } + + xobj = xref.fetch(xobj); + } + + if (!(xobj instanceof BaseStream)) { + throw new FormatError("XObject should be a stream"); + } + + const type = xobj.dict.get("Subtype"); + if (!(type instanceof Name)) { + throw new FormatError("XObject should have a Name subtype"); + } + + if (type.name !== "Form") { + emptyXObjectCache.set(name, xobj.dict.objId, true); + + resolveXObject(); + return; + } + + // Use a new `StateManager` to prevent incorrect positioning + // of textItems *after* the Form XObject, since errors in the + // data can otherwise prevent `restore` operators from + // executing. + // NOTE: Only an issue when `options.ignoreErrors === true`. + const currentState = stateManager.state.clone(); + const xObjStateManager = new StateManager(currentState); + + const matrix = lookupMatrix(xobj.dict.getArray("Matrix"), null); + if (matrix) { + xObjStateManager.transform(matrix); + } + + // Enqueue the `textContent` chunk before parsing the /Form + // XObject. + enqueueChunk(); + const sinkWrapper = { + enqueueInvoked: false, + + enqueue(chunk, size) { + this.enqueueInvoked = true; + sink.enqueue(chunk, size); + }, + + get desiredSize() { + return sink.desiredSize; + }, + + get ready() { + return sink.ready; + }, + }; + + self + .getTextContent({ + stream: xobj, + task, + resources: xobj.dict.get("Resources") || resources, + stateManager: xObjStateManager, + includeMarkedContent, + sink: sinkWrapper, + seenStyles, + viewBox, + lang, + markedContentData, + disableNormalization, + keepWhiteSpace, + }) + .then(function () { + if (!sinkWrapper.enqueueInvoked) { + emptyXObjectCache.set(name, xobj.dict.objId, true); + } + resolveXObject(); + }, rejectXObject); + }).catch(function (reason) { + if (reason instanceof AbortException) { + return; + } + if (self.options.ignoreErrors) { + // Error(s) in the XObject -- allow text-extraction to + // continue. + warn(`getTextContent - ignoring XObject: "${reason}".`); + return; + } + throw reason; + }) + ); + return; + case OPS.setGState: + isValidName = args[0] instanceof Name; + name = args[0].name; + + if (isValidName && emptyGStateCache.getByName(name)) { + break; + } + + next( + new Promise(function (resolveGState, rejectGState) { + if (!isValidName) { + throw new FormatError("GState must be referred to by name."); + } + + const extGState = resources.get("ExtGState"); + if (!(extGState instanceof Dict)) { + throw new FormatError("ExtGState should be a dictionary."); + } + + const gState = extGState.get(name); + // TODO: Attempt to lookup cached GStates by reference as well, + // if and only if there are PDF documents where doing so + // would significantly improve performance. + if (!(gState instanceof Dict)) { + throw new FormatError("GState should be a dictionary."); + } + + const gStateFont = gState.get("Font"); + if (!gStateFont) { + emptyGStateCache.set(name, gState.objId, true); + + resolveGState(); + return; + } + flushTextContentItem(); + + textState.fontName = null; + textState.fontSize = gStateFont[1]; + handleSetFont(null, gStateFont[0]).then( + resolveGState, + rejectGState + ); + }).catch(function (reason) { + if (reason instanceof AbortException) { + return; + } + if (self.options.ignoreErrors) { + // Error(s) in the ExtGState -- allow text-extraction to + // continue. + warn(`getTextContent - ignoring ExtGState: "${reason}".`); + return; + } + throw reason; + }) + ); + return; + case OPS.beginMarkedContent: + flushTextContentItem(); + if (includeMarkedContent) { + markedContentData.level++; + + textContent.items.push({ + type: "beginMarkedContent", + tag: args[0] instanceof Name ? args[0].name : null, + }); + } + break; + case OPS.beginMarkedContentProps: + flushTextContentItem(); + if (includeMarkedContent) { + markedContentData.level++; + + let mcid = null; + if (args[1] instanceof Dict) { + mcid = args[1].get("MCID"); + } + textContent.items.push({ + type: "beginMarkedContentProps", + id: Number.isInteger(mcid) + ? `${self.idFactory.getPageObjId()}_mc${mcid}` + : null, + tag: args[0] instanceof Name ? args[0].name : null, + }); + } + break; + case OPS.endMarkedContent: + flushTextContentItem(); + if (includeMarkedContent) { + if (markedContentData.level === 0) { + // Handle unbalanced beginMarkedContent/endMarkedContent + // operators (fixes issue15629.pdf). + break; + } + markedContentData.level--; + + textContent.items.push({ + type: "endMarkedContent", + }); + } + break; + case OPS.restore: + if ( + previousState && + (previousState.font !== textState.font || + previousState.fontSize !== textState.fontSize || + previousState.fontName !== textState.fontName) + ) { + flushTextContentItem(); + } + break; + } // switch + if (textContent.items.length >= sink.desiredSize) { + // Wait for ready, if we reach highWaterMark. + stop = true; + break; + } + } // while + if (stop) { + next(deferred); + return; + } + flushTextContentItem(); + enqueueChunk(); + resolve(); + }).catch(reason => { + if (reason instanceof AbortException) { + return; + } + if (this.options.ignoreErrors) { + // Error(s) in the TextContent -- allow text-extraction to continue. + warn( + `getTextContent - ignoring errors during "${task.name}" ` + + `task: "${reason}".` + ); + + flushTextContentItem(); + enqueueChunk(); + return; + } + throw reason; + }); + } + + async extractDataStructures(dict, properties) { + const xref = this.xref; + let cidToGidBytes; + // 9.10.2 + const toUnicodePromise = this.readToUnicode(properties.toUnicode); + + if (properties.composite) { + // CIDSystemInfo helps to match CID to glyphs + const cidSystemInfo = dict.get("CIDSystemInfo"); + if (cidSystemInfo instanceof Dict) { + properties.cidSystemInfo = { + registry: stringToPDFString(cidSystemInfo.get("Registry")), + ordering: stringToPDFString(cidSystemInfo.get("Ordering")), + supplement: cidSystemInfo.get("Supplement"), + }; + } + + try { + const cidToGidMap = dict.get("CIDToGIDMap"); + if (cidToGidMap instanceof BaseStream) { + cidToGidBytes = cidToGidMap.getBytes(); + } + } catch (ex) { + if (!this.options.ignoreErrors) { + throw ex; + } + warn(`extractDataStructures - ignoring CIDToGIDMap data: "${ex}".`); + } + } + + // Based on 9.6.6 of the spec the encoding can come from multiple places + // and depends on the font type. The base encoding and differences are + // read here, but the encoding that is actually used is chosen during + // glyph mapping in the font. + // TODO: Loading the built in encoding in the font would allow the + // differences to be merged in here not require us to hold on to it. + const differences = []; + let baseEncodingName = null; + let encoding; + if (dict.has("Encoding")) { + encoding = dict.get("Encoding"); + if (encoding instanceof Dict) { + baseEncodingName = encoding.get("BaseEncoding"); + baseEncodingName = + baseEncodingName instanceof Name ? baseEncodingName.name : null; + // Load the differences between the base and original + if (encoding.has("Differences")) { + const diffEncoding = encoding.get("Differences"); + let index = 0; + for (const entry of diffEncoding) { + const data = xref.fetchIfRef(entry); + if (typeof data === "number") { + index = data; + } else if (data instanceof Name) { + differences[index++] = data.name; + } else { + throw new FormatError( + `Invalid entry in 'Differences' array: ${data}` + ); + } + } + } + } else if (encoding instanceof Name) { + baseEncodingName = encoding.name; + } else { + const msg = "Encoding is not a Name nor a Dict"; + + if (!this.options.ignoreErrors) { + throw new FormatError(msg); + } + warn(msg); + } + // According to table 114 if the encoding is a named encoding it must be + // one of these predefined encodings. + if ( + baseEncodingName !== "MacRomanEncoding" && + baseEncodingName !== "MacExpertEncoding" && + baseEncodingName !== "WinAnsiEncoding" + ) { + baseEncodingName = null; + } + } + + const nonEmbeddedFont = !properties.file || properties.isInternalFont, + isSymbolsFontName = getSymbolsFonts()[properties.name]; + // Ignore an incorrectly specified named encoding for non-embedded + // symbol fonts (fixes issue16464.pdf). + if (baseEncodingName && nonEmbeddedFont && isSymbolsFontName) { + baseEncodingName = null; + } + + if (baseEncodingName) { + properties.defaultEncoding = getEncoding(baseEncodingName); + } else { + const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); + const isNonsymbolicFont = !!(properties.flags & FontFlags.Nonsymbolic); + // According to "Table 114" in section "9.6.6.1 General" (under + // "9.6.6 Character Encoding") of the PDF specification, a Nonsymbolic + // font should use the `StandardEncoding` if no encoding is specified. + encoding = StandardEncoding; + if (properties.type === "TrueType" && !isNonsymbolicFont) { + encoding = WinAnsiEncoding; + } + // The Symbolic attribute can be misused for regular fonts + // Heuristic: we have to check if the font is a standard one also + if (isSymbolicFont || isSymbolsFontName) { + encoding = MacRomanEncoding; + if (nonEmbeddedFont) { + if (/Symbol/i.test(properties.name)) { + encoding = SymbolSetEncoding; + } else if (/Dingbats/i.test(properties.name)) { + encoding = ZapfDingbatsEncoding; + } else if (/Wingdings/i.test(properties.name)) { + encoding = WinAnsiEncoding; + } + } + } + properties.defaultEncoding = encoding; + } + + properties.differences = differences; + properties.baseEncodingName = baseEncodingName; + properties.hasEncoding = !!baseEncodingName || differences.length > 0; + properties.dict = dict; + + properties.toUnicode = await toUnicodePromise; + + const builtToUnicode = await this.buildToUnicode(properties); + properties.toUnicode = builtToUnicode; + + if (cidToGidBytes) { + properties.cidToGidMap = this.readCidToGidMap( + cidToGidBytes, + builtToUnicode + ); + } + return properties; + } + + /** + * @returns {Array} + * @private + */ + _simpleFontToUnicode(properties, forceGlyphs = false) { + assert(!properties.composite, "Must be a simple font."); + + const toUnicode = []; + const encoding = properties.defaultEncoding.slice(); + const baseEncodingName = properties.baseEncodingName; + // Merge in the differences array. + const differences = properties.differences; + for (const charcode in differences) { + const glyphName = differences[charcode]; + if (glyphName === ".notdef") { + // Skip .notdef to prevent rendering errors, e.g. boxes appearing + // where there should be spaces (fixes issue5256.pdf). + continue; + } + encoding[charcode] = glyphName; + } + const glyphsUnicodeMap = getGlyphsUnicode(); + for (const charcode in encoding) { + // a) Map the character code to a character name. + let glyphName = encoding[charcode]; + if (glyphName === "") { + continue; + } + // b) Look up the character name in the Adobe Glyph List (see the + // Bibliography) to obtain the corresponding Unicode value. + let unicode = glyphsUnicodeMap[glyphName]; + if (unicode !== undefined) { + toUnicode[charcode] = String.fromCharCode(unicode); + continue; + } + // (undocumented) c) Few heuristics to recognize unknown glyphs + // NOTE: Adobe Reader does not do this step, but OSX Preview does + let code = 0; + switch (glyphName[0]) { + case "G": // Gxx glyph + if (glyphName.length === 3) { + code = parseInt(glyphName.substring(1), 16); + } + break; + case "g": // g00xx glyph + if (glyphName.length === 5) { + code = parseInt(glyphName.substring(1), 16); + } + break; + case "C": // Cdd{d} glyph + case "c": // cdd{d} glyph + if (glyphName.length >= 3 && glyphName.length <= 4) { + const codeStr = glyphName.substring(1); + + if (forceGlyphs) { + code = parseInt(codeStr, 16); + break; + } + // Normally the Cdd{d}/cdd{d} glyphName format will contain + // regular, i.e. base 10, charCodes (see issue4550.pdf)... + code = +codeStr; + + // ... however some PDF generators violate that assumption by + // containing glyph, i.e. base 16, codes instead. + // In that case we need to re-parse the *entire* encoding to + // prevent broken text-selection (fixes issue9655_reduced.pdf). + if (Number.isNaN(code) && Number.isInteger(parseInt(codeStr, 16))) { + return this._simpleFontToUnicode( + properties, + /* forceGlyphs */ true + ); + } + } + break; + case "u": // 'uniXXXX'/'uXXXX{XX}' glyphs + unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); + if (unicode !== -1) { + code = unicode; + } + break; + default: + // Support (some) non-standard ligatures. + switch (glyphName) { + case "f_h": + case "f_t": + case "T_h": + toUnicode[charcode] = glyphName.replaceAll("_", ""); + continue; + } + break; + } + if (code > 0 && code <= 0x10ffff && Number.isInteger(code)) { + // If `baseEncodingName` is one the predefined encodings, and `code` + // equals `charcode`, using the glyph defined in the baseEncoding + // seems to yield a better `toUnicode` mapping (fixes issue 5070). + if (baseEncodingName && code === +charcode) { + const baseEncoding = getEncoding(baseEncodingName); + if (baseEncoding && (glyphName = baseEncoding[charcode])) { + toUnicode[charcode] = String.fromCharCode( + glyphsUnicodeMap[glyphName] + ); + continue; + } + } + toUnicode[charcode] = String.fromCodePoint(code); + } + } + return toUnicode; + } + + /** + * Builds a char code to unicode map based on section 9.10 of the spec. + * @param {Object} properties Font properties object. + * @returns {Promise} A Promise that is resolved with a + * {ToUnicodeMap|IdentityToUnicodeMap} object. + */ + async buildToUnicode(properties) { + properties.hasIncludedToUnicodeMap = properties.toUnicode?.length > 0; + + // Section 9.10.2 Mapping Character Codes to Unicode Values + if (properties.hasIncludedToUnicodeMap) { + // Some fonts contain incomplete ToUnicode data, causing issues with + // text-extraction. For simple fonts, containing encoding information, + // use a fallback ToUnicode map to improve this (fixes issue8229.pdf). + if (!properties.composite && properties.hasEncoding) { + properties.fallbackToUnicode = this._simpleFontToUnicode(properties); + } + return properties.toUnicode; + } + + // According to the spec if the font is a simple font we should only map + // to unicode if the base encoding is MacRoman, MacExpert, or WinAnsi or + // the differences array only contains adobe standard or symbol set names, + // in pratice it seems better to always try to create a toUnicode map + // based of the default encoding. + if (!properties.composite /* is simple font */) { + return new ToUnicodeMap(this._simpleFontToUnicode(properties)); + } + + // If the font is a composite font that uses one of the predefined CMaps + // listed in Table 118 (except Identityโ€“H and Identityโ€“V) or whose + // descendant CIDFont uses the Adobe-GB1, Adobe-CNS1, Adobe-Japan1, or + // Adobe-Korea1 character collection: + if ( + properties.composite && + ((properties.cMap.builtInCMap && + !(properties.cMap instanceof IdentityCMap)) || + // The font is supposed to have a CIDSystemInfo dictionary, but some + // PDFs don't include it (fixes issue 17689), hence the `?'. + (properties.cidSystemInfo?.registry === "Adobe" && + (properties.cidSystemInfo.ordering === "GB1" || + properties.cidSystemInfo.ordering === "CNS1" || + properties.cidSystemInfo.ordering === "Japan1" || + properties.cidSystemInfo.ordering === "Korea1"))) + ) { + // Then: + // a) Map the character code to a character identifier (CID) according + // to the fontโ€™s CMap. + // b) Obtain the registry and ordering of the character collection used + // by the fontโ€™s CMap (for example, Adobe and Japan1) from its + // CIDSystemInfo dictionary. + const { registry, ordering } = properties.cidSystemInfo; + // c) Construct a second CMap name by concatenating the registry and + // ordering obtained in step (b) in the format registryโ€“orderingโ€“UCS2 + // (for example, Adobeโ€“Japan1โ€“UCS2). + const ucs2CMapName = Name.get(`${registry}-${ordering}-UCS2`); + // d) Obtain the CMap with the name constructed in step (c) (available + // from the ASN Web site; see the Bibliography). + const ucs2CMap = await CMapFactory.create({ + encoding: ucs2CMapName, + fetchBuiltInCMap: this._fetchBuiltInCMapBound, + useCMap: null, + }); + const toUnicode = [], + buf = []; + properties.cMap.forEach(function (charcode, cid) { + if (cid > 0xffff) { + throw new FormatError("Max size of CID is 65,535"); + } + // e) Map the CID obtained in step (a) according to the CMap + // obtained in step (d), producing a Unicode value. + const ucs2 = ucs2CMap.lookup(cid); + if (ucs2) { + buf.length = 0; + // Support multi-byte entries (fixes issue16176.pdf). + for (let i = 0, ii = ucs2.length; i < ii; i += 2) { + buf.push((ucs2.charCodeAt(i) << 8) + ucs2.charCodeAt(i + 1)); + } + toUnicode[charcode] = String.fromCharCode(...buf); + } + }); + return new ToUnicodeMap(toUnicode); + } + + // The viewer's choice, just use an identity map. + return new IdentityToUnicodeMap(properties.firstChar, properties.lastChar); + } + + async readToUnicode(cmapObj) { + if (!cmapObj) { + return null; + } + if (cmapObj instanceof Name) { + const cmap = await CMapFactory.create({ + encoding: cmapObj, + fetchBuiltInCMap: this._fetchBuiltInCMapBound, + useCMap: null, + }); + + if (cmap instanceof IdentityCMap) { + return new IdentityToUnicodeMap(0, 0xffff); + } + return new ToUnicodeMap(cmap.getMap()); + } + if (cmapObj instanceof BaseStream) { + try { + const cmap = await CMapFactory.create({ + encoding: cmapObj, + fetchBuiltInCMap: this._fetchBuiltInCMapBound, + useCMap: null, + }); + + if (cmap instanceof IdentityCMap) { + return new IdentityToUnicodeMap(0, 0xffff); + } + const map = new Array(cmap.length); + // Convert UTF-16BE + // NOTE: cmap can be a sparse array, so use forEach instead of + // `for(;;)` to iterate over all keys. + cmap.forEach(function (charCode, token) { + // Some cmaps contain *only* CID characters (fixes issue9367.pdf). + if (typeof token === "number") { + map[charCode] = String.fromCodePoint(token); + return; + } + // Add back omitted leading zeros on odd length tokens + // (fixes issue #18099) + if (token.length % 2 !== 0) { + token = "\u0000" + token; + } + const str = []; + for (let k = 0; k < token.length; k += 2) { + const w1 = (token.charCodeAt(k) << 8) | token.charCodeAt(k + 1); + if ((w1 & 0xf800) !== 0xd800) { + // w1 < 0xD800 || w1 > 0xDFFF + str.push(w1); + continue; + } + k += 2; + const w2 = (token.charCodeAt(k) << 8) | token.charCodeAt(k + 1); + str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000); + } + map[charCode] = String.fromCodePoint(...str); + }); + return new ToUnicodeMap(map); + } catch (reason) { + if (reason instanceof AbortException) { + return null; + } + if (this.options.ignoreErrors) { + warn(`readToUnicode - ignoring ToUnicode data: "${reason}".`); + return null; + } + throw reason; + } + } + return null; + } + + readCidToGidMap(glyphsData, toUnicode) { + // Extract the encoding from the CIDToGIDMap + + // Set encoding 0 to later verify the font has an encoding + const result = []; + for (let j = 0, jj = glyphsData.length; j < jj; j++) { + const glyphID = (glyphsData[j++] << 8) | glyphsData[j]; + const code = j >> 1; + if (glyphID === 0 && !toUnicode.has(code)) { + continue; + } + result[code] = glyphID; + } + return result; + } + + extractWidths(dict, descriptor, properties) { + const xref = this.xref; + let glyphsWidths = []; + let defaultWidth = 0; + const glyphsVMetrics = []; + let defaultVMetrics; + if (properties.composite) { + const dw = dict.get("DW"); + defaultWidth = typeof dw === "number" ? Math.ceil(dw) : 1000; + + const widths = dict.get("W"); + if (Array.isArray(widths)) { + for (let i = 0, ii = widths.length; i < ii; i++) { + let start = xref.fetchIfRef(widths[i++]); + if (!Number.isInteger(start)) { + break; // Invalid /W data. + } + const code = xref.fetchIfRef(widths[i]); + + if (Array.isArray(code)) { + for (const c of code) { + const width = xref.fetchIfRef(c); + if (typeof width === "number") { + glyphsWidths[start] = width; + } + start++; + } + } else if (Number.isInteger(code)) { + const width = xref.fetchIfRef(widths[++i]); + if (typeof width !== "number") { + continue; + } + for (let j = start; j <= code; j++) { + glyphsWidths[j] = width; + } + } else { + break; // Invalid /W data. + } + } + } + + if (properties.vertical) { + const dw2 = dict.getArray("DW2"); + let vmetrics = isNumberArray(dw2, 2) ? dw2 : [880, -1000]; + defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]]; + vmetrics = dict.get("W2"); + if (Array.isArray(vmetrics)) { + for (let i = 0, ii = vmetrics.length; i < ii; i++) { + let start = xref.fetchIfRef(vmetrics[i++]); + if (!Number.isInteger(start)) { + break; // Invalid /W2 data. + } + const code = xref.fetchIfRef(vmetrics[i]); + + if (Array.isArray(code)) { + for (let j = 0, jj = code.length; j < jj; j++) { + const vmetric = [ + xref.fetchIfRef(code[j++]), + xref.fetchIfRef(code[j++]), + xref.fetchIfRef(code[j]), + ]; + if (isNumberArray(vmetric, null)) { + glyphsVMetrics[start] = vmetric; + } + start++; + } + } else if (Number.isInteger(code)) { + const vmetric = [ + xref.fetchIfRef(vmetrics[++i]), + xref.fetchIfRef(vmetrics[++i]), + xref.fetchIfRef(vmetrics[++i]), + ]; + if (!isNumberArray(vmetric, null)) { + continue; + } + for (let j = start; j <= code; j++) { + glyphsVMetrics[j] = vmetric; + } + } else { + break; // Invalid /W2 data. + } + } + } + } + } else { + const widths = dict.get("Widths"); + if (Array.isArray(widths)) { + let j = properties.firstChar; + for (const w of widths) { + const width = xref.fetchIfRef(w); + if (typeof width === "number") { + glyphsWidths[j] = width; + } + j++; + } + const missingWidth = descriptor.get("MissingWidth"); + defaultWidth = typeof missingWidth === "number" ? missingWidth : 0; + } else { + // Trying get the BaseFont metrics (see comment above). + const baseFontName = dict.get("BaseFont"); + if (baseFontName instanceof Name) { + const metrics = this.getBaseFontMetrics(baseFontName.name); + + glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties); + defaultWidth = metrics.defaultWidth; + } + } + } + + // Heuristic: detection of monospace font by checking all non-zero widths + let isMonospace = true; + let firstWidth = defaultWidth; + for (const glyph in glyphsWidths) { + const glyphWidth = glyphsWidths[glyph]; + if (!glyphWidth) { + continue; + } + if (!firstWidth) { + firstWidth = glyphWidth; + continue; + } + if (firstWidth !== glyphWidth) { + isMonospace = false; + break; + } + } + if (isMonospace) { + properties.flags |= FontFlags.FixedPitch; + } else { + // Clear the flag. + properties.flags &= ~FontFlags.FixedPitch; + } + + properties.defaultWidth = defaultWidth; + properties.widths = glyphsWidths; + properties.defaultVMetrics = defaultVMetrics; + properties.vmetrics = glyphsVMetrics; + } + + isSerifFont(baseFontName) { + // Simulating descriptor flags attribute + const fontNameWoStyle = baseFontName.split("-", 1)[0]; + return ( + fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle) + ); + } + + getBaseFontMetrics(name) { + let defaultWidth = 0; + let widths = Object.create(null); + let monospace = false; + const stdFontMap = getStdFontMap(); + let lookupName = stdFontMap[name] || name; + const Metrics = getMetrics(); + + if (!(lookupName in Metrics)) { + // Use default fonts for looking up font metrics if the passed + // font is not a base font + lookupName = this.isSerifFont(name) ? "Times-Roman" : "Helvetica"; + } + const glyphWidths = Metrics[lookupName]; + + if (typeof glyphWidths === "number") { + defaultWidth = glyphWidths; + monospace = true; + } else { + widths = glyphWidths(); // expand lazy widths array + } + + return { + defaultWidth, + monospace, + widths, + }; + } + + buildCharCodeToWidth(widthsByGlyphName, properties) { + const widths = Object.create(null); + const differences = properties.differences; + const encoding = properties.defaultEncoding; + for (let charCode = 0; charCode < 256; charCode++) { + if (charCode in differences && widthsByGlyphName[differences[charCode]]) { + widths[charCode] = widthsByGlyphName[differences[charCode]]; + continue; + } + if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) { + widths[charCode] = widthsByGlyphName[encoding[charCode]]; + continue; + } + } + return widths; + } + + preEvaluateFont(dict) { + const baseDict = dict; + let type = dict.get("Subtype"); + if (!(type instanceof Name)) { + throw new FormatError("invalid font Subtype"); + } + + let composite = false; + let hash; + if (type.name === "Type0") { + // If font is a composite + // - get the descendant font + // - set the type according to the descendant font + // - get the FontDescriptor from the descendant font + const df = dict.get("DescendantFonts"); + if (!df) { + throw new FormatError("Descendant fonts are not specified"); + } + dict = Array.isArray(df) ? this.xref.fetchIfRef(df[0]) : df; + + if (!(dict instanceof Dict)) { + throw new FormatError("Descendant font is not a dictionary."); + } + type = dict.get("Subtype"); + if (!(type instanceof Name)) { + throw new FormatError("invalid font Subtype"); + } + composite = true; + } + + let firstChar = dict.get("FirstChar"); + if (!Number.isInteger(firstChar)) { + firstChar = 0; + } + let lastChar = dict.get("LastChar"); + if (!Number.isInteger(lastChar)) { + lastChar = composite ? 0xffff : 0xff; + } + const descriptor = dict.get("FontDescriptor"); + const toUnicode = dict.get("ToUnicode") || baseDict.get("ToUnicode"); + + if (descriptor) { + hash = new MurmurHash3_64(); + + const encoding = baseDict.getRaw("Encoding"); + if (encoding instanceof Name) { + hash.update(encoding.name); + } else if (encoding instanceof Ref) { + hash.update(encoding.toString()); + } else if (encoding instanceof Dict) { + for (const entry of encoding.getRawValues()) { + if (entry instanceof Name) { + hash.update(entry.name); + } else if (entry instanceof Ref) { + hash.update(entry.toString()); + } else if (Array.isArray(entry)) { + // 'Differences' array (fixes bug1157493.pdf). + const diffLength = entry.length, + diffBuf = new Array(diffLength); + + for (let j = 0; j < diffLength; j++) { + const diffEntry = entry[j]; + if (diffEntry instanceof Name) { + diffBuf[j] = diffEntry.name; + } else if ( + typeof diffEntry === "number" || + diffEntry instanceof Ref + ) { + diffBuf[j] = diffEntry.toString(); + } + } + hash.update(diffBuf.join()); + } + } + } + + hash.update(`${firstChar}-${lastChar}`); // Fixes issue10665_reduced.pdf + + if (toUnicode instanceof BaseStream) { + const stream = toUnicode.str || toUnicode; + const uint8array = stream.buffer + ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) + : new Uint8Array( + stream.bytes.buffer, + stream.start, + stream.end - stream.start + ); + hash.update(uint8array); + } else if (toUnicode instanceof Name) { + hash.update(toUnicode.name); + } + + const widths = dict.get("Widths") || baseDict.get("Widths"); + if (Array.isArray(widths)) { + const widthsBuf = []; + for (const entry of widths) { + if (typeof entry === "number" || entry instanceof Ref) { + widthsBuf.push(entry.toString()); + } + } + hash.update(widthsBuf.join()); + } + + if (composite) { + hash.update("compositeFont"); + + const compositeWidths = dict.get("W") || baseDict.get("W"); + if (Array.isArray(compositeWidths)) { + const widthsBuf = []; + for (const entry of compositeWidths) { + if (typeof entry === "number" || entry instanceof Ref) { + widthsBuf.push(entry.toString()); + } else if (Array.isArray(entry)) { + const subWidthsBuf = []; + for (const element of entry) { + if (typeof element === "number" || element instanceof Ref) { + subWidthsBuf.push(element.toString()); + } + } + widthsBuf.push(`[${subWidthsBuf.join()}]`); + } + } + hash.update(widthsBuf.join()); + } + + const cidToGidMap = + dict.getRaw("CIDToGIDMap") || baseDict.getRaw("CIDToGIDMap"); + if (cidToGidMap instanceof Name) { + hash.update(cidToGidMap.name); + } else if (cidToGidMap instanceof Ref) { + hash.update(cidToGidMap.toString()); + } else if (cidToGidMap instanceof BaseStream) { + hash.update(cidToGidMap.peekBytes()); + } + } + } + + return { + descriptor, + dict, + baseDict, + composite, + type: type.name, + firstChar, + lastChar, + toUnicode, + hash: hash ? hash.hexdigest() : "", + }; + } + + async translateFont({ + descriptor, + dict, + baseDict, + composite, + type, + firstChar, + lastChar, + toUnicode, + cssFontInfo, + }) { + const isType3Font = type === "Type3"; + + if (!descriptor) { + if (isType3Font) { + const bbox = lookupNormalRect(dict.getArray("FontBBox"), [0, 0, 0, 0]); + // FontDescriptor is only required for Type3 fonts when the document + // is a tagged pdf. Create a barbebones one to get by. + descriptor = new Dict(null); + descriptor.set("FontName", Name.get(type)); + descriptor.set("FontBBox", bbox); + } else { + // Before PDF 1.5 if the font was one of the base 14 fonts, having a + // FontDescriptor was not required. + // This case is here for compatibility. + let baseFontName = dict.get("BaseFont"); + if (!(baseFontName instanceof Name)) { + throw new FormatError("Base font is not specified"); + } + + // Using base font name as a font name. + baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-"); + const metrics = this.getBaseFontMetrics(baseFontName); + + // Simulating descriptor flags attribute + const fontNameWoStyle = baseFontName.split("-", 1)[0]; + const flags = + (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | + (metrics.monospace ? FontFlags.FixedPitch : 0) | + (getSymbolsFonts()[fontNameWoStyle] + ? FontFlags.Symbolic + : FontFlags.Nonsymbolic); + + const properties = { + type, + name: baseFontName, + loadedName: baseDict.loadedName, + systemFontInfo: null, + widths: metrics.widths, + defaultWidth: metrics.defaultWidth, + isSimulatedFlags: true, + flags, + firstChar, + lastChar, + toUnicode, + xHeight: 0, + capHeight: 0, + italicAngle: 0, + isType3Font, + }; + const widths = dict.get("Widths"); + + const standardFontName = getStandardFontName(baseFontName); + let file = null; + if (standardFontName) { + file = await this.fetchStandardFontData(standardFontName); + properties.isInternalFont = !!file; + } + if (!properties.isInternalFont && this.options.useSystemFonts) { + properties.systemFontInfo = getFontSubstitution( + this.systemFontCache, + this.idFactory, + this.options.standardFontDataUrl, + baseFontName, + standardFontName, + type + ); + } + + const newProperties = await this.extractDataStructures( + dict, + properties + ); + if (Array.isArray(widths)) { + const glyphWidths = []; + let j = firstChar; + for (const w of widths) { + const width = this.xref.fetchIfRef(w); + if (typeof width === "number") { + glyphWidths[j] = width; + } + j++; + } + newProperties.widths = glyphWidths; + } else { + newProperties.widths = this.buildCharCodeToWidth( + metrics.widths, + newProperties + ); + } + return new Font(baseFontName, file, newProperties); + } + } + + // According to the spec if 'FontDescriptor' is declared, 'FirstChar', + // 'LastChar' and 'Widths' should exist too, but some PDF encoders seem + // to ignore this rule when a variant of a standard font is used. + // TODO Fill the width array depending on which of the base font this is + // a variant. + + let fontName = descriptor.get("FontName"); + let baseFont = dict.get("BaseFont"); + // Some bad PDFs have a string as the font name. + if (typeof fontName === "string") { + fontName = Name.get(fontName); + } + if (typeof baseFont === "string") { + baseFont = Name.get(baseFont); + } + + const fontNameStr = fontName?.name; + const baseFontStr = baseFont?.name; + if (!isType3Font && fontNameStr !== baseFontStr) { + info( + `The FontDescriptor's FontName is "${fontNameStr}" but ` + + `should be the same as the Font's BaseFont "${baseFontStr}".` + ); + // - Workaround for cases where e.g. fontNameStr = 'Arial' and + // baseFontStr = 'Arial,Bold' (needed when no font file is embedded). + // + // - Workaround for cases where e.g. fontNameStr = 'wg09np' and + // baseFontStr = 'Wingdings-Regular' (fixes issue7454.pdf). + if ( + fontNameStr && + baseFontStr && + (baseFontStr.startsWith(fontNameStr) || + (!isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr))) + ) { + fontName = null; + } + } + fontName ||= baseFont; + + if (!(fontName instanceof Name)) { + throw new FormatError("invalid font name"); + } + + let fontFile, subtype, length1, length2, length3; + try { + fontFile = descriptor.get("FontFile", "FontFile2", "FontFile3"); + + if (fontFile) { + if (!(fontFile instanceof BaseStream)) { + throw new FormatError("FontFile should be a stream"); + } else if (fontFile.isEmpty) { + throw new FormatError("FontFile is empty"); + } + } + } catch (ex) { + if (!this.options.ignoreErrors) { + throw ex; + } + warn(`translateFont - fetching "${fontName.name}" font file: "${ex}".`); + fontFile = null; + } + let isInternalFont = false; + let glyphScaleFactors = null; + let systemFontInfo = null; + if (fontFile) { + if (fontFile.dict) { + const subtypeEntry = fontFile.dict.get("Subtype"); + if (subtypeEntry instanceof Name) { + subtype = subtypeEntry.name; + } + length1 = fontFile.dict.get("Length1"); + length2 = fontFile.dict.get("Length2"); + length3 = fontFile.dict.get("Length3"); + } + } else if (cssFontInfo) { + // We've a missing XFA font. + const standardFontName = getXfaFontName(fontName.name); + if (standardFontName) { + cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`; + cssFontInfo.metrics = standardFontName.metrics || null; + glyphScaleFactors = standardFontName.factors || null; + fontFile = await this.fetchStandardFontData(standardFontName.name); + isInternalFont = !!fontFile; + + // We're using a substitution font but for example widths (if any) + // are related to the glyph positions in the font. + // So we overwrite everything here to be sure that widths are + // correct. + baseDict = dict = getXfaFontDict(fontName.name); + composite = true; + } + } else if (!isType3Font) { + const standardFontName = getStandardFontName(fontName.name); + if (standardFontName) { + fontFile = await this.fetchStandardFontData(standardFontName); + isInternalFont = !!fontFile; + } + if (!isInternalFont && this.options.useSystemFonts) { + systemFontInfo = getFontSubstitution( + this.systemFontCache, + this.idFactory, + this.options.standardFontDataUrl, + fontName.name, + standardFontName, + type + ); + } + } + + const fontMatrix = lookupMatrix( + dict.getArray("FontMatrix"), + FONT_IDENTITY_MATRIX + ); + const bbox = lookupNormalRect( + descriptor.getArray("FontBBox") || dict.getArray("FontBBox"), + undefined + ); + let ascent = descriptor.get("Ascent"); + if (typeof ascent !== "number") { + ascent = undefined; + } + let descent = descriptor.get("Descent"); + if (typeof descent !== "number") { + descent = undefined; + } + let xHeight = descriptor.get("XHeight"); + if (typeof xHeight !== "number") { + xHeight = 0; + } + let capHeight = descriptor.get("CapHeight"); + if (typeof capHeight !== "number") { + capHeight = 0; + } + let flags = descriptor.get("Flags"); + if (!Number.isInteger(flags)) { + flags = 0; + } + let italicAngle = descriptor.get("ItalicAngle"); + if (typeof italicAngle !== "number") { + italicAngle = 0; + } + + const properties = { + type, + name: fontName.name, + subtype, + file: fontFile, + length1, + length2, + length3, + isInternalFont, + loadedName: baseDict.loadedName, + composite, + fixedPitch: false, + fontMatrix, + firstChar, + lastChar, + toUnicode, + bbox, + ascent, + descent, + xHeight, + capHeight, + flags, + italicAngle, + isType3Font, + cssFontInfo, + scaleFactors: glyphScaleFactors, + systemFontInfo, + }; + + if (composite) { + const cidEncoding = baseDict.get("Encoding"); + if (cidEncoding instanceof Name) { + properties.cidEncoding = cidEncoding.name; + } + const cMap = await CMapFactory.create({ + encoding: cidEncoding, + fetchBuiltInCMap: this._fetchBuiltInCMapBound, + useCMap: null, + }); + properties.cMap = cMap; + properties.vertical = properties.cMap.vertical; + } + + const newProperties = await this.extractDataStructures(dict, properties); + this.extractWidths(dict, descriptor, newProperties); + + return new Font(fontName.name, fontFile, newProperties); + } + + static buildFontPaths(font, glyphs, handler, evaluatorOptions) { + function buildPath(fontChar) { + const glyphName = `${font.loadedName}_path_${fontChar}`; + try { + if (font.renderer.hasBuiltPath(fontChar)) { + return; + } + handler.send("commonobj", [ + glyphName, + "FontPath", + font.renderer.getPathJs(fontChar), + ]); + } catch (reason) { + if (evaluatorOptions.ignoreErrors) { + warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`); + return; + } + throw reason; + } + } + + for (const glyph of glyphs) { + buildPath(glyph.fontChar); + + // If the glyph has an accent we need to build a path for its + // fontChar too, otherwise CanvasGraphics_paintChar will fail. + const accent = glyph.accent; + if (accent?.fontChar) { + buildPath(accent.fontChar); + } + } + } + + static get fallbackFontDict() { + const dict = new Dict(); + dict.set("BaseFont", Name.get("Helvetica")); + dict.set("Type", Name.get("FallbackType")); + dict.set("Subtype", Name.get("FallbackType")); + dict.set("Encoding", Name.get("WinAnsiEncoding")); + + return shadow(this, "fallbackFontDict", dict); + } +} + +class TranslatedFont { + constructor({ loadedName, font, dict, evaluatorOptions }) { + this.loadedName = loadedName; + this.font = font; + this.dict = dict; + this._evaluatorOptions = evaluatorOptions || DefaultPartialEvaluatorOptions; + this.type3Loaded = null; + this.type3Dependencies = font.isType3Font ? new Set() : null; + this.sent = false; + } + + send(handler) { + if (this.sent) { + return; + } + this.sent = true; + + handler.send("commonobj", [ + this.loadedName, + "Font", + this.font.exportData(this._evaluatorOptions.fontExtraProperties), + ]); + } + + fallback(handler) { + if (!this.font.data) { + return; + } + // When font loading failed, fall back to the built-in font renderer. + this.font.disableFontFace = true; + // An arbitrary number of text rendering operators could have been + // encountered between the point in time when the 'Font' message was sent + // to the main-thread, and the point in time when the 'FontFallback' + // message was received on the worker-thread. + // To ensure that all 'FontPath's are available on the main-thread, when + // font loading failed, attempt to resend *all* previously parsed glyphs. + PartialEvaluator.buildFontPaths( + this.font, + /* glyphs = */ this.font.glyphCacheValues, + handler, + this._evaluatorOptions + ); + } + + loadType3Data(evaluator, resources, task) { + if (this.type3Loaded) { + return this.type3Loaded; + } + if (!this.font.isType3Font) { + throw new Error("Must be a Type3 font."); + } + // When parsing Type3 glyphs, always ignore them if there are errors. + // Compared to the parsing of e.g. an entire page, it doesn't really + // make sense to only be able to render a Type3 glyph partially. + const type3Evaluator = evaluator.clone({ ignoreErrors: false }); + // Prevent circular references in Type3 fonts. + const type3FontRefs = new RefSet(evaluator.type3FontRefs); + if (this.dict.objId && !type3FontRefs.has(this.dict.objId)) { + type3FontRefs.put(this.dict.objId); + } + type3Evaluator.type3FontRefs = type3FontRefs; + + const translatedFont = this.font, + type3Dependencies = this.type3Dependencies; + let loadCharProcsPromise = Promise.resolve(); + const charProcs = this.dict.get("CharProcs"); + const fontResources = this.dict.get("Resources") || resources; + const charProcOperatorList = Object.create(null); + + const fontBBox = Util.normalizeRect(translatedFont.bbox || [0, 0, 0, 0]), + width = fontBBox[2] - fontBBox[0], + height = fontBBox[3] - fontBBox[1]; + const fontBBoxSize = Math.hypot(width, height); + + for (const key of charProcs.getKeys()) { + loadCharProcsPromise = loadCharProcsPromise.then(() => { + const glyphStream = charProcs.get(key); + const operatorList = new OperatorList(); + return type3Evaluator + .getOperatorList({ + stream: glyphStream, + task, + resources: fontResources, + operatorList, + }) + .then(() => { + // According to the PDF specification, section "9.6.5 Type 3 Fonts" + // and "Table 113": + // "A glyph description that begins with the d1 operator should + // not execute any operators that set the colour (or other + // colour-related parameters) in the graphics state; + // any use of such operators shall be ignored." + if (operatorList.fnArray[0] === OPS.setCharWidthAndBounds) { + this._removeType3ColorOperators(operatorList, fontBBoxSize); + } + charProcOperatorList[key] = operatorList.getIR(); + + for (const dependency of operatorList.dependencies) { + type3Dependencies.add(dependency); + } + }) + .catch(function (reason) { + warn(`Type3 font resource "${key}" is not available.`); + const dummyOperatorList = new OperatorList(); + charProcOperatorList[key] = dummyOperatorList.getIR(); + }); + }); + } + this.type3Loaded = loadCharProcsPromise.then(() => { + translatedFont.charProcOperatorList = charProcOperatorList; + if (this._bbox) { + translatedFont.isCharBBox = true; + translatedFont.bbox = this._bbox; + } + }); + return this.type3Loaded; + } + + /** + * @private + */ + _removeType3ColorOperators(operatorList, fontBBoxSize = NaN) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + operatorList.fnArray[0] === OPS.setCharWidthAndBounds, + "Type3 glyph shall start with the d1 operator." + ); + } + const charBBox = Util.normalizeRect(operatorList.argsArray[0].slice(2)), + width = charBBox[2] - charBBox[0], + height = charBBox[3] - charBBox[1]; + const charBBoxSize = Math.hypot(width, height); + + if (width === 0 || height === 0) { + // Skip the d1 operator when its bounds are bogus (fixes issue14953.pdf). + operatorList.fnArray.splice(0, 1); + operatorList.argsArray.splice(0, 1); + } else if ( + fontBBoxSize === 0 || + Math.round(charBBoxSize / fontBBoxSize) >= 10 + ) { + // Override the fontBBox when it's undefined/empty, or when it's at least + // (approximately) one order of magnitude smaller than the charBBox + // (fixes issue14999_reduced.pdf). + if (!this._bbox) { + this._bbox = [Infinity, Infinity, -Infinity, -Infinity]; + } + this._bbox[0] = Math.min(this._bbox[0], charBBox[0]); + this._bbox[1] = Math.min(this._bbox[1], charBBox[1]); + this._bbox[2] = Math.max(this._bbox[2], charBBox[2]); + this._bbox[3] = Math.max(this._bbox[3], charBBox[3]); + } + + let i = 0, + ii = operatorList.length; + while (i < ii) { + switch (operatorList.fnArray[i]) { + case OPS.setCharWidthAndBounds: + break; // Handled above. + case OPS.setStrokeColorSpace: + case OPS.setFillColorSpace: + case OPS.setStrokeColor: + case OPS.setStrokeColorN: + case OPS.setFillColor: + case OPS.setFillColorN: + case OPS.setStrokeGray: + case OPS.setFillGray: + case OPS.setStrokeRGBColor: + case OPS.setFillRGBColor: + case OPS.setStrokeCMYKColor: + case OPS.setFillCMYKColor: + case OPS.shadingFill: + case OPS.setRenderingIntent: + operatorList.fnArray.splice(i, 1); + operatorList.argsArray.splice(i, 1); + ii--; + continue; + + case OPS.setGState: + const [gStateObj] = operatorList.argsArray[i]; + let j = 0, + jj = gStateObj.length; + while (j < jj) { + const [gStateKey] = gStateObj[j]; + switch (gStateKey) { + case "TR": + case "TR2": + case "HT": + case "BG": + case "BG2": + case "UCR": + case "UCR2": + gStateObj.splice(j, 1); + jj--; + continue; + } + j++; + } + break; + } + i++; + } + } +} + +class StateManager { + constructor(initialState = new EvalState()) { + this.state = initialState; + this.stateStack = []; + } + + save() { + const old = this.state; + this.stateStack.push(this.state); + this.state = old.clone(); + } + + restore() { + const prev = this.stateStack.pop(); + if (prev) { + this.state = prev; + } + } + + transform(args) { + this.state.ctm = Util.transform(this.state.ctm, args); + } +} + +class TextState { + constructor() { + this.ctm = new Float32Array(IDENTITY_MATRIX); + this.fontName = null; + this.fontSize = 0; + this.loadedName = null; + this.font = null; + this.fontMatrix = FONT_IDENTITY_MATRIX; + this.textMatrix = IDENTITY_MATRIX.slice(); + this.textLineMatrix = IDENTITY_MATRIX.slice(); + this.charSpacing = 0; + this.wordSpacing = 0; + this.leading = 0; + this.textHScale = 1; + this.textRise = 0; + } + + setTextMatrix(a, b, c, d, e, f) { + const m = this.textMatrix; + m[0] = a; + m[1] = b; + m[2] = c; + m[3] = d; + m[4] = e; + m[5] = f; + } + + setTextLineMatrix(a, b, c, d, e, f) { + const m = this.textLineMatrix; + m[0] = a; + m[1] = b; + m[2] = c; + m[3] = d; + m[4] = e; + m[5] = f; + } + + translateTextMatrix(x, y) { + const m = this.textMatrix; + m[4] = m[0] * x + m[2] * y + m[4]; + m[5] = m[1] * x + m[3] * y + m[5]; + } + + translateTextLineMatrix(x, y) { + const m = this.textLineMatrix; + m[4] = m[0] * x + m[2] * y + m[4]; + m[5] = m[1] * x + m[3] * y + m[5]; + } + + carriageReturn() { + this.translateTextLineMatrix(0, -this.leading); + this.textMatrix = this.textLineMatrix.slice(); + } + + clone() { + const clone = Object.create(this); + clone.textMatrix = this.textMatrix.slice(); + clone.textLineMatrix = this.textLineMatrix.slice(); + clone.fontMatrix = this.fontMatrix.slice(); + return clone; + } +} + +class EvalState { + constructor() { + this.ctm = new Float32Array(IDENTITY_MATRIX); + this.font = null; + this.textRenderingMode = TextRenderingMode.FILL; + this._fillColorSpace = ColorSpace.singletons.gray; + this._strokeColorSpace = ColorSpace.singletons.gray; + this.patternFillColorSpace = null; + this.patternStrokeColorSpace = null; + } + + get fillColorSpace() { + return this._fillColorSpace; + } + + set fillColorSpace(colorSpace) { + this._fillColorSpace = this.patternFillColorSpace = colorSpace; + } + + get strokeColorSpace() { + return this._strokeColorSpace; + } + + set strokeColorSpace(colorSpace) { + this._strokeColorSpace = this.patternStrokeColorSpace = colorSpace; + } + + clone() { + return Object.create(this); + } +} + +class EvaluatorPreprocessor { + static get opMap() { + // Specifies properties for each command + // + // If variableArgs === true: [0, `numArgs`] expected + // If variableArgs === false: exactly `numArgs` expected + return shadow( + this, + "opMap", + Object.assign(Object.create(null), { + // Graphic state + w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false }, + J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false }, + j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false }, + M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false }, + d: { id: OPS.setDash, numArgs: 2, variableArgs: false }, + ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false }, + i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false }, + gs: { id: OPS.setGState, numArgs: 1, variableArgs: false }, + q: { id: OPS.save, numArgs: 0, variableArgs: false }, + Q: { id: OPS.restore, numArgs: 0, variableArgs: false }, + cm: { id: OPS.transform, numArgs: 6, variableArgs: false }, + + // Path + m: { id: OPS.moveTo, numArgs: 2, variableArgs: false }, + l: { id: OPS.lineTo, numArgs: 2, variableArgs: false }, + c: { id: OPS.curveTo, numArgs: 6, variableArgs: false }, + v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false }, + y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false }, + h: { id: OPS.closePath, numArgs: 0, variableArgs: false }, + re: { id: OPS.rectangle, numArgs: 4, variableArgs: false }, + S: { id: OPS.stroke, numArgs: 0, variableArgs: false }, + s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false }, + f: { id: OPS.fill, numArgs: 0, variableArgs: false }, + F: { id: OPS.fill, numArgs: 0, variableArgs: false }, + "f*": { id: OPS.eoFill, numArgs: 0, variableArgs: false }, + B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false }, + "B*": { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false }, + b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false }, + "b*": { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false }, + n: { id: OPS.endPath, numArgs: 0, variableArgs: false }, + + // Clipping + W: { id: OPS.clip, numArgs: 0, variableArgs: false }, + "W*": { id: OPS.eoClip, numArgs: 0, variableArgs: false }, + + // Text + BT: { id: OPS.beginText, numArgs: 0, variableArgs: false }, + ET: { id: OPS.endText, numArgs: 0, variableArgs: false }, + Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false }, + Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false }, + Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false }, + TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false }, + Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false }, + Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false }, + Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false }, + Td: { id: OPS.moveText, numArgs: 2, variableArgs: false }, + TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false }, + Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false }, + "T*": { id: OPS.nextLine, numArgs: 0, variableArgs: false }, + Tj: { id: OPS.showText, numArgs: 1, variableArgs: false }, + TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false }, + "'": { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false }, + '"': { + id: OPS.nextLineSetSpacingShowText, + numArgs: 3, + variableArgs: false, + }, + + // Type3 fonts + d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false }, + d1: { + id: OPS.setCharWidthAndBounds, + numArgs: 6, + variableArgs: false, + }, + + // Color + CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false }, + cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false }, + SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true }, + SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true }, + sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true }, + scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true }, + G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false }, + g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false }, + RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false }, + rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false }, + K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false }, + k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false }, + + // Shading + sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false }, + + // Images + BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false }, + ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false }, + EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false }, + + // XObjects + Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false }, + MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false }, + DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false }, + BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false }, + BDC: { + id: OPS.beginMarkedContentProps, + numArgs: 2, + variableArgs: false, + }, + EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false }, + + // Compatibility + BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false }, + EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false }, + + // (reserved partial commands for the lexer) + BM: null, + BD: null, + true: null, + fa: null, + fal: null, + fals: null, + false: null, + nu: null, + nul: null, + null: null, + }) + ); + } + + static MAX_INVALID_PATH_OPS = 10; + + constructor(stream, xref, stateManager = new StateManager()) { + // TODO(mduan): pass array of knownCommands rather than this.opMap + // dictionary + this.parser = new Parser({ + lexer: new Lexer(stream, EvaluatorPreprocessor.opMap), + xref, + }); + this.stateManager = stateManager; + this.nonProcessedArgs = []; + this._isPathOp = false; + this._numInvalidPathOPS = 0; + } + + get savedStatesDepth() { + return this.stateManager.stateStack.length; + } + + // |operation| is an object with two fields: + // + // - |fn| is an out param. + // + // - |args| is an inout param. On entry, it should have one of two values. + // + // - An empty array. This indicates that the caller is providing the + // array in which the args will be stored in. The caller should use + // this value if it can reuse a single array for each call to read(). + // + // - |null|. This indicates that the caller needs this function to create + // the array in which any args are stored in. If there are zero args, + // this function will leave |operation.args| as |null| (thus avoiding + // allocations that would occur if we used an empty array to represent + // zero arguments). Otherwise, it will replace |null| with a new array + // containing the arguments. The caller should use this value if it + // cannot reuse an array for each call to read(). + // + // These two modes are present because this function is very hot and so + // avoiding allocations where possible is worthwhile. + // + read(operation) { + let args = operation.args; + while (true) { + const obj = this.parser.getObj(); + if (obj instanceof Cmd) { + const cmd = obj.cmd; + // Check that the command is valid + const opSpec = EvaluatorPreprocessor.opMap[cmd]; + if (!opSpec) { + warn(`Unknown command "${cmd}".`); + continue; + } + + const fn = opSpec.id; + const numArgs = opSpec.numArgs; + let argsLength = args !== null ? args.length : 0; + + // If the *previous* command wasn't a path operator, reset the heuristic + // used with incomplete path operators below (fixes issue14917.pdf). + if (!this._isPathOp) { + this._numInvalidPathOPS = 0; + } + this._isPathOp = fn >= OPS.moveTo && fn <= OPS.endPath; + + if (!opSpec.variableArgs) { + // Postscript commands can be nested, e.g. /F2 /GS2 gs 5.711 Tf + if (argsLength !== numArgs) { + const nonProcessedArgs = this.nonProcessedArgs; + while (argsLength > numArgs) { + nonProcessedArgs.push(args.shift()); + argsLength--; + } + while (argsLength < numArgs && nonProcessedArgs.length !== 0) { + if (args === null) { + args = []; + } + args.unshift(nonProcessedArgs.pop()); + argsLength++; + } + } + + if (argsLength < numArgs) { + const partialMsg = + `command ${cmd}: expected ${numArgs} args, ` + + `but received ${argsLength} args.`; + + // Incomplete path operators, in particular, can result in fairly + // chaotic rendering artifacts. Hence the following heuristics is + // used to error, rather than just warn, once a number of invalid + // path operators have been encountered (fixes bug1443140.pdf). + if ( + this._isPathOp && + ++this._numInvalidPathOPS > + EvaluatorPreprocessor.MAX_INVALID_PATH_OPS + ) { + throw new FormatError(`Invalid ${partialMsg}`); + } + // If we receive too few arguments, it's not possible to execute + // the command, hence we skip the command. + warn(`Skipping ${partialMsg}`); + if (args !== null) { + args.length = 0; + } + continue; + } + } else if (argsLength > numArgs) { + info( + `Command ${cmd}: expected [0, ${numArgs}] args, ` + + `but received ${argsLength} args.` + ); + } + + // TODO figure out how to type-check vararg functions + this.preprocessCommand(fn, args); + + operation.fn = fn; + operation.args = args; + return true; + } + if (obj === EOF) { + return false; // no more commands + } + // argument + if (obj !== null) { + if (args === null) { + args = []; + } + args.push(obj); + if (args.length > 33) { + throw new FormatError("Too many arguments"); + } + } + } + } + + preprocessCommand(fn, args) { + switch (fn | 0) { + case OPS.save: + this.stateManager.save(); + break; + case OPS.restore: + this.stateManager.restore(); + break; + case OPS.transform: + this.stateManager.transform(args); + break; + } + } +} + +export { EvaluatorPreprocessor, PartialEvaluator }; diff --git a/frontend/public/pdf.js/src/core/file_spec.js b/frontend/public/pdf.js/src/core/file_spec.js new file mode 100644 index 0000000..003ae64 --- /dev/null +++ b/frontend/public/pdf.js/src/core/file_spec.js @@ -0,0 +1,130 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { shadow, stringToPDFString, warn } from "../shared/util.js"; +import { BaseStream } from "./base_stream.js"; +import { Dict } from "./primitives.js"; + +function pickPlatformItem(dict) { + if (!(dict instanceof Dict)) { + return null; + } + // Look for the filename in this order: + // UF, F, Unix, Mac, DOS + if (dict.has("UF")) { + return dict.get("UF"); + } else if (dict.has("F")) { + return dict.get("F"); + } else if (dict.has("Unix")) { + return dict.get("Unix"); + } else if (dict.has("Mac")) { + return dict.get("Mac"); + } else if (dict.has("DOS")) { + return dict.get("DOS"); + } + return null; +} + +function stripPath(str) { + return str.substring(str.lastIndexOf("/") + 1); +} + +/** + * "A PDF file can refer to the contents of another file by using a File + * Specification (PDF 1.1)", see the spec (7.11) for more details. + * NOTE: Only embedded files are supported (as part of the attachments support) + * TODO: support the 'URL' file system (with caching if !/V), portable + * collections attributes and related files (/RF) + */ +class FileSpec { + #contentAvailable = false; + + constructor(root, xref, skipContent = false) { + if (!(root instanceof Dict)) { + return; + } + this.xref = xref; + this.root = root; + if (root.has("FS")) { + this.fs = root.get("FS"); + } + if (root.has("RF")) { + warn("Related file specifications are not supported"); + } + if (!skipContent) { + if (root.has("EF")) { + this.#contentAvailable = true; + } else { + warn("Non-embedded file specifications are not supported"); + } + } + } + + get filename() { + let filename = ""; + + const item = pickPlatformItem(this.root); + if (item && typeof item === "string") { + filename = stringToPDFString(item) + .replaceAll("\\\\", "\\") + .replaceAll("\\/", "/") + .replaceAll("\\", "/"); + } + return shadow(this, "filename", filename || "unnamed"); + } + + get content() { + if (!this.#contentAvailable) { + return null; + } + this._contentRef ||= pickPlatformItem(this.root?.get("EF")); + + let content = null; + if (this._contentRef) { + const fileObj = this.xref.fetchIfRef(this._contentRef); + if (fileObj instanceof BaseStream) { + content = fileObj.getBytes(); + } else { + warn( + "Embedded file specification points to non-existing/invalid content" + ); + } + } else { + warn("Embedded file specification does not have any content"); + } + return content; + } + + get description() { + let description = ""; + + const desc = this.root?.get("Desc"); + if (desc && typeof desc === "string") { + description = stringToPDFString(desc); + } + return shadow(this, "description", description); + } + + get serializable() { + return { + rawFilename: this.filename, + filename: stripPath(this.filename), + content: this.content, + description: this.description, + }; + } +} + +export { FileSpec }; diff --git a/frontend/public/pdf.js/src/core/flate_stream.js b/frontend/public/pdf.js/src/core/flate_stream.js new file mode 100644 index 0000000..90bcefb --- /dev/null +++ b/frontend/public/pdf.js/src/core/flate_stream.js @@ -0,0 +1,477 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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. + */ +/* Copyright 1996-2003 Glyph & Cog, LLC + * + * The flate stream implementation contained in this file is a JavaScript port + * of XPDF's implementation, made available under the Apache 2.0 open source + * license. + */ + +import { FormatError, info } from "../shared/util.js"; +import { DecodeStream } from "./decode_stream.js"; +import { Stream } from "./stream.js"; + +const codeLenCodeMap = new Int32Array([ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, +]); + +const lengthDecode = new Int32Array([ + 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, + 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, + 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, + 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102, +]); + +const distDecode = new Int32Array([ + 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, + 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, + 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, + 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001, +]); + +const fixedLitCodeTab = [ + new Int32Array([ + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, + 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, + 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, + 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, + 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, + 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, + 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, + 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, + 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, + 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, + 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, + 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, + 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, + 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, + 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, + 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, + 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, + 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, + 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, + 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, + 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, + 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, + 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, + 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, + 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, + 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, + 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, + 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, + 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, + 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, + 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, + 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, + 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, + 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, + 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, + 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, + 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, + 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, + 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, + 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, + 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, + 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, + 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, + 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, + 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, + 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, + 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, + 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, + 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff, + ]), + 9, +]; + +const fixedDistCodeTab = [ + new Int32Array([ + 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, + 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, + 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, + 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000, + ]), + 5, +]; + +class FlateStream extends DecodeStream { + constructor(str, maybeLength) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + + const cmf = str.getByte(); + const flg = str.getByte(); + if (cmf === -1 || flg === -1) { + throw new FormatError(`Invalid header in flate stream: ${cmf}, ${flg}`); + } + if ((cmf & 0x0f) !== 0x08) { + throw new FormatError( + `Unknown compression method in flate stream: ${cmf}, ${flg}` + ); + } + if (((cmf << 8) + flg) % 31 !== 0) { + throw new FormatError(`Bad FCHECK in flate stream: ${cmf}, ${flg}`); + } + if (flg & 0x20) { + throw new FormatError(`FDICT bit set in flate stream: ${cmf}, ${flg}`); + } + + this.codeSize = 0; + this.codeBuf = 0; + } + + async getImageData(length, _decoderOptions) { + const data = await this.asyncGetBytes(); + return data?.subarray(0, length) || this.getBytes(length); + } + + async asyncGetBytes() { + this.str.reset(); + const bytes = this.str.getBytes(); + + try { + const { readable, writable } = new DecompressionStream("deflate"); + const writer = writable.getWriter(); + await writer.ready; + + // We can't await writer.write() because it'll block until the reader + // starts which happens few lines below. + writer + .write(bytes) + .then(async () => { + await writer.ready; + await writer.close(); + }) + .catch(() => {}); + + const chunks = []; + let totalLength = 0; + + for await (const chunk of readable) { + chunks.push(chunk); + totalLength += chunk.byteLength; + } + const data = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + data.set(chunk, offset); + offset += chunk.byteLength; + } + + return data; + } catch { + // DecompressionStream failed (for example because there are some extra + // bytes after the end of the compressed data), so we fallback to our + // decoder. + // We already get the bytes from the underlying stream, so we just reuse + // them to avoid get them again. + this.str = new Stream( + bytes, + 2 /* = header size (see ctor) */, + bytes.length, + this.str.dict + ); + this.reset(); + return null; + } + } + + get isAsync() { + return true; + } + + getBits(bits) { + const str = this.str; + let codeSize = this.codeSize; + let codeBuf = this.codeBuf; + + let b; + while (codeSize < bits) { + if ((b = str.getByte()) === -1) { + throw new FormatError("Bad encoding in flate stream"); + } + codeBuf |= b << codeSize; + codeSize += 8; + } + b = codeBuf & ((1 << bits) - 1); + this.codeBuf = codeBuf >> bits; + this.codeSize = codeSize -= bits; + + return b; + } + + getCode(table) { + const str = this.str; + const codes = table[0]; + const maxLen = table[1]; + let codeSize = this.codeSize; + let codeBuf = this.codeBuf; + + let b; + while (codeSize < maxLen) { + if ((b = str.getByte()) === -1) { + // premature end of stream. code might however still be valid. + // codeSize < codeLen check below guards against incomplete codeVal. + break; + } + codeBuf |= b << codeSize; + codeSize += 8; + } + const code = codes[codeBuf & ((1 << maxLen) - 1)]; + const codeLen = code >> 16; + const codeVal = code & 0xffff; + if (codeLen < 1 || codeSize < codeLen) { + throw new FormatError("Bad encoding in flate stream"); + } + this.codeBuf = codeBuf >> codeLen; + this.codeSize = codeSize - codeLen; + return codeVal; + } + + generateHuffmanTable(lengths) { + const n = lengths.length; + + // find max code length + let maxLen = 0; + let i; + for (i = 0; i < n; ++i) { + if (lengths[i] > maxLen) { + maxLen = lengths[i]; + } + } + + // build the table + const size = 1 << maxLen; + const codes = new Int32Array(size); + for ( + let len = 1, code = 0, skip = 2; + len <= maxLen; + ++len, code <<= 1, skip <<= 1 + ) { + for (let val = 0; val < n; ++val) { + if (lengths[val] === len) { + // bit-reverse the code + let code2 = 0; + let t = code; + for (i = 0; i < len; ++i) { + code2 = (code2 << 1) | (t & 1); + t >>= 1; + } + + // fill the table entries + for (i = code2; i < size; i += skip) { + codes[i] = (len << 16) | val; + } + ++code; + } + } + } + + return [codes, maxLen]; + } + + #endsStreamOnError(err) { + info(err); + this.eof = true; + } + + readBlock() { + let buffer, hdr, len; + const str = this.str; + // read block header + try { + hdr = this.getBits(3); + } catch (ex) { + this.#endsStreamOnError(ex.message); + return; + } + if (hdr & 1) { + this.eof = true; + } + hdr >>= 1; + + if (hdr === 0) { + // uncompressed block + let b; + + if ((b = str.getByte()) === -1) { + this.#endsStreamOnError("Bad block header in flate stream"); + return; + } + let blockLen = b; + if ((b = str.getByte()) === -1) { + this.#endsStreamOnError("Bad block header in flate stream"); + return; + } + blockLen |= b << 8; + if ((b = str.getByte()) === -1) { + this.#endsStreamOnError("Bad block header in flate stream"); + return; + } + let check = b; + if ((b = str.getByte()) === -1) { + this.#endsStreamOnError("Bad block header in flate stream"); + return; + } + check |= b << 8; + if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) { + // Ignoring error for bad "empty" block (see issue 1277) + throw new FormatError("Bad uncompressed block length in flate stream"); + } + + this.codeBuf = 0; + this.codeSize = 0; + + const bufferLength = this.bufferLength, + end = bufferLength + blockLen; + buffer = this.ensureBuffer(end); + this.bufferLength = end; + + if (blockLen === 0) { + if (str.peekByte() === -1) { + this.eof = true; + } + } else { + const block = str.getBytes(blockLen); + buffer.set(block, bufferLength); + if (block.length < blockLen) { + this.eof = true; + } + } + return; + } + + let litCodeTable; + let distCodeTable; + if (hdr === 1) { + // compressed block, fixed codes + litCodeTable = fixedLitCodeTab; + distCodeTable = fixedDistCodeTab; + } else if (hdr === 2) { + // compressed block, dynamic codes + const numLitCodes = this.getBits(5) + 257; + const numDistCodes = this.getBits(5) + 1; + const numCodeLenCodes = this.getBits(4) + 4; + + // build the code lengths code table + const codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length); + + let i; + for (i = 0; i < numCodeLenCodes; ++i) { + codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3); + } + const codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); + + // build the literal and distance code tables + len = 0; + i = 0; + const codes = numLitCodes + numDistCodes; + const codeLengths = new Uint8Array(codes); + let bitsLength, bitsOffset, what; + while (i < codes) { + const code = this.getCode(codeLenCodeTab); + if (code === 16) { + bitsLength = 2; + bitsOffset = 3; + what = len; + } else if (code === 17) { + bitsLength = 3; + bitsOffset = 3; + what = len = 0; + } else if (code === 18) { + bitsLength = 7; + bitsOffset = 11; + what = len = 0; + } else { + codeLengths[i++] = len = code; + continue; + } + + let repeatLength = this.getBits(bitsLength) + bitsOffset; + while (repeatLength-- > 0) { + codeLengths[i++] = what; + } + } + + litCodeTable = this.generateHuffmanTable( + codeLengths.subarray(0, numLitCodes) + ); + distCodeTable = this.generateHuffmanTable( + codeLengths.subarray(numLitCodes, codes) + ); + } else { + throw new FormatError("Unknown block type in flate stream"); + } + + buffer = this.buffer; + let limit = buffer ? buffer.length : 0; + let pos = this.bufferLength; + while (true) { + let code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; + } + buffer[pos++] = code1; + continue; + } + if (code1 === 256) { + this.bufferLength = pos; + return; + } + code1 -= 257; + code1 = lengthDecode[code1]; + let code2 = code1 >> 16; + if (code2 > 0) { + code2 = this.getBits(code2); + } + len = (code1 & 0xffff) + code2; + code1 = this.getCode(distCodeTable); + code1 = distDecode[code1]; + code2 = code1 >> 16; + if (code2 > 0) { + code2 = this.getBits(code2); + } + const dist = (code1 & 0xffff) + code2; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; + } + for (let k = 0; k < len; ++k, ++pos) { + buffer[pos] = buffer[pos - dist]; + } + } + } +} + +export { FlateStream }; diff --git a/frontend/public/pdf.js/src/core/font_renderer.js b/frontend/public/pdf.js/src/core/font_renderer.js new file mode 100644 index 0000000..0cd2798 --- /dev/null +++ b/frontend/public/pdf.js/src/core/font_renderer.js @@ -0,0 +1,937 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + assert, + bytesToString, + FONT_IDENTITY_MATRIX, + FormatError, + unreachable, + Util, + warn, +} from "../shared/util.js"; +import { + isNumberArray, + readInt16, + readInt8, + readUint16, + readUint32, +} from "./core_utils.js"; +import { CFFParser } from "./cff_parser.js"; +import { getGlyphsUnicode } from "./glyphlist.js"; +import { StandardEncoding } from "./encodings.js"; +import { Stream } from "./stream.js"; + +// TODO: use DataView and its methods. + +function getFloat214(data, offset) { + return readInt16(data, offset) / 16384; +} + +function getSubroutineBias(subrs) { + const numSubrs = subrs.length; + let bias = 32768; + if (numSubrs < 1240) { + bias = 107; + } else if (numSubrs < 33900) { + bias = 1131; + } + return bias; +} + +function parseCmap(data, start, end) { + const offset = + readUint16(data, start + 2) === 1 + ? readUint32(data, start + 8) + : readUint32(data, start + 16); + const format = readUint16(data, start + offset); + let ranges, p, i; + if (format === 4) { + readUint16(data, start + offset + 2); // length + const segCount = readUint16(data, start + offset + 6) >> 1; + p = start + offset + 14; + ranges = []; + for (i = 0; i < segCount; i++, p += 2) { + ranges[i] = { end: readUint16(data, p) }; + } + p += 2; + for (i = 0; i < segCount; i++, p += 2) { + ranges[i].start = readUint16(data, p); + } + for (i = 0; i < segCount; i++, p += 2) { + ranges[i].idDelta = readUint16(data, p); + } + for (i = 0; i < segCount; i++, p += 2) { + let idOffset = readUint16(data, p); + if (idOffset === 0) { + continue; + } + ranges[i].ids = []; + for (let j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) { + ranges[i].ids[j] = readUint16(data, p + idOffset); + idOffset += 2; + } + } + return ranges; + } else if (format === 12) { + const groups = readUint32(data, start + offset + 12); + p = start + offset + 16; + ranges = []; + for (i = 0; i < groups; i++) { + start = readUint32(data, p); + ranges.push({ + start, + end: readUint32(data, p + 4), + idDelta: readUint32(data, p + 8) - start, + }); + p += 12; + } + return ranges; + } + throw new FormatError(`unsupported cmap: ${format}`); +} + +function parseCff(data, start, end, seacAnalysisEnabled) { + const properties = {}; + const parser = new CFFParser( + new Stream(data, start, end - start), + properties, + seacAnalysisEnabled + ); + const cff = parser.parse(); + return { + glyphs: cff.charStrings.objects, + subrs: cff.topDict.privateDict?.subrsIndex?.objects, + gsubrs: cff.globalSubrIndex?.objects, + isCFFCIDFont: cff.isCIDFont, + fdSelect: cff.fdSelect, + fdArray: cff.fdArray, + }; +} + +function parseGlyfTable(glyf, loca, isGlyphLocationsLong) { + let itemSize, itemDecode; + if (isGlyphLocationsLong) { + itemSize = 4; + itemDecode = readUint32; + } else { + itemSize = 2; + itemDecode = (data, offset) => 2 * readUint16(data, offset); + } + const glyphs = []; + let startOffset = itemDecode(loca, 0); + for (let j = itemSize; j < loca.length; j += itemSize) { + const endOffset = itemDecode(loca, j); + glyphs.push(glyf.subarray(startOffset, endOffset)); + startOffset = endOffset; + } + return glyphs; +} + +function lookupCmap(ranges, unicode) { + const code = unicode.codePointAt(0); + let gid = 0, + l = 0, + r = ranges.length - 1; + while (l < r) { + const c = (l + r + 1) >> 1; + if (code < ranges[c].start) { + r = c - 1; + } else { + l = c; + } + } + if (ranges[l].start <= code && code <= ranges[l].end) { + gid = + (ranges[l].idDelta + + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code)) & + 0xffff; + } + return { + charCode: code, + glyphId: gid, + }; +} + +function compileGlyf(code, cmds, font) { + function moveTo(x, y) { + if (firstPoint) { + // Close the current subpath in adding a straight line to the first point. + cmds.add("L", firstPoint); + } + firstPoint = [x, y]; + cmds.add("M", [x, y]); + } + function lineTo(x, y) { + cmds.add("L", [x, y]); + } + function quadraticCurveTo(xa, ya, x, y) { + cmds.add("Q", [xa, ya, x, y]); + } + + let i = 0; + const numberOfContours = readInt16(code, i); + let flags; + let firstPoint = null; + let x = 0, + y = 0; + i += 10; + if (numberOfContours < 0) { + // composite glyph + do { + flags = readUint16(code, i); + const glyphIndex = readUint16(code, i + 2); + i += 4; + let arg1, arg2; + if (flags & 0x01) { + if (flags & 0x02) { + arg1 = readInt16(code, i); + arg2 = readInt16(code, i + 2); + } else { + arg1 = readUint16(code, i); + arg2 = readUint16(code, i + 2); + } + i += 4; + } else if (flags & 0x02) { + arg1 = readInt8(code, i++); + arg2 = readInt8(code, i++); + } else { + arg1 = code[i++]; + arg2 = code[i++]; + } + if (flags & 0x02) { + x = arg1; + y = arg2; + } else { + x = 0; + y = 0; + } + let scaleX = 1, + scaleY = 1, + scale01 = 0, + scale10 = 0; + if (flags & 0x08) { + scaleX = scaleY = getFloat214(code, i); + i += 2; + } else if (flags & 0x40) { + scaleX = getFloat214(code, i); + scaleY = getFloat214(code, i + 2); + i += 4; + } else if (flags & 0x80) { + scaleX = getFloat214(code, i); + scale01 = getFloat214(code, i + 2); + scale10 = getFloat214(code, i + 4); + scaleY = getFloat214(code, i + 6); + i += 8; + } + const subglyph = font.glyphs[glyphIndex]; + if (subglyph) { + // TODO: the transform should be applied only if there is a scale: + // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1205 + cmds.save(); + cmds.transform([scaleX, scale01, scale10, scaleY, x, y]); + + if (!(flags & 0x02)) { + // TODO: we must use arg1 and arg2 to make something similar to: + // https://github.com/freetype/freetype/blob/edd4fedc5427cf1cf1f4b045e53ff91eb282e9d4/src/truetype/ttgload.c#L1209 + } + compileGlyf(subglyph, cmds, font); + cmds.restore(); + } + } while (flags & 0x20); + } else { + // simple glyph + const endPtsOfContours = []; + let j, jj; + for (j = 0; j < numberOfContours; j++) { + endPtsOfContours.push(readUint16(code, i)); + i += 2; + } + const instructionLength = readUint16(code, i); + i += 2 + instructionLength; // skipping the instructions + const numberOfPoints = endPtsOfContours.at(-1) + 1; + const points = []; + while (points.length < numberOfPoints) { + flags = code[i++]; + let repeat = 1; + if (flags & 0x08) { + repeat += code[i++]; + } + while (repeat-- > 0) { + points.push({ flags }); + } + } + for (j = 0; j < numberOfPoints; j++) { + switch (points[j].flags & 0x12) { + case 0x00: + x += readInt16(code, i); + i += 2; + break; + case 0x02: + x -= code[i++]; + break; + case 0x12: + x += code[i++]; + break; + } + points[j].x = x; + } + for (j = 0; j < numberOfPoints; j++) { + switch (points[j].flags & 0x24) { + case 0x00: + y += readInt16(code, i); + i += 2; + break; + case 0x04: + y -= code[i++]; + break; + case 0x24: + y += code[i++]; + break; + } + points[j].y = y; + } + + let startPoint = 0; + for (i = 0; i < numberOfContours; i++) { + const endPoint = endPtsOfContours[i]; + // contours might have implicit points, which is located in the middle + // between two neighboring off-curve points + const contour = points.slice(startPoint, endPoint + 1); + if (contour[0].flags & 1) { + contour.push(contour[0]); // using start point at the contour end + } else if (contour.at(-1).flags & 1) { + // first is off-curve point, trying to use one from the end + contour.unshift(contour.at(-1)); + } else { + // start and end are off-curve points, creating implicit one + const p = { + flags: 1, + x: (contour[0].x + contour.at(-1).x) / 2, + y: (contour[0].y + contour.at(-1).y) / 2, + }; + contour.unshift(p); + contour.push(p); + } + moveTo(contour[0].x, contour[0].y); + for (j = 1, jj = contour.length; j < jj; j++) { + if (contour[j].flags & 1) { + lineTo(contour[j].x, contour[j].y); + } else if (contour[j + 1].flags & 1) { + quadraticCurveTo( + contour[j].x, + contour[j].y, + contour[j + 1].x, + contour[j + 1].y + ); + j++; + } else { + quadraticCurveTo( + contour[j].x, + contour[j].y, + (contour[j].x + contour[j + 1].x) / 2, + (contour[j].y + contour[j + 1].y) / 2 + ); + } + } + startPoint = endPoint + 1; + } + } +} + +function compileCharString(charStringCode, cmds, font, glyphId) { + function moveTo(x, y) { + if (firstPoint) { + // Close the current subpath in adding a straight line to the first point. + cmds.add("L", firstPoint); + } + firstPoint = [x, y]; + cmds.add("M", [x, y]); + } + function lineTo(x, y) { + cmds.add("L", [x, y]); + } + function bezierCurveTo(x1, y1, x2, y2, x, y) { + cmds.add("C", [x1, y1, x2, y2, x, y]); + } + + const stack = []; + let x = 0, + y = 0; + let stems = 0; + let firstPoint = null; + + function parse(code) { + let i = 0; + while (i < code.length) { + let stackClean = false; + let v = code[i++]; + let xa, xb, ya, yb, y1, y2, y3, n, subrCode; + switch (v) { + case 1: // hstem + stems += stack.length >> 1; + stackClean = true; + break; + case 3: // vstem + stems += stack.length >> 1; + stackClean = true; + break; + case 4: // vmoveto + y += stack.pop(); + moveTo(x, y); + stackClean = true; + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + lineTo(x, y); + } + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + lineTo(x, y); + if (stack.length === 0) { + break; + } + y += stack.shift(); + lineTo(x, y); + } + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + lineTo(x, y); + if (stack.length === 0) { + break; + } + x += stack.shift(); + lineTo(x, y); + } + break; + case 8: // rrcurveto + while (stack.length > 0) { + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + } + break; + case 10: // callsubr + n = stack.pop(); + subrCode = null; + if (font.isCFFCIDFont) { + const fdIndex = font.fdSelect.getFDIndex(glyphId); + if (fdIndex >= 0 && fdIndex < font.fdArray.length) { + const fontDict = font.fdArray[fdIndex]; + let subrs; + if (fontDict.privateDict?.subrsIndex) { + subrs = fontDict.privateDict.subrsIndex.objects; + } + if (subrs) { + // Add subroutine bias. + n += getSubroutineBias(subrs); + subrCode = subrs[n]; + } + } else { + warn("Invalid fd index for glyph index."); + } + } else { + subrCode = font.subrs[n + font.subrsBias]; + } + if (subrCode) { + parse(subrCode); + } + break; + case 11: // return + return; + case 12: + v = code[i++]; + switch (v) { + case 34: // flex + xa = x + stack.shift(); + xb = xa + stack.shift(); + y1 = y + stack.shift(); + x = xb + stack.shift(); + bezierCurveTo(xa, y, xb, y1, x, y1); + xa = x + stack.shift(); + xb = xa + stack.shift(); + x = xb + stack.shift(); + bezierCurveTo(xa, y1, xb, y, x, y); + break; + case 35: // flex + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + stack.pop(); // fd + break; + case 36: // hflex1 + xa = x + stack.shift(); + y1 = y + stack.shift(); + xb = xa + stack.shift(); + y2 = y1 + stack.shift(); + x = xb + stack.shift(); + bezierCurveTo(xa, y1, xb, y2, x, y2); + xa = x + stack.shift(); + xb = xa + stack.shift(); + y3 = y2 + stack.shift(); + x = xb + stack.shift(); + bezierCurveTo(xa, y2, xb, y3, x, y); + break; + case 37: // flex1 + const x0 = x, + y0 = y; + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb; + y = yb; + if (Math.abs(x - x0) > Math.abs(y - y0)) { + x += stack.shift(); + } else { + y += stack.shift(); + } + bezierCurveTo(xa, ya, xb, yb, x, y); + break; + default: + throw new FormatError(`unknown operator: 12 ${v}`); + } + break; + case 14: // endchar + if (stack.length >= 4) { + const achar = stack.pop(); + const bchar = stack.pop(); + y = stack.pop(); + x = stack.pop(); + cmds.save(); + cmds.translate(x, y); + let cmap = lookupCmap( + font.cmap, + String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]) + ); + compileCharString( + font.glyphs[cmap.glyphId], + cmds, + font, + cmap.glyphId + ); + cmds.restore(); + + cmap = lookupCmap( + font.cmap, + String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]]) + ); + compileCharString( + font.glyphs[cmap.glyphId], + cmds, + font, + cmap.glyphId + ); + } + return; + case 18: // hstemhm + stems += stack.length >> 1; + stackClean = true; + break; + case 19: // hintmask + stems += stack.length >> 1; + i += (stems + 7) >> 3; + stackClean = true; + break; + case 20: // cntrmask + stems += stack.length >> 1; + i += (stems + 7) >> 3; + stackClean = true; + break; + case 21: // rmoveto + y += stack.pop(); + x += stack.pop(); + moveTo(x, y); + stackClean = true; + break; + case 22: // hmoveto + x += stack.pop(); + moveTo(x, y); + stackClean = true; + break; + case 23: // vstemhm + stems += stack.length >> 1; + stackClean = true; + break; + case 24: // rcurveline + while (stack.length > 2) { + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + } + x += stack.shift(); + y += stack.shift(); + lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + lineTo(x, y); + } + xa = x + stack.shift(); + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + while (stack.length > 0) { + xa = x; + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb; + y = yb + stack.shift(); + bezierCurveTo(xa, ya, xb, yb, x, y); + } + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + while (stack.length > 0) { + xa = x + stack.shift(); + ya = y; + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb; + bezierCurveTo(xa, ya, xb, yb, x, y); + } + break; + case 28: + stack.push(readInt16(code, i)); + i += 2; + break; + case 29: // callgsubr + n = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[n]; + if (subrCode) { + parse(subrCode); + } + break; + case 30: // vhcurveto + while (stack.length > 0) { + xa = x; + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + (stack.length === 1 ? stack.shift() : 0); + bezierCurveTo(xa, ya, xb, yb, x, y); + if (stack.length === 0) { + break; + } + + xa = x + stack.shift(); + ya = y; + xb = xa + stack.shift(); + yb = ya + stack.shift(); + y = yb + stack.shift(); + x = xb + (stack.length === 1 ? stack.shift() : 0); + bezierCurveTo(xa, ya, xb, yb, x, y); + } + break; + case 31: // hvcurveto + while (stack.length > 0) { + xa = x + stack.shift(); + ya = y; + xb = xa + stack.shift(); + yb = ya + stack.shift(); + y = yb + stack.shift(); + x = xb + (stack.length === 1 ? stack.shift() : 0); + bezierCurveTo(xa, ya, xb, yb, x, y); + if (stack.length === 0) { + break; + } + + xa = x; + ya = y + stack.shift(); + xb = xa + stack.shift(); + yb = ya + stack.shift(); + x = xb + stack.shift(); + y = yb + (stack.length === 1 ? stack.shift() : 0); + bezierCurveTo(xa, ya, xb, yb, x, y); + } + break; + default: + if (v < 32) { + throw new FormatError(`unknown operator: ${v}`); + } + if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + stack.push((v - 247) * 256 + code[i++] + 108); + } else if (v < 255) { + stack.push(-(v - 251) * 256 - code[i++] - 108); + } else { + stack.push( + ((code[i] << 24) | + (code[i + 1] << 16) | + (code[i + 2] << 8) | + code[i + 3]) / + 65536 + ); + i += 4; + } + break; + } + if (stackClean) { + stack.length = 0; + } + } + } + parse(charStringCode); +} + +const NOOP = ""; + +class Commands { + cmds = []; + + transformStack = []; + + currentTransform = [1, 0, 0, 1, 0, 0]; + + add(cmd, args) { + if (args) { + const [a, b, c, d, e, f] = this.currentTransform; + for (let i = 0, ii = args.length; i < ii; i += 2) { + const x = args[i]; + const y = args[i + 1]; + args[i] = a * x + c * y + e; + args[i + 1] = b * x + d * y + f; + } + this.cmds.push(`${cmd}${args.join(" ")}`); + } else { + this.cmds.push(cmd); + } + } + + transform(transf) { + this.currentTransform = Util.transform(this.currentTransform, transf); + } + + translate(x, y) { + this.transform([1, 0, 0, 1, x, y]); + } + + save() { + this.transformStack.push(this.currentTransform.slice()); + } + + restore() { + this.currentTransform = this.transformStack.pop() || [1, 0, 0, 1, 0, 0]; + } + + getSVG() { + return this.cmds.join(""); + } +} + +class CompiledFont { + constructor(fontMatrix) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === CompiledFont + ) { + unreachable("Cannot initialize CompiledFont."); + } + this.fontMatrix = fontMatrix; + + this.compiledGlyphs = Object.create(null); + this.compiledCharCodeToGlyphId = Object.create(null); + } + + getPathJs(unicode) { + const { charCode, glyphId } = lookupCmap(this.cmap, unicode); + let fn = this.compiledGlyphs[glyphId], + compileEx; + if (fn === undefined) { + try { + fn = this.compileGlyph(this.glyphs[glyphId], glyphId); + } catch (ex) { + fn = NOOP; // Avoid attempting to re-compile a corrupt glyph. + + compileEx = ex; + } + this.compiledGlyphs[glyphId] = fn; + } + this.compiledCharCodeToGlyphId[charCode] ??= glyphId; + + if (compileEx) { + throw compileEx; + } + return fn; + } + + compileGlyph(code, glyphId) { + if (!code?.length || code[0] === 14) { + return NOOP; + } + + let fontMatrix = this.fontMatrix; + if (this.isCFFCIDFont) { + // Top DICT's FontMatrix can be ignored because CFFCompiler always + // removes it and copies to FDArray DICTs. + const fdIndex = this.fdSelect.getFDIndex(glyphId); + if (fdIndex >= 0 && fdIndex < this.fdArray.length) { + const fontDict = this.fdArray[fdIndex]; + fontMatrix = fontDict.getByName("FontMatrix") || FONT_IDENTITY_MATRIX; + } else { + warn("Invalid fd index for glyph index."); + } + } + assert(isNumberArray(fontMatrix, 6), "Expected a valid fontMatrix."); + + const cmds = new Commands(); + cmds.transform(fontMatrix.slice()); + this.compileGlyphImpl(code, cmds, glyphId); + cmds.add("Z"); + + return cmds.getSVG(); + } + + compileGlyphImpl() { + unreachable("Children classes should implement this."); + } + + hasBuiltPath(unicode) { + const { charCode, glyphId } = lookupCmap(this.cmap, unicode); + return ( + this.compiledGlyphs[glyphId] !== undefined && + this.compiledCharCodeToGlyphId[charCode] !== undefined + ); + } +} + +class TrueTypeCompiled extends CompiledFont { + constructor(glyphs, cmap, fontMatrix) { + super(fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0]); + + this.glyphs = glyphs; + this.cmap = cmap; + } + + compileGlyphImpl(code, cmds) { + compileGlyf(code, cmds, this); + } +} + +class Type2Compiled extends CompiledFont { + constructor(cffInfo, cmap, fontMatrix) { + super(fontMatrix || [0.001, 0, 0, 0.001, 0, 0]); + + this.glyphs = cffInfo.glyphs; + this.gsubrs = cffInfo.gsubrs || []; + this.subrs = cffInfo.subrs || []; + this.cmap = cmap; + this.glyphNameMap = getGlyphsUnicode(); + + this.gsubrsBias = getSubroutineBias(this.gsubrs); + this.subrsBias = getSubroutineBias(this.subrs); + + this.isCFFCIDFont = cffInfo.isCFFCIDFont; + this.fdSelect = cffInfo.fdSelect; + this.fdArray = cffInfo.fdArray; + } + + compileGlyphImpl(code, cmds, glyphId) { + compileCharString(code, cmds, this, glyphId); + } +} + +class FontRendererFactory { + static create(font, seacAnalysisEnabled) { + const data = new Uint8Array(font.data); + let cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm; + const numTables = readUint16(data, 4); + for (let i = 0, p = 12; i < numTables; i++, p += 16) { + const tag = bytesToString(data.subarray(p, p + 4)); + const offset = readUint32(data, p + 8); + const length = readUint32(data, p + 12); + switch (tag) { + case "cmap": + cmap = parseCmap(data, offset, offset + length); + break; + case "glyf": + glyf = data.subarray(offset, offset + length); + break; + case "loca": + loca = data.subarray(offset, offset + length); + break; + case "head": + unitsPerEm = readUint16(data, offset + 18); + indexToLocFormat = readUint16(data, offset + 50); + break; + case "CFF ": + cff = parseCff(data, offset, offset + length, seacAnalysisEnabled); + break; + } + } + + if (glyf) { + const fontMatrix = !unitsPerEm + ? font.fontMatrix + : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0]; + return new TrueTypeCompiled( + parseGlyfTable(glyf, loca, indexToLocFormat), + cmap, + fontMatrix + ); + } + return new Type2Compiled(cff, cmap, font.fontMatrix); + } +} + +export { FontRendererFactory }; diff --git a/frontend/public/pdf.js/src/core/font_substitutions.js b/frontend/public/pdf.js/src/core/font_substitutions.js new file mode 100644 index 0000000..41bc5cc --- /dev/null +++ b/frontend/public/pdf.js/src/core/font_substitutions.js @@ -0,0 +1,565 @@ +/* Copyright 2023 Mozilla Foundation + * + * 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 { normalizeFontName } from "./fonts_utils.js"; +import { validateFontName } from "./core_utils.js"; +import { warn } from "../shared/util.js"; + +const NORMAL = { + style: "normal", + weight: "normal", +}; +const BOLD = { + style: "normal", + weight: "bold", +}; +const ITALIC = { + style: "italic", + weight: "normal", +}; +const BOLDITALIC = { + style: "italic", + weight: "bold", +}; + +const substitutionMap = new Map([ + [ + "Times-Roman", + { + local: [ + "Times New Roman", + "Times-Roman", + "Times", + "Liberation Serif", + "Nimbus Roman", + "Nimbus Roman L", + "Tinos", + "Thorndale", + "TeX Gyre Termes", + "FreeSerif", + "Linux Libertine O", + "Libertinus Serif", + "DejaVu Serif", + "Bitstream Vera Serif", + "Ubuntu", + ], + style: NORMAL, + ultimate: "serif", + }, + ], + [ + "Times-Bold", + { + alias: "Times-Roman", + style: BOLD, + ultimate: "serif", + }, + ], + [ + "Times-Italic", + { + alias: "Times-Roman", + style: ITALIC, + ultimate: "serif", + }, + ], + [ + "Times-BoldItalic", + { + alias: "Times-Roman", + style: BOLDITALIC, + ultimate: "serif", + }, + ], + [ + "Helvetica", + { + local: [ + "Helvetica", + "Helvetica Neue", + "Arial", + "Arial Nova", + "Liberation Sans", + "Arimo", + "Nimbus Sans", + "Nimbus Sans L", + "A030", + "TeX Gyre Heros", + "FreeSans", + "DejaVu Sans", + "Albany", + "Bitstream Vera Sans", + "Arial Unicode MS", + "Microsoft Sans Serif", + "Apple Symbols", + "Cantarell", + ], + path: "LiberationSans-Regular.ttf", + style: NORMAL, + ultimate: "sans-serif", + }, + ], + [ + "Helvetica-Bold", + { + alias: "Helvetica", + path: "LiberationSans-Bold.ttf", + style: BOLD, + ultimate: "sans-serif", + }, + ], + [ + "Helvetica-Oblique", + { + alias: "Helvetica", + path: "LiberationSans-Italic.ttf", + style: ITALIC, + ultimate: "sans-serif", + }, + ], + [ + "Helvetica-BoldOblique", + { + alias: "Helvetica", + path: "LiberationSans-BoldItalic.ttf", + style: BOLDITALIC, + ultimate: "sans-serif", + }, + ], + [ + "Courier", + { + local: [ + "Courier", + "Courier New", + "Liberation Mono", + "Nimbus Mono", + "Nimbus Mono L", + "Cousine", + "Cumberland", + "TeX Gyre Cursor", + "FreeMono", + "Linux Libertine Mono O", + "Libertinus Mono", + ], + style: NORMAL, + ultimate: "monospace", + }, + ], + [ + "Courier-Bold", + { + alias: "Courier", + style: BOLD, + ultimate: "monospace", + }, + ], + [ + "Courier-Oblique", + { + alias: "Courier", + style: ITALIC, + ultimate: "monospace", + }, + ], + [ + "Courier-BoldOblique", + { + alias: "Courier", + style: BOLDITALIC, + ultimate: "monospace", + }, + ], + [ + "ArialBlack", + { + local: ["Arial Black"], + style: { + style: "normal", + weight: "900", + }, + fallback: "Helvetica-Bold", + }, + ], + [ + "ArialBlack-Bold", + { + alias: "ArialBlack", + }, + ], + [ + "ArialBlack-Italic", + { + alias: "ArialBlack", + style: { + style: "italic", + weight: "900", + }, + fallback: "Helvetica-BoldOblique", + }, + ], + [ + "ArialBlack-BoldItalic", + { + alias: "ArialBlack-Italic", + }, + ], + [ + "ArialNarrow", + { + local: [ + "Arial Narrow", + "Liberation Sans Narrow", + "Helvetica Condensed", + "Nimbus Sans Narrow", + "TeX Gyre Heros Cn", + ], + style: NORMAL, + fallback: "Helvetica", + }, + ], + [ + "ArialNarrow-Bold", + { + alias: "ArialNarrow", + style: BOLD, + fallback: "Helvetica-Bold", + }, + ], + [ + "ArialNarrow-Italic", + { + alias: "ArialNarrow", + style: ITALIC, + fallback: "Helvetica-Oblique", + }, + ], + [ + "ArialNarrow-BoldItalic", + { + alias: "ArialNarrow", + style: BOLDITALIC, + fallback: "Helvetica-BoldOblique", + }, + ], + [ + "Calibri", + { + local: ["Calibri", "Carlito"], + style: NORMAL, + fallback: "Helvetica", + }, + ], + [ + "Calibri-Bold", + { + alias: "Calibri", + style: BOLD, + fallback: "Helvetica-Bold", + }, + ], + [ + "Calibri-Italic", + { + alias: "Calibri", + style: ITALIC, + fallback: "Helvetica-Oblique", + }, + ], + [ + "Calibri-BoldItalic", + { + alias: "Calibri", + style: BOLDITALIC, + fallback: "Helvetica-BoldOblique", + }, + ], + [ + "Wingdings", + { + local: ["Wingdings", "URW Dingbats"], + style: NORMAL, + }, + ], + [ + "Wingdings-Regular", + { + alias: "Wingdings", + }, + ], + [ + "Wingdings-Bold", + { + alias: "Wingdings", + }, + ], +]); + +const fontAliases = new Map([["Arial-Black", "ArialBlack"]]); + +function getStyleToAppend(style) { + switch (style) { + case BOLD: + return "Bold"; + case ITALIC: + return "Italic"; + case BOLDITALIC: + return "Bold Italic"; + default: + if (style?.weight === "bold") { + return "Bold"; + } + if (style?.style === "italic") { + return "Italic"; + } + } + return ""; +} + +function getFamilyName(str) { + // See https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/14d466b30a8ab4a9d789977ed94f2c30e7209267/src/fcname.c#L137. + const keywords = new Set([ + "thin", + "extralight", + "ultralight", + "demilight", + "semilight", + "light", + "book", + "regular", + "normal", + "medium", + "demibold", + "semibold", + "bold", + "extrabold", + "ultrabold", + "black", + "heavy", + "extrablack", + "ultrablack", + "roman", + "italic", + "oblique", + "ultracondensed", + "extracondensed", + "condensed", + "semicondensed", + "normal", + "semiexpanded", + "expanded", + "extraexpanded", + "ultraexpanded", + "bolditalic", + ]); + return str + .split(/[- ,+]+/g) + .filter(tok => !keywords.has(tok.toLowerCase())) + .join(" "); +} + +/** + * Generate font description. + * @param {Object} param0, font substitution description. + * @param {Array} src, contains src values (local(...) or url(...)). + * @param {String} localFontPath, path to local fonts. + * @param {boolean} useFallback, whether to use fallback font. + * @param {boolean} usePath, whether to use path to font. + * @param {String} append, style (Bold, Italic, ...) to append to font name. + * @return {Object} { style, ultimate }. + */ +function generateFont( + { alias, local, path, fallback, style, ultimate }, + src, + localFontPath, + useFallback = true, + usePath = true, + append = "" +) { + const result = { + style: null, + ultimate: null, + }; + if (local) { + const extra = append ? ` ${append}` : ""; + for (const name of local) { + src.push(`local(${name}${extra})`); + } + } + if (alias) { + const substitution = substitutionMap.get(alias); + const aliasAppend = append || getStyleToAppend(style); + Object.assign( + result, + generateFont( + substitution, + src, + localFontPath, + /* useFallback = */ useFallback && !fallback, + /* usePath = */ usePath && !path, + aliasAppend + ) + ); + } + if (style) { + result.style = style; + } + if (ultimate) { + result.ultimate = ultimate; + } + if (useFallback && fallback) { + const fallbackInfo = substitutionMap.get(fallback); + const { ultimate: fallbackUltimate } = generateFont( + fallbackInfo, + src, + localFontPath, + useFallback, + /* usePath = */ usePath && !path, + append + ); + result.ultimate ||= fallbackUltimate; + } + if (usePath && path && localFontPath) { + src.push(`url(${localFontPath}${path})`); + } + + return result; +} + +/** + * Get a font substitution for a given font. + * The general idea is to have enough information to create a CSS rule like + * this: + * @font-face { + * font-family: 'Times'; + * src: local('Times New Roman'), local('Subst1'), local('Subst2'), + * url(.../TimesNewRoman.ttf) + * font-weight: normal; + * font-style: normal; + * } + * or use the FontFace API. + * + * @param {Map} systemFontCache The cache of local fonts. + * @param {Object} idFactory The ids factory. + * @param {String} localFontPath Path to the fonts directory. + * @param {String} baseFontName The font name to be substituted. + * @param {String|undefined} standardFontName The standard font name to use + * if the base font is not available. + * @param {String} type The font type. + * @returns an Object with the CSS, the loaded name, the src and the style. + */ +function getFontSubstitution( + systemFontCache, + idFactory, + localFontPath, + baseFontName, + standardFontName, + type +) { + if (baseFontName.startsWith("InvalidPDFjsFont_")) { + return null; + } + + if ( + (type === "TrueType" || type === "Type1") && + /^[A-Z]{6}\+/.test(baseFontName) + ) { + // When the font is a subset, we need to remove the prefix (see 9.6.4). + baseFontName = baseFontName.slice(7); + } + + // It's possible to have a font name with spaces, commas or dashes, hence we + // just replace them by a dash. + baseFontName = normalizeFontName(baseFontName); + + const key = baseFontName; + let substitutionInfo = systemFontCache.get(key); + if (substitutionInfo) { + return substitutionInfo; + } + + // First, check if we've a substitution for the base font. + let substitution = substitutionMap.get(baseFontName); + if (!substitution) { + // Check if we've an alias for the base font, Arial-Black is the same as + // ArialBlack + for (const [alias, subst] of fontAliases) { + if (baseFontName.startsWith(alias)) { + baseFontName = `${subst}${baseFontName.substring(alias.length)}`; + substitution = substitutionMap.get(baseFontName); + break; + } + } + } + + let mustAddBaseFont = false; + if (!substitution) { + // If not, check if we've a substitution for the standard font. + substitution = substitutionMap.get(standardFontName); + mustAddBaseFont = true; + } + + const loadedName = `${idFactory.getDocId()}_s${idFactory.createFontId()}`; + if (!substitution) { + if (!validateFontName(baseFontName)) { + warn(`Cannot substitute the font because of its name: ${baseFontName}`); + systemFontCache.set(key, null); + // If the baseFontName is not valid we don't want to use it. + return null; + } + // Maybe we'll be lucky and the OS will have the font. + const bold = /bold/gi.test(baseFontName); + const italic = /oblique|italic/gi.test(baseFontName); + const style = + (bold && italic && BOLDITALIC) || + (bold && BOLD) || + (italic && ITALIC) || + NORMAL; + substitutionInfo = { + css: `"${getFamilyName(baseFontName)}",${loadedName}`, + guessFallback: true, + loadedName, + baseFontName, + src: `local(${baseFontName})`, + style, + }; + systemFontCache.set(key, substitutionInfo); + return substitutionInfo; + } + + const src = []; + // Maybe the OS will have the exact font we want so just prepend it to the + // list. + if (mustAddBaseFont && validateFontName(baseFontName)) { + src.push(`local(${baseFontName})`); + } + const { style, ultimate } = generateFont(substitution, src, localFontPath); + const guessFallback = ultimate === null; + const fallback = guessFallback ? "" : `,${ultimate}`; + + substitutionInfo = { + css: `"${getFamilyName(baseFontName)}",${loadedName}${fallback}`, + guessFallback, + loadedName, + baseFontName, + src: src.join(","), + style, + }; + systemFontCache.set(key, substitutionInfo); + + return substitutionInfo; +} + +export { getFontSubstitution }; diff --git a/frontend/public/pdf.js/src/core/fonts.js b/frontend/public/pdf.js/src/core/fonts.js new file mode 100644 index 0000000..3769c88 --- /dev/null +++ b/frontend/public/pdf.js/src/core/fonts.js @@ -0,0 +1,3610 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + assert, + bytesToString, + FONT_IDENTITY_MATRIX, + FormatError, + info, + shadow, + string32, + warn, +} from "../shared/util.js"; +import { CFFCompiler, CFFParser } from "./cff_parser.js"; +import { + FontFlags, + getVerticalPresentationForm, + MacStandardGlyphOrdering, + normalizeFontName, + recoverGlyphName, + SEAC_ANALYSIS_ENABLED, +} from "./fonts_utils.js"; +import { + getCharUnicodeCategory, + getUnicodeForGlyph, + getUnicodeRangeFor, + mapSpecialUnicodeValues, +} from "./unicode.js"; +import { getDingbatsGlyphsUnicode, getGlyphsUnicode } from "./glyphlist.js"; +import { + getEncoding, + MacRomanEncoding, + StandardEncoding, + SymbolSetEncoding, + WinAnsiEncoding, + ZapfDingbatsEncoding, +} from "./encodings.js"; +import { + getGlyphMapForStandardFonts, + getNonStdFontMap, + getSerifFonts, + getStdFontMap, + getSupplementalGlyphMapForArialBlack, + getSupplementalGlyphMapForCalibri, +} from "./standard_fonts.js"; +import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js"; +import { CFFFont } from "./cff_font.js"; +import { FontRendererFactory } from "./font_renderer.js"; +import { getFontBasicMetrics } from "./metrics.js"; +import { GlyfTable } from "./glyf.js"; +import { IdentityCMap } from "./cmap.js"; +import { OpenTypeFileBuilder } from "./opentype_file_builder.js"; +import { readUint32 } from "./core_utils.js"; +import { Stream } from "./stream.js"; +import { Type1Font } from "./type1_font.js"; + +// Unicode Private Use Areas: +const PRIVATE_USE_AREAS = [ + [0xe000, 0xf8ff], // BMP (0) + [0x100000, 0x10fffd], // PUP (16) +]; + +// PDF Glyph Space Units are one Thousandth of a TextSpace Unit +// except for Type 3 fonts +const PDF_GLYPH_SPACE_UNITS = 1000; + +const EXPORT_DATA_PROPERTIES = [ + "ascent", + "bbox", + "black", + "bold", + "charProcOperatorList", + "composite", + "cssFontInfo", + "data", + "defaultVMetrics", + "defaultWidth", + "descent", + "fallbackName", + "fontMatrix", + "isInvalidPDFjsFont", + "isType3Font", + "italic", + "loadedName", + "mimetype", + "missingFile", + "name", + "remeasure", + "subtype", + "systemFontInfo", + "type", + "vertical", +]; + +const EXPORT_DATA_EXTRA_PROPERTIES = [ + "cMap", + "defaultEncoding", + "differences", + "isMonospace", + "isSerifFont", + "isSymbolicFont", + "seacMap", + "toFontChar", + "toUnicode", + "vmetrics", + "widths", +]; + +function adjustWidths(properties) { + if (!properties.fontMatrix) { + return; + } + if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) { + return; + } + // adjusting width to fontMatrix scale + const scale = 0.001 / properties.fontMatrix[0]; + const glyphsWidths = properties.widths; + for (const glyph in glyphsWidths) { + glyphsWidths[glyph] *= scale; + } + properties.defaultWidth *= scale; +} + +function adjustTrueTypeToUnicode(properties, isSymbolicFont, nameRecords) { + if (properties.isInternalFont) { + return; + } + if (properties.hasIncludedToUnicodeMap) { + return; // The font dictionary has a `ToUnicode` entry. + } + if (properties.hasEncoding) { + return; // The font dictionary has an `Encoding` entry. + } + if (properties.toUnicode instanceof IdentityToUnicodeMap) { + return; + } + if (!isSymbolicFont) { + return; // A non-symbolic font should default to `StandardEncoding`. + } + if (nameRecords.length === 0) { + return; + } + + // Try to infer if the fallback encoding should really be `WinAnsiEncoding`. + if (properties.defaultEncoding === WinAnsiEncoding) { + return; + } + for (const r of nameRecords) { + if (!isWinNameRecord(r)) { + return; // Not Windows, hence `WinAnsiEncoding` wouldn't make sense. + } + } + const encoding = WinAnsiEncoding; + + const toUnicode = [], + glyphsUnicodeMap = getGlyphsUnicode(); + for (const charCode in encoding) { + const glyphName = encoding[charCode]; + if (glyphName === "") { + continue; + } + const unicode = glyphsUnicodeMap[glyphName]; + if (unicode === undefined) { + continue; + } + toUnicode[charCode] = String.fromCharCode(unicode); + } + if (toUnicode.length > 0) { + properties.toUnicode.amend(toUnicode); + } +} + +function adjustType1ToUnicode(properties, builtInEncoding) { + if (properties.isInternalFont) { + return; + } + if (properties.hasIncludedToUnicodeMap) { + return; // The font dictionary has a `ToUnicode` entry. + } + if (builtInEncoding === properties.defaultEncoding) { + return; // No point in trying to adjust `toUnicode` if the encodings match. + } + if (properties.toUnicode instanceof IdentityToUnicodeMap) { + return; + } + const toUnicode = [], + glyphsUnicodeMap = getGlyphsUnicode(); + for (const charCode in builtInEncoding) { + if (properties.hasEncoding) { + if ( + properties.baseEncodingName || + properties.differences[charCode] !== undefined + ) { + continue; // The font dictionary has an `Encoding`/`Differences` entry. + } + } + const glyphName = builtInEncoding[charCode]; + const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); + if (unicode !== -1) { + toUnicode[charCode] = String.fromCharCode(unicode); + } + } + if (toUnicode.length > 0) { + properties.toUnicode.amend(toUnicode); + } +} + +/** + * NOTE: This function should only be called at the *end* of font-parsing, + * after e.g. `adjustType1ToUnicode` has run, to prevent any issues. + */ +function amendFallbackToUnicode(properties) { + if (!properties.fallbackToUnicode) { + return; + } + if (properties.toUnicode instanceof IdentityToUnicodeMap) { + return; + } + const toUnicode = []; + for (const charCode in properties.fallbackToUnicode) { + if (properties.toUnicode.has(charCode)) { + continue; // The font dictionary has a `ToUnicode` entry. + } + toUnicode[charCode] = properties.fallbackToUnicode[charCode]; + } + if (toUnicode.length > 0) { + properties.toUnicode.amend(toUnicode); + } +} + +class Glyph { + constructor( + originalCharCode, + fontChar, + unicode, + accent, + width, + vmetric, + operatorListId, + isSpace, + isInFont + ) { + this.originalCharCode = originalCharCode; + this.fontChar = fontChar; + this.unicode = unicode; + this.accent = accent; + this.width = width; + this.vmetric = vmetric; + this.operatorListId = operatorListId; + this.isSpace = isSpace; + this.isInFont = isInFont; + } + + /** + * This property, which is only used by `PartialEvaluator.getTextContent`, + * is purposely made non-serializable. + * @type {Object} + */ + get category() { + return shadow( + this, + "category", + getCharUnicodeCategory(this.unicode), + /* nonSerializable = */ true + ); + } +} + +function int16(b0, b1) { + return (b0 << 8) + b1; +} + +function writeSignedInt16(bytes, index, value) { + bytes[index + 1] = value; + bytes[index] = value >>> 8; +} + +function signedInt16(b0, b1) { + const value = (b0 << 8) + b1; + return value & (1 << 15) ? value - 0x10000 : value; +} + +function writeUint32(bytes, index, value) { + bytes[index + 3] = value & 0xff; + bytes[index + 2] = value >>> 8; + bytes[index + 1] = value >>> 16; + bytes[index] = value >>> 24; +} + +function int32(b0, b1, b2, b3) { + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; +} + +function string16(value) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + typeof value === "number" && Math.abs(value) < 2 ** 16, + `string16: Unexpected input "${value}".` + ); + } + return String.fromCharCode((value >> 8) & 0xff, value & 0xff); +} + +function safeString16(value) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + typeof value === "number" && !Number.isNaN(value), + `safeString16: Unexpected input "${value}".` + ); + } + // clamp value to the 16-bit int range + if (value > 0x7fff) { + value = 0x7fff; + } else if (value < -0x8000) { + value = -0x8000; + } + return String.fromCharCode((value >> 8) & 0xff, value & 0xff); +} + +function isTrueTypeFile(file) { + const header = file.peekBytes(4); + return ( + readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true" + ); +} + +function isTrueTypeCollectionFile(file) { + const header = file.peekBytes(4); + return bytesToString(header) === "ttcf"; +} + +function isOpenTypeFile(file) { + const header = file.peekBytes(4); + return bytesToString(header) === "OTTO"; +} + +function isType1File(file) { + const header = file.peekBytes(2); + // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21). + if (header[0] === 0x25 && header[1] === 0x21) { + return true; + } + // ... obviously some fonts violate that part of the specification, + // please refer to the comment in |Type1Font| below (pfb file header). + if (header[0] === 0x80 && header[1] === 0x01) { + return true; + } + return false; +} + +/** + * Compared to other font formats, the header in CFF files is not constant + * but contains version numbers. To reduce the possibility of misclassifying + * font files as CFF, it's recommended to check for other font formats first. + */ +function isCFFFile(file) { + const header = file.peekBytes(4); + if ( + /* major version, [1, 255] */ header[0] >= 1 && + /* minor version, [0, 255]; header[1] */ + /* header size, [0, 255]; header[2] */ + /* offset(0) size, [1, 4] */ header[3] >= 1 && + header[3] <= 4 + ) { + return true; + } + return false; +} + +function getFontFileType(file, { type, subtype, composite }) { + let fileType, fileSubtype; + + if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) { + fileType = composite ? "CIDFontType2" : "TrueType"; + } else if (isOpenTypeFile(file)) { + fileType = composite ? "CIDFontType2" : "OpenType"; + } else if (isType1File(file)) { + if (composite) { + fileType = "CIDFontType0"; + } else { + fileType = type === "MMType1" ? "MMType1" : "Type1"; + } + } else if (isCFFFile(file)) { + if (composite) { + fileType = "CIDFontType0"; + fileSubtype = "CIDFontType0C"; + } else { + fileType = type === "MMType1" ? "MMType1" : "Type1"; + fileSubtype = "Type1C"; + } + } else { + warn("getFontFileType: Unable to detect correct font file Type/Subtype."); + fileType = type; + fileSubtype = subtype; + } + + return [fileType, fileSubtype]; +} + +function applyStandardFontGlyphMap(map, glyphMap) { + for (const charCode in glyphMap) { + map[+charCode] = glyphMap[charCode]; + } +} + +function buildToFontChar(encoding, glyphsUnicodeMap, differences) { + const toFontChar = []; + let unicode; + for (let i = 0, ii = encoding.length; i < ii; i++) { + unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap); + if (unicode !== -1) { + toFontChar[i] = unicode; + } + } + for (const charCode in differences) { + unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap); + if (unicode !== -1) { + toFontChar[+charCode] = unicode; + } + } + return toFontChar; +} + +// Please refer to: +// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html +function isMacNameRecord(r) { + return r.platform === 1 && r.encoding === 0 && r.language === 0; +} + +// Please refer to: +// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html +// - https://learn.microsoft.com/en-us/typography/opentype/spec/name#windows-language-ids +function isWinNameRecord(r) { + return r.platform === 3 && r.encoding === 1 && r.language === 0x409; +} + +function convertCidString(charCode, cid, shouldThrow = false) { + switch (cid.length) { + case 1: + return cid.charCodeAt(0); + case 2: + return (cid.charCodeAt(0) << 8) | cid.charCodeAt(1); + } + const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`; + if (shouldThrow) { + throw new FormatError(msg); + } + warn(msg); + return cid; +} + +/** + * Rebuilds the char code to glyph ID map by moving all char codes to the + * private use area. This is done to avoid issues with various problematic + * unicode areas where either a glyph won't be drawn or is deformed by a + * shaper. + * @returns {Object} Two properties: + * 'toFontChar' - maps original char codes(the value that will be read + * from commands such as show text) to the char codes that will be used in the + * font that we build + * 'charCodeToGlyphId' - maps the new font char codes to glyph ids + */ +function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId, toUnicode) { + const newMap = Object.create(null); + const toUnicodeExtraMap = new Map(); + const toFontChar = []; + const usedGlyphIds = new Set(); + let privateUseAreaIndex = 0; + const privateUseOffetStart = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; + let nextAvailableFontCharCode = privateUseOffetStart; + let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; + const isInPrivateArea = code => + (PRIVATE_USE_AREAS[0][0] <= code && code <= PRIVATE_USE_AREAS[0][1]) || + (PRIVATE_USE_AREAS[1][0] <= code && code <= PRIVATE_USE_AREAS[1][1]); + let LIGATURE_TO_UNICODE = null; + + for (const originalCharCode in charCodeToGlyphId) { + let glyphId = charCodeToGlyphId[originalCharCode]; + // For missing glyphs don't create the mappings so the glyph isn't + // drawn. + if (!hasGlyph(glyphId)) { + continue; + } + if (nextAvailableFontCharCode > privateUseOffetEnd) { + privateUseAreaIndex++; + if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) { + warn("Ran out of space in font private use area."); + break; + } + nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; + privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; + } + const fontCharCode = nextAvailableFontCharCode++; + if (glyphId === 0) { + glyphId = newGlyphZeroId; + } + + // Fix for bug 1778484: + // The charcodes are moved into a private use area to fix some rendering + // issues (https://github.com/mozilla/pdf.js/pull/9340) but when printing + // to PDF the generated font will contain wrong chars. We can avoid that by + // adding the unicode to the cmap and the print backend will then map the + // glyph ids to the correct unicode. + let unicode = toUnicode.get(originalCharCode); + if (typeof unicode === "string") { + if (unicode.length === 1) { + unicode = unicode.codePointAt(0); + } else { + if (!LIGATURE_TO_UNICODE) { + LIGATURE_TO_UNICODE = new Map(); + // The code range [0xfb00, 0xfb4f] contains some ligature characters + // but not all. + // See https://www.compart.com/en/unicode/block/U+FB00. + for (let i = 0xfb00; i <= 0xfb4f; i++) { + const normalized = String.fromCharCode(i).normalize("NFKD"); + if (normalized.length > 1) { + LIGATURE_TO_UNICODE.set(normalized, i); + } + } + } + unicode = LIGATURE_TO_UNICODE.get(unicode) || unicode.codePointAt(0); + } + } + if (unicode && !isInPrivateArea(unicode) && !usedGlyphIds.has(glyphId)) { + toUnicodeExtraMap.set(unicode, glyphId); + usedGlyphIds.add(glyphId); + } + + newMap[fontCharCode] = glyphId; + toFontChar[originalCharCode] = fontCharCode; + } + return { + toFontChar, + charCodeToGlyphId: newMap, + toUnicodeExtraMap, + nextAvailableFontCharCode, + }; +} + +function getRanges(glyphs, toUnicodeExtraMap, numGlyphs) { + // Array.sort() sorts by characters, not numerically, so convert to an + // array of characters. + const codes = []; + for (const charCode in glyphs) { + // Remove an invalid glyph ID mappings to make OTS happy. + if (glyphs[charCode] >= numGlyphs) { + continue; + } + codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] }); + } + if (toUnicodeExtraMap) { + for (const [unicode, glyphId] of toUnicodeExtraMap) { + if (glyphId >= numGlyphs) { + continue; + } + codes.push({ fontCharCode: unicode, glyphId }); + } + } + // Some fonts have zero glyphs and are used only for text selection, but + // there needs to be at least one to build a valid cmap table. + if (codes.length === 0) { + codes.push({ fontCharCode: 0, glyphId: 0 }); + } + codes.sort(function fontGetRangesSort(a, b) { + return a.fontCharCode - b.fontCharCode; + }); + + // Split the sorted codes into ranges. + const ranges = []; + const length = codes.length; + for (let n = 0; n < length; ) { + const start = codes[n].fontCharCode; + const codeIndices = [codes[n].glyphId]; + ++n; + let end = start; + while (n < length && end + 1 === codes[n].fontCharCode) { + codeIndices.push(codes[n].glyphId); + ++end; + ++n; + if (end === 0xffff) { + break; + } + } + ranges.push([start, end, codeIndices]); + } + + return ranges; +} + +function createCmapTable(glyphs, toUnicodeExtraMap, numGlyphs) { + const ranges = getRanges(glyphs, toUnicodeExtraMap, numGlyphs); + const numTables = ranges.at(-1)[1] > 0xffff ? 2 : 1; + let cmap = + "\x00\x00" + // version + string16(numTables) + // numTables + "\x00\x03" + // platformID + "\x00\x01" + // encodingID + string32(4 + numTables * 8); // start of the table record + + let i, ii, j, jj; + for (i = ranges.length - 1; i >= 0; --i) { + if (ranges[i][0] <= 0xffff) { + break; + } + } + const bmpLength = i + 1; + + if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) { + ranges[i][1] = 0xfffe; + } + const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0; + const segCount = bmpLength + trailingRangesCount; + const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2); + + // Fill up the 4 parallel arrays describing the segments. + let startCount = ""; + let endCount = ""; + let idDeltas = ""; + let idRangeOffsets = ""; + let glyphsIds = ""; + let bias = 0; + + let range, start, end, codes; + for (i = 0, ii = bmpLength; i < ii; i++) { + range = ranges[i]; + start = range[0]; + end = range[1]; + startCount += string16(start); + endCount += string16(end); + codes = range[2]; + let contiguous = true; + for (j = 1, jj = codes.length; j < jj; ++j) { + if (codes[j] !== codes[j - 1] + 1) { + contiguous = false; + break; + } + } + if (!contiguous) { + const offset = (segCount - i) * 2 + bias * 2; + bias += end - start + 1; + + idDeltas += string16(0); + idRangeOffsets += string16(offset); + + for (j = 0, jj = codes.length; j < jj; ++j) { + glyphsIds += string16(codes[j]); + } + } else { + const startCode = codes[0]; + + idDeltas += string16((startCode - start) & 0xffff); + idRangeOffsets += string16(0); + } + } + + if (trailingRangesCount > 0) { + endCount += "\xFF\xFF"; + startCount += "\xFF\xFF"; + idDeltas += "\x00\x01"; + idRangeOffsets += "\x00\x00"; + } + + const format314 = + "\x00\x00" + // language + string16(2 * segCount) + + string16(searchParams.range) + + string16(searchParams.entry) + + string16(searchParams.rangeShift) + + endCount + + "\x00\x00" + + startCount + + idDeltas + + idRangeOffsets + + glyphsIds; + + let format31012 = ""; + let header31012 = ""; + if (numTables > 1) { + cmap += + "\x00\x03" + // platformID + "\x00\x0A" + // encodingID + string32(4 + numTables * 8 + 4 + format314.length); // start of the table record + format31012 = ""; + for (i = 0, ii = ranges.length; i < ii; i++) { + range = ranges[i]; + start = range[0]; + codes = range[2]; + let code = codes[0]; + for (j = 1, jj = codes.length; j < jj; ++j) { + if (codes[j] !== codes[j - 1] + 1) { + end = range[0] + j - 1; + format31012 += + string32(start) + // startCharCode + string32(end) + // endCharCode + string32(code); // startGlyphID + start = end + 1; + code = codes[j]; + } + } + format31012 += + string32(start) + // startCharCode + string32(range[1]) + // endCharCode + string32(code); // startGlyphID + } + header31012 = + "\x00\x0C" + // format + "\x00\x00" + // reserved + string32(format31012.length + 16) + // length + "\x00\x00\x00\x00" + // language + string32(format31012.length / 12); // nGroups + } + + return ( + cmap + + "\x00\x04" + // format + string16(format314.length + 4) + // length + format314 + + header31012 + + format31012 + ); +} + +function validateOS2Table(os2, file) { + file.pos = (file.start || 0) + os2.offset; + const version = file.getUint16(); + // TODO verify all OS/2 tables fields, but currently we validate only those + // that give us issues + file.skip(60); // skipping type, misc sizes, panose, unicode ranges + const selection = file.getUint16(); + if (version < 4 && selection & 0x0300) { + return false; + } + const firstChar = file.getUint16(); + const lastChar = file.getUint16(); + if (firstChar > lastChar) { + return false; + } + file.skip(6); // skipping sTypoAscender/Descender/LineGap + const usWinAscent = file.getUint16(); + if (usWinAscent === 0) { + // makes font unreadable by windows + return false; + } + + // OS/2 appears to be valid, resetting some fields + os2.data[8] = os2.data[9] = 0; // IE rejects fonts if fsType != 0 + return true; +} + +function createOS2Table(properties, charstrings, override) { + override ||= { + unitsPerEm: 0, + yMax: 0, + yMin: 0, + ascent: 0, + descent: 0, + }; + + let ulUnicodeRange1 = 0; + let ulUnicodeRange2 = 0; + let ulUnicodeRange3 = 0; + let ulUnicodeRange4 = 0; + + let firstCharIndex = null; + let lastCharIndex = 0; + let position = -1; + + if (charstrings) { + for (let code in charstrings) { + code |= 0; + if (firstCharIndex > code || !firstCharIndex) { + firstCharIndex = code; + } + if (lastCharIndex < code) { + lastCharIndex = code; + } + + position = getUnicodeRangeFor(code, position); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << (position - 32); + } else if (position < 96) { + ulUnicodeRange3 |= 1 << (position - 64); + } else if (position < 123) { + ulUnicodeRange4 |= 1 << (position - 96); + } else { + throw new FormatError( + "Unicode ranges Bits > 123 are reserved for internal usage" + ); + } + } + if (lastCharIndex > 0xffff) { + // OS2 only supports a 16 bit int. The spec says if supplementary + // characters are used the field should just be set to 0xFFFF. + lastCharIndex = 0xffff; + } + } else { + // TODO + firstCharIndex = 0; + lastCharIndex = 255; + } + + const bbox = properties.bbox || [0, 0, 0, 0]; + const unitsPerEm = + override.unitsPerEm || + (properties.fontMatrix + ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs)) + : 1000); + + // if the font units differ to the PDF glyph space units + // then scale up the values + const scale = properties.ascentScaled + ? 1.0 + : unitsPerEm / PDF_GLYPH_SPACE_UNITS; + + const typoAscent = + override.ascent || Math.round(scale * (properties.ascent || bbox[3])); + let typoDescent = + override.descent || Math.round(scale * (properties.descent || bbox[1])); + if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) { + typoDescent = -typoDescent; // fixing incorrect descent + } + const winAscent = override.yMax || typoAscent; + const winDescent = -override.yMin || -typoDescent; + + return ( + "\x00\x03" + // version + "\x02\x24" + // xAvgCharWidth + "\x01\xF4" + // usWeightClass + "\x00\x05" + // usWidthClass + "\x00\x00" + // fstype (0 to let the font loads via font-face on IE) + "\x02\x8A" + // ySubscriptXSize + "\x02\xBB" + // ySubscriptYSize + "\x00\x00" + // ySubscriptXOffset + "\x00\x8C" + // ySubscriptYOffset + "\x02\x8A" + // ySuperScriptXSize + "\x02\xBB" + // ySuperScriptYSize + "\x00\x00" + // ySuperScriptXOffset + "\x01\xDF" + // ySuperScriptYOffset + "\x00\x31" + // yStrikeOutSize + "\x01\x02" + // yStrikeOutPosition + "\x00\x00" + // sFamilyClass + "\x00\x00\x06" + + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + + "\x00\x00\x00\x00\x00\x00" + // Panose + string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31) + string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63) + string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95) + string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127) + "\x2A\x32\x31\x2A" + // achVendID + string16(properties.italicAngle ? 1 : 0) + // fsSelection + string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex + string16(lastCharIndex || properties.lastChar) + // usLastCharIndex + string16(typoAscent) + // sTypoAscender + string16(typoDescent) + // sTypoDescender + "\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value) + string16(winAscent) + // usWinAscent + string16(winDescent) + // usWinDescent + "\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31) + "\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63) + string16(properties.xHeight) + // sxHeight + string16(properties.capHeight) + // sCapHeight + string16(0) + // usDefaultChar + string16(firstCharIndex || properties.firstChar) + // usBreakChar + "\x00\x03" + ); // usMaxContext +} + +function createPostTable(properties) { + const angle = Math.floor(properties.italicAngle * 2 ** 16); + return ( + "\x00\x03\x00\x00" + // Version number + string32(angle) + // italicAngle + "\x00\x00" + // underlinePosition + "\x00\x00" + // underlineThickness + string32(properties.fixedPitch ? 1 : 0) + // isFixedPitch + "\x00\x00\x00\x00" + // minMemType42 + "\x00\x00\x00\x00" + // maxMemType42 + "\x00\x00\x00\x00" + // minMemType1 + "\x00\x00\x00\x00" + ); // maxMemType1 +} + +function createPostscriptName(name) { + // See https://docs.microsoft.com/en-us/typography/opentype/spec/recom#name. + return name.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g, "").slice(0, 63); +} + +function createNameTable(name, proto) { + if (!proto) { + proto = [[], []]; // no strings and unicode strings + } + + const strings = [ + proto[0][0] || "Original licence", // 0.Copyright + proto[0][1] || name, // 1.Font family + proto[0][2] || "Unknown", // 2.Font subfamily (font weight) + proto[0][3] || "uniqueID", // 3.Unique ID + proto[0][4] || name, // 4.Full font name + proto[0][5] || "Version 0.11", // 5.Version + proto[0][6] || createPostscriptName(name), // 6.Postscript name + proto[0][7] || "Unknown", // 7.Trademark + proto[0][8] || "Unknown", // 8.Manufacturer + proto[0][9] || "Unknown", // 9.Designer + ]; + + // Mac want 1-byte per character strings while Windows want + // 2-bytes per character, so duplicate the names table + const stringsUnicode = []; + let i, ii, j, jj, str; + for (i = 0, ii = strings.length; i < ii; i++) { + str = proto[1][i] || strings[i]; + + const strBufUnicode = []; + for (j = 0, jj = str.length; j < jj; j++) { + strBufUnicode.push(string16(str.charCodeAt(j))); + } + stringsUnicode.push(strBufUnicode.join("")); + } + + const names = [strings, stringsUnicode]; + const platforms = ["\x00\x01", "\x00\x03"]; + const encodings = ["\x00\x00", "\x00\x01"]; + const languages = ["\x00\x00", "\x04\x09"]; + + const namesRecordCount = strings.length * platforms.length; + let nameTable = + "\x00\x00" + // format + string16(namesRecordCount) + // Number of names Record + string16(namesRecordCount * 12 + 6); // Storage + + // Build the name records field + let strOffset = 0; + for (i = 0, ii = platforms.length; i < ii; i++) { + const strs = names[i]; + for (j = 0, jj = strs.length; j < jj; j++) { + str = strs[j]; + const nameRecord = + platforms[i] + // platform ID + encodings[i] + // encoding ID + languages[i] + // language ID + string16(j) + // name ID + string16(str.length) + + string16(strOffset); + nameTable += nameRecord; + strOffset += str.length; + } + } + + nameTable += strings.join("") + stringsUnicode.join(""); + return nameTable; +} + +/** + * 'Font' is the class the outside world should use, it encapsulate all the font + * decoding logics whatever type it is (assuming the font type is supported). + */ +class Font { + constructor(name, file, properties) { + this.name = name; + this.psName = null; + this.mimetype = null; + this.disableFontFace = false; + + this.loadedName = properties.loadedName; + this.isType3Font = properties.isType3Font; + this.missingFile = false; + this.cssFontInfo = properties.cssFontInfo; + + this._charsCache = Object.create(null); + this._glyphCache = Object.create(null); + + let isSerifFont = !!(properties.flags & FontFlags.Serif); + // Fallback to checking the font name, in order to improve text-selection, + // since the /Flags-entry is often wrong (fixes issue13845.pdf). + if (!isSerifFont && !properties.isSimulatedFlags) { + const baseName = name.replaceAll(/[,_]/g, "-").split("-", 1)[0], + serifFonts = getSerifFonts(); + for (const namePart of baseName.split("+")) { + if (serifFonts[namePart]) { + isSerifFont = true; + break; + } + } + } + this.isSerifFont = isSerifFont; + + this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); + this.isMonospace = !!(properties.flags & FontFlags.FixedPitch); + + let { type, subtype } = properties; + this.type = type; + this.subtype = subtype; + this.systemFontInfo = properties.systemFontInfo; + + const matches = name.match(/^InvalidPDFjsFont_(.*)_\d+$/); + this.isInvalidPDFjsFont = !!matches; + if (this.isInvalidPDFjsFont) { + this.fallbackName = matches[1]; + } else if (this.isMonospace) { + this.fallbackName = "monospace"; + } else if (this.isSerifFont) { + this.fallbackName = "serif"; + } else { + this.fallbackName = "sans-serif"; + } + + if (this.systemFontInfo?.guessFallback) { + // Once the fallback name is guessed, we don't want to guess it again. + this.systemFontInfo.guessFallback = false; + this.systemFontInfo.css += `,${this.fallbackName}`; + } + + this.differences = properties.differences; + this.widths = properties.widths; + this.defaultWidth = properties.defaultWidth; + this.composite = properties.composite; + this.cMap = properties.cMap; + this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS; + this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS; + this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS; + this.lineHeight = this.ascent - this.descent; + this.fontMatrix = properties.fontMatrix; + this.bbox = properties.bbox; + this.defaultEncoding = properties.defaultEncoding; + + this.toUnicode = properties.toUnicode; + this.toFontChar = []; + + if (properties.type === "Type3") { + for (let charCode = 0; charCode < 256; charCode++) { + this.toFontChar[charCode] = + this.differences[charCode] || properties.defaultEncoding[charCode]; + } + return; + } + + this.cidEncoding = properties.cidEncoding || ""; + this.vertical = !!properties.vertical; + if (this.vertical) { + this.vmetrics = properties.vmetrics; + this.defaultVMetrics = properties.defaultVMetrics; + } + + if (!file || file.isEmpty) { + if (file) { + // Some bad PDF generators will include empty font files, + // attempting to recover by assuming that no file exists. + warn('Font file is empty in "' + name + '" (' + this.loadedName + ")"); + } + this.fallbackToSystemFont(properties); + return; + } + + // Parse the font file to determine the correct type/subtype, rather than + // relying on the (often incorrect) data in the font dictionary; (see e.g. + // issue6782.pdf, issue7598.pdf, and issue9949.pdf). + [type, subtype] = getFontFileType(file, properties); + + if (type !== this.type || subtype !== this.subtype) { + info( + "Inconsistent font file Type/SubType, expected: " + + `${this.type}/${this.subtype} but found: ${type}/${subtype}.` + ); + } + + let data; + try { + switch (type) { + case "MMType1": + info("MMType1 font (" + name + "), falling back to Type1."); + /* falls through */ + case "Type1": + case "CIDFontType0": + this.mimetype = "font/opentype"; + + const cff = + subtype === "Type1C" || subtype === "CIDFontType0C" + ? new CFFFont(file, properties) + : new Type1Font(name, file, properties); + + adjustWidths(properties); + + // Wrap the CFF data inside an OTF font file + data = this.convert(name, cff, properties); + break; + + case "OpenType": + case "TrueType": + case "CIDFontType2": + this.mimetype = "font/opentype"; + + // Repair the TrueType file. It is can be damaged in the point of + // view of the sanitizer + data = this.checkAndRepair(name, file, properties); + if (this.isOpenType) { + adjustWidths(properties); + + type = "OpenType"; + } + break; + + default: + throw new FormatError(`Font ${type} is not supported`); + } + } catch (e) { + warn(e); + this.fallbackToSystemFont(properties); + return; + } + + amendFallbackToUnicode(properties); + this.data = data; + + // Transfer some properties again that could change during font conversion + this.type = type; + this.subtype = subtype; + this.fontMatrix = properties.fontMatrix; + this.widths = properties.widths; + this.defaultWidth = properties.defaultWidth; + this.toUnicode = properties.toUnicode; + this.seacMap = properties.seacMap; + } + + get renderer() { + const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); + return shadow(this, "renderer", renderer); + } + + exportData(extraProperties = false) { + const exportDataProperties = extraProperties + ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] + : EXPORT_DATA_PROPERTIES; + + const data = Object.create(null); + let property, value; + for (property of exportDataProperties) { + value = this[property]; + // Ignore properties that haven't been explicitly set. + if (value !== undefined) { + data[property] = value; + } + } + return data; + } + + fallbackToSystemFont(properties) { + this.missingFile = true; + // The file data is not specified. Trying to fix the font name + // to be used with the canvas.font. + const { name, type } = this; + let fontName = normalizeFontName(name); + const stdFontMap = getStdFontMap(), + nonStdFontMap = getNonStdFontMap(); + const isStandardFont = !!stdFontMap[fontName]; + const isMappedToStandardFont = !!( + nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]] + ); + + fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; + + const fontBasicMetricsMap = getFontBasicMetrics(); + const metrics = fontBasicMetricsMap[fontName]; + if (metrics) { + if (isNaN(this.ascent)) { + this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS; + } + if (isNaN(this.descent)) { + this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS; + } + if (isNaN(this.capHeight)) { + this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS; + } + } + + this.bold = /bold/gi.test(fontName); + this.italic = /oblique|italic/gi.test(fontName); + + // Use 'name' instead of 'fontName' here because the original + // name ArialBlack for example will be replaced by Helvetica. + this.black = /Black/g.test(name); + + // Use 'name' instead of 'fontName' here because the original + // name ArialNarrow for example will be replaced by Helvetica. + const isNarrow = /Narrow/g.test(name); + + // if at least one width is present, remeasure all chars when exists + this.remeasure = + (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0; + if ( + (isStandardFont || isMappedToStandardFont) && + type === "CIDFontType2" && + this.cidEncoding.startsWith("Identity-") + ) { + const cidToGidMap = properties.cidToGidMap; + // Standard fonts might be embedded as CID font without glyph mapping. + // Building one based on GlyphMapForStandardFonts. + const map = []; + applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts()); + + if (/Arial-?Black/i.test(name)) { + applyStandardFontGlyphMap(map, getSupplementalGlyphMapForArialBlack()); + } else if (/Calibri/i.test(name)) { + applyStandardFontGlyphMap(map, getSupplementalGlyphMapForCalibri()); + } + + // Always update the glyph mapping with the `cidToGidMap` when it exists + // (fixes issue12418_reduced.pdf). + if (cidToGidMap) { + for (const charCode in map) { + const cid = map[charCode]; + if (cidToGidMap[cid] !== undefined) { + map[+charCode] = cidToGidMap[cid]; + } + } + // When the /CIDToGIDMap is "incomplete", fallback to the included + // /ToUnicode-map regardless of its encoding (fixes issue11915.pdf). + if ( + cidToGidMap.length !== this.toUnicode.length && + properties.hasIncludedToUnicodeMap && + this.toUnicode instanceof IdentityToUnicodeMap + ) { + this.toUnicode.forEach(function (charCode, unicodeCharCode) { + const cid = map[charCode]; + if (cidToGidMap[cid] === undefined) { + map[+charCode] = unicodeCharCode; + } + }); + } + } + + if (!(this.toUnicode instanceof IdentityToUnicodeMap)) { + this.toUnicode.forEach(function (charCode, unicodeCharCode) { + map[+charCode] = unicodeCharCode; + }); + } + this.toFontChar = map; + this.toUnicode = new ToUnicodeMap(map); + } else if (/Symbol/i.test(fontName)) { + this.toFontChar = buildToFontChar( + SymbolSetEncoding, + getGlyphsUnicode(), + this.differences + ); + } else if (/Dingbats/i.test(fontName)) { + this.toFontChar = buildToFontChar( + ZapfDingbatsEncoding, + getDingbatsGlyphsUnicode(), + this.differences + ); + } else if (isStandardFont || isMappedToStandardFont) { + const map = buildToFontChar( + this.defaultEncoding, + getGlyphsUnicode(), + this.differences + ); + + if ( + type === "CIDFontType2" && + !this.cidEncoding.startsWith("Identity-") && + !(this.toUnicode instanceof IdentityToUnicodeMap) + ) { + this.toUnicode.forEach(function (charCode, unicodeCharCode) { + map[+charCode] = unicodeCharCode; + }); + } + this.toFontChar = map; + } else { + const glyphsUnicodeMap = getGlyphsUnicode(); + const map = []; + this.toUnicode.forEach((charCode, unicodeCharCode) => { + if (!this.composite) { + const glyphName = + this.differences[charCode] || this.defaultEncoding[charCode]; + const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); + if (unicode !== -1) { + unicodeCharCode = unicode; + } + } + map[+charCode] = unicodeCharCode; + }); + + // Attempt to improve the glyph mapping for (some) composite fonts that + // appear to lack meaningful ToUnicode data. + if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) { + if (/Tahoma|Verdana/i.test(name)) { + // Fixes issue15719.pdf and issue11242_reduced.pdf. + applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts()); + } + } + this.toFontChar = map; + } + + amendFallbackToUnicode(properties); + this.loadedName = fontName.split("-", 1)[0]; + } + + checkAndRepair(name, font, properties) { + const VALID_TABLES = [ + "OS/2", + "cmap", + "head", + "hhea", + "hmtx", + "maxp", + "name", + "post", + "loca", + "glyf", + "fpgm", + "prep", + "cvt ", + "CFF ", + ]; + + function readTables(file, numTables) { + const tables = Object.create(null); + tables["OS/2"] = null; + tables.cmap = null; + tables.head = null; + tables.hhea = null; + tables.hmtx = null; + tables.maxp = null; + tables.name = null; + tables.post = null; + + for (let i = 0; i < numTables; i++) { + const table = readTableEntry(file); + if (!VALID_TABLES.includes(table.tag)) { + continue; // skipping table if it's not a required or optional table + } + if (table.length === 0) { + continue; // skipping empty tables + } + tables[table.tag] = table; + } + return tables; + } + + function readTableEntry(file) { + const tag = file.getString(4); + + const checksum = file.getInt32() >>> 0; + const offset = file.getInt32() >>> 0; + const length = file.getInt32() >>> 0; + + // Read the table associated data + const previousPosition = file.pos; + file.pos = file.start || 0; + file.skip(offset); + const data = file.getBytes(length); + file.pos = previousPosition; + + if (tag === "head") { + // clearing checksum adjustment + data[8] = data[9] = data[10] = data[11] = 0; + data[17] |= 0x20; // Set font optimized for cleartype flag. + } + + return { + tag, + checksum, + length, + offset, + data, + }; + } + + function readOpenTypeHeader(ttf) { + return { + version: ttf.getString(4), + numTables: ttf.getUint16(), + searchRange: ttf.getUint16(), + entrySelector: ttf.getUint16(), + rangeShift: ttf.getUint16(), + }; + } + + function readTrueTypeCollectionHeader(ttc) { + const ttcTag = ttc.getString(4); + assert(ttcTag === "ttcf", "Must be a TrueType Collection font."); + + const majorVersion = ttc.getUint16(); + const minorVersion = ttc.getUint16(); + const numFonts = ttc.getInt32() >>> 0; + const offsetTable = []; + for (let i = 0; i < numFonts; i++) { + offsetTable.push(ttc.getInt32() >>> 0); + } + + const header = { + ttcTag, + majorVersion, + minorVersion, + numFonts, + offsetTable, + }; + switch (majorVersion) { + case 1: + return header; + case 2: + header.dsigTag = ttc.getInt32() >>> 0; + header.dsigLength = ttc.getInt32() >>> 0; + header.dsigOffset = ttc.getInt32() >>> 0; + return header; + } + throw new FormatError( + `Invalid TrueType Collection majorVersion: ${majorVersion}.` + ); + } + + function readTrueTypeCollectionData(ttc, fontName) { + const { numFonts, offsetTable } = readTrueTypeCollectionHeader(ttc); + const fontNameParts = fontName.split("+"); + let fallbackData; + + for (let i = 0; i < numFonts; i++) { + ttc.pos = (ttc.start || 0) + offsetTable[i]; + const potentialHeader = readOpenTypeHeader(ttc); + const potentialTables = readTables(ttc, potentialHeader.numTables); + + if (!potentialTables.name) { + throw new FormatError( + 'TrueType Collection font must contain a "name" table.' + ); + } + const [nameTable] = readNameTable(potentialTables.name); + + for (let j = 0, jj = nameTable.length; j < jj; j++) { + for (let k = 0, kk = nameTable[j].length; k < kk; k++) { + const nameEntry = nameTable[j][k]?.replaceAll(/\s/g, ""); + if (!nameEntry) { + continue; + } + if (nameEntry === fontName) { + return { + header: potentialHeader, + tables: potentialTables, + }; + } + if (fontNameParts.length < 2) { + continue; + } + for (const part of fontNameParts) { + if (nameEntry === part) { + fallbackData = { + name: part, + header: potentialHeader, + tables: potentialTables, + }; + } + } + } + } + } + if (fallbackData) { + warn( + `TrueType Collection does not contain "${fontName}" font, ` + + `falling back to "${fallbackData.name}" font instead.` + ); + return { + header: fallbackData.header, + tables: fallbackData.tables, + }; + } + throw new FormatError( + `TrueType Collection does not contain "${fontName}" font.` + ); + } + + /** + * Read the appropriate subtable from the cmap according to 9.6.6.4 from + * PDF spec + */ + function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) { + if (!cmap) { + warn("No cmap table available."); + return { + platformId: -1, + encodingId: -1, + mappings: [], + hasShortCmap: false, + }; + } + let segment; + let start = (file.start || 0) + cmap.offset; + file.pos = start; + + file.skip(2); // version + const numTables = file.getUint16(); + + let potentialTable; + let canBreak = false; + // There's an order of preference in terms of which cmap subtable to + // use: + // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table + // - symbolic fonts the preference is a 3,0 table then a 1,0 table + // The following takes advantage of the fact that the tables are sorted + // to work. + for (let i = 0; i < numTables; i++) { + const platformId = file.getUint16(); + const encodingId = file.getUint16(); + const offset = file.getInt32() >>> 0; + let useTable = false; + + // Sometimes there are multiple of the same type of table. Default + // to choosing the first table and skip the rest. + if ( + potentialTable?.platformId === platformId && + potentialTable?.encodingId === encodingId + ) { + continue; + } + + if ( + platformId === 0 && + (encodingId === /* Unicode Default */ 0 || + encodingId === /* Unicode 1.1 */ 1 || + encodingId === /* Unicode BMP */ 3) + ) { + useTable = true; + // Continue the loop since there still may be a higher priority + // table. + } else if (platformId === 1 && encodingId === 0) { + useTable = true; + // Continue the loop since there still may be a higher priority + // table. + } else if ( + platformId === 3 && + encodingId === 1 && + (hasEncoding || !potentialTable) + ) { + useTable = true; + if (!isSymbolicFont) { + canBreak = true; + } + } else if (isSymbolicFont && platformId === 3 && encodingId === 0) { + useTable = true; + + let correctlySorted = true; + if (i < numTables - 1) { + const nextBytes = file.peekBytes(2), + nextPlatformId = int16(nextBytes[0], nextBytes[1]); + if (nextPlatformId < platformId) { + correctlySorted = false; + } + } + if (correctlySorted) { + canBreak = true; + } + } + + if (useTable) { + potentialTable = { + platformId, + encodingId, + offset, + }; + } + if (canBreak) { + break; + } + } + + if (potentialTable) { + file.pos = start + potentialTable.offset; + } + if (!potentialTable || file.peekByte() === -1) { + warn("Could not find a preferred cmap table."); + return { + platformId: -1, + encodingId: -1, + mappings: [], + hasShortCmap: false, + }; + } + + const format = file.getUint16(); + let hasShortCmap = false; + const mappings = []; + let j, glyphId; + + // TODO(mack): refactor this cmap subtable reading logic out + if (format === 0) { + file.skip(2 + 2); // length + language + + for (j = 0; j < 256; j++) { + const index = file.getByte(); + if (!index) { + continue; + } + mappings.push({ + charCode: j, + glyphId: index, + }); + } + hasShortCmap = true; + } else if (format === 2) { + file.skip(2 + 2); // length + language + + const subHeaderKeys = []; + let maxSubHeaderKey = 0; + // Read subHeaderKeys. If subHeaderKeys[i] === 0, then i is a + // single-byte character. Otherwise, i is the first byte of a + // multi-byte character, and the value is 8*index into + // subHeaders. + for (let i = 0; i < 256; i++) { + const subHeaderKey = file.getUint16() >> 3; + subHeaderKeys.push(subHeaderKey); + maxSubHeaderKey = Math.max(subHeaderKey, maxSubHeaderKey); + } + // Read subHeaders. The number of entries is determined + // dynamically based on the subHeaderKeys found above. + const subHeaders = []; + for (let i = 0; i <= maxSubHeaderKey; i++) { + subHeaders.push({ + firstCode: file.getUint16(), + entryCount: file.getUint16(), + idDelta: signedInt16(file.getByte(), file.getByte()), + idRangePos: file.pos + file.getUint16(), + }); + } + for (let i = 0; i < 256; i++) { + if (subHeaderKeys[i] === 0) { + // i is a single-byte code. + file.pos = subHeaders[0].idRangePos + 2 * i; + glyphId = file.getUint16(); + mappings.push({ + charCode: i, + glyphId, + }); + } else { + // i is the first byte of a two-byte code. + const s = subHeaders[subHeaderKeys[i]]; + for (j = 0; j < s.entryCount; j++) { + const charCode = (i << 8) + j + s.firstCode; + file.pos = s.idRangePos + 2 * j; + glyphId = file.getUint16(); + if (glyphId !== 0) { + glyphId = (glyphId + s.idDelta) % 65536; + } + mappings.push({ + charCode, + glyphId, + }); + } + } + } + } else if (format === 4) { + file.skip(2 + 2); // length + language + + // re-creating the table in format 4 since the encoding + // might be changed + const segCount = file.getUint16() >> 1; + file.skip(6); // skipping range fields + const segments = []; + let segIndex; + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments.push({ end: file.getUint16() }); + } + file.skip(2); + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments[segIndex].start = file.getUint16(); + } + + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments[segIndex].delta = file.getUint16(); + } + + let offsetsCount = 0, + offsetIndex; + for (segIndex = 0; segIndex < segCount; segIndex++) { + segment = segments[segIndex]; + const rangeOffset = file.getUint16(); + if (!rangeOffset) { + segment.offsetIndex = -1; + continue; + } + + offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); + segment.offsetIndex = offsetIndex; + offsetsCount = Math.max( + offsetsCount, + offsetIndex + segment.end - segment.start + 1 + ); + } + + const offsets = []; + for (j = 0; j < offsetsCount; j++) { + offsets.push(file.getUint16()); + } + + for (segIndex = 0; segIndex < segCount; segIndex++) { + segment = segments[segIndex]; + start = segment.start; + const end = segment.end; + const delta = segment.delta; + offsetIndex = segment.offsetIndex; + + for (j = start; j <= end; j++) { + if (j === 0xffff) { + continue; + } + + glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start]; + glyphId = (glyphId + delta) & 0xffff; + mappings.push({ + charCode: j, + glyphId, + }); + } + } + } else if (format === 6) { + file.skip(2 + 2); // length + language + + // Format 6 is a 2-bytes dense mapping, which means the font data + // lives glue together even if they are pretty far in the unicode + // table. (This looks weird, so I can have missed something), this + // works on Linux but seems to fails on Mac so let's rewrite the + // cmap table to a 3-1-4 style + const firstCode = file.getUint16(); + const entryCount = file.getUint16(); + + for (j = 0; j < entryCount; j++) { + glyphId = file.getUint16(); + const charCode = firstCode + j; + + mappings.push({ + charCode, + glyphId, + }); + } + } else if (format === 12) { + file.skip(2 + 4 + 4); // reserved + length + language + + const nGroups = file.getInt32() >>> 0; + for (j = 0; j < nGroups; j++) { + const startCharCode = file.getInt32() >>> 0; + const endCharCode = file.getInt32() >>> 0; + let glyphCode = file.getInt32() >>> 0; + + for ( + let charCode = startCharCode; + charCode <= endCharCode; + charCode++ + ) { + mappings.push({ + charCode, + glyphId: glyphCode++, + }); + } + } + } else { + warn("cmap table has unsupported format: " + format); + return { + platformId: -1, + encodingId: -1, + mappings: [], + hasShortCmap: false, + }; + } + + // removing duplicate entries + mappings.sort(function (a, b) { + return a.charCode - b.charCode; + }); + const finalMappings = [], + seenCharCodes = new Set(); + for (const map of mappings) { + const { charCode } = map; + + if (seenCharCodes.has(charCode)) { + continue; + } + seenCharCodes.add(charCode); + finalMappings.push(map); + } + + return { + platformId: potentialTable.platformId, + encodingId: potentialTable.encodingId, + mappings: finalMappings, + hasShortCmap, + }; + } + + function sanitizeMetrics( + file, + header, + metrics, + headTable, + numGlyphs, + dupFirstEntry + ) { + if (!header) { + if (metrics) { + metrics.data = null; + } + return; + } + + file.pos = (file.start || 0) + header.offset; + file.pos += 4; // version + file.pos += 2; // ascent + file.pos += 2; // descent + file.pos += 2; // linegap + file.pos += 2; // adv_width_max + file.pos += 2; // min_sb1 + file.pos += 2; // min_sb2 + file.pos += 2; // max_extent + file.pos += 2; // caret_slope_rise + file.pos += 2; // caret_slope_run + const caretOffset = file.getUint16(); + file.pos += 8; // reserved + file.pos += 2; // format + let numOfMetrics = file.getUint16(); + + if (caretOffset !== 0) { + const macStyle = int16(headTable.data[44], headTable.data[45]); + if (!(macStyle & 2)) { + // Suppress OTS warnings about the `caretOffset` in the hhea-table. + header.data[22] = 0; + header.data[23] = 0; + } + } + + if (numOfMetrics > numGlyphs) { + info( + `The numOfMetrics (${numOfMetrics}) should not be ` + + `greater than the numGlyphs (${numGlyphs}).` + ); + // Reduce numOfMetrics if it is greater than numGlyphs + numOfMetrics = numGlyphs; + header.data[34] = (numOfMetrics & 0xff00) >> 8; + header.data[35] = numOfMetrics & 0x00ff; + } + + const numOfSidebearings = numGlyphs - numOfMetrics; + const numMissing = + numOfSidebearings - ((metrics.length - numOfMetrics * 4) >> 1); + + if (numMissing > 0) { + // For each missing glyph, we set both the width and lsb to 0 (zero). + // Since we need to add two properties for each glyph, this explains + // the use of |numMissing * 2| when initializing the typed array. + const entries = new Uint8Array(metrics.length + numMissing * 2); + entries.set(metrics.data); + if (dupFirstEntry) { + // Set the sidebearing value of the duplicated glyph. + entries[metrics.length] = metrics.data[2]; + entries[metrics.length + 1] = metrics.data[3]; + } + metrics.data = entries; + } + } + + function sanitizeGlyph( + source, + sourceStart, + sourceEnd, + dest, + destStart, + hintsValid + ) { + const glyphProfile = { + length: 0, + sizeOfInstructions: 0, + }; + if ( + sourceStart < 0 || + sourceStart >= source.length || + sourceEnd > source.length || + sourceEnd - sourceStart <= 12 + ) { + // If the offsets are wrong or the glyph is too small, remove it. + return glyphProfile; + } + const glyf = source.subarray(sourceStart, sourceEnd); + + // Sanitize the glyph bounding box. + const xMin = signedInt16(glyf[2], glyf[3]); + const yMin = signedInt16(glyf[4], glyf[5]); + const xMax = signedInt16(glyf[6], glyf[7]); + const yMax = signedInt16(glyf[8], glyf[9]); + + if (xMin > xMax) { + writeSignedInt16(glyf, 2, xMax); + writeSignedInt16(glyf, 6, xMin); + } + if (yMin > yMax) { + writeSignedInt16(glyf, 4, yMax); + writeSignedInt16(glyf, 8, yMin); + } + + const contoursCount = signedInt16(glyf[0], glyf[1]); + if (contoursCount < 0) { + if (contoursCount < -1) { + // OTS doesn't like contour count to be less than -1. + // The glyph data offsets are very likely wrong and + // having something lower than -1, very likely, implies + // to have some garbage data. + return glyphProfile; + } + // complex glyph, writing as is + dest.set(glyf, destStart); + glyphProfile.length = glyf.length; + return glyphProfile; + } + + let i, + j = 10, + flagsCount = 0; + for (i = 0; i < contoursCount; i++) { + const endPoint = (glyf[j] << 8) | glyf[j + 1]; + flagsCount = endPoint + 1; + j += 2; + } + // skipping instructions + const instructionsStart = j; + const instructionsLength = (glyf[j] << 8) | glyf[j + 1]; + glyphProfile.sizeOfInstructions = instructionsLength; + j += 2 + instructionsLength; + const instructionsEnd = j; + // validating flags + let coordinatesLength = 0; + for (i = 0; i < flagsCount; i++) { + const flag = glyf[j++]; + if (flag & 0xc0) { + // reserved flags must be zero, cleaning up + glyf[j - 1] = flag & 0x3f; + } + let xLength = 2; + if (flag & 2) { + xLength = 1; + } else if (flag & 16) { + xLength = 0; + } + let yLength = 2; + if (flag & 4) { + yLength = 1; + } else if (flag & 32) { + yLength = 0; + } + const xyLength = xLength + yLength; + coordinatesLength += xyLength; + if (flag & 8) { + const repeat = glyf[j++]; + if (repeat === 0) { + // The repeat count should be non-zero when the repeat flag is set. + glyf[j - 1] ^= 8; + } + i += repeat; + coordinatesLength += repeat * xyLength; + } + } + // glyph without coordinates will be rejected + if (coordinatesLength === 0) { + return glyphProfile; + } + let glyphDataLength = j + coordinatesLength; + if (glyphDataLength > glyf.length) { + // not enough data for coordinates + return glyphProfile; + } + if (!hintsValid && instructionsLength > 0) { + dest.set(glyf.subarray(0, instructionsStart), destStart); + dest.set([0, 0], destStart + instructionsStart); + dest.set( + glyf.subarray(instructionsEnd, glyphDataLength), + destStart + instructionsStart + 2 + ); + glyphDataLength -= instructionsLength; + if (glyf.length - glyphDataLength > 3) { + glyphDataLength = (glyphDataLength + 3) & ~3; + } + glyphProfile.length = glyphDataLength; + return glyphProfile; + } + if (glyf.length - glyphDataLength > 3) { + // truncating and aligning to 4 bytes the long glyph data + glyphDataLength = (glyphDataLength + 3) & ~3; + dest.set(glyf.subarray(0, glyphDataLength), destStart); + glyphProfile.length = glyphDataLength; + return glyphProfile; + } + // glyph data is fine + dest.set(glyf, destStart); + glyphProfile.length = glyf.length; + return glyphProfile; + } + + function sanitizeHead(head, numGlyphs, locaLength) { + const data = head.data; + + // Validate version: + // Should always be 0x00010000 + const version = int32(data[0], data[1], data[2], data[3]); + if (version >> 16 !== 1) { + info("Attempting to fix invalid version in head table: " + version); + data[0] = 0; + data[1] = 1; + data[2] = 0; + data[3] = 0; + } + + const indexToLocFormat = int16(data[50], data[51]); + if (indexToLocFormat < 0 || indexToLocFormat > 1) { + info( + "Attempting to fix invalid indexToLocFormat in head table: " + + indexToLocFormat + ); + + // The value of indexToLocFormat should be 0 if the loca table + // consists of short offsets, and should be 1 if the loca table + // consists of long offsets. + // + // The number of entries in the loca table should be numGlyphs + 1. + // + // Using this information, we can work backwards to deduce if the + // size of each offset in the loca table, and thus figure out the + // appropriate value for indexToLocFormat. + + const numGlyphsPlusOne = numGlyphs + 1; + if (locaLength === numGlyphsPlusOne << 1) { + // 0x0000 indicates the loca table consists of short offsets + data[50] = 0; + data[51] = 0; + } else if (locaLength === numGlyphsPlusOne << 2) { + // 0x0001 indicates the loca table consists of long offsets + data[50] = 0; + data[51] = 1; + } else { + throw new FormatError( + "Could not fix indexToLocFormat: " + indexToLocFormat + ); + } + } + } + + function sanitizeGlyphLocations( + loca, + glyf, + numGlyphs, + isGlyphLocationsLong, + hintsValid, + dupFirstEntry, + maxSizeOfInstructions + ) { + let itemSize, itemDecode, itemEncode; + if (isGlyphLocationsLong) { + itemSize = 4; + itemDecode = function fontItemDecodeLong(data, offset) { + return ( + (data[offset] << 24) | + (data[offset + 1] << 16) | + (data[offset + 2] << 8) | + data[offset + 3] + ); + }; + itemEncode = function fontItemEncodeLong(data, offset, value) { + data[offset] = (value >>> 24) & 0xff; + data[offset + 1] = (value >> 16) & 0xff; + data[offset + 2] = (value >> 8) & 0xff; + data[offset + 3] = value & 0xff; + }; + } else { + itemSize = 2; + itemDecode = function fontItemDecode(data, offset) { + return (data[offset] << 9) | (data[offset + 1] << 1); + }; + itemEncode = function fontItemEncode(data, offset, value) { + data[offset] = (value >> 9) & 0xff; + data[offset + 1] = (value >> 1) & 0xff; + }; + } + // The first glyph is duplicated. + const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs; + const locaDataSize = itemSize * (1 + numGlyphsOut); + // Resize loca table to account for duplicated glyph. + const locaData = new Uint8Array(locaDataSize); + locaData.set(loca.data.subarray(0, locaDataSize)); + loca.data = locaData; + // removing the invalid glyphs + const oldGlyfData = glyf.data; + const oldGlyfDataLength = oldGlyfData.length; + const newGlyfData = new Uint8Array(oldGlyfDataLength); + + // The spec says the offsets should be in ascending order, however + // this is not true for some fonts or they use the offset of 0 to mark a + // glyph as missing. OTS requires the offsets to be in order and not to + // be zero, so we must sort and rebuild the loca table and potentially + // re-arrange the glyf data. + let i, j; + const locaEntries = []; + // There are numGlyphs + 1 loca table entries. + for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) { + let offset = itemDecode(locaData, j); + if (offset > oldGlyfDataLength) { + offset = oldGlyfDataLength; + } + locaEntries.push({ + index: i, + offset, + endOffset: 0, + }); + } + locaEntries.sort((a, b) => a.offset - b.offset); + // Now the offsets are sorted, calculate the end offset of each glyph. + // The last loca entry's endOffset is not calculated since it's the end + // of the data and will be stored on the previous entry's endOffset. + for (i = 0; i < numGlyphs; i++) { + locaEntries[i].endOffset = locaEntries[i + 1].offset; + } + // Re-sort so glyphs aren't out of order. + locaEntries.sort((a, b) => a.index - b.index); + // Calculate the endOffset of the "first" glyph correctly when there are + // *multiple* empty ones at the start of the data (fixes issue14618.pdf). + for (i = 0; i < numGlyphs; i++) { + const { offset, endOffset } = locaEntries[i]; + if (offset !== 0 || endOffset !== 0) { + break; + } + const nextOffset = locaEntries[i + 1].offset; + if (nextOffset === 0) { + continue; + } + locaEntries[i].endOffset = nextOffset; + break; + } + + // If the last offset is 0 in the loca table then we can't compute the + // endOffset for the last glyph. So in such a case we set the endOffset + // to the end of the data (fixes issue #17671). + const last = locaEntries.at(-2); + if (last.offset !== 0 && last.endOffset === 0) { + last.endOffset = oldGlyfDataLength; + } + + const missingGlyphs = Object.create(null); + let writeOffset = 0; + itemEncode(locaData, 0, writeOffset); + for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { + const glyphProfile = sanitizeGlyph( + oldGlyfData, + locaEntries[i].offset, + locaEntries[i].endOffset, + newGlyfData, + writeOffset, + hintsValid + ); + const newLength = glyphProfile.length; + if (newLength === 0) { + missingGlyphs[i] = true; + } + if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) { + maxSizeOfInstructions = glyphProfile.sizeOfInstructions; + } + writeOffset += newLength; + itemEncode(locaData, j, writeOffset); + } + + if (writeOffset === 0) { + // glyf table cannot be empty -- redoing the glyf and loca tables + // to have single glyph with one point + const simpleGlyph = new Uint8Array([ + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0, + ]); + for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) { + itemEncode(locaData, j, simpleGlyph.length); + } + glyf.data = simpleGlyph; + } else if (dupFirstEntry) { + // Browsers will not display a glyph at position 0. Typically glyph 0 + // is notdef, but a number of fonts put a valid glyph there so it must + // be duplicated and appended. + const firstEntryLength = itemDecode(locaData, itemSize); + if (newGlyfData.length > firstEntryLength + writeOffset) { + glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset); + } else { + glyf.data = new Uint8Array(firstEntryLength + writeOffset); + glyf.data.set(newGlyfData.subarray(0, writeOffset)); + } + glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset); + itemEncode( + loca.data, + locaData.length - itemSize, + writeOffset + firstEntryLength + ); + } else { + glyf.data = newGlyfData.subarray(0, writeOffset); + } + return { + missingGlyphs, + maxSizeOfInstructions, + }; + } + + function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) { + const start = (font.start || 0) + post.offset; + font.pos = start; + + const length = post.length, + end = start + length; + const version = font.getInt32(); + // skip rest to the tables + font.skip(28); + + let glyphNames; + let valid = true; + let i; + + switch (version) { + case 0x00010000: + glyphNames = MacStandardGlyphOrdering; + break; + case 0x00020000: + const numGlyphs = font.getUint16(); + if (numGlyphs !== maxpNumGlyphs) { + valid = false; + break; + } + const glyphNameIndexes = []; + for (i = 0; i < numGlyphs; ++i) { + const index = font.getUint16(); + if (index >= 32768) { + valid = false; + break; + } + glyphNameIndexes.push(index); + } + if (!valid) { + break; + } + const customNames = [], + strBuf = []; + while (font.pos < end) { + const stringLength = font.getByte(); + strBuf.length = stringLength; + for (i = 0; i < stringLength; ++i) { + strBuf[i] = String.fromCharCode(font.getByte()); + } + customNames.push(strBuf.join("")); + } + glyphNames = []; + for (i = 0; i < numGlyphs; ++i) { + const j = glyphNameIndexes[i]; + if (j < 258) { + glyphNames.push(MacStandardGlyphOrdering[j]); + continue; + } + glyphNames.push(customNames[j - 258]); + } + break; + case 0x00030000: + break; + default: + warn("Unknown/unsupported post table version " + version); + valid = false; + if (propertiesObj.defaultEncoding) { + glyphNames = propertiesObj.defaultEncoding; + } + break; + } + propertiesObj.glyphNames = glyphNames; + return valid; + } + + function readNameTable(nameTable) { + const start = (font.start || 0) + nameTable.offset; + font.pos = start; + + const names = [[], []], + records = []; + const length = nameTable.length, + end = start + length; + const format = font.getUint16(); + const FORMAT_0_HEADER_LENGTH = 6; + if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { + // unsupported name table format or table "too" small + return [names, records]; + } + const numRecords = font.getUint16(); + const stringsStart = font.getUint16(); + const NAME_RECORD_LENGTH = 12; + let i, ii; + + for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) { + const r = { + platform: font.getUint16(), + encoding: font.getUint16(), + language: font.getUint16(), + name: font.getUint16(), + length: font.getUint16(), + offset: font.getUint16(), + }; + // using only Macintosh and Windows platform/encoding names + if (isMacNameRecord(r) || isWinNameRecord(r)) { + records.push(r); + } + } + for (i = 0, ii = records.length; i < ii; i++) { + const record = records[i]; + if (record.length <= 0) { + continue; // Nothing to process, ignoring. + } + const pos = start + stringsStart + record.offset; + if (pos + record.length > end) { + continue; // outside of name table, ignoring + } + font.pos = pos; + const nameIndex = record.name; + if (record.encoding) { + // unicode + let str = ""; + for (let j = 0, jj = record.length; j < jj; j += 2) { + str += String.fromCharCode(font.getUint16()); + } + names[1][nameIndex] = str; + } else { + names[0][nameIndex] = font.getString(record.length); + } + } + return [names, records]; + } + + // prettier-ignore + const TTOpsStackDeltas = [ + 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, + -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, + 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, + 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2, + 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1, + -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1, + -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, + -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2]; + // 0xC0-DF == -1 and 0xE0-FF == -2 + + function sanitizeTTProgram(table, ttContext) { + let data = table.data; + let i = 0, + j, + n, + b, + funcId, + pc, + lastEndf = 0, + lastDeff = 0; + const stack = []; + const callstack = []; + const functionsCalled = []; + let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; + let inFDEF = false, + ifLevel = 0, + inELSE = 0; + for (let ii = data.length; i < ii; ) { + const op = data[i++]; + // The TrueType instruction set docs can be found at + // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html + if (op === 0x40) { + // NPUSHB - pushes n bytes + n = data[i++]; + if (inFDEF || inELSE) { + i += n; + } else { + for (j = 0; j < n; j++) { + stack.push(data[i++]); + } + } + } else if (op === 0x41) { + // NPUSHW - pushes n words + n = data[i++]; + if (inFDEF || inELSE) { + i += n * 2; + } else { + for (j = 0; j < n; j++) { + b = data[i++]; + stack.push((b << 8) | data[i++]); + } + } + } else if ((op & 0xf8) === 0xb0) { + // PUSHB - pushes bytes + n = op - 0xb0 + 1; + if (inFDEF || inELSE) { + i += n; + } else { + for (j = 0; j < n; j++) { + stack.push(data[i++]); + } + } + } else if ((op & 0xf8) === 0xb8) { + // PUSHW - pushes words + n = op - 0xb8 + 1; + if (inFDEF || inELSE) { + i += n * 2; + } else { + for (j = 0; j < n; j++) { + b = data[i++]; + stack.push(signedInt16(b, data[i++])); + } + } + } else if (op === 0x2b && !tooComplexToFollowFunctions) { + // CALL + if (!inFDEF && !inELSE) { + // collecting information about which functions are used + funcId = stack.at(-1); + if (isNaN(funcId)) { + info("TT: CALL empty stack (or invalid entry)."); + } else { + ttContext.functionsUsed[funcId] = true; + if (funcId in ttContext.functionsStackDeltas) { + const newStackLength = + stack.length + ttContext.functionsStackDeltas[funcId]; + if (newStackLength < 0) { + warn("TT: CALL invalid functions stack delta."); + ttContext.hintsValid = false; + return; + } + stack.length = newStackLength; + } else if ( + funcId in ttContext.functionsDefined && + !functionsCalled.includes(funcId) + ) { + callstack.push({ data, i, stackTop: stack.length - 1 }); + functionsCalled.push(funcId); + pc = ttContext.functionsDefined[funcId]; + if (!pc) { + warn("TT: CALL non-existent function"); + ttContext.hintsValid = false; + return; + } + data = pc.data; + i = pc.i; + } + } + } + } else if (op === 0x2c && !tooComplexToFollowFunctions) { + // FDEF + if (inFDEF || inELSE) { + warn("TT: nested FDEFs not allowed"); + tooComplexToFollowFunctions = true; + } + inFDEF = true; + // collecting information about which functions are defined + lastDeff = i; + funcId = stack.pop(); + ttContext.functionsDefined[funcId] = { data, i }; + } else if (op === 0x2d) { + // ENDF - end of function + if (inFDEF) { + inFDEF = false; + lastEndf = i; + } else { + pc = callstack.pop(); + if (!pc) { + warn("TT: ENDF bad stack"); + ttContext.hintsValid = false; + return; + } + funcId = functionsCalled.pop(); + data = pc.data; + i = pc.i; + ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop; + } + } else if (op === 0x89) { + // IDEF - instruction definition + if (inFDEF || inELSE) { + warn("TT: nested IDEFs not allowed"); + tooComplexToFollowFunctions = true; + } + inFDEF = true; + // recording it as a function to track ENDF + lastDeff = i; + } else if (op === 0x58) { + // IF + ++ifLevel; + } else if (op === 0x1b) { + // ELSE + inELSE = ifLevel; + } else if (op === 0x59) { + // EIF + if (inELSE === ifLevel) { + inELSE = 0; + } + --ifLevel; + } else if (op === 0x1c) { + // JMPR + if (!inFDEF && !inELSE) { + const offset = stack.at(-1); + // only jumping forward to prevent infinite loop + if (offset > 0) { + i += offset - 1; + } + } + } + // Adjusting stack not extactly, but just enough to get function id + if (!inFDEF && !inELSE) { + let stackDelta = 0; + if (op <= 0x8e) { + stackDelta = TTOpsStackDeltas[op]; + } else if (op >= 0xc0 && op <= 0xdf) { + stackDelta = -1; + } else if (op >= 0xe0) { + stackDelta = -2; + } + if (op >= 0x71 && op <= 0x75) { + n = stack.pop(); + if (!isNaN(n)) { + stackDelta = -n * 2; + } + } + while (stackDelta < 0 && stack.length > 0) { + stack.pop(); + stackDelta++; + } + while (stackDelta > 0) { + stack.push(NaN); // pushing any number into stack + stackDelta--; + } + } + } + ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; + const content = [data]; + if (i > data.length) { + content.push(new Uint8Array(i - data.length)); + } + if (lastDeff > lastEndf) { + warn("TT: complementing a missing function tail"); + // new function definition started, but not finished + // complete function by [CLEAR, ENDF] + content.push(new Uint8Array([0x22, 0x2d])); + } + foldTTTable(table, content); + } + + function checkInvalidFunctions(ttContext, maxFunctionDefs) { + if (ttContext.tooComplexToFollowFunctions) { + return; + } + if (ttContext.functionsDefined.length > maxFunctionDefs) { + warn("TT: more functions defined than expected"); + ttContext.hintsValid = false; + return; + } + for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { + if (j > maxFunctionDefs) { + warn("TT: invalid function id: " + j); + ttContext.hintsValid = false; + return; + } + if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) { + warn("TT: undefined function: " + j); + ttContext.hintsValid = false; + return; + } + } + } + + function foldTTTable(table, content) { + if (content.length > 1) { + // concatenating the content items + let newLength = 0; + let j, jj; + for (j = 0, jj = content.length; j < jj; j++) { + newLength += content[j].length; + } + newLength = (newLength + 3) & ~3; + const result = new Uint8Array(newLength); + let pos = 0; + for (j = 0, jj = content.length; j < jj; j++) { + result.set(content[j], pos); + pos += content[j].length; + } + table.data = result; + table.length = newLength; + } + } + + function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) { + const ttContext = { + functionsDefined: [], + functionsUsed: [], + functionsStackDeltas: [], + tooComplexToFollowFunctions: false, + hintsValid: true, + }; + if (fpgm) { + sanitizeTTProgram(fpgm, ttContext); + } + if (prep) { + sanitizeTTProgram(prep, ttContext); + } + if (fpgm) { + checkInvalidFunctions(ttContext, maxFunctionDefs); + } + if (cvt && cvt.length & 1) { + const cvtData = new Uint8Array(cvt.length + 1); + cvtData.set(cvt.data); + cvt.data = cvtData; + } + return ttContext.hintsValid; + } + + // The following steps modify the original font data, making copy + font = new Stream(new Uint8Array(font.getBytes())); + + let header, tables; + if (isTrueTypeCollectionFile(font)) { + const ttcData = readTrueTypeCollectionData(font, this.name); + header = ttcData.header; + tables = ttcData.tables; + } else { + header = readOpenTypeHeader(font); + tables = readTables(font, header.numTables); + } + let cff, cffFile; + + const isTrueType = !tables["CFF "]; + if (!isTrueType) { + const isComposite = + properties.composite && + (properties.cidToGidMap?.length > 0 || + !(properties.cMap instanceof IdentityCMap)); + // OpenType font (skip composite fonts with non-default glyph mapping). + if ( + (header.version === "OTTO" && !isComposite) || + !tables.head || + !tables.hhea || + !tables.maxp || + !tables.post + ) { + // No major tables: throwing everything at `CFFFont`. + cffFile = new Stream(tables["CFF "].data); + cff = new CFFFont(cffFile, properties); + + adjustWidths(properties); + + return this.convert(name, cff, properties); + } + + delete tables.glyf; + delete tables.loca; + delete tables.fpgm; + delete tables.prep; + delete tables["cvt "]; + this.isOpenType = true; + } else { + if (!tables.loca) { + throw new FormatError('Required "loca" table is not found'); + } + if (!tables.glyf) { + warn('Required "glyf" table is not found -- trying to recover.'); + // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below. + tables.glyf = { + tag: "glyf", + data: new Uint8Array(0), + }; + } + this.isOpenType = false; + } + + if (!tables.maxp) { + throw new FormatError('Required "maxp" table is not found'); + } + + font.pos = (font.start || 0) + tables.maxp.offset; + let version = font.getInt32(); + const numGlyphs = font.getUint16(); + + if (version !== 0x00010000 && version !== 0x00005000) { + // https://learn.microsoft.com/en-us/typography/opentype/spec/maxp + if (tables.maxp.length === 6) { + version = 0x0005000; + } else if (tables.maxp.length >= 32) { + version = 0x00010000; + } else { + throw new FormatError(`"maxp" table has a wrong version number`); + } + writeUint32(tables.maxp.data, 0, version); + } + + if (properties.scaleFactors?.length === numGlyphs && isTrueType) { + const { scaleFactors } = properties; + const isGlyphLocationsLong = int16( + tables.head.data[50], + tables.head.data[51] + ); + + const glyphs = new GlyfTable({ + glyfTable: tables.glyf.data, + isGlyphLocationsLong, + locaTable: tables.loca.data, + numGlyphs, + }); + glyphs.scale(scaleFactors); + + const { glyf, loca, isLocationLong } = glyphs.write(); + tables.glyf.data = glyf; + tables.loca.data = loca; + + if (isLocationLong !== !!isGlyphLocationsLong) { + tables.head.data[50] = 0; + tables.head.data[51] = isLocationLong ? 1 : 0; + } + + const metrics = tables.hmtx.data; + + for (let i = 0; i < numGlyphs; i++) { + const j = 4 * i; + const advanceWidth = Math.round( + scaleFactors[i] * int16(metrics[j], metrics[j + 1]) + ); + metrics[j] = (advanceWidth >> 8) & 0xff; + metrics[j + 1] = advanceWidth & 0xff; + const lsb = Math.round( + scaleFactors[i] * signedInt16(metrics[j + 2], metrics[j + 3]) + ); + writeSignedInt16(metrics, j + 2, lsb); + } + } + + // Glyph 0 is duplicated and appended. + let numGlyphsOut = numGlyphs + 1; + let dupFirstEntry = true; + if (numGlyphsOut > 0xffff) { + dupFirstEntry = false; + numGlyphsOut = numGlyphs; + warn("Not enough space in glyfs to duplicate first glyph."); + } + let maxFunctionDefs = 0; + let maxSizeOfInstructions = 0; + if (version >= 0x00010000 && tables.maxp.length >= 32) { + // maxZones can be invalid + font.pos += 8; + const maxZones = font.getUint16(); + if (maxZones > 2) { + // reset to 2 if font has invalid maxZones + tables.maxp.data[14] = 0; + tables.maxp.data[15] = 2; + } + font.pos += 4; + maxFunctionDefs = font.getUint16(); + font.pos += 4; + maxSizeOfInstructions = font.getUint16(); + } + + tables.maxp.data[4] = numGlyphsOut >> 8; + tables.maxp.data[5] = numGlyphsOut & 255; + + const hintsValid = sanitizeTTPrograms( + tables.fpgm, + tables.prep, + tables["cvt "], + maxFunctionDefs + ); + if (!hintsValid) { + delete tables.fpgm; + delete tables.prep; + delete tables["cvt "]; + } + + // Ensure the hmtx table contains the advance width and + // sidebearings information for numGlyphs in the maxp table + sanitizeMetrics( + font, + tables.hhea, + tables.hmtx, + tables.head, + numGlyphsOut, + dupFirstEntry + ); + + if (!tables.head) { + throw new FormatError('Required "head" table is not found'); + } + + sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); + + let missingGlyphs = Object.create(null); + if (isTrueType) { + const isGlyphLocationsLong = int16( + tables.head.data[50], + tables.head.data[51] + ); + const glyphsInfo = sanitizeGlyphLocations( + tables.loca, + tables.glyf, + numGlyphs, + isGlyphLocationsLong, + hintsValid, + dupFirstEntry, + maxSizeOfInstructions + ); + missingGlyphs = glyphsInfo.missingGlyphs; + + // Some fonts have incorrect maxSizeOfInstructions values, so we use + // the computed value instead. + if (version >= 0x00010000 && tables.maxp.length >= 32) { + tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8; + tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255; + } + } + if (!tables.hhea) { + throw new FormatError('Required "hhea" table is not found'); + } + + // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth + // Sometimes it's 0. That needs to be fixed + if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) { + tables.hhea.data[10] = 0xff; + tables.hhea.data[11] = 0xff; + } + + // Extract some more font properties from the OpenType head and + // hhea tables; yMin and descent value are always negative. + const metricsOverride = { + unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), + yMax: signedInt16(tables.head.data[42], tables.head.data[43]), + yMin: signedInt16(tables.head.data[38], tables.head.data[39]), + ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]), + descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]), + lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9]), + }; + + // PDF FontDescriptor metrics lie -- using data from actual font. + this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm; + this.descent = metricsOverride.descent / metricsOverride.unitsPerEm; + this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm; + + if (this.cssFontInfo?.lineHeight) { + this.lineHeight = this.cssFontInfo.metrics.lineHeight; + this.lineGap = this.cssFontInfo.metrics.lineGap; + } else { + this.lineHeight = this.ascent - this.descent + this.lineGap; + } + + // The 'post' table has glyphs names. + if (tables.post) { + readPostScriptTable(tables.post, properties, numGlyphs); + } + + // The original 'post' table is not needed, replace it. + tables.post = { + tag: "post", + data: createPostTable(properties), + }; + + const charCodeToGlyphId = Object.create(null); + + // Helper function to try to skip mapping of empty glyphs. + function hasGlyph(glyphId) { + return !missingGlyphs[glyphId]; + } + + if (properties.composite) { + const cidToGidMap = properties.cidToGidMap || []; + const isCidToGidMapEmpty = cidToGidMap.length === 0; + + properties.cMap.forEach(function (charCode, cid) { + if (typeof cid === "string") { + cid = convertCidString(charCode, cid, /* shouldThrow = */ true); + } + if (cid > 0xffff) { + throw new FormatError("Max size of CID is 65,535"); + } + let glyphId = -1; + if (isCidToGidMapEmpty) { + glyphId = cid; + } else if (cidToGidMap[cid] !== undefined) { + glyphId = cidToGidMap[cid]; + } + + if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) { + charCodeToGlyphId[charCode] = glyphId; + } + }); + } else { + // Most of the following logic in this code branch is based on the + // 9.6.6.4 of the PDF spec. + const cmapTable = readCmapTable( + tables.cmap, + font, + this.isSymbolicFont, + properties.hasEncoding + ); + const cmapPlatformId = cmapTable.platformId; + const cmapEncodingId = cmapTable.encodingId; + const cmapMappings = cmapTable.mappings; + let baseEncoding = [], + forcePostTable = false; + if ( + properties.hasEncoding && + (properties.baseEncodingName === "MacRomanEncoding" || + properties.baseEncodingName === "WinAnsiEncoding") + ) { + baseEncoding = getEncoding(properties.baseEncodingName); + } + + // If the font has an encoding and is not symbolic then follow the rules + // in section 9.6.6.4 of the spec on how to map 3,1 and 1,0 cmaps. + if ( + properties.hasEncoding && + !this.isSymbolicFont && + ((cmapPlatformId === 3 && cmapEncodingId === 1) || + (cmapPlatformId === 1 && cmapEncodingId === 0)) + ) { + const glyphsUnicodeMap = getGlyphsUnicode(); + for (let charCode = 0; charCode < 256; charCode++) { + let glyphName; + if (this.differences[charCode] !== undefined) { + glyphName = this.differences[charCode]; + } else if (baseEncoding.length && baseEncoding[charCode] !== "") { + glyphName = baseEncoding[charCode]; + } else { + glyphName = StandardEncoding[charCode]; + } + if (!glyphName) { + continue; + } + // Ensure that non-standard glyph names are resolved to valid ones. + const standardGlyphName = recoverGlyphName( + glyphName, + glyphsUnicodeMap + ); + + let unicodeOrCharCode; + if (cmapPlatformId === 3 && cmapEncodingId === 1) { + unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName]; + } else if (cmapPlatformId === 1 && cmapEncodingId === 0) { + // TODO: the encoding needs to be updated with mac os table. + unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName); + } + + if (unicodeOrCharCode === undefined) { + // Not a valid glyph name, fallback to using the /ToUnicode map + // when no post-table exists (fixes issue13316_reduced.pdf). + if ( + !properties.glyphNames && + properties.hasIncludedToUnicodeMap && + !(this.toUnicode instanceof IdentityToUnicodeMap) + ) { + const unicode = this.toUnicode.get(charCode); + if (unicode) { + unicodeOrCharCode = unicode.codePointAt(0); + } + } + + if (unicodeOrCharCode === undefined) { + continue; // No valid glyph mapping found. + } + } + + for (const mapping of cmapMappings) { + if (mapping.charCode !== unicodeOrCharCode) { + continue; + } + charCodeToGlyphId[charCode] = mapping.glyphId; + break; + } + } + } else if (cmapPlatformId === 0) { + // Default Unicode semantics, use the charcodes as is. + for (const mapping of cmapMappings) { + charCodeToGlyphId[mapping.charCode] = mapping.glyphId; + } + // Always prefer the BaseEncoding/Differences arrays, when they exist + // (fixes issue13433.pdf). + forcePostTable = true; + } else if (cmapPlatformId === 3 && cmapEncodingId === 0) { + // When a (3, 0) cmap table is present, it is used instead but the + // spec has special rules for char codes in the range of 0xF000 to + // 0xF0FF and it says the (3, 0) table should map the values from + // the (1, 0) table by prepending 0xF0 to the char codes. To reverse + // this, the upper bits of the char code are cleared, but only for the + // special range since some PDFs have char codes outside of this range + // (e.g. 0x2013) which when masked would overwrite other values in the + // cmap. + for (const mapping of cmapMappings) { + let charCode = mapping.charCode; + if (charCode >= 0xf000 && charCode <= 0xf0ff) { + charCode &= 0xff; + } + charCodeToGlyphId[charCode] = mapping.glyphId; + } + } else { + // When there is only a (1, 0) cmap table, the char code is a single + // byte and it is used directly as the char code. + for (const mapping of cmapMappings) { + charCodeToGlyphId[mapping.charCode] = mapping.glyphId; + } + } + + // Last, try to map any missing charcodes using the post table. + if ( + properties.glyphNames && + (baseEncoding.length || this.differences.length) + ) { + for (let i = 0; i < 256; ++i) { + if (!forcePostTable && charCodeToGlyphId[i] !== undefined) { + continue; + } + const glyphName = this.differences[i] || baseEncoding[i]; + if (!glyphName) { + continue; + } + const glyphId = properties.glyphNames.indexOf(glyphName); + if (glyphId > 0 && hasGlyph(glyphId)) { + charCodeToGlyphId[i] = glyphId; + } + } + } + } + + if (charCodeToGlyphId.length === 0) { + // defines at least one glyph + charCodeToGlyphId[0] = 0; + } + + // Typically glyph 0 is duplicated and the mapping must be updated, but if + // there isn't enough room to duplicate, the glyph id is left the same. In + // this case, glyph 0 may not work correctly, but that is better than + // having the whole font fail. + let glyphZeroId = numGlyphsOut - 1; + if (!dupFirstEntry) { + glyphZeroId = 0; + } + + // When `cssFontInfo` is set, the font is used to render text in the HTML + // view (e.g. with Xfa) so nothing must be moved in the private use area. + if (!properties.cssFontInfo) { + // Converting glyphs and ids into font's cmap table + const newMapping = adjustMapping( + charCodeToGlyphId, + hasGlyph, + glyphZeroId, + this.toUnicode + ); + this.toFontChar = newMapping.toFontChar; + tables.cmap = { + tag: "cmap", + data: createCmapTable( + newMapping.charCodeToGlyphId, + newMapping.toUnicodeExtraMap, + numGlyphsOut + ), + }; + + if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) { + tables["OS/2"] = { + tag: "OS/2", + data: createOS2Table( + properties, + newMapping.charCodeToGlyphId, + metricsOverride + ), + }; + } + } + + if (!isTrueType) { + try { + // Trying to repair CFF file + cffFile = new Stream(tables["CFF "].data); + const parser = new CFFParser( + cffFile, + properties, + SEAC_ANALYSIS_ENABLED + ); + cff = parser.parse(); + cff.duplicateFirstGlyph(); + const compiler = new CFFCompiler(cff); + tables["CFF "].data = compiler.compile(); + } catch { + warn("Failed to compile font " + properties.loadedName); + } + } + + // Re-creating 'name' table + if (!tables.name) { + tables.name = { + tag: "name", + data: createNameTable(this.name), + }; + } else { + // ... using existing 'name' table as prototype + const [namePrototype, nameRecords] = readNameTable(tables.name); + + tables.name.data = createNameTable(name, namePrototype); + this.psName = namePrototype[0][6] || null; + + if (!properties.composite) { + // For TrueType fonts that do not include `ToUnicode` or `Encoding` + // data, attempt to use the name-table to improve text selection. + adjustTrueTypeToUnicode(properties, this.isSymbolicFont, nameRecords); + } + } + + const builder = new OpenTypeFileBuilder(header.version); + for (const tableTag in tables) { + builder.addTable(tableTag, tables[tableTag].data); + } + return builder.toArray(); + } + + convert(fontName, font, properties) { + // TODO: Check the charstring widths to determine this. + properties.fixedPitch = false; + + if (properties.builtInEncoding) { + // For Type1 fonts that do not include either `ToUnicode` or `Encoding` + // data, attempt to use the `builtInEncoding` to improve text selection. + adjustType1ToUnicode(properties, properties.builtInEncoding); + } + + // Type 1 fonts have a notdef inserted at the beginning, so glyph 0 + // becomes glyph 1. In a CFF font glyph 0 is appended to the end of the + // char strings. + let glyphZeroId = 1; + if (font instanceof CFFFont) { + glyphZeroId = font.numGlyphs - 1; + } + const mapping = font.getGlyphMapping(properties); + let newMapping = null; + let newCharCodeToGlyphId = mapping; + let toUnicodeExtraMap = null; + + // When `cssFontInfo` is set, the font is used to render text in the HTML + // view (e.g. with Xfa) so nothing must be moved in the private use area. + if (!properties.cssFontInfo) { + newMapping = adjustMapping( + mapping, + font.hasGlyphId.bind(font), + glyphZeroId, + this.toUnicode + ); + this.toFontChar = newMapping.toFontChar; + newCharCodeToGlyphId = newMapping.charCodeToGlyphId; + toUnicodeExtraMap = newMapping.toUnicodeExtraMap; + } + const numGlyphs = font.numGlyphs; + + function getCharCodes(charCodeToGlyphId, glyphId) { + let charCodes = null; + for (const charCode in charCodeToGlyphId) { + if (glyphId === charCodeToGlyphId[charCode]) { + (charCodes ||= []).push(charCode | 0); + } + } + return charCodes; + } + + function createCharCode(charCodeToGlyphId, glyphId) { + for (const charCode in charCodeToGlyphId) { + if (glyphId === charCodeToGlyphId[charCode]) { + return charCode | 0; + } + } + newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = + glyphId; + return newMapping.nextAvailableFontCharCode++; + } + + const seacs = font.seacs; + if (newMapping && SEAC_ANALYSIS_ENABLED && seacs?.length) { + const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; + const charset = font.getCharset(); + const seacMap = Object.create(null); + for (let glyphId in seacs) { + glyphId |= 0; + const seac = seacs[glyphId]; + const baseGlyphName = StandardEncoding[seac[2]]; + const accentGlyphName = StandardEncoding[seac[3]]; + const baseGlyphId = charset.indexOf(baseGlyphName); + const accentGlyphId = charset.indexOf(accentGlyphName); + if (baseGlyphId < 0 || accentGlyphId < 0) { + continue; + } + const accentOffset = { + x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4], + y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5], + }; + + const charCodes = getCharCodes(mapping, glyphId); + if (!charCodes) { + // There's no point in mapping it if the char code was never mapped + // to begin with. + continue; + } + for (const charCode of charCodes) { + // Find a fontCharCode that maps to the base and accent glyphs. + // If one doesn't exists, create it. + const charCodeToGlyphId = newMapping.charCodeToGlyphId; + const baseFontCharCode = createCharCode( + charCodeToGlyphId, + baseGlyphId + ); + const accentFontCharCode = createCharCode( + charCodeToGlyphId, + accentGlyphId + ); + seacMap[charCode] = { + baseFontCharCode, + accentFontCharCode, + accentOffset, + }; + } + } + properties.seacMap = seacMap; + } + + const unitsPerEm = properties.fontMatrix + ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs)) + : 1000; + + const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F"); + // PostScript Font Program + builder.addTable("CFF ", font.data); + // OS/2 and Windows Specific metrics + builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId)); + // Character to glyphs mapping + builder.addTable( + "cmap", + createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs) + ); + // Font header + builder.addTable( + "head", + "\x00\x01\x00\x00" + // Version number + "\x00\x00\x10\x00" + // fontRevision + "\x00\x00\x00\x00" + // checksumAdjustement + "\x5F\x0F\x3C\xF5" + // magicNumber + "\x00\x00" + // Flags + safeString16(unitsPerEm) + // unitsPerEM + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date + "\x00\x00" + // xMin + safeString16(properties.descent) + // yMin + "\x0F\xFF" + // xMax + safeString16(properties.ascent) + // yMax + string16(properties.italicAngle ? 2 : 0) + // macStyle + "\x00\x11" + // lowestRecPPEM + "\x00\x00" + // fontDirectionHint + "\x00\x00" + // indexToLocFormat + "\x00\x00" + ); // glyphDataFormat + + // Horizontal header + builder.addTable( + "hhea", + "\x00\x01\x00\x00" + // Version number + safeString16(properties.ascent) + // Typographic Ascent + safeString16(properties.descent) + // Typographic Descent + "\x00\x00" + // Line Gap + "\xFF\xFF" + // advanceWidthMax + "\x00\x00" + // minLeftSidebearing + "\x00\x00" + // minRightSidebearing + "\x00\x00" + // xMaxExtent + safeString16(properties.capHeight) + // caretSlopeRise + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun + "\x00\x00" + // caretOffset + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // metricDataFormat + string16(numGlyphs) + ); // Number of HMetrics + + // Horizontal metrics + builder.addTable( + "hmtx", + (function fontFieldsHmtx() { + const charstrings = font.charstrings; + const cffWidths = font.cff ? font.cff.widths : null; + let hmtx = "\x00\x00\x00\x00"; // Fake .notdef + for (let i = 1, ii = numGlyphs; i < ii; i++) { + let width = 0; + if (charstrings) { + const charstring = charstrings[i - 1]; + width = "width" in charstring ? charstring.width : 0; + } else if (cffWidths) { + width = Math.ceil(cffWidths[i] || 0); + } + hmtx += string16(width) + string16(0); + } + return hmtx; + })() + ); + + // Maximum profile + builder.addTable( + "maxp", + "\x00\x00\x50\x00" + string16(numGlyphs) // Version number + ); // Num of glyphs + + // Naming tables + builder.addTable("name", createNameTable(fontName)); + + // PostScript information + builder.addTable("post", createPostTable(properties)); + + return builder.toArray(); + } + + /** + * @private + */ + get _spaceWidth() { + // trying to estimate space character width + const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"]; + let width; + for (const glyphName of possibleSpaceReplacements) { + // if possible, getting width by glyph name + if (glyphName in this.widths) { + width = this.widths[glyphName]; + break; + } + const glyphsUnicodeMap = getGlyphsUnicode(); + const glyphUnicode = glyphsUnicodeMap[glyphName]; + // finding the charcode via unicodeToCID map + let charcode = 0; + if (this.composite && this.cMap.contains(glyphUnicode)) { + charcode = this.cMap.lookup(glyphUnicode); + + if (typeof charcode === "string") { + charcode = convertCidString(glyphUnicode, charcode); + } + } + // ... via toUnicode map + if (!charcode && this.toUnicode) { + charcode = this.toUnicode.charCodeOf(glyphUnicode); + } + // setting it to unicode if negative or undefined + if (charcode <= 0) { + charcode = glyphUnicode; + } + // trying to get width via charcode + width = this.widths[charcode]; + if (width) { + break; // the non-zero width found + } + } + return shadow(this, "_spaceWidth", width || this.defaultWidth); + } + + /** + * @private + */ + _charToGlyph(charcode, isSpace = false) { + let glyph = this._glyphCache[charcode]; + // All `Glyph`-properties, except `isSpace` in multi-byte strings, + // depend indirectly on the `charcode`. + if (glyph?.isSpace === isSpace) { + return glyph; + } + let fontCharCode, width, operatorListId; + + let widthCode = charcode; + if (this.cMap?.contains(charcode)) { + widthCode = this.cMap.lookup(charcode); + + if (typeof widthCode === "string") { + widthCode = convertCidString(charcode, widthCode); + } + } + width = this.widths[widthCode]; + if (typeof width !== "number") { + width = this.defaultWidth; + } + const vmetric = this.vmetrics?.[widthCode]; + + let unicode = this.toUnicode.get(charcode) || charcode; + if (typeof unicode === "number") { + unicode = String.fromCharCode(unicode); + } + + let isInFont = this.toFontChar[charcode] !== undefined; + // First try the toFontChar map, if it's not there then try falling + // back to the char code. + fontCharCode = this.toFontChar[charcode] || charcode; + if (this.missingFile) { + const glyphName = + this.differences[charcode] || this.defaultEncoding[charcode]; + if ( + (glyphName === ".notdef" || glyphName === "") && + this.type === "Type1" + ) { + // .notdef glyphs should be invisible in non-embedded Type1 fonts, so + // replace them with spaces. + fontCharCode = 0x20; + + if (glyphName === "") { + // Ensure that other relevant glyph properties are also updated + // (fixes issue18059.pdf). + width ||= this._spaceWidth; + unicode = String.fromCharCode(fontCharCode); + } + } + fontCharCode = mapSpecialUnicodeValues(fontCharCode); + } + + if (this.isType3Font) { + // Font char code in this case is actually a glyph name. + operatorListId = fontCharCode; + } + + let accent = null; + if (this.seacMap?.[charcode]) { + isInFont = true; + const seac = this.seacMap[charcode]; + fontCharCode = seac.baseFontCharCode; + accent = { + fontChar: String.fromCodePoint(seac.accentFontCharCode), + offset: seac.accentOffset, + }; + } + + let fontChar = ""; + if (typeof fontCharCode === "number") { + if (fontCharCode <= 0x10ffff) { + fontChar = String.fromCodePoint(fontCharCode); + } else { + warn(`charToGlyph - invalid fontCharCode: ${fontCharCode}`); + } + } + + if (this.missingFile && this.vertical && fontChar.length === 1) { + const vertical = getVerticalPresentationForm()[fontChar.charCodeAt(0)]; + if (vertical) { + fontChar = unicode = String.fromCharCode(vertical); + } + } + + glyph = new Glyph( + charcode, + fontChar, + unicode, + accent, + width, + vmetric, + operatorListId, + isSpace, + isInFont + ); + return (this._glyphCache[charcode] = glyph); + } + + charsToGlyphs(chars) { + // If we translated this string before, just grab it from the cache. + let glyphs = this._charsCache[chars]; + if (glyphs) { + return glyphs; + } + glyphs = []; + + if (this.cMap) { + // Composite fonts have multi-byte strings, convert the string from + // single-byte to multi-byte. + const c = Object.create(null), + ii = chars.length; + let i = 0; + while (i < ii) { + this.cMap.readCharCode(chars, i, c); + const { charcode, length } = c; + i += length; + // Space is char with code 0x20 and length 1 in multiple-byte codes. + const glyph = this._charToGlyph( + charcode, + length === 1 && chars.charCodeAt(i - 1) === 0x20 + ); + glyphs.push(glyph); + } + } else { + for (let i = 0, ii = chars.length; i < ii; ++i) { + const charcode = chars.charCodeAt(i); + const glyph = this._charToGlyph(charcode, charcode === 0x20); + glyphs.push(glyph); + } + } + + // Enter the translated string into the cache. + return (this._charsCache[chars] = glyphs); + } + + /** + * Chars can have different sizes (depends on the encoding). + * @param {String} a string encoded with font encoding. + * @returns {Array>} the positions of each char in the string. + */ + getCharPositions(chars) { + // This function doesn't use a cache because + // it's called only when saving or printing. + const positions = []; + + if (this.cMap) { + const c = Object.create(null); + let i = 0; + while (i < chars.length) { + this.cMap.readCharCode(chars, i, c); + const length = c.length; + positions.push([i, i + length]); + i += length; + } + } else { + for (let i = 0, ii = chars.length; i < ii; ++i) { + positions.push([i, i + 1]); + } + } + + return positions; + } + + get glyphCacheValues() { + return Object.values(this._glyphCache); + } + + /** + * Encode a js string using font encoding. + * The resulting array contains an encoded string at even positions + * (can be empty) and a non-encoded one at odd positions. + * @param {String} a js string. + * @returns {Array} an array of encoded strings or non-encoded ones. + */ + encodeString(str) { + const buffers = []; + const currentBuf = []; + + // buffers will contain: encoded, non-encoded, encoded, ... + // currentBuf is pushed in buffers each time there is a change. + // So when buffers.length is odd then the last string is an encoded one + // and currentBuf contains non-encoded chars. + const hasCurrentBufErrors = () => buffers.length % 2 === 1; + + const getCharCode = + this.toUnicode instanceof IdentityToUnicodeMap + ? unicode => this.toUnicode.charCodeOf(unicode) + : unicode => this.toUnicode.charCodeOf(String.fromCodePoint(unicode)); + + for (let i = 0, ii = str.length; i < ii; i++) { + const unicode = str.codePointAt(i); + if (unicode > 0xd7ff && (unicode < 0xe000 || unicode > 0xfffd)) { + // unicode is represented by two uint16 + i++; + } + if (this.toUnicode) { + const charCode = getCharCode(unicode); + if (charCode !== -1) { + if (hasCurrentBufErrors()) { + buffers.push(currentBuf.join("")); + currentBuf.length = 0; + } + const charCodeLength = this.cMap + ? this.cMap.getCharCodeLength(charCode) + : 1; + for (let j = charCodeLength - 1; j >= 0; j--) { + currentBuf.push(String.fromCharCode((charCode >> (8 * j)) & 0xff)); + } + continue; + } + } + + // unicode can't be encoded + if (!hasCurrentBufErrors()) { + buffers.push(currentBuf.join("")); + currentBuf.length = 0; + } + currentBuf.push(String.fromCodePoint(unicode)); + } + + buffers.push(currentBuf.join("")); + + return buffers; + } +} + +class ErrorFont { + constructor(error) { + this.error = error; + this.loadedName = "g_font_error"; + this.missingFile = true; + } + + charsToGlyphs() { + return []; + } + + encodeString(chars) { + return [chars]; + } + + exportData(extraProperties = false) { + return { error: this.error }; + } +} + +export { ErrorFont, Font }; diff --git a/frontend/public/pdf.js/src/core/fonts_utils.js b/frontend/public/pdf.js/src/core/fonts_utils.js new file mode 100644 index 0000000..20c8e87 --- /dev/null +++ b/frontend/public/pdf.js/src/core/fonts_utils.js @@ -0,0 +1,218 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { getEncoding, StandardEncoding } from "./encodings.js"; +import { getGlyphsUnicode } from "./glyphlist.js"; +import { getLookupTableFactory } from "./core_utils.js"; +import { getUnicodeForGlyph } from "./unicode.js"; +import { info } from "../shared/util.js"; + +// Accented characters have issues on Windows and Linux. When this flag is +// enabled glyphs that use seac and seac style endchar operators are truncated +// and we instead just store the glyph id's of the base glyph and its accent to +// be drawn individually. +// Linux (freetype) requires that when a seac style endchar is used +// that the charset must be a predefined one, however we build a +// custom one. Windows just refuses to draw glyphs with seac operators. +const SEAC_ANALYSIS_ENABLED = true; + +const FontFlags = { + FixedPitch: 1, + Serif: 2, + Symbolic: 4, + Script: 8, + Nonsymbolic: 32, + Italic: 64, + AllCap: 65536, + SmallCap: 131072, + ForceBold: 262144, +}; + +// prettier-ignore +const MacStandardGlyphOrdering = [ + ".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", + "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", + "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", + "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", + "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", + "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", + "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", + "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", + "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", + "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", + "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", + "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", + "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", + "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", + "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", + "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", + "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", + "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", + "questiondown", "exclamdown", "logicalnot", "radical", "florin", + "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", + "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", + "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", + "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", + "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", + "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", + "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", + "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", + "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", + "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", + "Lslash", "lslash", "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", + "Eth", "eth", "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply", + "onesuperior", "twosuperior", "threesuperior", "onehalf", "onequarter", + "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla", + "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat"]; + +// Some bad PDF generators, e.g. Scribus PDF, include glyph names +// in a 'uniXXXX' format -- attempting to recover proper ones. +function recoverGlyphName(name, glyphsUnicodeMap) { + if (glyphsUnicodeMap[name] !== undefined) { + return name; + } + // The glyph name is non-standard, trying to recover. + const unicode = getUnicodeForGlyph(name, glyphsUnicodeMap); + if (unicode !== -1) { + for (const key in glyphsUnicodeMap) { + if (glyphsUnicodeMap[key] === unicode) { + return key; + } + } + } + info("Unable to recover a standard glyph name for: " + name); + return name; +} + +/** + * Shared logic for building a char code to glyph id mapping for Type1 and + * simple CFF fonts. See section 9.6.6.2 of the spec. + * @param {Object} properties Font properties object. + * @param {Object} builtInEncoding The encoding contained within the actual font + * data. + * @param {Array} glyphNames Array of glyph names where the index is the + * glyph ID. + * @returns {Object} A char code to glyph ID map. + */ +function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { + const charCodeToGlyphId = Object.create(null); + let glyphId, charCode, baseEncoding; + const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); + + if (properties.isInternalFont) { + baseEncoding = builtInEncoding; + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); + charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : /* notdef = */ 0; + } + } else if (properties.baseEncodingName) { + // If a valid base encoding name was used, the mapping is initialized with + // that. + baseEncoding = getEncoding(properties.baseEncodingName); + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); + charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : /* notdef = */ 0; + } + } else if (isSymbolicFont) { + // For a symbolic font the encoding should be the fonts built-in encoding. + for (charCode in builtInEncoding) { + charCodeToGlyphId[charCode] = builtInEncoding[charCode]; + } + } else { + // For non-symbolic fonts that don't have a base encoding the standard + // encoding should be used. + baseEncoding = StandardEncoding; + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); + charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : /* notdef = */ 0; + } + } + + // Lastly, merge in the differences. + const differences = properties.differences; + let glyphsUnicodeMap; + if (differences) { + for (charCode in differences) { + const glyphName = differences[charCode]; + glyphId = glyphNames.indexOf(glyphName); + + if (glyphId === -1) { + if (!glyphsUnicodeMap) { + glyphsUnicodeMap = getGlyphsUnicode(); + } + const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); + if (standardGlyphName !== glyphName) { + glyphId = glyphNames.indexOf(standardGlyphName); + } + } + charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : /* notdef = */ 0; + } + } + return charCodeToGlyphId; +} + +function normalizeFontName(name) { + return name.replaceAll(/[,_]/g, "-").replaceAll(/\s/g, ""); +} + +const getVerticalPresentationForm = getLookupTableFactory(t => { + // This table has been found at + // https://searchfox.org/mozilla-central/rev/cbdfa503a87597b20719aae5f6a1efccd6cb3b7b/gfx/thebes/gfxHarfBuzzShaper.cpp#251-294 + t[0x2013] = 0xfe32; // EN DASH + t[0x2014] = 0xfe31; // EM DASH + t[0x2025] = 0xfe30; // TWO DOT LEADER + t[0x2026] = 0xfe19; // HORIZONTAL ELLIPSIS + t[0x3001] = 0xfe11; // IDEOGRAPHIC COMMA + t[0x3002] = 0xfe12; // IDEOGRAPHIC FULL STOP + t[0x3008] = 0xfe3f; // LEFT ANGLE BRACKET + t[0x3009] = 0xfe40; // RIGHT ANGLE BRACKET + t[0x300a] = 0xfe3d; // LEFT DOUBLE ANGLE BRACKET + t[0x300b] = 0xfe3e; // RIGHT DOUBLE ANGLE BRACKET + t[0x300c] = 0xfe41; // LEFT CORNER BRACKET + t[0x300d] = 0xfe42; // RIGHT CORNER BRACKET + t[0x300e] = 0xfe43; // LEFT WHITE CORNER BRACKET + t[0x300f] = 0xfe44; // RIGHT WHITE CORNER BRACKET + t[0x3010] = 0xfe3b; // LEFT BLACK LENTICULAR BRACKET + t[0x3011] = 0xfe3c; // RIGHT BLACK LENTICULAR BRACKET + t[0x3014] = 0xfe39; // LEFT TORTOISE SHELL BRACKET + t[0x3015] = 0xfe3a; // RIGHT TORTOISE SHELL BRACKET + t[0x3016] = 0xfe17; // LEFT WHITE LENTICULAR BRACKET + t[0x3017] = 0xfe18; // RIGHT WHITE LENTICULAR BRACKET + t[0xfe4f] = 0xfe34; // WAVY LOW LINE + t[0xff01] = 0xfe15; // FULLWIDTH EXCLAMATION MARK + t[0xff08] = 0xfe35; // FULLWIDTH LEFT PARENTHESIS + t[0xff09] = 0xfe36; // FULLWIDTH RIGHT PARENTHESIS + t[0xff0c] = 0xfe10; // FULLWIDTH COMMA + t[0xff1a] = 0xfe13; // FULLWIDTH COLON + t[0xff1b] = 0xfe14; // FULLWIDTH SEMICOLON + t[0xff1f] = 0xfe16; // FULLWIDTH QUESTION MARK + t[0xff3b] = 0xfe47; // FULLWIDTH LEFT SQUARE BRACKET + t[0xff3d] = 0xfe48; // FULLWIDTH RIGHT SQUARE BRACKET + t[0xff3f] = 0xfe33; // FULLWIDTH LOW LINE + t[0xff5b] = 0xfe37; // FULLWIDTH LEFT CURLY BRACKET + t[0xff5d] = 0xfe38; // FULLWIDTH RIGHT CURLY BRACKET +}); + +export { + FontFlags, + getVerticalPresentationForm, + MacStandardGlyphOrdering, + normalizeFontName, + recoverGlyphName, + SEAC_ANALYSIS_ENABLED, + type1FontGlyphMapping, +}; diff --git a/frontend/public/pdf.js/src/core/function.js b/frontend/public/pdf.js/src/core/function.js new file mode 100644 index 0000000..6864b13 --- /dev/null +++ b/frontend/public/pdf.js/src/core/function.js @@ -0,0 +1,1232 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { Dict, Ref } from "./primitives.js"; +import { + FeatureTest, + FormatError, + info, + shadow, + unreachable, +} from "../shared/util.js"; +import { PostScriptLexer, PostScriptParser } from "./ps_parser.js"; +import { BaseStream } from "./base_stream.js"; +import { isNumberArray } from "./core_utils.js"; +import { LocalFunctionCache } from "./image_utils.js"; + +class PDFFunctionFactory { + constructor({ xref, isEvalSupported = true }) { + this.xref = xref; + this.isEvalSupported = isEvalSupported !== false; + } + + create(fn) { + const cachedFunction = this.getCached(fn); + if (cachedFunction) { + return cachedFunction; + } + const parsedFunction = PDFFunction.parse({ + xref: this.xref, + isEvalSupported: this.isEvalSupported, + fn: fn instanceof Ref ? this.xref.fetch(fn) : fn, + }); + + // Attempt to cache the parsed Function, by reference. + this._cache(fn, parsedFunction); + + return parsedFunction; + } + + createFromArray(fnObj) { + const cachedFunction = this.getCached(fnObj); + if (cachedFunction) { + return cachedFunction; + } + const parsedFunction = PDFFunction.parseArray({ + xref: this.xref, + isEvalSupported: this.isEvalSupported, + fnObj: fnObj instanceof Ref ? this.xref.fetch(fnObj) : fnObj, + }); + + // Attempt to cache the parsed Function, by reference. + this._cache(fnObj, parsedFunction); + + return parsedFunction; + } + + getCached(cacheKey) { + let fnRef; + if (cacheKey instanceof Ref) { + fnRef = cacheKey; + } else if (cacheKey instanceof Dict) { + fnRef = cacheKey.objId; + } else if (cacheKey instanceof BaseStream) { + fnRef = cacheKey.dict?.objId; + } + if (fnRef) { + const localFunction = this._localFunctionCache.getByRef(fnRef); + if (localFunction) { + return localFunction; + } + } + return null; + } + + /** + * @private + */ + _cache(cacheKey, parsedFunction) { + if (!parsedFunction) { + throw new Error( + 'PDFFunctionFactory._cache - expected "parsedFunction" argument.' + ); + } + let fnRef; + if (cacheKey instanceof Ref) { + fnRef = cacheKey; + } else if (cacheKey instanceof Dict) { + fnRef = cacheKey.objId; + } else if (cacheKey instanceof BaseStream) { + fnRef = cacheKey.dict?.objId; + } + if (fnRef) { + this._localFunctionCache.set(/* name = */ null, fnRef, parsedFunction); + } + } + + /** + * @private + */ + get _localFunctionCache() { + return shadow(this, "_localFunctionCache", new LocalFunctionCache()); + } +} + +function toNumberArray(arr) { + if (!Array.isArray(arr)) { + return null; + } + if (!isNumberArray(arr, null)) { + // Non-number is found -- convert all items to numbers. + return arr.map(x => +x); + } + return arr; +} + +class PDFFunction { + static getSampleArray(size, outputSize, bps, stream) { + let i, ii; + let length = 1; + for (i = 0, ii = size.length; i < ii; i++) { + length *= size[i]; + } + length *= outputSize; + + const array = new Array(length); + let codeSize = 0; + let codeBuf = 0; + // 32 is a valid bps so shifting won't work + const sampleMul = 1.0 / (2.0 ** bps - 1); + + const strBytes = stream.getBytes((length * bps + 7) / 8); + let strIdx = 0; + for (i = 0; i < length; i++) { + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps; + array[i] = (codeBuf >> codeSize) * sampleMul; + codeBuf &= (1 << codeSize) - 1; + } + return array; + } + + static parse({ xref, isEvalSupported, fn }) { + const dict = fn.dict || fn; + const typeNum = dict.get("FunctionType"); + + switch (typeNum) { + case 0: + return this.constructSampled({ xref, isEvalSupported, fn, dict }); + case 1: + break; + case 2: + return this.constructInterpolated({ xref, isEvalSupported, dict }); + case 3: + return this.constructStiched({ xref, isEvalSupported, dict }); + case 4: + return this.constructPostScript({ xref, isEvalSupported, fn, dict }); + } + throw new FormatError("Unknown type of function"); + } + + static parseArray({ xref, isEvalSupported, fnObj }) { + if (!Array.isArray(fnObj)) { + // not an array -- parsing as regular function + return this.parse({ xref, isEvalSupported, fn: fnObj }); + } + + const fnArray = []; + for (const fn of fnObj) { + fnArray.push( + this.parse({ xref, isEvalSupported, fn: xref.fetchIfRef(fn) }) + ); + } + return function (src, srcOffset, dest, destOffset) { + for (let i = 0, ii = fnArray.length; i < ii; i++) { + fnArray[i](src, srcOffset, dest, destOffset + i); + } + }; + } + + static constructSampled({ xref, isEvalSupported, fn, dict }) { + function toMultiArray(arr) { + const inputLength = arr.length; + const out = []; + let index = 0; + for (let i = 0; i < inputLength; i += 2) { + out[index++] = [arr[i], arr[i + 1]]; + } + return out; + } + // See chapter 3, page 109 of the PDF reference + function interpolate(x, xmin, xmax, ymin, ymax) { + return ymin + (x - xmin) * ((ymax - ymin) / (xmax - xmin)); + } + + let domain = toNumberArray(dict.getArray("Domain")); + let range = toNumberArray(dict.getArray("Range")); + + if (!domain || !range) { + throw new FormatError("No domain or range"); + } + + const inputSize = domain.length / 2; + const outputSize = range.length / 2; + + domain = toMultiArray(domain); + range = toMultiArray(range); + + const size = toNumberArray(dict.getArray("Size")); + const bps = dict.get("BitsPerSample"); + const order = dict.get("Order") || 1; + if (order !== 1) { + // No description how cubic spline interpolation works in PDF32000:2008 + // As in poppler, ignoring order, linear interpolation may work as good + info("No support for cubic spline interpolation: " + order); + } + + let encode = toNumberArray(dict.getArray("Encode")); + if (!encode) { + encode = []; + for (let i = 0; i < inputSize; ++i) { + encode.push([0, size[i] - 1]); + } + } else { + encode = toMultiArray(encode); + } + + let decode = toNumberArray(dict.getArray("Decode")); + decode = !decode ? range : toMultiArray(decode); + + const samples = this.getSampleArray(size, outputSize, bps, fn); + // const mask = 2 ** bps - 1; + + return function constructSampledFn(src, srcOffset, dest, destOffset) { + // See chapter 3, page 110 of the PDF reference. + + // Building the cube vertices: its part and sample index + // http://rjwagner49.com/Mathematics/Interpolation.pdf + const cubeVertices = 1 << inputSize; + const cubeN = new Float64Array(cubeVertices).fill(1); + const cubeVertex = new Uint32Array(cubeVertices); + let i, j; + + let k = outputSize, + pos = 1; + // Map x_i to y_j for 0 <= i < m using the sampled function. + for (i = 0; i < inputSize; ++i) { + // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) + const domain_2i = domain[i][0]; + const domain_2i_1 = domain[i][1]; + const xi = Math.min( + Math.max(src[srcOffset + i], domain_2i), + domain_2i_1 + ); + + // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, + // Encode_2i, Encode_2i+1) + let e = interpolate( + xi, + domain_2i, + domain_2i_1, + encode[i][0], + encode[i][1] + ); + + // e_i' = min(max(e_i, 0), Size_i - 1) + const size_i = size[i]; + e = Math.min(Math.max(e, 0), size_i - 1); + + // Adjusting the cube: N and vertex sample index + const e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; + const n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); + const n1 = e - e0; // (e - e0) / (e1 - e0); + const offset0 = e0 * k; + const offset1 = offset0 + k; // e1 * k + for (j = 0; j < cubeVertices; j++) { + if (j & pos) { + cubeN[j] *= n1; + cubeVertex[j] += offset1; + } else { + cubeN[j] *= n0; + cubeVertex[j] += offset0; + } + } + + k *= size_i; + pos <<= 1; + } + + for (j = 0; j < outputSize; ++j) { + // Sum all cube vertices' samples portions + let rj = 0; + for (i = 0; i < cubeVertices; i++) { + rj += samples[cubeVertex[i] + j] * cubeN[i]; + } + + // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, + // Decode_2j, Decode_2j+1) + rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); + + // y_j = min(max(r_j, range_2j), range_2j+1) + dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + } + }; + } + + static constructInterpolated({ xref, isEvalSupported, dict }) { + const c0 = toNumberArray(dict.getArray("C0")) || [0]; + const c1 = toNumberArray(dict.getArray("C1")) || [1]; + const n = dict.get("N"); + + const diff = []; + for (let i = 0, ii = c0.length; i < ii; ++i) { + diff.push(c1[i] - c0[i]); + } + const length = diff.length; + + return function constructInterpolatedFn(src, srcOffset, dest, destOffset) { + const x = n === 1 ? src[srcOffset] : src[srcOffset] ** n; + + for (let j = 0; j < length; ++j) { + dest[destOffset + j] = c0[j] + x * diff[j]; + } + }; + } + + static constructStiched({ xref, isEvalSupported, dict }) { + const domain = toNumberArray(dict.getArray("Domain")); + + if (!domain) { + throw new FormatError("No domain"); + } + + const inputSize = domain.length / 2; + if (inputSize !== 1) { + throw new FormatError("Bad domain for stiched function"); + } + + const fns = []; + for (const fn of dict.get("Functions")) { + fns.push(this.parse({ xref, isEvalSupported, fn: xref.fetchIfRef(fn) })); + } + + const bounds = toNumberArray(dict.getArray("Bounds")); + const encode = toNumberArray(dict.getArray("Encode")); + const tmpBuf = new Float32Array(1); + + return function constructStichedFn(src, srcOffset, dest, destOffset) { + const clip = function constructStichedFromIRClip(v, min, max) { + if (v > max) { + v = max; + } else if (v < min) { + v = min; + } + return v; + }; + + // clip to domain + const v = clip(src[srcOffset], domain[0], domain[1]); + // calculate which bound the value is in + const length = bounds.length; + let i; + for (i = 0; i < length; ++i) { + if (v < bounds[i]) { + break; + } + } + + // encode value into domain of function + let dmin = domain[0]; + if (i > 0) { + dmin = bounds[i - 1]; + } + let dmax = domain[1]; + if (i < bounds.length) { + dmax = bounds[i]; + } + + const rmin = encode[2 * i]; + const rmax = encode[2 * i + 1]; + + // Prevent the value from becoming NaN as a result + // of division by zero (fixes issue6113.pdf). + tmpBuf[0] = + dmin === dmax + ? rmin + : rmin + ((v - dmin) * (rmax - rmin)) / (dmax - dmin); + + // call the appropriate function + fns[i](tmpBuf, 0, dest, destOffset); + }; + } + + static constructPostScript({ xref, isEvalSupported, fn, dict }) { + const domain = toNumberArray(dict.getArray("Domain")); + const range = toNumberArray(dict.getArray("Range")); + + if (!domain) { + throw new FormatError("No domain."); + } + + if (!range) { + throw new FormatError("No range."); + } + + const lexer = new PostScriptLexer(fn); + const parser = new PostScriptParser(lexer); + const code = parser.parse(); + + if (isEvalSupported && FeatureTest.isEvalSupported) { + const compiled = new PostScriptCompiler().compile(code, domain, range); + if (compiled) { + // Compiled function consists of simple expressions such as addition, + // subtraction, Math.max, and also contains 'var' and 'return' + // statements. See the generation in the PostScriptCompiler below. + // eslint-disable-next-line no-new-func + return new Function("src", "srcOffset", "dest", "destOffset", compiled); + } + } + info("Unable to compile PS function"); + + const numOutputs = range.length >> 1; + const numInputs = domain.length >> 1; + const evaluator = new PostScriptEvaluator(code); + // Cache the values for a big speed up, the cache size is limited though + // since the number of possible values can be huge from a PS function. + const cache = Object.create(null); + // The MAX_CACHE_SIZE is set to ~4x the maximum number of distinct values + // seen in our tests. + const MAX_CACHE_SIZE = 2048 * 4; + let cache_available = MAX_CACHE_SIZE; + const tmpBuf = new Float32Array(numInputs); + + return function constructPostScriptFn(src, srcOffset, dest, destOffset) { + let i, value; + let key = ""; + const input = tmpBuf; + for (i = 0; i < numInputs; i++) { + value = src[srcOffset + i]; + input[i] = value; + key += value + "_"; + } + + const cachedValue = cache[key]; + if (cachedValue !== undefined) { + dest.set(cachedValue, destOffset); + return; + } + + const output = new Float32Array(numOutputs); + const stack = evaluator.execute(input); + const stackIndex = stack.length - numOutputs; + for (i = 0; i < numOutputs; i++) { + value = stack[stackIndex + i]; + let bound = range[i * 2]; + if (value < bound) { + value = bound; + } else { + bound = range[i * 2 + 1]; + if (value > bound) { + value = bound; + } + } + output[i] = value; + } + if (cache_available > 0) { + cache_available--; + cache[key] = output; + } + dest.set(output, destOffset); + }; + } +} + +function isPDFFunction(v) { + let fnDict; + if (v instanceof Dict) { + fnDict = v; + } else if (v instanceof BaseStream) { + fnDict = v.dict; + } else { + return false; + } + return fnDict.has("FunctionType"); +} + +class PostScriptStack { + static MAX_STACK_SIZE = 100; + + constructor(initialStack) { + this.stack = initialStack ? Array.from(initialStack) : []; + } + + push(value) { + if (this.stack.length >= PostScriptStack.MAX_STACK_SIZE) { + throw new Error("PostScript function stack overflow."); + } + this.stack.push(value); + } + + pop() { + if (this.stack.length <= 0) { + throw new Error("PostScript function stack underflow."); + } + return this.stack.pop(); + } + + copy(n) { + if (this.stack.length + n >= PostScriptStack.MAX_STACK_SIZE) { + throw new Error("PostScript function stack overflow."); + } + const stack = this.stack; + for (let i = stack.length - n, j = n - 1; j >= 0; j--, i++) { + stack.push(stack[i]); + } + } + + index(n) { + this.push(this.stack[this.stack.length - n - 1]); + } + + // rotate the last n stack elements p times + roll(n, p) { + const stack = this.stack; + const l = stack.length - n; + const r = stack.length - 1; + const c = l + (p - Math.floor(p / n) * n); + + for (let i = l, j = r; i < j; i++, j--) { + const t = stack[i]; + stack[i] = stack[j]; + stack[j] = t; + } + for (let i = l, j = c - 1; i < j; i++, j--) { + const t = stack[i]; + stack[i] = stack[j]; + stack[j] = t; + } + for (let i = c, j = r; i < j; i++, j--) { + const t = stack[i]; + stack[i] = stack[j]; + stack[j] = t; + } + } +} + +class PostScriptEvaluator { + constructor(operators) { + this.operators = operators; + } + + execute(initialStack) { + const stack = new PostScriptStack(initialStack); + let counter = 0; + const operators = this.operators; + const length = operators.length; + let operator, a, b; + while (counter < length) { + operator = operators[counter++]; + if (typeof operator === "number") { + // Operator is really an operand and should be pushed to the stack. + stack.push(operator); + continue; + } + switch (operator) { + // non standard ps operators + case "jz": // jump if false + b = stack.pop(); + a = stack.pop(); + if (!a) { + counter = b; + } + break; + case "j": // jump + a = stack.pop(); + counter = a; + break; + + // all ps operators in alphabetical order (excluding if/ifelse) + case "abs": + a = stack.pop(); + stack.push(Math.abs(a)); + break; + case "add": + b = stack.pop(); + a = stack.pop(); + stack.push(a + b); + break; + case "and": + b = stack.pop(); + a = stack.pop(); + if (typeof a === "boolean" && typeof b === "boolean") { + stack.push(a && b); + } else { + stack.push(a & b); + } + break; + case "atan": + b = stack.pop(); + a = stack.pop(); + a = (Math.atan2(a, b) / Math.PI) * 180; + if (a < 0) { + a += 360; + } + stack.push(a); + break; + case "bitshift": + b = stack.pop(); + a = stack.pop(); + if (a > 0) { + stack.push(a << b); + } else { + stack.push(a >> b); + } + break; + case "ceiling": + a = stack.pop(); + stack.push(Math.ceil(a)); + break; + case "copy": + a = stack.pop(); + stack.copy(a); + break; + case "cos": + a = stack.pop(); + stack.push(Math.cos(((a % 360) / 180) * Math.PI)); + break; + case "cvi": + a = stack.pop() | 0; + stack.push(a); + break; + case "cvr": + // noop + break; + case "div": + b = stack.pop(); + a = stack.pop(); + stack.push(a / b); + break; + case "dup": + stack.copy(1); + break; + case "eq": + b = stack.pop(); + a = stack.pop(); + stack.push(a === b); + break; + case "exch": + stack.roll(2, 1); + break; + case "exp": + b = stack.pop(); + a = stack.pop(); + stack.push(a ** b); + break; + case "false": + stack.push(false); + break; + case "floor": + a = stack.pop(); + stack.push(Math.floor(a)); + break; + case "ge": + b = stack.pop(); + a = stack.pop(); + stack.push(a >= b); + break; + case "gt": + b = stack.pop(); + a = stack.pop(); + stack.push(a > b); + break; + case "idiv": + b = stack.pop(); + a = stack.pop(); + stack.push((a / b) | 0); + break; + case "index": + a = stack.pop(); + stack.index(a); + break; + case "le": + b = stack.pop(); + a = stack.pop(); + stack.push(a <= b); + break; + case "ln": + a = stack.pop(); + stack.push(Math.log(a)); + break; + case "log": + a = stack.pop(); + stack.push(Math.log10(a)); + break; + case "lt": + b = stack.pop(); + a = stack.pop(); + stack.push(a < b); + break; + case "mod": + b = stack.pop(); + a = stack.pop(); + stack.push(a % b); + break; + case "mul": + b = stack.pop(); + a = stack.pop(); + stack.push(a * b); + break; + case "ne": + b = stack.pop(); + a = stack.pop(); + stack.push(a !== b); + break; + case "neg": + a = stack.pop(); + stack.push(-a); + break; + case "not": + a = stack.pop(); + if (typeof a === "boolean") { + stack.push(!a); + } else { + stack.push(~a); + } + break; + case "or": + b = stack.pop(); + a = stack.pop(); + if (typeof a === "boolean" && typeof b === "boolean") { + stack.push(a || b); + } else { + stack.push(a | b); + } + break; + case "pop": + stack.pop(); + break; + case "roll": + b = stack.pop(); + a = stack.pop(); + stack.roll(a, b); + break; + case "round": + a = stack.pop(); + stack.push(Math.round(a)); + break; + case "sin": + a = stack.pop(); + stack.push(Math.sin(((a % 360) / 180) * Math.PI)); + break; + case "sqrt": + a = stack.pop(); + stack.push(Math.sqrt(a)); + break; + case "sub": + b = stack.pop(); + a = stack.pop(); + stack.push(a - b); + break; + case "true": + stack.push(true); + break; + case "truncate": + a = stack.pop(); + a = a < 0 ? Math.ceil(a) : Math.floor(a); + stack.push(a); + break; + case "xor": + b = stack.pop(); + a = stack.pop(); + if (typeof a === "boolean" && typeof b === "boolean") { + stack.push(a !== b); + } else { + stack.push(a ^ b); + } + break; + default: + throw new FormatError(`Unknown operator ${operator}`); + } + } + return stack.stack; + } +} + +class AstNode { + constructor(type) { + this.type = type; + } + + visit(visitor) { + unreachable("abstract method"); + } +} + +class AstArgument extends AstNode { + constructor(index, min, max) { + super("args"); + this.index = index; + this.min = min; + this.max = max; + } + + visit(visitor) { + visitor.visitArgument(this); + } +} + +class AstLiteral extends AstNode { + constructor(number) { + super("literal"); + this.number = number; + this.min = number; + this.max = number; + } + + visit(visitor) { + visitor.visitLiteral(this); + } +} + +class AstBinaryOperation extends AstNode { + constructor(op, arg1, arg2, min, max) { + super("binary"); + this.op = op; + this.arg1 = arg1; + this.arg2 = arg2; + this.min = min; + this.max = max; + } + + visit(visitor) { + visitor.visitBinaryOperation(this); + } +} + +class AstMin extends AstNode { + constructor(arg, max) { + super("max"); + this.arg = arg; + this.min = arg.min; + this.max = max; + } + + visit(visitor) { + visitor.visitMin(this); + } +} + +class AstVariable extends AstNode { + constructor(index, min, max) { + super("var"); + this.index = index; + this.min = min; + this.max = max; + } + + visit(visitor) { + visitor.visitVariable(this); + } +} + +class AstVariableDefinition extends AstNode { + constructor(variable, arg) { + super("definition"); + this.variable = variable; + this.arg = arg; + } + + visit(visitor) { + visitor.visitVariableDefinition(this); + } +} + +class ExpressionBuilderVisitor { + constructor() { + this.parts = []; + } + + visitArgument(arg) { + this.parts.push( + "Math.max(", + arg.min, + ", Math.min(", + arg.max, + ", src[srcOffset + ", + arg.index, + "]))" + ); + } + + visitVariable(variable) { + this.parts.push("v", variable.index); + } + + visitLiteral(literal) { + this.parts.push(literal.number); + } + + visitBinaryOperation(operation) { + this.parts.push("("); + operation.arg1.visit(this); + this.parts.push(" ", operation.op, " "); + operation.arg2.visit(this); + this.parts.push(")"); + } + + visitVariableDefinition(definition) { + this.parts.push("var "); + definition.variable.visit(this); + this.parts.push(" = "); + definition.arg.visit(this); + this.parts.push(";"); + } + + visitMin(max) { + this.parts.push("Math.min("); + max.arg.visit(this); + this.parts.push(", ", max.max, ")"); + } + + toString() { + return this.parts.join(""); + } +} + +function buildAddOperation(num1, num2) { + if (num2.type === "literal" && num2.number === 0) { + // optimization: second operand is 0 + return num1; + } + if (num1.type === "literal" && num1.number === 0) { + // optimization: first operand is 0 + return num2; + } + if (num2.type === "literal" && num1.type === "literal") { + // optimization: operands operand are literals + return new AstLiteral(num1.number + num2.number); + } + return new AstBinaryOperation( + "+", + num1, + num2, + num1.min + num2.min, + num1.max + num2.max + ); +} + +function buildMulOperation(num1, num2) { + if (num2.type === "literal") { + // optimization: second operands is a literal... + if (num2.number === 0) { + return new AstLiteral(0); // and it's 0 + } else if (num2.number === 1) { + return num1; // and it's 1 + } else if (num1.type === "literal") { + // ... and first operands is a literal too + return new AstLiteral(num1.number * num2.number); + } + } + if (num1.type === "literal") { + // optimization: first operands is a literal... + if (num1.number === 0) { + return new AstLiteral(0); // and it's 0 + } else if (num1.number === 1) { + return num2; // and it's 1 + } + } + const min = Math.min( + num1.min * num2.min, + num1.min * num2.max, + num1.max * num2.min, + num1.max * num2.max + ); + const max = Math.max( + num1.min * num2.min, + num1.min * num2.max, + num1.max * num2.min, + num1.max * num2.max + ); + return new AstBinaryOperation("*", num1, num2, min, max); +} + +function buildSubOperation(num1, num2) { + if (num2.type === "literal") { + // optimization: second operands is a literal... + if (num2.number === 0) { + return num1; // ... and it's 0 + } else if (num1.type === "literal") { + // ... and first operands is a literal too + return new AstLiteral(num1.number - num2.number); + } + } + if ( + num2.type === "binary" && + num2.op === "-" && + num1.type === "literal" && + num1.number === 1 && + num2.arg1.type === "literal" && + num2.arg1.number === 1 + ) { + // optimization for case: 1 - (1 - x) + return num2.arg2; + } + return new AstBinaryOperation( + "-", + num1, + num2, + num1.min - num2.max, + num1.max - num2.min + ); +} + +function buildMinOperation(num1, max) { + if (num1.min >= max) { + // optimization: num1 min value is not less than required max + return new AstLiteral(max); // just returning max + } else if (num1.max <= max) { + // optimization: num1 max value is not greater than required max + return num1; // just returning an argument + } + return new AstMin(num1, max); +} + +// Most of the PDFs functions consist of simple operations such as: +// roll, exch, sub, cvr, pop, index, dup, mul, if, gt, add. +// +// We can compile most of such programs, and at the same moment, we can +// optimize some expressions using basic math properties. Keeping track of +// min/max values will allow us to avoid extra Math.min/Math.max calls. +class PostScriptCompiler { + compile(code, domain, range) { + const stack = []; + const instructions = []; + const inputSize = domain.length >> 1, + outputSize = range.length >> 1; + let lastRegister = 0; + let n, j; + let num1, num2, ast1, ast2, tmpVar, item; + for (let i = 0; i < inputSize; i++) { + stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1])); + } + + for (let i = 0, ii = code.length; i < ii; i++) { + item = code[i]; + if (typeof item === "number") { + stack.push(new AstLiteral(item)); + continue; + } + + switch (item) { + case "add": + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildAddOperation(num1, num2)); + break; + case "cvr": + if (stack.length < 1) { + return null; + } + break; + case "mul": + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildMulOperation(num1, num2)); + break; + case "sub": + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildSubOperation(num1, num2)); + break; + case "exch": + if (stack.length < 2) { + return null; + } + ast1 = stack.pop(); + ast2 = stack.pop(); + stack.push(ast1, ast2); + break; + case "pop": + if (stack.length < 1) { + return null; + } + stack.pop(); + break; + case "index": + if (stack.length < 1) { + return null; + } + num1 = stack.pop(); + if (num1.type !== "literal") { + return null; + } + n = num1.number; + if (n < 0 || !Number.isInteger(n) || stack.length < n) { + return null; + } + ast1 = stack[stack.length - n - 1]; + if (ast1.type === "literal" || ast1.type === "var") { + stack.push(ast1); + break; + } + tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); + stack[stack.length - n - 1] = tmpVar; + stack.push(tmpVar); + instructions.push(new AstVariableDefinition(tmpVar, ast1)); + break; + case "dup": + if (stack.length < 1) { + return null; + } + if ( + typeof code[i + 1] === "number" && + code[i + 2] === "gt" && + code[i + 3] === i + 7 && + code[i + 4] === "jz" && + code[i + 5] === "pop" && + code[i + 6] === code[i + 1] + ) { + // special case of the commands sequence for the min operation + num1 = stack.pop(); + stack.push(buildMinOperation(num1, code[i + 1])); + i += 6; + break; + } + ast1 = stack.at(-1); + if (ast1.type === "literal" || ast1.type === "var") { + // we don't have to save into intermediate variable a literal or + // variable. + stack.push(ast1); + break; + } + tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); + stack[stack.length - 1] = tmpVar; + stack.push(tmpVar); + instructions.push(new AstVariableDefinition(tmpVar, ast1)); + break; + case "roll": + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + if (num2.type !== "literal" || num1.type !== "literal") { + // both roll operands must be numbers + return null; + } + j = num2.number; + n = num1.number; + if ( + n <= 0 || + !Number.isInteger(n) || + !Number.isInteger(j) || + stack.length < n + ) { + // ... and integers + return null; + } + j = ((j % n) + n) % n; + if (j === 0) { + break; // just skipping -- there are nothing to rotate + } + stack.push(...stack.splice(stack.length - n, n - j)); + break; + default: + return null; // unsupported operator + } + } + + if (stack.length !== outputSize) { + return null; + } + + const result = []; + for (const instruction of instructions) { + const statementBuilder = new ExpressionBuilderVisitor(); + instruction.visit(statementBuilder); + result.push(statementBuilder.toString()); + } + for (let i = 0, ii = stack.length; i < ii; i++) { + const expr = stack[i], + statementBuilder = new ExpressionBuilderVisitor(); + expr.visit(statementBuilder); + const min = range[i * 2], + max = range[i * 2 + 1]; + const out = [statementBuilder.toString()]; + if (min > expr.min) { + out.unshift("Math.max(", min, ", "); + out.push(")"); + } + if (max < expr.max) { + out.unshift("Math.min(", max, ", "); + out.push(")"); + } + out.unshift("dest[destOffset + ", i, "] = "); + out.push(";"); + result.push(out.join("")); + } + return result.join("\n"); + } +} + +export { + isPDFFunction, + PDFFunctionFactory, + PostScriptCompiler, + PostScriptEvaluator, +}; diff --git a/frontend/public/pdf.js/src/core/glyf.js b/frontend/public/pdf.js/src/core/glyf.js new file mode 100644 index 0000000..d5ddb33 --- /dev/null +++ b/frontend/public/pdf.js/src/core/glyf.js @@ -0,0 +1,700 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +const ON_CURVE_POINT = 1 << 0; +const X_SHORT_VECTOR = 1 << 1; +const Y_SHORT_VECTOR = 1 << 2; +const REPEAT_FLAG = 1 << 3; +const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 1 << 4; +const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 1 << 5; +const OVERLAP_SIMPLE = 1 << 6; + +const ARG_1_AND_2_ARE_WORDS = 1 << 0; +const ARGS_ARE_XY_VALUES = 1 << 1; +// const ROUND_XY_TO_GRID = 1 << 2; +const WE_HAVE_A_SCALE = 1 << 3; +const MORE_COMPONENTS = 1 << 5; +const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6; +const WE_HAVE_A_TWO_BY_TWO = 1 << 7; +const WE_HAVE_INSTRUCTIONS = 1 << 8; +// const USE_MY_METRICS = 1 << 9; +// const OVERLAP_COMPOUND = 1 << 10; +// const SCALED_COMPONENT_OFFSET = 1 << 11; +// const UNSCALED_COMPONENT_OFFSET = 1 << 12; + +/** + * GlyfTable object represents a glyf table containing glyph information: + * - glyph header (xMin, yMin, xMax, yMax); + * - contours if any; + * - components if the glyph is a composite. + * + * It's possible to re-scale each glyph in order to have a new font which + * exactly fits an other one: the goal is to be able to build some substitution + * font for well-known fonts (Myriad, Arial, ...). + * + * A full description of glyf table can be found here + * https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html + */ +class GlyfTable { + constructor({ glyfTable, isGlyphLocationsLong, locaTable, numGlyphs }) { + this.glyphs = []; + const loca = new DataView( + locaTable.buffer, + locaTable.byteOffset, + locaTable.byteLength + ); + const glyf = new DataView( + glyfTable.buffer, + glyfTable.byteOffset, + glyfTable.byteLength + ); + const offsetSize = isGlyphLocationsLong ? 4 : 2; + let prev = isGlyphLocationsLong ? loca.getUint32(0) : 2 * loca.getUint16(0); + let pos = 0; + for (let i = 0; i < numGlyphs; i++) { + pos += offsetSize; + const next = isGlyphLocationsLong + ? loca.getUint32(pos) + : 2 * loca.getUint16(pos); + if (next === prev) { + this.glyphs.push(new Glyph({})); + continue; + } + + const glyph = Glyph.parse(prev, glyf); + this.glyphs.push(glyph); + + prev = next; + } + } + + getSize() { + return this.glyphs.reduce((a, g) => { + const size = g.getSize(); + // Round to next multiple of 4 if needed. + return a + ((size + 3) & ~3); + }, 0); + } + + write() { + const totalSize = this.getSize(); + const glyfTable = new DataView(new ArrayBuffer(totalSize)); + const isLocationLong = totalSize > /* 0xffff * 2 */ 0x1fffe; + const offsetSize = isLocationLong ? 4 : 2; + const locaTable = new DataView( + new ArrayBuffer((this.glyphs.length + 1) * offsetSize) + ); + + if (isLocationLong) { + locaTable.setUint32(0, 0); + } else { + locaTable.setUint16(0, 0); + } + + let pos = 0; + let locaIndex = 0; + for (const glyph of this.glyphs) { + pos += glyph.write(pos, glyfTable); + // Round to next multiple of 4 if needed. + pos = (pos + 3) & ~3; + + locaIndex += offsetSize; + if (isLocationLong) { + locaTable.setUint32(locaIndex, pos); + } else { + locaTable.setUint16(locaIndex, pos >> 1); + } + } + + return { + isLocationLong, + loca: new Uint8Array(locaTable.buffer), + glyf: new Uint8Array(glyfTable.buffer), + }; + } + + scale(factors) { + for (let i = 0, ii = this.glyphs.length; i < ii; i++) { + this.glyphs[i].scale(factors[i]); + } + } +} + +class Glyph { + constructor({ header = null, simple = null, composites = null }) { + this.header = header; + this.simple = simple; + this.composites = composites; + } + + static parse(pos, glyf) { + const [read, header] = GlyphHeader.parse(pos, glyf); + pos += read; + + if (header.numberOfContours < 0) { + // Composite glyph. + const composites = []; + while (true) { + const [n, composite] = CompositeGlyph.parse(pos, glyf); + pos += n; + composites.push(composite); + if (!(composite.flags & MORE_COMPONENTS)) { + break; + } + } + + return new Glyph({ header, composites }); + } + + const simple = SimpleGlyph.parse(pos, glyf, header.numberOfContours); + + return new Glyph({ header, simple }); + } + + getSize() { + if (!this.header) { + return 0; + } + const size = this.simple + ? this.simple.getSize() + : this.composites.reduce((a, c) => a + c.getSize(), 0); + return this.header.getSize() + size; + } + + write(pos, buf) { + if (!this.header) { + return 0; + } + + const spos = pos; + pos += this.header.write(pos, buf); + if (this.simple) { + pos += this.simple.write(pos, buf); + } else { + for (const composite of this.composites) { + pos += composite.write(pos, buf); + } + } + + return pos - spos; + } + + scale(factor) { + if (!this.header) { + return; + } + + const xMiddle = (this.header.xMin + this.header.xMax) / 2; + this.header.scale(xMiddle, factor); + if (this.simple) { + this.simple.scale(xMiddle, factor); + } else { + for (const composite of this.composites) { + composite.scale(xMiddle, factor); + } + } + } +} + +class GlyphHeader { + constructor({ numberOfContours, xMin, yMin, xMax, yMax }) { + this.numberOfContours = numberOfContours; + this.xMin = xMin; + this.yMin = yMin; + this.xMax = xMax; + this.yMax = yMax; + } + + static parse(pos, glyf) { + return [ + 10, + new GlyphHeader({ + numberOfContours: glyf.getInt16(pos), + xMin: glyf.getInt16(pos + 2), + yMin: glyf.getInt16(pos + 4), + xMax: glyf.getInt16(pos + 6), + yMax: glyf.getInt16(pos + 8), + }), + ]; + } + + getSize() { + return 10; + } + + write(pos, buf) { + buf.setInt16(pos, this.numberOfContours); + buf.setInt16(pos + 2, this.xMin); + buf.setInt16(pos + 4, this.yMin); + buf.setInt16(pos + 6, this.xMax); + buf.setInt16(pos + 8, this.yMax); + + return 10; + } + + scale(x, factor) { + this.xMin = Math.round(x + (this.xMin - x) * factor); + this.xMax = Math.round(x + (this.xMax - x) * factor); + } +} + +class Contour { + constructor({ flags, xCoordinates, yCoordinates }) { + this.xCoordinates = xCoordinates; + this.yCoordinates = yCoordinates; + this.flags = flags; + } +} + +class SimpleGlyph { + constructor({ contours, instructions }) { + this.contours = contours; + this.instructions = instructions; + } + + static parse(pos, glyf, numberOfContours) { + const endPtsOfContours = []; + for (let i = 0; i < numberOfContours; i++) { + const endPt = glyf.getUint16(pos); + pos += 2; + endPtsOfContours.push(endPt); + } + const numberOfPt = endPtsOfContours[numberOfContours - 1] + 1; + const instructionLength = glyf.getUint16(pos); + pos += 2; + const instructions = new Uint8Array(glyf).slice( + pos, + pos + instructionLength + ); + pos += instructionLength; + + const flags = []; + for (let i = 0; i < numberOfPt; pos++, i++) { + let flag = glyf.getUint8(pos); + flags.push(flag); + if (flag & REPEAT_FLAG) { + const count = glyf.getUint8(++pos); + flag ^= REPEAT_FLAG; + for (let m = 0; m < count; m++) { + flags.push(flag); + } + i += count; + } + } + + const allXCoordinates = []; + let xCoordinates = []; + let yCoordinates = []; + let pointFlags = []; + const contours = []; + let endPtsOfContoursIndex = 0; + let lastCoordinate = 0; + + // Get x coordinates. + for (let i = 0; i < numberOfPt; i++) { + const flag = flags[i]; + if (flag & X_SHORT_VECTOR) { + // 8-bits unsigned value. + const x = glyf.getUint8(pos++); + lastCoordinate += flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR ? x : -x; + xCoordinates.push(lastCoordinate); + } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) { + // IS_SAME. + xCoordinates.push(lastCoordinate); + } else { + lastCoordinate += glyf.getInt16(pos); + pos += 2; + xCoordinates.push(lastCoordinate); + } + + if (endPtsOfContours[endPtsOfContoursIndex] === i) { + // Next entry is the first one of a new contour. + endPtsOfContoursIndex++; + allXCoordinates.push(xCoordinates); + xCoordinates = []; + } + } + + lastCoordinate = 0; + endPtsOfContoursIndex = 0; + for (let i = 0; i < numberOfPt; i++) { + const flag = flags[i]; + if (flag & Y_SHORT_VECTOR) { + // 8-bits unsigned value. + const y = glyf.getUint8(pos++); + lastCoordinate += flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR ? y : -y; + yCoordinates.push(lastCoordinate); + } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) { + // IS_SAME. + yCoordinates.push(lastCoordinate); + } else { + lastCoordinate += glyf.getInt16(pos); + pos += 2; + yCoordinates.push(lastCoordinate); + } + + pointFlags.push((flag & ON_CURVE_POINT) | (flag & OVERLAP_SIMPLE)); + + if (endPtsOfContours[endPtsOfContoursIndex] === i) { + // Next entry is the first one of a new contour. + xCoordinates = allXCoordinates[endPtsOfContoursIndex]; + endPtsOfContoursIndex++; + contours.push( + new Contour({ + flags: pointFlags, + xCoordinates, + yCoordinates, + }) + ); + yCoordinates = []; + pointFlags = []; + } + } + + return new SimpleGlyph({ + contours, + instructions, + }); + } + + getSize() { + let size = this.contours.length * 2 + 2 + this.instructions.length; + let lastX = 0; + let lastY = 0; + for (const contour of this.contours) { + size += contour.flags.length; + for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) { + const x = contour.xCoordinates[i]; + const y = contour.yCoordinates[i]; + let abs = Math.abs(x - lastX); + if (abs > 255) { + size += 2; + } else if (abs > 0) { + size += 1; + } + lastX = x; + + abs = Math.abs(y - lastY); + if (abs > 255) { + size += 2; + } else if (abs > 0) { + size += 1; + } + lastY = y; + } + } + return size; + } + + write(pos, buf) { + const spos = pos; + const xCoordinates = []; + const yCoordinates = []; + const flags = []; + let lastX = 0; + let lastY = 0; + + for (const contour of this.contours) { + for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) { + let flag = contour.flags[i]; + const x = contour.xCoordinates[i]; + let delta = x - lastX; + if (delta === 0) { + flag |= X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR; + xCoordinates.push(0); + } else { + const abs = Math.abs(delta); + if (abs <= 255) { + flag |= + delta >= 0 + ? X_SHORT_VECTOR | X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR + : X_SHORT_VECTOR; + xCoordinates.push(abs); + } else { + xCoordinates.push(delta); + } + } + lastX = x; + + const y = contour.yCoordinates[i]; + delta = y - lastY; + if (delta === 0) { + flag |= Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR; + yCoordinates.push(0); + } else { + const abs = Math.abs(delta); + if (abs <= 255) { + flag |= + delta >= 0 + ? Y_SHORT_VECTOR | Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR + : Y_SHORT_VECTOR; + yCoordinates.push(abs); + } else { + yCoordinates.push(delta); + } + } + lastY = y; + + flags.push(flag); + } + + // Write endPtsOfContours entry. + buf.setUint16(pos, xCoordinates.length - 1); + pos += 2; + } + + // Write instructionLength. + buf.setUint16(pos, this.instructions.length); + pos += 2; + if (this.instructions.length) { + // Write instructions. + new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set( + this.instructions, + pos + ); + pos += this.instructions.length; + } + + // Write flags. + for (const flag of flags) { + buf.setUint8(pos++, flag); + } + + // Write xCoordinates. + for (let i = 0, ii = xCoordinates.length; i < ii; i++) { + const x = xCoordinates[i]; + const flag = flags[i]; + if (flag & X_SHORT_VECTOR) { + buf.setUint8(pos++, x); + } else if (!(flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)) { + buf.setInt16(pos, x); + pos += 2; + } + } + + // Write yCoordinates. + for (let i = 0, ii = yCoordinates.length; i < ii; i++) { + const y = yCoordinates[i]; + const flag = flags[i]; + if (flag & Y_SHORT_VECTOR) { + buf.setUint8(pos++, y); + } else if (!(flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)) { + buf.setInt16(pos, y); + pos += 2; + } + } + + return pos - spos; + } + + scale(x, factor) { + for (const contour of this.contours) { + if (contour.xCoordinates.length === 0) { + continue; + } + + for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) { + contour.xCoordinates[i] = Math.round( + x + (contour.xCoordinates[i] - x) * factor + ); + } + } + } +} + +class CompositeGlyph { + constructor({ + flags, + glyphIndex, + argument1, + argument2, + transf, + instructions, + }) { + this.flags = flags; + this.glyphIndex = glyphIndex; + this.argument1 = argument1; + this.argument2 = argument2; + this.transf = transf; + this.instructions = instructions; + } + + static parse(pos, glyf) { + const spos = pos; + const transf = []; + let flags = glyf.getUint16(pos); + const glyphIndex = glyf.getUint16(pos + 2); + pos += 4; + + let argument1, argument2; + if (flags & ARG_1_AND_2_ARE_WORDS) { + if (flags & ARGS_ARE_XY_VALUES) { + argument1 = glyf.getInt16(pos); + argument2 = glyf.getInt16(pos + 2); + } else { + argument1 = glyf.getUint16(pos); + argument2 = glyf.getUint16(pos + 2); + } + pos += 4; + flags ^= ARG_1_AND_2_ARE_WORDS; + } else { + if (flags & ARGS_ARE_XY_VALUES) { + argument1 = glyf.getInt8(pos); + argument2 = glyf.getInt8(pos + 1); + } else { + argument1 = glyf.getUint8(pos); + argument2 = glyf.getUint8(pos + 1); + } + pos += 2; + } + + if (flags & WE_HAVE_A_SCALE) { + // Single F2.14. + transf.push(glyf.getUint16(pos)); + pos += 2; + } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { + // Two F2.14. + transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2)); + pos += 4; + } else if (flags & WE_HAVE_A_TWO_BY_TWO) { + // Four F2.14. + transf.push( + glyf.getUint16(pos), + glyf.getUint16(pos + 2), + glyf.getUint16(pos + 4), + glyf.getUint16(pos + 6) + ); + pos += 8; + } + + let instructions = null; + if (flags & WE_HAVE_INSTRUCTIONS) { + const instructionLength = glyf.getUint16(pos); + pos += 2; + instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength); + pos += instructionLength; + } + + return [ + pos - spos, + new CompositeGlyph({ + flags, + glyphIndex, + argument1, + argument2, + transf, + instructions, + }), + ]; + } + + getSize() { + let size = 2 + 2 + this.transf.length * 2; + if (this.flags & WE_HAVE_INSTRUCTIONS) { + size += 2 + this.instructions.length; + } + + size += 2; + if (this.flags & 2) { + // Arguments are signed. + if ( + !( + this.argument1 >= -128 && + this.argument1 <= 127 && + this.argument2 >= -128 && + this.argument2 <= 127 + ) + ) { + size += 2; + } + } else if ( + !( + this.argument1 >= 0 && + this.argument1 <= 255 && + this.argument2 >= 0 && + this.argument2 <= 255 + ) + ) { + size += 2; + } + + return size; + } + + write(pos, buf) { + const spos = pos; + + if (this.flags & ARGS_ARE_XY_VALUES) { + // Arguments are signed. + if ( + !( + this.argument1 >= -128 && + this.argument1 <= 127 && + this.argument2 >= -128 && + this.argument2 <= 127 + ) + ) { + this.flags |= ARG_1_AND_2_ARE_WORDS; + } + } else if ( + !( + this.argument1 >= 0 && + this.argument1 <= 255 && + this.argument2 >= 0 && + this.argument2 <= 255 + ) + ) { + this.flags |= ARG_1_AND_2_ARE_WORDS; + } + + buf.setUint16(pos, this.flags); + buf.setUint16(pos + 2, this.glyphIndex); + pos += 4; + + if (this.flags & ARG_1_AND_2_ARE_WORDS) { + if (this.flags & ARGS_ARE_XY_VALUES) { + buf.setInt16(pos, this.argument1); + buf.setInt16(pos + 2, this.argument2); + } else { + buf.setUint16(pos, this.argument1); + buf.setUint16(pos + 2, this.argument2); + } + pos += 4; + } else { + buf.setUint8(pos, this.argument1); + buf.setUint8(pos + 1, this.argument2); + pos += 2; + } + + if (this.flags & WE_HAVE_INSTRUCTIONS) { + buf.setUint16(pos, this.instructions.length); + pos += 2; + // Write instructions. + if (this.instructions.length) { + new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set( + this.instructions, + pos + ); + pos += this.instructions.length; + } + } + + return pos - spos; + } + + scale(x, factor) {} +} + +export { GlyfTable }; diff --git a/frontend/public/pdf.js/src/core/glyphlist.js b/frontend/public/pdf.js/src/core/glyphlist.js new file mode 100644 index 0000000..d624d3f --- /dev/null +++ b/frontend/public/pdf.js/src/core/glyphlist.js @@ -0,0 +1,4557 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { getLookupTableFactory } from "./core_utils.js"; + +const getGlyphsUnicode = getLookupTableFactory(function (t) { + t.A = 0x0041; + t.AE = 0x00c6; + t.AEacute = 0x01fc; + t.AEmacron = 0x01e2; + t.AEsmall = 0xf7e6; + t.Aacute = 0x00c1; + t.Aacutesmall = 0xf7e1; + t.Abreve = 0x0102; + t.Abreveacute = 0x1eae; + t.Abrevecyrillic = 0x04d0; + t.Abrevedotbelow = 0x1eb6; + t.Abrevegrave = 0x1eb0; + t.Abrevehookabove = 0x1eb2; + t.Abrevetilde = 0x1eb4; + t.Acaron = 0x01cd; + t.Acircle = 0x24b6; + t.Acircumflex = 0x00c2; + t.Acircumflexacute = 0x1ea4; + t.Acircumflexdotbelow = 0x1eac; + t.Acircumflexgrave = 0x1ea6; + t.Acircumflexhookabove = 0x1ea8; + t.Acircumflexsmall = 0xf7e2; + t.Acircumflextilde = 0x1eaa; + t.Acute = 0xf6c9; + t.Acutesmall = 0xf7b4; + t.Acyrillic = 0x0410; + t.Adblgrave = 0x0200; + t.Adieresis = 0x00c4; + t.Adieresiscyrillic = 0x04d2; + t.Adieresismacron = 0x01de; + t.Adieresissmall = 0xf7e4; + t.Adotbelow = 0x1ea0; + t.Adotmacron = 0x01e0; + t.Agrave = 0x00c0; + t.Agravesmall = 0xf7e0; + t.Ahookabove = 0x1ea2; + t.Aiecyrillic = 0x04d4; + t.Ainvertedbreve = 0x0202; + t.Alpha = 0x0391; + t.Alphatonos = 0x0386; + t.Amacron = 0x0100; + t.Amonospace = 0xff21; + t.Aogonek = 0x0104; + t.Aring = 0x00c5; + t.Aringacute = 0x01fa; + t.Aringbelow = 0x1e00; + t.Aringsmall = 0xf7e5; + t.Asmall = 0xf761; + t.Atilde = 0x00c3; + t.Atildesmall = 0xf7e3; + t.Aybarmenian = 0x0531; + t.B = 0x0042; + t.Bcircle = 0x24b7; + t.Bdotaccent = 0x1e02; + t.Bdotbelow = 0x1e04; + t.Becyrillic = 0x0411; + t.Benarmenian = 0x0532; + t.Beta = 0x0392; + t.Bhook = 0x0181; + t.Blinebelow = 0x1e06; + t.Bmonospace = 0xff22; + t.Brevesmall = 0xf6f4; + t.Bsmall = 0xf762; + t.Btopbar = 0x0182; + t.C = 0x0043; + t.Caarmenian = 0x053e; + t.Cacute = 0x0106; + t.Caron = 0xf6ca; + t.Caronsmall = 0xf6f5; + t.Ccaron = 0x010c; + t.Ccedilla = 0x00c7; + t.Ccedillaacute = 0x1e08; + t.Ccedillasmall = 0xf7e7; + t.Ccircle = 0x24b8; + t.Ccircumflex = 0x0108; + t.Cdot = 0x010a; + t.Cdotaccent = 0x010a; + t.Cedillasmall = 0xf7b8; + t.Chaarmenian = 0x0549; + t.Cheabkhasiancyrillic = 0x04bc; + t.Checyrillic = 0x0427; + t.Chedescenderabkhasiancyrillic = 0x04be; + t.Chedescendercyrillic = 0x04b6; + t.Chedieresiscyrillic = 0x04f4; + t.Cheharmenian = 0x0543; + t.Chekhakassiancyrillic = 0x04cb; + t.Cheverticalstrokecyrillic = 0x04b8; + t.Chi = 0x03a7; + t.Chook = 0x0187; + t.Circumflexsmall = 0xf6f6; + t.Cmonospace = 0xff23; + t.Coarmenian = 0x0551; + t.Csmall = 0xf763; + t.D = 0x0044; + t.DZ = 0x01f1; + t.DZcaron = 0x01c4; + t.Daarmenian = 0x0534; + t.Dafrican = 0x0189; + t.Dcaron = 0x010e; + t.Dcedilla = 0x1e10; + t.Dcircle = 0x24b9; + t.Dcircumflexbelow = 0x1e12; + t.Dcroat = 0x0110; + t.Ddotaccent = 0x1e0a; + t.Ddotbelow = 0x1e0c; + t.Decyrillic = 0x0414; + t.Deicoptic = 0x03ee; + t.Delta = 0x2206; + t.Deltagreek = 0x0394; + t.Dhook = 0x018a; + t.Dieresis = 0xf6cb; + t.DieresisAcute = 0xf6cc; + t.DieresisGrave = 0xf6cd; + t.Dieresissmall = 0xf7a8; + t.Digammagreek = 0x03dc; + t.Djecyrillic = 0x0402; + t.Dlinebelow = 0x1e0e; + t.Dmonospace = 0xff24; + t.Dotaccentsmall = 0xf6f7; + t.Dslash = 0x0110; + t.Dsmall = 0xf764; + t.Dtopbar = 0x018b; + t.Dz = 0x01f2; + t.Dzcaron = 0x01c5; + t.Dzeabkhasiancyrillic = 0x04e0; + t.Dzecyrillic = 0x0405; + t.Dzhecyrillic = 0x040f; + t.E = 0x0045; + t.Eacute = 0x00c9; + t.Eacutesmall = 0xf7e9; + t.Ebreve = 0x0114; + t.Ecaron = 0x011a; + t.Ecedillabreve = 0x1e1c; + t.Echarmenian = 0x0535; + t.Ecircle = 0x24ba; + t.Ecircumflex = 0x00ca; + t.Ecircumflexacute = 0x1ebe; + t.Ecircumflexbelow = 0x1e18; + t.Ecircumflexdotbelow = 0x1ec6; + t.Ecircumflexgrave = 0x1ec0; + t.Ecircumflexhookabove = 0x1ec2; + t.Ecircumflexsmall = 0xf7ea; + t.Ecircumflextilde = 0x1ec4; + t.Ecyrillic = 0x0404; + t.Edblgrave = 0x0204; + t.Edieresis = 0x00cb; + t.Edieresissmall = 0xf7eb; + t.Edot = 0x0116; + t.Edotaccent = 0x0116; + t.Edotbelow = 0x1eb8; + t.Efcyrillic = 0x0424; + t.Egrave = 0x00c8; + t.Egravesmall = 0xf7e8; + t.Eharmenian = 0x0537; + t.Ehookabove = 0x1eba; + t.Eightroman = 0x2167; + t.Einvertedbreve = 0x0206; + t.Eiotifiedcyrillic = 0x0464; + t.Elcyrillic = 0x041b; + t.Elevenroman = 0x216a; + t.Emacron = 0x0112; + t.Emacronacute = 0x1e16; + t.Emacrongrave = 0x1e14; + t.Emcyrillic = 0x041c; + t.Emonospace = 0xff25; + t.Encyrillic = 0x041d; + t.Endescendercyrillic = 0x04a2; + t.Eng = 0x014a; + t.Enghecyrillic = 0x04a4; + t.Enhookcyrillic = 0x04c7; + t.Eogonek = 0x0118; + t.Eopen = 0x0190; + t.Epsilon = 0x0395; + t.Epsilontonos = 0x0388; + t.Ercyrillic = 0x0420; + t.Ereversed = 0x018e; + t.Ereversedcyrillic = 0x042d; + t.Escyrillic = 0x0421; + t.Esdescendercyrillic = 0x04aa; + t.Esh = 0x01a9; + t.Esmall = 0xf765; + t.Eta = 0x0397; + t.Etarmenian = 0x0538; + t.Etatonos = 0x0389; + t.Eth = 0x00d0; + t.Ethsmall = 0xf7f0; + t.Etilde = 0x1ebc; + t.Etildebelow = 0x1e1a; + t.Euro = 0x20ac; + t.Ezh = 0x01b7; + t.Ezhcaron = 0x01ee; + t.Ezhreversed = 0x01b8; + t.F = 0x0046; + t.Fcircle = 0x24bb; + t.Fdotaccent = 0x1e1e; + t.Feharmenian = 0x0556; + t.Feicoptic = 0x03e4; + t.Fhook = 0x0191; + t.Fitacyrillic = 0x0472; + t.Fiveroman = 0x2164; + t.Fmonospace = 0xff26; + t.Fourroman = 0x2163; + t.Fsmall = 0xf766; + t.G = 0x0047; + t.GBsquare = 0x3387; + t.Gacute = 0x01f4; + t.Gamma = 0x0393; + t.Gammaafrican = 0x0194; + t.Gangiacoptic = 0x03ea; + t.Gbreve = 0x011e; + t.Gcaron = 0x01e6; + t.Gcedilla = 0x0122; + t.Gcircle = 0x24bc; + t.Gcircumflex = 0x011c; + t.Gcommaaccent = 0x0122; + t.Gdot = 0x0120; + t.Gdotaccent = 0x0120; + t.Gecyrillic = 0x0413; + t.Ghadarmenian = 0x0542; + t.Ghemiddlehookcyrillic = 0x0494; + t.Ghestrokecyrillic = 0x0492; + t.Gheupturncyrillic = 0x0490; + t.Ghook = 0x0193; + t.Gimarmenian = 0x0533; + t.Gjecyrillic = 0x0403; + t.Gmacron = 0x1e20; + t.Gmonospace = 0xff27; + t.Grave = 0xf6ce; + t.Gravesmall = 0xf760; + t.Gsmall = 0xf767; + t.Gsmallhook = 0x029b; + t.Gstroke = 0x01e4; + t.H = 0x0048; + t.H18533 = 0x25cf; + t.H18543 = 0x25aa; + t.H18551 = 0x25ab; + t.H22073 = 0x25a1; + t.HPsquare = 0x33cb; + t.Haabkhasiancyrillic = 0x04a8; + t.Hadescendercyrillic = 0x04b2; + t.Hardsigncyrillic = 0x042a; + t.Hbar = 0x0126; + t.Hbrevebelow = 0x1e2a; + t.Hcedilla = 0x1e28; + t.Hcircle = 0x24bd; + t.Hcircumflex = 0x0124; + t.Hdieresis = 0x1e26; + t.Hdotaccent = 0x1e22; + t.Hdotbelow = 0x1e24; + t.Hmonospace = 0xff28; + t.Hoarmenian = 0x0540; + t.Horicoptic = 0x03e8; + t.Hsmall = 0xf768; + t.Hungarumlaut = 0xf6cf; + t.Hungarumlautsmall = 0xf6f8; + t.Hzsquare = 0x3390; + t.I = 0x0049; + t.IAcyrillic = 0x042f; + t.IJ = 0x0132; + t.IUcyrillic = 0x042e; + t.Iacute = 0x00cd; + t.Iacutesmall = 0xf7ed; + t.Ibreve = 0x012c; + t.Icaron = 0x01cf; + t.Icircle = 0x24be; + t.Icircumflex = 0x00ce; + t.Icircumflexsmall = 0xf7ee; + t.Icyrillic = 0x0406; + t.Idblgrave = 0x0208; + t.Idieresis = 0x00cf; + t.Idieresisacute = 0x1e2e; + t.Idieresiscyrillic = 0x04e4; + t.Idieresissmall = 0xf7ef; + t.Idot = 0x0130; + t.Idotaccent = 0x0130; + t.Idotbelow = 0x1eca; + t.Iebrevecyrillic = 0x04d6; + t.Iecyrillic = 0x0415; + t.Ifraktur = 0x2111; + t.Igrave = 0x00cc; + t.Igravesmall = 0xf7ec; + t.Ihookabove = 0x1ec8; + t.Iicyrillic = 0x0418; + t.Iinvertedbreve = 0x020a; + t.Iishortcyrillic = 0x0419; + t.Imacron = 0x012a; + t.Imacroncyrillic = 0x04e2; + t.Imonospace = 0xff29; + t.Iniarmenian = 0x053b; + t.Iocyrillic = 0x0401; + t.Iogonek = 0x012e; + t.Iota = 0x0399; + t.Iotaafrican = 0x0196; + t.Iotadieresis = 0x03aa; + t.Iotatonos = 0x038a; + t.Ismall = 0xf769; + t.Istroke = 0x0197; + t.Itilde = 0x0128; + t.Itildebelow = 0x1e2c; + t.Izhitsacyrillic = 0x0474; + t.Izhitsadblgravecyrillic = 0x0476; + t.J = 0x004a; + t.Jaarmenian = 0x0541; + t.Jcircle = 0x24bf; + t.Jcircumflex = 0x0134; + t.Jecyrillic = 0x0408; + t.Jheharmenian = 0x054b; + t.Jmonospace = 0xff2a; + t.Jsmall = 0xf76a; + t.K = 0x004b; + t.KBsquare = 0x3385; + t.KKsquare = 0x33cd; + t.Kabashkircyrillic = 0x04a0; + t.Kacute = 0x1e30; + t.Kacyrillic = 0x041a; + t.Kadescendercyrillic = 0x049a; + t.Kahookcyrillic = 0x04c3; + t.Kappa = 0x039a; + t.Kastrokecyrillic = 0x049e; + t.Kaverticalstrokecyrillic = 0x049c; + t.Kcaron = 0x01e8; + t.Kcedilla = 0x0136; + t.Kcircle = 0x24c0; + t.Kcommaaccent = 0x0136; + t.Kdotbelow = 0x1e32; + t.Keharmenian = 0x0554; + t.Kenarmenian = 0x053f; + t.Khacyrillic = 0x0425; + t.Kheicoptic = 0x03e6; + t.Khook = 0x0198; + t.Kjecyrillic = 0x040c; + t.Klinebelow = 0x1e34; + t.Kmonospace = 0xff2b; + t.Koppacyrillic = 0x0480; + t.Koppagreek = 0x03de; + t.Ksicyrillic = 0x046e; + t.Ksmall = 0xf76b; + t.L = 0x004c; + t.LJ = 0x01c7; + t.LL = 0xf6bf; + t.Lacute = 0x0139; + t.Lambda = 0x039b; + t.Lcaron = 0x013d; + t.Lcedilla = 0x013b; + t.Lcircle = 0x24c1; + t.Lcircumflexbelow = 0x1e3c; + t.Lcommaaccent = 0x013b; + t.Ldot = 0x013f; + t.Ldotaccent = 0x013f; + t.Ldotbelow = 0x1e36; + t.Ldotbelowmacron = 0x1e38; + t.Liwnarmenian = 0x053c; + t.Lj = 0x01c8; + t.Ljecyrillic = 0x0409; + t.Llinebelow = 0x1e3a; + t.Lmonospace = 0xff2c; + t.Lslash = 0x0141; + t.Lslashsmall = 0xf6f9; + t.Lsmall = 0xf76c; + t.M = 0x004d; + t.MBsquare = 0x3386; + t.Macron = 0xf6d0; + t.Macronsmall = 0xf7af; + t.Macute = 0x1e3e; + t.Mcircle = 0x24c2; + t.Mdotaccent = 0x1e40; + t.Mdotbelow = 0x1e42; + t.Menarmenian = 0x0544; + t.Mmonospace = 0xff2d; + t.Msmall = 0xf76d; + t.Mturned = 0x019c; + t.Mu = 0x039c; + t.N = 0x004e; + t.NJ = 0x01ca; + t.Nacute = 0x0143; + t.Ncaron = 0x0147; + t.Ncedilla = 0x0145; + t.Ncircle = 0x24c3; + t.Ncircumflexbelow = 0x1e4a; + t.Ncommaaccent = 0x0145; + t.Ndotaccent = 0x1e44; + t.Ndotbelow = 0x1e46; + t.Nhookleft = 0x019d; + t.Nineroman = 0x2168; + t.Nj = 0x01cb; + t.Njecyrillic = 0x040a; + t.Nlinebelow = 0x1e48; + t.Nmonospace = 0xff2e; + t.Nowarmenian = 0x0546; + t.Nsmall = 0xf76e; + t.Ntilde = 0x00d1; + t.Ntildesmall = 0xf7f1; + t.Nu = 0x039d; + t.O = 0x004f; + t.OE = 0x0152; + t.OEsmall = 0xf6fa; + t.Oacute = 0x00d3; + t.Oacutesmall = 0xf7f3; + t.Obarredcyrillic = 0x04e8; + t.Obarreddieresiscyrillic = 0x04ea; + t.Obreve = 0x014e; + t.Ocaron = 0x01d1; + t.Ocenteredtilde = 0x019f; + t.Ocircle = 0x24c4; + t.Ocircumflex = 0x00d4; + t.Ocircumflexacute = 0x1ed0; + t.Ocircumflexdotbelow = 0x1ed8; + t.Ocircumflexgrave = 0x1ed2; + t.Ocircumflexhookabove = 0x1ed4; + t.Ocircumflexsmall = 0xf7f4; + t.Ocircumflextilde = 0x1ed6; + t.Ocyrillic = 0x041e; + t.Odblacute = 0x0150; + t.Odblgrave = 0x020c; + t.Odieresis = 0x00d6; + t.Odieresiscyrillic = 0x04e6; + t.Odieresissmall = 0xf7f6; + t.Odotbelow = 0x1ecc; + t.Ogoneksmall = 0xf6fb; + t.Ograve = 0x00d2; + t.Ogravesmall = 0xf7f2; + t.Oharmenian = 0x0555; + t.Ohm = 0x2126; + t.Ohookabove = 0x1ece; + t.Ohorn = 0x01a0; + t.Ohornacute = 0x1eda; + t.Ohorndotbelow = 0x1ee2; + t.Ohorngrave = 0x1edc; + t.Ohornhookabove = 0x1ede; + t.Ohorntilde = 0x1ee0; + t.Ohungarumlaut = 0x0150; + t.Oi = 0x01a2; + t.Oinvertedbreve = 0x020e; + t.Omacron = 0x014c; + t.Omacronacute = 0x1e52; + t.Omacrongrave = 0x1e50; + t.Omega = 0x2126; + t.Omegacyrillic = 0x0460; + t.Omegagreek = 0x03a9; + t.Omegaroundcyrillic = 0x047a; + t.Omegatitlocyrillic = 0x047c; + t.Omegatonos = 0x038f; + t.Omicron = 0x039f; + t.Omicrontonos = 0x038c; + t.Omonospace = 0xff2f; + t.Oneroman = 0x2160; + t.Oogonek = 0x01ea; + t.Oogonekmacron = 0x01ec; + t.Oopen = 0x0186; + t.Oslash = 0x00d8; + t.Oslashacute = 0x01fe; + t.Oslashsmall = 0xf7f8; + t.Osmall = 0xf76f; + t.Ostrokeacute = 0x01fe; + t.Otcyrillic = 0x047e; + t.Otilde = 0x00d5; + t.Otildeacute = 0x1e4c; + t.Otildedieresis = 0x1e4e; + t.Otildesmall = 0xf7f5; + t.P = 0x0050; + t.Pacute = 0x1e54; + t.Pcircle = 0x24c5; + t.Pdotaccent = 0x1e56; + t.Pecyrillic = 0x041f; + t.Peharmenian = 0x054a; + t.Pemiddlehookcyrillic = 0x04a6; + t.Phi = 0x03a6; + t.Phook = 0x01a4; + t.Pi = 0x03a0; + t.Piwrarmenian = 0x0553; + t.Pmonospace = 0xff30; + t.Psi = 0x03a8; + t.Psicyrillic = 0x0470; + t.Psmall = 0xf770; + t.Q = 0x0051; + t.Qcircle = 0x24c6; + t.Qmonospace = 0xff31; + t.Qsmall = 0xf771; + t.R = 0x0052; + t.Raarmenian = 0x054c; + t.Racute = 0x0154; + t.Rcaron = 0x0158; + t.Rcedilla = 0x0156; + t.Rcircle = 0x24c7; + t.Rcommaaccent = 0x0156; + t.Rdblgrave = 0x0210; + t.Rdotaccent = 0x1e58; + t.Rdotbelow = 0x1e5a; + t.Rdotbelowmacron = 0x1e5c; + t.Reharmenian = 0x0550; + t.Rfraktur = 0x211c; + t.Rho = 0x03a1; + t.Ringsmall = 0xf6fc; + t.Rinvertedbreve = 0x0212; + t.Rlinebelow = 0x1e5e; + t.Rmonospace = 0xff32; + t.Rsmall = 0xf772; + t.Rsmallinverted = 0x0281; + t.Rsmallinvertedsuperior = 0x02b6; + t.S = 0x0053; + t.SF010000 = 0x250c; + t.SF020000 = 0x2514; + t.SF030000 = 0x2510; + t.SF040000 = 0x2518; + t.SF050000 = 0x253c; + t.SF060000 = 0x252c; + t.SF070000 = 0x2534; + t.SF080000 = 0x251c; + t.SF090000 = 0x2524; + t.SF100000 = 0x2500; + t.SF110000 = 0x2502; + t.SF190000 = 0x2561; + t.SF200000 = 0x2562; + t.SF210000 = 0x2556; + t.SF220000 = 0x2555; + t.SF230000 = 0x2563; + t.SF240000 = 0x2551; + t.SF250000 = 0x2557; + t.SF260000 = 0x255d; + t.SF270000 = 0x255c; + t.SF280000 = 0x255b; + t.SF360000 = 0x255e; + t.SF370000 = 0x255f; + t.SF380000 = 0x255a; + t.SF390000 = 0x2554; + t.SF400000 = 0x2569; + t.SF410000 = 0x2566; + t.SF420000 = 0x2560; + t.SF430000 = 0x2550; + t.SF440000 = 0x256c; + t.SF450000 = 0x2567; + t.SF460000 = 0x2568; + t.SF470000 = 0x2564; + t.SF480000 = 0x2565; + t.SF490000 = 0x2559; + t.SF500000 = 0x2558; + t.SF510000 = 0x2552; + t.SF520000 = 0x2553; + t.SF530000 = 0x256b; + t.SF540000 = 0x256a; + t.Sacute = 0x015a; + t.Sacutedotaccent = 0x1e64; + t.Sampigreek = 0x03e0; + t.Scaron = 0x0160; + t.Scarondotaccent = 0x1e66; + t.Scaronsmall = 0xf6fd; + t.Scedilla = 0x015e; + t.Schwa = 0x018f; + t.Schwacyrillic = 0x04d8; + t.Schwadieresiscyrillic = 0x04da; + t.Scircle = 0x24c8; + t.Scircumflex = 0x015c; + t.Scommaaccent = 0x0218; + t.Sdotaccent = 0x1e60; + t.Sdotbelow = 0x1e62; + t.Sdotbelowdotaccent = 0x1e68; + t.Seharmenian = 0x054d; + t.Sevenroman = 0x2166; + t.Shaarmenian = 0x0547; + t.Shacyrillic = 0x0428; + t.Shchacyrillic = 0x0429; + t.Sheicoptic = 0x03e2; + t.Shhacyrillic = 0x04ba; + t.Shimacoptic = 0x03ec; + t.Sigma = 0x03a3; + t.Sixroman = 0x2165; + t.Smonospace = 0xff33; + t.Softsigncyrillic = 0x042c; + t.Ssmall = 0xf773; + t.Stigmagreek = 0x03da; + t.T = 0x0054; + t.Tau = 0x03a4; + t.Tbar = 0x0166; + t.Tcaron = 0x0164; + t.Tcedilla = 0x0162; + t.Tcircle = 0x24c9; + t.Tcircumflexbelow = 0x1e70; + t.Tcommaaccent = 0x0162; + t.Tdotaccent = 0x1e6a; + t.Tdotbelow = 0x1e6c; + t.Tecyrillic = 0x0422; + t.Tedescendercyrillic = 0x04ac; + t.Tenroman = 0x2169; + t.Tetsecyrillic = 0x04b4; + t.Theta = 0x0398; + t.Thook = 0x01ac; + t.Thorn = 0x00de; + t.Thornsmall = 0xf7fe; + t.Threeroman = 0x2162; + t.Tildesmall = 0xf6fe; + t.Tiwnarmenian = 0x054f; + t.Tlinebelow = 0x1e6e; + t.Tmonospace = 0xff34; + t.Toarmenian = 0x0539; + t.Tonefive = 0x01bc; + t.Tonesix = 0x0184; + t.Tonetwo = 0x01a7; + t.Tretroflexhook = 0x01ae; + t.Tsecyrillic = 0x0426; + t.Tshecyrillic = 0x040b; + t.Tsmall = 0xf774; + t.Twelveroman = 0x216b; + t.Tworoman = 0x2161; + t.U = 0x0055; + t.Uacute = 0x00da; + t.Uacutesmall = 0xf7fa; + t.Ubreve = 0x016c; + t.Ucaron = 0x01d3; + t.Ucircle = 0x24ca; + t.Ucircumflex = 0x00db; + t.Ucircumflexbelow = 0x1e76; + t.Ucircumflexsmall = 0xf7fb; + t.Ucyrillic = 0x0423; + t.Udblacute = 0x0170; + t.Udblgrave = 0x0214; + t.Udieresis = 0x00dc; + t.Udieresisacute = 0x01d7; + t.Udieresisbelow = 0x1e72; + t.Udieresiscaron = 0x01d9; + t.Udieresiscyrillic = 0x04f0; + t.Udieresisgrave = 0x01db; + t.Udieresismacron = 0x01d5; + t.Udieresissmall = 0xf7fc; + t.Udotbelow = 0x1ee4; + t.Ugrave = 0x00d9; + t.Ugravesmall = 0xf7f9; + t.Uhookabove = 0x1ee6; + t.Uhorn = 0x01af; + t.Uhornacute = 0x1ee8; + t.Uhorndotbelow = 0x1ef0; + t.Uhorngrave = 0x1eea; + t.Uhornhookabove = 0x1eec; + t.Uhorntilde = 0x1eee; + t.Uhungarumlaut = 0x0170; + t.Uhungarumlautcyrillic = 0x04f2; + t.Uinvertedbreve = 0x0216; + t.Ukcyrillic = 0x0478; + t.Umacron = 0x016a; + t.Umacroncyrillic = 0x04ee; + t.Umacrondieresis = 0x1e7a; + t.Umonospace = 0xff35; + t.Uogonek = 0x0172; + t.Upsilon = 0x03a5; + t.Upsilon1 = 0x03d2; + t.Upsilonacutehooksymbolgreek = 0x03d3; + t.Upsilonafrican = 0x01b1; + t.Upsilondieresis = 0x03ab; + t.Upsilondieresishooksymbolgreek = 0x03d4; + t.Upsilonhooksymbol = 0x03d2; + t.Upsilontonos = 0x038e; + t.Uring = 0x016e; + t.Ushortcyrillic = 0x040e; + t.Usmall = 0xf775; + t.Ustraightcyrillic = 0x04ae; + t.Ustraightstrokecyrillic = 0x04b0; + t.Utilde = 0x0168; + t.Utildeacute = 0x1e78; + t.Utildebelow = 0x1e74; + t.V = 0x0056; + t.Vcircle = 0x24cb; + t.Vdotbelow = 0x1e7e; + t.Vecyrillic = 0x0412; + t.Vewarmenian = 0x054e; + t.Vhook = 0x01b2; + t.Vmonospace = 0xff36; + t.Voarmenian = 0x0548; + t.Vsmall = 0xf776; + t.Vtilde = 0x1e7c; + t.W = 0x0057; + t.Wacute = 0x1e82; + t.Wcircle = 0x24cc; + t.Wcircumflex = 0x0174; + t.Wdieresis = 0x1e84; + t.Wdotaccent = 0x1e86; + t.Wdotbelow = 0x1e88; + t.Wgrave = 0x1e80; + t.Wmonospace = 0xff37; + t.Wsmall = 0xf777; + t.X = 0x0058; + t.Xcircle = 0x24cd; + t.Xdieresis = 0x1e8c; + t.Xdotaccent = 0x1e8a; + t.Xeharmenian = 0x053d; + t.Xi = 0x039e; + t.Xmonospace = 0xff38; + t.Xsmall = 0xf778; + t.Y = 0x0059; + t.Yacute = 0x00dd; + t.Yacutesmall = 0xf7fd; + t.Yatcyrillic = 0x0462; + t.Ycircle = 0x24ce; + t.Ycircumflex = 0x0176; + t.Ydieresis = 0x0178; + t.Ydieresissmall = 0xf7ff; + t.Ydotaccent = 0x1e8e; + t.Ydotbelow = 0x1ef4; + t.Yericyrillic = 0x042b; + t.Yerudieresiscyrillic = 0x04f8; + t.Ygrave = 0x1ef2; + t.Yhook = 0x01b3; + t.Yhookabove = 0x1ef6; + t.Yiarmenian = 0x0545; + t.Yicyrillic = 0x0407; + t.Yiwnarmenian = 0x0552; + t.Ymonospace = 0xff39; + t.Ysmall = 0xf779; + t.Ytilde = 0x1ef8; + t.Yusbigcyrillic = 0x046a; + t.Yusbigiotifiedcyrillic = 0x046c; + t.Yuslittlecyrillic = 0x0466; + t.Yuslittleiotifiedcyrillic = 0x0468; + t.Z = 0x005a; + t.Zaarmenian = 0x0536; + t.Zacute = 0x0179; + t.Zcaron = 0x017d; + t.Zcaronsmall = 0xf6ff; + t.Zcircle = 0x24cf; + t.Zcircumflex = 0x1e90; + t.Zdot = 0x017b; + t.Zdotaccent = 0x017b; + t.Zdotbelow = 0x1e92; + t.Zecyrillic = 0x0417; + t.Zedescendercyrillic = 0x0498; + t.Zedieresiscyrillic = 0x04de; + t.Zeta = 0x0396; + t.Zhearmenian = 0x053a; + t.Zhebrevecyrillic = 0x04c1; + t.Zhecyrillic = 0x0416; + t.Zhedescendercyrillic = 0x0496; + t.Zhedieresiscyrillic = 0x04dc; + t.Zlinebelow = 0x1e94; + t.Zmonospace = 0xff3a; + t.Zsmall = 0xf77a; + t.Zstroke = 0x01b5; + t.a = 0x0061; + t.aabengali = 0x0986; + t.aacute = 0x00e1; + t.aadeva = 0x0906; + t.aagujarati = 0x0a86; + t.aagurmukhi = 0x0a06; + t.aamatragurmukhi = 0x0a3e; + t.aarusquare = 0x3303; + t.aavowelsignbengali = 0x09be; + t.aavowelsigndeva = 0x093e; + t.aavowelsigngujarati = 0x0abe; + t.abbreviationmarkarmenian = 0x055f; + t.abbreviationsigndeva = 0x0970; + t.abengali = 0x0985; + t.abopomofo = 0x311a; + t.abreve = 0x0103; + t.abreveacute = 0x1eaf; + t.abrevecyrillic = 0x04d1; + t.abrevedotbelow = 0x1eb7; + t.abrevegrave = 0x1eb1; + t.abrevehookabove = 0x1eb3; + t.abrevetilde = 0x1eb5; + t.acaron = 0x01ce; + t.acircle = 0x24d0; + t.acircumflex = 0x00e2; + t.acircumflexacute = 0x1ea5; + t.acircumflexdotbelow = 0x1ead; + t.acircumflexgrave = 0x1ea7; + t.acircumflexhookabove = 0x1ea9; + t.acircumflextilde = 0x1eab; + t.acute = 0x00b4; + t.acutebelowcmb = 0x0317; + t.acutecmb = 0x0301; + t.acutecomb = 0x0301; + t.acutedeva = 0x0954; + t.acutelowmod = 0x02cf; + t.acutetonecmb = 0x0341; + t.acyrillic = 0x0430; + t.adblgrave = 0x0201; + t.addakgurmukhi = 0x0a71; + t.adeva = 0x0905; + t.adieresis = 0x00e4; + t.adieresiscyrillic = 0x04d3; + t.adieresismacron = 0x01df; + t.adotbelow = 0x1ea1; + t.adotmacron = 0x01e1; + t.ae = 0x00e6; + t.aeacute = 0x01fd; + t.aekorean = 0x3150; + t.aemacron = 0x01e3; + t.afii00208 = 0x2015; + t.afii08941 = 0x20a4; + t.afii10017 = 0x0410; + t.afii10018 = 0x0411; + t.afii10019 = 0x0412; + t.afii10020 = 0x0413; + t.afii10021 = 0x0414; + t.afii10022 = 0x0415; + t.afii10023 = 0x0401; + t.afii10024 = 0x0416; + t.afii10025 = 0x0417; + t.afii10026 = 0x0418; + t.afii10027 = 0x0419; + t.afii10028 = 0x041a; + t.afii10029 = 0x041b; + t.afii10030 = 0x041c; + t.afii10031 = 0x041d; + t.afii10032 = 0x041e; + t.afii10033 = 0x041f; + t.afii10034 = 0x0420; + t.afii10035 = 0x0421; + t.afii10036 = 0x0422; + t.afii10037 = 0x0423; + t.afii10038 = 0x0424; + t.afii10039 = 0x0425; + t.afii10040 = 0x0426; + t.afii10041 = 0x0427; + t.afii10042 = 0x0428; + t.afii10043 = 0x0429; + t.afii10044 = 0x042a; + t.afii10045 = 0x042b; + t.afii10046 = 0x042c; + t.afii10047 = 0x042d; + t.afii10048 = 0x042e; + t.afii10049 = 0x042f; + t.afii10050 = 0x0490; + t.afii10051 = 0x0402; + t.afii10052 = 0x0403; + t.afii10053 = 0x0404; + t.afii10054 = 0x0405; + t.afii10055 = 0x0406; + t.afii10056 = 0x0407; + t.afii10057 = 0x0408; + t.afii10058 = 0x0409; + t.afii10059 = 0x040a; + t.afii10060 = 0x040b; + t.afii10061 = 0x040c; + t.afii10062 = 0x040e; + t.afii10063 = 0xf6c4; + t.afii10064 = 0xf6c5; + t.afii10065 = 0x0430; + t.afii10066 = 0x0431; + t.afii10067 = 0x0432; + t.afii10068 = 0x0433; + t.afii10069 = 0x0434; + t.afii10070 = 0x0435; + t.afii10071 = 0x0451; + t.afii10072 = 0x0436; + t.afii10073 = 0x0437; + t.afii10074 = 0x0438; + t.afii10075 = 0x0439; + t.afii10076 = 0x043a; + t.afii10077 = 0x043b; + t.afii10078 = 0x043c; + t.afii10079 = 0x043d; + t.afii10080 = 0x043e; + t.afii10081 = 0x043f; + t.afii10082 = 0x0440; + t.afii10083 = 0x0441; + t.afii10084 = 0x0442; + t.afii10085 = 0x0443; + t.afii10086 = 0x0444; + t.afii10087 = 0x0445; + t.afii10088 = 0x0446; + t.afii10089 = 0x0447; + t.afii10090 = 0x0448; + t.afii10091 = 0x0449; + t.afii10092 = 0x044a; + t.afii10093 = 0x044b; + t.afii10094 = 0x044c; + t.afii10095 = 0x044d; + t.afii10096 = 0x044e; + t.afii10097 = 0x044f; + t.afii10098 = 0x0491; + t.afii10099 = 0x0452; + t.afii10100 = 0x0453; + t.afii10101 = 0x0454; + t.afii10102 = 0x0455; + t.afii10103 = 0x0456; + t.afii10104 = 0x0457; + t.afii10105 = 0x0458; + t.afii10106 = 0x0459; + t.afii10107 = 0x045a; + t.afii10108 = 0x045b; + t.afii10109 = 0x045c; + t.afii10110 = 0x045e; + t.afii10145 = 0x040f; + t.afii10146 = 0x0462; + t.afii10147 = 0x0472; + t.afii10148 = 0x0474; + t.afii10192 = 0xf6c6; + t.afii10193 = 0x045f; + t.afii10194 = 0x0463; + t.afii10195 = 0x0473; + t.afii10196 = 0x0475; + t.afii10831 = 0xf6c7; + t.afii10832 = 0xf6c8; + t.afii10846 = 0x04d9; + t.afii299 = 0x200e; + t.afii300 = 0x200f; + t.afii301 = 0x200d; + t.afii57381 = 0x066a; + t.afii57388 = 0x060c; + t.afii57392 = 0x0660; + t.afii57393 = 0x0661; + t.afii57394 = 0x0662; + t.afii57395 = 0x0663; + t.afii57396 = 0x0664; + t.afii57397 = 0x0665; + t.afii57398 = 0x0666; + t.afii57399 = 0x0667; + t.afii57400 = 0x0668; + t.afii57401 = 0x0669; + t.afii57403 = 0x061b; + t.afii57407 = 0x061f; + t.afii57409 = 0x0621; + t.afii57410 = 0x0622; + t.afii57411 = 0x0623; + t.afii57412 = 0x0624; + t.afii57413 = 0x0625; + t.afii57414 = 0x0626; + t.afii57415 = 0x0627; + t.afii57416 = 0x0628; + t.afii57417 = 0x0629; + t.afii57418 = 0x062a; + t.afii57419 = 0x062b; + t.afii57420 = 0x062c; + t.afii57421 = 0x062d; + t.afii57422 = 0x062e; + t.afii57423 = 0x062f; + t.afii57424 = 0x0630; + t.afii57425 = 0x0631; + t.afii57426 = 0x0632; + t.afii57427 = 0x0633; + t.afii57428 = 0x0634; + t.afii57429 = 0x0635; + t.afii57430 = 0x0636; + t.afii57431 = 0x0637; + t.afii57432 = 0x0638; + t.afii57433 = 0x0639; + t.afii57434 = 0x063a; + t.afii57440 = 0x0640; + t.afii57441 = 0x0641; + t.afii57442 = 0x0642; + t.afii57443 = 0x0643; + t.afii57444 = 0x0644; + t.afii57445 = 0x0645; + t.afii57446 = 0x0646; + t.afii57448 = 0x0648; + t.afii57449 = 0x0649; + t.afii57450 = 0x064a; + t.afii57451 = 0x064b; + t.afii57452 = 0x064c; + t.afii57453 = 0x064d; + t.afii57454 = 0x064e; + t.afii57455 = 0x064f; + t.afii57456 = 0x0650; + t.afii57457 = 0x0651; + t.afii57458 = 0x0652; + t.afii57470 = 0x0647; + t.afii57505 = 0x06a4; + t.afii57506 = 0x067e; + t.afii57507 = 0x0686; + t.afii57508 = 0x0698; + t.afii57509 = 0x06af; + t.afii57511 = 0x0679; + t.afii57512 = 0x0688; + t.afii57513 = 0x0691; + t.afii57514 = 0x06ba; + t.afii57519 = 0x06d2; + t.afii57534 = 0x06d5; + t.afii57636 = 0x20aa; + t.afii57645 = 0x05be; + t.afii57658 = 0x05c3; + t.afii57664 = 0x05d0; + t.afii57665 = 0x05d1; + t.afii57666 = 0x05d2; + t.afii57667 = 0x05d3; + t.afii57668 = 0x05d4; + t.afii57669 = 0x05d5; + t.afii57670 = 0x05d6; + t.afii57671 = 0x05d7; + t.afii57672 = 0x05d8; + t.afii57673 = 0x05d9; + t.afii57674 = 0x05da; + t.afii57675 = 0x05db; + t.afii57676 = 0x05dc; + t.afii57677 = 0x05dd; + t.afii57678 = 0x05de; + t.afii57679 = 0x05df; + t.afii57680 = 0x05e0; + t.afii57681 = 0x05e1; + t.afii57682 = 0x05e2; + t.afii57683 = 0x05e3; + t.afii57684 = 0x05e4; + t.afii57685 = 0x05e5; + t.afii57686 = 0x05e6; + t.afii57687 = 0x05e7; + t.afii57688 = 0x05e8; + t.afii57689 = 0x05e9; + t.afii57690 = 0x05ea; + t.afii57694 = 0xfb2a; + t.afii57695 = 0xfb2b; + t.afii57700 = 0xfb4b; + t.afii57705 = 0xfb1f; + t.afii57716 = 0x05f0; + t.afii57717 = 0x05f1; + t.afii57718 = 0x05f2; + t.afii57723 = 0xfb35; + t.afii57793 = 0x05b4; + t.afii57794 = 0x05b5; + t.afii57795 = 0x05b6; + t.afii57796 = 0x05bb; + t.afii57797 = 0x05b8; + t.afii57798 = 0x05b7; + t.afii57799 = 0x05b0; + t.afii57800 = 0x05b2; + t.afii57801 = 0x05b1; + t.afii57802 = 0x05b3; + t.afii57803 = 0x05c2; + t.afii57804 = 0x05c1; + t.afii57806 = 0x05b9; + t.afii57807 = 0x05bc; + t.afii57839 = 0x05bd; + t.afii57841 = 0x05bf; + t.afii57842 = 0x05c0; + t.afii57929 = 0x02bc; + t.afii61248 = 0x2105; + t.afii61289 = 0x2113; + t.afii61352 = 0x2116; + t.afii61573 = 0x202c; + t.afii61574 = 0x202d; + t.afii61575 = 0x202e; + t.afii61664 = 0x200c; + t.afii63167 = 0x066d; + t.afii64937 = 0x02bd; + t.agrave = 0x00e0; + t.agujarati = 0x0a85; + t.agurmukhi = 0x0a05; + t.ahiragana = 0x3042; + t.ahookabove = 0x1ea3; + t.aibengali = 0x0990; + t.aibopomofo = 0x311e; + t.aideva = 0x0910; + t.aiecyrillic = 0x04d5; + t.aigujarati = 0x0a90; + t.aigurmukhi = 0x0a10; + t.aimatragurmukhi = 0x0a48; + t.ainarabic = 0x0639; + t.ainfinalarabic = 0xfeca; + t.aininitialarabic = 0xfecb; + t.ainmedialarabic = 0xfecc; + t.ainvertedbreve = 0x0203; + t.aivowelsignbengali = 0x09c8; + t.aivowelsigndeva = 0x0948; + t.aivowelsigngujarati = 0x0ac8; + t.akatakana = 0x30a2; + t.akatakanahalfwidth = 0xff71; + t.akorean = 0x314f; + t.alef = 0x05d0; + t.alefarabic = 0x0627; + t.alefdageshhebrew = 0xfb30; + t.aleffinalarabic = 0xfe8e; + t.alefhamzaabovearabic = 0x0623; + t.alefhamzaabovefinalarabic = 0xfe84; + t.alefhamzabelowarabic = 0x0625; + t.alefhamzabelowfinalarabic = 0xfe88; + t.alefhebrew = 0x05d0; + t.aleflamedhebrew = 0xfb4f; + t.alefmaddaabovearabic = 0x0622; + t.alefmaddaabovefinalarabic = 0xfe82; + t.alefmaksuraarabic = 0x0649; + t.alefmaksurafinalarabic = 0xfef0; + t.alefmaksurainitialarabic = 0xfef3; + t.alefmaksuramedialarabic = 0xfef4; + t.alefpatahhebrew = 0xfb2e; + t.alefqamatshebrew = 0xfb2f; + t.aleph = 0x2135; + t.allequal = 0x224c; + t.alpha = 0x03b1; + t.alphatonos = 0x03ac; + t.amacron = 0x0101; + t.amonospace = 0xff41; + t.ampersand = 0x0026; + t.ampersandmonospace = 0xff06; + t.ampersandsmall = 0xf726; + t.amsquare = 0x33c2; + t.anbopomofo = 0x3122; + t.angbopomofo = 0x3124; + t.angbracketleft = 0x3008; // Glyph is missing from Adobe's original list. + t.angbracketright = 0x3009; // Glyph is missing from Adobe's original list. + t.angkhankhuthai = 0x0e5a; + t.angle = 0x2220; + t.anglebracketleft = 0x3008; + t.anglebracketleftvertical = 0xfe3f; + t.anglebracketright = 0x3009; + t.anglebracketrightvertical = 0xfe40; + t.angleleft = 0x2329; + t.angleright = 0x232a; + t.angstrom = 0x212b; + t.anoteleia = 0x0387; + t.anudattadeva = 0x0952; + t.anusvarabengali = 0x0982; + t.anusvaradeva = 0x0902; + t.anusvaragujarati = 0x0a82; + t.aogonek = 0x0105; + t.apaatosquare = 0x3300; + t.aparen = 0x249c; + t.apostrophearmenian = 0x055a; + t.apostrophemod = 0x02bc; + t.apple = 0xf8ff; + t.approaches = 0x2250; + t.approxequal = 0x2248; + t.approxequalorimage = 0x2252; + t.approximatelyequal = 0x2245; + t.araeaekorean = 0x318e; + t.araeakorean = 0x318d; + t.arc = 0x2312; + t.arighthalfring = 0x1e9a; + t.aring = 0x00e5; + t.aringacute = 0x01fb; + t.aringbelow = 0x1e01; + t.arrowboth = 0x2194; + t.arrowdashdown = 0x21e3; + t.arrowdashleft = 0x21e0; + t.arrowdashright = 0x21e2; + t.arrowdashup = 0x21e1; + t.arrowdblboth = 0x21d4; + t.arrowdbldown = 0x21d3; + t.arrowdblleft = 0x21d0; + t.arrowdblright = 0x21d2; + t.arrowdblup = 0x21d1; + t.arrowdown = 0x2193; + t.arrowdownleft = 0x2199; + t.arrowdownright = 0x2198; + t.arrowdownwhite = 0x21e9; + t.arrowheaddownmod = 0x02c5; + t.arrowheadleftmod = 0x02c2; + t.arrowheadrightmod = 0x02c3; + t.arrowheadupmod = 0x02c4; + t.arrowhorizex = 0xf8e7; + t.arrowleft = 0x2190; + t.arrowleftdbl = 0x21d0; + t.arrowleftdblstroke = 0x21cd; + t.arrowleftoverright = 0x21c6; + t.arrowleftwhite = 0x21e6; + t.arrowright = 0x2192; + t.arrowrightdblstroke = 0x21cf; + t.arrowrightheavy = 0x279e; + t.arrowrightoverleft = 0x21c4; + t.arrowrightwhite = 0x21e8; + t.arrowtableft = 0x21e4; + t.arrowtabright = 0x21e5; + t.arrowup = 0x2191; + t.arrowupdn = 0x2195; + t.arrowupdnbse = 0x21a8; + t.arrowupdownbase = 0x21a8; + t.arrowupleft = 0x2196; + t.arrowupleftofdown = 0x21c5; + t.arrowupright = 0x2197; + t.arrowupwhite = 0x21e7; + t.arrowvertex = 0xf8e6; + t.asciicircum = 0x005e; + t.asciicircummonospace = 0xff3e; + t.asciitilde = 0x007e; + t.asciitildemonospace = 0xff5e; + t.ascript = 0x0251; + t.ascriptturned = 0x0252; + t.asmallhiragana = 0x3041; + t.asmallkatakana = 0x30a1; + t.asmallkatakanahalfwidth = 0xff67; + t.asterisk = 0x002a; + t.asteriskaltonearabic = 0x066d; + t.asteriskarabic = 0x066d; + t.asteriskmath = 0x2217; + t.asteriskmonospace = 0xff0a; + t.asterisksmall = 0xfe61; + t.asterism = 0x2042; + t.asuperior = 0xf6e9; + t.asymptoticallyequal = 0x2243; + t.at = 0x0040; + t.atilde = 0x00e3; + t.atmonospace = 0xff20; + t.atsmall = 0xfe6b; + t.aturned = 0x0250; + t.aubengali = 0x0994; + t.aubopomofo = 0x3120; + t.audeva = 0x0914; + t.augujarati = 0x0a94; + t.augurmukhi = 0x0a14; + t.aulengthmarkbengali = 0x09d7; + t.aumatragurmukhi = 0x0a4c; + t.auvowelsignbengali = 0x09cc; + t.auvowelsigndeva = 0x094c; + t.auvowelsigngujarati = 0x0acc; + t.avagrahadeva = 0x093d; + t.aybarmenian = 0x0561; + t.ayin = 0x05e2; + t.ayinaltonehebrew = 0xfb20; + t.ayinhebrew = 0x05e2; + t.b = 0x0062; + t.babengali = 0x09ac; + t.backslash = 0x005c; + t.backslashmonospace = 0xff3c; + t.badeva = 0x092c; + t.bagujarati = 0x0aac; + t.bagurmukhi = 0x0a2c; + t.bahiragana = 0x3070; + t.bahtthai = 0x0e3f; + t.bakatakana = 0x30d0; + t.bar = 0x007c; + t.barmonospace = 0xff5c; + t.bbopomofo = 0x3105; + t.bcircle = 0x24d1; + t.bdotaccent = 0x1e03; + t.bdotbelow = 0x1e05; + t.beamedsixteenthnotes = 0x266c; + t.because = 0x2235; + t.becyrillic = 0x0431; + t.beharabic = 0x0628; + t.behfinalarabic = 0xfe90; + t.behinitialarabic = 0xfe91; + t.behiragana = 0x3079; + t.behmedialarabic = 0xfe92; + t.behmeeminitialarabic = 0xfc9f; + t.behmeemisolatedarabic = 0xfc08; + t.behnoonfinalarabic = 0xfc6d; + t.bekatakana = 0x30d9; + t.benarmenian = 0x0562; + t.bet = 0x05d1; + t.beta = 0x03b2; + t.betasymbolgreek = 0x03d0; + t.betdagesh = 0xfb31; + t.betdageshhebrew = 0xfb31; + t.bethebrew = 0x05d1; + t.betrafehebrew = 0xfb4c; + t.bhabengali = 0x09ad; + t.bhadeva = 0x092d; + t.bhagujarati = 0x0aad; + t.bhagurmukhi = 0x0a2d; + t.bhook = 0x0253; + t.bihiragana = 0x3073; + t.bikatakana = 0x30d3; + t.bilabialclick = 0x0298; + t.bindigurmukhi = 0x0a02; + t.birusquare = 0x3331; + t.blackcircle = 0x25cf; + t.blackdiamond = 0x25c6; + t.blackdownpointingtriangle = 0x25bc; + t.blackleftpointingpointer = 0x25c4; + t.blackleftpointingtriangle = 0x25c0; + t.blacklenticularbracketleft = 0x3010; + t.blacklenticularbracketleftvertical = 0xfe3b; + t.blacklenticularbracketright = 0x3011; + t.blacklenticularbracketrightvertical = 0xfe3c; + t.blacklowerlefttriangle = 0x25e3; + t.blacklowerrighttriangle = 0x25e2; + t.blackrectangle = 0x25ac; + t.blackrightpointingpointer = 0x25ba; + t.blackrightpointingtriangle = 0x25b6; + t.blacksmallsquare = 0x25aa; + t.blacksmilingface = 0x263b; + t.blacksquare = 0x25a0; + t.blackstar = 0x2605; + t.blackupperlefttriangle = 0x25e4; + t.blackupperrighttriangle = 0x25e5; + t.blackuppointingsmalltriangle = 0x25b4; + t.blackuppointingtriangle = 0x25b2; + t.blank = 0x2423; + t.blinebelow = 0x1e07; + t.block = 0x2588; + t.bmonospace = 0xff42; + t.bobaimaithai = 0x0e1a; + t.bohiragana = 0x307c; + t.bokatakana = 0x30dc; + t.bparen = 0x249d; + t.bqsquare = 0x33c3; + t.braceex = 0xf8f4; + t.braceleft = 0x007b; + t.braceleftbt = 0xf8f3; + t.braceleftmid = 0xf8f2; + t.braceleftmonospace = 0xff5b; + t.braceleftsmall = 0xfe5b; + t.bracelefttp = 0xf8f1; + t.braceleftvertical = 0xfe37; + t.braceright = 0x007d; + t.bracerightbt = 0xf8fe; + t.bracerightmid = 0xf8fd; + t.bracerightmonospace = 0xff5d; + t.bracerightsmall = 0xfe5c; + t.bracerighttp = 0xf8fc; + t.bracerightvertical = 0xfe38; + t.bracketleft = 0x005b; + t.bracketleftbt = 0xf8f0; + t.bracketleftex = 0xf8ef; + t.bracketleftmonospace = 0xff3b; + t.bracketlefttp = 0xf8ee; + t.bracketright = 0x005d; + t.bracketrightbt = 0xf8fb; + t.bracketrightex = 0xf8fa; + t.bracketrightmonospace = 0xff3d; + t.bracketrighttp = 0xf8f9; + t.breve = 0x02d8; + t.brevebelowcmb = 0x032e; + t.brevecmb = 0x0306; + t.breveinvertedbelowcmb = 0x032f; + t.breveinvertedcmb = 0x0311; + t.breveinverteddoublecmb = 0x0361; + t.bridgebelowcmb = 0x032a; + t.bridgeinvertedbelowcmb = 0x033a; + t.brokenbar = 0x00a6; + t.bstroke = 0x0180; + t.bsuperior = 0xf6ea; + t.btopbar = 0x0183; + t.buhiragana = 0x3076; + t.bukatakana = 0x30d6; + t.bullet = 0x2022; + t.bulletinverse = 0x25d8; + t.bulletoperator = 0x2219; + t.bullseye = 0x25ce; + t.c = 0x0063; + t.caarmenian = 0x056e; + t.cabengali = 0x099a; + t.cacute = 0x0107; + t.cadeva = 0x091a; + t.cagujarati = 0x0a9a; + t.cagurmukhi = 0x0a1a; + t.calsquare = 0x3388; + t.candrabindubengali = 0x0981; + t.candrabinducmb = 0x0310; + t.candrabindudeva = 0x0901; + t.candrabindugujarati = 0x0a81; + t.capslock = 0x21ea; + t.careof = 0x2105; + t.caron = 0x02c7; + t.caronbelowcmb = 0x032c; + t.caroncmb = 0x030c; + t.carriagereturn = 0x21b5; + t.cbopomofo = 0x3118; + t.ccaron = 0x010d; + t.ccedilla = 0x00e7; + t.ccedillaacute = 0x1e09; + t.ccircle = 0x24d2; + t.ccircumflex = 0x0109; + t.ccurl = 0x0255; + t.cdot = 0x010b; + t.cdotaccent = 0x010b; + t.cdsquare = 0x33c5; + t.cedilla = 0x00b8; + t.cedillacmb = 0x0327; + t.cent = 0x00a2; + t.centigrade = 0x2103; + t.centinferior = 0xf6df; + t.centmonospace = 0xffe0; + t.centoldstyle = 0xf7a2; + t.centsuperior = 0xf6e0; + t.chaarmenian = 0x0579; + t.chabengali = 0x099b; + t.chadeva = 0x091b; + t.chagujarati = 0x0a9b; + t.chagurmukhi = 0x0a1b; + t.chbopomofo = 0x3114; + t.cheabkhasiancyrillic = 0x04bd; + t.checkmark = 0x2713; + t.checyrillic = 0x0447; + t.chedescenderabkhasiancyrillic = 0x04bf; + t.chedescendercyrillic = 0x04b7; + t.chedieresiscyrillic = 0x04f5; + t.cheharmenian = 0x0573; + t.chekhakassiancyrillic = 0x04cc; + t.cheverticalstrokecyrillic = 0x04b9; + t.chi = 0x03c7; + t.chieuchacirclekorean = 0x3277; + t.chieuchaparenkorean = 0x3217; + t.chieuchcirclekorean = 0x3269; + t.chieuchkorean = 0x314a; + t.chieuchparenkorean = 0x3209; + t.chochangthai = 0x0e0a; + t.chochanthai = 0x0e08; + t.chochingthai = 0x0e09; + t.chochoethai = 0x0e0c; + t.chook = 0x0188; + t.cieucacirclekorean = 0x3276; + t.cieucaparenkorean = 0x3216; + t.cieuccirclekorean = 0x3268; + t.cieuckorean = 0x3148; + t.cieucparenkorean = 0x3208; + t.cieucuparenkorean = 0x321c; + t.circle = 0x25cb; + t.circlecopyrt = 0x00a9; // Glyph is missing from Adobe's original list. + t.circlemultiply = 0x2297; + t.circleot = 0x2299; + t.circleplus = 0x2295; + t.circlepostalmark = 0x3036; + t.circlewithlefthalfblack = 0x25d0; + t.circlewithrighthalfblack = 0x25d1; + t.circumflex = 0x02c6; + t.circumflexbelowcmb = 0x032d; + t.circumflexcmb = 0x0302; + t.clear = 0x2327; + t.clickalveolar = 0x01c2; + t.clickdental = 0x01c0; + t.clicklateral = 0x01c1; + t.clickretroflex = 0x01c3; + t.club = 0x2663; + t.clubsuitblack = 0x2663; + t.clubsuitwhite = 0x2667; + t.cmcubedsquare = 0x33a4; + t.cmonospace = 0xff43; + t.cmsquaredsquare = 0x33a0; + t.coarmenian = 0x0581; + t.colon = 0x003a; + t.colonmonetary = 0x20a1; + t.colonmonospace = 0xff1a; + t.colonsign = 0x20a1; + t.colonsmall = 0xfe55; + t.colontriangularhalfmod = 0x02d1; + t.colontriangularmod = 0x02d0; + t.comma = 0x002c; + t.commaabovecmb = 0x0313; + t.commaaboverightcmb = 0x0315; + t.commaaccent = 0xf6c3; + t.commaarabic = 0x060c; + t.commaarmenian = 0x055d; + t.commainferior = 0xf6e1; + t.commamonospace = 0xff0c; + t.commareversedabovecmb = 0x0314; + t.commareversedmod = 0x02bd; + t.commasmall = 0xfe50; + t.commasuperior = 0xf6e2; + t.commaturnedabovecmb = 0x0312; + t.commaturnedmod = 0x02bb; + t.compass = 0x263c; + t.congruent = 0x2245; + t.contourintegral = 0x222e; + t.control = 0x2303; + t.controlACK = 0x0006; + t.controlBEL = 0x0007; + t.controlBS = 0x0008; + t.controlCAN = 0x0018; + t.controlCR = 0x000d; + t.controlDC1 = 0x0011; + t.controlDC2 = 0x0012; + t.controlDC3 = 0x0013; + t.controlDC4 = 0x0014; + t.controlDEL = 0x007f; + t.controlDLE = 0x0010; + t.controlEM = 0x0019; + t.controlENQ = 0x0005; + t.controlEOT = 0x0004; + t.controlESC = 0x001b; + t.controlETB = 0x0017; + t.controlETX = 0x0003; + t.controlFF = 0x000c; + t.controlFS = 0x001c; + t.controlGS = 0x001d; + t.controlHT = 0x0009; + t.controlLF = 0x000a; + t.controlNAK = 0x0015; + t.controlNULL = 0x0000; // Glyph is missing from Adobe's original list. + t.controlRS = 0x001e; + t.controlSI = 0x000f; + t.controlSO = 0x000e; + t.controlSOT = 0x0002; + t.controlSTX = 0x0001; + t.controlSUB = 0x001a; + t.controlSYN = 0x0016; + t.controlUS = 0x001f; + t.controlVT = 0x000b; + t.copyright = 0x00a9; + t.copyrightsans = 0xf8e9; + t.copyrightserif = 0xf6d9; + t.cornerbracketleft = 0x300c; + t.cornerbracketlefthalfwidth = 0xff62; + t.cornerbracketleftvertical = 0xfe41; + t.cornerbracketright = 0x300d; + t.cornerbracketrighthalfwidth = 0xff63; + t.cornerbracketrightvertical = 0xfe42; + t.corporationsquare = 0x337f; + t.cosquare = 0x33c7; + t.coverkgsquare = 0x33c6; + t.cparen = 0x249e; + t.cruzeiro = 0x20a2; + t.cstretched = 0x0297; + t.curlyand = 0x22cf; + t.curlyor = 0x22ce; + t.currency = 0x00a4; + t.cyrBreve = 0xf6d1; + t.cyrFlex = 0xf6d2; + t.cyrbreve = 0xf6d4; + t.cyrflex = 0xf6d5; + t.d = 0x0064; + t.daarmenian = 0x0564; + t.dabengali = 0x09a6; + t.dadarabic = 0x0636; + t.dadeva = 0x0926; + t.dadfinalarabic = 0xfebe; + t.dadinitialarabic = 0xfebf; + t.dadmedialarabic = 0xfec0; + t.dagesh = 0x05bc; + t.dageshhebrew = 0x05bc; + t.dagger = 0x2020; + t.daggerdbl = 0x2021; + t.dagujarati = 0x0aa6; + t.dagurmukhi = 0x0a26; + t.dahiragana = 0x3060; + t.dakatakana = 0x30c0; + t.dalarabic = 0x062f; + t.dalet = 0x05d3; + t.daletdagesh = 0xfb33; + t.daletdageshhebrew = 0xfb33; + t.dalethebrew = 0x05d3; + t.dalfinalarabic = 0xfeaa; + t.dammaarabic = 0x064f; + t.dammalowarabic = 0x064f; + t.dammatanaltonearabic = 0x064c; + t.dammatanarabic = 0x064c; + t.danda = 0x0964; + t.dargahebrew = 0x05a7; + t.dargalefthebrew = 0x05a7; + t.dasiapneumatacyrilliccmb = 0x0485; + t.dblGrave = 0xf6d3; + t.dblanglebracketleft = 0x300a; + t.dblanglebracketleftvertical = 0xfe3d; + t.dblanglebracketright = 0x300b; + t.dblanglebracketrightvertical = 0xfe3e; + t.dblarchinvertedbelowcmb = 0x032b; + t.dblarrowleft = 0x21d4; + t.dblarrowright = 0x21d2; + t.dbldanda = 0x0965; + t.dblgrave = 0xf6d6; + t.dblgravecmb = 0x030f; + t.dblintegral = 0x222c; + t.dbllowline = 0x2017; + t.dbllowlinecmb = 0x0333; + t.dbloverlinecmb = 0x033f; + t.dblprimemod = 0x02ba; + t.dblverticalbar = 0x2016; + t.dblverticallineabovecmb = 0x030e; + t.dbopomofo = 0x3109; + t.dbsquare = 0x33c8; + t.dcaron = 0x010f; + t.dcedilla = 0x1e11; + t.dcircle = 0x24d3; + t.dcircumflexbelow = 0x1e13; + t.dcroat = 0x0111; + t.ddabengali = 0x09a1; + t.ddadeva = 0x0921; + t.ddagujarati = 0x0aa1; + t.ddagurmukhi = 0x0a21; + t.ddalarabic = 0x0688; + t.ddalfinalarabic = 0xfb89; + t.dddhadeva = 0x095c; + t.ddhabengali = 0x09a2; + t.ddhadeva = 0x0922; + t.ddhagujarati = 0x0aa2; + t.ddhagurmukhi = 0x0a22; + t.ddotaccent = 0x1e0b; + t.ddotbelow = 0x1e0d; + t.decimalseparatorarabic = 0x066b; + t.decimalseparatorpersian = 0x066b; + t.decyrillic = 0x0434; + t.degree = 0x00b0; + t.dehihebrew = 0x05ad; + t.dehiragana = 0x3067; + t.deicoptic = 0x03ef; + t.dekatakana = 0x30c7; + t.deleteleft = 0x232b; + t.deleteright = 0x2326; + t.delta = 0x03b4; + t.deltaturned = 0x018d; + t.denominatorminusonenumeratorbengali = 0x09f8; + t.dezh = 0x02a4; + t.dhabengali = 0x09a7; + t.dhadeva = 0x0927; + t.dhagujarati = 0x0aa7; + t.dhagurmukhi = 0x0a27; + t.dhook = 0x0257; + t.dialytikatonos = 0x0385; + t.dialytikatonoscmb = 0x0344; + t.diamond = 0x2666; + t.diamondsuitwhite = 0x2662; + t.dieresis = 0x00a8; + t.dieresisacute = 0xf6d7; + t.dieresisbelowcmb = 0x0324; + t.dieresiscmb = 0x0308; + t.dieresisgrave = 0xf6d8; + t.dieresistonos = 0x0385; + t.dihiragana = 0x3062; + t.dikatakana = 0x30c2; + t.dittomark = 0x3003; + t.divide = 0x00f7; + t.divides = 0x2223; + t.divisionslash = 0x2215; + t.djecyrillic = 0x0452; + t.dkshade = 0x2593; + t.dlinebelow = 0x1e0f; + t.dlsquare = 0x3397; + t.dmacron = 0x0111; + t.dmonospace = 0xff44; + t.dnblock = 0x2584; + t.dochadathai = 0x0e0e; + t.dodekthai = 0x0e14; + t.dohiragana = 0x3069; + t.dokatakana = 0x30c9; + t.dollar = 0x0024; + t.dollarinferior = 0xf6e3; + t.dollarmonospace = 0xff04; + t.dollaroldstyle = 0xf724; + t.dollarsmall = 0xfe69; + t.dollarsuperior = 0xf6e4; + t.dong = 0x20ab; + t.dorusquare = 0x3326; + t.dotaccent = 0x02d9; + t.dotaccentcmb = 0x0307; + t.dotbelowcmb = 0x0323; + t.dotbelowcomb = 0x0323; + t.dotkatakana = 0x30fb; + t.dotlessi = 0x0131; + t.dotlessj = 0xf6be; + t.dotlessjstrokehook = 0x0284; + t.dotmath = 0x22c5; + t.dottedcircle = 0x25cc; + t.doubleyodpatah = 0xfb1f; + t.doubleyodpatahhebrew = 0xfb1f; + t.downtackbelowcmb = 0x031e; + t.downtackmod = 0x02d5; + t.dparen = 0x249f; + t.dsuperior = 0xf6eb; + t.dtail = 0x0256; + t.dtopbar = 0x018c; + t.duhiragana = 0x3065; + t.dukatakana = 0x30c5; + t.dz = 0x01f3; + t.dzaltone = 0x02a3; + t.dzcaron = 0x01c6; + t.dzcurl = 0x02a5; + t.dzeabkhasiancyrillic = 0x04e1; + t.dzecyrillic = 0x0455; + t.dzhecyrillic = 0x045f; + t.e = 0x0065; + t.eacute = 0x00e9; + t.earth = 0x2641; + t.ebengali = 0x098f; + t.ebopomofo = 0x311c; + t.ebreve = 0x0115; + t.ecandradeva = 0x090d; + t.ecandragujarati = 0x0a8d; + t.ecandravowelsigndeva = 0x0945; + t.ecandravowelsigngujarati = 0x0ac5; + t.ecaron = 0x011b; + t.ecedillabreve = 0x1e1d; + t.echarmenian = 0x0565; + t.echyiwnarmenian = 0x0587; + t.ecircle = 0x24d4; + t.ecircumflex = 0x00ea; + t.ecircumflexacute = 0x1ebf; + t.ecircumflexbelow = 0x1e19; + t.ecircumflexdotbelow = 0x1ec7; + t.ecircumflexgrave = 0x1ec1; + t.ecircumflexhookabove = 0x1ec3; + t.ecircumflextilde = 0x1ec5; + t.ecyrillic = 0x0454; + t.edblgrave = 0x0205; + t.edeva = 0x090f; + t.edieresis = 0x00eb; + t.edot = 0x0117; + t.edotaccent = 0x0117; + t.edotbelow = 0x1eb9; + t.eegurmukhi = 0x0a0f; + t.eematragurmukhi = 0x0a47; + t.efcyrillic = 0x0444; + t.egrave = 0x00e8; + t.egujarati = 0x0a8f; + t.eharmenian = 0x0567; + t.ehbopomofo = 0x311d; + t.ehiragana = 0x3048; + t.ehookabove = 0x1ebb; + t.eibopomofo = 0x311f; + t.eight = 0x0038; + t.eightarabic = 0x0668; + t.eightbengali = 0x09ee; + t.eightcircle = 0x2467; + t.eightcircleinversesansserif = 0x2791; + t.eightdeva = 0x096e; + t.eighteencircle = 0x2471; + t.eighteenparen = 0x2485; + t.eighteenperiod = 0x2499; + t.eightgujarati = 0x0aee; + t.eightgurmukhi = 0x0a6e; + t.eighthackarabic = 0x0668; + t.eighthangzhou = 0x3028; + t.eighthnotebeamed = 0x266b; + t.eightideographicparen = 0x3227; + t.eightinferior = 0x2088; + t.eightmonospace = 0xff18; + t.eightoldstyle = 0xf738; + t.eightparen = 0x247b; + t.eightperiod = 0x248f; + t.eightpersian = 0x06f8; + t.eightroman = 0x2177; + t.eightsuperior = 0x2078; + t.eightthai = 0x0e58; + t.einvertedbreve = 0x0207; + t.eiotifiedcyrillic = 0x0465; + t.ekatakana = 0x30a8; + t.ekatakanahalfwidth = 0xff74; + t.ekonkargurmukhi = 0x0a74; + t.ekorean = 0x3154; + t.elcyrillic = 0x043b; + t.element = 0x2208; + t.elevencircle = 0x246a; + t.elevenparen = 0x247e; + t.elevenperiod = 0x2492; + t.elevenroman = 0x217a; + t.ellipsis = 0x2026; + t.ellipsisvertical = 0x22ee; + t.emacron = 0x0113; + t.emacronacute = 0x1e17; + t.emacrongrave = 0x1e15; + t.emcyrillic = 0x043c; + t.emdash = 0x2014; + t.emdashvertical = 0xfe31; + t.emonospace = 0xff45; + t.emphasismarkarmenian = 0x055b; + t.emptyset = 0x2205; + t.enbopomofo = 0x3123; + t.encyrillic = 0x043d; + t.endash = 0x2013; + t.endashvertical = 0xfe32; + t.endescendercyrillic = 0x04a3; + t.eng = 0x014b; + t.engbopomofo = 0x3125; + t.enghecyrillic = 0x04a5; + t.enhookcyrillic = 0x04c8; + t.enspace = 0x2002; + t.eogonek = 0x0119; + t.eokorean = 0x3153; + t.eopen = 0x025b; + t.eopenclosed = 0x029a; + t.eopenreversed = 0x025c; + t.eopenreversedclosed = 0x025e; + t.eopenreversedhook = 0x025d; + t.eparen = 0x24a0; + t.epsilon = 0x03b5; + t.epsilontonos = 0x03ad; + t.equal = 0x003d; + t.equalmonospace = 0xff1d; + t.equalsmall = 0xfe66; + t.equalsuperior = 0x207c; + t.equivalence = 0x2261; + t.erbopomofo = 0x3126; + t.ercyrillic = 0x0440; + t.ereversed = 0x0258; + t.ereversedcyrillic = 0x044d; + t.escyrillic = 0x0441; + t.esdescendercyrillic = 0x04ab; + t.esh = 0x0283; + t.eshcurl = 0x0286; + t.eshortdeva = 0x090e; + t.eshortvowelsigndeva = 0x0946; + t.eshreversedloop = 0x01aa; + t.eshsquatreversed = 0x0285; + t.esmallhiragana = 0x3047; + t.esmallkatakana = 0x30a7; + t.esmallkatakanahalfwidth = 0xff6a; + t.estimated = 0x212e; + t.esuperior = 0xf6ec; + t.eta = 0x03b7; + t.etarmenian = 0x0568; + t.etatonos = 0x03ae; + t.eth = 0x00f0; + t.etilde = 0x1ebd; + t.etildebelow = 0x1e1b; + t.etnahtafoukhhebrew = 0x0591; + t.etnahtafoukhlefthebrew = 0x0591; + t.etnahtahebrew = 0x0591; + t.etnahtalefthebrew = 0x0591; + t.eturned = 0x01dd; + t.eukorean = 0x3161; + t.euro = 0x20ac; + t.evowelsignbengali = 0x09c7; + t.evowelsigndeva = 0x0947; + t.evowelsigngujarati = 0x0ac7; + t.exclam = 0x0021; + t.exclamarmenian = 0x055c; + t.exclamdbl = 0x203c; + t.exclamdown = 0x00a1; + t.exclamdownsmall = 0xf7a1; + t.exclammonospace = 0xff01; + t.exclamsmall = 0xf721; + t.existential = 0x2203; + t.ezh = 0x0292; + t.ezhcaron = 0x01ef; + t.ezhcurl = 0x0293; + t.ezhreversed = 0x01b9; + t.ezhtail = 0x01ba; + t.f = 0x0066; + t.fadeva = 0x095e; + t.fagurmukhi = 0x0a5e; + t.fahrenheit = 0x2109; + t.fathaarabic = 0x064e; + t.fathalowarabic = 0x064e; + t.fathatanarabic = 0x064b; + t.fbopomofo = 0x3108; + t.fcircle = 0x24d5; + t.fdotaccent = 0x1e1f; + t.feharabic = 0x0641; + t.feharmenian = 0x0586; + t.fehfinalarabic = 0xfed2; + t.fehinitialarabic = 0xfed3; + t.fehmedialarabic = 0xfed4; + t.feicoptic = 0x03e5; + t.female = 0x2640; + t.ff = 0xfb00; + t.f_f = 0xfb00; + t.ffi = 0xfb03; + t.f_f_i = 0xfb03; + t.ffl = 0xfb04; + t.f_f_l = 0xfb04; + t.fi = 0xfb01; + t.f_i = 0xfb01; + t.fifteencircle = 0x246e; + t.fifteenparen = 0x2482; + t.fifteenperiod = 0x2496; + t.figuredash = 0x2012; + t.filledbox = 0x25a0; + t.filledrect = 0x25ac; + t.finalkaf = 0x05da; + t.finalkafdagesh = 0xfb3a; + t.finalkafdageshhebrew = 0xfb3a; + t.finalkafhebrew = 0x05da; + t.finalmem = 0x05dd; + t.finalmemhebrew = 0x05dd; + t.finalnun = 0x05df; + t.finalnunhebrew = 0x05df; + t.finalpe = 0x05e3; + t.finalpehebrew = 0x05e3; + t.finaltsadi = 0x05e5; + t.finaltsadihebrew = 0x05e5; + t.firsttonechinese = 0x02c9; + t.fisheye = 0x25c9; + t.fitacyrillic = 0x0473; + t.five = 0x0035; + t.fivearabic = 0x0665; + t.fivebengali = 0x09eb; + t.fivecircle = 0x2464; + t.fivecircleinversesansserif = 0x278e; + t.fivedeva = 0x096b; + t.fiveeighths = 0x215d; + t.fivegujarati = 0x0aeb; + t.fivegurmukhi = 0x0a6b; + t.fivehackarabic = 0x0665; + t.fivehangzhou = 0x3025; + t.fiveideographicparen = 0x3224; + t.fiveinferior = 0x2085; + t.fivemonospace = 0xff15; + t.fiveoldstyle = 0xf735; + t.fiveparen = 0x2478; + t.fiveperiod = 0x248c; + t.fivepersian = 0x06f5; + t.fiveroman = 0x2174; + t.fivesuperior = 0x2075; + t.fivethai = 0x0e55; + t.fl = 0xfb02; + t.f_l = 0xfb02; + t.florin = 0x0192; + t.fmonospace = 0xff46; + t.fmsquare = 0x3399; + t.fofanthai = 0x0e1f; + t.fofathai = 0x0e1d; + t.fongmanthai = 0x0e4f; + t.forall = 0x2200; + t.four = 0x0034; + t.fourarabic = 0x0664; + t.fourbengali = 0x09ea; + t.fourcircle = 0x2463; + t.fourcircleinversesansserif = 0x278d; + t.fourdeva = 0x096a; + t.fourgujarati = 0x0aea; + t.fourgurmukhi = 0x0a6a; + t.fourhackarabic = 0x0664; + t.fourhangzhou = 0x3024; + t.fourideographicparen = 0x3223; + t.fourinferior = 0x2084; + t.fourmonospace = 0xff14; + t.fournumeratorbengali = 0x09f7; + t.fouroldstyle = 0xf734; + t.fourparen = 0x2477; + t.fourperiod = 0x248b; + t.fourpersian = 0x06f4; + t.fourroman = 0x2173; + t.foursuperior = 0x2074; + t.fourteencircle = 0x246d; + t.fourteenparen = 0x2481; + t.fourteenperiod = 0x2495; + t.fourthai = 0x0e54; + t.fourthtonechinese = 0x02cb; + t.fparen = 0x24a1; + t.fraction = 0x2044; + t.franc = 0x20a3; + t.g = 0x0067; + t.gabengali = 0x0997; + t.gacute = 0x01f5; + t.gadeva = 0x0917; + t.gafarabic = 0x06af; + t.gaffinalarabic = 0xfb93; + t.gafinitialarabic = 0xfb94; + t.gafmedialarabic = 0xfb95; + t.gagujarati = 0x0a97; + t.gagurmukhi = 0x0a17; + t.gahiragana = 0x304c; + t.gakatakana = 0x30ac; + t.gamma = 0x03b3; + t.gammalatinsmall = 0x0263; + t.gammasuperior = 0x02e0; + t.gangiacoptic = 0x03eb; + t.gbopomofo = 0x310d; + t.gbreve = 0x011f; + t.gcaron = 0x01e7; + t.gcedilla = 0x0123; + t.gcircle = 0x24d6; + t.gcircumflex = 0x011d; + t.gcommaaccent = 0x0123; + t.gdot = 0x0121; + t.gdotaccent = 0x0121; + t.gecyrillic = 0x0433; + t.gehiragana = 0x3052; + t.gekatakana = 0x30b2; + t.geometricallyequal = 0x2251; + t.gereshaccenthebrew = 0x059c; + t.gereshhebrew = 0x05f3; + t.gereshmuqdamhebrew = 0x059d; + t.germandbls = 0x00df; + t.gershayimaccenthebrew = 0x059e; + t.gershayimhebrew = 0x05f4; + t.getamark = 0x3013; + t.ghabengali = 0x0998; + t.ghadarmenian = 0x0572; + t.ghadeva = 0x0918; + t.ghagujarati = 0x0a98; + t.ghagurmukhi = 0x0a18; + t.ghainarabic = 0x063a; + t.ghainfinalarabic = 0xfece; + t.ghaininitialarabic = 0xfecf; + t.ghainmedialarabic = 0xfed0; + t.ghemiddlehookcyrillic = 0x0495; + t.ghestrokecyrillic = 0x0493; + t.gheupturncyrillic = 0x0491; + t.ghhadeva = 0x095a; + t.ghhagurmukhi = 0x0a5a; + t.ghook = 0x0260; + t.ghzsquare = 0x3393; + t.gihiragana = 0x304e; + t.gikatakana = 0x30ae; + t.gimarmenian = 0x0563; + t.gimel = 0x05d2; + t.gimeldagesh = 0xfb32; + t.gimeldageshhebrew = 0xfb32; + t.gimelhebrew = 0x05d2; + t.gjecyrillic = 0x0453; + t.glottalinvertedstroke = 0x01be; + t.glottalstop = 0x0294; + t.glottalstopinverted = 0x0296; + t.glottalstopmod = 0x02c0; + t.glottalstopreversed = 0x0295; + t.glottalstopreversedmod = 0x02c1; + t.glottalstopreversedsuperior = 0x02e4; + t.glottalstopstroke = 0x02a1; + t.glottalstopstrokereversed = 0x02a2; + t.gmacron = 0x1e21; + t.gmonospace = 0xff47; + t.gohiragana = 0x3054; + t.gokatakana = 0x30b4; + t.gparen = 0x24a2; + t.gpasquare = 0x33ac; + t.gradient = 0x2207; + t.grave = 0x0060; + t.gravebelowcmb = 0x0316; + t.gravecmb = 0x0300; + t.gravecomb = 0x0300; + t.gravedeva = 0x0953; + t.gravelowmod = 0x02ce; + t.gravemonospace = 0xff40; + t.gravetonecmb = 0x0340; + t.greater = 0x003e; + t.greaterequal = 0x2265; + t.greaterequalorless = 0x22db; + t.greatermonospace = 0xff1e; + t.greaterorequivalent = 0x2273; + t.greaterorless = 0x2277; + t.greateroverequal = 0x2267; + t.greatersmall = 0xfe65; + t.gscript = 0x0261; + t.gstroke = 0x01e5; + t.guhiragana = 0x3050; + t.guillemotleft = 0x00ab; + t.guillemotright = 0x00bb; + t.guilsinglleft = 0x2039; + t.guilsinglright = 0x203a; + t.gukatakana = 0x30b0; + t.guramusquare = 0x3318; + t.gysquare = 0x33c9; + t.h = 0x0068; + t.haabkhasiancyrillic = 0x04a9; + t.haaltonearabic = 0x06c1; + t.habengali = 0x09b9; + t.hadescendercyrillic = 0x04b3; + t.hadeva = 0x0939; + t.hagujarati = 0x0ab9; + t.hagurmukhi = 0x0a39; + t.haharabic = 0x062d; + t.hahfinalarabic = 0xfea2; + t.hahinitialarabic = 0xfea3; + t.hahiragana = 0x306f; + t.hahmedialarabic = 0xfea4; + t.haitusquare = 0x332a; + t.hakatakana = 0x30cf; + t.hakatakanahalfwidth = 0xff8a; + t.halantgurmukhi = 0x0a4d; + t.hamzaarabic = 0x0621; + t.hamzalowarabic = 0x0621; + t.hangulfiller = 0x3164; + t.hardsigncyrillic = 0x044a; + t.harpoonleftbarbup = 0x21bc; + t.harpoonrightbarbup = 0x21c0; + t.hasquare = 0x33ca; + t.hatafpatah = 0x05b2; + t.hatafpatah16 = 0x05b2; + t.hatafpatah23 = 0x05b2; + t.hatafpatah2f = 0x05b2; + t.hatafpatahhebrew = 0x05b2; + t.hatafpatahnarrowhebrew = 0x05b2; + t.hatafpatahquarterhebrew = 0x05b2; + t.hatafpatahwidehebrew = 0x05b2; + t.hatafqamats = 0x05b3; + t.hatafqamats1b = 0x05b3; + t.hatafqamats28 = 0x05b3; + t.hatafqamats34 = 0x05b3; + t.hatafqamatshebrew = 0x05b3; + t.hatafqamatsnarrowhebrew = 0x05b3; + t.hatafqamatsquarterhebrew = 0x05b3; + t.hatafqamatswidehebrew = 0x05b3; + t.hatafsegol = 0x05b1; + t.hatafsegol17 = 0x05b1; + t.hatafsegol24 = 0x05b1; + t.hatafsegol30 = 0x05b1; + t.hatafsegolhebrew = 0x05b1; + t.hatafsegolnarrowhebrew = 0x05b1; + t.hatafsegolquarterhebrew = 0x05b1; + t.hatafsegolwidehebrew = 0x05b1; + t.hbar = 0x0127; + t.hbopomofo = 0x310f; + t.hbrevebelow = 0x1e2b; + t.hcedilla = 0x1e29; + t.hcircle = 0x24d7; + t.hcircumflex = 0x0125; + t.hdieresis = 0x1e27; + t.hdotaccent = 0x1e23; + t.hdotbelow = 0x1e25; + t.he = 0x05d4; + t.heart = 0x2665; + t.heartsuitblack = 0x2665; + t.heartsuitwhite = 0x2661; + t.hedagesh = 0xfb34; + t.hedageshhebrew = 0xfb34; + t.hehaltonearabic = 0x06c1; + t.heharabic = 0x0647; + t.hehebrew = 0x05d4; + t.hehfinalaltonearabic = 0xfba7; + t.hehfinalalttwoarabic = 0xfeea; + t.hehfinalarabic = 0xfeea; + t.hehhamzaabovefinalarabic = 0xfba5; + t.hehhamzaaboveisolatedarabic = 0xfba4; + t.hehinitialaltonearabic = 0xfba8; + t.hehinitialarabic = 0xfeeb; + t.hehiragana = 0x3078; + t.hehmedialaltonearabic = 0xfba9; + t.hehmedialarabic = 0xfeec; + t.heiseierasquare = 0x337b; + t.hekatakana = 0x30d8; + t.hekatakanahalfwidth = 0xff8d; + t.hekutaarusquare = 0x3336; + t.henghook = 0x0267; + t.herutusquare = 0x3339; + t.het = 0x05d7; + t.hethebrew = 0x05d7; + t.hhook = 0x0266; + t.hhooksuperior = 0x02b1; + t.hieuhacirclekorean = 0x327b; + t.hieuhaparenkorean = 0x321b; + t.hieuhcirclekorean = 0x326d; + t.hieuhkorean = 0x314e; + t.hieuhparenkorean = 0x320d; + t.hihiragana = 0x3072; + t.hikatakana = 0x30d2; + t.hikatakanahalfwidth = 0xff8b; + t.hiriq = 0x05b4; + t.hiriq14 = 0x05b4; + t.hiriq21 = 0x05b4; + t.hiriq2d = 0x05b4; + t.hiriqhebrew = 0x05b4; + t.hiriqnarrowhebrew = 0x05b4; + t.hiriqquarterhebrew = 0x05b4; + t.hiriqwidehebrew = 0x05b4; + t.hlinebelow = 0x1e96; + t.hmonospace = 0xff48; + t.hoarmenian = 0x0570; + t.hohipthai = 0x0e2b; + t.hohiragana = 0x307b; + t.hokatakana = 0x30db; + t.hokatakanahalfwidth = 0xff8e; + t.holam = 0x05b9; + t.holam19 = 0x05b9; + t.holam26 = 0x05b9; + t.holam32 = 0x05b9; + t.holamhebrew = 0x05b9; + t.holamnarrowhebrew = 0x05b9; + t.holamquarterhebrew = 0x05b9; + t.holamwidehebrew = 0x05b9; + t.honokhukthai = 0x0e2e; + t.hookabovecomb = 0x0309; + t.hookcmb = 0x0309; + t.hookpalatalizedbelowcmb = 0x0321; + t.hookretroflexbelowcmb = 0x0322; + t.hoonsquare = 0x3342; + t.horicoptic = 0x03e9; + t.horizontalbar = 0x2015; + t.horncmb = 0x031b; + t.hotsprings = 0x2668; + t.house = 0x2302; + t.hparen = 0x24a3; + t.hsuperior = 0x02b0; + t.hturned = 0x0265; + t.huhiragana = 0x3075; + t.huiitosquare = 0x3333; + t.hukatakana = 0x30d5; + t.hukatakanahalfwidth = 0xff8c; + t.hungarumlaut = 0x02dd; + t.hungarumlautcmb = 0x030b; + t.hv = 0x0195; + t.hyphen = 0x002d; + t.hypheninferior = 0xf6e5; + t.hyphenmonospace = 0xff0d; + t.hyphensmall = 0xfe63; + t.hyphensuperior = 0xf6e6; + t.hyphentwo = 0x2010; + t.i = 0x0069; + t.iacute = 0x00ed; + t.iacyrillic = 0x044f; + t.ibengali = 0x0987; + t.ibopomofo = 0x3127; + t.ibreve = 0x012d; + t.icaron = 0x01d0; + t.icircle = 0x24d8; + t.icircumflex = 0x00ee; + t.icyrillic = 0x0456; + t.idblgrave = 0x0209; + t.ideographearthcircle = 0x328f; + t.ideographfirecircle = 0x328b; + t.ideographicallianceparen = 0x323f; + t.ideographiccallparen = 0x323a; + t.ideographiccentrecircle = 0x32a5; + t.ideographicclose = 0x3006; + t.ideographiccomma = 0x3001; + t.ideographiccommaleft = 0xff64; + t.ideographiccongratulationparen = 0x3237; + t.ideographiccorrectcircle = 0x32a3; + t.ideographicearthparen = 0x322f; + t.ideographicenterpriseparen = 0x323d; + t.ideographicexcellentcircle = 0x329d; + t.ideographicfestivalparen = 0x3240; + t.ideographicfinancialcircle = 0x3296; + t.ideographicfinancialparen = 0x3236; + t.ideographicfireparen = 0x322b; + t.ideographichaveparen = 0x3232; + t.ideographichighcircle = 0x32a4; + t.ideographiciterationmark = 0x3005; + t.ideographiclaborcircle = 0x3298; + t.ideographiclaborparen = 0x3238; + t.ideographicleftcircle = 0x32a7; + t.ideographiclowcircle = 0x32a6; + t.ideographicmedicinecircle = 0x32a9; + t.ideographicmetalparen = 0x322e; + t.ideographicmoonparen = 0x322a; + t.ideographicnameparen = 0x3234; + t.ideographicperiod = 0x3002; + t.ideographicprintcircle = 0x329e; + t.ideographicreachparen = 0x3243; + t.ideographicrepresentparen = 0x3239; + t.ideographicresourceparen = 0x323e; + t.ideographicrightcircle = 0x32a8; + t.ideographicsecretcircle = 0x3299; + t.ideographicselfparen = 0x3242; + t.ideographicsocietyparen = 0x3233; + t.ideographicspace = 0x3000; + t.ideographicspecialparen = 0x3235; + t.ideographicstockparen = 0x3231; + t.ideographicstudyparen = 0x323b; + t.ideographicsunparen = 0x3230; + t.ideographicsuperviseparen = 0x323c; + t.ideographicwaterparen = 0x322c; + t.ideographicwoodparen = 0x322d; + t.ideographiczero = 0x3007; + t.ideographmetalcircle = 0x328e; + t.ideographmooncircle = 0x328a; + t.ideographnamecircle = 0x3294; + t.ideographsuncircle = 0x3290; + t.ideographwatercircle = 0x328c; + t.ideographwoodcircle = 0x328d; + t.ideva = 0x0907; + t.idieresis = 0x00ef; + t.idieresisacute = 0x1e2f; + t.idieresiscyrillic = 0x04e5; + t.idotbelow = 0x1ecb; + t.iebrevecyrillic = 0x04d7; + t.iecyrillic = 0x0435; + t.ieungacirclekorean = 0x3275; + t.ieungaparenkorean = 0x3215; + t.ieungcirclekorean = 0x3267; + t.ieungkorean = 0x3147; + t.ieungparenkorean = 0x3207; + t.igrave = 0x00ec; + t.igujarati = 0x0a87; + t.igurmukhi = 0x0a07; + t.ihiragana = 0x3044; + t.ihookabove = 0x1ec9; + t.iibengali = 0x0988; + t.iicyrillic = 0x0438; + t.iideva = 0x0908; + t.iigujarati = 0x0a88; + t.iigurmukhi = 0x0a08; + t.iimatragurmukhi = 0x0a40; + t.iinvertedbreve = 0x020b; + t.iishortcyrillic = 0x0439; + t.iivowelsignbengali = 0x09c0; + t.iivowelsigndeva = 0x0940; + t.iivowelsigngujarati = 0x0ac0; + t.ij = 0x0133; + t.ikatakana = 0x30a4; + t.ikatakanahalfwidth = 0xff72; + t.ikorean = 0x3163; + t.ilde = 0x02dc; + t.iluyhebrew = 0x05ac; + t.imacron = 0x012b; + t.imacroncyrillic = 0x04e3; + t.imageorapproximatelyequal = 0x2253; + t.imatragurmukhi = 0x0a3f; + t.imonospace = 0xff49; + t.increment = 0x2206; + t.infinity = 0x221e; + t.iniarmenian = 0x056b; + t.integral = 0x222b; + t.integralbottom = 0x2321; + t.integralbt = 0x2321; + t.integralex = 0xf8f5; + t.integraltop = 0x2320; + t.integraltp = 0x2320; + t.intersection = 0x2229; + t.intisquare = 0x3305; + t.invbullet = 0x25d8; + t.invcircle = 0x25d9; + t.invsmileface = 0x263b; + t.iocyrillic = 0x0451; + t.iogonek = 0x012f; + t.iota = 0x03b9; + t.iotadieresis = 0x03ca; + t.iotadieresistonos = 0x0390; + t.iotalatin = 0x0269; + t.iotatonos = 0x03af; + t.iparen = 0x24a4; + t.irigurmukhi = 0x0a72; + t.ismallhiragana = 0x3043; + t.ismallkatakana = 0x30a3; + t.ismallkatakanahalfwidth = 0xff68; + t.issharbengali = 0x09fa; + t.istroke = 0x0268; + t.isuperior = 0xf6ed; + t.iterationhiragana = 0x309d; + t.iterationkatakana = 0x30fd; + t.itilde = 0x0129; + t.itildebelow = 0x1e2d; + t.iubopomofo = 0x3129; + t.iucyrillic = 0x044e; + t.ivowelsignbengali = 0x09bf; + t.ivowelsigndeva = 0x093f; + t.ivowelsigngujarati = 0x0abf; + t.izhitsacyrillic = 0x0475; + t.izhitsadblgravecyrillic = 0x0477; + t.j = 0x006a; + t.jaarmenian = 0x0571; + t.jabengali = 0x099c; + t.jadeva = 0x091c; + t.jagujarati = 0x0a9c; + t.jagurmukhi = 0x0a1c; + t.jbopomofo = 0x3110; + t.jcaron = 0x01f0; + t.jcircle = 0x24d9; + t.jcircumflex = 0x0135; + t.jcrossedtail = 0x029d; + t.jdotlessstroke = 0x025f; + t.jecyrillic = 0x0458; + t.jeemarabic = 0x062c; + t.jeemfinalarabic = 0xfe9e; + t.jeeminitialarabic = 0xfe9f; + t.jeemmedialarabic = 0xfea0; + t.jeharabic = 0x0698; + t.jehfinalarabic = 0xfb8b; + t.jhabengali = 0x099d; + t.jhadeva = 0x091d; + t.jhagujarati = 0x0a9d; + t.jhagurmukhi = 0x0a1d; + t.jheharmenian = 0x057b; + t.jis = 0x3004; + t.jmonospace = 0xff4a; + t.jparen = 0x24a5; + t.jsuperior = 0x02b2; + t.k = 0x006b; + t.kabashkircyrillic = 0x04a1; + t.kabengali = 0x0995; + t.kacute = 0x1e31; + t.kacyrillic = 0x043a; + t.kadescendercyrillic = 0x049b; + t.kadeva = 0x0915; + t.kaf = 0x05db; + t.kafarabic = 0x0643; + t.kafdagesh = 0xfb3b; + t.kafdageshhebrew = 0xfb3b; + t.kaffinalarabic = 0xfeda; + t.kafhebrew = 0x05db; + t.kafinitialarabic = 0xfedb; + t.kafmedialarabic = 0xfedc; + t.kafrafehebrew = 0xfb4d; + t.kagujarati = 0x0a95; + t.kagurmukhi = 0x0a15; + t.kahiragana = 0x304b; + t.kahookcyrillic = 0x04c4; + t.kakatakana = 0x30ab; + t.kakatakanahalfwidth = 0xff76; + t.kappa = 0x03ba; + t.kappasymbolgreek = 0x03f0; + t.kapyeounmieumkorean = 0x3171; + t.kapyeounphieuphkorean = 0x3184; + t.kapyeounpieupkorean = 0x3178; + t.kapyeounssangpieupkorean = 0x3179; + t.karoriisquare = 0x330d; + t.kashidaautoarabic = 0x0640; + t.kashidaautonosidebearingarabic = 0x0640; + t.kasmallkatakana = 0x30f5; + t.kasquare = 0x3384; + t.kasraarabic = 0x0650; + t.kasratanarabic = 0x064d; + t.kastrokecyrillic = 0x049f; + t.katahiraprolongmarkhalfwidth = 0xff70; + t.kaverticalstrokecyrillic = 0x049d; + t.kbopomofo = 0x310e; + t.kcalsquare = 0x3389; + t.kcaron = 0x01e9; + t.kcedilla = 0x0137; + t.kcircle = 0x24da; + t.kcommaaccent = 0x0137; + t.kdotbelow = 0x1e33; + t.keharmenian = 0x0584; + t.kehiragana = 0x3051; + t.kekatakana = 0x30b1; + t.kekatakanahalfwidth = 0xff79; + t.kenarmenian = 0x056f; + t.kesmallkatakana = 0x30f6; + t.kgreenlandic = 0x0138; + t.khabengali = 0x0996; + t.khacyrillic = 0x0445; + t.khadeva = 0x0916; + t.khagujarati = 0x0a96; + t.khagurmukhi = 0x0a16; + t.khaharabic = 0x062e; + t.khahfinalarabic = 0xfea6; + t.khahinitialarabic = 0xfea7; + t.khahmedialarabic = 0xfea8; + t.kheicoptic = 0x03e7; + t.khhadeva = 0x0959; + t.khhagurmukhi = 0x0a59; + t.khieukhacirclekorean = 0x3278; + t.khieukhaparenkorean = 0x3218; + t.khieukhcirclekorean = 0x326a; + t.khieukhkorean = 0x314b; + t.khieukhparenkorean = 0x320a; + t.khokhaithai = 0x0e02; + t.khokhonthai = 0x0e05; + t.khokhuatthai = 0x0e03; + t.khokhwaithai = 0x0e04; + t.khomutthai = 0x0e5b; + t.khook = 0x0199; + t.khorakhangthai = 0x0e06; + t.khzsquare = 0x3391; + t.kihiragana = 0x304d; + t.kikatakana = 0x30ad; + t.kikatakanahalfwidth = 0xff77; + t.kiroguramusquare = 0x3315; + t.kiromeetorusquare = 0x3316; + t.kirosquare = 0x3314; + t.kiyeokacirclekorean = 0x326e; + t.kiyeokaparenkorean = 0x320e; + t.kiyeokcirclekorean = 0x3260; + t.kiyeokkorean = 0x3131; + t.kiyeokparenkorean = 0x3200; + t.kiyeoksioskorean = 0x3133; + t.kjecyrillic = 0x045c; + t.klinebelow = 0x1e35; + t.klsquare = 0x3398; + t.kmcubedsquare = 0x33a6; + t.kmonospace = 0xff4b; + t.kmsquaredsquare = 0x33a2; + t.kohiragana = 0x3053; + t.kohmsquare = 0x33c0; + t.kokaithai = 0x0e01; + t.kokatakana = 0x30b3; + t.kokatakanahalfwidth = 0xff7a; + t.kooposquare = 0x331e; + t.koppacyrillic = 0x0481; + t.koreanstandardsymbol = 0x327f; + t.koroniscmb = 0x0343; + t.kparen = 0x24a6; + t.kpasquare = 0x33aa; + t.ksicyrillic = 0x046f; + t.ktsquare = 0x33cf; + t.kturned = 0x029e; + t.kuhiragana = 0x304f; + t.kukatakana = 0x30af; + t.kukatakanahalfwidth = 0xff78; + t.kvsquare = 0x33b8; + t.kwsquare = 0x33be; + t.l = 0x006c; + t.labengali = 0x09b2; + t.lacute = 0x013a; + t.ladeva = 0x0932; + t.lagujarati = 0x0ab2; + t.lagurmukhi = 0x0a32; + t.lakkhangyaothai = 0x0e45; + t.lamaleffinalarabic = 0xfefc; + t.lamalefhamzaabovefinalarabic = 0xfef8; + t.lamalefhamzaaboveisolatedarabic = 0xfef7; + t.lamalefhamzabelowfinalarabic = 0xfefa; + t.lamalefhamzabelowisolatedarabic = 0xfef9; + t.lamalefisolatedarabic = 0xfefb; + t.lamalefmaddaabovefinalarabic = 0xfef6; + t.lamalefmaddaaboveisolatedarabic = 0xfef5; + t.lamarabic = 0x0644; + t.lambda = 0x03bb; + t.lambdastroke = 0x019b; + t.lamed = 0x05dc; + t.lameddagesh = 0xfb3c; + t.lameddageshhebrew = 0xfb3c; + t.lamedhebrew = 0x05dc; + t.lamfinalarabic = 0xfede; + t.lamhahinitialarabic = 0xfcca; + t.laminitialarabic = 0xfedf; + t.lamjeeminitialarabic = 0xfcc9; + t.lamkhahinitialarabic = 0xfccb; + t.lamlamhehisolatedarabic = 0xfdf2; + t.lammedialarabic = 0xfee0; + t.lammeemhahinitialarabic = 0xfd88; + t.lammeeminitialarabic = 0xfccc; + t.largecircle = 0x25ef; + t.lbar = 0x019a; + t.lbelt = 0x026c; + t.lbopomofo = 0x310c; + t.lcaron = 0x013e; + t.lcedilla = 0x013c; + t.lcircle = 0x24db; + t.lcircumflexbelow = 0x1e3d; + t.lcommaaccent = 0x013c; + t.ldot = 0x0140; + t.ldotaccent = 0x0140; + t.ldotbelow = 0x1e37; + t.ldotbelowmacron = 0x1e39; + t.leftangleabovecmb = 0x031a; + t.lefttackbelowcmb = 0x0318; + t.less = 0x003c; + t.lessequal = 0x2264; + t.lessequalorgreater = 0x22da; + t.lessmonospace = 0xff1c; + t.lessorequivalent = 0x2272; + t.lessorgreater = 0x2276; + t.lessoverequal = 0x2266; + t.lesssmall = 0xfe64; + t.lezh = 0x026e; + t.lfblock = 0x258c; + t.lhookretroflex = 0x026d; + t.lira = 0x20a4; + t.liwnarmenian = 0x056c; + t.lj = 0x01c9; + t.ljecyrillic = 0x0459; + t.ll = 0xf6c0; + t.lladeva = 0x0933; + t.llagujarati = 0x0ab3; + t.llinebelow = 0x1e3b; + t.llladeva = 0x0934; + t.llvocalicbengali = 0x09e1; + t.llvocalicdeva = 0x0961; + t.llvocalicvowelsignbengali = 0x09e3; + t.llvocalicvowelsigndeva = 0x0963; + t.lmiddletilde = 0x026b; + t.lmonospace = 0xff4c; + t.lmsquare = 0x33d0; + t.lochulathai = 0x0e2c; + t.logicaland = 0x2227; + t.logicalnot = 0x00ac; + t.logicalnotreversed = 0x2310; + t.logicalor = 0x2228; + t.lolingthai = 0x0e25; + t.longs = 0x017f; + t.lowlinecenterline = 0xfe4e; + t.lowlinecmb = 0x0332; + t.lowlinedashed = 0xfe4d; + t.lozenge = 0x25ca; + t.lparen = 0x24a7; + t.lslash = 0x0142; + t.lsquare = 0x2113; + t.lsuperior = 0xf6ee; + t.ltshade = 0x2591; + t.luthai = 0x0e26; + t.lvocalicbengali = 0x098c; + t.lvocalicdeva = 0x090c; + t.lvocalicvowelsignbengali = 0x09e2; + t.lvocalicvowelsigndeva = 0x0962; + t.lxsquare = 0x33d3; + t.m = 0x006d; + t.mabengali = 0x09ae; + t.macron = 0x00af; + t.macronbelowcmb = 0x0331; + t.macroncmb = 0x0304; + t.macronlowmod = 0x02cd; + t.macronmonospace = 0xffe3; + t.macute = 0x1e3f; + t.madeva = 0x092e; + t.magujarati = 0x0aae; + t.magurmukhi = 0x0a2e; + t.mahapakhhebrew = 0x05a4; + t.mahapakhlefthebrew = 0x05a4; + t.mahiragana = 0x307e; + t.maichattawalowleftthai = 0xf895; + t.maichattawalowrightthai = 0xf894; + t.maichattawathai = 0x0e4b; + t.maichattawaupperleftthai = 0xf893; + t.maieklowleftthai = 0xf88c; + t.maieklowrightthai = 0xf88b; + t.maiekthai = 0x0e48; + t.maiekupperleftthai = 0xf88a; + t.maihanakatleftthai = 0xf884; + t.maihanakatthai = 0x0e31; + t.maitaikhuleftthai = 0xf889; + t.maitaikhuthai = 0x0e47; + t.maitholowleftthai = 0xf88f; + t.maitholowrightthai = 0xf88e; + t.maithothai = 0x0e49; + t.maithoupperleftthai = 0xf88d; + t.maitrilowleftthai = 0xf892; + t.maitrilowrightthai = 0xf891; + t.maitrithai = 0x0e4a; + t.maitriupperleftthai = 0xf890; + t.maiyamokthai = 0x0e46; + t.makatakana = 0x30de; + t.makatakanahalfwidth = 0xff8f; + t.male = 0x2642; + t.mansyonsquare = 0x3347; + t.maqafhebrew = 0x05be; + t.mars = 0x2642; + t.masoracirclehebrew = 0x05af; + t.masquare = 0x3383; + t.mbopomofo = 0x3107; + t.mbsquare = 0x33d4; + t.mcircle = 0x24dc; + t.mcubedsquare = 0x33a5; + t.mdotaccent = 0x1e41; + t.mdotbelow = 0x1e43; + t.meemarabic = 0x0645; + t.meemfinalarabic = 0xfee2; + t.meeminitialarabic = 0xfee3; + t.meemmedialarabic = 0xfee4; + t.meemmeeminitialarabic = 0xfcd1; + t.meemmeemisolatedarabic = 0xfc48; + t.meetorusquare = 0x334d; + t.mehiragana = 0x3081; + t.meizierasquare = 0x337e; + t.mekatakana = 0x30e1; + t.mekatakanahalfwidth = 0xff92; + t.mem = 0x05de; + t.memdagesh = 0xfb3e; + t.memdageshhebrew = 0xfb3e; + t.memhebrew = 0x05de; + t.menarmenian = 0x0574; + t.merkhahebrew = 0x05a5; + t.merkhakefulahebrew = 0x05a6; + t.merkhakefulalefthebrew = 0x05a6; + t.merkhalefthebrew = 0x05a5; + t.mhook = 0x0271; + t.mhzsquare = 0x3392; + t.middledotkatakanahalfwidth = 0xff65; + t.middot = 0x00b7; + t.mieumacirclekorean = 0x3272; + t.mieumaparenkorean = 0x3212; + t.mieumcirclekorean = 0x3264; + t.mieumkorean = 0x3141; + t.mieumpansioskorean = 0x3170; + t.mieumparenkorean = 0x3204; + t.mieumpieupkorean = 0x316e; + t.mieumsioskorean = 0x316f; + t.mihiragana = 0x307f; + t.mikatakana = 0x30df; + t.mikatakanahalfwidth = 0xff90; + t.minus = 0x2212; + t.minusbelowcmb = 0x0320; + t.minuscircle = 0x2296; + t.minusmod = 0x02d7; + t.minusplus = 0x2213; + t.minute = 0x2032; + t.miribaarusquare = 0x334a; + t.mirisquare = 0x3349; + t.mlonglegturned = 0x0270; + t.mlsquare = 0x3396; + t.mmcubedsquare = 0x33a3; + t.mmonospace = 0xff4d; + t.mmsquaredsquare = 0x339f; + t.mohiragana = 0x3082; + t.mohmsquare = 0x33c1; + t.mokatakana = 0x30e2; + t.mokatakanahalfwidth = 0xff93; + t.molsquare = 0x33d6; + t.momathai = 0x0e21; + t.moverssquare = 0x33a7; + t.moverssquaredsquare = 0x33a8; + t.mparen = 0x24a8; + t.mpasquare = 0x33ab; + t.mssquare = 0x33b3; + t.msuperior = 0xf6ef; + t.mturned = 0x026f; + t.mu = 0x00b5; + t.mu1 = 0x00b5; + t.muasquare = 0x3382; + t.muchgreater = 0x226b; + t.muchless = 0x226a; + t.mufsquare = 0x338c; + t.mugreek = 0x03bc; + t.mugsquare = 0x338d; + t.muhiragana = 0x3080; + t.mukatakana = 0x30e0; + t.mukatakanahalfwidth = 0xff91; + t.mulsquare = 0x3395; + t.multiply = 0x00d7; + t.mumsquare = 0x339b; + t.munahhebrew = 0x05a3; + t.munahlefthebrew = 0x05a3; + t.musicalnote = 0x266a; + t.musicalnotedbl = 0x266b; + t.musicflatsign = 0x266d; + t.musicsharpsign = 0x266f; + t.mussquare = 0x33b2; + t.muvsquare = 0x33b6; + t.muwsquare = 0x33bc; + t.mvmegasquare = 0x33b9; + t.mvsquare = 0x33b7; + t.mwmegasquare = 0x33bf; + t.mwsquare = 0x33bd; + t.n = 0x006e; + t.nabengali = 0x09a8; + t.nabla = 0x2207; + t.nacute = 0x0144; + t.nadeva = 0x0928; + t.nagujarati = 0x0aa8; + t.nagurmukhi = 0x0a28; + t.nahiragana = 0x306a; + t.nakatakana = 0x30ca; + t.nakatakanahalfwidth = 0xff85; + t.napostrophe = 0x0149; + t.nasquare = 0x3381; + t.nbopomofo = 0x310b; + t.nbspace = 0x00a0; + t.ncaron = 0x0148; + t.ncedilla = 0x0146; + t.ncircle = 0x24dd; + t.ncircumflexbelow = 0x1e4b; + t.ncommaaccent = 0x0146; + t.ndotaccent = 0x1e45; + t.ndotbelow = 0x1e47; + t.nehiragana = 0x306d; + t.nekatakana = 0x30cd; + t.nekatakanahalfwidth = 0xff88; + t.newsheqelsign = 0x20aa; + t.nfsquare = 0x338b; + t.ngabengali = 0x0999; + t.ngadeva = 0x0919; + t.ngagujarati = 0x0a99; + t.ngagurmukhi = 0x0a19; + t.ngonguthai = 0x0e07; + t.nhiragana = 0x3093; + t.nhookleft = 0x0272; + t.nhookretroflex = 0x0273; + t.nieunacirclekorean = 0x326f; + t.nieunaparenkorean = 0x320f; + t.nieuncieuckorean = 0x3135; + t.nieuncirclekorean = 0x3261; + t.nieunhieuhkorean = 0x3136; + t.nieunkorean = 0x3134; + t.nieunpansioskorean = 0x3168; + t.nieunparenkorean = 0x3201; + t.nieunsioskorean = 0x3167; + t.nieuntikeutkorean = 0x3166; + t.nihiragana = 0x306b; + t.nikatakana = 0x30cb; + t.nikatakanahalfwidth = 0xff86; + t.nikhahitleftthai = 0xf899; + t.nikhahitthai = 0x0e4d; + t.nine = 0x0039; + t.ninearabic = 0x0669; + t.ninebengali = 0x09ef; + t.ninecircle = 0x2468; + t.ninecircleinversesansserif = 0x2792; + t.ninedeva = 0x096f; + t.ninegujarati = 0x0aef; + t.ninegurmukhi = 0x0a6f; + t.ninehackarabic = 0x0669; + t.ninehangzhou = 0x3029; + t.nineideographicparen = 0x3228; + t.nineinferior = 0x2089; + t.ninemonospace = 0xff19; + t.nineoldstyle = 0xf739; + t.nineparen = 0x247c; + t.nineperiod = 0x2490; + t.ninepersian = 0x06f9; + t.nineroman = 0x2178; + t.ninesuperior = 0x2079; + t.nineteencircle = 0x2472; + t.nineteenparen = 0x2486; + t.nineteenperiod = 0x249a; + t.ninethai = 0x0e59; + t.nj = 0x01cc; + t.njecyrillic = 0x045a; + t.nkatakana = 0x30f3; + t.nkatakanahalfwidth = 0xff9d; + t.nlegrightlong = 0x019e; + t.nlinebelow = 0x1e49; + t.nmonospace = 0xff4e; + t.nmsquare = 0x339a; + t.nnabengali = 0x09a3; + t.nnadeva = 0x0923; + t.nnagujarati = 0x0aa3; + t.nnagurmukhi = 0x0a23; + t.nnnadeva = 0x0929; + t.nohiragana = 0x306e; + t.nokatakana = 0x30ce; + t.nokatakanahalfwidth = 0xff89; + t.nonbreakingspace = 0x00a0; + t.nonenthai = 0x0e13; + t.nonuthai = 0x0e19; + t.noonarabic = 0x0646; + t.noonfinalarabic = 0xfee6; + t.noonghunnaarabic = 0x06ba; + t.noonghunnafinalarabic = 0xfb9f; + t.nooninitialarabic = 0xfee7; + t.noonjeeminitialarabic = 0xfcd2; + t.noonjeemisolatedarabic = 0xfc4b; + t.noonmedialarabic = 0xfee8; + t.noonmeeminitialarabic = 0xfcd5; + t.noonmeemisolatedarabic = 0xfc4e; + t.noonnoonfinalarabic = 0xfc8d; + t.notcontains = 0x220c; + t.notelement = 0x2209; + t.notelementof = 0x2209; + t.notequal = 0x2260; + t.notgreater = 0x226f; + t.notgreaternorequal = 0x2271; + t.notgreaternorless = 0x2279; + t.notidentical = 0x2262; + t.notless = 0x226e; + t.notlessnorequal = 0x2270; + t.notparallel = 0x2226; + t.notprecedes = 0x2280; + t.notsubset = 0x2284; + t.notsucceeds = 0x2281; + t.notsuperset = 0x2285; + t.nowarmenian = 0x0576; + t.nparen = 0x24a9; + t.nssquare = 0x33b1; + t.nsuperior = 0x207f; + t.ntilde = 0x00f1; + t.nu = 0x03bd; + t.nuhiragana = 0x306c; + t.nukatakana = 0x30cc; + t.nukatakanahalfwidth = 0xff87; + t.nuktabengali = 0x09bc; + t.nuktadeva = 0x093c; + t.nuktagujarati = 0x0abc; + t.nuktagurmukhi = 0x0a3c; + t.numbersign = 0x0023; + t.numbersignmonospace = 0xff03; + t.numbersignsmall = 0xfe5f; + t.numeralsigngreek = 0x0374; + t.numeralsignlowergreek = 0x0375; + t.numero = 0x2116; + t.nun = 0x05e0; + t.nundagesh = 0xfb40; + t.nundageshhebrew = 0xfb40; + t.nunhebrew = 0x05e0; + t.nvsquare = 0x33b5; + t.nwsquare = 0x33bb; + t.nyabengali = 0x099e; + t.nyadeva = 0x091e; + t.nyagujarati = 0x0a9e; + t.nyagurmukhi = 0x0a1e; + t.o = 0x006f; + t.oacute = 0x00f3; + t.oangthai = 0x0e2d; + t.obarred = 0x0275; + t.obarredcyrillic = 0x04e9; + t.obarreddieresiscyrillic = 0x04eb; + t.obengali = 0x0993; + t.obopomofo = 0x311b; + t.obreve = 0x014f; + t.ocandradeva = 0x0911; + t.ocandragujarati = 0x0a91; + t.ocandravowelsigndeva = 0x0949; + t.ocandravowelsigngujarati = 0x0ac9; + t.ocaron = 0x01d2; + t.ocircle = 0x24de; + t.ocircumflex = 0x00f4; + t.ocircumflexacute = 0x1ed1; + t.ocircumflexdotbelow = 0x1ed9; + t.ocircumflexgrave = 0x1ed3; + t.ocircumflexhookabove = 0x1ed5; + t.ocircumflextilde = 0x1ed7; + t.ocyrillic = 0x043e; + t.odblacute = 0x0151; + t.odblgrave = 0x020d; + t.odeva = 0x0913; + t.odieresis = 0x00f6; + t.odieresiscyrillic = 0x04e7; + t.odotbelow = 0x1ecd; + t.oe = 0x0153; + t.oekorean = 0x315a; + t.ogonek = 0x02db; + t.ogonekcmb = 0x0328; + t.ograve = 0x00f2; + t.ogujarati = 0x0a93; + t.oharmenian = 0x0585; + t.ohiragana = 0x304a; + t.ohookabove = 0x1ecf; + t.ohorn = 0x01a1; + t.ohornacute = 0x1edb; + t.ohorndotbelow = 0x1ee3; + t.ohorngrave = 0x1edd; + t.ohornhookabove = 0x1edf; + t.ohorntilde = 0x1ee1; + t.ohungarumlaut = 0x0151; + t.oi = 0x01a3; + t.oinvertedbreve = 0x020f; + t.okatakana = 0x30aa; + t.okatakanahalfwidth = 0xff75; + t.okorean = 0x3157; + t.olehebrew = 0x05ab; + t.omacron = 0x014d; + t.omacronacute = 0x1e53; + t.omacrongrave = 0x1e51; + t.omdeva = 0x0950; + t.omega = 0x03c9; + t.omega1 = 0x03d6; + t.omegacyrillic = 0x0461; + t.omegalatinclosed = 0x0277; + t.omegaroundcyrillic = 0x047b; + t.omegatitlocyrillic = 0x047d; + t.omegatonos = 0x03ce; + t.omgujarati = 0x0ad0; + t.omicron = 0x03bf; + t.omicrontonos = 0x03cc; + t.omonospace = 0xff4f; + t.one = 0x0031; + t.onearabic = 0x0661; + t.onebengali = 0x09e7; + t.onecircle = 0x2460; + t.onecircleinversesansserif = 0x278a; + t.onedeva = 0x0967; + t.onedotenleader = 0x2024; + t.oneeighth = 0x215b; + t.onefitted = 0xf6dc; + t.onegujarati = 0x0ae7; + t.onegurmukhi = 0x0a67; + t.onehackarabic = 0x0661; + t.onehalf = 0x00bd; + t.onehangzhou = 0x3021; + t.oneideographicparen = 0x3220; + t.oneinferior = 0x2081; + t.onemonospace = 0xff11; + t.onenumeratorbengali = 0x09f4; + t.oneoldstyle = 0xf731; + t.oneparen = 0x2474; + t.oneperiod = 0x2488; + t.onepersian = 0x06f1; + t.onequarter = 0x00bc; + t.oneroman = 0x2170; + t.onesuperior = 0x00b9; + t.onethai = 0x0e51; + t.onethird = 0x2153; + t.oogonek = 0x01eb; + t.oogonekmacron = 0x01ed; + t.oogurmukhi = 0x0a13; + t.oomatragurmukhi = 0x0a4b; + t.oopen = 0x0254; + t.oparen = 0x24aa; + t.openbullet = 0x25e6; + t.option = 0x2325; + t.ordfeminine = 0x00aa; + t.ordmasculine = 0x00ba; + t.orthogonal = 0x221f; + t.oshortdeva = 0x0912; + t.oshortvowelsigndeva = 0x094a; + t.oslash = 0x00f8; + t.oslashacute = 0x01ff; + t.osmallhiragana = 0x3049; + t.osmallkatakana = 0x30a9; + t.osmallkatakanahalfwidth = 0xff6b; + t.ostrokeacute = 0x01ff; + t.osuperior = 0xf6f0; + t.otcyrillic = 0x047f; + t.otilde = 0x00f5; + t.otildeacute = 0x1e4d; + t.otildedieresis = 0x1e4f; + t.oubopomofo = 0x3121; + t.overline = 0x203e; + t.overlinecenterline = 0xfe4a; + t.overlinecmb = 0x0305; + t.overlinedashed = 0xfe49; + t.overlinedblwavy = 0xfe4c; + t.overlinewavy = 0xfe4b; + t.overscore = 0x00af; + t.ovowelsignbengali = 0x09cb; + t.ovowelsigndeva = 0x094b; + t.ovowelsigngujarati = 0x0acb; + t.p = 0x0070; + t.paampssquare = 0x3380; + t.paasentosquare = 0x332b; + t.pabengali = 0x09aa; + t.pacute = 0x1e55; + t.padeva = 0x092a; + t.pagedown = 0x21df; + t.pageup = 0x21de; + t.pagujarati = 0x0aaa; + t.pagurmukhi = 0x0a2a; + t.pahiragana = 0x3071; + t.paiyannoithai = 0x0e2f; + t.pakatakana = 0x30d1; + t.palatalizationcyrilliccmb = 0x0484; + t.palochkacyrillic = 0x04c0; + t.pansioskorean = 0x317f; + t.paragraph = 0x00b6; + t.parallel = 0x2225; + t.parenleft = 0x0028; + t.parenleftaltonearabic = 0xfd3e; + t.parenleftbt = 0xf8ed; + t.parenleftex = 0xf8ec; + t.parenleftinferior = 0x208d; + t.parenleftmonospace = 0xff08; + t.parenleftsmall = 0xfe59; + t.parenleftsuperior = 0x207d; + t.parenlefttp = 0xf8eb; + t.parenleftvertical = 0xfe35; + t.parenright = 0x0029; + t.parenrightaltonearabic = 0xfd3f; + t.parenrightbt = 0xf8f8; + t.parenrightex = 0xf8f7; + t.parenrightinferior = 0x208e; + t.parenrightmonospace = 0xff09; + t.parenrightsmall = 0xfe5a; + t.parenrightsuperior = 0x207e; + t.parenrighttp = 0xf8f6; + t.parenrightvertical = 0xfe36; + t.partialdiff = 0x2202; + t.paseqhebrew = 0x05c0; + t.pashtahebrew = 0x0599; + t.pasquare = 0x33a9; + t.patah = 0x05b7; + t.patah11 = 0x05b7; + t.patah1d = 0x05b7; + t.patah2a = 0x05b7; + t.patahhebrew = 0x05b7; + t.patahnarrowhebrew = 0x05b7; + t.patahquarterhebrew = 0x05b7; + t.patahwidehebrew = 0x05b7; + t.pazerhebrew = 0x05a1; + t.pbopomofo = 0x3106; + t.pcircle = 0x24df; + t.pdotaccent = 0x1e57; + t.pe = 0x05e4; + t.pecyrillic = 0x043f; + t.pedagesh = 0xfb44; + t.pedageshhebrew = 0xfb44; + t.peezisquare = 0x333b; + t.pefinaldageshhebrew = 0xfb43; + t.peharabic = 0x067e; + t.peharmenian = 0x057a; + t.pehebrew = 0x05e4; + t.pehfinalarabic = 0xfb57; + t.pehinitialarabic = 0xfb58; + t.pehiragana = 0x307a; + t.pehmedialarabic = 0xfb59; + t.pekatakana = 0x30da; + t.pemiddlehookcyrillic = 0x04a7; + t.perafehebrew = 0xfb4e; + t.percent = 0x0025; + t.percentarabic = 0x066a; + t.percentmonospace = 0xff05; + t.percentsmall = 0xfe6a; + t.period = 0x002e; + t.periodarmenian = 0x0589; + t.periodcentered = 0x00b7; + t.periodhalfwidth = 0xff61; + t.periodinferior = 0xf6e7; + t.periodmonospace = 0xff0e; + t.periodsmall = 0xfe52; + t.periodsuperior = 0xf6e8; + t.perispomenigreekcmb = 0x0342; + t.perpendicular = 0x22a5; + t.perthousand = 0x2030; + t.peseta = 0x20a7; + t.pfsquare = 0x338a; + t.phabengali = 0x09ab; + t.phadeva = 0x092b; + t.phagujarati = 0x0aab; + t.phagurmukhi = 0x0a2b; + t.phi = 0x03c6; + t.phi1 = 0x03d5; + t.phieuphacirclekorean = 0x327a; + t.phieuphaparenkorean = 0x321a; + t.phieuphcirclekorean = 0x326c; + t.phieuphkorean = 0x314d; + t.phieuphparenkorean = 0x320c; + t.philatin = 0x0278; + t.phinthuthai = 0x0e3a; + t.phisymbolgreek = 0x03d5; + t.phook = 0x01a5; + t.phophanthai = 0x0e1e; + t.phophungthai = 0x0e1c; + t.phosamphaothai = 0x0e20; + t.pi = 0x03c0; + t.pieupacirclekorean = 0x3273; + t.pieupaparenkorean = 0x3213; + t.pieupcieuckorean = 0x3176; + t.pieupcirclekorean = 0x3265; + t.pieupkiyeokkorean = 0x3172; + t.pieupkorean = 0x3142; + t.pieupparenkorean = 0x3205; + t.pieupsioskiyeokkorean = 0x3174; + t.pieupsioskorean = 0x3144; + t.pieupsiostikeutkorean = 0x3175; + t.pieupthieuthkorean = 0x3177; + t.pieuptikeutkorean = 0x3173; + t.pihiragana = 0x3074; + t.pikatakana = 0x30d4; + t.pisymbolgreek = 0x03d6; + t.piwrarmenian = 0x0583; + t.planckover2pi = 0x210f; + t.planckover2pi1 = 0x210f; + t.plus = 0x002b; + t.plusbelowcmb = 0x031f; + t.pluscircle = 0x2295; + t.plusminus = 0x00b1; + t.plusmod = 0x02d6; + t.plusmonospace = 0xff0b; + t.plussmall = 0xfe62; + t.plussuperior = 0x207a; + t.pmonospace = 0xff50; + t.pmsquare = 0x33d8; + t.pohiragana = 0x307d; + t.pointingindexdownwhite = 0x261f; + t.pointingindexleftwhite = 0x261c; + t.pointingindexrightwhite = 0x261e; + t.pointingindexupwhite = 0x261d; + t.pokatakana = 0x30dd; + t.poplathai = 0x0e1b; + t.postalmark = 0x3012; + t.postalmarkface = 0x3020; + t.pparen = 0x24ab; + t.precedes = 0x227a; + t.prescription = 0x211e; + t.primemod = 0x02b9; + t.primereversed = 0x2035; + t.product = 0x220f; + t.projective = 0x2305; + t.prolongedkana = 0x30fc; + t.propellor = 0x2318; + t.propersubset = 0x2282; + t.propersuperset = 0x2283; + t.proportion = 0x2237; + t.proportional = 0x221d; + t.psi = 0x03c8; + t.psicyrillic = 0x0471; + t.psilipneumatacyrilliccmb = 0x0486; + t.pssquare = 0x33b0; + t.puhiragana = 0x3077; + t.pukatakana = 0x30d7; + t.pvsquare = 0x33b4; + t.pwsquare = 0x33ba; + t.q = 0x0071; + t.qadeva = 0x0958; + t.qadmahebrew = 0x05a8; + t.qafarabic = 0x0642; + t.qaffinalarabic = 0xfed6; + t.qafinitialarabic = 0xfed7; + t.qafmedialarabic = 0xfed8; + t.qamats = 0x05b8; + t.qamats10 = 0x05b8; + t.qamats1a = 0x05b8; + t.qamats1c = 0x05b8; + t.qamats27 = 0x05b8; + t.qamats29 = 0x05b8; + t.qamats33 = 0x05b8; + t.qamatsde = 0x05b8; + t.qamatshebrew = 0x05b8; + t.qamatsnarrowhebrew = 0x05b8; + t.qamatsqatanhebrew = 0x05b8; + t.qamatsqatannarrowhebrew = 0x05b8; + t.qamatsqatanquarterhebrew = 0x05b8; + t.qamatsqatanwidehebrew = 0x05b8; + t.qamatsquarterhebrew = 0x05b8; + t.qamatswidehebrew = 0x05b8; + t.qarneyparahebrew = 0x059f; + t.qbopomofo = 0x3111; + t.qcircle = 0x24e0; + t.qhook = 0x02a0; + t.qmonospace = 0xff51; + t.qof = 0x05e7; + t.qofdagesh = 0xfb47; + t.qofdageshhebrew = 0xfb47; + t.qofhebrew = 0x05e7; + t.qparen = 0x24ac; + t.quarternote = 0x2669; + t.qubuts = 0x05bb; + t.qubuts18 = 0x05bb; + t.qubuts25 = 0x05bb; + t.qubuts31 = 0x05bb; + t.qubutshebrew = 0x05bb; + t.qubutsnarrowhebrew = 0x05bb; + t.qubutsquarterhebrew = 0x05bb; + t.qubutswidehebrew = 0x05bb; + t.question = 0x003f; + t.questionarabic = 0x061f; + t.questionarmenian = 0x055e; + t.questiondown = 0x00bf; + t.questiondownsmall = 0xf7bf; + t.questiongreek = 0x037e; + t.questionmonospace = 0xff1f; + t.questionsmall = 0xf73f; + t.quotedbl = 0x0022; + t.quotedblbase = 0x201e; + t.quotedblleft = 0x201c; + t.quotedblmonospace = 0xff02; + t.quotedblprime = 0x301e; + t.quotedblprimereversed = 0x301d; + t.quotedblright = 0x201d; + t.quoteleft = 0x2018; + t.quoteleftreversed = 0x201b; + t.quotereversed = 0x201b; + t.quoteright = 0x2019; + t.quoterightn = 0x0149; + t.quotesinglbase = 0x201a; + t.quotesingle = 0x0027; + t.quotesinglemonospace = 0xff07; + t.r = 0x0072; + t.raarmenian = 0x057c; + t.rabengali = 0x09b0; + t.racute = 0x0155; + t.radeva = 0x0930; + t.radical = 0x221a; + t.radicalex = 0xf8e5; + t.radoverssquare = 0x33ae; + t.radoverssquaredsquare = 0x33af; + t.radsquare = 0x33ad; + t.rafe = 0x05bf; + t.rafehebrew = 0x05bf; + t.ragujarati = 0x0ab0; + t.ragurmukhi = 0x0a30; + t.rahiragana = 0x3089; + t.rakatakana = 0x30e9; + t.rakatakanahalfwidth = 0xff97; + t.ralowerdiagonalbengali = 0x09f1; + t.ramiddlediagonalbengali = 0x09f0; + t.ramshorn = 0x0264; + t.ratio = 0x2236; + t.rbopomofo = 0x3116; + t.rcaron = 0x0159; + t.rcedilla = 0x0157; + t.rcircle = 0x24e1; + t.rcommaaccent = 0x0157; + t.rdblgrave = 0x0211; + t.rdotaccent = 0x1e59; + t.rdotbelow = 0x1e5b; + t.rdotbelowmacron = 0x1e5d; + t.referencemark = 0x203b; + t.reflexsubset = 0x2286; + t.reflexsuperset = 0x2287; + t.registered = 0x00ae; + t.registersans = 0xf8e8; + t.registerserif = 0xf6da; + t.reharabic = 0x0631; + t.reharmenian = 0x0580; + t.rehfinalarabic = 0xfeae; + t.rehiragana = 0x308c; + t.rekatakana = 0x30ec; + t.rekatakanahalfwidth = 0xff9a; + t.resh = 0x05e8; + t.reshdageshhebrew = 0xfb48; + t.reshhebrew = 0x05e8; + t.reversedtilde = 0x223d; + t.reviahebrew = 0x0597; + t.reviamugrashhebrew = 0x0597; + t.revlogicalnot = 0x2310; + t.rfishhook = 0x027e; + t.rfishhookreversed = 0x027f; + t.rhabengali = 0x09dd; + t.rhadeva = 0x095d; + t.rho = 0x03c1; + t.rhook = 0x027d; + t.rhookturned = 0x027b; + t.rhookturnedsuperior = 0x02b5; + t.rhosymbolgreek = 0x03f1; + t.rhotichookmod = 0x02de; + t.rieulacirclekorean = 0x3271; + t.rieulaparenkorean = 0x3211; + t.rieulcirclekorean = 0x3263; + t.rieulhieuhkorean = 0x3140; + t.rieulkiyeokkorean = 0x313a; + t.rieulkiyeoksioskorean = 0x3169; + t.rieulkorean = 0x3139; + t.rieulmieumkorean = 0x313b; + t.rieulpansioskorean = 0x316c; + t.rieulparenkorean = 0x3203; + t.rieulphieuphkorean = 0x313f; + t.rieulpieupkorean = 0x313c; + t.rieulpieupsioskorean = 0x316b; + t.rieulsioskorean = 0x313d; + t.rieulthieuthkorean = 0x313e; + t.rieultikeutkorean = 0x316a; + t.rieulyeorinhieuhkorean = 0x316d; + t.rightangle = 0x221f; + t.righttackbelowcmb = 0x0319; + t.righttriangle = 0x22bf; + t.rihiragana = 0x308a; + t.rikatakana = 0x30ea; + t.rikatakanahalfwidth = 0xff98; + t.ring = 0x02da; + t.ringbelowcmb = 0x0325; + t.ringcmb = 0x030a; + t.ringhalfleft = 0x02bf; + t.ringhalfleftarmenian = 0x0559; + t.ringhalfleftbelowcmb = 0x031c; + t.ringhalfleftcentered = 0x02d3; + t.ringhalfright = 0x02be; + t.ringhalfrightbelowcmb = 0x0339; + t.ringhalfrightcentered = 0x02d2; + t.rinvertedbreve = 0x0213; + t.rittorusquare = 0x3351; + t.rlinebelow = 0x1e5f; + t.rlongleg = 0x027c; + t.rlonglegturned = 0x027a; + t.rmonospace = 0xff52; + t.rohiragana = 0x308d; + t.rokatakana = 0x30ed; + t.rokatakanahalfwidth = 0xff9b; + t.roruathai = 0x0e23; + t.rparen = 0x24ad; + t.rrabengali = 0x09dc; + t.rradeva = 0x0931; + t.rragurmukhi = 0x0a5c; + t.rreharabic = 0x0691; + t.rrehfinalarabic = 0xfb8d; + t.rrvocalicbengali = 0x09e0; + t.rrvocalicdeva = 0x0960; + t.rrvocalicgujarati = 0x0ae0; + t.rrvocalicvowelsignbengali = 0x09c4; + t.rrvocalicvowelsigndeva = 0x0944; + t.rrvocalicvowelsigngujarati = 0x0ac4; + t.rsuperior = 0xf6f1; + t.rtblock = 0x2590; + t.rturned = 0x0279; + t.rturnedsuperior = 0x02b4; + t.ruhiragana = 0x308b; + t.rukatakana = 0x30eb; + t.rukatakanahalfwidth = 0xff99; + t.rupeemarkbengali = 0x09f2; + t.rupeesignbengali = 0x09f3; + t.rupiah = 0xf6dd; + t.ruthai = 0x0e24; + t.rvocalicbengali = 0x098b; + t.rvocalicdeva = 0x090b; + t.rvocalicgujarati = 0x0a8b; + t.rvocalicvowelsignbengali = 0x09c3; + t.rvocalicvowelsigndeva = 0x0943; + t.rvocalicvowelsigngujarati = 0x0ac3; + t.s = 0x0073; + t.sabengali = 0x09b8; + t.sacute = 0x015b; + t.sacutedotaccent = 0x1e65; + t.sadarabic = 0x0635; + t.sadeva = 0x0938; + t.sadfinalarabic = 0xfeba; + t.sadinitialarabic = 0xfebb; + t.sadmedialarabic = 0xfebc; + t.sagujarati = 0x0ab8; + t.sagurmukhi = 0x0a38; + t.sahiragana = 0x3055; + t.sakatakana = 0x30b5; + t.sakatakanahalfwidth = 0xff7b; + t.sallallahoualayhewasallamarabic = 0xfdfa; + t.samekh = 0x05e1; + t.samekhdagesh = 0xfb41; + t.samekhdageshhebrew = 0xfb41; + t.samekhhebrew = 0x05e1; + t.saraaathai = 0x0e32; + t.saraaethai = 0x0e41; + t.saraaimaimalaithai = 0x0e44; + t.saraaimaimuanthai = 0x0e43; + t.saraamthai = 0x0e33; + t.saraathai = 0x0e30; + t.saraethai = 0x0e40; + t.saraiileftthai = 0xf886; + t.saraiithai = 0x0e35; + t.saraileftthai = 0xf885; + t.saraithai = 0x0e34; + t.saraothai = 0x0e42; + t.saraueeleftthai = 0xf888; + t.saraueethai = 0x0e37; + t.saraueleftthai = 0xf887; + t.sarauethai = 0x0e36; + t.sarauthai = 0x0e38; + t.sarauuthai = 0x0e39; + t.sbopomofo = 0x3119; + t.scaron = 0x0161; + t.scarondotaccent = 0x1e67; + t.scedilla = 0x015f; + t.schwa = 0x0259; + t.schwacyrillic = 0x04d9; + t.schwadieresiscyrillic = 0x04db; + t.schwahook = 0x025a; + t.scircle = 0x24e2; + t.scircumflex = 0x015d; + t.scommaaccent = 0x0219; + t.sdotaccent = 0x1e61; + t.sdotbelow = 0x1e63; + t.sdotbelowdotaccent = 0x1e69; + t.seagullbelowcmb = 0x033c; + t.second = 0x2033; + t.secondtonechinese = 0x02ca; + t.section = 0x00a7; + t.seenarabic = 0x0633; + t.seenfinalarabic = 0xfeb2; + t.seeninitialarabic = 0xfeb3; + t.seenmedialarabic = 0xfeb4; + t.segol = 0x05b6; + t.segol13 = 0x05b6; + t.segol1f = 0x05b6; + t.segol2c = 0x05b6; + t.segolhebrew = 0x05b6; + t.segolnarrowhebrew = 0x05b6; + t.segolquarterhebrew = 0x05b6; + t.segoltahebrew = 0x0592; + t.segolwidehebrew = 0x05b6; + t.seharmenian = 0x057d; + t.sehiragana = 0x305b; + t.sekatakana = 0x30bb; + t.sekatakanahalfwidth = 0xff7e; + t.semicolon = 0x003b; + t.semicolonarabic = 0x061b; + t.semicolonmonospace = 0xff1b; + t.semicolonsmall = 0xfe54; + t.semivoicedmarkkana = 0x309c; + t.semivoicedmarkkanahalfwidth = 0xff9f; + t.sentisquare = 0x3322; + t.sentosquare = 0x3323; + t.seven = 0x0037; + t.sevenarabic = 0x0667; + t.sevenbengali = 0x09ed; + t.sevencircle = 0x2466; + t.sevencircleinversesansserif = 0x2790; + t.sevendeva = 0x096d; + t.seveneighths = 0x215e; + t.sevengujarati = 0x0aed; + t.sevengurmukhi = 0x0a6d; + t.sevenhackarabic = 0x0667; + t.sevenhangzhou = 0x3027; + t.sevenideographicparen = 0x3226; + t.seveninferior = 0x2087; + t.sevenmonospace = 0xff17; + t.sevenoldstyle = 0xf737; + t.sevenparen = 0x247a; + t.sevenperiod = 0x248e; + t.sevenpersian = 0x06f7; + t.sevenroman = 0x2176; + t.sevensuperior = 0x2077; + t.seventeencircle = 0x2470; + t.seventeenparen = 0x2484; + t.seventeenperiod = 0x2498; + t.seventhai = 0x0e57; + t.sfthyphen = 0x00ad; + t.shaarmenian = 0x0577; + t.shabengali = 0x09b6; + t.shacyrillic = 0x0448; + t.shaddaarabic = 0x0651; + t.shaddadammaarabic = 0xfc61; + t.shaddadammatanarabic = 0xfc5e; + t.shaddafathaarabic = 0xfc60; + t.shaddakasraarabic = 0xfc62; + t.shaddakasratanarabic = 0xfc5f; + t.shade = 0x2592; + t.shadedark = 0x2593; + t.shadelight = 0x2591; + t.shademedium = 0x2592; + t.shadeva = 0x0936; + t.shagujarati = 0x0ab6; + t.shagurmukhi = 0x0a36; + t.shalshelethebrew = 0x0593; + t.shbopomofo = 0x3115; + t.shchacyrillic = 0x0449; + t.sheenarabic = 0x0634; + t.sheenfinalarabic = 0xfeb6; + t.sheeninitialarabic = 0xfeb7; + t.sheenmedialarabic = 0xfeb8; + t.sheicoptic = 0x03e3; + t.sheqel = 0x20aa; + t.sheqelhebrew = 0x20aa; + t.sheva = 0x05b0; + t.sheva115 = 0x05b0; + t.sheva15 = 0x05b0; + t.sheva22 = 0x05b0; + t.sheva2e = 0x05b0; + t.shevahebrew = 0x05b0; + t.shevanarrowhebrew = 0x05b0; + t.shevaquarterhebrew = 0x05b0; + t.shevawidehebrew = 0x05b0; + t.shhacyrillic = 0x04bb; + t.shimacoptic = 0x03ed; + t.shin = 0x05e9; + t.shindagesh = 0xfb49; + t.shindageshhebrew = 0xfb49; + t.shindageshshindot = 0xfb2c; + t.shindageshshindothebrew = 0xfb2c; + t.shindageshsindot = 0xfb2d; + t.shindageshsindothebrew = 0xfb2d; + t.shindothebrew = 0x05c1; + t.shinhebrew = 0x05e9; + t.shinshindot = 0xfb2a; + t.shinshindothebrew = 0xfb2a; + t.shinsindot = 0xfb2b; + t.shinsindothebrew = 0xfb2b; + t.shook = 0x0282; + t.sigma = 0x03c3; + t.sigma1 = 0x03c2; + t.sigmafinal = 0x03c2; + t.sigmalunatesymbolgreek = 0x03f2; + t.sihiragana = 0x3057; + t.sikatakana = 0x30b7; + t.sikatakanahalfwidth = 0xff7c; + t.siluqhebrew = 0x05bd; + t.siluqlefthebrew = 0x05bd; + t.similar = 0x223c; + t.sindothebrew = 0x05c2; + t.siosacirclekorean = 0x3274; + t.siosaparenkorean = 0x3214; + t.sioscieuckorean = 0x317e; + t.sioscirclekorean = 0x3266; + t.sioskiyeokkorean = 0x317a; + t.sioskorean = 0x3145; + t.siosnieunkorean = 0x317b; + t.siosparenkorean = 0x3206; + t.siospieupkorean = 0x317d; + t.siostikeutkorean = 0x317c; + t.six = 0x0036; + t.sixarabic = 0x0666; + t.sixbengali = 0x09ec; + t.sixcircle = 0x2465; + t.sixcircleinversesansserif = 0x278f; + t.sixdeva = 0x096c; + t.sixgujarati = 0x0aec; + t.sixgurmukhi = 0x0a6c; + t.sixhackarabic = 0x0666; + t.sixhangzhou = 0x3026; + t.sixideographicparen = 0x3225; + t.sixinferior = 0x2086; + t.sixmonospace = 0xff16; + t.sixoldstyle = 0xf736; + t.sixparen = 0x2479; + t.sixperiod = 0x248d; + t.sixpersian = 0x06f6; + t.sixroman = 0x2175; + t.sixsuperior = 0x2076; + t.sixteencircle = 0x246f; + t.sixteencurrencydenominatorbengali = 0x09f9; + t.sixteenparen = 0x2483; + t.sixteenperiod = 0x2497; + t.sixthai = 0x0e56; + t.slash = 0x002f; + t.slashmonospace = 0xff0f; + t.slong = 0x017f; + t.slongdotaccent = 0x1e9b; + t.smileface = 0x263a; + t.smonospace = 0xff53; + t.sofpasuqhebrew = 0x05c3; + t.softhyphen = 0x00ad; + t.softsigncyrillic = 0x044c; + t.sohiragana = 0x305d; + t.sokatakana = 0x30bd; + t.sokatakanahalfwidth = 0xff7f; + t.soliduslongoverlaycmb = 0x0338; + t.solidusshortoverlaycmb = 0x0337; + t.sorusithai = 0x0e29; + t.sosalathai = 0x0e28; + t.sosothai = 0x0e0b; + t.sosuathai = 0x0e2a; + t.space = 0x0020; + t.spacehackarabic = 0x0020; + t.spade = 0x2660; + t.spadesuitblack = 0x2660; + t.spadesuitwhite = 0x2664; + t.sparen = 0x24ae; + t.squarebelowcmb = 0x033b; + t.squarecc = 0x33c4; + t.squarecm = 0x339d; + t.squarediagonalcrosshatchfill = 0x25a9; + t.squarehorizontalfill = 0x25a4; + t.squarekg = 0x338f; + t.squarekm = 0x339e; + t.squarekmcapital = 0x33ce; + t.squareln = 0x33d1; + t.squarelog = 0x33d2; + t.squaremg = 0x338e; + t.squaremil = 0x33d5; + t.squaremm = 0x339c; + t.squaremsquared = 0x33a1; + t.squareorthogonalcrosshatchfill = 0x25a6; + t.squareupperlefttolowerrightfill = 0x25a7; + t.squareupperrighttolowerleftfill = 0x25a8; + t.squareverticalfill = 0x25a5; + t.squarewhitewithsmallblack = 0x25a3; + t.srsquare = 0x33db; + t.ssabengali = 0x09b7; + t.ssadeva = 0x0937; + t.ssagujarati = 0x0ab7; + t.ssangcieuckorean = 0x3149; + t.ssanghieuhkorean = 0x3185; + t.ssangieungkorean = 0x3180; + t.ssangkiyeokkorean = 0x3132; + t.ssangnieunkorean = 0x3165; + t.ssangpieupkorean = 0x3143; + t.ssangsioskorean = 0x3146; + t.ssangtikeutkorean = 0x3138; + t.ssuperior = 0xf6f2; + t.sterling = 0x00a3; + t.sterlingmonospace = 0xffe1; + t.strokelongoverlaycmb = 0x0336; + t.strokeshortoverlaycmb = 0x0335; + t.subset = 0x2282; + t.subsetnotequal = 0x228a; + t.subsetorequal = 0x2286; + t.succeeds = 0x227b; + t.suchthat = 0x220b; + t.suhiragana = 0x3059; + t.sukatakana = 0x30b9; + t.sukatakanahalfwidth = 0xff7d; + t.sukunarabic = 0x0652; + t.summation = 0x2211; + t.sun = 0x263c; + t.superset = 0x2283; + t.supersetnotequal = 0x228b; + t.supersetorequal = 0x2287; + t.svsquare = 0x33dc; + t.syouwaerasquare = 0x337c; + t.t = 0x0074; + t.tabengali = 0x09a4; + t.tackdown = 0x22a4; + t.tackleft = 0x22a3; + t.tadeva = 0x0924; + t.tagujarati = 0x0aa4; + t.tagurmukhi = 0x0a24; + t.taharabic = 0x0637; + t.tahfinalarabic = 0xfec2; + t.tahinitialarabic = 0xfec3; + t.tahiragana = 0x305f; + t.tahmedialarabic = 0xfec4; + t.taisyouerasquare = 0x337d; + t.takatakana = 0x30bf; + t.takatakanahalfwidth = 0xff80; + t.tatweelarabic = 0x0640; + t.tau = 0x03c4; + t.tav = 0x05ea; + t.tavdages = 0xfb4a; + t.tavdagesh = 0xfb4a; + t.tavdageshhebrew = 0xfb4a; + t.tavhebrew = 0x05ea; + t.tbar = 0x0167; + t.tbopomofo = 0x310a; + t.tcaron = 0x0165; + t.tccurl = 0x02a8; + t.tcedilla = 0x0163; + t.tcheharabic = 0x0686; + t.tchehfinalarabic = 0xfb7b; + t.tchehinitialarabic = 0xfb7c; + t.tchehmedialarabic = 0xfb7d; + t.tcircle = 0x24e3; + t.tcircumflexbelow = 0x1e71; + t.tcommaaccent = 0x0163; + t.tdieresis = 0x1e97; + t.tdotaccent = 0x1e6b; + t.tdotbelow = 0x1e6d; + t.tecyrillic = 0x0442; + t.tedescendercyrillic = 0x04ad; + t.teharabic = 0x062a; + t.tehfinalarabic = 0xfe96; + t.tehhahinitialarabic = 0xfca2; + t.tehhahisolatedarabic = 0xfc0c; + t.tehinitialarabic = 0xfe97; + t.tehiragana = 0x3066; + t.tehjeeminitialarabic = 0xfca1; + t.tehjeemisolatedarabic = 0xfc0b; + t.tehmarbutaarabic = 0x0629; + t.tehmarbutafinalarabic = 0xfe94; + t.tehmedialarabic = 0xfe98; + t.tehmeeminitialarabic = 0xfca4; + t.tehmeemisolatedarabic = 0xfc0e; + t.tehnoonfinalarabic = 0xfc73; + t.tekatakana = 0x30c6; + t.tekatakanahalfwidth = 0xff83; + t.telephone = 0x2121; + t.telephoneblack = 0x260e; + t.telishagedolahebrew = 0x05a0; + t.telishaqetanahebrew = 0x05a9; + t.tencircle = 0x2469; + t.tenideographicparen = 0x3229; + t.tenparen = 0x247d; + t.tenperiod = 0x2491; + t.tenroman = 0x2179; + t.tesh = 0x02a7; + t.tet = 0x05d8; + t.tetdagesh = 0xfb38; + t.tetdageshhebrew = 0xfb38; + t.tethebrew = 0x05d8; + t.tetsecyrillic = 0x04b5; + t.tevirhebrew = 0x059b; + t.tevirlefthebrew = 0x059b; + t.thabengali = 0x09a5; + t.thadeva = 0x0925; + t.thagujarati = 0x0aa5; + t.thagurmukhi = 0x0a25; + t.thalarabic = 0x0630; + t.thalfinalarabic = 0xfeac; + t.thanthakhatlowleftthai = 0xf898; + t.thanthakhatlowrightthai = 0xf897; + t.thanthakhatthai = 0x0e4c; + t.thanthakhatupperleftthai = 0xf896; + t.theharabic = 0x062b; + t.thehfinalarabic = 0xfe9a; + t.thehinitialarabic = 0xfe9b; + t.thehmedialarabic = 0xfe9c; + t.thereexists = 0x2203; + t.therefore = 0x2234; + t.theta = 0x03b8; + t.theta1 = 0x03d1; + t.thetasymbolgreek = 0x03d1; + t.thieuthacirclekorean = 0x3279; + t.thieuthaparenkorean = 0x3219; + t.thieuthcirclekorean = 0x326b; + t.thieuthkorean = 0x314c; + t.thieuthparenkorean = 0x320b; + t.thirteencircle = 0x246c; + t.thirteenparen = 0x2480; + t.thirteenperiod = 0x2494; + t.thonangmonthothai = 0x0e11; + t.thook = 0x01ad; + t.thophuthaothai = 0x0e12; + t.thorn = 0x00fe; + t.thothahanthai = 0x0e17; + t.thothanthai = 0x0e10; + t.thothongthai = 0x0e18; + t.thothungthai = 0x0e16; + t.thousandcyrillic = 0x0482; + t.thousandsseparatorarabic = 0x066c; + t.thousandsseparatorpersian = 0x066c; + t.three = 0x0033; + t.threearabic = 0x0663; + t.threebengali = 0x09e9; + t.threecircle = 0x2462; + t.threecircleinversesansserif = 0x278c; + t.threedeva = 0x0969; + t.threeeighths = 0x215c; + t.threegujarati = 0x0ae9; + t.threegurmukhi = 0x0a69; + t.threehackarabic = 0x0663; + t.threehangzhou = 0x3023; + t.threeideographicparen = 0x3222; + t.threeinferior = 0x2083; + t.threemonospace = 0xff13; + t.threenumeratorbengali = 0x09f6; + t.threeoldstyle = 0xf733; + t.threeparen = 0x2476; + t.threeperiod = 0x248a; + t.threepersian = 0x06f3; + t.threequarters = 0x00be; + t.threequartersemdash = 0xf6de; + t.threeroman = 0x2172; + t.threesuperior = 0x00b3; + t.threethai = 0x0e53; + t.thzsquare = 0x3394; + t.tihiragana = 0x3061; + t.tikatakana = 0x30c1; + t.tikatakanahalfwidth = 0xff81; + t.tikeutacirclekorean = 0x3270; + t.tikeutaparenkorean = 0x3210; + t.tikeutcirclekorean = 0x3262; + t.tikeutkorean = 0x3137; + t.tikeutparenkorean = 0x3202; + t.tilde = 0x02dc; + t.tildebelowcmb = 0x0330; + t.tildecmb = 0x0303; + t.tildecomb = 0x0303; + t.tildedoublecmb = 0x0360; + t.tildeoperator = 0x223c; + t.tildeoverlaycmb = 0x0334; + t.tildeverticalcmb = 0x033e; + t.timescircle = 0x2297; + t.tipehahebrew = 0x0596; + t.tipehalefthebrew = 0x0596; + t.tippigurmukhi = 0x0a70; + t.titlocyrilliccmb = 0x0483; + t.tiwnarmenian = 0x057f; + t.tlinebelow = 0x1e6f; + t.tmonospace = 0xff54; + t.toarmenian = 0x0569; + t.tohiragana = 0x3068; + t.tokatakana = 0x30c8; + t.tokatakanahalfwidth = 0xff84; + t.tonebarextrahighmod = 0x02e5; + t.tonebarextralowmod = 0x02e9; + t.tonebarhighmod = 0x02e6; + t.tonebarlowmod = 0x02e8; + t.tonebarmidmod = 0x02e7; + t.tonefive = 0x01bd; + t.tonesix = 0x0185; + t.tonetwo = 0x01a8; + t.tonos = 0x0384; + t.tonsquare = 0x3327; + t.topatakthai = 0x0e0f; + t.tortoiseshellbracketleft = 0x3014; + t.tortoiseshellbracketleftsmall = 0xfe5d; + t.tortoiseshellbracketleftvertical = 0xfe39; + t.tortoiseshellbracketright = 0x3015; + t.tortoiseshellbracketrightsmall = 0xfe5e; + t.tortoiseshellbracketrightvertical = 0xfe3a; + t.totaothai = 0x0e15; + t.tpalatalhook = 0x01ab; + t.tparen = 0x24af; + t.trademark = 0x2122; + t.trademarksans = 0xf8ea; + t.trademarkserif = 0xf6db; + t.tretroflexhook = 0x0288; + t.triagdn = 0x25bc; + t.triaglf = 0x25c4; + t.triagrt = 0x25ba; + t.triagup = 0x25b2; + t.ts = 0x02a6; + t.tsadi = 0x05e6; + t.tsadidagesh = 0xfb46; + t.tsadidageshhebrew = 0xfb46; + t.tsadihebrew = 0x05e6; + t.tsecyrillic = 0x0446; + t.tsere = 0x05b5; + t.tsere12 = 0x05b5; + t.tsere1e = 0x05b5; + t.tsere2b = 0x05b5; + t.tserehebrew = 0x05b5; + t.tserenarrowhebrew = 0x05b5; + t.tserequarterhebrew = 0x05b5; + t.tserewidehebrew = 0x05b5; + t.tshecyrillic = 0x045b; + t.tsuperior = 0xf6f3; + t.ttabengali = 0x099f; + t.ttadeva = 0x091f; + t.ttagujarati = 0x0a9f; + t.ttagurmukhi = 0x0a1f; + t.tteharabic = 0x0679; + t.ttehfinalarabic = 0xfb67; + t.ttehinitialarabic = 0xfb68; + t.ttehmedialarabic = 0xfb69; + t.tthabengali = 0x09a0; + t.tthadeva = 0x0920; + t.tthagujarati = 0x0aa0; + t.tthagurmukhi = 0x0a20; + t.tturned = 0x0287; + t.tuhiragana = 0x3064; + t.tukatakana = 0x30c4; + t.tukatakanahalfwidth = 0xff82; + t.tusmallhiragana = 0x3063; + t.tusmallkatakana = 0x30c3; + t.tusmallkatakanahalfwidth = 0xff6f; + t.twelvecircle = 0x246b; + t.twelveparen = 0x247f; + t.twelveperiod = 0x2493; + t.twelveroman = 0x217b; + t.twentycircle = 0x2473; + t.twentyhangzhou = 0x5344; + t.twentyparen = 0x2487; + t.twentyperiod = 0x249b; + t.two = 0x0032; + t.twoarabic = 0x0662; + t.twobengali = 0x09e8; + t.twocircle = 0x2461; + t.twocircleinversesansserif = 0x278b; + t.twodeva = 0x0968; + t.twodotenleader = 0x2025; + t.twodotleader = 0x2025; + t.twodotleadervertical = 0xfe30; + t.twogujarati = 0x0ae8; + t.twogurmukhi = 0x0a68; + t.twohackarabic = 0x0662; + t.twohangzhou = 0x3022; + t.twoideographicparen = 0x3221; + t.twoinferior = 0x2082; + t.twomonospace = 0xff12; + t.twonumeratorbengali = 0x09f5; + t.twooldstyle = 0xf732; + t.twoparen = 0x2475; + t.twoperiod = 0x2489; + t.twopersian = 0x06f2; + t.tworoman = 0x2171; + t.twostroke = 0x01bb; + t.twosuperior = 0x00b2; + t.twothai = 0x0e52; + t.twothirds = 0x2154; + t.u = 0x0075; + t.uacute = 0x00fa; + t.ubar = 0x0289; + t.ubengali = 0x0989; + t.ubopomofo = 0x3128; + t.ubreve = 0x016d; + t.ucaron = 0x01d4; + t.ucircle = 0x24e4; + t.ucircumflex = 0x00fb; + t.ucircumflexbelow = 0x1e77; + t.ucyrillic = 0x0443; + t.udattadeva = 0x0951; + t.udblacute = 0x0171; + t.udblgrave = 0x0215; + t.udeva = 0x0909; + t.udieresis = 0x00fc; + t.udieresisacute = 0x01d8; + t.udieresisbelow = 0x1e73; + t.udieresiscaron = 0x01da; + t.udieresiscyrillic = 0x04f1; + t.udieresisgrave = 0x01dc; + t.udieresismacron = 0x01d6; + t.udotbelow = 0x1ee5; + t.ugrave = 0x00f9; + t.ugujarati = 0x0a89; + t.ugurmukhi = 0x0a09; + t.uhiragana = 0x3046; + t.uhookabove = 0x1ee7; + t.uhorn = 0x01b0; + t.uhornacute = 0x1ee9; + t.uhorndotbelow = 0x1ef1; + t.uhorngrave = 0x1eeb; + t.uhornhookabove = 0x1eed; + t.uhorntilde = 0x1eef; + t.uhungarumlaut = 0x0171; + t.uhungarumlautcyrillic = 0x04f3; + t.uinvertedbreve = 0x0217; + t.ukatakana = 0x30a6; + t.ukatakanahalfwidth = 0xff73; + t.ukcyrillic = 0x0479; + t.ukorean = 0x315c; + t.umacron = 0x016b; + t.umacroncyrillic = 0x04ef; + t.umacrondieresis = 0x1e7b; + t.umatragurmukhi = 0x0a41; + t.umonospace = 0xff55; + t.underscore = 0x005f; + t.underscoredbl = 0x2017; + t.underscoremonospace = 0xff3f; + t.underscorevertical = 0xfe33; + t.underscorewavy = 0xfe4f; + t.union = 0x222a; + t.universal = 0x2200; + t.uogonek = 0x0173; + t.uparen = 0x24b0; + t.upblock = 0x2580; + t.upperdothebrew = 0x05c4; + t.upsilon = 0x03c5; + t.upsilondieresis = 0x03cb; + t.upsilondieresistonos = 0x03b0; + t.upsilonlatin = 0x028a; + t.upsilontonos = 0x03cd; + t.uptackbelowcmb = 0x031d; + t.uptackmod = 0x02d4; + t.uragurmukhi = 0x0a73; + t.uring = 0x016f; + t.ushortcyrillic = 0x045e; + t.usmallhiragana = 0x3045; + t.usmallkatakana = 0x30a5; + t.usmallkatakanahalfwidth = 0xff69; + t.ustraightcyrillic = 0x04af; + t.ustraightstrokecyrillic = 0x04b1; + t.utilde = 0x0169; + t.utildeacute = 0x1e79; + t.utildebelow = 0x1e75; + t.uubengali = 0x098a; + t.uudeva = 0x090a; + t.uugujarati = 0x0a8a; + t.uugurmukhi = 0x0a0a; + t.uumatragurmukhi = 0x0a42; + t.uuvowelsignbengali = 0x09c2; + t.uuvowelsigndeva = 0x0942; + t.uuvowelsigngujarati = 0x0ac2; + t.uvowelsignbengali = 0x09c1; + t.uvowelsigndeva = 0x0941; + t.uvowelsigngujarati = 0x0ac1; + t.v = 0x0076; + t.vadeva = 0x0935; + t.vagujarati = 0x0ab5; + t.vagurmukhi = 0x0a35; + t.vakatakana = 0x30f7; + t.vav = 0x05d5; + t.vavdagesh = 0xfb35; + t.vavdagesh65 = 0xfb35; + t.vavdageshhebrew = 0xfb35; + t.vavhebrew = 0x05d5; + t.vavholam = 0xfb4b; + t.vavholamhebrew = 0xfb4b; + t.vavvavhebrew = 0x05f0; + t.vavyodhebrew = 0x05f1; + t.vcircle = 0x24e5; + t.vdotbelow = 0x1e7f; + t.vecyrillic = 0x0432; + t.veharabic = 0x06a4; + t.vehfinalarabic = 0xfb6b; + t.vehinitialarabic = 0xfb6c; + t.vehmedialarabic = 0xfb6d; + t.vekatakana = 0x30f9; + t.venus = 0x2640; + t.verticalbar = 0x007c; + t.verticallineabovecmb = 0x030d; + t.verticallinebelowcmb = 0x0329; + t.verticallinelowmod = 0x02cc; + t.verticallinemod = 0x02c8; + t.vewarmenian = 0x057e; + t.vhook = 0x028b; + t.vikatakana = 0x30f8; + t.viramabengali = 0x09cd; + t.viramadeva = 0x094d; + t.viramagujarati = 0x0acd; + t.visargabengali = 0x0983; + t.visargadeva = 0x0903; + t.visargagujarati = 0x0a83; + t.vmonospace = 0xff56; + t.voarmenian = 0x0578; + t.voicediterationhiragana = 0x309e; + t.voicediterationkatakana = 0x30fe; + t.voicedmarkkana = 0x309b; + t.voicedmarkkanahalfwidth = 0xff9e; + t.vokatakana = 0x30fa; + t.vparen = 0x24b1; + t.vtilde = 0x1e7d; + t.vturned = 0x028c; + t.vuhiragana = 0x3094; + t.vukatakana = 0x30f4; + t.w = 0x0077; + t.wacute = 0x1e83; + t.waekorean = 0x3159; + t.wahiragana = 0x308f; + t.wakatakana = 0x30ef; + t.wakatakanahalfwidth = 0xff9c; + t.wakorean = 0x3158; + t.wasmallhiragana = 0x308e; + t.wasmallkatakana = 0x30ee; + t.wattosquare = 0x3357; + t.wavedash = 0x301c; + t.wavyunderscorevertical = 0xfe34; + t.wawarabic = 0x0648; + t.wawfinalarabic = 0xfeee; + t.wawhamzaabovearabic = 0x0624; + t.wawhamzaabovefinalarabic = 0xfe86; + t.wbsquare = 0x33dd; + t.wcircle = 0x24e6; + t.wcircumflex = 0x0175; + t.wdieresis = 0x1e85; + t.wdotaccent = 0x1e87; + t.wdotbelow = 0x1e89; + t.wehiragana = 0x3091; + t.weierstrass = 0x2118; + t.wekatakana = 0x30f1; + t.wekorean = 0x315e; + t.weokorean = 0x315d; + t.wgrave = 0x1e81; + t.whitebullet = 0x25e6; + t.whitecircle = 0x25cb; + t.whitecircleinverse = 0x25d9; + t.whitecornerbracketleft = 0x300e; + t.whitecornerbracketleftvertical = 0xfe43; + t.whitecornerbracketright = 0x300f; + t.whitecornerbracketrightvertical = 0xfe44; + t.whitediamond = 0x25c7; + t.whitediamondcontainingblacksmalldiamond = 0x25c8; + t.whitedownpointingsmalltriangle = 0x25bf; + t.whitedownpointingtriangle = 0x25bd; + t.whiteleftpointingsmalltriangle = 0x25c3; + t.whiteleftpointingtriangle = 0x25c1; + t.whitelenticularbracketleft = 0x3016; + t.whitelenticularbracketright = 0x3017; + t.whiterightpointingsmalltriangle = 0x25b9; + t.whiterightpointingtriangle = 0x25b7; + t.whitesmallsquare = 0x25ab; + t.whitesmilingface = 0x263a; + t.whitesquare = 0x25a1; + t.whitestar = 0x2606; + t.whitetelephone = 0x260f; + t.whitetortoiseshellbracketleft = 0x3018; + t.whitetortoiseshellbracketright = 0x3019; + t.whiteuppointingsmalltriangle = 0x25b5; + t.whiteuppointingtriangle = 0x25b3; + t.wihiragana = 0x3090; + t.wikatakana = 0x30f0; + t.wikorean = 0x315f; + t.wmonospace = 0xff57; + t.wohiragana = 0x3092; + t.wokatakana = 0x30f2; + t.wokatakanahalfwidth = 0xff66; + t.won = 0x20a9; + t.wonmonospace = 0xffe6; + t.wowaenthai = 0x0e27; + t.wparen = 0x24b2; + t.wring = 0x1e98; + t.wsuperior = 0x02b7; + t.wturned = 0x028d; + t.wynn = 0x01bf; + t.x = 0x0078; + t.xabovecmb = 0x033d; + t.xbopomofo = 0x3112; + t.xcircle = 0x24e7; + t.xdieresis = 0x1e8d; + t.xdotaccent = 0x1e8b; + t.xeharmenian = 0x056d; + t.xi = 0x03be; + t.xmonospace = 0xff58; + t.xparen = 0x24b3; + t.xsuperior = 0x02e3; + t.y = 0x0079; + t.yaadosquare = 0x334e; + t.yabengali = 0x09af; + t.yacute = 0x00fd; + t.yadeva = 0x092f; + t.yaekorean = 0x3152; + t.yagujarati = 0x0aaf; + t.yagurmukhi = 0x0a2f; + t.yahiragana = 0x3084; + t.yakatakana = 0x30e4; + t.yakatakanahalfwidth = 0xff94; + t.yakorean = 0x3151; + t.yamakkanthai = 0x0e4e; + t.yasmallhiragana = 0x3083; + t.yasmallkatakana = 0x30e3; + t.yasmallkatakanahalfwidth = 0xff6c; + t.yatcyrillic = 0x0463; + t.ycircle = 0x24e8; + t.ycircumflex = 0x0177; + t.ydieresis = 0x00ff; + t.ydotaccent = 0x1e8f; + t.ydotbelow = 0x1ef5; + t.yeharabic = 0x064a; + t.yehbarreearabic = 0x06d2; + t.yehbarreefinalarabic = 0xfbaf; + t.yehfinalarabic = 0xfef2; + t.yehhamzaabovearabic = 0x0626; + t.yehhamzaabovefinalarabic = 0xfe8a; + t.yehhamzaaboveinitialarabic = 0xfe8b; + t.yehhamzaabovemedialarabic = 0xfe8c; + t.yehinitialarabic = 0xfef3; + t.yehmedialarabic = 0xfef4; + t.yehmeeminitialarabic = 0xfcdd; + t.yehmeemisolatedarabic = 0xfc58; + t.yehnoonfinalarabic = 0xfc94; + t.yehthreedotsbelowarabic = 0x06d1; + t.yekorean = 0x3156; + t.yen = 0x00a5; + t.yenmonospace = 0xffe5; + t.yeokorean = 0x3155; + t.yeorinhieuhkorean = 0x3186; + t.yerahbenyomohebrew = 0x05aa; + t.yerahbenyomolefthebrew = 0x05aa; + t.yericyrillic = 0x044b; + t.yerudieresiscyrillic = 0x04f9; + t.yesieungkorean = 0x3181; + t.yesieungpansioskorean = 0x3183; + t.yesieungsioskorean = 0x3182; + t.yetivhebrew = 0x059a; + t.ygrave = 0x1ef3; + t.yhook = 0x01b4; + t.yhookabove = 0x1ef7; + t.yiarmenian = 0x0575; + t.yicyrillic = 0x0457; + t.yikorean = 0x3162; + t.yinyang = 0x262f; + t.yiwnarmenian = 0x0582; + t.ymonospace = 0xff59; + t.yod = 0x05d9; + t.yoddagesh = 0xfb39; + t.yoddageshhebrew = 0xfb39; + t.yodhebrew = 0x05d9; + t.yodyodhebrew = 0x05f2; + t.yodyodpatahhebrew = 0xfb1f; + t.yohiragana = 0x3088; + t.yoikorean = 0x3189; + t.yokatakana = 0x30e8; + t.yokatakanahalfwidth = 0xff96; + t.yokorean = 0x315b; + t.yosmallhiragana = 0x3087; + t.yosmallkatakana = 0x30e7; + t.yosmallkatakanahalfwidth = 0xff6e; + t.yotgreek = 0x03f3; + t.yoyaekorean = 0x3188; + t.yoyakorean = 0x3187; + t.yoyakthai = 0x0e22; + t.yoyingthai = 0x0e0d; + t.yparen = 0x24b4; + t.ypogegrammeni = 0x037a; + t.ypogegrammenigreekcmb = 0x0345; + t.yr = 0x01a6; + t.yring = 0x1e99; + t.ysuperior = 0x02b8; + t.ytilde = 0x1ef9; + t.yturned = 0x028e; + t.yuhiragana = 0x3086; + t.yuikorean = 0x318c; + t.yukatakana = 0x30e6; + t.yukatakanahalfwidth = 0xff95; + t.yukorean = 0x3160; + t.yusbigcyrillic = 0x046b; + t.yusbigiotifiedcyrillic = 0x046d; + t.yuslittlecyrillic = 0x0467; + t.yuslittleiotifiedcyrillic = 0x0469; + t.yusmallhiragana = 0x3085; + t.yusmallkatakana = 0x30e5; + t.yusmallkatakanahalfwidth = 0xff6d; + t.yuyekorean = 0x318b; + t.yuyeokorean = 0x318a; + t.yyabengali = 0x09df; + t.yyadeva = 0x095f; + t.z = 0x007a; + t.zaarmenian = 0x0566; + t.zacute = 0x017a; + t.zadeva = 0x095b; + t.zagurmukhi = 0x0a5b; + t.zaharabic = 0x0638; + t.zahfinalarabic = 0xfec6; + t.zahinitialarabic = 0xfec7; + t.zahiragana = 0x3056; + t.zahmedialarabic = 0xfec8; + t.zainarabic = 0x0632; + t.zainfinalarabic = 0xfeb0; + t.zakatakana = 0x30b6; + t.zaqefgadolhebrew = 0x0595; + t.zaqefqatanhebrew = 0x0594; + t.zarqahebrew = 0x0598; + t.zayin = 0x05d6; + t.zayindagesh = 0xfb36; + t.zayindageshhebrew = 0xfb36; + t.zayinhebrew = 0x05d6; + t.zbopomofo = 0x3117; + t.zcaron = 0x017e; + t.zcircle = 0x24e9; + t.zcircumflex = 0x1e91; + t.zcurl = 0x0291; + t.zdot = 0x017c; + t.zdotaccent = 0x017c; + t.zdotbelow = 0x1e93; + t.zecyrillic = 0x0437; + t.zedescendercyrillic = 0x0499; + t.zedieresiscyrillic = 0x04df; + t.zehiragana = 0x305c; + t.zekatakana = 0x30bc; + t.zero = 0x0030; + t.zeroarabic = 0x0660; + t.zerobengali = 0x09e6; + t.zerodeva = 0x0966; + t.zerogujarati = 0x0ae6; + t.zerogurmukhi = 0x0a66; + t.zerohackarabic = 0x0660; + t.zeroinferior = 0x2080; + t.zeromonospace = 0xff10; + t.zerooldstyle = 0xf730; + t.zeropersian = 0x06f0; + t.zerosuperior = 0x2070; + t.zerothai = 0x0e50; + t.zerowidthjoiner = 0xfeff; + t.zerowidthnonjoiner = 0x200c; + t.zerowidthspace = 0x200b; + t.zeta = 0x03b6; + t.zhbopomofo = 0x3113; + t.zhearmenian = 0x056a; + t.zhebrevecyrillic = 0x04c2; + t.zhecyrillic = 0x0436; + t.zhedescendercyrillic = 0x0497; + t.zhedieresiscyrillic = 0x04dd; + t.zihiragana = 0x3058; + t.zikatakana = 0x30b8; + t.zinorhebrew = 0x05ae; + t.zlinebelow = 0x1e95; + t.zmonospace = 0xff5a; + t.zohiragana = 0x305e; + t.zokatakana = 0x30be; + t.zparen = 0x24b5; + t.zretroflexhook = 0x0290; + t.zstroke = 0x01b6; + t.zuhiragana = 0x305a; + t.zukatakana = 0x30ba; + t[".notdef"] = 0x0000; + + // TeX-specific glyph names. + t.angbracketleftbig = 0x2329; + t.angbracketleftBig = 0x2329; + t.angbracketleftbigg = 0x2329; + t.angbracketleftBigg = 0x2329; + t.angbracketrightBig = 0x232a; + t.angbracketrightbig = 0x232a; + t.angbracketrightBigg = 0x232a; + t.angbracketrightbigg = 0x232a; + t.arrowhookleft = 0x21aa; + t.arrowhookright = 0x21a9; + t.arrowlefttophalf = 0x21bc; + t.arrowleftbothalf = 0x21bd; + t.arrownortheast = 0x2197; + t.arrownorthwest = 0x2196; + t.arrowrighttophalf = 0x21c0; + t.arrowrightbothalf = 0x21c1; + t.arrowsoutheast = 0x2198; + t.arrowsouthwest = 0x2199; + t.backslashbig = 0x2216; + t.backslashBig = 0x2216; + t.backslashBigg = 0x2216; + t.backslashbigg = 0x2216; + t.bardbl = 0x2016; + t.bracehtipdownleft = 0xfe37; + t.bracehtipdownright = 0xfe37; + t.bracehtipupleft = 0xfe38; + t.bracehtipupright = 0xfe38; + t.braceleftBig = 0x007b; + t.braceleftbig = 0x007b; + t.braceleftbigg = 0x007b; + t.braceleftBigg = 0x007b; + t.bracerightBig = 0x007d; + t.bracerightbig = 0x007d; + t.bracerightbigg = 0x007d; + t.bracerightBigg = 0x007d; + t.bracketleftbig = 0x005b; + t.bracketleftBig = 0x005b; + t.bracketleftbigg = 0x005b; + t.bracketleftBigg = 0x005b; + t.bracketrightBig = 0x005d; + t.bracketrightbig = 0x005d; + t.bracketrightbigg = 0x005d; + t.bracketrightBigg = 0x005d; + t.ceilingleftbig = 0x2308; + t.ceilingleftBig = 0x2308; + t.ceilingleftBigg = 0x2308; + t.ceilingleftbigg = 0x2308; + t.ceilingrightbig = 0x2309; + t.ceilingrightBig = 0x2309; + t.ceilingrightbigg = 0x2309; + t.ceilingrightBigg = 0x2309; + t.circledotdisplay = 0x2299; + t.circledottext = 0x2299; + t.circlemultiplydisplay = 0x2297; + t.circlemultiplytext = 0x2297; + t.circleplusdisplay = 0x2295; + t.circleplustext = 0x2295; + t.contintegraldisplay = 0x222e; + t.contintegraltext = 0x222e; + t.coproductdisplay = 0x2210; + t.coproducttext = 0x2210; + t.floorleftBig = 0x230a; + t.floorleftbig = 0x230a; + t.floorleftbigg = 0x230a; + t.floorleftBigg = 0x230a; + t.floorrightbig = 0x230b; + t.floorrightBig = 0x230b; + t.floorrightBigg = 0x230b; + t.floorrightbigg = 0x230b; + t.hatwide = 0x0302; + t.hatwider = 0x0302; + t.hatwidest = 0x0302; + t.intercal = 0x1d40; + t.integraldisplay = 0x222b; + t.integraltext = 0x222b; + t.intersectiondisplay = 0x22c2; + t.intersectiontext = 0x22c2; + t.logicalanddisplay = 0x2227; + t.logicalandtext = 0x2227; + t.logicalordisplay = 0x2228; + t.logicalortext = 0x2228; + t.parenleftBig = 0x0028; + t.parenleftbig = 0x0028; + t.parenleftBigg = 0x0028; + t.parenleftbigg = 0x0028; + t.parenrightBig = 0x0029; + t.parenrightbig = 0x0029; + t.parenrightBigg = 0x0029; + t.parenrightbigg = 0x0029; + t.prime = 0x2032; + t.productdisplay = 0x220f; + t.producttext = 0x220f; + t.radicalbig = 0x221a; + t.radicalBig = 0x221a; + t.radicalBigg = 0x221a; + t.radicalbigg = 0x221a; + t.radicalbt = 0x221a; + t.radicaltp = 0x221a; + t.radicalvertex = 0x221a; + t.slashbig = 0x002f; + t.slashBig = 0x002f; + t.slashBigg = 0x002f; + t.slashbigg = 0x002f; + t.summationdisplay = 0x2211; + t.summationtext = 0x2211; + t.tildewide = 0x02dc; + t.tildewider = 0x02dc; + t.tildewidest = 0x02dc; + t.uniondisplay = 0x22c3; + t.unionmultidisplay = 0x228e; + t.unionmultitext = 0x228e; + t.unionsqdisplay = 0x2294; + t.unionsqtext = 0x2294; + t.uniontext = 0x22c3; + t.vextenddouble = 0x2225; + t.vextendsingle = 0x2223; +}); + +const getDingbatsGlyphsUnicode = getLookupTableFactory(function (t) { + t.space = 0x0020; + t.a1 = 0x2701; + t.a2 = 0x2702; + t.a202 = 0x2703; + t.a3 = 0x2704; + t.a4 = 0x260e; + t.a5 = 0x2706; + t.a119 = 0x2707; + t.a118 = 0x2708; + t.a117 = 0x2709; + t.a11 = 0x261b; + t.a12 = 0x261e; + t.a13 = 0x270c; + t.a14 = 0x270d; + t.a15 = 0x270e; + t.a16 = 0x270f; + t.a105 = 0x2710; + t.a17 = 0x2711; + t.a18 = 0x2712; + t.a19 = 0x2713; + t.a20 = 0x2714; + t.a21 = 0x2715; + t.a22 = 0x2716; + t.a23 = 0x2717; + t.a24 = 0x2718; + t.a25 = 0x2719; + t.a26 = 0x271a; + t.a27 = 0x271b; + t.a28 = 0x271c; + t.a6 = 0x271d; + t.a7 = 0x271e; + t.a8 = 0x271f; + t.a9 = 0x2720; + t.a10 = 0x2721; + t.a29 = 0x2722; + t.a30 = 0x2723; + t.a31 = 0x2724; + t.a32 = 0x2725; + t.a33 = 0x2726; + t.a34 = 0x2727; + t.a35 = 0x2605; + t.a36 = 0x2729; + t.a37 = 0x272a; + t.a38 = 0x272b; + t.a39 = 0x272c; + t.a40 = 0x272d; + t.a41 = 0x272e; + t.a42 = 0x272f; + t.a43 = 0x2730; + t.a44 = 0x2731; + t.a45 = 0x2732; + t.a46 = 0x2733; + t.a47 = 0x2734; + t.a48 = 0x2735; + t.a49 = 0x2736; + t.a50 = 0x2737; + t.a51 = 0x2738; + t.a52 = 0x2739; + t.a53 = 0x273a; + t.a54 = 0x273b; + t.a55 = 0x273c; + t.a56 = 0x273d; + t.a57 = 0x273e; + t.a58 = 0x273f; + t.a59 = 0x2740; + t.a60 = 0x2741; + t.a61 = 0x2742; + t.a62 = 0x2743; + t.a63 = 0x2744; + t.a64 = 0x2745; + t.a65 = 0x2746; + t.a66 = 0x2747; + t.a67 = 0x2748; + t.a68 = 0x2749; + t.a69 = 0x274a; + t.a70 = 0x274b; + t.a71 = 0x25cf; + t.a72 = 0x274d; + t.a73 = 0x25a0; + t.a74 = 0x274f; + t.a203 = 0x2750; + t.a75 = 0x2751; + t.a204 = 0x2752; + t.a76 = 0x25b2; + t.a77 = 0x25bc; + t.a78 = 0x25c6; + t.a79 = 0x2756; + t.a81 = 0x25d7; + t.a82 = 0x2758; + t.a83 = 0x2759; + t.a84 = 0x275a; + t.a97 = 0x275b; + t.a98 = 0x275c; + t.a99 = 0x275d; + t.a100 = 0x275e; + t.a101 = 0x2761; + t.a102 = 0x2762; + t.a103 = 0x2763; + t.a104 = 0x2764; + t.a106 = 0x2765; + t.a107 = 0x2766; + t.a108 = 0x2767; + t.a112 = 0x2663; + t.a111 = 0x2666; + t.a110 = 0x2665; + t.a109 = 0x2660; + t.a120 = 0x2460; + t.a121 = 0x2461; + t.a122 = 0x2462; + t.a123 = 0x2463; + t.a124 = 0x2464; + t.a125 = 0x2465; + t.a126 = 0x2466; + t.a127 = 0x2467; + t.a128 = 0x2468; + t.a129 = 0x2469; + t.a130 = 0x2776; + t.a131 = 0x2777; + t.a132 = 0x2778; + t.a133 = 0x2779; + t.a134 = 0x277a; + t.a135 = 0x277b; + t.a136 = 0x277c; + t.a137 = 0x277d; + t.a138 = 0x277e; + t.a139 = 0x277f; + t.a140 = 0x2780; + t.a141 = 0x2781; + t.a142 = 0x2782; + t.a143 = 0x2783; + t.a144 = 0x2784; + t.a145 = 0x2785; + t.a146 = 0x2786; + t.a147 = 0x2787; + t.a148 = 0x2788; + t.a149 = 0x2789; + t.a150 = 0x278a; + t.a151 = 0x278b; + t.a152 = 0x278c; + t.a153 = 0x278d; + t.a154 = 0x278e; + t.a155 = 0x278f; + t.a156 = 0x2790; + t.a157 = 0x2791; + t.a158 = 0x2792; + t.a159 = 0x2793; + t.a160 = 0x2794; + t.a161 = 0x2192; + t.a163 = 0x2194; + t.a164 = 0x2195; + t.a196 = 0x2798; + t.a165 = 0x2799; + t.a192 = 0x279a; + t.a166 = 0x279b; + t.a167 = 0x279c; + t.a168 = 0x279d; + t.a169 = 0x279e; + t.a170 = 0x279f; + t.a171 = 0x27a0; + t.a172 = 0x27a1; + t.a173 = 0x27a2; + t.a162 = 0x27a3; + t.a174 = 0x27a4; + t.a175 = 0x27a5; + t.a176 = 0x27a6; + t.a177 = 0x27a7; + t.a178 = 0x27a8; + t.a179 = 0x27a9; + t.a193 = 0x27aa; + t.a180 = 0x27ab; + t.a199 = 0x27ac; + t.a181 = 0x27ad; + t.a200 = 0x27ae; + t.a182 = 0x27af; + t.a201 = 0x27b1; + t.a183 = 0x27b2; + t.a184 = 0x27b3; + t.a197 = 0x27b4; + t.a185 = 0x27b5; + t.a194 = 0x27b6; + t.a198 = 0x27b7; + t.a186 = 0x27b8; + t.a195 = 0x27b9; + t.a187 = 0x27ba; + t.a188 = 0x27bb; + t.a189 = 0x27bc; + t.a190 = 0x27bd; + t.a191 = 0x27be; + t.a89 = 0x2768; // 0xF8D7 + t.a90 = 0x2769; // 0xF8D8 + t.a93 = 0x276a; // 0xF8D9 + t.a94 = 0x276b; // 0xF8DA + t.a91 = 0x276c; // 0xF8DB + t.a92 = 0x276d; // 0xF8DC + t.a205 = 0x276e; // 0xF8DD + t.a85 = 0x276f; // 0xF8DE + t.a206 = 0x2770; // 0xF8DF + t.a86 = 0x2771; // 0xF8E0 + t.a87 = 0x2772; // 0xF8E1 + t.a88 = 0x2773; // 0xF8E2 + t.a95 = 0x2774; // 0xF8E3 + t.a96 = 0x2775; // 0xF8E4 + t[".notdef"] = 0x0000; +}); + +export { getDingbatsGlyphsUnicode, getGlyphsUnicode }; diff --git a/frontend/public/pdf.js/src/core/helvetica_factors.js b/frontend/public/pdf.js/src/core/helvetica_factors.js new file mode 100644 index 0000000..6b3847e --- /dev/null +++ b/frontend/public/pdf.js/src/core/helvetica_factors.js @@ -0,0 +1,352 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +// Factors to rescale LiberationSans-Bold.ttf to have the same +// metrics as NimbusSans-Bold.otf. +const HelveticaBoldFactors = [ + 0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, + 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, + 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, + 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, + 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, + 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, + 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, + 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, + 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, + 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, + 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, + 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, + 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, + 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, + 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, + 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, + 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, + 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, + 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, + 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, + 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 0.99977, 0.99973, 0.99977, 1.03374, 0.99977, 1.00026, 1.00001, 0.99973, + 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, + 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, + 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, + 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.00042, 0.99973, 0.99973, 1.0006, + 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.03828, + 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, + 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, + 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, + 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, + 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, + 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, + 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, + 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, + 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, + 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, + 0.99998, 1.00034, 0.99977, 1, 0.99997, 1.00026, 1.00078, 1.00036, 0.99973, + 1.00013, 1.0006, 0.99977, 0.99977, 0.99988, 0.85148, 1.00001, 1.00026, + 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 1.00069, + 1.00022, 0.99977, 1.00001, 0.99984, 1.00026, 1.00001, 1.00024, 1.00001, + 0.9999, 1, 1.0006, 1.00001, 1.00041, 0.99962, 1.00026, 1.0006, 0.99995, + 1.00041, 0.99942, 0.99973, 0.99927, 1.00082, 0.99902, 1.00026, 1.00087, + 1.0006, 1.00069, 0.99973, 0.99867, 0.99973, 0.9993, 1.00026, 1.00049, 1.00056, + 1, 0.99988, 0.99935, 0.99995, 0.99954, 1.00055, 0.99945, 1.00032, 1.0006, + 0.99995, 1.00026, 0.99995, 1.00032, 1.00001, 1.00008, 0.99971, 1.00019, + 0.9994, 1.00001, 1.0006, 1.00044, 0.99973, 1.00023, 1.00047, 1, 0.99942, + 0.99561, 0.99989, 1.00035, 0.99977, 1.00035, 0.99977, 1.00019, 0.99944, + 1.00001, 1.00021, 0.99926, 1.00035, 1.00035, 0.99942, 1.00048, 0.99999, + 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.99989, 1.00057, + 1.00001, 0.99936, 1.00052, 1.00012, 0.99996, 1.00043, 1, 1.00035, 0.9994, + 0.99976, 1.00035, 0.99973, 1.00052, 1.00041, 1.00119, 1.00037, 0.99973, + 1.00002, 0.99986, 1.00041, 1.00041, 0.99902, 0.9996, 1.00034, 0.99999, + 1.00026, 0.99999, 1.00026, 0.99973, 1.00052, 0.99973, 1, 0.99973, 1.00041, + 1.00075, 0.9994, 1.0003, 0.99999, 1, 1.00041, 0.99955, 1, 0.99915, 0.99973, + 0.99973, 1.00026, 1.00119, 0.99955, 0.99973, 1.0006, 0.99911, 1.0006, 1.00026, + 0.99972, 1.00026, 0.99902, 1.00041, 0.99973, 0.99999, 1, 1, 1.00038, 1.0005, + 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, + 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, + 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, + 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 1.00047, 1.00023, 1, + 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, + 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, + 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, + 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, + 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, + 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, + 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, + 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const HelveticaBoldMetrics = { lineHeight: 1.2, lineGap: 0.2 }; + +// Factors to rescale LiberationSans-BoldItalic.ttf to have the same +// metrics as NimbusSans-BoldItalic.otf. +const HelveticaBoldItalicFactors = [ + 0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, + 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, + 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, + 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, + 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, + 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, + 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, + 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, + 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, + 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, + 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, + 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, + 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, + 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, + 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, + 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, + 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, + 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, + 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, + 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, + 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 0.99977, 0.99973, 0.99977, 1.0044, 0.99977, 1.00026, 1.00001, 0.99973, + 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, + 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, + 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, + 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99971, 0.99973, 0.99973, 1.0006, + 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.01011, + 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, + 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, + 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, + 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, + 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, + 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, + 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, + 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, + 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, + 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, + 0.99998, 0.99998, 0.99977, 1, 1, 1.00026, 0.99969, 0.99972, 0.99981, 0.9998, + 1.0006, 0.99977, 0.99977, 1.00022, 0.91155, 1.00001, 1.00026, 0.99977, + 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 0.99966, 1.00022, + 1.00032, 1.00001, 0.99944, 1.00026, 1.00001, 0.99968, 1.00001, 1.00047, 1, + 1.0006, 1.00001, 0.99981, 1.00101, 1.00026, 1.0006, 0.99948, 0.99981, 1.00064, + 0.99973, 0.99942, 1.00101, 1.00061, 1.00026, 1.00069, 1.0006, 1.00014, + 0.99973, 1.01322, 0.99973, 1.00065, 1.00026, 1.00012, 0.99923, 1, 1.00064, + 1.00076, 0.99948, 1.00055, 1.00063, 1.00007, 0.99943, 1.0006, 0.99948, + 1.00026, 0.99948, 0.99943, 1.00001, 1.00001, 1.00029, 1.00038, 1.00035, + 1.00001, 1.0006, 1.0006, 0.99973, 0.99978, 1.00001, 1.00057, 0.99989, 0.99967, + 0.99964, 0.99967, 0.99977, 0.99999, 0.99977, 1.00038, 0.99977, 1.00001, + 0.99973, 1.00066, 0.99967, 0.99967, 1.00041, 0.99998, 0.99999, 0.99977, + 1.00022, 0.99967, 1.00001, 0.99977, 1.00026, 0.99964, 1.00031, 1.00001, + 0.99999, 0.99999, 1, 1.00023, 1, 1, 0.99999, 1.00035, 1.00001, 0.99999, + 0.99973, 0.99977, 0.99999, 1.00058, 0.99973, 0.99973, 0.99955, 0.9995, + 1.00026, 1.00026, 1.00032, 0.99989, 1.00034, 0.99999, 1.00026, 1.00026, + 1.00026, 0.99973, 0.45998, 0.99973, 1.00026, 0.99973, 1.00001, 0.99999, + 0.99982, 0.99994, 0.99996, 1, 1.00042, 1.00044, 1.00029, 1.00023, 0.99973, + 0.99973, 1.00026, 0.99949, 1.00002, 0.99973, 1.0006, 1.0006, 1.0006, 0.99975, + 1.00026, 1.00026, 1.00032, 0.98685, 0.99973, 1.00026, 1, 1, 0.99966, 1.00044, + 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, + 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, + 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, + 1.66475, 1, 0.99973, 0.99973, 1, 0.99973, 0.99971, 0.99978, 1, 1, 0.99991, + 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00098, 1, 1, 1, 1.00049, 1, 1, + 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, + 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, + 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, + 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, + 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, + 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, + 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const HelveticaBoldItalicMetrics = { lineHeight: 1.35, lineGap: 0.2 }; + +// Factors to rescale LiberationSans-Italic.ttf to have the same +// metrics as NimbusSans-Italic.otf. +const HelveticaItalicFactors = [ + 0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, + 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, + 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, + 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, + 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, + 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, + 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, + 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, + 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, + 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, + 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, + 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, + 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, + 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, + 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, + 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, + 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, + 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, + 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, + 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, + 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.0288, + 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, + 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, + 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, + 1.0006, 0.99946, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, + 0.99973, 0.99924, 0.99973, 1.06311, 0.99973, 1.00024, 0.99973, 0.99924, + 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00041, 0.9998, + 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, + 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, + 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.89547, 1.00026, 1.0006, + 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, + 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, + 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, + 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, + 0.99998, 1.00001, 1, 1.00054, 0.99977, 1.00084, 1.00007, 0.99973, 1.00013, + 0.99924, 1.00001, 1.00001, 0.99945, 0.91221, 1.00001, 1.00026, 0.99977, + 1.00022, 1.0006, 1.00001, 1.00001, 0.99999, 0.99977, 0.99933, 1.00022, + 1.00054, 1.00001, 1.00065, 1.00026, 1.00001, 1.0001, 1.00001, 1.00052, 1, + 1.0006, 1.00001, 0.99945, 0.99897, 0.99968, 0.99924, 1.00036, 0.99945, + 0.99949, 1, 1.0006, 0.99897, 0.99918, 0.99968, 0.99911, 0.99924, 1, 0.99962, + 1.01487, 1, 1.0005, 0.99973, 1.00012, 1.00043, 1, 0.99995, 0.99994, 1.00036, + 0.99947, 1.00019, 1.00063, 1.00025, 0.99924, 1.00036, 0.99973, 1.00036, + 1.00025, 1.00001, 1.00001, 1.00027, 1.0001, 1.00068, 1.00001, 1.0006, 1.0006, + 1, 1.00008, 0.99957, 0.99972, 0.9994, 0.99954, 0.99975, 1.00051, 1.00001, + 1.00019, 1.00001, 1.0001, 0.99986, 1.00001, 1.00001, 1.00038, 0.99954, + 0.99954, 0.9994, 1.00066, 0.99999, 0.99977, 1.00022, 1.00054, 1.00001, + 0.99977, 1.00026, 0.99975, 1.0001, 1.00001, 0.99993, 0.9995, 0.99955, 1.00016, + 0.99978, 0.99974, 1.00019, 1.00022, 0.99955, 1.00053, 0.99973, 1.00089, + 1.00005, 0.99967, 1.00048, 0.99973, 1.00002, 1.00034, 0.99973, 0.99973, + 0.99964, 1.00006, 1.00066, 0.99947, 0.99973, 0.98894, 0.99973, 1, 0.44898, 1, + 0.99946, 1, 1.00039, 1.00082, 0.99991, 0.99991, 0.99985, 1.00022, 1.00023, + 1.00061, 1.00006, 0.99966, 0.99973, 0.99973, 0.99973, 1.00019, 1.0008, 1, + 0.99924, 0.99924, 0.99924, 0.99983, 1.00044, 0.99973, 0.99964, 0.98332, 1, + 0.99973, 1, 1, 0.99962, 0.99895, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, + 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, + 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, + 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, + 0.99973, 1.00423, 0.99925, 0.99999, 1, 0.99991, 0.99984, 1.00002, 1.00002, + 1.00002, 1.00002, 1.00049, 1, 1.00245, 1, 1, 1, 1, 0.96329, 1, 1.20985, + 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, + 1.00003, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, + 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, + 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, + 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const HelveticaItalicMetrics = { lineHeight: 1.35, lineGap: 0.2 }; + +// Factors to rescale LiberationSans-Regular.ttf to have the same +// metrics as NimbusSans-Regular.otf. +const HelveticaRegularFactors = [ + 0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, + 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, + 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, + 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, + 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, + 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, + 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, + 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, + 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, + 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, + 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, + 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, + 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, + 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, + 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, + 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, + 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, + 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, + 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, + 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, + 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, + 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.04596, + 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, + 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, + 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, + 1.0006, 1.00019, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, + 0.99973, 0.99924, 0.99973, 1.02572, 0.99973, 1.00005, 0.99973, 0.99924, + 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99999, 0.9998, + 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, + 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, + 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.84533, 1.00026, 1.0006, + 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, + 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, + 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, + 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, + 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, + 0.99998, 0.99928, 1, 0.99977, 1.00013, 1.00055, 0.99947, 0.99945, 0.99941, + 0.99924, 1.00001, 1.00001, 1.0004, 0.91621, 1.00001, 1.00026, 0.99977, + 1.00022, 1.0006, 1.00001, 1.00005, 0.99999, 0.99977, 1.00015, 1.00022, + 0.99977, 1.00001, 0.99973, 1.00026, 1.00001, 1.00019, 1.00001, 0.99946, 1, + 1.0006, 1.00001, 0.99978, 1.00045, 0.99973, 0.99924, 1.00023, 0.99978, + 0.99966, 1, 1.00065, 1.00045, 1.00019, 0.99973, 0.99973, 0.99924, 1, 1, + 0.96499, 1, 1.00055, 0.99973, 1.00008, 1.00027, 1, 0.9997, 0.99995, 1.00023, + 0.99933, 1.00019, 1.00015, 1.00031, 0.99924, 1.00023, 0.99973, 1.00023, + 1.00031, 1.00001, 0.99928, 1.00029, 1.00092, 1.00035, 1.00001, 1.0006, 1.0006, + 1, 0.99988, 0.99975, 1, 1.00082, 0.99561, 0.9996, 1.00035, 1.00001, 0.99962, + 1.00001, 1.00092, 0.99964, 1.00001, 0.99963, 0.99999, 1.00035, 1.00035, + 1.00082, 0.99962, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, + 1.00026, 0.9996, 0.99967, 1.00001, 1.00034, 1.00074, 1.00054, 1.00053, + 1.00063, 0.99971, 0.99962, 1.00035, 0.99975, 0.99977, 0.99973, 1.00043, + 0.99953, 1.0007, 0.99915, 0.99973, 1.00008, 0.99892, 1.00073, 1.00073, + 1.00114, 0.99915, 1.00073, 0.99955, 0.99973, 1.00092, 0.99973, 1, 0.99998, 1, + 1.0003, 1, 1.00043, 1.00001, 0.99969, 1.0003, 1, 1.00035, 1.00001, 0.9995, 1, + 1.00092, 0.99973, 0.99973, 0.99973, 1.0007, 0.9995, 1, 0.99924, 1.0006, + 0.99924, 0.99972, 1.00062, 0.99973, 1.00114, 1.00073, 1, 0.99955, 1, 1, + 1.00047, 0.99968, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, + 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, + 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, + 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, + 0.99971, 0.99925, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, + 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, + 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, + 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, + 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, + 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, + 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const HelveticaRegularMetrics = { lineHeight: 1.2, lineGap: 0.2 }; + +export { + HelveticaBoldFactors, + HelveticaBoldItalicFactors, + HelveticaBoldItalicMetrics, + HelveticaBoldMetrics, + HelveticaItalicFactors, + HelveticaItalicMetrics, + HelveticaRegularFactors, + HelveticaRegularMetrics, +}; diff --git a/frontend/public/pdf.js/src/core/image.js b/frontend/public/pdf.js/src/core/image.js new file mode 100644 index 0000000..7ab44bc --- /dev/null +++ b/frontend/public/pdf.js/src/core/image.js @@ -0,0 +1,1076 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + assert, + FeatureTest, + FormatError, + ImageKind, + warn, +} from "../shared/util.js"; +import { + convertBlackAndWhiteToRGBA, + convertToRGBA, +} from "../shared/image_utils.js"; +import { BaseStream } from "./base_stream.js"; +import { ColorSpace } from "./colorspace.js"; +import { DecodeStream } from "./decode_stream.js"; +import { ImageResizer } from "./image_resizer.js"; +import { JpegStream } from "./jpeg_stream.js"; +import { JpxImage } from "./jpx.js"; +import { Name } from "./primitives.js"; + +/** + * Decode and clamp a value. The formula is different from the spec because we + * don't decode to float range [0,1], we decode it in the [0,max] range. + */ +function decodeAndClamp(value, addend, coefficient, max) { + value = addend + value * coefficient; + // Clamp the value to the range + if (value < 0) { + value = 0; + } else if (value > max) { + value = max; + } + return value; +} + +/** + * Resizes an image mask with 1 component. + * @param {TypedArray} src - The source buffer. + * @param {number} bpc - Number of bits per component. + * @param {number} w1 - Original width. + * @param {number} h1 - Original height. + * @param {number} w2 - New width. + * @param {number} h2 - New height. + * @returns {TypedArray} The resized image mask buffer. + */ +function resizeImageMask(src, bpc, w1, h1, w2, h2) { + const length = w2 * h2; + let dest; + if (bpc <= 8) { + dest = new Uint8Array(length); + } else if (bpc <= 16) { + dest = new Uint16Array(length); + } else { + dest = new Uint32Array(length); + } + const xRatio = w1 / w2; + const yRatio = h1 / h2; + let i, + j, + py, + newIndex = 0, + oldIndex; + const xScaled = new Uint16Array(w2); + const w1Scanline = w1; + + for (i = 0; i < w2; i++) { + xScaled[i] = Math.floor(i * xRatio); + } + for (i = 0; i < h2; i++) { + py = Math.floor(i * yRatio) * w1Scanline; + for (j = 0; j < w2; j++) { + oldIndex = py + xScaled[j]; + dest[newIndex++] = src[oldIndex]; + } + } + return dest; +} + +class PDFImage { + constructor({ + xref, + res, + image, + isInline = false, + smask = null, + mask = null, + isMask = false, + pdfFunctionFactory, + localColorSpaceCache, + }) { + this.image = image; + const dict = image.dict; + + const filter = dict.get("F", "Filter"); + let filterName; + if (filter instanceof Name) { + filterName = filter.name; + } else if (Array.isArray(filter)) { + const filterZero = xref.fetchIfRef(filter[0]); + if (filterZero instanceof Name) { + filterName = filterZero.name; + } + } + switch (filterName) { + case "JPXDecode": + ({ + width: image.width, + height: image.height, + componentsCount: image.numComps, + bitsPerComponent: image.bitsPerComponent, + } = JpxImage.parseImageProperties(image.stream)); + image.stream.reset(); + this.jpxDecoderOptions = { + numComponents: 0, + isIndexedColormap: false, + smaskInData: dict.has("SMaskInData"), + }; + break; + case "JBIG2Decode": + image.bitsPerComponent = 1; + image.numComps = 1; + break; + } + + let width = dict.get("W", "Width"); + let height = dict.get("H", "Height"); + + if ( + Number.isInteger(image.width) && + image.width > 0 && + Number.isInteger(image.height) && + image.height > 0 && + (image.width !== width || image.height !== height) + ) { + warn( + "PDFImage - using the Width/Height of the image data, " + + "rather than the image dictionary." + ); + width = image.width; + height = image.height; + } + if (width < 1 || height < 1) { + throw new FormatError( + `Invalid image width: ${width} or height: ${height}` + ); + } + this.width = width; + this.height = height; + + this.interpolate = dict.get("I", "Interpolate"); + this.imageMask = dict.get("IM", "ImageMask") || false; + this.matte = dict.get("Matte") || false; + + let bitsPerComponent = image.bitsPerComponent; + if (!bitsPerComponent) { + bitsPerComponent = dict.get("BPC", "BitsPerComponent"); + if (!bitsPerComponent) { + if (this.imageMask) { + bitsPerComponent = 1; + } else { + throw new FormatError( + `Bits per component missing in image: ${this.imageMask}` + ); + } + } + } + this.bpc = bitsPerComponent; + + if (!this.imageMask) { + let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace"); + const hasColorSpace = !!colorSpace; + if (!hasColorSpace) { + if (this.jpxDecoderOptions) { + colorSpace = Name.get("DeviceRGBA"); + } else { + switch (image.numComps) { + case 1: + colorSpace = Name.get("DeviceGray"); + break; + case 3: + colorSpace = Name.get("DeviceRGB"); + break; + case 4: + colorSpace = Name.get("DeviceCMYK"); + break; + default: + throw new Error( + `Images with ${image.numComps} color components not supported.` + ); + } + } + } else if (this.jpxDecoderOptions?.smaskInData) { + // If the jpx image has a color space then it mustn't be used in order + // to be able to use the color space that comes from the pdf. + colorSpace = Name.get("DeviceRGBA"); + } + + this.colorSpace = ColorSpace.parse({ + cs: colorSpace, + xref, + resources: isInline ? res : null, + pdfFunctionFactory, + localColorSpaceCache, + }); + this.numComps = this.colorSpace.numComps; + + if (this.jpxDecoderOptions) { + this.jpxDecoderOptions.numComponents = hasColorSpace + ? this.numComps + : 0; + // If the jpx image has a color space then it musn't be used in order to + // be able to use the color space that comes from the pdf. + this.jpxDecoderOptions.isIndexedColormap = + this.colorSpace.name === "Indexed"; + } + } + + this.decode = dict.getArray("D", "Decode"); + this.needsDecode = false; + if ( + this.decode && + ((this.colorSpace && + !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent)) || + (isMask && + !ColorSpace.isDefaultDecode(this.decode, /* numComps = */ 1))) + ) { + this.needsDecode = true; + // Do some preprocessing to avoid more math. + const max = (1 << bitsPerComponent) - 1; + this.decodeCoefficients = []; + this.decodeAddends = []; + const isIndexed = this.colorSpace?.name === "Indexed"; + for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) { + const dmin = this.decode[i]; + const dmax = this.decode[i + 1]; + this.decodeCoefficients[j] = isIndexed + ? (dmax - dmin) / max + : dmax - dmin; + this.decodeAddends[j] = isIndexed ? dmin : max * dmin; + } + } + + if (smask) { + this.smask = new PDFImage({ + xref, + res, + image: smask, + isInline, + pdfFunctionFactory, + localColorSpaceCache, + }); + } else if (mask) { + if (mask instanceof BaseStream) { + const maskDict = mask.dict, + imageMask = maskDict.get("IM", "ImageMask"); + if (!imageMask) { + warn("Ignoring /Mask in image without /ImageMask."); + } else { + this.mask = new PDFImage({ + xref, + res, + image: mask, + isInline, + isMask: true, + pdfFunctionFactory, + localColorSpaceCache, + }); + } + } else { + // Color key mask (just an array). + this.mask = mask; + } + } + } + + /** + * Handles processing of image data and returns the Promise that is resolved + * with a PDFImage when the image is ready to be used. + */ + static async buildImage({ + xref, + res, + image, + isInline = false, + pdfFunctionFactory, + localColorSpaceCache, + }) { + const imageData = image; + let smaskData = null; + let maskData = null; + + const smask = image.dict.get("SMask"); + const mask = image.dict.get("Mask"); + + if (smask) { + if (smask instanceof BaseStream) { + smaskData = smask; + } else { + warn("Unsupported /SMask format."); + } + } else if (mask) { + if (mask instanceof BaseStream || Array.isArray(mask)) { + maskData = mask; + } else { + warn("Unsupported /Mask format."); + } + } + + return new PDFImage({ + xref, + res, + image: imageData, + isInline, + smask: smaskData, + mask: maskData, + pdfFunctionFactory, + localColorSpaceCache, + }); + } + + static createRawMask({ + imgArray, + width, + height, + imageIsFromDecodeStream, + inverseDecode, + interpolate, + }) { + // |imgArray| might not contain full data for every pixel of the mask, so + // we need to distinguish between |computedLength| and |actualLength|. + // In particular, if inverseDecode is true, then the array we return must + // have a length of |computedLength|. + + const computedLength = ((width + 7) >> 3) * height; + const actualLength = imgArray.byteLength; + const haveFullData = computedLength === actualLength; + let data, i; + + if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) { + // imgArray came from a DecodeStream and its data is in an appropriate + // form, so we can just transfer it. + data = imgArray; + } else if (!inverseDecode) { + data = new Uint8Array(imgArray); + } else { + data = new Uint8Array(computedLength); + data.set(imgArray); + data.fill(0xff, actualLength); + } + + // If necessary, invert the original mask data (but not any extra we might + // have added above). It's safe to modify the array -- whether it's the + // original or a copy, we're about to transfer it anyway, so nothing else + // in this thread can be relying on its contents. + if (inverseDecode) { + for (i = 0; i < actualLength; i++) { + data[i] ^= 0xff; + } + } + + return { data, width, height, interpolate }; + } + + static async createMask({ + imgArray, + width, + height, + imageIsFromDecodeStream, + inverseDecode, + interpolate, + isOffscreenCanvasSupported = false, + }) { + const isSingleOpaquePixel = + width === 1 && + height === 1 && + inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128)); + + if (isSingleOpaquePixel) { + return { isSingleOpaquePixel }; + } + + if (isOffscreenCanvasSupported) { + if (ImageResizer.needsToBeResized(width, height)) { + const data = new Uint8ClampedArray(width * height * 4); + convertBlackAndWhiteToRGBA({ + src: imgArray, + dest: data, + width, + height, + nonBlackColor: 0, + inverseDecode, + }); + return ImageResizer.createImage({ + kind: ImageKind.RGBA_32BPP, + data, + width, + height, + interpolate, + }); + } + + const canvas = new OffscreenCanvas(width, height); + const ctx = canvas.getContext("2d"); + const imgData = ctx.createImageData(width, height); + convertBlackAndWhiteToRGBA({ + src: imgArray, + dest: imgData.data, + width, + height, + nonBlackColor: 0, + inverseDecode, + }); + + ctx.putImageData(imgData, 0, 0); + const bitmap = canvas.transferToImageBitmap(); + + return { + data: null, + width, + height, + interpolate, + bitmap, + }; + } + + // Get the data almost as they're and they'll be decoded + // just before being drawn. + return this.createRawMask({ + imgArray, + width, + height, + inverseDecode, + imageIsFromDecodeStream, + interpolate, + }); + } + + get drawWidth() { + return Math.max(this.width, this.smask?.width || 0, this.mask?.width || 0); + } + + get drawHeight() { + return Math.max( + this.height, + this.smask?.height || 0, + this.mask?.height || 0 + ); + } + + decodeBuffer(buffer) { + const bpc = this.bpc; + const numComps = this.numComps; + + const decodeAddends = this.decodeAddends; + const decodeCoefficients = this.decodeCoefficients; + const max = (1 << bpc) - 1; + let i, ii; + + if (bpc === 1) { + // If the buffer needed decode that means it just needs to be inverted. + for (i = 0, ii = buffer.length; i < ii; i++) { + buffer[i] = +!buffer[i]; + } + return; + } + let index = 0; + for (i = 0, ii = this.width * this.height; i < ii; i++) { + for (let j = 0; j < numComps; j++) { + buffer[index] = decodeAndClamp( + buffer[index], + decodeAddends[j], + decodeCoefficients[j], + max + ); + index++; + } + } + } + + getComponents(buffer) { + const bpc = this.bpc; + + // This image doesn't require any extra work. + if (bpc === 8) { + return buffer; + } + + const width = this.width; + const height = this.height; + const numComps = this.numComps; + + const length = width * height * numComps; + let bufferPos = 0; + let output; + if (bpc <= 8) { + output = new Uint8Array(length); + } else if (bpc <= 16) { + output = new Uint16Array(length); + } else { + output = new Uint32Array(length); + } + const rowComps = width * numComps; + + const max = (1 << bpc) - 1; + let i = 0, + ii, + buf; + + if (bpc === 1) { + // Optimization for reading 1 bpc images. + let mask, loop1End, loop2End; + for (let j = 0; j < height; j++) { + loop1End = i + (rowComps & ~7); + loop2End = i + rowComps; + + // unroll loop for all full bytes + while (i < loop1End) { + buf = buffer[bufferPos++]; + output[i] = (buf >> 7) & 1; + output[i + 1] = (buf >> 6) & 1; + output[i + 2] = (buf >> 5) & 1; + output[i + 3] = (buf >> 4) & 1; + output[i + 4] = (buf >> 3) & 1; + output[i + 5] = (buf >> 2) & 1; + output[i + 6] = (buf >> 1) & 1; + output[i + 7] = buf & 1; + i += 8; + } + + // handle remaining bits + if (i < loop2End) { + buf = buffer[bufferPos++]; + mask = 128; + while (i < loop2End) { + output[i++] = +!!(buf & mask); + mask >>= 1; + } + } + } + } else { + // The general case that handles all other bpc values. + let bits = 0; + buf = 0; + for (i = 0, ii = length; i < ii; ++i) { + if (i % rowComps === 0) { + buf = 0; + bits = 0; + } + + while (bits < bpc) { + buf = (buf << 8) | buffer[bufferPos++]; + bits += 8; + } + + const remainingBits = bits - bpc; + let value = buf >> remainingBits; + if (value < 0) { + value = 0; + } else if (value > max) { + value = max; + } + output[i] = value; + buf &= (1 << remainingBits) - 1; + bits = remainingBits; + } + } + return output; + } + + async fillOpacity(rgbaBuf, width, height, actualHeight, image) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + rgbaBuf instanceof Uint8ClampedArray, + 'PDFImage.fillOpacity: Unsupported "rgbaBuf" type.' + ); + } + const smask = this.smask; + const mask = this.mask; + let alphaBuf, sw, sh, i, ii, j; + + if (smask) { + sw = smask.width; + sh = smask.height; + alphaBuf = new Uint8ClampedArray(sw * sh); + await smask.fillGrayBuffer(alphaBuf); + if (sw !== width || sh !== height) { + alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height); + } + } else if (mask) { + if (mask instanceof PDFImage) { + sw = mask.width; + sh = mask.height; + alphaBuf = new Uint8ClampedArray(sw * sh); + mask.numComps = 1; + await mask.fillGrayBuffer(alphaBuf); + + // Need to invert values in rgbaBuf + for (i = 0, ii = sw * sh; i < ii; ++i) { + alphaBuf[i] = 255 - alphaBuf[i]; + } + + if (sw !== width || sh !== height) { + alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height); + } + } else if (Array.isArray(mask)) { + // Color key mask: if any of the components are outside the range + // then they should be painted. + alphaBuf = new Uint8ClampedArray(width * height); + const numComps = this.numComps; + for (i = 0, ii = width * height; i < ii; ++i) { + let opacity = 0; + const imageOffset = i * numComps; + for (j = 0; j < numComps; ++j) { + const color = image[imageOffset + j]; + const maskOffset = j * 2; + if (color < mask[maskOffset] || color > mask[maskOffset + 1]) { + opacity = 255; + break; + } + } + alphaBuf[i] = opacity; + } + } else { + throw new FormatError("Unknown mask format."); + } + } + + if (alphaBuf) { + for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { + rgbaBuf[j] = alphaBuf[i]; + } + } else { + // No mask. + for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { + rgbaBuf[j] = 255; + } + } + } + + undoPreblend(buffer, width, height) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + buffer instanceof Uint8ClampedArray, + 'PDFImage.undoPreblend: Unsupported "buffer" type.' + ); + } + const matte = this.smask?.matte; + if (!matte) { + return; + } + const matteRgb = this.colorSpace.getRgb(matte, 0); + const matteR = matteRgb[0]; + const matteG = matteRgb[1]; + const matteB = matteRgb[2]; + const length = width * height * 4; + for (let i = 0; i < length; i += 4) { + const alpha = buffer[i + 3]; + if (alpha === 0) { + // according formula we have to get Infinity in all components + // making it white (typical paper color) should be okay + buffer[i] = 255; + buffer[i + 1] = 255; + buffer[i + 2] = 255; + continue; + } + const k = 255 / alpha; + buffer[i] = (buffer[i] - matteR) * k + matteR; + buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG; + buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB; + } + } + + async createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) { + const drawWidth = this.drawWidth; + const drawHeight = this.drawHeight; + const imgData = { + width: drawWidth, + height: drawHeight, + interpolate: this.interpolate, + kind: 0, + data: null, + // Other fields are filled in below. + }; + + const numComps = this.numComps; + const originalWidth = this.width; + const originalHeight = this.height; + const bpc = this.bpc; + + // Rows start at byte boundary. + const rowBytes = (originalWidth * numComps * bpc + 7) >> 3; + const mustBeResized = + isOffscreenCanvasSupported && + ImageResizer.needsToBeResized(drawWidth, drawHeight); + + if (!this.smask && !this.mask && this.colorSpace.name === "DeviceRGBA") { + imgData.kind = ImageKind.RGBA_32BPP; + const imgArray = (imgData.data = await this.getImageBytes( + originalHeight * originalWidth * 4, + {} + )); + + if (isOffscreenCanvasSupported) { + if (!mustBeResized) { + return this.createBitmap( + ImageKind.RGBA_32BPP, + drawWidth, + drawHeight, + imgArray + ); + } + return ImageResizer.createImage(imgData, false); + } + + return imgData; + } + + if (!forceRGBA) { + // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image + // without any complications, we pass a same-sized copy to the main + // thread rather than expanding by 32x to RGBA form. This saves *lots* + // of memory for many scanned documents. It's also much faster. + // + // Similarly, if it is a 24-bit-per pixel RGB image without any + // complications, we avoid expanding by 1.333x to RGBA form. + let kind; + if (this.colorSpace.name === "DeviceGray" && bpc === 1) { + kind = ImageKind.GRAYSCALE_1BPP; + } else if ( + this.colorSpace.name === "DeviceRGB" && + bpc === 8 && + !this.needsDecode + ) { + kind = ImageKind.RGB_24BPP; + } + if ( + kind && + !this.smask && + !this.mask && + drawWidth === originalWidth && + drawHeight === originalHeight + ) { + const image = await this.#getImage(originalWidth, originalHeight); + if (image) { + return image; + } + const data = await this.getImageBytes(originalHeight * rowBytes, {}); + if (isOffscreenCanvasSupported) { + if (mustBeResized) { + return ImageResizer.createImage( + { + data, + kind, + width: drawWidth, + height: drawHeight, + interpolate: this.interpolate, + }, + this.needsDecode + ); + } + return this.createBitmap(kind, originalWidth, originalHeight, data); + } + imgData.kind = kind; + imgData.data = data; + + if (this.needsDecode) { + // Invert the buffer (which must be grayscale if we reached here). + assert( + kind === ImageKind.GRAYSCALE_1BPP, + "PDFImage.createImageData: The image must be grayscale." + ); + const buffer = imgData.data; + for (let i = 0, ii = buffer.length; i < ii; i++) { + buffer[i] ^= 0xff; + } + } + return imgData; + } + if ( + this.image instanceof JpegStream && + !this.smask && + !this.mask && + !this.needsDecode + ) { + let imageLength = originalHeight * rowBytes; + if (isOffscreenCanvasSupported && !mustBeResized) { + let isHandled = false; + switch (this.colorSpace.name) { + case "DeviceGray": + // Avoid truncating the image, since `JpegImage.getData` + // will expand the image data when `forceRGB === true`. + imageLength *= 4; + isHandled = true; + break; + case "DeviceRGB": + imageLength = (imageLength / 3) * 4; + isHandled = true; + break; + case "DeviceCMYK": + isHandled = true; + break; + } + + if (isHandled) { + const image = await this.#getImage(drawWidth, drawHeight); + if (image) { + return image; + } + const rgba = await this.getImageBytes(imageLength, { + drawWidth, + drawHeight, + forceRGBA: true, + }); + return this.createBitmap( + ImageKind.RGBA_32BPP, + drawWidth, + drawHeight, + rgba + ); + } + } else { + switch (this.colorSpace.name) { + case "DeviceGray": + imageLength *= 3; + /* falls through */ + case "DeviceRGB": + case "DeviceCMYK": + imgData.kind = ImageKind.RGB_24BPP; + imgData.data = await this.getImageBytes(imageLength, { + drawWidth, + drawHeight, + forceRGB: true, + }); + if (mustBeResized) { + // The image is too big so we resize it. + return ImageResizer.createImage(imgData); + } + return imgData; + } + } + } + } + + const imgArray = await this.getImageBytes(originalHeight * rowBytes, { + internal: true, + }); + // imgArray can be incomplete (e.g. after CCITT fax encoding). + const actualHeight = + 0 | (((imgArray.length / rowBytes) * drawHeight) / originalHeight); + + const comps = this.getComponents(imgArray); + + // If opacity data is present, use RGBA_32BPP form. Otherwise, use the + // more compact RGB_24BPP form if allowable. + let alpha01, maybeUndoPreblend; + + let canvas, ctx, canvasImgData, data; + if (isOffscreenCanvasSupported && !mustBeResized) { + canvas = new OffscreenCanvas(drawWidth, drawHeight); + ctx = canvas.getContext("2d"); + canvasImgData = ctx.createImageData(drawWidth, drawHeight); + data = canvasImgData.data; + } + + imgData.kind = ImageKind.RGBA_32BPP; + + if (!forceRGBA && !this.smask && !this.mask) { + if (!isOffscreenCanvasSupported || mustBeResized) { + imgData.kind = ImageKind.RGB_24BPP; + data = new Uint8ClampedArray(drawWidth * drawHeight * 3); + alpha01 = 0; + } else { + const arr = new Uint32Array(data.buffer); + arr.fill(FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff); + alpha01 = 1; + } + maybeUndoPreblend = false; + } else { + if (!isOffscreenCanvasSupported || mustBeResized) { + data = new Uint8ClampedArray(drawWidth * drawHeight * 4); + } + + alpha01 = 1; + maybeUndoPreblend = true; + + // Color key masking (opacity) must be performed before decoding. + await this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps); + } + + if (this.needsDecode) { + this.decodeBuffer(comps); + } + this.colorSpace.fillRgb( + data, + originalWidth, + originalHeight, + drawWidth, + drawHeight, + actualHeight, + bpc, + comps, + alpha01 + ); + if (maybeUndoPreblend) { + this.undoPreblend(data, drawWidth, actualHeight); + } + + if (isOffscreenCanvasSupported && !mustBeResized) { + ctx.putImageData(canvasImgData, 0, 0); + const bitmap = canvas.transferToImageBitmap(); + + return { + data: null, + width: drawWidth, + height: drawHeight, + bitmap, + interpolate: this.interpolate, + }; + } + + imgData.data = data; + if (mustBeResized) { + return ImageResizer.createImage(imgData); + } + return imgData; + } + + async fillGrayBuffer(buffer) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + buffer instanceof Uint8ClampedArray, + 'PDFImage.fillGrayBuffer: Unsupported "buffer" type.' + ); + } + const numComps = this.numComps; + if (numComps !== 1) { + throw new FormatError( + `Reading gray scale from a color image: ${numComps}` + ); + } + + const width = this.width; + const height = this.height; + const bpc = this.bpc; + + // rows start at byte boundary + const rowBytes = (width * numComps * bpc + 7) >> 3; + const imgArray = await this.getImageBytes(height * rowBytes, { + internal: true, + }); + + const comps = this.getComponents(imgArray); + let i, length; + + if (bpc === 1) { + // inline decoding (= inversion) for 1 bpc images + length = width * height; + if (this.needsDecode) { + // invert and scale to {0, 255} + for (i = 0; i < length; ++i) { + buffer[i] = (comps[i] - 1) & 255; + } + } else { + // scale to {0, 255} + for (i = 0; i < length; ++i) { + buffer[i] = -comps[i] & 255; + } + } + return; + } + + if (this.needsDecode) { + this.decodeBuffer(comps); + } + length = width * height; + // we aren't using a colorspace so we need to scale the value + const scale = 255 / ((1 << bpc) - 1); + for (i = 0; i < length; ++i) { + buffer[i] = scale * comps[i]; + } + } + + createBitmap(kind, width, height, src) { + const canvas = new OffscreenCanvas(width, height); + const ctx = canvas.getContext("2d"); + let imgData; + if (kind === ImageKind.RGBA_32BPP) { + imgData = new ImageData(src, width, height); + } else { + imgData = ctx.createImageData(width, height); + convertToRGBA({ + kind, + src, + dest: new Uint32Array(imgData.data.buffer), + width, + height, + inverseDecode: this.needsDecode, + }); + } + ctx.putImageData(imgData, 0, 0); + const bitmap = canvas.transferToImageBitmap(); + + return { + data: null, + width, + height, + bitmap, + interpolate: this.interpolate, + }; + } + + async #getImage(width, height) { + const bitmap = await this.image.getTransferableImage(); + if (!bitmap) { + return null; + } + return { + data: null, + width, + height, + bitmap, + interpolate: this.interpolate, + }; + } + + async getImageBytes( + length, + { + drawWidth, + drawHeight, + forceRGBA = false, + forceRGB = false, + internal = false, + } + ) { + this.image.reset(); + this.image.drawWidth = drawWidth || this.width; + this.image.drawHeight = drawHeight || this.height; + this.image.forceRGBA = !!forceRGBA; + this.image.forceRGB = !!forceRGB; + const imageBytes = await this.image.getImageData( + length, + this.jpxDecoderOptions + ); + + // If imageBytes came from a DecodeStream, we're safe to transfer it + // (and thus detach its underlying buffer) because it will constitute + // the entire DecodeStream's data. But if it came from a Stream, we + // need to copy it because it'll only be a portion of the Stream's + // data, and the rest will be read later on. + if (internal || this.image instanceof DecodeStream) { + return imageBytes; + } + assert( + imageBytes instanceof Uint8Array, + 'PDFImage.getImageBytes: Unsupported "imageBytes" type.' + ); + return new Uint8Array(imageBytes); + } +} + +export { PDFImage }; diff --git a/frontend/public/pdf.js/src/core/image_resizer.js b/frontend/public/pdf.js/src/core/image_resizer.js new file mode 100644 index 0000000..3268a74 --- /dev/null +++ b/frontend/public/pdf.js/src/core/image_resizer.js @@ -0,0 +1,537 @@ +/* Copyright 2023 Mozilla Foundation + * + * 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 { FeatureTest, ImageKind, shadow, warn } from "../shared/util.js"; +import { convertToRGBA } from "../shared/image_utils.js"; +import { MAX_INT_32 } from "./core_utils.js"; + +const MIN_IMAGE_DIM = 2048; + +// In Chrome, there aren't max dimensions but only a max area. So an image with +// a very large dimensions is acceptable but it probably doesn't hurt to reduce +// it when considering that it will finally rendered on a small canvas. +const MAX_IMAGE_DIM = 65537; +const MAX_ERROR = 128; + +// Large images are encoded in using the BMP format (it's a way faster than +// encoding in PNG because there are no checksums to compute). +// Unfortunately, createImageBitmap will create a task in the main thread in +// order to build the bitmap, hence the main thread is blocked during the +// decoding which can be a bit long (see bug 1817244). +// When bug 1759728 is fixed we can remove the BMP workaround and at least it +// should be a way faster to create the bitmap. + +class ImageResizer { + static #goodSquareLength = MIN_IMAGE_DIM; + + static #isImageDecoderSupported = FeatureTest.isImageDecoderSupported; + + constructor(imgData, isMask) { + this._imgData = imgData; + this._isMask = isMask; + } + + static get canUseImageDecoder() { + return shadow( + this, + "canUseImageDecoder", + this.#isImageDecoderSupported + ? ImageDecoder.isTypeSupported("image/bmp") + : Promise.resolve(false) + ); + } + + static needsToBeResized(width, height) { + if (width <= this.#goodSquareLength && height <= this.#goodSquareLength) { + return false; + } + + const { MAX_DIM } = this; + if (width > MAX_DIM || height > MAX_DIM) { + return true; + } + + const area = width * height; + if (this._hasMaxArea) { + return area > this.MAX_AREA; + } + + if (area < this.#goodSquareLength ** 2) { + return false; + } + + // We try as much as possible to avoid to compute the max area. + if (this._areGoodDims(width, height)) { + this.#goodSquareLength = Math.max( + this.#goodSquareLength, + Math.floor(Math.sqrt(width * height)) + ); + return false; + } + + // TODO: the computation can be a bit long because we potentially allocate + // some large canvas, so in the Firefox case this value (and MAX_DIM) can be + // infered from prefs (MAX_AREA = gfx.max-alloc-size / 4, 4 is because of + // RGBA). + this.#goodSquareLength = this._guessMax( + this.#goodSquareLength, + MAX_DIM, + MAX_ERROR, + 0 + ); + const maxArea = (this.MAX_AREA = this.#goodSquareLength ** 2); + + return area > maxArea; + } + + static get MAX_DIM() { + return shadow( + this, + "MAX_DIM", + this._guessMax(MIN_IMAGE_DIM, MAX_IMAGE_DIM, 0, 1) + ); + } + + static get MAX_AREA() { + this._hasMaxArea = true; + return shadow( + this, + "MAX_AREA", + this._guessMax(this.#goodSquareLength, this.MAX_DIM, MAX_ERROR, 0) ** 2 + ); + } + + static set MAX_AREA(area) { + if (area >= 0) { + this._hasMaxArea = true; + shadow(this, "MAX_AREA", area); + } + } + + static setOptions({ + canvasMaxAreaInBytes = -1, + isImageDecoderSupported = false, + }) { + if (!this._hasMaxArea) { + // Divide by 4 to have the value in pixels. + this.MAX_AREA = canvasMaxAreaInBytes >> 2; + } + this.#isImageDecoderSupported = isImageDecoderSupported; + } + + static _areGoodDims(width, height) { + try { + // This code is working in either Firefox or Chrome. + // There is a faster solution using transferToImageBitmap which is faster + // in Firefox (when the dimensions are wrong the resulting bitmap has its + // dimensions equal to 1) but it doesn't find the correct values in + // Chrome. + const canvas = new OffscreenCanvas(width, height); + const ctx = canvas.getContext("2d"); + ctx.fillRect(0, 0, 1, 1); + const opacity = ctx.getImageData(0, 0, 1, 1).data[3]; + canvas.width = canvas.height = 1; + return opacity !== 0; + } catch { + return false; + } + } + + static _guessMax(start, end, tolerance, defaultHeight) { + // We don't really need to have exact values. + // When we're here then we're in a corner case: we've a very large image. + // So we could potentially downscale an image which fits in the canvas, + // it's maybe a pity in term of perfs but we'll reduce the memory use. + // The tolerance is the acceptable error we can make on the limit we want. + // On Chrome, when the tolerance 0 then it can take ~5s to compute the max + // area... and having a tolerance of 128 divides the time by 2. + while (start + tolerance + 1 < end) { + const middle = Math.floor((start + end) / 2); + const height = defaultHeight || middle; + if (this._areGoodDims(middle, height)) { + start = middle; + } else { + end = middle; + } + } + return start; + } + + static async createImage(imgData, isMask = false) { + return new ImageResizer(imgData, isMask)._createImage(); + } + + async _createImage() { + const { _imgData: imgData } = this; + const { width, height } = imgData; + + if (width * height * 4 > MAX_INT_32) { + // The resulting RGBA image is too large. + // We just rescale the data. + const result = this.#rescaleImageData(); + if (result) { + return result; + } + } + + const data = this._encodeBMP(); + let decoder, imagePromise; + + if (await ImageResizer.canUseImageDecoder) { + decoder = new ImageDecoder({ + data, + type: "image/bmp", + preferAnimation: false, + transfer: [data.buffer], + }); + imagePromise = decoder + .decode() + .catch(reason => { + warn(`BMP image decoding failed: ${reason}`); + // It's a bit unfortunate to create the BMP twice but we shouldn't be + // here in the first place. + return createImageBitmap( + new Blob([this._encodeBMP().buffer], { + type: "image/bmp", + }) + ); + }) + .finally(() => { + decoder.close(); + }); + } else { + imagePromise = createImageBitmap( + new Blob([data.buffer], { + type: "image/bmp", + }) + ); + } + + const { MAX_AREA, MAX_DIM } = ImageResizer; + const minFactor = Math.max( + width / MAX_DIM, + height / MAX_DIM, + Math.sqrt((width * height) / MAX_AREA) + ); + + const firstFactor = Math.max(minFactor, 2); + + // Add 1 to the ratio and round it with 1 digit. + // We add 1.25 in order to have a final image under the limits + // (and not just at the limits) to reduce memory use. + const factor = Math.round(10 * (minFactor + 1.25)) / 10 / firstFactor; + + const N = Math.floor(Math.log2(factor)); + const steps = new Array(N + 2).fill(2); + steps[0] = firstFactor; + steps.splice(-1, 1, factor / (1 << N)); + + let newWidth = width; + let newHeight = height; + const result = await imagePromise; + let bitmap = result.image || result; + + for (const step of steps) { + const prevWidth = newWidth; + const prevHeight = newHeight; + + // See bug 1820511 (Windows specific bug). + // TODO: once the above bug is fixed we could revert to: + // newWidth = Math.floor(newWidth / 2); + newWidth = Math.floor(newWidth / step) - 1; + newHeight = Math.floor(newHeight / step) - 1; + + const canvas = new OffscreenCanvas(newWidth, newHeight); + const ctx = canvas.getContext("2d"); + ctx.drawImage( + bitmap, + 0, + 0, + prevWidth, + prevHeight, + 0, + 0, + newWidth, + newHeight + ); + + // Release the resources associated with the bitmap. + bitmap.close(); + bitmap = canvas.transferToImageBitmap(); + } + + imgData.data = null; + imgData.bitmap = bitmap; + imgData.width = newWidth; + imgData.height = newHeight; + + return imgData; + } + + #rescaleImageData() { + const { _imgData: imgData } = this; + const { data, width, height, kind } = imgData; + const rgbaSize = width * height * 4; + // K is such as width * height * 4 / 2 ** K <= 2 ** 31 - 1 + const K = Math.ceil(Math.log2(rgbaSize / MAX_INT_32)); + const newWidth = width >> K; + const newHeight = height >> K; + let rgbaData; + let maxHeight = height; + + // We try to allocate the buffer with the maximum size but it can fail. + try { + rgbaData = new Uint8Array(rgbaSize); + } catch { + // n is such as 2 ** n - 1 > width * height * 4 + let n = Math.floor(Math.log2(rgbaSize + 1)); + + while (true) { + try { + rgbaData = new Uint8Array(2 ** n - 1); + break; + } catch { + n -= 1; + } + } + + maxHeight = Math.floor((2 ** n - 1) / (width * 4)); + const newSize = width * maxHeight * 4; + if (newSize < rgbaData.length) { + rgbaData = new Uint8Array(newSize); + } + } + + const src32 = new Uint32Array(rgbaData.buffer); + const dest32 = new Uint32Array(newWidth * newHeight); + + let srcPos = 0; + let newIndex = 0; + const step = Math.ceil(height / maxHeight); + const remainder = height % maxHeight === 0 ? height : height % maxHeight; + for (let k = 0; k < step; k++) { + const h = k < step - 1 ? maxHeight : remainder; + ({ srcPos } = convertToRGBA({ + kind, + src: data, + dest: src32, + width, + height: h, + inverseDecode: this._isMask, + srcPos, + })); + + for (let i = 0, ii = h >> K; i < ii; i++) { + const buf = src32.subarray((i << K) * width); + for (let j = 0; j < newWidth; j++) { + dest32[newIndex++] = buf[j << K]; + } + } + } + + if (ImageResizer.needsToBeResized(newWidth, newHeight)) { + imgData.data = dest32; + imgData.width = newWidth; + imgData.height = newHeight; + imgData.kind = ImageKind.RGBA_32BPP; + + return null; + } + + const canvas = new OffscreenCanvas(newWidth, newHeight); + const ctx = canvas.getContext("2d", { willReadFrequently: true }); + ctx.putImageData( + new ImageData(new Uint8ClampedArray(dest32.buffer), newWidth, newHeight), + 0, + 0 + ); + imgData.data = null; + imgData.bitmap = canvas.transferToImageBitmap(); + imgData.width = newWidth; + imgData.height = newHeight; + + return imgData; + } + + _encodeBMP() { + const { width, height, kind } = this._imgData; + let data = this._imgData.data; + let bitPerPixel; + let colorTable = new Uint8Array(0); + let maskTable = colorTable; + let compression = 0; + + // Each row of the image must be padded in order to have a final size which + // is a multiple of 4. + + switch (kind) { + case ImageKind.GRAYSCALE_1BPP: { + bitPerPixel = 1; + colorTable = new Uint8Array( + this._isMask + ? [255, 255, 255, 255, 0, 0, 0, 0] + : [0, 0, 0, 0, 255, 255, 255, 255] + ); + const rowLen = (width + 7) >> 3; + const rowSize = (rowLen + 3) & -4; + if (rowLen !== rowSize) { + const newData = new Uint8Array(rowSize * height); + let k = 0; + for ( + let i = 0, ii = height * rowLen; + i < ii; + i += rowLen, k += rowSize + ) { + newData.set(data.subarray(i, i + rowLen), k); + } + data = newData; + } + break; + } + case ImageKind.RGB_24BPP: { + bitPerPixel = 24; + if (width & 3) { + const rowLen = 3 * width; + const rowSize = (rowLen + 3) & -4; + const extraLen = rowSize - rowLen; + const newData = new Uint8Array(rowSize * height); + let k = 0; + for (let i = 0, ii = height * rowLen; i < ii; i += rowLen) { + const row = data.subarray(i, i + rowLen); + for (let j = 0; j < rowLen; j += 3) { + newData[k++] = row[j + 2]; + newData[k++] = row[j + 1]; + newData[k++] = row[j]; + } + k += extraLen; + } + data = newData; + } else { + for (let i = 0, ii = data.length; i < ii; i += 3) { + // Just swap R and B. + const tmp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = tmp; + } + } + break; + } + case ImageKind.RGBA_32BPP: + bitPerPixel = 32; + compression = 3; + maskTable = new Uint8Array( + 4 /* R mask */ + + 4 /* G mask */ + + 4 /* B mask */ + + 4 /* A mask */ + + 52 /* Windows color space stuff */ + ); + const view = new DataView(maskTable.buffer); + if (FeatureTest.isLittleEndian) { + view.setUint32(0, 0x000000ff, true); + view.setUint32(4, 0x0000ff00, true); + view.setUint32(8, 0x00ff0000, true); + view.setUint32(12, 0xff000000, true); + } else { + view.setUint32(0, 0xff000000, true); + view.setUint32(4, 0x00ff0000, true); + view.setUint32(8, 0x0000ff00, true); + view.setUint32(12, 0x000000ff, true); + } + break; + default: + throw new Error("invalid format"); + } + + let i = 0; + const headerLength = 40 + maskTable.length; + const fileLength = 14 + headerLength + colorTable.length + data.length; + const bmpData = new Uint8Array(fileLength); + const view = new DataView(bmpData.buffer); + + // Signature. + view.setUint16(i, 0x4d42, true); + i += 2; + + // File size. + view.setUint32(i, fileLength, true); + i += 4; + + // Reserved. + view.setUint32(i, 0, true); + i += 4; + + // Data offset. + view.setUint32(i, 14 + headerLength + colorTable.length, true); + i += 4; + + // Header size. + view.setUint32(i, headerLength, true); + i += 4; + + // Width. + view.setInt32(i, width, true); + i += 4; + + // Height. + // Negative height indicates that the image is stored from top to bottom. + view.setInt32(i, -height, true); + i += 4; + + // Number of planes (must be 1). + view.setUint16(i, 1, true); + i += 2; + + // Number of bit per pixel. + view.setUint16(i, bitPerPixel, true); + i += 2; + + // Compression method. + view.setUint32(i, compression, true); + i += 4; + + // The image size. + view.setUint32(i, 0, true); + i += 4; + + // Horizontal resolution. + view.setInt32(i, 0, true); + i += 4; + + // Vertical resolution. + view.setInt32(i, 0, true); + i += 4; + + // Number of colors in the palette (0 to default). + view.setUint32(i, colorTable.length / 4, true); + i += 4; + + // Number of important colors used (0 to default). + view.setUint32(i, 0, true); + i += 4; + + bmpData.set(maskTable, i); + i += maskTable.length; + + bmpData.set(colorTable, i); + i += colorTable.length; + + bmpData.set(data, i); + + return bmpData; + } +} + +export { ImageResizer }; diff --git a/frontend/public/pdf.js/src/core/image_utils.js b/frontend/public/pdf.js/src/core/image_utils.js new file mode 100644 index 0000000..05e2b7b --- /dev/null +++ b/frontend/public/pdf.js/src/core/image_utils.js @@ -0,0 +1,300 @@ +/* Copyright 2019 Mozilla Foundation + * + * 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 { assert, unreachable, warn } from "../shared/util.js"; +import { RefSet, RefSetCache } from "./primitives.js"; + +class BaseLocalCache { + constructor(options) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === BaseLocalCache + ) { + unreachable("Cannot initialize BaseLocalCache."); + } + this._onlyRefs = options?.onlyRefs === true; + + if (!this._onlyRefs) { + this._nameRefMap = new Map(); + this._imageMap = new Map(); + } + this._imageCache = new RefSetCache(); + } + + getByName(name) { + if (this._onlyRefs) { + unreachable("Should not call `getByName` method."); + } + const ref = this._nameRefMap.get(name); + if (ref) { + return this.getByRef(ref); + } + return this._imageMap.get(name) || null; + } + + getByRef(ref) { + return this._imageCache.get(ref) || null; + } + + set(name, ref, data) { + unreachable("Abstract method `set` called."); + } +} + +class LocalImageCache extends BaseLocalCache { + set(name, ref = null, data) { + if (typeof name !== "string") { + throw new Error('LocalImageCache.set - expected "name" argument.'); + } + if (ref) { + if (this._imageCache.has(ref)) { + return; + } + this._nameRefMap.set(name, ref); + this._imageCache.put(ref, data); + return; + } + // name + if (this._imageMap.has(name)) { + return; + } + this._imageMap.set(name, data); + } +} + +class LocalColorSpaceCache extends BaseLocalCache { + set(name = null, ref = null, data) { + if (typeof name !== "string" && !ref) { + throw new Error( + 'LocalColorSpaceCache.set - expected "name" and/or "ref" argument.' + ); + } + if (ref) { + if (this._imageCache.has(ref)) { + return; + } + if (name !== null) { + // Optional when `ref` is defined. + this._nameRefMap.set(name, ref); + } + this._imageCache.put(ref, data); + return; + } + // name + if (this._imageMap.has(name)) { + return; + } + this._imageMap.set(name, data); + } +} + +class LocalFunctionCache extends BaseLocalCache { + constructor(options) { + super({ onlyRefs: true }); + } + + set(name = null, ref, data) { + if (!ref) { + throw new Error('LocalFunctionCache.set - expected "ref" argument.'); + } + if (this._imageCache.has(ref)) { + return; + } + this._imageCache.put(ref, data); + } +} + +class LocalGStateCache extends BaseLocalCache { + set(name, ref = null, data) { + if (typeof name !== "string") { + throw new Error('LocalGStateCache.set - expected "name" argument.'); + } + if (ref) { + if (this._imageCache.has(ref)) { + return; + } + this._nameRefMap.set(name, ref); + this._imageCache.put(ref, data); + return; + } + // name + if (this._imageMap.has(name)) { + return; + } + this._imageMap.set(name, data); + } +} + +class LocalTilingPatternCache extends BaseLocalCache { + constructor(options) { + super({ onlyRefs: true }); + } + + set(name = null, ref, data) { + if (!ref) { + throw new Error('LocalTilingPatternCache.set - expected "ref" argument.'); + } + if (this._imageCache.has(ref)) { + return; + } + this._imageCache.put(ref, data); + } +} + +class RegionalImageCache extends BaseLocalCache { + constructor(options) { + super({ onlyRefs: true }); + } + + set(name = null, ref, data) { + if (!ref) { + throw new Error('RegionalImageCache.set - expected "ref" argument.'); + } + if (this._imageCache.has(ref)) { + return; + } + this._imageCache.put(ref, data); + } +} + +class GlobalImageCache { + static NUM_PAGES_THRESHOLD = 2; + + static MIN_IMAGES_TO_CACHE = 10; + + static MAX_BYTE_SIZE = 5e7; // Fifty megabytes. + + #decodeFailedSet = new RefSet(); + + constructor() { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + GlobalImageCache.NUM_PAGES_THRESHOLD > 1, + "GlobalImageCache - invalid NUM_PAGES_THRESHOLD constant." + ); + } + this._refCache = new RefSetCache(); + this._imageCache = new RefSetCache(); + } + + get #byteSize() { + let byteSize = 0; + for (const imageData of this._imageCache) { + byteSize += imageData.byteSize; + } + return byteSize; + } + + get #cacheLimitReached() { + if (this._imageCache.size < GlobalImageCache.MIN_IMAGES_TO_CACHE) { + return false; + } + if (this.#byteSize < GlobalImageCache.MAX_BYTE_SIZE) { + return false; + } + return true; + } + + shouldCache(ref, pageIndex) { + let pageIndexSet = this._refCache.get(ref); + if (!pageIndexSet) { + pageIndexSet = new Set(); + this._refCache.put(ref, pageIndexSet); + } + pageIndexSet.add(pageIndex); + + if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) { + return false; + } + if (!this._imageCache.has(ref) && this.#cacheLimitReached) { + return false; + } + return true; + } + + addDecodeFailed(ref) { + this.#decodeFailedSet.put(ref); + } + + hasDecodeFailed(ref) { + return this.#decodeFailedSet.has(ref); + } + + /** + * PLEASE NOTE: Must be called *after* the `setData` method. + */ + addByteSize(ref, byteSize) { + const imageData = this._imageCache.get(ref); + if (!imageData) { + return; // The image data isn't cached (the limit was reached). + } + if (imageData.byteSize) { + return; // The byte-size has already been set. + } + imageData.byteSize = byteSize; + } + + getData(ref, pageIndex) { + const pageIndexSet = this._refCache.get(ref); + if (!pageIndexSet) { + return null; + } + if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) { + return null; + } + const imageData = this._imageCache.get(ref); + if (!imageData) { + return null; + } + // Ensure that we keep track of all pages containing the image reference. + pageIndexSet.add(pageIndex); + + return imageData; + } + + setData(ref, data) { + if (!this._refCache.has(ref)) { + throw new Error( + 'GlobalImageCache.setData - expected "shouldCache" to have been called.' + ); + } + if (this._imageCache.has(ref)) { + return; + } + if (this.#cacheLimitReached) { + warn("GlobalImageCache.setData - cache limit reached."); + return; + } + this._imageCache.put(ref, data); + } + + clear(onlyData = false) { + if (!onlyData) { + this.#decodeFailedSet.clear(); + this._refCache.clear(); + } + this._imageCache.clear(); + } +} + +export { + GlobalImageCache, + LocalColorSpaceCache, + LocalFunctionCache, + LocalGStateCache, + LocalImageCache, + LocalTilingPatternCache, + RegionalImageCache, +}; diff --git a/frontend/public/pdf.js/src/core/jbig2.js b/frontend/public/pdf.js/src/core/jbig2.js new file mode 100644 index 0000000..448f23e --- /dev/null +++ b/frontend/public/pdf.js/src/core/jbig2.js @@ -0,0 +1,2596 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { BaseException, shadow } from "../shared/util.js"; +import { + log2, + MAX_INT_32, + MIN_INT_32, + readInt8, + readUint16, + readUint32, +} from "./core_utils.js"; +import { ArithmeticDecoder } from "./arithmetic_decoder.js"; +import { CCITTFaxDecoder } from "./ccitt.js"; + +class Jbig2Error extends BaseException { + constructor(msg) { + super(msg, "Jbig2Error"); + } +} + +// Utility data structures +class ContextCache { + getContexts(id) { + if (id in this) { + return this[id]; + } + return (this[id] = new Int8Array(1 << 16)); + } +} + +class DecodingContext { + constructor(data, start, end) { + this.data = data; + this.start = start; + this.end = end; + } + + get decoder() { + const decoder = new ArithmeticDecoder(this.data, this.start, this.end); + return shadow(this, "decoder", decoder); + } + + get contextCache() { + const cache = new ContextCache(); + return shadow(this, "contextCache", cache); + } +} + +// Annex A. Arithmetic Integer Decoding Procedure +// A.2 Procedure for decoding values +function decodeInteger(contextCache, procedure, decoder) { + const contexts = contextCache.getContexts(procedure); + let prev = 1; + + function readBits(length) { + let v = 0; + for (let i = 0; i < length; i++) { + const bit = decoder.readBit(contexts, prev); + prev = prev < 256 ? (prev << 1) | bit : (((prev << 1) | bit) & 511) | 256; + v = (v << 1) | bit; + } + return v >>> 0; + } + + const sign = readBits(1); + // prettier-ignore + /* eslint-disable no-nested-ternary */ + const value = readBits(1) ? + (readBits(1) ? + (readBits(1) ? + (readBits(1) ? + (readBits(1) ? + (readBits(32) + 4436) : + readBits(12) + 340) : + readBits(8) + 84) : + readBits(6) + 20) : + readBits(4) + 4) : + readBits(2); + /* eslint-enable no-nested-ternary */ + let signedValue; + if (sign === 0) { + signedValue = value; + } else if (value > 0) { + signedValue = -value; + } + // Ensure that the integer value doesn't underflow or overflow. + if (signedValue >= MIN_INT_32 && signedValue <= MAX_INT_32) { + return signedValue; + } + return null; +} + +// A.3 The IAID decoding procedure +function decodeIAID(contextCache, decoder, codeLength) { + const contexts = contextCache.getContexts("IAID"); + + let prev = 1; + for (let i = 0; i < codeLength; i++) { + const bit = decoder.readBit(contexts, prev); + prev = (prev << 1) | bit; + } + if (codeLength < 31) { + return prev & ((1 << codeLength) - 1); + } + return prev & 0x7fffffff; +} + +// 7.3 Segment types +const SegmentTypes = [ + "SymbolDictionary", + null, + null, + null, + "IntermediateTextRegion", + null, + "ImmediateTextRegion", + "ImmediateLosslessTextRegion", + null, + null, + null, + null, + null, + null, + null, + null, + "PatternDictionary", + null, + null, + null, + "IntermediateHalftoneRegion", + null, + "ImmediateHalftoneRegion", + "ImmediateLosslessHalftoneRegion", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "IntermediateGenericRegion", + null, + "ImmediateGenericRegion", + "ImmediateLosslessGenericRegion", + "IntermediateGenericRefinementRegion", + null, + "ImmediateGenericRefinementRegion", + "ImmediateLosslessGenericRefinementRegion", + null, + null, + null, + null, + "PageInformation", + "EndOfPage", + "EndOfStripe", + "EndOfFile", + "Profiles", + "Tables", + null, + null, + null, + null, + null, + null, + null, + null, + "Extension", +]; + +const CodingTemplates = [ + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: 2, y: -1 }, + { x: -4, y: 0 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: 2, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: 2, y: -1 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -3, y: -1 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -4, y: 0 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], +]; + +const RefinementTemplates = [ + { + coding: [ + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + ], + reference: [ + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: -1, y: 1 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ], + }, + { + coding: [ + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + ], + reference: [ + { x: 0, y: -1 }, + { x: -1, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ], + }, +]; + +// See 6.2.5.7 Decoding the bitmap. +const ReusedContexts = [ + 0x9b25, // 10011 0110010 0101 + 0x0795, // 0011 110010 101 + 0x00e5, // 001 11001 01 + 0x0195, // 011001 0101 +]; + +const RefinementReusedContexts = [ + 0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference) + 0x0008, // '0000' + '001000' +]; + +function decodeBitmapTemplate0(width, height, decodingContext) { + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + const bitmap = []; + let contextLabel, i, j, pixel, row, row1, row2; + + // ...ooooo.... + // ..ooooooo... Context template for current pixel (X) + // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel) + const OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 + + for (i = 0; i < height; i++) { + row = bitmap[i] = new Uint8Array(width); + row1 = i < 1 ? row : bitmap[i - 1]; + row2 = i < 2 ? row : bitmap[i - 2]; + + // At the beginning of each row: + // Fill contextLabel with pixels that are above/right of (X) + contextLabel = + (row2[0] << 13) | + (row2[1] << 12) | + (row2[2] << 11) | + (row1[0] << 7) | + (row1[1] << 6) | + (row1[2] << 5) | + (row1[3] << 4); + + for (j = 0; j < width; j++) { + row[j] = pixel = decoder.readBit(contexts, contextLabel); + + // At each pixel: Clear contextLabel pixels that are shifted + // out of the context, then add new ones. + contextLabel = + ((contextLabel & OLD_PIXEL_MASK) << 1) | + (j + 3 < width ? row2[j + 3] << 11 : 0) | + (j + 4 < width ? row1[j + 4] << 4 : 0) | + pixel; + } + } + + return bitmap; +} + +// 6.2 Generic Region Decoding Procedure +function decodeBitmap( + mmr, + width, + height, + templateIndex, + prediction, + skip, + at, + decodingContext +) { + if (mmr) { + const input = new Reader( + decodingContext.data, + decodingContext.start, + decodingContext.end + ); + return decodeMMRBitmap(input, width, height, false); + } + + // Use optimized version for the most common case + if ( + templateIndex === 0 && + !skip && + !prediction && + at.length === 4 && + at[0].x === 3 && + at[0].y === -1 && + at[1].x === -3 && + at[1].y === -1 && + at[2].x === 2 && + at[2].y === -2 && + at[3].x === -2 && + at[3].y === -2 + ) { + return decodeBitmapTemplate0(width, height, decodingContext); + } + + const useskip = !!skip; + const template = CodingTemplates[templateIndex].concat(at); + + // Sorting is non-standard, and it is not required. But sorting increases + // the number of template bits that can be reused from the previous + // contextLabel in the main loop. + template.sort(function (a, b) { + return a.y - b.y || a.x - b.x; + }); + + const templateLength = template.length; + const templateX = new Int8Array(templateLength); + const templateY = new Int8Array(templateLength); + const changingTemplateEntries = []; + let reuseMask = 0, + minX = 0, + maxX = 0, + minY = 0; + let c, k; + + for (k = 0; k < templateLength; k++) { + templateX[k] = template[k].x; + templateY[k] = template[k].y; + minX = Math.min(minX, template[k].x); + maxX = Math.max(maxX, template[k].x); + minY = Math.min(minY, template[k].y); + // Check if the template pixel appears in two consecutive context labels, + // so it can be reused. Otherwise, we add it to the list of changing + // template entries. + if ( + k < templateLength - 1 && + template[k].y === template[k + 1].y && + template[k].x === template[k + 1].x - 1 + ) { + reuseMask |= 1 << (templateLength - 1 - k); + } else { + changingTemplateEntries.push(k); + } + } + const changingEntriesLength = changingTemplateEntries.length; + + const changingTemplateX = new Int8Array(changingEntriesLength); + const changingTemplateY = new Int8Array(changingEntriesLength); + const changingTemplateBit = new Uint16Array(changingEntriesLength); + for (c = 0; c < changingEntriesLength; c++) { + k = changingTemplateEntries[c]; + changingTemplateX[c] = template[k].x; + changingTemplateY[c] = template[k].y; + changingTemplateBit[c] = 1 << (templateLength - 1 - k); + } + + // Get the safe bounding box edges from the width, height, minX, maxX, minY + const sbb_left = -minX; + const sbb_top = -minY; + const sbb_right = width - maxX; + + const pseudoPixelContext = ReusedContexts[templateIndex]; + let row = new Uint8Array(width); + const bitmap = []; + + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + + let ltp = 0, + j, + i0, + j0, + contextLabel = 0, + bit, + shift; + for (let i = 0; i < height; i++) { + if (prediction) { + const sltp = decoder.readBit(contexts, pseudoPixelContext); + ltp ^= sltp; + if (ltp) { + bitmap.push(row); // duplicate previous row + continue; + } + } + row = new Uint8Array(row); + bitmap.push(row); + for (j = 0; j < width; j++) { + if (useskip && skip[i][j]) { + row[j] = 0; + continue; + } + // Are we in the middle of a scanline, so we can reuse contextLabel + // bits? + if (j >= sbb_left && j < sbb_right && i >= sbb_top) { + // If yes, we can just shift the bits that are reusable and only + // fetch the remaining ones. + contextLabel = (contextLabel << 1) & reuseMask; + for (k = 0; k < changingEntriesLength; k++) { + i0 = i + changingTemplateY[k]; + j0 = j + changingTemplateX[k]; + bit = bitmap[i0][j0]; + if (bit) { + bit = changingTemplateBit[k]; + contextLabel |= bit; + } + } + } else { + // compute the contextLabel from scratch + contextLabel = 0; + shift = templateLength - 1; + for (k = 0; k < templateLength; k++, shift--) { + j0 = j + templateX[k]; + if (j0 >= 0 && j0 < width) { + i0 = i + templateY[k]; + if (i0 >= 0) { + bit = bitmap[i0][j0]; + if (bit) { + contextLabel |= bit << shift; + } + } + } + } + } + const pixel = decoder.readBit(contexts, contextLabel); + row[j] = pixel; + } + } + return bitmap; +} + +// 6.3.2 Generic Refinement Region Decoding Procedure +function decodeRefinement( + width, + height, + templateIndex, + referenceBitmap, + offsetX, + offsetY, + prediction, + at, + decodingContext +) { + let codingTemplate = RefinementTemplates[templateIndex].coding; + if (templateIndex === 0) { + codingTemplate = codingTemplate.concat([at[0]]); + } + const codingTemplateLength = codingTemplate.length; + const codingTemplateX = new Int32Array(codingTemplateLength); + const codingTemplateY = new Int32Array(codingTemplateLength); + let k; + for (k = 0; k < codingTemplateLength; k++) { + codingTemplateX[k] = codingTemplate[k].x; + codingTemplateY[k] = codingTemplate[k].y; + } + + let referenceTemplate = RefinementTemplates[templateIndex].reference; + if (templateIndex === 0) { + referenceTemplate = referenceTemplate.concat([at[1]]); + } + const referenceTemplateLength = referenceTemplate.length; + const referenceTemplateX = new Int32Array(referenceTemplateLength); + const referenceTemplateY = new Int32Array(referenceTemplateLength); + for (k = 0; k < referenceTemplateLength; k++) { + referenceTemplateX[k] = referenceTemplate[k].x; + referenceTemplateY[k] = referenceTemplate[k].y; + } + const referenceWidth = referenceBitmap[0].length; + const referenceHeight = referenceBitmap.length; + + const pseudoPixelContext = RefinementReusedContexts[templateIndex]; + const bitmap = []; + + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GR"); + + let ltp = 0; + for (let i = 0; i < height; i++) { + if (prediction) { + const sltp = decoder.readBit(contexts, pseudoPixelContext); + ltp ^= sltp; + if (ltp) { + throw new Jbig2Error("prediction is not supported"); + } + } + const row = new Uint8Array(width); + bitmap.push(row); + for (let j = 0; j < width; j++) { + let i0, j0; + let contextLabel = 0; + for (k = 0; k < codingTemplateLength; k++) { + i0 = i + codingTemplateY[k]; + j0 = j + codingTemplateX[k]; + if (i0 < 0 || j0 < 0 || j0 >= width) { + contextLabel <<= 1; // out of bound pixel + } else { + contextLabel = (contextLabel << 1) | bitmap[i0][j0]; + } + } + for (k = 0; k < referenceTemplateLength; k++) { + i0 = i + referenceTemplateY[k] - offsetY; + j0 = j + referenceTemplateX[k] - offsetX; + if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) { + contextLabel <<= 1; // out of bound pixel + } else { + contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; + } + } + const pixel = decoder.readBit(contexts, contextLabel); + row[j] = pixel; + } + } + + return bitmap; +} + +// 6.5.5 Decoding the symbol dictionary +function decodeSymbolDictionary( + huffman, + refinement, + symbols, + numberOfNewSymbols, + numberOfExportedSymbols, + huffmanTables, + templateIndex, + at, + refinementTemplateIndex, + refinementAt, + decodingContext, + huffmanInput +) { + if (huffman && refinement) { + throw new Jbig2Error("symbol refinement with Huffman is not supported"); + } + + const newSymbols = []; + let currentHeight = 0; + let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); + + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; + let tableB1, symbolWidths; + if (huffman) { + tableB1 = getStandardTable(1); // standard table B.1 + symbolWidths = []; + symbolCodeLength = Math.max(symbolCodeLength, 1); // 6.5.8.2.3 + } + + while (newSymbols.length < numberOfNewSymbols) { + const deltaHeight = huffman + ? huffmanTables.tableDeltaHeight.decode(huffmanInput) + : decodeInteger(contextCache, "IADH", decoder); // 6.5.6 + currentHeight += deltaHeight; + let currentWidth = 0, + totalWidth = 0; + const firstSymbol = huffman ? symbolWidths.length : 0; + while (true) { + const deltaWidth = huffman + ? huffmanTables.tableDeltaWidth.decode(huffmanInput) + : decodeInteger(contextCache, "IADW", decoder); // 6.5.7 + if (deltaWidth === null) { + break; // OOB + } + currentWidth += deltaWidth; + totalWidth += currentWidth; + let bitmap; + if (refinement) { + // 6.5.8.2 Refinement/aggregate-coded symbol bitmap + const numberOfInstances = decodeInteger(contextCache, "IAAI", decoder); + if (numberOfInstances > 1) { + bitmap = decodeTextRegion( + huffman, + refinement, + currentWidth, + currentHeight, + 0, + numberOfInstances, + 1, // strip size + symbols.concat(newSymbols), + symbolCodeLength, + 0, // transposed + 0, // ds offset + 1, // top left 7.4.3.1.1 + 0, // OR operator + huffmanTables, + refinementTemplateIndex, + refinementAt, + decodingContext, + 0, + huffmanInput + ); + } else { + const symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); + const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 + const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 + const symbol = + symbolId < symbols.length + ? symbols[symbolId] + : newSymbols[symbolId - symbols.length]; + bitmap = decodeRefinement( + currentWidth, + currentHeight, + refinementTemplateIndex, + symbol, + rdx, + rdy, + false, + refinementAt, + decodingContext + ); + } + newSymbols.push(bitmap); + } else if (huffman) { + // Store only symbol width and decode a collective bitmap when the + // height class is done. + symbolWidths.push(currentWidth); + } else { + // 6.5.8.1 Direct-coded symbol bitmap + bitmap = decodeBitmap( + false, + currentWidth, + currentHeight, + templateIndex, + false, + null, + at, + decodingContext + ); + newSymbols.push(bitmap); + } + } + if (huffman && !refinement) { + // 6.5.9 Height class collective bitmap + const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput); + huffmanInput.byteAlign(); + let collectiveBitmap; + if (bitmapSize === 0) { + // Uncompressed collective bitmap + collectiveBitmap = readUncompressedBitmap( + huffmanInput, + totalWidth, + currentHeight + ); + } else { + // MMR collective bitmap + const originalEnd = huffmanInput.end; + const bitmapEnd = huffmanInput.position + bitmapSize; + huffmanInput.end = bitmapEnd; + collectiveBitmap = decodeMMRBitmap( + huffmanInput, + totalWidth, + currentHeight, + false + ); + huffmanInput.end = originalEnd; + huffmanInput.position = bitmapEnd; + } + const numberOfSymbolsDecoded = symbolWidths.length; + if (firstSymbol === numberOfSymbolsDecoded - 1) { + // collectiveBitmap is a single symbol. + newSymbols.push(collectiveBitmap); + } else { + // Divide collectiveBitmap into symbols. + let i, + y, + xMin = 0, + xMax, + bitmapWidth, + symbolBitmap; + for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) { + bitmapWidth = symbolWidths[i]; + xMax = xMin + bitmapWidth; + symbolBitmap = []; + for (y = 0; y < currentHeight; y++) { + symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); + } + newSymbols.push(symbolBitmap); + xMin = xMax; + } + } + } + } + + // 6.5.10 Exported symbols + const exportedSymbols = [], + flags = []; + let currentFlag = false, + i, + ii; + const totalSymbolsLength = symbols.length + numberOfNewSymbols; + while (flags.length < totalSymbolsLength) { + let runLength = huffman + ? tableB1.decode(huffmanInput) + : decodeInteger(contextCache, "IAEX", decoder); + while (runLength--) { + flags.push(currentFlag); + } + currentFlag = !currentFlag; + } + for (i = 0, ii = symbols.length; i < ii; i++) { + if (flags[i]) { + exportedSymbols.push(symbols[i]); + } + } + for (let j = 0; j < numberOfNewSymbols; i++, j++) { + if (flags[i]) { + exportedSymbols.push(newSymbols[j]); + } + } + return exportedSymbols; +} + +function decodeTextRegion( + huffman, + refinement, + width, + height, + defaultPixelValue, + numberOfSymbolInstances, + stripSize, + inputSymbols, + symbolCodeLength, + transposed, + dsOffset, + referenceCorner, + combinationOperator, + huffmanTables, + refinementTemplateIndex, + refinementAt, + decodingContext, + logStripSize, + huffmanInput +) { + if (huffman && refinement) { + throw new Jbig2Error("refinement with Huffman is not supported"); + } + + // Prepare bitmap + const bitmap = []; + let i, row; + for (i = 0; i < height; i++) { + row = new Uint8Array(width); + if (defaultPixelValue) { + row.fill(defaultPixelValue); + } + bitmap.push(row); + } + + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; + + let stripT = huffman + ? -huffmanTables.tableDeltaT.decode(huffmanInput) + : -decodeInteger(contextCache, "IADT", decoder); // 6.4.6 + let firstS = 0; + i = 0; + while (i < numberOfSymbolInstances) { + const deltaT = huffman + ? huffmanTables.tableDeltaT.decode(huffmanInput) + : decodeInteger(contextCache, "IADT", decoder); // 6.4.6 + stripT += deltaT; + + const deltaFirstS = huffman + ? huffmanTables.tableFirstS.decode(huffmanInput) + : decodeInteger(contextCache, "IAFS", decoder); // 6.4.7 + firstS += deltaFirstS; + let currentS = firstS; + do { + let currentT = 0; // 6.4.9 + if (stripSize > 1) { + currentT = huffman + ? huffmanInput.readBits(logStripSize) + : decodeInteger(contextCache, "IAIT", decoder); + } + const t = stripSize * stripT + currentT; + const symbolId = huffman + ? huffmanTables.symbolIDTable.decode(huffmanInput) + : decodeIAID(contextCache, decoder, symbolCodeLength); + const applyRefinement = + refinement && + (huffman + ? huffmanInput.readBit() + : decodeInteger(contextCache, "IARI", decoder)); + let symbolBitmap = inputSymbols[symbolId]; + let symbolWidth = symbolBitmap[0].length; + let symbolHeight = symbolBitmap.length; + if (applyRefinement) { + const rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 + const rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 + const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 + const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 + symbolWidth += rdw; + symbolHeight += rdh; + symbolBitmap = decodeRefinement( + symbolWidth, + symbolHeight, + refinementTemplateIndex, + symbolBitmap, + (rdw >> 1) + rdx, + (rdh >> 1) + rdy, + false, + refinementAt, + decodingContext + ); + } + + let increment = 0; + if (!transposed) { + if (referenceCorner > 1) { + currentS += symbolWidth - 1; + } else { + increment = symbolWidth - 1; + } + } else if (!(referenceCorner & 1)) { + currentS += symbolHeight - 1; + } else { + increment = symbolHeight - 1; + } + + const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); + const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); + let s2, t2, symbolRow; + if (transposed) { + // Place Symbol Bitmap from T1,S1 + for (s2 = 0; s2 < symbolHeight; s2++) { + row = bitmap[offsetS + s2]; + if (!row) { + continue; + } + symbolRow = symbolBitmap[s2]; + // To ignore Parts of Symbol bitmap which goes + // outside bitmap region + const maxWidth = Math.min(width - offsetT, symbolWidth); + switch (combinationOperator) { + case 0: // OR + for (t2 = 0; t2 < maxWidth; t2++) { + row[offsetT + t2] |= symbolRow[t2]; + } + break; + case 2: // XOR + for (t2 = 0; t2 < maxWidth; t2++) { + row[offsetT + t2] ^= symbolRow[t2]; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + } else { + for (t2 = 0; t2 < symbolHeight; t2++) { + row = bitmap[offsetT + t2]; + if (!row) { + continue; + } + symbolRow = symbolBitmap[t2]; + switch (combinationOperator) { + case 0: // OR + for (s2 = 0; s2 < symbolWidth; s2++) { + row[offsetS + s2] |= symbolRow[s2]; + } + break; + case 2: // XOR + for (s2 = 0; s2 < symbolWidth; s2++) { + row[offsetS + s2] ^= symbolRow[s2]; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + } + i++; + const deltaS = huffman + ? huffmanTables.tableDeltaS.decode(huffmanInput) + : decodeInteger(contextCache, "IADS", decoder); // 6.4.8 + if (deltaS === null) { + break; // OOB + } + currentS += increment + deltaS + dsOffset; + } while (true); + } + return bitmap; +} + +function decodePatternDictionary( + mmr, + patternWidth, + patternHeight, + maxPatternIndex, + template, + decodingContext +) { + const at = []; + if (!mmr) { + at.push({ + x: -patternWidth, + y: 0, + }); + if (template === 0) { + at.push( + { + x: -3, + y: -1, + }, + { + x: 2, + y: -2, + }, + { + x: -2, + y: -2, + } + ); + } + } + const collectiveWidth = (maxPatternIndex + 1) * patternWidth; + const collectiveBitmap = decodeBitmap( + mmr, + collectiveWidth, + patternHeight, + template, + false, + null, + at, + decodingContext + ); + // Divide collective bitmap into patterns. + const patterns = []; + for (let i = 0; i <= maxPatternIndex; i++) { + const patternBitmap = []; + const xMin = patternWidth * i; + const xMax = xMin + patternWidth; + for (let y = 0; y < patternHeight; y++) { + patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); + } + patterns.push(patternBitmap); + } + return patterns; +} + +function decodeHalftoneRegion( + mmr, + patterns, + template, + regionWidth, + regionHeight, + defaultPixelValue, + enableSkip, + combinationOperator, + gridWidth, + gridHeight, + gridOffsetX, + gridOffsetY, + gridVectorX, + gridVectorY, + decodingContext +) { + const skip = null; + if (enableSkip) { + throw new Jbig2Error("skip is not supported"); + } + if (combinationOperator !== 0) { + throw new Jbig2Error( + `operator "${combinationOperator}" is not supported in halftone region` + ); + } + + // Prepare bitmap. + const regionBitmap = []; + let i, j, row; + for (i = 0; i < regionHeight; i++) { + row = new Uint8Array(regionWidth); + if (defaultPixelValue) { + row.fill(defaultPixelValue); + } + regionBitmap.push(row); + } + + const numberOfPatterns = patterns.length; + const pattern0 = patterns[0]; + const patternWidth = pattern0[0].length, + patternHeight = pattern0.length; + const bitsPerValue = log2(numberOfPatterns); + const at = []; + if (!mmr) { + at.push({ + x: template <= 1 ? 3 : 2, + y: -1, + }); + if (template === 0) { + at.push( + { + x: -3, + y: -1, + }, + { + x: 2, + y: -2, + }, + { + x: -2, + y: -2, + } + ); + } + } + // Annex C. Gray-scale Image Decoding Procedure. + const grayScaleBitPlanes = []; + let mmrInput, bitmap; + if (mmr) { + // MMR bit planes are in one continuous stream. Only EOFB codes indicate + // the end of each bitmap, so EOFBs must be decoded. + mmrInput = new Reader( + decodingContext.data, + decodingContext.start, + decodingContext.end + ); + } + for (i = bitsPerValue - 1; i >= 0; i--) { + if (mmr) { + bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true); + } else { + bitmap = decodeBitmap( + false, + gridWidth, + gridHeight, + template, + false, + skip, + at, + decodingContext + ); + } + grayScaleBitPlanes[i] = bitmap; + } + // 6.6.5.2 Rendering the patterns. + let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow; + for (mg = 0; mg < gridHeight; mg++) { + for (ng = 0; ng < gridWidth; ng++) { + bit = 0; + patternIndex = 0; + for (j = bitsPerValue - 1; j >= 0; j--) { + bit ^= grayScaleBitPlanes[j][mg][ng]; // Gray decoding + patternIndex |= bit << j; + } + patternBitmap = patterns[patternIndex]; + x = (gridOffsetX + mg * gridVectorY + ng * gridVectorX) >> 8; + y = (gridOffsetY + mg * gridVectorX - ng * gridVectorY) >> 8; + // Draw patternBitmap at (x, y). + if ( + x >= 0 && + x + patternWidth <= regionWidth && + y >= 0 && + y + patternHeight <= regionHeight + ) { + for (i = 0; i < patternHeight; i++) { + regionRow = regionBitmap[y + i]; + patternRow = patternBitmap[i]; + for (j = 0; j < patternWidth; j++) { + regionRow[x + j] |= patternRow[j]; + } + } + } else { + let regionX, regionY; + for (i = 0; i < patternHeight; i++) { + regionY = y + i; + if (regionY < 0 || regionY >= regionHeight) { + continue; + } + regionRow = regionBitmap[regionY]; + patternRow = patternBitmap[i]; + for (j = 0; j < patternWidth; j++) { + regionX = x + j; + if (regionX >= 0 && regionX < regionWidth) { + regionRow[regionX] |= patternRow[j]; + } + } + } + } + } + } + return regionBitmap; +} + +function readSegmentHeader(data, start) { + const segmentHeader = {}; + segmentHeader.number = readUint32(data, start); + const flags = data[start + 4]; + const segmentType = flags & 0x3f; + if (!SegmentTypes[segmentType]) { + throw new Jbig2Error("invalid segment type: " + segmentType); + } + segmentHeader.type = segmentType; + segmentHeader.typeName = SegmentTypes[segmentType]; + segmentHeader.deferredNonRetain = !!(flags & 0x80); + + const pageAssociationFieldSize = !!(flags & 0x40); + const referredFlags = data[start + 5]; + let referredToCount = (referredFlags >> 5) & 7; + const retainBits = [referredFlags & 31]; + let position = start + 6; + if (referredFlags === 7) { + referredToCount = readUint32(data, position - 1) & 0x1fffffff; + position += 3; + let bytes = (referredToCount + 7) >> 3; + retainBits[0] = data[position++]; + while (--bytes > 0) { + retainBits.push(data[position++]); + } + } else if (referredFlags === 5 || referredFlags === 6) { + throw new Jbig2Error("invalid referred-to flags"); + } + + segmentHeader.retainBits = retainBits; + + let referredToSegmentNumberSize = 4; + if (segmentHeader.number <= 256) { + referredToSegmentNumberSize = 1; + } else if (segmentHeader.number <= 65536) { + referredToSegmentNumberSize = 2; + } + const referredTo = []; + let i, ii; + for (i = 0; i < referredToCount; i++) { + let number; + if (referredToSegmentNumberSize === 1) { + number = data[position]; + } else if (referredToSegmentNumberSize === 2) { + number = readUint16(data, position); + } else { + number = readUint32(data, position); + } + referredTo.push(number); + position += referredToSegmentNumberSize; + } + segmentHeader.referredTo = referredTo; + if (!pageAssociationFieldSize) { + segmentHeader.pageAssociation = data[position++]; + } else { + segmentHeader.pageAssociation = readUint32(data, position); + position += 4; + } + segmentHeader.length = readUint32(data, position); + position += 4; + + if (segmentHeader.length === 0xffffffff) { + // 7.2.7 Segment data length, unknown segment length + if (segmentType === 38) { + // ImmediateGenericRegion + const genericRegionInfo = readRegionSegmentInformation(data, position); + const genericRegionSegmentFlags = + data[position + RegionSegmentInformationFieldLength]; + const genericRegionMmr = !!(genericRegionSegmentFlags & 1); + // searching for the segment end + const searchPatternLength = 6; + const searchPattern = new Uint8Array(searchPatternLength); + if (!genericRegionMmr) { + searchPattern[0] = 0xff; + searchPattern[1] = 0xac; + } + searchPattern[2] = (genericRegionInfo.height >>> 24) & 0xff; + searchPattern[3] = (genericRegionInfo.height >> 16) & 0xff; + searchPattern[4] = (genericRegionInfo.height >> 8) & 0xff; + searchPattern[5] = genericRegionInfo.height & 0xff; + for (i = position, ii = data.length; i < ii; i++) { + let j = 0; + while (j < searchPatternLength && searchPattern[j] === data[i + j]) { + j++; + } + if (j === searchPatternLength) { + segmentHeader.length = i + searchPatternLength; + break; + } + } + if (segmentHeader.length === 0xffffffff) { + throw new Jbig2Error("segment end was not found"); + } + } else { + throw new Jbig2Error("invalid unknown segment length"); + } + } + segmentHeader.headerEnd = position; + return segmentHeader; +} + +function readSegments(header, data, start, end) { + const segments = []; + let position = start; + while (position < end) { + const segmentHeader = readSegmentHeader(data, position); + position = segmentHeader.headerEnd; + const segment = { + header: segmentHeader, + data, + }; + if (!header.randomAccess) { + segment.start = position; + position += segmentHeader.length; + segment.end = position; + } + segments.push(segment); + if (segmentHeader.type === 51) { + break; // end of file is found + } + } + if (header.randomAccess) { + for (let i = 0, ii = segments.length; i < ii; i++) { + segments[i].start = position; + position += segments[i].header.length; + segments[i].end = position; + } + } + return segments; +} + +// 7.4.1 Region segment information field +function readRegionSegmentInformation(data, start) { + return { + width: readUint32(data, start), + height: readUint32(data, start + 4), + x: readUint32(data, start + 8), + y: readUint32(data, start + 12), + combinationOperator: data[start + 16] & 7, + }; +} +const RegionSegmentInformationFieldLength = 17; + +function processSegment(segment, visitor) { + const header = segment.header; + + const data = segment.data, + end = segment.end; + let position = segment.start; + let args, at, i, atLength; + switch (header.type) { + case 0: // SymbolDictionary + // 7.4.2 Symbol dictionary segment syntax + const dictionary = {}; + const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 + dictionary.huffman = !!(dictionaryFlags & 1); + dictionary.refinement = !!(dictionaryFlags & 2); + dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; + dictionary.huffmanDWSelector = (dictionaryFlags >> 4) & 3; + dictionary.bitmapSizeSelector = (dictionaryFlags >> 6) & 1; + dictionary.aggregationInstancesSelector = (dictionaryFlags >> 7) & 1; + dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256); + dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512); + dictionary.template = (dictionaryFlags >> 10) & 3; + dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; + position += 2; + if (!dictionary.huffman) { + atLength = dictionary.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + dictionary.at = at; + } + if (dictionary.refinement && !dictionary.refinementTemplate) { + at = []; + for (i = 0; i < 2; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + dictionary.refinementAt = at; + } + dictionary.numberOfExportedSymbols = readUint32(data, position); + position += 4; + dictionary.numberOfNewSymbols = readUint32(data, position); + position += 4; + args = [ + dictionary, + header.number, + header.referredTo, + data, + position, + end, + ]; + break; + case 6: // ImmediateTextRegion + case 7: // ImmediateLosslessTextRegion + const textRegion = {}; + textRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const textRegionSegmentFlags = readUint16(data, position); + position += 2; + textRegion.huffman = !!(textRegionSegmentFlags & 1); + textRegion.refinement = !!(textRegionSegmentFlags & 2); + textRegion.logStripSize = (textRegionSegmentFlags >> 2) & 3; + textRegion.stripSize = 1 << textRegion.logStripSize; + textRegion.referenceCorner = (textRegionSegmentFlags >> 4) & 3; + textRegion.transposed = !!(textRegionSegmentFlags & 64); + textRegion.combinationOperator = (textRegionSegmentFlags >> 7) & 3; + textRegion.defaultPixelValue = (textRegionSegmentFlags >> 9) & 1; + textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; + textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; + if (textRegion.huffman) { + const textRegionHuffmanFlags = readUint16(data, position); + position += 2; + textRegion.huffmanFS = textRegionHuffmanFlags & 3; + textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; + textRegion.huffmanDT = (textRegionHuffmanFlags >> 4) & 3; + textRegion.huffmanRefinementDW = (textRegionHuffmanFlags >> 6) & 3; + textRegion.huffmanRefinementDH = (textRegionHuffmanFlags >> 8) & 3; + textRegion.huffmanRefinementDX = (textRegionHuffmanFlags >> 10) & 3; + textRegion.huffmanRefinementDY = (textRegionHuffmanFlags >> 12) & 3; + textRegion.huffmanRefinementSizeSelector = !!( + textRegionHuffmanFlags & 0x4000 + ); + } + if (textRegion.refinement && !textRegion.refinementTemplate) { + at = []; + for (i = 0; i < 2; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + textRegion.refinementAt = at; + } + textRegion.numberOfSymbolInstances = readUint32(data, position); + position += 4; + args = [textRegion, header.referredTo, data, position, end]; + break; + case 16: // PatternDictionary + // 7.4.4. Pattern dictionary segment syntax + const patternDictionary = {}; + const patternDictionaryFlags = data[position++]; + patternDictionary.mmr = !!(patternDictionaryFlags & 1); + patternDictionary.template = (patternDictionaryFlags >> 1) & 3; + patternDictionary.patternWidth = data[position++]; + patternDictionary.patternHeight = data[position++]; + patternDictionary.maxPatternIndex = readUint32(data, position); + position += 4; + args = [patternDictionary, header.number, data, position, end]; + break; + case 22: // ImmediateHalftoneRegion + case 23: // ImmediateLosslessHalftoneRegion + // 7.4.5 Halftone region segment syntax + const halftoneRegion = {}; + halftoneRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const halftoneRegionFlags = data[position++]; + halftoneRegion.mmr = !!(halftoneRegionFlags & 1); + halftoneRegion.template = (halftoneRegionFlags >> 1) & 3; + halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); + halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7; + halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1; + halftoneRegion.gridWidth = readUint32(data, position); + position += 4; + halftoneRegion.gridHeight = readUint32(data, position); + position += 4; + halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; + position += 4; + halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; + position += 4; + halftoneRegion.gridVectorX = readUint16(data, position); + position += 2; + halftoneRegion.gridVectorY = readUint16(data, position); + position += 2; + args = [halftoneRegion, header.referredTo, data, position, end]; + break; + case 38: // ImmediateGenericRegion + case 39: // ImmediateLosslessGenericRegion + const genericRegion = {}; + genericRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const genericRegionSegmentFlags = data[position++]; + genericRegion.mmr = !!(genericRegionSegmentFlags & 1); + genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; + genericRegion.prediction = !!(genericRegionSegmentFlags & 8); + if (!genericRegion.mmr) { + atLength = genericRegion.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + genericRegion.at = at; + } + args = [genericRegion, data, position, end]; + break; + case 48: // PageInformation + const pageInfo = { + width: readUint32(data, position), + height: readUint32(data, position + 4), + resolutionX: readUint32(data, position + 8), + resolutionY: readUint32(data, position + 12), + }; + if (pageInfo.height === 0xffffffff) { + delete pageInfo.height; + } + const pageSegmentFlags = data[position + 16]; + readUint16(data, position + 17); // pageStripingInformation + pageInfo.lossless = !!(pageSegmentFlags & 1); + pageInfo.refinement = !!(pageSegmentFlags & 2); + pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1; + pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3; + pageInfo.requiresBuffer = !!(pageSegmentFlags & 32); + pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64); + args = [pageInfo]; + break; + case 49: // EndOfPage + break; + case 50: // EndOfStripe + break; + case 51: // EndOfFile + break; + case 53: // Tables + args = [header.number, data, position, end]; + break; + case 62: // 7.4.15 defines 2 extension types which + // are comments and can be ignored. + break; + default: + throw new Jbig2Error( + `segment type ${header.typeName}(${header.type}) is not implemented` + ); + } + const callbackName = "on" + header.typeName; + if (callbackName in visitor) { + // eslint-disable-next-line prefer-spread + visitor[callbackName].apply(visitor, args); + } +} + +function processSegments(segments, visitor) { + for (let i = 0, ii = segments.length; i < ii; i++) { + processSegment(segments[i], visitor); + } +} + +function parseJbig2Chunks(chunks) { + const visitor = new SimpleSegmentVisitor(); + for (let i = 0, ii = chunks.length; i < ii; i++) { + const chunk = chunks[i]; + const segments = readSegments({}, chunk.data, chunk.start, chunk.end); + processSegments(segments, visitor); + } + return visitor.buffer; +} + +function parseJbig2(data) { + if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("IMAGE_DECODERS")) { + throw new Error("Not implemented: parseJbig2"); + } + const end = data.length; + let position = 0; + + if ( + data[position] !== 0x97 || + data[position + 1] !== 0x4a || + data[position + 2] !== 0x42 || + data[position + 3] !== 0x32 || + data[position + 4] !== 0x0d || + data[position + 5] !== 0x0a || + data[position + 6] !== 0x1a || + data[position + 7] !== 0x0a + ) { + throw new Jbig2Error("parseJbig2 - invalid header."); + } + + const header = Object.create(null); + position += 8; + const flags = data[position++]; + header.randomAccess = !(flags & 1); + if (!(flags & 2)) { + header.numberOfPages = readUint32(data, position); + position += 4; + } + + const segments = readSegments(header, data, position, end); + const visitor = new SimpleSegmentVisitor(); + processSegments(segments, visitor); + + const { width, height } = visitor.currentPageInfo; + const bitPacked = visitor.buffer; + const imgData = new Uint8ClampedArray(width * height); + let q = 0, + k = 0; + for (let i = 0; i < height; i++) { + let mask = 0, + buffer; + for (let j = 0; j < width; j++) { + if (!mask) { + mask = 128; + buffer = bitPacked[k++]; + } + imgData[q++] = buffer & mask ? 0 : 255; + mask >>= 1; + } + } + + return { imgData, width, height }; +} + +class SimpleSegmentVisitor { + onPageInformation(info) { + this.currentPageInfo = info; + const rowSize = (info.width + 7) >> 3; + const buffer = new Uint8ClampedArray(rowSize * info.height); + // The contents of ArrayBuffers are initialized to 0. + // Fill the buffer with 0xFF only if info.defaultPixelValue is set + if (info.defaultPixelValue) { + buffer.fill(0xff); + } + this.buffer = buffer; + } + + drawBitmap(regionInfo, bitmap) { + const pageInfo = this.currentPageInfo; + const width = regionInfo.width, + height = regionInfo.height; + const rowSize = (pageInfo.width + 7) >> 3; + const combinationOperator = pageInfo.combinationOperatorOverride + ? regionInfo.combinationOperator + : pageInfo.combinationOperator; + const buffer = this.buffer; + const mask0 = 128 >> (regionInfo.x & 7); + let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); + let i, j, mask, offset; + switch (combinationOperator) { + case 0: // OR + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { + if (bitmap[i][j]) { + buffer[offset] |= mask; + } + mask >>= 1; + if (!mask) { + mask = 128; + offset++; + } + } + offset0 += rowSize; + } + break; + case 2: // XOR + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { + if (bitmap[i][j]) { + buffer[offset] ^= mask; + } + mask >>= 1; + if (!mask) { + mask = 128; + offset++; + } + } + offset0 += rowSize; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + + onImmediateGenericRegion(region, data, start, end) { + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeBitmap( + region.mmr, + regionInfo.width, + regionInfo.height, + region.template, + region.prediction, + null, + region.at, + decodingContext + ); + this.drawBitmap(regionInfo, bitmap); + } + + onImmediateLosslessGenericRegion() { + this.onImmediateGenericRegion(...arguments); + } + + onSymbolDictionary( + dictionary, + currentSegment, + referredSegments, + data, + start, + end + ) { + let huffmanTables, huffmanInput; + if (dictionary.huffman) { + huffmanTables = getSymbolDictionaryHuffmanTables( + dictionary, + referredSegments, + this.customTables + ); + huffmanInput = new Reader(data, start, end); + } + + // Combines exported symbols from all referred segments + let symbols = this.symbols; + if (!symbols) { + this.symbols = symbols = {}; + } + + const inputSymbols = []; + for (const referredSegment of referredSegments) { + const referredSymbols = symbols[referredSegment]; + // referredSymbols is undefined when we have a reference to a Tables + // segment instead of a SymbolDictionary. + if (referredSymbols) { + inputSymbols.push(...referredSymbols); + } + } + + const decodingContext = new DecodingContext(data, start, end); + symbols[currentSegment] = decodeSymbolDictionary( + dictionary.huffman, + dictionary.refinement, + inputSymbols, + dictionary.numberOfNewSymbols, + dictionary.numberOfExportedSymbols, + huffmanTables, + dictionary.template, + dictionary.at, + dictionary.refinementTemplate, + dictionary.refinementAt, + decodingContext, + huffmanInput + ); + } + + onImmediateTextRegion(region, referredSegments, data, start, end) { + const regionInfo = region.info; + let huffmanTables, huffmanInput; + + // Combines exported symbols from all referred segments + const symbols = this.symbols; + const inputSymbols = []; + for (const referredSegment of referredSegments) { + const referredSymbols = symbols[referredSegment]; + // referredSymbols is undefined when we have a reference to a Tables + // segment instead of a SymbolDictionary. + if (referredSymbols) { + inputSymbols.push(...referredSymbols); + } + } + const symbolCodeLength = log2(inputSymbols.length); + if (region.huffman) { + huffmanInput = new Reader(data, start, end); + huffmanTables = getTextRegionHuffmanTables( + region, + referredSegments, + this.customTables, + inputSymbols.length, + huffmanInput + ); + } + + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeTextRegion( + region.huffman, + region.refinement, + regionInfo.width, + regionInfo.height, + region.defaultPixelValue, + region.numberOfSymbolInstances, + region.stripSize, + inputSymbols, + symbolCodeLength, + region.transposed, + region.dsOffset, + region.referenceCorner, + region.combinationOperator, + huffmanTables, + region.refinementTemplate, + region.refinementAt, + decodingContext, + region.logStripSize, + huffmanInput + ); + this.drawBitmap(regionInfo, bitmap); + } + + onImmediateLosslessTextRegion() { + this.onImmediateTextRegion(...arguments); + } + + onPatternDictionary(dictionary, currentSegment, data, start, end) { + let patterns = this.patterns; + if (!patterns) { + this.patterns = patterns = {}; + } + const decodingContext = new DecodingContext(data, start, end); + patterns[currentSegment] = decodePatternDictionary( + dictionary.mmr, + dictionary.patternWidth, + dictionary.patternHeight, + dictionary.maxPatternIndex, + dictionary.template, + decodingContext + ); + } + + onImmediateHalftoneRegion(region, referredSegments, data, start, end) { + // HalftoneRegion refers to exactly one PatternDictionary. + const patterns = this.patterns[referredSegments[0]]; + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeHalftoneRegion( + region.mmr, + patterns, + region.template, + regionInfo.width, + regionInfo.height, + region.defaultPixelValue, + region.enableSkip, + region.combinationOperator, + region.gridWidth, + region.gridHeight, + region.gridOffsetX, + region.gridOffsetY, + region.gridVectorX, + region.gridVectorY, + decodingContext + ); + this.drawBitmap(regionInfo, bitmap); + } + + onImmediateLosslessHalftoneRegion() { + this.onImmediateHalftoneRegion(...arguments); + } + + onTables(currentSegment, data, start, end) { + let customTables = this.customTables; + if (!customTables) { + this.customTables = customTables = {}; + } + customTables[currentSegment] = decodeTablesSegment(data, start, end); + } +} + +class HuffmanLine { + constructor(lineData) { + if (lineData.length === 2) { + // OOB line. + this.isOOB = true; + this.rangeLow = 0; + this.prefixLength = lineData[0]; + this.rangeLength = 0; + this.prefixCode = lineData[1]; + this.isLowerRange = false; + } else { + // Normal, upper range or lower range line. + // Upper range lines are processed like normal lines. + this.isOOB = false; + this.rangeLow = lineData[0]; + this.prefixLength = lineData[1]; + this.rangeLength = lineData[2]; + this.prefixCode = lineData[3]; + this.isLowerRange = lineData[4] === "lower"; + } + } +} + +class HuffmanTreeNode { + constructor(line) { + this.children = []; + if (line) { + // Leaf node + this.isLeaf = true; + this.rangeLength = line.rangeLength; + this.rangeLow = line.rangeLow; + this.isLowerRange = line.isLowerRange; + this.isOOB = line.isOOB; + } else { + // Intermediate or root node + this.isLeaf = false; + } + } + + buildTree(line, shift) { + const bit = (line.prefixCode >> shift) & 1; + if (shift <= 0) { + // Create a leaf node. + this.children[bit] = new HuffmanTreeNode(line); + } else { + // Create an intermediate node and continue recursively. + let node = this.children[bit]; + if (!node) { + this.children[bit] = node = new HuffmanTreeNode(null); + } + node.buildTree(line, shift - 1); + } + } + + decodeNode(reader) { + if (this.isLeaf) { + if (this.isOOB) { + return null; + } + const htOffset = reader.readBits(this.rangeLength); + return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset); + } + const node = this.children[reader.readBit()]; + if (!node) { + throw new Jbig2Error("invalid Huffman data"); + } + return node.decodeNode(reader); + } +} + +class HuffmanTable { + constructor(lines, prefixCodesDone) { + if (!prefixCodesDone) { + this.assignPrefixCodes(lines); + } + // Create Huffman tree. + this.rootNode = new HuffmanTreeNode(null); + for (let i = 0, ii = lines.length; i < ii; i++) { + const line = lines[i]; + if (line.prefixLength > 0) { + this.rootNode.buildTree(line, line.prefixLength - 1); + } + } + } + + decode(reader) { + return this.rootNode.decodeNode(reader); + } + + assignPrefixCodes(lines) { + // Annex B.3 Assigning the prefix codes. + const linesLength = lines.length; + let prefixLengthMax = 0; + for (let i = 0; i < linesLength; i++) { + prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength); + } + + const histogram = new Uint32Array(prefixLengthMax + 1); + for (let i = 0; i < linesLength; i++) { + histogram[lines[i].prefixLength]++; + } + let currentLength = 1, + firstCode = 0, + currentCode, + currentTemp, + line; + histogram[0] = 0; + + while (currentLength <= prefixLengthMax) { + firstCode = (firstCode + histogram[currentLength - 1]) << 1; + currentCode = firstCode; + currentTemp = 0; + while (currentTemp < linesLength) { + line = lines[currentTemp]; + if (line.prefixLength === currentLength) { + line.prefixCode = currentCode; + currentCode++; + } + currentTemp++; + } + currentLength++; + } + } +} + +function decodeTablesSegment(data, start, end) { + // Decodes a Tables segment, i.e., a custom Huffman table. + // Annex B.2 Code table structure. + const flags = data[start]; + const lowestValue = readUint32(data, start + 1) & 0xffffffff; + const highestValue = readUint32(data, start + 5) & 0xffffffff; + const reader = new Reader(data, start + 9, end); + + const prefixSizeBits = ((flags >> 1) & 7) + 1; + const rangeSizeBits = ((flags >> 4) & 7) + 1; + const lines = []; + let prefixLength, + rangeLength, + currentRangeLow = lowestValue; + + // Normal table lines + do { + prefixLength = reader.readBits(prefixSizeBits); + rangeLength = reader.readBits(rangeSizeBits); + lines.push( + new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]) + ); + currentRangeLow += 1 << rangeLength; + } while (currentRangeLow < highestValue); + + // Lower range table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"])); + + // Upper range table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0])); + + if (flags & 1) { + // Out-of-band table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([prefixLength, 0])); + } + + return new HuffmanTable(lines, false); +} + +const standardTablesCache = {}; + +function getStandardTable(number) { + // Annex B.5 Standard Huffman tables. + let table = standardTablesCache[number]; + if (table) { + return table; + } + let lines; + switch (number) { + case 1: + lines = [ + [0, 1, 4, 0x0], + [16, 2, 8, 0x2], + [272, 3, 16, 0x6], + [65808, 3, 32, 0x7], // upper + ]; + break; + case 2: + lines = [ + [0, 1, 0, 0x0], + [1, 2, 0, 0x2], + [2, 3, 0, 0x6], + [3, 4, 3, 0xe], + [11, 5, 6, 0x1e], + [75, 6, 32, 0x3e], // upper + [6, 0x3f], // OOB + ]; + break; + case 3: + lines = [ + [-256, 8, 8, 0xfe], + [0, 1, 0, 0x0], + [1, 2, 0, 0x2], + [2, 3, 0, 0x6], + [3, 4, 3, 0xe], + [11, 5, 6, 0x1e], + [-257, 8, 32, 0xff, "lower"], + [75, 7, 32, 0x7e], // upper + [6, 0x3e], // OOB + ]; + break; + case 4: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 0, 0x6], + [4, 4, 3, 0xe], + [12, 5, 6, 0x1e], + [76, 5, 32, 0x1f], // upper + ]; + break; + case 5: + lines = [ + [-255, 7, 8, 0x7e], + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 0, 0x6], + [4, 4, 3, 0xe], + [12, 5, 6, 0x1e], + [-256, 7, 32, 0x7f, "lower"], + [76, 6, 32, 0x3e], // upper + ]; + break; + case 6: + lines = [ + [-2048, 5, 10, 0x1c], + [-1024, 4, 9, 0x8], + [-512, 4, 8, 0x9], + [-256, 4, 7, 0xa], + [-128, 5, 6, 0x1d], + [-64, 5, 5, 0x1e], + [-32, 4, 5, 0xb], + [0, 2, 7, 0x0], + [128, 3, 7, 0x2], + [256, 3, 8, 0x3], + [512, 4, 9, 0xc], + [1024, 4, 10, 0xd], + [-2049, 6, 32, 0x3e, "lower"], + [2048, 6, 32, 0x3f], // upper + ]; + break; + case 7: + lines = [ + [-1024, 4, 9, 0x8], + [-512, 3, 8, 0x0], + [-256, 4, 7, 0x9], + [-128, 5, 6, 0x1a], + [-64, 5, 5, 0x1b], + [-32, 4, 5, 0xa], + [0, 4, 5, 0xb], + [32, 5, 5, 0x1c], + [64, 5, 6, 0x1d], + [128, 4, 7, 0xc], + [256, 3, 8, 0x1], + [512, 3, 9, 0x2], + [1024, 3, 10, 0x3], + [-1025, 5, 32, 0x1e, "lower"], + [2048, 5, 32, 0x1f], // upper + ]; + break; + case 8: + lines = [ + [-15, 8, 3, 0xfc], + [-7, 9, 1, 0x1fc], + [-5, 8, 1, 0xfd], + [-3, 9, 0, 0x1fd], + [-2, 7, 0, 0x7c], + [-1, 4, 0, 0xa], + [0, 2, 1, 0x0], + [2, 5, 0, 0x1a], + [3, 6, 0, 0x3a], + [4, 3, 4, 0x4], + [20, 6, 1, 0x3b], + [22, 4, 4, 0xb], + [38, 4, 5, 0xc], + [70, 5, 6, 0x1b], + [134, 5, 7, 0x1c], + [262, 6, 7, 0x3c], + [390, 7, 8, 0x7d], + [646, 6, 10, 0x3d], + [-16, 9, 32, 0x1fe, "lower"], + [1670, 9, 32, 0x1ff], // upper + [2, 0x1], // OOB + ]; + break; + case 9: + lines = [ + [-31, 8, 4, 0xfc], + [-15, 9, 2, 0x1fc], + [-11, 8, 2, 0xfd], + [-7, 9, 1, 0x1fd], + [-5, 7, 1, 0x7c], + [-3, 4, 1, 0xa], + [-1, 3, 1, 0x2], + [1, 3, 1, 0x3], + [3, 5, 1, 0x1a], + [5, 6, 1, 0x3a], + [7, 3, 5, 0x4], + [39, 6, 2, 0x3b], + [43, 4, 5, 0xb], + [75, 4, 6, 0xc], + [139, 5, 7, 0x1b], + [267, 5, 8, 0x1c], + [523, 6, 8, 0x3c], + [779, 7, 9, 0x7d], + [1291, 6, 11, 0x3d], + [-32, 9, 32, 0x1fe, "lower"], + [3339, 9, 32, 0x1ff], // upper + [2, 0x0], // OOB + ]; + break; + case 10: + lines = [ + [-21, 7, 4, 0x7a], + [-5, 8, 0, 0xfc], + [-4, 7, 0, 0x7b], + [-3, 5, 0, 0x18], + [-2, 2, 2, 0x0], + [2, 5, 0, 0x19], + [3, 6, 0, 0x36], + [4, 7, 0, 0x7c], + [5, 8, 0, 0xfd], + [6, 2, 6, 0x1], + [70, 5, 5, 0x1a], + [102, 6, 5, 0x37], + [134, 6, 6, 0x38], + [198, 6, 7, 0x39], + [326, 6, 8, 0x3a], + [582, 6, 9, 0x3b], + [1094, 6, 10, 0x3c], + [2118, 7, 11, 0x7d], + [-22, 8, 32, 0xfe, "lower"], + [4166, 8, 32, 0xff], // upper + [2, 0x2], // OOB + ]; + break; + case 11: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 1, 0x2], + [4, 4, 0, 0xc], + [5, 4, 1, 0xd], + [7, 5, 1, 0x1c], + [9, 5, 2, 0x1d], + [13, 6, 2, 0x3c], + [17, 7, 2, 0x7a], + [21, 7, 3, 0x7b], + [29, 7, 4, 0x7c], + [45, 7, 5, 0x7d], + [77, 7, 6, 0x7e], + [141, 7, 32, 0x7f], // upper + ]; + break; + case 12: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 1, 0x6], + [5, 5, 0, 0x1c], + [6, 5, 1, 0x1d], + [8, 6, 1, 0x3c], + [10, 7, 0, 0x7a], + [11, 7, 1, 0x7b], + [13, 7, 2, 0x7c], + [17, 7, 3, 0x7d], + [25, 7, 4, 0x7e], + [41, 8, 5, 0xfe], + [73, 8, 32, 0xff], // upper + ]; + break; + case 13: + lines = [ + [1, 1, 0, 0x0], + [2, 3, 0, 0x4], + [3, 4, 0, 0xc], + [4, 5, 0, 0x1c], + [5, 4, 1, 0xd], + [7, 3, 3, 0x5], + [15, 6, 1, 0x3a], + [17, 6, 2, 0x3b], + [21, 6, 3, 0x3c], + [29, 6, 4, 0x3d], + [45, 6, 5, 0x3e], + [77, 7, 6, 0x7e], + [141, 7, 32, 0x7f], // upper + ]; + break; + case 14: + lines = [ + [-2, 3, 0, 0x4], + [-1, 3, 0, 0x5], + [0, 1, 0, 0x0], + [1, 3, 0, 0x6], + [2, 3, 0, 0x7], + ]; + break; + case 15: + lines = [ + [-24, 7, 4, 0x7c], + [-8, 6, 2, 0x3c], + [-4, 5, 1, 0x1c], + [-2, 4, 0, 0xc], + [-1, 3, 0, 0x4], + [0, 1, 0, 0x0], + [1, 3, 0, 0x5], + [2, 4, 0, 0xd], + [3, 5, 1, 0x1d], + [5, 6, 2, 0x3d], + [9, 7, 4, 0x7d], + [-25, 7, 32, 0x7e, "lower"], + [25, 7, 32, 0x7f], // upper + ]; + break; + default: + throw new Jbig2Error(`standard table B.${number} does not exist`); + } + + for (let i = 0, ii = lines.length; i < ii; i++) { + lines[i] = new HuffmanLine(lines[i]); + } + table = new HuffmanTable(lines, true); + standardTablesCache[number] = table; + return table; +} + +class Reader { + constructor(data, start, end) { + this.data = data; + this.start = start; + this.end = end; + this.position = start; + this.shift = -1; + this.currentByte = 0; + } + + readBit() { + if (this.shift < 0) { + if (this.position >= this.end) { + throw new Jbig2Error("end of data while reading bit"); + } + this.currentByte = this.data[this.position++]; + this.shift = 7; + } + const bit = (this.currentByte >> this.shift) & 1; + this.shift--; + return bit; + } + + readBits(numBits) { + let result = 0, + i; + for (i = numBits - 1; i >= 0; i--) { + result |= this.readBit() << i; + } + return result; + } + + byteAlign() { + this.shift = -1; + } + + next() { + if (this.position >= this.end) { + return -1; + } + return this.data[this.position++]; + } +} + +function getCustomHuffmanTable(index, referredTo, customTables) { + // Returns a Tables segment that has been earlier decoded. + // See 7.4.2.1.6 (symbol dictionary) or 7.4.3.1.6 (text region). + let currentIndex = 0; + for (let i = 0, ii = referredTo.length; i < ii; i++) { + const table = customTables[referredTo[i]]; + if (table) { + if (index === currentIndex) { + return table; + } + currentIndex++; + } + } + throw new Jbig2Error("can't find custom Huffman table"); +} + +function getTextRegionHuffmanTables( + textRegion, + referredTo, + customTables, + numberOfSymbols, + reader +) { + // 7.4.3.1.7 Symbol ID Huffman table decoding + + // Read code lengths for RUNCODEs 0...34. + const codes = []; + for (let i = 0; i <= 34; i++) { + const codeLength = reader.readBits(4); + codes.push(new HuffmanLine([i, codeLength, 0, 0])); + } + // Assign Huffman codes for RUNCODEs. + const runCodesTable = new HuffmanTable(codes, false); + + // Read a Huffman code using the assignment above. + // Interpret the RUNCODE codes and the additional bits (if any). + codes.length = 0; + for (let i = 0; i < numberOfSymbols; ) { + const codeLength = runCodesTable.decode(reader); + if (codeLength >= 32) { + let repeatedLength, numberOfRepeats, j; + switch (codeLength) { + case 32: + if (i === 0) { + throw new Jbig2Error("no previous value in symbol ID table"); + } + numberOfRepeats = reader.readBits(2) + 3; + repeatedLength = codes[i - 1].prefixLength; + break; + case 33: + numberOfRepeats = reader.readBits(3) + 3; + repeatedLength = 0; + break; + case 34: + numberOfRepeats = reader.readBits(7) + 11; + repeatedLength = 0; + break; + default: + throw new Jbig2Error("invalid code length in symbol ID table"); + } + for (j = 0; j < numberOfRepeats; j++) { + codes.push(new HuffmanLine([i, repeatedLength, 0, 0])); + i++; + } + } else { + codes.push(new HuffmanLine([i, codeLength, 0, 0])); + i++; + } + } + reader.byteAlign(); + const symbolIDTable = new HuffmanTable(codes, false); + + // 7.4.3.1.6 Text region segment Huffman table selection + + let customIndex = 0, + tableFirstS, + tableDeltaS, + tableDeltaT; + + switch (textRegion.huffmanFS) { + case 0: + case 1: + tableFirstS = getStandardTable(textRegion.huffmanFS + 6); + break; + case 3: + tableFirstS = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman FS selector"); + } + + switch (textRegion.huffmanDS) { + case 0: + case 1: + case 2: + tableDeltaS = getStandardTable(textRegion.huffmanDS + 8); + break; + case 3: + tableDeltaS = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DS selector"); + } + + switch (textRegion.huffmanDT) { + case 0: + case 1: + case 2: + tableDeltaT = getStandardTable(textRegion.huffmanDT + 11); + break; + case 3: + tableDeltaT = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DT selector"); + } + + if (textRegion.refinement) { + // Load tables RDW, RDH, RDX and RDY. + throw new Jbig2Error("refinement with Huffman is not supported"); + } + + return { + symbolIDTable, + tableFirstS, + tableDeltaS, + tableDeltaT, + }; +} + +function getSymbolDictionaryHuffmanTables( + dictionary, + referredTo, + customTables +) { + // 7.4.2.1.6 Symbol dictionary segment Huffman table selection + + let customIndex = 0, + tableDeltaHeight, + tableDeltaWidth; + switch (dictionary.huffmanDHSelector) { + case 0: + case 1: + tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4); + break; + case 3: + tableDeltaHeight = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DH selector"); + } + + switch (dictionary.huffmanDWSelector) { + case 0: + case 1: + tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2); + break; + case 3: + tableDeltaWidth = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DW selector"); + } + + let tableBitmapSize, tableAggregateInstances; + if (dictionary.bitmapSizeSelector) { + tableBitmapSize = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + } else { + tableBitmapSize = getStandardTable(1); + } + + if (dictionary.aggregationInstancesSelector) { + tableAggregateInstances = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + } else { + tableAggregateInstances = getStandardTable(1); + } + + return { + tableDeltaHeight, + tableDeltaWidth, + tableBitmapSize, + tableAggregateInstances, + }; +} + +function readUncompressedBitmap(reader, width, height) { + const bitmap = []; + for (let y = 0; y < height; y++) { + const row = new Uint8Array(width); + bitmap.push(row); + for (let x = 0; x < width; x++) { + row[x] = reader.readBit(); + } + reader.byteAlign(); + } + return bitmap; +} + +function decodeMMRBitmap(input, width, height, endOfBlock) { + // MMR is the same compression algorithm as the PDF filter + // CCITTFaxDecode with /K -1. + const params = { + K: -1, + Columns: width, + Rows: height, + BlackIs1: true, + EndOfBlock: endOfBlock, + }; + const decoder = new CCITTFaxDecoder(input, params); + const bitmap = []; + let currentByte, + eof = false; + + for (let y = 0; y < height; y++) { + const row = new Uint8Array(width); + bitmap.push(row); + let shift = -1; + for (let x = 0; x < width; x++) { + if (shift < 0) { + currentByte = decoder.readNextChar(); + if (currentByte === -1) { + // Set the rest of the bits to zero. + currentByte = 0; + eof = true; + } + shift = 7; + } + row[x] = (currentByte >> shift) & 1; + shift--; + } + } + + if (endOfBlock && !eof) { + // Read until EOFB has been consumed. + const lookForEOFLimit = 5; + for (let i = 0; i < lookForEOFLimit; i++) { + if (decoder.readNextChar() === -1) { + break; + } + } + } + + return bitmap; +} + +class Jbig2Image { + parseChunks(chunks) { + return parseJbig2Chunks(chunks); + } + + parse(data) { + if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("IMAGE_DECODERS")) { + throw new Error("Not implemented: Jbig2Image.parse"); + } + const { imgData, width, height } = parseJbig2(data); + this.width = width; + this.height = height; + return imgData; + } +} + +export { Jbig2Error, Jbig2Image }; diff --git a/frontend/public/pdf.js/src/core/jbig2_stream.js b/frontend/public/pdf.js/src/core/jbig2_stream.js new file mode 100644 index 0000000..126669c --- /dev/null +++ b/frontend/public/pdf.js/src/core/jbig2_stream.js @@ -0,0 +1,85 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { BaseStream } from "./base_stream.js"; +import { DecodeStream } from "./decode_stream.js"; +import { Dict } from "./primitives.js"; +import { Jbig2Image } from "./jbig2.js"; +import { shadow } from "../shared/util.js"; + +/** + * For JBIG2's we use a library to decode these images and + * the stream behaves like all the other DecodeStreams. + */ +class Jbig2Stream extends DecodeStream { + constructor(stream, maybeLength, params) { + super(maybeLength); + + this.stream = stream; + this.dict = stream.dict; + this.maybeLength = maybeLength; + this.params = params; + } + + get bytes() { + // If `this.maybeLength` is null, we'll get the entire stream. + return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); + } + + ensureBuffer(requested) { + // No-op, since `this.readBlock` will always parse the entire image and + // directly insert all of its data into `this.buffer`. + } + + readBlock() { + this.decodeImage(); + } + + decodeImage(bytes) { + if (this.eof) { + return this.buffer; + } + bytes ||= this.bytes; + const jbig2Image = new Jbig2Image(); + + const chunks = []; + if (this.params instanceof Dict) { + const globalsStream = this.params.get("JBIG2Globals"); + if (globalsStream instanceof BaseStream) { + const globals = globalsStream.getBytes(); + chunks.push({ data: globals, start: 0, end: globals.length }); + } + } + chunks.push({ data: bytes, start: 0, end: bytes.length }); + const data = jbig2Image.parseChunks(chunks); + const dataLength = data.length; + + // JBIG2 had black as 1 and white as 0, inverting the colors + for (let i = 0; i < dataLength; i++) { + data[i] ^= 0xff; + } + this.buffer = data; + this.bufferLength = dataLength; + this.eof = true; + + return this.buffer; + } + + get canAsyncDecodeImageFromBuffer() { + return this.stream.isAsync; + } +} + +export { Jbig2Stream }; diff --git a/frontend/public/pdf.js/src/core/jpeg_stream.js b/frontend/public/pdf.js/src/core/jpeg_stream.js new file mode 100644 index 0000000..4af24ee --- /dev/null +++ b/frontend/public/pdf.js/src/core/jpeg_stream.js @@ -0,0 +1,199 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { FeatureTest, shadow, warn } from "../shared/util.js"; +import { DecodeStream } from "./decode_stream.js"; +import { Dict } from "./primitives.js"; +import { JpegImage } from "./jpg.js"; + +/** + * For JPEG's we use a library to decode these images and the stream behaves + * like all the other DecodeStreams. + */ +class JpegStream extends DecodeStream { + static #isImageDecoderSupported = FeatureTest.isImageDecoderSupported; + + constructor(stream, maybeLength, params) { + super(maybeLength); + + this.stream = stream; + this.dict = stream.dict; + this.maybeLength = maybeLength; + this.params = params; + } + + static get canUseImageDecoder() { + return shadow( + this, + "canUseImageDecoder", + this.#isImageDecoderSupported + ? ImageDecoder.isTypeSupported("image/jpeg") + : Promise.resolve(false) + ); + } + + static setOptions({ isImageDecoderSupported = false }) { + this.#isImageDecoderSupported = isImageDecoderSupported; + } + + get bytes() { + // If `this.maybeLength` is null, we'll get the entire stream. + return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); + } + + ensureBuffer(requested) { + // No-op, since `this.readBlock` will always parse the entire image and + // directly insert all of its data into `this.buffer`. + } + + readBlock() { + this.decodeImage(); + } + + get jpegOptions() { + const jpegOptions = { + decodeTransform: undefined, + colorTransform: undefined, + }; + + // Checking if values need to be transformed before conversion. + const decodeArr = this.dict.getArray("D", "Decode"); + if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) { + const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8; + const decodeArrLength = decodeArr.length; + const transform = new Int32Array(decodeArrLength); + let transformNeeded = false; + const maxValue = (1 << bitsPerComponent) - 1; + for (let i = 0; i < decodeArrLength; i += 2) { + transform[i] = ((decodeArr[i + 1] - decodeArr[i]) * 256) | 0; + transform[i + 1] = (decodeArr[i] * maxValue) | 0; + if (transform[i] !== 256 || transform[i + 1] !== 0) { + transformNeeded = true; + } + } + if (transformNeeded) { + jpegOptions.decodeTransform = transform; + } + } + // Fetching the 'ColorTransform' entry, if it exists. + if (this.params instanceof Dict) { + const colorTransform = this.params.get("ColorTransform"); + if (Number.isInteger(colorTransform)) { + jpegOptions.colorTransform = colorTransform; + } + } + return shadow(this, "jpegOptions", jpegOptions); + } + + #skipUselessBytes(data) { + // Some images may contain 'junk' before the SOI (start-of-image) marker. + // Note: this seems to mainly affect inline images. + for (let i = 0, ii = data.length - 1; i < ii; i++) { + if (data[i] === 0xff && data[i + 1] === 0xd8) { + if (i > 0) { + data = data.subarray(i); + } + break; + } + } + return data; + } + + decodeImage(bytes) { + if (this.eof) { + return this.buffer; + } + bytes = this.#skipUselessBytes(bytes || this.bytes); + + // TODO: if an image has a mask we need to combine the data. + // So ideally get a VideoFrame from getTransferableImage and then use + // copyTo. + + const jpegImage = new JpegImage(this.jpegOptions); + jpegImage.parse(bytes); + const data = jpegImage.getData({ + width: this.drawWidth, + height: this.drawHeight, + forceRGBA: this.forceRGBA, + forceRGB: this.forceRGB, + isSourcePDF: true, + }); + this.buffer = data; + this.bufferLength = data.length; + this.eof = true; + + return this.buffer; + } + + get canAsyncDecodeImageFromBuffer() { + return this.stream.isAsync; + } + + async getTransferableImage() { + if (!(await JpegStream.canUseImageDecoder)) { + return null; + } + const jpegOptions = this.jpegOptions; + if (jpegOptions.decodeTransform) { + // TODO: We could decode the image thanks to ImageDecoder and then + // get the pixels with copyTo and apply the decodeTransform. + return null; + } + let decoder; + try { + // TODO: If the stream is Flate & DCT we could try to just pipe the + // the DecompressionStream into the ImageDecoder: it'll avoid the + // intermediate ArrayBuffer. + const bytes = + (this.canAsyncDecodeImageFromBuffer && + (await this.stream.asyncGetBytes())) || + this.bytes; + if (!bytes) { + return null; + } + let data = this.#skipUselessBytes(bytes); + const useImageDecoder = JpegImage.canUseImageDecoder( + data, + jpegOptions.colorTransform + ); + if (!useImageDecoder) { + return null; + } + if (useImageDecoder.exifStart) { + // Replace the entire EXIF-block with dummy data, to ensure that a + // non-default EXIF orientation won't cause the image to be rotated + // when using `ImageDecoder` (fixes bug1942064.pdf). + // + // Copy the data first, to avoid modifying the original PDF document. + data = data.slice(); + data.fill(0x00, useImageDecoder.exifStart, useImageDecoder.exifEnd); + } + decoder = new ImageDecoder({ + data, + type: "image/jpeg", + preferAnimation: false, + }); + + return (await decoder.decode()).image; + } catch (reason) { + warn(`getTransferableImage - failed: "${reason}".`); + return null; + } finally { + decoder?.close(); + } + } +} + +export { JpegStream }; diff --git a/frontend/public/pdf.js/src/core/jpg.js b/frontend/public/pdf.js/src/core/jpg.js new file mode 100644 index 0000000..26ff546 --- /dev/null +++ b/frontend/public/pdf.js/src/core/jpg.js @@ -0,0 +1,1667 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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 { assert, BaseException, warn } from "../shared/util.js"; +import { grayToRGBA } from "../shared/image_utils.js"; +import { readUint16 } from "./core_utils.js"; + +class JpegError extends BaseException { + constructor(msg) { + super(msg, "JpegError"); + } +} + +class DNLMarkerError extends BaseException { + constructor(message, scanLines) { + super(message, "DNLMarkerError"); + this.scanLines = scanLines; + } +} + +class EOIMarkerError extends BaseException { + constructor(msg) { + super(msg, "EOIMarkerError"); + } +} + +/** + * This code was forked from https://github.com/notmasteryet/jpgjs. + * The original version was created by GitHub user notmasteryet. + * + * - The JPEG specification can be found in the ITU CCITT Recommendation T.81 + * (www.w3.org/Graphics/JPEG/itu-t81.pdf) + * - The JFIF specification can be found in the JPEG File Interchange Format + * (www.w3.org/Graphics/JPEG/jfif3.pdf) + * - The Adobe Application-Specific JPEG markers in the + * Supporting the DCT Filters in PostScript Level 2, Technical Note #5116 + * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) + */ + +// prettier-ignore +const dctZigZag = new Uint8Array([ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 +]); + +const dctCos1 = 4017; // cos(pi/16) +const dctSin1 = 799; // sin(pi/16) +const dctCos3 = 3406; // cos(3*pi/16) +const dctSin3 = 2276; // sin(3*pi/16) +const dctCos6 = 1567; // cos(6*pi/16) +const dctSin6 = 3784; // sin(6*pi/16) +const dctSqrt2 = 5793; // sqrt(2) +const dctSqrt1d2 = 2896; // sqrt(2) / 2 + +function buildHuffmanTable(codeLengths, values) { + let k = 0, + i, + j, + length = 16; + while (length > 0 && !codeLengths[length - 1]) { + length--; + } + const code = [{ children: [], index: 0 }]; + let p = code[0], + q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + while (p.index > 0) { + p = code.pop(); + } + p.index++; + code.push(p); + while (code.length <= i) { + code.push((q = { children: [], index: 0 })); + p.children[p.index] = q.children; + p = q; + } + k++; + } + if (i + 1 < length) { + // p here points to last code + code.push((q = { children: [], index: 0 })); + p.children[p.index] = q.children; + p = q; + } + } + return code[0].children; +} + +function getBlockBufferOffset(component, row, col) { + return 64 * ((component.blocksPerLine + 1) * row + col); +} + +function decodeScan( + data, + offset, + frame, + components, + resetInterval, + spectralStart, + spectralEnd, + successivePrev, + successive, + parseDNLMarker = false +) { + const mcusPerLine = frame.mcusPerLine; + const progressive = frame.progressive; + + const startOffset = offset; + let bitsData = 0, + bitsCount = 0; + + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData === 0xff) { + const nextByte = data[offset++]; + if (nextByte) { + if (nextByte === /* DNL = */ 0xdc && parseDNLMarker) { + offset += 2; // Skip marker length. + + const scanLines = readUint16(data, offset); + offset += 2; + if (scanLines > 0 && scanLines !== frame.scanLines) { + throw new DNLMarkerError( + "Found DNL marker (0xFFDC) while parsing scan data", + scanLines + ); + } + } else if (nextByte === /* EOI = */ 0xd9) { + if (parseDNLMarker) { + // NOTE: only 8-bit JPEG images are supported in this decoder. + const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0); + // Heuristic to attempt to handle corrupt JPEG images with too + // large `scanLines` parameter, by falling back to the currently + // parsed number of scanLines when it's at least (approximately) + // one "half" order of magnitude smaller than expected (fixes + // issue10880.pdf, issue10989.pdf, issue15492.pdf). + if ( + maybeScanLines > 0 && + Math.round(frame.scanLines / maybeScanLines) >= 5 + ) { + throw new DNLMarkerError( + "Found EOI marker (0xFFD9) while parsing scan data, " + + "possibly caused by incorrect `scanLines` parameter", + maybeScanLines + ); + } + } + throw new EOIMarkerError( + "Found EOI marker (0xFFD9) while parsing scan data" + ); + } + throw new JpegError( + `unexpected marker ${((bitsData << 8) | nextByte).toString(16)}` + ); + } + // unstuff 0 + } + bitsCount = 7; + return bitsData >>> 7; + } + + function decodeHuffman(tree) { + let node = tree; + while (true) { + node = node[readBit()]; + switch (typeof node) { + case "number": + return node; + case "object": + continue; + } + throw new JpegError("invalid huffman sequence"); + } + } + + function receive(length) { + let n = 0; + while (length > 0) { + n = (n << 1) | readBit(); + length--; + } + return n; + } + + function receiveAndExtend(length) { + if (length === 1) { + return readBit() === 1 ? 1 : -1; + } + const n = receive(length); + if (n >= 1 << (length - 1)) { + return n; + } + return n + (-1 << length) + 1; + } + + function decodeBaseline(component, blockOffset) { + const t = decodeHuffman(component.huffmanTableDC); + const diff = t === 0 ? 0 : receiveAndExtend(t); + component.blockData[blockOffset] = component.pred += diff; + let k = 1; + while (k < 64) { + const rs = decodeHuffman(component.huffmanTableAC); + const s = rs & 15, + r = rs >> 4; + if (s === 0) { + if (r < 15) { + break; + } + k += 16; + continue; + } + k += r; + const z = dctZigZag[k]; + component.blockData[blockOffset + z] = receiveAndExtend(s); + k++; + } + } + + function decodeDCFirst(component, blockOffset) { + const t = decodeHuffman(component.huffmanTableDC); + const diff = t === 0 ? 0 : receiveAndExtend(t) << successive; + component.blockData[blockOffset] = component.pred += diff; + } + + function decodeDCSuccessive(component, blockOffset) { + component.blockData[blockOffset] |= readBit() << successive; + } + + let eobrun = 0; + function decodeACFirst(component, blockOffset) { + if (eobrun > 0) { + eobrun--; + return; + } + let k = spectralStart; + const e = spectralEnd; + while (k <= e) { + const rs = decodeHuffman(component.huffmanTableAC); + const s = rs & 15, + r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; + } + k += 16; + continue; + } + k += r; + const z = dctZigZag[k]; + component.blockData[blockOffset + z] = + receiveAndExtend(s) * (1 << successive); + k++; + } + } + + let successiveACState = 0, + successiveACNextValue; + function decodeACSuccessive(component, blockOffset) { + let k = spectralStart; + const e = spectralEnd; + let r = 0; + let s; + let rs; + while (k <= e) { + const offsetZ = blockOffset + dctZigZag[k]; + const sign = component.blockData[offsetZ] < 0 ? -1 : 1; + switch (successiveACState) { + case 0: // initial state + rs = decodeHuffman(component.huffmanTableAC); + s = rs & 15; + r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r); + successiveACState = 4; + } else { + r = 16; + successiveACState = 1; + } + } else { + if (s !== 1) { + throw new JpegError("invalid ACn encoding"); + } + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (component.blockData[offsetZ]) { + component.blockData[offsetZ] += sign * (readBit() << successive); + } else { + r--; + if (r === 0) { + successiveACState = successiveACState === 2 ? 3 : 0; + } + } + break; + case 3: // set value for a zero item + if (component.blockData[offsetZ]) { + component.blockData[offsetZ] += sign * (readBit() << successive); + } else { + component.blockData[offsetZ] = successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (component.blockData[offsetZ]) { + component.blockData[offsetZ] += sign * (readBit() << successive); + } + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) { + successiveACState = 0; + } + } + } + + let blockRow = 0; + function decodeMcu(component, decode, mcu, row, col) { + const mcuRow = (mcu / mcusPerLine) | 0; + const mcuCol = mcu % mcusPerLine; + blockRow = mcuRow * component.v + row; + const blockCol = mcuCol * component.h + col; + const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, blockOffset); + } + + function decodeBlock(component, decode, mcu) { + blockRow = (mcu / component.blocksPerLine) | 0; + const blockCol = mcu % component.blocksPerLine; + const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, blockOffset); + } + + const componentsLength = components.length; + let component, i, j, k, n; + let decodeFn; + if (progressive) { + if (spectralStart === 0) { + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + } else { + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + } + } else { + decodeFn = decodeBaseline; + } + + let mcu = 0, + fileMarker; + const mcuExpected = + componentsLength === 1 + ? components[0].blocksPerLine * components[0].blocksPerColumn + : mcusPerLine * frame.mcusPerColumn; + + let h, v; + while (mcu <= mcuExpected) { + // reset interval stuff + const mcuToRead = resetInterval + ? Math.min(mcuExpected - mcu, resetInterval) + : mcuExpected; + + // The `mcuToRead === 0` case should only occur when all of the expected + // MCU data has been already parsed, i.e. when `mcu === mcuExpected`, but + // some corrupt JPEG images contain more data than intended and we thus + // want to skip over any extra RSTx markers below (fixes issue11794.pdf). + if (mcuToRead > 0) { + for (i = 0; i < componentsLength; i++) { + components[i].pred = 0; + } + eobrun = 0; + + if (componentsLength === 1) { + component = components[0]; + for (n = 0; n < mcuToRead; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < mcuToRead; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + } + } + } + + // find marker + bitsCount = 0; + fileMarker = findNextFileMarker(data, offset); + if (!fileMarker) { + break; // Reached the end of the image data without finding any marker. + } + if (fileMarker.invalid) { + // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip + // past those to attempt to find a valid marker (fixes issue4090.pdf). + const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive"; + warn( + `decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}` + ); + offset = fileMarker.offset; + } + if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) { + // RSTx + offset += 2; + } else { + break; + } + } + + return offset - startOffset; +} + +// A port of poppler's IDCT method which in turn is taken from: +// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, +// 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', +// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, +// 988-991. +function quantizeAndInverse(component, blockBufferOffset, p) { + const qt = component.quantizationTable, + blockData = component.blockData; + let v0, v1, v2, v3, v4, v5, v6, v7; + let p0, p1, p2, p3, p4, p5, p6, p7; + let t; + + if (!qt) { + throw new JpegError("missing required Quantization Table."); + } + + // inverse DCT on rows + for (let row = 0; row < 64; row += 8) { + // gather block data + p0 = blockData[blockBufferOffset + row]; + p1 = blockData[blockBufferOffset + row + 1]; + p2 = blockData[blockBufferOffset + row + 2]; + p3 = blockData[blockBufferOffset + row + 3]; + p4 = blockData[blockBufferOffset + row + 4]; + p5 = blockData[blockBufferOffset + row + 5]; + p6 = blockData[blockBufferOffset + row + 6]; + p7 = blockData[blockBufferOffset + row + 7]; + + // dequant p0 + p0 *= qt[row]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { + t = (dctSqrt2 * p0 + 512) >> 10; + p[row] = t; + p[row + 1] = t; + p[row + 2] = t; + p[row + 3] = t; + p[row + 4] = t; + p[row + 5] = t; + p[row + 6] = t; + p[row + 7] = t; + continue; + } + // dequant p1 ... p7 + p1 *= qt[row + 1]; + p2 *= qt[row + 2]; + p3 *= qt[row + 3]; + p4 *= qt[row + 4]; + p5 *= qt[row + 5]; + p6 *= qt[row + 6]; + p7 *= qt[row + 7]; + + // stage 4 + v0 = (dctSqrt2 * p0 + 128) >> 8; + v1 = (dctSqrt2 * p4 + 128) >> 8; + v2 = p2; + v3 = p6; + v4 = (dctSqrt1d2 * (p1 - p7) + 128) >> 8; + v7 = (dctSqrt1d2 * (p1 + p7) + 128) >> 8; + v5 = p3 << 4; + v6 = p5 << 4; + + // stage 3 + v0 = (v0 + v1 + 1) >> 1; + v1 = v0 - v1; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[row] = v0 + v7; + p[row + 7] = v0 - v7; + p[row + 1] = v1 + v6; + p[row + 6] = v1 - v6; + p[row + 2] = v2 + v5; + p[row + 5] = v2 - v5; + p[row + 3] = v3 + v4; + p[row + 4] = v3 - v4; + } + + // inverse DCT on columns + for (let col = 0; col < 8; ++col) { + p0 = p[col]; + p1 = p[col + 8]; + p2 = p[col + 16]; + p3 = p[col + 24]; + p4 = p[col + 32]; + p5 = p[col + 40]; + p6 = p[col + 48]; + p7 = p[col + 56]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { + t = (dctSqrt2 * p0 + 8192) >> 14; + // Convert to 8-bit. + if (t < -2040) { + t = 0; + } else if (t >= 2024) { + t = 255; + } else { + t = (t + 2056) >> 4; + } + blockData[blockBufferOffset + col] = t; + blockData[blockBufferOffset + col + 8] = t; + blockData[blockBufferOffset + col + 16] = t; + blockData[blockBufferOffset + col + 24] = t; + blockData[blockBufferOffset + col + 32] = t; + blockData[blockBufferOffset + col + 40] = t; + blockData[blockBufferOffset + col + 48] = t; + blockData[blockBufferOffset + col + 56] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p0 + 2048) >> 12; + v1 = (dctSqrt2 * p4 + 2048) >> 12; + v2 = p2; + v3 = p6; + v4 = (dctSqrt1d2 * (p1 - p7) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p1 + p7) + 2048) >> 12; + v5 = p3; + v6 = p5; + + // stage 3 + // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when + // converting to UInt8 range later. + v0 = ((v0 + v1 + 1) >> 1) + 4112; + v1 = v0 - v1; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p0 = v0 + v7; + p7 = v0 - v7; + p1 = v1 + v6; + p6 = v1 - v6; + p2 = v2 + v5; + p5 = v2 - v5; + p3 = v3 + v4; + p4 = v3 - v4; + + // Convert to 8-bit integers. + if (p0 < 16) { + p0 = 0; + } else if (p0 >= 4080) { + p0 = 255; + } else { + p0 >>= 4; + } + if (p1 < 16) { + p1 = 0; + } else if (p1 >= 4080) { + p1 = 255; + } else { + p1 >>= 4; + } + if (p2 < 16) { + p2 = 0; + } else if (p2 >= 4080) { + p2 = 255; + } else { + p2 >>= 4; + } + if (p3 < 16) { + p3 = 0; + } else if (p3 >= 4080) { + p3 = 255; + } else { + p3 >>= 4; + } + if (p4 < 16) { + p4 = 0; + } else if (p4 >= 4080) { + p4 = 255; + } else { + p4 >>= 4; + } + if (p5 < 16) { + p5 = 0; + } else if (p5 >= 4080) { + p5 = 255; + } else { + p5 >>= 4; + } + if (p6 < 16) { + p6 = 0; + } else if (p6 >= 4080) { + p6 = 255; + } else { + p6 >>= 4; + } + if (p7 < 16) { + p7 = 0; + } else if (p7 >= 4080) { + p7 = 255; + } else { + p7 >>= 4; + } + + // store block data + blockData[blockBufferOffset + col] = p0; + blockData[blockBufferOffset + col + 8] = p1; + blockData[blockBufferOffset + col + 16] = p2; + blockData[blockBufferOffset + col + 24] = p3; + blockData[blockBufferOffset + col + 32] = p4; + blockData[blockBufferOffset + col + 40] = p5; + blockData[blockBufferOffset + col + 48] = p6; + blockData[blockBufferOffset + col + 56] = p7; + } +} + +function buildComponentData(frame, component) { + const blocksPerLine = component.blocksPerLine; + const blocksPerColumn = component.blocksPerColumn; + const computationBuffer = new Int16Array(64); + + for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) { + const offset = getBlockBufferOffset(component, blockRow, blockCol); + quantizeAndInverse(component, offset, computationBuffer); + } + } + return component.blockData; +} + +function findNextFileMarker(data, currentPos, startPos = currentPos) { + const maxPos = data.length - 1; + let newPos = startPos < currentPos ? startPos : currentPos; + + if (currentPos >= maxPos) { + return null; // Don't attempt to read non-existent data and just return. + } + const currentMarker = readUint16(data, currentPos); + if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) { + return { + invalid: null, + marker: currentMarker, + offset: currentPos, + }; + } + let newMarker = readUint16(data, newPos); + while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) { + if (++newPos >= maxPos) { + return null; // Don't attempt to read non-existent data and just return. + } + newMarker = readUint16(data, newPos); + } + return { + invalid: currentMarker.toString(16), + marker: newMarker, + offset: newPos, + }; +} + +function prepareComponents(frame) { + const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); + const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); + for (const component of frame.components) { + const blocksPerLine = Math.ceil( + (Math.ceil(frame.samplesPerLine / 8) * component.h) / frame.maxH + ); + const blocksPerColumn = Math.ceil( + (Math.ceil(frame.scanLines / 8) * component.v) / frame.maxV + ); + const blocksPerLineForMcu = mcusPerLine * component.h; + const blocksPerColumnForMcu = mcusPerColumn * component.v; + + const blocksBufferSize = + 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); + component.blockData = new Int16Array(blocksBufferSize); + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + } + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; +} + +function readDataBlock(data, offset) { + const length = readUint16(data, offset); + offset += 2; + let endOffset = offset + length - 2; + + const fileMarker = findNextFileMarker(data, endOffset, offset); + if (fileMarker?.invalid) { + warn( + "readDataBlock - incorrect length, current marker is: " + + fileMarker.invalid + ); + endOffset = fileMarker.offset; + } + + const array = data.subarray(offset, endOffset); + return { + appData: array, + oldOffset: offset, + newOffset: offset + array.length, + }; +} + +function skipData(data, offset) { + const length = readUint16(data, offset); + offset += 2; + const endOffset = offset + length - 2; + + const fileMarker = findNextFileMarker(data, endOffset, offset); + if (fileMarker?.invalid) { + return fileMarker.offset; + } + return endOffset; +} + +class JpegImage { + constructor({ decodeTransform = null, colorTransform = -1 } = {}) { + this._decodeTransform = decodeTransform; + this._colorTransform = colorTransform; + } + + static canUseImageDecoder(data, colorTransform = -1) { + let exifOffsets = null; + let offset = 0; + let numComponents = null; + let fileMarker = readUint16(data, offset); + offset += 2; + if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) { + throw new JpegError("SOI not found"); + } + fileMarker = readUint16(data, offset); + offset += 2; + + markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) { + switch (fileMarker) { + case 0xffe1: // APP1 - Exif + // TODO: Remove this once https://github.com/w3c/webcodecs/issues/870 + // is fixed. + const { appData, oldOffset, newOffset } = readDataBlock(data, offset); + offset = newOffset; + + // 'Exif\x00\x00' + if ( + appData[0] === 0x45 && + appData[1] === 0x78 && + appData[2] === 0x69 && + appData[3] === 0x66 && + appData[4] === 0 && + appData[5] === 0 + ) { + if (exifOffsets) { + throw new JpegError("Duplicate EXIF-blocks found."); + } + // Don't do the EXIF-block replacement here, see `JpegStream`, + // since that can modify the original PDF document. + exifOffsets = { exifStart: oldOffset + 6, exifEnd: newOffset }; + } + fileMarker = readUint16(data, offset); + offset += 2; + continue; + case 0xffc0: // SOF0 (Start of Frame, Baseline DCT) + case 0xffc1: // SOF1 (Start of Frame, Extended DCT) + case 0xffc2: // SOF2 (Start of Frame, Progressive DCT) + // Skip marker length. + // Skip precision. + // Skip scanLines. + // Skip samplesPerLine. + numComponents = data[offset + (2 + 1 + 2 + 2)]; + break markerLoop; + case 0xffff: // Fill bytes + if (data[offset] !== 0xff) { + // Avoid skipping a valid marker. + offset--; + } + break; + } + offset = skipData(data, offset); + fileMarker = readUint16(data, offset); + offset += 2; + } + if (numComponents === 4) { + return null; + } + if (numComponents === 3 && colorTransform === 0) { + return null; + } + return exifOffsets || {}; + } + + parse(data, { dnlScanLines = null } = {}) { + let offset = 0; + let jfif = null; + let adobe = null; + let frame, resetInterval; + let numSOSMarkers = 0; + const quantizationTables = []; + const huffmanTablesAC = [], + huffmanTablesDC = []; + + let fileMarker = readUint16(data, offset); + offset += 2; + if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) { + throw new JpegError("SOI not found"); + } + fileMarker = readUint16(data, offset); + offset += 2; + + markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) { + let i, j, l; + switch (fileMarker) { + case 0xffe0: // APP0 (Application Specific) + case 0xffe1: // APP1 + case 0xffe2: // APP2 + case 0xffe3: // APP3 + case 0xffe4: // APP4 + case 0xffe5: // APP5 + case 0xffe6: // APP6 + case 0xffe7: // APP7 + case 0xffe8: // APP8 + case 0xffe9: // APP9 + case 0xffea: // APP10 + case 0xffeb: // APP11 + case 0xffec: // APP12 + case 0xffed: // APP13 + case 0xffee: // APP14 + case 0xffef: // APP15 + case 0xfffe: // COM (Comment) + const { appData, newOffset } = readDataBlock(data, offset); + offset = newOffset; + + if (fileMarker === 0xffe0) { + // 'JFIF\x00' + if ( + appData[0] === 0x4a && + appData[1] === 0x46 && + appData[2] === 0x49 && + appData[3] === 0x46 && + appData[4] === 0 + ) { + jfif = { + version: { major: appData[5], minor: appData[6] }, + densityUnits: appData[7], + xDensity: (appData[8] << 8) | appData[9], + yDensity: (appData[10] << 8) | appData[11], + thumbWidth: appData[12], + thumbHeight: appData[13], + thumbData: appData.subarray( + 14, + 14 + 3 * appData[12] * appData[13] + ), + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xffee) { + // 'Adobe' + if ( + appData[0] === 0x41 && + appData[1] === 0x64 && + appData[2] === 0x6f && + appData[3] === 0x62 && + appData[4] === 0x65 + ) { + adobe = { + version: (appData[5] << 8) | appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11], + }; + } + } + break; + + case 0xffdb: // DQT (Define Quantization Tables) + const quantizationTablesLength = readUint16(data, offset); + offset += 2; + const quantizationTablesEnd = quantizationTablesLength + offset - 2; + let z; + while (offset < quantizationTablesEnd) { + const quantizationTableSpec = data[offset++]; + const tableData = new Uint16Array(64); + if (quantizationTableSpec >> 4 === 0) { + // 8 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = data[offset++]; + } + } else if (quantizationTableSpec >> 4 === 1) { + // 16 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = readUint16(data, offset); + offset += 2; + } + } else { + throw new JpegError("DQT - invalid table spec"); + } + quantizationTables[quantizationTableSpec & 15] = tableData; + } + break; + + case 0xffc0: // SOF0 (Start of Frame, Baseline DCT) + case 0xffc1: // SOF1 (Start of Frame, Extended DCT) + case 0xffc2: // SOF2 (Start of Frame, Progressive DCT) + if (frame) { + throw new JpegError("Only single frame JPEGs supported"); + } + offset += 2; // Skip marker length. + + frame = {}; + frame.extended = fileMarker === 0xffc1; + frame.progressive = fileMarker === 0xffc2; + frame.precision = data[offset++]; + const sofScanLines = readUint16(data, offset); + offset += 2; + frame.scanLines = dnlScanLines || sofScanLines; + frame.samplesPerLine = readUint16(data, offset); + offset += 2; + frame.components = []; + frame.componentIds = {}; + const componentsCount = data[offset++]; + let maxH = 0, + maxV = 0; + for (i = 0; i < componentsCount; i++) { + const componentId = data[offset]; + const h = data[offset + 1] >> 4; + const v = data[offset + 1] & 15; + if (maxH < h) { + maxH = h; + } + if (maxV < v) { + maxV = v; + } + const qId = data[offset + 2]; + l = frame.components.push({ + h, + v, + quantizationId: qId, + quantizationTable: null, // See comment below. + }); + frame.componentIds[componentId] = l - 1; + offset += 3; + } + frame.maxH = maxH; + frame.maxV = maxV; + prepareComponents(frame); + break; + + case 0xffc4: // DHT (Define Huffman Tables) + const huffmanLength = readUint16(data, offset); + offset += 2; + for (i = 2; i < huffmanLength; ) { + const huffmanTableSpec = data[offset++]; + const codeLengths = new Uint8Array(16); + let codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) { + codeLengthSum += codeLengths[j] = data[offset]; + } + const huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) { + huffmanValues[j] = data[offset]; + } + i += 17 + codeLengthSum; + + (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[ + huffmanTableSpec & 15 + ] = buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xffdd: // DRI (Define Restart Interval) + offset += 2; // Skip marker length. + + resetInterval = readUint16(data, offset); + offset += 2; + break; + + case 0xffda: // SOS (Start of Scan) + // A DNL marker (0xFFDC), if it exists, is only allowed at the end + // of the first scan segment and may only occur once in an image. + // Furthermore, to prevent an infinite loop, do *not* attempt to + // parse DNL markers during re-parsing of the JPEG scan data. + const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines; + + offset += 2; // Skip marker length. + + const selectorsCount = data[offset++], + components = []; + for (i = 0; i < selectorsCount; i++) { + const index = data[offset++]; + const componentIndex = frame.componentIds[index]; + const component = frame.components[componentIndex]; + component.index = index; + const tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + const spectralStart = data[offset++], + spectralEnd = data[offset++], + successiveApproximation = data[offset++]; + try { + const processed = decodeScan( + data, + offset, + frame, + components, + resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15, + parseDNLMarker + ); + offset += processed; + } catch (ex) { + if (ex instanceof DNLMarkerError) { + warn(`${ex.message} -- attempting to re-parse the JPEG image.`); + return this.parse(data, { dnlScanLines: ex.scanLines }); + } else if (ex instanceof EOIMarkerError) { + warn(`${ex.message} -- ignoring the rest of the image data.`); + break markerLoop; + } + throw ex; + } + break; + + case 0xffdc: // DNL (Define Number of Lines) + // Ignore the marker, since it's being handled in `decodeScan`. + offset += 4; + break; + + case 0xffff: // Fill bytes + if (data[offset] !== 0xff) { + // Avoid skipping a valid marker. + offset--; + } + break; + + default: + // Could be incorrect encoding -- the last 0xFF byte of the previous + // block could have been eaten by the encoder, hence we fallback to + // `startPos = offset - 3` when looking for the next valid marker. + const nextFileMarker = findNextFileMarker( + data, + /* currentPos = */ offset - 2, + /* startPos = */ offset - 3 + ); + if (nextFileMarker?.invalid) { + warn( + "JpegImage.parse - unexpected data, current marker is: " + + nextFileMarker.invalid + ); + offset = nextFileMarker.offset; + break; + } + if (!nextFileMarker || offset >= data.length - 1) { + warn( + "JpegImage.parse - reached the end of the image data " + + "without finding an EOI marker (0xFFD9)." + ); + break markerLoop; + } + throw new JpegError( + "JpegImage.parse - unknown marker: " + fileMarker.toString(16) + ); + } + fileMarker = readUint16(data, offset); + offset += 2; + } + + if (!frame) { + throw new JpegError("JpegImage.parse - no frame data found."); + } + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (const component of frame.components) { + // Prevent errors when DQT markers are placed after SOF{n} markers, + // by assigning the `quantizationTable` entry after the entire image + // has been parsed (fixes issue7406.pdf). + const quantizationTable = quantizationTables[component.quantizationId]; + if (quantizationTable) { + component.quantizationTable = quantizationTable; + } + + this.components.push({ + index: component.index, + output: buildComponentData(frame, component), + scaleX: component.h / frame.maxH, + scaleY: component.v / frame.maxV, + blocksPerLine: component.blocksPerLine, + blocksPerColumn: component.blocksPerColumn, + }); + } + this.numComponents = this.components.length; + return undefined; + } + + _getLinearizedBlockData(width, height, isSourcePDF = false) { + const scaleX = this.width / width, + scaleY = this.height / height; + + let component, componentScaleX, componentScaleY, blocksPerScanline; + let x, y, i, j, k; + let index; + let offset = 0; + let output; + const numComponents = this.components.length; + const dataLength = width * height * numComponents; + const data = new Uint8ClampedArray(dataLength); + const xScaleBlockOffset = new Uint32Array(width); + const mask3LSB = 0xfffffff8; // used to clear the 3 LSBs + let lastComponentScaleX; + + for (i = 0; i < numComponents; i++) { + component = this.components[i]; + componentScaleX = component.scaleX * scaleX; + componentScaleY = component.scaleY * scaleY; + offset = i; + output = component.output; + blocksPerScanline = (component.blocksPerLine + 1) << 3; + // Precalculate the `xScaleBlockOffset`. Since it doesn't depend on the + // component data, that's only necessary when `componentScaleX` changes. + if (componentScaleX !== lastComponentScaleX) { + for (x = 0; x < width; x++) { + j = 0 | (x * componentScaleX); + xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7); + } + lastComponentScaleX = componentScaleX; + } + // linearize the blocks of the component + for (y = 0; y < height; y++) { + j = 0 | (y * componentScaleY); + index = (blocksPerScanline * (j & mask3LSB)) | ((j & 7) << 3); + for (x = 0; x < width; x++) { + data[offset] = output[index + xScaleBlockOffset[x]]; + offset += numComponents; + } + } + } + + // decodeTransform contains pairs of multiplier (-256..256) and additive + let transform = this._decodeTransform; + + // In PDF files, JPEG images with CMYK colour spaces are usually inverted + // (this can be observed by extracting the raw image data). + // Since the conversion algorithms (see below) were written primarily for + // the PDF use-cases, attempting to use `JpegImage` to parse standalone + // JPEG (CMYK) images may thus result in inverted images (see issue 9513). + // + // Unfortunately it's not (always) possible to tell, from the image data + // alone, if it needs to be inverted. Thus in an attempt to provide better + // out-of-box behaviour when `JpegImage` is used standalone, default to + // inverting JPEG (CMYK) images if and only if the image data does *not* + // come from a PDF file and no `decodeTransform` was passed by the user. + if (!isSourcePDF && numComponents === 4 && !transform) { + transform = new Int32Array([-256, 255, -256, 255, -256, 255, -256, 255]); + } + + if (transform) { + for (i = 0; i < dataLength; ) { + for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { + data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; + } + } + } + return data; + } + + get _isColorConversionNeeded() { + if (this.adobe) { + // The adobe transform marker overrides any previous setting. + return !!this.adobe.transformCode; + } + if (this.numComponents === 3) { + if (this._colorTransform === 0) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `0`, + // then the colours should *not* be transformed. + return false; + } else if ( + this.components[0].index === /* "R" = */ 0x52 && + this.components[1].index === /* "G" = */ 0x47 && + this.components[2].index === /* "B" = */ 0x42 + ) { + // If the three components are indexed as RGB in ASCII + // then the colours should *not* be transformed. + return false; + } + return true; + } + // `this.numComponents !== 3` + if (this._colorTransform === 1) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `1`, + // then the colours should be transformed. + return true; + } + return false; + } + + _convertYccToRgb(data) { + let Y, Cb, Cr; + for (let i = 0, length = data.length; i < length; i += 3) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i] = Y - 179.456 + 1.402 * Cr; + data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; + data[i + 2] = Y - 226.816 + 1.772 * Cb; + } + return data; + } + + _convertYccToRgba(data, out) { + for (let i = 0, j = 0, length = data.length; i < length; i += 3, j += 4) { + const Y = data[i]; + const Cb = data[i + 1]; + const Cr = data[i + 2]; + out[j] = Y - 179.456 + 1.402 * Cr; + out[j + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; + out[j + 2] = Y - 226.816 + 1.772 * Cb; + out[j + 3] = 255; + } + return out; + } + + _convertYcckToRgb(data) { + let Y, Cb, Cr, k; + let offset = 0; + for (let i = 0, length = data.length; i < length; i += 4) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + k = data[i + 3]; + + data[offset++] = + -122.67195406894 + + Cb * + (-6.60635669420364e-5 * Cb + + 0.000437130475926232 * Cr - + 5.4080610064599e-5 * Y + + 0.00048449797120281 * k - + 0.154362151871126) + + Cr * + (-0.000957964378445773 * Cr + + 0.000817076911346625 * Y - + 0.00477271405408747 * k + + 1.53380253221734) + + Y * + (0.000961250184130688 * Y - + 0.00266257332283933 * k + + 0.48357088451265) + + k * (-0.000336197177618394 * k + 0.484791561490776); + + data[offset++] = + 107.268039397724 + + Cb * + (2.19927104525741e-5 * Cb - + 0.000640992018297945 * Cr + + 0.000659397001245577 * Y + + 0.000426105652938837 * k - + 0.176491792462875) + + Cr * + (-0.000778269941513683 * Cr + + 0.00130872261408275 * Y + + 0.000770482631801132 * k - + 0.151051492775562) + + Y * + (0.00126935368114843 * Y - + 0.00265090189010898 * k + + 0.25802910206845) + + k * (-0.000318913117588328 * k - 0.213742400323665); + + data[offset++] = + -20.810012546947 + + Cb * + (-0.000570115196973677 * Cb - + 2.63409051004589e-5 * Cr + + 0.0020741088115012 * Y - + 0.00288260236853442 * k + + 0.814272968359295) + + Cr * + (-1.53496057440975e-5 * Cr - + 0.000132689043961446 * Y + + 0.000560833691242812 * k - + 0.195152027534049) + + Y * + (0.00174418132927582 * Y - + 0.00255243321439347 * k + + 0.116935020465145) + + k * (-0.000343531996510555 * k + 0.24165260232407); + } + // Ensure that only the converted RGB data is returned. + return data.subarray(0, offset); + } + + _convertYcckToRgba(data) { + for (let i = 0, length = data.length; i < length; i += 4) { + const Y = data[i]; + const Cb = data[i + 1]; + const Cr = data[i + 2]; + const k = data[i + 3]; + + data[i] = + -122.67195406894 + + Cb * + (-6.60635669420364e-5 * Cb + + 0.000437130475926232 * Cr - + 5.4080610064599e-5 * Y + + 0.00048449797120281 * k - + 0.154362151871126) + + Cr * + (-0.000957964378445773 * Cr + + 0.000817076911346625 * Y - + 0.00477271405408747 * k + + 1.53380253221734) + + Y * + (0.000961250184130688 * Y - + 0.00266257332283933 * k + + 0.48357088451265) + + k * (-0.000336197177618394 * k + 0.484791561490776); + + data[i + 1] = + 107.268039397724 + + Cb * + (2.19927104525741e-5 * Cb - + 0.000640992018297945 * Cr + + 0.000659397001245577 * Y + + 0.000426105652938837 * k - + 0.176491792462875) + + Cr * + (-0.000778269941513683 * Cr + + 0.00130872261408275 * Y + + 0.000770482631801132 * k - + 0.151051492775562) + + Y * + (0.00126935368114843 * Y - + 0.00265090189010898 * k + + 0.25802910206845) + + k * (-0.000318913117588328 * k - 0.213742400323665); + + data[i + 2] = + -20.810012546947 + + Cb * + (-0.000570115196973677 * Cb - + 2.63409051004589e-5 * Cr + + 0.0020741088115012 * Y - + 0.00288260236853442 * k + + 0.814272968359295) + + Cr * + (-1.53496057440975e-5 * Cr - + 0.000132689043961446 * Y + + 0.000560833691242812 * k - + 0.195152027534049) + + Y * + (0.00174418132927582 * Y - + 0.00255243321439347 * k + + 0.116935020465145) + + k * (-0.000343531996510555 * k + 0.24165260232407); + data[i + 3] = 255; + } + return data; + } + + _convertYcckToCmyk(data) { + let Y, Cb, Cr; + for (let i = 0, length = data.length; i < length; i += 4) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i] = 434.456 - Y - 1.402 * Cr; + data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr; + data[i + 2] = 481.816 - Y - 1.772 * Cb; + // K in data[i + 3] is unchanged + } + return data; + } + + _convertCmykToRgb(data) { + let c, m, y, k; + let offset = 0; + for (let i = 0, length = data.length; i < length; i += 4) { + c = data[i]; + m = data[i + 1]; + y = data[i + 2]; + k = data[i + 3]; + + data[offset++] = + 255 + + c * + (-0.00006747147073602441 * c + + 0.0008379262121013727 * m + + 0.0002894718188643294 * y + + 0.003264231057537806 * k - + 1.1185611867203937) + + m * + (0.000026374107616089405 * m - + 0.00008626949158638572 * y - + 0.0002748769067499491 * k - + 0.02155688794978967) + + y * + (-0.00003878099212869363 * y - + 0.0003267808279485286 * k + + 0.0686742238595345) - + k * (0.0003361971776183937 * k + 0.7430659151342254); + + data[offset++] = + 255 + + c * + (0.00013596372813588848 * c + + 0.000924537132573585 * m + + 0.00010567359618683593 * y + + 0.0004791864687436512 * k - + 0.3109689587515875) + + m * + (-0.00023545346108370344 * m + + 0.0002702845253534714 * y + + 0.0020200308977307156 * k - + 0.7488052167015494) + + y * + (0.00006834815998235662 * y + + 0.00015168452363460973 * k - + 0.09751927774728933) - + k * (0.0003189131175883281 * k + 0.7364883807733168); + + data[offset++] = + 255 + + c * + (0.000013598650411385307 * c + + 0.00012423956175490851 * m + + 0.0004751985097583589 * y - + 0.0000036729317476630422 * k - + 0.05562186980264034) + + m * + (0.00016141380598724676 * m + + 0.0009692239130725186 * y + + 0.0007782692450036253 * k - + 0.44015232367526463) + + y * + (5.068882914068769e-7 * y + + 0.0017778369011375071 * k - + 0.7591454649749609) - + k * (0.0003435319965105553 * k + 0.7063770186160144); + } + // Ensure that only the converted RGB data is returned. + return data.subarray(0, offset); + } + + _convertCmykToRgba(data) { + for (let i = 0, length = data.length; i < length; i += 4) { + const c = data[i]; + const m = data[i + 1]; + const y = data[i + 2]; + const k = data[i + 3]; + + data[i] = + 255 + + c * + (-0.00006747147073602441 * c + + 0.0008379262121013727 * m + + 0.0002894718188643294 * y + + 0.003264231057537806 * k - + 1.1185611867203937) + + m * + (0.000026374107616089405 * m - + 0.00008626949158638572 * y - + 0.0002748769067499491 * k - + 0.02155688794978967) + + y * + (-0.00003878099212869363 * y - + 0.0003267808279485286 * k + + 0.0686742238595345) - + k * (0.0003361971776183937 * k + 0.7430659151342254); + + data[i + 1] = + 255 + + c * + (0.00013596372813588848 * c + + 0.000924537132573585 * m + + 0.00010567359618683593 * y + + 0.0004791864687436512 * k - + 0.3109689587515875) + + m * + (-0.00023545346108370344 * m + + 0.0002702845253534714 * y + + 0.0020200308977307156 * k - + 0.7488052167015494) + + y * + (0.00006834815998235662 * y + + 0.00015168452363460973 * k - + 0.09751927774728933) - + k * (0.0003189131175883281 * k + 0.7364883807733168); + + data[i + 2] = + 255 + + c * + (0.000013598650411385307 * c + + 0.00012423956175490851 * m + + 0.0004751985097583589 * y - + 0.0000036729317476630422 * k - + 0.05562186980264034) + + m * + (0.00016141380598724676 * m + + 0.0009692239130725186 * y + + 0.0007782692450036253 * k - + 0.44015232367526463) + + y * + (5.068882914068769e-7 * y + + 0.0017778369011375071 * k - + 0.7591454649749609) - + k * (0.0003435319965105553 * k + 0.7063770186160144); + data[i + 3] = 255; + } + return data; + } + + getData({ + width, + height, + forceRGBA = false, + forceRGB = false, + isSourcePDF = false, + }) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + isSourcePDF === true, + 'JpegImage.getData: Unexpected "isSourcePDF" value for PDF files.' + ); + } + if (this.numComponents > 4) { + throw new JpegError("Unsupported color mode"); + } + // Type of data: Uint8ClampedArray(width * height * numComponents) + const data = this._getLinearizedBlockData(width, height, isSourcePDF); + + if (this.numComponents === 1 && (forceRGBA || forceRGB)) { + const len = data.length * (forceRGBA ? 4 : 3); + const rgbaData = new Uint8ClampedArray(len); + let offset = 0; + if (forceRGBA) { + grayToRGBA(data, new Uint32Array(rgbaData.buffer)); + } else { + for (const grayColor of data) { + rgbaData[offset++] = grayColor; + rgbaData[offset++] = grayColor; + rgbaData[offset++] = grayColor; + } + } + return rgbaData; + } else if (this.numComponents === 3 && this._isColorConversionNeeded) { + if (forceRGBA) { + const rgbaData = new Uint8ClampedArray((data.length / 3) * 4); + return this._convertYccToRgba(data, rgbaData); + } + return this._convertYccToRgb(data); + } else if (this.numComponents === 4) { + if (this._isColorConversionNeeded) { + if (forceRGBA) { + return this._convertYcckToRgba(data); + } + if (forceRGB) { + return this._convertYcckToRgb(data); + } + return this._convertYcckToCmyk(data); + } else if (forceRGBA) { + return this._convertCmykToRgba(data); + } else if (forceRGB) { + return this._convertCmykToRgb(data); + } + } + return data; + } +} + +export { JpegError, JpegImage }; diff --git a/frontend/public/pdf.js/src/core/jpx.js b/frontend/public/pdf.js/src/core/jpx.js new file mode 100644 index 0000000..94a4dc9 --- /dev/null +++ b/frontend/public/pdf.js/src/core/jpx.js @@ -0,0 +1,149 @@ +/* Copyright 2024 Mozilla Foundation + * + * 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 { BaseException, warn } from "../shared/util.js"; +import { fetchBinaryData } from "./core_utils.js"; +import OpenJPEG from "../../external/openjpeg/openjpeg.js"; +import { Stream } from "./stream.js"; + +class JpxError extends BaseException { + constructor(msg) { + super(msg, "JpxError"); + } +} + +class JpxImage { + static #buffer = null; + + static #handler = null; + + static #modulePromise = null; + + static #wasmUrl = null; + + static setOptions({ handler, wasmUrl }) { + if (!this.#buffer) { + this.#wasmUrl = wasmUrl || null; + if (wasmUrl === null) { + this.#handler = handler; + } + } + } + + static async #instantiateWasm(imports, successCallback) { + const filename = "openjpeg.wasm"; + try { + if (!this.#buffer) { + if (this.#wasmUrl !== null) { + this.#buffer = await fetchBinaryData(`${this.#wasmUrl}${filename}`); + } else { + this.#buffer = await this.#handler.sendWithPromise( + "FetchBinaryData", + { type: "wasmFactory", filename } + ); + } + } + const results = await WebAssembly.instantiate(this.#buffer, imports); + return successCallback(results.instance); + } finally { + this.#handler = null; + this.#wasmUrl = null; + } + } + + static async decode( + bytes, + { numComponents = 4, isIndexedColormap = false, smaskInData = false } = {} + ) { + this.#modulePromise ||= OpenJPEG({ + warn, + instantiateWasm: this.#instantiateWasm.bind(this), + }); + + const module = await this.#modulePromise; + let ptr; + + try { + const size = bytes.length; + ptr = module._malloc(size); + module.HEAPU8.set(bytes, ptr); + const ret = module._jp2_decode( + ptr, + size, + numComponents > 0 ? numComponents : 0, + !!isIndexedColormap, + !!smaskInData + ); + if (ret) { + const { errorMessages } = module; + if (errorMessages) { + delete module.errorMessages; + throw new JpxError(errorMessages); + } + throw new JpxError("Unknown error"); + } + const { imageData } = module; + module.imageData = null; + + return imageData; + } finally { + if (ptr) { + module._free(ptr); + } + } + } + + static cleanup() { + this.#modulePromise = null; + } + + static parseImageProperties(stream) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("IMAGE_DECODERS")) { + if (stream instanceof ArrayBuffer || ArrayBuffer.isView(stream)) { + stream = new Stream(stream); + } else { + throw new JpxError("Invalid data format, must be a TypedArray."); + } + } + // No need to use OpenJPEG here since we're only getting very basic + // information which are located in the first bytes of the file. + let newByte = stream.getByte(); + while (newByte >= 0) { + const oldByte = newByte; + newByte = stream.getByte(); + const code = (oldByte << 8) | newByte; + // Image and tile size (SIZ) + if (code === 0xff51) { + stream.skip(4); + const Xsiz = stream.getInt32() >>> 0; // Byte 4 + const Ysiz = stream.getInt32() >>> 0; // Byte 8 + const XOsiz = stream.getInt32() >>> 0; // Byte 12 + const YOsiz = stream.getInt32() >>> 0; // Byte 16 + stream.skip(16); + const Csiz = stream.getUint16(); // Byte 36 + return { + width: Xsiz - XOsiz, + height: Ysiz - YOsiz, + // Results are always returned as `Uint8ClampedArray`s. + bitsPerComponent: 8, + componentsCount: Csiz, + }; + } + } + throw new JpxError("No size marker found in JPX stream"); + } +} + +export { JpxError, JpxImage }; diff --git a/frontend/public/pdf.js/src/core/jpx_stream.js b/frontend/public/pdf.js/src/core/jpx_stream.js new file mode 100644 index 0000000..e454d58 --- /dev/null +++ b/frontend/public/pdf.js/src/core/jpx_stream.js @@ -0,0 +1,69 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { shadow, unreachable } from "../shared/util.js"; +import { DecodeStream } from "./decode_stream.js"; +import { JpxImage } from "./jpx.js"; + +/** + * For JPEG 2000's we use a library to decode these images and + * the stream behaves like all the other DecodeStreams. + */ +class JpxStream extends DecodeStream { + constructor(stream, maybeLength, params) { + super(maybeLength); + + this.stream = stream; + this.dict = stream.dict; + this.maybeLength = maybeLength; + this.params = params; + } + + get bytes() { + // If `this.maybeLength` is null, we'll get the entire stream. + return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); + } + + ensureBuffer(requested) { + // No-op, since `this.readBlock` will always parse the entire image and + // directly insert all of its data into `this.buffer`. + } + + readBlock(decoderOptions) { + unreachable("JpxStream.readBlock"); + } + + get isAsyncDecoder() { + return true; + } + + async decodeImage(bytes, decoderOptions) { + if (this.eof) { + return this.buffer; + } + bytes ||= this.bytes; + this.buffer = await JpxImage.decode(bytes, decoderOptions); + this.bufferLength = this.buffer.length; + this.eof = true; + + return this.buffer; + } + + get canAsyncDecodeImageFromBuffer() { + return this.stream.isAsync; + } +} + +export { JpxStream }; diff --git a/frontend/public/pdf.js/src/core/liberationsans_widths.js b/frontend/public/pdf.js/src/core/liberationsans_widths.js new file mode 100644 index 0000000..36c869e --- /dev/null +++ b/frontend/public/pdf.js/src/core/liberationsans_widths.js @@ -0,0 +1,429 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +// Widths of glyphs in LiberationSans-Bold.ttf. +const LiberationSansBoldWidths = [ + 365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, + 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, + 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, + 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, + 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, + 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, + 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, + 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, + 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, + 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, + 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, + 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, + 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, + 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 719, 722, 611, 667, + 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, + 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 785, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 385, 611, + 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, + 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, + 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, + 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, + 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, + 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 465, 722, 333, 853, 906, + 474, 825, 927, 838, 278, 722, 722, 601, 719, 667, 611, 722, 778, 278, 722, + 667, 833, 722, 644, 778, 722, 667, 600, 611, 667, 821, 667, 809, 802, 278, + 667, 615, 451, 611, 278, 582, 615, 610, 556, 606, 475, 460, 611, 541, 278, + 558, 556, 612, 556, 445, 611, 766, 619, 520, 684, 446, 582, 715, 576, 753, + 845, 278, 582, 611, 582, 845, 667, 669, 885, 567, 711, 667, 278, 276, 556, + 1094, 1062, 875, 610, 722, 622, 719, 722, 719, 722, 567, 712, 667, 904, 626, + 719, 719, 610, 702, 833, 722, 778, 719, 667, 722, 611, 622, 854, 667, 730, + 703, 1005, 1019, 870, 979, 719, 711, 1031, 719, 556, 618, 615, 417, 635, 556, + 709, 497, 615, 615, 500, 635, 740, 604, 611, 604, 611, 556, 490, 556, 875, + 556, 615, 581, 833, 844, 729, 854, 615, 552, 854, 583, 556, 556, 611, 417, + 552, 556, 278, 281, 278, 969, 906, 611, 500, 615, 556, 604, 778, 611, 487, + 447, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, + 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, + 333, 604, 333, 167, 396, 556, 556, 1094, 556, 885, 489, 1115, 1000, 768, 600, + 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, + 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, + 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, + 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, + 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, + 333, 333, 333, 333, 333, +]; + +// Char code of glyphs in LiberationSans-Bold.ttf. +const LiberationSansBoldMapping = [ + -1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, + 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, + 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, + 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, + 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, + 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, + 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, + 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, + 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, + 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, + 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, + 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, + 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, + 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, + 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, + 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, + 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, + 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, + 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, + 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, + 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, + 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, + 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, + 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, + 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, + 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, + 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, + 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, + 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, + 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, + 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, + 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, + 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, + 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; + +// Widths of glyphs in LiberationSans-BoldItalic.ttf. +const LiberationSansBoldItalicWidths = [ + 365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, + 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, + 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, + 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, + 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, + 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, + 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, + 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, + 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, + 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, + 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, + 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, + 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, + 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 740, 722, 611, 667, + 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, + 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 782, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 396, 611, + 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, + 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, + 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, + 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, + 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, + 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 722, 333, 854, 906, + 473, 844, 930, 847, 278, 722, 722, 610, 671, 667, 611, 722, 778, 278, 722, + 667, 833, 722, 657, 778, 718, 667, 590, 611, 667, 822, 667, 829, 781, 278, + 667, 620, 479, 611, 278, 591, 620, 621, 556, 610, 479, 492, 611, 558, 278, + 566, 556, 603, 556, 450, 611, 712, 605, 532, 664, 409, 591, 704, 578, 773, + 834, 278, 591, 611, 591, 834, 667, 667, 886, 614, 719, 667, 278, 278, 556, + 1094, 1042, 854, 622, 719, 677, 719, 722, 708, 722, 614, 722, 667, 927, 643, + 719, 719, 615, 687, 833, 722, 778, 719, 667, 722, 611, 677, 781, 667, 729, + 708, 979, 989, 854, 1000, 708, 719, 1042, 729, 556, 619, 604, 534, 618, 556, + 736, 510, 611, 611, 507, 622, 740, 604, 611, 611, 611, 556, 889, 556, 885, + 556, 646, 583, 889, 935, 707, 854, 594, 552, 865, 589, 556, 556, 611, 469, + 563, 556, 278, 278, 278, 969, 906, 611, 507, 619, 556, 611, 778, 611, 575, + 467, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, + 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, + 333, 604, 333, 167, 396, 556, 556, 1104, 556, 885, 516, 1146, 1000, 768, 600, + 834, 834, 834, 834, 999, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, + 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, + 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, + 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, + 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, + 333, 333, 333, 333, 333, +]; + +// Char code of glyphs in LiberationSans-BoldItalic.ttf. +const LiberationSansBoldItalicMapping = [ + -1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, + 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, + 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, + 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, + 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, + 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, + 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, + 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, + 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, + 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, + 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, + 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, + 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, + 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, + 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, + 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, + 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, + 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, + 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, + 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, + 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, + 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, + 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, + 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, + 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, + 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, + 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, + 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, + 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, + 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, + 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, + 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, + 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, + 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; + +// Widths of glyphs in LiberationSans-Italic.ttf. +const LiberationSansItalicWidths = [ + 365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, + 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, + 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, + 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, + 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, + 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, + 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, + 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, + 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, + 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, + 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, + 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, + 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, + 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 625, 722, 556, 667, + 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, + 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, + 278, 733, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 281, 556, + 400, 556, 222, 722, 556, 722, 556, 722, 556, 615, 723, 556, 778, 556, 778, + 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, + 667, 500, 667, 500, 611, 278, 611, 354, 611, 278, 722, 556, 722, 556, 722, + 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, + 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, + 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 789, 846, + 389, 794, 865, 775, 222, 667, 667, 570, 671, 667, 611, 722, 778, 278, 667, + 667, 833, 722, 648, 778, 725, 667, 600, 611, 667, 837, 667, 831, 761, 278, + 667, 570, 439, 555, 222, 550, 570, 571, 500, 556, 439, 463, 555, 542, 222, + 500, 492, 548, 500, 447, 556, 670, 573, 486, 603, 374, 550, 652, 546, 728, + 779, 222, 550, 556, 550, 779, 667, 667, 843, 544, 708, 667, 278, 278, 500, + 1066, 982, 844, 589, 715, 639, 724, 667, 651, 667, 544, 704, 667, 917, 614, + 715, 715, 589, 686, 833, 722, 778, 725, 667, 722, 611, 639, 795, 667, 727, + 673, 920, 923, 805, 886, 651, 694, 1022, 682, 556, 562, 522, 493, 553, 556, + 688, 465, 556, 556, 472, 564, 686, 550, 556, 556, 556, 500, 833, 500, 835, + 500, 572, 518, 830, 851, 621, 736, 526, 492, 752, 534, 556, 556, 556, 378, + 496, 500, 222, 222, 222, 910, 828, 556, 472, 565, 500, 556, 778, 556, 492, + 339, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, + 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, + 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1083, 1000, 768, 600, + 834, 834, 834, 834, 1000, 500, 998, 500, 1000, 500, 500, 494, 612, 823, 713, + 584, 549, 713, 979, 719, 274, 549, 549, 584, 549, 549, 604, 584, 604, 604, + 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, + 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, + 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, + 324, 316, 328, 398, 285, +]; + +// Char code of glyphs in LiberationSans-Italic.ttf. +const LiberationSansItalicMapping = [ + -1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, + 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, + 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, + 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, + 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, + 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, + 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, + 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, + 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, + 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, + 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, + 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, + 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, + 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, + 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, + 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, + 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, + 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, + 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, + 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, + 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, + 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, + 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, + 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, + 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, + 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, + 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, + 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, + 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, + 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, + 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, + 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, + 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, + 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; + +// Widths of glyphs in LiberationSans-Regular.ttf. +const LiberationSansRegularWidths = [ + 365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, + 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, + 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, + 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, + 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, + 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, + 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, + 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, + 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, + 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, + 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, + 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, + 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, + 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 615, 722, 556, 667, + 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, + 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, + 278, 735, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 292, 556, + 334, 556, 222, 722, 556, 722, 556, 722, 556, 604, 723, 556, 778, 556, 778, + 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, + 667, 500, 667, 500, 611, 278, 611, 375, 611, 278, 722, 556, 722, 556, 722, + 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, + 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, + 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 784, 838, + 384, 774, 855, 752, 222, 667, 667, 551, 668, 667, 611, 722, 778, 278, 667, + 668, 833, 722, 650, 778, 722, 667, 618, 611, 667, 798, 667, 835, 748, 278, + 667, 578, 446, 556, 222, 547, 578, 575, 500, 557, 446, 441, 556, 556, 222, + 500, 500, 576, 500, 448, 556, 690, 569, 482, 617, 395, 547, 648, 525, 713, + 781, 222, 547, 556, 547, 781, 667, 667, 865, 542, 719, 667, 278, 278, 500, + 1057, 1010, 854, 583, 722, 635, 719, 667, 656, 667, 542, 677, 667, 923, 604, + 719, 719, 583, 656, 833, 722, 778, 719, 667, 722, 611, 635, 760, 667, 740, + 667, 917, 938, 792, 885, 656, 719, 1010, 722, 556, 573, 531, 365, 583, 556, + 669, 458, 559, 559, 438, 583, 688, 552, 556, 542, 556, 500, 458, 500, 823, + 500, 573, 521, 802, 823, 625, 719, 521, 510, 750, 542, 556, 556, 556, 365, + 510, 500, 222, 278, 222, 906, 812, 556, 438, 559, 500, 552, 778, 556, 489, + 411, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, + 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, + 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1073, 1000, 768, 600, + 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, + 584, 549, 713, 979, 719, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, + 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, + 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, + 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, + 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, + 324, 316, 328, 398, 285, +]; + +// Char code of glyphs in LiberationSans-Regular.ttf. +const LiberationSansRegularMapping = [ + -1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, + 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, + 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, + 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, + 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, + 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, + 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, + 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, + 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, + 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, + 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, + 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, + 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, + 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, + 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, + 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, + 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, + 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, + 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, + 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, + 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, + 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, + 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, + 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, + 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, + 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, + 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, + 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, + 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, + 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, + 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, + 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, + 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, + 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +]; + +export { + LiberationSansBoldItalicMapping, + LiberationSansBoldItalicWidths, + LiberationSansBoldMapping, + LiberationSansBoldWidths, + LiberationSansItalicMapping, + LiberationSansItalicWidths, + LiberationSansRegularMapping, + LiberationSansRegularWidths, +}; diff --git a/frontend/public/pdf.js/src/core/lzw_stream.js b/frontend/public/pdf.js/src/core/lzw_stream.js new file mode 100644 index 0000000..9a0ae74 --- /dev/null +++ b/frontend/public/pdf.js/src/core/lzw_stream.js @@ -0,0 +1,150 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { DecodeStream } from "./decode_stream.js"; + +class LZWStream extends DecodeStream { + constructor(str, maybeLength, earlyChange) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + this.cachedData = 0; + this.bitsCached = 0; + + const maxLzwDictionarySize = 4096; + const lzwState = { + earlyChange, + codeLength: 9, + nextCode: 258, + dictionaryValues: new Uint8Array(maxLzwDictionarySize), + dictionaryLengths: new Uint16Array(maxLzwDictionarySize), + dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), + currentSequence: new Uint8Array(maxLzwDictionarySize), + currentSequenceLength: 0, + }; + for (let i = 0; i < 256; ++i) { + lzwState.dictionaryValues[i] = i; + lzwState.dictionaryLengths[i] = 1; + } + this.lzwState = lzwState; + } + + readBits(n) { + let bitsCached = this.bitsCached; + let cachedData = this.cachedData; + while (bitsCached < n) { + const c = this.str.getByte(); + if (c === -1) { + this.eof = true; + return null; + } + cachedData = (cachedData << 8) | c; + bitsCached += 8; + } + this.bitsCached = bitsCached -= n; + this.cachedData = cachedData; + this.lastCode = null; + return (cachedData >>> bitsCached) & ((1 << n) - 1); + } + + readBlock() { + const blockSize = 512, + decodedSizeDelta = blockSize; + let estimatedDecodedSize = blockSize * 2; + let i, j, q; + + const lzwState = this.lzwState; + if (!lzwState) { + return; // eof was found + } + + const earlyChange = lzwState.earlyChange; + let nextCode = lzwState.nextCode; + const dictionaryValues = lzwState.dictionaryValues; + const dictionaryLengths = lzwState.dictionaryLengths; + const dictionaryPrevCodes = lzwState.dictionaryPrevCodes; + let codeLength = lzwState.codeLength; + let prevCode = lzwState.prevCode; + const currentSequence = lzwState.currentSequence; + let currentSequenceLength = lzwState.currentSequenceLength; + + let decodedLength = 0; + let currentBufferLength = this.bufferLength; + let buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + + for (i = 0; i < blockSize; i++) { + const code = this.readBits(codeLength); + const hasPrev = currentSequenceLength > 0; + if (code < 256) { + currentSequence[0] = code; + currentSequenceLength = 1; + } else if (code >= 258) { + if (code < nextCode) { + currentSequenceLength = dictionaryLengths[code]; + for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { + currentSequence[j] = dictionaryValues[q]; + q = dictionaryPrevCodes[q]; + } + } else { + currentSequence[currentSequenceLength++] = currentSequence[0]; + } + } else if (code === 256) { + codeLength = 9; + nextCode = 258; + currentSequenceLength = 0; + continue; + } else { + this.eof = true; + delete this.lzwState; + break; + } + + if (hasPrev) { + dictionaryPrevCodes[nextCode] = prevCode; + dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; + dictionaryValues[nextCode] = currentSequence[0]; + nextCode++; + codeLength = + (nextCode + earlyChange) & (nextCode + earlyChange - 1) + ? codeLength + : Math.min( + Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, + 12 + ) | 0; + } + prevCode = code; + + decodedLength += currentSequenceLength; + if (estimatedDecodedSize < decodedLength) { + do { + estimatedDecodedSize += decodedSizeDelta; + } while (estimatedDecodedSize < decodedLength); + buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + } + for (j = 0; j < currentSequenceLength; j++) { + buffer[currentBufferLength++] = currentSequence[j]; + } + } + lzwState.nextCode = nextCode; + lzwState.codeLength = codeLength; + lzwState.prevCode = prevCode; + lzwState.currentSequenceLength = currentSequenceLength; + + this.bufferLength = currentBufferLength; + } +} + +export { LZWStream }; diff --git a/frontend/public/pdf.js/src/core/metadata_parser.js b/frontend/public/pdf.js/src/core/metadata_parser.js new file mode 100644 index 0000000..9541dae --- /dev/null +++ b/frontend/public/pdf.js/src/core/metadata_parser.js @@ -0,0 +1,146 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { SimpleXMLParser } from "./xml_parser.js"; + +class MetadataParser { + constructor(data) { + // Ghostscript may produce invalid metadata, so try to repair that first. + data = this._repair(data); + + // Convert the string to an XML document. + const parser = new SimpleXMLParser({ lowerCaseName: true }); + const xmlDocument = parser.parseFromString(data); + + this._metadataMap = new Map(); + this._data = data; + + if (xmlDocument) { + this._parse(xmlDocument); + } + } + + _repair(data) { + // Start by removing any "junk" before the first tag (see issue 10395). + return data + .replace(/^[^<]+/, "") + .replaceAll(/>\\376\\377([^<]+)/g, function (all, codes) { + const bytes = codes + .replaceAll(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) { + return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1); + }) + .replaceAll(/&(amp|apos|gt|lt|quot);/g, function (str, name) { + switch (name) { + case "amp": + return "&"; + case "apos": + return "'"; + case "gt": + return ">"; + case "lt": + return "<"; + case "quot": + return '"'; + } + throw new Error(`_repair: ${name} isn't defined.`); + }); + + const charBuf = [">"]; + for (let i = 0, ii = bytes.length; i < ii; i += 2) { + const code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1); + if ( + code >= /* Space = */ 32 && + code < /* Delete = */ 127 && + code !== /* '<' = */ 60 && + code !== /* '>' = */ 62 && + code !== /* '&' = */ 38 + ) { + charBuf.push(String.fromCharCode(code)); + } else { + charBuf.push( + "&#x" + (0x10000 + code).toString(16).substring(1) + ";" + ); + } + } + return charBuf.join(""); + }); + } + + _getSequence(entry) { + const name = entry.nodeName; + if (name !== "rdf:bag" && name !== "rdf:seq" && name !== "rdf:alt") { + return null; + } + return entry.childNodes.filter(node => node.nodeName === "rdf:li"); + } + + _parseArray(entry) { + if (!entry.hasChildNodes()) { + return; + } + // Child must be a Bag (unordered array) or a Seq. + const [seqNode] = entry.childNodes; + const sequence = this._getSequence(seqNode) || []; + + this._metadataMap.set( + entry.nodeName, + sequence.map(node => node.textContent.trim()) + ); + } + + _parse(xmlDocument) { + let rdf = xmlDocument.documentElement; + + if (rdf.nodeName !== "rdf:rdf") { + // Wrapped in + rdf = rdf.firstChild; + while (rdf && rdf.nodeName !== "rdf:rdf") { + rdf = rdf.nextSibling; + } + } + + if (!rdf || rdf.nodeName !== "rdf:rdf" || !rdf.hasChildNodes()) { + return; + } + + for (const desc of rdf.childNodes) { + if (desc.nodeName !== "rdf:description") { + continue; + } + + for (const entry of desc.childNodes) { + const name = entry.nodeName; + switch (name) { + case "#text": + continue; + case "dc:creator": + case "dc:subject": + this._parseArray(entry); + continue; + } + this._metadataMap.set(name, entry.textContent.trim()); + } + } + } + + get serializable() { + return { + parsedData: this._metadataMap, + rawData: this._data, + }; + } +} + +export { MetadataParser }; diff --git a/frontend/public/pdf.js/src/core/metrics.js b/frontend/public/pdf.js/src/core/metrics.js new file mode 100644 index 0000000..c61c860 --- /dev/null +++ b/frontend/public/pdf.js/src/core/metrics.js @@ -0,0 +1,3057 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { getLookupTableFactory } from "./core_utils.js"; + +// The Metrics object contains glyph widths (in glyph space units). +// As per PDF spec, for most fonts (Type 3 being an exception) a glyph +// space unit corresponds to 1/1000th of text space unit. +const getMetrics = getLookupTableFactory(function (t) { + t.Courier = 600; + t["Courier-Bold"] = 600; + t["Courier-BoldOblique"] = 600; + t["Courier-Oblique"] = 600; + // eslint-disable-next-line no-shadow + t.Helvetica = getLookupTableFactory(function (t) { + t.space = 278; + t.exclam = 278; + t.quotedbl = 355; + t.numbersign = 556; + t.dollar = 556; + t.percent = 889; + t.ampersand = 667; + t.quoteright = 222; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 389; + t.plus = 584; + t.comma = 278; + t.hyphen = 333; + t.period = 278; + t.slash = 278; + t.zero = 556; + t.one = 556; + t.two = 556; + t.three = 556; + t.four = 556; + t.five = 556; + t.six = 556; + t.seven = 556; + t.eight = 556; + t.nine = 556; + t.colon = 278; + t.semicolon = 278; + t.less = 584; + t.equal = 584; + t.greater = 584; + t.question = 556; + t.at = 1015; + t.A = 667; + t.B = 667; + t.C = 722; + t.D = 722; + t.E = 667; + t.F = 611; + t.G = 778; + t.H = 722; + t.I = 278; + t.J = 500; + t.K = 667; + t.L = 556; + t.M = 833; + t.N = 722; + t.O = 778; + t.P = 667; + t.Q = 778; + t.R = 722; + t.S = 667; + t.T = 611; + t.U = 722; + t.V = 667; + t.W = 944; + t.X = 667; + t.Y = 667; + t.Z = 611; + t.bracketleft = 278; + t.backslash = 278; + t.bracketright = 278; + t.asciicircum = 469; + t.underscore = 556; + t.quoteleft = 222; + t.a = 556; + t.b = 556; + t.c = 500; + t.d = 556; + t.e = 556; + t.f = 278; + t.g = 556; + t.h = 556; + t.i = 222; + t.j = 222; + t.k = 500; + t.l = 222; + t.m = 833; + t.n = 556; + t.o = 556; + t.p = 556; + t.q = 556; + t.r = 333; + t.s = 500; + t.t = 278; + t.u = 556; + t.v = 500; + t.w = 722; + t.x = 500; + t.y = 500; + t.z = 500; + t.braceleft = 334; + t.bar = 260; + t.braceright = 334; + t.asciitilde = 584; + t.exclamdown = 333; + t.cent = 556; + t.sterling = 556; + t.fraction = 167; + t.yen = 556; + t.florin = 556; + t.section = 556; + t.currency = 556; + t.quotesingle = 191; + t.quotedblleft = 333; + t.guillemotleft = 556; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 500; + t.fl = 500; + t.endash = 556; + t.dagger = 556; + t.daggerdbl = 556; + t.periodcentered = 278; + t.paragraph = 537; + t.bullet = 350; + t.quotesinglbase = 222; + t.quotedblbase = 333; + t.quotedblright = 333; + t.guillemotright = 556; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 611; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 1000; + t.ordfeminine = 370; + t.Lslash = 556; + t.Oslash = 778; + t.OE = 1000; + t.ordmasculine = 365; + t.ae = 889; + t.dotlessi = 278; + t.lslash = 222; + t.oslash = 611; + t.oe = 944; + t.germandbls = 611; + t.Idieresis = 278; + t.eacute = 556; + t.abreve = 556; + t.uhungarumlaut = 556; + t.ecaron = 556; + t.Ydieresis = 667; + t.divide = 584; + t.Yacute = 667; + t.Acircumflex = 667; + t.aacute = 556; + t.Ucircumflex = 722; + t.yacute = 500; + t.scommaaccent = 500; + t.ecircumflex = 556; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 556; + t.Uacute = 722; + t.uogonek = 556; + t.Edieresis = 667; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 737; + t.Emacron = 667; + t.ccaron = 500; + t.aring = 556; + t.Ncommaaccent = 722; + t.lacute = 222; + t.agrave = 556; + t.Tcommaaccent = 611; + t.Cacute = 722; + t.atilde = 556; + t.Edotaccent = 667; + t.scaron = 500; + t.scedilla = 500; + t.iacute = 278; + t.lozenge = 471; + t.Rcaron = 722; + t.Gcommaaccent = 778; + t.ucircumflex = 556; + t.acircumflex = 556; + t.Amacron = 667; + t.rcaron = 333; + t.ccedilla = 500; + t.Zdotaccent = 611; + t.Thorn = 667; + t.Omacron = 778; + t.Racute = 722; + t.Sacute = 667; + t.dcaron = 643; + t.Umacron = 722; + t.uring = 556; + t.threesuperior = 333; + t.Ograve = 778; + t.Agrave = 667; + t.Abreve = 667; + t.multiply = 584; + t.uacute = 556; + t.Tcaron = 611; + t.partialdiff = 476; + t.ydieresis = 500; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 667; + t.adieresis = 556; + t.edieresis = 556; + t.cacute = 500; + t.nacute = 556; + t.umacron = 556; + t.Ncaron = 722; + t.Iacute = 278; + t.plusminus = 584; + t.brokenbar = 260; + t.registered = 737; + t.Gbreve = 778; + t.Idotaccent = 278; + t.summation = 600; + t.Egrave = 667; + t.racute = 333; + t.omacron = 556; + t.Zacute = 611; + t.Zcaron = 611; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 722; + t.lcommaaccent = 222; + t.tcaron = 317; + t.eogonek = 556; + t.Uogonek = 722; + t.Aacute = 667; + t.Adieresis = 667; + t.egrave = 556; + t.zacute = 500; + t.iogonek = 222; + t.Oacute = 778; + t.oacute = 556; + t.amacron = 556; + t.sacute = 500; + t.idieresis = 278; + t.Ocircumflex = 778; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 556; + t.twosuperior = 333; + t.Odieresis = 778; + t.mu = 556; + t.igrave = 278; + t.ohungarumlaut = 556; + t.Eogonek = 667; + t.dcroat = 556; + t.threequarters = 834; + t.Scedilla = 667; + t.lcaron = 299; + t.Kcommaaccent = 667; + t.Lacute = 556; + t.trademark = 1000; + t.edotaccent = 556; + t.Igrave = 278; + t.Imacron = 278; + t.Lcaron = 556; + t.onehalf = 834; + t.lessequal = 549; + t.ocircumflex = 556; + t.ntilde = 556; + t.Uhungarumlaut = 722; + t.Eacute = 667; + t.emacron = 556; + t.gbreve = 556; + t.onequarter = 834; + t.Scaron = 667; + t.Scommaaccent = 667; + t.Ohungarumlaut = 778; + t.degree = 400; + t.ograve = 556; + t.Ccaron = 722; + t.ugrave = 556; + t.radical = 453; + t.Dcaron = 722; + t.rcommaaccent = 333; + t.Ntilde = 722; + t.otilde = 556; + t.Rcommaaccent = 722; + t.Lcommaaccent = 556; + t.Atilde = 667; + t.Aogonek = 667; + t.Aring = 667; + t.Otilde = 778; + t.zdotaccent = 500; + t.Ecaron = 667; + t.Iogonek = 278; + t.kcommaaccent = 500; + t.minus = 584; + t.Icircumflex = 278; + t.ncaron = 556; + t.tcommaaccent = 278; + t.logicalnot = 584; + t.odieresis = 556; + t.udieresis = 556; + t.notequal = 549; + t.gcommaaccent = 556; + t.eth = 556; + t.zcaron = 500; + t.ncommaaccent = 556; + t.onesuperior = 333; + t.imacron = 278; + t.Euro = 556; + }); + // eslint-disable-next-line no-shadow + t["Helvetica-Bold"] = getLookupTableFactory(function (t) { + t.space = 278; + t.exclam = 333; + t.quotedbl = 474; + t.numbersign = 556; + t.dollar = 556; + t.percent = 889; + t.ampersand = 722; + t.quoteright = 278; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 389; + t.plus = 584; + t.comma = 278; + t.hyphen = 333; + t.period = 278; + t.slash = 278; + t.zero = 556; + t.one = 556; + t.two = 556; + t.three = 556; + t.four = 556; + t.five = 556; + t.six = 556; + t.seven = 556; + t.eight = 556; + t.nine = 556; + t.colon = 333; + t.semicolon = 333; + t.less = 584; + t.equal = 584; + t.greater = 584; + t.question = 611; + t.at = 975; + t.A = 722; + t.B = 722; + t.C = 722; + t.D = 722; + t.E = 667; + t.F = 611; + t.G = 778; + t.H = 722; + t.I = 278; + t.J = 556; + t.K = 722; + t.L = 611; + t.M = 833; + t.N = 722; + t.O = 778; + t.P = 667; + t.Q = 778; + t.R = 722; + t.S = 667; + t.T = 611; + t.U = 722; + t.V = 667; + t.W = 944; + t.X = 667; + t.Y = 667; + t.Z = 611; + t.bracketleft = 333; + t.backslash = 278; + t.bracketright = 333; + t.asciicircum = 584; + t.underscore = 556; + t.quoteleft = 278; + t.a = 556; + t.b = 611; + t.c = 556; + t.d = 611; + t.e = 556; + t.f = 333; + t.g = 611; + t.h = 611; + t.i = 278; + t.j = 278; + t.k = 556; + t.l = 278; + t.m = 889; + t.n = 611; + t.o = 611; + t.p = 611; + t.q = 611; + t.r = 389; + t.s = 556; + t.t = 333; + t.u = 611; + t.v = 556; + t.w = 778; + t.x = 556; + t.y = 556; + t.z = 500; + t.braceleft = 389; + t.bar = 280; + t.braceright = 389; + t.asciitilde = 584; + t.exclamdown = 333; + t.cent = 556; + t.sterling = 556; + t.fraction = 167; + t.yen = 556; + t.florin = 556; + t.section = 556; + t.currency = 556; + t.quotesingle = 238; + t.quotedblleft = 500; + t.guillemotleft = 556; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 611; + t.fl = 611; + t.endash = 556; + t.dagger = 556; + t.daggerdbl = 556; + t.periodcentered = 278; + t.paragraph = 556; + t.bullet = 350; + t.quotesinglbase = 278; + t.quotedblbase = 500; + t.quotedblright = 500; + t.guillemotright = 556; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 611; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 1000; + t.ordfeminine = 370; + t.Lslash = 611; + t.Oslash = 778; + t.OE = 1000; + t.ordmasculine = 365; + t.ae = 889; + t.dotlessi = 278; + t.lslash = 278; + t.oslash = 611; + t.oe = 944; + t.germandbls = 611; + t.Idieresis = 278; + t.eacute = 556; + t.abreve = 556; + t.uhungarumlaut = 611; + t.ecaron = 556; + t.Ydieresis = 667; + t.divide = 584; + t.Yacute = 667; + t.Acircumflex = 722; + t.aacute = 556; + t.Ucircumflex = 722; + t.yacute = 556; + t.scommaaccent = 556; + t.ecircumflex = 556; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 556; + t.Uacute = 722; + t.uogonek = 611; + t.Edieresis = 667; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 737; + t.Emacron = 667; + t.ccaron = 556; + t.aring = 556; + t.Ncommaaccent = 722; + t.lacute = 278; + t.agrave = 556; + t.Tcommaaccent = 611; + t.Cacute = 722; + t.atilde = 556; + t.Edotaccent = 667; + t.scaron = 556; + t.scedilla = 556; + t.iacute = 278; + t.lozenge = 494; + t.Rcaron = 722; + t.Gcommaaccent = 778; + t.ucircumflex = 611; + t.acircumflex = 556; + t.Amacron = 722; + t.rcaron = 389; + t.ccedilla = 556; + t.Zdotaccent = 611; + t.Thorn = 667; + t.Omacron = 778; + t.Racute = 722; + t.Sacute = 667; + t.dcaron = 743; + t.Umacron = 722; + t.uring = 611; + t.threesuperior = 333; + t.Ograve = 778; + t.Agrave = 722; + t.Abreve = 722; + t.multiply = 584; + t.uacute = 611; + t.Tcaron = 611; + t.partialdiff = 494; + t.ydieresis = 556; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 667; + t.adieresis = 556; + t.edieresis = 556; + t.cacute = 556; + t.nacute = 611; + t.umacron = 611; + t.Ncaron = 722; + t.Iacute = 278; + t.plusminus = 584; + t.brokenbar = 280; + t.registered = 737; + t.Gbreve = 778; + t.Idotaccent = 278; + t.summation = 600; + t.Egrave = 667; + t.racute = 389; + t.omacron = 611; + t.Zacute = 611; + t.Zcaron = 611; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 722; + t.lcommaaccent = 278; + t.tcaron = 389; + t.eogonek = 556; + t.Uogonek = 722; + t.Aacute = 722; + t.Adieresis = 722; + t.egrave = 556; + t.zacute = 500; + t.iogonek = 278; + t.Oacute = 778; + t.oacute = 611; + t.amacron = 556; + t.sacute = 556; + t.idieresis = 278; + t.Ocircumflex = 778; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 611; + t.twosuperior = 333; + t.Odieresis = 778; + t.mu = 611; + t.igrave = 278; + t.ohungarumlaut = 611; + t.Eogonek = 667; + t.dcroat = 611; + t.threequarters = 834; + t.Scedilla = 667; + t.lcaron = 400; + t.Kcommaaccent = 722; + t.Lacute = 611; + t.trademark = 1000; + t.edotaccent = 556; + t.Igrave = 278; + t.Imacron = 278; + t.Lcaron = 611; + t.onehalf = 834; + t.lessequal = 549; + t.ocircumflex = 611; + t.ntilde = 611; + t.Uhungarumlaut = 722; + t.Eacute = 667; + t.emacron = 556; + t.gbreve = 611; + t.onequarter = 834; + t.Scaron = 667; + t.Scommaaccent = 667; + t.Ohungarumlaut = 778; + t.degree = 400; + t.ograve = 611; + t.Ccaron = 722; + t.ugrave = 611; + t.radical = 549; + t.Dcaron = 722; + t.rcommaaccent = 389; + t.Ntilde = 722; + t.otilde = 611; + t.Rcommaaccent = 722; + t.Lcommaaccent = 611; + t.Atilde = 722; + t.Aogonek = 722; + t.Aring = 722; + t.Otilde = 778; + t.zdotaccent = 500; + t.Ecaron = 667; + t.Iogonek = 278; + t.kcommaaccent = 556; + t.minus = 584; + t.Icircumflex = 278; + t.ncaron = 611; + t.tcommaaccent = 333; + t.logicalnot = 584; + t.odieresis = 611; + t.udieresis = 611; + t.notequal = 549; + t.gcommaaccent = 611; + t.eth = 611; + t.zcaron = 500; + t.ncommaaccent = 611; + t.onesuperior = 333; + t.imacron = 278; + t.Euro = 556; + }); + // eslint-disable-next-line no-shadow + t["Helvetica-BoldOblique"] = getLookupTableFactory(function (t) { + t.space = 278; + t.exclam = 333; + t.quotedbl = 474; + t.numbersign = 556; + t.dollar = 556; + t.percent = 889; + t.ampersand = 722; + t.quoteright = 278; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 389; + t.plus = 584; + t.comma = 278; + t.hyphen = 333; + t.period = 278; + t.slash = 278; + t.zero = 556; + t.one = 556; + t.two = 556; + t.three = 556; + t.four = 556; + t.five = 556; + t.six = 556; + t.seven = 556; + t.eight = 556; + t.nine = 556; + t.colon = 333; + t.semicolon = 333; + t.less = 584; + t.equal = 584; + t.greater = 584; + t.question = 611; + t.at = 975; + t.A = 722; + t.B = 722; + t.C = 722; + t.D = 722; + t.E = 667; + t.F = 611; + t.G = 778; + t.H = 722; + t.I = 278; + t.J = 556; + t.K = 722; + t.L = 611; + t.M = 833; + t.N = 722; + t.O = 778; + t.P = 667; + t.Q = 778; + t.R = 722; + t.S = 667; + t.T = 611; + t.U = 722; + t.V = 667; + t.W = 944; + t.X = 667; + t.Y = 667; + t.Z = 611; + t.bracketleft = 333; + t.backslash = 278; + t.bracketright = 333; + t.asciicircum = 584; + t.underscore = 556; + t.quoteleft = 278; + t.a = 556; + t.b = 611; + t.c = 556; + t.d = 611; + t.e = 556; + t.f = 333; + t.g = 611; + t.h = 611; + t.i = 278; + t.j = 278; + t.k = 556; + t.l = 278; + t.m = 889; + t.n = 611; + t.o = 611; + t.p = 611; + t.q = 611; + t.r = 389; + t.s = 556; + t.t = 333; + t.u = 611; + t.v = 556; + t.w = 778; + t.x = 556; + t.y = 556; + t.z = 500; + t.braceleft = 389; + t.bar = 280; + t.braceright = 389; + t.asciitilde = 584; + t.exclamdown = 333; + t.cent = 556; + t.sterling = 556; + t.fraction = 167; + t.yen = 556; + t.florin = 556; + t.section = 556; + t.currency = 556; + t.quotesingle = 238; + t.quotedblleft = 500; + t.guillemotleft = 556; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 611; + t.fl = 611; + t.endash = 556; + t.dagger = 556; + t.daggerdbl = 556; + t.periodcentered = 278; + t.paragraph = 556; + t.bullet = 350; + t.quotesinglbase = 278; + t.quotedblbase = 500; + t.quotedblright = 500; + t.guillemotright = 556; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 611; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 1000; + t.ordfeminine = 370; + t.Lslash = 611; + t.Oslash = 778; + t.OE = 1000; + t.ordmasculine = 365; + t.ae = 889; + t.dotlessi = 278; + t.lslash = 278; + t.oslash = 611; + t.oe = 944; + t.germandbls = 611; + t.Idieresis = 278; + t.eacute = 556; + t.abreve = 556; + t.uhungarumlaut = 611; + t.ecaron = 556; + t.Ydieresis = 667; + t.divide = 584; + t.Yacute = 667; + t.Acircumflex = 722; + t.aacute = 556; + t.Ucircumflex = 722; + t.yacute = 556; + t.scommaaccent = 556; + t.ecircumflex = 556; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 556; + t.Uacute = 722; + t.uogonek = 611; + t.Edieresis = 667; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 737; + t.Emacron = 667; + t.ccaron = 556; + t.aring = 556; + t.Ncommaaccent = 722; + t.lacute = 278; + t.agrave = 556; + t.Tcommaaccent = 611; + t.Cacute = 722; + t.atilde = 556; + t.Edotaccent = 667; + t.scaron = 556; + t.scedilla = 556; + t.iacute = 278; + t.lozenge = 494; + t.Rcaron = 722; + t.Gcommaaccent = 778; + t.ucircumflex = 611; + t.acircumflex = 556; + t.Amacron = 722; + t.rcaron = 389; + t.ccedilla = 556; + t.Zdotaccent = 611; + t.Thorn = 667; + t.Omacron = 778; + t.Racute = 722; + t.Sacute = 667; + t.dcaron = 743; + t.Umacron = 722; + t.uring = 611; + t.threesuperior = 333; + t.Ograve = 778; + t.Agrave = 722; + t.Abreve = 722; + t.multiply = 584; + t.uacute = 611; + t.Tcaron = 611; + t.partialdiff = 494; + t.ydieresis = 556; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 667; + t.adieresis = 556; + t.edieresis = 556; + t.cacute = 556; + t.nacute = 611; + t.umacron = 611; + t.Ncaron = 722; + t.Iacute = 278; + t.plusminus = 584; + t.brokenbar = 280; + t.registered = 737; + t.Gbreve = 778; + t.Idotaccent = 278; + t.summation = 600; + t.Egrave = 667; + t.racute = 389; + t.omacron = 611; + t.Zacute = 611; + t.Zcaron = 611; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 722; + t.lcommaaccent = 278; + t.tcaron = 389; + t.eogonek = 556; + t.Uogonek = 722; + t.Aacute = 722; + t.Adieresis = 722; + t.egrave = 556; + t.zacute = 500; + t.iogonek = 278; + t.Oacute = 778; + t.oacute = 611; + t.amacron = 556; + t.sacute = 556; + t.idieresis = 278; + t.Ocircumflex = 778; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 611; + t.twosuperior = 333; + t.Odieresis = 778; + t.mu = 611; + t.igrave = 278; + t.ohungarumlaut = 611; + t.Eogonek = 667; + t.dcroat = 611; + t.threequarters = 834; + t.Scedilla = 667; + t.lcaron = 400; + t.Kcommaaccent = 722; + t.Lacute = 611; + t.trademark = 1000; + t.edotaccent = 556; + t.Igrave = 278; + t.Imacron = 278; + t.Lcaron = 611; + t.onehalf = 834; + t.lessequal = 549; + t.ocircumflex = 611; + t.ntilde = 611; + t.Uhungarumlaut = 722; + t.Eacute = 667; + t.emacron = 556; + t.gbreve = 611; + t.onequarter = 834; + t.Scaron = 667; + t.Scommaaccent = 667; + t.Ohungarumlaut = 778; + t.degree = 400; + t.ograve = 611; + t.Ccaron = 722; + t.ugrave = 611; + t.radical = 549; + t.Dcaron = 722; + t.rcommaaccent = 389; + t.Ntilde = 722; + t.otilde = 611; + t.Rcommaaccent = 722; + t.Lcommaaccent = 611; + t.Atilde = 722; + t.Aogonek = 722; + t.Aring = 722; + t.Otilde = 778; + t.zdotaccent = 500; + t.Ecaron = 667; + t.Iogonek = 278; + t.kcommaaccent = 556; + t.minus = 584; + t.Icircumflex = 278; + t.ncaron = 611; + t.tcommaaccent = 333; + t.logicalnot = 584; + t.odieresis = 611; + t.udieresis = 611; + t.notequal = 549; + t.gcommaaccent = 611; + t.eth = 611; + t.zcaron = 500; + t.ncommaaccent = 611; + t.onesuperior = 333; + t.imacron = 278; + t.Euro = 556; + }); + // eslint-disable-next-line no-shadow + t["Helvetica-Oblique"] = getLookupTableFactory(function (t) { + t.space = 278; + t.exclam = 278; + t.quotedbl = 355; + t.numbersign = 556; + t.dollar = 556; + t.percent = 889; + t.ampersand = 667; + t.quoteright = 222; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 389; + t.plus = 584; + t.comma = 278; + t.hyphen = 333; + t.period = 278; + t.slash = 278; + t.zero = 556; + t.one = 556; + t.two = 556; + t.three = 556; + t.four = 556; + t.five = 556; + t.six = 556; + t.seven = 556; + t.eight = 556; + t.nine = 556; + t.colon = 278; + t.semicolon = 278; + t.less = 584; + t.equal = 584; + t.greater = 584; + t.question = 556; + t.at = 1015; + t.A = 667; + t.B = 667; + t.C = 722; + t.D = 722; + t.E = 667; + t.F = 611; + t.G = 778; + t.H = 722; + t.I = 278; + t.J = 500; + t.K = 667; + t.L = 556; + t.M = 833; + t.N = 722; + t.O = 778; + t.P = 667; + t.Q = 778; + t.R = 722; + t.S = 667; + t.T = 611; + t.U = 722; + t.V = 667; + t.W = 944; + t.X = 667; + t.Y = 667; + t.Z = 611; + t.bracketleft = 278; + t.backslash = 278; + t.bracketright = 278; + t.asciicircum = 469; + t.underscore = 556; + t.quoteleft = 222; + t.a = 556; + t.b = 556; + t.c = 500; + t.d = 556; + t.e = 556; + t.f = 278; + t.g = 556; + t.h = 556; + t.i = 222; + t.j = 222; + t.k = 500; + t.l = 222; + t.m = 833; + t.n = 556; + t.o = 556; + t.p = 556; + t.q = 556; + t.r = 333; + t.s = 500; + t.t = 278; + t.u = 556; + t.v = 500; + t.w = 722; + t.x = 500; + t.y = 500; + t.z = 500; + t.braceleft = 334; + t.bar = 260; + t.braceright = 334; + t.asciitilde = 584; + t.exclamdown = 333; + t.cent = 556; + t.sterling = 556; + t.fraction = 167; + t.yen = 556; + t.florin = 556; + t.section = 556; + t.currency = 556; + t.quotesingle = 191; + t.quotedblleft = 333; + t.guillemotleft = 556; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 500; + t.fl = 500; + t.endash = 556; + t.dagger = 556; + t.daggerdbl = 556; + t.periodcentered = 278; + t.paragraph = 537; + t.bullet = 350; + t.quotesinglbase = 222; + t.quotedblbase = 333; + t.quotedblright = 333; + t.guillemotright = 556; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 611; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 1000; + t.ordfeminine = 370; + t.Lslash = 556; + t.Oslash = 778; + t.OE = 1000; + t.ordmasculine = 365; + t.ae = 889; + t.dotlessi = 278; + t.lslash = 222; + t.oslash = 611; + t.oe = 944; + t.germandbls = 611; + t.Idieresis = 278; + t.eacute = 556; + t.abreve = 556; + t.uhungarumlaut = 556; + t.ecaron = 556; + t.Ydieresis = 667; + t.divide = 584; + t.Yacute = 667; + t.Acircumflex = 667; + t.aacute = 556; + t.Ucircumflex = 722; + t.yacute = 500; + t.scommaaccent = 500; + t.ecircumflex = 556; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 556; + t.Uacute = 722; + t.uogonek = 556; + t.Edieresis = 667; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 737; + t.Emacron = 667; + t.ccaron = 500; + t.aring = 556; + t.Ncommaaccent = 722; + t.lacute = 222; + t.agrave = 556; + t.Tcommaaccent = 611; + t.Cacute = 722; + t.atilde = 556; + t.Edotaccent = 667; + t.scaron = 500; + t.scedilla = 500; + t.iacute = 278; + t.lozenge = 471; + t.Rcaron = 722; + t.Gcommaaccent = 778; + t.ucircumflex = 556; + t.acircumflex = 556; + t.Amacron = 667; + t.rcaron = 333; + t.ccedilla = 500; + t.Zdotaccent = 611; + t.Thorn = 667; + t.Omacron = 778; + t.Racute = 722; + t.Sacute = 667; + t.dcaron = 643; + t.Umacron = 722; + t.uring = 556; + t.threesuperior = 333; + t.Ograve = 778; + t.Agrave = 667; + t.Abreve = 667; + t.multiply = 584; + t.uacute = 556; + t.Tcaron = 611; + t.partialdiff = 476; + t.ydieresis = 500; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 667; + t.adieresis = 556; + t.edieresis = 556; + t.cacute = 500; + t.nacute = 556; + t.umacron = 556; + t.Ncaron = 722; + t.Iacute = 278; + t.plusminus = 584; + t.brokenbar = 260; + t.registered = 737; + t.Gbreve = 778; + t.Idotaccent = 278; + t.summation = 600; + t.Egrave = 667; + t.racute = 333; + t.omacron = 556; + t.Zacute = 611; + t.Zcaron = 611; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 722; + t.lcommaaccent = 222; + t.tcaron = 317; + t.eogonek = 556; + t.Uogonek = 722; + t.Aacute = 667; + t.Adieresis = 667; + t.egrave = 556; + t.zacute = 500; + t.iogonek = 222; + t.Oacute = 778; + t.oacute = 556; + t.amacron = 556; + t.sacute = 500; + t.idieresis = 278; + t.Ocircumflex = 778; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 556; + t.twosuperior = 333; + t.Odieresis = 778; + t.mu = 556; + t.igrave = 278; + t.ohungarumlaut = 556; + t.Eogonek = 667; + t.dcroat = 556; + t.threequarters = 834; + t.Scedilla = 667; + t.lcaron = 299; + t.Kcommaaccent = 667; + t.Lacute = 556; + t.trademark = 1000; + t.edotaccent = 556; + t.Igrave = 278; + t.Imacron = 278; + t.Lcaron = 556; + t.onehalf = 834; + t.lessequal = 549; + t.ocircumflex = 556; + t.ntilde = 556; + t.Uhungarumlaut = 722; + t.Eacute = 667; + t.emacron = 556; + t.gbreve = 556; + t.onequarter = 834; + t.Scaron = 667; + t.Scommaaccent = 667; + t.Ohungarumlaut = 778; + t.degree = 400; + t.ograve = 556; + t.Ccaron = 722; + t.ugrave = 556; + t.radical = 453; + t.Dcaron = 722; + t.rcommaaccent = 333; + t.Ntilde = 722; + t.otilde = 556; + t.Rcommaaccent = 722; + t.Lcommaaccent = 556; + t.Atilde = 667; + t.Aogonek = 667; + t.Aring = 667; + t.Otilde = 778; + t.zdotaccent = 500; + t.Ecaron = 667; + t.Iogonek = 278; + t.kcommaaccent = 500; + t.minus = 584; + t.Icircumflex = 278; + t.ncaron = 556; + t.tcommaaccent = 278; + t.logicalnot = 584; + t.odieresis = 556; + t.udieresis = 556; + t.notequal = 549; + t.gcommaaccent = 556; + t.eth = 556; + t.zcaron = 500; + t.ncommaaccent = 556; + t.onesuperior = 333; + t.imacron = 278; + t.Euro = 556; + }); + // eslint-disable-next-line no-shadow + t.Symbol = getLookupTableFactory(function (t) { + t.space = 250; + t.exclam = 333; + t.universal = 713; + t.numbersign = 500; + t.existential = 549; + t.percent = 833; + t.ampersand = 778; + t.suchthat = 439; + t.parenleft = 333; + t.parenright = 333; + t.asteriskmath = 500; + t.plus = 549; + t.comma = 250; + t.minus = 549; + t.period = 250; + t.slash = 278; + t.zero = 500; + t.one = 500; + t.two = 500; + t.three = 500; + t.four = 500; + t.five = 500; + t.six = 500; + t.seven = 500; + t.eight = 500; + t.nine = 500; + t.colon = 278; + t.semicolon = 278; + t.less = 549; + t.equal = 549; + t.greater = 549; + t.question = 444; + t.congruent = 549; + t.Alpha = 722; + t.Beta = 667; + t.Chi = 722; + t.Delta = 612; + t.Epsilon = 611; + t.Phi = 763; + t.Gamma = 603; + t.Eta = 722; + t.Iota = 333; + t.theta1 = 631; + t.Kappa = 722; + t.Lambda = 686; + t.Mu = 889; + t.Nu = 722; + t.Omicron = 722; + t.Pi = 768; + t.Theta = 741; + t.Rho = 556; + t.Sigma = 592; + t.Tau = 611; + t.Upsilon = 690; + t.sigma1 = 439; + t.Omega = 768; + t.Xi = 645; + t.Psi = 795; + t.Zeta = 611; + t.bracketleft = 333; + t.therefore = 863; + t.bracketright = 333; + t.perpendicular = 658; + t.underscore = 500; + t.radicalex = 500; + t.alpha = 631; + t.beta = 549; + t.chi = 549; + t.delta = 494; + t.epsilon = 439; + t.phi = 521; + t.gamma = 411; + t.eta = 603; + t.iota = 329; + t.phi1 = 603; + t.kappa = 549; + t.lambda = 549; + t.mu = 576; + t.nu = 521; + t.omicron = 549; + t.pi = 549; + t.theta = 521; + t.rho = 549; + t.sigma = 603; + t.tau = 439; + t.upsilon = 576; + t.omega1 = 713; + t.omega = 686; + t.xi = 493; + t.psi = 686; + t.zeta = 494; + t.braceleft = 480; + t.bar = 200; + t.braceright = 480; + t.similar = 549; + t.Euro = 750; + t.Upsilon1 = 620; + t.minute = 247; + t.lessequal = 549; + t.fraction = 167; + t.infinity = 713; + t.florin = 500; + t.club = 753; + t.diamond = 753; + t.heart = 753; + t.spade = 753; + t.arrowboth = 1042; + t.arrowleft = 987; + t.arrowup = 603; + t.arrowright = 987; + t.arrowdown = 603; + t.degree = 400; + t.plusminus = 549; + t.second = 411; + t.greaterequal = 549; + t.multiply = 549; + t.proportional = 713; + t.partialdiff = 494; + t.bullet = 460; + t.divide = 549; + t.notequal = 549; + t.equivalence = 549; + t.approxequal = 549; + t.ellipsis = 1000; + t.arrowvertex = 603; + t.arrowhorizex = 1000; + t.carriagereturn = 658; + t.aleph = 823; + t.Ifraktur = 686; + t.Rfraktur = 795; + t.weierstrass = 987; + t.circlemultiply = 768; + t.circleplus = 768; + t.emptyset = 823; + t.intersection = 768; + t.union = 768; + t.propersuperset = 713; + t.reflexsuperset = 713; + t.notsubset = 713; + t.propersubset = 713; + t.reflexsubset = 713; + t.element = 713; + t.notelement = 713; + t.angle = 768; + t.gradient = 713; + t.registerserif = 790; + t.copyrightserif = 790; + t.trademarkserif = 890; + t.product = 823; + t.radical = 549; + t.dotmath = 250; + t.logicalnot = 713; + t.logicaland = 603; + t.logicalor = 603; + t.arrowdblboth = 1042; + t.arrowdblleft = 987; + t.arrowdblup = 603; + t.arrowdblright = 987; + t.arrowdbldown = 603; + t.lozenge = 494; + t.angleleft = 329; + t.registersans = 790; + t.copyrightsans = 790; + t.trademarksans = 786; + t.summation = 713; + t.parenlefttp = 384; + t.parenleftex = 384; + t.parenleftbt = 384; + t.bracketlefttp = 384; + t.bracketleftex = 384; + t.bracketleftbt = 384; + t.bracelefttp = 494; + t.braceleftmid = 494; + t.braceleftbt = 494; + t.braceex = 494; + t.angleright = 329; + t.integral = 274; + t.integraltp = 686; + t.integralex = 686; + t.integralbt = 686; + t.parenrighttp = 384; + t.parenrightex = 384; + t.parenrightbt = 384; + t.bracketrighttp = 384; + t.bracketrightex = 384; + t.bracketrightbt = 384; + t.bracerighttp = 494; + t.bracerightmid = 494; + t.bracerightbt = 494; + t.apple = 790; + }); + // eslint-disable-next-line no-shadow + t["Times-Roman"] = getLookupTableFactory(function (t) { + t.space = 250; + t.exclam = 333; + t.quotedbl = 408; + t.numbersign = 500; + t.dollar = 500; + t.percent = 833; + t.ampersand = 778; + t.quoteright = 333; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 500; + t.plus = 564; + t.comma = 250; + t.hyphen = 333; + t.period = 250; + t.slash = 278; + t.zero = 500; + t.one = 500; + t.two = 500; + t.three = 500; + t.four = 500; + t.five = 500; + t.six = 500; + t.seven = 500; + t.eight = 500; + t.nine = 500; + t.colon = 278; + t.semicolon = 278; + t.less = 564; + t.equal = 564; + t.greater = 564; + t.question = 444; + t.at = 921; + t.A = 722; + t.B = 667; + t.C = 667; + t.D = 722; + t.E = 611; + t.F = 556; + t.G = 722; + t.H = 722; + t.I = 333; + t.J = 389; + t.K = 722; + t.L = 611; + t.M = 889; + t.N = 722; + t.O = 722; + t.P = 556; + t.Q = 722; + t.R = 667; + t.S = 556; + t.T = 611; + t.U = 722; + t.V = 722; + t.W = 944; + t.X = 722; + t.Y = 722; + t.Z = 611; + t.bracketleft = 333; + t.backslash = 278; + t.bracketright = 333; + t.asciicircum = 469; + t.underscore = 500; + t.quoteleft = 333; + t.a = 444; + t.b = 500; + t.c = 444; + t.d = 500; + t.e = 444; + t.f = 333; + t.g = 500; + t.h = 500; + t.i = 278; + t.j = 278; + t.k = 500; + t.l = 278; + t.m = 778; + t.n = 500; + t.o = 500; + t.p = 500; + t.q = 500; + t.r = 333; + t.s = 389; + t.t = 278; + t.u = 500; + t.v = 500; + t.w = 722; + t.x = 500; + t.y = 500; + t.z = 444; + t.braceleft = 480; + t.bar = 200; + t.braceright = 480; + t.asciitilde = 541; + t.exclamdown = 333; + t.cent = 500; + t.sterling = 500; + t.fraction = 167; + t.yen = 500; + t.florin = 500; + t.section = 500; + t.currency = 500; + t.quotesingle = 180; + t.quotedblleft = 444; + t.guillemotleft = 500; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 556; + t.fl = 556; + t.endash = 500; + t.dagger = 500; + t.daggerdbl = 500; + t.periodcentered = 250; + t.paragraph = 453; + t.bullet = 350; + t.quotesinglbase = 333; + t.quotedblbase = 444; + t.quotedblright = 444; + t.guillemotright = 500; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 444; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 889; + t.ordfeminine = 276; + t.Lslash = 611; + t.Oslash = 722; + t.OE = 889; + t.ordmasculine = 310; + t.ae = 667; + t.dotlessi = 278; + t.lslash = 278; + t.oslash = 500; + t.oe = 722; + t.germandbls = 500; + t.Idieresis = 333; + t.eacute = 444; + t.abreve = 444; + t.uhungarumlaut = 500; + t.ecaron = 444; + t.Ydieresis = 722; + t.divide = 564; + t.Yacute = 722; + t.Acircumflex = 722; + t.aacute = 444; + t.Ucircumflex = 722; + t.yacute = 500; + t.scommaaccent = 389; + t.ecircumflex = 444; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 444; + t.Uacute = 722; + t.uogonek = 500; + t.Edieresis = 611; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 760; + t.Emacron = 611; + t.ccaron = 444; + t.aring = 444; + t.Ncommaaccent = 722; + t.lacute = 278; + t.agrave = 444; + t.Tcommaaccent = 611; + t.Cacute = 667; + t.atilde = 444; + t.Edotaccent = 611; + t.scaron = 389; + t.scedilla = 389; + t.iacute = 278; + t.lozenge = 471; + t.Rcaron = 667; + t.Gcommaaccent = 722; + t.ucircumflex = 500; + t.acircumflex = 444; + t.Amacron = 722; + t.rcaron = 333; + t.ccedilla = 444; + t.Zdotaccent = 611; + t.Thorn = 556; + t.Omacron = 722; + t.Racute = 667; + t.Sacute = 556; + t.dcaron = 588; + t.Umacron = 722; + t.uring = 500; + t.threesuperior = 300; + t.Ograve = 722; + t.Agrave = 722; + t.Abreve = 722; + t.multiply = 564; + t.uacute = 500; + t.Tcaron = 611; + t.partialdiff = 476; + t.ydieresis = 500; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 611; + t.adieresis = 444; + t.edieresis = 444; + t.cacute = 444; + t.nacute = 500; + t.umacron = 500; + t.Ncaron = 722; + t.Iacute = 333; + t.plusminus = 564; + t.brokenbar = 200; + t.registered = 760; + t.Gbreve = 722; + t.Idotaccent = 333; + t.summation = 600; + t.Egrave = 611; + t.racute = 333; + t.omacron = 500; + t.Zacute = 611; + t.Zcaron = 611; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 667; + t.lcommaaccent = 278; + t.tcaron = 326; + t.eogonek = 444; + t.Uogonek = 722; + t.Aacute = 722; + t.Adieresis = 722; + t.egrave = 444; + t.zacute = 444; + t.iogonek = 278; + t.Oacute = 722; + t.oacute = 500; + t.amacron = 444; + t.sacute = 389; + t.idieresis = 278; + t.Ocircumflex = 722; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 500; + t.twosuperior = 300; + t.Odieresis = 722; + t.mu = 500; + t.igrave = 278; + t.ohungarumlaut = 500; + t.Eogonek = 611; + t.dcroat = 500; + t.threequarters = 750; + t.Scedilla = 556; + t.lcaron = 344; + t.Kcommaaccent = 722; + t.Lacute = 611; + t.trademark = 980; + t.edotaccent = 444; + t.Igrave = 333; + t.Imacron = 333; + t.Lcaron = 611; + t.onehalf = 750; + t.lessequal = 549; + t.ocircumflex = 500; + t.ntilde = 500; + t.Uhungarumlaut = 722; + t.Eacute = 611; + t.emacron = 444; + t.gbreve = 500; + t.onequarter = 750; + t.Scaron = 556; + t.Scommaaccent = 556; + t.Ohungarumlaut = 722; + t.degree = 400; + t.ograve = 500; + t.Ccaron = 667; + t.ugrave = 500; + t.radical = 453; + t.Dcaron = 722; + t.rcommaaccent = 333; + t.Ntilde = 722; + t.otilde = 500; + t.Rcommaaccent = 667; + t.Lcommaaccent = 611; + t.Atilde = 722; + t.Aogonek = 722; + t.Aring = 722; + t.Otilde = 722; + t.zdotaccent = 444; + t.Ecaron = 611; + t.Iogonek = 333; + t.kcommaaccent = 500; + t.minus = 564; + t.Icircumflex = 333; + t.ncaron = 500; + t.tcommaaccent = 278; + t.logicalnot = 564; + t.odieresis = 500; + t.udieresis = 500; + t.notequal = 549; + t.gcommaaccent = 500; + t.eth = 500; + t.zcaron = 444; + t.ncommaaccent = 500; + t.onesuperior = 300; + t.imacron = 278; + t.Euro = 500; + }); + // eslint-disable-next-line no-shadow + t["Times-Bold"] = getLookupTableFactory(function (t) { + t.space = 250; + t.exclam = 333; + t.quotedbl = 555; + t.numbersign = 500; + t.dollar = 500; + t.percent = 1000; + t.ampersand = 833; + t.quoteright = 333; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 500; + t.plus = 570; + t.comma = 250; + t.hyphen = 333; + t.period = 250; + t.slash = 278; + t.zero = 500; + t.one = 500; + t.two = 500; + t.three = 500; + t.four = 500; + t.five = 500; + t.six = 500; + t.seven = 500; + t.eight = 500; + t.nine = 500; + t.colon = 333; + t.semicolon = 333; + t.less = 570; + t.equal = 570; + t.greater = 570; + t.question = 500; + t.at = 930; + t.A = 722; + t.B = 667; + t.C = 722; + t.D = 722; + t.E = 667; + t.F = 611; + t.G = 778; + t.H = 778; + t.I = 389; + t.J = 500; + t.K = 778; + t.L = 667; + t.M = 944; + t.N = 722; + t.O = 778; + t.P = 611; + t.Q = 778; + t.R = 722; + t.S = 556; + t.T = 667; + t.U = 722; + t.V = 722; + t.W = 1000; + t.X = 722; + t.Y = 722; + t.Z = 667; + t.bracketleft = 333; + t.backslash = 278; + t.bracketright = 333; + t.asciicircum = 581; + t.underscore = 500; + t.quoteleft = 333; + t.a = 500; + t.b = 556; + t.c = 444; + t.d = 556; + t.e = 444; + t.f = 333; + t.g = 500; + t.h = 556; + t.i = 278; + t.j = 333; + t.k = 556; + t.l = 278; + t.m = 833; + t.n = 556; + t.o = 500; + t.p = 556; + t.q = 556; + t.r = 444; + t.s = 389; + t.t = 333; + t.u = 556; + t.v = 500; + t.w = 722; + t.x = 500; + t.y = 500; + t.z = 444; + t.braceleft = 394; + t.bar = 220; + t.braceright = 394; + t.asciitilde = 520; + t.exclamdown = 333; + t.cent = 500; + t.sterling = 500; + t.fraction = 167; + t.yen = 500; + t.florin = 500; + t.section = 500; + t.currency = 500; + t.quotesingle = 278; + t.quotedblleft = 500; + t.guillemotleft = 500; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 556; + t.fl = 556; + t.endash = 500; + t.dagger = 500; + t.daggerdbl = 500; + t.periodcentered = 250; + t.paragraph = 540; + t.bullet = 350; + t.quotesinglbase = 333; + t.quotedblbase = 500; + t.quotedblright = 500; + t.guillemotright = 500; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 500; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 1000; + t.ordfeminine = 300; + t.Lslash = 667; + t.Oslash = 778; + t.OE = 1000; + t.ordmasculine = 330; + t.ae = 722; + t.dotlessi = 278; + t.lslash = 278; + t.oslash = 500; + t.oe = 722; + t.germandbls = 556; + t.Idieresis = 389; + t.eacute = 444; + t.abreve = 500; + t.uhungarumlaut = 556; + t.ecaron = 444; + t.Ydieresis = 722; + t.divide = 570; + t.Yacute = 722; + t.Acircumflex = 722; + t.aacute = 500; + t.Ucircumflex = 722; + t.yacute = 500; + t.scommaaccent = 389; + t.ecircumflex = 444; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 500; + t.Uacute = 722; + t.uogonek = 556; + t.Edieresis = 667; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 747; + t.Emacron = 667; + t.ccaron = 444; + t.aring = 500; + t.Ncommaaccent = 722; + t.lacute = 278; + t.agrave = 500; + t.Tcommaaccent = 667; + t.Cacute = 722; + t.atilde = 500; + t.Edotaccent = 667; + t.scaron = 389; + t.scedilla = 389; + t.iacute = 278; + t.lozenge = 494; + t.Rcaron = 722; + t.Gcommaaccent = 778; + t.ucircumflex = 556; + t.acircumflex = 500; + t.Amacron = 722; + t.rcaron = 444; + t.ccedilla = 444; + t.Zdotaccent = 667; + t.Thorn = 611; + t.Omacron = 778; + t.Racute = 722; + t.Sacute = 556; + t.dcaron = 672; + t.Umacron = 722; + t.uring = 556; + t.threesuperior = 300; + t.Ograve = 778; + t.Agrave = 722; + t.Abreve = 722; + t.multiply = 570; + t.uacute = 556; + t.Tcaron = 667; + t.partialdiff = 494; + t.ydieresis = 500; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 667; + t.adieresis = 500; + t.edieresis = 444; + t.cacute = 444; + t.nacute = 556; + t.umacron = 556; + t.Ncaron = 722; + t.Iacute = 389; + t.plusminus = 570; + t.brokenbar = 220; + t.registered = 747; + t.Gbreve = 778; + t.Idotaccent = 389; + t.summation = 600; + t.Egrave = 667; + t.racute = 444; + t.omacron = 500; + t.Zacute = 667; + t.Zcaron = 667; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 722; + t.lcommaaccent = 278; + t.tcaron = 416; + t.eogonek = 444; + t.Uogonek = 722; + t.Aacute = 722; + t.Adieresis = 722; + t.egrave = 444; + t.zacute = 444; + t.iogonek = 278; + t.Oacute = 778; + t.oacute = 500; + t.amacron = 500; + t.sacute = 389; + t.idieresis = 278; + t.Ocircumflex = 778; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 556; + t.twosuperior = 300; + t.Odieresis = 778; + t.mu = 556; + t.igrave = 278; + t.ohungarumlaut = 500; + t.Eogonek = 667; + t.dcroat = 556; + t.threequarters = 750; + t.Scedilla = 556; + t.lcaron = 394; + t.Kcommaaccent = 778; + t.Lacute = 667; + t.trademark = 1000; + t.edotaccent = 444; + t.Igrave = 389; + t.Imacron = 389; + t.Lcaron = 667; + t.onehalf = 750; + t.lessequal = 549; + t.ocircumflex = 500; + t.ntilde = 556; + t.Uhungarumlaut = 722; + t.Eacute = 667; + t.emacron = 444; + t.gbreve = 500; + t.onequarter = 750; + t.Scaron = 556; + t.Scommaaccent = 556; + t.Ohungarumlaut = 778; + t.degree = 400; + t.ograve = 500; + t.Ccaron = 722; + t.ugrave = 556; + t.radical = 549; + t.Dcaron = 722; + t.rcommaaccent = 444; + t.Ntilde = 722; + t.otilde = 500; + t.Rcommaaccent = 722; + t.Lcommaaccent = 667; + t.Atilde = 722; + t.Aogonek = 722; + t.Aring = 722; + t.Otilde = 778; + t.zdotaccent = 444; + t.Ecaron = 667; + t.Iogonek = 389; + t.kcommaaccent = 556; + t.minus = 570; + t.Icircumflex = 389; + t.ncaron = 556; + t.tcommaaccent = 333; + t.logicalnot = 570; + t.odieresis = 500; + t.udieresis = 556; + t.notequal = 549; + t.gcommaaccent = 500; + t.eth = 500; + t.zcaron = 444; + t.ncommaaccent = 556; + t.onesuperior = 300; + t.imacron = 278; + t.Euro = 500; + }); + // eslint-disable-next-line no-shadow + t["Times-BoldItalic"] = getLookupTableFactory(function (t) { + t.space = 250; + t.exclam = 389; + t.quotedbl = 555; + t.numbersign = 500; + t.dollar = 500; + t.percent = 833; + t.ampersand = 778; + t.quoteright = 333; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 500; + t.plus = 570; + t.comma = 250; + t.hyphen = 333; + t.period = 250; + t.slash = 278; + t.zero = 500; + t.one = 500; + t.two = 500; + t.three = 500; + t.four = 500; + t.five = 500; + t.six = 500; + t.seven = 500; + t.eight = 500; + t.nine = 500; + t.colon = 333; + t.semicolon = 333; + t.less = 570; + t.equal = 570; + t.greater = 570; + t.question = 500; + t.at = 832; + t.A = 667; + t.B = 667; + t.C = 667; + t.D = 722; + t.E = 667; + t.F = 667; + t.G = 722; + t.H = 778; + t.I = 389; + t.J = 500; + t.K = 667; + t.L = 611; + t.M = 889; + t.N = 722; + t.O = 722; + t.P = 611; + t.Q = 722; + t.R = 667; + t.S = 556; + t.T = 611; + t.U = 722; + t.V = 667; + t.W = 889; + t.X = 667; + t.Y = 611; + t.Z = 611; + t.bracketleft = 333; + t.backslash = 278; + t.bracketright = 333; + t.asciicircum = 570; + t.underscore = 500; + t.quoteleft = 333; + t.a = 500; + t.b = 500; + t.c = 444; + t.d = 500; + t.e = 444; + t.f = 333; + t.g = 500; + t.h = 556; + t.i = 278; + t.j = 278; + t.k = 500; + t.l = 278; + t.m = 778; + t.n = 556; + t.o = 500; + t.p = 500; + t.q = 500; + t.r = 389; + t.s = 389; + t.t = 278; + t.u = 556; + t.v = 444; + t.w = 667; + t.x = 500; + t.y = 444; + t.z = 389; + t.braceleft = 348; + t.bar = 220; + t.braceright = 348; + t.asciitilde = 570; + t.exclamdown = 389; + t.cent = 500; + t.sterling = 500; + t.fraction = 167; + t.yen = 500; + t.florin = 500; + t.section = 500; + t.currency = 500; + t.quotesingle = 278; + t.quotedblleft = 500; + t.guillemotleft = 500; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 556; + t.fl = 556; + t.endash = 500; + t.dagger = 500; + t.daggerdbl = 500; + t.periodcentered = 250; + t.paragraph = 500; + t.bullet = 350; + t.quotesinglbase = 333; + t.quotedblbase = 500; + t.quotedblright = 500; + t.guillemotright = 500; + t.ellipsis = 1000; + t.perthousand = 1000; + t.questiondown = 500; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 1000; + t.AE = 944; + t.ordfeminine = 266; + t.Lslash = 611; + t.Oslash = 722; + t.OE = 944; + t.ordmasculine = 300; + t.ae = 722; + t.dotlessi = 278; + t.lslash = 278; + t.oslash = 500; + t.oe = 722; + t.germandbls = 500; + t.Idieresis = 389; + t.eacute = 444; + t.abreve = 500; + t.uhungarumlaut = 556; + t.ecaron = 444; + t.Ydieresis = 611; + t.divide = 570; + t.Yacute = 611; + t.Acircumflex = 667; + t.aacute = 500; + t.Ucircumflex = 722; + t.yacute = 444; + t.scommaaccent = 389; + t.ecircumflex = 444; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 500; + t.Uacute = 722; + t.uogonek = 556; + t.Edieresis = 667; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 747; + t.Emacron = 667; + t.ccaron = 444; + t.aring = 500; + t.Ncommaaccent = 722; + t.lacute = 278; + t.agrave = 500; + t.Tcommaaccent = 611; + t.Cacute = 667; + t.atilde = 500; + t.Edotaccent = 667; + t.scaron = 389; + t.scedilla = 389; + t.iacute = 278; + t.lozenge = 494; + t.Rcaron = 667; + t.Gcommaaccent = 722; + t.ucircumflex = 556; + t.acircumflex = 500; + t.Amacron = 667; + t.rcaron = 389; + t.ccedilla = 444; + t.Zdotaccent = 611; + t.Thorn = 611; + t.Omacron = 722; + t.Racute = 667; + t.Sacute = 556; + t.dcaron = 608; + t.Umacron = 722; + t.uring = 556; + t.threesuperior = 300; + t.Ograve = 722; + t.Agrave = 667; + t.Abreve = 667; + t.multiply = 570; + t.uacute = 556; + t.Tcaron = 611; + t.partialdiff = 494; + t.ydieresis = 444; + t.Nacute = 722; + t.icircumflex = 278; + t.Ecircumflex = 667; + t.adieresis = 500; + t.edieresis = 444; + t.cacute = 444; + t.nacute = 556; + t.umacron = 556; + t.Ncaron = 722; + t.Iacute = 389; + t.plusminus = 570; + t.brokenbar = 220; + t.registered = 747; + t.Gbreve = 722; + t.Idotaccent = 389; + t.summation = 600; + t.Egrave = 667; + t.racute = 389; + t.omacron = 500; + t.Zacute = 611; + t.Zcaron = 611; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 667; + t.lcommaaccent = 278; + t.tcaron = 366; + t.eogonek = 444; + t.Uogonek = 722; + t.Aacute = 667; + t.Adieresis = 667; + t.egrave = 444; + t.zacute = 389; + t.iogonek = 278; + t.Oacute = 722; + t.oacute = 500; + t.amacron = 500; + t.sacute = 389; + t.idieresis = 278; + t.Ocircumflex = 722; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 500; + t.twosuperior = 300; + t.Odieresis = 722; + t.mu = 576; + t.igrave = 278; + t.ohungarumlaut = 500; + t.Eogonek = 667; + t.dcroat = 500; + t.threequarters = 750; + t.Scedilla = 556; + t.lcaron = 382; + t.Kcommaaccent = 667; + t.Lacute = 611; + t.trademark = 1000; + t.edotaccent = 444; + t.Igrave = 389; + t.Imacron = 389; + t.Lcaron = 611; + t.onehalf = 750; + t.lessequal = 549; + t.ocircumflex = 500; + t.ntilde = 556; + t.Uhungarumlaut = 722; + t.Eacute = 667; + t.emacron = 444; + t.gbreve = 500; + t.onequarter = 750; + t.Scaron = 556; + t.Scommaaccent = 556; + t.Ohungarumlaut = 722; + t.degree = 400; + t.ograve = 500; + t.Ccaron = 667; + t.ugrave = 556; + t.radical = 549; + t.Dcaron = 722; + t.rcommaaccent = 389; + t.Ntilde = 722; + t.otilde = 500; + t.Rcommaaccent = 667; + t.Lcommaaccent = 611; + t.Atilde = 667; + t.Aogonek = 667; + t.Aring = 667; + t.Otilde = 722; + t.zdotaccent = 389; + t.Ecaron = 667; + t.Iogonek = 389; + t.kcommaaccent = 500; + t.minus = 606; + t.Icircumflex = 389; + t.ncaron = 556; + t.tcommaaccent = 278; + t.logicalnot = 606; + t.odieresis = 500; + t.udieresis = 556; + t.notequal = 549; + t.gcommaaccent = 500; + t.eth = 500; + t.zcaron = 389; + t.ncommaaccent = 556; + t.onesuperior = 300; + t.imacron = 278; + t.Euro = 500; + }); + // eslint-disable-next-line no-shadow + t["Times-Italic"] = getLookupTableFactory(function (t) { + t.space = 250; + t.exclam = 333; + t.quotedbl = 420; + t.numbersign = 500; + t.dollar = 500; + t.percent = 833; + t.ampersand = 778; + t.quoteright = 333; + t.parenleft = 333; + t.parenright = 333; + t.asterisk = 500; + t.plus = 675; + t.comma = 250; + t.hyphen = 333; + t.period = 250; + t.slash = 278; + t.zero = 500; + t.one = 500; + t.two = 500; + t.three = 500; + t.four = 500; + t.five = 500; + t.six = 500; + t.seven = 500; + t.eight = 500; + t.nine = 500; + t.colon = 333; + t.semicolon = 333; + t.less = 675; + t.equal = 675; + t.greater = 675; + t.question = 500; + t.at = 920; + t.A = 611; + t.B = 611; + t.C = 667; + t.D = 722; + t.E = 611; + t.F = 611; + t.G = 722; + t.H = 722; + t.I = 333; + t.J = 444; + t.K = 667; + t.L = 556; + t.M = 833; + t.N = 667; + t.O = 722; + t.P = 611; + t.Q = 722; + t.R = 611; + t.S = 500; + t.T = 556; + t.U = 722; + t.V = 611; + t.W = 833; + t.X = 611; + t.Y = 556; + t.Z = 556; + t.bracketleft = 389; + t.backslash = 278; + t.bracketright = 389; + t.asciicircum = 422; + t.underscore = 500; + t.quoteleft = 333; + t.a = 500; + t.b = 500; + t.c = 444; + t.d = 500; + t.e = 444; + t.f = 278; + t.g = 500; + t.h = 500; + t.i = 278; + t.j = 278; + t.k = 444; + t.l = 278; + t.m = 722; + t.n = 500; + t.o = 500; + t.p = 500; + t.q = 500; + t.r = 389; + t.s = 389; + t.t = 278; + t.u = 500; + t.v = 444; + t.w = 667; + t.x = 444; + t.y = 444; + t.z = 389; + t.braceleft = 400; + t.bar = 275; + t.braceright = 400; + t.asciitilde = 541; + t.exclamdown = 389; + t.cent = 500; + t.sterling = 500; + t.fraction = 167; + t.yen = 500; + t.florin = 500; + t.section = 500; + t.currency = 500; + t.quotesingle = 214; + t.quotedblleft = 556; + t.guillemotleft = 500; + t.guilsinglleft = 333; + t.guilsinglright = 333; + t.fi = 500; + t.fl = 500; + t.endash = 500; + t.dagger = 500; + t.daggerdbl = 500; + t.periodcentered = 250; + t.paragraph = 523; + t.bullet = 350; + t.quotesinglbase = 333; + t.quotedblbase = 556; + t.quotedblright = 556; + t.guillemotright = 500; + t.ellipsis = 889; + t.perthousand = 1000; + t.questiondown = 500; + t.grave = 333; + t.acute = 333; + t.circumflex = 333; + t.tilde = 333; + t.macron = 333; + t.breve = 333; + t.dotaccent = 333; + t.dieresis = 333; + t.ring = 333; + t.cedilla = 333; + t.hungarumlaut = 333; + t.ogonek = 333; + t.caron = 333; + t.emdash = 889; + t.AE = 889; + t.ordfeminine = 276; + t.Lslash = 556; + t.Oslash = 722; + t.OE = 944; + t.ordmasculine = 310; + t.ae = 667; + t.dotlessi = 278; + t.lslash = 278; + t.oslash = 500; + t.oe = 667; + t.germandbls = 500; + t.Idieresis = 333; + t.eacute = 444; + t.abreve = 500; + t.uhungarumlaut = 500; + t.ecaron = 444; + t.Ydieresis = 556; + t.divide = 675; + t.Yacute = 556; + t.Acircumflex = 611; + t.aacute = 500; + t.Ucircumflex = 722; + t.yacute = 444; + t.scommaaccent = 389; + t.ecircumflex = 444; + t.Uring = 722; + t.Udieresis = 722; + t.aogonek = 500; + t.Uacute = 722; + t.uogonek = 500; + t.Edieresis = 611; + t.Dcroat = 722; + t.commaaccent = 250; + t.copyright = 760; + t.Emacron = 611; + t.ccaron = 444; + t.aring = 500; + t.Ncommaaccent = 667; + t.lacute = 278; + t.agrave = 500; + t.Tcommaaccent = 556; + t.Cacute = 667; + t.atilde = 500; + t.Edotaccent = 611; + t.scaron = 389; + t.scedilla = 389; + t.iacute = 278; + t.lozenge = 471; + t.Rcaron = 611; + t.Gcommaaccent = 722; + t.ucircumflex = 500; + t.acircumflex = 500; + t.Amacron = 611; + t.rcaron = 389; + t.ccedilla = 444; + t.Zdotaccent = 556; + t.Thorn = 611; + t.Omacron = 722; + t.Racute = 611; + t.Sacute = 500; + t.dcaron = 544; + t.Umacron = 722; + t.uring = 500; + t.threesuperior = 300; + t.Ograve = 722; + t.Agrave = 611; + t.Abreve = 611; + t.multiply = 675; + t.uacute = 500; + t.Tcaron = 556; + t.partialdiff = 476; + t.ydieresis = 444; + t.Nacute = 667; + t.icircumflex = 278; + t.Ecircumflex = 611; + t.adieresis = 500; + t.edieresis = 444; + t.cacute = 444; + t.nacute = 500; + t.umacron = 500; + t.Ncaron = 667; + t.Iacute = 333; + t.plusminus = 675; + t.brokenbar = 275; + t.registered = 760; + t.Gbreve = 722; + t.Idotaccent = 333; + t.summation = 600; + t.Egrave = 611; + t.racute = 389; + t.omacron = 500; + t.Zacute = 556; + t.Zcaron = 556; + t.greaterequal = 549; + t.Eth = 722; + t.Ccedilla = 667; + t.lcommaaccent = 278; + t.tcaron = 300; + t.eogonek = 444; + t.Uogonek = 722; + t.Aacute = 611; + t.Adieresis = 611; + t.egrave = 444; + t.zacute = 389; + t.iogonek = 278; + t.Oacute = 722; + t.oacute = 500; + t.amacron = 500; + t.sacute = 389; + t.idieresis = 278; + t.Ocircumflex = 722; + t.Ugrave = 722; + t.Delta = 612; + t.thorn = 500; + t.twosuperior = 300; + t.Odieresis = 722; + t.mu = 500; + t.igrave = 278; + t.ohungarumlaut = 500; + t.Eogonek = 611; + t.dcroat = 500; + t.threequarters = 750; + t.Scedilla = 500; + t.lcaron = 300; + t.Kcommaaccent = 667; + t.Lacute = 556; + t.trademark = 980; + t.edotaccent = 444; + t.Igrave = 333; + t.Imacron = 333; + t.Lcaron = 611; + t.onehalf = 750; + t.lessequal = 549; + t.ocircumflex = 500; + t.ntilde = 500; + t.Uhungarumlaut = 722; + t.Eacute = 611; + t.emacron = 444; + t.gbreve = 500; + t.onequarter = 750; + t.Scaron = 500; + t.Scommaaccent = 500; + t.Ohungarumlaut = 722; + t.degree = 400; + t.ograve = 500; + t.Ccaron = 667; + t.ugrave = 500; + t.radical = 453; + t.Dcaron = 722; + t.rcommaaccent = 389; + t.Ntilde = 667; + t.otilde = 500; + t.Rcommaaccent = 611; + t.Lcommaaccent = 556; + t.Atilde = 611; + t.Aogonek = 611; + t.Aring = 611; + t.Otilde = 722; + t.zdotaccent = 389; + t.Ecaron = 611; + t.Iogonek = 333; + t.kcommaaccent = 444; + t.minus = 675; + t.Icircumflex = 333; + t.ncaron = 500; + t.tcommaaccent = 278; + t.logicalnot = 675; + t.odieresis = 500; + t.udieresis = 500; + t.notequal = 549; + t.gcommaaccent = 500; + t.eth = 500; + t.zcaron = 389; + t.ncommaaccent = 500; + t.onesuperior = 300; + t.imacron = 278; + t.Euro = 500; + }); + // eslint-disable-next-line no-shadow + t.ZapfDingbats = getLookupTableFactory(function (t) { + t.space = 278; + t.a1 = 974; + t.a2 = 961; + t.a202 = 974; + t.a3 = 980; + t.a4 = 719; + t.a5 = 789; + t.a119 = 790; + t.a118 = 791; + t.a117 = 690; + t.a11 = 960; + t.a12 = 939; + t.a13 = 549; + t.a14 = 855; + t.a15 = 911; + t.a16 = 933; + t.a105 = 911; + t.a17 = 945; + t.a18 = 974; + t.a19 = 755; + t.a20 = 846; + t.a21 = 762; + t.a22 = 761; + t.a23 = 571; + t.a24 = 677; + t.a25 = 763; + t.a26 = 760; + t.a27 = 759; + t.a28 = 754; + t.a6 = 494; + t.a7 = 552; + t.a8 = 537; + t.a9 = 577; + t.a10 = 692; + t.a29 = 786; + t.a30 = 788; + t.a31 = 788; + t.a32 = 790; + t.a33 = 793; + t.a34 = 794; + t.a35 = 816; + t.a36 = 823; + t.a37 = 789; + t.a38 = 841; + t.a39 = 823; + t.a40 = 833; + t.a41 = 816; + t.a42 = 831; + t.a43 = 923; + t.a44 = 744; + t.a45 = 723; + t.a46 = 749; + t.a47 = 790; + t.a48 = 792; + t.a49 = 695; + t.a50 = 776; + t.a51 = 768; + t.a52 = 792; + t.a53 = 759; + t.a54 = 707; + t.a55 = 708; + t.a56 = 682; + t.a57 = 701; + t.a58 = 826; + t.a59 = 815; + t.a60 = 789; + t.a61 = 789; + t.a62 = 707; + t.a63 = 687; + t.a64 = 696; + t.a65 = 689; + t.a66 = 786; + t.a67 = 787; + t.a68 = 713; + t.a69 = 791; + t.a70 = 785; + t.a71 = 791; + t.a72 = 873; + t.a73 = 761; + t.a74 = 762; + t.a203 = 762; + t.a75 = 759; + t.a204 = 759; + t.a76 = 892; + t.a77 = 892; + t.a78 = 788; + t.a79 = 784; + t.a81 = 438; + t.a82 = 138; + t.a83 = 277; + t.a84 = 415; + t.a97 = 392; + t.a98 = 392; + t.a99 = 668; + t.a100 = 668; + t.a89 = 390; + t.a90 = 390; + t.a93 = 317; + t.a94 = 317; + t.a91 = 276; + t.a92 = 276; + t.a205 = 509; + t.a85 = 509; + t.a206 = 410; + t.a86 = 410; + t.a87 = 234; + t.a88 = 234; + t.a95 = 334; + t.a96 = 334; + t.a101 = 732; + t.a102 = 544; + t.a103 = 544; + t.a104 = 910; + t.a106 = 667; + t.a107 = 760; + t.a108 = 760; + t.a112 = 776; + t.a111 = 595; + t.a110 = 694; + t.a109 = 626; + t.a120 = 788; + t.a121 = 788; + t.a122 = 788; + t.a123 = 788; + t.a124 = 788; + t.a125 = 788; + t.a126 = 788; + t.a127 = 788; + t.a128 = 788; + t.a129 = 788; + t.a130 = 788; + t.a131 = 788; + t.a132 = 788; + t.a133 = 788; + t.a134 = 788; + t.a135 = 788; + t.a136 = 788; + t.a137 = 788; + t.a138 = 788; + t.a139 = 788; + t.a140 = 788; + t.a141 = 788; + t.a142 = 788; + t.a143 = 788; + t.a144 = 788; + t.a145 = 788; + t.a146 = 788; + t.a147 = 788; + t.a148 = 788; + t.a149 = 788; + t.a150 = 788; + t.a151 = 788; + t.a152 = 788; + t.a153 = 788; + t.a154 = 788; + t.a155 = 788; + t.a156 = 788; + t.a157 = 788; + t.a158 = 788; + t.a159 = 788; + t.a160 = 894; + t.a161 = 838; + t.a163 = 1016; + t.a164 = 458; + t.a196 = 748; + t.a165 = 924; + t.a192 = 748; + t.a166 = 918; + t.a167 = 927; + t.a168 = 928; + t.a169 = 928; + t.a170 = 834; + t.a171 = 873; + t.a172 = 828; + t.a173 = 924; + t.a162 = 924; + t.a174 = 917; + t.a175 = 930; + t.a176 = 931; + t.a177 = 463; + t.a178 = 883; + t.a179 = 836; + t.a193 = 836; + t.a180 = 867; + t.a199 = 867; + t.a181 = 696; + t.a200 = 696; + t.a182 = 874; + t.a201 = 874; + t.a183 = 760; + t.a184 = 946; + t.a197 = 771; + t.a185 = 865; + t.a194 = 771; + t.a198 = 888; + t.a186 = 967; + t.a195 = 888; + t.a187 = 831; + t.a188 = 873; + t.a189 = 927; + t.a190 = 970; + t.a191 = 918; + }); +}); + +const getFontBasicMetrics = getLookupTableFactory(function (t) { + t.Courier = { + ascent: 629, + descent: -157, + capHeight: 562, + xHeight: -426, + }; + t["Courier-Bold"] = { + ascent: 629, + descent: -157, + capHeight: 562, + xHeight: 439, + }; + t["Courier-Oblique"] = { + ascent: 629, + descent: -157, + capHeight: 562, + xHeight: 426, + }; + t["Courier-BoldOblique"] = { + ascent: 629, + descent: -157, + capHeight: 562, + xHeight: 426, + }; + t.Helvetica = { + ascent: 718, + descent: -207, + capHeight: 718, + xHeight: 523, + }; + t["Helvetica-Bold"] = { + ascent: 718, + descent: -207, + capHeight: 718, + xHeight: 532, + }; + t["Helvetica-Oblique"] = { + ascent: 718, + descent: -207, + capHeight: 718, + xHeight: 523, + }; + t["Helvetica-BoldOblique"] = { + ascent: 718, + descent: -207, + capHeight: 718, + xHeight: 532, + }; + t["Times-Roman"] = { + ascent: 683, + descent: -217, + capHeight: 662, + xHeight: 450, + }; + t["Times-Bold"] = { + ascent: 683, + descent: -217, + capHeight: 676, + xHeight: 461, + }; + t["Times-Italic"] = { + ascent: 683, + descent: -217, + capHeight: 653, + xHeight: 441, + }; + t["Times-BoldItalic"] = { + ascent: 683, + descent: -217, + capHeight: 669, + xHeight: 462, + }; + t.Symbol = { + ascent: Math.NaN, + descent: Math.NaN, + capHeight: Math.NaN, + xHeight: Math.NaN, + }; + t.ZapfDingbats = { + ascent: Math.NaN, + descent: Math.NaN, + capHeight: Math.NaN, + xHeight: Math.NaN, + }; +}); + +export { getFontBasicMetrics, getMetrics }; diff --git a/frontend/public/pdf.js/src/core/myriadpro_factors.js b/frontend/public/pdf.js/src/core/myriadpro_factors.js new file mode 100644 index 0000000..42da43c --- /dev/null +++ b/frontend/public/pdf.js/src/core/myriadpro_factors.js @@ -0,0 +1,291 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +// Factors to rescale LiberationSans-Bold.ttf to have the same +// metrics as MyriadPro-Bold.otf. +const MyriadProBoldFactors = [ + 1.36898, 1, 1, 0.72706, 0.80479, 0.83734, 0.98894, 0.99793, 0.9897, 0.93884, + 0.86209, 0.94292, 0.94292, 1.16661, 1.02058, 0.93582, 0.96694, 0.93582, + 1.19137, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, + 0.99793, 0.99793, 0.99793, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, + 0.72851, 0.78966, 0.90838, 0.83637, 0.82391, 0.96376, 0.80061, 0.86275, + 0.8768, 0.95407, 1.0258, 0.73901, 0.85022, 0.83655, 1.0156, 0.95546, 0.92179, + 0.87107, 0.92179, 0.82114, 0.8096, 0.89713, 0.94438, 0.95353, 0.94083, + 0.91905, 0.90406, 0.9446, 0.94292, 1.18777, 0.94292, 1.02058, 0.89903, + 0.90088, 0.94938, 0.97898, 0.81093, 0.97571, 0.94938, 1.024, 0.9577, 0.95933, + 0.98621, 1.0474, 0.97455, 0.98981, 0.9672, 0.95933, 0.9446, 0.97898, 0.97407, + 0.97646, 0.78036, 1.10208, 0.95442, 0.95298, 0.97579, 0.9332, 0.94039, 0.938, + 0.80687, 1.01149, 0.80687, 1.02058, 0.80479, 0.99793, 0.99793, 0.99793, + 0.99793, 1.01149, 1.00872, 0.90088, 0.91882, 1.0213, 0.8361, 1.02058, 0.62295, + 0.54324, 0.89022, 1.08595, 1, 1, 0.90088, 1, 0.97455, 0.93582, 0.90088, 1, + 1.05686, 0.8361, 0.99642, 0.99642, 0.99642, 0.72851, 0.90838, 0.90838, + 0.90838, 0.90838, 0.90838, 0.90838, 0.868, 0.82391, 0.80061, 0.80061, 0.80061, + 0.80061, 1.0258, 1.0258, 1.0258, 1.0258, 0.97484, 0.95546, 0.92179, 0.92179, + 0.92179, 0.92179, 0.92179, 1.02058, 0.92179, 0.94438, 0.94438, 0.94438, + 0.94438, 0.90406, 0.86958, 0.98225, 0.94938, 0.94938, 0.94938, 0.94938, + 0.94938, 0.94938, 0.9031, 0.81093, 0.94938, 0.94938, 0.94938, 0.94938, + 0.98621, 0.98621, 0.98621, 0.98621, 0.93969, 0.95933, 0.9446, 0.9446, 0.9446, + 0.9446, 0.9446, 1.08595, 0.9446, 0.95442, 0.95442, 0.95442, 0.95442, 0.94039, + 0.97898, 0.94039, 0.90838, 0.94938, 0.90838, 0.94938, 0.90838, 0.94938, + 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, + 0.96376, 0.84313, 0.97484, 0.97571, 0.80061, 0.94938, 0.80061, 0.94938, + 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.8768, 0.9577, 0.8768, + 0.9577, 0.8768, 0.9577, 1, 1, 0.95407, 0.95933, 0.97069, 0.95933, 1.0258, + 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, + 0.887, 1.01591, 0.73901, 1.0474, 1, 1, 0.97455, 0.83655, 0.98981, 1, 1, + 0.83655, 0.73977, 0.83655, 0.73903, 0.84638, 1.033, 0.95546, 0.95933, 1, 1, + 0.95546, 0.95933, 0.8271, 0.95417, 0.95933, 0.92179, 0.9446, 0.92179, 0.9446, + 0.92179, 0.9446, 0.936, 0.91964, 0.82114, 0.97646, 1, 1, 0.82114, 0.97646, + 0.8096, 0.78036, 0.8096, 0.78036, 1, 1, 0.8096, 0.78036, 1, 1, 0.89713, + 0.77452, 0.89713, 1.10208, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, + 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94083, + 0.97579, 0.90406, 0.94039, 0.90406, 0.9446, 0.938, 0.9446, 0.938, 0.9446, + 0.938, 1, 0.99793, 0.90838, 0.94938, 0.868, 0.9031, 0.92179, 0.9446, 1, 1, + 0.89713, 1.10208, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, + 0.90088, 0.90088, 0.90088, 0.90989, 0.9358, 0.91945, 0.83181, 0.75261, + 0.87992, 0.82976, 0.96034, 0.83689, 0.97268, 1.0078, 0.90838, 0.83637, 0.8019, + 0.90157, 0.80061, 0.9446, 0.95407, 0.92436, 1.0258, 0.85022, 0.97153, 1.0156, + 0.95546, 0.89192, 0.92179, 0.92361, 0.87107, 0.96318, 0.89713, 0.93704, + 0.95638, 0.91905, 0.91709, 0.92796, 1.0258, 0.93704, 0.94836, 1.0373, 0.95933, + 1.0078, 0.95871, 0.94836, 0.96174, 0.92601, 0.9498, 0.98607, 0.95776, 0.95933, + 1.05453, 1.0078, 0.98275, 0.9314, 0.95617, 0.91701, 1.05993, 0.9446, 0.78367, + 0.9553, 1, 0.86832, 1.0128, 0.95871, 0.99394, 0.87548, 0.96361, 0.86774, + 1.0078, 0.95871, 0.9446, 0.95871, 0.86774, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.94083, 0.97579, 0.94083, 0.97579, 0.94083, + 0.97579, 0.90406, 0.94039, 0.96694, 1, 0.89903, 1, 1, 1, 0.93582, 0.93582, + 0.93582, 1, 0.908, 0.908, 0.918, 0.94219, 0.94219, 0.96544, 1, 1.285, 1, 1, + 0.81079, 0.81079, 1, 1, 0.74854, 1, 1, 1, 1, 0.99793, 1, 1, 1, 0.65, 1, + 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.17173, 1, 0.80535, 0.76169, + 1.02058, 1.0732, 1.05486, 1, 1, 1.30692, 1.08595, 1.08595, 1, 1.08595, + 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.16161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const MyriadProBoldMetrics = { lineHeight: 1.2, lineGap: 0.2 }; + +// Factors to rescale LiberationSans-BoldItalic.ttf to have the same +// metrics as MyriadPro-BoldIt.otf. +const MyriadProBoldItalicFactors = [ + 1.36898, 1, 1, 0.66227, 0.80779, 0.81625, 0.97276, 0.97276, 0.97733, 0.92222, + 0.83266, 0.94292, 0.94292, 1.16148, 1.02058, 0.93582, 0.96694, 0.93582, + 1.17337, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, + 0.97276, 0.97276, 0.97276, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, + 0.71541, 0.76813, 0.85576, 0.80591, 0.80729, 0.94299, 0.77512, 0.83655, + 0.86523, 0.92222, 0.98621, 0.71743, 0.81698, 0.79726, 0.98558, 0.92222, + 0.90637, 0.83809, 0.90637, 0.80729, 0.76463, 0.86275, 0.90699, 0.91605, + 0.9154, 0.85308, 0.85458, 0.90531, 0.94292, 1.21296, 0.94292, 1.02058, + 0.89903, 1.18616, 0.99613, 0.91677, 0.78216, 0.91677, 0.90083, 0.98796, + 0.9135, 0.92168, 0.95381, 0.98981, 0.95298, 0.95381, 0.93459, 0.92168, + 0.91513, 0.92004, 0.91677, 0.95077, 0.748, 1.04502, 0.91677, 0.92061, 0.94236, + 0.89544, 0.89364, 0.9, 0.80687, 0.8578, 0.80687, 1.02058, 0.80779, 0.97276, + 0.97276, 0.97276, 0.97276, 0.8578, 0.99973, 1.18616, 0.91339, 1.08074, + 0.82891, 1.02058, 0.55509, 0.71526, 0.89022, 1.08595, 1, 1, 1.18616, 1, + 0.96736, 0.93582, 1.18616, 1, 1.04864, 0.82711, 0.99043, 0.99043, 0.99043, + 0.71541, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.845, 0.80729, + 0.77512, 0.77512, 0.77512, 0.77512, 0.98621, 0.98621, 0.98621, 0.98621, + 0.95961, 0.92222, 0.90637, 0.90637, 0.90637, 0.90637, 0.90637, 1.02058, + 0.90251, 0.90699, 0.90699, 0.90699, 0.90699, 0.85458, 0.83659, 0.94951, + 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.85811, 0.78216, + 0.90083, 0.90083, 0.90083, 0.90083, 0.95381, 0.95381, 0.95381, 0.95381, + 0.9135, 0.92168, 0.91513, 0.91513, 0.91513, 0.91513, 0.91513, 1.08595, + 0.91677, 0.91677, 0.91677, 0.91677, 0.91677, 0.89364, 0.92332, 0.89364, + 0.85576, 0.99613, 0.85576, 0.99613, 0.85576, 0.99613, 0.80729, 0.78216, + 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.94299, 0.76783, + 0.95961, 0.91677, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, + 0.77512, 0.90083, 0.77512, 0.90083, 0.86523, 0.9135, 0.86523, 0.9135, 0.86523, + 0.9135, 1, 1, 0.92222, 0.92168, 0.92222, 0.92168, 0.98621, 0.95381, 0.98621, + 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.86036, + 0.97096, 0.71743, 0.98981, 1, 1, 0.95298, 0.79726, 0.95381, 1, 1, 0.79726, + 0.6894, 0.79726, 0.74321, 0.81691, 1.0006, 0.92222, 0.92168, 1, 1, 0.92222, + 0.92168, 0.79464, 0.92098, 0.92168, 0.90637, 0.91513, 0.90637, 0.91513, + 0.90637, 0.91513, 0.909, 0.87514, 0.80729, 0.95077, 1, 1, 0.80729, 0.95077, + 0.76463, 0.748, 0.76463, 0.748, 1, 1, 0.76463, 0.748, 1, 1, 0.86275, 0.72651, + 0.86275, 1.04502, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, + 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.9154, 0.94236, + 0.85458, 0.89364, 0.85458, 0.90531, 0.9, 0.90531, 0.9, 0.90531, 0.9, 1, + 0.97276, 0.85576, 0.99613, 0.845, 0.85811, 0.90251, 0.91677, 1, 1, 0.86275, + 1.04502, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, + 1.18616, 1.18616, 1.00899, 1.30628, 0.85576, 0.80178, 0.66862, 0.7927, + 0.69323, 0.88127, 0.72459, 0.89711, 0.95381, 0.85576, 0.80591, 0.7805, + 0.94729, 0.77512, 0.90531, 0.92222, 0.90637, 0.98621, 0.81698, 0.92655, + 0.98558, 0.92222, 0.85359, 0.90637, 0.90976, 0.83809, 0.94523, 0.86275, + 0.83509, 0.93157, 0.85308, 0.83392, 0.92346, 0.98621, 0.83509, 0.92886, + 0.91324, 0.92168, 0.95381, 0.90646, 0.92886, 0.90557, 0.86847, 0.90276, + 0.91324, 0.86842, 0.92168, 0.99531, 0.95381, 0.9224, 0.85408, 0.92699, + 0.86847, 1.0051, 0.91513, 0.80487, 0.93481, 1, 0.88159, 1.05214, 0.90646, + 0.97355, 0.81539, 0.89398, 0.85923, 0.95381, 0.90646, 0.91513, 0.90646, + 0.85923, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0.9154, 0.94236, 0.9154, 0.94236, 0.9154, 0.94236, 0.85458, 0.89364, 0.96694, + 1, 0.89903, 1, 1, 1, 0.91782, 0.91782, 0.91782, 1, 0.896, 0.896, 0.896, + 0.9332, 0.9332, 0.95973, 1, 1.26, 1, 1, 0.80479, 0.80178, 1, 1, 0.85633, 1, 1, + 1, 1, 0.97276, 1, 1, 1, 0.698, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1.14542, 1, 0.79199, 0.78694, 1.02058, 1.03493, 1.05486, 1, 1, 1.23026, + 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.20006, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const MyriadProBoldItalicMetrics = { lineHeight: 1.2, lineGap: 0.2 }; + +// Factors to rescale LiberationSans-Italic.ttf to have the same +// metrics as MyriadPro-It.otf. +const MyriadProItalicFactors = [ + 1.36898, 1, 1, 0.65507, 0.84943, 0.85639, 0.88465, 0.88465, 0.86936, 0.88307, + 0.86948, 0.85283, 0.85283, 1.06383, 1.02058, 0.75945, 0.9219, 0.75945, + 1.17337, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, + 0.88465, 0.88465, 0.88465, 0.75945, 0.75945, 1.02058, 1.02058, 1.02058, + 0.69046, 0.70926, 0.85158, 0.77812, 0.76852, 0.89591, 0.70466, 0.76125, + 0.80094, 0.86822, 0.83864, 0.728, 0.77212, 0.79475, 0.93637, 0.87514, 0.8588, + 0.76013, 0.8588, 0.72421, 0.69866, 0.77598, 0.85991, 0.80811, 0.87832, + 0.78112, 0.77512, 0.8562, 1.0222, 1.18417, 1.0222, 1.27014, 0.89903, 1.15012, + 0.93859, 0.94399, 0.846, 0.94399, 0.81453, 1.0186, 0.94219, 0.96017, 1.03075, + 1.02175, 0.912, 1.03075, 0.96998, 0.96017, 0.93859, 0.94399, 0.94399, 0.95493, + 0.746, 1.12658, 0.94578, 0.91, 0.979, 0.882, 0.882, 0.83, 0.85034, 0.83537, + 0.85034, 1.02058, 0.70869, 0.88465, 0.88465, 0.88465, 0.88465, 0.83537, + 0.90083, 1.15012, 0.9161, 0.94565, 0.73541, 1.02058, 0.53609, 0.69353, + 0.79519, 1.08595, 1, 1, 1.15012, 1, 0.91974, 0.75945, 1.15012, 1, 0.9446, + 0.73361, 0.9005, 0.9005, 0.9005, 0.62864, 0.85158, 0.85158, 0.85158, 0.85158, + 0.85158, 0.85158, 0.773, 0.76852, 0.70466, 0.70466, 0.70466, 0.70466, 0.83864, + 0.83864, 0.83864, 0.83864, 0.90561, 0.87514, 0.8588, 0.8588, 0.8588, 0.8588, + 0.8588, 1.02058, 0.85751, 0.85991, 0.85991, 0.85991, 0.85991, 0.77512, + 0.76013, 0.88075, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, + 0.8075, 0.846, 0.81453, 0.81453, 0.81453, 0.81453, 0.82424, 0.82424, 0.82424, + 0.82424, 0.9278, 0.96017, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, + 1.08595, 0.8562, 0.94578, 0.94578, 0.94578, 0.94578, 0.882, 0.94578, 0.882, + 0.85158, 0.93859, 0.85158, 0.93859, 0.85158, 0.93859, 0.76852, 0.846, 0.76852, + 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.89591, 0.8544, 0.90561, 0.94399, + 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, + 0.70466, 0.81453, 0.80094, 0.94219, 0.80094, 0.94219, 0.80094, 0.94219, 1, 1, + 0.86822, 0.96017, 0.86822, 0.96017, 0.83864, 0.82424, 0.83864, 0.82424, + 0.83864, 0.82424, 0.83864, 1.03075, 0.83864, 0.82424, 0.81402, 1.02738, 0.728, + 1.02175, 1, 1, 0.912, 0.79475, 1.03075, 1, 1, 0.79475, 0.83911, 0.79475, + 0.66266, 0.80553, 1.06676, 0.87514, 0.96017, 1, 1, 0.87514, 0.96017, 0.86865, + 0.87396, 0.96017, 0.8588, 0.93859, 0.8588, 0.93859, 0.8588, 0.93859, 0.867, + 0.84759, 0.72421, 0.95493, 1, 1, 0.72421, 0.95493, 0.69866, 0.746, 0.69866, + 0.746, 1, 1, 0.69866, 0.746, 1, 1, 0.77598, 0.88417, 0.77598, 1.12658, + 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, + 0.85991, 0.94578, 0.85991, 0.94578, 0.87832, 0.979, 0.77512, 0.882, 0.77512, + 0.8562, 0.83, 0.8562, 0.83, 0.8562, 0.83, 1, 0.88465, 0.85158, 0.93859, 0.773, + 0.8075, 0.85751, 0.8562, 1, 1, 0.77598, 1.12658, 1.15012, 1.15012, 1.15012, + 1.15012, 1.15012, 1.15313, 1.15012, 1.15012, 1.15012, 1.08106, 1.03901, + 0.85158, 0.77025, 0.62264, 0.7646, 0.65351, 0.86026, 0.69461, 0.89947, + 1.03075, 0.85158, 0.77812, 0.76449, 0.88836, 0.70466, 0.8562, 0.86822, 0.8588, + 0.83864, 0.77212, 0.85308, 0.93637, 0.87514, 0.82352, 0.8588, 0.85701, + 0.76013, 0.89058, 0.77598, 0.8156, 0.82565, 0.78112, 0.77899, 0.89386, + 0.83864, 0.8156, 0.9486, 0.92388, 0.96186, 1.03075, 0.91123, 0.9486, 0.93298, + 0.878, 0.93942, 0.92388, 0.84596, 0.96186, 0.95119, 1.03075, 0.922, 0.88787, + 0.95829, 0.88, 0.93559, 0.93859, 0.78815, 0.93758, 1, 0.89217, 1.03737, + 0.91123, 0.93969, 0.77487, 0.85769, 0.86799, 1.03075, 0.91123, 0.93859, + 0.91123, 0.86799, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0.87832, 0.979, 0.87832, 0.979, 0.87832, 0.979, 0.77512, 0.882, 0.9219, + 1, 0.89903, 1, 1, 1, 0.87321, 0.87321, 0.87321, 1, 1.027, 1.027, 1.027, + 0.86847, 0.86847, 0.79121, 1, 1.124, 1, 1, 0.73572, 0.73572, 1, 1, 0.85034, 1, + 1, 1, 1, 0.88465, 1, 1, 1, 0.669, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1.04828, 1, 0.74948, 0.75187, 1.02058, 0.98391, 1.02119, 1, 1, 1.06233, + 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05233, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const MyriadProItalicMetrics = { lineHeight: 1.2, lineGap: 0.2 }; + +// Factors to rescale LiberationSans-Regular.ttf to have the same +// metrics as MyriadPro-Regular.otf. +const MyriadProRegularFactors = [ + 1.36898, 1, 1, 0.76305, 0.82784, 0.94935, 0.89364, 0.92241, 0.89073, 0.90706, + 0.98472, 0.85283, 0.85283, 1.0664, 1.02058, 0.74505, 0.9219, 0.74505, 1.23456, + 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, + 0.92241, 0.92241, 0.74505, 0.74505, 1.02058, 1.02058, 1.02058, 0.73002, + 0.72601, 0.91755, 0.8126, 0.80314, 0.92222, 0.73764, 0.79726, 0.83051, + 0.90284, 0.86023, 0.74, 0.8126, 0.84869, 0.96518, 0.91115, 0.8858, 0.79761, + 0.8858, 0.74498, 0.73914, 0.81363, 0.89591, 0.83659, 0.89633, 0.85608, 0.8111, + 0.90531, 1.0222, 1.22736, 1.0222, 1.27014, 0.89903, 0.90088, 0.86667, 1.0231, + 0.896, 1.01411, 0.90083, 1.05099, 1.00512, 0.99793, 1.05326, 1.09377, 0.938, + 1.06226, 1.00119, 0.99793, 0.98714, 1.0231, 1.01231, 0.98196, 0.792, 1.19137, + 0.99074, 0.962, 1.01915, 0.926, 0.942, 0.856, 0.85034, 0.92006, 0.85034, + 1.02058, 0.69067, 0.92241, 0.92241, 0.92241, 0.92241, 0.92006, 0.9332, + 0.90088, 0.91882, 0.93484, 0.75339, 1.02058, 0.56866, 0.54324, 0.79519, + 1.08595, 1, 1, 0.90088, 1, 0.95325, 0.74505, 0.90088, 1, 0.97198, 0.75339, + 0.91009, 0.91009, 0.91009, 0.66466, 0.91755, 0.91755, 0.91755, 0.91755, + 0.91755, 0.91755, 0.788, 0.80314, 0.73764, 0.73764, 0.73764, 0.73764, 0.86023, + 0.86023, 0.86023, 0.86023, 0.92915, 0.91115, 0.8858, 0.8858, 0.8858, 0.8858, + 0.8858, 1.02058, 0.8858, 0.89591, 0.89591, 0.89591, 0.89591, 0.8111, 0.79611, + 0.89713, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86936, 0.896, + 0.90083, 0.90083, 0.90083, 0.90083, 0.84224, 0.84224, 0.84224, 0.84224, + 0.97276, 0.99793, 0.98714, 0.98714, 0.98714, 0.98714, 0.98714, 1.08595, + 0.89876, 0.99074, 0.99074, 0.99074, 0.99074, 0.942, 1.0231, 0.942, 0.91755, + 0.86667, 0.91755, 0.86667, 0.91755, 0.86667, 0.80314, 0.896, 0.80314, 0.896, + 0.80314, 0.896, 0.80314, 0.896, 0.92222, 0.93372, 0.92915, 1.01411, 0.73764, + 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, + 0.90083, 0.83051, 1.00512, 0.83051, 1.00512, 0.83051, 1.00512, 1, 1, 0.90284, + 0.99793, 0.90976, 0.99793, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, + 0.84224, 0.86023, 1.05326, 0.86023, 0.84224, 0.82873, 1.07469, 0.74, 1.09377, + 1, 1, 0.938, 0.84869, 1.06226, 1, 1, 0.84869, 0.83704, 0.84869, 0.81441, + 0.85588, 1.08927, 0.91115, 0.99793, 1, 1, 0.91115, 0.99793, 0.91887, 0.90991, + 0.99793, 0.8858, 0.98714, 0.8858, 0.98714, 0.8858, 0.98714, 0.894, 0.91434, + 0.74498, 0.98196, 1, 1, 0.74498, 0.98196, 0.73914, 0.792, 0.73914, 0.792, 1, + 1, 0.73914, 0.792, 1, 1, 0.81363, 0.904, 0.81363, 1.19137, 0.89591, 0.99074, + 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, + 0.89591, 0.99074, 0.89633, 1.01915, 0.8111, 0.942, 0.8111, 0.90531, 0.856, + 0.90531, 0.856, 0.90531, 0.856, 1, 0.92241, 0.91755, 0.86667, 0.788, 0.86936, + 0.8858, 0.89876, 1, 1, 0.81363, 1.19137, 0.90088, 0.90088, 0.90088, 0.90088, + 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90388, 1.03901, 0.92138, + 0.78105, 0.7154, 0.86169, 0.80513, 0.94007, 0.82528, 0.98612, 1.06226, + 0.91755, 0.8126, 0.81884, 0.92819, 0.73764, 0.90531, 0.90284, 0.8858, 0.86023, + 0.8126, 0.91172, 0.96518, 0.91115, 0.83089, 0.8858, 0.87791, 0.79761, 0.89297, + 0.81363, 0.88157, 0.89992, 0.85608, 0.81992, 0.94307, 0.86023, 0.88157, + 0.95308, 0.98699, 0.99793, 1.06226, 0.95817, 0.95308, 0.97358, 0.928, 0.98088, + 0.98699, 0.92761, 0.99793, 0.96017, 1.06226, 0.986, 0.944, 0.95978, 0.938, + 0.96705, 0.98714, 0.80442, 0.98972, 1, 0.89762, 1.04552, 0.95817, 0.99007, + 0.87064, 0.91879, 0.88888, 1.06226, 0.95817, 0.98714, 0.95817, 0.88888, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89633, 1.01915, + 0.89633, 1.01915, 0.89633, 1.01915, 0.8111, 0.942, 0.9219, 1, 0.89903, 1, 1, + 1, 0.93173, 0.93173, 0.93173, 1, 1.06304, 1.06304, 1.06904, 0.89903, 0.89903, + 0.80549, 1, 1.156, 1, 1, 0.76575, 0.76575, 1, 1, 0.72458, 1, 1, 1, 1, 0.92241, + 1, 1, 1, 0.619, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.07257, 1, + 0.74705, 0.71119, 1.02058, 1.024, 1.02119, 1, 1, 1.1536, 1.08595, 1.08595, 1, + 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05638, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, +]; +const MyriadProRegularMetrics = { lineHeight: 1.2, lineGap: 0.2 }; + +export { + MyriadProBoldFactors, + MyriadProBoldItalicFactors, + MyriadProBoldItalicMetrics, + MyriadProBoldMetrics, + MyriadProItalicFactors, + MyriadProItalicMetrics, + MyriadProRegularFactors, + MyriadProRegularMetrics, +}; diff --git a/frontend/public/pdf.js/src/core/name_number_tree.js b/frontend/public/pdf.js/src/core/name_number_tree.js new file mode 100644 index 0000000..461711d --- /dev/null +++ b/frontend/public/pdf.js/src/core/name_number_tree.js @@ -0,0 +1,162 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { Dict, RefSet } from "./primitives.js"; +import { FormatError, unreachable, warn } from "../shared/util.js"; + +/** + * A NameTree/NumberTree is like a Dict but has some advantageous properties, + * see the specification (7.9.6 and 7.9.7) for additional details. + * TODO: implement all the Dict functions and make this more efficient. + */ +class NameOrNumberTree { + constructor(root, xref, type) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === NameOrNumberTree + ) { + unreachable("Cannot initialize NameOrNumberTree."); + } + this.root = root; + this.xref = xref; + this._type = type; + } + + getAll() { + const map = new Map(); + if (!this.root) { + return map; + } + const xref = this.xref; + // Reading Name/Number tree. + const processed = new RefSet(); + processed.put(this.root); + const queue = [this.root]; + while (queue.length > 0) { + const obj = xref.fetchIfRef(queue.shift()); + if (!(obj instanceof Dict)) { + continue; + } + if (obj.has("Kids")) { + const kids = obj.get("Kids"); + if (!Array.isArray(kids)) { + continue; + } + for (const kid of kids) { + if (processed.has(kid)) { + throw new FormatError(`Duplicate entry in "${this._type}" tree.`); + } + queue.push(kid); + processed.put(kid); + } + continue; + } + const entries = obj.get(this._type); + if (!Array.isArray(entries)) { + continue; + } + for (let i = 0, ii = entries.length; i < ii; i += 2) { + map.set(xref.fetchIfRef(entries[i]), xref.fetchIfRef(entries[i + 1])); + } + } + return map; + } + + getRaw(key) { + if (!this.root) { + return null; + } + const xref = this.xref; + let kidsOrEntries = xref.fetchIfRef(this.root); + let loopCount = 0; + const MAX_LEVELS = 10; + + // Perform a binary search to quickly find the entry that + // contains the key we are looking for. + while (kidsOrEntries.has("Kids")) { + if (++loopCount > MAX_LEVELS) { + warn(`Search depth limit reached for "${this._type}" tree.`); + return null; + } + + const kids = kidsOrEntries.get("Kids"); + if (!Array.isArray(kids)) { + return null; + } + + let l = 0, + r = kids.length - 1; + while (l <= r) { + const m = (l + r) >> 1; + const kid = xref.fetchIfRef(kids[m]); + const limits = kid.get("Limits"); + + if (key < xref.fetchIfRef(limits[0])) { + r = m - 1; + } else if (key > xref.fetchIfRef(limits[1])) { + l = m + 1; + } else { + kidsOrEntries = kid; + break; + } + } + if (l > r) { + return null; + } + } + + // If we get here, then we have found the right entry. Now go through the + // entries in the dictionary until we find the key we're looking for. + const entries = kidsOrEntries.get(this._type); + if (Array.isArray(entries)) { + // Perform a binary search to reduce the lookup time. + let l = 0, + r = entries.length - 2; + while (l <= r) { + // Check only even indices (0, 2, 4, ...) because the + // odd indices contain the actual data. + const tmp = (l + r) >> 1, + m = tmp + (tmp & 1); + const currentKey = xref.fetchIfRef(entries[m]); + if (key < currentKey) { + r = m - 2; + } else if (key > currentKey) { + l = m + 2; + } else { + return entries[m + 1]; + } + } + } + return null; + } + + get(key) { + return this.xref.fetchIfRef(this.getRaw(key)); + } +} + +class NameTree extends NameOrNumberTree { + constructor(root, xref) { + super(root, xref, "Names"); + } +} + +class NumberTree extends NameOrNumberTree { + constructor(root, xref) { + super(root, xref, "Nums"); + } +} + +export { NameTree, NumberTree }; diff --git a/frontend/public/pdf.js/src/core/object_loader.js b/frontend/public/pdf.js/src/core/object_loader.js new file mode 100644 index 0000000..6f75e45 --- /dev/null +++ b/frontend/public/pdf.js/src/core/object_loader.js @@ -0,0 +1,150 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { Dict, Ref, RefSet } from "./primitives.js"; +import { BaseStream } from "./base_stream.js"; +import { MissingDataException } from "./core_utils.js"; +import { warn } from "../shared/util.js"; + +function mayHaveChildren(value) { + return ( + value instanceof Ref || + value instanceof Dict || + value instanceof BaseStream || + Array.isArray(value) + ); +} + +function addChildren(node, nodesToVisit) { + if (node instanceof Dict) { + node = node.getRawValues(); + } else if (node instanceof BaseStream) { + node = node.dict.getRawValues(); + } else if (!Array.isArray(node)) { + return; + } + for (const rawValue of node) { + if (mayHaveChildren(rawValue)) { + nodesToVisit.push(rawValue); + } + } +} + +/** + * A helper for loading missing data in `Dict` graphs. It traverses the graph + * depth first and queues up any objects that have missing data. Once it has + * has traversed as many objects that are available it attempts to bundle the + * missing data requests and then resume from the nodes that weren't ready. + * + * NOTE: It provides protection from circular references by keeping track of + * loaded references. However, you must be careful not to load any graphs + * that have references to the catalog or other pages since that will cause the + * entire PDF document object graph to be traversed. + */ +class ObjectLoader { + constructor(dict, keys, xref) { + this.dict = dict; + this.keys = keys; + this.xref = xref; + this.refSet = null; + } + + async load() { + // Don't walk the graph if all the data is already loaded. + if (this.xref.stream.isDataLoaded) { + return undefined; + } + + const { keys, dict } = this; + this.refSet = new RefSet(); + // Setup the initial nodes to visit. + const nodesToVisit = []; + for (const key of keys) { + const rawValue = dict.getRaw(key); + // Skip nodes that are guaranteed to be empty. + if (rawValue !== undefined) { + nodesToVisit.push(rawValue); + } + } + return this._walk(nodesToVisit); + } + + async _walk(nodesToVisit) { + const nodesToRevisit = []; + const pendingRequests = []; + // DFS walk of the object graph. + while (nodesToVisit.length) { + let currentNode = nodesToVisit.pop(); + + // Only references or chunked streams can cause missing data exceptions. + if (currentNode instanceof Ref) { + // Skip nodes that have already been visited. + if (this.refSet.has(currentNode)) { + continue; + } + try { + this.refSet.put(currentNode); + currentNode = this.xref.fetch(currentNode); + } catch (ex) { + if (!(ex instanceof MissingDataException)) { + warn(`ObjectLoader._walk - requesting all data: "${ex}".`); + this.refSet = null; + + const { manager } = this.xref.stream; + return manager.requestAllChunks(); + } + nodesToRevisit.push(currentNode); + pendingRequests.push({ begin: ex.begin, end: ex.end }); + } + } + if (currentNode instanceof BaseStream) { + const baseStreams = currentNode.getBaseStreams(); + if (baseStreams) { + let foundMissingData = false; + for (const stream of baseStreams) { + if (stream.isDataLoaded) { + continue; + } + foundMissingData = true; + pendingRequests.push({ begin: stream.start, end: stream.end }); + } + if (foundMissingData) { + nodesToRevisit.push(currentNode); + } + } + } + + addChildren(currentNode, nodesToVisit); + } + + if (pendingRequests.length) { + await this.xref.stream.manager.requestRanges(pendingRequests); + + for (const node of nodesToRevisit) { + // Remove any reference nodes from the current `RefSet` so they + // aren't skipped when we revist them. + if (node instanceof Ref) { + this.refSet.remove(node); + } + } + return this._walk(nodesToRevisit); + } + // Everything is loaded. + this.refSet = null; + return undefined; + } +} + +export { ObjectLoader }; diff --git a/frontend/public/pdf.js/src/core/opentype_file_builder.js b/frontend/public/pdf.js/src/core/opentype_file_builder.js new file mode 100644 index 0000000..e02a0ee --- /dev/null +++ b/frontend/public/pdf.js/src/core/opentype_file_builder.js @@ -0,0 +1,154 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { readUint32 } from "./core_utils.js"; +import { string32 } from "../shared/util.js"; + +function writeInt16(dest, offset, num) { + dest[offset] = (num >> 8) & 0xff; + dest[offset + 1] = num & 0xff; +} + +function writeInt32(dest, offset, num) { + dest[offset] = (num >> 24) & 0xff; + dest[offset + 1] = (num >> 16) & 0xff; + dest[offset + 2] = (num >> 8) & 0xff; + dest[offset + 3] = num & 0xff; +} + +function writeData(dest, offset, data) { + if (data instanceof Uint8Array) { + dest.set(data, offset); + } else if (typeof data === "string") { + for (let i = 0, ii = data.length; i < ii; i++) { + dest[offset++] = data.charCodeAt(i) & 0xff; + } + } else { + // treating everything else as array + for (const num of data) { + dest[offset++] = num & 0xff; + } + } +} + +const OTF_HEADER_SIZE = 12; +const OTF_TABLE_ENTRY_SIZE = 16; + +class OpenTypeFileBuilder { + constructor(sfnt) { + this.sfnt = sfnt; + this.tables = Object.create(null); + } + + static getSearchParams(entriesCount, entrySize) { + let maxPower2 = 1, + log2 = 0; + while ((maxPower2 ^ entriesCount) > maxPower2) { + maxPower2 <<= 1; + log2++; + } + const searchRange = maxPower2 * entrySize; + return { + range: searchRange, + entry: log2, + rangeShift: entrySize * entriesCount - searchRange, + }; + } + + toArray() { + let sfnt = this.sfnt; + + // Tables needs to be written by ascendant alphabetic order + const tables = this.tables; + const tablesNames = Object.keys(tables); + tablesNames.sort(); + const numTables = tablesNames.length; + + let i, j, jj, table, tableName; + // layout the tables data + let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; + const tableOffsets = [offset]; + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + const paddedLength = ((table.length + 3) & ~3) >>> 0; + offset += paddedLength; + tableOffsets.push(offset); + } + + const file = new Uint8Array(offset); + // write the table data first (mostly for checksum) + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + writeData(file, tableOffsets[i], table); + } + + // sfnt version (4 bytes) + if (sfnt === "true") { + // Windows hates the Mac TrueType sfnt version number + sfnt = string32(0x00010000); + } + file[0] = sfnt.charCodeAt(0) & 0xff; + file[1] = sfnt.charCodeAt(1) & 0xff; + file[2] = sfnt.charCodeAt(2) & 0xff; + file[3] = sfnt.charCodeAt(3) & 0xff; + + // numTables (2 bytes) + writeInt16(file, 4, numTables); + + const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); + + // searchRange (2 bytes) + writeInt16(file, 6, searchParams.range); + // entrySelector (2 bytes) + writeInt16(file, 8, searchParams.entry); + // rangeShift (2 bytes) + writeInt16(file, 10, searchParams.rangeShift); + + offset = OTF_HEADER_SIZE; + // writing table entries + for (i = 0; i < numTables; i++) { + tableName = tablesNames[i]; + file[offset] = tableName.charCodeAt(0) & 0xff; + file[offset + 1] = tableName.charCodeAt(1) & 0xff; + file[offset + 2] = tableName.charCodeAt(2) & 0xff; + file[offset + 3] = tableName.charCodeAt(3) & 0xff; + + // checksum + let checksum = 0; + for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { + const quad = readUint32(file, j); + checksum = (checksum + quad) >>> 0; + } + writeInt32(file, offset + 4, checksum); + + // offset + writeInt32(file, offset + 8, tableOffsets[i]); + // length + writeInt32(file, offset + 12, tables[tableName].length); + + offset += OTF_TABLE_ENTRY_SIZE; + } + return file; + } + + addTable(tag, data) { + if (tag in this.tables) { + throw new Error("Table " + tag + " already exists"); + } + this.tables[tag] = data; + } +} + +export { OpenTypeFileBuilder }; diff --git a/frontend/public/pdf.js/src/core/operator_list.js b/frontend/public/pdf.js/src/core/operator_list.js new file mode 100644 index 0000000..1212e6e --- /dev/null +++ b/frontend/public/pdf.js/src/core/operator_list.js @@ -0,0 +1,736 @@ +/* Copyright 2017 Mozilla Foundation + * + * 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 { ImageKind, OPS, RenderingIntentFlag, warn } from "../shared/util.js"; + +function addState(parentState, pattern, checkFn, iterateFn, processFn) { + let state = parentState; + for (let i = 0, ii = pattern.length - 1; i < ii; i++) { + const item = pattern[i]; + state = state[item] ||= []; + } + state[pattern.at(-1)] = { + checkFn, + iterateFn, + processFn, + }; +} + +const InitialState = []; + +// This replaces (save, transform, paintInlineImageXObject, restore)+ +// sequences with one |paintInlineImageXObjectGroup| operation. +addState( + InitialState, + [OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore], + null, + function iterateInlineImageGroup(context, i) { + const fnArray = context.fnArray; + const iFirstSave = context.iCurr - 3; + const pos = (i - iFirstSave) % 4; + switch (pos) { + case 0: + return fnArray[i] === OPS.save; + case 1: + return fnArray[i] === OPS.transform; + case 2: + return fnArray[i] === OPS.paintInlineImageXObject; + case 3: + return fnArray[i] === OPS.restore; + } + throw new Error(`iterateInlineImageGroup - invalid pos: ${pos}`); + }, + function foundInlineImageGroup(context, i) { + const MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10; + const MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200; + const MAX_WIDTH = 1000; + const IMAGE_PADDING = 1; + + const fnArray = context.fnArray, + argsArray = context.argsArray; + const curr = context.iCurr; + const iFirstSave = curr - 3; + const iFirstTransform = curr - 2; + const iFirstPIIXO = curr - 1; + + const count = Math.min( + Math.floor((i - iFirstSave) / 4), + MAX_IMAGES_IN_INLINE_IMAGES_BLOCK + ); + if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) { + return i - ((i - iFirstSave) % 4); + } + + // assuming that heights of those image is too small (~1 pixel) + // packing as much as possible by lines + let maxX = 0; + const map = []; + let maxLineHeight = 0; + let currentX = IMAGE_PADDING, + currentY = IMAGE_PADDING; + for (let q = 0; q < count; q++) { + const transform = argsArray[iFirstTransform + (q << 2)]; + const img = argsArray[iFirstPIIXO + (q << 2)][0]; + if (currentX + img.width > MAX_WIDTH) { + // starting new line + maxX = Math.max(maxX, currentX); + currentY += maxLineHeight + 2 * IMAGE_PADDING; + currentX = 0; + maxLineHeight = 0; + } + map.push({ + transform, + x: currentX, + y: currentY, + w: img.width, + h: img.height, + }); + currentX += img.width + 2 * IMAGE_PADDING; + maxLineHeight = Math.max(maxLineHeight, img.height); + } + const imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING; + const imgHeight = currentY + maxLineHeight + IMAGE_PADDING; + const imgData = new Uint8Array(imgWidth * imgHeight * 4); + const imgRowSize = imgWidth << 2; + for (let q = 0; q < count; q++) { + const data = argsArray[iFirstPIIXO + (q << 2)][0].data; + // Copy image by lines and extends pixels into padding. + const rowSize = map[q].w << 2; + let dataOffset = 0; + let offset = (map[q].x + map[q].y * imgWidth) << 2; + imgData.set(data.subarray(0, rowSize), offset - imgRowSize); + for (let k = 0, kk = map[q].h; k < kk; k++) { + imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset); + dataOffset += rowSize; + offset += imgRowSize; + } + imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset); + while (offset >= 0) { + data[offset - 4] = data[offset]; + data[offset - 3] = data[offset + 1]; + data[offset - 2] = data[offset + 2]; + data[offset - 1] = data[offset + 3]; + data[offset + rowSize] = data[offset + rowSize - 4]; + data[offset + rowSize + 1] = data[offset + rowSize - 3]; + data[offset + rowSize + 2] = data[offset + rowSize - 2]; + data[offset + rowSize + 3] = data[offset + rowSize - 1]; + offset -= imgRowSize; + } + } + + const img = { + width: imgWidth, + height: imgHeight, + }; + if (context.isOffscreenCanvasSupported) { + const canvas = new OffscreenCanvas(imgWidth, imgHeight); + const ctx = canvas.getContext("2d"); + ctx.putImageData( + new ImageData( + new Uint8ClampedArray(imgData.buffer), + imgWidth, + imgHeight + ), + 0, + 0 + ); + img.bitmap = canvas.transferToImageBitmap(); + img.data = null; + } else { + img.kind = ImageKind.RGBA_32BPP; + img.data = imgData; + } + + // Replace queue items. + fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup); + argsArray.splice(iFirstSave, count * 4, [img, map]); + + return iFirstSave + 1; + } +); + +// This replaces (save, transform, paintImageMaskXObject, restore)+ +// sequences with one |paintImageMaskXObjectGroup| or one +// |paintImageMaskXObjectRepeat| operation. +addState( + InitialState, + [OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore], + null, + function iterateImageMaskGroup(context, i) { + const fnArray = context.fnArray; + const iFirstSave = context.iCurr - 3; + const pos = (i - iFirstSave) % 4; + switch (pos) { + case 0: + return fnArray[i] === OPS.save; + case 1: + return fnArray[i] === OPS.transform; + case 2: + return fnArray[i] === OPS.paintImageMaskXObject; + case 3: + return fnArray[i] === OPS.restore; + } + throw new Error(`iterateImageMaskGroup - invalid pos: ${pos}`); + }, + function foundImageMaskGroup(context, i) { + const MIN_IMAGES_IN_MASKS_BLOCK = 10; + const MAX_IMAGES_IN_MASKS_BLOCK = 100; + const MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000; + + const fnArray = context.fnArray, + argsArray = context.argsArray; + const curr = context.iCurr; + const iFirstSave = curr - 3; + const iFirstTransform = curr - 2; + const iFirstPIMXO = curr - 1; + + // At this point, i is the index of the first op past the last valid + // quartet. + let count = Math.floor((i - iFirstSave) / 4); + if (count < MIN_IMAGES_IN_MASKS_BLOCK) { + return i - ((i - iFirstSave) % 4); + } + + let isSameImage = false; + let iTransform, transformArgs; + const firstPIMXOArg0 = argsArray[iFirstPIMXO][0]; + const firstTransformArg0 = argsArray[iFirstTransform][0], + firstTransformArg1 = argsArray[iFirstTransform][1], + firstTransformArg2 = argsArray[iFirstTransform][2], + firstTransformArg3 = argsArray[iFirstTransform][3]; + + if (firstTransformArg1 === firstTransformArg2) { + isSameImage = true; + iTransform = iFirstTransform + 4; + let iPIMXO = iFirstPIMXO + 4; + for (let q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) { + transformArgs = argsArray[iTransform]; + if ( + argsArray[iPIMXO][0] !== firstPIMXOArg0 || + transformArgs[0] !== firstTransformArg0 || + transformArgs[1] !== firstTransformArg1 || + transformArgs[2] !== firstTransformArg2 || + transformArgs[3] !== firstTransformArg3 + ) { + if (q < MIN_IMAGES_IN_MASKS_BLOCK) { + isSameImage = false; + } else { + count = q; + } + break; // different image or transform + } + } + } + + if (isSameImage) { + count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK); + const positions = new Float32Array(count * 2); + iTransform = iFirstTransform; + for (let q = 0; q < count; q++, iTransform += 4) { + transformArgs = argsArray[iTransform]; + positions[q << 1] = transformArgs[4]; + positions[(q << 1) + 1] = transformArgs[5]; + } + + // Replace queue items. + fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat); + argsArray.splice(iFirstSave, count * 4, [ + firstPIMXOArg0, + firstTransformArg0, + firstTransformArg1, + firstTransformArg2, + firstTransformArg3, + positions, + ]); + } else { + count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK); + const images = []; + for (let q = 0; q < count; q++) { + transformArgs = argsArray[iFirstTransform + (q << 2)]; + const maskParams = argsArray[iFirstPIMXO + (q << 2)][0]; + images.push({ + data: maskParams.data, + width: maskParams.width, + height: maskParams.height, + interpolate: maskParams.interpolate, + count: maskParams.count, + transform: transformArgs, + }); + } + + // Replace queue items. + fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup); + argsArray.splice(iFirstSave, count * 4, [images]); + } + + return iFirstSave + 1; + } +); + +// This replaces (save, transform, paintImageXObject, restore)+ sequences +// with one paintImageXObjectRepeat operation, if the |transform| and +// |paintImageXObjectRepeat| ops are appropriate. +addState( + InitialState, + [OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore], + function (context) { + const argsArray = context.argsArray; + const iFirstTransform = context.iCurr - 2; + return ( + argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0 + ); + }, + function iterateImageGroup(context, i) { + const fnArray = context.fnArray, + argsArray = context.argsArray; + const iFirstSave = context.iCurr - 3; + const pos = (i - iFirstSave) % 4; + switch (pos) { + case 0: + return fnArray[i] === OPS.save; + case 1: + if (fnArray[i] !== OPS.transform) { + return false; + } + const iFirstTransform = context.iCurr - 2; + const firstTransformArg0 = argsArray[iFirstTransform][0]; + const firstTransformArg3 = argsArray[iFirstTransform][3]; + if ( + argsArray[i][0] !== firstTransformArg0 || + argsArray[i][1] !== 0 || + argsArray[i][2] !== 0 || + argsArray[i][3] !== firstTransformArg3 + ) { + return false; // transforms don't match + } + return true; + case 2: + if (fnArray[i] !== OPS.paintImageXObject) { + return false; + } + const iFirstPIXO = context.iCurr - 1; + const firstPIXOArg0 = argsArray[iFirstPIXO][0]; + if (argsArray[i][0] !== firstPIXOArg0) { + return false; // images don't match + } + return true; + case 3: + return fnArray[i] === OPS.restore; + } + throw new Error(`iterateImageGroup - invalid pos: ${pos}`); + }, + function (context, i) { + const MIN_IMAGES_IN_BLOCK = 3; + const MAX_IMAGES_IN_BLOCK = 1000; + + const fnArray = context.fnArray, + argsArray = context.argsArray; + const curr = context.iCurr; + const iFirstSave = curr - 3; + const iFirstTransform = curr - 2; + const iFirstPIXO = curr - 1; + const firstPIXOArg0 = argsArray[iFirstPIXO][0]; + const firstTransformArg0 = argsArray[iFirstTransform][0]; + const firstTransformArg3 = argsArray[iFirstTransform][3]; + + // At this point, i is the index of the first op past the last valid + // quartet. + const count = Math.min( + Math.floor((i - iFirstSave) / 4), + MAX_IMAGES_IN_BLOCK + ); + if (count < MIN_IMAGES_IN_BLOCK) { + return i - ((i - iFirstSave) % 4); + } + + // Extract the (x,y) positions from all of the matching transforms. + const positions = new Float32Array(count * 2); + let iTransform = iFirstTransform; + for (let q = 0; q < count; q++, iTransform += 4) { + const transformArgs = argsArray[iTransform]; + positions[q << 1] = transformArgs[4]; + positions[(q << 1) + 1] = transformArgs[5]; + } + + // Replace queue items. + const args = [ + firstPIXOArg0, + firstTransformArg0, + firstTransformArg3, + positions, + ]; + fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat); + argsArray.splice(iFirstSave, count * 4, args); + + return iFirstSave + 1; + } +); + +// This replaces (beginText, setFont, setTextMatrix, showText, endText)+ +// sequences with (beginText, setFont, (setTextMatrix, showText)+, endText)+ +// sequences, if the font for each one is the same. +addState( + InitialState, + [OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText], + null, + function iterateShowTextGroup(context, i) { + const fnArray = context.fnArray, + argsArray = context.argsArray; + const iFirstSave = context.iCurr - 4; + const pos = (i - iFirstSave) % 5; + switch (pos) { + case 0: + return fnArray[i] === OPS.beginText; + case 1: + return fnArray[i] === OPS.setFont; + case 2: + return fnArray[i] === OPS.setTextMatrix; + case 3: + if (fnArray[i] !== OPS.showText) { + return false; + } + const iFirstSetFont = context.iCurr - 3; + const firstSetFontArg0 = argsArray[iFirstSetFont][0]; + const firstSetFontArg1 = argsArray[iFirstSetFont][1]; + if ( + argsArray[i][0] !== firstSetFontArg0 || + argsArray[i][1] !== firstSetFontArg1 + ) { + return false; // fonts don't match + } + return true; + case 4: + return fnArray[i] === OPS.endText; + } + throw new Error(`iterateShowTextGroup - invalid pos: ${pos}`); + }, + function (context, i) { + const MIN_CHARS_IN_BLOCK = 3; + const MAX_CHARS_IN_BLOCK = 1000; + + const fnArray = context.fnArray, + argsArray = context.argsArray; + const curr = context.iCurr; + const iFirstBeginText = curr - 4; + const iFirstSetFont = curr - 3; + const iFirstSetTextMatrix = curr - 2; + const iFirstShowText = curr - 1; + const iFirstEndText = curr; + const firstSetFontArg0 = argsArray[iFirstSetFont][0]; + const firstSetFontArg1 = argsArray[iFirstSetFont][1]; + + // At this point, i is the index of the first op past the last valid + // quintet. + let count = Math.min( + Math.floor((i - iFirstBeginText) / 5), + MAX_CHARS_IN_BLOCK + ); + if (count < MIN_CHARS_IN_BLOCK) { + return i - ((i - iFirstBeginText) % 5); + } + + // If the preceding quintet is (, setFont, setTextMatrix, + // showText, endText), include that as well. (E.g. might be + // |dependency|.) + let iFirst = iFirstBeginText; + if ( + iFirstBeginText >= 4 && + fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && + fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && + fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && + fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && + argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && + argsArray[iFirstBeginText - 4][1] === firstSetFontArg1 + ) { + count++; + iFirst -= 5; + } + + // Remove (endText, beginText, setFont) trios. + let iEndText = iFirst + 4; + for (let q = 1; q < count; q++) { + fnArray.splice(iEndText, 3); + argsArray.splice(iEndText, 3); + iEndText += 2; + } + + return iEndText + 1; + } +); + +class NullOptimizer { + constructor(queue) { + this.queue = queue; + } + + _optimize() {} + + push(fn, args) { + this.queue.fnArray.push(fn); + this.queue.argsArray.push(args); + this._optimize(); + } + + flush() {} + + reset() {} +} + +class QueueOptimizer extends NullOptimizer { + constructor(queue) { + super(queue); + this.state = null; + this.context = { + iCurr: 0, + fnArray: queue.fnArray, + argsArray: queue.argsArray, + isOffscreenCanvasSupported: false, + }; + this.match = null; + this.lastProcessed = 0; + } + + // eslint-disable-next-line accessor-pairs + set isOffscreenCanvasSupported(value) { + this.context.isOffscreenCanvasSupported = value; + } + + _optimize() { + // Process new fnArray item(s) chunk. + const fnArray = this.queue.fnArray; + let i = this.lastProcessed, + ii = fnArray.length; + let state = this.state; + let match = this.match; + if (!state && !match && i + 1 === ii && !InitialState[fnArray[i]]) { + // Micro-optimization for the common case: last item is not + // optimizable, just skipping it. + this.lastProcessed = ii; + return; + } + + const context = this.context; + while (i < ii) { + if (match) { + // Already find a block of potentially optimizable items, iterating... + const iterate = (0, match.iterateFn)(context, i); + if (iterate) { + i++; + continue; + } + // Found last items for the block, processing... + i = (0, match.processFn)(context, i + 1); + ii = fnArray.length; + match = null; + state = null; + if (i >= ii) { + break; + } + } + // Find the potentially optimizable items. + state = (state || InitialState)[fnArray[i]]; + if (!state || Array.isArray(state)) { + i++; + continue; + } + // Found a start of the block based on addState rules. + context.iCurr = i; + i++; + if (state.checkFn && !(0, state.checkFn)(context)) { + // Check failed, continue search... + state = null; + continue; + } + match = state; + state = null; + } + this.state = state; + this.match = match; + this.lastProcessed = i; + } + + flush() { + while (this.match) { + const length = this.queue.fnArray.length; + this.lastProcessed = (0, this.match.processFn)(this.context, length); + this.match = null; + this.state = null; + // Repeat optimization until all chunks are exhausted. + this._optimize(); + } + } + + reset() { + this.state = null; + this.match = null; + this.lastProcessed = 0; + } +} + +class OperatorList { + static CHUNK_SIZE = 1000; + + // Close to chunk size. + static CHUNK_SIZE_ABOUT = this.CHUNK_SIZE - 5; + + constructor(intent = 0, streamSink) { + this._streamSink = streamSink; + this.fnArray = []; + this.argsArray = []; + this.optimizer = + streamSink && !(intent & RenderingIntentFlag.OPLIST) + ? new QueueOptimizer(this) + : new NullOptimizer(this); + this.dependencies = new Set(); + this._totalLength = 0; + this.weight = 0; + this._resolved = streamSink ? null : Promise.resolve(); + } + + // eslint-disable-next-line accessor-pairs + set isOffscreenCanvasSupported(value) { + this.optimizer.isOffscreenCanvasSupported = value; + } + + get length() { + return this.argsArray.length; + } + + get ready() { + return this._resolved || this._streamSink.ready; + } + + /** + * @type {number} The total length of the entire operator list, since + * `this.length === 0` after flushing. + */ + get totalLength() { + return this._totalLength + this.length; + } + + addOp(fn, args) { + this.optimizer.push(fn, args); + this.weight++; + if (this._streamSink) { + if (this.weight >= OperatorList.CHUNK_SIZE) { + this.flush(); + } else if ( + this.weight >= OperatorList.CHUNK_SIZE_ABOUT && + (fn === OPS.restore || fn === OPS.endText) + ) { + // Heuristic to flush on boundary of restore or endText. + this.flush(); + } + } + } + + addImageOps(fn, args, optionalContent, hasMask = false) { + if (hasMask) { + this.addOp(OPS.save); + this.addOp(OPS.setGState, [[["SMask", false]]]); + } + if (optionalContent !== undefined) { + this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); + } + + this.addOp(fn, args); + + if (optionalContent !== undefined) { + this.addOp(OPS.endMarkedContent, []); + } + if (hasMask) { + this.addOp(OPS.restore); + } + } + + addDependency(dependency) { + if (this.dependencies.has(dependency)) { + return; + } + this.dependencies.add(dependency); + this.addOp(OPS.dependency, [dependency]); + } + + addDependencies(dependencies) { + for (const dependency of dependencies) { + this.addDependency(dependency); + } + } + + addOpList(opList) { + if (!(opList instanceof OperatorList)) { + warn('addOpList - ignoring invalid "opList" parameter.'); + return; + } + for (const dependency of opList.dependencies) { + this.dependencies.add(dependency); + } + for (let i = 0, ii = opList.length; i < ii; i++) { + this.addOp(opList.fnArray[i], opList.argsArray[i]); + } + } + + getIR() { + return { + fnArray: this.fnArray, + argsArray: this.argsArray, + length: this.length, + }; + } + + get _transfers() { + const transfers = []; + const { fnArray, argsArray, length } = this; + for (let i = 0; i < length; i++) { + switch (fnArray[i]) { + case OPS.paintInlineImageXObject: + case OPS.paintInlineImageXObjectGroup: + case OPS.paintImageMaskXObject: + const arg = argsArray[i][0]; // First parameter in imgData. + if (!arg.cached && arg.data?.buffer instanceof ArrayBuffer) { + transfers.push(arg.data.buffer); + } + break; + } + } + return transfers; + } + + flush(lastChunk = false, separateAnnots = null) { + this.optimizer.flush(); + const length = this.length; + this._totalLength += length; + + this._streamSink.enqueue( + { + fnArray: this.fnArray, + argsArray: this.argsArray, + lastChunk, + separateAnnots, + length, + }, + 1, + this._transfers + ); + + this.dependencies.clear(); + this.fnArray.length = 0; + this.argsArray.length = 0; + this.weight = 0; + this.optimizer.reset(); + } +} + +export { OperatorList }; diff --git a/frontend/public/pdf.js/src/core/parser.js b/frontend/public/pdf.js/src/core/parser.js new file mode 100644 index 0000000..50ea563 --- /dev/null +++ b/frontend/public/pdf.js/src/core/parser.js @@ -0,0 +1,1423 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + assert, + bytesToString, + FormatError, + info, + warn, +} from "../shared/util.js"; +import { Cmd, Dict, EOF, isCmd, Name, Ref } from "./primitives.js"; +import { + isWhiteSpace, + MissingDataException, + ParserEOFException, +} from "./core_utils.js"; +import { NullStream, Stream } from "./stream.js"; +import { Ascii85Stream } from "./ascii_85_stream.js"; +import { AsciiHexStream } from "./ascii_hex_stream.js"; +import { CCITTFaxStream } from "./ccitt_stream.js"; +import { FlateStream } from "./flate_stream.js"; +import { Jbig2Stream } from "./jbig2_stream.js"; +import { JpegStream } from "./jpeg_stream.js"; +import { JpxStream } from "./jpx_stream.js"; +import { LZWStream } from "./lzw_stream.js"; +import { PredictorStream } from "./predictor_stream.js"; +import { RunLengthStream } from "./run_length_stream.js"; + +const MAX_LENGTH_TO_CACHE = 1000; + +function getInlineImageCacheKey(bytes) { + const strBuf = [], + ii = bytes.length; + let i = 0; + while (i < ii - 1) { + strBuf.push((bytes[i++] << 8) | bytes[i++]); + } + // Handle an odd number of elements. + if (i < ii) { + strBuf.push(bytes[i]); + } + // We purposely include the "raw" length in the cacheKey, to prevent any + // possible issues with hash collisions in the inline image cache. + // Here we also assume that `strBuf` is never larger than 8192 elements, + // please refer to the `bytesToString` implementation. + return ii + "_" + String.fromCharCode.apply(null, strBuf); +} + +class Parser { + constructor({ lexer, xref, allowStreams = false, recoveryMode = false }) { + this.lexer = lexer; + this.xref = xref; + this.allowStreams = allowStreams; + this.recoveryMode = recoveryMode; + + this.imageCache = Object.create(null); + this._imageId = 0; + this.refill(); + } + + refill() { + this.buf1 = this.lexer.getObj(); + this.buf2 = this.lexer.getObj(); + } + + shift() { + if (this.buf2 instanceof Cmd && this.buf2.cmd === "ID") { + this.buf1 = this.buf2; + this.buf2 = null; + } else { + this.buf1 = this.buf2; + this.buf2 = this.lexer.getObj(); + } + } + + tryShift() { + try { + this.shift(); + return true; + } catch (e) { + if (e instanceof MissingDataException) { + throw e; + } + // Upon failure, the caller should reset this.lexer.pos to a known good + // state and call this.shift() twice to reset the buffers. + return false; + } + } + + getObj(cipherTransform = null) { + const buf1 = this.buf1; + this.shift(); + + if (buf1 instanceof Cmd) { + switch (buf1.cmd) { + case "BI": // inline image + return this.makeInlineImage(cipherTransform); + case "[": // array + const array = []; + while (!isCmd(this.buf1, "]") && this.buf1 !== EOF) { + array.push(this.getObj(cipherTransform)); + } + if (this.buf1 === EOF) { + if (this.recoveryMode) { + return array; + } + throw new ParserEOFException("End of file inside array."); + } + this.shift(); + return array; + case "<<": // dictionary or stream + const dict = new Dict(this.xref); + while (!isCmd(this.buf1, ">>") && this.buf1 !== EOF) { + if (!(this.buf1 instanceof Name)) { + info("Malformed dictionary: key must be a name object"); + this.shift(); + continue; + } + + const key = this.buf1.name; + this.shift(); + if (this.buf1 === EOF) { + break; + } + dict.set(key, this.getObj(cipherTransform)); + } + if (this.buf1 === EOF) { + if (this.recoveryMode) { + return dict; + } + throw new ParserEOFException("End of file inside dictionary."); + } + + // Stream objects are not allowed inside content streams or + // object streams. + if (isCmd(this.buf2, "stream")) { + return this.allowStreams + ? this.makeStream(dict, cipherTransform) + : dict; + } + this.shift(); + return dict; + default: // simple object + return buf1; + } + } + + if (Number.isInteger(buf1)) { + // indirect reference or integer + if (Number.isInteger(this.buf1) && isCmd(this.buf2, "R")) { + const ref = Ref.get(buf1, this.buf1); + this.shift(); + this.shift(); + return ref; + } + return buf1; + } + + if (typeof buf1 === "string") { + if (cipherTransform) { + return cipherTransform.decryptString(buf1); + } + return buf1; + } + + // simple object + return buf1; + } + + /** + * Find the end of the stream by searching for the /EI\s/. + * @returns {number} The inline stream length. + */ + findDefaultInlineStreamEnd(stream) { + const E = 0x45, + I = 0x49, + SPACE = 0x20, + LF = 0xa, + CR = 0xd, + NUL = 0x0; + const { knownCommands } = this.lexer, + startPos = stream.pos, + n = 15; + let state = 0, + ch, + maybeEIPos; + while ((ch = stream.getByte()) !== -1) { + if (state === 0) { + state = ch === E ? 1 : 0; + } else if (state === 1) { + state = ch === I ? 2 : 0; + } else { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert(state === 2, "findDefaultInlineStreamEnd - invalid state."); + } + if (ch === SPACE || ch === LF || ch === CR) { + maybeEIPos = stream.pos; + // Let's check that the next `n` bytes are ASCII... just to be sure. + const followingBytes = stream.peekBytes(n); + + const ii = followingBytes.length; + if (ii === 0) { + break; // The end of the stream was reached, nothing to check. + } + for (let i = 0; i < ii; i++) { + ch = followingBytes[i]; + if (ch === NUL && followingBytes[i + 1] !== NUL) { + // NUL bytes are not supposed to occur *outside* of inline + // images, but some PDF generators violate that assumption, + // thus breaking the EI detection heuristics used below. + // + // However, we can't unconditionally treat NUL bytes as "ASCII", + // since that *could* result in inline images being truncated. + // + // To attempt to address this, we'll still treat any *sequence* + // of NUL bytes as non-ASCII, but for a *single* NUL byte we'll + // continue checking the `followingBytes` (fixes issue8823.pdf). + continue; + } + if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7f)) { + // Not a LF, CR, SPACE or any visible ASCII character, i.e. + // it's binary stuff. Resetting the state. + state = 0; + break; + } + } + + if (state !== 2) { + continue; + } + if (!knownCommands) { + warn( + "findDefaultInlineStreamEnd - `lexer.knownCommands` is undefined." + ); + continue; + } + // Check that the "EI" sequence isn't part of the image data, since + // that would cause the image to be truncated (fixes issue11124.pdf). + // + // Check more than the `followingBytes` to be able to find operators + // with multiple arguments, e.g. transform (cm) with decimal arguments + // (fixes issue19494.pdf). + const tmpLexer = new Lexer( + new Stream(stream.peekBytes(5 * n)), + knownCommands + ); + // Reduce the number of (potential) warning messages. + tmpLexer._hexStringWarn = () => {}; + let numArgs = 0; + + while (true) { + const nextObj = tmpLexer.getObj(); + + if (nextObj === EOF) { + state = 0; // No valid command found, resetting the state. + break; + } + if (nextObj instanceof Cmd) { + const knownCommand = knownCommands[nextObj.cmd]; + if (!knownCommand) { + // Not a valid command, i.e. the inline image data *itself* + // contains an "EI" sequence. Resetting the state. + state = 0; + break; + } else if ( + knownCommand.variableArgs + ? numArgs <= knownCommand.numArgs + : numArgs === knownCommand.numArgs + ) { + break; // Valid command found. + } + numArgs = 0; + continue; + } + numArgs++; + } + + if (state === 2) { + break; // Finished! + } + } else { + state = 0; + } + } + } + + if (ch === -1) { + warn( + "findDefaultInlineStreamEnd: " + + "Reached the end of the stream without finding a valid EI marker" + ); + if (maybeEIPos) { + warn('... trying to recover by using the last "EI" occurrence.'); + stream.skip(-(stream.pos - maybeEIPos)); // Reset the stream position. + } + } + + let endOffset = 4; + stream.skip(-endOffset); // Set the stream position to just before "EI". + ch = stream.peekByte(); + stream.skip(endOffset); // ... and remember to reset the stream position. + + // Ensure that we don't accidentally truncate the inline image, when the + // data is immediately followed by the "EI" marker (fixes issue10388.pdf). + if (!isWhiteSpace(ch)) { + endOffset--; + } + return stream.pos - endOffset - startPos; + } + + /** + * Find the EOI (end-of-image) marker 0xFFD9 of the stream. + * @returns {number} The inline stream length. + */ + findDCTDecodeInlineStreamEnd(stream) { + const startPos = stream.pos; + let foundEOI = false, + b, + markerLength; + while ((b = stream.getByte()) !== -1) { + if (b !== 0xff) { + // Not a valid marker. + continue; + } + switch (stream.getByte()) { + case 0x00: // Byte stuffing. + // 0xFF00 appears to be a very common byte sequence in JPEG images. + break; + + case 0xff: // Fill byte. + // Avoid skipping a valid marker, resetting the stream position. + stream.skip(-1); + break; + + case 0xd9: // EOI + foundEOI = true; + break; + + case 0xc0: // SOF0 + case 0xc1: // SOF1 + case 0xc2: // SOF2 + case 0xc3: // SOF3 + /* falls through */ + case 0xc5: // SOF5 + case 0xc6: // SOF6 + case 0xc7: // SOF7 + /* falls through */ + case 0xc9: // SOF9 + case 0xca: // SOF10 + case 0xcb: // SOF11 + /* falls through */ + case 0xcd: // SOF13 + case 0xce: // SOF14 + case 0xcf: // SOF15 + /* falls through */ + case 0xc4: // DHT + case 0xcc: // DAC + /* falls through */ + case 0xda: // SOS + case 0xdb: // DQT + case 0xdc: // DNL + case 0xdd: // DRI + case 0xde: // DHP + case 0xdf: // EXP + /* falls through */ + case 0xe0: // APP0 + case 0xe1: // APP1 + case 0xe2: // APP2 + case 0xe3: // APP3 + case 0xe4: // APP4 + case 0xe5: // APP5 + case 0xe6: // APP6 + case 0xe7: // APP7 + case 0xe8: // APP8 + case 0xe9: // APP9 + case 0xea: // APP10 + case 0xeb: // APP11 + case 0xec: // APP12 + case 0xed: // APP13 + case 0xee: // APP14 + case 0xef: // APP15 + /* falls through */ + case 0xfe: // COM + // The marker should be followed by the length of the segment. + markerLength = stream.getUint16(); + if (markerLength > 2) { + // |markerLength| contains the byte length of the marker segment, + // including its own length (2 bytes) and excluding the marker. + stream.skip(markerLength - 2); // Jump to the next marker. + } else { + // The marker length is invalid, resetting the stream position. + stream.skip(-2); + } + break; + } + if (foundEOI) { + break; + } + } + const length = stream.pos - startPos; + if (b === -1) { + warn( + "Inline DCTDecode image stream: " + + "EOI marker not found, searching for /EI/ instead." + ); + stream.skip(-length); // Reset the stream position. + return this.findDefaultInlineStreamEnd(stream); + } + this.inlineStreamSkipEI(stream); + return length; + } + + /** + * Find the EOD (end-of-data) marker '~>' (i.e. TILDE + GT) of the stream. + * @returns {number} The inline stream length. + */ + findASCII85DecodeInlineStreamEnd(stream) { + const TILDE = 0x7e, + GT = 0x3e; + const startPos = stream.pos; + let ch; + while ((ch = stream.getByte()) !== -1) { + if (ch === TILDE) { + const tildePos = stream.pos; + + ch = stream.peekByte(); + // Handle corrupt PDF documents which contains whitespace "inside" of + // the EOD marker (fixes issue10614.pdf). + while (isWhiteSpace(ch)) { + stream.skip(); + ch = stream.peekByte(); + } + if (ch === GT) { + stream.skip(); + break; + } + // Handle corrupt PDF documents which contains truncated EOD markers, + // where the '>' character is missing (fixes issue11385.pdf). + if (stream.pos > tildePos) { + const maybeEI = stream.peekBytes(2); + if (maybeEI[0] === /* E = */ 0x45 && maybeEI[1] === /* I = */ 0x49) { + break; + } + } + } + } + const length = stream.pos - startPos; + if (ch === -1) { + warn( + "Inline ASCII85Decode image stream: " + + "EOD marker not found, searching for /EI/ instead." + ); + stream.skip(-length); // Reset the stream position. + return this.findDefaultInlineStreamEnd(stream); + } + this.inlineStreamSkipEI(stream); + return length; + } + + /** + * Find the EOD (end-of-data) marker '>' (i.e. GT) of the stream. + * @returns {number} The inline stream length. + */ + findASCIIHexDecodeInlineStreamEnd(stream) { + const GT = 0x3e; + const startPos = stream.pos; + let ch; + while ((ch = stream.getByte()) !== -1) { + if (ch === GT) { + break; + } + } + const length = stream.pos - startPos; + if (ch === -1) { + warn( + "Inline ASCIIHexDecode image stream: " + + "EOD marker not found, searching for /EI/ instead." + ); + stream.skip(-length); // Reset the stream position. + return this.findDefaultInlineStreamEnd(stream); + } + this.inlineStreamSkipEI(stream); + return length; + } + + /** + * Skip over the /EI/ for streams where we search for an EOD marker. + */ + inlineStreamSkipEI(stream) { + const E = 0x45, + I = 0x49; + let state = 0, + ch; + while ((ch = stream.getByte()) !== -1) { + if (state === 0) { + state = ch === E ? 1 : 0; + } else if (state === 1) { + state = ch === I ? 2 : 0; + } else if (state === 2) { + break; + } + } + } + + makeInlineImage(cipherTransform) { + const lexer = this.lexer; + const stream = lexer.stream; + + // Parse dictionary, but initialize it lazily to improve performance with + // cached inline images (see issue 2618). + const dictMap = Object.create(null); + let dictLength; + while (!isCmd(this.buf1, "ID") && this.buf1 !== EOF) { + if (!(this.buf1 instanceof Name)) { + throw new FormatError("Dictionary key must be a name object"); + } + const key = this.buf1.name; + this.shift(); + if (this.buf1 === EOF) { + break; + } + dictMap[key] = this.getObj(cipherTransform); + } + if (lexer.beginInlineImagePos !== -1) { + dictLength = stream.pos - lexer.beginInlineImagePos; + } + + // Extract the name of the first (i.e. the current) image filter. + const filter = this.xref.fetchIfRef(dictMap.F || dictMap.Filter); + let filterName; + if (filter instanceof Name) { + filterName = filter.name; + } else if (Array.isArray(filter)) { + const filterZero = this.xref.fetchIfRef(filter[0]); + if (filterZero instanceof Name) { + filterName = filterZero.name; + } + } + + // Parse image stream. + const startPos = stream.pos; + let length; + switch (filterName) { + case "DCT": + case "DCTDecode": + length = this.findDCTDecodeInlineStreamEnd(stream); + break; + case "A85": + case "ASCII85Decode": + length = this.findASCII85DecodeInlineStreamEnd(stream); + break; + case "AHx": + case "ASCIIHexDecode": + length = this.findASCIIHexDecodeInlineStreamEnd(stream); + break; + default: + length = this.findDefaultInlineStreamEnd(stream); + } + + // Cache all images below the MAX_LENGTH_TO_CACHE threshold by their + // stringified content, to prevent possible hash collisions. + let cacheKey; + if (length < MAX_LENGTH_TO_CACHE && dictLength > 0) { + const initialStreamPos = stream.pos; + // Set the stream position to the beginning of the dictionary data... + stream.pos = lexer.beginInlineImagePos; + // ... and fetch the bytes of the dictionary *and* the inline image. + cacheKey = getInlineImageCacheKey(stream.getBytes(dictLength + length)); + // Finally, don't forget to reset the stream position. + stream.pos = initialStreamPos; + + const cacheEntry = this.imageCache[cacheKey]; + if (cacheEntry !== undefined) { + this.buf2 = Cmd.get("EI"); + this.shift(); + + cacheEntry.reset(); + return cacheEntry; + } + } + + const dict = new Dict(this.xref); + for (const key in dictMap) { + dict.set(key, dictMap[key]); + } + let imageStream = stream.makeSubStream(startPos, length, dict); + if (cipherTransform) { + imageStream = cipherTransform.createStream(imageStream, length); + } + + imageStream = this.filter(imageStream, dict, length); + imageStream.dict = dict; + if (cacheKey !== undefined) { + imageStream.cacheKey = `inline_img_${++this._imageId}`; + this.imageCache[cacheKey] = imageStream; + } + + this.buf2 = Cmd.get("EI"); + this.shift(); + + return imageStream; + } + + #findStreamLength(startPos) { + const { stream } = this.lexer; + stream.pos = startPos; + + const SCAN_BLOCK_LENGTH = 2048; + const signatureLength = "endstream".length; + + const END_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64]); + const endLength = END_SIGNATURE.length; + + // Ideally we'd directly search for "endstream", however there are corrupt + // PDF documents where the command is incomplete; hence we search for: + // 1. The normal case. + // 2. The misspelled case (fixes issue18122.pdf). + // 3. The truncated case (fixes issue10004.pdf). + const PARTIAL_SIGNATURE = [ + new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6d]), // "stream" + new Uint8Array([0x73, 0x74, 0x65, 0x61, 0x6d]), // "steam", + new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61]), // "strea" + ]; + const normalLength = signatureLength - endLength; + + while (stream.pos < stream.end) { + const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); + const scanLength = scanBytes.length - signatureLength; + + if (scanLength <= 0) { + break; + } + let pos = 0; + while (pos < scanLength) { + let j = 0; + while (j < endLength && scanBytes[pos + j] === END_SIGNATURE[j]) { + j++; + } + if (j >= endLength) { + // "end" found, find the complete command. + let found = false; + for (const part of PARTIAL_SIGNATURE) { + const partLen = part.length; + let k = 0; + while (k < partLen && scanBytes[pos + j + k] === part[k]) { + k++; + } + if (k >= normalLength) { + // Found "endstream" command. + found = true; + break; + } + if (k >= partLen) { + // Found "endsteam" or "endstea" command. + // Ensure that the byte immediately following the corrupt + // endstream command is a space, to prevent false positives. + const lastByte = scanBytes[pos + j + k]; + if (isWhiteSpace(lastByte)) { + info( + `Found "${bytesToString([...END_SIGNATURE, ...part])}" when ` + + "searching for endstream command." + ); + found = true; + } + break; + } + } + + if (found) { + stream.pos += pos; + return stream.pos - startPos; + } + } + pos++; + } + stream.pos += scanLength; + } + return -1; + } + + makeStream(dict, cipherTransform) { + const lexer = this.lexer; + let stream = lexer.stream; + + // Get the stream's start position. + lexer.skipToNextLine(); + const startPos = stream.pos - 1; + + // Get the length. + let length = dict.get("Length"); + if (!Number.isInteger(length)) { + info(`Bad length "${length && length.toString()}" in stream.`); + length = 0; + } + + // Skip over the stream data. + stream.pos = startPos + length; + lexer.nextChar(); + + // Shift '>>' and check whether the new object marks the end of the stream. + if (this.tryShift() && isCmd(this.buf2, "endstream")) { + this.shift(); // 'stream' + } else { + // Bad stream length, scanning for endstream command. + length = this.#findStreamLength(startPos); + if (length < 0) { + throw new FormatError("Missing endstream command."); + } + + lexer.nextChar(); + this.shift(); + this.shift(); + } + this.shift(); // 'endstream' + + stream = stream.makeSubStream(startPos, length, dict); + if (cipherTransform) { + stream = cipherTransform.createStream(stream, length); + } + stream = this.filter(stream, dict, length); + stream.dict = dict; + return stream; + } + + filter(stream, dict, length) { + let filter = dict.get("F", "Filter"); + let params = dict.get("DP", "DecodeParms"); + + if (filter instanceof Name) { + if (Array.isArray(params)) { + warn("/DecodeParms should not be an Array, when /Filter is a Name."); + } + return this.makeFilter(stream, filter.name, length, params); + } + + let maybeLength = length; + if (Array.isArray(filter)) { + const filterArray = filter; + const paramsArray = params; + for (let i = 0, ii = filterArray.length; i < ii; ++i) { + filter = this.xref.fetchIfRef(filterArray[i]); + if (!(filter instanceof Name)) { + throw new FormatError(`Bad filter name "${filter}"`); + } + + params = null; + if (Array.isArray(paramsArray) && i in paramsArray) { + params = this.xref.fetchIfRef(paramsArray[i]); + } + stream = this.makeFilter(stream, filter.name, maybeLength, params); + // After the first stream the `length` variable is invalid. + maybeLength = null; + } + } + return stream; + } + + makeFilter(stream, name, maybeLength, params) { + // Since the 'Length' entry in the stream dictionary can be completely + // wrong, e.g. zero for non-empty streams, only skip parsing the stream + // when we can be absolutely certain that it actually is empty. + if (maybeLength === 0) { + warn(`Empty "${name}" stream.`); + return new NullStream(); + } + + try { + switch (name) { + case "Fl": + case "FlateDecode": + if (params) { + return new PredictorStream( + new FlateStream(stream, maybeLength), + maybeLength, + params + ); + } + return new FlateStream(stream, maybeLength); + case "LZW": + case "LZWDecode": + let earlyChange = 1; + if (params) { + if (params.has("EarlyChange")) { + earlyChange = params.get("EarlyChange"); + } + return new PredictorStream( + new LZWStream(stream, maybeLength, earlyChange), + maybeLength, + params + ); + } + return new LZWStream(stream, maybeLength, earlyChange); + case "DCT": + case "DCTDecode": + return new JpegStream(stream, maybeLength, params); + case "JPX": + case "JPXDecode": + return new JpxStream(stream, maybeLength, params); + case "A85": + case "ASCII85Decode": + return new Ascii85Stream(stream, maybeLength); + case "AHx": + case "ASCIIHexDecode": + return new AsciiHexStream(stream, maybeLength); + case "CCF": + case "CCITTFaxDecode": + return new CCITTFaxStream(stream, maybeLength, params); + case "RL": + case "RunLengthDecode": + return new RunLengthStream(stream, maybeLength); + case "JBIG2Decode": + return new Jbig2Stream(stream, maybeLength, params); + } + warn(`Filter "${name}" is not supported.`); + return stream; + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(`Invalid stream: "${ex}"`); + return new NullStream(); + } + } +} + +// A '1' in this array means the character is white space. A '1' or +// '2' means the character ends a name or command. +// prettier-ignore +const specialChars = [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx +]; + +function toHexDigit(ch) { + if (ch >= /* '0' = */ 0x30 && ch /* '9' = */ <= 0x39) { + return ch & 0x0f; + } + if ( + (ch >= /* 'A' = */ 0x41 && ch <= /* 'F' = */ 0x46) || + (ch >= /* 'a' = */ 0x61 && ch <= /* 'f' = */ 0x66) + ) { + return (ch & 0x0f) + 9; + } + return -1; +} + +class Lexer { + constructor(stream, knownCommands = null) { + this.stream = stream; + this.nextChar(); + + // While lexing, we build up many strings one char at a time. Using += for + // this can result in lots of garbage strings. It's better to build an + // array of single-char strings and then join() them together at the end. + // And reusing a single array (i.e. |this.strBuf|) over and over for this + // purpose uses less memory than using a new array for each string. + this.strBuf = []; + + // The PDFs might have "glued" commands with other commands, operands or + // literals, e.g. "q1". The knownCommands is a dictionary of the valid + // commands and their prefixes. The prefixes are built the following way: + // if there a command that is a prefix of the other valid command or + // literal (e.g. 'f' and 'false') the following prefixes must be included, + // 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no + // other commands or literals as a prefix. The knowCommands is optional. + this.knownCommands = knownCommands; + + this._hexStringNumWarn = 0; + this.beginInlineImagePos = -1; + } + + nextChar() { + return (this.currentChar = this.stream.getByte()); + } + + peekChar() { + return this.stream.peekByte(); + } + + getNumber() { + let ch = this.currentChar; + let eNotation = false; + let divideBy = 0; // Different from 0 if it's a floating point value. + let sign = 1; + + if (ch === /* '-' = */ 0x2d) { + sign = -1; + ch = this.nextChar(); + + if (ch === /* '-' = */ 0x2d) { + // Ignore double negative (this is consistent with Adobe Reader). + ch = this.nextChar(); + } + } else if (ch === /* '+' = */ 0x2b) { + ch = this.nextChar(); + } + if (ch === /* LF = */ 0x0a || ch === /* CR = */ 0x0d) { + // Ignore line-breaks (this is consistent with Adobe Reader). + do { + ch = this.nextChar(); + } while (ch === 0x0a || ch === 0x0d); + } + if (ch === /* '.' = */ 0x2e) { + divideBy = 10; + ch = this.nextChar(); + } + if (ch < /* '0' = */ 0x30 || ch > /* '9' = */ 0x39) { + const msg = `Invalid number: ${String.fromCharCode(ch)} (charCode ${ch})`; + + if (isWhiteSpace(ch) || ch === /* EOF = */ -1) { + // This is consistent with Adobe Reader (fixes issue9252.pdf, + // issue15604.pdf, bug1753983.pdf). + info(`Lexer.getNumber - "${msg}".`); + return 0; + } + throw new FormatError(msg); + } + + let baseValue = ch - 0x30; // '0' + let powerValue = 0; + let powerValueSign = 1; + + while ((ch = this.nextChar()) >= 0) { + if (ch >= /* '0' = */ 0x30 && ch <= /* '9' = */ 0x39) { + const currentDigit = ch - 0x30; // '0' + if (eNotation) { + // We are after an 'e' or 'E'. + powerValue = powerValue * 10 + currentDigit; + } else { + if (divideBy !== 0) { + // We are after a point. + divideBy *= 10; + } + baseValue = baseValue * 10 + currentDigit; + } + } else if (ch === /* '.' = */ 0x2e) { + if (divideBy === 0) { + divideBy = 1; + } else { + // A number can have only one dot. + break; + } + } else if (ch === /* '-' = */ 0x2d) { + // Ignore minus signs in the middle of numbers to match + // Adobe's behavior. + warn("Badly formatted number: minus sign in the middle"); + } else if (ch === /* 'E' = */ 0x45 || ch === /* 'e' = */ 0x65) { + // 'E' can be either a scientific notation or the beginning of a new + // operator. + ch = this.peekChar(); + if (ch === /* '+' = */ 0x2b || ch === /* '-' = */ 0x2d) { + powerValueSign = ch === 0x2d ? -1 : 1; + this.nextChar(); // Consume the sign character. + } else if (ch < /* '0' = */ 0x30 || ch > /* '9' = */ 0x39) { + // The 'E' must be the beginning of a new operator. + break; + } + eNotation = true; + } else { + // The last character doesn't belong to us. + break; + } + } + + if (divideBy !== 0) { + baseValue /= divideBy; + } + if (eNotation) { + baseValue *= 10 ** (powerValueSign * powerValue); + } + return sign * baseValue; + } + + getString() { + let numParen = 1; + let done = false; + const strBuf = this.strBuf; + strBuf.length = 0; + + let ch = this.nextChar(); + while (true) { + let charBuffered = false; + switch (ch | 0) { + case -1: + warn("Unterminated string"); + done = true; + break; + case 0x28: // '(' + ++numParen; + strBuf.push("("); + break; + case 0x29: // ')' + if (--numParen === 0) { + this.nextChar(); // consume strings ')' + done = true; + } else { + strBuf.push(")"); + } + break; + case 0x5c: // '\\' + ch = this.nextChar(); + switch (ch) { + case -1: + warn("Unterminated string"); + done = true; + break; + case 0x6e: // 'n' + strBuf.push("\n"); + break; + case 0x72: // 'r' + strBuf.push("\r"); + break; + case 0x74: // 't' + strBuf.push("\t"); + break; + case 0x62: // 'b' + strBuf.push("\b"); + break; + case 0x66: // 'f' + strBuf.push("\f"); + break; + case 0x5c: // '\' + case 0x28: // '(' + case 0x29: // ')' + strBuf.push(String.fromCharCode(ch)); + break; + case 0x30: // '0' + case 0x31: // '1' + case 0x32: // '2' + case 0x33: // '3' + case 0x34: // '4' + case 0x35: // '5' + case 0x36: // '6' + case 0x37: // '7' + let x = ch & 0x0f; + ch = this.nextChar(); + charBuffered = true; + if (ch >= /* '0' = */ 0x30 && ch <= /* '7' = */ 0x37) { + x = (x << 3) + (ch & 0x0f); + ch = this.nextChar(); + if (ch >= /* '0' = */ 0x30 && ch /* '7' = */ <= 0x37) { + charBuffered = false; + x = (x << 3) + (ch & 0x0f); + } + } + strBuf.push(String.fromCharCode(x)); + break; + case 0x0d: // CR + if (this.peekChar() === /* LF = */ 0x0a) { + this.nextChar(); + } + break; + case 0x0a: // LF + break; + default: + strBuf.push(String.fromCharCode(ch)); + break; + } + break; + default: + strBuf.push(String.fromCharCode(ch)); + break; + } + if (done) { + break; + } + if (!charBuffered) { + ch = this.nextChar(); + } + } + return strBuf.join(""); + } + + getName() { + let ch, previousCh; + const strBuf = this.strBuf; + strBuf.length = 0; + + while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) { + if (ch === /* '#' = */ 0x23) { + ch = this.nextChar(); + if (specialChars[ch]) { + warn( + "Lexer_getName: " + + "NUMBER SIGN (#) should be followed by a hexadecimal number." + ); + strBuf.push("#"); + break; + } + const x = toHexDigit(ch); + if (x !== -1) { + previousCh = ch; + ch = this.nextChar(); + const x2 = toHexDigit(ch); + if (x2 === -1) { + warn( + `Lexer_getName: Illegal digit (${String.fromCharCode(ch)}) ` + + "in hexadecimal number." + ); + strBuf.push("#", String.fromCharCode(previousCh)); + if (specialChars[ch]) { + break; + } + strBuf.push(String.fromCharCode(ch)); + continue; + } + strBuf.push(String.fromCharCode((x << 4) | x2)); + } else { + strBuf.push("#", String.fromCharCode(ch)); + } + } else { + strBuf.push(String.fromCharCode(ch)); + } + } + if (strBuf.length > 127) { + warn(`Name token is longer than allowed by the spec: ${strBuf.length}`); + } + return Name.get(strBuf.join("")); + } + + /** + * @private + */ + _hexStringWarn(ch) { + const MAX_HEX_STRING_NUM_WARN = 5; + + if (this._hexStringNumWarn++ === MAX_HEX_STRING_NUM_WARN) { + warn("getHexString - ignoring additional invalid characters."); + return; + } + if (this._hexStringNumWarn > MAX_HEX_STRING_NUM_WARN) { + // Limit the number of warning messages printed for a `this.getHexString` + // invocation, since corrupt PDF documents may otherwise spam the console + // enough to affect general performance negatively. + return; + } + warn(`getHexString - ignoring invalid character: ${ch}`); + } + + getHexString() { + const strBuf = this.strBuf; + strBuf.length = 0; + let ch = this.currentChar; + let firstDigit = -1, + digit = -1; + this._hexStringNumWarn = 0; + + while (true) { + if (ch < 0) { + warn("Unterminated hex string"); + break; + } else if (ch === /* '>' = */ 0x3e) { + this.nextChar(); + break; + } else if (specialChars[ch] === 1) { + ch = this.nextChar(); + continue; + } else { + digit = toHexDigit(ch); + if (digit === -1) { + this._hexStringWarn(ch); + } else if (firstDigit === -1) { + firstDigit = digit; + } else { + strBuf.push(String.fromCharCode((firstDigit << 4) | digit)); + firstDigit = -1; + } + ch = this.nextChar(); + } + } + + // According to the PDF spec, section "7.3.4.3 Hexadecimal Strings": + // "If the final digit of a hexadecimal string is missingโ€”that is, if there + // is an odd number of digitsโ€”the final digit shall be assumed to be 0." + if (firstDigit !== -1) { + strBuf.push(String.fromCharCode(firstDigit << 4)); + } + return strBuf.join(""); + } + + getObj() { + // Skip whitespace and comments. + let comment = false; + let ch = this.currentChar; + while (true) { + if (ch < 0) { + return EOF; + } + if (comment) { + if (ch === /* LF = */ 0x0a || ch === /* CR = */ 0x0d) { + comment = false; + } + } else if (ch === /* '%' = */ 0x25) { + comment = true; + } else if (specialChars[ch] !== 1) { + break; + } + ch = this.nextChar(); + } + + // Start reading a token. + switch (ch | 0) { + case 0x30: // '0' + case 0x31: // '1' + case 0x32: // '2' + case 0x33: // '3' + case 0x34: // '4' + case 0x35: // '5' + case 0x36: // '6' + case 0x37: // '7' + case 0x38: // '8' + case 0x39: // '9' + case 0x2b: // '+' + case 0x2d: // '-' + case 0x2e: // '.' + return this.getNumber(); + case 0x28: // '(' + return this.getString(); + case 0x2f: // '/' + return this.getName(); + // array punctuation + case 0x5b: // '[' + this.nextChar(); + return Cmd.get("["); + case 0x5d: // ']' + this.nextChar(); + return Cmd.get("]"); + // hex string or dict punctuation + case 0x3c: // '<' + ch = this.nextChar(); + if (ch === 0x3c) { + // dict punctuation + this.nextChar(); + return Cmd.get("<<"); + } + return this.getHexString(); + // dict punctuation + case 0x3e: // '>' + ch = this.nextChar(); + if (ch === 0x3e) { + this.nextChar(); + return Cmd.get(">>"); + } + return Cmd.get(">"); + case 0x7b: // '{' + this.nextChar(); + return Cmd.get("{"); + case 0x7d: // '}' + this.nextChar(); + return Cmd.get("}"); + case 0x29: // ')' + // Consume the current character in order to avoid permanently hanging + // the worker thread if `Lexer.getObj` is called from within a loop + // containing try-catch statements, since we would otherwise attempt + // to parse the *same* character over and over (fixes issue8061.pdf). + this.nextChar(); + throw new FormatError(`Illegal character: ${ch}`); + } + + // Start reading a command. + let str = String.fromCharCode(ch); + // A valid command cannot start with a non-visible ASCII character, + // and the next character may be (the start of) a valid command. + if (ch < 0x20 || ch > 0x7f) { + const nextCh = this.peekChar(); + if (nextCh >= 0x20 && nextCh <= 0x7f) { + this.nextChar(); + return Cmd.get(str); + } + } + const knownCommands = this.knownCommands; + let knownCommandFound = knownCommands?.[str] !== undefined; + while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) { + // Stop if a known command is found and next character does not make + // the string a command. + const possibleCommand = str + String.fromCharCode(ch); + if (knownCommandFound && knownCommands[possibleCommand] === undefined) { + break; + } + if (str.length === 128) { + throw new FormatError(`Command token too long: ${str.length}`); + } + str = possibleCommand; + knownCommandFound = knownCommands?.[str] !== undefined; + } + if (str === "true") { + return true; + } + if (str === "false") { + return false; + } + if (str === "null") { + return null; + } + + if (str === "BI") { + // Keep track of the current stream position, since it's needed in order + // to correctly cache inline images; see `Parser.makeInlineImage`. + this.beginInlineImagePos = this.stream.pos; + } + + return Cmd.get(str); + } + + skipToNextLine() { + let ch = this.currentChar; + while (ch >= 0) { + if (ch === /* CR = */ 0x0d) { + ch = this.nextChar(); + if (ch === /* LF = */ 0x0a) { + this.nextChar(); + } + break; + } else if (ch === /* LF = */ 0x0a) { + this.nextChar(); + break; + } + ch = this.nextChar(); + } + } +} + +class Linearization { + static create(stream) { + function getInt(linDict, name, allowZeroValue = false) { + const obj = linDict.get(name); + if (Number.isInteger(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) { + return obj; + } + throw new Error( + `The "${name}" parameter in the linearization ` + + "dictionary is invalid." + ); + } + + function getHints(linDict) { + const hints = linDict.get("H"); + let hintsLength; + + if ( + Array.isArray(hints) && + ((hintsLength = hints.length) === 2 || hintsLength === 4) + ) { + for (let index = 0; index < hintsLength; index++) { + const hint = hints[index]; + if (!(Number.isInteger(hint) && hint > 0)) { + throw new Error( + `Hint (${index}) in the linearization dictionary is invalid.` + ); + } + } + return hints; + } + throw new Error("Hint array in the linearization dictionary is invalid."); + } + + const parser = new Parser({ + lexer: new Lexer(stream), + xref: null, + }); + const obj1 = parser.getObj(); + const obj2 = parser.getObj(); + const obj3 = parser.getObj(); + const linDict = parser.getObj(); + let obj, length; + if ( + !( + Number.isInteger(obj1) && + Number.isInteger(obj2) && + isCmd(obj3, "obj") && + linDict instanceof Dict && + typeof (obj = linDict.get("Linearized")) === "number" && + obj > 0 + ) + ) { + return null; // No valid linearization dictionary found. + } else if ((length = getInt(linDict, "L")) !== stream.length) { + throw new Error( + 'The "L" parameter in the linearization dictionary ' + + "does not equal the stream length." + ); + } + return { + length, + hints: getHints(linDict), + objectNumberFirst: getInt(linDict, "O"), + endFirst: getInt(linDict, "E"), + numPages: getInt(linDict, "N"), + mainXRefEntriesOffset: getInt(linDict, "T"), + pageFirst: linDict.has("P") + ? getInt(linDict, "P", /* allowZeroValue = */ true) + : 0, + }; + } +} + +export { Lexer, Linearization, Parser }; diff --git a/frontend/public/pdf.js/src/core/pattern.js b/frontend/public/pdf.js/src/core/pattern.js new file mode 100644 index 0000000..598d5d2 --- /dev/null +++ b/frontend/public/pdf.js/src/core/pattern.js @@ -0,0 +1,1026 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + assert, + FormatError, + IDENTITY_MATRIX, + info, + unreachable, + Util, + warn, +} from "../shared/util.js"; +import { + isBooleanArray, + isNumberArray, + lookupMatrix, + lookupNormalRect, + MissingDataException, +} from "./core_utils.js"; +import { BaseStream } from "./base_stream.js"; +import { ColorSpace } from "./colorspace.js"; + +const ShadingType = { + FUNCTION_BASED: 1, + AXIAL: 2, + RADIAL: 3, + FREE_FORM_MESH: 4, + LATTICE_FORM_MESH: 5, + COONS_PATCH_MESH: 6, + TENSOR_PATCH_MESH: 7, +}; + +class Pattern { + constructor() { + unreachable("Cannot initialize Pattern."); + } + + static parseShading( + shading, + xref, + res, + pdfFunctionFactory, + localColorSpaceCache + ) { + const dict = shading instanceof BaseStream ? shading.dict : shading; + const type = dict.get("ShadingType"); + + try { + switch (type) { + case ShadingType.AXIAL: + case ShadingType.RADIAL: + return new RadialAxialShading( + dict, + xref, + res, + pdfFunctionFactory, + localColorSpaceCache + ); + case ShadingType.FREE_FORM_MESH: + case ShadingType.LATTICE_FORM_MESH: + case ShadingType.COONS_PATCH_MESH: + case ShadingType.TENSOR_PATCH_MESH: + return new MeshShading( + shading, + xref, + res, + pdfFunctionFactory, + localColorSpaceCache + ); + default: + throw new FormatError("Unsupported ShadingType: " + type); + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(ex); + return new DummyShading(); + } + } +} + +class BaseShading { + // A small number to offset the first/last color stops so we can insert ones + // to support extend. Number.MIN_VALUE is too small and breaks the extend. + static SMALL_NUMBER = 1e-6; + + constructor() { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === BaseShading + ) { + unreachable("Cannot initialize BaseShading."); + } + } + + getIR() { + unreachable("Abstract method `getIR` called."); + } +} + +// Radial and axial shading have very similar implementations +// If needed, the implementations can be broken into two classes. +class RadialAxialShading extends BaseShading { + constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) { + super(); + this.shadingType = dict.get("ShadingType"); + let coordsLen = 0; + if (this.shadingType === ShadingType.AXIAL) { + coordsLen = 4; + } else if (this.shadingType === ShadingType.RADIAL) { + coordsLen = 6; + } + this.coordsArr = dict.getArray("Coords"); + if (!isNumberArray(this.coordsArr, coordsLen)) { + throw new FormatError("RadialAxialShading: Invalid /Coords array."); + } + const cs = ColorSpace.parse({ + cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"), + xref, + resources, + pdfFunctionFactory, + localColorSpaceCache, + }); + this.bbox = lookupNormalRect(dict.getArray("BBox"), null); + + let t0 = 0.0, + t1 = 1.0; + const domainArr = dict.getArray("Domain"); + if (isNumberArray(domainArr, 2)) { + [t0, t1] = domainArr; + } + + let extendStart = false, + extendEnd = false; + const extendArr = dict.getArray("Extend"); + if (isBooleanArray(extendArr, 2)) { + [extendStart, extendEnd] = extendArr; + } + + if ( + this.shadingType === ShadingType.RADIAL && + (!extendStart || !extendEnd) + ) { + // Radial gradient only currently works if either circle is fully within + // the other circle. + const [x1, y1, r1, x2, y2, r2] = this.coordsArr; + const distance = Math.hypot(x1 - x2, y1 - y2); + if (r1 <= r2 + distance && r2 <= r1 + distance) { + warn("Unsupported radial gradient."); + } + } + + this.extendStart = extendStart; + this.extendEnd = extendEnd; + + const fnObj = dict.getRaw("Function"); + const fn = pdfFunctionFactory.createFromArray(fnObj); + + // Use lcm(1,2,3,4,5,6,7,8,10) = 840 (including 9 increases this to 2520) + // to catch evenly spaced stops. oeis.org/A003418 + const NUMBER_OF_SAMPLES = 840; + const step = (t1 - t0) / NUMBER_OF_SAMPLES; + + const colorStops = (this.colorStops = []); + + // Protect against bad domains. + if (t0 >= t1 || step <= 0) { + // Acrobat doesn't seem to handle these cases so we'll ignore for + // now. + info("Bad shading domain."); + return; + } + + const color = new Float32Array(cs.numComps), + ratio = new Float32Array(1); + let rgbColor; + + let iBase = 0; + ratio[0] = t0; + fn(ratio, 0, color, 0); + let rgbBase = cs.getRgb(color, 0); + const cssColorBase = Util.makeHexColor(rgbBase[0], rgbBase[1], rgbBase[2]); + colorStops.push([0, cssColorBase]); + + let iPrev = 1; + ratio[0] = t0 + step; + fn(ratio, 0, color, 0); + let rgbPrev = cs.getRgb(color, 0); + + // Slopes are rise / run. + // A max slope is from the least value the base component could have been + // to the greatest value the current component could have been. + // A min slope is from the greatest value the base component could have been + // to the least value the current component could have been. + // Each component could have been rounded up to .5 from its original value + // so the conservative deltas are +-1 (+-.5 for base and -+.5 for current). + + // The run is iPrev - iBase = 1, so omitted. + let maxSlopeR = rgbPrev[0] - rgbBase[0] + 1; + let maxSlopeG = rgbPrev[1] - rgbBase[1] + 1; + let maxSlopeB = rgbPrev[2] - rgbBase[2] + 1; + let minSlopeR = rgbPrev[0] - rgbBase[0] - 1; + let minSlopeG = rgbPrev[1] - rgbBase[1] - 1; + let minSlopeB = rgbPrev[2] - rgbBase[2] - 1; + + for (let i = 2; i < NUMBER_OF_SAMPLES; i++) { + ratio[0] = t0 + i * step; + fn(ratio, 0, color, 0); + rgbColor = cs.getRgb(color, 0); + + // Keep going if the maximum minimum slope <= the minimum maximum slope. + // Otherwise add a rgbPrev color stop and make it the new base. + + const run = i - iBase; + maxSlopeR = Math.min(maxSlopeR, (rgbColor[0] - rgbBase[0] + 1) / run); + maxSlopeG = Math.min(maxSlopeG, (rgbColor[1] - rgbBase[1] + 1) / run); + maxSlopeB = Math.min(maxSlopeB, (rgbColor[2] - rgbBase[2] + 1) / run); + minSlopeR = Math.max(minSlopeR, (rgbColor[0] - rgbBase[0] - 1) / run); + minSlopeG = Math.max(minSlopeG, (rgbColor[1] - rgbBase[1] - 1) / run); + minSlopeB = Math.max(minSlopeB, (rgbColor[2] - rgbBase[2] - 1) / run); + + const slopesExist = + minSlopeR <= maxSlopeR && + minSlopeG <= maxSlopeG && + minSlopeB <= maxSlopeB; + + if (!slopesExist) { + const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]); + colorStops.push([iPrev / NUMBER_OF_SAMPLES, cssColor]); + + // TODO: When fn frequency is high (iPrev - iBase === 1 twice in a row), + // send the color space and function to do the sampling display side. + + // The run is i - iPrev = 1, so omitted. + maxSlopeR = rgbColor[0] - rgbPrev[0] + 1; + maxSlopeG = rgbColor[1] - rgbPrev[1] + 1; + maxSlopeB = rgbColor[2] - rgbPrev[2] + 1; + minSlopeR = rgbColor[0] - rgbPrev[0] - 1; + minSlopeG = rgbColor[1] - rgbPrev[1] - 1; + minSlopeB = rgbColor[2] - rgbPrev[2] - 1; + + iBase = iPrev; + rgbBase = rgbPrev; + } + + iPrev = i; + rgbPrev = rgbColor; + } + const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]); + colorStops.push([1, cssColor]); + + let background = "transparent"; + if (dict.has("Background")) { + rgbColor = cs.getRgb(dict.get("Background"), 0); + background = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]); + } + + if (!extendStart) { + // Insert a color stop at the front and offset the first real color stop + // so it doesn't conflict with the one we insert. + colorStops.unshift([0, background]); + colorStops[1][0] += BaseShading.SMALL_NUMBER; + } + if (!extendEnd) { + // Same idea as above in extendStart but for the end. + colorStops.at(-1)[0] -= BaseShading.SMALL_NUMBER; + colorStops.push([1, background]); + } + + this.colorStops = colorStops; + } + + getIR() { + const { coordsArr, shadingType } = this; + let type, p0, p1, r0, r1; + if (shadingType === ShadingType.AXIAL) { + p0 = [coordsArr[0], coordsArr[1]]; + p1 = [coordsArr[2], coordsArr[3]]; + r0 = null; + r1 = null; + type = "axial"; + } else if (shadingType === ShadingType.RADIAL) { + p0 = [coordsArr[0], coordsArr[1]]; + p1 = [coordsArr[3], coordsArr[4]]; + r0 = coordsArr[2]; + r1 = coordsArr[5]; + type = "radial"; + } else { + unreachable(`getPattern type unknown: ${shadingType}`); + } + + return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1]; + } +} + +// All mesh shadings. For now, they will be presented as set of the triangles +// to be drawn on the canvas and rgb color for each vertex. +class MeshStreamReader { + constructor(stream, context) { + this.stream = stream; + this.context = context; + this.buffer = 0; + this.bufferLength = 0; + + const numComps = context.numComps; + this.tmpCompsBuf = new Float32Array(numComps); + const csNumComps = context.colorSpace.numComps; + this.tmpCsCompsBuf = context.colorFn + ? new Float32Array(csNumComps) + : this.tmpCompsBuf; + } + + get hasData() { + if (this.stream.end) { + return this.stream.pos < this.stream.end; + } + if (this.bufferLength > 0) { + return true; + } + const nextByte = this.stream.getByte(); + if (nextByte < 0) { + return false; + } + this.buffer = nextByte; + this.bufferLength = 8; + return true; + } + + readBits(n) { + const { stream } = this; + let { buffer, bufferLength } = this; + + if (n === 32) { + if (bufferLength === 0) { + return stream.getInt32() >>> 0; + } + buffer = + (buffer << 24) | + (stream.getByte() << 16) | + (stream.getByte() << 8) | + stream.getByte(); + const nextByte = stream.getByte(); + this.buffer = nextByte & ((1 << bufferLength) - 1); + return ( + ((buffer << (8 - bufferLength)) | + ((nextByte & 0xff) >> bufferLength)) >>> + 0 + ); + } + if (n === 8 && bufferLength === 0) { + return stream.getByte(); + } + while (bufferLength < n) { + buffer = (buffer << 8) | stream.getByte(); + bufferLength += 8; + } + bufferLength -= n; + this.bufferLength = bufferLength; + this.buffer = buffer & ((1 << bufferLength) - 1); + return buffer >> bufferLength; + } + + align() { + this.buffer = 0; + this.bufferLength = 0; + } + + readFlag() { + return this.readBits(this.context.bitsPerFlag); + } + + readCoordinate() { + const bitsPerCoordinate = this.context.bitsPerCoordinate; + const xi = this.readBits(bitsPerCoordinate); + const yi = this.readBits(bitsPerCoordinate); + const decode = this.context.decode; + const scale = + bitsPerCoordinate < 32 + ? 1 / ((1 << bitsPerCoordinate) - 1) + : 2.3283064365386963e-10; // 2 ^ -32 + return [ + xi * scale * (decode[1] - decode[0]) + decode[0], + yi * scale * (decode[3] - decode[2]) + decode[2], + ]; + } + + readComponents() { + const numComps = this.context.numComps; + const bitsPerComponent = this.context.bitsPerComponent; + const scale = + bitsPerComponent < 32 + ? 1 / ((1 << bitsPerComponent) - 1) + : 2.3283064365386963e-10; // 2 ^ -32 + const decode = this.context.decode; + const components = this.tmpCompsBuf; + for (let i = 0, j = 4; i < numComps; i++, j += 2) { + const ci = this.readBits(bitsPerComponent); + components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j]; + } + const color = this.tmpCsCompsBuf; + if (this.context.colorFn) { + this.context.colorFn(components, 0, color, 0); + } + return this.context.colorSpace.getRgb(color, 0); + } +} + +let bCache = Object.create(null); + +function buildB(count) { + const lut = []; + for (let i = 0; i <= count; i++) { + const t = i / count, + t_ = 1 - t; + lut.push( + new Float32Array([t_ ** 3, 3 * t * t_ ** 2, 3 * t ** 2 * t_, t ** 3]) + ); + } + return lut; +} +function getB(count) { + return (bCache[count] ||= buildB(count)); +} + +function clearPatternCaches() { + bCache = Object.create(null); +} + +class MeshShading extends BaseShading { + static MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3; + + static MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20; + + // Count of triangles per entire mesh bounds. + static TRIANGLE_DENSITY = 20; + + constructor( + stream, + xref, + resources, + pdfFunctionFactory, + localColorSpaceCache + ) { + super(); + if (!(stream instanceof BaseStream)) { + throw new FormatError("Mesh data is not a stream"); + } + const dict = stream.dict; + this.shadingType = dict.get("ShadingType"); + this.bbox = lookupNormalRect(dict.getArray("BBox"), null); + const cs = ColorSpace.parse({ + cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"), + xref, + resources, + pdfFunctionFactory, + localColorSpaceCache, + }); + this.background = dict.has("Background") + ? cs.getRgb(dict.get("Background"), 0) + : null; + + const fnObj = dict.getRaw("Function"); + const fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null; + + this.coords = []; + this.colors = []; + this.figures = []; + + const decodeContext = { + bitsPerCoordinate: dict.get("BitsPerCoordinate"), + bitsPerComponent: dict.get("BitsPerComponent"), + bitsPerFlag: dict.get("BitsPerFlag"), + decode: dict.getArray("Decode"), + colorFn: fn, + colorSpace: cs, + numComps: fn ? 1 : cs.numComps, + }; + const reader = new MeshStreamReader(stream, decodeContext); + + let patchMesh = false; + switch (this.shadingType) { + case ShadingType.FREE_FORM_MESH: + this._decodeType4Shading(reader); + break; + case ShadingType.LATTICE_FORM_MESH: + const verticesPerRow = dict.get("VerticesPerRow") | 0; + if (verticesPerRow < 2) { + throw new FormatError("Invalid VerticesPerRow"); + } + this._decodeType5Shading(reader, verticesPerRow); + break; + case ShadingType.COONS_PATCH_MESH: + this._decodeType6Shading(reader); + patchMesh = true; + break; + case ShadingType.TENSOR_PATCH_MESH: + this._decodeType7Shading(reader); + patchMesh = true; + break; + default: + unreachable("Unsupported mesh type."); + break; + } + + if (patchMesh) { + // Dirty bounds calculation, to determine how dense the triangles will be. + this._updateBounds(); + for (let i = 0, ii = this.figures.length; i < ii; i++) { + this._buildFigureFromPatch(i); + } + } + // Calculate bounds. + this._updateBounds(); + + this._packData(); + } + + _decodeType4Shading(reader) { + const coords = this.coords; + const colors = this.colors; + const operators = []; + const ps = []; // not maintaining cs since that will match ps + let verticesLeft = 0; // assuming we have all data to start a new triangle + while (reader.hasData) { + const f = reader.readFlag(); + const coord = reader.readCoordinate(); + const color = reader.readComponents(); + if (verticesLeft === 0) { + // ignoring flags if we started a triangle + if (!(0 <= f && f <= 2)) { + throw new FormatError("Unknown type4 flag"); + } + switch (f) { + case 0: + verticesLeft = 3; + break; + case 1: + ps.push(ps.at(-2), ps.at(-1)); + verticesLeft = 1; + break; + case 2: + ps.push(ps.at(-3), ps.at(-1)); + verticesLeft = 1; + break; + } + operators.push(f); + } + ps.push(coords.length); + coords.push(coord); + colors.push(color); + verticesLeft--; + + reader.align(); + } + this.figures.push({ + type: "triangles", + coords: new Int32Array(ps), + colors: new Int32Array(ps), + }); + } + + _decodeType5Shading(reader, verticesPerRow) { + const coords = this.coords; + const colors = this.colors; + const ps = []; // not maintaining cs since that will match ps + while (reader.hasData) { + const coord = reader.readCoordinate(); + const color = reader.readComponents(); + ps.push(coords.length); + coords.push(coord); + colors.push(color); + } + this.figures.push({ + type: "lattice", + coords: new Int32Array(ps), + colors: new Int32Array(ps), + verticesPerRow, + }); + } + + _decodeType6Shading(reader) { + // A special case of Type 7. The p11, p12, p21, p22 automatically filled + const coords = this.coords; + const colors = this.colors; + const ps = new Int32Array(16); // p00, p10, ..., p30, p01, ..., p33 + const cs = new Int32Array(4); // c00, c30, c03, c33 + while (reader.hasData) { + const f = reader.readFlag(); + if (!(0 <= f && f <= 3)) { + throw new FormatError("Unknown type6 flag"); + } + const pi = coords.length; + for (let i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) { + coords.push(reader.readCoordinate()); + } + const ci = colors.length; + for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) { + colors.push(reader.readComponents()); + } + let tmp1, tmp2, tmp3, tmp4; + switch (f) { + // prettier-ignore + case 0: + ps[12] = pi + 3; ps[13] = pi + 4; ps[14] = pi + 5; ps[15] = pi + 6; + ps[ 8] = pi + 2; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 7; + ps[ 4] = pi + 1; /* calculated below */ ps[ 7] = pi + 8; + ps[ 0] = pi; ps[ 1] = pi + 11; ps[ 2] = pi + 10; ps[ 3] = pi + 9; + cs[2] = ci + 1; cs[3] = ci + 2; + cs[0] = ci; cs[1] = ci + 3; + break; + // prettier-ignore + case 1: + tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15]; + ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; + ps[ 8] = tmp3; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3; + ps[ 4] = tmp2; /* calculated below */ ps[ 7] = pi + 4; + ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5; + tmp1 = cs[2]; tmp2 = cs[3]; + cs[2] = tmp2; cs[3] = ci; + cs[0] = tmp1; cs[1] = ci + 1; + break; + // prettier-ignore + case 2: + tmp1 = ps[15]; + tmp2 = ps[11]; + ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; + ps[ 8] = ps[7]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3; + ps[ 4] = tmp2; /* calculated below */ ps[ 7] = pi + 4; + ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5; + tmp1 = cs[3]; + cs[2] = cs[1]; cs[3] = ci; + cs[0] = tmp1; cs[1] = ci + 1; + break; + // prettier-ignore + case 3: + ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; + ps[ 8] = ps[1]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3; + ps[ 4] = ps[2]; /* calculated below */ ps[ 7] = pi + 4; + ps[ 0] = ps[3]; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5; + cs[2] = cs[0]; cs[3] = ci; + cs[0] = cs[1]; cs[1] = ci + 1; + break; + } + // set p11, p12, p21, p22 + ps[5] = coords.length; + coords.push([ + (-4 * coords[ps[0]][0] - + coords[ps[15]][0] + + 6 * (coords[ps[4]][0] + coords[ps[1]][0]) - + 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + + 3 * (coords[ps[13]][0] + coords[ps[7]][0])) / + 9, + (-4 * coords[ps[0]][1] - + coords[ps[15]][1] + + 6 * (coords[ps[4]][1] + coords[ps[1]][1]) - + 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + + 3 * (coords[ps[13]][1] + coords[ps[7]][1])) / + 9, + ]); + ps[6] = coords.length; + coords.push([ + (-4 * coords[ps[3]][0] - + coords[ps[12]][0] + + 6 * (coords[ps[2]][0] + coords[ps[7]][0]) - + 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + + 3 * (coords[ps[4]][0] + coords[ps[14]][0])) / + 9, + (-4 * coords[ps[3]][1] - + coords[ps[12]][1] + + 6 * (coords[ps[2]][1] + coords[ps[7]][1]) - + 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + + 3 * (coords[ps[4]][1] + coords[ps[14]][1])) / + 9, + ]); + ps[9] = coords.length; + coords.push([ + (-4 * coords[ps[12]][0] - + coords[ps[3]][0] + + 6 * (coords[ps[8]][0] + coords[ps[13]][0]) - + 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + + 3 * (coords[ps[11]][0] + coords[ps[1]][0])) / + 9, + (-4 * coords[ps[12]][1] - + coords[ps[3]][1] + + 6 * (coords[ps[8]][1] + coords[ps[13]][1]) - + 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + + 3 * (coords[ps[11]][1] + coords[ps[1]][1])) / + 9, + ]); + ps[10] = coords.length; + coords.push([ + (-4 * coords[ps[15]][0] - + coords[ps[0]][0] + + 6 * (coords[ps[11]][0] + coords[ps[14]][0]) - + 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + + 3 * (coords[ps[2]][0] + coords[ps[8]][0])) / + 9, + (-4 * coords[ps[15]][1] - + coords[ps[0]][1] + + 6 * (coords[ps[11]][1] + coords[ps[14]][1]) - + 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + + 3 * (coords[ps[2]][1] + coords[ps[8]][1])) / + 9, + ]); + this.figures.push({ + type: "patch", + coords: new Int32Array(ps), // making copies of ps and cs + colors: new Int32Array(cs), + }); + } + } + + _decodeType7Shading(reader) { + const coords = this.coords; + const colors = this.colors; + const ps = new Int32Array(16); // p00, p10, ..., p30, p01, ..., p33 + const cs = new Int32Array(4); // c00, c30, c03, c33 + while (reader.hasData) { + const f = reader.readFlag(); + if (!(0 <= f && f <= 3)) { + throw new FormatError("Unknown type7 flag"); + } + const pi = coords.length; + for (let i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) { + coords.push(reader.readCoordinate()); + } + const ci = colors.length; + for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) { + colors.push(reader.readComponents()); + } + let tmp1, tmp2, tmp3, tmp4; + switch (f) { + // prettier-ignore + case 0: + ps[12] = pi + 3; ps[13] = pi + 4; ps[14] = pi + 5; ps[15] = pi + 6; + ps[ 8] = pi + 2; ps[ 9] = pi + 13; ps[10] = pi + 14; ps[11] = pi + 7; + ps[ 4] = pi + 1; ps[ 5] = pi + 12; ps[ 6] = pi + 15; ps[ 7] = pi + 8; + ps[ 0] = pi; ps[ 1] = pi + 11; ps[ 2] = pi + 10; ps[ 3] = pi + 9; + cs[2] = ci + 1; cs[3] = ci + 2; + cs[0] = ci; cs[1] = ci + 3; + break; + // prettier-ignore + case 1: + tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15]; + ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; + ps[ 8] = tmp3; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3; + ps[ 4] = tmp2; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4; + ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5; + tmp1 = cs[2]; tmp2 = cs[3]; + cs[2] = tmp2; cs[3] = ci; + cs[0] = tmp1; cs[1] = ci + 1; + break; + // prettier-ignore + case 2: + tmp1 = ps[15]; + tmp2 = ps[11]; + ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; + ps[ 8] = ps[7]; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3; + ps[ 4] = tmp2; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4; + ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5; + tmp1 = cs[3]; + cs[2] = cs[1]; cs[3] = ci; + cs[0] = tmp1; cs[1] = ci + 1; + break; + // prettier-ignore + case 3: + ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2; + ps[ 8] = ps[1]; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3; + ps[ 4] = ps[2]; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4; + ps[ 0] = ps[3]; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5; + cs[2] = cs[0]; cs[3] = ci; + cs[0] = cs[1]; cs[1] = ci + 1; + break; + } + this.figures.push({ + type: "patch", + coords: new Int32Array(ps), // making copies of ps and cs + colors: new Int32Array(cs), + }); + } + } + + _buildFigureFromPatch(index) { + const figure = this.figures[index]; + assert(figure.type === "patch", "Unexpected patch mesh figure"); + + const coords = this.coords, + colors = this.colors; + const pi = figure.coords; + const ci = figure.colors; + + const figureMinX = Math.min( + coords[pi[0]][0], + coords[pi[3]][0], + coords[pi[12]][0], + coords[pi[15]][0] + ); + const figureMinY = Math.min( + coords[pi[0]][1], + coords[pi[3]][1], + coords[pi[12]][1], + coords[pi[15]][1] + ); + const figureMaxX = Math.max( + coords[pi[0]][0], + coords[pi[3]][0], + coords[pi[12]][0], + coords[pi[15]][0] + ); + const figureMaxY = Math.max( + coords[pi[0]][1], + coords[pi[3]][1], + coords[pi[12]][1], + coords[pi[15]][1] + ); + let splitXBy = Math.ceil( + ((figureMaxX - figureMinX) * MeshShading.TRIANGLE_DENSITY) / + (this.bounds[2] - this.bounds[0]) + ); + splitXBy = Math.max( + MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, + Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy) + ); + let splitYBy = Math.ceil( + ((figureMaxY - figureMinY) * MeshShading.TRIANGLE_DENSITY) / + (this.bounds[3] - this.bounds[1]) + ); + splitYBy = Math.max( + MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, + Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy) + ); + + const verticesPerRow = splitXBy + 1; + const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow); + const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow); + let k = 0; + const cl = new Uint8Array(3), + cr = new Uint8Array(3); + const c0 = colors[ci[0]], + c1 = colors[ci[1]], + c2 = colors[ci[2]], + c3 = colors[ci[3]]; + const bRow = getB(splitYBy), + bCol = getB(splitXBy); + for (let row = 0; row <= splitYBy; row++) { + cl[0] = ((c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy) | 0; + cl[1] = ((c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy) | 0; + cl[2] = ((c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy) | 0; + + cr[0] = ((c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy) | 0; + cr[1] = ((c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy) | 0; + cr[2] = ((c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy) | 0; + + for (let col = 0; col <= splitXBy; col++, k++) { + if ( + (row === 0 || row === splitYBy) && + (col === 0 || col === splitXBy) + ) { + continue; + } + let x = 0, + y = 0; + let q = 0; + for (let i = 0; i <= 3; i++) { + for (let j = 0; j <= 3; j++, q++) { + const m = bRow[row][i] * bCol[col][j]; + x += coords[pi[q]][0] * m; + y += coords[pi[q]][1] * m; + } + } + figureCoords[k] = coords.length; + coords.push([x, y]); + figureColors[k] = colors.length; + const newColor = new Uint8Array(3); + newColor[0] = ((cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy) | 0; + newColor[1] = ((cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy) | 0; + newColor[2] = ((cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy) | 0; + colors.push(newColor); + } + } + figureCoords[0] = pi[0]; + figureColors[0] = ci[0]; + figureCoords[splitXBy] = pi[3]; + figureColors[splitXBy] = ci[1]; + figureCoords[verticesPerRow * splitYBy] = pi[12]; + figureColors[verticesPerRow * splitYBy] = ci[2]; + figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15]; + figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3]; + + this.figures[index] = { + type: "lattice", + coords: figureCoords, + colors: figureColors, + verticesPerRow, + }; + } + + _updateBounds() { + let minX = this.coords[0][0], + minY = this.coords[0][1], + maxX = minX, + maxY = minY; + for (let i = 1, ii = this.coords.length; i < ii; i++) { + const x = this.coords[i][0], + y = this.coords[i][1]; + minX = minX > x ? x : minX; + minY = minY > y ? y : minY; + maxX = maxX < x ? x : maxX; + maxY = maxY < y ? y : maxY; + } + this.bounds = [minX, minY, maxX, maxY]; + } + + _packData() { + let i, ii, j, jj; + + const coords = this.coords; + const coordsPacked = new Float32Array(coords.length * 2); + for (i = 0, j = 0, ii = coords.length; i < ii; i++) { + const xy = coords[i]; + coordsPacked[j++] = xy[0]; + coordsPacked[j++] = xy[1]; + } + this.coords = coordsPacked; + + const colors = this.colors; + const colorsPacked = new Uint8Array(colors.length * 3); + for (i = 0, j = 0, ii = colors.length; i < ii; i++) { + const c = colors[i]; + colorsPacked[j++] = c[0]; + colorsPacked[j++] = c[1]; + colorsPacked[j++] = c[2]; + } + this.colors = colorsPacked; + + const figures = this.figures; + for (i = 0, ii = figures.length; i < ii; i++) { + const figure = figures[i], + ps = figure.coords, + cs = figure.colors; + for (j = 0, jj = ps.length; j < jj; j++) { + ps[j] *= 2; + cs[j] *= 3; + } + } + } + + getIR() { + const { bounds } = this; + // Ensure that the shading has non-zero width and height, to prevent errors + // in `pattern_helper.js` (fixes issue17848.pdf). + if (bounds[2] - bounds[0] === 0 || bounds[3] - bounds[1] === 0) { + throw new FormatError(`Invalid MeshShading bounds: [${bounds}].`); + } + + return [ + "Mesh", + this.shadingType, + this.coords, + this.colors, + this.figures, + bounds, + this.bbox, + this.background, + ]; + } +} + +class DummyShading extends BaseShading { + getIR() { + return ["Dummy"]; + } +} + +function getTilingPatternIR(operatorList, dict, color) { + const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX); + const bbox = lookupNormalRect(dict.getArray("BBox"), null); + // Ensure that the pattern has a non-zero width and height, to prevent errors + // in `pattern_helper.js` (fixes issue8330.pdf). + if (!bbox || bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) { + throw new FormatError(`Invalid getTilingPatternIR /BBox array.`); + } + const xstep = dict.get("XStep"); + if (typeof xstep !== "number") { + throw new FormatError(`Invalid getTilingPatternIR /XStep value.`); + } + const ystep = dict.get("YStep"); + if (typeof ystep !== "number") { + throw new FormatError(`Invalid getTilingPatternIR /YStep value.`); + } + const paintType = dict.get("PaintType"); + if (!Number.isInteger(paintType)) { + throw new FormatError(`Invalid getTilingPatternIR /PaintType value.`); + } + const tilingType = dict.get("TilingType"); + if (!Number.isInteger(tilingType)) { + throw new FormatError(`Invalid getTilingPatternIR /TilingType value.`); + } + + return [ + "TilingPattern", + color, + operatorList, + matrix, + bbox, + xstep, + ystep, + paintType, + tilingType, + ]; +} + +export { clearPatternCaches, getTilingPatternIR, Pattern }; diff --git a/frontend/public/pdf.js/src/core/pdf_manager.js b/frontend/public/pdf.js/src/core/pdf_manager.js new file mode 100644 index 0000000..3b277ea --- /dev/null +++ b/frontend/public/pdf.js/src/core/pdf_manager.js @@ -0,0 +1,230 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + createValidAbsoluteUrl, + FeatureTest, + unreachable, + warn, +} from "../shared/util.js"; +import { ChunkedStreamManager } from "./chunked_stream.js"; +import { ImageResizer } from "./image_resizer.js"; +import { JpegStream } from "./jpeg_stream.js"; +import { JpxImage } from "./jpx.js"; +import { MissingDataException } from "./core_utils.js"; +import { PDFDocument } from "./document.js"; +import { Stream } from "./stream.js"; + +function parseDocBaseUrl(url) { + if (url) { + const absoluteUrl = createValidAbsoluteUrl(url); + if (absoluteUrl) { + return absoluteUrl.href; + } + warn(`Invalid absolute docBaseUrl: "${url}".`); + } + return null; +} + +class BasePdfManager { + constructor({ + // source, + // disableAutoFetch, + docBaseUrl, + docId, + enableXfa, + evaluatorOptions, + handler, + // length, + password, + // rangeChunkSize, + }) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + this.constructor === BasePdfManager + ) { + unreachable("Cannot initialize BasePdfManager."); + } + this._docBaseUrl = parseDocBaseUrl(docBaseUrl); + this._docId = docId; + this._password = password; + this.enableXfa = enableXfa; + + // Check `OffscreenCanvas` and `ImageDecoder` support once, + // rather than repeatedly throughout the worker-thread code. + evaluatorOptions.isOffscreenCanvasSupported &&= + FeatureTest.isOffscreenCanvasSupported; + evaluatorOptions.isImageDecoderSupported &&= + FeatureTest.isImageDecoderSupported; + this.evaluatorOptions = Object.freeze(evaluatorOptions); + + // Initially image-options once per document. + ImageResizer.setOptions(evaluatorOptions); + JpegStream.setOptions(evaluatorOptions); + JpxImage.setOptions({ ...evaluatorOptions, handler }); + } + + get docId() { + return this._docId; + } + + get password() { + return this._password; + } + + get docBaseUrl() { + return this._docBaseUrl; + } + + get catalog() { + return this.pdfDocument.catalog; + } + + ensureDoc(prop, args) { + return this.ensure(this.pdfDocument, prop, args); + } + + ensureXRef(prop, args) { + return this.ensure(this.pdfDocument.xref, prop, args); + } + + ensureCatalog(prop, args) { + return this.ensure(this.pdfDocument.catalog, prop, args); + } + + getPage(pageIndex) { + return this.pdfDocument.getPage(pageIndex); + } + + fontFallback(id, handler) { + return this.pdfDocument.fontFallback(id, handler); + } + + loadXfaFonts(handler, task) { + return this.pdfDocument.loadXfaFonts(handler, task); + } + + loadXfaImages() { + return this.pdfDocument.loadXfaImages(); + } + + serializeXfaData(annotationStorage) { + return this.pdfDocument.serializeXfaData(annotationStorage); + } + + cleanup(manuallyTriggered = false) { + return this.pdfDocument.cleanup(manuallyTriggered); + } + + async ensure(obj, prop, args) { + unreachable("Abstract method `ensure` called"); + } + + requestRange(begin, end) { + unreachable("Abstract method `requestRange` called"); + } + + requestLoadedStream(noFetch = false) { + unreachable("Abstract method `requestLoadedStream` called"); + } + + sendProgressiveData(chunk) { + unreachable("Abstract method `sendProgressiveData` called"); + } + + updatePassword(password) { + this._password = password; + } + + terminate(reason) { + unreachable("Abstract method `terminate` called"); + } +} + +class LocalPdfManager extends BasePdfManager { + constructor(args) { + super(args); + + const stream = new Stream(args.source); + this.pdfDocument = new PDFDocument(this, stream); + this._loadedStreamPromise = Promise.resolve(stream); + } + + async ensure(obj, prop, args) { + const value = obj[prop]; + if (typeof value === "function") { + return value.apply(obj, args); + } + return value; + } + + requestRange(begin, end) { + return Promise.resolve(); + } + + requestLoadedStream(noFetch = false) { + return this._loadedStreamPromise; + } + + terminate(reason) {} +} + +class NetworkPdfManager extends BasePdfManager { + constructor(args) { + super(args); + + this.streamManager = new ChunkedStreamManager(args.source, { + msgHandler: args.handler, + length: args.length, + disableAutoFetch: args.disableAutoFetch, + rangeChunkSize: args.rangeChunkSize, + }); + this.pdfDocument = new PDFDocument(this, this.streamManager.getStream()); + } + + async ensure(obj, prop, args) { + try { + const value = obj[prop]; + if (typeof value === "function") { + return value.apply(obj, args); + } + return value; + } catch (ex) { + if (!(ex instanceof MissingDataException)) { + throw ex; + } + await this.requestRange(ex.begin, ex.end); + return this.ensure(obj, prop, args); + } + } + + requestRange(begin, end) { + return this.streamManager.requestRange(begin, end); + } + + requestLoadedStream(noFetch = false) { + return this.streamManager.requestAllChunks(noFetch); + } + + sendProgressiveData(chunk) { + this.streamManager.onReceiveData({ chunk }); + } + + terminate(reason) { + this.streamManager.abort(reason); + } +} + +export { LocalPdfManager, NetworkPdfManager }; diff --git a/frontend/public/pdf.js/src/core/predictor_stream.js b/frontend/public/pdf.js/src/core/predictor_stream.js new file mode 100644 index 0000000..ac807a3 --- /dev/null +++ b/frontend/public/pdf.js/src/core/predictor_stream.js @@ -0,0 +1,234 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { DecodeStream } from "./decode_stream.js"; +import { Dict } from "./primitives.js"; +import { FormatError } from "../shared/util.js"; + +class PredictorStream extends DecodeStream { + constructor(str, maybeLength, params) { + super(maybeLength); + + if (!(params instanceof Dict)) { + return str; // no prediction + } + const predictor = (this.predictor = params.get("Predictor") || 1); + + if (predictor <= 1) { + return str; // no prediction + } + if (predictor !== 2 && (predictor < 10 || predictor > 15)) { + throw new FormatError(`Unsupported predictor: ${predictor}`); + } + + this.readBlock = predictor === 2 ? this.readBlockTiff : this.readBlockPng; + + this.str = str; + this.dict = str.dict; + + const colors = (this.colors = params.get("Colors") || 1); + const bits = (this.bits = params.get("BPC", "BitsPerComponent") || 8); + const columns = (this.columns = params.get("Columns") || 1); + + this.pixBytes = (colors * bits + 7) >> 3; + this.rowBytes = (columns * colors * bits + 7) >> 3; + + return this; + } + + readBlockTiff() { + const rowBytes = this.rowBytes; + + const bufferLength = this.bufferLength; + const buffer = this.ensureBuffer(bufferLength + rowBytes); + + const bits = this.bits; + const colors = this.colors; + + const rawBytes = this.str.getBytes(rowBytes); + this.eof = !rawBytes.length; + if (this.eof) { + return; + } + + let inbuf = 0, + outbuf = 0; + let inbits = 0, + outbits = 0; + let pos = bufferLength; + let i; + + if (bits === 1 && colors === 1) { + // Optimized version of the loop in the "else"-branch + // for 1 bit-per-component and 1 color TIFF images. + for (i = 0; i < rowBytes; ++i) { + let c = rawBytes[i] ^ inbuf; + c ^= c >> 1; + c ^= c >> 2; + c ^= c >> 4; + inbuf = (c & 1) << 7; + buffer[pos++] = c; + } + } else if (bits === 8) { + for (i = 0; i < colors; ++i) { + buffer[pos++] = rawBytes[i]; + } + for (; i < rowBytes; ++i) { + buffer[pos] = buffer[pos - colors] + rawBytes[i]; + pos++; + } + } else if (bits === 16) { + const bytesPerPixel = colors * 2; + for (i = 0; i < bytesPerPixel; ++i) { + buffer[pos++] = rawBytes[i]; + } + for (; i < rowBytes; i += 2) { + const sum = + ((rawBytes[i] & 0xff) << 8) + + (rawBytes[i + 1] & 0xff) + + ((buffer[pos - bytesPerPixel] & 0xff) << 8) + + (buffer[pos - bytesPerPixel + 1] & 0xff); + buffer[pos++] = (sum >> 8) & 0xff; + buffer[pos++] = sum & 0xff; + } + } else { + const compArray = new Uint8Array(colors + 1); + const bitMask = (1 << bits) - 1; + let j = 0, + k = bufferLength; + const columns = this.columns; + for (i = 0; i < columns; ++i) { + for (let kk = 0; kk < colors; ++kk) { + if (inbits < bits) { + inbuf = (inbuf << 8) | (rawBytes[j++] & 0xff); + inbits += 8; + } + compArray[kk] = + (compArray[kk] + (inbuf >> (inbits - bits))) & bitMask; + inbits -= bits; + outbuf = (outbuf << bits) | compArray[kk]; + outbits += bits; + if (outbits >= 8) { + buffer[k++] = (outbuf >> (outbits - 8)) & 0xff; + outbits -= 8; + } + } + } + if (outbits > 0) { + buffer[k++] = + (outbuf << (8 - outbits)) + (inbuf & ((1 << (8 - outbits)) - 1)); + } + } + this.bufferLength += rowBytes; + } + + readBlockPng() { + const rowBytes = this.rowBytes; + const pixBytes = this.pixBytes; + + const predictor = this.str.getByte(); + const rawBytes = this.str.getBytes(rowBytes); + this.eof = !rawBytes.length; + if (this.eof) { + return; + } + + const bufferLength = this.bufferLength; + const buffer = this.ensureBuffer(bufferLength + rowBytes); + + let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); + if (prevRow.length === 0) { + prevRow = new Uint8Array(rowBytes); + } + + let i, + j = bufferLength, + up, + c; + switch (predictor) { + case 0: + for (i = 0; i < rowBytes; ++i) { + buffer[j++] = rawBytes[i]; + } + break; + case 1: + for (i = 0; i < pixBytes; ++i) { + buffer[j++] = rawBytes[i]; + } + for (; i < rowBytes; ++i) { + buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xff; + j++; + } + break; + case 2: + for (i = 0; i < rowBytes; ++i) { + buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xff; + } + break; + case 3: + for (i = 0; i < pixBytes; ++i) { + buffer[j++] = (prevRow[i] >> 1) + rawBytes[i]; + } + for (; i < rowBytes; ++i) { + buffer[j] = + (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & 0xff; + j++; + } + break; + case 4: + // we need to save the up left pixels values. the simplest way + // is to create a new buffer + for (i = 0; i < pixBytes; ++i) { + up = prevRow[i]; + c = rawBytes[i]; + buffer[j++] = up + c; + } + for (; i < rowBytes; ++i) { + up = prevRow[i]; + const upLeft = prevRow[i - pixBytes]; + const left = buffer[j - pixBytes]; + const p = left + up - upLeft; + + let pa = p - left; + if (pa < 0) { + pa = -pa; + } + let pb = p - up; + if (pb < 0) { + pb = -pb; + } + let pc = p - upLeft; + if (pc < 0) { + pc = -pc; + } + + c = rawBytes[i]; + if (pa <= pb && pa <= pc) { + buffer[j++] = left + c; + } else if (pb <= pc) { + buffer[j++] = up + c; + } else { + buffer[j++] = upLeft + c; + } + } + break; + default: + throw new FormatError(`Unsupported predictor: ${predictor}`); + } + this.bufferLength += rowBytes; + } +} + +export { PredictorStream }; diff --git a/frontend/public/pdf.js/src/core/primitives.js b/frontend/public/pdf.js/src/core/primitives.js new file mode 100644 index 0000000..c48a34e --- /dev/null +++ b/frontend/public/pdf.js/src/core/primitives.js @@ -0,0 +1,440 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { assert, shadow, unreachable } from "../shared/util.js"; + +const CIRCULAR_REF = Symbol("CIRCULAR_REF"); +const EOF = Symbol("EOF"); + +let CmdCache = Object.create(null); +let NameCache = Object.create(null); +let RefCache = Object.create(null); + +function clearPrimitiveCaches() { + CmdCache = Object.create(null); + NameCache = Object.create(null); + RefCache = Object.create(null); +} + +class Name { + constructor(name) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + typeof name !== "string" + ) { + unreachable('Name: The "name" must be a string.'); + } + this.name = name; + } + + static get(name) { + // eslint-disable-next-line no-restricted-syntax + return (NameCache[name] ||= new Name(name)); + } +} + +class Cmd { + constructor(cmd) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + typeof cmd !== "string" + ) { + unreachable('Cmd: The "cmd" must be a string.'); + } + this.cmd = cmd; + } + + static get(cmd) { + // eslint-disable-next-line no-restricted-syntax + return (CmdCache[cmd] ||= new Cmd(cmd)); + } +} + +const nonSerializable = function nonSerializableClosure() { + return nonSerializable; // Creating closure on some variable. +}; + +class Dict { + constructor(xref = null) { + // Map should only be used internally, use functions below to access. + this._map = new Map(); + this.xref = xref; + this.objId = null; + this.suppressEncryption = false; + this.__nonSerializable__ = nonSerializable; // Disable cloning of the Dict. + } + + assignXref(newXref) { + this.xref = newXref; + } + + get size() { + return this._map.size; + } + + // Automatically dereferences Ref objects. + get(key1, key2, key3) { + let value = this._map.get(key1); + if (value === undefined && key2 !== undefined) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + key2.length < key1.length + ) { + unreachable("Dict.get: Expected keys to be ordered by length."); + } + value = this._map.get(key2); + if (value === undefined && key3 !== undefined) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + key3.length < key2.length + ) { + unreachable("Dict.get: Expected keys to be ordered by length."); + } + value = this._map.get(key3); + } + } + if (value instanceof Ref && this.xref) { + return this.xref.fetch(value, this.suppressEncryption); + } + return value; + } + + // Same as get(), but returns a promise and uses fetchIfRefAsync(). + async getAsync(key1, key2, key3) { + let value = this._map.get(key1); + if (value === undefined && key2 !== undefined) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + key2.length < key1.length + ) { + unreachable("Dict.getAsync: Expected keys to be ordered by length."); + } + value = this._map.get(key2); + if (value === undefined && key3 !== undefined) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + key3.length < key2.length + ) { + unreachable("Dict.getAsync: Expected keys to be ordered by length."); + } + value = this._map.get(key3); + } + } + if (value instanceof Ref && this.xref) { + return this.xref.fetchAsync(value, this.suppressEncryption); + } + return value; + } + + // Same as get(), but dereferences all elements if the result is an Array. + getArray(key1, key2, key3) { + let value = this._map.get(key1); + if (value === undefined && key2 !== undefined) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + key2.length < key1.length + ) { + unreachable("Dict.getArray: Expected keys to be ordered by length."); + } + value = this._map.get(key2); + if (value === undefined && key3 !== undefined) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + key3.length < key2.length + ) { + unreachable("Dict.getArray: Expected keys to be ordered by length."); + } + value = this._map.get(key3); + } + } + if (value instanceof Ref && this.xref) { + value = this.xref.fetch(value, this.suppressEncryption); + } + + if (Array.isArray(value)) { + value = value.slice(); // Ensure that we don't modify the Dict data. + for (let i = 0, ii = value.length; i < ii; i++) { + if (value[i] instanceof Ref && this.xref) { + value[i] = this.xref.fetch(value[i], this.suppressEncryption); + } + } + } + return value; + } + + // No dereferencing. + getRaw(key) { + return this._map.get(key); + } + + getKeys() { + return [...this._map.keys()]; + } + + // No dereferencing. + getRawValues() { + return [...this._map.values()]; + } + + set(key, value) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + if (typeof key !== "string") { + unreachable('Dict.set: The "key" must be a string.'); + } else if (value === undefined) { + unreachable('Dict.set: The "value" cannot be undefined.'); + } + } + this._map.set(key, value); + } + + has(key) { + return this._map.has(key); + } + + *[Symbol.iterator]() { + for (const [key, value] of this._map) { + yield [ + key, + value instanceof Ref && this.xref + ? this.xref.fetch(value, this.suppressEncryption) + : value, + ]; + } + } + + static get empty() { + const emptyDict = new Dict(null); + + emptyDict.set = (key, value) => { + unreachable("Should not call `set` on the empty dictionary."); + }; + return shadow(this, "empty", emptyDict); + } + + static merge({ xref, dictArray, mergeSubDicts = false }) { + const mergedDict = new Dict(xref), + properties = new Map(); + + for (const dict of dictArray) { + if (!(dict instanceof Dict)) { + continue; + } + for (const [key, value] of dict._map) { + let property = properties.get(key); + if (property === undefined) { + property = []; + properties.set(key, property); + } else if (!mergeSubDicts || !(value instanceof Dict)) { + // Ignore additional entries, if either: + // - This is a "shallow" merge, where only the first element matters. + // - The value is *not* a `Dict`, since other types cannot be merged. + continue; + } + property.push(value); + } + } + for (const [name, values] of properties) { + if (values.length === 1 || !(values[0] instanceof Dict)) { + mergedDict._map.set(name, values[0]); + continue; + } + const subDict = new Dict(xref); + + for (const dict of values) { + for (const [key, value] of dict._map) { + if (!subDict._map.has(key)) { + subDict._map.set(key, value); + } + } + } + if (subDict.size > 0) { + mergedDict._map.set(name, subDict); + } + } + properties.clear(); + + return mergedDict.size > 0 ? mergedDict : Dict.empty; + } + + clone() { + const dict = new Dict(this.xref); + for (const key of this.getKeys()) { + dict.set(key, this.getRaw(key)); + } + return dict; + } + + delete(key) { + delete this._map[key]; + } +} + +class Ref { + constructor(num, gen) { + this.num = num; + this.gen = gen; + } + + toString() { + // This function is hot, so we make the string as compact as possible. + // |this.gen| is almost always zero, so we treat that case specially. + if (this.gen === 0) { + return `${this.num}R`; + } + 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 + return (RefCache[key] ||= new Ref(num, gen)); + } +} + +// The reference is identified by number and generation. +// This structure stores only one instance of the reference. +class RefSet { + constructor(parent = null) { + if ( + (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) && + parent && + !(parent instanceof RefSet) + ) { + unreachable('RefSet: Invalid "parent" value.'); + } + this._set = new Set(parent?._set); + } + + has(ref) { + return this._set.has(ref.toString()); + } + + put(ref) { + this._set.add(ref.toString()); + } + + remove(ref) { + this._set.delete(ref.toString()); + } + + [Symbol.iterator]() { + return this._set.values(); + } + + clear() { + this._set.clear(); + } +} + +class RefSetCache { + constructor() { + this._map = new Map(); + } + + get size() { + return this._map.size; + } + + get(ref) { + return this._map.get(ref.toString()); + } + + has(ref) { + return this._map.has(ref.toString()); + } + + put(ref, obj) { + this._map.set(ref.toString(), obj); + } + + putAlias(ref, aliasRef) { + this._map.set(ref.toString(), this.get(aliasRef)); + } + + [Symbol.iterator]() { + return this._map.values(); + } + + clear() { + this._map.clear(); + } + + *values() { + yield* this._map.values(); + } + + *items() { + for (const [ref, value] of this._map) { + yield [Ref.fromString(ref), value]; + } + } +} + +function isName(v, name) { + return v instanceof Name && (name === undefined || v.name === name); +} + +function isCmd(v, cmd) { + return v instanceof Cmd && (cmd === undefined || v.cmd === cmd); +} + +function isDict(v, type) { + return ( + v instanceof Dict && (type === undefined || isName(v.get("Type"), type)) + ); +} + +function isRefsEqual(v1, v2) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + v1 instanceof Ref && v2 instanceof Ref, + "isRefsEqual: Both parameters should be `Ref`s." + ); + } + return v1.num === v2.num && v1.gen === v2.gen; +} + +export { + CIRCULAR_REF, + clearPrimitiveCaches, + Cmd, + Dict, + EOF, + isCmd, + isDict, + isName, + isRefsEqual, + Name, + Ref, + RefSet, + RefSetCache, +}; diff --git a/frontend/public/pdf.js/src/core/ps_parser.js b/frontend/public/pdf.js/src/core/ps_parser.js new file mode 100644 index 0000000..7ab24e7 --- /dev/null +++ b/frontend/public/pdf.js/src/core/ps_parser.js @@ -0,0 +1,268 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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 { FormatError, shadow } from "../shared/util.js"; +import { EOF } from "./primitives.js"; +import { isWhiteSpace } from "./core_utils.js"; + +class PostScriptParser { + constructor(lexer) { + this.lexer = lexer; + this.operators = []; + this.token = null; + this.prev = null; + } + + nextToken() { + this.prev = this.token; + this.token = this.lexer.getToken(); + } + + accept(type) { + if (this.token.type === type) { + this.nextToken(); + return true; + } + return false; + } + + expect(type) { + if (this.accept(type)) { + return true; + } + throw new FormatError( + `Unexpected symbol: found ${this.token.type} expected ${type}.` + ); + } + + parse() { + this.nextToken(); + this.expect(PostScriptTokenTypes.LBRACE); + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + return this.operators; + } + + parseBlock() { + while (true) { + if (this.accept(PostScriptTokenTypes.NUMBER)) { + this.operators.push(this.prev.value); + } else if (this.accept(PostScriptTokenTypes.OPERATOR)) { + this.operators.push(this.prev.value); + } else if (this.accept(PostScriptTokenTypes.LBRACE)) { + this.parseCondition(); + } else { + return; + } + } + } + + parseCondition() { + // Add two place holders that will be updated later + const conditionLocation = this.operators.length; + this.operators.push(null, null); + + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + if (this.accept(PostScriptTokenTypes.IF)) { + // The true block is right after the 'if' so it just falls through on true + // else it jumps and skips the true block. + this.operators[conditionLocation] = this.operators.length; + this.operators[conditionLocation + 1] = "jz"; + } else if (this.accept(PostScriptTokenTypes.LBRACE)) { + const jumpLocation = this.operators.length; + this.operators.push(null, null); + const endOfTrue = this.operators.length; + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + this.expect(PostScriptTokenTypes.IFELSE); + // The jump is added at the end of the true block to skip the false block. + this.operators[jumpLocation] = this.operators.length; + this.operators[jumpLocation + 1] = "j"; + + this.operators[conditionLocation] = endOfTrue; + this.operators[conditionLocation + 1] = "jz"; + } else { + throw new FormatError("PS Function: error parsing conditional."); + } + } +} + +const PostScriptTokenTypes = { + LBRACE: 0, + RBRACE: 1, + NUMBER: 2, + OPERATOR: 3, + IF: 4, + IFELSE: 5, +}; + +class PostScriptToken { + static get opCache() { + return shadow(this, "opCache", Object.create(null)); + } + + constructor(type, value) { + this.type = type; + this.value = value; + } + + static getOperator(op) { + return (PostScriptToken.opCache[op] ||= new PostScriptToken( + PostScriptTokenTypes.OPERATOR, + op + )); + } + + static get LBRACE() { + return shadow( + this, + "LBRACE", + new PostScriptToken(PostScriptTokenTypes.LBRACE, "{") + ); + } + + static get RBRACE() { + return shadow( + this, + "RBRACE", + new PostScriptToken(PostScriptTokenTypes.RBRACE, "}") + ); + } + + static get IF() { + return shadow( + this, + "IF", + new PostScriptToken(PostScriptTokenTypes.IF, "IF") + ); + } + + static get IFELSE() { + return shadow( + this, + "IFELSE", + new PostScriptToken(PostScriptTokenTypes.IFELSE, "IFELSE") + ); + } +} + +class PostScriptLexer { + constructor(stream) { + this.stream = stream; + this.nextChar(); + + this.strBuf = []; + } + + nextChar() { + return (this.currentChar = this.stream.getByte()); + } + + getToken() { + let comment = false; + let ch = this.currentChar; + + // skip comments + while (true) { + if (ch < 0) { + return EOF; + } + + if (comment) { + if (ch === 0x0a || ch === 0x0d) { + comment = false; + } + } else if (ch === /* '%' = */ 0x25) { + comment = true; + } else if (!isWhiteSpace(ch)) { + break; + } + ch = this.nextChar(); + } + switch (ch | 0) { + case 0x30: // '0' + case 0x31: // '1' + case 0x32: // '2' + case 0x33: // '3' + case 0x34: // '4' + case 0x35: // '5' + case 0x36: // '6' + case 0x37: // '7' + case 0x38: // '8' + case 0x39: // '9' + case 0x2b: // '+' + case 0x2d: // '-' + case 0x2e: // '.' + return new PostScriptToken( + PostScriptTokenTypes.NUMBER, + this.getNumber() + ); + case 0x7b: // '{' + this.nextChar(); + return PostScriptToken.LBRACE; + case 0x7d: // '}' + this.nextChar(); + return PostScriptToken.RBRACE; + } + // operator + const strBuf = this.strBuf; + strBuf.length = 0; + strBuf[0] = String.fromCharCode(ch); + + while ( + (ch = this.nextChar()) >= 0 && + ((ch >= /* 'A' = */ 0x41 && ch <= /* 'Z' = */ 0x5a) || + (ch >= /* 'a' = */ 0x61 && ch <= /* 'z' = */ 0x7a)) + ) { + strBuf.push(String.fromCharCode(ch)); + } + const str = strBuf.join(""); + switch (str.toLowerCase()) { + case "if": + return PostScriptToken.IF; + case "ifelse": + return PostScriptToken.IFELSE; + default: + return PostScriptToken.getOperator(str); + } + } + + getNumber() { + let ch = this.currentChar; + const strBuf = this.strBuf; + strBuf.length = 0; + strBuf[0] = String.fromCharCode(ch); + + while ((ch = this.nextChar()) >= 0) { + if ( + (ch >= /* '0' = */ 0x30 && ch <= /* '9' = */ 0x39) || + ch === /* '-' = */ 0x2d || + ch === /* '.' = */ 0x2e + ) { + strBuf.push(String.fromCharCode(ch)); + } else { + break; + } + } + const value = parseFloat(strBuf.join("")); + if (isNaN(value)) { + throw new FormatError(`Invalid floating point number: ${value}`); + } + return value; + } +} + +export { PostScriptLexer, PostScriptParser }; diff --git a/frontend/public/pdf.js/src/core/run_length_stream.js b/frontend/public/pdf.js/src/core/run_length_stream.js new file mode 100644 index 0000000..8df345f --- /dev/null +++ b/frontend/public/pdf.js/src/core/run_length_stream.js @@ -0,0 +1,61 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { DecodeStream } from "./decode_stream.js"; + +class RunLengthStream extends DecodeStream { + constructor(str, maybeLength) { + super(maybeLength); + + this.str = str; + this.dict = str.dict; + } + + readBlock() { + // The repeatHeader has following format. The first byte defines type of run + // and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes + // (in addition to the second byte from the header), n = 129 through 255 - + // duplicate the second byte from the header (257 - n) times, n = 128 - end. + const repeatHeader = this.str.getBytes(2); + if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) { + this.eof = true; + return; + } + + let buffer; + let bufferLength = this.bufferLength; + let n = repeatHeader[0]; + if (n < 128) { + // copy n bytes + buffer = this.ensureBuffer(bufferLength + n + 1); + buffer[bufferLength++] = repeatHeader[1]; + if (n > 0) { + const source = this.str.getBytes(n); + buffer.set(source, bufferLength); + bufferLength += n; + } + } else { + n = 257 - n; + const b = repeatHeader[1]; + buffer = this.ensureBuffer(bufferLength + n + 1); + for (let i = 0; i < n; i++) { + buffer[bufferLength++] = b; + } + } + this.bufferLength = bufferLength; + } +} + +export { RunLengthStream }; diff --git a/frontend/public/pdf.js/src/core/segoeui_factors.js b/frontend/public/pdf.js/src/core/segoeui_factors.js new file mode 100644 index 0000000..f1d2eb7 --- /dev/null +++ b/frontend/public/pdf.js/src/core/segoeui_factors.js @@ -0,0 +1,308 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +// Factors to rescale LiberationSans-Bold.ttf to have the same +// metrics as segoeuib.ttf. +const SegoeuiBoldFactors = [ + 1.76738, 1, 1, 0.99297, 0.9824, 1.04016, 1.06497, 1.03424, 0.97529, 1.17647, + 1.23203, 1.1085, 1.1085, 1.16939, 1.2107, 0.9754, 1.21408, 0.9754, 1.59578, + 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, + 1.03424, 1.03424, 0.81378, 0.81378, 1.2107, 1.2107, 1.2107, 0.71703, 0.97847, + 0.97363, 0.88776, 0.8641, 1.02096, 0.79795, 0.85132, 0.914, 1.06085, 1.1406, + 0.8007, 0.89858, 0.83693, 1.14889, 1.09398, 0.97489, 0.92094, 0.97489, + 0.90399, 0.84041, 0.95923, 1.00135, 1, 1.06467, 0.98243, 0.90996, 0.99361, + 1.1085, 1.56942, 1.1085, 1.2107, 0.74627, 0.94282, 0.96752, 1.01519, 0.86304, + 1.01359, 0.97278, 1.15103, 1.01359, 0.98561, 1.02285, 1.02285, 1.00527, + 1.02285, 1.0302, 0.99041, 1.0008, 1.01519, 1.01359, 1.02258, 0.79104, 1.16862, + 0.99041, 0.97454, 1.02511, 0.99298, 0.96752, 0.95801, 0.94856, 1.16579, + 0.94856, 1.2107, 0.9824, 1.03424, 1.03424, 1, 1.03424, 1.16579, 0.8727, + 1.3871, 1.18622, 1.10818, 1.04478, 1.2107, 1.18622, 0.75155, 0.94994, 1.28826, + 1.21408, 1.21408, 0.91056, 1, 0.91572, 0.9754, 0.64663, 1.18328, 1.24866, + 1.04478, 1.14169, 1.15749, 1.17389, 0.71703, 0.97363, 0.97363, 0.97363, + 0.97363, 0.97363, 0.97363, 0.93506, 0.8641, 0.79795, 0.79795, 0.79795, + 0.79795, 1.1406, 1.1406, 1.1406, 1.1406, 1.02096, 1.09398, 0.97426, 0.97426, + 0.97426, 0.97426, 0.97426, 1.2107, 0.97489, 1.00135, 1.00135, 1.00135, + 1.00135, 0.90996, 0.92094, 1.02798, 0.96752, 0.96752, 0.96752, 0.96752, + 0.96752, 0.96752, 0.93136, 0.86304, 0.97278, 0.97278, 0.97278, 0.97278, + 1.02285, 1.02285, 1.02285, 1.02285, 0.97122, 0.99041, 1, 1, 1, 1, 1, 1.28826, + 1.0008, 0.99041, 0.99041, 0.99041, 0.99041, 0.96752, 1.01519, 0.96752, + 0.97363, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.8641, 0.86304, 0.8641, + 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 1.02096, 1.03057, 1.02096, 1.03517, + 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, + 0.79795, 0.97278, 0.914, 1.01359, 0.914, 1.01359, 0.914, 1.01359, 1, 1, + 1.06085, 0.98561, 1.06085, 1.00879, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, + 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 0.97138, 1.08692, 0.8007, 1.02285, + 1, 1, 1.00527, 0.83693, 1.02285, 1, 1, 0.83693, 0.9455, 0.83693, 0.90418, + 0.83693, 1.13005, 1.09398, 0.99041, 1, 1, 1.09398, 0.99041, 0.96692, 1.09251, + 0.99041, 0.97489, 1.0008, 0.97489, 1.0008, 0.97489, 1.0008, 0.93994, 0.97931, + 0.90399, 1.02258, 1, 1, 0.90399, 1.02258, 0.84041, 0.79104, 0.84041, 0.79104, + 0.84041, 0.79104, 0.84041, 0.79104, 1, 1, 0.95923, 1.07034, 0.95923, 1.16862, + 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, + 1.00135, 0.99041, 1.00135, 0.99041, 1.06467, 1.02511, 0.90996, 0.96752, + 0.90996, 0.99361, 0.95801, 0.99361, 0.95801, 0.99361, 0.95801, 1.07733, + 1.03424, 0.97363, 0.96752, 0.93506, 0.93136, 0.97489, 1.0008, 1, 1, 0.95923, + 1.16862, 1.15103, 1.15103, 1.01173, 1.03959, 0.75953, 0.81378, 0.79912, + 1.15103, 1.21994, 0.95161, 0.87815, 1.01149, 0.81525, 0.7676, 0.98167, + 1.01134, 1.02546, 0.84097, 1.03089, 1.18102, 0.97363, 0.88776, 0.85134, + 0.97826, 0.79795, 0.99361, 1.06085, 0.97489, 1.1406, 0.89858, 1.0388, 1.14889, + 1.09398, 0.86039, 0.97489, 1.0595, 0.92094, 0.94793, 0.95923, 0.90996, + 0.99346, 0.98243, 1.02112, 0.95493, 1.1406, 0.90996, 1.03574, 1.02597, 1.0008, + 1.18102, 1.06628, 1.03574, 1.0192, 1.01932, 1.00886, 0.97531, 1.0106, 1.0008, + 1.13189, 1.18102, 1.02277, 0.98683, 1.0016, 0.99561, 1.07237, 1.0008, 0.90434, + 0.99921, 0.93803, 0.8965, 1.23085, 1.06628, 1.04983, 0.96268, 1.0499, 0.98439, + 1.18102, 1.06628, 1.0008, 1.06628, 0.98439, 0.79795, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1.09466, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.97278, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.02065, 1, 1, 1, 1, 1, 1, 1.06467, 1.02511, + 1.06467, 1.02511, 1.06467, 1.02511, 0.90996, 0.96752, 1, 1.21408, 0.89903, 1, + 1, 0.75155, 1.04394, 1.04394, 1.04394, 1.04394, 0.98633, 0.98633, 0.98633, + 0.73047, 0.73047, 1.20642, 0.91211, 1.25635, 1.222, 1.02956, 1.03372, 1.03372, + 0.96039, 1.24633, 1, 1.12454, 0.93503, 1.03424, 1.19687, 1.03424, 1, 1, 1, + 0.771, 1, 1, 1.15749, 1.15749, 1.15749, 1.10948, 0.86279, 0.94434, 0.86279, + 0.94434, 0.86182, 1, 1, 1.16897, 1, 0.96085, 0.90137, 1.2107, 1.18416, + 1.13973, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21172, 1.29004, + 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, + 1.18874, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, + 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.09193, 1.09193, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const SegoeuiBoldMetrics = { lineHeight: 1.33008, lineGap: 0 }; + +// Factors to rescale LiberationSans-BoldItalic.ttf to have the same +// metrics as segoeuiz.ttf. +const SegoeuiBoldItalicFactors = [ + 1.76738, 1, 1, 0.98946, 1.03959, 1.04016, 1.02809, 1.036, 0.97639, 1.10953, + 1.23203, 1.11144, 1.11144, 1.16939, 1.21237, 0.9754, 1.21261, 0.9754, 1.59754, + 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 0.81378, + 0.81378, 1.21237, 1.21237, 1.21237, 0.73541, 0.97847, 0.97363, 0.89723, + 0.87897, 1.0426, 0.79429, 0.85292, 0.91149, 1.05815, 1.1406, 0.79631, 0.90128, + 0.83853, 1.04396, 1.10615, 0.97552, 0.94436, 0.97552, 0.88641, 0.80527, + 0.96083, 1.00135, 1, 1.06777, 0.9817, 0.91142, 0.99361, 1.11144, 1.57293, + 1.11144, 1.21237, 0.74627, 1.31818, 1.06585, 0.97042, 0.83055, 0.97042, + 0.93503, 1.1261, 0.97042, 0.97922, 1.14236, 0.94552, 1.01054, 1.14236, + 1.02471, 0.97922, 0.94165, 0.97042, 0.97042, 1.0276, 0.78929, 1.1261, 0.97922, + 0.95874, 1.02197, 0.98507, 0.96752, 0.97168, 0.95107, 1.16579, 0.95107, + 1.21237, 1.03959, 1.036, 1.036, 1, 1.036, 1.16579, 0.87357, 1.31818, 1.18754, + 1.26781, 1.05356, 1.21237, 1.18622, 0.79487, 0.94994, 1.29004, 1.24047, + 1.24047, 1.31818, 1, 0.91484, 0.9754, 1.31818, 1.1349, 1.24866, 1.05356, + 1.13934, 1.15574, 1.17389, 0.73541, 0.97363, 0.97363, 0.97363, 0.97363, + 0.97363, 0.97363, 0.94385, 0.87897, 0.79429, 0.79429, 0.79429, 0.79429, + 1.1406, 1.1406, 1.1406, 1.1406, 1.0426, 1.10615, 0.97552, 0.97552, 0.97552, + 0.97552, 0.97552, 1.21237, 0.97552, 1.00135, 1.00135, 1.00135, 1.00135, + 0.91142, 0.94436, 0.98721, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, + 1.06585, 0.96705, 0.83055, 0.93503, 0.93503, 0.93503, 0.93503, 1.14236, + 1.14236, 1.14236, 1.14236, 0.93125, 0.97922, 0.94165, 0.94165, 0.94165, + 0.94165, 0.94165, 1.29004, 0.94165, 0.97922, 0.97922, 0.97922, 0.97922, + 0.96752, 0.97042, 0.96752, 0.97363, 1.06585, 0.97363, 1.06585, 0.97363, + 1.06585, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, + 0.83055, 1.0426, 1.0033, 1.0426, 0.97042, 0.79429, 0.93503, 0.79429, 0.93503, + 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.91149, 0.97042, + 0.91149, 0.97042, 0.91149, 0.97042, 1, 1, 1.05815, 0.97922, 1.05815, 0.97922, + 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, + 1.14236, 0.97441, 1.04302, 0.79631, 1.01582, 1, 1, 1.01054, 0.83853, 1.14236, + 1, 1, 0.83853, 1.09125, 0.83853, 0.90418, 0.83853, 1.19508, 1.10615, 0.97922, + 1, 1, 1.10615, 0.97922, 1.01034, 1.10466, 0.97922, 0.97552, 0.94165, 0.97552, + 0.94165, 0.97552, 0.94165, 0.91602, 0.91981, 0.88641, 1.0276, 1, 1, 0.88641, + 1.0276, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, + 0.78929, 1, 1, 0.96083, 1.05403, 0.95923, 1.16862, 1.00135, 0.97922, 1.00135, + 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, + 0.97922, 1.06777, 1.02197, 0.91142, 0.96752, 0.91142, 0.99361, 0.97168, + 0.99361, 0.97168, 0.99361, 0.97168, 1.23199, 1.036, 0.97363, 1.06585, 0.94385, + 0.96705, 0.97552, 0.94165, 1, 1, 0.96083, 1.1261, 1.31818, 1.31818, 1.31818, + 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 0.95161, 1.27126, + 1.00811, 0.83284, 0.77702, 0.99137, 0.95253, 1.0347, 0.86142, 1.07205, + 1.14236, 0.97363, 0.89723, 0.86869, 1.09818, 0.79429, 0.99361, 1.05815, + 0.97552, 1.1406, 0.90128, 1.06662, 1.04396, 1.10615, 0.84918, 0.97552, + 1.04694, 0.94436, 0.98015, 0.96083, 0.91142, 1.00356, 0.9817, 1.01945, + 0.98999, 1.1406, 0.91142, 1.04961, 0.9898, 1.00639, 1.14236, 1.07514, 1.04961, + 0.99607, 1.02897, 1.008, 0.9898, 0.95134, 1.00639, 1.11121, 1.14236, 1.00518, + 0.97981, 1.02186, 1, 1.08578, 0.94165, 0.99314, 0.98387, 0.93028, 0.93377, + 1.35125, 1.07514, 1.10687, 0.93491, 1.04232, 1.00351, 1.14236, 1.07514, + 0.94165, 1.07514, 1.00351, 0.79429, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1.09097, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.93503, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0.96609, 1, 1, 1, 1, 1, 1, 1.06777, 1.02197, 1.06777, + 1.02197, 1.06777, 1.02197, 0.91142, 0.96752, 1, 1.21261, 0.89903, 1, 1, + 0.75155, 1.04745, 1.04745, 1.04745, 1.04394, 0.98633, 0.98633, 0.98633, + 0.72959, 0.72959, 1.20502, 0.91406, 1.26514, 1.222, 1.02956, 1.03372, 1.03372, + 0.96039, 1.24633, 1, 1.09125, 0.93327, 1.03336, 1.16541, 1.036, 1, 1, 1, + 0.771, 1, 1, 1.15574, 1.15574, 1.15574, 1.15574, 0.86364, 0.94434, 0.86279, + 0.94434, 0.86224, 1, 1, 1.16798, 1, 0.96085, 0.90068, 1.21237, 1.18416, + 1.13904, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21339, 1.29004, + 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, + 1.18775, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, + 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.13269, 1.13269, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const SegoeuiBoldItalicMetrics = { lineHeight: 1.33008, lineGap: 0 }; + +// Factors to rescale LiberationSans-Italic.ttf to have the same +// metrics as segoeuii.ttf. +const SegoeuiItalicFactors = [ + 1.76738, 1, 1, 0.98946, 1.14763, 1.05365, 1.06234, 0.96927, 0.92586, 1.15373, + 1.18414, 0.91349, 0.91349, 1.07403, 1.17308, 0.78383, 1.20088, 0.78383, + 1.42531, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, + 0.96927, 0.96927, 0.96927, 0.78383, 0.78383, 1.17308, 1.17308, 1.17308, + 0.77349, 0.94565, 0.94729, 0.85944, 0.88506, 0.9858, 0.74817, 0.80016, + 0.88449, 0.98039, 0.95782, 0.69238, 0.89898, 0.83231, 0.98183, 1.03989, + 0.96924, 0.86237, 0.96924, 0.80595, 0.74524, 0.86091, 0.95402, 0.94143, + 0.98448, 0.8858, 0.83089, 0.93285, 1.0949, 1.39016, 1.0949, 1.45994, 0.74627, + 1.04839, 0.97454, 0.97454, 0.87207, 0.97454, 0.87533, 1.06151, 0.97454, + 1.00176, 1.16484, 1.08132, 0.98047, 1.16484, 1.02989, 1.01054, 0.96225, + 0.97454, 0.97454, 1.06598, 0.79004, 1.16344, 1.00351, 0.94629, 0.9973, + 0.91016, 0.96777, 0.9043, 0.91082, 0.92481, 0.91082, 1.17308, 0.95748, + 0.96927, 0.96927, 1, 0.96927, 0.92481, 0.80597, 1.04839, 1.23393, 1.1781, + 0.9245, 1.17308, 1.20808, 0.63218, 0.94261, 1.24822, 1.09971, 1.09971, + 1.04839, 1, 0.85273, 0.78032, 1.04839, 1.09971, 1.22326, 0.9245, 1.09836, + 1.13525, 1.15222, 0.70424, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, + 0.94729, 0.85498, 0.88506, 0.74817, 0.74817, 0.74817, 0.74817, 0.95782, + 0.95782, 0.95782, 0.95782, 0.9858, 1.03989, 0.96924, 0.96924, 0.96924, + 0.96924, 0.96924, 1.17308, 0.96924, 0.95402, 0.95402, 0.95402, 0.95402, + 0.83089, 0.86237, 0.88409, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, + 0.97454, 0.92916, 0.87207, 0.87533, 0.87533, 0.87533, 0.87533, 0.93146, + 0.93146, 0.93146, 0.93146, 0.93854, 1.01054, 0.96225, 0.96225, 0.96225, + 0.96225, 0.96225, 1.24822, 0.8761, 1.00351, 1.00351, 1.00351, 1.00351, + 0.96777, 0.97454, 0.96777, 0.94729, 0.97454, 0.94729, 0.97454, 0.94729, + 0.97454, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, + 0.87207, 0.9858, 0.95391, 0.9858, 0.97454, 0.74817, 0.87533, 0.74817, 0.87533, + 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.88449, 0.97454, + 0.88449, 0.97454, 0.88449, 0.97454, 1, 1, 0.98039, 1.00176, 0.98039, 1.00176, + 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 1.16484, + 0.95782, 0.93146, 0.84421, 1.12761, 0.69238, 1.08132, 1, 1, 0.98047, 0.83231, + 1.16484, 1, 1, 0.84723, 1.04861, 0.84723, 0.78755, 0.83231, 1.23736, 1.03989, + 1.01054, 1, 1, 1.03989, 1.01054, 0.9857, 1.03849, 1.01054, 0.96924, 0.96225, + 0.96924, 0.96225, 0.96924, 0.96225, 0.92383, 0.90171, 0.80595, 1.06598, 1, 1, + 0.80595, 1.06598, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, + 0.74524, 0.79004, 1, 1, 0.86091, 1.02759, 0.85771, 1.16344, 0.95402, 1.00351, + 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, + 0.95402, 1.00351, 0.98448, 0.9973, 0.83089, 0.96777, 0.83089, 0.93285, 0.9043, + 0.93285, 0.9043, 0.93285, 0.9043, 1.31868, 0.96927, 0.94729, 0.97454, 0.85498, + 0.92916, 0.96924, 0.8761, 1, 1, 0.86091, 1.16344, 1.04839, 1.04839, 1.04839, + 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 0.81965, 0.81965, + 0.94729, 0.78032, 0.71022, 0.90883, 0.84171, 0.99877, 0.77596, 1.05734, 1.2, + 0.94729, 0.85944, 0.82791, 0.9607, 0.74817, 0.93285, 0.98039, 0.96924, + 0.95782, 0.89898, 0.98316, 0.98183, 1.03989, 0.78614, 0.96924, 0.97642, + 0.86237, 0.86075, 0.86091, 0.83089, 0.90082, 0.8858, 0.97296, 1.01284, + 0.95782, 0.83089, 1.0976, 1.04, 1.03342, 1.2, 1.0675, 1.0976, 0.98205, + 1.03809, 1.05097, 1.04, 0.95364, 1.03342, 1.05401, 1.2, 1.02148, 1.0119, + 1.04724, 1.0127, 1.02732, 0.96225, 0.8965, 0.97783, 0.93574, 0.94818, 1.30679, + 1.0675, 1.11826, 0.99821, 1.0557, 1.0326, 1.2, 1.0675, 0.96225, 1.0675, + 1.0326, 0.74817, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03754, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0.98705, 1, 1, 1, 1, 1, 1, 0.98448, 0.9973, 0.98448, 0.9973, 0.98448, 0.9973, + 0.83089, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 0.94945, 0.94945, + 0.94945, 0.94945, 1.12317, 1.12317, 1.12317, 0.67603, 0.67603, 1.15621, + 0.73584, 1.21191, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, + 1.07497, 0.87709, 0.96927, 1.01473, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.09836, + 1.09836, 1.09836, 1.01522, 0.86321, 0.94434, 0.8649, 0.94434, 0.86182, 1, 1, + 1.083, 1, 0.91578, 0.86438, 1.17308, 1.18416, 1.14589, 0.69825, 0.97622, + 1.96791, 1.24822, 1.24822, 1.17308, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, + 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.17984, 1.42603, 1, 1.42603, + 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, + 1.10742, 1.10742, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const SegoeuiItalicMetrics = { lineHeight: 1.33008, lineGap: 0 }; + +// Factors to rescale LiberationSans-Regular.ttf to have the same +// metrics as segoeui.ttf. +const SegoeuiRegularFactors = [ + 1.76738, 1, 1, 0.98594, 1.02285, 1.10454, 1.06234, 0.96927, 0.92037, 1.19985, + 1.2046, 0.90616, 0.90616, 1.07152, 1.1714, 0.78032, 1.20088, 0.78032, 1.40246, + 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, + 0.96927, 0.96927, 0.78032, 0.78032, 1.1714, 1.1714, 1.1714, 0.80597, 0.94084, + 0.96706, 0.85944, 0.85734, 0.97093, 0.75842, 0.79936, 0.88198, 0.9831, + 0.95782, 0.71387, 0.86969, 0.84636, 1.07796, 1.03584, 0.96924, 0.83968, + 0.96924, 0.82826, 0.79649, 0.85771, 0.95132, 0.93119, 0.98965, 0.88433, + 0.8287, 0.93365, 1.08612, 1.3638, 1.08612, 1.45786, 0.74627, 0.80499, 0.91484, + 1.05707, 0.92383, 1.05882, 0.9403, 1.12654, 1.05882, 1.01756, 1.09011, + 1.09011, 0.99414, 1.09011, 1.034, 1.01756, 1.05356, 1.05707, 1.05882, 1.04399, + 0.84863, 1.21968, 1.01756, 0.95801, 1.00068, 0.91797, 0.96777, 0.9043, + 0.90351, 0.92105, 0.90351, 1.1714, 0.85337, 0.96927, 0.96927, 0.99912, + 0.96927, 0.92105, 0.80597, 1.2434, 1.20808, 1.05937, 0.90957, 1.1714, 1.20808, + 0.75155, 0.94261, 1.24644, 1.09971, 1.09971, 0.84751, 1, 0.85273, 0.78032, + 0.61584, 1.05425, 1.17914, 0.90957, 1.08665, 1.11593, 1.14169, 0.73381, + 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.86035, 0.85734, + 0.75842, 0.75842, 0.75842, 0.75842, 0.95782, 0.95782, 0.95782, 0.95782, + 0.97093, 1.03584, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.1714, + 0.96924, 0.95132, 0.95132, 0.95132, 0.95132, 0.8287, 0.83968, 0.89049, + 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.93575, 0.92383, + 0.9403, 0.9403, 0.9403, 0.9403, 0.8717, 0.8717, 0.8717, 0.8717, 1.00527, + 1.01756, 1.05356, 1.05356, 1.05356, 1.05356, 1.05356, 1.24644, 0.95923, + 1.01756, 1.01756, 1.01756, 1.01756, 0.96777, 1.05707, 0.96777, 0.96706, + 0.91484, 0.96706, 0.91484, 0.96706, 0.91484, 0.85734, 0.92383, 0.85734, + 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.97093, 1.0969, 0.97093, + 1.05882, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, + 0.75842, 0.9403, 0.88198, 1.05882, 0.88198, 1.05882, 0.88198, 1.05882, 1, 1, + 0.9831, 1.01756, 0.9831, 1.01756, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, + 0.8717, 0.95782, 1.09011, 0.95782, 0.8717, 0.84784, 1.11551, 0.71387, 1.09011, + 1, 1, 0.99414, 0.84636, 1.09011, 1, 1, 0.84636, 1.0536, 0.84636, 0.94298, + 0.84636, 1.23297, 1.03584, 1.01756, 1, 1, 1.03584, 1.01756, 1.00323, 1.03444, + 1.01756, 0.96924, 1.05356, 0.96924, 1.05356, 0.96924, 1.05356, 0.93066, + 0.98293, 0.82826, 1.04399, 1, 1, 0.82826, 1.04399, 0.79649, 0.84863, 0.79649, + 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 1, 1, 0.85771, 1.17318, 0.85771, + 1.21968, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, + 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.98965, 1.00068, 0.8287, + 0.96777, 0.8287, 0.93365, 0.9043, 0.93365, 0.9043, 0.93365, 0.9043, 1.08571, + 0.96927, 0.96706, 0.91484, 0.86035, 0.93575, 0.96924, 0.95923, 1, 1, 0.85771, + 1.21968, 1.11437, 1.11437, 0.93109, 0.91202, 0.60411, 0.84164, 0.55572, + 1.01173, 0.97361, 0.81818, 0.81818, 0.96635, 0.78032, 0.72727, 0.92366, + 0.98601, 1.03405, 0.77968, 1.09799, 1.2, 0.96706, 0.85944, 0.85638, 0.96491, + 0.75842, 0.93365, 0.9831, 0.96924, 0.95782, 0.86969, 0.94152, 1.07796, + 1.03584, 0.78437, 0.96924, 0.98715, 0.83968, 0.83491, 0.85771, 0.8287, + 0.94492, 0.88433, 0.9287, 1.0098, 0.95782, 0.8287, 1.0625, 0.98248, 1.03424, + 1.2, 1.01071, 1.0625, 0.95246, 1.03809, 1.04912, 0.98248, 1.00221, 1.03424, + 1.05443, 1.2, 1.04785, 0.99609, 1.00169, 1.05176, 0.99346, 1.05356, 0.9087, + 1.03004, 0.95542, 0.93117, 1.23362, 1.01071, 1.07831, 1.02512, 1.05205, + 1.03502, 1.2, 1.01071, 1.05356, 1.01071, 1.03502, 0.75842, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1.03719, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9403, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04021, 1, 1, 1, 1, 1, 1, 0.98965, + 1.00068, 0.98965, 1.00068, 0.98965, 1.00068, 0.8287, 0.96777, 1, 1.20088, + 0.89903, 1, 1, 0.75155, 1.03077, 1.03077, 1.03077, 1.03077, 1.13196, 1.13196, + 1.13196, 0.67428, 0.67428, 1.16039, 0.73291, 1.20996, 1.22135, 1.06483, + 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87796, 0.96927, 1.01518, + 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.10539, 1.10539, 1.11358, 1.06967, 0.86279, + 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86507, 1.1714, + 1.18416, 1.14589, 0.69825, 0.97622, 1.9697, 1.24822, 1.24822, 1.17238, + 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, + 0.87025, 1.18083, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, + 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10938, 1.10938, 1, 1, 1, + 1.05425, 1.09971, 1.09971, 1.09971, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +]; +const SegoeuiRegularMetrics = { lineHeight: 1.33008, lineGap: 0 }; + +export { + SegoeuiBoldFactors, + SegoeuiBoldItalicFactors, + SegoeuiBoldItalicMetrics, + SegoeuiBoldMetrics, + SegoeuiItalicFactors, + SegoeuiItalicMetrics, + SegoeuiRegularFactors, + SegoeuiRegularMetrics, +}; diff --git a/frontend/public/pdf.js/src/core/standard_fonts.js b/frontend/public/pdf.js/src/core/standard_fonts.js new file mode 100644 index 0000000..9e7cea5 --- /dev/null +++ b/frontend/public/pdf.js/src/core/standard_fonts.js @@ -0,0 +1,920 @@ +/* Copyright 2015 Mozilla Foundation + * + * 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 { getLookupTableFactory } from "./core_utils.js"; +import { normalizeFontName } from "./fonts_utils.js"; + +/** + * Hold a map of decoded fonts and of the standard fourteen Type1 + * fonts and their acronyms. + */ +const getStdFontMap = getLookupTableFactory(function (t) { + // The standard 14 fonts: + t["Times-Roman"] = "Times-Roman"; + t.Helvetica = "Helvetica"; + t.Courier = "Courier"; + t.Symbol = "Symbol"; + t["Times-Bold"] = "Times-Bold"; + t["Helvetica-Bold"] = "Helvetica-Bold"; + t["Courier-Bold"] = "Courier-Bold"; + t.ZapfDingbats = "ZapfDingbats"; + t["Times-Italic"] = "Times-Italic"; + t["Helvetica-Oblique"] = "Helvetica-Oblique"; + t["Courier-Oblique"] = "Courier-Oblique"; + t["Times-BoldItalic"] = "Times-BoldItalic"; + t["Helvetica-BoldOblique"] = "Helvetica-BoldOblique"; + t["Courier-BoldOblique"] = "Courier-BoldOblique"; + + // Extra mappings + t.ArialNarrow = "Helvetica"; + t["ArialNarrow-Bold"] = "Helvetica-Bold"; + t["ArialNarrow-BoldItalic"] = "Helvetica-BoldOblique"; + t["ArialNarrow-Italic"] = "Helvetica-Oblique"; + t.ArialBlack = "Helvetica"; + t["ArialBlack-Bold"] = "Helvetica-Bold"; + t["ArialBlack-BoldItalic"] = "Helvetica-BoldOblique"; + t["ArialBlack-Italic"] = "Helvetica-Oblique"; + t["Arial-Black"] = "Helvetica"; + t["Arial-Black-Bold"] = "Helvetica-Bold"; + t["Arial-Black-BoldItalic"] = "Helvetica-BoldOblique"; + t["Arial-Black-Italic"] = "Helvetica-Oblique"; + t.Arial = "Helvetica"; + t["Arial-Bold"] = "Helvetica-Bold"; + t["Arial-BoldItalic"] = "Helvetica-BoldOblique"; + t["Arial-Italic"] = "Helvetica-Oblique"; + t.ArialMT = "Helvetica"; + t["Arial-BoldItalicMT"] = "Helvetica-BoldOblique"; + t["Arial-BoldMT"] = "Helvetica-Bold"; + t["Arial-ItalicMT"] = "Helvetica-Oblique"; + t["Arial-BoldItalicMT-BoldItalic"] = "Helvetica-BoldOblique"; + t["Arial-BoldMT-Bold"] = "Helvetica-Bold"; + t["Arial-ItalicMT-Italic"] = "Helvetica-Oblique"; + t.ArialUnicodeMS = "Helvetica"; + t["ArialUnicodeMS-Bold"] = "Helvetica-Bold"; + t["ArialUnicodeMS-BoldItalic"] = "Helvetica-BoldOblique"; + t["ArialUnicodeMS-Italic"] = "Helvetica-Oblique"; + t["Courier-BoldItalic"] = "Courier-BoldOblique"; + t["Courier-Italic"] = "Courier-Oblique"; + t.CourierNew = "Courier"; + t["CourierNew-Bold"] = "Courier-Bold"; + t["CourierNew-BoldItalic"] = "Courier-BoldOblique"; + t["CourierNew-Italic"] = "Courier-Oblique"; + t["CourierNewPS-BoldItalicMT"] = "Courier-BoldOblique"; + t["CourierNewPS-BoldMT"] = "Courier-Bold"; + t["CourierNewPS-ItalicMT"] = "Courier-Oblique"; + t.CourierNewPSMT = "Courier"; + t["Helvetica-BoldItalic"] = "Helvetica-BoldOblique"; + t["Helvetica-Italic"] = "Helvetica-Oblique"; + t["HelveticaLTStd-Bold"] = "Helvetica-Bold"; + t["Symbol-Bold"] = "Symbol"; + t["Symbol-BoldItalic"] = "Symbol"; + t["Symbol-Italic"] = "Symbol"; + t.TimesNewRoman = "Times-Roman"; + t["TimesNewRoman-Bold"] = "Times-Bold"; + t["TimesNewRoman-BoldItalic"] = "Times-BoldItalic"; + t["TimesNewRoman-Italic"] = "Times-Italic"; + t.TimesNewRomanPS = "Times-Roman"; + t["TimesNewRomanPS-Bold"] = "Times-Bold"; + t["TimesNewRomanPS-BoldItalic"] = "Times-BoldItalic"; + t["TimesNewRomanPS-BoldItalicMT"] = "Times-BoldItalic"; + t["TimesNewRomanPS-BoldMT"] = "Times-Bold"; + t["TimesNewRomanPS-Italic"] = "Times-Italic"; + t["TimesNewRomanPS-ItalicMT"] = "Times-Italic"; + t.TimesNewRomanPSMT = "Times-Roman"; + t["TimesNewRomanPSMT-Bold"] = "Times-Bold"; + t["TimesNewRomanPSMT-BoldItalic"] = "Times-BoldItalic"; + t["TimesNewRomanPSMT-Italic"] = "Times-Italic"; +}); + +// Contains mapping for standard fonts and xfa fonts. +const getFontNameToFileMap = getLookupTableFactory(function (t) { + t.Courier = "FoxitFixed.pfb"; + t["Courier-Bold"] = "FoxitFixedBold.pfb"; + t["Courier-BoldOblique"] = "FoxitFixedBoldItalic.pfb"; + t["Courier-Oblique"] = "FoxitFixedItalic.pfb"; + t.Helvetica = "LiberationSans-Regular.ttf"; + t["Helvetica-Bold"] = "LiberationSans-Bold.ttf"; + t["Helvetica-BoldOblique"] = "LiberationSans-BoldItalic.ttf"; + t["Helvetica-Oblique"] = "LiberationSans-Italic.ttf"; + t["Times-Roman"] = "FoxitSerif.pfb"; + t["Times-Bold"] = "FoxitSerifBold.pfb"; + t["Times-BoldItalic"] = "FoxitSerifBoldItalic.pfb"; + t["Times-Italic"] = "FoxitSerifItalic.pfb"; + t.Symbol = "FoxitSymbol.pfb"; + t.ZapfDingbats = "FoxitDingbats.pfb"; + t["LiberationSans-Regular"] = "LiberationSans-Regular.ttf"; + t["LiberationSans-Bold"] = "LiberationSans-Bold.ttf"; + t["LiberationSans-Italic"] = "LiberationSans-Italic.ttf"; + t["LiberationSans-BoldItalic"] = "LiberationSans-BoldItalic.ttf"; +}); + +/** + * Holds the map of the non-standard fonts that might be included as + * a standard fonts without glyph data. + */ +const getNonStdFontMap = getLookupTableFactory(function (t) { + t.Calibri = "Helvetica"; + t["Calibri-Bold"] = "Helvetica-Bold"; + t["Calibri-BoldItalic"] = "Helvetica-BoldOblique"; + t["Calibri-Italic"] = "Helvetica-Oblique"; + t.CenturyGothic = "Helvetica"; + t["CenturyGothic-Bold"] = "Helvetica-Bold"; + t["CenturyGothic-BoldItalic"] = "Helvetica-BoldOblique"; + t["CenturyGothic-Italic"] = "Helvetica-Oblique"; + t.ComicSansMS = "Comic Sans MS"; + t["ComicSansMS-Bold"] = "Comic Sans MS-Bold"; + t["ComicSansMS-BoldItalic"] = "Comic Sans MS-BoldItalic"; + t["ComicSansMS-Italic"] = "Comic Sans MS-Italic"; + t.GillSansMT = "Helvetica"; + t["GillSansMT-Bold"] = "Helvetica-Bold"; + t["GillSansMT-BoldItalic"] = "Helvetica-BoldOblique"; + t["GillSansMT-Italic"] = "Helvetica-Oblique"; + t.Impact = "Helvetica"; + t["ItcSymbol-Bold"] = "Helvetica-Bold"; + t["ItcSymbol-BoldItalic"] = "Helvetica-BoldOblique"; + t["ItcSymbol-Book"] = "Helvetica"; + t["ItcSymbol-BookItalic"] = "Helvetica-Oblique"; + t["ItcSymbol-Medium"] = "Helvetica"; + t["ItcSymbol-MediumItalic"] = "Helvetica-Oblique"; + t.LucidaConsole = "Courier"; + t["LucidaConsole-Bold"] = "Courier-Bold"; + t["LucidaConsole-BoldItalic"] = "Courier-BoldOblique"; + t["LucidaConsole-Italic"] = "Courier-Oblique"; + t["LucidaSans-Demi"] = "Helvetica-Bold"; + t["MS-Gothic"] = "MS Gothic"; + t["MS-Gothic-Bold"] = "MS Gothic-Bold"; + t["MS-Gothic-BoldItalic"] = "MS Gothic-BoldItalic"; + t["MS-Gothic-Italic"] = "MS Gothic-Italic"; + t["MS-Mincho"] = "MS Mincho"; + t["MS-Mincho-Bold"] = "MS Mincho-Bold"; + t["MS-Mincho-BoldItalic"] = "MS Mincho-BoldItalic"; + t["MS-Mincho-Italic"] = "MS Mincho-Italic"; + t["MS-PGothic"] = "MS PGothic"; + t["MS-PGothic-Bold"] = "MS PGothic-Bold"; + t["MS-PGothic-BoldItalic"] = "MS PGothic-BoldItalic"; + t["MS-PGothic-Italic"] = "MS PGothic-Italic"; + t["MS-PMincho"] = "MS PMincho"; + t["MS-PMincho-Bold"] = "MS PMincho-Bold"; + t["MS-PMincho-BoldItalic"] = "MS PMincho-BoldItalic"; + t["MS-PMincho-Italic"] = "MS PMincho-Italic"; + t.NuptialScript = "Times-Italic"; + t.SegoeUISymbol = "Helvetica"; +}); + +const getSerifFonts = getLookupTableFactory(function (t) { + t["Adobe Jenson"] = true; + t["Adobe Text"] = true; + t.Albertus = true; + t.Aldus = true; + t.Alexandria = true; + t.Algerian = true; + t["American Typewriter"] = true; + t.Antiqua = true; + t.Apex = true; + t.Arno = true; + t.Aster = true; + t.Aurora = true; + t.Baskerville = true; + t.Bell = true; + t.Bembo = true; + t["Bembo Schoolbook"] = true; + t.Benguiat = true; + t["Berkeley Old Style"] = true; + t["Bernhard Modern"] = true; + t["Berthold City"] = true; + t.Bodoni = true; + t["Bauer Bodoni"] = true; + t["Book Antiqua"] = true; + t.Bookman = true; + t["Bordeaux Roman"] = true; + t["Californian FB"] = true; + t.Calisto = true; + t.Calvert = true; + t.Capitals = true; + t.Cambria = true; + t.Cartier = true; + t.Caslon = true; + t.Catull = true; + t.Centaur = true; + t["Century Old Style"] = true; + t["Century Schoolbook"] = true; + t.Chaparral = true; + t["Charis SIL"] = true; + t.Cheltenham = true; + t["Cholla Slab"] = true; + t.Clarendon = true; + t.Clearface = true; + t.Cochin = true; + t.Colonna = true; + t["Computer Modern"] = true; + t["Concrete Roman"] = true; + t.Constantia = true; + t["Cooper Black"] = true; + t.Corona = true; + t.Ecotype = true; + t.Egyptienne = true; + t.Elephant = true; + t.Excelsior = true; + t.Fairfield = true; + t["FF Scala"] = true; + t.Folkard = true; + t.Footlight = true; + t.FreeSerif = true; + t["Friz Quadrata"] = true; + t.Garamond = true; + t.Gentium = true; + t.Georgia = true; + t.Gloucester = true; + t["Goudy Old Style"] = true; + t["Goudy Schoolbook"] = true; + t["Goudy Pro Font"] = true; + t.Granjon = true; + t["Guardian Egyptian"] = true; + t.Heather = true; + t.Hercules = true; + t["High Tower Text"] = true; + t.Hiroshige = true; + t["Hoefler Text"] = true; + t["Humana Serif"] = true; + t.Imprint = true; + t["Ionic No. 5"] = true; + t.Janson = true; + t.Joanna = true; + t.Korinna = true; + t.Lexicon = true; + t.LiberationSerif = true; + t["Liberation Serif"] = true; + t["Linux Libertine"] = true; + t.Literaturnaya = true; + t.Lucida = true; + t["Lucida Bright"] = true; + t.Melior = true; + t.Memphis = true; + t.Miller = true; + t.Minion = true; + t.Modern = true; + t["Mona Lisa"] = true; + t["Mrs Eaves"] = true; + t["MS Serif"] = true; + t["Museo Slab"] = true; + t["New York"] = true; + t["Nimbus Roman"] = true; + t["NPS Rawlinson Roadway"] = true; + t.NuptialScript = true; + t.Palatino = true; + t.Perpetua = true; + t.Plantin = true; + t["Plantin Schoolbook"] = true; + t.Playbill = true; + t["Poor Richard"] = true; + t["Rawlinson Roadway"] = true; + t.Renault = true; + t.Requiem = true; + t.Rockwell = true; + t.Roman = true; + t["Rotis Serif"] = true; + t.Sabon = true; + t.Scala = true; + t.Seagull = true; + t.Sistina = true; + t.Souvenir = true; + t.STIX = true; + t["Stone Informal"] = true; + t["Stone Serif"] = true; + t.Sylfaen = true; + t.Times = true; + t.Trajan = true; + t["Trinitรฉ"] = true; + t["Trump Mediaeval"] = true; + t.Utopia = true; + t["Vale Type"] = true; + t["Bitstream Vera"] = true; + t["Vera Serif"] = true; + t.Versailles = true; + t.Wanted = true; + t.Weiss = true; + t["Wide Latin"] = true; + t.Windsor = true; + t.XITS = true; +}); + +const getSymbolsFonts = getLookupTableFactory(function (t) { + t.Dingbats = true; + t.Symbol = true; + t.ZapfDingbats = true; + t.Wingdings = true; + t["Wingdings-Bold"] = true; + t["Wingdings-Regular"] = true; +}); + +// Glyph map for well-known standard fonts. Sometimes Ghostscript uses CID +// fonts, but does not embed the CID to GID mapping. The mapping is incomplete +// for all glyphs, but common for some set of the standard fonts. +const getGlyphMapForStandardFonts = getLookupTableFactory(function (t) { + t[2] = 10; + t[3] = 32; + t[4] = 33; + t[5] = 34; + t[6] = 35; + t[7] = 36; + t[8] = 37; + t[9] = 38; + t[10] = 39; + t[11] = 40; + t[12] = 41; + t[13] = 42; + t[14] = 43; + t[15] = 44; + t[16] = 45; + t[17] = 46; + t[18] = 47; + t[19] = 48; + t[20] = 49; + t[21] = 50; + t[22] = 51; + t[23] = 52; + t[24] = 53; + t[25] = 54; + t[26] = 55; + t[27] = 56; + t[28] = 57; + t[29] = 58; + t[30] = 894; + t[31] = 60; + t[32] = 61; + t[33] = 62; + t[34] = 63; + t[35] = 64; + t[36] = 65; + t[37] = 66; + t[38] = 67; + t[39] = 68; + t[40] = 69; + t[41] = 70; + t[42] = 71; + t[43] = 72; + t[44] = 73; + t[45] = 74; + t[46] = 75; + t[47] = 76; + t[48] = 77; + t[49] = 78; + t[50] = 79; + t[51] = 80; + t[52] = 81; + t[53] = 82; + t[54] = 83; + t[55] = 84; + t[56] = 85; + t[57] = 86; + t[58] = 87; + t[59] = 88; + t[60] = 89; + t[61] = 90; + t[62] = 91; + t[63] = 92; + t[64] = 93; + t[65] = 94; + t[66] = 95; + t[67] = 96; + t[68] = 97; + t[69] = 98; + t[70] = 99; + t[71] = 100; + t[72] = 101; + t[73] = 102; + t[74] = 103; + t[75] = 104; + t[76] = 105; + t[77] = 106; + t[78] = 107; + t[79] = 108; + t[80] = 109; + t[81] = 110; + t[82] = 111; + t[83] = 112; + t[84] = 113; + t[85] = 114; + t[86] = 115; + t[87] = 116; + t[88] = 117; + t[89] = 118; + t[90] = 119; + t[91] = 120; + t[92] = 121; + t[93] = 122; + t[94] = 123; + t[95] = 124; + t[96] = 125; + t[97] = 126; + t[98] = 196; + t[99] = 197; + t[100] = 199; + t[101] = 201; + t[102] = 209; + t[103] = 214; + t[104] = 220; + t[105] = 225; + t[106] = 224; + t[107] = 226; + t[108] = 228; + t[109] = 227; + t[110] = 229; + t[111] = 231; + t[112] = 233; + t[113] = 232; + t[114] = 234; + t[115] = 235; + t[116] = 237; + t[117] = 236; + t[118] = 238; + t[119] = 239; + t[120] = 241; + t[121] = 243; + t[122] = 242; + t[123] = 244; + t[124] = 246; + t[125] = 245; + t[126] = 250; + t[127] = 249; + t[128] = 251; + t[129] = 252; + t[130] = 8224; + t[131] = 176; + t[132] = 162; + t[133] = 163; + t[134] = 167; + t[135] = 8226; + t[136] = 182; + t[137] = 223; + t[138] = 174; + t[139] = 169; + t[140] = 8482; + t[141] = 180; + t[142] = 168; + t[143] = 8800; + t[144] = 198; + t[145] = 216; + t[146] = 8734; + t[147] = 177; + t[148] = 8804; + t[149] = 8805; + t[150] = 165; + t[151] = 181; + t[152] = 8706; + t[153] = 8721; + t[154] = 8719; + t[156] = 8747; + t[157] = 170; + t[158] = 186; + t[159] = 8486; + t[160] = 230; + t[161] = 248; + t[162] = 191; + t[163] = 161; + t[164] = 172; + t[165] = 8730; + t[166] = 402; + t[167] = 8776; + t[168] = 8710; + t[169] = 171; + t[170] = 187; + t[171] = 8230; + t[179] = 8220; + t[180] = 8221; + t[181] = 8216; + t[182] = 8217; + t[200] = 193; + t[203] = 205; + t[207] = 211; + t[210] = 218; + t[223] = 711; + t[224] = 321; + t[225] = 322; + t[226] = 352; + t[227] = 353; + t[228] = 381; + t[229] = 382; + t[233] = 221; + t[234] = 253; + t[252] = 263; + t[253] = 268; + t[254] = 269; + t[258] = 258; + t[260] = 260; + t[261] = 261; + t[265] = 280; + t[266] = 281; + t[267] = 282; + t[268] = 283; + t[269] = 313; + t[275] = 323; + t[276] = 324; + t[278] = 328; + t[283] = 344; + t[284] = 345; + t[285] = 346; + t[286] = 347; + t[292] = 367; + t[295] = 377; + t[296] = 378; + t[298] = 380; + t[305] = 963; + t[306] = 964; + t[307] = 966; + t[308] = 8215; + t[309] = 8252; + t[310] = 8319; + t[311] = 8359; + t[312] = 8592; + t[313] = 8593; + t[337] = 9552; + t[493] = 1039; + t[494] = 1040; + t[672] = 1488; + t[673] = 1489; + t[674] = 1490; + t[675] = 1491; + t[676] = 1492; + t[677] = 1493; + t[678] = 1494; + t[679] = 1495; + t[680] = 1496; + t[681] = 1497; + t[682] = 1498; + t[683] = 1499; + t[684] = 1500; + t[685] = 1501; + t[686] = 1502; + t[687] = 1503; + t[688] = 1504; + t[689] = 1505; + t[690] = 1506; + t[691] = 1507; + t[692] = 1508; + t[693] = 1509; + t[694] = 1510; + t[695] = 1511; + t[696] = 1512; + t[697] = 1513; + t[698] = 1514; + t[705] = 1524; + t[706] = 8362; + t[710] = 64288; + t[711] = 64298; + t[759] = 1617; + t[761] = 1776; + t[763] = 1778; + t[775] = 1652; + t[777] = 1764; + t[778] = 1780; + t[779] = 1781; + t[780] = 1782; + t[782] = 771; + t[783] = 64726; + t[786] = 8363; + t[788] = 8532; + t[790] = 768; + t[791] = 769; + t[792] = 768; + t[795] = 803; + t[797] = 64336; + t[798] = 64337; + t[799] = 64342; + t[800] = 64343; + t[801] = 64344; + t[802] = 64345; + t[803] = 64362; + t[804] = 64363; + t[805] = 64364; + t[2424] = 7821; + t[2425] = 7822; + t[2426] = 7823; + t[2427] = 7824; + t[2428] = 7825; + t[2429] = 7826; + t[2430] = 7827; + t[2433] = 7682; + t[2678] = 8045; + t[2679] = 8046; + t[2830] = 1552; + t[2838] = 686; + t[2840] = 751; + t[2842] = 753; + t[2843] = 754; + t[2844] = 755; + t[2846] = 757; + t[2856] = 767; + t[2857] = 848; + t[2858] = 849; + t[2862] = 853; + t[2863] = 854; + t[2864] = 855; + t[2865] = 861; + t[2866] = 862; + t[2906] = 7460; + t[2908] = 7462; + t[2909] = 7463; + t[2910] = 7464; + t[2912] = 7466; + t[2913] = 7467; + t[2914] = 7468; + t[2916] = 7470; + t[2917] = 7471; + t[2918] = 7472; + t[2920] = 7474; + t[2921] = 7475; + t[2922] = 7476; + t[2924] = 7478; + t[2925] = 7479; + t[2926] = 7480; + t[2928] = 7482; + t[2929] = 7483; + t[2930] = 7484; + t[2932] = 7486; + t[2933] = 7487; + t[2934] = 7488; + t[2936] = 7490; + t[2937] = 7491; + t[2938] = 7492; + t[2940] = 7494; + t[2941] = 7495; + t[2942] = 7496; + t[2944] = 7498; + t[2946] = 7500; + t[2948] = 7502; + t[2950] = 7504; + t[2951] = 7505; + t[2952] = 7506; + t[2954] = 7508; + t[2955] = 7509; + t[2956] = 7510; + t[2958] = 7512; + t[2959] = 7513; + t[2960] = 7514; + t[2962] = 7516; + t[2963] = 7517; + t[2964] = 7518; + t[2966] = 7520; + t[2967] = 7521; + t[2968] = 7522; + t[2970] = 7524; + t[2971] = 7525; + t[2972] = 7526; + t[2974] = 7528; + t[2975] = 7529; + t[2976] = 7530; + t[2978] = 1537; + t[2979] = 1538; + t[2980] = 1539; + t[2982] = 1549; + t[2983] = 1551; + t[2984] = 1552; + t[2986] = 1554; + t[2987] = 1555; + t[2988] = 1556; + t[2990] = 1623; + t[2991] = 1624; + t[2995] = 1775; + t[2999] = 1791; + t[3002] = 64290; + t[3003] = 64291; + t[3004] = 64292; + t[3006] = 64294; + t[3007] = 64295; + t[3008] = 64296; + t[3011] = 1900; + t[3014] = 8223; + t[3015] = 8244; + t[3017] = 7532; + t[3018] = 7533; + t[3019] = 7534; + t[3075] = 7590; + t[3076] = 7591; + t[3079] = 7594; + t[3080] = 7595; + t[3083] = 7598; + t[3084] = 7599; + t[3087] = 7602; + t[3088] = 7603; + t[3091] = 7606; + t[3092] = 7607; + t[3095] = 7610; + t[3096] = 7611; + t[3099] = 7614; + t[3100] = 7615; + t[3103] = 7618; + t[3104] = 7619; + t[3107] = 8337; + t[3108] = 8338; + t[3116] = 1884; + t[3119] = 1885; + t[3120] = 1885; + t[3123] = 1886; + t[3124] = 1886; + t[3127] = 1887; + t[3128] = 1887; + t[3131] = 1888; + t[3132] = 1888; + t[3135] = 1889; + t[3136] = 1889; + t[3139] = 1890; + t[3140] = 1890; + t[3143] = 1891; + t[3144] = 1891; + t[3147] = 1892; + t[3148] = 1892; + t[3153] = 580; + t[3154] = 581; + t[3157] = 584; + t[3158] = 585; + t[3161] = 588; + t[3162] = 589; + t[3165] = 891; + t[3166] = 892; + t[3169] = 1274; + t[3170] = 1275; + t[3173] = 1278; + t[3174] = 1279; + t[3181] = 7622; + t[3182] = 7623; + t[3282] = 11799; + t[3316] = 578; + t[3379] = 42785; + t[3393] = 1159; + t[3416] = 8377; +}); + +// The glyph map for ArialBlack differs slightly from the glyph map used for +// other well-known standard fonts. Hence we use this (incomplete) CID to GID +// mapping to adjust the glyph map for non-embedded ArialBlack fonts. +const getSupplementalGlyphMapForArialBlack = getLookupTableFactory( + function (t) { + t[227] = 322; + t[264] = 261; + t[291] = 346; + } +); + +// The glyph map for Calibri (a Windows font) differs from the glyph map used +// in the standard fonts. Hence we use this (incomplete) CID to GID mapping to +// adjust the glyph map for non-embedded Calibri fonts. +const getSupplementalGlyphMapForCalibri = getLookupTableFactory(function (t) { + t[1] = 32; + t[4] = 65; + t[5] = 192; + t[6] = 193; + t[9] = 196; + t[17] = 66; + t[18] = 67; + t[21] = 268; + t[24] = 68; + t[28] = 69; + t[29] = 200; + t[30] = 201; + t[32] = 282; + t[38] = 70; + t[39] = 71; + t[44] = 72; + t[47] = 73; + t[48] = 204; + t[49] = 205; + t[58] = 74; + t[60] = 75; + t[62] = 76; + t[68] = 77; + t[69] = 78; + t[75] = 79; + t[76] = 210; + t[80] = 214; + t[87] = 80; + t[89] = 81; + t[90] = 82; + t[92] = 344; + t[94] = 83; + t[97] = 352; + t[100] = 84; + t[104] = 85; + t[109] = 220; + t[115] = 86; + t[116] = 87; + t[121] = 88; + t[122] = 89; + t[124] = 221; + t[127] = 90; + t[129] = 381; + t[258] = 97; + t[259] = 224; + t[260] = 225; + t[263] = 228; + t[268] = 261; + t[271] = 98; + t[272] = 99; + t[273] = 263; + t[275] = 269; + t[282] = 100; + t[286] = 101; + t[287] = 232; + t[288] = 233; + t[290] = 283; + t[295] = 281; + t[296] = 102; + t[336] = 103; + t[346] = 104; + t[349] = 105; + t[350] = 236; + t[351] = 237; + t[361] = 106; + t[364] = 107; + t[367] = 108; + t[371] = 322; + t[373] = 109; + t[374] = 110; + t[381] = 111; + t[382] = 242; + t[383] = 243; + t[386] = 246; + t[393] = 112; + t[395] = 113; + t[396] = 114; + t[398] = 345; + t[400] = 115; + t[401] = 347; + t[403] = 353; + t[410] = 116; + t[437] = 117; + t[442] = 252; + t[448] = 118; + t[449] = 119; + t[454] = 120; + t[455] = 121; + t[457] = 253; + t[460] = 122; + t[462] = 382; + t[463] = 380; + t[853] = 44; + t[855] = 58; + t[856] = 46; + t[876] = 47; + t[878] = 45; + t[882] = 45; + t[894] = 40; + t[895] = 41; + t[896] = 91; + t[897] = 93; + t[923] = 64; + t[1004] = 48; + t[1005] = 49; + t[1006] = 50; + t[1007] = 51; + t[1008] = 52; + t[1009] = 53; + t[1010] = 54; + t[1011] = 55; + t[1012] = 56; + t[1013] = 57; + t[1081] = 37; + t[1085] = 43; + t[1086] = 45; +}); + +function getStandardFontName(name) { + const fontName = normalizeFontName(name); + const stdFontMap = getStdFontMap(); + return stdFontMap[fontName]; +} + +function isKnownFontName(name) { + const fontName = normalizeFontName(name); + return !!( + getStdFontMap()[fontName] || + getNonStdFontMap()[fontName] || + getSerifFonts()[fontName] || + getSymbolsFonts()[fontName] + ); +} + +export { + getFontNameToFileMap, + getGlyphMapForStandardFonts, + getNonStdFontMap, + getSerifFonts, + getStandardFontName, + getStdFontMap, + getSupplementalGlyphMapForArialBlack, + getSupplementalGlyphMapForCalibri, + getSymbolsFonts, + isKnownFontName, +}; diff --git a/frontend/public/pdf.js/src/core/stream.js b/frontend/public/pdf.js/src/core/stream.js new file mode 100644 index 0000000..7bc9791 --- /dev/null +++ b/frontend/public/pdf.js/src/core/stream.js @@ -0,0 +1,99 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { BaseStream } from "./base_stream.js"; +import { stringToBytes } from "../shared/util.js"; + +class Stream extends BaseStream { + constructor(arrayBuffer, start, length, dict) { + super(); + + this.bytes = + arrayBuffer instanceof Uint8Array + ? arrayBuffer + : new Uint8Array(arrayBuffer); + this.start = start || 0; + this.pos = this.start; + this.end = start + length || this.bytes.length; + this.dict = dict; + } + + get length() { + return this.end - this.start; + } + + get isEmpty() { + return this.length === 0; + } + + getByte() { + if (this.pos >= this.end) { + return -1; + } + return this.bytes[this.pos++]; + } + + getBytes(length) { + const bytes = this.bytes; + const pos = this.pos; + const strEnd = this.end; + + if (!length) { + return bytes.subarray(pos, strEnd); + } + let end = pos + length; + if (end > strEnd) { + end = strEnd; + } + this.pos = end; + return bytes.subarray(pos, end); + } + + getByteRange(begin, end) { + if (begin < 0) { + begin = 0; + } + if (end > this.end) { + end = this.end; + } + return this.bytes.subarray(begin, end); + } + + reset() { + this.pos = this.start; + } + + moveStart() { + this.start = this.pos; + } + + makeSubStream(start, length, dict = null) { + return new Stream(this.bytes.buffer, start, length, dict); + } +} + +class StringStream extends Stream { + constructor(str) { + super(stringToBytes(str)); + } +} + +class NullStream extends Stream { + constructor() { + super(new Uint8Array(0)); + } +} + +export { NullStream, Stream, StringStream }; diff --git a/frontend/public/pdf.js/src/core/struct_tree.js b/frontend/public/pdf.js/src/core/struct_tree.js new file mode 100644 index 0000000..fa27a14 --- /dev/null +++ b/frontend/public/pdf.js/src/core/struct_tree.js @@ -0,0 +1,913 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { AnnotationPrefix, stringToPDFString, warn } from "../shared/util.js"; +import { Dict, isName, Name, Ref, RefSetCache } from "./primitives.js"; +import { lookupNormalRect, stringToAsciiOrUTF16BE } from "./core_utils.js"; +import { NumberTree } from "./name_number_tree.js"; + +const MAX_DEPTH = 40; + +const StructElementType = { + PAGE_CONTENT: 1, + STREAM_CONTENT: 2, + OBJECT: 3, + ANNOTATION: 4, + ELEMENT: 5, +}; + +class StructTreeRoot { + constructor(rootDict, rootRef) { + this.dict = rootDict; + this.ref = rootRef instanceof Ref ? rootRef : null; + this.roleMap = new Map(); + this.structParentIds = null; + } + + init() { + this.readRoleMap(); + } + + #addIdToPage(pageRef, id, type) { + if (!(pageRef instanceof Ref) || id < 0) { + return; + } + this.structParentIds ||= new RefSetCache(); + let ids = this.structParentIds.get(pageRef); + if (!ids) { + ids = []; + this.structParentIds.put(pageRef, ids); + } + ids.push([id, type]); + } + + addAnnotationIdToPage(pageRef, id) { + this.#addIdToPage(pageRef, id, StructElementType.ANNOTATION); + } + + readRoleMap() { + const roleMapDict = this.dict.get("RoleMap"); + if (!(roleMapDict instanceof Dict)) { + return; + } + for (const [key, value] of roleMapDict) { + if (value instanceof Name) { + this.roleMap.set(key, value.name); + } + } + } + + static async canCreateStructureTree({ + catalogRef, + pdfManager, + newAnnotationsByPage, + }) { + if (!(catalogRef instanceof Ref)) { + warn("Cannot save the struct tree: no catalog reference."); + return false; + } + + let nextKey = 0; + let hasNothingToUpdate = true; + + for (const [pageIndex, elements] of newAnnotationsByPage) { + const { ref: pageRef } = await pdfManager.getPage(pageIndex); + if (!(pageRef instanceof Ref)) { + warn(`Cannot save the struct tree: page ${pageIndex} has no ref.`); + hasNothingToUpdate = true; + break; + } + for (const element of elements) { + if (element.accessibilityData?.type) { + // Each tag must have a structure type. + element.parentTreeId = nextKey++; + hasNothingToUpdate = false; + } + } + } + + if (hasNothingToUpdate) { + for (const elements of newAnnotationsByPage.values()) { + for (const element of elements) { + delete element.parentTreeId; + } + } + return false; + } + + return true; + } + + static async createStructureTree({ + newAnnotationsByPage, + xref, + catalogRef, + pdfManager, + changes, + }) { + const root = pdfManager.catalog.cloneDict(); + const cache = new RefSetCache(); + cache.put(catalogRef, root); + + const structTreeRootRef = xref.getNewTemporaryRef(); + root.set("StructTreeRoot", structTreeRootRef); + + const structTreeRoot = new Dict(xref); + structTreeRoot.set("Type", Name.get("StructTreeRoot")); + const parentTreeRef = xref.getNewTemporaryRef(); + structTreeRoot.set("ParentTree", parentTreeRef); + const kids = []; + structTreeRoot.set("K", kids); + cache.put(structTreeRootRef, structTreeRoot); + + const parentTree = new Dict(xref); + const nums = []; + parentTree.set("Nums", nums); + + const nextKey = await this.#writeKids({ + newAnnotationsByPage, + structTreeRootRef, + structTreeRoot: null, + kids, + nums, + xref, + pdfManager, + changes, + cache, + }); + structTreeRoot.set("ParentTreeNextKey", nextKey); + + cache.put(parentTreeRef, parentTree); + + for (const [ref, obj] of cache.items()) { + changes.put(ref, { + data: obj, + }); + } + } + + async canUpdateStructTree({ pdfManager, xref, newAnnotationsByPage }) { + if (!this.ref) { + warn("Cannot update the struct tree: no root reference."); + return false; + } + + let nextKey = this.dict.get("ParentTreeNextKey"); + if (!Number.isInteger(nextKey) || nextKey < 0) { + warn("Cannot update the struct tree: invalid next key."); + return false; + } + + const parentTree = this.dict.get("ParentTree"); + if (!(parentTree instanceof Dict)) { + warn("Cannot update the struct tree: ParentTree isn't a dict."); + return false; + } + const nums = parentTree.get("Nums"); + if (!Array.isArray(nums)) { + warn("Cannot update the struct tree: nums isn't an array."); + return false; + } + const numberTree = new NumberTree(parentTree, xref); + + for (const pageIndex of newAnnotationsByPage.keys()) { + const { pageDict } = await pdfManager.getPage(pageIndex); + if (!pageDict.has("StructParents")) { + // StructParents is required when the content stream has some tagged + // contents but a page can just have tagged annotations. + continue; + } + const id = pageDict.get("StructParents"); + if (!Number.isInteger(id) || !Array.isArray(numberTree.get(id))) { + warn(`Cannot save the struct tree: page ${pageIndex} has a wrong id.`); + return false; + } + } + + let hasNothingToUpdate = true; + for (const [pageIndex, elements] of newAnnotationsByPage) { + const { pageDict } = await pdfManager.getPage(pageIndex); + StructTreeRoot.#collectParents({ + elements, + xref: this.dict.xref, + pageDict, + numberTree, + }); + + for (const element of elements) { + if (element.accessibilityData?.type) { + // structParent can be undefined and in this case the positivity check + // will fail (it's why the expression isn't equivalent to a `.<.`). + if (!(element.accessibilityData.structParent >= 0)) { + // Each tag must have a structure type. + element.parentTreeId = nextKey++; + } + hasNothingToUpdate = false; + } + } + } + + if (hasNothingToUpdate) { + for (const elements of newAnnotationsByPage.values()) { + for (const element of elements) { + delete element.parentTreeId; + delete element.structTreeParent; + } + } + return false; + } + + return true; + } + + async updateStructureTree({ newAnnotationsByPage, pdfManager, changes }) { + const xref = this.dict.xref; + const structTreeRoot = this.dict.clone(); + const structTreeRootRef = this.ref; + const cache = new RefSetCache(); + cache.put(structTreeRootRef, structTreeRoot); + + let parentTreeRef = structTreeRoot.getRaw("ParentTree"); + let parentTree; + if (parentTreeRef instanceof Ref) { + parentTree = xref.fetch(parentTreeRef); + } else { + parentTree = parentTreeRef; + parentTreeRef = xref.getNewTemporaryRef(); + structTreeRoot.set("ParentTree", parentTreeRef); + } + parentTree = parentTree.clone(); + cache.put(parentTreeRef, parentTree); + + let nums = parentTree.getRaw("Nums"); + let numsRef = null; + if (nums instanceof Ref) { + numsRef = nums; + nums = xref.fetch(numsRef); + } + nums = nums.slice(); + if (!numsRef) { + parentTree.set("Nums", nums); + } + + const newNextKey = await StructTreeRoot.#writeKids({ + newAnnotationsByPage, + structTreeRootRef, + structTreeRoot: this, + kids: null, + nums, + xref, + pdfManager, + changes, + cache, + }); + + if (newNextKey === -1) { + // No new tags were added. + return; + } + + structTreeRoot.set("ParentTreeNextKey", newNextKey); + + if (numsRef) { + cache.put(numsRef, nums); + } + + for (const [ref, obj] of cache.items()) { + changes.put(ref, { + data: obj, + }); + } + } + + static async #writeKids({ + newAnnotationsByPage, + structTreeRootRef, + structTreeRoot, + kids, + nums, + xref, + pdfManager, + changes, + cache, + }) { + const objr = Name.get("OBJR"); + let nextKey = -1; + let structTreePageObjs; + + for (const [pageIndex, elements] of newAnnotationsByPage) { + const page = await pdfManager.getPage(pageIndex); + const { ref: pageRef } = page; + const isPageRef = pageRef instanceof Ref; + for (const { + accessibilityData, + ref, + parentTreeId, + structTreeParent, + } of elements) { + if (!accessibilityData?.type) { + continue; + } + + // We've some accessibility data, so we need to create a new tag or + // update an existing one. + const { structParent } = accessibilityData; + + if ( + structTreeRoot && + Number.isInteger(structParent) && + structParent >= 0 + ) { + let objs = (structTreePageObjs ||= new Map()).get(pageIndex); + if (objs === undefined) { + // We need to collect the objects for the page. + const structTreePage = new StructTreePage( + structTreeRoot, + page.pageDict + ); + objs = structTreePage.collectObjects(pageRef); + structTreePageObjs.set(pageIndex, objs); + } + const objRef = objs?.get(structParent); + if (objRef) { + // We update the existing tag. + const tagDict = xref.fetch(objRef).clone(); + StructTreeRoot.#writeProperties(tagDict, accessibilityData); + changes.put(objRef, { + data: tagDict, + }); + continue; + } + } + nextKey = Math.max(nextKey, parentTreeId); + + const tagRef = xref.getNewTemporaryRef(); + const tagDict = new Dict(xref); + + StructTreeRoot.#writeProperties(tagDict, accessibilityData); + + await this.#updateParentTag({ + structTreeParent, + tagDict, + newTagRef: tagRef, + structTreeRootRef, + fallbackKids: kids, + xref, + cache, + }); + + const objDict = new Dict(xref); + tagDict.set("K", objDict); + objDict.set("Type", objr); + if (isPageRef) { + // Pg is optional. + objDict.set("Pg", pageRef); + } + objDict.set("Obj", ref); + + cache.put(tagRef, tagDict); + nums.push(parentTreeId, tagRef); + } + } + return nextKey + 1; + } + + static #writeProperties( + tagDict, + { type, title, lang, alt, expanded, actualText } + ) { + // The structure type is required. + tagDict.set("S", Name.get(type)); + + if (title) { + tagDict.set("T", stringToAsciiOrUTF16BE(title)); + } + if (lang) { + tagDict.set("Lang", stringToAsciiOrUTF16BE(lang)); + } + if (alt) { + tagDict.set("Alt", stringToAsciiOrUTF16BE(alt)); + } + if (expanded) { + tagDict.set("E", stringToAsciiOrUTF16BE(expanded)); + } + if (actualText) { + tagDict.set("ActualText", stringToAsciiOrUTF16BE(actualText)); + } + } + + static #collectParents({ elements, xref, pageDict, numberTree }) { + const idToElements = new Map(); + for (const element of elements) { + if (element.structTreeParentId) { + const id = parseInt(element.structTreeParentId.split("_mc")[1], 10); + let elems = idToElements.get(id); + if (!elems) { + elems = []; + idToElements.set(id, elems); + } + elems.push(element); + } + } + + const id = pageDict.get("StructParents"); + if (!Number.isInteger(id)) { + return; + } + // The parentArray type has already been checked by the caller. + const parentArray = numberTree.get(id); + + const updateElement = (kid, pageKid, kidRef) => { + const elems = idToElements.get(kid); + if (elems) { + const parentRef = pageKid.getRaw("P"); + const parentDict = xref.fetchIfRef(parentRef); + if (parentRef instanceof Ref && parentDict instanceof Dict) { + // It should always the case, but we check just in case. + const params = { ref: kidRef, dict: pageKid }; + for (const element of elems) { + element.structTreeParent = params; + } + } + return true; + } + return false; + }; + for (const kidRef of parentArray) { + if (!(kidRef instanceof Ref)) { + continue; + } + const pageKid = xref.fetch(kidRef); + const k = pageKid.get("K"); + if (Number.isInteger(k)) { + updateElement(k, pageKid, kidRef); + continue; + } + + if (!Array.isArray(k)) { + continue; + } + for (let kid of k) { + kid = xref.fetchIfRef(kid); + if (Number.isInteger(kid) && updateElement(kid, pageKid, kidRef)) { + break; + } + if (!(kid instanceof Dict)) { + continue; + } + if (!isName(kid.get("Type"), "MCR")) { + break; + } + const mcid = kid.get("MCID"); + if (Number.isInteger(mcid) && updateElement(mcid, pageKid, kidRef)) { + break; + } + } + } + } + + static async #updateParentTag({ + structTreeParent, + tagDict, + newTagRef, + structTreeRootRef, + fallbackKids, + xref, + cache, + }) { + let ref = null; + let parentRef; + if (structTreeParent) { + ({ ref } = structTreeParent); + + // We get the parent of the tag. + parentRef = structTreeParent.dict.getRaw("P") || structTreeRootRef; + } else { + parentRef = structTreeRootRef; + } + + tagDict.set("P", parentRef); + + // We get the kids in order to insert a new tag at the right position. + const parentDict = xref.fetchIfRef(parentRef); + if (!parentDict) { + fallbackKids.push(newTagRef); + return; + } + + let cachedParentDict = cache.get(parentRef); + if (!cachedParentDict) { + cachedParentDict = parentDict.clone(); + cache.put(parentRef, cachedParentDict); + } + const parentKidsRaw = cachedParentDict.getRaw("K"); + let cachedParentKids = + parentKidsRaw instanceof Ref ? cache.get(parentKidsRaw) : null; + if (!cachedParentKids) { + cachedParentKids = xref.fetchIfRef(parentKidsRaw); + cachedParentKids = Array.isArray(cachedParentKids) + ? cachedParentKids.slice() + : [parentKidsRaw]; + const parentKidsRef = xref.getNewTemporaryRef(); + cachedParentDict.set("K", parentKidsRef); + cache.put(parentKidsRef, cachedParentKids); + } + + const index = cachedParentKids.indexOf(ref); + cachedParentKids.splice( + index >= 0 ? index + 1 : cachedParentKids.length, + 0, + newTagRef + ); + } +} + +/** + * Instead of loading the whole tree we load just the page's relevant structure + * elements, which means we need a wrapper structure to represent the tree. + */ +class StructElementNode { + constructor(tree, dict) { + this.tree = tree; + this.dict = dict; + this.kids = []; + this.parseKids(); + } + + get role() { + const nameObj = this.dict.get("S"); + const name = nameObj instanceof Name ? nameObj.name : ""; + const { root } = this.tree; + if (root.roleMap.has(name)) { + return root.roleMap.get(name); + } + return name; + } + + parseKids() { + let pageObjId = null; + const objRef = this.dict.getRaw("Pg"); + if (objRef instanceof Ref) { + pageObjId = objRef.toString(); + } + const kids = this.dict.get("K"); + if (Array.isArray(kids)) { + for (const kid of kids) { + const element = this.parseKid(pageObjId, kid); + if (element) { + this.kids.push(element); + } + } + } else { + const element = this.parseKid(pageObjId, kids); + if (element) { + this.kids.push(element); + } + } + } + + parseKid(pageObjId, kid) { + // A direct link to content, the integer is an mcid. + if (Number.isInteger(kid)) { + if (this.tree.pageDict.objId !== pageObjId) { + return null; + } + + return new StructElement({ + type: StructElementType.PAGE_CONTENT, + mcid: kid, + pageObjId, + }); + } + + // Find the dictionary for the kid. + let kidDict = null; + if (kid instanceof Ref) { + kidDict = this.dict.xref.fetch(kid); + } else if (kid instanceof Dict) { + kidDict = kid; + } + if (!kidDict) { + return null; + } + const pageRef = kidDict.getRaw("Pg"); + if (pageRef instanceof Ref) { + pageObjId = pageRef.toString(); + } + + const type = + kidDict.get("Type") instanceof Name ? kidDict.get("Type").name : null; + if (type === "MCR") { + if (this.tree.pageDict.objId !== pageObjId) { + return null; + } + const kidRef = kidDict.getRaw("Stm"); + return new StructElement({ + type: StructElementType.STREAM_CONTENT, + refObjId: kidRef instanceof Ref ? kidRef.toString() : null, + pageObjId, + mcid: kidDict.get("MCID"), + }); + } + + if (type === "OBJR") { + if (this.tree.pageDict.objId !== pageObjId) { + return null; + } + const kidRef = kidDict.getRaw("Obj"); + return new StructElement({ + type: StructElementType.OBJECT, + refObjId: kidRef instanceof Ref ? kidRef.toString() : null, + pageObjId, + }); + } + + return new StructElement({ + type: StructElementType.ELEMENT, + dict: kidDict, + }); + } +} + +class StructElement { + constructor({ + type, + dict = null, + mcid = null, + pageObjId = null, + refObjId = null, + }) { + this.type = type; + this.dict = dict; + this.mcid = mcid; + this.pageObjId = pageObjId; + this.refObjId = refObjId; + this.parentNode = null; + } +} + +class StructTreePage { + constructor(structTreeRoot, pageDict) { + this.root = structTreeRoot; + this.rootDict = structTreeRoot ? structTreeRoot.dict : null; + this.pageDict = pageDict; + this.nodes = []; + } + + /** + * Collect all the objects (i.e. tag) that are part of the page and return a + * map of the structure element id to the object reference. + * @param {Ref} pageRef + * @returns {Map} + */ + collectObjects(pageRef) { + if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) { + return null; + } + + const parentTree = this.rootDict.get("ParentTree"); + if (!parentTree) { + return null; + } + const ids = this.root.structParentIds?.get(pageRef); + if (!ids) { + return null; + } + + const map = new Map(); + const numberTree = new NumberTree(parentTree, this.rootDict.xref); + + for (const [elemId] of ids) { + const obj = numberTree.getRaw(elemId); + if (obj instanceof Ref) { + map.set(elemId, obj); + } + } + return map; + } + + parse(pageRef) { + if (!this.root || !this.rootDict || !(pageRef instanceof Ref)) { + return; + } + + const parentTree = this.rootDict.get("ParentTree"); + if (!parentTree) { + return; + } + const id = this.pageDict.get("StructParents"); + const ids = this.root.structParentIds?.get(pageRef); + if (!Number.isInteger(id) && !ids) { + return; + } + + const map = new Map(); + const numberTree = new NumberTree(parentTree, this.rootDict.xref); + + if (Number.isInteger(id)) { + const parentArray = numberTree.get(id); + if (Array.isArray(parentArray)) { + for (const ref of parentArray) { + if (ref instanceof Ref) { + this.addNode(this.rootDict.xref.fetch(ref), map); + } + } + } + } + + if (!ids) { + return; + } + for (const [elemId, type] of ids) { + const obj = numberTree.get(elemId); + if (obj) { + const elem = this.addNode(this.rootDict.xref.fetchIfRef(obj), map); + if ( + elem?.kids?.length === 1 && + elem.kids[0].type === StructElementType.OBJECT + ) { + // The node in the struct tree is wrapping an object (annotation + // or xobject), so we need to update the type of the node to match + // the type of the object. + elem.kids[0].type = type; + } + } + } + } + + addNode(dict, map, level = 0) { + if (level > MAX_DEPTH) { + warn("StructTree MAX_DEPTH reached."); + return null; + } + if (!(dict instanceof Dict)) { + return null; + } + + if (map.has(dict)) { + return map.get(dict); + } + + const element = new StructElementNode(this, dict); + map.set(dict, element); + + const parent = dict.get("P"); + + if (!parent || isName(parent.get("Type"), "StructTreeRoot")) { + if (!this.addTopLevelNode(dict, element)) { + map.delete(dict); + } + return element; + } + + const parentNode = this.addNode(parent, map, level + 1); + if (!parentNode) { + return element; + } + let save = false; + for (const kid of parentNode.kids) { + if (kid.type === StructElementType.ELEMENT && kid.dict === dict) { + kid.parentNode = element; + save = true; + } + } + if (!save) { + map.delete(dict); + } + return element; + } + + addTopLevelNode(dict, element) { + const obj = this.rootDict.get("K"); + if (!obj) { + return false; + } + + if (obj instanceof Dict) { + if (obj.objId !== dict.objId) { + return false; + } + this.nodes[0] = element; + return true; + } + + if (!Array.isArray(obj)) { + return true; + } + let save = false; + for (let i = 0; i < obj.length; i++) { + const kidRef = obj[i]; + if (kidRef?.toString() === dict.objId) { + this.nodes[i] = element; + save = true; + } + } + return save; + } + + /** + * Convert the tree structure into a simplified object literal that can + * be sent to the main thread. + * @returns {Object} + */ + get serializable() { + function nodeToSerializable(node, parent, level = 0) { + if (level > MAX_DEPTH) { + warn("StructTree too deep to be fully serialized."); + return; + } + const obj = Object.create(null); + obj.role = node.role; + obj.children = []; + parent.children.push(obj); + let alt = node.dict.get("Alt"); + if (typeof alt !== "string") { + alt = node.dict.get("ActualText"); + } + if (typeof alt === "string") { + obj.alt = stringToPDFString(alt); + } + + const a = node.dict.get("A"); + if (a instanceof Dict) { + const bbox = lookupNormalRect(a.getArray("BBox"), null); + if (bbox) { + obj.bbox = bbox; + } else { + const width = a.get("Width"); + const height = a.get("Height"); + if ( + typeof width === "number" && + width > 0 && + typeof height === "number" && + height > 0 + ) { + obj.bbox = [0, 0, width, height]; + } + } + // TODO: If the bbox is not available, we should try to get it from + // the content stream. + // For example when rendering on the canvas the commands between the + // beginning and the end of the marked-content sequence, we can + // compute the overall bbox. + } + + const lang = node.dict.get("Lang"); + if (typeof lang === "string") { + obj.lang = stringToPDFString(lang); + } + + for (const kid of node.kids) { + const kidElement = + kid.type === StructElementType.ELEMENT ? kid.parentNode : null; + if (kidElement) { + nodeToSerializable(kidElement, obj, level + 1); + continue; + } else if ( + kid.type === StructElementType.PAGE_CONTENT || + kid.type === StructElementType.STREAM_CONTENT + ) { + obj.children.push({ + type: "content", + id: `p${kid.pageObjId}_mc${kid.mcid}`, + }); + } else if (kid.type === StructElementType.OBJECT) { + obj.children.push({ + type: "object", + id: kid.refObjId, + }); + } else if (kid.type === StructElementType.ANNOTATION) { + obj.children.push({ + type: "annotation", + id: `${AnnotationPrefix}${kid.refObjId}`, + }); + } + } + } + + const root = Object.create(null); + root.children = []; + root.role = "Root"; + for (const child of this.nodes) { + if (!child) { + continue; + } + nodeToSerializable(child, root); + } + return root; + } +} + +export { StructTreePage, StructTreeRoot }; diff --git a/frontend/public/pdf.js/src/core/to_unicode_map.js b/frontend/public/pdf.js/src/core/to_unicode_map.js new file mode 100644 index 0000000..e1a0e5c --- /dev/null +++ b/frontend/public/pdf.js/src/core/to_unicode_map.js @@ -0,0 +1,103 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { unreachable } from "../shared/util.js"; + +class ToUnicodeMap { + constructor(cmap = []) { + // The elements of this._map can be integers or strings, depending on how + // `cmap` was created. + this._map = cmap; + } + + get length() { + return this._map.length; + } + + forEach(callback) { + for (const charCode in this._map) { + callback(charCode, this._map[charCode].codePointAt(0)); + } + } + + has(i) { + return this._map[i] !== undefined; + } + + get(i) { + return this._map[i]; + } + + charCodeOf(value) { + // `Array.prototype.indexOf` is *extremely* inefficient for arrays which + // are both very sparse and very large (see issue8372.pdf). + const map = this._map; + if (map.length <= 0x10000) { + return map.indexOf(value); + } + for (const charCode in map) { + if (map[charCode] === value) { + return charCode | 0; + } + } + return -1; + } + + amend(map) { + for (const charCode in map) { + this._map[charCode] = map[charCode]; + } + } +} + +class IdentityToUnicodeMap { + constructor(firstChar, lastChar) { + this.firstChar = firstChar; + this.lastChar = lastChar; + } + + get length() { + return this.lastChar + 1 - this.firstChar; + } + + forEach(callback) { + for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) { + callback(i, i); + } + } + + has(i) { + return this.firstChar <= i && i <= this.lastChar; + } + + get(i) { + if (this.firstChar <= i && i <= this.lastChar) { + return String.fromCharCode(i); + } + return undefined; + } + + charCodeOf(v) { + return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar + ? v + : -1; + } + + amend(map) { + unreachable("Should not call amend()"); + } +} + +export { IdentityToUnicodeMap, ToUnicodeMap }; diff --git a/frontend/public/pdf.js/src/core/type1_font.js b/frontend/public/pdf.js/src/core/type1_font.js new file mode 100644 index 0000000..94f8105 --- /dev/null +++ b/frontend/public/pdf.js/src/core/type1_font.js @@ -0,0 +1,423 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + CFF, + CFFCharset, + CFFCompiler, + CFFHeader, + CFFIndex, + CFFPrivateDict, + CFFStandardStrings, + CFFStrings, + CFFTopDict, +} from "./cff_parser.js"; +import { FormatError, warn } from "../shared/util.js"; +import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; +import { isWhiteSpace } from "./core_utils.js"; +import { Stream } from "./stream.js"; +import { Type1Parser } from "./type1_parser.js"; + +function findBlock(streamBytes, signature, startIndex) { + const streamBytesLength = streamBytes.length; + const signatureLength = signature.length; + const scanLength = streamBytesLength - signatureLength; + + let i = startIndex, + found = false; + while (i < scanLength) { + let j = 0; + while (j < signatureLength && streamBytes[i + j] === signature[j]) { + j++; + } + if (j >= signatureLength) { + // `signature` found, skip over whitespace. + i += j; + while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) { + i++; + } + found = true; + break; + } + i++; + } + return { + found, + length: i, + }; +} + +function getHeaderBlock(stream, suggestedLength) { + const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; + + const streamStartPos = stream.pos; // Save the initial stream position. + let headerBytes, headerBytesLength, block; + try { + headerBytes = stream.getBytes(suggestedLength); + headerBytesLength = headerBytes.length; + } catch { + // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array + // cannot hold the result of `getBytes`, and fallback to simply checking + // the entire stream (fixes issue3928.pdf). + } + + if (headerBytesLength === suggestedLength) { + // Most of the time `suggestedLength` is correct, so to speed things up we + // initially only check the last few bytes to see if the header was found. + // Otherwise we (potentially) check the entire stream to prevent errors in + // `Type1Parser` (fixes issue5686.pdf). + block = findBlock( + headerBytes, + EEXEC_SIGNATURE, + suggestedLength - 2 * EEXEC_SIGNATURE.length + ); + + if (block.found && block.length === suggestedLength) { + return { + stream: new Stream(headerBytes), + length: suggestedLength, + }; + } + } + warn('Invalid "Length1" property in Type1 font -- trying to recover.'); + stream.pos = streamStartPos; // Reset the stream position. + + const SCAN_BLOCK_LENGTH = 2048; + let actualLength; + while (true) { + const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); + block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); + + if (block.length === 0) { + break; + } + stream.pos += block.length; // Update the stream position. + + if (block.found) { + actualLength = stream.pos - streamStartPos; + break; + } + } + stream.pos = streamStartPos; // Reset the stream position. + + if (actualLength) { + return { + stream: new Stream(stream.getBytes(actualLength)), + length: actualLength, + }; + } + warn('Unable to recover "Length1" property in Type1 font -- using as is.'); + return { + stream: new Stream(stream.getBytes(suggestedLength)), + length: suggestedLength, + }; +} + +function getEexecBlock(stream, suggestedLength) { + // We should ideally parse the eexec block to ensure that `suggestedLength` + // is correct, so we don't truncate the block data if it's too small. + // However, this would also require checking if the fixed-content portion + // exists (using the 'Length3' property), and ensuring that it's valid. + // + // Given that `suggestedLength` almost always is correct, all the validation + // would require a great deal of unnecessary parsing for most fonts. + // To save time, we always fetch the entire stream instead, which also avoid + // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`). + // + // NOTE: This means that the function can include the fixed-content portion + // in the returned eexec block. In practice this does *not* seem to matter, + // since `Type1Parser_extractFontProgram` will skip over any non-commands. + const eexecBytes = stream.getBytes(); + if (eexecBytes.length === 0) { + throw new FormatError("getEexecBlock - no font program found."); + } + return { + stream: new Stream(eexecBytes), + length: eexecBytes.length, + }; +} + +/** + * Type1Font is also a CIDFontType0. + */ +class Type1Font { + constructor(name, file, properties) { + // Some bad generators embed pfb file as is, we have to strip 6-byte header. + // Also, length1 and length2 might be off by 6 bytes as well. + // http://www.math.ubc.ca/~cass/piscript/type1.pdf + const PFB_HEADER_SIZE = 6; + let headerBlockLength = properties.length1; + let eexecBlockLength = properties.length2; + let pfbHeader = file.peekBytes(PFB_HEADER_SIZE); + const pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01; + if (pfbHeaderPresent) { + file.skip(PFB_HEADER_SIZE); + headerBlockLength = + (pfbHeader[5] << 24) | + (pfbHeader[4] << 16) | + (pfbHeader[3] << 8) | + pfbHeader[2]; + } + + // Get the data block containing glyphs and subrs information + const headerBlock = getHeaderBlock(file, headerBlockLength); + const headerBlockParser = new Type1Parser( + headerBlock.stream, + false, + SEAC_ANALYSIS_ENABLED + ); + headerBlockParser.extractFontHeader(properties); + + if (pfbHeaderPresent) { + pfbHeader = file.getBytes(PFB_HEADER_SIZE); + eexecBlockLength = + (pfbHeader[5] << 24) | + (pfbHeader[4] << 16) | + (pfbHeader[3] << 8) | + pfbHeader[2]; + } + + // Decrypt the data blocks and retrieve it's content + const eexecBlock = getEexecBlock(file, eexecBlockLength); + const eexecBlockParser = new Type1Parser( + eexecBlock.stream, + true, + SEAC_ANALYSIS_ENABLED + ); + const data = eexecBlockParser.extractFontProgram(properties); + for (const key in data.properties) { + properties[key] = data.properties[key]; + } + + const charstrings = data.charstrings; + const type2Charstrings = this.getType2Charstrings(charstrings); + const subrs = this.getType2Subrs(data.subrs); + + this.charstrings = charstrings; + this.data = this.wrap( + name, + type2Charstrings, + this.charstrings, + subrs, + properties + ); + this.seacs = this.getSeacs(data.charstrings); + } + + get numGlyphs() { + return this.charstrings.length + 1; + } + + getCharset() { + const charset = [".notdef"]; + for (const { glyphName } of this.charstrings) { + charset.push(glyphName); + } + return charset; + } + + getGlyphMapping(properties) { + const charstrings = this.charstrings; + + if (properties.composite) { + const charCodeToGlyphId = Object.create(null); + // Map CIDs directly to GIDs. + for ( + let glyphId = 0, charstringsLen = charstrings.length; + glyphId < charstringsLen; + glyphId++ + ) { + const charCode = properties.cMap.charCodeOf(glyphId); + // Add 1 because glyph 0 is duplicated. + charCodeToGlyphId[charCode] = glyphId + 1; + } + return charCodeToGlyphId; + } + + const glyphNames = [".notdef"]; + let builtInEncoding, glyphId; + for (glyphId = 0; glyphId < charstrings.length; glyphId++) { + glyphNames.push(charstrings[glyphId].glyphName); + } + const encoding = properties.builtInEncoding; + if (encoding) { + builtInEncoding = Object.create(null); + for (const charCode in encoding) { + glyphId = glyphNames.indexOf(encoding[charCode]); + if (glyphId >= 0) { + builtInEncoding[charCode] = glyphId; + } + } + } + + return type1FontGlyphMapping(properties, builtInEncoding, glyphNames); + } + + hasGlyphId(id) { + if (id < 0 || id >= this.numGlyphs) { + return false; + } + if (id === 0) { + // notdef is always defined. + return true; + } + const glyph = this.charstrings[id - 1]; + return glyph.charstring.length > 0; + } + + getSeacs(charstrings) { + const seacMap = []; + for (let i = 0, ii = charstrings.length; i < ii; i++) { + const charstring = charstrings[i]; + if (charstring.seac) { + // Offset by 1 for .notdef + seacMap[i + 1] = charstring.seac; + } + } + return seacMap; + } + + getType2Charstrings(type1Charstrings) { + const type2Charstrings = []; + for (const type1Charstring of type1Charstrings) { + type2Charstrings.push(type1Charstring.charstring); + } + return type2Charstrings; + } + + getType2Subrs(type1Subrs) { + let bias = 0; + const count = type1Subrs.length; + if (count < 1133) { + bias = 107; + } else if (count < 33769) { + bias = 1131; + } else { + bias = 32768; + } + + // Add a bunch of empty subrs to deal with the Type2 bias + const type2Subrs = []; + let i; + for (i = 0; i < bias; i++) { + type2Subrs.push([0x0b]); + } + + for (i = 0; i < count; i++) { + type2Subrs.push(type1Subrs[i]); + } + + return type2Subrs; + } + + wrap(name, glyphs, charstrings, subrs, properties) { + const cff = new CFF(); + cff.header = new CFFHeader(1, 0, 4, 4); + + cff.names = [name]; + + const topDict = new CFFTopDict(); + // CFF strings IDs 0...390 are predefined names, so refering + // to entries in our own String INDEX starts at SID 391. + topDict.setByName("version", 391); + topDict.setByName("Notice", 392); + topDict.setByName("FullName", 393); + topDict.setByName("FamilyName", 394); + topDict.setByName("Weight", 395); + topDict.setByName("Encoding", null); // placeholder + topDict.setByName("FontMatrix", properties.fontMatrix); + topDict.setByName("FontBBox", properties.bbox); + topDict.setByName("charset", null); // placeholder + topDict.setByName("CharStrings", null); // placeholder + topDict.setByName("Private", null); // placeholder + cff.topDict = topDict; + + const strings = new CFFStrings(); + strings.add("Version 0.11"); // Version + strings.add("See original notice"); // Notice + strings.add(name); // FullName + strings.add(name); // FamilyName + strings.add("Medium"); // Weight + cff.strings = strings; + + cff.globalSubrIndex = new CFFIndex(); + + const count = glyphs.length; + const charsetArray = [".notdef"]; + let i, ii; + for (i = 0; i < count; i++) { + const glyphName = charstrings[i].glyphName; + const index = CFFStandardStrings.indexOf(glyphName); + if (index === -1) { + strings.add(glyphName); + } + charsetArray.push(glyphName); + } + cff.charset = new CFFCharset(false, 0, charsetArray); + + const charStringsIndex = new CFFIndex(); + charStringsIndex.add([0x8b, 0x0e]); // .notdef + for (i = 0; i < count; i++) { + charStringsIndex.add(glyphs[i]); + } + cff.charStrings = charStringsIndex; + + const privateDict = new CFFPrivateDict(); + privateDict.setByName("Subrs", null); // placeholder + const fields = [ + "BlueValues", + "OtherBlues", + "FamilyBlues", + "FamilyOtherBlues", + "StemSnapH", + "StemSnapV", + "BlueShift", + "BlueFuzz", + "BlueScale", + "LanguageGroup", + "ExpansionFactor", + "ForceBold", + "StdHW", + "StdVW", + ]; + for (i = 0, ii = fields.length; i < ii; i++) { + const field = fields[i]; + if (!(field in properties.privateData)) { + continue; + } + const value = properties.privateData[field]; + if (Array.isArray(value)) { + // All of the private dictionary array data in CFF must be stored as + // "delta-encoded" numbers. + for (let j = value.length - 1; j > 0; j--) { + value[j] -= value[j - 1]; // ... difference from previous value + } + } + privateDict.setByName(field, value); + } + cff.topDict.privateDict = privateDict; + + const subrIndex = new CFFIndex(); + for (i = 0, ii = subrs.length; i < ii; i++) { + subrIndex.add(subrs[i]); + } + privateDict.subrsIndex = subrIndex; + + const compiler = new CFFCompiler(cff); + return compiler.compile(); + } +} + +export { Type1Font }; diff --git a/frontend/public/pdf.js/src/core/type1_parser.js b/frontend/public/pdf.js/src/core/type1_parser.js new file mode 100644 index 0000000..13d0439 --- /dev/null +++ b/frontend/public/pdf.js/src/core/type1_parser.js @@ -0,0 +1,773 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { getEncoding } from "./encodings.js"; +import { isWhiteSpace } from "./core_utils.js"; +import { Stream } from "./stream.js"; +import { warn } from "../shared/util.js"; + +// Hinting is currently disabled due to unknown problems on windows +// in tracemonkey and various other pdfs with type1 fonts. +const HINTING_ENABLED = false; + +const COMMAND_MAP = { + hstem: [1], + vstem: [3], + vmoveto: [4], + rlineto: [5], + hlineto: [6], + vlineto: [7], + rrcurveto: [8], + callsubr: [10], + flex: [12, 35], + drop: [12, 18], + endchar: [14], + rmoveto: [21], + hmoveto: [22], + vhcurveto: [30], + hvcurveto: [31], +}; + +/** + * CharStrings are encoded following the the CharString Encoding sequence + * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. + * The value in a byte indicates a command, a number, or subsequent bytes + * that are to be interpreted in a special way. + * + * CharString Number Encoding: + * A CharString byte containing the values from 32 through 255 inclusive + * indicate an integer. These values are decoded in four ranges. + * + * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, + * indicate the integer v - 139. Thus, the integer values from -107 through + * 107 inclusive may be encoded in single byte. + * + * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * [(v - 247) x 256] + w + 108 + * + * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * -[(v - 251) * 256] - w - 108 + * + * 4. A CharString containing the value 255 indicates that the next 4 bytes + * are a two complement signed integer. The first of these bytes contains the + * highest order bits, the second byte contains the next higher order bits + * and the fourth byte contain the lowest order bits. + * + * + * CharString Command Encoding: + * CharStrings commands are encoded in 1 or 2 bytes. + * + * Single byte commands are encoded in 1 byte that contains a value between + * 0 and 31 inclusive. + * If a command byte contains the value 12, then the value in the next byte + * indicates a command. This "escape" mechanism allows many extra commands + * to be encoded and this encoding technique helps to minimize the length of + * the charStrings. + */ +class Type1CharString { + constructor() { + this.width = 0; + this.lsb = 0; + this.flexing = false; + this.output = []; + this.stack = []; + } + + convert(encoded, subrs, seacAnalysisEnabled) { + const count = encoded.length; + let error = false; + let wx, sbx, subrNumber; + for (let i = 0; i < count; i++) { + let value = encoded[i]; + if (value < 32) { + if (value === 12) { + value = (value << 8) + encoded[++i]; + } + switch (value) { + case 1: // hstem + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + error = this.executeCommand(2, COMMAND_MAP.hstem); + break; + case 3: // vstem + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + error = this.executeCommand(2, COMMAND_MAP.vstem); + break; + case 4: // vmoveto + if (this.flexing) { + if (this.stack.length < 1) { + error = true; + break; + } + // Add the dx for flex and but also swap the values so they are + // the right order. + const dy = this.stack.pop(); + this.stack.push(0, dy); + break; + } + error = this.executeCommand(1, COMMAND_MAP.vmoveto); + break; + case 5: // rlineto + error = this.executeCommand(2, COMMAND_MAP.rlineto); + break; + case 6: // hlineto + error = this.executeCommand(1, COMMAND_MAP.hlineto); + break; + case 7: // vlineto + error = this.executeCommand(1, COMMAND_MAP.vlineto); + break; + case 8: // rrcurveto + error = this.executeCommand(6, COMMAND_MAP.rrcurveto); + break; + case 9: // closepath + // closepath is a Type1 command that does not take argument and is + // useless in Type2 and it can simply be ignored. + this.stack = []; + break; + case 10: // callsubr + if (this.stack.length < 1) { + error = true; + break; + } + subrNumber = this.stack.pop(); + if (!subrs[subrNumber]) { + error = true; + break; + } + error = this.convert(subrs[subrNumber], subrs, seacAnalysisEnabled); + break; + case 11: // return + return error; + case 13: // hsbw + if (this.stack.length < 2) { + error = true; + break; + } + // To convert to type2 we have to move the width value to the + // first part of the charstring and then use hmoveto with lsb. + wx = this.stack.pop(); + sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(wx, sbx); + error = this.executeCommand(2, COMMAND_MAP.hmoveto); + break; + case 14: // endchar + this.output.push(COMMAND_MAP.endchar[0]); + break; + case 21: // rmoveto + if (this.flexing) { + break; + } + error = this.executeCommand(2, COMMAND_MAP.rmoveto); + break; + case 22: // hmoveto + if (this.flexing) { + // Add the dy for flex. + this.stack.push(0); + break; + } + error = this.executeCommand(1, COMMAND_MAP.hmoveto); + break; + case 30: // vhcurveto + error = this.executeCommand(4, COMMAND_MAP.vhcurveto); + break; + case 31: // hvcurveto + error = this.executeCommand(4, COMMAND_MAP.hvcurveto); + break; + case (12 << 8) + 0: // dotsection + // dotsection is a Type1 command to specify some hinting feature + // for dots that do not take a parameter and it can safely be + // ignored for Type2. + this.stack = []; + break; + case (12 << 8) + 1: // vstem3 + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + // [vh]stem3 are Type1 only and Type2 supports [vh]stem with + // multiple parameters, so instead of returning [vh]stem3 take a + // shortcut and return [vhstem] instead. + error = this.executeCommand(2, COMMAND_MAP.vstem); + break; + case (12 << 8) + 2: // hstem3 + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + // See vstem3. + error = this.executeCommand(2, COMMAND_MAP.hstem); + break; + case (12 << 8) + 6: // seac + // seac is like type 2's special endchar but it doesn't use the + // first argument asb, so remove it. + if (seacAnalysisEnabled) { + const asb = this.stack.at(-5); + this.seac = this.stack.splice(-4, 4); + this.seac[0] += this.lsb - asb; + error = this.executeCommand(0, COMMAND_MAP.endchar); + } else { + error = this.executeCommand(4, COMMAND_MAP.endchar); + } + break; + case (12 << 8) + 7: // sbw + if (this.stack.length < 4) { + error = true; + break; + } + // To convert to type2 we have to move the width value to the + // first part of the charstring and then use rmoveto with + // (dx, dy). The height argument will not be used for vmtx and + // vhea tables reconstruction -- ignoring it. + this.stack.pop(); // wy + wx = this.stack.pop(); + const sby = this.stack.pop(); + sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(wx, sbx, sby); + error = this.executeCommand(3, COMMAND_MAP.rmoveto); + break; + case (12 << 8) + 12: // div + if (this.stack.length < 2) { + error = true; + break; + } + const num2 = this.stack.pop(); + const num1 = this.stack.pop(); + this.stack.push(num1 / num2); + break; + case (12 << 8) + 16: // callothersubr + if (this.stack.length < 2) { + error = true; + break; + } + subrNumber = this.stack.pop(); + const numArgs = this.stack.pop(); + if (subrNumber === 0 && numArgs === 3) { + const flexArgs = this.stack.splice(-17, 17); + this.stack.push( + flexArgs[2] + flexArgs[0], // bcp1x + rpx + flexArgs[3] + flexArgs[1], // bcp1y + rpy + flexArgs[4], // bcp2x + flexArgs[5], // bcp2y + flexArgs[6], // p2x + flexArgs[7], // p2y + flexArgs[8], // bcp3x + flexArgs[9], // bcp3y + flexArgs[10], // bcp4x + flexArgs[11], // bcp4y + flexArgs[12], // p3x + flexArgs[13], // p3y + flexArgs[14] // flexDepth + // 15 = finalx unused by flex + // 16 = finaly unused by flex + ); + error = this.executeCommand(13, COMMAND_MAP.flex, true); + this.flexing = false; + this.stack.push(flexArgs[15], flexArgs[16]); + } else if (subrNumber === 1 && numArgs === 0) { + this.flexing = true; + } + break; + case (12 << 8) + 17: // pop + // Ignore this since it is only used with othersubr. + break; + case (12 << 8) + 33: // setcurrentpoint + // Ignore for now. + this.stack = []; + break; + default: + warn('Unknown type 1 charstring command of "' + value + '"'); + break; + } + if (error) { + break; + } + continue; + } else if (value <= 246) { + value -= 139; + } else if (value <= 250) { + value = (value - 247) * 256 + encoded[++i] + 108; + } else if (value <= 254) { + value = -((value - 251) * 256) - encoded[++i] - 108; + } else { + value = + ((encoded[++i] & 0xff) << 24) | + ((encoded[++i] & 0xff) << 16) | + ((encoded[++i] & 0xff) << 8) | + ((encoded[++i] & 0xff) << 0); + } + this.stack.push(value); + } + return error; + } + + executeCommand(howManyArgs, command, keepStack) { + const stackLength = this.stack.length; + if (howManyArgs > stackLength) { + return true; + } + const start = stackLength - howManyArgs; + for (let i = start; i < stackLength; i++) { + let value = this.stack[i]; + if (Number.isInteger(value)) { + this.output.push(28, (value >> 8) & 0xff, value & 0xff); + } else { + // fixed point + value = (65536 * value) | 0; + this.output.push( + 255, + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff + ); + } + } + this.output.push(...command); + if (keepStack) { + this.stack.splice(start, howManyArgs); + } else { + this.stack.length = 0; + } + return false; + } +} + +/** + * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence + * of Plaintext Bytes. The function takes a key as a parameter which can be + * for decrypting the eexec block or for decoding charStrings. + */ +const EEXEC_ENCRYPT_KEY = 55665; +const CHAR_STRS_ENCRYPT_KEY = 4330; + +function isHexDigit(code) { + return ( + (code >= 48 && code <= 57) || // '0'-'9' + (code >= 65 && code <= 70) || // 'A'-'F' + (code >= 97 && code <= 102) // 'a'-'f' + ); +} + +function decrypt(data, key, discardNumber) { + if (discardNumber >= data.length) { + return new Uint8Array(0); + } + const c1 = 52845, + c2 = 22719; + let r = key | 0, + i, + j; + for (i = 0; i < discardNumber; i++) { + r = ((data[i] + r) * c1 + c2) & ((1 << 16) - 1); + } + const count = data.length - discardNumber; + const decrypted = new Uint8Array(count); + for (i = discardNumber, j = 0; j < count; i++, j++) { + const value = data[i]; + decrypted[j] = value ^ (r >> 8); + r = ((value + r) * c1 + c2) & ((1 << 16) - 1); + } + return decrypted; +} + +function decryptAscii(data, key, discardNumber) { + const c1 = 52845, + c2 = 22719; + let r = key | 0; + const count = data.length, + maybeLength = count >>> 1; + const decrypted = new Uint8Array(maybeLength); + let i, j; + for (i = 0, j = 0; i < count; i++) { + const digit1 = data[i]; + if (!isHexDigit(digit1)) { + continue; + } + i++; + let digit2; + while (i < count && !isHexDigit((digit2 = data[i]))) { + i++; + } + if (i < count) { + const value = parseInt(String.fromCharCode(digit1, digit2), 16); + decrypted[j++] = value ^ (r >> 8); + r = ((value + r) * c1 + c2) & ((1 << 16) - 1); + } + } + return decrypted.slice(discardNumber, j); +} + +function isSpecial(c) { + return ( + c === /* '/' = */ 0x2f || + c === /* '[' = */ 0x5b || + c === /* ']' = */ 0x5d || + c === /* '{' = */ 0x7b || + c === /* '}' = */ 0x7d || + c === /* '(' = */ 0x28 || + c === /* ')' = */ 0x29 + ); +} + +/** + * Type1Parser encapsulate the needed code for parsing a Type1 font program. + * Some of its logic depends on the Type2 charstrings structure. + * NOTE: This doesn't really parse the font since that would require evaluation + * of PostScript, but it is possible in most cases to extract what we need + * without a full parse. + */ +class Type1Parser { + constructor(stream, encrypted, seacAnalysisEnabled) { + if (encrypted) { + const data = stream.getBytes(); + const isBinary = !( + (isHexDigit(data[0]) || isWhiteSpace(data[0])) && + isHexDigit(data[1]) && + isHexDigit(data[2]) && + isHexDigit(data[3]) && + isHexDigit(data[4]) && + isHexDigit(data[5]) && + isHexDigit(data[6]) && + isHexDigit(data[7]) + ); + stream = new Stream( + isBinary + ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) + : decryptAscii(data, EEXEC_ENCRYPT_KEY, 4) + ); + } + this.seacAnalysisEnabled = !!seacAnalysisEnabled; + + this.stream = stream; + this.nextChar(); + } + + readNumberArray() { + this.getToken(); // read '[' or '{' (arrays can start with either) + const array = []; + while (true) { + const token = this.getToken(); + if (token === null || token === "]" || token === "}") { + break; + } + array.push(parseFloat(token || 0)); + } + return array; + } + + readNumber() { + const token = this.getToken(); + return parseFloat(token || 0); + } + + readInt() { + // Use '| 0' to prevent setting a double into length such as the double + // does not flow into the loop variable. + const token = this.getToken(); + return parseInt(token || 0, 10) | 0; + } + + readBoolean() { + const token = this.getToken(); + // Use 1 and 0 since that's what type2 charstrings use. + return token === "true" ? 1 : 0; + } + + nextChar() { + return (this.currentChar = this.stream.getByte()); + } + + prevChar() { + this.stream.skip(-2); + return (this.currentChar = this.stream.getByte()); + } + + getToken() { + // Eat whitespace and comments. + let comment = false; + let ch = this.currentChar; + while (true) { + if (ch === -1) { + return null; + } + + if (comment) { + if (ch === 0x0a || ch === 0x0d) { + comment = false; + } + } else if (ch === /* '%' = */ 0x25) { + comment = true; + } else if (!isWhiteSpace(ch)) { + break; + } + ch = this.nextChar(); + } + if (isSpecial(ch)) { + this.nextChar(); + return String.fromCharCode(ch); + } + let token = ""; + do { + token += String.fromCharCode(ch); + ch = this.nextChar(); + } while (ch >= 0 && !isWhiteSpace(ch) && !isSpecial(ch)); + return token; + } + + readCharStrings(bytes, lenIV) { + if (lenIV === -1) { + // This isn't in the spec, but Adobe's tx program handles -1 + // as plain text. + return bytes; + } + return decrypt(bytes, CHAR_STRS_ENCRYPT_KEY, lenIV); + } + + /* + * Returns an object containing a Subrs array and a CharStrings + * array extracted from and eexec encrypted block of data + */ + extractFontProgram(properties) { + const stream = this.stream; + + const subrs = [], + charstrings = []; + const privateData = Object.create(null); + privateData.lenIV = 4; + const program = { + subrs: [], + charstrings: [], + properties: { + privateData, + }, + }; + let token, length, data, lenIV; + while ((token = this.getToken()) !== null) { + if (token !== "/") { + continue; + } + token = this.getToken(); + switch (token) { + case "CharStrings": + // The number immediately following CharStrings must be greater or + // equal to the number of CharStrings. + this.getToken(); + this.getToken(); // read in 'dict' + this.getToken(); // read in 'dup' + this.getToken(); // read in 'begin' + while (true) { + token = this.getToken(); + if (token === null || token === "end") { + break; + } + + if (token !== "/") { + continue; + } + const glyph = this.getToken(); + length = this.readInt(); + this.getToken(); // read in 'RD' or '-|' + data = length > 0 ? stream.getBytes(length) : new Uint8Array(0); + lenIV = program.properties.privateData.lenIV; + const encoded = this.readCharStrings(data, lenIV); + this.nextChar(); + token = this.getToken(); // read in 'ND' or '|-' + if (token === "noaccess") { + this.getToken(); // read in 'def' + } else if (token === "/") { + // The expected 'ND' or '|-' token is missing, avoid swallowing + // the start of the next glyph (fixes issue14462_reduced.pdf). + this.prevChar(); + } + charstrings.push({ + glyph, + encoded, + }); + } + break; + case "Subrs": + this.readInt(); // num + this.getToken(); // read in 'array' + while (this.getToken() === "dup") { + const index = this.readInt(); + length = this.readInt(); + this.getToken(); // read in 'RD' or '-|' + data = length > 0 ? stream.getBytes(length) : new Uint8Array(0); + lenIV = program.properties.privateData.lenIV; + const encoded = this.readCharStrings(data, lenIV); + this.nextChar(); + token = this.getToken(); // read in 'NP' or '|' + if (token === "noaccess") { + this.getToken(); // read in 'put' + } + subrs[index] = encoded; + } + break; + case "BlueValues": + case "OtherBlues": + case "FamilyBlues": + case "FamilyOtherBlues": + const blueArray = this.readNumberArray(); + // *Blue* values may contain invalid data: disables reading of + // those values when hinting is disabled. + if ( + blueArray.length > 0 && + blueArray.length % 2 === 0 && + HINTING_ENABLED + ) { + program.properties.privateData[token] = blueArray; + } + break; + case "StemSnapH": + case "StemSnapV": + program.properties.privateData[token] = this.readNumberArray(); + break; + case "StdHW": + case "StdVW": + program.properties.privateData[token] = this.readNumberArray()[0]; + break; + case "BlueShift": + case "lenIV": + case "BlueFuzz": + case "BlueScale": + case "LanguageGroup": + program.properties.privateData[token] = this.readNumber(); + break; + case "ExpansionFactor": + // Firefox doesn't render correctly a font with a null factor on + // Windows (see issue 15289), hence we just reset it to its default + // value (0.06). + program.properties.privateData[token] = this.readNumber() || 0.06; + break; + case "ForceBold": + program.properties.privateData[token] = this.readBoolean(); + break; + } + } + + for (const { encoded, glyph } of charstrings) { + const charString = new Type1CharString(); + const error = charString.convert( + encoded, + subrs, + this.seacAnalysisEnabled + ); + let output = charString.output; + if (error) { + // It seems when FreeType encounters an error while evaluating a glyph + // that it completely ignores the glyph so we'll mimic that behaviour + // here and put an endchar to make the validator happy. + output = [14]; + } + const charStringObject = { + glyphName: glyph, + charstring: output, + width: charString.width, + lsb: charString.lsb, + seac: charString.seac, + }; + if (glyph === ".notdef") { + // Make sure .notdef is at index zero (issue #11477). + program.charstrings.unshift(charStringObject); + } else { + program.charstrings.push(charStringObject); + } + + // Attempt to replace missing widths, from the font dictionary /Widths + // entry, with ones from the font data (fixes issue11150_reduced.pdf). + if (properties.builtInEncoding) { + const index = properties.builtInEncoding.indexOf(glyph); + if ( + index > -1 && + properties.widths[index] === undefined && + index >= properties.firstChar && + index <= properties.lastChar + ) { + properties.widths[index] = charString.width; + } + } + } + + return program; + } + + extractFontHeader(properties) { + let token; + while ((token = this.getToken()) !== null) { + if (token !== "/") { + continue; + } + token = this.getToken(); + switch (token) { + case "FontMatrix": + const matrix = this.readNumberArray(); + properties.fontMatrix = matrix; + break; + case "Encoding": + const encodingArg = this.getToken(); + let encoding; + if (!/^\d+$/.test(encodingArg)) { + // encoding name is specified + encoding = getEncoding(encodingArg); + } else { + encoding = []; + const size = parseInt(encodingArg, 10) | 0; + this.getToken(); // read in 'array' + + for (let j = 0; j < size; j++) { + token = this.getToken(); + // skipping till first dup or def (e.g. ignoring for statement) + while (token !== "dup" && token !== "def") { + token = this.getToken(); + if (token === null) { + return; // invalid header + } + } + if (token === "def") { + break; // read all array data + } + const index = this.readInt(); + this.getToken(); // read in '/' + const glyph = this.getToken(); + encoding[index] = glyph; + this.getToken(); // read the in 'put' + } + } + properties.builtInEncoding = encoding; + break; + case "FontBBox": + const fontBBox = this.readNumberArray(); + // adjusting ascent/descent + properties.ascent = Math.max(fontBBox[3], fontBBox[1]); + properties.descent = Math.min(fontBBox[1], fontBBox[3]); + properties.ascentScaled = true; + break; + } + } + } +} + +export { Type1Parser }; diff --git a/frontend/public/pdf.js/src/core/unicode.js b/frontend/public/pdf.js/src/core/unicode.js new file mode 100644 index 0000000..c631fd3 --- /dev/null +++ b/frontend/public/pdf.js/src/core/unicode.js @@ -0,0 +1,273 @@ +/* Copyright 2016 Mozilla Foundation + * + * 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 { getLookupTableFactory } from "./core_utils.js"; + +// Some characters, e.g. copyrightserif, are mapped to the private use area +// and might not be displayed using standard fonts. Mapping/hacking well-known +// chars to the similar equivalents in the normal characters range. +const getSpecialPUASymbols = getLookupTableFactory(function (t) { + t[63721] = 0x00a9; // copyrightsans (0xF8E9) => copyright + t[63193] = 0x00a9; // copyrightserif (0xF6D9) => copyright + t[63720] = 0x00ae; // registersans (0xF8E8) => registered + t[63194] = 0x00ae; // registerserif (0xF6DA) => registered + t[63722] = 0x2122; // trademarksans (0xF8EA) => trademark + t[63195] = 0x2122; // trademarkserif (0xF6DB) => trademark + t[63729] = 0x23a7; // bracelefttp (0xF8F1) + t[63730] = 0x23a8; // braceleftmid (0xF8F2) + t[63731] = 0x23a9; // braceleftbt (0xF8F3) + t[63740] = 0x23ab; // bracerighttp (0xF8FC) + t[63741] = 0x23ac; // bracerightmid (0xF8FD) + t[63742] = 0x23ad; // bracerightbt (0xF8FE) + t[63726] = 0x23a1; // bracketlefttp (0xF8EE) + t[63727] = 0x23a2; // bracketleftex (0xF8EF) + t[63728] = 0x23a3; // bracketleftbt (0xF8F0) + t[63737] = 0x23a4; // bracketrighttp (0xF8F9) + t[63738] = 0x23a5; // bracketrightex (0xF8FA) + t[63739] = 0x23a6; // bracketrightbt (0xF8FB) + t[63723] = 0x239b; // parenlefttp (0xF8EB) + t[63724] = 0x239c; // parenleftex (0xF8EC) + t[63725] = 0x239d; // parenleftbt (0xF8ED) + t[63734] = 0x239e; // parenrighttp (0xF8F6) + t[63735] = 0x239f; // parenrightex (0xF8F7) + t[63736] = 0x23a0; // parenrightbt (0xF8F8) +}); + +function mapSpecialUnicodeValues(code) { + if (code >= 0xfff0 && code <= 0xffff) { + // Specials unicode block. + return 0; + } else if (code >= 0xf600 && code <= 0xf8ff) { + return getSpecialPUASymbols()[code] || code; + } else if (code === /* softhyphen = */ 0x00ad) { + return 0x002d; // hyphen + } + return code; +} + +function getUnicodeForGlyph(name, glyphsUnicodeMap) { + let unicode = glyphsUnicodeMap[name]; + if (unicode !== undefined) { + return unicode; + } + if (!name) { + return -1; + } + // Try to recover valid Unicode values from 'uniXXXX'/'uXXXX{XX}' glyphs. + if (name[0] === "u") { + const nameLen = name.length; + let hexStr; + + if (nameLen === 7 && name[1] === "n" && name[2] === "i") { + // 'uniXXXX' + hexStr = name.substring(3); + } else if (nameLen >= 5 && nameLen <= 7) { + // 'uXXXX{XX}' + hexStr = name.substring(1); + } else { + return -1; + } + // Check for upper-case hexadecimal characters, to avoid false positives. + if (hexStr === hexStr.toUpperCase()) { + unicode = parseInt(hexStr, 16); + if (unicode >= 0) { + return unicode; + } + } + } + return -1; +} + +// See https://learn.microsoft.com/en-us/typography/opentype/spec/os2#ulunicoderange1-bits-031ulunicoderange2-bits-3263ulunicoderange3-bits-6495ulunicoderange4-bits-96127 +const UnicodeRanges = [ + [0x0000, 0x007f], // 0 - Basic Latin + [0x0080, 0x00ff], // 1 - Latin-1 Supplement + [0x0100, 0x017f], // 2 - Latin Extended-A + [0x0180, 0x024f], // 3 - Latin Extended-B + [0x0250, 0x02af, 0x1d00, 0x1d7f, 0x1d80, 0x1dbf], // 4 - IPA Extensions - Phonetic Extensions - Phonetic Extensions Supplement + [0x02b0, 0x02ff, 0xa700, 0xa71f], // 5 - Spacing Modifier Letters - Modifier Tone Letters + [0x0300, 0x036f, 0x1dc0, 0x1dff], // 6 - Combining Diacritical Marks - Combining Diacritical Marks Supplement + [0x0370, 0x03ff], // 7 - Greek and Coptic + [0x2c80, 0x2cff], // 8 - Coptic + [0x0400, 0x04ff, 0x0500, 0x052f, 0x2de0, 0x2dff, 0xa640, 0xa69f], // 9 - Cyrillic - Cyrillic Supplement - Cyrillic Extended-A - Cyrillic Extended-B + [0x0530, 0x058f], // 10 - Armenian + [0x0590, 0x05ff], // 11 - Hebrew + [0xa500, 0xa63f], // 12 - Vai + [0x0600, 0x06ff, 0x0750, 0x077f], // 13 - Arabic - Arabic Supplement + [0x07c0, 0x07ff], // 14 - NKo + [0x0900, 0x097f], // 15 - Devanagari + [0x0980, 0x09ff], // 16 - Bengali + [0x0a00, 0x0a7f], // 17 - Gurmukhi + [0x0a80, 0x0aff], // 18 - Gujarati + [0x0b00, 0x0b7f], // 19 - Oriya + [0x0b80, 0x0bff], // 20 - Tamil + [0x0c00, 0x0c7f], // 21 - Telugu + [0x0c80, 0x0cff], // 22 - Kannada + [0x0d00, 0x0d7f], // 23 - Malayalam + [0x0e00, 0x0e7f], // 24 - Thai + [0x0e80, 0x0eff], // 25 - Lao + [0x10a0, 0x10ff, 0x2d00, 0x2d2f], // 26 - Georgian - Georgian Supplement + [0x1b00, 0x1b7f], // 27 - Balinese + [0x1100, 0x11ff], // 28 - Hangul Jamo + [0x1e00, 0x1eff, 0x2c60, 0x2c7f, 0xa720, 0xa7ff], // 29 - Latin Extended Additional - Latin Extended-C - Latin Extended-D + [0x1f00, 0x1fff], // 30 - Greek Extended + [0x2000, 0x206f, 0x2e00, 0x2e7f], // 31 - General Punctuation - Supplemental Punctuation + [0x2070, 0x209f], // 32 - Superscripts And Subscripts + [0x20a0, 0x20cf], // 33 - Currency Symbol + [0x20d0, 0x20ff], // 34 - Combining Diacritical Marks + [0x2100, 0x214f], // 35 - Letterlike Symbols + [0x2150, 0x218f], // 36 - Number Forms + [0x2190, 0x21ff, 0x27f0, 0x27ff, 0x2900, 0x297f, 0x2b00, 0x2bff], // 37 - Arrows - Supplemental Arrows-A - Supplemental Arrows-B - Miscellaneous Symbols and Arrows + [0x2200, 0x22ff, 0x2a00, 0x2aff, 0x27c0, 0x27ef, 0x2980, 0x29ff], // 38 - Mathematical Operators - Supplemental Mathematical Operators - Miscellaneous Mathematical Symbols-A - Miscellaneous Mathematical Symbols-B + [0x2300, 0x23ff], // 39 - Miscellaneous Technical + [0x2400, 0x243f], // 40 - Control Pictures + [0x2440, 0x245f], // 41 - Optical Character Recognition + [0x2460, 0x24ff], // 42 - Enclosed Alphanumerics + [0x2500, 0x257f], // 43 - Box Drawing + [0x2580, 0x259f], // 44 - Block Elements + [0x25a0, 0x25ff], // 45 - Geometric Shapes + [0x2600, 0x26ff], // 46 - Miscellaneous Symbols + [0x2700, 0x27bf], // 47 - Dingbats + [0x3000, 0x303f], // 48 - CJK Symbols And Punctuation + [0x3040, 0x309f], // 49 - Hiragana + [0x30a0, 0x30ff, 0x31f0, 0x31ff], // 50 - Katakana - Katakana Phonetic Extensions + [0x3100, 0x312f, 0x31a0, 0x31bf], // 51 - Bopomofo - Bopomofo Extended + [0x3130, 0x318f], // 52 - Hangul Compatibility Jamo + [0xa840, 0xa87f], // 53 - Phags-pa + [0x3200, 0x32ff], // 54 - Enclosed CJK Letters And Months + [0x3300, 0x33ff], // 55 - CJK Compatibility + [0xac00, 0xd7af], // 56 - Hangul Syllables + [0xd800, 0xdfff], // 57 - Non-Plane 0 * + [0x10900, 0x1091f], // 58 - Phoenicia + [ + 0x4e00, 0x9fff, 0x2e80, 0x2eff, 0x2f00, 0x2fdf, 0x2ff0, 0x2fff, 0x3400, + 0x4dbf, 0x20000, 0x2a6df, 0x3190, 0x319f, + ], // 59 - CJK Unified Ideographs - CJK Radicals Supplement - Kangxi Radicals - Ideographic Description Characters - CJK Unified Ideographs Extension A - CJK Unified Ideographs Extension B - Kanbun + [0xe000, 0xf8ff], // 60 - Private Use Area (plane 0) + [0x31c0, 0x31ef, 0xf900, 0xfaff, 0x2f800, 0x2fa1f], // 61 - CJK Strokes - CJK Compatibility Ideographs - CJK Compatibility Ideographs Supplement + [0xfb00, 0xfb4f], // 62 - Alphabetic Presentation Forms + [0xfb50, 0xfdff], // 63 - Arabic Presentation Forms-A + [0xfe20, 0xfe2f], // 64 - Combining Half Marks + [0xfe10, 0xfe1f], // 65 - Vertical Forms + [0xfe50, 0xfe6f], // 66 - Small Form Variants + [0xfe70, 0xfeff], // 67 - Arabic Presentation Forms-B + [0xff00, 0xffef], // 68 - Halfwidth And Fullwidth Forms + [0xfff0, 0xffff], // 69 - Specials + [0x0f00, 0x0fff], // 70 - Tibetan + [0x0700, 0x074f], // 71 - Syriac + [0x0780, 0x07bf], // 72 - Thaana + [0x0d80, 0x0dff], // 73 - Sinhala + [0x1000, 0x109f], // 74 - Myanmar + [0x1200, 0x137f, 0x1380, 0x139f, 0x2d80, 0x2ddf], // 75 - Ethiopic - Ethiopic Supplement - Ethiopic Extended + [0x13a0, 0x13ff], // 76 - Cherokee + [0x1400, 0x167f], // 77 - Unified Canadian Aboriginal Syllabics + [0x1680, 0x169f], // 78 - Ogham + [0x16a0, 0x16ff], // 79 - Runic + [0x1780, 0x17ff], // 80 - Khmer + [0x1800, 0x18af], // 81 - Mongolian + [0x2800, 0x28ff], // 82 - Braille Patterns + [0xa000, 0xa48f], // 83 - Yi Syllables + [0x1700, 0x171f, 0x1720, 0x173f, 0x1740, 0x175f, 0x1760, 0x177f], // 84 - Tagalog - Hanunoo - Buhid - Tagbanwa + [0x10300, 0x1032f], // 85 - Old Italic + [0x10330, 0x1034f], // 86 - Gothic + [0x10400, 0x1044f], // 87 - Deseret + [0x1d000, 0x1d0ff, 0x1d100, 0x1d1ff, 0x1d200, 0x1d24f], // 88 - Byzantine Musical Symbols - Musical Symbols - Ancient Greek Musical Notation + [0x1d400, 0x1d7ff], // 89 - Mathematical Alphanumeric Symbols + [0xff000, 0xffffd], // 90 - Private Use (plane 15) + [0xfe00, 0xfe0f, 0xe0100, 0xe01ef], // 91 - Variation Selectors - Variation Selectors Supplement + [0xe0000, 0xe007f], // 92 - Tags + [0x1900, 0x194f], // 93 - Limbu + [0x1950, 0x197f], // 94 - Tai Le + [0x1980, 0x19df], // 95 - New Tai Lue + [0x1a00, 0x1a1f], // 96 - Buginese + [0x2c00, 0x2c5f], // 97 - Glagolitic + [0x2d30, 0x2d7f], // 98 - Tifinagh + [0x4dc0, 0x4dff], // 99 - Yijing Hexagram Symbols + [0xa800, 0xa82f], // 100 - Syloti Nagri + [0x10000, 0x1007f, 0x10080, 0x100ff, 0x10100, 0x1013f], // 101 - Linear B Syllabary - Linear B Ideograms - Aegean Numbers + [0x10140, 0x1018f], // 102 - Ancient Greek Numbers + [0x10380, 0x1039f], // 103 - Ugaritic + [0x103a0, 0x103df], // 104 - Old Persian + [0x10450, 0x1047f], // 105 - Shavian + [0x10480, 0x104af], // 106 - Osmanya + [0x10800, 0x1083f], // 107 - Cypriot Syllabary + [0x10a00, 0x10a5f], // 108 - Kharoshthi + [0x1d300, 0x1d35f], // 109 - Tai Xuan Jing Symbols + [0x12000, 0x123ff, 0x12400, 0x1247f], // 110 - Cuneiform - Cuneiform Numbers and Punctuation + [0x1d360, 0x1d37f], // 111 - Counting Rod Numerals + [0x1b80, 0x1bbf], // 112 - Sundanese + [0x1c00, 0x1c4f], // 113 - Lepcha + [0x1c50, 0x1c7f], // 114 - Ol Chiki + [0xa880, 0xa8df], // 115 - Saurashtra + [0xa900, 0xa92f], // 116 - Kayah Li + [0xa930, 0xa95f], // 117 - Rejang + [0xaa00, 0xaa5f], // 118 - Cham + [0x10190, 0x101cf], // 119 - Ancient Symbols + [0x101d0, 0x101ff], // 120 - Phaistos Disc + [0x102a0, 0x102df, 0x10280, 0x1029f, 0x10920, 0x1093f], // 121 - Carian - Lycian - Lydian + [0x1f030, 0x1f09f, 0x1f000, 0x1f02f], // 122 - Domino Tiles - Mahjong Tiles +]; + +function getUnicodeRangeFor(value, lastPosition = -1) { + // TODO: create a map range => position, sort the ranges and cache it. + // Then we can make a binary search for finding a range for a given unicode. + if (lastPosition !== -1) { + const range = UnicodeRanges[lastPosition]; + for (let i = 0, ii = range.length; i < ii; i += 2) { + if (value >= range[i] && value <= range[i + 1]) { + return lastPosition; + } + } + } + for (let i = 0, ii = UnicodeRanges.length; i < ii; i++) { + const range = UnicodeRanges[i]; + for (let j = 0, jj = range.length; j < jj; j += 2) { + if (value >= range[j] && value <= range[j + 1]) { + return i; + } + } + } + return -1; +} + +const SpecialCharRegExp = new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$", "u"); +const CategoryCache = new Map(); + +function getCharUnicodeCategory(char) { + const cachedCategory = CategoryCache.get(char); + if (cachedCategory) { + return cachedCategory; + } + const groups = char.match(SpecialCharRegExp); + const category = { + isWhitespace: !!groups?.[1], + isZeroWidthDiacritic: !!groups?.[2], + isInvisibleFormatMark: !!groups?.[3], + }; + CategoryCache.set(char, category); + return category; +} + +function clearUnicodeCaches() { + CategoryCache.clear(); +} + +export { + clearUnicodeCaches, + getCharUnicodeCategory, + getUnicodeForGlyph, + getUnicodeRangeFor, + mapSpecialUnicodeValues, +}; diff --git a/frontend/public/pdf.js/src/core/worker.js b/frontend/public/pdf.js/src/core/worker.js new file mode 100644 index 0000000..f1ab5b5 --- /dev/null +++ b/frontend/public/pdf.js/src/core/worker.js @@ -0,0 +1,891 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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 { + AbortException, + assert, + getVerbosityLevel, + info, + isNodeJS, + PasswordException, + setVerbosityLevel, + stringToPDFString, + VerbosityLevel, + warn, +} from "../shared/util.js"; +import { + arrayBuffersToBytes, + getNewAnnotationsMap, + XRefParseException, +} from "./core_utils.js"; +import { Dict, isDict, Ref, RefSetCache } from "./primitives.js"; +import { LocalPdfManager, NetworkPdfManager } from "./pdf_manager.js"; +import { MessageHandler, wrapReason } from "../shared/message_handler.js"; +import { AnnotationFactory } from "./annotation.js"; +import { clearGlobalCaches } from "./cleanup_helper.js"; +import { incrementalUpdate } from "./writer.js"; +import { PDFWorkerStream } from "./worker_stream.js"; +import { StructTreeRoot } from "./struct_tree.js"; + +class WorkerTask { + constructor(name) { + this.name = name; + this.terminated = false; + this._capability = Promise.withResolvers(); + } + + get finished() { + return this._capability.promise; + } + + finish() { + this._capability.resolve(); + } + + terminate() { + this.terminated = true; + } + + ensureNotTerminated() { + if (this.terminated) { + throw new Error("Worker task was terminated"); + } + } +} + +class WorkerMessageHandler { + static { + // Worker thread (and not Node.js)? + if ( + typeof window === "undefined" && + !isNodeJS && + typeof self !== "undefined" && + /* isMessagePort = */ + typeof self.postMessage === "function" && + "onmessage" in self + ) { + this.initializeFromPort(self); + } + } + + static setup(handler, port) { + let testMessageProcessed = false; + handler.on("test", data => { + if (testMessageProcessed) { + return; // we already processed 'test' message once + } + testMessageProcessed = true; + + // Ensure that `TypedArray`s can be sent to the worker. + handler.send("test", data instanceof Uint8Array); + }); + + handler.on("configure", data => { + setVerbosityLevel(data.verbosity); + }); + + handler.on("GetDocRequest", data => this.createDocumentHandler(data, port)); + } + + static createDocumentHandler(docParams, port) { + // This context is actually holds references on pdfManager and handler, + // until the latter is destroyed. + let pdfManager; + let terminated = false; + let cancelXHRs = null; + const WorkerTasks = new Set(); + const verbosity = getVerbosityLevel(); + + const { docId, apiVersion } = docParams; + const workerVersion = + typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING") + ? PDFJSDev.eval("BUNDLE_VERSION") + : null; + if (apiVersion !== workerVersion) { + throw new Error( + `The API version "${apiVersion}" does not match ` + + `the Worker version "${workerVersion}".` + ); + } + + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + // Fail early, and predictably, rather than having (some) fonts fail to + // load/render with slightly cryptic error messages in environments where + // the `Array.prototype` has been *incorrectly* extended. + // + // PLEASE NOTE: We do *not* want to slow down font parsing by adding + // `hasOwnProperty` checks all over the code-base. + const enumerableProperties = []; + for (const property in []) { + enumerableProperties.push(property); + } + if (enumerableProperties.length) { + throw new Error( + "The `Array.prototype` contains unexpected enumerable properties: " + + enumerableProperties.join(", ") + + "; thus breaking e.g. `for...in` iteration of `Array`s." + ); + } + } + const workerHandlerName = docId + "_worker"; + let handler = new MessageHandler(workerHandlerName, docId, port); + + function ensureNotTerminated() { + if (terminated) { + throw new Error("Worker was terminated"); + } + } + + function startWorkerTask(task) { + WorkerTasks.add(task); + } + + function finishWorkerTask(task) { + task.finish(); + WorkerTasks.delete(task); + } + + async function loadDocument(recoveryMode) { + await pdfManager.ensureDoc("checkHeader"); + await pdfManager.ensureDoc("parseStartXRef"); + await pdfManager.ensureDoc("parse", [recoveryMode]); + + // Check that at least the first page can be successfully loaded, + // since otherwise the XRef table is definitely not valid. + await pdfManager.ensureDoc("checkFirstPage", [recoveryMode]); + // Check that the last page can be successfully loaded, to ensure that + // `numPages` is correct, and fallback to walking the entire /Pages-tree. + await pdfManager.ensureDoc("checkLastPage", [recoveryMode]); + + const isPureXfa = await pdfManager.ensureDoc("isPureXfa"); + if (isPureXfa) { + const task = new WorkerTask("loadXfaFonts"); + startWorkerTask(task); + + await Promise.all([ + pdfManager + .loadXfaFonts(handler, task) + .catch(reason => { + // Ignore errors, to allow the document to load. + }) + .then(() => finishWorkerTask(task)), + pdfManager.loadXfaImages(), + ]); + } + + const [numPages, fingerprints] = await Promise.all([ + pdfManager.ensureDoc("numPages"), + pdfManager.ensureDoc("fingerprints"), + ]); + + // Get htmlForXfa after numPages to avoid to create HTML twice. + const htmlForXfa = isPureXfa + ? await pdfManager.ensureDoc("htmlForXfa") + : null; + + return { numPages, fingerprints, htmlForXfa }; + } + + async function getPdfManager({ + data, + password, + disableAutoFetch, + rangeChunkSize, + length, + docBaseUrl, + enableXfa, + evaluatorOptions, + }) { + const pdfManagerArgs = { + source: null, + disableAutoFetch, + docBaseUrl, + docId, + enableXfa, + evaluatorOptions, + handler, + length, + password, + rangeChunkSize, + }; + + if (data) { + pdfManagerArgs.source = data; + + return new LocalPdfManager(pdfManagerArgs); + } + const pdfStream = new PDFWorkerStream(handler), + fullRequest = pdfStream.getFullReader(); + + const pdfManagerCapability = Promise.withResolvers(); + let newPdfManager, + cachedChunks = [], + loaded = 0; + + fullRequest.headersReady + .then(function () { + if (!fullRequest.isRangeSupported) { + return; + } + pdfManagerArgs.source = pdfStream; + pdfManagerArgs.length = fullRequest.contentLength; + // We don't need auto-fetch when streaming is enabled. + pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported; + + newPdfManager = new NetworkPdfManager(pdfManagerArgs); + // There may be a chance that `newPdfManager` is not initialized for + // the first few runs of `readchunk` block of code. Be sure to send + // all cached chunks, if any, to chunked_stream via pdf_manager. + for (const chunk of cachedChunks) { + newPdfManager.sendProgressiveData(chunk); + } + + cachedChunks = []; + pdfManagerCapability.resolve(newPdfManager); + cancelXHRs = null; + }) + .catch(function (reason) { + pdfManagerCapability.reject(reason); + cancelXHRs = null; + }); + + new Promise(function (resolve, reject) { + const readChunk = function ({ value, done }) { + try { + ensureNotTerminated(); + if (done) { + if (!newPdfManager) { + const pdfFile = arrayBuffersToBytes(cachedChunks); + cachedChunks = []; + + if (length && pdfFile.length !== length) { + warn("reported HTTP length is different from actual"); + } + pdfManagerArgs.source = pdfFile; + + newPdfManager = new LocalPdfManager(pdfManagerArgs); + pdfManagerCapability.resolve(newPdfManager); + } + cancelXHRs = null; + return; + } + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + value instanceof ArrayBuffer, + "readChunk (getPdfManager) - expected an ArrayBuffer." + ); + } + loaded += value.byteLength; + + if (!fullRequest.isStreamingSupported) { + handler.send("DocProgress", { + loaded, + total: Math.max(loaded, fullRequest.contentLength || 0), + }); + } + + if (newPdfManager) { + newPdfManager.sendProgressiveData(value); + } else { + cachedChunks.push(value); + } + fullRequest.read().then(readChunk, reject); + } catch (e) { + reject(e); + } + }; + fullRequest.read().then(readChunk, reject); + }).catch(function (e) { + pdfManagerCapability.reject(e); + cancelXHRs = null; + }); + + cancelXHRs = reason => { + pdfStream.cancelAllRequests(reason); + }; + + return pdfManagerCapability.promise; + } + + function setupDoc(data) { + function onSuccess(doc) { + ensureNotTerminated(); + handler.send("GetDoc", { pdfInfo: doc }); + } + + function onFailure(ex) { + ensureNotTerminated(); + + if (ex instanceof PasswordException) { + const task = new WorkerTask(`PasswordException: response ${ex.code}`); + startWorkerTask(task); + + handler + .sendWithPromise("PasswordRequest", ex) + .then(function ({ password }) { + finishWorkerTask(task); + pdfManager.updatePassword(password); + pdfManagerReady(); + }) + .catch(function () { + finishWorkerTask(task); + handler.send("DocException", ex); + }); + } else { + // Ensure that we always fallback to `UnknownErrorException`. + handler.send("DocException", wrapReason(ex)); + } + } + + function pdfManagerReady() { + ensureNotTerminated(); + + loadDocument(false).then(onSuccess, function (reason) { + ensureNotTerminated(); + + // Try again with recoveryMode == true + if (!(reason instanceof XRefParseException)) { + onFailure(reason); + return; + } + pdfManager.requestLoadedStream().then(function () { + ensureNotTerminated(); + + loadDocument(true).then(onSuccess, onFailure); + }); + }); + } + + ensureNotTerminated(); + + getPdfManager(data) + .then(function (newPdfManager) { + if (terminated) { + // We were in a process of setting up the manager, but it got + // terminated in the middle. + newPdfManager.terminate( + new AbortException("Worker was terminated.") + ); + throw new Error("Worker was terminated"); + } + pdfManager = newPdfManager; + + pdfManager.requestLoadedStream(/* noFetch = */ true).then(stream => { + handler.send("DataLoaded", { length: stream.bytes.byteLength }); + }); + }) + .then(pdfManagerReady, onFailure); + } + + handler.on("GetPage", function (data) { + return pdfManager.getPage(data.pageIndex).then(function (page) { + return Promise.all([ + pdfManager.ensure(page, "rotate"), + pdfManager.ensure(page, "ref"), + pdfManager.ensure(page, "userUnit"), + pdfManager.ensure(page, "view"), + ]).then(function ([rotate, ref, userUnit, view]) { + return { + rotate, + ref, + refStr: ref?.toString() ?? null, + userUnit, + view, + }; + }); + }); + }); + + handler.on("GetPageIndex", function (data) { + const pageRef = Ref.get(data.num, data.gen); + return pdfManager.ensureCatalog("getPageIndex", [pageRef]); + }); + + handler.on("GetDestinations", function (data) { + return pdfManager.ensureCatalog("destinations"); + }); + + handler.on("GetDestination", function (data) { + return pdfManager.ensureCatalog("getDestination", [data.id]); + }); + + handler.on("GetPageLabels", function (data) { + return pdfManager.ensureCatalog("pageLabels"); + }); + + handler.on("GetPageLayout", function (data) { + return pdfManager.ensureCatalog("pageLayout"); + }); + + handler.on("GetPageMode", function (data) { + return pdfManager.ensureCatalog("pageMode"); + }); + + handler.on("GetViewerPreferences", function (data) { + return pdfManager.ensureCatalog("viewerPreferences"); + }); + + handler.on("GetOpenAction", function (data) { + return pdfManager.ensureCatalog("openAction"); + }); + + handler.on("GetAttachments", function (data) { + return pdfManager.ensureCatalog("attachments"); + }); + + handler.on("GetDocJSActions", function (data) { + return pdfManager.ensureCatalog("jsActions"); + }); + + handler.on("GetPageJSActions", function ({ pageIndex }) { + return pdfManager.getPage(pageIndex).then(function (page) { + return pdfManager.ensure(page, "jsActions"); + }); + }); + + handler.on("GetOutline", function (data) { + return pdfManager.ensureCatalog("documentOutline"); + }); + + handler.on("GetOptionalContentConfig", function (data) { + return pdfManager.ensureCatalog("optionalContentConfig"); + }); + + handler.on("GetPermissions", function (data) { + return pdfManager.ensureCatalog("permissions"); + }); + + handler.on("GetMetadata", function (data) { + return Promise.all([ + pdfManager.ensureDoc("documentInfo"), + pdfManager.ensureCatalog("metadata"), + ]); + }); + + handler.on("GetMarkInfo", function (data) { + return pdfManager.ensureCatalog("markInfo"); + }); + + handler.on("GetData", function (data) { + return pdfManager.requestLoadedStream().then(function (stream) { + return stream.bytes; + }); + }); + + handler.on("GetAnnotations", function ({ pageIndex, intent }) { + return pdfManager.getPage(pageIndex).then(function (page) { + const task = new WorkerTask(`GetAnnotations: page ${pageIndex}`); + startWorkerTask(task); + + return page.getAnnotationsData(handler, task, intent).then( + data => { + finishWorkerTask(task); + return data; + }, + reason => { + finishWorkerTask(task); + throw reason; + } + ); + }); + }); + + handler.on("GetFieldObjects", function (data) { + return pdfManager + .ensureDoc("fieldObjects") + .then(fieldObjects => fieldObjects?.allFields || null); + }); + + handler.on("HasJSActions", function (data) { + return pdfManager.ensureDoc("hasJSActions"); + }); + + handler.on("GetCalculationOrderIds", function (data) { + return pdfManager.ensureDoc("calculationOrderIds"); + }); + + handler.on( + "SaveDocument", + async function ({ isPureXfa, numPages, annotationStorage, filename }) { + const globalPromises = [ + pdfManager.requestLoadedStream(), + pdfManager.ensureCatalog("acroForm"), + pdfManager.ensureCatalog("acroFormRef"), + pdfManager.ensureDoc("startXRef"), + pdfManager.ensureDoc("xref"), + pdfManager.ensureDoc("linearization"), + pdfManager.ensureCatalog("structTreeRoot"), + ]; + const changes = new RefSetCache(); + const promises = []; + + const newAnnotationsByPage = !isPureXfa + ? getNewAnnotationsMap(annotationStorage) + : null; + const [ + stream, + acroForm, + acroFormRef, + startXRef, + xref, + linearization, + _structTreeRoot, + ] = await Promise.all(globalPromises); + const catalogRef = xref.trailer.getRaw("Root") || null; + let structTreeRoot; + + if (newAnnotationsByPage) { + if (!_structTreeRoot) { + if ( + await StructTreeRoot.canCreateStructureTree({ + catalogRef, + pdfManager, + newAnnotationsByPage, + }) + ) { + structTreeRoot = null; + } + } else if ( + await _structTreeRoot.canUpdateStructTree({ + pdfManager, + xref, + newAnnotationsByPage, + }) + ) { + structTreeRoot = _structTreeRoot; + } + + const imagePromises = AnnotationFactory.generateImages( + annotationStorage.values(), + xref, + pdfManager.evaluatorOptions.isOffscreenCanvasSupported + ); + const newAnnotationPromises = + structTreeRoot === undefined ? promises : []; + for (const [pageIndex, annotations] of newAnnotationsByPage) { + newAnnotationPromises.push( + pdfManager.getPage(pageIndex).then(page => { + const task = new WorkerTask(`Save (editor): page ${pageIndex}`); + startWorkerTask(task); + + return page + .saveNewAnnotations( + handler, + task, + annotations, + imagePromises, + changes + ) + .finally(function () { + finishWorkerTask(task); + }); + }) + ); + } + if (structTreeRoot === null) { + // No structTreeRoot exists, so we need to create one. + promises.push( + Promise.all(newAnnotationPromises).then(async () => { + await StructTreeRoot.createStructureTree({ + newAnnotationsByPage, + xref, + catalogRef, + pdfManager, + changes, + }); + }) + ); + } else if (structTreeRoot) { + promises.push( + Promise.all(newAnnotationPromises).then(async () => { + await structTreeRoot.updateStructureTree({ + newAnnotationsByPage, + pdfManager, + changes, + }); + }) + ); + } + } + + if (isPureXfa) { + promises.push(pdfManager.serializeXfaData(annotationStorage)); + } else { + for (let pageIndex = 0; pageIndex < numPages; pageIndex++) { + promises.push( + pdfManager.getPage(pageIndex).then(function (page) { + const task = new WorkerTask(`Save: page ${pageIndex}`); + startWorkerTask(task); + + return page + .save(handler, task, annotationStorage, changes) + .finally(function () { + finishWorkerTask(task); + }); + }) + ); + } + } + const refs = await Promise.all(promises); + + let xfaData = null; + if (isPureXfa) { + xfaData = refs[0]; + if (!xfaData) { + return stream.bytes; + } + } else if (changes.size === 0) { + // No new refs so just return the initial bytes + return stream.bytes; + } + + const needAppearances = + acroFormRef && + acroForm instanceof Dict && + changes.values().some(ref => ref.needAppearances); + + const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || null; + let xfaDatasetsRef = null; + let hasXfaDatasetsEntry = false; + if (Array.isArray(xfa)) { + for (let i = 0, ii = xfa.length; i < ii; i += 2) { + if (xfa[i] === "datasets") { + xfaDatasetsRef = xfa[i + 1]; + hasXfaDatasetsEntry = true; + } + } + if (xfaDatasetsRef === null) { + xfaDatasetsRef = xref.getNewTemporaryRef(); + } + } else if (xfa) { + // TODO: Support XFA streams. + warn("Unsupported XFA type."); + } + + let newXrefInfo = Object.create(null); + if (xref.trailer) { + // Get string info from Info in order to compute fileId. + const infoObj = Object.create(null); + const xrefInfo = xref.trailer.get("Info") || null; + if (xrefInfo instanceof Dict) { + for (const [key, value] of xrefInfo) { + if (typeof value === "string") { + infoObj[key] = stringToPDFString(value); + } + } + } + + newXrefInfo = { + rootRef: catalogRef, + encryptRef: xref.trailer.getRaw("Encrypt") || null, + newRef: xref.getNewTemporaryRef(), + infoRef: xref.trailer.getRaw("Info") || null, + info: infoObj, + fileIds: xref.trailer.get("ID") || null, + startXRef: linearization + ? startXRef + : (xref.lastXRefStreamPos ?? startXRef), + filename, + }; + } + + return incrementalUpdate({ + originalData: stream.bytes, + xrefInfo: newXrefInfo, + changes, + xref, + hasXfa: !!xfa, + xfaDatasetsRef, + hasXfaDatasetsEntry, + needAppearances, + acroFormRef, + acroForm, + xfaData, + // Use the same kind of XRef as the previous one. + useXrefStream: isDict(xref.topDict, "XRef"), + }).finally(() => { + xref.resetNewTemporaryRef(); + }); + } + ); + + handler.on("GetOperatorList", function (data, sink) { + const pageIndex = data.pageIndex; + pdfManager.getPage(pageIndex).then(function (page) { + const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`); + startWorkerTask(task); + + // NOTE: Keep this condition in sync with the `info` helper function. + const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0; + + // Pre compile the pdf page and fetch the fonts/images. + page + .getOperatorList({ + handler, + sink, + task, + intent: data.intent, + cacheKey: data.cacheKey, + annotationStorage: data.annotationStorage, + modifiedIds: data.modifiedIds, + }) + .then( + function (operatorListInfo) { + finishWorkerTask(task); + + if (start) { + info( + `page=${pageIndex + 1} - getOperatorList: time=` + + `${Date.now() - start}ms, len=${operatorListInfo.length}` + ); + } + sink.close(); + }, + function (reason) { + finishWorkerTask(task); + if (task.terminated) { + return; // ignoring errors from the terminated thread + } + sink.error(reason); + + // TODO: Should `reason` be re-thrown here (currently that casues + // "Uncaught exception: ..." messages in the console)? + } + ); + }); + }); + + handler.on("GetTextContent", function (data, sink) { + const { pageIndex, includeMarkedContent, disableNormalization } = data; + + pdfManager.getPage(pageIndex).then(function (page) { + const task = new WorkerTask("GetTextContent: page " + pageIndex); + startWorkerTask(task); + + // NOTE: Keep this condition in sync with the `info` helper function. + const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0; + + page + .extractTextContent({ + handler, + task, + sink, + includeMarkedContent, + disableNormalization, + }) + .then( + function () { + finishWorkerTask(task); + + if (start) { + info( + `page=${pageIndex + 1} - getTextContent: time=` + + `${Date.now() - start}ms` + ); + } + sink.close(); + }, + function (reason) { + finishWorkerTask(task); + if (task.terminated) { + return; // ignoring errors from the terminated thread + } + sink.error(reason); + + // TODO: Should `reason` be re-thrown here (currently that casues + // "Uncaught exception: ..." messages in the console)? + } + ); + }); + }); + + handler.on("GetStructTree", function (data) { + return pdfManager.getPage(data.pageIndex).then(function (page) { + return pdfManager.ensure(page, "getStructTree"); + }); + }); + + handler.on("FontFallback", function (data) { + return pdfManager.fontFallback(data.id, handler); + }); + + handler.on("Cleanup", function (data) { + return pdfManager.cleanup(/* manuallyTriggered = */ true); + }); + + handler.on("Terminate", function (data) { + terminated = true; + + const waitOn = []; + if (pdfManager) { + pdfManager.terminate(new AbortException("Worker was terminated.")); + + const cleanupPromise = pdfManager.cleanup(); + waitOn.push(cleanupPromise); + + pdfManager = null; + } else { + clearGlobalCaches(); + } + cancelXHRs?.(new AbortException("Worker was terminated.")); + + for (const task of WorkerTasks) { + waitOn.push(task.finished); + task.terminate(); + } + + return Promise.all(waitOn).then(function () { + // Notice that even if we destroying handler, resolved response promise + // must be sent back. + handler.destroy(); + handler = null; + }); + }); + + handler.on("Ready", function (data) { + setupDoc(docParams); + docParams = null; // we don't need docParams anymore -- saving memory. + }); + + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + handler.on("GetXFADatasets", function (data) { + return pdfManager.ensureDoc("xfaDatasets"); + }); + handler.on("GetXRefPrevValue", function (data) { + return pdfManager + .ensureXRef("trailer") + .then(trailer => trailer.get("Prev")); + }); + handler.on("GetStartXRefPos", function (data) { + return pdfManager.ensureDoc("startXRef"); + }); + handler.on("GetAnnotArray", function (data) { + return pdfManager.getPage(data.pageIndex).then(function (page) { + return page.annotations.map(a => a.toString()); + }); + }); + } + + return workerHandlerName; + } + + static initializeFromPort(port) { + const handler = new MessageHandler("worker", "main", port); + this.setup(handler, port); + handler.send("ready", null); + } +} + +export { WorkerMessageHandler, WorkerTask }; diff --git a/frontend/public/pdf.js/src/core/worker_stream.js b/frontend/public/pdf.js/src/core/worker_stream.js new file mode 100644 index 0000000..0479353 --- /dev/null +++ b/frontend/public/pdf.js/src/core/worker_stream.js @@ -0,0 +1,134 @@ +/* Copyright 2019 Mozilla Foundation + * + * 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 { assert } from "../shared/util.js"; + +/** @implements {IPDFStream} */ +class PDFWorkerStream { + constructor(msgHandler) { + this._msgHandler = msgHandler; + this._contentLength = null; + this._fullRequestReader = null; + this._rangeRequestReaders = []; + } + + getFullReader() { + assert( + !this._fullRequestReader, + "PDFWorkerStream.getFullReader can only be called once." + ); + this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler); + return this._fullRequestReader; + } + + getRangeReader(begin, end) { + const reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler); + this._rangeRequestReaders.push(reader); + return reader; + } + + cancelAllRequests(reason) { + this._fullRequestReader?.cancel(reason); + + for (const reader of this._rangeRequestReaders.slice(0)) { + reader.cancel(reason); + } + } +} + +/** @implements {IPDFStreamReader} */ +class PDFWorkerStreamReader { + constructor(msgHandler) { + this._msgHandler = msgHandler; + this.onProgress = null; + + this._contentLength = null; + this._isRangeSupported = false; + this._isStreamingSupported = false; + + const readableStream = this._msgHandler.sendWithStream("GetReader"); + this._reader = readableStream.getReader(); + + this._headersReady = this._msgHandler + .sendWithPromise("ReaderHeadersReady") + .then(data => { + this._isStreamingSupported = data.isStreamingSupported; + this._isRangeSupported = data.isRangeSupported; + this._contentLength = data.contentLength; + }); + } + + get headersReady() { + return this._headersReady; + } + + get contentLength() { + return this._contentLength; + } + + get isStreamingSupported() { + return this._isStreamingSupported; + } + + get isRangeSupported() { + return this._isRangeSupported; + } + + async read() { + const { value, done } = await this._reader.read(); + if (done) { + return { value: undefined, done: true }; + } + // `value` is wrapped into Uint8Array, we need to + // unwrap it to ArrayBuffer for further processing. + return { value: value.buffer, done: false }; + } + + cancel(reason) { + this._reader.cancel(reason); + } +} + +/** @implements {IPDFStreamRangeReader} */ +class PDFWorkerStreamRangeReader { + constructor(begin, end, msgHandler) { + this._msgHandler = msgHandler; + this.onProgress = null; + + const readableStream = this._msgHandler.sendWithStream("GetRangeReader", { + begin, + end, + }); + this._reader = readableStream.getReader(); + } + + get isStreamingSupported() { + return false; + } + + async read() { + const { value, done } = await this._reader.read(); + if (done) { + return { value: undefined, done: true }; + } + return { value: value.buffer, done: false }; + } + + cancel(reason) { + this._reader.cancel(reason); + } +} + +export { PDFWorkerStream }; diff --git a/frontend/public/pdf.js/src/core/writer.js b/frontend/public/pdf.js/src/core/writer.js new file mode 100644 index 0000000..a9095d9 --- /dev/null +++ b/frontend/public/pdf.js/src/core/writer.js @@ -0,0 +1,487 @@ +/* Copyright 2020 Mozilla Foundation + * + * 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 { bytesToString, info, warn } from "../shared/util.js"; +import { Dict, isName, Name, Ref } from "./primitives.js"; +import { + escapePDFName, + escapeString, + getSizeInBytes, + numberToString, + parseXFAPath, +} from "./core_utils.js"; +import { SimpleDOMNode, SimpleXMLParser } from "./xml_parser.js"; +import { Stream, StringStream } from "./stream.js"; +import { BaseStream } from "./base_stream.js"; +import { calculateMD5 } from "./crypto.js"; + +async function writeObject(ref, obj, buffer, { encrypt = null }) { + const transform = encrypt?.createCipherTransform(ref.num, ref.gen); + buffer.push(`${ref.num} ${ref.gen} obj\n`); + if (obj instanceof Dict) { + await writeDict(obj, buffer, transform); + } else if (obj instanceof BaseStream) { + await writeStream(obj, buffer, transform); + } else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) { + await writeArray(obj, buffer, transform); + } + buffer.push("\nendobj\n"); +} + +async function writeDict(dict, buffer, transform) { + buffer.push("<<"); + for (const key of dict.getKeys()) { + buffer.push(` /${escapePDFName(key)} `); + await writeValue(dict.getRaw(key), buffer, transform); + } + buffer.push(">>"); +} + +async function writeStream(stream, buffer, transform) { + let bytes = stream.getBytes(); + const { dict } = stream; + + const [filter, params] = await Promise.all([ + dict.getAsync("Filter"), + dict.getAsync("DecodeParms"), + ]); + + const filterZero = Array.isArray(filter) + ? await dict.xref.fetchIfRefAsync(filter[0]) + : filter; + const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode"); + + // If the string is too small there is no real benefit in compressing it. + // The number 256 is arbitrary, but it should be reasonable. + const MIN_LENGTH_FOR_COMPRESSING = 256; + + if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) { + try { + const cs = new CompressionStream("deflate"); + const writer = cs.writable.getWriter(); + await writer.ready; + writer + .write(bytes) + .then(async () => { + await writer.ready; + await writer.close(); + }) + .catch(() => {}); + + // Response::text doesn't return the correct data. + const buf = await new Response(cs.readable).arrayBuffer(); + bytes = new Uint8Array(buf); + + let newFilter, newParams; + if (!filter) { + newFilter = Name.get("FlateDecode"); + } else if (!isFilterZeroFlateDecode) { + newFilter = Array.isArray(filter) + ? [Name.get("FlateDecode"), ...filter] + : [Name.get("FlateDecode"), filter]; + if (params) { + newParams = Array.isArray(params) + ? [null, ...params] + : [null, params]; + } + } + if (newFilter) { + dict.set("Filter", newFilter); + } + if (newParams) { + dict.set("DecodeParms", newParams); + } + } catch (ex) { + info(`writeStream - cannot compress data: "${ex}".`); + } + } + + let string = bytesToString(bytes); + if (transform) { + string = transform.encryptString(string); + } + + dict.set("Length", string.length); + await writeDict(dict, buffer, transform); + buffer.push(" stream\n", string, "\nendstream"); +} + +async function writeArray(array, buffer, transform) { + buffer.push("["); + let first = true; + for (const val of array) { + if (!first) { + buffer.push(" "); + } else { + first = false; + } + await writeValue(val, buffer, transform); + } + buffer.push("]"); +} + +async function writeValue(value, buffer, transform) { + if (value instanceof Name) { + buffer.push(`/${escapePDFName(value.name)}`); + } else if (value instanceof Ref) { + buffer.push(`${value.num} ${value.gen} R`); + } else if (Array.isArray(value) || ArrayBuffer.isView(value)) { + await writeArray(value, buffer, transform); + } else if (typeof value === "string") { + if (transform) { + value = transform.encryptString(value); + } + buffer.push(`(${escapeString(value)})`); + } else if (typeof value === "number") { + buffer.push(numberToString(value)); + } else if (typeof value === "boolean") { + buffer.push(value.toString()); + } else if (value instanceof Dict) { + await writeDict(value, buffer, transform); + } else if (value instanceof BaseStream) { + await writeStream(value, buffer, transform); + } else if (value === null) { + buffer.push("null"); + } else { + warn(`Unhandled value in writer: ${typeof value}, please file a bug.`); + } +} + +function writeInt(number, size, offset, buffer) { + for (let i = size + offset - 1; i > offset - 1; i--) { + buffer[i] = number & 0xff; + number >>= 8; + } + return offset + size; +} + +function writeString(string, offset, buffer) { + for (let i = 0, len = string.length; i < len; i++) { + buffer[offset + i] = string.charCodeAt(i) & 0xff; + } +} + +function computeMD5(filesize, xrefInfo) { + const time = Math.floor(Date.now() / 1000); + const filename = xrefInfo.filename || ""; + const md5Buffer = [time.toString(), filename, filesize.toString()]; + let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0); + for (const value of Object.values(xrefInfo.info)) { + md5Buffer.push(value); + md5BufferLen += value.length; + } + + const array = new Uint8Array(md5BufferLen); + let offset = 0; + for (const str of md5Buffer) { + writeString(str, offset, array); + offset += str.length; + } + return bytesToString(calculateMD5(array)); +} + +function writeXFADataForAcroform(str, changes) { + const xml = new SimpleXMLParser({ hasAttributes: true }).parseFromString(str); + + for (const { xfa } of changes) { + if (!xfa) { + continue; + } + const { path, value } = xfa; + if (!path) { + continue; + } + const nodePath = parseXFAPath(path); + let node = xml.documentElement.searchNode(nodePath, 0); + if (!node && nodePath.length > 1) { + // If we're lucky the last element in the path will identify the node. + node = xml.documentElement.searchNode([nodePath.at(-1)], 0); + } + if (node) { + node.childNodes = Array.isArray(value) + ? value.map(val => new SimpleDOMNode("value", val)) + : [new SimpleDOMNode("#text", value)]; + } else { + warn(`Node not found for path: ${path}`); + } + } + const buffer = []; + xml.documentElement.dump(buffer); + return buffer.join(""); +} + +async function updateAcroform({ + xref, + acroForm, + acroFormRef, + hasXfa, + hasXfaDatasetsEntry, + xfaDatasetsRef, + needAppearances, + changes, +}) { + if (hasXfa && !hasXfaDatasetsEntry && !xfaDatasetsRef) { + warn("XFA - Cannot save it"); + } + + if (!needAppearances && (!hasXfa || !xfaDatasetsRef || hasXfaDatasetsEntry)) { + return; + } + + const dict = acroForm.clone(); + + if (hasXfa && !hasXfaDatasetsEntry) { + // We've a XFA array which doesn't contain a datasets entry. + // So we'll update the AcroForm dictionary to have an XFA containing + // the datasets. + const newXfa = acroForm.get("XFA").slice(); + newXfa.splice(2, 0, "datasets"); + newXfa.splice(3, 0, xfaDatasetsRef); + + dict.set("XFA", newXfa); + } + + if (needAppearances) { + dict.set("NeedAppearances", true); + } + + changes.put(acroFormRef, { + data: dict, + }); +} + +function updateXFA({ xfaData, xfaDatasetsRef, changes, xref }) { + if (xfaData === null) { + const datasets = xref.fetchIfRef(xfaDatasetsRef); + xfaData = writeXFADataForAcroform(datasets.getString(), changes); + } + const xfaDataStream = new StringStream(xfaData); + xfaDataStream.dict = new Dict(xref); + xfaDataStream.dict.set("Type", Name.get("EmbeddedFile")); + + changes.put(xfaDatasetsRef, { + data: xfaDataStream, + }); +} + +async function getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer) { + buffer.push("xref\n"); + const indexes = getIndexes(newRefs); + let indexesPosition = 0; + for (const { ref, data } of newRefs) { + if (ref.num === indexes[indexesPosition]) { + buffer.push( + `${indexes[indexesPosition]} ${indexes[indexesPosition + 1]}\n` + ); + indexesPosition += 2; + } + // The EOL is \r\n to make sure that every entry is exactly 20 bytes long. + // (see 7.5.4 - Cross-Reference Table). + if (data !== null) { + buffer.push( + `${baseOffset.toString().padStart(10, "0")} ${Math.min(ref.gen, 0xffff).toString().padStart(5, "0")} n\r\n` + ); + baseOffset += data.length; + } else { + buffer.push( + `0000000000 ${Math.min(ref.gen + 1, 0xffff) + .toString() + .padStart(5, "0")} f\r\n` + ); + } + } + computeIDs(baseOffset, xrefInfo, newXref); + buffer.push("trailer\n"); + await writeDict(newXref, buffer); + buffer.push("\nstartxref\n", baseOffset.toString(), "\n%%EOF\n"); +} + +function getIndexes(newRefs) { + const indexes = []; + for (const { ref } of newRefs) { + if (ref.num === indexes.at(-2) + indexes.at(-1)) { + indexes[indexes.length - 1] += 1; + } else { + indexes.push(ref.num, 1); + } + } + return indexes; +} + +async function getXRefStreamTable( + xrefInfo, + baseOffset, + newRefs, + newXref, + buffer +) { + const xrefTableData = []; + let maxOffset = 0; + let maxGen = 0; + for (const { ref, data } of newRefs) { + let gen; + maxOffset = Math.max(maxOffset, baseOffset); + if (data !== null) { + gen = Math.min(ref.gen, 0xffff); + xrefTableData.push([1, baseOffset, gen]); + baseOffset += data.length; + } else { + gen = Math.min(ref.gen + 1, 0xffff); + xrefTableData.push([0, 0, gen]); + } + maxGen = Math.max(maxGen, gen); + } + newXref.set("Index", getIndexes(newRefs)); + const offsetSize = getSizeInBytes(maxOffset); + const maxGenSize = getSizeInBytes(maxGen); + const sizes = [1, offsetSize, maxGenSize]; + newXref.set("W", sizes); + computeIDs(baseOffset, xrefInfo, newXref); + + const structSize = sizes.reduce((a, x) => a + x, 0); + const data = new Uint8Array(structSize * xrefTableData.length); + const stream = new Stream(data); + stream.dict = newXref; + + let offset = 0; + for (const [type, objOffset, gen] of xrefTableData) { + offset = writeInt(type, sizes[0], offset, data); + offset = writeInt(objOffset, sizes[1], offset, data); + offset = writeInt(gen, sizes[2], offset, data); + } + + await writeObject(xrefInfo.newRef, stream, buffer, {}); + buffer.push("startxref\n", baseOffset.toString(), "\n%%EOF\n"); +} + +function computeIDs(baseOffset, xrefInfo, newXref) { + if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) { + const md5 = computeMD5(baseOffset, xrefInfo); + newXref.set("ID", [xrefInfo.fileIds[0], md5]); + } +} + +function getTrailerDict(xrefInfo, changes, useXrefStream) { + const newXref = new Dict(null); + newXref.set("Prev", xrefInfo.startXRef); + const refForXrefTable = xrefInfo.newRef; + if (useXrefStream) { + changes.put(refForXrefTable, { data: "" }); + newXref.set("Size", refForXrefTable.num + 1); + newXref.set("Type", Name.get("XRef")); + } else { + newXref.set("Size", refForXrefTable.num); + } + if (xrefInfo.rootRef !== null) { + newXref.set("Root", xrefInfo.rootRef); + } + if (xrefInfo.infoRef !== null) { + newXref.set("Info", xrefInfo.infoRef); + } + if (xrefInfo.encryptRef !== null) { + newXref.set("Encrypt", xrefInfo.encryptRef); + } + return newXref; +} + +async function writeChanges(changes, xref, buffer = []) { + const newRefs = []; + for (const [ref, { data }] of changes.items()) { + if (data === null || typeof data === "string") { + newRefs.push({ ref, data }); + continue; + } + await writeObject(ref, data, buffer, xref); + newRefs.push({ ref, data: buffer.join("") }); + buffer.length = 0; + } + return newRefs.sort((a, b) => /* compare the refs */ a.ref.num - b.ref.num); +} + +async function incrementalUpdate({ + originalData, + xrefInfo, + changes, + xref = null, + hasXfa = false, + xfaDatasetsRef = null, + hasXfaDatasetsEntry = false, + needAppearances, + acroFormRef = null, + acroForm = null, + xfaData = null, + useXrefStream = false, +}) { + await updateAcroform({ + xref, + acroForm, + acroFormRef, + hasXfa, + hasXfaDatasetsEntry, + xfaDatasetsRef, + needAppearances, + changes, + }); + + if (hasXfa) { + updateXFA({ + xfaData, + xfaDatasetsRef, + changes, + xref, + }); + } + + const newXref = getTrailerDict(xrefInfo, changes, useXrefStream); + const buffer = []; + const newRefs = await writeChanges(changes, xref, buffer); + let baseOffset = originalData.length; + const lastByte = originalData.at(-1); + if (lastByte !== /* \n */ 0x0a && lastByte !== /* \r */ 0x0d) { + // Avoid to concatenate %%EOF with an object definition + buffer.push("\n"); + baseOffset += 1; + } + + for (const { data } of newRefs) { + if (data !== null) { + buffer.push(data); + } + } + + await (useXrefStream + ? getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer) + : getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer)); + + const totalLength = buffer.reduce( + (a, str) => a + str.length, + originalData.length + ); + const array = new Uint8Array(totalLength); + + // Original data + array.set(originalData); + let offset = originalData.length; + + // New data + for (const str of buffer) { + writeString(str, offset, array); + offset += str.length; + } + + return array; +} + +export { incrementalUpdate, writeChanges, writeDict, writeObject }; diff --git a/frontend/public/pdf.js/src/core/xfa/bind.js b/frontend/public/pdf.js/src/core/xfa/bind.js new file mode 100644 index 0000000..4c67521 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/bind.js @@ -0,0 +1,674 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $appendChild, + $clone, + $consumed, + $content, + $data, + $finalize, + $getAttributeIt, + $getChildren, + $getDataValue, + $getParent, + $getRealChildrenByNameIt, + $hasSettableValue, + $indexOf, + $insertAt, + $isBindable, + $isDataValue, + $isDescendent, + $namespaceId, + $nodeName, + $removeChild, + $setValue, + $text, +} from "./symbol_utils.js"; +import { BindItems, Field, Items, SetProperty, Text } from "./template.js"; +import { createDataNode, searchNode } from "./som.js"; +import { XFAAttribute, XFAObjectArray, XmlObject } from "./xfa_object.js"; +import { NamespaceIds } from "./namespaces.js"; +import { warn } from "../../shared/util.js"; + +const NS_DATASETS = NamespaceIds.datasets.id; + +function createText(content) { + const node = new Text({}); + node[$content] = content; + return node; +} + +class Binder { + constructor(root) { + this.root = root; + this.datasets = root.datasets; + this.data = + root.datasets?.data || new XmlObject(NamespaceIds.datasets.id, "data"); + this.emptyMerge = this.data[$getChildren]().length === 0; + + this.root.form = this.form = root.template[$clone](); + } + + _isConsumeData() { + return !this.emptyMerge && this._mergeMode; + } + + _isMatchTemplate() { + return !this._isConsumeData(); + } + + bind() { + this._bindElement(this.form, this.data); + return this.form; + } + + getData() { + return this.data; + } + + _bindValue(formNode, data, picture) { + // Nodes must have the same "type": container or value. + // Here we make the link between form node and + // data node (through $data property): we'll use it + // to save form data. + + formNode[$data] = data; + if (formNode[$hasSettableValue]()) { + if (data[$isDataValue]()) { + const value = data[$getDataValue](); + // TODO: use picture. + formNode[$setValue](createText(value)); + } else if ( + formNode instanceof Field && + formNode.ui?.choiceList?.open === "multiSelect" + ) { + const value = data[$getChildren]() + .map(child => child[$content].trim()) + .join("\n"); + formNode[$setValue](createText(value)); + } else if (this._isConsumeData()) { + warn(`XFA - Nodes haven't the same type.`); + } + } else if (!data[$isDataValue]() || this._isMatchTemplate()) { + this._bindElement(formNode, data); + } else { + warn(`XFA - Nodes haven't the same type.`); + } + } + + _findDataByNameToConsume(name, isValue, dataNode, global) { + if (!name) { + return null; + } + + // Firstly, we try to find a node with the given name: + // - in dataNode; + // - if not found, then in parent; + // - and if not in found, then in grand-parent. + let generator, match; + for (let i = 0; i < 3; i++) { + generator = dataNode[$getRealChildrenByNameIt]( + name, + /* allTransparent = */ false, + /* skipConsumed = */ true + ); + // Try to find a match of the same kind. + while (true) { + match = generator.next().value; + if (!match) { + break; + } + + if (isValue === match[$isDataValue]()) { + return match; + } + } + if ( + dataNode[$namespaceId] === NamespaceIds.datasets.id && + dataNode[$nodeName] === "data" + ) { + break; + } + dataNode = dataNode[$getParent](); + } + + if (!global) { + return null; + } + + // Secondly, if global try to find it just under the root of datasets + // (which is the location of global variables). + generator = this.data[$getRealChildrenByNameIt]( + name, + /* allTransparent = */ true, + /* skipConsumed = */ false + ); + + match = generator.next().value; + if (match) { + return match; + } + + // Thirdly, try to find it in attributes. + generator = this.data[$getAttributeIt](name, /* skipConsumed = */ true); + match = generator.next().value; + if (match?.[$isDataValue]()) { + return match; + } + + return null; + } + + _setProperties(formNode, dataNode) { + // For example: + // + // + // + // + // + + if (!formNode.hasOwnProperty("setProperty")) { + return; + } + + for (const { ref, target, connection } of formNode.setProperty.children) { + if (connection) { + // TODO: evaluate if we should implement this feature. + // Skip for security reasons. + continue; + } + if (!ref) { + continue; + } + + const nodes = searchNode( + this.root, + dataNode, + ref, + false /* = dotDotAllowed */, + false /* = useCache */ + ); + if (!nodes) { + warn(`XFA - Invalid reference: ${ref}.`); + continue; + } + const [node] = nodes; + + if (!node[$isDescendent](this.data)) { + warn(`XFA - Invalid node: must be a data node.`); + continue; + } + + const targetNodes = searchNode( + this.root, + formNode, + target, + false /* = dotDotAllowed */, + false /* = useCache */ + ); + if (!targetNodes) { + warn(`XFA - Invalid target: ${target}.`); + continue; + } + const [targetNode] = targetNodes; + + if (!targetNode[$isDescendent](formNode)) { + warn(`XFA - Invalid target: must be a property or subproperty.`); + continue; + } + + const targetParent = targetNode[$getParent](); + if ( + targetNode instanceof SetProperty || + targetParent instanceof SetProperty + ) { + warn( + `XFA - Invalid target: cannot be a setProperty or one of its properties.` + ); + continue; + } + + if ( + targetNode instanceof BindItems || + targetParent instanceof BindItems + ) { + warn( + `XFA - Invalid target: cannot be a bindItems or one of its properties.` + ); + continue; + } + + const content = node[$text](); + const name = targetNode[$nodeName]; + + if (targetNode instanceof XFAAttribute) { + const attrs = Object.create(null); + attrs[name] = content; + const obj = Reflect.construct( + Object.getPrototypeOf(targetParent).constructor, + [attrs] + ); + targetParent[name] = obj[name]; + continue; + } + + if (!targetNode.hasOwnProperty($content)) { + warn(`XFA - Invalid node to use in setProperty`); + continue; + } + + targetNode[$data] = node; + targetNode[$content] = content; + targetNode[$finalize](); + } + } + + _bindItems(formNode, dataNode) { + // For example: + // + // + // + // + + if ( + !formNode.hasOwnProperty("items") || + !formNode.hasOwnProperty("bindItems") || + formNode.bindItems.isEmpty() + ) { + return; + } + + for (const item of formNode.items.children) { + formNode[$removeChild](item); + } + + formNode.items.clear(); + + const labels = new Items({}); + const values = new Items({}); + + formNode[$appendChild](labels); + formNode.items.push(labels); + + formNode[$appendChild](values); + formNode.items.push(values); + + for (const { ref, labelRef, valueRef, connection } of formNode.bindItems + .children) { + if (connection) { + // TODO: evaluate if we should implement this feature. + // Skip for security reasons. + continue; + } + if (!ref) { + continue; + } + + const nodes = searchNode( + this.root, + dataNode, + ref, + false /* = dotDotAllowed */, + false /* = useCache */ + ); + if (!nodes) { + warn(`XFA - Invalid reference: ${ref}.`); + continue; + } + for (const node of nodes) { + if (!node[$isDescendent](this.datasets)) { + warn(`XFA - Invalid ref (${ref}): must be a datasets child.`); + continue; + } + + const labelNodes = searchNode( + this.root, + node, + labelRef, + true /* = dotDotAllowed */, + false /* = useCache */ + ); + if (!labelNodes) { + warn(`XFA - Invalid label: ${labelRef}.`); + continue; + } + const [labelNode] = labelNodes; + + if (!labelNode[$isDescendent](this.datasets)) { + warn(`XFA - Invalid label: must be a datasets child.`); + continue; + } + + const valueNodes = searchNode( + this.root, + node, + valueRef, + true /* = dotDotAllowed */, + false /* = useCache */ + ); + if (!valueNodes) { + warn(`XFA - Invalid value: ${valueRef}.`); + continue; + } + const [valueNode] = valueNodes; + + if (!valueNode[$isDescendent](this.datasets)) { + warn(`XFA - Invalid value: must be a datasets child.`); + continue; + } + + const label = createText(labelNode[$text]()); + const value = createText(valueNode[$text]()); + + labels[$appendChild](label); + labels.text.push(label); + + values[$appendChild](value); + values.text.push(value); + } + } + } + + _bindOccurrences(formNode, matches, picture) { + // Insert nodes which are not in the template but reflect + // what we've in data tree. + + let baseClone; + if (matches.length > 1) { + // Clone before binding to avoid bad state. + baseClone = formNode[$clone](); + baseClone[$removeChild](baseClone.occur); + baseClone.occur = null; + } + + this._bindValue(formNode, matches[0], picture); + this._setProperties(formNode, matches[0]); + this._bindItems(formNode, matches[0]); + + if (matches.length === 1) { + return; + } + + const parent = formNode[$getParent](); + const name = formNode[$nodeName]; + const pos = parent[$indexOf](formNode); + + for (let i = 1, ii = matches.length; i < ii; i++) { + const match = matches[i]; + const clone = baseClone[$clone](); + parent[name].push(clone); + parent[$insertAt](pos + i, clone); + + this._bindValue(clone, match, picture); + this._setProperties(clone, match); + this._bindItems(clone, match); + } + } + + _createOccurrences(formNode) { + if (!this.emptyMerge) { + return; + } + + const { occur } = formNode; + if (!occur || occur.initial <= 1) { + return; + } + + const parent = formNode[$getParent](); + const name = formNode[$nodeName]; + + if (!(parent[name] instanceof XFAObjectArray)) { + return; + } + + let currentNumber; + if (formNode.name) { + currentNumber = parent[name].children.filter( + e => e.name === formNode.name + ).length; + } else { + currentNumber = parent[name].children.length; + } + + const pos = parent[$indexOf](formNode) + 1; + const ii = occur.initial - currentNumber; + if (ii) { + const nodeClone = formNode[$clone](); + nodeClone[$removeChild](nodeClone.occur); + nodeClone.occur = null; + parent[name].push(nodeClone); + parent[$insertAt](pos, nodeClone); + + for (let i = 1; i < ii; i++) { + const clone = nodeClone[$clone](); + parent[name].push(clone); + parent[$insertAt](pos + i, clone); + } + } + } + + _getOccurInfo(formNode) { + const { name, occur } = formNode; + if (!occur || !name) { + return [1, 1]; + } + const max = occur.max === -1 ? Infinity : occur.max; + return [occur.min, max]; + } + + _setAndBind(formNode, dataNode) { + this._setProperties(formNode, dataNode); + this._bindItems(formNode, dataNode); + this._bindElement(formNode, dataNode); + } + + _bindElement(formNode, dataNode) { + // Some nodes can be useless because min=0 so remove them + // after the loop to avoid bad things. + + const uselessNodes = []; + + this._createOccurrences(formNode); + + for (const child of formNode[$getChildren]()) { + if (child[$data]) { + // Already bound. + continue; + } + + if (this._mergeMode === undefined && child[$nodeName] === "subform") { + this._mergeMode = child.mergeMode === "consumeData"; + + // XFA specs p. 182: + // The highest-level subform and the data node representing + // the current record are special; they are always + // bound even if their names don't match. + const dataChildren = dataNode[$getChildren](); + if (dataChildren.length > 0) { + this._bindOccurrences(child, [dataChildren[0]], null); + } else if (this.emptyMerge) { + const nsId = + dataNode[$namespaceId] === NS_DATASETS + ? -1 + : dataNode[$namespaceId]; + const dataChild = (child[$data] = new XmlObject( + nsId, + child.name || "root" + )); + dataNode[$appendChild](dataChild); + this._bindElement(child, dataChild); + } + continue; + } + + if (!child[$isBindable]()) { + // The node cannot contain some new data so there is nothing + // to create in the data node. + continue; + } + + let global = false; + let picture = null; + let ref = null; + let match = null; + if (child.bind) { + switch (child.bind.match) { + case "none": + this._setAndBind(child, dataNode); + continue; + case "global": + global = true; + break; + case "dataRef": + if (!child.bind.ref) { + warn(`XFA - ref is empty in node ${child[$nodeName]}.`); + this._setAndBind(child, dataNode); + continue; + } + ref = child.bind.ref; + break; + default: + break; + } + if (child.bind.picture) { + picture = child.bind.picture[$content]; + } + } + + const [min, max] = this._getOccurInfo(child); + + if (ref) { + // Don't use a cache for searching: nodes can change during binding. + match = searchNode( + this.root, + dataNode, + ref, + true /* = dotDotAllowed */, + false /* = useCache */ + ); + if (match === null) { + // Nothing found: we must create some nodes in data in order + // to have something to match with the given expression. + // See http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.364.2157&rep=rep1&type=pdf#page=199 + match = createDataNode(this.data, dataNode, ref); + if (!match) { + // For example if the node contains a .(...) then it isn't + // findable. + // TODO: remove this when .(...) is implemented. + continue; + } + if (this._isConsumeData()) { + match[$consumed] = true; + } + + // Don't bind the value in newly created node because it's empty. + this._setAndBind(child, match); + continue; + } else { + if (this._isConsumeData()) { + // Filter out consumed nodes. + match = match.filter(node => !node[$consumed]); + } + if (match.length > max) { + match = match.slice(0, max); + } else if (match.length === 0) { + match = null; + } + if (match && this._isConsumeData()) { + match.forEach(node => { + node[$consumed] = true; + }); + } + } + } else { + if (!child.name) { + this._setAndBind(child, dataNode); + continue; + } + if (this._isConsumeData()) { + // In consumeData mode, search for the next node with the given name. + // occurs.max gives us the max number of node to match. + const matches = []; + while (matches.length < max) { + const found = this._findDataByNameToConsume( + child.name, + child[$hasSettableValue](), + dataNode, + global + ); + + if (!found) { + break; + } + found[$consumed] = true; + matches.push(found); + } + match = matches.length > 0 ? matches : null; + } else { + // If we've an empty merge, there are no reason + // to make multiple bind so skip consumed nodes. + match = dataNode[$getRealChildrenByNameIt]( + child.name, + /* allTransparent = */ false, + /* skipConsumed = */ this.emptyMerge + ).next().value; + if (!match) { + // If there is no match (no data) and `min === 0` then + // the container is entirely excluded. + // https://www.pdfa.org/norm-refs/XFA-3_3.pdf#G12.1428332 + if (min === 0) { + uselessNodes.push(child); + continue; + } + // We're in matchTemplate mode so create a node in data to reflect + // what we've in template. + const nsId = + dataNode[$namespaceId] === NS_DATASETS + ? -1 + : dataNode[$namespaceId]; + match = child[$data] = new XmlObject(nsId, child.name); + if (this.emptyMerge) { + match[$consumed] = true; + } + dataNode[$appendChild](match); + + // Don't bind the value in newly created node because it's empty. + this._setAndBind(child, match); + continue; + } + if (this.emptyMerge) { + match[$consumed] = true; + } + match = [match]; + } + } + + if (match) { + this._bindOccurrences(child, match, picture); + } else if (min > 0) { + this._setAndBind(child, dataNode); + } else { + uselessNodes.push(child); + } + } + + uselessNodes.forEach(node => node[$getParent]()[$removeChild](node)); + } +} + +export { Binder }; diff --git a/frontend/public/pdf.js/src/core/xfa/builder.js b/frontend/public/pdf.js/src/core/xfa/builder.js new file mode 100644 index 0000000..48400c3 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/builder.js @@ -0,0 +1,207 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { + $cleanup, + $finalize, + $ids, + $isNsAgnostic, + $nsAttributes, + $onChild, + $resolvePrototypes, + $root, +} from "./symbol_utils.js"; +import { NamespaceSetUp } from "./setup.js"; +import { Template } from "./template.js"; +import { UnknownNamespace } from "./unknown.js"; +import { warn } from "../../shared/util.js"; +import { XFAObject } from "./xfa_object.js"; + +class Root extends XFAObject { + constructor(ids) { + super(-1, "root", Object.create(null)); + this.element = null; + this[$ids] = ids; + } + + [$onChild](child) { + this.element = child; + return true; + } + + [$finalize]() { + super[$finalize](); + if (this.element.template instanceof Template) { + // Set the root element in $ids using a symbol in order + // to avoid conflict with real IDs. + this[$ids].set($root, this.element); + + this.element.template[$resolvePrototypes](this[$ids]); + this.element.template[$ids] = this[$ids]; + } + } +} + +class Empty extends XFAObject { + constructor() { + super(-1, "", Object.create(null)); + } + + [$onChild](_) { + return false; + } +} + +class Builder { + constructor(rootNameSpace = null) { + this._namespaceStack = []; + this._nsAgnosticLevel = 0; + + // Each prefix has its own stack + this._namespacePrefixes = new Map(); + this._namespaces = new Map(); + this._nextNsId = Math.max( + ...Object.values(NamespaceIds).map(({ id }) => id) + ); + this._currentNamespace = + rootNameSpace || new UnknownNamespace(++this._nextNsId); + } + + buildRoot(ids) { + return new Root(ids); + } + + build({ nsPrefix, name, attributes, namespace, prefixes }) { + const hasNamespaceDef = namespace !== null; + if (hasNamespaceDef) { + // Define the current namespace to use. + this._namespaceStack.push(this._currentNamespace); + this._currentNamespace = this._searchNamespace(namespace); + } + + if (prefixes) { + // The xml node may have namespace prefix definitions + this._addNamespacePrefix(prefixes); + } + + if (attributes.hasOwnProperty($nsAttributes)) { + // Only support xfa-data namespace. + const dataTemplate = NamespaceSetUp.datasets; + const nsAttrs = attributes[$nsAttributes]; + let xfaAttrs = null; + for (const [ns, attrs] of Object.entries(nsAttrs)) { + const nsToUse = this._getNamespaceToUse(ns); + if (nsToUse === dataTemplate) { + xfaAttrs = { xfa: attrs }; + break; + } + } + if (xfaAttrs) { + attributes[$nsAttributes] = xfaAttrs; + } else { + delete attributes[$nsAttributes]; + } + } + + const namespaceToUse = this._getNamespaceToUse(nsPrefix); + const node = + namespaceToUse?.[$buildXFAObject](name, attributes) || new Empty(); + + if (node[$isNsAgnostic]()) { + this._nsAgnosticLevel++; + } + + // In case the node has some namespace things, + // we must pop the different stacks. + if (hasNamespaceDef || prefixes || node[$isNsAgnostic]()) { + node[$cleanup] = { + hasNamespace: hasNamespaceDef, + prefixes, + nsAgnostic: node[$isNsAgnostic](), + }; + } + + return node; + } + + isNsAgnostic() { + return this._nsAgnosticLevel > 0; + } + + _searchNamespace(nsName) { + let ns = this._namespaces.get(nsName); + if (ns) { + return ns; + } + for (const [name, { check }] of Object.entries(NamespaceIds)) { + if (check(nsName)) { + ns = NamespaceSetUp[name]; + if (ns) { + this._namespaces.set(nsName, ns); + return ns; + } + // The namespace is known but not handled. + break; + } + } + + ns = new UnknownNamespace(++this._nextNsId); + this._namespaces.set(nsName, ns); + return ns; + } + + _addNamespacePrefix(prefixes) { + for (const { prefix, value } of prefixes) { + const namespace = this._searchNamespace(value); + let prefixStack = this._namespacePrefixes.get(prefix); + if (!prefixStack) { + prefixStack = []; + this._namespacePrefixes.set(prefix, prefixStack); + } + prefixStack.push(namespace); + } + } + + _getNamespaceToUse(prefix) { + if (!prefix) { + return this._currentNamespace; + } + const prefixStack = this._namespacePrefixes.get(prefix); + if (prefixStack?.length > 0) { + return prefixStack.at(-1); + } + + warn(`Unknown namespace prefix: ${prefix}.`); + return null; + } + + clean(data) { + const { hasNamespace, prefixes, nsAgnostic } = data; + if (hasNamespace) { + this._currentNamespace = this._namespaceStack.pop(); + } + if (prefixes) { + prefixes.forEach(({ prefix }) => { + this._namespacePrefixes.get(prefix).pop(); + }); + } + if (nsAgnostic) { + this._nsAgnosticLevel--; + } + } +} + +export { Builder }; diff --git a/frontend/public/pdf.js/src/core/xfa/config.js b/frontend/public/pdf.js/src/core/xfa/config.js new file mode 100644 index 0000000..aef9aa5 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/config.js @@ -0,0 +1,1925 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { $content, $finalize } from "./symbol_utils.js"; +import { + ContentObject, + IntegerObject, + Option01, + Option10, + OptionObject, + StringObject, + XFAObject, + XFAObjectArray, +} from "./xfa_object.js"; +import { getInteger, getStringOption } from "./utils.js"; +import { shadow, warn } from "../../shared/util.js"; + +const CONFIG_NS_ID = NamespaceIds.config.id; + +class Acrobat extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "acrobat", /* hasChildren = */ true); + this.acrobat7 = null; + this.autoSave = null; + this.common = null; + this.validate = null; + this.validateApprovalSignatures = null; + this.submitUrl = new XFAObjectArray(); + } +} + +class Acrobat7 extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "acrobat7", /* hasChildren = */ true); + this.dynamicRender = null; + } +} + +class ADBE_JSConsole extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "ADBE_JSConsole", ["delegate", "Enable", "Disable"]); + } +} + +class ADBE_JSDebugger extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "ADBE_JSDebugger", ["delegate", "Enable", "Disable"]); + } +} + +class AddSilentPrint extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "addSilentPrint"); + } +} + +class AddViewerPreferences extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "addViewerPreferences"); + } +} + +class AdjustData extends Option10 { + constructor(attributes) { + super(CONFIG_NS_ID, "adjustData"); + } +} + +class AdobeExtensionLevel extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "adobeExtensionLevel", 0, n => n >= 1 && n <= 8); + } +} + +class Agent extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "agent", /* hasChildren = */ true); + this.name = attributes.name ? attributes.name.trim() : ""; + this.common = new XFAObjectArray(); + } +} + +class AlwaysEmbed extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "alwaysEmbed"); + } +} + +class Amd extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "amd"); + } +} + +class Area extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "area"); + this.level = getInteger({ + data: attributes.level, + defaultValue: 0, + validate: n => n >= 1 && n <= 3, + }); + this.name = getStringOption(attributes.name, [ + "", + "barcode", + "coreinit", + "deviceDriver", + "font", + "general", + "layout", + "merge", + "script", + "signature", + "sourceSet", + "templateCache", + ]); + } +} + +class Attributes extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "attributes", ["preserve", "delegate", "ignore"]); + } +} + +class AutoSave extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "autoSave", ["disabled", "enabled"]); + } +} + +class Base extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "base"); + } +} + +class BatchOutput extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "batchOutput"); + this.format = getStringOption(attributes.format, [ + "none", + "concat", + "zip", + "zipCompress", + ]); + } +} + +class BehaviorOverride extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "behaviorOverride"); + } + + [$finalize]() { + this[$content] = new Map( + this[$content] + .trim() + .split(/\s+/) + .filter(x => x.includes(":")) + .map(x => x.split(":", 2)) + ); + } +} + +class Cache extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "cache", /* hasChildren = */ true); + this.templateCache = null; + } +} + +class Change extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "change"); + } +} + +class Common extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "common", /* hasChildren = */ true); + this.data = null; + this.locale = null; + this.localeSet = null; + this.messaging = null; + this.suppressBanner = null; + this.template = null; + this.validationMessaging = null; + this.versionControl = null; + this.log = new XFAObjectArray(); + } +} + +class Compress extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "compress"); + this.scope = getStringOption(attributes.scope, ["imageOnly", "document"]); + } +} + +class CompressLogicalStructure extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "compressLogicalStructure"); + } +} + +class CompressObjectStream extends Option10 { + constructor(attributes) { + super(CONFIG_NS_ID, "compressObjectStream"); + } +} + +class Compression extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "compression", /* hasChildren = */ true); + this.compressLogicalStructure = null; + this.compressObjectStream = null; + this.level = null; + this.type = null; + } +} + +class Config extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "config", /* hasChildren = */ true); + this.acrobat = null; + this.present = null; + this.trace = null; + this.agent = new XFAObjectArray(); + } +} + +class Conformance extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "conformance", ["A", "B"]); + } +} + +class ContentCopy extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "contentCopy"); + } +} + +class Copies extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "copies", 1, n => n >= 1); + } +} + +class Creator extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "creator"); + } +} + +class CurrentPage extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "currentPage", 0, n => n >= 0); + } +} + +class Data extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "data", /* hasChildren = */ true); + this.adjustData = null; + this.attributes = null; + this.incrementalLoad = null; + this.outputXSL = null; + this.range = null; + this.record = null; + this.startNode = null; + this.uri = null; + this.window = null; + this.xsl = null; + this.excludeNS = new XFAObjectArray(); + this.transform = new XFAObjectArray(); + } +} + +class Debug extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "debug", /* hasChildren = */ true); + this.uri = null; + } +} + +class DefaultTypeface extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "defaultTypeface"); + this.writingScript = getStringOption(attributes.writingScript, [ + "*", + "Arabic", + "Cyrillic", + "EastEuropeanRoman", + "Greek", + "Hebrew", + "Japanese", + "Korean", + "Roman", + "SimplifiedChinese", + "Thai", + "TraditionalChinese", + "Vietnamese", + ]); + } +} + +class Destination extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "destination", [ + "pdf", + "pcl", + "ps", + "webClient", + "zpl", + ]); + } +} + +class DocumentAssembly extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "documentAssembly"); + } +} + +class Driver extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "driver", /* hasChildren = */ true); + this.name = attributes.name ? attributes.name.trim() : ""; + this.fontInfo = null; + this.xdc = null; + } +} + +class DuplexOption extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "duplexOption", [ + "simplex", + "duplexFlipLongEdge", + "duplexFlipShortEdge", + ]); + } +} + +class DynamicRender extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "dynamicRender", ["forbidden", "required"]); + } +} + +class Embed extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "embed"); + } +} + +class Encrypt extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "encrypt"); + } +} + +class Encryption extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "encryption", /* hasChildren = */ true); + this.encrypt = null; + this.encryptionLevel = null; + this.permissions = null; + } +} + +class EncryptionLevel extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "encryptionLevel", ["40bit", "128bit"]); + } +} + +class Enforce extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "enforce"); + } +} + +class Equate extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "equate"); + + this.force = getInteger({ + data: attributes.force, + defaultValue: 1, + validate: n => n === 0, + }); + + this.from = attributes.from || ""; + this.to = attributes.to || ""; + } +} + +class EquateRange extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "equateRange"); + + this.from = attributes.from || ""; + this.to = attributes.to || ""; + this._unicodeRange = attributes.unicodeRange || ""; + } + + get unicodeRange() { + const ranges = []; + const unicodeRegex = /U\+([0-9a-fA-F]+)/; + const unicodeRange = this._unicodeRange; + for (let range of unicodeRange + .split(",") + .map(x => x.trim()) + .filter(x => !!x)) { + range = range.split("-", 2).map(x => { + const found = x.match(unicodeRegex); + if (!found) { + return 0; + } + return parseInt(found[1], 16); + }); + if (range.length === 1) { + range.push(range[0]); + } + ranges.push(range); + } + return shadow(this, "unicodeRange", ranges); + } +} + +class Exclude extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "exclude"); + } + + [$finalize]() { + this[$content] = this[$content] + .trim() + .split(/\s+/) + .filter( + x => + x && + [ + "calculate", + "close", + "enter", + "exit", + "initialize", + "ready", + "validate", + ].includes(x) + ); + } +} + +class ExcludeNS extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "excludeNS"); + } +} + +class FlipLabel extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "flipLabel", ["usePrinterSetting", "on", "off"]); + } +} + +class FontInfo extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "fontInfo", /* hasChildren = */ true); + this.embed = null; + this.map = null; + this.subsetBelow = null; + this.alwaysEmbed = new XFAObjectArray(); + this.defaultTypeface = new XFAObjectArray(); + this.neverEmbed = new XFAObjectArray(); + } +} + +class FormFieldFilling extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "formFieldFilling"); + } +} + +class GroupParent extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "groupParent"); + } +} + +class IfEmpty extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "ifEmpty", [ + "dataValue", + "dataGroup", + "ignore", + "remove", + ]); + } +} + +class IncludeXDPContent extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "includeXDPContent"); + } +} + +class IncrementalLoad extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "incrementalLoad", ["none", "forwardOnly"]); + } +} + +class IncrementalMerge extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "incrementalMerge"); + } +} + +class Interactive extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "interactive"); + } +} + +class Jog extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "jog", ["usePrinterSetting", "none", "pageSet"]); + } +} + +class LabelPrinter extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "labelPrinter", /* hasChildren = */ true); + this.name = getStringOption(attributes.name, ["zpl", "dpl", "ipl", "tcpl"]); + this.batchOutput = null; + this.flipLabel = null; + this.fontInfo = null; + this.xdc = null; + } +} + +class Layout extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "layout", ["paginate", "panel"]); + } +} + +class Level extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "level", 0, n => n > 0); + } +} + +class Linearized extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "linearized"); + } +} + +class Locale extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "locale"); + } +} + +class LocaleSet extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "localeSet"); + } +} + +class Log extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "log", /* hasChildren = */ true); + this.mode = null; + this.threshold = null; + this.to = null; + this.uri = null; + } +} + +// Renamed in MapElement to avoid confusion with usual js Map. +class MapElement extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "map", /* hasChildren = */ true); + this.equate = new XFAObjectArray(); + this.equateRange = new XFAObjectArray(); + } +} + +class MediumInfo extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "mediumInfo", /* hasChildren = */ true); + this.map = null; + } +} + +class Message extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "message", /* hasChildren = */ true); + this.msgId = null; + this.severity = null; + } +} + +class Messaging extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "messaging", /* hasChildren = */ true); + this.message = new XFAObjectArray(); + } +} + +class Mode extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "mode", ["append", "overwrite"]); + } +} + +class ModifyAnnots extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "modifyAnnots"); + } +} + +class MsgId extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "msgId", 1, n => n >= 1); + } +} + +class NameAttr extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "nameAttr"); + } +} + +class NeverEmbed extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "neverEmbed"); + } +} + +class NumberOfCopies extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "numberOfCopies", null, n => n >= 2 && n <= 5); + } +} + +class OpenAction extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "openAction", /* hasChildren = */ true); + this.destination = null; + } +} + +class Output extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "output", /* hasChildren = */ true); + this.to = null; + this.type = null; + this.uri = null; + } +} + +class OutputBin extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "outputBin"); + } +} + +class OutputXSL extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "outputXSL", /* hasChildren = */ true); + this.uri = null; + } +} + +class Overprint extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "overprint", ["none", "both", "draw", "field"]); + } +} + +class Packets extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "packets"); + } + + [$finalize]() { + if (this[$content] === "*") { + return; + } + this[$content] = this[$content] + .trim() + .split(/\s+/) + .filter(x => + ["config", "datasets", "template", "xfdf", "xslt"].includes(x) + ); + } +} + +class PageOffset extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "pageOffset"); + this.x = getInteger({ + data: attributes.x, + defaultValue: "useXDCSetting", + validate: n => true, + }); + this.y = getInteger({ + data: attributes.y, + defaultValue: "useXDCSetting", + validate: n => true, + }); + } +} + +class PageRange extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "pageRange"); + } + + [$finalize]() { + const numbers = this[$content] + .trim() + .split(/\s+/) + .map(x => parseInt(x, 10)); + const ranges = []; + for (let i = 0, ii = numbers.length; i < ii; i += 2) { + ranges.push(numbers.slice(i, i + 2)); + } + this[$content] = ranges; + } +} + +class Pagination extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "pagination", [ + "simplex", + "duplexShortEdge", + "duplexLongEdge", + ]); + } +} + +class PaginationOverride extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "paginationOverride", [ + "none", + "forceDuplex", + "forceDuplexLongEdge", + "forceDuplexShortEdge", + "forceSimplex", + ]); + } +} + +class Part extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "part", 1, n => false); + } +} + +class Pcl extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "pcl", /* hasChildren = */ true); + this.name = attributes.name || ""; + this.batchOutput = null; + this.fontInfo = null; + this.jog = null; + this.mediumInfo = null; + this.outputBin = null; + this.pageOffset = null; + this.staple = null; + this.xdc = null; + } +} + +class Pdf extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "pdf", /* hasChildren = */ true); + this.name = attributes.name || ""; + this.adobeExtensionLevel = null; + this.batchOutput = null; + this.compression = null; + this.creator = null; + this.encryption = null; + this.fontInfo = null; + this.interactive = null; + this.linearized = null; + this.openAction = null; + this.pdfa = null; + this.producer = null; + this.renderPolicy = null; + this.scriptModel = null; + this.silentPrint = null; + this.submitFormat = null; + this.tagged = null; + this.version = null; + this.viewerPreferences = null; + this.xdc = null; + } +} + +class Pdfa extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "pdfa", /* hasChildren = */ true); + this.amd = null; + this.conformance = null; + this.includeXDPContent = null; + this.part = null; + } +} + +class Permissions extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "permissions", /* hasChildren = */ true); + this.accessibleContent = null; + this.change = null; + this.contentCopy = null; + this.documentAssembly = null; + this.formFieldFilling = null; + this.modifyAnnots = null; + this.plaintextMetadata = null; + this.print = null; + this.printHighQuality = null; + } +} + +class PickTrayByPDFSize extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "pickTrayByPDFSize"); + } +} + +class Picture extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "picture"); + } + + // TODO: check the validity of the picture clause. + // See page 1150 in the spec. +} + +class PlaintextMetadata extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "plaintextMetadata"); + } +} + +class Presence extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "presence", [ + "preserve", + "dissolve", + "dissolveStructure", + "ignore", + "remove", + ]); + } +} + +class Present extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "present", /* hasChildren = */ true); + this.behaviorOverride = null; + this.cache = null; + this.common = null; + this.copies = null; + this.destination = null; + this.incrementalMerge = null; + this.layout = null; + this.output = null; + this.overprint = null; + this.pagination = null; + this.paginationOverride = null; + this.script = null; + this.validate = null; + this.xdp = null; + this.driver = new XFAObjectArray(); + this.labelPrinter = new XFAObjectArray(); + this.pcl = new XFAObjectArray(); + this.pdf = new XFAObjectArray(); + this.ps = new XFAObjectArray(); + this.submitUrl = new XFAObjectArray(); + this.webClient = new XFAObjectArray(); + this.zpl = new XFAObjectArray(); + } +} + +class Print extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "print"); + } +} + +class PrintHighQuality extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "printHighQuality"); + } +} + +class PrintScaling extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "printScaling", ["appdefault", "noScaling"]); + } +} + +class PrinterName extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "printerName"); + } +} + +class Producer extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "producer"); + } +} + +class Ps extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "ps", /* hasChildren = */ true); + this.name = attributes.name || ""; + this.batchOutput = null; + this.fontInfo = null; + this.jog = null; + this.mediumInfo = null; + this.outputBin = null; + this.staple = null; + this.xdc = null; + } +} + +class Range extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "range"); + } + + [$finalize]() { + this[$content] = this[$content] + .trim() + .split(/\s*,\s*/, 2) + .map(range => range.split("-").map(x => parseInt(x.trim(), 10))) + .filter(range => range.every(x => !isNaN(x))) + .map(range => { + if (range.length === 1) { + range.push(range[0]); + } + return range; + }); + } +} + +class Record extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "record"); + } + + [$finalize]() { + this[$content] = this[$content].trim(); + const n = parseInt(this[$content], 10); + if (!isNaN(n) && n >= 0) { + this[$content] = n; + } + } +} + +class Relevant extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "relevant"); + } + + [$finalize]() { + this[$content] = this[$content].trim().split(/\s+/); + } +} + +class Rename extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "rename"); + } + + [$finalize]() { + this[$content] = this[$content].trim(); + // String must be a XFA name: same as XML one except that there + // is no colon. + if ( + this[$content].toLowerCase().startsWith("xml") || + new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*", "u").test(this[$content]) + ) { + warn("XFA - Rename: invalid XFA name"); + } + } +} + +class RenderPolicy extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "renderPolicy", ["server", "client"]); + } +} + +class RunScripts extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "runScripts", ["both", "client", "none", "server"]); + } +} + +class Script extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "script", /* hasChildren = */ true); + this.currentPage = null; + this.exclude = null; + this.runScripts = null; + } +} + +class ScriptModel extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "scriptModel", ["XFA", "none"]); + } +} + +class Severity extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "severity", [ + "ignore", + "error", + "information", + "trace", + "warning", + ]); + } +} + +class SilentPrint extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "silentPrint", /* hasChildren = */ true); + this.addSilentPrint = null; + this.printerName = null; + } +} + +class Staple extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "staple"); + this.mode = getStringOption(attributes.mode, [ + "usePrinterSetting", + "on", + "off", + ]); + } +} + +class StartNode extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "startNode"); + } +} + +class StartPage extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "startPage", 0, n => true); + } +} + +class SubmitFormat extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "submitFormat", [ + "html", + "delegate", + "fdf", + "xml", + "pdf", + ]); + } +} + +class SubmitUrl extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "submitUrl"); + } +} + +class SubsetBelow extends IntegerObject { + constructor(attributes) { + super(CONFIG_NS_ID, "subsetBelow", 100, n => n >= 0 && n <= 100); + } +} + +class SuppressBanner extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "suppressBanner"); + } +} + +class Tagged extends Option01 { + constructor(attributes) { + super(CONFIG_NS_ID, "tagged"); + } +} + +class Template extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "template", /* hasChildren = */ true); + this.base = null; + this.relevant = null; + this.startPage = null; + this.uri = null; + this.xsl = null; + } +} + +class Threshold extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "threshold", [ + "trace", + "error", + "information", + "warning", + ]); + } +} + +class To extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "to", [ + "null", + "memory", + "stderr", + "stdout", + "system", + "uri", + ]); + } +} + +class TemplateCache extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "templateCache"); + this.maxEntries = getInteger({ + data: attributes.maxEntries, + defaultValue: 5, + validate: n => n >= 0, + }); + } +} + +class Trace extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "trace", /* hasChildren = */ true); + this.area = new XFAObjectArray(); + } +} + +class Transform extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "transform", /* hasChildren = */ true); + this.groupParent = null; + this.ifEmpty = null; + this.nameAttr = null; + this.picture = null; + this.presence = null; + this.rename = null; + this.whitespace = null; + } +} + +class Type extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "type", [ + "none", + "ascii85", + "asciiHex", + "ccittfax", + "flate", + "lzw", + "runLength", + "native", + "xdp", + "mergedXDP", + ]); + } +} + +class Uri extends StringObject { + constructor(attributes) { + super(CONFIG_NS_ID, "uri"); + } +} + +class Validate extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "validate", [ + "preSubmit", + "prePrint", + "preExecute", + "preSave", + ]); + } +} + +class ValidateApprovalSignatures extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "validateApprovalSignatures"); + } + + [$finalize]() { + this[$content] = this[$content] + .trim() + .split(/\s+/) + .filter(x => ["docReady", "postSign"].includes(x)); + } +} + +class ValidationMessaging extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "validationMessaging", [ + "allMessagesIndividually", + "allMessagesTogether", + "firstMessageOnly", + "noMessages", + ]); + } +} + +class Version extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "version", ["1.7", "1.6", "1.5", "1.4", "1.3", "1.2"]); + } +} + +class VersionControl extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "VersionControl"); + this.outputBelow = getStringOption(attributes.outputBelow, [ + "warn", + "error", + "update", + ]); + this.sourceAbove = getStringOption(attributes.sourceAbove, [ + "warn", + "error", + ]); + this.sourceBelow = getStringOption(attributes.sourceBelow, [ + "update", + "maintain", + ]); + } +} + +class ViewerPreferences extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "viewerPreferences", /* hasChildren = */ true); + this.ADBE_JSConsole = null; + this.ADBE_JSDebugger = null; + this.addViewerPreferences = null; + this.duplexOption = null; + this.enforce = null; + this.numberOfCopies = null; + this.pageRange = null; + this.pickTrayByPDFSize = null; + this.printScaling = null; + } +} + +class WebClient extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "webClient", /* hasChildren = */ true); + this.name = attributes.name ? attributes.name.trim() : ""; + this.fontInfo = null; + this.xdc = null; + } +} + +class Whitespace extends OptionObject { + constructor(attributes) { + super(CONFIG_NS_ID, "whitespace", [ + "preserve", + "ltrim", + "normalize", + "rtrim", + "trim", + ]); + } +} + +class Window extends ContentObject { + constructor(attributes) { + super(CONFIG_NS_ID, "window"); + } + + [$finalize]() { + const pair = this[$content] + .trim() + .split(/\s*,\s*/, 2) + .map(x => parseInt(x, 10)); + if (pair.some(x => isNaN(x))) { + this[$content] = [0, 0]; + return; + } + if (pair.length === 1) { + pair.push(pair[0]); + } + this[$content] = pair; + } +} + +class Xdc extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "xdc", /* hasChildren = */ true); + this.uri = new XFAObjectArray(); + this.xsl = new XFAObjectArray(); + } +} + +class Xdp extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "xdp", /* hasChildren = */ true); + this.packets = null; + } +} + +class Xsl extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "xsl", /* hasChildren = */ true); + this.debug = null; + this.uri = null; + } +} + +class Zpl extends XFAObject { + constructor(attributes) { + super(CONFIG_NS_ID, "zpl", /* hasChildren = */ true); + this.name = attributes.name ? attributes.name.trim() : ""; + this.batchOutput = null; + this.flipLabel = null; + this.fontInfo = null; + this.xdc = null; + } +} + +class ConfigNamespace { + static [$buildXFAObject](name, attributes) { + if (ConfigNamespace.hasOwnProperty(name)) { + return ConfigNamespace[name](attributes); + } + return undefined; + } + + static acrobat(attrs) { + return new Acrobat(attrs); + } + + static acrobat7(attrs) { + return new Acrobat7(attrs); + } + + static ADBE_JSConsole(attrs) { + return new ADBE_JSConsole(attrs); + } + + static ADBE_JSDebugger(attrs) { + return new ADBE_JSDebugger(attrs); + } + + static addSilentPrint(attrs) { + return new AddSilentPrint(attrs); + } + + static addViewerPreferences(attrs) { + return new AddViewerPreferences(attrs); + } + + static adjustData(attrs) { + return new AdjustData(attrs); + } + + static adobeExtensionLevel(attrs) { + return new AdobeExtensionLevel(attrs); + } + + static agent(attrs) { + return new Agent(attrs); + } + + static alwaysEmbed(attrs) { + return new AlwaysEmbed(attrs); + } + + static amd(attrs) { + return new Amd(attrs); + } + + static area(attrs) { + return new Area(attrs); + } + + static attributes(attrs) { + return new Attributes(attrs); + } + + static autoSave(attrs) { + return new AutoSave(attrs); + } + + static base(attrs) { + return new Base(attrs); + } + + static batchOutput(attrs) { + return new BatchOutput(attrs); + } + + static behaviorOverride(attrs) { + return new BehaviorOverride(attrs); + } + + static cache(attrs) { + return new Cache(attrs); + } + + static change(attrs) { + return new Change(attrs); + } + + static common(attrs) { + return new Common(attrs); + } + + static compress(attrs) { + return new Compress(attrs); + } + + static compressLogicalStructure(attrs) { + return new CompressLogicalStructure(attrs); + } + + static compressObjectStream(attrs) { + return new CompressObjectStream(attrs); + } + + static compression(attrs) { + return new Compression(attrs); + } + + static config(attrs) { + return new Config(attrs); + } + + static conformance(attrs) { + return new Conformance(attrs); + } + + static contentCopy(attrs) { + return new ContentCopy(attrs); + } + + static copies(attrs) { + return new Copies(attrs); + } + + static creator(attrs) { + return new Creator(attrs); + } + + static currentPage(attrs) { + return new CurrentPage(attrs); + } + + static data(attrs) { + return new Data(attrs); + } + + static debug(attrs) { + return new Debug(attrs); + } + + static defaultTypeface(attrs) { + return new DefaultTypeface(attrs); + } + + static destination(attrs) { + return new Destination(attrs); + } + + static documentAssembly(attrs) { + return new DocumentAssembly(attrs); + } + + static driver(attrs) { + return new Driver(attrs); + } + + static duplexOption(attrs) { + return new DuplexOption(attrs); + } + + static dynamicRender(attrs) { + return new DynamicRender(attrs); + } + + static embed(attrs) { + return new Embed(attrs); + } + + static encrypt(attrs) { + return new Encrypt(attrs); + } + + static encryption(attrs) { + return new Encryption(attrs); + } + + static encryptionLevel(attrs) { + return new EncryptionLevel(attrs); + } + + static enforce(attrs) { + return new Enforce(attrs); + } + + static equate(attrs) { + return new Equate(attrs); + } + + static equateRange(attrs) { + return new EquateRange(attrs); + } + + static exclude(attrs) { + return new Exclude(attrs); + } + + static excludeNS(attrs) { + return new ExcludeNS(attrs); + } + + static flipLabel(attrs) { + return new FlipLabel(attrs); + } + + static fontInfo(attrs) { + return new FontInfo(attrs); + } + + static formFieldFilling(attrs) { + return new FormFieldFilling(attrs); + } + + static groupParent(attrs) { + return new GroupParent(attrs); + } + + static ifEmpty(attrs) { + return new IfEmpty(attrs); + } + + static includeXDPContent(attrs) { + return new IncludeXDPContent(attrs); + } + + static incrementalLoad(attrs) { + return new IncrementalLoad(attrs); + } + + static incrementalMerge(attrs) { + return new IncrementalMerge(attrs); + } + + static interactive(attrs) { + return new Interactive(attrs); + } + + static jog(attrs) { + return new Jog(attrs); + } + + static labelPrinter(attrs) { + return new LabelPrinter(attrs); + } + + static layout(attrs) { + return new Layout(attrs); + } + + static level(attrs) { + return new Level(attrs); + } + + static linearized(attrs) { + return new Linearized(attrs); + } + + static locale(attrs) { + return new Locale(attrs); + } + + static localeSet(attrs) { + return new LocaleSet(attrs); + } + + static log(attrs) { + return new Log(attrs); + } + + static map(attrs) { + return new MapElement(attrs); + } + + static mediumInfo(attrs) { + return new MediumInfo(attrs); + } + + static message(attrs) { + return new Message(attrs); + } + + static messaging(attrs) { + return new Messaging(attrs); + } + + static mode(attrs) { + return new Mode(attrs); + } + + static modifyAnnots(attrs) { + return new ModifyAnnots(attrs); + } + + static msgId(attrs) { + return new MsgId(attrs); + } + + static nameAttr(attrs) { + return new NameAttr(attrs); + } + + static neverEmbed(attrs) { + return new NeverEmbed(attrs); + } + + static numberOfCopies(attrs) { + return new NumberOfCopies(attrs); + } + + static openAction(attrs) { + return new OpenAction(attrs); + } + + static output(attrs) { + return new Output(attrs); + } + + static outputBin(attrs) { + return new OutputBin(attrs); + } + + static outputXSL(attrs) { + return new OutputXSL(attrs); + } + + static overprint(attrs) { + return new Overprint(attrs); + } + + static packets(attrs) { + return new Packets(attrs); + } + + static pageOffset(attrs) { + return new PageOffset(attrs); + } + + static pageRange(attrs) { + return new PageRange(attrs); + } + + static pagination(attrs) { + return new Pagination(attrs); + } + + static paginationOverride(attrs) { + return new PaginationOverride(attrs); + } + + static part(attrs) { + return new Part(attrs); + } + + static pcl(attrs) { + return new Pcl(attrs); + } + + static pdf(attrs) { + return new Pdf(attrs); + } + + static pdfa(attrs) { + return new Pdfa(attrs); + } + + static permissions(attrs) { + return new Permissions(attrs); + } + + static pickTrayByPDFSize(attrs) { + return new PickTrayByPDFSize(attrs); + } + + static picture(attrs) { + return new Picture(attrs); + } + + static plaintextMetadata(attrs) { + return new PlaintextMetadata(attrs); + } + + static presence(attrs) { + return new Presence(attrs); + } + + static present(attrs) { + return new Present(attrs); + } + + static print(attrs) { + return new Print(attrs); + } + + static printHighQuality(attrs) { + return new PrintHighQuality(attrs); + } + + static printScaling(attrs) { + return new PrintScaling(attrs); + } + + static printerName(attrs) { + return new PrinterName(attrs); + } + + static producer(attrs) { + return new Producer(attrs); + } + + static ps(attrs) { + return new Ps(attrs); + } + + static range(attrs) { + return new Range(attrs); + } + + static record(attrs) { + return new Record(attrs); + } + + static relevant(attrs) { + return new Relevant(attrs); + } + + static rename(attrs) { + return new Rename(attrs); + } + + static renderPolicy(attrs) { + return new RenderPolicy(attrs); + } + + static runScripts(attrs) { + return new RunScripts(attrs); + } + + static script(attrs) { + return new Script(attrs); + } + + static scriptModel(attrs) { + return new ScriptModel(attrs); + } + + static severity(attrs) { + return new Severity(attrs); + } + + static silentPrint(attrs) { + return new SilentPrint(attrs); + } + + static staple(attrs) { + return new Staple(attrs); + } + + static startNode(attrs) { + return new StartNode(attrs); + } + + static startPage(attrs) { + return new StartPage(attrs); + } + + static submitFormat(attrs) { + return new SubmitFormat(attrs); + } + + static submitUrl(attrs) { + return new SubmitUrl(attrs); + } + + static subsetBelow(attrs) { + return new SubsetBelow(attrs); + } + + static suppressBanner(attrs) { + return new SuppressBanner(attrs); + } + + static tagged(attrs) { + return new Tagged(attrs); + } + + static template(attrs) { + return new Template(attrs); + } + + static templateCache(attrs) { + return new TemplateCache(attrs); + } + + static threshold(attrs) { + return new Threshold(attrs); + } + + static to(attrs) { + return new To(attrs); + } + + static trace(attrs) { + return new Trace(attrs); + } + + static transform(attrs) { + return new Transform(attrs); + } + + static type(attrs) { + return new Type(attrs); + } + + static uri(attrs) { + return new Uri(attrs); + } + + static validate(attrs) { + return new Validate(attrs); + } + + static validateApprovalSignatures(attrs) { + return new ValidateApprovalSignatures(attrs); + } + + static validationMessaging(attrs) { + return new ValidationMessaging(attrs); + } + + static version(attrs) { + return new Version(attrs); + } + + static versionControl(attrs) { + return new VersionControl(attrs); + } + + static viewerPreferences(attrs) { + return new ViewerPreferences(attrs); + } + + static webClient(attrs) { + return new WebClient(attrs); + } + + static whitespace(attrs) { + return new Whitespace(attrs); + } + + static window(attrs) { + return new Window(attrs); + } + + static xdc(attrs) { + return new Xdc(attrs); + } + + static xdp(attrs) { + return new Xdp(attrs); + } + + static xsl(attrs) { + return new Xsl(attrs); + } + + static zpl(attrs) { + return new Zpl(attrs); + } +} + +export { ConfigNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/connection_set.js b/frontend/public/pdf.js/src/core/xfa/connection_set.js new file mode 100644 index 0000000..7e14c5f --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/connection_set.js @@ -0,0 +1,202 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { StringObject, XFAObject, XFAObjectArray } from "./xfa_object.js"; + +const CONNECTION_SET_NS_ID = NamespaceIds.connectionSet.id; + +class ConnectionSet extends XFAObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "connectionSet", /* hasChildren = */ true); + this.wsdlConnection = new XFAObjectArray(); + this.xmlConnection = new XFAObjectArray(); + this.xsdConnection = new XFAObjectArray(); + } +} + +class EffectiveInputPolicy extends XFAObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "effectiveInputPolicy"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class EffectiveOutputPolicy extends XFAObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "effectiveOutputPolicy"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Operation extends StringObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "operation"); + this.id = attributes.id || ""; + this.input = attributes.input || ""; + this.name = attributes.name || ""; + this.output = attributes.output || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class RootElement extends StringObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "rootElement"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class SoapAction extends StringObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "soapAction"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class SoapAddress extends StringObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "soapAddress"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Uri extends StringObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "uri"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class WsdlAddress extends StringObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "wsdlAddress"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class WsdlConnection extends XFAObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "wsdlConnection", /* hasChildren = */ true); + this.dataDescription = attributes.dataDescription || ""; + this.name = attributes.name || ""; + this.effectiveInputPolicy = null; + this.effectiveOutputPolicy = null; + this.operation = null; + this.soapAction = null; + this.soapAddress = null; + this.wsdlAddress = null; + } +} + +class XmlConnection extends XFAObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "xmlConnection", /* hasChildren = */ true); + this.dataDescription = attributes.dataDescription || ""; + this.name = attributes.name || ""; + this.uri = null; + } +} + +class XsdConnection extends XFAObject { + constructor(attributes) { + super(CONNECTION_SET_NS_ID, "xsdConnection", /* hasChildren = */ true); + this.dataDescription = attributes.dataDescription || ""; + this.name = attributes.name || ""; + this.rootElement = null; + this.uri = null; + } +} + +class ConnectionSetNamespace { + static [$buildXFAObject](name, attributes) { + if (ConnectionSetNamespace.hasOwnProperty(name)) { + return ConnectionSetNamespace[name](attributes); + } + return undefined; + } + + static connectionSet(attrs) { + return new ConnectionSet(attrs); + } + + static effectiveInputPolicy(attrs) { + return new EffectiveInputPolicy(attrs); + } + + static effectiveOutputPolicy(attrs) { + return new EffectiveOutputPolicy(attrs); + } + + static operation(attrs) { + return new Operation(attrs); + } + + static rootElement(attrs) { + return new RootElement(attrs); + } + + static soapAction(attrs) { + return new SoapAction(attrs); + } + + static soapAddress(attrs) { + return new SoapAddress(attrs); + } + + static uri(attrs) { + return new Uri(attrs); + } + + static wsdlAddress(attrs) { + return new WsdlAddress(attrs); + } + + static wsdlConnection(attrs) { + return new WsdlConnection(attrs); + } + + static xmlConnection(attrs) { + return new XmlConnection(attrs); + } + + static xsdConnection(attrs) { + return new XsdConnection(attrs); + } +} + +export { ConnectionSetNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/data.js b/frontend/public/pdf.js/src/core/xfa/data.js new file mode 100644 index 0000000..7cfb6da --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/data.js @@ -0,0 +1,82 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $getAttributes, + $getChildren, + $nodeName, + $setValue, + $toString, + $uid, +} from "./symbol_utils.js"; + +class DataHandler { + constructor(root, data) { + this.data = data; + this.dataset = root.datasets || null; + } + + serialize(storage) { + const stack = [[-1, this.data[$getChildren]()]]; + + while (stack.length > 0) { + const last = stack.at(-1); + const [i, children] = last; + if (i + 1 === children.length) { + stack.pop(); + continue; + } + + const child = children[++last[0]]; + const storageEntry = storage.get(child[$uid]); + if (storageEntry) { + child[$setValue](storageEntry); + } else { + const attributes = child[$getAttributes](); + for (const value of attributes.values()) { + const entry = storage.get(value[$uid]); + if (entry) { + value[$setValue](entry); + break; + } + } + } + + const nodes = child[$getChildren](); + if (nodes.length > 0) { + stack.push([-1, nodes]); + } + } + + const buf = [ + ``, + ]; + if (this.dataset) { + // Dump nodes other than data: they can contains for example + // some data for choice lists. + for (const child of this.dataset[$getChildren]()) { + if (child[$nodeName] !== "data") { + child[$toString](buf); + } + } + } + this.data[$toString](buf); + buf.push(""); + + return buf.join(""); + } +} + +export { DataHandler }; diff --git a/frontend/public/pdf.js/src/core/xfa/datasets.js b/frontend/public/pdf.js/src/core/xfa/datasets.js new file mode 100644 index 0000000..644c92e --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/datasets.js @@ -0,0 +1,75 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $appendChild, + $isNsAgnostic, + $namespaceId, + $nodeName, + $onChild, +} from "./symbol_utils.js"; +import { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { XFAObject, XmlObject } from "./xfa_object.js"; + +const DATASETS_NS_ID = NamespaceIds.datasets.id; + +class Data extends XmlObject { + constructor(attributes) { + super(DATASETS_NS_ID, "data", attributes); + } + + [$isNsAgnostic]() { + return true; + } +} + +class Datasets extends XFAObject { + constructor(attributes) { + super(DATASETS_NS_ID, "datasets", /* hasChildren = */ true); + this.data = null; + this.Signature = null; + } + + [$onChild](child) { + const name = child[$nodeName]; + if ( + (name === "data" && child[$namespaceId] === DATASETS_NS_ID) || + (name === "Signature" && + child[$namespaceId] === NamespaceIds.signature.id) + ) { + this[name] = child; + } + this[$appendChild](child); + } +} + +class DatasetsNamespace { + static [$buildXFAObject](name, attributes) { + if (DatasetsNamespace.hasOwnProperty(name)) { + return DatasetsNamespace[name](attributes); + } + return undefined; + } + + static datasets(attributes) { + return new Datasets(attributes); + } + + static data(attributes) { + return new Data(attributes); + } +} + +export { DatasetsNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/factory.js b/frontend/public/pdf.js/src/core/xfa/factory.js new file mode 100644 index 0000000..7f9f4f2 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/factory.js @@ -0,0 +1,179 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $appendChild, + $globalData, + $nodeName, + $text, + $toHTML, + $toPages, +} from "./symbol_utils.js"; +import { Binder } from "./bind.js"; +import { DataHandler } from "./data.js"; +import { FontFinder } from "./fonts.js"; +import { stripQuotes } from "./utils.js"; +import { warn } from "../../shared/util.js"; +import { XFAParser } from "./parser.js"; +import { XhtmlNamespace } from "./xhtml.js"; + +class XFAFactory { + constructor(data) { + try { + this.root = new XFAParser().parse(XFAFactory._createDocument(data)); + const binder = new Binder(this.root); + this.form = binder.bind(); + this.dataHandler = new DataHandler(this.root, binder.getData()); + this.form[$globalData].template = this.form; + } catch (e) { + warn(`XFA - an error occurred during parsing and binding: ${e}`); + } + } + + isValid() { + return this.root && this.form; + } + + /** + * In order to avoid to block the event loop, the conversion + * into pages is made asynchronously. + */ + _createPagesHelper() { + const iterator = this.form[$toPages](); + return new Promise((resolve, reject) => { + const nextIteration = () => { + try { + const value = iterator.next(); + if (value.done) { + resolve(value.value); + } else { + setTimeout(nextIteration, 0); + } + } catch (e) { + reject(e); + } + }; + setTimeout(nextIteration, 0); + }); + } + + async _createPages() { + try { + this.pages = await this._createPagesHelper(); + this.dims = this.pages.children.map(c => { + const { width, height } = c.attributes.style; + return [0, 0, parseInt(width), parseInt(height)]; + }); + } catch (e) { + warn(`XFA - an error occurred during layout: ${e}`); + } + } + + getBoundingBox(pageIndex) { + return this.dims[pageIndex]; + } + + async getNumPages() { + if (!this.pages) { + await this._createPages(); + } + return this.dims.length; + } + + setImages(images) { + this.form[$globalData].images = images; + } + + setFonts(fonts) { + this.form[$globalData].fontFinder = new FontFinder(fonts); + const missingFonts = []; + for (let typeface of this.form[$globalData].usedTypefaces) { + typeface = stripQuotes(typeface); + const font = this.form[$globalData].fontFinder.find(typeface); + if (!font) { + missingFonts.push(typeface); + } + } + + if (missingFonts.length > 0) { + return missingFonts; + } + + return null; + } + + appendFonts(fonts, reallyMissingFonts) { + this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts); + } + + async getPages() { + if (!this.pages) { + await this._createPages(); + } + const pages = this.pages; + this.pages = null; + return pages; + } + + serializeData(storage) { + return this.dataHandler.serialize(storage); + } + + static _createDocument(data) { + if (!data["/xdp:xdp"]) { + return data["xdp:xdp"]; + } + return Object.values(data).join(""); + } + + static getRichTextAsHtml(rc) { + if (!rc || typeof rc !== "string") { + return null; + } + + try { + let root = new XFAParser(XhtmlNamespace, /* richText */ true).parse(rc); + if (!["body", "xhtml"].includes(root[$nodeName])) { + // No body, so create one. + const newRoot = XhtmlNamespace.body({}); + newRoot[$appendChild](root); + root = newRoot; + } + + const result = root[$toHTML](); + if (!result.success) { + return null; + } + + const { html } = result; + const { attributes } = html; + if (attributes) { + if (attributes.class) { + attributes.class = attributes.class.filter( + attr => !attr.startsWith("xfa") + ); + } + attributes.dir = "auto"; + } + + return { html, str: root[$text]() }; + } catch (e) { + warn(`XFA - an error occurred during parsing of rich text: ${e}`); + } + return null; + } +} + +export { XFAFactory }; diff --git a/frontend/public/pdf.js/src/core/xfa/fonts.js b/frontend/public/pdf.js/src/core/xfa/fonts.js new file mode 100644 index 0000000..844e236 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/fonts.js @@ -0,0 +1,206 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $globalData } from "./symbol_utils.js"; +import { stripQuotes } from "./utils.js"; +import { warn } from "../../shared/util.js"; + +class FontFinder { + constructor(pdfFonts) { + this.fonts = new Map(); + this.cache = new Map(); + this.warned = new Set(); + this.defaultFont = null; + this.add(pdfFonts); + } + + add(pdfFonts, reallyMissingFonts = null) { + for (const pdfFont of pdfFonts) { + this.addPdfFont(pdfFont); + } + for (const pdfFont of this.fonts.values()) { + if (!pdfFont.regular) { + pdfFont.regular = pdfFont.italic || pdfFont.bold || pdfFont.bolditalic; + } + } + + if (!reallyMissingFonts || reallyMissingFonts.size === 0) { + return; + } + const myriad = this.fonts.get("PdfJS-Fallback-PdfJS-XFA"); + for (const missing of reallyMissingFonts) { + this.fonts.set(missing, myriad); + } + } + + addPdfFont(pdfFont) { + const cssFontInfo = pdfFont.cssFontInfo; + const name = cssFontInfo.fontFamily; + let font = this.fonts.get(name); + if (!font) { + font = Object.create(null); + this.fonts.set(name, font); + if (!this.defaultFont) { + this.defaultFont = font; + } + } + let property = ""; + const fontWeight = parseFloat(cssFontInfo.fontWeight); + if (parseFloat(cssFontInfo.italicAngle) !== 0) { + property = fontWeight >= 700 ? "bolditalic" : "italic"; + } else if (fontWeight >= 700) { + property = "bold"; + } + + if (!property) { + if (pdfFont.name.includes("Bold") || pdfFont.psName?.includes("Bold")) { + property = "bold"; + } + if ( + pdfFont.name.includes("Italic") || + pdfFont.name.endsWith("It") || + pdfFont.psName?.includes("Italic") || + pdfFont.psName?.endsWith("It") + ) { + property += "italic"; + } + } + + if (!property) { + property = "regular"; + } + + font[property] = pdfFont; + } + + getDefault() { + return this.defaultFont; + } + + find(fontName, mustWarn = true) { + let font = this.fonts.get(fontName) || this.cache.get(fontName); + if (font) { + return font; + } + + const pattern = /,|-|_| |bolditalic|bold|italic|regular|it/gi; + let name = fontName.replaceAll(pattern, ""); + font = this.fonts.get(name); + if (font) { + this.cache.set(fontName, font); + return font; + } + name = name.toLowerCase(); + + const maybe = []; + for (const [family, pdfFont] of this.fonts.entries()) { + if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) { + maybe.push(pdfFont); + } + } + + if (maybe.length === 0) { + for (const [, pdfFont] of this.fonts.entries()) { + if ( + pdfFont.regular.name + ?.replaceAll(pattern, "") + .toLowerCase() + .startsWith(name) + ) { + maybe.push(pdfFont); + } + } + } + + if (maybe.length === 0) { + name = name.replaceAll(/psmt|mt/gi, ""); + for (const [family, pdfFont] of this.fonts.entries()) { + if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) { + maybe.push(pdfFont); + } + } + } + + if (maybe.length === 0) { + for (const pdfFont of this.fonts.values()) { + if ( + pdfFont.regular.name + ?.replaceAll(pattern, "") + .toLowerCase() + .startsWith(name) + ) { + maybe.push(pdfFont); + } + } + } + + if (maybe.length >= 1) { + if (maybe.length !== 1 && mustWarn) { + warn(`XFA - Too many choices to guess the correct font: ${fontName}`); + } + this.cache.set(fontName, maybe[0]); + return maybe[0]; + } + + if (mustWarn && !this.warned.has(fontName)) { + this.warned.add(fontName); + warn(`XFA - Cannot find the font: ${fontName}`); + } + return null; + } +} + +function selectFont(xfaFont, typeface) { + if (xfaFont.posture === "italic") { + if (xfaFont.weight === "bold") { + return typeface.bolditalic; + } + return typeface.italic; + } else if (xfaFont.weight === "bold") { + return typeface.bold; + } + + return typeface.regular; +} + +function getMetrics(xfaFont, real = false) { + let pdfFont = null; + if (xfaFont) { + const name = stripQuotes(xfaFont.typeface); + const typeface = xfaFont[$globalData].fontFinder.find(name); + pdfFont = selectFont(xfaFont, typeface); + } + + if (!pdfFont) { + return { + lineHeight: 12, + lineGap: 2, + lineNoGap: 10, + }; + } + + const size = xfaFont.size || 10; + const lineHeight = pdfFont.lineHeight + ? Math.max(real ? 0 : 1.2, pdfFont.lineHeight) + : 1.2; + const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap; + return { + lineHeight: lineHeight * size, + lineGap: lineGap * size, + lineNoGap: Math.max(1, lineHeight - lineGap) * size, + }; +} + +export { FontFinder, getMetrics, selectFont }; diff --git a/frontend/public/pdf.js/src/core/xfa/formcalc_lexer.js b/frontend/public/pdf.js/src/core/xfa/formcalc_lexer.js new file mode 100644 index 0000000..d6a4eaf --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/formcalc_lexer.js @@ -0,0 +1,385 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +const KEYWORDS = new Set([ + "and", + "break", + "continue", + "do", + "downto", + "else", + "elseif", + "end", + "endfor", + "endfunc", + "endif", + "endwhile", + "eq", + "exit", + "for", + "foreach", + "func", + "ge", + "gt", + "if", + "in", + "infinity", + "le", + "lt", + "nan", + "ne", + "not", + "null", + "or", + "return", + "step", + "then", + "this", + "throw", + "upto", + "var", + "while", +]); + +const TOKEN = { + /* Appears in expression */ + and: 0, + divide: 1, + dot: 2, + dotDot: 3, + dotHash: 4, + dotStar: 5, + eq: 6, + ge: 7, + gt: 8, + le: 9, + leftBracket: 10, + leftParen: 11, + lt: 12, + minus: 13, + ne: 14, + not: 15, + null: 16, + number: 17, + or: 18, + plus: 19, + rightBracket: 20, + rightParen: 21, + string: 22, + this: 23, + times: 24, + identifier: 25, // in main statements too + + /* Main statements */ + break: 26, + continue: 27, + do: 28, + for: 29, + foreach: 30, + func: 31, + if: 32, + var: 33, + while: 34, + + /* Others */ + assign: 35, + comma: 36, + downto: 37, + else: 38, + elseif: 39, + end: 40, + endif: 41, + endfor: 42, + endfunc: 43, + endwhile: 44, + eof: 45, + exit: 46, + in: 47, + infinity: 48, + nan: 49, + return: 50, + step: 51, + then: 52, + throw: 53, + upto: 54, +}; + +const hexPattern = /^[uU]([0-9a-fA-F]{4,8})/; +const numberPattern = /^\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?/; +const dotNumberPattern = /^\d*(?:[Ee][+-]?\d+)?/; +const eolPattern = /[\r\n]+/; +const identifierPattern = new RegExp("^[\\p{L}_$!][\\p{L}\\p{N}_$]*", "u"); + +class Token { + constructor(id, value = null) { + this.id = id; + this.value = value; + } +} + +const Singletons = (function () { + const obj = Object.create(null); + const nonSingleton = new Set([ + "identifier", + "string", + "number", + "nan", + "infinity", + ]); + for (const [name, id] of Object.entries(TOKEN)) { + if (!nonSingleton.has(name)) { + obj[name] = new Token(id); + } + } + obj.nan = new Token(TOKEN.number, NaN); + obj.infinity = new Token(TOKEN.number, Infinity); + + return obj; +})(); + +class Lexer { + constructor(data) { + this.data = data; + this.pos = 0; + this.len = data.length; + this.strBuf = []; + } + + skipUntilEOL() { + const match = this.data.slice(this.pos).match(eolPattern); + if (match) { + this.pos += match.index + match[0].length; + } else { + // No eol so consume all the chars. + this.pos = this.len; + } + } + + getIdentifier() { + this.pos--; + const match = this.data.slice(this.pos).match(identifierPattern); + if (!match) { + throw new Error( + `Invalid token in FormCalc expression at position ${this.pos}.` + ); + } + + const identifier = this.data.slice(this.pos, this.pos + match[0].length); + this.pos += match[0].length; + + const lower = identifier.toLowerCase(); + if (!KEYWORDS.has(lower)) { + return new Token(TOKEN.identifier, identifier); + } + + return Singletons[lower]; + } + + getString() { + const str = this.strBuf; + const data = this.data; + let start = this.pos; + while (this.pos < this.len) { + const char = data.charCodeAt(this.pos++); + if (char === 0x22 /* = " */) { + if (data.charCodeAt(this.pos) === 0x22 /* = " */) { + // Escaped quote. + str.push(data.slice(start, this.pos++)); + start = this.pos; + continue; + } + // End of string + break; + } + + if (char === 0x5c /* = \ */) { + const match = data.substring(this.pos, this.pos + 10).match(hexPattern); + if (!match) { + continue; + } + + str.push(data.slice(start, this.pos - 1)); + const code = match[1]; + if (code.length === 4) { + str.push(String.fromCharCode(parseInt(code, 16))); + start = this.pos += 5; + } else if (code.length !== 8) { + str.push(String.fromCharCode(parseInt(code.slice(0, 4), 16))); + start = this.pos += 5; + } else { + str.push(String.fromCharCode(parseInt(code, 16))); + start = this.pos += 9; + } + } + } + + const lastChunk = data.slice(start, this.pos - 1); + if (str.length === 0) { + return new Token(TOKEN.string, lastChunk); + } + + str.push(lastChunk); + const string = str.join(""); + str.length = 0; + + return new Token(TOKEN.string, string); + } + + getNumber(first) { + const match = this.data.substring(this.pos).match(numberPattern); + if (!match[0]) { + return new Token(TOKEN.number, first - 0x30 /* = 0 */); + } + const number = parseFloat( + this.data.substring(this.pos - 1, this.pos + match[0].length) + ); + + this.pos += match[0].length; + return new Token(TOKEN.number, number); + } + + getCompOperator(alt1, alt2) { + if (this.data.charCodeAt(this.pos) === 0x3d /* = = */) { + this.pos++; + return alt1; + } + return alt2; + } + + getLower() { + const char = this.data.charCodeAt(this.pos); + if (char === 0x3d /* = = */) { + this.pos++; + return Singletons.le; + } + + if (char === 0x3e /* = > */) { + this.pos++; + return Singletons.ne; + } + + return Singletons.lt; + } + + getSlash() { + if (this.data.charCodeAt(this.pos) === 0x2f /* = / */) { + this.skipUntilEOL(); + return false; + } + return true; + } + + getDot() { + const char = this.data.charCodeAt(this.pos); + if (char === 0x2e /* = . */) { + this.pos++; + return Singletons.dotDot; + } + + if (char === 0x2a /* = * */) { + this.pos++; + return Singletons.dotStar; + } + + if (char === 0x23 /* = # */) { + this.pos++; + return Singletons.dotHash; + } + + if (0x30 /* = 0 */ <= char && char <= 0x39 /* = 9 */) { + this.pos++; + const match = this.data.substring(this.pos).match(dotNumberPattern); + if (!match) { + return new Token(TOKEN.number, (char - 0x30) /* = 0 */ / 10); + } + const end = this.pos + match[0].length; + const number = parseFloat(this.data.substring(this.pos - 2, end)); + this.pos = end; + return new Token(TOKEN.number, number); + } + + return Singletons.dot; + } + + next() { + while (this.pos < this.len) { + const char = this.data.charCodeAt(this.pos++); + switch (char) { + case 0x09 /* = \t */: + case 0x0a /* = \n */: + case 0x0b /* = \v */: + case 0x0c /* = \f */: + case 0x0d /* = \r */: + case 0x20 /* = */: + break; + case 0x22 /* = " */: + return this.getString(); + case 0x26 /* = & */: + return Singletons.and; + case 0x28 /* = ( */: + return Singletons.leftParen; + case 0x29 /* = ) */: + return Singletons.rightParen; + case 0x2a /* = * */: + return Singletons.times; + case 0x2b /* = + */: + return Singletons.plus; + case 0x2c /* = , */: + return Singletons.comma; + case 0x2d /* = - */: + return Singletons.minus; + case 0x2e /* = . */: + return this.getDot(); + case 0x2f /* = / */: + if (this.getSlash()) { + return Singletons.divide; + } + // It was a comment. + break; + case 0x30 /* = 0 */: + case 0x31 /* = 1 */: + case 0x32 /* = 2 */: + case 0x33 /* = 3 */: + case 0x34 /* = 4 */: + case 0x35 /* = 5 */: + case 0x36 /* = 6 */: + case 0x37 /* = 7 */: + case 0x38 /* = 8 */: + case 0x39 /* = 9 */: + return this.getNumber(char); + case 0x3b /* = ; */: + this.skipUntilEOL(); + break; + case 0x3c /* = < */: + return this.getLower(); + case 0x3d /* = = */: + return this.getCompOperator(Singletons.eq, Singletons.assign); + case 0x3e /* = > */: + return this.getCompOperator(Singletons.ge, Singletons.gt); + case 0x5b /* = [ */: + return Singletons.leftBracket; + case 0x5d /* = ] */: + return Singletons.rightBracket; + case 0x7c /* = | */: + return Singletons.or; + default: + return this.getIdentifier(); + } + } + return Singletons.eof; + } +} + +export { Lexer, Token, TOKEN }; diff --git a/frontend/public/pdf.js/src/core/xfa/formcalc_parser.js b/frontend/public/pdf.js/src/core/xfa/formcalc_parser.js new file mode 100644 index 0000000..dfbc1cc --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/formcalc_parser.js @@ -0,0 +1,1340 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { Lexer, TOKEN } from "./formcalc_lexer.js"; + +const Errors = { + assignment: "Invalid token in assignment.", + block: "Invalid token in do ... end declaration.", + elseif: "Invalid elseif declaration.", + for: "Invalid token in for ... endfor declaration.", + foreach: "Invalid token in foreach ... endfor declaration.", + func: "Invalid token in func declaration.", + if: "Invalid token if ... endif declaration.", + index: "Invalid token in index.", + params: "Invalid token in parameter list.", + var: "Invalid token in var declaration.", + while: "Invalid token while ... endwhile declaration.", +}; + +const BUILTINS = new Set([ + // Arithmetic. + "abs", + "avg", + "ceil", + "count", + "floor", + "max", + "min", + "mod", + "round", + "sum", + // Date and time. + "date", + "date2num", + "datefmt", + "isodate2num", + "isotime2num", + "localdatefmt", + "localtimefmt", + "num2date", + "num2gmtime", + "num2time", + "time", + "time2num", + "timefmt", + // Financial. + "apr", + "cterm", + "fv", + "ipmt", + "npv", + "pmt", + "ppmt", + "pv", + "rate", + "term", + // Logical. + "choose", + "exists", + "hasvalue", + "oneof", + "within", + // String. + "at", + "concat", + "decode", + "encode", + "format", + "left", + "len", + "lower", + "ltrim", + "parse", + "replace", + "right", + "rtrim", + "space", + "str", + "stuff", + "substr", + "uuid", + "upper", + "wordnum", + // Url. + "get", + "post", + "put", + // Miscellaneous. + "eval", + "ref", + "unitvalue", + "unittype", + // Undocumented. + "acos", + "asin", + "atan", + "cos", + "deg2rad", + "exp", + "log", + "pi", + "pow", + "rad2deg", + "sin", + "sqrt", + "tan", +]); + +const LTR = true; +const RTL = false; + +const Operators = { + dot: { id: 0, prec: 0, assoc: RTL, nargs: 0, repr: "." }, + dotDot: { id: 1, prec: 0, assoc: RTL, nargs: 0, repr: ".." }, + dotHash: { id: 2, prec: 0, assoc: RTL, nargs: 0, repr: ".#" }, + + call: { id: 1, prec: 1, assoc: LTR, nargs: 0 }, + + // Unary operators. + minus: { id: 4, nargs: 1, prec: 2, assoc: RTL, repr: "-", op: x => -x }, + plus: { id: 5, nargs: 1, prec: 2, assoc: RTL, repr: "+", op: x => +x }, + not: { + id: 6, + nargs: 1, + prec: 2, + assoc: RTL, + repr: "!", + op: x => (!x ? 1 : 0), + }, + + mul: { id: 7, nargs: 2, prec: 3, assoc: LTR, repr: "*", op: (x, y) => x * y }, + div: { id: 8, nargs: 2, prec: 3, assoc: LTR, repr: "/", op: (x, y) => x / y }, + + add: { id: 9, nargs: 2, prec: 4, assoc: LTR, repr: "+", op: (x, y) => x + y }, + sub: { + id: 10, + nargs: 2, + prec: 4, + assoc: LTR, + repr: "-", + op: (x, y) => x - y, + }, + + lt: { + id: 11, + nargs: 2, + prec: 5, + assoc: LTR, + repr: "<", + op: (x, y) => (x < y ? 1 : 0), + }, + le: { + id: 12, + nargs: 2, + prec: 5, + assoc: LTR, + repr: "<=", + op: (x, y) => (x <= y ? 1 : 0), + }, + gt: { + id: 13, + nargs: 2, + prec: 5, + assoc: LTR, + repr: ">", + op: (x, y) => (x > y ? 1 : 0), + }, + ge: { + id: 14, + nargs: 2, + prec: 5, + assoc: LTR, + repr: ">=", + op: (x, y) => (x >= y ? 1 : 0), + }, + + eq: { + id: 15, + nargs: 2, + prec: 6, + assoc: LTR, + repr: "===", + op: (x, y) => (x === y ? 1 : 0), + }, + ne: { + id: 16, + nargs: 2, + prec: 6, + assoc: LTR, + repr: "!==", + op: (x, y) => (x !== y ? 1 : 0), + }, + + and: { + id: 17, + nargs: 2, + prec: 7, + assoc: LTR, + repr: "&&", + op: (x, y) => (x && y ? 1 : 0), + }, + + or: { + id: 18, + nargs: 2, + prec: 8, + assoc: LTR, + repr: "||", + op: (x, y) => (x || y ? 1 : 0), + }, + + // Not real operators. + paren: { id: 19, prec: 9, assoc: RTL, nargs: 0 }, + subscript: { id: 20, prec: 9, assoc: RTL, nargs: 0 }, +}; + +const OPERATOR = true; +const OPERAND = false; + +// How it works... +// +// There is two stacks: one for operands and one for operators. +// Each time an operand is met (number, identifier, ...), +// it's pushed on operands stack. +// Unary operators such as + or - are guessed according to the last pushed +// thing: +// for example, if an operand has been push then a '-' is a subtraction +// but if an operator has been push (e.g. '*') then a '-' is the negate +// operation ('... * - ...' can't be a subtraction). +// Each time an operator is met its precedence is compared with the one of the +// operator on top of operators stack: +// - if top has precendence on operator then top is applied to the operands +// on their stack; +// - else just push the operator. +// For example: 1 + 2 * 3 +// round 1: operands: [1], operators: [] +// round 2: operands: [1], operators: [+] +// round 3: operands: [1, 2], operators: [+] +// +// + has not the precedence on * +// round 4: operands: [1, 2], operators: [+, *] +// round 5: operands: [1, 2, 3], operators: [+, *] +// no more token: apply operators on operands: +// round 6: operands: [1, 6], operators: [+] +// round 7: operands: [7], operators: [] +// Parenthesis are treated like an operator with no precedence on the real ones. +// As a consequence, any operation is done before this fake one and when +// a right parenthesis is met then we can apply operators to operands +// until the opening parenthesis is met. +// +class SimpleExprParser { + constructor(lexer) { + this.lexer = lexer; + this.operands = []; + this.operators = []; + this.last = OPERATOR; + } + + reset() { + this.operands.length = 0; + this.operators.length = 0; + this.last = OPERATOR; + } + + parse(tok) { + tok ||= this.lexer.next(); + + while (true) { + // Token ids (see form_lexer.js) are consecutive in order + // to have switch table with no holes. + switch (tok.id) { + case TOKEN.and: + if (this.last === OPERAND) { + this.pushOperator(Operators.and); + break; + } + return [tok, this.getNode()]; + case TOKEN.divide: + if (this.last === OPERAND) { + this.pushOperator(Operators.div); + break; + } + return [tok, this.getNode()]; + case TOKEN.dot: + if (this.last === OPERAND) { + this.pushOperator(Operators.dot); + break; + } + return [tok, this.getNode()]; + case TOKEN.dotDot: + if (this.last === OPERAND) { + this.pushOperator(Operators.dotDot); + break; + } + return [tok, this.getNode()]; + case TOKEN.dotHash: + if (this.last === OPERAND) { + this.pushOperator(Operators.dotHash); + break; + } + return [tok, this.getNode()]; + case TOKEN.dotStar: + if (this.last === OPERAND) { + this.pushOperator(Operators.dot); + this.pushOperand(new AstEveryOccurence()); + break; + } + return [tok, this.getNode()]; + case TOKEN.eq: + if (this.last === OPERAND) { + this.pushOperator(Operators.eq); + break; + } + return [tok, this.getNode()]; + case TOKEN.ge: + if (this.last === OPERAND) { + this.pushOperator(Operators.ge); + break; + } + return [tok, this.getNode()]; + case TOKEN.gt: + if (this.last === OPERAND) { + this.pushOperator(Operators.gt); + break; + } + return [tok, this.getNode()]; + case TOKEN.le: + if (this.last === OPERAND) { + this.pushOperator(Operators.le); + break; + } + return [tok, this.getNode()]; + case TOKEN.leftBracket: + if (this.last === OPERAND) { + this.flushWithOperator(Operators.subscript); + const operand = this.operands.pop(); + const index = SimpleExprParser.parseIndex(this.lexer); + this.operands.push(new AstSubscript(operand, index)); + this.last = OPERAND; + break; + } + return [tok, this.getNode()]; + case TOKEN.leftParen: + if (this.last === OPERAND) { + const lastOperand = this.operands.at(-1); + if (!(lastOperand instanceof AstIdentifier)) { + return [tok, this.getNode()]; + } + lastOperand.toLowerCase(); + const name = lastOperand.id; + + this.flushWithOperator(Operators.call); + const callee = this.operands.pop(); + const params = SimpleExprParser.parseParams(this.lexer); + + if (callee instanceof AstIdentifier && BUILTINS.has(name)) { + this.operands.push(new AstBuiltinCall(name, params)); + } else { + this.operands.push(new AstCall(callee, params)); + } + + this.last = OPERAND; + } else { + this.operators.push(Operators.paren); + this.last = OPERATOR; + } + break; + case TOKEN.lt: + if (this.last === OPERAND) { + this.pushOperator(Operators.lt); + break; + } + return [tok, this.getNode()]; + case TOKEN.minus: + if (this.last === OPERATOR) { + this.pushOperator(Operators.minus); + } else { + this.pushOperator(Operators.sub); + } + break; + case TOKEN.ne: + if (this.last === OPERAND) { + this.pushOperator(Operators.ne); + break; + } + return [tok, this.getNode()]; + case TOKEN.not: + if (this.last === OPERAND) { + this.pushOperator(Operators.not); + break; + } + return [tok, this.getNode()]; + case TOKEN.null: + if (this.last === OPERATOR) { + this.pushOperand(new AstNull()); + break; + } + return [tok, this.getNode()]; + case TOKEN.number: + if (this.last === OPERATOR) { + this.pushOperand(new AstNumber(tok.value)); + break; + } + return [tok, this.getNode()]; + case TOKEN.or: + if (this.last === OPERAND) { + this.pushOperator(Operators.or); + break; + } + return [tok, this.getNode()]; + case TOKEN.plus: + if (this.last === OPERATOR) { + this.pushOperator(Operators.plus); + } else { + this.pushOperator(Operators.add); + } + break; + case TOKEN.rightBracket: + if (!this.flushUntil(Operators.subscript.id)) { + return [tok, this.getNode()]; + } + break; + case TOKEN.rightParen: + if (!this.flushUntil(Operators.paren.id)) { + return [tok, this.getNode()]; + } + break; + case TOKEN.string: + if (this.last === OPERATOR) { + this.pushOperand(new AstString(tok.value)); + break; + } + return [tok, this.getNode()]; + case TOKEN.this: + if (this.last === OPERATOR) { + this.pushOperand(new AstThis()); + break; + } + return [tok, this.getNode()]; + case TOKEN.times: + if (this.last === OPERAND) { + this.pushOperator(Operators.mul); + break; + } + return [tok, this.getNode()]; + case TOKEN.identifier: + if (this.last === OPERATOR) { + this.pushOperand(new AstIdentifier(tok.value)); + break; + } + return [tok, this.getNode()]; + default: + return [tok, this.getNode()]; + } + tok = this.lexer.next(); + } + } + + static parseParams(lexer) { + const parser = new SimpleExprParser(lexer); + const params = []; + while (true) { + const [tok, param] = parser.parse(); + if (param) { + params.push(param); + } + if (tok.id === TOKEN.rightParen) { + return params; + } else if (tok.id !== TOKEN.comma) { + throw new Error(Errors.params); + } + parser.reset(); + } + } + + static parseIndex(lexer) { + let tok = lexer.next(); + if (tok.id === TOKEN.times) { + tok = lexer.next(); + if (tok.id !== TOKEN.rightBracket) { + throw new Error(Errors.index); + } + return new AstEveryOccurence(); + } + const [token, expr] = new SimpleExprParser(lexer).parse(tok); + if (token.id !== TOKEN.rightBracket) { + throw new Error(Errors.index); + } + return expr; + } + + pushOperator(op) { + this.flushWithOperator(op); + this.operators.push(op); + this.last = OPERATOR; + } + + pushOperand(op) { + this.operands.push(op); + this.last = OPERAND; + } + + operate(op) { + if (op.nargs === 1) { + const arg = this.operands.pop(); + this.operands.push(AstUnaryOperator.getOperatorOrValue(op, arg)); + } else { + const arg2 = this.operands.pop(); + const arg1 = this.operands.pop(); + this.operands.push(AstBinaryOperator.getOperatorOrValue(op, arg1, arg2)); + } + } + + flushWithOperator(op) { + while (true) { + const top = this.operators.at(-1); + if (top) { + if (top.id >= 0 && SimpleExprParser.checkPrecedence(top, op)) { + this.operators.pop(); + this.operate(top); + continue; + } + } + return; + } + } + + flush() { + while (true) { + const op = this.operators.pop(); + if (!op) { + return; + } + this.operate(op); + } + } + + flushUntil(id) { + while (true) { + const op = this.operators.pop(); + if (!op) { + return false; + } + if (op.id === id) { + return true; + } + this.operate(op); + } + } + + getNode() { + this.flush(); + return this.operands.pop(); + } + + static checkPrecedence(left, right) { + return ( + left.prec < right.prec || (left.prec === right.prec && left.assoc === LTR) + ); + } +} + +class Leaf { + dump() { + throw new Error("Not implemented method"); + } + + isSomPredicate() { + return false; + } + + isDotExpression() { + return false; + } + + isConstant() { + return false; + } + + toNumber() { + return 0; + } + + toComparable() { + return null; + } +} + +class AstCall extends Leaf { + constructor(callee, params) { + super(); + this.callee = callee; + this.params = params; + } + + dump() { + return { + callee: this.callee.dump(), + params: this.params.map(x => x.dump()), + }; + } +} + +class AstBuiltinCall extends Leaf { + constructor(id, params) { + super(); + this.id = id; + this.params = params; + } + + dump() { + return { + builtin: this.id, + params: this.params.map(x => x.dump()), + }; + } +} + +class AstSubscript extends Leaf { + constructor(operand, index) { + super(); + this.operand = operand; + this.index = index; + } + + dump() { + return { + operand: this.operand.dump(), + index: this.index.dump(), + }; + } +} + +class AstBinaryOperator extends Leaf { + constructor(id, left, right, repr) { + super(); + this.id = id; + this.left = left; + this.right = right; + this.repr = repr; + } + + dump() { + return { + operator: this.repr, + left: this.left.dump(), + right: this.right.dump(), + }; + } + + isDotExpression() { + return Operators.dot.id <= this.id && this.id <= Operators.dotHash.id; + } + + isSomPredicate() { + return ( + this.isDotExpression() || + (Operators.lt.id <= this.id && + this.id <= Operators.or.id && + ((this.left.isDotExpression() && this.right.isConstant()) || + (this.left.isConstant() && this.right.isDotExpression()) || + (this.left.isDotExpression() && this.right.isDotExpression()))) + ); + } + + static getOperatorOrValue(operator, left, right) { + if (!left.isConstant() || !right.isConstant()) { + return new AstBinaryOperator(operator.id, left, right, operator.repr); + } + + if ( + Operators.lt.id <= operator.id && + operator.id <= Operators.ne.id && + !(left instanceof AstNumber) && + !(right instanceof AstNumber) + ) { + return new AstNumber( + operator.op(left.toComparable(), right.toComparable()) + ); + } + + return new AstNumber(operator.op(left.toNumber(), right.toNumber())); + } +} + +class AstUnaryOperator extends Leaf { + constructor(id, arg, repr) { + super(); + this.id = id; + this.arg = arg; + this.repr = repr; + } + + dump() { + return { + operator: this.repr, + arg: this.arg.dump(), + }; + } + + static getOperatorOrValue(operator, arg) { + if (!arg.isConstant()) { + return new AstUnaryOperator(operator.id, arg, operator.repr); + } + + return new AstNumber(operator.op(arg.toNumber())); + } +} + +class AstNumber extends Leaf { + constructor(number) { + super(); + this.number = number; + } + + dump() { + return this.number; + } + + isConstant() { + return true; + } + + toNumber() { + return this.number; + } +} + +class AstString extends Leaf { + constructor(str) { + super(); + this.str = str; + } + + dump() { + return this.str; + } + + isConstant() { + return true; + } + + toNumber() { + return !isNaN(this.str) ? parseFloat(this.str) : 0; + } + + toComparable() { + return this.str; + } +} + +class AstThis extends Leaf { + dump() { + return { special: "this" }; + } +} + +class AstIdentifier extends Leaf { + constructor(id) { + super(); + this.id = id; + } + + dump() { + return { id: this.id }; + } + + toLowerCase() { + this.id = this.id.toLowerCase(); + } +} + +class AstNull extends Leaf { + dump() { + return { special: null }; + } + + isConstant() { + return true; + } + + toComparable() { + return null; + } +} + +class AstEveryOccurence { + dump() { + return { special: "*" }; + } +} + +class VarDecl extends Leaf { + constructor(id, expr) { + super(); + this.id = id; + this.expr = expr; + } + + dump() { + return { + var: this.id, + expr: this.expr.dump(), + }; + } +} + +class Assignment extends Leaf { + constructor(id, expr) { + super(); + this.id = id; + this.expr = expr; + } + + dump() { + return { + assignment: this.id, + expr: this.expr.dump(), + }; + } +} + +class FuncDecl extends Leaf { + constructor(id, params, body) { + super(); + this.id = id; + this.params = params; + this.body = body; + } + + dump() { + return { + func: this.id, + params: this.params, + body: this.body.dump(), + }; + } +} + +class IfDecl extends Leaf { + constructor(condition, thenClause, elseIfClause, elseClause) { + super(); + this.condition = condition; + this.then = thenClause; + this.elseif = elseIfClause; + this.else = elseClause; + } + + dump() { + return { + decl: "if", + condition: this.condition.dump(), + then: this.then.dump(), + elseif: this.elseif ? this.elseif.map(x => x.dump()) : null, + else: this.else ? this.else.dump() : null, + }; + } +} + +class ElseIfDecl extends Leaf { + constructor(condition, thenClause) { + super(); + this.condition = condition; + this.then = thenClause; + } + + dump() { + return { + decl: "elseif", + condition: this.condition.dump(), + then: this.then.dump(), + }; + } +} + +class WhileDecl extends Leaf { + constructor(condition, whileClause) { + super(); + this.condition = condition; + this.body = whileClause; + } + + dump() { + return { + decl: "while", + condition: this.condition.dump(), + body: this.body.dump(), + }; + } +} + +class ForDecl extends Leaf { + constructor(assignment, upto, end, step, body) { + super(); + this.assignment = assignment; + this.upto = upto; + this.end = end; + this.step = step; + this.body = body; + } + + dump() { + return { + decl: "for", + assignment: this.assignment.dump(), + type: this.upto ? "upto" : "downto", + end: this.end.dump(), + step: this.step ? this.step.dump() : null, + body: this.body.dump(), + }; + } +} + +class ForeachDecl extends Leaf { + constructor(id, params, body) { + super(); + this.id = id; + this.params = params; + this.body = body; + } + + dump() { + return { + decl: "foreach", + id: this.id, + params: this.params.map(x => x.dump()), + body: this.body.dump(), + }; + } +} + +class BlockDecl extends Leaf { + constructor(body) { + super(); + this.body = body; + } + + dump() { + return { + decl: "block", + body: this.body.dump(), + }; + } +} + +class ExprList extends Leaf { + constructor(expressions) { + super(); + this.expressions = expressions; + } + + dump() { + return this.expressions.map(x => x.dump()); + } +} + +class BreakDecl extends Leaf { + dump() { + return { special: "break" }; + } +} + +class ContinueDecl extends Leaf { + dump() { + return { special: "continue" }; + } +} + +class Parser { + constructor(code) { + this.lexer = new Lexer(code); + } + + parse() { + const [tok, decls] = this.parseExprList(); + if (tok.id !== TOKEN.eof) { + throw new Error("Invalid token in Form code"); + } + return decls; + } + + parseExprList() { + const expressions = []; + let tok = null, + expr; + while (true) { + [tok, expr] = this.parseExpr(tok); + if (!expr) { + return [tok, new ExprList(expressions)]; + } + expressions.push(expr); + } + } + + parseExpr(tok) { + tok ||= this.lexer.next(); + switch (tok.id) { + case TOKEN.identifier: + return this.parseAssigmentOrExpr(tok); + case TOKEN.break: + return [null, new BreakDecl()]; + case TOKEN.continue: + return [null, new ContinueDecl()]; + case TOKEN.do: + return this.parseBlock(); + case TOKEN.for: + return this.parseFor(); + case TOKEN.foreach: + return this.parseForeach(); + case TOKEN.func: + return this.parseFuncDecl(); + case TOKEN.if: + return this.parseIf(); + case TOKEN.var: + return this.parseVarDecl(); + case TOKEN.while: + return this.parseWhile(); + default: + return this.parseSimpleExpr(tok); + } + } + + parseAssigmentOrExpr(tok) { + const savedTok = tok; + + tok = this.lexer.next(); + if (tok.id === TOKEN.assign) { + const [tok1, expr] = this.parseSimpleExpr(null); + return [tok1, new Assignment(savedTok.value, expr)]; + } + + const parser = new SimpleExprParser(this.lexer); + parser.pushOperand(new AstIdentifier(savedTok.value)); + + return parser.parse(tok); + } + + parseBlock() { + const [tok1, body] = this.parseExprList(); + + const tok = tok1 || this.lexer.next(); + if (tok.id !== TOKEN.end) { + throw new Error(Errors.block); + } + + return [null, new BlockDecl(body)]; + } + + parseVarDecl() { + // 'var' Identifier ('=' SimpleExpression)? + let tok = this.lexer.next(); + if (tok.id !== TOKEN.identifier) { + throw new Error(Errors.var); + } + + const identifier = tok.value; + + tok = this.lexer.next(); + if (tok.id !== TOKEN.assign) { + return [tok, new VarDecl(identifier, null)]; + } + + const [tok1, expr] = this.parseSimpleExpr(); + return [tok1, new VarDecl(identifier, expr)]; + } + + parseFuncDecl() { + // 'func' Identifier ParameterList 'do' ExpressionList 'endfunc'. + let tok = this.lexer.next(); + if (tok.id !== TOKEN.identifier) { + throw new Error(Errors.func); + } + + const identifier = tok.value; + const params = this.parseParamList(); + + tok = this.lexer.next(); + if (tok.id !== TOKEN.do) { + throw new Error(Errors.func); + } + + const [tok1, body] = this.parseExprList(); + + tok = tok1 || this.lexer.next(); + if (tok.id !== TOKEN.endfunc) { + throw new Error(Errors.func); + } + + return [null, new FuncDecl(identifier, params, body)]; + } + + parseParamList() { + // '(' Identifier * ')'. + const params = []; + + let tok = this.lexer.next(); + if (tok.id !== TOKEN.leftParen) { + throw new Error(Errors.func); + } + + tok = this.lexer.next(); + if (tok.id === TOKEN.rightParen) { + return params; + } + + while (true) { + if (tok.id !== TOKEN.identifier) { + throw new Error(Errors.func); + } + params.push(tok.value); + tok = this.lexer.next(); + if (tok.id === TOKEN.rightParen) { + return params; + } + if (tok.id !== TOKEN.comma) { + throw new Error(Errors.func); + } + tok = this.lexer.next(); + } + } + + parseSimpleExpr(tok = null) { + return new SimpleExprParser(this.lexer).parse(tok); + } + + parseIf() { + // 'if' '(' SimpleExpression ')' then ExpressionList + // ('elseif' '(' SimpleExpression ')' then ExpressionList )* + // ('else' ExpressionList)? + // 'endif'. + let elseIfClause = []; + let tok = this.lexer.next(); + if (tok.id !== TOKEN.leftParen) { + throw new Error(Errors.if); + } + + const [tok1, condition] = this.parseSimpleExpr(); + + tok = tok1 || this.lexer.next(); + if (tok.id !== TOKEN.rightParen) { + throw new Error(Errors.if); + } + + tok = this.lexer.next(); + if (tok.id !== TOKEN.then) { + throw new Error(Errors.if); + } + + const [tok2, thenClause] = this.parseExprList(); + tok = tok2 || this.lexer.next(); + + while (tok.id === TOKEN.elseif) { + tok = this.lexer.next(); + if (tok.id !== TOKEN.leftParen) { + throw new Error(Errors.elseif); + } + + const [tok3, elseIfCondition] = this.parseSimpleExpr(); + + tok = tok3 || this.lexer.next(); + if (tok.id !== TOKEN.rightParen) { + throw new Error(Errors.elseif); + } + + tok = this.lexer.next(); + if (tok.id !== TOKEN.then) { + throw new Error(Errors.elseif); + } + + const [tok4, elseIfThenClause] = this.parseExprList(); + elseIfClause.push(new ElseIfDecl(elseIfCondition, elseIfThenClause)); + + tok = tok4 || this.lexer.next(); + } + + if (elseIfClause.length === 0) { + elseIfClause = null; + } + + if (tok.id === TOKEN.endif) { + return [null, new IfDecl(condition, thenClause, elseIfClause, null)]; + } + + if (tok.id !== TOKEN.else) { + throw new Error(Errors.if); + } + + const [tok5, elseClause] = this.parseExprList(); + + tok = tok5 || this.lexer.next(); + if (tok.id !== TOKEN.endif) { + throw new Error(Errors.if); + } + + return [null, new IfDecl(condition, thenClause, elseIfClause, elseClause)]; + } + + parseWhile() { + // 'while' '(' SimpleExpression ')' 'do' ExprList 'endwhile' + let tok = this.lexer.next(); + if (tok.id !== TOKEN.leftParen) { + throw new Error(Errors.while); + } + + const [tok1, condition] = this.parseSimpleExpr(); + + tok = tok1 || this.lexer.next(); + if (tok.id !== TOKEN.rightParen) { + throw new Error(Errors.while); + } + + tok = this.lexer.next(); + if (tok.id !== TOKEN.do) { + throw new Error(Errors.while); + } + + const [tok2, whileClause] = this.parseExprList(); + + tok = tok2 || this.lexer.next(); + if (tok.id !== TOKEN.endwhile) { + throw new Error(Errors.while); + } + + return [null, new WhileDecl(condition, whileClause)]; + } + + parseAssignment() { + let tok = this.lexer.next(); + let hasVar = false; + if (tok.id === TOKEN.var) { + hasVar = true; + tok = this.lexer.next(); + } + + if (tok.id !== TOKEN.identifier) { + throw new Error(Errors.assignment); + } + + const identifier = tok.value; + + tok = this.lexer.next(); + if (tok.id !== TOKEN.assign) { + throw new Error(Errors.assignment); + } + + const [tok1, expr] = this.parseSimpleExpr(); + if (hasVar) { + return [tok1, new VarDecl(identifier, expr)]; + } + return [tok1, new Assignment(identifier, expr)]; + } + + parseFor() { + // 'for' Assignment ('upto'|'downto') Expr ('step' Expr)? 'do' + // ExprList 'endfor' + let tok, + step = null; + let upto = false; + const [tok1, assignment] = this.parseAssignment(); + + tok = tok1 || this.lexer.next(); + if (tok.id === TOKEN.upto) { + upto = true; + } else if (tok.id !== TOKEN.downto) { + throw new Error(Errors.for); + } + + const [tok2, end] = this.parseSimpleExpr(); + + tok = tok2 || this.lexer.next(); + if (tok.id === TOKEN.step) { + [tok, step] = this.parseSimpleExpr(); + tok ||= this.lexer.next(); + } + + if (tok.id !== TOKEN.do) { + throw new Error(Errors.for); + } + + const [tok3, body] = this.parseExprList(); + + tok = tok3 || this.lexer.next(); + if (tok.id !== TOKEN.endfor) { + throw new Error(Errors.for); + } + + return [null, new ForDecl(assignment, upto, end, step, body)]; + } + + parseForeach() { + // 'for' Identifier 'in' '(' ArgumentList ')' 'do' + // ExprList 'endfor' + let tok = this.lexer.next(); + if (tok.id !== TOKEN.identifier) { + throw new Error(Errors.foreach); + } + + const identifier = tok.value; + + tok = this.lexer.next(); + if (tok.id !== TOKEN.in) { + throw new Error(Errors.foreach); + } + + tok = this.lexer.next(); + if (tok.id !== TOKEN.leftParen) { + throw new Error(Errors.foreach); + } + + const params = SimpleExprParser.parseParams(this.lexer); + + tok = this.lexer.next(); + if (tok.id !== TOKEN.do) { + throw new Error(Errors.foreach); + } + + const [tok1, body] = this.parseExprList(); + + tok = tok1 || this.lexer.next(); + if (tok.id !== TOKEN.endfor) { + throw new Error(Errors.foreach); + } + + return [null, new ForeachDecl(identifier, params, body)]; + } +} + +export { Errors, Parser }; diff --git a/frontend/public/pdf.js/src/core/xfa/html_utils.js b/frontend/public/pdf.js/src/core/xfa/html_utils.js new file mode 100644 index 0000000..71aaf83 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/html_utils.js @@ -0,0 +1,647 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $content, + $extra, + $getParent, + $getSubformParent, + $getTemplateRoot, + $globalData, + $nodeName, + $pushGlyphs, + $text, + $toStyle, +} from "./symbol_utils.js"; +import { createValidAbsoluteUrl, warn } from "../../shared/util.js"; +import { getMeasurement, stripQuotes } from "./utils.js"; +import { selectFont } from "./fonts.js"; +import { TextMeasure } from "./text.js"; +import { XFAObject } from "./xfa_object.js"; + +function measureToString(m) { + if (typeof m === "string") { + return "0px"; + } + + return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`; +} + +const converters = { + anchorType(node, style) { + const parent = node[$getSubformParent](); + if (!parent || (parent.layout && parent.layout !== "position")) { + // anchorType is only used in a positioned layout. + return; + } + + if (!("transform" in style)) { + style.transform = ""; + } + switch (node.anchorType) { + case "bottomCenter": + style.transform += "translate(-50%, -100%)"; + break; + case "bottomLeft": + style.transform += "translate(0,-100%)"; + break; + case "bottomRight": + style.transform += "translate(-100%,-100%)"; + break; + case "middleCenter": + style.transform += "translate(-50%,-50%)"; + break; + case "middleLeft": + style.transform += "translate(0,-50%)"; + break; + case "middleRight": + style.transform += "translate(-100%,-50%)"; + break; + case "topCenter": + style.transform += "translate(-50%,0)"; + break; + case "topRight": + style.transform += "translate(-100%,0)"; + break; + } + }, + dimensions(node, style) { + const parent = node[$getSubformParent](); + let width = node.w; + const height = node.h; + if (parent.layout?.includes("row")) { + const extra = parent[$extra]; + const colSpan = node.colSpan; + let w; + if (colSpan === -1) { + w = extra.columnWidths + .slice(extra.currentColumn) + .reduce((a, x) => a + x, 0); + extra.currentColumn = 0; + } else { + w = extra.columnWidths + .slice(extra.currentColumn, extra.currentColumn + colSpan) + .reduce((a, x) => a + x, 0); + extra.currentColumn = + (extra.currentColumn + node.colSpan) % extra.columnWidths.length; + } + + if (!isNaN(w)) { + width = node.w = w; + } + } + + style.width = width !== "" ? measureToString(width) : "auto"; + + style.height = height !== "" ? measureToString(height) : "auto"; + }, + position(node, style) { + const parent = node[$getSubformParent](); + if (parent?.layout && parent.layout !== "position") { + // IRL, we've some x/y in tb layout. + // Specs say x/y is only used in positioned layout. + return; + } + + style.position = "absolute"; + style.left = measureToString(node.x); + style.top = measureToString(node.y); + }, + rotate(node, style) { + if (node.rotate) { + if (!("transform" in style)) { + style.transform = ""; + } + style.transform += `rotate(-${node.rotate}deg)`; + style.transformOrigin = "top left"; + } + }, + presence(node, style) { + switch (node.presence) { + case "invisible": + style.visibility = "hidden"; + break; + case "hidden": + case "inactive": + style.display = "none"; + break; + } + }, + hAlign(node, style) { + if (node[$nodeName] === "para") { + switch (node.hAlign) { + case "justifyAll": + style.textAlign = "justify-all"; + break; + case "radix": + // TODO: implement this correctly ! + style.textAlign = "left"; + break; + default: + style.textAlign = node.hAlign; + } + } else { + switch (node.hAlign) { + case "left": + style.alignSelf = "start"; + break; + case "center": + style.alignSelf = "center"; + break; + case "right": + style.alignSelf = "end"; + break; + } + } + }, + margin(node, style) { + if (node.margin) { + style.margin = node.margin[$toStyle]().margin; + } + }, +}; + +function setMinMaxDimensions(node, style) { + const parent = node[$getSubformParent](); + if (parent.layout === "position") { + if (node.minW > 0) { + style.minWidth = measureToString(node.minW); + } + if (node.maxW > 0) { + style.maxWidth = measureToString(node.maxW); + } + if (node.minH > 0) { + style.minHeight = measureToString(node.minH); + } + if (node.maxH > 0) { + style.maxHeight = measureToString(node.maxH); + } + } +} + +function layoutText(text, xfaFont, margin, lineHeight, fontFinder, width) { + const measure = new TextMeasure(xfaFont, margin, lineHeight, fontFinder); + if (typeof text === "string") { + measure.addString(text); + } else { + text[$pushGlyphs](measure); + } + + return measure.compute(width); +} + +function layoutNode(node, availableSpace) { + let height = null; + let width = null; + let isBroken = false; + + if ((!node.w || !node.h) && node.value) { + let marginH = 0; + let marginV = 0; + if (node.margin) { + marginH = node.margin.leftInset + node.margin.rightInset; + marginV = node.margin.topInset + node.margin.bottomInset; + } + + let lineHeight = null; + let margin = null; + if (node.para) { + margin = Object.create(null); + lineHeight = node.para.lineHeight === "" ? null : node.para.lineHeight; + margin.top = node.para.spaceAbove === "" ? 0 : node.para.spaceAbove; + margin.bottom = node.para.spaceBelow === "" ? 0 : node.para.spaceBelow; + margin.left = node.para.marginLeft === "" ? 0 : node.para.marginLeft; + margin.right = node.para.marginRight === "" ? 0 : node.para.marginRight; + } + + let font = node.font; + if (!font) { + const root = node[$getTemplateRoot](); + let parent = node[$getParent](); + while (parent && parent !== root) { + if (parent.font) { + font = parent.font; + break; + } + parent = parent[$getParent](); + } + } + + const maxWidth = (node.w || availableSpace.width) - marginH; + const fontFinder = node[$globalData].fontFinder; + if ( + node.value.exData && + node.value.exData[$content] && + node.value.exData.contentType === "text/html" + ) { + const res = layoutText( + node.value.exData[$content], + font, + margin, + lineHeight, + fontFinder, + maxWidth + ); + width = res.width; + height = res.height; + isBroken = res.isBroken; + } else { + const text = node.value[$text](); + if (text) { + const res = layoutText( + text, + font, + margin, + lineHeight, + fontFinder, + maxWidth + ); + width = res.width; + height = res.height; + isBroken = res.isBroken; + } + } + + if (width !== null && !node.w) { + width += marginH; + } + + if (height !== null && !node.h) { + height += marginV; + } + } + return { w: width, h: height, isBroken }; +} + +function computeBbox(node, html, availableSpace) { + let bbox; + if (node.w !== "" && node.h !== "") { + bbox = [node.x, node.y, node.w, node.h]; + } else { + if (!availableSpace) { + return null; + } + let width = node.w; + if (width === "") { + if (node.maxW === 0) { + const parent = node[$getSubformParent](); + width = parent.layout === "position" && parent.w !== "" ? 0 : node.minW; + } else { + width = Math.min(node.maxW, availableSpace.width); + } + html.attributes.style.width = measureToString(width); + } + + let height = node.h; + if (height === "") { + if (node.maxH === 0) { + const parent = node[$getSubformParent](); + height = + parent.layout === "position" && parent.h !== "" ? 0 : node.minH; + } else { + height = Math.min(node.maxH, availableSpace.height); + } + html.attributes.style.height = measureToString(height); + } + + bbox = [node.x, node.y, width, height]; + } + return bbox; +} + +function fixDimensions(node) { + const parent = node[$getSubformParent](); + if (parent.layout?.includes("row")) { + const extra = parent[$extra]; + const colSpan = node.colSpan; + let width; + if (colSpan === -1) { + width = extra.columnWidths + .slice(extra.currentColumn) + .reduce((a, w) => a + w, 0); + } else { + width = extra.columnWidths + .slice(extra.currentColumn, extra.currentColumn + colSpan) + .reduce((a, w) => a + w, 0); + } + if (!isNaN(width)) { + node.w = width; + } + } + + if (parent.layout && parent.layout !== "position") { + // Useless in this context. + node.x = node.y = 0; + } + + if (node.layout === "table") { + if (node.w === "" && Array.isArray(node.columnWidths)) { + node.w = node.columnWidths.reduce((a, x) => a + x, 0); + } + } +} + +function layoutClass(node) { + switch (node.layout) { + case "position": + return "xfaPosition"; + case "lr-tb": + return "xfaLrTb"; + case "rl-row": + return "xfaRlRow"; + case "rl-tb": + return "xfaRlTb"; + case "row": + return "xfaRow"; + case "table": + return "xfaTable"; + case "tb": + return "xfaTb"; + default: + return "xfaPosition"; + } +} + +function toStyle(node, ...names) { + const style = Object.create(null); + for (const name of names) { + const value = node[name]; + if (value === null) { + continue; + } + if (converters.hasOwnProperty(name)) { + converters[name](node, style); + continue; + } + + if (value instanceof XFAObject) { + const newStyle = value[$toStyle](); + if (newStyle) { + Object.assign(style, newStyle); + } else { + warn(`(DEBUG) - XFA - style for ${name} not implemented yet`); + } + } + } + return style; +} + +function createWrapper(node, html) { + const { attributes } = html; + const { style } = attributes; + + const wrapper = { + name: "div", + attributes: { + class: ["xfaWrapper"], + style: Object.create(null), + }, + children: [], + }; + + attributes.class.push("xfaWrapped"); + + if (node.border) { + const { widths, insets } = node.border[$extra]; + let width, height; + let top = insets[0]; + let left = insets[3]; + const insetsH = insets[0] + insets[2]; + const insetsW = insets[1] + insets[3]; + switch (node.border.hand) { + case "even": + top -= widths[0] / 2; + left -= widths[3] / 2; + width = `calc(100% + ${(widths[1] + widths[3]) / 2 - insetsW}px)`; + height = `calc(100% + ${(widths[0] + widths[2]) / 2 - insetsH}px)`; + break; + case "left": + top -= widths[0]; + left -= widths[3]; + width = `calc(100% + ${widths[1] + widths[3] - insetsW}px)`; + height = `calc(100% + ${widths[0] + widths[2] - insetsH}px)`; + break; + case "right": + width = insetsW ? `calc(100% - ${insetsW}px)` : "100%"; + height = insetsH ? `calc(100% - ${insetsH}px)` : "100%"; + break; + } + const classNames = ["xfaBorder"]; + if (isPrintOnly(node.border)) { + classNames.push("xfaPrintOnly"); + } + + const border = { + name: "div", + attributes: { + class: classNames, + style: { + top: `${top}px`, + left: `${left}px`, + width, + height, + }, + }, + children: [], + }; + + for (const key of [ + "border", + "borderWidth", + "borderColor", + "borderRadius", + "borderStyle", + ]) { + if (style[key] !== undefined) { + border.attributes.style[key] = style[key]; + delete style[key]; + } + } + wrapper.children.push(border, html); + } else { + wrapper.children.push(html); + } + + for (const key of [ + "background", + "backgroundClip", + "top", + "left", + "width", + "height", + "minWidth", + "minHeight", + "maxWidth", + "maxHeight", + "transform", + "transformOrigin", + "visibility", + ]) { + if (style[key] !== undefined) { + wrapper.attributes.style[key] = style[key]; + delete style[key]; + } + } + + wrapper.attributes.style.position = + style.position === "absolute" ? "absolute" : "relative"; + delete style.position; + + if (style.alignSelf) { + wrapper.attributes.style.alignSelf = style.alignSelf; + delete style.alignSelf; + } + + return wrapper; +} + +function fixTextIndent(styles) { + const indent = getMeasurement(styles.textIndent, "0px"); + if (indent >= 0) { + return; + } + + // If indent is negative then it's a hanging indent. + const align = styles.textAlign === "right" ? "right" : "left"; + const name = "padding" + (align === "left" ? "Left" : "Right"); + const padding = getMeasurement(styles[name], "0px"); + styles[name] = `${padding - indent}px`; +} + +function setAccess(node, classNames) { + switch (node.access) { + case "nonInteractive": + classNames.push("xfaNonInteractive"); + break; + case "readOnly": + classNames.push("xfaReadOnly"); + break; + case "protected": + classNames.push("xfaDisabled"); + break; + } +} + +function isPrintOnly(node) { + return ( + node.relevant.length > 0 && + !node.relevant[0].excluded && + node.relevant[0].viewname === "print" + ); +} + +function getCurrentPara(node) { + const stack = node[$getTemplateRoot]()[$extra].paraStack; + return stack.length ? stack.at(-1) : null; +} + +function setPara(node, nodeStyle, value) { + if (value.attributes.class?.includes("xfaRich")) { + if (nodeStyle) { + if (node.h === "") { + nodeStyle.height = "auto"; + } + if (node.w === "") { + nodeStyle.width = "auto"; + } + } + + const para = getCurrentPara(node); + if (para) { + // By definition exData are external data so para + // has no effect on it. + const valueStyle = value.attributes.style; + valueStyle.display = "flex"; + valueStyle.flexDirection = "column"; + switch (para.vAlign) { + case "top": + valueStyle.justifyContent = "start"; + break; + case "bottom": + valueStyle.justifyContent = "end"; + break; + case "middle": + valueStyle.justifyContent = "center"; + break; + } + + const paraStyle = para[$toStyle](); + for (const [key, val] of Object.entries(paraStyle)) { + if (!(key in valueStyle)) { + valueStyle[key] = val; + } + } + } + } +} + +function setFontFamily(xfaFont, node, fontFinder, style) { + if (!fontFinder) { + // The font cannot be found in the pdf so use the default one. + delete style.fontFamily; + return; + } + + const name = stripQuotes(xfaFont.typeface); + style.fontFamily = `"${name}"`; + + const typeface = fontFinder.find(name); + if (typeface) { + const { fontFamily } = typeface.regular.cssFontInfo; + if (fontFamily !== name) { + style.fontFamily = `"${fontFamily}"`; + } + + const para = getCurrentPara(node); + if (para && para.lineHeight !== "") { + return; + } + + if (style.lineHeight) { + // Already something so don't overwrite. + return; + } + + const pdfFont = selectFont(xfaFont, typeface); + if (pdfFont) { + style.lineHeight = Math.max(1.2, pdfFont.lineHeight); + } + } +} + +function fixURL(str) { + const absoluteUrl = createValidAbsoluteUrl(str, /* baseUrl = */ null, { + addDefaultProtocol: true, + tryConvertEncoding: true, + }); + return absoluteUrl ? absoluteUrl.href : null; +} + +export { + computeBbox, + createWrapper, + fixDimensions, + fixTextIndent, + fixURL, + isPrintOnly, + layoutClass, + layoutNode, + measureToString, + setAccess, + setFontFamily, + setMinMaxDimensions, + setPara, + toStyle, +}; diff --git a/frontend/public/pdf.js/src/core/xfa/layout.js b/frontend/public/pdf.js/src/core/xfa/layout.js new file mode 100644 index 0000000..e3442a2 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/layout.js @@ -0,0 +1,386 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $extra, + $flushHTML, + $getSubformParent, + $getTemplateRoot, + $isSplittable, + $isThereMoreWidth, +} from "./symbol_utils.js"; +import { measureToString } from "./html_utils.js"; + +// Subform and ExclGroup have a layout so they share these functions. + +/** + * How layout works ? + * + * A container has an initial space (with a width and a height) to fit in, + * which means that once all the children have been added then + * the total width/height must be lower than the given ones in + * the initial space. + * So if the container has known dimensions and these ones are ok with the + * space then continue else we return HTMLResult.FAILURE: it's up to the + * parent to deal with this failure (e.g. if parent layout is lr-tb and + * we fail to add a child at end of line (lr) then we try to add it on the + * next line). + * And then we run through the children, each child gets its initial space + * in calling its parent $getAvailableSpace method + * (see _filteredChildrenGenerator and $childrenToHTML in xfa_object.js) + * then we try to layout child in its space. If everything is ok then we add + * the result to its parent through $addHTML which will recompute the available + * space in parent according to its layout property else we return + * HTMLResult.Failure. + * Before a failure some children may have been layed out: they've been saved in + * [$extra].children and [$extra] has properties generator and failingNode + * in order to save the state where we were before a failure. + * This [$extra].children property is useful when a container has to be splited. + * So if a container is unbreakable, we must delete its [$extra] property before + * returning. + */ + +function createLine(node, children) { + return { + name: "div", + attributes: { + class: [node.layout === "lr-tb" ? "xfaLr" : "xfaRl"], + }, + children, + }; +} + +function flushHTML(node) { + if (!node[$extra]) { + return null; + } + + const attributes = node[$extra].attributes; + const html = { + name: "div", + attributes, + children: node[$extra].children, + }; + + if (node[$extra].failingNode) { + const htmlFromFailing = node[$extra].failingNode[$flushHTML](); + if (htmlFromFailing) { + if (node.layout.endsWith("-tb")) { + html.children.push(createLine(node, [htmlFromFailing])); + } else { + html.children.push(htmlFromFailing); + } + } + } + + if (html.children.length === 0) { + return null; + } + + return html; +} + +function addHTML(node, html, bbox) { + const extra = node[$extra]; + const availableSpace = extra.availableSpace; + + const [x, y, w, h] = bbox; + switch (node.layout) { + case "position": { + extra.width = Math.max(extra.width, x + w); + extra.height = Math.max(extra.height, y + h); + extra.children.push(html); + break; + } + case "lr-tb": + case "rl-tb": + if (!extra.line || extra.attempt === 1) { + extra.line = createLine(node, []); + extra.children.push(extra.line); + extra.numberInLine = 0; + } + + extra.numberInLine += 1; + extra.line.children.push(html); + + if (extra.attempt === 0) { + // Add the element on the line + extra.currentWidth += w; + extra.height = Math.max(extra.height, extra.prevHeight + h); + } else { + extra.currentWidth = w; + extra.prevHeight = extra.height; + extra.height += h; + + // The element has been added on a new line so switch to line mode now. + extra.attempt = 0; + } + extra.width = Math.max(extra.width, extra.currentWidth); + break; + case "rl-row": + case "row": { + extra.children.push(html); + extra.width += w; + extra.height = Math.max(extra.height, h); + const height = measureToString(extra.height); + for (const child of extra.children) { + child.attributes.style.height = height; + } + break; + } + case "table": { + extra.width = Math.min(availableSpace.width, Math.max(extra.width, w)); + extra.height += h; + extra.children.push(html); + break; + } + case "tb": { + // Even if the subform can possibly take all the available width, + // we must compute the final width as it is in order to be able + // for example to center the subform within its parent. + extra.width = Math.min(availableSpace.width, Math.max(extra.width, w)); + extra.height += h; + extra.children.push(html); + break; + } + } +} + +function getAvailableSpace(node) { + const availableSpace = node[$extra].availableSpace; + const marginV = node.margin + ? node.margin.topInset + node.margin.bottomInset + : 0; + const marginH = node.margin + ? node.margin.leftInset + node.margin.rightInset + : 0; + + switch (node.layout) { + case "lr-tb": + case "rl-tb": + if (node[$extra].attempt === 0) { + return { + width: availableSpace.width - marginH - node[$extra].currentWidth, + height: availableSpace.height - marginV - node[$extra].prevHeight, + }; + } + return { + width: availableSpace.width - marginH, + height: availableSpace.height - marginV - node[$extra].height, + }; + case "rl-row": + case "row": + const width = node[$extra].columnWidths + .slice(node[$extra].currentColumn) + .reduce((a, x) => a + x); + return { width, height: availableSpace.height - marginH }; + case "table": + case "tb": + return { + width: availableSpace.width - marginH, + height: availableSpace.height - marginV - node[$extra].height, + }; + case "position": + default: + return availableSpace; + } +} + +function getTransformedBBox(node) { + // Take into account rotation and anchor to get the real bounding box. + let w = node.w === "" ? NaN : node.w; + let h = node.h === "" ? NaN : node.h; + let [centerX, centerY] = [0, 0]; + switch (node.anchorType || "") { + case "bottomCenter": + [centerX, centerY] = [w / 2, h]; + break; + case "bottomLeft": + [centerX, centerY] = [0, h]; + break; + case "bottomRight": + [centerX, centerY] = [w, h]; + break; + case "middleCenter": + [centerX, centerY] = [w / 2, h / 2]; + break; + case "middleLeft": + [centerX, centerY] = [0, h / 2]; + break; + case "middleRight": + [centerX, centerY] = [w, h / 2]; + break; + case "topCenter": + [centerX, centerY] = [w / 2, 0]; + break; + case "topRight": + [centerX, centerY] = [w, 0]; + break; + } + + let x, y; + switch (node.rotate || 0) { + case 0: + [x, y] = [-centerX, -centerY]; + break; + case 90: + [x, y] = [-centerY, centerX]; + [w, h] = [h, -w]; + break; + case 180: + [x, y] = [centerX, centerY]; + [w, h] = [-w, -h]; + break; + case 270: + [x, y] = [centerY, -centerX]; + [w, h] = [-h, w]; + break; + } + + return [ + node.x + x + Math.min(0, w), + node.y + y + Math.min(0, h), + Math.abs(w), + Math.abs(h), + ]; +} + +/** + * Returning true means that the node will be layed out + * else the layout will go to its next step (changing of line + * in case of lr-tb or changing content area...). + */ +function checkDimensions(node, space) { + if (node[$getTemplateRoot]()[$extra].firstUnsplittable === null) { + return true; + } + + if (node.w === 0 || node.h === 0) { + return true; + } + + const ERROR = 2; + const parent = node[$getSubformParent](); + const attempt = parent[$extra]?.attempt || 0; + + const [, y, w, h] = getTransformedBBox(node); + switch (parent.layout) { + case "lr-tb": + case "rl-tb": + if (attempt === 0) { + // Try to put an element in the line. + + if (!node[$getTemplateRoot]()[$extra].noLayoutFailure) { + if (node.h !== "" && Math.round(h - space.height) > ERROR) { + // Not enough height. + return false; + } + + if (node.w !== "") { + if (Math.round(w - space.width) <= ERROR) { + return true; + } + if (parent[$extra].numberInLine === 0) { + return space.height > ERROR; + } + + return false; + } + + return space.width > ERROR; + } + + // No layout failure. + + // Put the element on the line but we can fail + // and then in the second step (next line) we'll accept. + if (node.w !== "") { + return Math.round(w - space.width) <= ERROR; + } + + return space.width > ERROR; + } + + // Second attempt: try to put the element on the next line. + + if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { + // We cannot fail. + return true; + } + + if (node.h !== "" && Math.round(h - space.height) > ERROR) { + return false; + } + + if (node.w === "" || Math.round(w - space.width) <= ERROR) { + return space.height > ERROR; + } + + if (parent[$isThereMoreWidth]()) { + return false; + } + + return space.height > ERROR; + case "table": + case "tb": + if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { + return true; + } + + // If the node has a height then check if it's fine with available height. + // If the node is breakable then we can return true. + if (node.h !== "" && !node[$isSplittable]()) { + return Math.round(h - space.height) <= ERROR; + } + // Else wait and see: this node will be layed out itself + // in the provided space and maybe a children won't fit. + + if (node.w === "" || Math.round(w - space.width) <= ERROR) { + return space.height > ERROR; + } + + if (parent[$isThereMoreWidth]()) { + return false; + } + + return space.height > ERROR; + case "position": + if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { + return true; + } + + if (node.h === "" || Math.round(h + y - space.height) <= ERROR) { + return true; + } + + const area = node[$getTemplateRoot]()[$extra].currentContentArea; + return h + y > area.h; + case "rl-row": + case "row": + if (node[$getTemplateRoot]()[$extra].noLayoutFailure) { + return true; + } + + if (node.h !== "") { + return Math.round(h - space.height) <= ERROR; + } + return true; + default: + // No layout, so accept everything. + return true; + } +} + +export { addHTML, checkDimensions, flushHTML, getAvailableSpace }; diff --git a/frontend/public/pdf.js/src/core/xfa/locale_set.js b/frontend/public/pdf.js/src/core/xfa/locale_set.js new file mode 100644 index 0000000..8295016 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/locale_set.js @@ -0,0 +1,345 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { + ContentObject, + StringObject, + XFAObject, + XFAObjectArray, +} from "./xfa_object.js"; +import { getInteger, getStringOption } from "./utils.js"; + +const LOCALE_SET_NS_ID = NamespaceIds.localeSet.id; + +class CalendarSymbols extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "calendarSymbols", /* hasChildren = */ true); + this.name = "gregorian"; + this.dayNames = new XFAObjectArray(2); + this.eraNames = null; + this.meridiemNames = null; + this.monthNames = new XFAObjectArray(2); + } +} + +class CurrencySymbol extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "currencySymbol"); + this.name = getStringOption(attributes.name, [ + "symbol", + "isoname", + "decimal", + ]); + } +} + +class CurrencySymbols extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "currencySymbols", /* hasChildren = */ true); + this.currencySymbol = new XFAObjectArray(3); + } +} + +class DatePattern extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "datePattern"); + this.name = getStringOption(attributes.name, [ + "full", + "long", + "med", + "short", + ]); + } +} + +class DatePatterns extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "datePatterns", /* hasChildren = */ true); + this.datePattern = new XFAObjectArray(4); + } +} + +class DateTimeSymbols extends ContentObject { + // TODO: spec unclear about the format of the array. + + constructor(attributes) { + super(LOCALE_SET_NS_ID, "dateTimeSymbols"); + } +} + +class Day extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "day"); + } +} + +class DayNames extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "dayNames", /* hasChildren = */ true); + this.abbr = getInteger({ + data: attributes.abbr, + defaultValue: 0, + validate: x => x === 1, + }); + this.day = new XFAObjectArray(7); + } +} + +class Era extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "era"); + } +} + +class EraNames extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "eraNames", /* hasChildren = */ true); + this.era = new XFAObjectArray(2); + } +} + +class Locale extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "locale", /* hasChildren = */ true); + this.desc = attributes.desc || ""; + this.name = "isoname"; + this.calendarSymbols = null; + this.currencySymbols = null; + this.datePatterns = null; + this.dateTimeSymbols = null; + this.numberPatterns = null; + this.numberSymbols = null; + this.timePatterns = null; + this.typeFaces = null; + } +} + +class LocaleSet extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "localeSet", /* hasChildren = */ true); + this.locale = new XFAObjectArray(); + } +} + +class Meridiem extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "meridiem"); + } +} + +class MeridiemNames extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "meridiemNames", /* hasChildren = */ true); + this.meridiem = new XFAObjectArray(2); + } +} + +class Month extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "month"); + } +} + +class MonthNames extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "monthNames", /* hasChildren = */ true); + this.abbr = getInteger({ + data: attributes.abbr, + defaultValue: 0, + validate: x => x === 1, + }); + this.month = new XFAObjectArray(12); + } +} + +class NumberPattern extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "numberPattern"); + this.name = getStringOption(attributes.name, [ + "full", + "long", + "med", + "short", + ]); + } +} + +class NumberPatterns extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "numberPatterns", /* hasChildren = */ true); + this.numberPattern = new XFAObjectArray(4); + } +} + +class NumberSymbol extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "numberSymbol"); + this.name = getStringOption(attributes.name, [ + "decimal", + "grouping", + "percent", + "minus", + "zero", + ]); + } +} + +class NumberSymbols extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "numberSymbols", /* hasChildren = */ true); + this.numberSymbol = new XFAObjectArray(5); + } +} + +class TimePattern extends StringObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "timePattern"); + this.name = getStringOption(attributes.name, [ + "full", + "long", + "med", + "short", + ]); + } +} + +class TimePatterns extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "timePatterns", /* hasChildren = */ true); + this.timePattern = new XFAObjectArray(4); + } +} + +class TypeFace extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "typeFace", /* hasChildren = */ true); + this.name = attributes.name | ""; + } +} + +class TypeFaces extends XFAObject { + constructor(attributes) { + super(LOCALE_SET_NS_ID, "typeFaces", /* hasChildren = */ true); + this.typeFace = new XFAObjectArray(); + } +} + +class LocaleSetNamespace { + static [$buildXFAObject](name, attributes) { + if (LocaleSetNamespace.hasOwnProperty(name)) { + return LocaleSetNamespace[name](attributes); + } + return undefined; + } + + static calendarSymbols(attrs) { + return new CalendarSymbols(attrs); + } + + static currencySymbol(attrs) { + return new CurrencySymbol(attrs); + } + + static currencySymbols(attrs) { + return new CurrencySymbols(attrs); + } + + static datePattern(attrs) { + return new DatePattern(attrs); + } + + static datePatterns(attrs) { + return new DatePatterns(attrs); + } + + static dateTimeSymbols(attrs) { + return new DateTimeSymbols(attrs); + } + + static day(attrs) { + return new Day(attrs); + } + + static dayNames(attrs) { + return new DayNames(attrs); + } + + static era(attrs) { + return new Era(attrs); + } + + static eraNames(attrs) { + return new EraNames(attrs); + } + + static locale(attrs) { + return new Locale(attrs); + } + + static localeSet(attrs) { + return new LocaleSet(attrs); + } + + static meridiem(attrs) { + return new Meridiem(attrs); + } + + static meridiemNames(attrs) { + return new MeridiemNames(attrs); + } + + static month(attrs) { + return new Month(attrs); + } + + static monthNames(attrs) { + return new MonthNames(attrs); + } + + static numberPattern(attrs) { + return new NumberPattern(attrs); + } + + static numberPatterns(attrs) { + return new NumberPatterns(attrs); + } + + static numberSymbol(attrs) { + return new NumberSymbol(attrs); + } + + static numberSymbols(attrs) { + return new NumberSymbols(attrs); + } + + static timePattern(attrs) { + return new TimePattern(attrs); + } + + static timePatterns(attrs) { + return new TimePatterns(attrs); + } + + static typeFace(attrs) { + return new TypeFace(attrs); + } + + static typeFaces(attrs) { + return new TypeFaces(attrs); + } +} + +export { LocaleSetNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/namespaces.js b/frontend/public/pdf.js/src/core/xfa/namespaces.js new file mode 100644 index 0000000..b8fb548 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/namespaces.js @@ -0,0 +1,81 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +const $buildXFAObject = Symbol(); + +const NamespaceIds = { + config: { + id: 0, + check: ns => ns.startsWith("http://www.xfa.org/schema/xci/"), + }, + connectionSet: { + id: 1, + check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-connection-set/"), + }, + datasets: { + id: 2, + check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-data/"), + }, + form: { + id: 3, + check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-form/"), + }, + localeSet: { + id: 4, + check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-locale-set/"), + }, + pdf: { + id: 5, + check: ns => ns === "http://ns.adobe.com/xdp/pdf/", + }, + signature: { + id: 6, + check: ns => ns === "http://www.w3.org/2000/09/xmldsig#", + }, + sourceSet: { + id: 7, + check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-source-set/"), + }, + stylesheet: { + id: 8, + check: ns => ns === "http://www.w3.org/1999/XSL/Transform", + }, + template: { + id: 9, + check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-template/"), + }, + xdc: { + id: 10, + check: ns => ns.startsWith("http://www.xfa.org/schema/xdc/"), + }, + xdp: { + id: 11, + check: ns => ns === "http://ns.adobe.com/xdp/", + }, + xfdf: { + id: 12, + check: ns => ns === "http://ns.adobe.com/xfdf/", + }, + xhtml: { + id: 13, + check: ns => ns === "http://www.w3.org/1999/xhtml", + }, + xmpmeta: { + id: 14, + check: ns => ns === "http://ns.adobe.com/xmpmeta/", + }, +}; + +export { $buildXFAObject, NamespaceIds }; diff --git a/frontend/public/pdf.js/src/core/xfa/parser.js b/frontend/public/pdf.js/src/core/xfa/parser.js new file mode 100644 index 0000000..8b6bdd6 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/parser.js @@ -0,0 +1,182 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $acceptWhitespace, + $clean, + $content, + $finalize, + $globalData, + $isCDATAXml, + $nsAttributes, + $onChild, + $onText, + $setId, +} from "./symbol_utils.js"; +import { XMLParserBase, XMLParserErrorCode } from "../xml_parser.js"; +import { Builder } from "./builder.js"; +import { warn } from "../../shared/util.js"; + +class XFAParser extends XMLParserBase { + constructor(rootNameSpace = null, richText = false) { + super(); + this._builder = new Builder(rootNameSpace); + this._stack = []; + this._globalData = { + usedTypefaces: new Set(), + }; + this._ids = new Map(); + this._current = this._builder.buildRoot(this._ids); + this._errorCode = XMLParserErrorCode.NoError; + this._whiteRegex = /^\s+$/; + this._nbsps = /\xa0+/g; + this._richText = richText; + } + + parse(data) { + this.parseXml(data); + + if (this._errorCode !== XMLParserErrorCode.NoError) { + return undefined; + } + + this._current[$finalize](); + + return this._current.element; + } + + onText(text) { + // Normally by definition a   is unbreakable + // but in real life Acrobat can break strings on  . + text = text.replace(this._nbsps, match => match.slice(1) + " "); + if (this._richText || this._current[$acceptWhitespace]()) { + this._current[$onText](text, this._richText); + return; + } + + if (this._whiteRegex.test(text)) { + return; + } + this._current[$onText](text.trim()); + } + + onCdata(text) { + this._current[$onText](text); + } + + _mkAttributes(attributes, tagName) { + // Transform attributes into an object and get out + // namespaces information. + let namespace = null; + let prefixes = null; + const attributeObj = Object.create({}); + for (const { name, value } of attributes) { + if (name === "xmlns") { + if (!namespace) { + namespace = value; + } else { + warn(`XFA - multiple namespace definition in <${tagName}>`); + } + } else if (name.startsWith("xmlns:")) { + const prefix = name.substring("xmlns:".length); + if (!prefixes) { + prefixes = []; + } + prefixes.push({ prefix, value }); + } else { + const i = name.indexOf(":"); + if (i === -1) { + attributeObj[name] = value; + } else { + // Attributes can have their own namespace. + // For example in data, we can have + let nsAttrs = attributeObj[$nsAttributes]; + if (!nsAttrs) { + nsAttrs = attributeObj[$nsAttributes] = Object.create(null); + } + const [ns, attrName] = [name.slice(0, i), name.slice(i + 1)]; + const attrs = (nsAttrs[ns] ||= Object.create(null)); + attrs[attrName] = value; + } + } + } + + return [namespace, prefixes, attributeObj]; + } + + _getNameAndPrefix(name, nsAgnostic) { + const i = name.indexOf(":"); + if (i === -1) { + return [name, null]; + } + return [name.substring(i + 1), nsAgnostic ? "" : name.substring(0, i)]; + } + + onBeginElement(tagName, attributes, isEmpty) { + const [namespace, prefixes, attributesObj] = this._mkAttributes( + attributes, + tagName + ); + const [name, nsPrefix] = this._getNameAndPrefix( + tagName, + this._builder.isNsAgnostic() + ); + const node = this._builder.build({ + nsPrefix, + name, + attributes: attributesObj, + namespace, + prefixes, + }); + node[$globalData] = this._globalData; + + if (isEmpty) { + // No children: just push the node into its parent. + node[$finalize](); + if (this._current[$onChild](node)) { + node[$setId](this._ids); + } + node[$clean](this._builder); + return; + } + + this._stack.push(this._current); + this._current = node; + } + + onEndElement(name) { + const node = this._current; + if (node[$isCDATAXml]() && typeof node[$content] === "string") { + const parser = new XFAParser(); + parser._globalData = this._globalData; + const root = parser.parse(node[$content]); + node[$content] = null; + node[$onChild](root); + } + + node[$finalize](); + this._current = this._stack.pop(); + if (this._current[$onChild](node)) { + node[$setId](this._ids); + } + node[$clean](this._builder); + } + + onError(code) { + this._errorCode = code; + } +} + +export { XFAParser }; diff --git a/frontend/public/pdf.js/src/core/xfa/setup.js b/frontend/public/pdf.js/src/core/xfa/setup.js new file mode 100644 index 0000000..0fab11b --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/setup.js @@ -0,0 +1,38 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { ConfigNamespace } from "./config.js"; +import { ConnectionSetNamespace } from "./connection_set.js"; +import { DatasetsNamespace } from "./datasets.js"; +import { LocaleSetNamespace } from "./locale_set.js"; +import { SignatureNamespace } from "./signature.js"; +import { StylesheetNamespace } from "./stylesheet.js"; +import { TemplateNamespace } from "./template.js"; +import { XdpNamespace } from "./xdp.js"; +import { XhtmlNamespace } from "./xhtml.js"; + +const NamespaceSetUp = { + config: ConfigNamespace, + connection: ConnectionSetNamespace, + datasets: DatasetsNamespace, + localeSet: LocaleSetNamespace, + signature: SignatureNamespace, + stylesheet: StylesheetNamespace, + template: TemplateNamespace, + xdp: XdpNamespace, + xhtml: XhtmlNamespace, +}; + +export { NamespaceSetUp }; diff --git a/frontend/public/pdf.js/src/core/xfa/signature.js b/frontend/public/pdf.js/src/core/xfa/signature.js new file mode 100644 index 0000000..c13b788 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/signature.js @@ -0,0 +1,40 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { XFAObject } from "./xfa_object.js"; + +const SIGNATURE_NS_ID = NamespaceIds.signature.id; + +class Signature extends XFAObject { + constructor(attributes) { + super(SIGNATURE_NS_ID, "signature", /* hasChildren = */ true); + } +} + +class SignatureNamespace { + static [$buildXFAObject](name, attributes) { + if (SignatureNamespace.hasOwnProperty(name)) { + return SignatureNamespace[name](attributes); + } + return undefined; + } + + static signature(attributes) { + return new Signature(attributes); + } +} + +export { SignatureNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/som.js b/frontend/public/pdf.js/src/core/xfa/som.js new file mode 100644 index 0000000..205420c --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/som.js @@ -0,0 +1,318 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $getChildren, + $getChildrenByClass, + $getChildrenByName, + $getParent, +} from "./symbol_utils.js"; +import { warn } from "../../shared/util.js"; + +const namePattern = /^[^.[]+/; +const indexPattern = /^[^\]]+/; +const operators = { + dot: 0, + dotDot: 1, + dotHash: 2, + dotBracket: 3, + dotParen: 4, +}; + +const shortcuts = new Map([ + ["$data", (root, current) => (root.datasets ? root.datasets.data : root)], + [ + "$record", + (root, current) => + (root.datasets ? root.datasets.data : root)[$getChildren]()[0], + ], + ["$template", (root, current) => root.template], + ["$connectionSet", (root, current) => root.connectionSet], + ["$form", (root, current) => root.form], + ["$layout", (root, current) => root.layout], + ["$host", (root, current) => root.host], + ["$dataWindow", (root, current) => root.dataWindow], + ["$event", (root, current) => root.event], + ["!", (root, current) => root.datasets], + ["$xfa", (root, current) => root], + ["xfa", (root, current) => root], + ["$", (root, current) => current], +]); + +const somCache = new WeakMap(); + +function parseIndex(index) { + index = index.trim(); + if (index === "*") { + return Infinity; + } + return parseInt(index, 10) || 0; +} + +// For now expressions containing .[...] or .(...) are not +// evaluated so don't parse them. +// TODO: implement that stuff and the remove the noExpr param. +function parseExpression(expr, dotDotAllowed, noExpr = true) { + let match = expr.match(namePattern); + if (!match) { + return null; + } + + let [name] = match; + const parsed = [ + { + name, + cacheName: "." + name, + index: 0, + js: null, + formCalc: null, + operator: operators.dot, + }, + ]; + + let pos = name.length; + + while (pos < expr.length) { + const spos = pos; + const char = expr.charAt(pos++); + if (char === "[") { + match = expr.slice(pos).match(indexPattern); + if (!match) { + warn("XFA - Invalid index in SOM expression"); + return null; + } + parsed.at(-1).index = parseIndex(match[0]); + pos += match[0].length + 1; + continue; + } + + let operator; + switch (expr.charAt(pos)) { + case ".": + if (!dotDotAllowed) { + return null; + } + pos++; + operator = operators.dotDot; + break; + case "#": + pos++; + operator = operators.dotHash; + break; + case "[": + if (noExpr) { + warn( + "XFA - SOM expression contains a FormCalc subexpression which is not supported for now." + ); + return null; + } + // TODO: FormCalc expression so need to use the parser + operator = operators.dotBracket; + break; + case "(": + if (noExpr) { + warn( + "XFA - SOM expression contains a JavaScript subexpression which is not supported for now." + ); + return null; + } + // TODO: + // JavaScript expression: should be a boolean operation with a path + // so maybe we can have our own parser for that stuff or + // maybe use the formcalc one. + operator = operators.dotParen; + break; + default: + operator = operators.dot; + break; + } + + match = expr.slice(pos).match(namePattern); + if (!match) { + break; + } + + [name] = match; + pos += name.length; + parsed.push({ + name, + cacheName: expr.slice(spos, pos), + operator, + index: 0, + js: null, + formCalc: null, + }); + } + return parsed; +} + +function searchNode( + root, + container, + expr, + dotDotAllowed = true, + useCache = true +) { + const parsed = parseExpression(expr, dotDotAllowed); + if (!parsed) { + return null; + } + + const fn = shortcuts.get(parsed[0].name); + let i = 0; + let isQualified; + if (fn) { + isQualified = true; + root = [fn(root, container)]; + i = 1; + } else { + isQualified = container === null; + root = [container || root]; + } + + for (let ii = parsed.length; i < ii; i++) { + const { name, cacheName, operator, index } = parsed[i]; + const nodes = []; + for (const node of root) { + if (!node.isXFAObject) { + continue; + } + + let children, cached; + + if (useCache) { + cached = somCache.get(node); + if (!cached) { + cached = new Map(); + somCache.set(node, cached); + } + children = cached.get(cacheName); + } + + if (!children) { + switch (operator) { + case operators.dot: + children = node[$getChildrenByName](name, false); + break; + case operators.dotDot: + children = node[$getChildrenByName](name, true); + break; + case operators.dotHash: + children = node[$getChildrenByClass](name); + children = children.isXFAObjectArray + ? children.children + : [children]; + break; + default: + break; + } + if (useCache) { + cached.set(cacheName, children); + } + } + + if (children.length > 0) { + nodes.push(children); + } + } + + if (nodes.length === 0 && !isQualified && i === 0) { + // We've an unqualified expression and we didn't find anything + // so look at container and siblings of container and so on. + // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.364.2157&rep=rep1&type=pdf#page=114 + const parent = container[$getParent](); + container = parent; + if (!container) { + return null; + } + i = -1; + root = [container]; + continue; + } + + root = isFinite(index) + ? nodes.filter(node => index < node.length).map(node => node[index]) + : nodes.flat(); + } + + if (root.length === 0) { + return null; + } + + return root; +} + +function createDataNode(root, container, expr) { + const parsed = parseExpression(expr); + if (!parsed) { + return null; + } + + if (parsed.some(x => x.operator === operators.dotDot)) { + return null; + } + + const fn = shortcuts.get(parsed[0].name); + let i = 0; + if (fn) { + root = fn(root, container); + i = 1; + } else { + root = container || root; + } + + for (let ii = parsed.length; i < ii; i++) { + const { name, operator, index } = parsed[i]; + if (!isFinite(index)) { + parsed[i].index = 0; + return root.createNodes(parsed.slice(i)); + } + + let children; + switch (operator) { + case operators.dot: + children = root[$getChildrenByName](name, false); + break; + case operators.dotDot: + children = root[$getChildrenByName](name, true); + break; + case operators.dotHash: + children = root[$getChildrenByClass](name); + children = children.isXFAObjectArray ? children.children : [children]; + break; + default: + break; + } + + if (children.length === 0) { + return root.createNodes(parsed.slice(i)); + } + + if (index < children.length) { + const child = children[index]; + if (!child.isXFAObject) { + warn(`XFA - Cannot create a node.`); + return null; + } + root = child; + } else { + parsed[i].index = index - children.length; + return root.createNodes(parsed.slice(i)); + } + } + return null; +} + +export { createDataNode, searchNode }; diff --git a/frontend/public/pdf.js/src/core/xfa/stylesheet.js b/frontend/public/pdf.js/src/core/xfa/stylesheet.js new file mode 100644 index 0000000..175b58f --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/stylesheet.js @@ -0,0 +1,40 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { XFAObject } from "./xfa_object.js"; + +const STYLESHEET_NS_ID = NamespaceIds.stylesheet.id; + +class Stylesheet extends XFAObject { + constructor(attributes) { + super(STYLESHEET_NS_ID, "stylesheet", /* hasChildren = */ true); + } +} + +class StylesheetNamespace { + static [$buildXFAObject](name, attributes) { + if (StylesheetNamespace.hasOwnProperty(name)) { + return StylesheetNamespace[name](attributes); + } + return undefined; + } + + static stylesheet(attributes) { + return new Stylesheet(attributes); + } +} + +export { StylesheetNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/symbol_utils.js b/frontend/public/pdf.js/src/core/xfa/symbol_utils.js new file mode 100644 index 0000000..fc4c267 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/symbol_utils.js @@ -0,0 +1,156 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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. + */ + +// We use these symbols to avoid name conflict between tags +// and properties/methods names. +const $acceptWhitespace = Symbol(); +const $addHTML = Symbol(); +const $appendChild = Symbol(); +const $childrenToHTML = Symbol(); +const $clean = Symbol(); +const $cleanPage = Symbol(); +const $cleanup = Symbol(); +const $clone = Symbol(); +const $consumed = Symbol(); +const $content = Symbol("content"); +const $data = Symbol("data"); +const $dump = Symbol(); +const $extra = Symbol("extra"); +const $finalize = Symbol(); +const $flushHTML = Symbol(); +const $getAttributeIt = Symbol(); +const $getAttributes = Symbol(); +const $getAvailableSpace = Symbol(); +const $getChildrenByClass = Symbol(); +const $getChildrenByName = Symbol(); +const $getChildrenByNameIt = Symbol(); +const $getDataValue = Symbol(); +const $getExtra = Symbol(); +const $getRealChildrenByNameIt = Symbol(); +const $getChildren = Symbol(); +const $getContainedChildren = Symbol(); +const $getNextPage = Symbol(); +const $getSubformParent = Symbol(); +const $getParent = Symbol(); +const $getTemplateRoot = Symbol(); +const $globalData = Symbol(); +const $hasSettableValue = Symbol(); +const $ids = Symbol(); +const $indexOf = Symbol(); +const $insertAt = Symbol(); +const $isCDATAXml = Symbol(); +const $isBindable = Symbol(); +const $isDataValue = Symbol(); +const $isDescendent = Symbol(); +const $isNsAgnostic = Symbol(); +const $isSplittable = Symbol(); +const $isThereMoreWidth = Symbol(); +const $isTransparent = Symbol(); +const $isUsable = Symbol(); +const $lastAttribute = Symbol(); +const $namespaceId = Symbol("namespaceId"); +const $nodeName = Symbol("nodeName"); +const $nsAttributes = Symbol(); +const $onChild = Symbol(); +const $onChildCheck = Symbol(); +const $onText = Symbol(); +const $pushGlyphs = Symbol(); +const $popPara = Symbol(); +const $pushPara = Symbol(); +const $removeChild = Symbol(); +const $root = Symbol("root"); +const $resolvePrototypes = Symbol(); +const $searchNode = Symbol(); +const $setId = Symbol(); +const $setSetAttributes = Symbol(); +const $setValue = Symbol(); +const $tabIndex = Symbol(); +const $text = Symbol(); +const $toPages = Symbol(); +const $toHTML = Symbol(); +const $toString = Symbol(); +const $toStyle = Symbol(); +const $uid = Symbol("uid"); + +export { + $acceptWhitespace, + $addHTML, + $appendChild, + $childrenToHTML, + $clean, + $cleanPage, + $cleanup, + $clone, + $consumed, + $content, + $data, + $dump, + $extra, + $finalize, + $flushHTML, + $getAttributeIt, + $getAttributes, + $getAvailableSpace, + $getChildren, + $getChildrenByClass, + $getChildrenByName, + $getChildrenByNameIt, + $getContainedChildren, + $getDataValue, + $getExtra, + $getNextPage, + $getParent, + $getRealChildrenByNameIt, + $getSubformParent, + $getTemplateRoot, + $globalData, + $hasSettableValue, + $ids, + $indexOf, + $insertAt, + $isBindable, + $isCDATAXml, + $isDataValue, + $isDescendent, + $isNsAgnostic, + $isSplittable, + $isThereMoreWidth, + $isTransparent, + $isUsable, + $lastAttribute, + $namespaceId, + $nodeName, + $nsAttributes, + $onChild, + $onChildCheck, + $onText, + $popPara, + $pushGlyphs, + $pushPara, + $removeChild, + $resolvePrototypes, + $root, + $searchNode, + $setId, + $setSetAttributes, + $setValue, + $tabIndex, + $text, + $toHTML, + $toPages, + $toString, + $toStyle, + $uid, +}; diff --git a/frontend/public/pdf.js/src/core/xfa/template.js b/frontend/public/pdf.js/src/core/xfa/template.js new file mode 100644 index 0000000..4fd5031 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/template.js @@ -0,0 +1,6630 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $acceptWhitespace, + $addHTML, + $appendChild, + $childrenToHTML, + $clean, + $cleanPage, + $content, + $data, + $extra, + $finalize, + $flushHTML, + $getAvailableSpace, + $getChildren, + $getContainedChildren, + $getExtra, + $getNextPage, + $getParent, + $getSubformParent, + $getTemplateRoot, + $globalData, + $hasSettableValue, + $ids, + $isBindable, + $isCDATAXml, + $isSplittable, + $isThereMoreWidth, + $isTransparent, + $isUsable, + $namespaceId, + $nodeName, + $onChild, + $onText, + $popPara, + $pushPara, + $removeChild, + $searchNode, + $setSetAttributes, + $setValue, + $tabIndex, + $text, + $toHTML, + $toPages, + $toStyle, + $uid, +} from "./symbol_utils.js"; +import { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { + addHTML, + checkDimensions, + flushHTML, + getAvailableSpace, +} from "./layout.js"; +import { + computeBbox, + createWrapper, + fixDimensions, + fixTextIndent, + fixURL, + isPrintOnly, + layoutClass, + layoutNode, + measureToString, + setAccess, + setFontFamily, + setMinMaxDimensions, + setPara, + toStyle, +} from "./html_utils.js"; +import { + ContentObject, + Option01, + OptionObject, + StringObject, + XFAObject, + XFAObjectArray, +} from "./xfa_object.js"; +import { fromBase64Util, Util, warn } from "../../shared/util.js"; +import { + getBBox, + getColor, + getFloat, + getInteger, + getKeyword, + getMeasurement, + getRatio, + getRelevant, + getStringOption, + HTMLResult, +} from "./utils.js"; +import { getMetrics } from "./fonts.js"; +import { recoverJsURL } from "../core_utils.js"; +import { searchNode } from "./som.js"; + +const TEMPLATE_NS_ID = NamespaceIds.template.id; +const SVG_NS = "http://www.w3.org/2000/svg"; + +// In case of lr-tb (and rl-tb) layouts, we try: +// - to put the container at the end of a line +// - and if it fails we try on the next line. +// If both tries failed then it's up to the parent +// to handle the situation. +const MAX_ATTEMPTS_FOR_LRTB_LAYOUT = 2; + +// It's possible to have a bug in the layout and so as +// a consequence we could loop for ever in Template::toHTML() +// so in order to avoid that (and avoid a OOM crash) we break +// the loop after having MAX_EMPTY_PAGES empty pages. +const MAX_EMPTY_PAGES = 3; + +// Default value to start with for the tabIndex property. +const DEFAULT_TAB_INDEX = 5000; + +const HEADING_PATTERN = /^H(\d+)$/; + +// Allowed mime types for images +const MIMES = new Set([ + "image/gif", + "image/jpeg", + "image/jpg", + "image/pjpeg", + "image/png", + "image/apng", + "image/x-png", + "image/bmp", + "image/x-ms-bmp", + "image/tiff", + "image/tif", + "application/octet-stream", +]); + +const IMAGES_HEADERS = [ + [[0x42, 0x4d], "image/bmp"], + [[0xff, 0xd8, 0xff], "image/jpeg"], + [[0x49, 0x49, 0x2a, 0x00], "image/tiff"], + [[0x4d, 0x4d, 0x00, 0x2a], "image/tiff"], + [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], "image/gif"], + [[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], "image/png"], +]; + +function getBorderDims(node) { + if (!node || !node.border) { + return { w: 0, h: 0 }; + } + + const borderExtra = node.border[$getExtra](); + if (!borderExtra) { + return { w: 0, h: 0 }; + } + + return { + w: + borderExtra.widths[0] + + borderExtra.widths[2] + + borderExtra.insets[0] + + borderExtra.insets[2], + h: + borderExtra.widths[1] + + borderExtra.widths[3] + + borderExtra.insets[1] + + borderExtra.insets[3], + }; +} + +function hasMargin(node) { + return ( + node.margin && + (node.margin.topInset || + node.margin.rightInset || + node.margin.bottomInset || + node.margin.leftInset) + ); +} + +function _setValue(templateNode, value) { + if (!templateNode.value) { + const nodeValue = new Value({}); + templateNode[$appendChild](nodeValue); + templateNode.value = nodeValue; + } + templateNode.value[$setValue](value); +} + +function* getContainedChildren(node) { + for (const child of node[$getChildren]()) { + if (child instanceof SubformSet) { + yield* child[$getContainedChildren](); + continue; + } + yield child; + } +} + +function isRequired(node) { + return node.validate?.nullTest === "error"; +} + +function setTabIndex(node) { + while (node) { + if (!node.traversal) { + node[$tabIndex] = node[$getParent]()[$tabIndex]; + return; + } + + if (node[$tabIndex]) { + return; + } + + let next = null; + for (const child of node.traversal[$getChildren]()) { + if (child.operation === "next") { + next = child; + break; + } + } + + if (!next || !next.ref) { + node[$tabIndex] = node[$getParent]()[$tabIndex]; + return; + } + + const root = node[$getTemplateRoot](); + node[$tabIndex] = ++root[$tabIndex]; + + const ref = root[$searchNode](next.ref, node); + if (!ref) { + return; + } + + node = ref[0]; + } +} + +function applyAssist(obj, attributes) { + const assist = obj.assist; + if (assist) { + const assistTitle = assist[$toHTML](); + if (assistTitle) { + attributes.title = assistTitle; + } + const role = assist.role; + const match = role.match(HEADING_PATTERN); + if (match) { + const ariaRole = "heading"; + const ariaLevel = match[1]; + attributes.role = ariaRole; + attributes["aria-level"] = ariaLevel; + } + } + // XXX: We could end up in a situation where the obj has a heading role and + // is also a table. For now prioritize the table role. + if (obj.layout === "table") { + attributes.role = "table"; + } else if (obj.layout === "row") { + attributes.role = "row"; + } else { + const parent = obj[$getParent](); + if (parent.layout === "row") { + attributes.role = parent.assist?.role === "TH" ? "columnheader" : "cell"; + } + } +} + +function ariaLabel(obj) { + if (!obj.assist) { + return null; + } + const assist = obj.assist; + if (assist.speak && assist.speak[$content] !== "") { + return assist.speak[$content]; + } + if (assist.toolTip) { + return assist.toolTip[$content]; + } + // TODO: support finding the related caption element. See xfa_bug1718037.pdf + // for an example. + return null; +} + +function valueToHtml(value) { + return HTMLResult.success({ + name: "div", + attributes: { + class: ["xfaRich"], + style: Object.create(null), + }, + children: [ + { + name: "span", + attributes: { + style: Object.create(null), + }, + value, + }, + ], + }); +} + +function setFirstUnsplittable(node) { + const root = node[$getTemplateRoot](); + if (root[$extra].firstUnsplittable === null) { + root[$extra].firstUnsplittable = node; + root[$extra].noLayoutFailure = true; + } +} + +function unsetFirstUnsplittable(node) { + const root = node[$getTemplateRoot](); + if (root[$extra].firstUnsplittable === node) { + root[$extra].noLayoutFailure = false; + } +} + +function handleBreak(node) { + if (node[$extra]) { + return false; + } + + node[$extra] = Object.create(null); + + if (node.targetType === "auto") { + return false; + } + + const root = node[$getTemplateRoot](); + let target = null; + if (node.target) { + target = root[$searchNode](node.target, node[$getParent]()); + if (!target) { + return false; + } + target = target[0]; + } + + const { currentPageArea, currentContentArea } = root[$extra]; + + if (node.targetType === "pageArea") { + if (!(target instanceof PageArea)) { + target = null; + } + + if (node.startNew) { + node[$extra].target = target || currentPageArea; + return true; + } else if (target && target !== currentPageArea) { + node[$extra].target = target; + return true; + } + + return false; + } + + if (!(target instanceof ContentArea)) { + target = null; + } + + const pageArea = target && target[$getParent](); + + let index; + let nextPageArea = pageArea; + if (node.startNew) { + // startNew === 1 so we must create a new container (pageArea or + // contentArea). + if (target) { + const contentAreas = pageArea.contentArea.children; + const indexForCurrent = contentAreas.indexOf(currentContentArea); + const indexForTarget = contentAreas.indexOf(target); + if (indexForCurrent !== -1 && indexForCurrent < indexForTarget) { + // The next container is after the current container so + // we can stay on the same page. + nextPageArea = null; + } + index = indexForTarget - 1; + } else { + index = currentPageArea.contentArea.children.indexOf(currentContentArea); + } + } else if (target && target !== currentContentArea) { + const contentAreas = pageArea.contentArea.children; + index = contentAreas.indexOf(target) - 1; + nextPageArea = pageArea === currentPageArea ? null : pageArea; + } else { + return false; + } + + node[$extra].target = nextPageArea; + node[$extra].index = index; + return true; +} + +function handleOverflow(node, extraNode, space) { + const root = node[$getTemplateRoot](); + const saved = root[$extra].noLayoutFailure; + const savedMethod = extraNode[$getSubformParent]; + + // Replace $getSubformParent to emulate that extraNode is just + // under node. + extraNode[$getSubformParent] = () => node; + + root[$extra].noLayoutFailure = true; + const res = extraNode[$toHTML](space); + node[$addHTML](res.html, res.bbox); + root[$extra].noLayoutFailure = saved; + extraNode[$getSubformParent] = savedMethod; +} + +class AppearanceFilter extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "appearanceFilter"); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Arc extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "arc", /* hasChildren = */ true); + this.circular = getInteger({ + data: attributes.circular, + defaultValue: 0, + validate: x => x === 1, + }); + this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); + this.id = attributes.id || ""; + this.startAngle = getFloat({ + data: attributes.startAngle, + defaultValue: 0, + validate: x => true, + }); + this.sweepAngle = getFloat({ + data: attributes.sweepAngle, + defaultValue: 360, + validate: x => true, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.edge = null; + this.fill = null; + } + + [$toHTML]() { + const edge = this.edge || new Edge({}); + const edgeStyle = edge[$toStyle](); + const style = Object.create(null); + if (this.fill?.presence === "visible") { + Object.assign(style, this.fill[$toStyle]()); + } else { + style.fill = "transparent"; + } + style.strokeWidth = measureToString( + edge.presence === "visible" ? edge.thickness : 0 + ); + style.stroke = edgeStyle.color; + let arc; + const attributes = { + xmlns: SVG_NS, + style: { + width: "100%", + height: "100%", + overflow: "visible", + }, + }; + + if (this.sweepAngle === 360) { + arc = { + name: "ellipse", + attributes: { + xmlns: SVG_NS, + cx: "50%", + cy: "50%", + rx: "50%", + ry: "50%", + style, + }, + }; + } else { + const startAngle = (this.startAngle * Math.PI) / 180; + const sweepAngle = (this.sweepAngle * Math.PI) / 180; + const largeArc = this.sweepAngle > 180 ? 1 : 0; + const [x1, y1, x2, y2] = [ + 50 * (1 + Math.cos(startAngle)), + 50 * (1 - Math.sin(startAngle)), + 50 * (1 + Math.cos(startAngle + sweepAngle)), + 50 * (1 - Math.sin(startAngle + sweepAngle)), + ]; + + arc = { + name: "path", + attributes: { + xmlns: SVG_NS, + d: `M ${x1} ${y1} A 50 50 0 ${largeArc} 0 ${x2} ${y2}`, + vectorEffect: "non-scaling-stroke", + style, + }, + }; + + Object.assign(attributes, { + viewBox: "0 0 100 100", + preserveAspectRatio: "none", + }); + } + + const svg = { + name: "svg", + children: [arc], + attributes, + }; + + const parent = this[$getParent]()[$getParent](); + if (hasMargin(parent)) { + return HTMLResult.success({ + name: "div", + attributes: { + style: { + display: "inline", + width: "100%", + height: "100%", + }, + }, + children: [svg], + }); + } + + svg.attributes.style.position = "absolute"; + return HTMLResult.success(svg); + } +} + +class Area extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "area", /* hasChildren = */ true); + this.colSpan = getInteger({ + data: attributes.colSpan, + defaultValue: 1, + validate: n => n >= 1 || n === -1, + }); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.x = getMeasurement(attributes.x, "0pt"); + this.y = getMeasurement(attributes.y, "0pt"); + this.desc = null; + this.extras = null; + this.area = new XFAObjectArray(); + this.draw = new XFAObjectArray(); + this.exObject = new XFAObjectArray(); + this.exclGroup = new XFAObjectArray(); + this.field = new XFAObjectArray(); + this.subform = new XFAObjectArray(); + this.subformSet = new XFAObjectArray(); + } + + *[$getContainedChildren]() { + // This function is overriden in order to fake that subforms under + // this set are in fact under parent subform. + yield* getContainedChildren(this); + } + + [$isTransparent]() { + return true; + } + + [$isBindable]() { + return true; + } + + [$addHTML](html, bbox) { + const [x, y, w, h] = bbox; + this[$extra].width = Math.max(this[$extra].width, x + w); + this[$extra].height = Math.max(this[$extra].height, y + h); + + this[$extra].children.push(html); + } + + [$getAvailableSpace]() { + return this[$extra].availableSpace; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + const style = toStyle(this, "position"); + const attributes = { + style, + id: this[$uid], + class: ["xfaArea"], + }; + + if (isPrintOnly(this)) { + attributes.class.push("xfaPrintOnly"); + } + + if (this.name) { + attributes.xfaName = this.name; + } + + const children = []; + this[$extra] = { + children, + width: 0, + height: 0, + availableSpace, + }; + + const result = this[$childrenToHTML]({ + filter: new Set([ + "area", + "draw", + "field", + "exclGroup", + "subform", + "subformSet", + ]), + include: true, + }); + + if (!result.success) { + if (result.isBreak()) { + return result; + } + // Nothing to propose for the element which doesn't fit the + // available space. + delete this[$extra]; + return HTMLResult.FAILURE; + } + + style.width = measureToString(this[$extra].width); + style.height = measureToString(this[$extra].height); + + const html = { + name: "div", + attributes, + children, + }; + + const bbox = [this.x, this.y, this[$extra].width, this[$extra].height]; + delete this[$extra]; + + return HTMLResult.success(html, bbox); + } +} + +class Assist extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "assist", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.role = attributes.role || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.speak = null; + this.toolTip = null; + } + + [$toHTML]() { + return this.toolTip?.[$content] || null; + } +} + +class Barcode extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "barcode", /* hasChildren = */ true); + this.charEncoding = getKeyword({ + data: attributes.charEncoding + ? attributes.charEncoding.toLowerCase() + : "", + defaultValue: "", + validate: k => + [ + "utf-8", + "big-five", + "fontspecific", + "gbk", + "gb-18030", + "gb-2312", + "ksc-5601", + "none", + "shift-jis", + "ucs-2", + "utf-16", + ].includes(k) || k.match(/iso-8859-\d{2}/), + }); + this.checksum = getStringOption(attributes.checksum, [ + "none", + "1mod10", + "1mod10_1mod11", + "2mod10", + "auto", + ]); + this.dataColumnCount = getInteger({ + data: attributes.dataColumnCount, + defaultValue: -1, + validate: x => x >= 0, + }); + this.dataLength = getInteger({ + data: attributes.dataLength, + defaultValue: -1, + validate: x => x >= 0, + }); + this.dataPrep = getStringOption(attributes.dataPrep, [ + "none", + "flateCompress", + ]); + this.dataRowCount = getInteger({ + data: attributes.dataRowCount, + defaultValue: -1, + validate: x => x >= 0, + }); + this.endChar = attributes.endChar || ""; + this.errorCorrectionLevel = getInteger({ + data: attributes.errorCorrectionLevel, + defaultValue: -1, + validate: x => x >= 0 && x <= 8, + }); + this.id = attributes.id || ""; + this.moduleHeight = getMeasurement(attributes.moduleHeight, "5mm"); + this.moduleWidth = getMeasurement(attributes.moduleWidth, "0.25mm"); + this.printCheckDigit = getInteger({ + data: attributes.printCheckDigit, + defaultValue: 0, + validate: x => x === 1, + }); + this.rowColumnRatio = getRatio(attributes.rowColumnRatio); + this.startChar = attributes.startChar || ""; + this.textLocation = getStringOption(attributes.textLocation, [ + "below", + "above", + "aboveEmbedded", + "belowEmbedded", + "none", + ]); + this.truncate = getInteger({ + data: attributes.truncate, + defaultValue: 0, + validate: x => x === 1, + }); + this.type = getStringOption( + attributes.type ? attributes.type.toLowerCase() : "", + [ + "aztec", + "codabar", + "code2of5industrial", + "code2of5interleaved", + "code2of5matrix", + "code2of5standard", + "code3of9", + "code3of9extended", + "code11", + "code49", + "code93", + "code128", + "code128a", + "code128b", + "code128c", + "code128sscc", + "datamatrix", + "ean8", + "ean8add2", + "ean8add5", + "ean13", + "ean13add2", + "ean13add5", + "ean13pwcd", + "fim", + "logmars", + "maxicode", + "msi", + "pdf417", + "pdf417macro", + "plessey", + "postauscust2", + "postauscust3", + "postausreplypaid", + "postausstandard", + "postukrm4scc", + "postusdpbc", + "postusimb", + "postusstandard", + "postus5zip", + "qrcode", + "rfid", + "rss14", + "rss14expanded", + "rss14limited", + "rss14stacked", + "rss14stackedomni", + "rss14truncated", + "telepen", + "ucc128", + "ucc128random", + "ucc128sscc", + "upca", + "upcaadd2", + "upcaadd5", + "upcapwcd", + "upce", + "upceadd2", + "upceadd5", + "upcean2", + "upcean5", + "upsmaxicode", + ] + ); + this.upsMode = getStringOption(attributes.upsMode, [ + "usCarrier", + "internationalCarrier", + "secureSymbol", + "standardSymbol", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.wideNarrowRatio = getRatio(attributes.wideNarrowRatio); + this.encrypt = null; + this.extras = null; + } +} + +class Bind extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "bind", /* hasChildren = */ true); + this.match = getStringOption(attributes.match, [ + "once", + "dataRef", + "global", + "none", + ]); + this.ref = attributes.ref || ""; + this.picture = null; + } +} + +class BindItems extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "bindItems"); + this.connection = attributes.connection || ""; + this.labelRef = attributes.labelRef || ""; + this.ref = attributes.ref || ""; + this.valueRef = attributes.valueRef || ""; + } +} + +class Bookend extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "bookend"); + this.id = attributes.id || ""; + this.leader = attributes.leader || ""; + this.trailer = attributes.trailer || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class BooleanElement extends Option01 { + constructor(attributes) { + super(TEMPLATE_NS_ID, "boolean"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$toHTML](availableSpace) { + return valueToHtml(this[$content] === 1 ? "1" : "0"); + } +} + +class Border extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "border", /* hasChildren = */ true); + this.break = getStringOption(attributes.break, ["close", "open"]); + this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); + this.id = attributes.id || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.corner = new XFAObjectArray(4); + this.edge = new XFAObjectArray(4); + this.extras = null; + this.fill = null; + this.margin = null; + } + + [$getExtra]() { + if (!this[$extra]) { + const edges = this.edge.children.slice(); + if (edges.length < 4) { + const defaultEdge = edges.at(-1) || new Edge({}); + for (let i = edges.length; i < 4; i++) { + edges.push(defaultEdge); + } + } + + const widths = edges.map(edge => edge.thickness); + const insets = [0, 0, 0, 0]; + if (this.margin) { + insets[0] = this.margin.topInset; + insets[1] = this.margin.rightInset; + insets[2] = this.margin.bottomInset; + insets[3] = this.margin.leftInset; + } + this[$extra] = { widths, insets, edges }; + } + return this[$extra]; + } + + [$toStyle]() { + // TODO: incomplete (hand). + const { edges } = this[$getExtra](); + const edgeStyles = edges.map(node => { + const style = node[$toStyle](); + style.color ||= "#000000"; + return style; + }); + + const style = Object.create(null); + if (this.margin) { + Object.assign(style, this.margin[$toStyle]()); + } + + if (this.fill?.presence === "visible") { + Object.assign(style, this.fill[$toStyle]()); + } + + if (this.corner.children.some(node => node.radius !== 0)) { + const cornerStyles = this.corner.children.map(node => node[$toStyle]()); + if (cornerStyles.length === 2 || cornerStyles.length === 3) { + const last = cornerStyles.at(-1); + for (let i = cornerStyles.length; i < 4; i++) { + cornerStyles.push(last); + } + } + + style.borderRadius = cornerStyles.map(s => s.radius).join(" "); + } + + switch (this.presence) { + case "invisible": + case "hidden": + style.borderStyle = ""; + break; + case "inactive": + style.borderStyle = "none"; + break; + default: + style.borderStyle = edgeStyles.map(s => s.style).join(" "); + break; + } + + style.borderWidth = edgeStyles.map(s => s.width).join(" "); + style.borderColor = edgeStyles.map(s => s.color).join(" "); + + return style; + } +} + +class Break extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "break", /* hasChildren = */ true); + this.after = getStringOption(attributes.after, [ + "auto", + "contentArea", + "pageArea", + "pageEven", + "pageOdd", + ]); + this.afterTarget = attributes.afterTarget || ""; + this.before = getStringOption(attributes.before, [ + "auto", + "contentArea", + "pageArea", + "pageEven", + "pageOdd", + ]); + this.beforeTarget = attributes.beforeTarget || ""; + this.bookendLeader = attributes.bookendLeader || ""; + this.bookendTrailer = attributes.bookendTrailer || ""; + this.id = attributes.id || ""; + this.overflowLeader = attributes.overflowLeader || ""; + this.overflowTarget = attributes.overflowTarget || ""; + this.overflowTrailer = attributes.overflowTrailer || ""; + this.startNew = getInteger({ + data: attributes.startNew, + defaultValue: 0, + validate: x => x === 1, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } +} + +class BreakAfter extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "breakAfter", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.leader = attributes.leader || ""; + this.startNew = getInteger({ + data: attributes.startNew, + defaultValue: 0, + validate: x => x === 1, + }); + this.target = attributes.target || ""; + this.targetType = getStringOption(attributes.targetType, [ + "auto", + "contentArea", + "pageArea", + ]); + this.trailer = attributes.trailer || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.script = null; + } +} + +class BreakBefore extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "breakBefore", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.leader = attributes.leader || ""; + this.startNew = getInteger({ + data: attributes.startNew, + defaultValue: 0, + validate: x => x === 1, + }); + this.target = attributes.target || ""; + this.targetType = getStringOption(attributes.targetType, [ + "auto", + "contentArea", + "pageArea", + ]); + this.trailer = attributes.trailer || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.script = null; + } + + [$toHTML](availableSpace) { + this[$extra] = {}; + return HTMLResult.FAILURE; + } +} + +class Button extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "button", /* hasChildren = */ true); + this.highlight = getStringOption(attributes.highlight, [ + "inverted", + "none", + "outline", + "push", + ]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } + + [$toHTML](availableSpace) { + // TODO: highlight. + + const parent = this[$getParent](); + const grandpa = parent[$getParent](); + const htmlButton = { + name: "button", + attributes: { + id: this[$uid], + class: ["xfaButton"], + style: {}, + }, + children: [], + }; + + for (const event of grandpa.event.children) { + // if (true) break; + if (event.activity !== "click" || !event.script) { + continue; + } + const jsURL = recoverJsURL(event.script[$content]); + if (!jsURL) { + continue; + } + const href = fixURL(jsURL.url); + if (!href) { + continue; + } + + // we've an url so generate a + htmlButton.children.push({ + name: "a", + attributes: { + id: "link" + this[$uid], + href, + newWindow: jsURL.newWindow, + class: ["xfaLink"], + style: {}, + }, + children: [], + }); + } + + return HTMLResult.success(htmlButton); + } +} + +class Calculate extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "calculate", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.override = getStringOption(attributes.override, [ + "disabled", + "error", + "ignore", + "warning", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.message = null; + this.script = null; + } +} + +class Caption extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "caption", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.placement = getStringOption(attributes.placement, [ + "left", + "bottom", + "inline", + "right", + "top", + ]); + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.reserve = Math.ceil(getMeasurement(attributes.reserve)); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.font = null; + this.margin = null; + this.para = null; + this.value = null; + } + + [$setValue](value) { + _setValue(this, value); + } + + [$getExtra](availableSpace) { + if (!this[$extra]) { + let { width, height } = availableSpace; + switch (this.placement) { + case "left": + case "right": + case "inline": + width = this.reserve <= 0 ? width : this.reserve; + break; + case "top": + case "bottom": + height = this.reserve <= 0 ? height : this.reserve; + break; + } + + this[$extra] = layoutNode(this, { width, height }); + } + return this[$extra]; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + if (!this.value) { + return HTMLResult.EMPTY; + } + + this[$pushPara](); + const value = this.value[$toHTML](availableSpace).html; + + if (!value) { + this[$popPara](); + return HTMLResult.EMPTY; + } + + const savedReserve = this.reserve; + if (this.reserve <= 0) { + const { w, h } = this[$getExtra](availableSpace); + switch (this.placement) { + case "left": + case "right": + case "inline": + this.reserve = w; + break; + case "top": + case "bottom": + this.reserve = h; + break; + } + } + + const children = []; + if (typeof value === "string") { + children.push({ + name: "#text", + value, + }); + } else { + children.push(value); + } + + const style = toStyle(this, "font", "margin", "visibility"); + switch (this.placement) { + case "left": + case "right": + if (this.reserve > 0) { + style.width = measureToString(this.reserve); + } + break; + case "top": + case "bottom": + if (this.reserve > 0) { + style.height = measureToString(this.reserve); + } + break; + } + + setPara(this, null, value); + this[$popPara](); + + this.reserve = savedReserve; + + return HTMLResult.success({ + name: "div", + attributes: { + style, + class: ["xfaCaption"], + }, + children, + }); + } +} + +class Certificate extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "certificate"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Certificates extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "certificates", /* hasChildren = */ true); + this.credentialServerPolicy = getStringOption( + attributes.credentialServerPolicy, + ["optional", "required"] + ); + this.id = attributes.id || ""; + this.url = attributes.url || ""; + this.urlPolicy = attributes.urlPolicy || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.encryption = null; + this.issuers = null; + this.keyUsage = null; + this.oids = null; + this.signing = null; + this.subjectDNs = null; + } +} + +class CheckButton extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "checkButton", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.mark = getStringOption(attributes.mark, [ + "default", + "check", + "circle", + "cross", + "diamond", + "square", + "star", + ]); + this.shape = getStringOption(attributes.shape, ["square", "round"]); + this.size = getMeasurement(attributes.size, "10pt"); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.extras = null; + this.margin = null; + } + + [$toHTML](availableSpace) { + // TODO: border, shape and mark. + + const style = toStyle("margin"); + const size = measureToString(this.size); + + style.width = style.height = size; + + let type; + let className; + let groupId; + const field = this[$getParent]()[$getParent](); + const items = + (field.items.children.length && + field.items.children[0][$toHTML]().html) || + []; + const exportedValue = { + on: (items[0] !== undefined ? items[0] : "on").toString(), + off: (items[1] !== undefined ? items[1] : "off").toString(), + }; + + const value = field.value?.[$text]() || "off"; + const checked = value === exportedValue.on || undefined; + const container = field[$getSubformParent](); + const fieldId = field[$uid]; + let dataId; + + if (container instanceof ExclGroup) { + groupId = container[$uid]; + type = "radio"; + className = "xfaRadio"; + dataId = container[$data]?.[$uid] || container[$uid]; + } else { + type = "checkbox"; + className = "xfaCheckbox"; + dataId = field[$data]?.[$uid] || field[$uid]; + } + + const input = { + name: "input", + attributes: { + class: [className], + style, + fieldId, + dataId, + type, + checked, + xfaOn: exportedValue.on, + xfaOff: exportedValue.off, + "aria-label": ariaLabel(field), + "aria-required": false, + }, + }; + + if (groupId) { + input.attributes.name = groupId; + } + + if (isRequired(field)) { + input.attributes["aria-required"] = true; + input.attributes.required = true; + } + + return HTMLResult.success({ + name: "label", + attributes: { + class: ["xfaLabel"], + }, + children: [input], + }); + } +} + +class ChoiceList extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "choiceList", /* hasChildren = */ true); + this.commitOn = getStringOption(attributes.commitOn, ["select", "exit"]); + this.id = attributes.id || ""; + this.open = getStringOption(attributes.open, [ + "userControl", + "always", + "multiSelect", + "onEntry", + ]); + this.textEntry = getInteger({ + data: attributes.textEntry, + defaultValue: 0, + validate: x => x === 1, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.extras = null; + this.margin = null; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + const style = toStyle(this, "border", "margin"); + const ui = this[$getParent](); + const field = ui[$getParent](); + const fontSize = field.font?.size || 10; + const optionStyle = { + fontSize: `calc(${fontSize}px * var(--total-scale-factor))`, + }; + const children = []; + + if (field.items.children.length > 0) { + const items = field.items; + let displayedIndex = 0; + let saveIndex = 0; + if (items.children.length === 2) { + displayedIndex = items.children[0].save; + saveIndex = 1 - displayedIndex; + } + const displayed = items.children[displayedIndex][$toHTML]().html; + const values = items.children[saveIndex][$toHTML]().html; + + let selected = false; + const value = field.value?.[$text]() || ""; + for (let i = 0, ii = displayed.length; i < ii; i++) { + const option = { + name: "option", + attributes: { + value: values[i] || displayed[i], + style: optionStyle, + }, + value: displayed[i], + }; + if (values[i] === value) { + option.attributes.selected = selected = true; + } + children.push(option); + } + + if (!selected) { + children.splice(0, 0, { + name: "option", + attributes: { + hidden: true, + selected: true, + }, + value: " ", + }); + } + } + + const selectAttributes = { + class: ["xfaSelect"], + fieldId: field[$uid], + dataId: field[$data]?.[$uid] || field[$uid], + style, + "aria-label": ariaLabel(field), + "aria-required": false, + }; + + if (isRequired(field)) { + selectAttributes["aria-required"] = true; + selectAttributes.required = true; + } + + if (this.open === "multiSelect") { + selectAttributes.multiple = true; + } + + return HTMLResult.success({ + name: "label", + attributes: { + class: ["xfaLabel"], + }, + children: [ + { + name: "select", + children, + attributes: selectAttributes, + }, + ], + }); + } +} + +class Color extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "color", /* hasChildren = */ true); + this.cSpace = getStringOption(attributes.cSpace, ["SRGB"]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.value = attributes.value ? getColor(attributes.value) : ""; + this.extras = null; + } + + [$hasSettableValue]() { + return false; + } + + [$toStyle]() { + return this.value + ? Util.makeHexColor(this.value.r, this.value.g, this.value.b) + : null; + } +} + +class Comb extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "comb"); + this.id = attributes.id || ""; + this.numberOfCells = getInteger({ + data: attributes.numberOfCells, + defaultValue: 0, + validate: x => x >= 0, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Connect extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "connect", /* hasChildren = */ true); + this.connection = attributes.connection || ""; + this.id = attributes.id || ""; + this.ref = attributes.ref || ""; + this.usage = getStringOption(attributes.usage, [ + "exportAndImport", + "exportOnly", + "importOnly", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.picture = null; + } +} + +class ContentArea extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "contentArea", /* hasChildren = */ true); + this.h = getMeasurement(attributes.h); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.w = getMeasurement(attributes.w); + this.x = getMeasurement(attributes.x, "0pt"); + this.y = getMeasurement(attributes.y, "0pt"); + this.desc = null; + this.extras = null; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + const left = measureToString(this.x); + const top = measureToString(this.y); + + const style = { + left, + top, + width: measureToString(this.w), + height: measureToString(this.h), + }; + + const classNames = ["xfaContentarea"]; + + if (isPrintOnly(this)) { + classNames.push("xfaPrintOnly"); + } + + return HTMLResult.success({ + name: "div", + children: [], + attributes: { + style, + class: classNames, + id: this[$uid], + }, + }); + } +} + +class Corner extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "corner", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.inverted = getInteger({ + data: attributes.inverted, + defaultValue: 0, + validate: x => x === 1, + }); + this.join = getStringOption(attributes.join, ["square", "round"]); + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.radius = getMeasurement(attributes.radius); + this.stroke = getStringOption(attributes.stroke, [ + "solid", + "dashDot", + "dashDotDot", + "dashed", + "dotted", + "embossed", + "etched", + "lowered", + "raised", + ]); + this.thickness = getMeasurement(attributes.thickness, "0.5pt"); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + } + + [$toStyle]() { + // In using CSS it's only possible to handle radius + // (at least with basic css). + // Is there a real use (interest ?) of all these properties ? + // Maybe it's possible to implement them using svg and border-image... + // TODO: implement all the missing properties. + const style = toStyle(this, "visibility"); + style.radius = measureToString(this.join === "square" ? 0 : this.radius); + return style; + } +} + +class DateElement extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "date"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + const date = this[$content].trim(); + this[$content] = date ? new Date(date) : null; + } + + [$toHTML](availableSpace) { + return valueToHtml(this[$content] ? this[$content].toString() : ""); + } +} + +class DateTime extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "dateTime"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + const date = this[$content].trim(); + this[$content] = date ? new Date(date) : null; + } + + [$toHTML](availableSpace) { + return valueToHtml(this[$content] ? this[$content].toString() : ""); + } +} + +class DateTimeEdit extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "dateTimeEdit", /* hasChildren = */ true); + this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, [ + "auto", + "off", + "on", + ]); + this.id = attributes.id || ""; + this.picker = getStringOption(attributes.picker, ["host", "none"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.comb = null; + this.extras = null; + this.margin = null; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + // When the picker is host we should use type=date for the input + // but we need to put the buttons outside the text-field. + const style = toStyle(this, "border", "font", "margin"); + const field = this[$getParent]()[$getParent](); + const html = { + name: "input", + attributes: { + type: "text", + fieldId: field[$uid], + dataId: field[$data]?.[$uid] || field[$uid], + class: ["xfaTextfield"], + style, + "aria-label": ariaLabel(field), + "aria-required": false, + }, + }; + + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + + return HTMLResult.success({ + name: "label", + attributes: { + class: ["xfaLabel"], + }, + children: [html], + }); + } +} + +class Decimal extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "decimal"); + this.fracDigits = getInteger({ + data: attributes.fracDigits, + defaultValue: 2, + validate: x => true, + }); + this.id = attributes.id || ""; + this.leadDigits = getInteger({ + data: attributes.leadDigits, + defaultValue: -1, + validate: x => true, + }); + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + const number = parseFloat(this[$content].trim()); + this[$content] = isNaN(number) ? null : number; + } + + [$toHTML](availableSpace) { + return valueToHtml( + this[$content] !== null ? this[$content].toString() : "" + ); + } +} + +class DefaultUi extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "defaultUi", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } +} + +class Desc extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "desc", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.boolean = new XFAObjectArray(); + this.date = new XFAObjectArray(); + this.dateTime = new XFAObjectArray(); + this.decimal = new XFAObjectArray(); + this.exData = new XFAObjectArray(); + this.float = new XFAObjectArray(); + this.image = new XFAObjectArray(); + this.integer = new XFAObjectArray(); + this.text = new XFAObjectArray(); + this.time = new XFAObjectArray(); + } +} + +class DigestMethod extends OptionObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "digestMethod", [ + "", + "SHA1", + "SHA256", + "SHA512", + "RIPEMD160", + ]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class DigestMethods extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "digestMethods", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.digestMethod = new XFAObjectArray(); + } +} + +class Draw extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "draw", /* hasChildren = */ true); + this.anchorType = getStringOption(attributes.anchorType, [ + "topLeft", + "bottomCenter", + "bottomLeft", + "bottomRight", + "middleCenter", + "middleLeft", + "middleRight", + "topCenter", + "topRight", + ]); + this.colSpan = getInteger({ + data: attributes.colSpan, + defaultValue: 1, + validate: n => n >= 1 || n === -1, + }); + this.h = attributes.h ? getMeasurement(attributes.h) : ""; + this.hAlign = getStringOption(attributes.hAlign, [ + "left", + "center", + "justify", + "justifyAll", + "radix", + "right", + ]); + this.id = attributes.id || ""; + this.locale = attributes.locale || ""; + this.maxH = getMeasurement(attributes.maxH, "0pt"); + this.maxW = getMeasurement(attributes.maxW, "0pt"); + this.minH = getMeasurement(attributes.minH, "0pt"); + this.minW = getMeasurement(attributes.minW, "0pt"); + this.name = attributes.name || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.relevant = getRelevant(attributes.relevant); + this.rotate = getInteger({ + data: attributes.rotate, + defaultValue: 0, + validate: x => x % 90 === 0, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.w = attributes.w ? getMeasurement(attributes.w) : ""; + this.x = getMeasurement(attributes.x, "0pt"); + this.y = getMeasurement(attributes.y, "0pt"); + this.assist = null; + this.border = null; + this.caption = null; + this.desc = null; + this.extras = null; + this.font = null; + this.keep = null; + this.margin = null; + this.para = null; + this.traversal = null; + this.ui = null; + this.value = null; + this.setProperty = new XFAObjectArray(); + } + + [$setValue](value) { + _setValue(this, value); + } + + [$toHTML](availableSpace) { + setTabIndex(this); + + if (this.presence === "hidden" || this.presence === "inactive") { + return HTMLResult.EMPTY; + } + + fixDimensions(this); + this[$pushPara](); + + // If at least one dimension is missing and we've a text + // then we can guess it in laying out the text. + const savedW = this.w; + const savedH = this.h; + const { w, h, isBroken } = layoutNode(this, availableSpace); + if (w && this.w === "") { + // If the parent layout is lr-tb with a w=100 and we already have a child + // which takes 90 on the current line. + // If we have a text with a length (in px) equal to 100 then it'll be + // splitted into almost 10 chunks: so it won't be nice. + // So if we've potentially more width to provide in some parent containers + // let's increase it to give a chance to have a better rendering. + if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) { + this[$popPara](); + return HTMLResult.FAILURE; + } + + this.w = w; + } + if (h && this.h === "") { + this.h = h; + } + + setFirstUnsplittable(this); + if (!checkDimensions(this, availableSpace)) { + this.w = savedW; + this.h = savedH; + this[$popPara](); + return HTMLResult.FAILURE; + } + unsetFirstUnsplittable(this); + + const style = toStyle( + this, + "font", + "hAlign", + "dimensions", + "position", + "presence", + "rotate", + "anchorType", + "border", + "margin" + ); + + setMinMaxDimensions(this, style); + + if (style.margin) { + style.padding = style.margin; + delete style.margin; + } + + const classNames = ["xfaDraw"]; + if (this.font) { + classNames.push("xfaFont"); + } + if (isPrintOnly(this)) { + classNames.push("xfaPrintOnly"); + } + + const attributes = { + style, + id: this[$uid], + class: classNames, + }; + + if (this.name) { + attributes.xfaName = this.name; + } + + const html = { + name: "div", + attributes, + children: [], + }; + + applyAssist(this, attributes); + + const bbox = computeBbox(this, html, availableSpace); + + const value = this.value ? this.value[$toHTML](availableSpace).html : null; + if (value === null) { + this.w = savedW; + this.h = savedH; + this[$popPara](); + return HTMLResult.success(createWrapper(this, html), bbox); + } + + html.children.push(value); + setPara(this, style, value); + + this.w = savedW; + this.h = savedH; + + this[$popPara](); + return HTMLResult.success(createWrapper(this, html), bbox); + } +} + +class Edge extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "edge", /* hasChildren = */ true); + this.cap = getStringOption(attributes.cap, ["square", "butt", "round"]); + this.id = attributes.id || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.stroke = getStringOption(attributes.stroke, [ + "solid", + "dashDot", + "dashDotDot", + "dashed", + "dotted", + "embossed", + "etched", + "lowered", + "raised", + ]); + this.thickness = getMeasurement(attributes.thickness, "0.5pt"); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + } + + [$toStyle]() { + // TODO: dashDot & dashDotDot. + const style = toStyle(this, "visibility"); + Object.assign(style, { + linecap: this.cap, + width: measureToString(this.thickness), + color: this.color ? this.color[$toStyle]() : "#000000", + style: "", + }); + + if (this.presence !== "visible") { + style.style = "none"; + } else { + switch (this.stroke) { + case "solid": + style.style = "solid"; + break; + case "dashDot": + style.style = "dashed"; + break; + case "dashDotDot": + style.style = "dashed"; + break; + case "dashed": + style.style = "dashed"; + break; + case "dotted": + style.style = "dotted"; + break; + case "embossed": + style.style = "ridge"; + break; + case "etched": + style.style = "groove"; + break; + case "lowered": + style.style = "inset"; + break; + case "raised": + style.style = "outset"; + break; + } + } + return style; + } +} + +class Encoding extends OptionObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encoding", [ + "adbe.x509.rsa_sha1", + "adbe.pkcs7.detached", + "adbe.pkcs7.sha1", + ]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Encodings extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encodings", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.encoding = new XFAObjectArray(); + } +} + +class Encrypt extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encrypt", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.certificate = null; + } +} + +class EncryptData extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encryptData", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.operation = getStringOption(attributes.operation, [ + "encrypt", + "decrypt", + ]); + this.target = attributes.target || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.filter = null; + this.manifest = null; + } +} + +class Encryption extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encryption", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.certificate = new XFAObjectArray(); + } +} + +class EncryptionMethod extends OptionObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encryptionMethod", [ + "", + "AES256-CBC", + "TRIPLEDES-CBC", + "AES128-CBC", + "AES192-CBC", + ]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class EncryptionMethods extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "encryptionMethods", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.encryptionMethod = new XFAObjectArray(); + } +} + +class Event extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "event", /* hasChildren = */ true); + this.activity = getStringOption(attributes.activity, [ + "click", + "change", + "docClose", + "docReady", + "enter", + "exit", + "full", + "indexChange", + "initialize", + "mouseDown", + "mouseEnter", + "mouseExit", + "mouseUp", + "postExecute", + "postOpen", + "postPrint", + "postSave", + "postSign", + "postSubmit", + "preExecute", + "preOpen", + "prePrint", + "preSave", + "preSign", + "preSubmit", + "ready", + "validationState", + ]); + this.id = attributes.id || ""; + this.listen = getStringOption(attributes.listen, [ + "refOnly", + "refAndDescendents", + ]); + this.name = attributes.name || ""; + this.ref = attributes.ref || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + + // One-of properties + this.encryptData = null; + this.execute = null; + this.script = null; + this.signData = null; + this.submit = null; + } +} + +class ExData extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "exData"); + this.contentType = attributes.contentType || ""; + this.href = attributes.href || ""; + this.id = attributes.id || ""; + this.maxLength = getInteger({ + data: attributes.maxLength, + defaultValue: -1, + validate: x => x >= -1, + }); + this.name = attributes.name || ""; + this.rid = attributes.rid || ""; + this.transferEncoding = getStringOption(attributes.transferEncoding, [ + "none", + "base64", + "package", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$isCDATAXml]() { + return this.contentType === "text/html"; + } + + [$onChild](child) { + if ( + this.contentType === "text/html" && + child[$namespaceId] === NamespaceIds.xhtml.id + ) { + this[$content] = child; + return true; + } + + if (this.contentType === "text/xml") { + this[$content] = child; + return true; + } + + return false; + } + + [$toHTML](availableSpace) { + if (this.contentType !== "text/html" || !this[$content]) { + // TODO: fix other cases. + return HTMLResult.EMPTY; + } + + return this[$content][$toHTML](availableSpace); + } +} + +class ExObject extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "exObject", /* hasChildren = */ true); + this.archive = attributes.archive || ""; + this.classId = attributes.classId || ""; + this.codeBase = attributes.codeBase || ""; + this.codeType = attributes.codeType || ""; + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.boolean = new XFAObjectArray(); + this.date = new XFAObjectArray(); + this.dateTime = new XFAObjectArray(); + this.decimal = new XFAObjectArray(); + this.exData = new XFAObjectArray(); + this.exObject = new XFAObjectArray(); + this.float = new XFAObjectArray(); + this.image = new XFAObjectArray(); + this.integer = new XFAObjectArray(); + this.text = new XFAObjectArray(); + this.time = new XFAObjectArray(); + } +} + +class ExclGroup extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "exclGroup", /* hasChildren = */ true); + this.access = getStringOption(attributes.access, [ + "open", + "nonInteractive", + "protected", + "readOnly", + ]); + this.accessKey = attributes.accessKey || ""; + this.anchorType = getStringOption(attributes.anchorType, [ + "topLeft", + "bottomCenter", + "bottomLeft", + "bottomRight", + "middleCenter", + "middleLeft", + "middleRight", + "topCenter", + "topRight", + ]); + this.colSpan = getInteger({ + data: attributes.colSpan, + defaultValue: 1, + validate: n => n >= 1 || n === -1, + }); + this.h = attributes.h ? getMeasurement(attributes.h) : ""; + this.hAlign = getStringOption(attributes.hAlign, [ + "left", + "center", + "justify", + "justifyAll", + "radix", + "right", + ]); + this.id = attributes.id || ""; + this.layout = getStringOption(attributes.layout, [ + "position", + "lr-tb", + "rl-row", + "rl-tb", + "row", + "table", + "tb", + ]); + this.maxH = getMeasurement(attributes.maxH, "0pt"); + this.maxW = getMeasurement(attributes.maxW, "0pt"); + this.minH = getMeasurement(attributes.minH, "0pt"); + this.minW = getMeasurement(attributes.minW, "0pt"); + this.name = attributes.name || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.w = attributes.w ? getMeasurement(attributes.w) : ""; + this.x = getMeasurement(attributes.x, "0pt"); + this.y = getMeasurement(attributes.y, "0pt"); + this.assist = null; + this.bind = null; + this.border = null; + this.calculate = null; + this.caption = null; + this.desc = null; + this.extras = null; + this.margin = null; + this.para = null; + this.traversal = null; + this.validate = null; + this.connect = new XFAObjectArray(); + this.event = new XFAObjectArray(); + this.field = new XFAObjectArray(); + this.setProperty = new XFAObjectArray(); + } + + [$isBindable]() { + return true; + } + + [$hasSettableValue]() { + return true; + } + + [$setValue](value) { + for (const field of this.field.children) { + if (!field.value) { + const nodeValue = new Value({}); + field[$appendChild](nodeValue); + field.value = nodeValue; + } + + field.value[$setValue](value); + } + } + + [$isThereMoreWidth]() { + return ( + (this.layout.endsWith("-tb") && + this[$extra].attempt === 0 && + this[$extra].numberInLine > 0) || + this[$getParent]()[$isThereMoreWidth]() + ); + } + + [$isSplittable]() { + // We cannot cache the result here because the contentArea + // can change. + const parent = this[$getSubformParent](); + if (!parent[$isSplittable]()) { + return false; + } + + if (this[$extra]._isSplittable !== undefined) { + return this[$extra]._isSplittable; + } + + if (this.layout === "position" || this.layout.includes("row")) { + this[$extra]._isSplittable = false; + return false; + } + + if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) { + // See comment in Subform::[$isSplittable] for an explanation. + return false; + } + + this[$extra]._isSplittable = true; + return true; + } + + [$flushHTML]() { + return flushHTML(this); + } + + [$addHTML](html, bbox) { + addHTML(this, html, bbox); + } + + [$getAvailableSpace]() { + return getAvailableSpace(this); + } + + [$toHTML](availableSpace) { + setTabIndex(this); + if ( + this.presence === "hidden" || + this.presence === "inactive" || + this.h === 0 || + this.w === 0 + ) { + return HTMLResult.EMPTY; + } + + fixDimensions(this); + + const children = []; + const attributes = { + id: this[$uid], + class: [], + }; + + setAccess(this, attributes.class); + + if (!this[$extra]) { + this[$extra] = Object.create(null); + } + + Object.assign(this[$extra], { + children, + attributes, + attempt: 0, + line: null, + numberInLine: 0, + availableSpace: { + width: Math.min(this.w || Infinity, availableSpace.width), + height: Math.min(this.h || Infinity, availableSpace.height), + }, + width: 0, + height: 0, + prevHeight: 0, + currentWidth: 0, + }); + + const isSplittable = this[$isSplittable](); + if (!isSplittable) { + setFirstUnsplittable(this); + } + + if (!checkDimensions(this, availableSpace)) { + return HTMLResult.FAILURE; + } + const filter = new Set(["field"]); + + if (this.layout.includes("row")) { + const columnWidths = this[$getSubformParent]().columnWidths; + if (Array.isArray(columnWidths) && columnWidths.length > 0) { + this[$extra].columnWidths = columnWidths; + this[$extra].currentColumn = 0; + } + } + + const style = toStyle( + this, + "anchorType", + "dimensions", + "position", + "presence", + "border", + "margin", + "hAlign" + ); + const classNames = ["xfaExclgroup"]; + const cl = layoutClass(this); + if (cl) { + classNames.push(cl); + } + + if (isPrintOnly(this)) { + classNames.push("xfaPrintOnly"); + } + + attributes.style = style; + attributes.class = classNames; + + if (this.name) { + attributes.xfaName = this.name; + } + + this[$pushPara](); + const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb"; + const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1; + for (; this[$extra].attempt < maxRun; this[$extra].attempt++) { + if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) { + // If the layout is lr-tb then having attempt equals to + // MAX_ATTEMPTS_FOR_LRTB_LAYOUT-1 means that we're trying to layout + // on the next line so this on is empty. + this[$extra].numberInLine = 0; + } + const result = this[$childrenToHTML]({ + filter, + include: true, + }); + if (result.success) { + break; + } + if (result.isBreak()) { + this[$popPara](); + return result; + } + if ( + isLrTb && + this[$extra].attempt === 0 && + this[$extra].numberInLine === 0 && + !this[$getTemplateRoot]()[$extra].noLayoutFailure + ) { + // See comment in Subform::[$toHTML]. + this[$extra].attempt = maxRun; + break; + } + } + + this[$popPara](); + + if (!isSplittable) { + unsetFirstUnsplittable(this); + } + + if (this[$extra].attempt === maxRun) { + if (!isSplittable) { + delete this[$extra]; + } + return HTMLResult.FAILURE; + } + + let marginH = 0; + let marginV = 0; + if (this.margin) { + marginH = this.margin.leftInset + this.margin.rightInset; + marginV = this.margin.topInset + this.margin.bottomInset; + } + + const width = Math.max(this[$extra].width + marginH, this.w || 0); + const height = Math.max(this[$extra].height + marginV, this.h || 0); + const bbox = [this.x, this.y, width, height]; + + if (this.w === "") { + style.width = measureToString(width); + } + if (this.h === "") { + style.height = measureToString(height); + } + + const html = { + name: "div", + attributes, + children, + }; + + applyAssist(this, attributes); + + delete this[$extra]; + + return HTMLResult.success(createWrapper(this, html), bbox); + } +} + +class Execute extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "execute"); + this.connection = attributes.connection || ""; + this.executeType = getStringOption(attributes.executeType, [ + "import", + "remerge", + ]); + this.id = attributes.id || ""; + this.runAt = getStringOption(attributes.runAt, [ + "client", + "both", + "server", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Extras extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "extras", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.boolean = new XFAObjectArray(); + this.date = new XFAObjectArray(); + this.dateTime = new XFAObjectArray(); + this.decimal = new XFAObjectArray(); + this.exData = new XFAObjectArray(); + this.extras = new XFAObjectArray(); + this.float = new XFAObjectArray(); + this.image = new XFAObjectArray(); + this.integer = new XFAObjectArray(); + this.text = new XFAObjectArray(); + this.time = new XFAObjectArray(); + } + + // (Spec) The XFA template grammar defines the extras and desc elements, + // which can be used to add human-readable or machine-readable + // data to a template. +} + +class Field extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "field", /* hasChildren = */ true); + this.access = getStringOption(attributes.access, [ + "open", + "nonInteractive", + "protected", + "readOnly", + ]); + this.accessKey = attributes.accessKey || ""; + this.anchorType = getStringOption(attributes.anchorType, [ + "topLeft", + "bottomCenter", + "bottomLeft", + "bottomRight", + "middleCenter", + "middleLeft", + "middleRight", + "topCenter", + "topRight", + ]); + this.colSpan = getInteger({ + data: attributes.colSpan, + defaultValue: 1, + validate: n => n >= 1 || n === -1, + }); + this.h = attributes.h ? getMeasurement(attributes.h) : ""; + this.hAlign = getStringOption(attributes.hAlign, [ + "left", + "center", + "justify", + "justifyAll", + "radix", + "right", + ]); + this.id = attributes.id || ""; + this.locale = attributes.locale || ""; + this.maxH = getMeasurement(attributes.maxH, "0pt"); + this.maxW = getMeasurement(attributes.maxW, "0pt"); + this.minH = getMeasurement(attributes.minH, "0pt"); + this.minW = getMeasurement(attributes.minW, "0pt"); + this.name = attributes.name || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.relevant = getRelevant(attributes.relevant); + this.rotate = getInteger({ + data: attributes.rotate, + defaultValue: 0, + validate: x => x % 90 === 0, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.w = attributes.w ? getMeasurement(attributes.w) : ""; + this.x = getMeasurement(attributes.x, "0pt"); + this.y = getMeasurement(attributes.y, "0pt"); + this.assist = null; + this.bind = null; + this.border = null; + this.calculate = null; + this.caption = null; + this.desc = null; + this.extras = null; + this.font = null; + this.format = null; + // For a choice list, one list is used to have display entries + // and the other for the exported values + this.items = new XFAObjectArray(2); + this.keep = null; + this.margin = null; + this.para = null; + this.traversal = null; + this.ui = null; + this.validate = null; + this.value = null; + this.bindItems = new XFAObjectArray(); + this.connect = new XFAObjectArray(); + this.event = new XFAObjectArray(); + this.setProperty = new XFAObjectArray(); + } + + [$isBindable]() { + return true; + } + + [$setValue](value) { + _setValue(this, value); + } + + [$toHTML](availableSpace) { + setTabIndex(this); + + if (!this.ui) { + // It's allowed to not have an ui, specs say: + // If the UI element contains no children or is not present, + // the application chooses a default user interface for the + // container, based on the type of the container's content. + + this.ui = new Ui({}); + this.ui[$globalData] = this[$globalData]; + this[$appendChild](this.ui); + let node; + + // The items element can have 2 element max and + // according to the items specs it's likely a good + // way to guess the correct ui type. + switch (this.items.children.length) { + case 0: + node = new TextEdit({}); + this.ui.textEdit = node; + break; + case 1: + node = new CheckButton({}); + this.ui.checkButton = node; + break; + case 2: + node = new ChoiceList({}); + this.ui.choiceList = node; + break; + } + this.ui[$appendChild](node); + } + + if ( + !this.ui || + this.presence === "hidden" || + this.presence === "inactive" || + this.h === 0 || + this.w === 0 + ) { + return HTMLResult.EMPTY; + } + + if (this.caption) { + // Maybe we already tried to layout this field with + // another availableSpace, so to avoid to use the cached + // value just delete it. + delete this.caption[$extra]; + } + + this[$pushPara](); + + const caption = this.caption + ? this.caption[$toHTML](availableSpace).html + : null; + const savedW = this.w; + const savedH = this.h; + let marginH = 0; + let marginV = 0; + if (this.margin) { + marginH = this.margin.leftInset + this.margin.rightInset; + marginV = this.margin.topInset + this.margin.bottomInset; + } + + let borderDims = null; + if (this.w === "" || this.h === "") { + let width = null; + let height = null; + + let uiW = 0; + let uiH = 0; + if (this.ui.checkButton) { + uiW = uiH = this.ui.checkButton.size; + } else { + const { w, h } = layoutNode(this, availableSpace); + if (w !== null) { + uiW = w; + uiH = h; + } else { + uiH = getMetrics(this.font, /* real = */ true).lineNoGap; + } + } + + borderDims = getBorderDims(this.ui[$getExtra]()); + uiW += borderDims.w; + uiH += borderDims.h; + + if (this.caption) { + const { w, h, isBroken } = this.caption[$getExtra](availableSpace); + // See comment in Draw::[$toHTML] to have an explanation + // about this line. + if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) { + this[$popPara](); + return HTMLResult.FAILURE; + } + + width = w; + height = h; + + switch (this.caption.placement) { + case "left": + case "right": + case "inline": + width += uiW; + break; + case "top": + case "bottom": + height += uiH; + break; + } + } else { + width = uiW; + height = uiH; + } + + if (width && this.w === "") { + width += marginH; + this.w = Math.min( + this.maxW <= 0 ? Infinity : this.maxW, + this.minW + 1 < width ? width : this.minW + ); + } + + if (height && this.h === "") { + height += marginV; + this.h = Math.min( + this.maxH <= 0 ? Infinity : this.maxH, + this.minH + 1 < height ? height : this.minH + ); + } + } + + this[$popPara](); + + fixDimensions(this); + + setFirstUnsplittable(this); + if (!checkDimensions(this, availableSpace)) { + this.w = savedW; + this.h = savedH; + this[$popPara](); + return HTMLResult.FAILURE; + } + unsetFirstUnsplittable(this); + + const style = toStyle( + this, + "font", + "dimensions", + "position", + "rotate", + "anchorType", + "presence", + "margin", + "hAlign" + ); + + setMinMaxDimensions(this, style); + + const classNames = ["xfaField"]; + // If no font, font properties are inherited. + if (this.font) { + classNames.push("xfaFont"); + } + + if (isPrintOnly(this)) { + classNames.push("xfaPrintOnly"); + } + + const attributes = { + style, + id: this[$uid], + class: classNames, + }; + + if (style.margin) { + style.padding = style.margin; + delete style.margin; + } + + setAccess(this, classNames); + + if (this.name) { + attributes.xfaName = this.name; + } + + const children = []; + const html = { + name: "div", + attributes, + children, + }; + + applyAssist(this, attributes); + + const borderStyle = this.border ? this.border[$toStyle]() : null; + const bbox = computeBbox(this, html, availableSpace); + const ui = this.ui[$toHTML]().html; + if (!ui) { + Object.assign(style, borderStyle); + return HTMLResult.success(createWrapper(this, html), bbox); + } + + if (this[$tabIndex]) { + if (ui.children?.[0]) { + ui.children[0].attributes.tabindex = this[$tabIndex]; + } else { + ui.attributes.tabindex = this[$tabIndex]; + } + } + + if (!ui.attributes.style) { + ui.attributes.style = Object.create(null); + } + + let aElement = null; + + if (this.ui.button) { + if (ui.children.length === 1) { + [aElement] = ui.children.splice(0, 1); + } + Object.assign(ui.attributes.style, borderStyle); + } else { + Object.assign(style, borderStyle); + } + + children.push(ui); + + if (this.value) { + if (this.ui.imageEdit) { + ui.children.push(this.value[$toHTML]().html); + } else if (!this.ui.button) { + let value = ""; + if (this.value.exData) { + value = this.value.exData[$text](); + } else if (this.value.text) { + value = this.value.text[$getExtra](); + } else { + const htmlValue = this.value[$toHTML]().html; + if (htmlValue !== null) { + value = htmlValue.children[0].value; + } + } + if (this.ui.textEdit && this.value.text?.maxChars) { + ui.children[0].attributes.maxLength = this.value.text.maxChars; + } + + if (value) { + if (this.ui.numericEdit) { + value = parseFloat(value); + value = isNaN(value) ? "" : value.toString(); + } + + if (ui.children[0].name === "textarea") { + ui.children[0].attributes.textContent = value; + } else { + ui.children[0].attributes.value = value; + } + } + } + } + + if (!this.ui.imageEdit && ui.children?.[0] && this.h) { + borderDims = borderDims || getBorderDims(this.ui[$getExtra]()); + + let captionHeight = 0; + if (this.caption && ["top", "bottom"].includes(this.caption.placement)) { + captionHeight = this.caption.reserve; + if (captionHeight <= 0) { + captionHeight = this.caption[$getExtra](availableSpace).h; + } + const inputHeight = this.h - captionHeight - marginV - borderDims.h; + ui.children[0].attributes.style.height = measureToString(inputHeight); + } else { + ui.children[0].attributes.style.height = "100%"; + } + } + + if (aElement) { + ui.children.push(aElement); + } + + if (!caption) { + if (ui.attributes.class) { + // Even if no caption this class will help to center the ui. + ui.attributes.class.push("xfaLeft"); + } + this.w = savedW; + this.h = savedH; + + return HTMLResult.success(createWrapper(this, html), bbox); + } + + if (this.ui.button) { + if (style.padding) { + delete style.padding; + } + if (caption.name === "div") { + caption.name = "span"; + } + ui.children.push(caption); + return HTMLResult.success(html, bbox); + } else if (this.ui.checkButton) { + caption.attributes.class[0] = "xfaCaptionForCheckButton"; + } + + if (!ui.attributes.class) { + ui.attributes.class = []; + } + + ui.children.splice(0, 0, caption); + + switch (this.caption.placement) { + case "left": + ui.attributes.class.push("xfaLeft"); + break; + case "right": + ui.attributes.class.push("xfaRight"); + break; + case "top": + ui.attributes.class.push("xfaTop"); + break; + case "bottom": + ui.attributes.class.push("xfaBottom"); + break; + case "inline": + // TODO; + ui.attributes.class.push("xfaLeft"); + break; + } + + this.w = savedW; + this.h = savedH; + return HTMLResult.success(createWrapper(this, html), bbox); + } +} + +class Fill extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "fill", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + + // One-of properties or none + this.linear = null; + this.pattern = null; + this.radial = null; + this.solid = null; + this.stipple = null; + } + + [$toStyle]() { + const parent = this[$getParent](); + const grandpa = parent[$getParent](); + const ggrandpa = grandpa[$getParent](); + const style = Object.create(null); + + // Use for color, i.e. #... + let propName = "color"; + + // Use for non-color, i.e. gradient, radial-gradient... + let altPropName = propName; + + if (parent instanceof Border) { + propName = "background-color"; + altPropName = "background"; + if (ggrandpa instanceof Ui) { + // The default fill color is white. + style.backgroundColor = "white"; + } + } + if (parent instanceof Rectangle || parent instanceof Arc) { + propName = altPropName = "fill"; + style.fill = "white"; + } + + for (const name of Object.getOwnPropertyNames(this)) { + if (name === "extras" || name === "color") { + continue; + } + const obj = this[name]; + if (!(obj instanceof XFAObject)) { + continue; + } + + const color = obj[$toStyle](this.color); + if (color) { + style[color.startsWith("#") ? propName : altPropName] = color; + } + return style; + } + + if (this.color?.value) { + const color = this.color[$toStyle](); + style[color.startsWith("#") ? propName : altPropName] = color; + } + + return style; + } +} + +class Filter extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "filter", /* hasChildren = */ true); + this.addRevocationInfo = getStringOption(attributes.addRevocationInfo, [ + "", + "required", + "optional", + "none", + ]); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.version = getInteger({ + data: this.version, + defaultValue: 5, + validate: x => x >= 1 && x <= 5, + }); + this.appearanceFilter = null; + this.certificates = null; + this.digestMethods = null; + this.encodings = null; + this.encryptionMethods = null; + this.handler = null; + this.lockDocument = null; + this.mdp = null; + this.reasons = null; + this.timeStamp = null; + } +} + +class Float extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "float"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + const number = parseFloat(this[$content].trim()); + this[$content] = isNaN(number) ? null : number; + } + + [$toHTML](availableSpace) { + return valueToHtml( + this[$content] !== null ? this[$content].toString() : "" + ); + } +} + +class Font extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "font", /* hasChildren = */ true); + this.baselineShift = getMeasurement(attributes.baselineShift); + this.fontHorizontalScale = getFloat({ + data: attributes.fontHorizontalScale, + defaultValue: 100, + validate: x => x >= 0, + }); + this.fontVerticalScale = getFloat({ + data: attributes.fontVerticalScale, + defaultValue: 100, + validate: x => x >= 0, + }); + this.id = attributes.id || ""; + this.kerningMode = getStringOption(attributes.kerningMode, [ + "none", + "pair", + ]); + this.letterSpacing = getMeasurement(attributes.letterSpacing, "0"); + this.lineThrough = getInteger({ + data: attributes.lineThrough, + defaultValue: 0, + validate: x => x === 1 || x === 2, + }); + this.lineThroughPeriod = getStringOption(attributes.lineThroughPeriod, [ + "all", + "word", + ]); + this.overline = getInteger({ + data: attributes.overline, + defaultValue: 0, + validate: x => x === 1 || x === 2, + }); + this.overlinePeriod = getStringOption(attributes.overlinePeriod, [ + "all", + "word", + ]); + this.posture = getStringOption(attributes.posture, ["normal", "italic"]); + this.size = getMeasurement(attributes.size, "10pt"); + this.typeface = attributes.typeface || "Courier"; + this.underline = getInteger({ + data: attributes.underline, + defaultValue: 0, + validate: x => x === 1 || x === 2, + }); + this.underlinePeriod = getStringOption(attributes.underlinePeriod, [ + "all", + "word", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.weight = getStringOption(attributes.weight, ["normal", "bold"]); + this.extras = null; + this.fill = null; + } + + [$clean](builder) { + super[$clean](builder); + this[$globalData].usedTypefaces.add(this.typeface); + } + + [$toStyle]() { + const style = toStyle(this, "fill"); + const color = style.color; + if (color) { + if (color === "#000000") { + // Default font color. + delete style.color; + } else if (!color.startsWith("#")) { + // We've a gradient which is not possible for a font color + // so use a workaround. + style.background = color; + style.backgroundClip = "text"; + style.color = "transparent"; + } + } + + if (this.baselineShift) { + style.verticalAlign = measureToString(this.baselineShift); + } + + // TODO: fontHorizontalScale + // TODO: fontVerticalScale + + style.fontKerning = this.kerningMode === "none" ? "none" : "normal"; + style.letterSpacing = measureToString(this.letterSpacing); + + if (this.lineThrough !== 0) { + style.textDecoration = "line-through"; + if (this.lineThrough === 2) { + style.textDecorationStyle = "double"; + } + } + + // TODO: lineThroughPeriod + + if (this.overline !== 0) { + style.textDecoration = "overline"; + if (this.overline === 2) { + style.textDecorationStyle = "double"; + } + } + + // TODO: overlinePeriod + + style.fontStyle = this.posture; + style.fontSize = measureToString(0.99 * this.size); + + setFontFamily(this, this, this[$globalData].fontFinder, style); + + if (this.underline !== 0) { + style.textDecoration = "underline"; + if (this.underline === 2) { + style.textDecorationStyle = "double"; + } + } + + // TODO: underlinePeriod + + style.fontWeight = this.weight; + + return style; + } +} + +class Format extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "format", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.picture = null; + } +} + +class Handler extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "handler"); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Hyphenation extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "hyphenation"); + this.excludeAllCaps = getInteger({ + data: attributes.excludeAllCaps, + defaultValue: 0, + validate: x => x === 1, + }); + this.excludeInitialCap = getInteger({ + data: attributes.excludeInitialCap, + defaultValue: 0, + validate: x => x === 1, + }); + this.hyphenate = getInteger({ + data: attributes.hyphenate, + defaultValue: 0, + validate: x => x === 1, + }); + this.id = attributes.id || ""; + this.pushCharacterCount = getInteger({ + data: attributes.pushCharacterCount, + defaultValue: 3, + validate: x => x >= 0, + }); + this.remainCharacterCount = getInteger({ + data: attributes.remainCharacterCount, + defaultValue: 3, + validate: x => x >= 0, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.wordCharacterCount = getInteger({ + data: attributes.wordCharacterCount, + defaultValue: 7, + validate: x => x >= 0, + }); + } +} + +class Image extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "image"); + this.aspect = getStringOption(attributes.aspect, [ + "fit", + "actual", + "height", + "none", + "width", + ]); + this.contentType = attributes.contentType || ""; + this.href = attributes.href || ""; + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.transferEncoding = getStringOption(attributes.transferEncoding, [ + "base64", + "none", + "package", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$toHTML]() { + if (this.contentType && !MIMES.has(this.contentType.toLowerCase())) { + return HTMLResult.EMPTY; + } + + let buffer = + this[$globalData].images && this[$globalData].images.get(this.href); + if (!buffer && (this.href || !this[$content])) { + // In general, we don't get remote data and use what we have + // in the pdf itself, so no picture for non null href. + return HTMLResult.EMPTY; + } + + if (!buffer && this.transferEncoding === "base64") { + buffer = fromBase64Util(this[$content]); + } + + if (!buffer) { + return HTMLResult.EMPTY; + } + + if (!this.contentType) { + for (const [header, type] of IMAGES_HEADERS) { + if ( + buffer.length > header.length && + header.every((x, i) => x === buffer[i]) + ) { + this.contentType = type; + break; + } + } + if (!this.contentType) { + return HTMLResult.EMPTY; + } + } + + // TODO: Firefox doesn't support natively tiff (and tif) format. + const blob = new Blob([buffer], { type: this.contentType }); + let style; + switch (this.aspect) { + case "fit": + case "actual": + // TODO: check what to do with actual. + // Normally we should return {auto, auto} for it but + // it implies some wrong rendering (see xfa_bug1716816.pdf). + break; + case "height": + style = { + height: "100%", + objectFit: "fill", + }; + break; + case "none": + style = { + width: "100%", + height: "100%", + objectFit: "fill", + }; + break; + case "width": + style = { + width: "100%", + objectFit: "fill", + }; + break; + } + const parent = this[$getParent](); + return HTMLResult.success({ + name: "img", + attributes: { + class: ["xfaImage"], + style, + src: URL.createObjectURL(blob), + alt: parent ? ariaLabel(parent[$getParent]()) : null, + }, + }); + } +} + +class ImageEdit extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "imageEdit", /* hasChildren = */ true); + this.data = getStringOption(attributes.data, ["link", "embed"]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.extras = null; + this.margin = null; + } + + [$toHTML](availableSpace) { + if (this.data === "embed") { + return HTMLResult.success({ + name: "div", + children: [], + attributes: {}, + }); + } + + return HTMLResult.EMPTY; + } +} + +class Integer extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "integer"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + const number = parseInt(this[$content].trim(), 10); + this[$content] = isNaN(number) ? null : number; + } + + [$toHTML](availableSpace) { + return valueToHtml( + this[$content] !== null ? this[$content].toString() : "" + ); + } +} + +class Issuers extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "issuers", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.certificate = new XFAObjectArray(); + } +} + +class Items extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "items", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.ref = attributes.ref || ""; + this.save = getInteger({ + data: attributes.save, + defaultValue: 0, + validate: x => x === 1, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.boolean = new XFAObjectArray(); + this.date = new XFAObjectArray(); + this.dateTime = new XFAObjectArray(); + this.decimal = new XFAObjectArray(); + this.exData = new XFAObjectArray(); + this.float = new XFAObjectArray(); + this.image = new XFAObjectArray(); + this.integer = new XFAObjectArray(); + this.text = new XFAObjectArray(); + this.time = new XFAObjectArray(); + } + + [$toHTML]() { + const output = []; + for (const child of this[$getChildren]()) { + output.push(child[$text]()); + } + return HTMLResult.success(output); + } +} + +class Keep extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "keep", /* hasChildren = */ true); + this.id = attributes.id || ""; + const options = ["none", "contentArea", "pageArea"]; + this.intact = getStringOption(attributes.intact, options); + this.next = getStringOption(attributes.next, options); + this.previous = getStringOption(attributes.previous, options); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } +} + +class KeyUsage extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "keyUsage"); + const options = ["", "yes", "no"]; + this.crlSign = getStringOption(attributes.crlSign, options); + this.dataEncipherment = getStringOption( + attributes.dataEncipherment, + options + ); + this.decipherOnly = getStringOption(attributes.decipherOnly, options); + this.digitalSignature = getStringOption( + attributes.digitalSignature, + options + ); + this.encipherOnly = getStringOption(attributes.encipherOnly, options); + this.id = attributes.id || ""; + this.keyAgreement = getStringOption(attributes.keyAgreement, options); + this.keyCertSign = getStringOption(attributes.keyCertSign, options); + this.keyEncipherment = getStringOption(attributes.keyEncipherment, options); + this.nonRepudiation = getStringOption(attributes.nonRepudiation, options); + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Line extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "line", /* hasChildren = */ true); + this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); + this.id = attributes.id || ""; + this.slope = getStringOption(attributes.slope, ["\\", "/"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.edge = null; + } + + [$toHTML]() { + const parent = this[$getParent]()[$getParent](); + const edge = this.edge || new Edge({}); + const edgeStyle = edge[$toStyle](); + const style = Object.create(null); + const thickness = edge.presence === "visible" ? edge.thickness : 0; + style.strokeWidth = measureToString(thickness); + style.stroke = edgeStyle.color; + let x1, y1, x2, y2; + let width = "100%"; + let height = "100%"; + + if (parent.w <= thickness) { + [x1, y1, x2, y2] = ["50%", 0, "50%", "100%"]; + width = style.strokeWidth; + } else if (parent.h <= thickness) { + [x1, y1, x2, y2] = [0, "50%", "100%", "50%"]; + height = style.strokeWidth; + } else if (this.slope === "\\") { + [x1, y1, x2, y2] = [0, 0, "100%", "100%"]; + } else { + [x1, y1, x2, y2] = [0, "100%", "100%", 0]; + } + + const line = { + name: "line", + attributes: { + xmlns: SVG_NS, + x1, + y1, + x2, + y2, + style, + }, + }; + + const svg = { + name: "svg", + children: [line], + attributes: { + xmlns: SVG_NS, + width, + height, + style: { + overflow: "visible", + }, + }, + }; + + if (hasMargin(parent)) { + return HTMLResult.success({ + name: "div", + attributes: { + style: { + display: "inline", + width: "100%", + height: "100%", + }, + }, + children: [svg], + }); + } + + svg.attributes.style.position = "absolute"; + return HTMLResult.success(svg); + } +} + +class Linear extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "linear", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, [ + "toRight", + "toBottom", + "toLeft", + "toTop", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + } + + [$toStyle](startColor) { + startColor = startColor ? startColor[$toStyle]() : "#FFFFFF"; + const transf = this.type.replace(/([RBLT])/, " $1").toLowerCase(); + const endColor = this.color ? this.color[$toStyle]() : "#000000"; + return `linear-gradient(${transf}, ${startColor}, ${endColor})`; + } +} + +class LockDocument extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "lockDocument"); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + this[$content] = getStringOption(this[$content], ["auto", "0", "1"]); + } +} + +class Manifest extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "manifest", /* hasChildren = */ true); + this.action = getStringOption(attributes.action, [ + "include", + "all", + "exclude", + ]); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.ref = new XFAObjectArray(); + } +} + +class Margin extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "margin", /* hasChildren = */ true); + this.bottomInset = getMeasurement(attributes.bottomInset, "0"); + this.id = attributes.id || ""; + this.leftInset = getMeasurement(attributes.leftInset, "0"); + this.rightInset = getMeasurement(attributes.rightInset, "0"); + this.topInset = getMeasurement(attributes.topInset, "0"); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } + + [$toStyle]() { + return { + margin: + measureToString(this.topInset) + + " " + + measureToString(this.rightInset) + + " " + + measureToString(this.bottomInset) + + " " + + measureToString(this.leftInset), + }; + } +} + +class Mdp extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "mdp"); + this.id = attributes.id || ""; + this.permissions = getInteger({ + data: attributes.permissions, + defaultValue: 2, + validate: x => x === 1 || x === 3, + }); + this.signatureType = getStringOption(attributes.signatureType, [ + "filler", + "author", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Medium extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "medium"); + this.id = attributes.id || ""; + this.imagingBBox = getBBox(attributes.imagingBBox); + this.long = getMeasurement(attributes.long); + this.orientation = getStringOption(attributes.orientation, [ + "portrait", + "landscape", + ]); + this.short = getMeasurement(attributes.short); + this.stock = attributes.stock || ""; + this.trayIn = getStringOption(attributes.trayIn, [ + "auto", + "delegate", + "pageFront", + ]); + this.trayOut = getStringOption(attributes.trayOut, ["auto", "delegate"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Message extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "message", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.text = new XFAObjectArray(); + } +} + +class NumericEdit extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "numericEdit", /* hasChildren = */ true); + this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, [ + "auto", + "off", + "on", + ]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.comb = null; + this.extras = null; + this.margin = null; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + const style = toStyle(this, "border", "font", "margin"); + const field = this[$getParent]()[$getParent](); + const html = { + name: "input", + attributes: { + type: "text", + fieldId: field[$uid], + dataId: field[$data]?.[$uid] || field[$uid], + class: ["xfaTextfield"], + style, + "aria-label": ariaLabel(field), + "aria-required": false, + }, + }; + + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + + return HTMLResult.success({ + name: "label", + attributes: { + class: ["xfaLabel"], + }, + children: [html], + }); + } +} + +class Occur extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "occur", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.initial = + attributes.initial !== "" + ? getInteger({ + data: attributes.initial, + defaultValue: "", + validate: x => true, + }) + : ""; + this.max = + attributes.max !== "" + ? getInteger({ + data: attributes.max, + defaultValue: 1, + validate: x => true, + }) + : ""; + this.min = + attributes.min !== "" + ? getInteger({ + data: attributes.min, + defaultValue: 1, + validate: x => true, + }) + : ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } + + [$clean]() { + const parent = this[$getParent](); + const originalMin = this.min; + + if (this.min === "") { + this.min = + parent instanceof PageArea || parent instanceof PageSet ? 0 : 1; + } + if (this.max === "") { + if (originalMin === "") { + this.max = + parent instanceof PageArea || parent instanceof PageSet ? -1 : 1; + } else { + this.max = this.min; + } + } + + if (this.max !== -1 && this.max < this.min) { + this.max = this.min; + } + + if (this.initial === "") { + this.initial = parent instanceof Template ? 1 : this.min; + } + } +} + +class Oid extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "oid"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Oids extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "oids", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.oid = new XFAObjectArray(); + } +} + +class Overflow extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "overflow"); + this.id = attributes.id || ""; + this.leader = attributes.leader || ""; + this.target = attributes.target || ""; + this.trailer = attributes.trailer || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$getExtra]() { + if (!this[$extra]) { + const parent = this[$getParent](); + const root = this[$getTemplateRoot](); + const target = root[$searchNode](this.target, parent); + const leader = root[$searchNode](this.leader, parent); + const trailer = root[$searchNode](this.trailer, parent); + this[$extra] = { + target: target?.[0] || null, + leader: leader?.[0] || null, + trailer: trailer?.[0] || null, + addLeader: false, + addTrailer: false, + }; + } + return this[$extra]; + } +} + +class PageArea extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "pageArea", /* hasChildren = */ true); + this.blankOrNotBlank = getStringOption(attributes.blankOrNotBlank, [ + "any", + "blank", + "notBlank", + ]); + this.id = attributes.id || ""; + this.initialNumber = getInteger({ + data: attributes.initialNumber, + defaultValue: 1, + validate: x => true, + }); + this.name = attributes.name || ""; + this.numbered = getInteger({ + data: attributes.numbered, + defaultValue: 1, + validate: x => true, + }); + this.oddOrEven = getStringOption(attributes.oddOrEven, [ + "any", + "even", + "odd", + ]); + this.pagePosition = getStringOption(attributes.pagePosition, [ + "any", + "first", + "last", + "only", + "rest", + ]); + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.desc = null; + this.extras = null; + this.medium = null; + this.occur = null; + this.area = new XFAObjectArray(); + this.contentArea = new XFAObjectArray(); + this.draw = new XFAObjectArray(); + this.exclGroup = new XFAObjectArray(); + this.field = new XFAObjectArray(); + this.subform = new XFAObjectArray(); + } + + [$isUsable]() { + if (!this[$extra]) { + this[$extra] = { + numberOfUse: 0, + }; + return true; + } + return ( + !this.occur || + this.occur.max === -1 || + this[$extra].numberOfUse < this.occur.max + ); + } + + [$cleanPage]() { + delete this[$extra]; + } + + [$getNextPage]() { + if (!this[$extra]) { + this[$extra] = { + numberOfUse: 0, + }; + } + + const parent = this[$getParent](); + if (parent.relation === "orderedOccurrence") { + if (this[$isUsable]()) { + this[$extra].numberOfUse += 1; + return this; + } + } + + return parent[$getNextPage](); + } + + [$getAvailableSpace]() { + return this[$extra].space || { width: 0, height: 0 }; + } + + [$toHTML]() { + // TODO: incomplete. + if (!this[$extra]) { + this[$extra] = { + numberOfUse: 1, + }; + } + + const children = []; + this[$extra].children = children; + + const style = Object.create(null); + if (this.medium && this.medium.short && this.medium.long) { + style.width = measureToString(this.medium.short); + style.height = measureToString(this.medium.long); + this[$extra].space = { + width: this.medium.short, + height: this.medium.long, + }; + if (this.medium.orientation === "landscape") { + const x = style.width; + style.width = style.height; + style.height = x; + this[$extra].space = { + width: this.medium.long, + height: this.medium.short, + }; + } + } else { + warn("XFA - No medium specified in pageArea: please file a bug."); + } + + this[$childrenToHTML]({ + filter: new Set(["area", "draw", "field", "subform"]), + include: true, + }); + + // contentarea must be the last container to be sure it is + // on top of the others. + this[$childrenToHTML]({ + filter: new Set(["contentArea"]), + include: true, + }); + + return HTMLResult.success({ + name: "div", + children, + attributes: { + class: ["xfaPage"], + id: this[$uid], + style, + xfaName: this.name, + }, + }); + } +} + +class PageSet extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "pageSet", /* hasChildren = */ true); + this.duplexImposition = getStringOption(attributes.duplexImposition, [ + "longEdge", + "shortEdge", + ]); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.relation = getStringOption(attributes.relation, [ + "orderedOccurrence", + "duplexPaginated", + "simplexPaginated", + ]); + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.occur = null; + this.pageArea = new XFAObjectArray(); + this.pageSet = new XFAObjectArray(); + } + + [$cleanPage]() { + for (const page of this.pageArea.children) { + page[$cleanPage](); + } + for (const page of this.pageSet.children) { + page[$cleanPage](); + } + } + + [$isUsable]() { + return ( + !this.occur || + this.occur.max === -1 || + this[$extra].numberOfUse < this.occur.max + ); + } + + [$getNextPage]() { + if (!this[$extra]) { + this[$extra] = { + numberOfUse: 1, + pageIndex: -1, + pageSetIndex: -1, + }; + } + + if (this.relation === "orderedOccurrence") { + if (this[$extra].pageIndex + 1 < this.pageArea.children.length) { + this[$extra].pageIndex += 1; + const pageArea = this.pageArea.children[this[$extra].pageIndex]; + return pageArea[$getNextPage](); + } + + if (this[$extra].pageSetIndex + 1 < this.pageSet.children.length) { + this[$extra].pageSetIndex += 1; + return this.pageSet.children[this[$extra].pageSetIndex][$getNextPage](); + } + + if (this[$isUsable]()) { + this[$extra].numberOfUse += 1; + this[$extra].pageIndex = -1; + this[$extra].pageSetIndex = -1; + return this[$getNextPage](); + } + + const parent = this[$getParent](); + if (parent instanceof PageSet) { + return parent[$getNextPage](); + } + + this[$cleanPage](); + return this[$getNextPage](); + } + const pageNumber = this[$getTemplateRoot]()[$extra].pageNumber; + const parity = pageNumber % 2 === 0 ? "even" : "odd"; + const position = pageNumber === 0 ? "first" : "rest"; + + let page = this.pageArea.children.find( + p => p.oddOrEven === parity && p.pagePosition === position + ); + if (page) { + return page; + } + + page = this.pageArea.children.find( + p => p.oddOrEven === "any" && p.pagePosition === position + ); + if (page) { + return page; + } + + page = this.pageArea.children.find( + p => p.oddOrEven === "any" && p.pagePosition === "any" + ); + if (page) { + return page; + } + + return this.pageArea.children[0]; + } +} + +class Para extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "para", /* hasChildren = */ true); + this.hAlign = getStringOption(attributes.hAlign, [ + "left", + "center", + "justify", + "justifyAll", + "radix", + "right", + ]); + this.id = attributes.id || ""; + this.lineHeight = attributes.lineHeight + ? getMeasurement(attributes.lineHeight, "0pt") + : ""; + this.marginLeft = attributes.marginLeft + ? getMeasurement(attributes.marginLeft, "0pt") + : ""; + this.marginRight = attributes.marginRight + ? getMeasurement(attributes.marginRight, "0pt") + : ""; + this.orphans = getInteger({ + data: attributes.orphans, + defaultValue: 0, + validate: x => x >= 0, + }); + this.preserve = attributes.preserve || ""; + this.radixOffset = attributes.radixOffset + ? getMeasurement(attributes.radixOffset, "0pt") + : ""; + this.spaceAbove = attributes.spaceAbove + ? getMeasurement(attributes.spaceAbove, "0pt") + : ""; + this.spaceBelow = attributes.spaceBelow + ? getMeasurement(attributes.spaceBelow, "0pt") + : ""; + this.tabDefault = attributes.tabDefault + ? getMeasurement(this.tabDefault) + : ""; + this.tabStops = (attributes.tabStops || "") + .trim() + .split(/\s+/) + .map((x, i) => (i % 2 === 1 ? getMeasurement(x) : x)); + this.textIndent = attributes.textIndent + ? getMeasurement(attributes.textIndent, "0pt") + : ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.vAlign = getStringOption(attributes.vAlign, [ + "top", + "bottom", + "middle", + ]); + this.widows = getInteger({ + data: attributes.widows, + defaultValue: 0, + validate: x => x >= 0, + }); + this.hyphenation = null; + } + + [$toStyle]() { + const style = toStyle(this, "hAlign"); + if (this.marginLeft !== "") { + style.paddingLeft = measureToString(this.marginLeft); + } + if (this.marginRight !== "") { + style.paddingRight = measureToString(this.marginRight); + } + if (this.spaceAbove !== "") { + style.paddingTop = measureToString(this.spaceAbove); + } + if (this.spaceBelow !== "") { + style.paddingBottom = measureToString(this.spaceBelow); + } + if (this.textIndent !== "") { + style.textIndent = measureToString(this.textIndent); + fixTextIndent(style); + } + + if (this.lineHeight > 0) { + style.lineHeight = measureToString(this.lineHeight); + } + + if (this.tabDefault !== "") { + style.tabSize = measureToString(this.tabDefault); + } + + if (this.tabStops.length > 0) { + // TODO + } + + if (this.hyphenatation) { + Object.assign(style, this.hyphenatation[$toStyle]()); + } + + return style; + } +} + +class PasswordEdit extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "passwordEdit", /* hasChildren = */ true); + this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, [ + "auto", + "off", + "on", + ]); + this.id = attributes.id || ""; + this.passwordChar = attributes.passwordChar || "*"; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.extras = null; + this.margin = null; + } +} + +class Pattern extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "pattern", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, [ + "crossHatch", + "crossDiagonal", + "diagonalLeft", + "diagonalRight", + "horizontal", + "vertical", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + } + + [$toStyle](startColor) { + startColor = startColor ? startColor[$toStyle]() : "#FFFFFF"; + const endColor = this.color ? this.color[$toStyle]() : "#000000"; + const width = 5; + const cmd = "repeating-linear-gradient"; + const colors = `${startColor},${startColor} ${width}px,${endColor} ${width}px,${endColor} ${ + 2 * width + }px`; + switch (this.type) { + case "crossHatch": + return `${cmd}(to top,${colors}) ${cmd}(to right,${colors})`; + case "crossDiagonal": + return `${cmd}(45deg,${colors}) ${cmd}(-45deg,${colors})`; + case "diagonalLeft": + return `${cmd}(45deg,${colors})`; + case "diagonalRight": + return `${cmd}(-45deg,${colors})`; + case "horizontal": + return `${cmd}(to top,${colors})`; + case "vertical": + return `${cmd}(to right,${colors})`; + } + + return ""; + } +} + +class Picture extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "picture"); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Proto extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "proto", /* hasChildren = */ true); + this.appearanceFilter = new XFAObjectArray(); + this.arc = new XFAObjectArray(); + this.area = new XFAObjectArray(); + this.assist = new XFAObjectArray(); + this.barcode = new XFAObjectArray(); + this.bindItems = new XFAObjectArray(); + this.bookend = new XFAObjectArray(); + this.boolean = new XFAObjectArray(); + this.border = new XFAObjectArray(); + this.break = new XFAObjectArray(); + this.breakAfter = new XFAObjectArray(); + this.breakBefore = new XFAObjectArray(); + this.button = new XFAObjectArray(); + this.calculate = new XFAObjectArray(); + this.caption = new XFAObjectArray(); + this.certificate = new XFAObjectArray(); + this.certificates = new XFAObjectArray(); + this.checkButton = new XFAObjectArray(); + this.choiceList = new XFAObjectArray(); + this.color = new XFAObjectArray(); + this.comb = new XFAObjectArray(); + this.connect = new XFAObjectArray(); + this.contentArea = new XFAObjectArray(); + this.corner = new XFAObjectArray(); + this.date = new XFAObjectArray(); + this.dateTime = new XFAObjectArray(); + this.dateTimeEdit = new XFAObjectArray(); + this.decimal = new XFAObjectArray(); + this.defaultUi = new XFAObjectArray(); + this.desc = new XFAObjectArray(); + this.digestMethod = new XFAObjectArray(); + this.digestMethods = new XFAObjectArray(); + this.draw = new XFAObjectArray(); + this.edge = new XFAObjectArray(); + this.encoding = new XFAObjectArray(); + this.encodings = new XFAObjectArray(); + this.encrypt = new XFAObjectArray(); + this.encryptData = new XFAObjectArray(); + this.encryption = new XFAObjectArray(); + this.encryptionMethod = new XFAObjectArray(); + this.encryptionMethods = new XFAObjectArray(); + this.event = new XFAObjectArray(); + this.exData = new XFAObjectArray(); + this.exObject = new XFAObjectArray(); + this.exclGroup = new XFAObjectArray(); + this.execute = new XFAObjectArray(); + this.extras = new XFAObjectArray(); + this.field = new XFAObjectArray(); + this.fill = new XFAObjectArray(); + this.filter = new XFAObjectArray(); + this.float = new XFAObjectArray(); + this.font = new XFAObjectArray(); + this.format = new XFAObjectArray(); + this.handler = new XFAObjectArray(); + this.hyphenation = new XFAObjectArray(); + this.image = new XFAObjectArray(); + this.imageEdit = new XFAObjectArray(); + this.integer = new XFAObjectArray(); + this.issuers = new XFAObjectArray(); + this.items = new XFAObjectArray(); + this.keep = new XFAObjectArray(); + this.keyUsage = new XFAObjectArray(); + this.line = new XFAObjectArray(); + this.linear = new XFAObjectArray(); + this.lockDocument = new XFAObjectArray(); + this.manifest = new XFAObjectArray(); + this.margin = new XFAObjectArray(); + this.mdp = new XFAObjectArray(); + this.medium = new XFAObjectArray(); + this.message = new XFAObjectArray(); + this.numericEdit = new XFAObjectArray(); + this.occur = new XFAObjectArray(); + this.oid = new XFAObjectArray(); + this.oids = new XFAObjectArray(); + this.overflow = new XFAObjectArray(); + this.pageArea = new XFAObjectArray(); + this.pageSet = new XFAObjectArray(); + this.para = new XFAObjectArray(); + this.passwordEdit = new XFAObjectArray(); + this.pattern = new XFAObjectArray(); + this.picture = new XFAObjectArray(); + this.radial = new XFAObjectArray(); + this.reason = new XFAObjectArray(); + this.reasons = new XFAObjectArray(); + this.rectangle = new XFAObjectArray(); + this.ref = new XFAObjectArray(); + this.script = new XFAObjectArray(); + this.setProperty = new XFAObjectArray(); + this.signData = new XFAObjectArray(); + this.signature = new XFAObjectArray(); + this.signing = new XFAObjectArray(); + this.solid = new XFAObjectArray(); + this.speak = new XFAObjectArray(); + this.stipple = new XFAObjectArray(); + this.subform = new XFAObjectArray(); + this.subformSet = new XFAObjectArray(); + this.subjectDN = new XFAObjectArray(); + this.subjectDNs = new XFAObjectArray(); + this.submit = new XFAObjectArray(); + this.text = new XFAObjectArray(); + this.textEdit = new XFAObjectArray(); + this.time = new XFAObjectArray(); + this.timeStamp = new XFAObjectArray(); + this.toolTip = new XFAObjectArray(); + this.traversal = new XFAObjectArray(); + this.traverse = new XFAObjectArray(); + this.ui = new XFAObjectArray(); + this.validate = new XFAObjectArray(); + this.value = new XFAObjectArray(); + this.variables = new XFAObjectArray(); + } +} + +class Radial extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "radial", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["toEdge", "toCenter"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + } + + [$toStyle](startColor) { + startColor = startColor ? startColor[$toStyle]() : "#FFFFFF"; + const endColor = this.color ? this.color[$toStyle]() : "#000000"; + const colors = + this.type === "toEdge" + ? `${startColor},${endColor}` + : `${endColor},${startColor}`; + return `radial-gradient(circle at center, ${colors})`; + } +} + +class Reason extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "reason"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Reasons extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "reasons", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.reason = new XFAObjectArray(); + } +} + +class Rectangle extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "rectangle", /* hasChildren = */ true); + this.hand = getStringOption(attributes.hand, ["even", "left", "right"]); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.corner = new XFAObjectArray(4); + this.edge = new XFAObjectArray(4); + this.fill = null; + } + + [$toHTML]() { + const edge = this.edge.children.length + ? this.edge.children[0] + : new Edge({}); + const edgeStyle = edge[$toStyle](); + const style = Object.create(null); + if (this.fill?.presence === "visible") { + Object.assign(style, this.fill[$toStyle]()); + } else { + style.fill = "transparent"; + } + style.strokeWidth = measureToString( + edge.presence === "visible" ? edge.thickness : 0 + ); + style.stroke = edgeStyle.color; + + const corner = this.corner.children.length + ? this.corner.children[0] + : new Corner({}); + const cornerStyle = corner[$toStyle](); + + const rect = { + name: "rect", + attributes: { + xmlns: SVG_NS, + width: "100%", + height: "100%", + x: 0, + y: 0, + rx: cornerStyle.radius, + ry: cornerStyle.radius, + style, + }, + }; + + const svg = { + name: "svg", + children: [rect], + attributes: { + xmlns: SVG_NS, + style: { + overflow: "visible", + }, + width: "100%", + height: "100%", + }, + }; + + const parent = this[$getParent]()[$getParent](); + if (hasMargin(parent)) { + return HTMLResult.success({ + name: "div", + attributes: { + style: { + display: "inline", + width: "100%", + height: "100%", + }, + }, + children: [svg], + }); + } + + svg.attributes.style.position = "absolute"; + return HTMLResult.success(svg); + } +} + +class RefElement extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "ref"); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Script extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "script"); + this.binding = attributes.binding || ""; + this.contentType = attributes.contentType || ""; + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.runAt = getStringOption(attributes.runAt, [ + "client", + "both", + "server", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class SetProperty extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "setProperty"); + this.connection = attributes.connection || ""; + this.ref = attributes.ref || ""; + this.target = attributes.target || ""; + } +} + +class SignData extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "signData", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.operation = getStringOption(attributes.operation, [ + "sign", + "clear", + "verify", + ]); + this.ref = attributes.ref || ""; + this.target = attributes.target || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.filter = null; + this.manifest = null; + } +} + +class Signature extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "signature", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["PDF1.3", "PDF1.6"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.border = null; + this.extras = null; + this.filter = null; + this.manifest = null; + this.margin = null; + } +} + +class Signing extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "signing", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.certificate = new XFAObjectArray(); + } +} + +class Solid extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "solid", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + } + + [$toStyle](startColor) { + return startColor ? startColor[$toStyle]() : "#FFFFFF"; + } +} + +class Speak extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "speak"); + this.disable = getInteger({ + data: attributes.disable, + defaultValue: 0, + validate: x => x === 1, + }); + this.id = attributes.id || ""; + this.priority = getStringOption(attributes.priority, [ + "custom", + "caption", + "name", + "toolTip", + ]); + this.rid = attributes.rid || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Stipple extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "stipple", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.rate = getInteger({ + data: attributes.rate, + defaultValue: 50, + validate: x => x >= 0 && x <= 100, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.color = null; + this.extras = null; + } + + [$toStyle](bgColor) { + const alpha = this.rate / 100; + return Util.makeHexColor( + Math.round(bgColor.value.r * (1 - alpha) + this.value.r * alpha), + Math.round(bgColor.value.g * (1 - alpha) + this.value.g * alpha), + Math.round(bgColor.value.b * (1 - alpha) + this.value.b * alpha) + ); + } +} + +class Subform extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "subform", /* hasChildren = */ true); + this.access = getStringOption(attributes.access, [ + "open", + "nonInteractive", + "protected", + "readOnly", + ]); + this.allowMacro = getInteger({ + data: attributes.allowMacro, + defaultValue: 0, + validate: x => x === 1, + }); + this.anchorType = getStringOption(attributes.anchorType, [ + "topLeft", + "bottomCenter", + "bottomLeft", + "bottomRight", + "middleCenter", + "middleLeft", + "middleRight", + "topCenter", + "topRight", + ]); + this.colSpan = getInteger({ + data: attributes.colSpan, + defaultValue: 1, + validate: n => n >= 1 || n === -1, + }); + this.columnWidths = (attributes.columnWidths || "") + .trim() + .split(/\s+/) + .map(x => (x === "-1" ? -1 : getMeasurement(x))); + this.h = attributes.h ? getMeasurement(attributes.h) : ""; + this.hAlign = getStringOption(attributes.hAlign, [ + "left", + "center", + "justify", + "justifyAll", + "radix", + "right", + ]); + this.id = attributes.id || ""; + this.layout = getStringOption(attributes.layout, [ + "position", + "lr-tb", + "rl-row", + "rl-tb", + "row", + "table", + "tb", + ]); + this.locale = attributes.locale || ""; + this.maxH = getMeasurement(attributes.maxH, "0pt"); + this.maxW = getMeasurement(attributes.maxW, "0pt"); + this.mergeMode = getStringOption(attributes.mergeMode, [ + "consumeData", + "matchTemplate", + ]); + this.minH = getMeasurement(attributes.minH, "0pt"); + this.minW = getMeasurement(attributes.minW, "0pt"); + this.name = attributes.name || ""; + this.presence = getStringOption(attributes.presence, [ + "visible", + "hidden", + "inactive", + "invisible", + ]); + this.relevant = getRelevant(attributes.relevant); + this.restoreState = getStringOption(attributes.restoreState, [ + "manual", + "auto", + ]); + this.scope = getStringOption(attributes.scope, ["name", "none"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.w = attributes.w ? getMeasurement(attributes.w) : ""; + this.x = getMeasurement(attributes.x, "0pt"); + this.y = getMeasurement(attributes.y, "0pt"); + this.assist = null; + this.bind = null; + this.bookend = null; + this.border = null; + this.break = null; + this.calculate = null; + this.desc = null; + this.extras = null; + this.keep = null; + this.margin = null; + this.occur = null; + this.overflow = null; + this.pageSet = null; + this.para = null; + this.traversal = null; + this.validate = null; + this.variables = null; + this.area = new XFAObjectArray(); + this.breakAfter = new XFAObjectArray(); + this.breakBefore = new XFAObjectArray(); + this.connect = new XFAObjectArray(); + this.draw = new XFAObjectArray(); + this.event = new XFAObjectArray(); + this.exObject = new XFAObjectArray(); + this.exclGroup = new XFAObjectArray(); + this.field = new XFAObjectArray(); + this.proto = new XFAObjectArray(); + this.setProperty = new XFAObjectArray(); + this.subform = new XFAObjectArray(); + this.subformSet = new XFAObjectArray(); + } + + [$getSubformParent]() { + const parent = this[$getParent](); + if (parent instanceof SubformSet) { + return parent[$getSubformParent](); + } + return parent; + } + + [$isBindable]() { + return true; + } + + [$isThereMoreWidth]() { + return ( + (this.layout.endsWith("-tb") && + this[$extra].attempt === 0 && + this[$extra].numberInLine > 0) || + this[$getParent]()[$isThereMoreWidth]() + ); + } + + *[$getContainedChildren]() { + // This function is overriden in order to fake that subforms under + // this set are in fact under parent subform. + yield* getContainedChildren(this); + } + + [$flushHTML]() { + return flushHTML(this); + } + + [$addHTML](html, bbox) { + addHTML(this, html, bbox); + } + + [$getAvailableSpace]() { + return getAvailableSpace(this); + } + + [$isSplittable]() { + // We cannot cache the result here because the contentArea + // can change. + const parent = this[$getSubformParent](); + if (!parent[$isSplittable]()) { + return false; + } + + if (this[$extra]._isSplittable !== undefined) { + return this[$extra]._isSplittable; + } + + if (this.layout === "position" || this.layout.includes("row")) { + this[$extra]._isSplittable = false; + return false; + } + + if (this.keep && this.keep.intact !== "none") { + this[$extra]._isSplittable = false; + return false; + } + + if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) { + // If parent can fit in w=100 and there's already an element which takes + // 90 then we've 10 for this element. Suppose this element has a tb layout + // and 5 elements have a width of 7 and the 6th has a width of 20: + // then this element (and all its content) must move on the next line. + // If this element is splittable then the first 5 children will stay + // at the end of the line: we don't want that. + return false; + } + + this[$extra]._isSplittable = true; + + return true; + } + + [$toHTML](availableSpace) { + setTabIndex(this); + + if (this.break) { + // break element is deprecated so plug it on one of its replacement + // breakBefore or breakAfter. + if (this.break.after !== "auto" || this.break.afterTarget !== "") { + const node = new BreakAfter({ + targetType: this.break.after, + target: this.break.afterTarget, + startNew: this.break.startNew.toString(), + }); + node[$globalData] = this[$globalData]; + this[$appendChild](node); + this.breakAfter.push(node); + } + + if (this.break.before !== "auto" || this.break.beforeTarget !== "") { + const node = new BreakBefore({ + targetType: this.break.before, + target: this.break.beforeTarget, + startNew: this.break.startNew.toString(), + }); + node[$globalData] = this[$globalData]; + this[$appendChild](node); + this.breakBefore.push(node); + } + + if (this.break.overflowTarget !== "") { + const node = new Overflow({ + target: this.break.overflowTarget, + leader: this.break.overflowLeader, + trailer: this.break.overflowTrailer, + }); + node[$globalData] = this[$globalData]; + this[$appendChild](node); + this.overflow.push(node); + } + + this[$removeChild](this.break); + this.break = null; + } + + if (this.presence === "hidden" || this.presence === "inactive") { + return HTMLResult.EMPTY; + } + + if ( + this.breakBefore.children.length > 1 || + this.breakAfter.children.length > 1 + ) { + // Specs are always talking about the breakBefore element + // and it doesn't really make sense to have several ones. + warn( + "XFA - Several breakBefore or breakAfter in subforms: please file a bug." + ); + } + + if (this.breakBefore.children.length >= 1) { + const breakBefore = this.breakBefore.children[0]; + if (handleBreak(breakBefore)) { + return HTMLResult.breakNode(breakBefore); + } + } + + if (this[$extra]?.afterBreakAfter) { + return HTMLResult.EMPTY; + } + + // TODO: incomplete. + fixDimensions(this); + const children = []; + const attributes = { + id: this[$uid], + class: [], + }; + + setAccess(this, attributes.class); + + if (!this[$extra]) { + this[$extra] = Object.create(null); + } + + Object.assign(this[$extra], { + children, + line: null, + attributes, + attempt: 0, + numberInLine: 0, + availableSpace: { + width: Math.min(this.w || Infinity, availableSpace.width), + height: Math.min(this.h || Infinity, availableSpace.height), + }, + width: 0, + height: 0, + prevHeight: 0, + currentWidth: 0, + }); + + const root = this[$getTemplateRoot](); + const savedNoLayoutFailure = root[$extra].noLayoutFailure; + + const isSplittable = this[$isSplittable](); + if (!isSplittable) { + setFirstUnsplittable(this); + } + + if (!checkDimensions(this, availableSpace)) { + return HTMLResult.FAILURE; + } + + const filter = new Set([ + "area", + "draw", + "exclGroup", + "field", + "subform", + "subformSet", + ]); + + if (this.layout.includes("row")) { + const columnWidths = this[$getSubformParent]().columnWidths; + if (Array.isArray(columnWidths) && columnWidths.length > 0) { + this[$extra].columnWidths = columnWidths; + this[$extra].currentColumn = 0; + } + } + + const style = toStyle( + this, + "anchorType", + "dimensions", + "position", + "presence", + "border", + "margin", + "hAlign" + ); + const classNames = ["xfaSubform"]; + const cl = layoutClass(this); + if (cl) { + classNames.push(cl); + } + + attributes.style = style; + attributes.class = classNames; + + if (this.name) { + attributes.xfaName = this.name; + } + + if (this.overflow) { + const overflowExtra = this.overflow[$getExtra](); + if (overflowExtra.addLeader) { + overflowExtra.addLeader = false; + handleOverflow(this, overflowExtra.leader, availableSpace); + } + } + + this[$pushPara](); + const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb"; + const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1; + for (; this[$extra].attempt < maxRun; this[$extra].attempt++) { + if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) { + // If the layout is lr-tb then having attempt equals to + // MAX_ATTEMPTS_FOR_LRTB_LAYOUT-1 means that we're trying to layout + // on the next line so this on is empty. + this[$extra].numberInLine = 0; + } + const result = this[$childrenToHTML]({ + filter, + include: true, + }); + if (result.success) { + break; + } + if (result.isBreak()) { + this[$popPara](); + return result; + } + if ( + isLrTb && + this[$extra].attempt === 0 && + this[$extra].numberInLine === 0 && + !root[$extra].noLayoutFailure + ) { + // We're failing to put the first element on the line so no + // need to test on the next line. + // The goal is not only to avoid some useless checks but to avoid + // bugs too: if a descendant managed to put a node and failed + // on the next one, going to the next step here will imply to + // visit the descendant again, clear [$extra].children and restart + // on the failing node, consequently the first node just disappears + // because it has never been flushed. + this[$extra].attempt = maxRun; + break; + } + } + + this[$popPara](); + if (!isSplittable) { + unsetFirstUnsplittable(this); + } + root[$extra].noLayoutFailure = savedNoLayoutFailure; + + if (this[$extra].attempt === maxRun) { + if (this.overflow) { + this[$getTemplateRoot]()[$extra].overflowNode = this.overflow; + } + + if (!isSplittable) { + // Since a new try will happen in a new container with maybe + // new dimensions, we invalidate already layed out components. + delete this[$extra]; + } + return HTMLResult.FAILURE; + } + + if (this.overflow) { + const overflowExtra = this.overflow[$getExtra](); + if (overflowExtra.addTrailer) { + overflowExtra.addTrailer = false; + handleOverflow(this, overflowExtra.trailer, availableSpace); + } + } + + let marginH = 0; + let marginV = 0; + if (this.margin) { + marginH = this.margin.leftInset + this.margin.rightInset; + marginV = this.margin.topInset + this.margin.bottomInset; + } + + const width = Math.max(this[$extra].width + marginH, this.w || 0); + const height = Math.max(this[$extra].height + marginV, this.h || 0); + const bbox = [this.x, this.y, width, height]; + + if (this.w === "") { + style.width = measureToString(width); + } + if (this.h === "") { + style.height = measureToString(height); + } + + if ( + (style.width === "0px" || style.height === "0px") && + children.length === 0 + ) { + return HTMLResult.EMPTY; + } + + const html = { + name: "div", + attributes, + children, + }; + + applyAssist(this, attributes); + + const result = HTMLResult.success(createWrapper(this, html), bbox); + + if (this.breakAfter.children.length >= 1) { + const breakAfter = this.breakAfter.children[0]; + if (handleBreak(breakAfter)) { + this[$extra].afterBreakAfter = result; + return HTMLResult.breakNode(breakAfter); + } + } + + delete this[$extra]; + + return result; + } +} + +class SubformSet extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "subformSet", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.relation = getStringOption(attributes.relation, [ + "ordered", + "choice", + "unordered", + ]); + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.bookend = null; + this.break = null; + this.desc = null; + this.extras = null; + this.occur = null; + this.overflow = null; + this.breakAfter = new XFAObjectArray(); + this.breakBefore = new XFAObjectArray(); + this.subform = new XFAObjectArray(); + this.subformSet = new XFAObjectArray(); + + // TODO: need to handle break stuff and relation. + } + + *[$getContainedChildren]() { + // This function is overriden in order to fake that subforms under + // this set are in fact under parent subform. + yield* getContainedChildren(this); + } + + [$getSubformParent]() { + let parent = this[$getParent](); + while (!(parent instanceof Subform)) { + parent = parent[$getParent](); + } + return parent; + } + + [$isBindable]() { + return true; + } +} + +class SubjectDN extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "subjectDN"); + this.delimiter = attributes.delimiter || ","; + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + this[$content] = new Map( + this[$content].split(this.delimiter).map(kv => { + kv = kv.split("=", 2); + kv[0] = kv[0].trim(); + return kv; + }) + ); + } +} + +class SubjectDNs extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "subjectDNs", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.subjectDN = new XFAObjectArray(); + } +} + +class Submit extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "submit", /* hasChildren = */ true); + this.embedPDF = getInteger({ + data: attributes.embedPDF, + defaultValue: 0, + validate: x => x === 1, + }); + this.format = getStringOption(attributes.format, [ + "xdp", + "formdata", + "pdf", + "urlencoded", + "xfd", + "xml", + ]); + this.id = attributes.id || ""; + this.target = attributes.target || ""; + this.textEncoding = getKeyword({ + data: attributes.textEncoding + ? attributes.textEncoding.toLowerCase() + : "", + defaultValue: "", + validate: k => + [ + "utf-8", + "big-five", + "fontspecific", + "gbk", + "gb-18030", + "gb-2312", + "ksc-5601", + "none", + "shift-jis", + "ucs-2", + "utf-16", + ].includes(k) || k.match(/iso-8859-\d{2}/), + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.xdpContent = attributes.xdpContent || ""; + this.encrypt = null; + this.encryptData = new XFAObjectArray(); + this.signData = new XFAObjectArray(); + } +} + +class Template extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "template", /* hasChildren = */ true); + this.baseProfile = getStringOption(attributes.baseProfile, [ + "full", + "interactiveForms", + ]); + this.extras = null; + + // Spec is unclear: + // A container element that describes a single subform capable of + // enclosing other containers. + // Can we have more than one subform ? + this.subform = new XFAObjectArray(); + } + + [$finalize]() { + if (this.subform.children.length === 0) { + warn("XFA - No subforms in template node."); + } + if (this.subform.children.length >= 2) { + warn("XFA - Several subforms in template node: please file a bug."); + } + this[$tabIndex] = DEFAULT_TAB_INDEX; + } + + [$isSplittable]() { + return true; + } + + [$searchNode](expr, container) { + if (expr.startsWith("#")) { + // This is an id. + return [this[$ids].get(expr.slice(1))]; + } + return searchNode(this, container, expr, true, true); + } + + /** + * This function is a generator because the conversion into + * pages is done asynchronously and we want to save the state + * of the function where we were in the previous iteration. + */ + *[$toPages]() { + if (!this.subform.children.length) { + return HTMLResult.success({ + name: "div", + children: [], + }); + } + this[$extra] = { + overflowNode: null, + firstUnsplittable: null, + currentContentArea: null, + currentPageArea: null, + noLayoutFailure: false, + pageNumber: 1, + pagePosition: "first", + oddOrEven: "odd", + blankOrNotBlank: "nonBlank", + paraStack: [], + }; + + const root = this.subform.children[0]; + root.pageSet[$cleanPage](); + + const pageAreas = root.pageSet.pageArea.children; + const mainHtml = { + name: "div", + children: [], + }; + + let pageArea = null; + let breakBefore = null; + let breakBeforeTarget = null; + if (root.breakBefore.children.length >= 1) { + breakBefore = root.breakBefore.children[0]; + breakBeforeTarget = breakBefore.target; + } else if ( + root.subform.children.length >= 1 && + root.subform.children[0].breakBefore.children.length >= 1 + ) { + breakBefore = root.subform.children[0].breakBefore.children[0]; + breakBeforeTarget = breakBefore.target; + } else if (root.break?.beforeTarget) { + breakBefore = root.break; + breakBeforeTarget = breakBefore.beforeTarget; + } else if ( + root.subform.children.length >= 1 && + root.subform.children[0].break?.beforeTarget + ) { + breakBefore = root.subform.children[0].break; + breakBeforeTarget = breakBefore.beforeTarget; + } + + if (breakBefore) { + const target = this[$searchNode]( + breakBeforeTarget, + breakBefore[$getParent]() + ); + if (target instanceof PageArea) { + pageArea = target; + // Consume breakBefore. + breakBefore[$extra] = {}; + } + } + + if (!pageArea) { + pageArea = pageAreas[0]; + } + + pageArea[$extra] = { + numberOfUse: 1, + }; + + const pageAreaParent = pageArea[$getParent](); + pageAreaParent[$extra] = { + numberOfUse: 1, + pageIndex: pageAreaParent.pageArea.children.indexOf(pageArea), + pageSetIndex: 0, + }; + + let targetPageArea; + let leader = null; + let trailer = null; + let hasSomething = true; + let hasSomethingCounter = 0; + let startIndex = 0; + + while (true) { + if (!hasSomething) { + mainHtml.children.pop(); + // Nothing has been added in the previous page + if (++hasSomethingCounter === MAX_EMPTY_PAGES) { + warn("XFA - Something goes wrong: please file a bug."); + return mainHtml; + } + } else { + hasSomethingCounter = 0; + } + + targetPageArea = null; + this[$extra].currentPageArea = pageArea; + const page = pageArea[$toHTML]().html; + mainHtml.children.push(page); + + if (leader) { + this[$extra].noLayoutFailure = true; + page.children.push(leader[$toHTML](pageArea[$extra].space).html); + leader = null; + } + + if (trailer) { + this[$extra].noLayoutFailure = true; + page.children.push(trailer[$toHTML](pageArea[$extra].space).html); + trailer = null; + } + + const contentAreas = pageArea.contentArea.children; + const htmlContentAreas = page.children.filter(node => + node.attributes.class.includes("xfaContentarea") + ); + + hasSomething = false; + this[$extra].firstUnsplittable = null; + this[$extra].noLayoutFailure = false; + + const flush = index => { + const html = root[$flushHTML](); + if (html) { + hasSomething ||= html.children?.length > 0; + htmlContentAreas[index].children.push(html); + } + }; + + for (let i = startIndex, ii = contentAreas.length; i < ii; i++) { + const contentArea = (this[$extra].currentContentArea = contentAreas[i]); + const space = { width: contentArea.w, height: contentArea.h }; + startIndex = 0; + + if (leader) { + htmlContentAreas[i].children.push(leader[$toHTML](space).html); + leader = null; + } + + if (trailer) { + htmlContentAreas[i].children.push(trailer[$toHTML](space).html); + trailer = null; + } + + const html = root[$toHTML](space); + if (html.success) { + if (html.html) { + hasSomething ||= html.html.children?.length > 0; + htmlContentAreas[i].children.push(html.html); + } else if (!hasSomething && mainHtml.children.length > 1) { + mainHtml.children.pop(); + } + return mainHtml; + } + + if (html.isBreak()) { + const node = html.breakNode; + flush(i); + + if (node.targetType === "auto") { + continue; + } + + if (node.leader) { + leader = this[$searchNode](node.leader, node[$getParent]()); + leader = leader ? leader[0] : null; + } + + if (node.trailer) { + trailer = this[$searchNode](node.trailer, node[$getParent]()); + trailer = trailer ? trailer[0] : null; + } + + if (node.targetType === "pageArea") { + targetPageArea = node[$extra].target; + i = Infinity; + } else if (!node[$extra].target) { + // We stay on the same page. + i = node[$extra].index; + } else { + targetPageArea = node[$extra].target; + startIndex = node[$extra].index + 1; + i = Infinity; + } + + continue; + } + + if (this[$extra].overflowNode) { + const node = this[$extra].overflowNode; + this[$extra].overflowNode = null; + + const overflowExtra = node[$getExtra](); + const target = overflowExtra.target; + overflowExtra.addLeader = overflowExtra.leader !== null; + overflowExtra.addTrailer = overflowExtra.trailer !== null; + + flush(i); + + const currentIndex = i; + + i = Infinity; + if (target instanceof PageArea) { + // We must stop the contentAreas filling and go to the next page. + targetPageArea = target; + } else if (target instanceof ContentArea) { + const index = contentAreas.indexOf(target); + if (index !== -1) { + if (index > currentIndex) { + // In the next loop iteration `i` will be incremented, note the + // `continue` just below, hence we need to subtract one here. + i = index - 1; + } else { + // The targetted contentArea has already been filled + // so create a new page. + startIndex = index; + } + } else { + targetPageArea = target[$getParent](); + startIndex = targetPageArea.contentArea.children.indexOf(target); + } + } + continue; + } + + flush(i); + } + + this[$extra].pageNumber += 1; + if (targetPageArea) { + if (targetPageArea[$isUsable]()) { + targetPageArea[$extra].numberOfUse += 1; + } else { + targetPageArea = null; + } + } + pageArea = targetPageArea || pageArea[$getNextPage](); + yield null; + } + } +} + +class Text extends ContentObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "text"); + this.id = attributes.id || ""; + this.maxChars = getInteger({ + data: attributes.maxChars, + defaultValue: 0, + validate: x => x >= 0, + }); + this.name = attributes.name || ""; + this.rid = attributes.rid || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$acceptWhitespace]() { + return true; + } + + [$onChild](child) { + if (child[$namespaceId] === NamespaceIds.xhtml.id) { + this[$content] = child; + return true; + } + warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`); + return false; + } + + [$onText](str) { + if (this[$content] instanceof XFAObject) { + return; + } + super[$onText](str); + } + + [$finalize]() { + if (typeof this[$content] === "string") { + this[$content] = this[$content].replaceAll("\r\n", "\n"); + } + } + + [$getExtra]() { + if (typeof this[$content] === "string") { + return this[$content] + .split(/[\u2029\u2028\n]/) + .reduce((acc, line) => { + if (line) { + acc.push(line); + } + return acc; + }, []) + .join("\n"); + } + return this[$content][$text](); + } + + [$toHTML](availableSpace) { + if (typeof this[$content] === "string") { + // \u2028 is a line separator. + // \u2029 is a paragraph separator. + const html = valueToHtml(this[$content]).html; + + if (this[$content].includes("\u2029")) { + // We've plain text containing a paragraph separator + // so convert it into a set of

. + html.name = "div"; + html.children = []; + this[$content] + .split("\u2029") + .map(para => + // Convert a paragraph into a set of (for lines) + // separated by
. + para.split(/[\u2028\n]/).reduce((acc, line) => { + acc.push( + { + name: "span", + value: line, + }, + { + name: "br", + } + ); + return acc; + }, []) + ) + .forEach(lines => { + html.children.push({ + name: "p", + children: lines, + }); + }); + } else if (/[\u2028\n]/.test(this[$content])) { + html.name = "div"; + html.children = []; + // Convert plain text into a set of (for lines) + // separated by
. + this[$content].split(/[\u2028\n]/).forEach(line => { + html.children.push( + { + name: "span", + value: line, + }, + { + name: "br", + } + ); + }); + } + + return HTMLResult.success(html); + } + + return this[$content][$toHTML](availableSpace); + } +} + +class TextEdit extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "textEdit", /* hasChildren = */ true); + this.allowRichText = getInteger({ + data: attributes.allowRichText, + defaultValue: 0, + validate: x => x === 1, + }); + this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, [ + "auto", + "off", + "on", + ]); + this.id = attributes.id || ""; + this.multiLine = getInteger({ + data: attributes.multiLine, + defaultValue: "", + validate: x => x === 0 || x === 1, + }); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.vScrollPolicy = getStringOption(attributes.vScrollPolicy, [ + "auto", + "off", + "on", + ]); + this.border = null; + this.comb = null; + this.extras = null; + this.margin = null; + } + + [$toHTML](availableSpace) { + // TODO: incomplete. + const style = toStyle(this, "border", "font", "margin"); + let html; + const field = this[$getParent]()[$getParent](); + if (this.multiLine === "") { + this.multiLine = field instanceof Draw ? 1 : 0; + } + if (this.multiLine === 1) { + html = { + name: "textarea", + attributes: { + dataId: field[$data]?.[$uid] || field[$uid], + fieldId: field[$uid], + class: ["xfaTextfield"], + style, + "aria-label": ariaLabel(field), + "aria-required": false, + }, + }; + } else { + html = { + name: "input", + attributes: { + type: "text", + dataId: field[$data]?.[$uid] || field[$uid], + fieldId: field[$uid], + class: ["xfaTextfield"], + style, + "aria-label": ariaLabel(field), + "aria-required": false, + }, + }; + } + + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + + return HTMLResult.success({ + name: "label", + attributes: { + class: ["xfaLabel"], + }, + children: [html], + }); + } +} + +class Time extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "time"); + this.id = attributes.id || ""; + this.name = attributes.name || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } + + [$finalize]() { + // TODO: need to handle the string as a time and not as a date. + const date = this[$content].trim(); + this[$content] = date ? new Date(date) : null; + } + + [$toHTML](availableSpace) { + return valueToHtml(this[$content] ? this[$content].toString() : ""); + } +} + +class TimeStamp extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "timeStamp"); + this.id = attributes.id || ""; + this.server = attributes.server || ""; + this.type = getStringOption(attributes.type, ["optional", "required"]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class ToolTip extends StringObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "toolTip"); + this.id = attributes.id || ""; + this.rid = attributes.rid || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + } +} + +class Traversal extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "traversal", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.traverse = new XFAObjectArray(); + } +} + +class Traverse extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "traverse", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.operation = getStringOption(attributes.operation, [ + "next", + "back", + "down", + "first", + "left", + "right", + "up", + ]); + this.ref = attributes.ref || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.script = null; + } + + get name() { + // SOM expression: see page 94 + return this.operation; + } + + [$isTransparent]() { + return false; + } +} + +class Ui extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "ui", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.picture = null; + + // One-of properties + this.barcode = null; + this.button = null; + this.checkButton = null; + this.choiceList = null; + this.dateTimeEdit = null; + this.defaultUi = null; + this.imageEdit = null; + this.numericEdit = null; + this.passwordEdit = null; + this.signature = null; + this.textEdit = null; + } + + [$getExtra]() { + if (this[$extra] === undefined) { + for (const name of Object.getOwnPropertyNames(this)) { + if (name === "extras" || name === "picture") { + continue; + } + const obj = this[name]; + if (!(obj instanceof XFAObject)) { + continue; + } + + this[$extra] = obj; + return obj; + } + this[$extra] = null; + } + return this[$extra]; + } + + [$toHTML](availableSpace) { + // TODO: picture. + const obj = this[$getExtra](); + if (obj) { + return obj[$toHTML](availableSpace); + } + return HTMLResult.EMPTY; + } +} + +class Validate extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "validate", /* hasChildren = */ true); + this.formatTest = getStringOption(attributes.formatTest, [ + "warning", + "disabled", + "error", + ]); + this.id = attributes.id || ""; + this.nullTest = getStringOption(attributes.nullTest, [ + "disabled", + "error", + "warning", + ]); + this.scriptTest = getStringOption(attributes.scriptTest, [ + "error", + "disabled", + "warning", + ]); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.extras = null; + this.message = null; + this.picture = null; + this.script = null; + } +} + +class Value extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "value", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.override = getInteger({ + data: attributes.override, + defaultValue: 0, + validate: x => x === 1, + }); + this.relevant = getRelevant(attributes.relevant); + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + + // One-of properties + this.arc = null; + this.boolean = null; + this.date = null; + this.dateTime = null; + this.decimal = null; + this.exData = null; + this.float = null; + this.image = null; + this.integer = null; + this.line = null; + this.rectangle = null; + this.text = null; + this.time = null; + } + + [$setValue](value) { + const parent = this[$getParent](); + if (parent instanceof Field) { + if (parent.ui?.imageEdit) { + if (!this.image) { + this.image = new Image({}); + this[$appendChild](this.image); + } + this.image[$content] = value[$content]; + return; + } + } + + const valueName = value[$nodeName]; + if (this[valueName] !== null) { + this[valueName][$content] = value[$content]; + return; + } + + // Reset all the properties. + for (const name of Object.getOwnPropertyNames(this)) { + const obj = this[name]; + if (obj instanceof XFAObject) { + this[name] = null; + this[$removeChild](obj); + } + } + + this[value[$nodeName]] = value; + this[$appendChild](value); + } + + [$text]() { + if (this.exData) { + if (typeof this.exData[$content] === "string") { + return this.exData[$content].trim(); + } + return this.exData[$content][$text]().trim(); + } + for (const name of Object.getOwnPropertyNames(this)) { + if (name === "image") { + continue; + } + const obj = this[name]; + if (obj instanceof XFAObject) { + return (obj[$content] || "").toString().trim(); + } + } + return null; + } + + [$toHTML](availableSpace) { + for (const name of Object.getOwnPropertyNames(this)) { + const obj = this[name]; + if (!(obj instanceof XFAObject)) { + continue; + } + + return obj[$toHTML](availableSpace); + } + + return HTMLResult.EMPTY; + } +} + +class Variables extends XFAObject { + constructor(attributes) { + super(TEMPLATE_NS_ID, "variables", /* hasChildren = */ true); + this.id = attributes.id || ""; + this.use = attributes.use || ""; + this.usehref = attributes.usehref || ""; + this.boolean = new XFAObjectArray(); + this.date = new XFAObjectArray(); + this.dateTime = new XFAObjectArray(); + this.decimal = new XFAObjectArray(); + this.exData = new XFAObjectArray(); + this.float = new XFAObjectArray(); + this.image = new XFAObjectArray(); + this.integer = new XFAObjectArray(); + this.manifest = new XFAObjectArray(); + this.script = new XFAObjectArray(); + this.text = new XFAObjectArray(); + this.time = new XFAObjectArray(); + } + + [$isTransparent]() { + return true; + } +} + +class TemplateNamespace { + static [$buildXFAObject](name, attributes) { + if (TemplateNamespace.hasOwnProperty(name)) { + const node = TemplateNamespace[name](attributes); + node[$setSetAttributes](attributes); + return node; + } + return undefined; + } + + static appearanceFilter(attrs) { + return new AppearanceFilter(attrs); + } + + static arc(attrs) { + return new Arc(attrs); + } + + static area(attrs) { + return new Area(attrs); + } + + static assist(attrs) { + return new Assist(attrs); + } + + static barcode(attrs) { + return new Barcode(attrs); + } + + static bind(attrs) { + return new Bind(attrs); + } + + static bindItems(attrs) { + return new BindItems(attrs); + } + + static bookend(attrs) { + return new Bookend(attrs); + } + + static boolean(attrs) { + return new BooleanElement(attrs); + } + + static border(attrs) { + return new Border(attrs); + } + + static break(attrs) { + return new Break(attrs); + } + + static breakAfter(attrs) { + return new BreakAfter(attrs); + } + + static breakBefore(attrs) { + return new BreakBefore(attrs); + } + + static button(attrs) { + return new Button(attrs); + } + + static calculate(attrs) { + return new Calculate(attrs); + } + + static caption(attrs) { + return new Caption(attrs); + } + + static certificate(attrs) { + return new Certificate(attrs); + } + + static certificates(attrs) { + return new Certificates(attrs); + } + + static checkButton(attrs) { + return new CheckButton(attrs); + } + + static choiceList(attrs) { + return new ChoiceList(attrs); + } + + static color(attrs) { + return new Color(attrs); + } + + static comb(attrs) { + return new Comb(attrs); + } + + static connect(attrs) { + return new Connect(attrs); + } + + static contentArea(attrs) { + return new ContentArea(attrs); + } + + static corner(attrs) { + return new Corner(attrs); + } + + static date(attrs) { + return new DateElement(attrs); + } + + static dateTime(attrs) { + return new DateTime(attrs); + } + + static dateTimeEdit(attrs) { + return new DateTimeEdit(attrs); + } + + static decimal(attrs) { + return new Decimal(attrs); + } + + static defaultUi(attrs) { + return new DefaultUi(attrs); + } + + static desc(attrs) { + return new Desc(attrs); + } + + static digestMethod(attrs) { + return new DigestMethod(attrs); + } + + static digestMethods(attrs) { + return new DigestMethods(attrs); + } + + static draw(attrs) { + return new Draw(attrs); + } + + static edge(attrs) { + return new Edge(attrs); + } + + static encoding(attrs) { + return new Encoding(attrs); + } + + static encodings(attrs) { + return new Encodings(attrs); + } + + static encrypt(attrs) { + return new Encrypt(attrs); + } + + static encryptData(attrs) { + return new EncryptData(attrs); + } + + static encryption(attrs) { + return new Encryption(attrs); + } + + static encryptionMethod(attrs) { + return new EncryptionMethod(attrs); + } + + static encryptionMethods(attrs) { + return new EncryptionMethods(attrs); + } + + static event(attrs) { + return new Event(attrs); + } + + static exData(attrs) { + return new ExData(attrs); + } + + static exObject(attrs) { + return new ExObject(attrs); + } + + static exclGroup(attrs) { + return new ExclGroup(attrs); + } + + static execute(attrs) { + return new Execute(attrs); + } + + static extras(attrs) { + return new Extras(attrs); + } + + static field(attrs) { + return new Field(attrs); + } + + static fill(attrs) { + return new Fill(attrs); + } + + static filter(attrs) { + return new Filter(attrs); + } + + static float(attrs) { + return new Float(attrs); + } + + static font(attrs) { + return new Font(attrs); + } + + static format(attrs) { + return new Format(attrs); + } + + static handler(attrs) { + return new Handler(attrs); + } + + static hyphenation(attrs) { + return new Hyphenation(attrs); + } + + static image(attrs) { + return new Image(attrs); + } + + static imageEdit(attrs) { + return new ImageEdit(attrs); + } + + static integer(attrs) { + return new Integer(attrs); + } + + static issuers(attrs) { + return new Issuers(attrs); + } + + static items(attrs) { + return new Items(attrs); + } + + static keep(attrs) { + return new Keep(attrs); + } + + static keyUsage(attrs) { + return new KeyUsage(attrs); + } + + static line(attrs) { + return new Line(attrs); + } + + static linear(attrs) { + return new Linear(attrs); + } + + static lockDocument(attrs) { + return new LockDocument(attrs); + } + + static manifest(attrs) { + return new Manifest(attrs); + } + + static margin(attrs) { + return new Margin(attrs); + } + + static mdp(attrs) { + return new Mdp(attrs); + } + + static medium(attrs) { + return new Medium(attrs); + } + + static message(attrs) { + return new Message(attrs); + } + + static numericEdit(attrs) { + return new NumericEdit(attrs); + } + + static occur(attrs) { + return new Occur(attrs); + } + + static oid(attrs) { + return new Oid(attrs); + } + + static oids(attrs) { + return new Oids(attrs); + } + + static overflow(attrs) { + return new Overflow(attrs); + } + + static pageArea(attrs) { + return new PageArea(attrs); + } + + static pageSet(attrs) { + return new PageSet(attrs); + } + + static para(attrs) { + return new Para(attrs); + } + + static passwordEdit(attrs) { + return new PasswordEdit(attrs); + } + + static pattern(attrs) { + return new Pattern(attrs); + } + + static picture(attrs) { + return new Picture(attrs); + } + + static proto(attrs) { + return new Proto(attrs); + } + + static radial(attrs) { + return new Radial(attrs); + } + + static reason(attrs) { + return new Reason(attrs); + } + + static reasons(attrs) { + return new Reasons(attrs); + } + + static rectangle(attrs) { + return new Rectangle(attrs); + } + + static ref(attrs) { + return new RefElement(attrs); + } + + static script(attrs) { + return new Script(attrs); + } + + static setProperty(attrs) { + return new SetProperty(attrs); + } + + static signData(attrs) { + return new SignData(attrs); + } + + static signature(attrs) { + return new Signature(attrs); + } + + static signing(attrs) { + return new Signing(attrs); + } + + static solid(attrs) { + return new Solid(attrs); + } + + static speak(attrs) { + return new Speak(attrs); + } + + static stipple(attrs) { + return new Stipple(attrs); + } + + static subform(attrs) { + return new Subform(attrs); + } + + static subformSet(attrs) { + return new SubformSet(attrs); + } + + static subjectDN(attrs) { + return new SubjectDN(attrs); + } + + static subjectDNs(attrs) { + return new SubjectDNs(attrs); + } + + static submit(attrs) { + return new Submit(attrs); + } + + static template(attrs) { + return new Template(attrs); + } + + static text(attrs) { + return new Text(attrs); + } + + static textEdit(attrs) { + return new TextEdit(attrs); + } + + static time(attrs) { + return new Time(attrs); + } + + static timeStamp(attrs) { + return new TimeStamp(attrs); + } + + static toolTip(attrs) { + return new ToolTip(attrs); + } + + static traversal(attrs) { + return new Traversal(attrs); + } + + static traverse(attrs) { + return new Traverse(attrs); + } + + static ui(attrs) { + return new Ui(attrs); + } + + static validate(attrs) { + return new Validate(attrs); + } + + static value(attrs) { + return new Value(attrs); + } + + static variables(attrs) { + return new Variables(attrs); + } +} + +export { + BindItems, + Field, + Items, + SetProperty, + Template, + TemplateNamespace, + Text, + Value, +}; diff --git a/frontend/public/pdf.js/src/core/xfa/text.js b/frontend/public/pdf.js/src/core/xfa/text.js new file mode 100644 index 0000000..0c12c4b --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/text.js @@ -0,0 +1,304 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { selectFont } from "./fonts.js"; + +const WIDTH_FACTOR = 1.02; + +class FontInfo { + constructor(xfaFont, margin, lineHeight, fontFinder) { + this.lineHeight = lineHeight; + this.paraMargin = margin || { + top: 0, + bottom: 0, + left: 0, + right: 0, + }; + + if (!xfaFont) { + [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder); + return; + } + + this.xfaFont = { + typeface: xfaFont.typeface, + posture: xfaFont.posture, + weight: xfaFont.weight, + size: xfaFont.size, + letterSpacing: xfaFont.letterSpacing, + }; + const typeface = fontFinder.find(xfaFont.typeface); + if (!typeface) { + [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder); + return; + } + + this.pdfFont = selectFont(xfaFont, typeface); + + if (!this.pdfFont) { + [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder); + } + } + + defaultFont(fontFinder) { + // TODO: Add a default font based on Liberation. + const font = + fontFinder.find("Helvetica", false) || + fontFinder.find("Myriad Pro", false) || + fontFinder.find("Arial", false) || + fontFinder.getDefault(); + if (font?.regular) { + const pdfFont = font.regular; + const info = pdfFont.cssFontInfo; + const xfaFont = { + typeface: info.fontFamily, + posture: "normal", + weight: "normal", + size: 10, + letterSpacing: 0, + }; + return [pdfFont, xfaFont]; + } + + const xfaFont = { + typeface: "Courier", + posture: "normal", + weight: "normal", + size: 10, + letterSpacing: 0, + }; + return [null, xfaFont]; + } +} + +class FontSelector { + constructor( + defaultXfaFont, + defaultParaMargin, + defaultLineHeight, + fontFinder + ) { + this.fontFinder = fontFinder; + this.stack = [ + new FontInfo( + defaultXfaFont, + defaultParaMargin, + defaultLineHeight, + fontFinder + ), + ]; + } + + pushData(xfaFont, margin, lineHeight) { + const lastFont = this.stack.at(-1); + for (const name of [ + "typeface", + "posture", + "weight", + "size", + "letterSpacing", + ]) { + if (!xfaFont[name]) { + xfaFont[name] = lastFont.xfaFont[name]; + } + } + + for (const name of ["top", "bottom", "left", "right"]) { + if (isNaN(margin[name])) { + margin[name] = lastFont.paraMargin[name]; + } + } + + const fontInfo = new FontInfo( + xfaFont, + margin, + lineHeight || lastFont.lineHeight, + this.fontFinder + ); + if (!fontInfo.pdfFont) { + fontInfo.pdfFont = lastFont.pdfFont; + } + + this.stack.push(fontInfo); + } + + popFont() { + this.stack.pop(); + } + + topFont() { + return this.stack.at(-1); + } +} + +/** + * Compute a text area dimensions based on font metrics. + */ +class TextMeasure { + constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts) { + this.glyphs = []; + this.fontSelector = new FontSelector( + defaultXfaFont, + defaultParaMargin, + defaultLineHeight, + fonts + ); + this.extraHeight = 0; + } + + pushData(xfaFont, margin, lineHeight) { + this.fontSelector.pushData(xfaFont, margin, lineHeight); + } + + popFont(xfaFont) { + return this.fontSelector.popFont(); + } + + addPara() { + const lastFont = this.fontSelector.topFont(); + this.extraHeight += lastFont.paraMargin.top + lastFont.paraMargin.bottom; + } + + addString(str) { + if (!str) { + return; + } + + const lastFont = this.fontSelector.topFont(); + const fontSize = lastFont.xfaFont.size; + if (lastFont.pdfFont) { + const letterSpacing = lastFont.xfaFont.letterSpacing; + const pdfFont = lastFont.pdfFont; + const fontLineHeight = pdfFont.lineHeight || 1.2; + const lineHeight = + lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize; + const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap; + const noGap = fontLineHeight - lineGap; + const firstLineHeight = Math.max(1, noGap) * fontSize; + const scale = fontSize / 1000; + const fallbackWidth = + pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width; + + for (const line of str.split(/[\u2029\n]/)) { + const encodedLine = pdfFont.encodeString(line).join(""); + const glyphs = pdfFont.charsToGlyphs(encodedLine); + + for (const glyph of glyphs) { + const width = glyph.width || fallbackWidth; + this.glyphs.push([ + width * scale + letterSpacing, + lineHeight, + firstLineHeight, + glyph.unicode, + false, + ]); + } + + this.glyphs.push([0, 0, 0, "\n", true]); + } + this.glyphs.pop(); + return; + } + + // When we have no font in the pdf, just use the font size as default width. + for (const line of str.split(/[\u2029\n]/)) { + for (const char of line.split("")) { + this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]); + } + + this.glyphs.push([0, 0, 0, "\n", true]); + } + this.glyphs.pop(); + } + + compute(maxWidth) { + let lastSpacePos = -1, + lastSpaceWidth = 0, + width = 0, + height = 0, + currentLineWidth = 0, + currentLineHeight = 0; + let isBroken = false; + let isFirstLine = true; + + for (let i = 0, ii = this.glyphs.length; i < ii; i++) { + const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = + this.glyphs[i]; + const isSpace = char === " "; + const glyphHeight = isFirstLine ? firstLineHeight : lineHeight; + if (isEOL) { + width = Math.max(width, currentLineWidth); + currentLineWidth = 0; + height += currentLineHeight; + currentLineHeight = glyphHeight; + lastSpacePos = -1; + lastSpaceWidth = 0; + isFirstLine = false; + continue; + } + + if (isSpace) { + if (currentLineWidth + glyphWidth > maxWidth) { + // We can break here but the space is not taken into account. + width = Math.max(width, currentLineWidth); + currentLineWidth = 0; + height += currentLineHeight; + currentLineHeight = glyphHeight; + lastSpacePos = -1; + lastSpaceWidth = 0; + isBroken = true; + isFirstLine = false; + } else { + currentLineHeight = Math.max(glyphHeight, currentLineHeight); + lastSpaceWidth = currentLineWidth; + currentLineWidth += glyphWidth; + lastSpacePos = i; + } + continue; + } + + if (currentLineWidth + glyphWidth > maxWidth) { + // We must break to the last white position (if available) + height += currentLineHeight; + currentLineHeight = glyphHeight; + if (lastSpacePos !== -1) { + i = lastSpacePos; + width = Math.max(width, lastSpaceWidth); + currentLineWidth = 0; + lastSpacePos = -1; + lastSpaceWidth = 0; + } else { + // Just break in the middle of the word + width = Math.max(width, currentLineWidth); + currentLineWidth = glyphWidth; + } + isBroken = true; + isFirstLine = false; + + continue; + } + + currentLineWidth += glyphWidth; + currentLineHeight = Math.max(glyphHeight, currentLineHeight); + } + + width = Math.max(width, currentLineWidth); + height += currentLineHeight + this.extraHeight; + + return { width: WIDTH_FACTOR * width, height, isBroken }; + } +} + +export { TextMeasure }; diff --git a/frontend/public/pdf.js/src/core/xfa/unknown.js b/frontend/public/pdf.js/src/core/xfa/unknown.js new file mode 100644 index 0000000..147863c --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/unknown.js @@ -0,0 +1,29 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject } from "./namespaces.js"; +import { XmlObject } from "./xfa_object.js"; + +class UnknownNamespace { + constructor(nsId) { + this.namespaceId = nsId; + } + + [$buildXFAObject](name, attributes) { + return new XmlObject(this.namespaceId, name, attributes); + } +} + +export { UnknownNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/utils.js b/frontend/public/pdf.js/src/core/xfa/utils.js new file mode 100644 index 0000000..6db780e --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/utils.js @@ -0,0 +1,215 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { shadow } from "../../shared/util.js"; + +const dimConverters = { + pt: x => x, + cm: x => (x / 2.54) * 72, + mm: x => (x / (10 * 2.54)) * 72, + in: x => x * 72, + px: x => x, +}; +const measurementPattern = /([+-]?\d+\.?\d*)(.*)/; + +function stripQuotes(str) { + if (str.startsWith("'") || str.startsWith('"')) { + return str.slice(1, -1); + } + return str; +} + +function getInteger({ data, defaultValue, validate }) { + if (!data) { + return defaultValue; + } + data = data.trim(); + const n = parseInt(data, 10); + if (!isNaN(n) && validate(n)) { + return n; + } + return defaultValue; +} + +function getFloat({ data, defaultValue, validate }) { + if (!data) { + return defaultValue; + } + data = data.trim(); + const n = parseFloat(data); + if (!isNaN(n) && validate(n)) { + return n; + } + return defaultValue; +} + +function getKeyword({ data, defaultValue, validate }) { + if (!data) { + return defaultValue; + } + data = data.trim(); + if (validate(data)) { + return data; + } + return defaultValue; +} + +function getStringOption(data, options) { + return getKeyword({ + data, + defaultValue: options[0], + validate: k => options.includes(k), + }); +} + +function getMeasurement(str, def = "0") { + def ||= "0"; + if (!str) { + return getMeasurement(def); + } + const match = str.trim().match(measurementPattern); + if (!match) { + return getMeasurement(def); + } + const [, valueStr, unit] = match; + const value = parseFloat(valueStr); + if (isNaN(value)) { + return getMeasurement(def); + } + + if (value === 0) { + return 0; + } + + const conv = dimConverters[unit]; + if (conv) { + return conv(value); + } + + return value; +} + +function getRatio(data) { + if (!data) { + return { num: 1, den: 1 }; + } + const ratio = data + .trim() + .split(/\s*:\s*/) + .map(x => parseFloat(x)) + .filter(x => !isNaN(x)); + if (ratio.length === 1) { + ratio.push(1); + } + + if (ratio.length === 0) { + return { num: 1, den: 1 }; + } + + const [num, den] = ratio; + return { num, den }; +} + +function getRelevant(data) { + if (!data) { + return []; + } + return data + .trim() + .split(/\s+/) + .map(e => ({ + excluded: e[0] === "-", + viewname: e.substring(1), + })); +} + +function getColor(data, def = [0, 0, 0]) { + let [r, g, b] = def; + if (!data) { + return { r, g, b }; + } + const color = data + .trim() + .split(/\s*,\s*/) + .map(c => Math.min(Math.max(0, parseInt(c.trim(), 10)), 255)) + .map(c => (isNaN(c) ? 0 : c)); + + if (color.length < 3) { + return { r, g, b }; + } + + [r, g, b] = color; + return { r, g, b }; +} + +function getBBox(data) { + const def = -1; + if (!data) { + return { x: def, y: def, width: def, height: def }; + } + const bbox = data + .trim() + .split(/\s*,\s*/) + .map(m => getMeasurement(m, "-1")); + if (bbox.length < 4 || bbox[2] < 0 || bbox[3] < 0) { + return { x: def, y: def, width: def, height: def }; + } + + const [x, y, width, height] = bbox; + return { x, y, width, height }; +} + +class HTMLResult { + static get FAILURE() { + return shadow(this, "FAILURE", new HTMLResult(false, null, null, null)); + } + + static get EMPTY() { + return shadow(this, "EMPTY", new HTMLResult(true, null, null, null)); + } + + constructor(success, html, bbox, breakNode) { + this.success = success; + this.html = html; + this.bbox = bbox; + this.breakNode = breakNode; + } + + isBreak() { + return !!this.breakNode; + } + + static breakNode(node) { + return new HTMLResult(false, null, null, node); + } + + static success(html, bbox = null) { + return new HTMLResult(true, html, bbox, null); + } +} + +export { + getBBox, + getColor, + getFloat, + getInteger, + getKeyword, + getMeasurement, + getRatio, + getRelevant, + getStringOption, + HTMLResult, + stripQuotes, +}; diff --git a/frontend/public/pdf.js/src/core/xfa/xdp.js b/frontend/public/pdf.js/src/core/xfa/xdp.js new file mode 100644 index 0000000..f396867 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/xdp.js @@ -0,0 +1,54 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { $namespaceId, $nodeName, $onChildCheck } from "./symbol_utils.js"; +import { XFAObject, XFAObjectArray } from "./xfa_object.js"; + +const XDP_NS_ID = NamespaceIds.xdp.id; + +class Xdp extends XFAObject { + constructor(attributes) { + super(XDP_NS_ID, "xdp", /* hasChildren = */ true); + this.uuid = attributes.uuid || ""; + this.timeStamp = attributes.timeStamp || ""; + this.config = null; + this.connectionSet = null; + this.datasets = null; + this.localeSet = null; + this.stylesheet = new XFAObjectArray(); + this.template = null; + } + + [$onChildCheck](child) { + const ns = NamespaceIds[child[$nodeName]]; + return ns && child[$namespaceId] === ns.id; + } +} + +class XdpNamespace { + static [$buildXFAObject](name, attributes) { + if (XdpNamespace.hasOwnProperty(name)) { + return XdpNamespace[name](attributes); + } + return undefined; + } + + static xdp(attributes) { + return new Xdp(attributes); + } +} + +export { XdpNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa/xfa_object.js b/frontend/public/pdf.js/src/core/xfa/xfa_object.js new file mode 100644 index 0000000..8de0c17 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/xfa_object.js @@ -0,0 +1,1109 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $acceptWhitespace, + $addHTML, + $appendChild, + $childrenToHTML, + $clean, + $cleanup, + $clone, + $consumed, + $content, + $dump, + $extra, + $finalize, + $flushHTML, + $getAttributeIt, + $getAttributes, + $getAvailableSpace, + $getChildren, + $getChildrenByClass, + $getChildrenByName, + $getChildrenByNameIt, + $getContainedChildren, + $getDataValue, + $getParent, + $getRealChildrenByNameIt, + $getSubformParent, + $getTemplateRoot, + $globalData, + $hasSettableValue, + $indexOf, + $insertAt, + $isBindable, + $isCDATAXml, + $isDataValue, + $isDescendent, + $isNsAgnostic, + $isSplittable, + $isThereMoreWidth, + $isTransparent, + $lastAttribute, + $namespaceId, + $nodeName, + $nsAttributes, + $onChild, + $onChildCheck, + $onText, + $popPara, + $pushPara, + $removeChild, + $resolvePrototypes, + $root, + $setId, + $setSetAttributes, + $setValue, + $text, + $toHTML, + $toString, + $toStyle, + $uid, +} from "./symbol_utils.js"; +import { getInteger, getKeyword, HTMLResult } from "./utils.js"; +import { shadow, utf8StringToString, warn } from "../../shared/util.js"; +import { encodeToXmlString } from "../core_utils.js"; +import { NamespaceIds } from "./namespaces.js"; +import { searchNode } from "./som.js"; + +const _applyPrototype = Symbol(); +const _attributes = Symbol(); +const _attributeNames = Symbol(); +const _children = Symbol("_children"); +const _cloneAttribute = Symbol(); +const _dataValue = Symbol(); +const _defaultValue = Symbol(); +const _filteredChildrenGenerator = Symbol(); +const _getPrototype = Symbol(); +const _getUnsetAttributes = Symbol(); +const _hasChildren = Symbol(); +const _max = Symbol(); +const _options = Symbol(); +const _parent = Symbol("parent"); +const _resolvePrototypesHelper = Symbol(); +const _setAttributes = Symbol(); +const _validator = Symbol(); + +let uid = 0; + +const NS_DATASETS = NamespaceIds.datasets.id; + +class XFAObject { + constructor(nsId, name, hasChildren = false) { + this[$namespaceId] = nsId; + this[$nodeName] = name; + this[_hasChildren] = hasChildren; + this[_parent] = null; + this[_children] = []; + this[$uid] = `${name}${uid++}`; + this[$globalData] = null; + } + + get isXFAObject() { + return true; + } + + get isXFAObjectArray() { + return false; + } + + createNodes(path) { + let root = this, + node = null; + for (const { name, index } of path) { + for (let i = 0, ii = isFinite(index) ? index : 0; i <= ii; i++) { + const nsId = + root[$namespaceId] === NS_DATASETS ? -1 : root[$namespaceId]; + node = new XmlObject(nsId, name); + root[$appendChild](node); + } + root = node; + } + return node; + } + + [$onChild](child) { + if (!this[_hasChildren] || !this[$onChildCheck](child)) { + return false; + } + + const name = child[$nodeName]; + const node = this[name]; + + if (node instanceof XFAObjectArray) { + if (node.push(child)) { + this[$appendChild](child); + return true; + } + } else { + // IRL it's possible to already have a node. + // So just replace it with the last version. + if (node !== null) { + this[$removeChild](node); + } + this[name] = child; + this[$appendChild](child); + return true; + } + + let id = ""; + if (this.id) { + id = ` (id: ${this.id})`; + } else if (this.name) { + id = ` (name: ${this.name} ${this.h.value})`; + } + warn(`XFA - node "${this[$nodeName]}"${id} has already enough "${name}"!`); + return false; + } + + [$onChildCheck](child) { + return ( + this.hasOwnProperty(child[$nodeName]) && + child[$namespaceId] === this[$namespaceId] + ); + } + + [$isNsAgnostic]() { + return false; + } + + [$acceptWhitespace]() { + return false; + } + + [$isCDATAXml]() { + return false; + } + + [$isBindable]() { + return false; + } + + [$popPara]() { + if (this.para) { + this[$getTemplateRoot]()[$extra].paraStack.pop(); + } + } + + [$pushPara]() { + this[$getTemplateRoot]()[$extra].paraStack.push(this.para); + } + + [$setId](ids) { + if (this.id && this[$namespaceId] === NamespaceIds.template.id) { + ids.set(this.id, this); + } + } + + [$getTemplateRoot]() { + return this[$globalData].template; + } + + [$isSplittable]() { + return false; + } + + /** + Return true if this node (typically a container) + can provide more width during layout. + The goal is to help to know what a descendant must + do in case of horizontal overflow. + */ + [$isThereMoreWidth]() { + return false; + } + + [$appendChild](child) { + child[_parent] = this; + this[_children].push(child); + if (!child[$globalData] && this[$globalData]) { + child[$globalData] = this[$globalData]; + } + } + + [$removeChild](child) { + const i = this[_children].indexOf(child); + this[_children].splice(i, 1); + } + + [$hasSettableValue]() { + return this.hasOwnProperty("value"); + } + + [$setValue](_) {} + + [$onText](_) {} + + [$finalize]() {} + + [$clean](builder) { + delete this[_hasChildren]; + if (this[$cleanup]) { + builder.clean(this[$cleanup]); + delete this[$cleanup]; + } + } + + [$indexOf](child) { + return this[_children].indexOf(child); + } + + [$insertAt](i, child) { + child[_parent] = this; + this[_children].splice(i, 0, child); + if (!child[$globalData] && this[$globalData]) { + child[$globalData] = this[$globalData]; + } + } + + /** + * If true the element is transparent when searching a node using + * a SOM expression which means that looking for "foo.bar" in + * <... name="foo"><... name="bar">... + * is fine because toto and titi are transparent. + */ + [$isTransparent]() { + return !this.name; + } + + [$lastAttribute]() { + return ""; + } + + [$text]() { + if (this[_children].length === 0) { + return this[$content]; + } + return this[_children].map(c => c[$text]()).join(""); + } + + get [_attributeNames]() { + // Lazily get attributes names + const proto = Object.getPrototypeOf(this); + if (!proto._attributes) { + const attributes = (proto._attributes = new Set()); + for (const name of Object.getOwnPropertyNames(this)) { + if ( + this[name] === null || + this[name] instanceof XFAObject || + this[name] instanceof XFAObjectArray + ) { + break; + } + attributes.add(name); + } + } + return shadow(this, _attributeNames, proto._attributes); + } + + [$isDescendent](parent) { + let node = this; + while (node) { + if (node === parent) { + return true; + } + node = node[$getParent](); + } + return false; + } + + [$getParent]() { + return this[_parent]; + } + + [$getSubformParent]() { + return this[$getParent](); + } + + [$getChildren](name = null) { + if (!name) { + return this[_children]; + } + + return this[name]; + } + + [$dump]() { + const dumped = Object.create(null); + if (this[$content]) { + dumped.$content = this[$content]; + } + + for (const name of Object.getOwnPropertyNames(this)) { + const value = this[name]; + if (value === null) { + continue; + } + if (value instanceof XFAObject) { + dumped[name] = value[$dump](); + } else if (value instanceof XFAObjectArray) { + if (!value.isEmpty()) { + dumped[name] = value.dump(); + } + } else { + dumped[name] = value; + } + } + + return dumped; + } + + [$toStyle]() { + return null; + } + + [$toHTML]() { + return HTMLResult.EMPTY; + } + + *[$getContainedChildren]() { + // This function is overriden in Subform and SubformSet. + for (const node of this[$getChildren]()) { + yield node; + } + } + + *[_filteredChildrenGenerator](filter, include) { + for (const node of this[$getContainedChildren]()) { + if (!filter || include === filter.has(node[$nodeName])) { + const availableSpace = this[$getAvailableSpace](); + const res = node[$toHTML](availableSpace); + if (!res.success) { + this[$extra].failingNode = node; + } + yield res; + } + } + } + + [$flushHTML]() { + return null; + } + + [$addHTML](html, bbox) { + this[$extra].children.push(html); + } + + [$getAvailableSpace]() {} + + [$childrenToHTML]({ filter = null, include = true }) { + if (!this[$extra].generator) { + this[$extra].generator = this[_filteredChildrenGenerator]( + filter, + include + ); + } else { + const availableSpace = this[$getAvailableSpace](); + const res = this[$extra].failingNode[$toHTML](availableSpace); + if (!res.success) { + return res; + } + if (res.html) { + this[$addHTML](res.html, res.bbox); + } + delete this[$extra].failingNode; + } + + while (true) { + const gen = this[$extra].generator.next(); + if (gen.done) { + break; + } + const res = gen.value; + if (!res.success) { + return res; + } + if (res.html) { + this[$addHTML](res.html, res.bbox); + } + } + + this[$extra].generator = null; + + return HTMLResult.EMPTY; + } + + [$setSetAttributes](attributes) { + // Just keep set attributes because it can be used in a proto. + this[_setAttributes] = new Set(Object.keys(attributes)); + } + + /** + * Get attribute names which have been set in the proto but not in this. + */ + [_getUnsetAttributes](protoAttributes) { + const allAttr = this[_attributeNames]; + const setAttr = this[_setAttributes]; + return [...protoAttributes].filter(x => allAttr.has(x) && !setAttr.has(x)); + } + + /** + * Update the node with properties coming from a prototype and apply + * this function recursively to all children. + */ + [$resolvePrototypes](ids, ancestors = new Set()) { + for (const child of this[_children]) { + child[_resolvePrototypesHelper](ids, ancestors); + } + } + + [_resolvePrototypesHelper](ids, ancestors) { + const proto = this[_getPrototype](ids, ancestors); + if (proto) { + // _applyPrototype will apply $resolvePrototypes with correct ancestors + // to avoid infinite loop. + this[_applyPrototype](proto, ids, ancestors); + } else { + this[$resolvePrototypes](ids, ancestors); + } + } + + [_getPrototype](ids, ancestors) { + const { use, usehref } = this; + if (!use && !usehref) { + return null; + } + + let proto = null; + let somExpression = null; + let id = null; + let ref = use; + + // If usehref and use are non-empty then use usehref. + if (usehref) { + ref = usehref; + // Href can be one of the following: + // - #ID + // - URI#ID + // - #som(expression) + // - URI#som(expression) + // - URI + // For now we don't handle URI other than "." (current document). + if (usehref.startsWith("#som(") && usehref.endsWith(")")) { + somExpression = usehref.slice("#som(".length, -1); + } else if (usehref.startsWith(".#som(") && usehref.endsWith(")")) { + somExpression = usehref.slice(".#som(".length, -1); + } else if (usehref.startsWith("#")) { + id = usehref.slice(1); + } else if (usehref.startsWith(".#")) { + id = usehref.slice(2); + } + } else if (use.startsWith("#")) { + id = use.slice(1); + } else { + somExpression = use; + } + + this.use = this.usehref = ""; + if (id) { + proto = ids.get(id); + } else { + proto = searchNode( + ids.get($root), + this, + somExpression, + true /* = dotDotAllowed */, + false /* = useCache */ + ); + if (proto) { + proto = proto[0]; + } + } + + if (!proto) { + warn(`XFA - Invalid prototype reference: ${ref}.`); + return null; + } + + if (proto[$nodeName] !== this[$nodeName]) { + warn( + `XFA - Incompatible prototype: ${proto[$nodeName]} !== ${this[$nodeName]}.` + ); + return null; + } + + if (ancestors.has(proto)) { + // We've a cycle so break it. + warn(`XFA - Cycle detected in prototypes use.`); + return null; + } + + ancestors.add(proto); + + // The prototype can have a "use" attribute itself. + const protoProto = proto[_getPrototype](ids, ancestors); + if (protoProto) { + proto[_applyPrototype](protoProto, ids, ancestors); + } + + // The prototype can have a child which itself has a "use" property. + proto[$resolvePrototypes](ids, ancestors); + + ancestors.delete(proto); + + return proto; + } + + [_applyPrototype](proto, ids, ancestors) { + if (ancestors.has(proto)) { + // We've a cycle so break it. + warn(`XFA - Cycle detected in prototypes use.`); + return; + } + + if (!this[$content] && proto[$content]) { + this[$content] = proto[$content]; + } + + const newAncestors = new Set(ancestors); + newAncestors.add(proto); + + for (const unsetAttrName of this[_getUnsetAttributes]( + proto[_setAttributes] + )) { + this[unsetAttrName] = proto[unsetAttrName]; + if (this[_setAttributes]) { + this[_setAttributes].add(unsetAttrName); + } + } + + for (const name of Object.getOwnPropertyNames(this)) { + if (this[_attributeNames].has(name)) { + continue; + } + const value = this[name]; + const protoValue = proto[name]; + + if (value instanceof XFAObjectArray) { + for (const child of value[_children]) { + child[_resolvePrototypesHelper](ids, ancestors); + } + + for ( + let i = value[_children].length, ii = protoValue[_children].length; + i < ii; + i++ + ) { + const child = proto[_children][i][$clone](); + if (value.push(child)) { + child[_parent] = this; + this[_children].push(child); + child[_resolvePrototypesHelper](ids, ancestors); + } else { + // No need to continue: other nodes will be rejected. + break; + } + } + continue; + } + + if (value !== null) { + value[$resolvePrototypes](ids, ancestors); + if (protoValue) { + // protoValue must be treated as a prototype for value. + value[_applyPrototype](protoValue, ids, ancestors); + } + continue; + } + + if (protoValue !== null) { + const child = protoValue[$clone](); + child[_parent] = this; + this[name] = child; + this[_children].push(child); + child[_resolvePrototypesHelper](ids, ancestors); + } + } + } + + static [_cloneAttribute](obj) { + if (Array.isArray(obj)) { + return obj.map(x => XFAObject[_cloneAttribute](x)); + } + if (typeof obj === "object" && obj !== null) { + return Object.assign({}, obj); + } + return obj; + } + + [$clone]() { + const clone = Object.create(Object.getPrototypeOf(this)); + for (const $symbol of Object.getOwnPropertySymbols(this)) { + try { + clone[$symbol] = this[$symbol]; + } catch { + shadow(clone, $symbol, this[$symbol]); + } + } + clone[$uid] = `${clone[$nodeName]}${uid++}`; + clone[_children] = []; + + for (const name of Object.getOwnPropertyNames(this)) { + if (this[_attributeNames].has(name)) { + clone[name] = XFAObject[_cloneAttribute](this[name]); + continue; + } + const value = this[name]; + clone[name] = + value instanceof XFAObjectArray + ? new XFAObjectArray(value[_max]) + : null; + } + + for (const child of this[_children]) { + const name = child[$nodeName]; + const clonedChild = child[$clone](); + clone[_children].push(clonedChild); + clonedChild[_parent] = clone; + if (clone[name] === null) { + clone[name] = clonedChild; + } else { + clone[name][_children].push(clonedChild); + } + } + + return clone; + } + + [$getChildren](name = null) { + if (!name) { + return this[_children]; + } + + return this[_children].filter(c => c[$nodeName] === name); + } + + [$getChildrenByClass](name) { + return this[name]; + } + + [$getChildrenByName](name, allTransparent, first = true) { + return Array.from(this[$getChildrenByNameIt](name, allTransparent, first)); + } + + *[$getChildrenByNameIt](name, allTransparent, first = true) { + if (name === "parent") { + yield this[_parent]; + return; + } + + for (const child of this[_children]) { + if (child[$nodeName] === name) { + yield child; + } + + if (child.name === name) { + yield child; + } + + if (allTransparent || child[$isTransparent]()) { + yield* child[$getChildrenByNameIt](name, allTransparent, false); + } + } + + if (first && this[_attributeNames].has(name)) { + yield new XFAAttribute(this, name, this[name]); + } + } +} + +class XFAObjectArray { + constructor(max = Infinity) { + this[_max] = max; + this[_children] = []; + } + + get isXFAObject() { + return false; + } + + get isXFAObjectArray() { + return true; + } + + push(child) { + const len = this[_children].length; + if (len <= this[_max]) { + this[_children].push(child); + return true; + } + warn( + `XFA - node "${child[$nodeName]}" accepts no more than ${this[_max]} children` + ); + return false; + } + + isEmpty() { + return this[_children].length === 0; + } + + dump() { + return this[_children].length === 1 + ? this[_children][0][$dump]() + : this[_children].map(x => x[$dump]()); + } + + [$clone]() { + const clone = new XFAObjectArray(this[_max]); + clone[_children] = this[_children].map(c => c[$clone]()); + return clone; + } + + get children() { + return this[_children]; + } + + clear() { + this[_children].length = 0; + } +} + +class XFAAttribute { + constructor(node, name, value) { + this[_parent] = node; + this[$nodeName] = name; + this[$content] = value; + this[$consumed] = false; + this[$uid] = `attribute${uid++}`; + } + + [$getParent]() { + return this[_parent]; + } + + [$isDataValue]() { + return true; + } + + [$getDataValue]() { + return this[$content].trim(); + } + + [$setValue](value) { + value = value.value || ""; + this[$content] = value.toString(); + } + + [$text]() { + return this[$content]; + } + + [$isDescendent](parent) { + return this[_parent] === parent || this[_parent][$isDescendent](parent); + } +} + +class XmlObject extends XFAObject { + constructor(nsId, name, attributes = {}) { + super(nsId, name); + this[$content] = ""; + this[_dataValue] = null; + if (name !== "#text") { + const map = new Map(); + this[_attributes] = map; + for (const [attrName, value] of Object.entries(attributes)) { + map.set(attrName, new XFAAttribute(this, attrName, value)); + } + if (attributes.hasOwnProperty($nsAttributes)) { + // XFA attributes. + const dataNode = attributes[$nsAttributes].xfa.dataNode; + if (dataNode !== undefined) { + if (dataNode === "dataGroup") { + this[_dataValue] = false; + } else if (dataNode === "dataValue") { + this[_dataValue] = true; + } + } + } + } + this[$consumed] = false; + } + + [$toString](buf) { + const tagName = this[$nodeName]; + if (tagName === "#text") { + buf.push(encodeToXmlString(this[$content])); + return; + } + const utf8TagName = utf8StringToString(tagName); + const prefix = this[$namespaceId] === NS_DATASETS ? "xfa:" : ""; + buf.push(`<${prefix}${utf8TagName}`); + for (const [name, value] of this[_attributes].entries()) { + const utf8Name = utf8StringToString(name); + buf.push(` ${utf8Name}="${encodeToXmlString(value[$content])}"`); + } + if (this[_dataValue] !== null) { + if (this[_dataValue]) { + buf.push(` xfa:dataNode="dataValue"`); + } else { + buf.push(` xfa:dataNode="dataGroup"`); + } + } + if (!this[$content] && this[_children].length === 0) { + buf.push("/>"); + return; + } + + buf.push(">"); + if (this[$content]) { + if (typeof this[$content] === "string") { + buf.push(encodeToXmlString(this[$content])); + } else { + this[$content][$toString](buf); + } + } else { + for (const child of this[_children]) { + child[$toString](buf); + } + } + buf.push(``); + } + + [$onChild](child) { + if (this[$content]) { + const node = new XmlObject(this[$namespaceId], "#text"); + this[$appendChild](node); + node[$content] = this[$content]; + this[$content] = ""; + } + this[$appendChild](child); + return true; + } + + [$onText](str) { + this[$content] += str; + } + + [$finalize]() { + if (this[$content] && this[_children].length > 0) { + const node = new XmlObject(this[$namespaceId], "#text"); + this[$appendChild](node); + node[$content] = this[$content]; + delete this[$content]; + } + } + + [$toHTML]() { + if (this[$nodeName] === "#text") { + return HTMLResult.success({ + name: "#text", + value: this[$content], + }); + } + + return HTMLResult.EMPTY; + } + + [$getChildren](name = null) { + if (!name) { + return this[_children]; + } + + return this[_children].filter(c => c[$nodeName] === name); + } + + [$getAttributes]() { + return this[_attributes]; + } + + [$getChildrenByClass](name) { + const value = this[_attributes].get(name); + if (value !== undefined) { + return value; + } + return this[$getChildren](name); + } + + *[$getChildrenByNameIt](name, allTransparent) { + const value = this[_attributes].get(name); + if (value) { + yield value; + } + + for (const child of this[_children]) { + if (child[$nodeName] === name) { + yield child; + } + + if (allTransparent) { + yield* child[$getChildrenByNameIt](name, allTransparent); + } + } + } + + *[$getAttributeIt](name, skipConsumed) { + const value = this[_attributes].get(name); + if (value && (!skipConsumed || !value[$consumed])) { + yield value; + } + for (const child of this[_children]) { + yield* child[$getAttributeIt](name, skipConsumed); + } + } + + *[$getRealChildrenByNameIt](name, allTransparent, skipConsumed) { + for (const child of this[_children]) { + if (child[$nodeName] === name && (!skipConsumed || !child[$consumed])) { + yield child; + } + + if (allTransparent) { + yield* child[$getRealChildrenByNameIt]( + name, + allTransparent, + skipConsumed + ); + } + } + } + + [$isDataValue]() { + if (this[_dataValue] === null) { + return ( + this[_children].length === 0 || + this[_children][0][$namespaceId] === NamespaceIds.xhtml.id + ); + } + return this[_dataValue]; + } + + [$getDataValue]() { + if (this[_dataValue] === null) { + if (this[_children].length === 0) { + return this[$content].trim(); + } + if (this[_children][0][$namespaceId] === NamespaceIds.xhtml.id) { + return this[_children][0][$text]().trim(); + } + return null; + } + return this[$content].trim(); + } + + [$setValue](value) { + value = value.value || ""; + this[$content] = value.toString(); + } + + [$dump](hasNS = false) { + const dumped = Object.create(null); + if (hasNS) { + dumped.$ns = this[$namespaceId]; + } + if (this[$content]) { + dumped.$content = this[$content]; + } + dumped.$name = this[$nodeName]; + + dumped.children = []; + for (const child of this[_children]) { + dumped.children.push(child[$dump](hasNS)); + } + + dumped.attributes = Object.create(null); + for (const [name, value] of this[_attributes]) { + dumped.attributes[name] = value[$content]; + } + + return dumped; + } +} + +class ContentObject extends XFAObject { + constructor(nsId, name) { + super(nsId, name); + this[$content] = ""; + } + + [$onText](text) { + this[$content] += text; + } + + [$finalize]() {} +} + +class OptionObject extends ContentObject { + constructor(nsId, name, options) { + super(nsId, name); + this[_options] = options; + } + + [$finalize]() { + this[$content] = getKeyword({ + data: this[$content], + defaultValue: this[_options][0], + validate: k => this[_options].includes(k), + }); + } + + [$clean](builder) { + super[$clean](builder); + delete this[_options]; + } +} + +class StringObject extends ContentObject { + [$finalize]() { + this[$content] = this[$content].trim(); + } +} + +class IntegerObject extends ContentObject { + constructor(nsId, name, defaultValue, validator) { + super(nsId, name); + this[_defaultValue] = defaultValue; + this[_validator] = validator; + } + + [$finalize]() { + this[$content] = getInteger({ + data: this[$content], + defaultValue: this[_defaultValue], + validate: this[_validator], + }); + } + + [$clean](builder) { + super[$clean](builder); + delete this[_defaultValue]; + delete this[_validator]; + } +} + +class Option01 extends IntegerObject { + constructor(nsId, name) { + super(nsId, name, 0, n => n === 1); + } +} + +class Option10 extends IntegerObject { + constructor(nsId, name) { + super(nsId, name, 1, n => n === 0); + } +} + +export { + ContentObject, + IntegerObject, + Option01, + Option10, + OptionObject, + StringObject, + XFAAttribute, + XFAObject, + XFAObjectArray, + XmlObject, +}; diff --git a/frontend/public/pdf.js/src/core/xfa/xhtml.js b/frontend/public/pdf.js/src/core/xfa/xhtml.js new file mode 100644 index 0000000..c7db14c --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa/xhtml.js @@ -0,0 +1,595 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + $acceptWhitespace, + $childrenToHTML, + $clean, + $content, + $extra, + $getChildren, + $getParent, + $globalData, + $nodeName, + $onText, + $pushGlyphs, + $text, + $toHTML, +} from "./symbol_utils.js"; +import { $buildXFAObject, NamespaceIds } from "./namespaces.js"; +import { + fixTextIndent, + fixURL, + measureToString, + setFontFamily, +} from "./html_utils.js"; +import { getMeasurement, HTMLResult, stripQuotes } from "./utils.js"; +import { XmlObject } from "./xfa_object.js"; + +const XHTML_NS_ID = NamespaceIds.xhtml.id; +const $richText = Symbol(); + +const VALID_STYLES = new Set([ + "color", + "font", + "font-family", + "font-size", + "font-stretch", + "font-style", + "font-weight", + "margin", + "margin-bottom", + "margin-left", + "margin-right", + "margin-top", + "letter-spacing", + "line-height", + "orphans", + "page-break-after", + "page-break-before", + "page-break-inside", + "tab-interval", + "tab-stop", + "text-align", + "text-decoration", + "text-indent", + "vertical-align", + "widows", + "kerning-mode", + "xfa-font-horizontal-scale", + "xfa-font-vertical-scale", + "xfa-spacerun", + "xfa-tab-stops", +]); + +const StyleMapping = new Map([ + ["page-break-after", "breakAfter"], + ["page-break-before", "breakBefore"], + ["page-break-inside", "breakInside"], + ["kerning-mode", value => (value === "none" ? "none" : "normal")], + [ + "xfa-font-horizontal-scale", + value => + `scaleX(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`, + ], + [ + "xfa-font-vertical-scale", + value => + `scaleY(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`, + ], + ["xfa-spacerun", ""], + ["xfa-tab-stops", ""], + [ + "font-size", + (value, original) => { + // The font size must be positive. + value = original.fontSize = Math.abs(getMeasurement(value)); + return measureToString(0.99 * value); + }, + ], + ["letter-spacing", value => measureToString(getMeasurement(value))], + ["line-height", value => measureToString(getMeasurement(value))], + ["margin", value => measureToString(getMeasurement(value))], + ["margin-bottom", value => measureToString(getMeasurement(value))], + ["margin-left", value => measureToString(getMeasurement(value))], + ["margin-right", value => measureToString(getMeasurement(value))], + ["margin-top", value => measureToString(getMeasurement(value))], + ["text-indent", value => measureToString(getMeasurement(value))], + ["font-family", value => value], + ["vertical-align", value => measureToString(getMeasurement(value))], +]); + +const spacesRegExp = /\s+/g; +const crlfRegExp = /[\r\n]+/g; +const crlfForRichTextRegExp = /\r\n?/g; + +function mapStyle(styleStr, node, richText) { + const style = Object.create(null); + if (!styleStr) { + return style; + } + const original = Object.create(null); + for (const [key, value] of styleStr.split(";").map(s => s.split(":", 2))) { + const mapping = StyleMapping.get(key); + if (mapping === "") { + continue; + } + let newValue = value; + if (mapping) { + newValue = + typeof mapping === "string" ? mapping : mapping(value, original); + } + if (key.endsWith("scale")) { + style.transform = style.transform + ? `${style[key]} ${newValue}` + : newValue; + } else { + style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] = + newValue; + } + } + + if (style.fontFamily) { + setFontFamily( + { + typeface: style.fontFamily, + weight: style.fontWeight || "normal", + posture: style.fontStyle || "normal", + size: original.fontSize || 0, + }, + node, + node[$globalData].fontFinder, + style + ); + } + + if ( + richText && + style.verticalAlign && + style.verticalAlign !== "0px" && + style.fontSize + ) { + // A non-zero verticalAlign means that we've a sub/super-script and + // consequently the font size must be decreased. + // https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#G11.2097514 + // And the two following factors to position the scripts have been + // found here: + // https://en.wikipedia.org/wiki/Subscript_and_superscript#Desktop_publishing + const SUB_SUPER_SCRIPT_FACTOR = 0.583; + const VERTICAL_FACTOR = 0.333; + const fontSize = getMeasurement(style.fontSize); + style.fontSize = measureToString(fontSize * SUB_SUPER_SCRIPT_FACTOR); + style.verticalAlign = measureToString( + Math.sign(getMeasurement(style.verticalAlign)) * + fontSize * + VERTICAL_FACTOR + ); + } + + if (richText && style.fontSize) { + style.fontSize = `calc(${style.fontSize} * var(--total-scale-factor))`; + } + + fixTextIndent(style); + return style; +} + +function checkStyle(node) { + if (!node.style) { + return ""; + } + + // Remove any non-allowed keys. + return node.style + .trim() + .split(/\s*;\s*/) + .filter(s => !!s) + .map(s => s.split(/\s*:\s*/, 2)) + .filter(([key, value]) => { + if (key === "font-family") { + node[$globalData].usedTypefaces.add(value); + } + return VALID_STYLES.has(key); + }) + .map(kv => kv.join(":")) + .join(";"); +} + +const NoWhites = new Set(["body", "html"]); + +class XhtmlObject extends XmlObject { + constructor(attributes, name) { + super(XHTML_NS_ID, name); + this[$richText] = false; + this.style = attributes.style || ""; + } + + [$clean](builder) { + super[$clean](builder); + this.style = checkStyle(this); + } + + [$acceptWhitespace]() { + return !NoWhites.has(this[$nodeName]); + } + + [$onText](str, richText = false) { + if (!richText) { + str = str.replaceAll(crlfRegExp, ""); + if (!this.style.includes("xfa-spacerun:yes")) { + str = str.replaceAll(spacesRegExp, " "); + } + } else { + this[$richText] = true; + } + + if (str) { + this[$content] += str; + } + } + + [$pushGlyphs](measure, mustPop = true) { + const xfaFont = Object.create(null); + const margin = { + top: NaN, + bottom: NaN, + left: NaN, + right: NaN, + }; + let lineHeight = null; + for (const [key, value] of this.style + .split(";") + .map(s => s.split(":", 2))) { + switch (key) { + case "font-family": + xfaFont.typeface = stripQuotes(value); + break; + case "font-size": + xfaFont.size = getMeasurement(value); + break; + case "font-weight": + xfaFont.weight = value; + break; + case "font-style": + xfaFont.posture = value; + break; + case "letter-spacing": + xfaFont.letterSpacing = getMeasurement(value); + break; + case "margin": + const values = value.split(/ \t/).map(x => getMeasurement(x)); + switch (values.length) { + case 1: + margin.top = + margin.bottom = + margin.left = + margin.right = + values[0]; + break; + case 2: + margin.top = margin.bottom = values[0]; + margin.left = margin.right = values[1]; + break; + case 3: + margin.top = values[0]; + margin.bottom = values[2]; + margin.left = margin.right = values[1]; + break; + case 4: + margin.top = values[0]; + margin.left = values[1]; + margin.bottom = values[2]; + margin.right = values[3]; + break; + } + break; + case "margin-top": + margin.top = getMeasurement(value); + break; + case "margin-bottom": + margin.bottom = getMeasurement(value); + break; + case "margin-left": + margin.left = getMeasurement(value); + break; + case "margin-right": + margin.right = getMeasurement(value); + break; + case "line-height": + lineHeight = getMeasurement(value); + break; + } + } + + measure.pushData(xfaFont, margin, lineHeight); + + if (this[$content]) { + measure.addString(this[$content]); + } else { + for (const child of this[$getChildren]()) { + if (child[$nodeName] === "#text") { + measure.addString(child[$content]); + continue; + } + child[$pushGlyphs](measure); + } + } + + if (mustPop) { + measure.popFont(); + } + } + + [$toHTML](availableSpace) { + const children = []; + this[$extra] = { + children, + }; + + this[$childrenToHTML]({}); + + if (children.length === 0 && !this[$content]) { + return HTMLResult.EMPTY; + } + + let value; + if (this[$richText]) { + value = this[$content] + ? this[$content].replaceAll(crlfForRichTextRegExp, "\n") + : undefined; + } else { + value = this[$content] || undefined; + } + + return HTMLResult.success({ + name: this[$nodeName], + attributes: { + href: this.href, + style: mapStyle(this.style, this, this[$richText]), + }, + children, + value, + }); + } +} + +class A extends XhtmlObject { + constructor(attributes) { + super(attributes, "a"); + this.href = fixURL(attributes.href) || ""; + } +} + +class B extends XhtmlObject { + constructor(attributes) { + super(attributes, "b"); + } + + [$pushGlyphs](measure) { + measure.pushFont({ weight: "bold" }); + super[$pushGlyphs](measure); + measure.popFont(); + } +} + +class Body extends XhtmlObject { + constructor(attributes) { + super(attributes, "body"); + } + + [$toHTML](availableSpace) { + const res = super[$toHTML](availableSpace); + const { html } = res; + if (!html) { + return HTMLResult.EMPTY; + } + html.name = "div"; + html.attributes.class = ["xfaRich"]; + return res; + } +} + +class Br extends XhtmlObject { + constructor(attributes) { + super(attributes, "br"); + } + + [$text]() { + return "\n"; + } + + [$pushGlyphs](measure) { + measure.addString("\n"); + } + + [$toHTML](availableSpace) { + return HTMLResult.success({ + name: "br", + }); + } +} + +class Html extends XhtmlObject { + constructor(attributes) { + super(attributes, "html"); + } + + [$toHTML](availableSpace) { + const children = []; + this[$extra] = { + children, + }; + + this[$childrenToHTML]({}); + if (children.length === 0) { + return HTMLResult.success({ + name: "div", + attributes: { + class: ["xfaRich"], + style: {}, + }, + value: this[$content] || "", + }); + } + + if (children.length === 1) { + const child = children[0]; + if (child.attributes?.class.includes("xfaRich")) { + return HTMLResult.success(child); + } + } + + return HTMLResult.success({ + name: "div", + attributes: { + class: ["xfaRich"], + style: {}, + }, + children, + }); + } +} + +class I extends XhtmlObject { + constructor(attributes) { + super(attributes, "i"); + } + + [$pushGlyphs](measure) { + measure.pushFont({ posture: "italic" }); + super[$pushGlyphs](measure); + measure.popFont(); + } +} + +class Li extends XhtmlObject { + constructor(attributes) { + super(attributes, "li"); + } +} + +class Ol extends XhtmlObject { + constructor(attributes) { + super(attributes, "ol"); + } +} + +class P extends XhtmlObject { + constructor(attributes) { + super(attributes, "p"); + } + + [$pushGlyphs](measure) { + super[$pushGlyphs](measure, /* mustPop = */ false); + measure.addString("\n"); + measure.addPara(); + measure.popFont(); + } + + [$text]() { + const siblings = this[$getParent]()[$getChildren](); + if (siblings.at(-1) === this) { + return super[$text](); + } + return super[$text]() + "\n"; + } +} + +class Span extends XhtmlObject { + constructor(attributes) { + super(attributes, "span"); + } +} + +class Sub extends XhtmlObject { + constructor(attributes) { + super(attributes, "sub"); + } +} + +class Sup extends XhtmlObject { + constructor(attributes) { + super(attributes, "sup"); + } +} + +class Ul extends XhtmlObject { + constructor(attributes) { + super(attributes, "ul"); + } +} + +class XhtmlNamespace { + static [$buildXFAObject](name, attributes) { + if (XhtmlNamespace.hasOwnProperty(name)) { + return XhtmlNamespace[name](attributes); + } + return undefined; + } + + static a(attributes) { + return new A(attributes); + } + + static b(attributes) { + return new B(attributes); + } + + static body(attributes) { + return new Body(attributes); + } + + static br(attributes) { + return new Br(attributes); + } + + static html(attributes) { + return new Html(attributes); + } + + static i(attributes) { + return new I(attributes); + } + + static li(attributes) { + return new Li(attributes); + } + + static ol(attributes) { + return new Ol(attributes); + } + + static p(attributes) { + return new P(attributes); + } + + static span(attributes) { + return new Span(attributes); + } + + static sub(attributes) { + return new Sub(attributes); + } + + static sup(attributes) { + return new Sup(attributes); + } + + static ul(attributes) { + return new Ul(attributes); + } +} + +export { XhtmlNamespace }; diff --git a/frontend/public/pdf.js/src/core/xfa_fonts.js b/frontend/public/pdf.js/src/core/xfa_fonts.js new file mode 100644 index 0000000..7f58a99 --- /dev/null +++ b/frontend/public/pdf.js/src/core/xfa_fonts.js @@ -0,0 +1,296 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + CalibriBoldFactors, + CalibriBoldItalicFactors, + CalibriBoldItalicMetrics, + CalibriBoldMetrics, + CalibriItalicFactors, + CalibriItalicMetrics, + CalibriRegularFactors, + CalibriRegularMetrics, +} from "./calibri_factors.js"; +import { Dict, Name } from "./primitives.js"; +import { + HelveticaBoldFactors, + HelveticaBoldItalicFactors, + HelveticaBoldItalicMetrics, + HelveticaBoldMetrics, + HelveticaItalicFactors, + HelveticaItalicMetrics, + HelveticaRegularFactors, + HelveticaRegularMetrics, +} from "./helvetica_factors.js"; +import { + LiberationSansBoldItalicMapping, + LiberationSansBoldItalicWidths, + LiberationSansBoldMapping, + LiberationSansBoldWidths, + LiberationSansItalicMapping, + LiberationSansItalicWidths, + LiberationSansRegularMapping, + LiberationSansRegularWidths, +} from "./liberationsans_widths.js"; +import { + MyriadProBoldFactors, + MyriadProBoldItalicFactors, + MyriadProBoldItalicMetrics, + MyriadProBoldMetrics, + MyriadProItalicFactors, + MyriadProItalicMetrics, + MyriadProRegularFactors, + MyriadProRegularMetrics, +} from "./myriadpro_factors.js"; +import { + SegoeuiBoldFactors, + SegoeuiBoldItalicFactors, + SegoeuiBoldItalicMetrics, + SegoeuiBoldMetrics, + SegoeuiItalicFactors, + SegoeuiItalicMetrics, + SegoeuiRegularFactors, + SegoeuiRegularMetrics, +} from "./segoeui_factors.js"; +import { getLookupTableFactory } from "./core_utils.js"; +import { normalizeFontName } from "./fonts_utils.js"; + +const getXFAFontMap = getLookupTableFactory(function (t) { + t["MyriadPro-Regular"] = t["PdfJS-Fallback-Regular"] = { + name: "LiberationSans-Regular", + factors: MyriadProRegularFactors, + baseWidths: LiberationSansRegularWidths, + baseMapping: LiberationSansRegularMapping, + metrics: MyriadProRegularMetrics, + }; + t["MyriadPro-Bold"] = t["PdfJS-Fallback-Bold"] = { + name: "LiberationSans-Bold", + factors: MyriadProBoldFactors, + baseWidths: LiberationSansBoldWidths, + baseMapping: LiberationSansBoldMapping, + metrics: MyriadProBoldMetrics, + }; + t["MyriadPro-It"] = + t["MyriadPro-Italic"] = + t["PdfJS-Fallback-Italic"] = + { + name: "LiberationSans-Italic", + factors: MyriadProItalicFactors, + baseWidths: LiberationSansItalicWidths, + baseMapping: LiberationSansItalicMapping, + metrics: MyriadProItalicMetrics, + }; + t["MyriadPro-BoldIt"] = + t["MyriadPro-BoldItalic"] = + t["PdfJS-Fallback-BoldItalic"] = + { + name: "LiberationSans-BoldItalic", + factors: MyriadProBoldItalicFactors, + baseWidths: LiberationSansBoldItalicWidths, + baseMapping: LiberationSansBoldItalicMapping, + metrics: MyriadProBoldItalicMetrics, + }; + t.ArialMT = + t.Arial = + t["Arial-Regular"] = + { + name: "LiberationSans-Regular", + baseWidths: LiberationSansRegularWidths, + baseMapping: LiberationSansRegularMapping, + }; + t["Arial-BoldMT"] = t["Arial-Bold"] = { + name: "LiberationSans-Bold", + baseWidths: LiberationSansBoldWidths, + baseMapping: LiberationSansBoldMapping, + }; + t["Arial-ItalicMT"] = t["Arial-Italic"] = { + name: "LiberationSans-Italic", + baseWidths: LiberationSansItalicWidths, + baseMapping: LiberationSansItalicMapping, + }; + t["Arial-BoldItalicMT"] = t["Arial-BoldItalic"] = { + name: "LiberationSans-BoldItalic", + baseWidths: LiberationSansBoldItalicWidths, + baseMapping: LiberationSansBoldItalicMapping, + }; + t["Calibri-Regular"] = { + name: "LiberationSans-Regular", + factors: CalibriRegularFactors, + baseWidths: LiberationSansRegularWidths, + baseMapping: LiberationSansRegularMapping, + metrics: CalibriRegularMetrics, + }; + t["Calibri-Bold"] = { + name: "LiberationSans-Bold", + factors: CalibriBoldFactors, + baseWidths: LiberationSansBoldWidths, + baseMapping: LiberationSansBoldMapping, + metrics: CalibriBoldMetrics, + }; + t["Calibri-Italic"] = { + name: "LiberationSans-Italic", + factors: CalibriItalicFactors, + baseWidths: LiberationSansItalicWidths, + baseMapping: LiberationSansItalicMapping, + metrics: CalibriItalicMetrics, + }; + t["Calibri-BoldItalic"] = { + name: "LiberationSans-BoldItalic", + factors: CalibriBoldItalicFactors, + baseWidths: LiberationSansBoldItalicWidths, + baseMapping: LiberationSansBoldItalicMapping, + metrics: CalibriBoldItalicMetrics, + }; + t["Segoeui-Regular"] = { + name: "LiberationSans-Regular", + factors: SegoeuiRegularFactors, + baseWidths: LiberationSansRegularWidths, + baseMapping: LiberationSansRegularMapping, + metrics: SegoeuiRegularMetrics, + }; + t["Segoeui-Bold"] = { + name: "LiberationSans-Bold", + factors: SegoeuiBoldFactors, + baseWidths: LiberationSansBoldWidths, + baseMapping: LiberationSansBoldMapping, + metrics: SegoeuiBoldMetrics, + }; + t["Segoeui-Italic"] = { + name: "LiberationSans-Italic", + factors: SegoeuiItalicFactors, + baseWidths: LiberationSansItalicWidths, + baseMapping: LiberationSansItalicMapping, + metrics: SegoeuiItalicMetrics, + }; + t["Segoeui-BoldItalic"] = { + name: "LiberationSans-BoldItalic", + factors: SegoeuiBoldItalicFactors, + baseWidths: LiberationSansBoldItalicWidths, + baseMapping: LiberationSansBoldItalicMapping, + metrics: SegoeuiBoldItalicMetrics, + }; + t["Helvetica-Regular"] = t.Helvetica = { + name: "LiberationSans-Regular", + factors: HelveticaRegularFactors, + baseWidths: LiberationSansRegularWidths, + baseMapping: LiberationSansRegularMapping, + metrics: HelveticaRegularMetrics, + }; + t["Helvetica-Bold"] = { + name: "LiberationSans-Bold", + factors: HelveticaBoldFactors, + baseWidths: LiberationSansBoldWidths, + baseMapping: LiberationSansBoldMapping, + metrics: HelveticaBoldMetrics, + }; + t["Helvetica-Italic"] = { + name: "LiberationSans-Italic", + factors: HelveticaItalicFactors, + baseWidths: LiberationSansItalicWidths, + baseMapping: LiberationSansItalicMapping, + metrics: HelveticaItalicMetrics, + }; + t["Helvetica-BoldItalic"] = { + name: "LiberationSans-BoldItalic", + factors: HelveticaBoldItalicFactors, + baseWidths: LiberationSansBoldItalicWidths, + baseMapping: LiberationSansBoldItalicMapping, + metrics: HelveticaBoldItalicMetrics, + }; +}); + +function getXfaFontName(name) { + const fontName = normalizeFontName(name); + const fontMap = getXFAFontMap(); + return fontMap[fontName]; +} + +function getXfaFontWidths(name) { + const info = getXfaFontName(name); + if (!info) { + return null; + } + + const { baseWidths, baseMapping, factors } = info; + const rescaledBaseWidths = !factors + ? baseWidths + : baseWidths.map((w, i) => w * factors[i]); + + let currentCode = -2; + let currentArray; + + // Widths array for composite font is: + // CharCode1 [10, 20, 30] ... + // which means: + // - CharCode1 has a width equal to 10 + // - CharCode1+1 has a width equal to 20 + // - CharCode1+2 has a width equal to 30 + // + // The baseMapping array contains a map for glyph index to unicode. + // So from baseMapping we'll get sorted unicodes and their positions + // (i.e. glyph indices) and then we put widths in an array for the + // the consecutive unicodes. + const newWidths = []; + for (const [unicode, glyphIndex] of baseMapping + .map( + (charUnicode, index) => [ + charUnicode, + index, + ] /* collect unicode and glyph index */ + ) + .sort( + ([unicode1], [unicode2]) => + unicode1 - unicode2 /* order by unicode only */ + )) { + if (unicode === -1) { + continue; + } + + if (unicode === currentCode + 1) { + currentArray.push(rescaledBaseWidths[glyphIndex]); + currentCode += 1; + } else { + currentCode = unicode; + currentArray = [rescaledBaseWidths[glyphIndex]]; + newWidths.push(unicode, currentArray); + } + } + + return newWidths; +} + +function getXfaFontDict(name) { + const widths = getXfaFontWidths(name); + const dict = new Dict(null); + dict.set("BaseFont", Name.get(name)); + dict.set("Type", Name.get("Font")); + dict.set("Subtype", Name.get("CIDFontType2")); + dict.set("Encoding", Name.get("Identity-H")); + dict.set("CIDToGIDMap", Name.get("Identity")); + dict.set("W", widths); + dict.set("FirstChar", widths[0]); + dict.set("LastChar", widths.at(-2) + widths.at(-1).length - 1); + const descriptor = new Dict(null); + dict.set("FontDescriptor", descriptor); + const systemInfo = new Dict(null); + systemInfo.set("Ordering", "Identity"); + systemInfo.set("Registry", "Adobe"); + systemInfo.set("Supplement", 0); + dict.set("CIDSystemInfo", systemInfo); + + return dict; +} + +export { getXfaFontDict, getXfaFontName }; diff --git a/frontend/public/pdf.js/src/core/xml_parser.js b/frontend/public/pdf.js/src/core/xml_parser.js new file mode 100644 index 0000000..a1a60bf --- /dev/null +++ b/frontend/public/pdf.js/src/core/xml_parser.js @@ -0,0 +1,517 @@ +/* Copyright 2018 Mozilla Foundation + * + * 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. + */ + +// The code for XMLParserBase copied from +// https://github.com/mozilla/shumway/blob/16451d8836fa85f4b16eeda8b4bda2fa9e2b22b0/src/avm2/natives/xml.ts + +import { encodeToXmlString } from "./core_utils.js"; + +const XMLParserErrorCode = { + NoError: 0, + EndOfDocument: -1, + UnterminatedCdat: -2, + UnterminatedXmlDeclaration: -3, + UnterminatedDoctypeDeclaration: -4, + UnterminatedComment: -5, + MalformedElement: -6, + OutOfMemory: -7, + UnterminatedAttributeValue: -8, + UnterminatedElement: -9, + ElementNeverBegun: -10, +}; + +function isWhitespace(s, index) { + const ch = s[index]; + return ch === " " || ch === "\n" || ch === "\r" || ch === "\t"; +} + +function isWhitespaceString(s) { + for (let i = 0, ii = s.length; i < ii; i++) { + if (!isWhitespace(s, i)) { + return false; + } + } + return true; +} + +class XMLParserBase { + _resolveEntities(s) { + return s.replaceAll(/&([^;]+);/g, (all, entity) => { + if (entity.substring(0, 2) === "#x") { + return String.fromCodePoint(parseInt(entity.substring(2), 16)); + } else if (entity.substring(0, 1) === "#") { + return String.fromCodePoint(parseInt(entity.substring(1), 10)); + } + switch (entity) { + case "lt": + return "<"; + case "gt": + return ">"; + case "amp": + return "&"; + case "quot": + return '"'; + case "apos": + return "'"; + } + return this.onResolveEntity(entity); + }); + } + + _parseContent(s, start) { + const attributes = []; + let pos = start; + + function skipWs() { + while (pos < s.length && isWhitespace(s, pos)) { + ++pos; + } + } + + while ( + pos < s.length && + !isWhitespace(s, pos) && + s[pos] !== ">" && + s[pos] !== "/" + ) { + ++pos; + } + const name = s.substring(start, pos); + skipWs(); + while ( + pos < s.length && + s[pos] !== ">" && + s[pos] !== "/" && + s[pos] !== "?" + ) { + skipWs(); + let attrName = "", + attrValue = ""; + while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== "=") { + attrName += s[pos]; + ++pos; + } + skipWs(); + if (s[pos] !== "=") { + return null; + } + ++pos; + skipWs(); + const attrEndChar = s[pos]; + if (attrEndChar !== '"' && attrEndChar !== "'") { + return null; + } + const attrEndIndex = s.indexOf(attrEndChar, ++pos); + if (attrEndIndex < 0) { + return null; + } + attrValue = s.substring(pos, attrEndIndex); + attributes.push({ + name: attrName, + value: this._resolveEntities(attrValue), + }); + pos = attrEndIndex + 1; + skipWs(); + } + return { + name, + attributes, + parsed: pos - start, + }; + } + + _parseProcessingInstruction(s, start) { + let pos = start; + + function skipWs() { + while (pos < s.length && isWhitespace(s, pos)) { + ++pos; + } + } + + while ( + pos < s.length && + !isWhitespace(s, pos) && + s[pos] !== ">" && + s[pos] !== "?" && + s[pos] !== "/" + ) { + ++pos; + } + const name = s.substring(start, pos); + skipWs(); + const attrStart = pos; + while (pos < s.length && (s[pos] !== "?" || s[pos + 1] !== ">")) { + ++pos; + } + const value = s.substring(attrStart, pos); + return { + name, + value, + parsed: pos - start, + }; + } + + parseXml(s) { + let i = 0; + while (i < s.length) { + const ch = s[i]; + let j = i; + if (ch === "<") { + ++j; + const ch2 = s[j]; + let q; + switch (ch2) { + case "/": + ++j; + q = s.indexOf(">", j); + if (q < 0) { + this.onError(XMLParserErrorCode.UnterminatedElement); + return; + } + this.onEndElement(s.substring(j, q)); + j = q + 1; + break; + case "?": + ++j; + const pi = this._parseProcessingInstruction(s, j); + if (s.substring(j + pi.parsed, j + pi.parsed + 2) !== "?>") { + this.onError(XMLParserErrorCode.UnterminatedXmlDeclaration); + return; + } + this.onPi(pi.name, pi.value); + j += pi.parsed + 2; + break; + case "!": + if (s.substring(j + 1, j + 3) === "--") { + q = s.indexOf("-->", j + 3); + if (q < 0) { + this.onError(XMLParserErrorCode.UnterminatedComment); + return; + } + this.onComment(s.substring(j + 3, q)); + j = q + 3; + } else if (s.substring(j + 1, j + 8) === "[CDATA[") { + q = s.indexOf("]]>", j + 8); + if (q < 0) { + this.onError(XMLParserErrorCode.UnterminatedCdat); + return; + } + this.onCdata(s.substring(j + 8, q)); + j = q + 3; + } else if (s.substring(j + 1, j + 8) === "DOCTYPE") { + const q2 = s.indexOf("[", j + 8); + let complexDoctype = false; + q = s.indexOf(">", j + 8); + if (q < 0) { + this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration); + return; + } + if (q2 > 0 && q > q2) { + q = s.indexOf("]>", j + 8); + if (q < 0) { + this.onError( + XMLParserErrorCode.UnterminatedDoctypeDeclaration + ); + return; + } + complexDoctype = true; + } + const doctypeContent = s.substring( + j + 8, + q + (complexDoctype ? 1 : 0) + ); + this.onDoctype(doctypeContent); + j = q + (complexDoctype ? 2 : 1); + } else { + this.onError(XMLParserErrorCode.MalformedElement); + return; + } + break; + default: + const content = this._parseContent(s, j); + if (content === null) { + this.onError(XMLParserErrorCode.MalformedElement); + return; + } + let isClosed = false; + if ( + s.substring(j + content.parsed, j + content.parsed + 2) === "/>" + ) { + isClosed = true; + } else if ( + s.substring(j + content.parsed, j + content.parsed + 1) !== ">" + ) { + this.onError(XMLParserErrorCode.UnterminatedElement); + return; + } + this.onBeginElement(content.name, content.attributes, isClosed); + j += content.parsed + (isClosed ? 2 : 1); + break; + } + } else { + while (j < s.length && s[j] !== "<") { + j++; + } + const text = s.substring(i, j); + this.onText(this._resolveEntities(text)); + } + i = j; + } + } + + onResolveEntity(name) { + return `&${name};`; + } + + onPi(name, value) {} + + onComment(text) {} + + onCdata(text) {} + + onDoctype(doctypeContent) {} + + onText(text) {} + + onBeginElement(name, attributes, isEmpty) {} + + onEndElement(name) {} + + onError(code) {} +} + +class SimpleDOMNode { + constructor(nodeName, nodeValue) { + this.nodeName = nodeName; + this.nodeValue = nodeValue; + + Object.defineProperty(this, "parentNode", { value: null, writable: true }); + } + + get firstChild() { + return this.childNodes?.[0]; + } + + get nextSibling() { + const childNodes = this.parentNode.childNodes; + if (!childNodes) { + return undefined; + } + const index = childNodes.indexOf(this); + if (index === -1) { + return undefined; + } + return childNodes[index + 1]; + } + + get textContent() { + if (!this.childNodes) { + return this.nodeValue || ""; + } + return this.childNodes + .map(function (child) { + return child.textContent; + }) + .join(""); + } + + get children() { + return this.childNodes || []; + } + + hasChildNodes() { + return this.childNodes?.length > 0; + } + + /** + * Search a node in the tree with the given path + * foo.bar[nnn], i.e. find the nnn-th node named + * bar under a node named foo. + * + * @param {Array} paths - an array of objects as + * returned by {parseXFAPath}. + * @param {number} pos - the current position in + * the paths array. + * @returns {SimpleDOMNode} The node corresponding + * to the path or null if not found. + */ + searchNode(paths, pos) { + if (pos >= paths.length) { + return this; + } + + const component = paths[pos]; + if (component.name.startsWith("#") && pos < paths.length - 1) { + // If it starts with a # then it's a class which is not a concept for + // datasets elements (https://www.pdfa.org/norm-refs/XFA-3_3.pdf#page=96). + return this.searchNode(paths, pos + 1); + } + const stack = []; + let node = this; + + while (true) { + if (component.name === node.nodeName) { + if (component.pos === 0) { + const res = node.searchNode(paths, pos + 1); + if (res !== null) { + return res; + } + } else if (stack.length === 0) { + return null; + } else { + const [parent] = stack.pop(); + let siblingPos = 0; + for (const child of parent.childNodes) { + if (component.name === child.nodeName) { + if (siblingPos === component.pos) { + return child.searchNode(paths, pos + 1); + } + siblingPos++; + } + } + // We didn't find the correct sibling + // so just return the first found node + return node.searchNode(paths, pos + 1); + } + } + + if (node.childNodes?.length > 0) { + stack.push([node, 0]); + node = node.childNodes[0]; + } else if (stack.length === 0) { + return null; + } else { + while (stack.length !== 0) { + const [parent, currentPos] = stack.pop(); + const newPos = currentPos + 1; + if (newPos < parent.childNodes.length) { + stack.push([parent, newPos]); + node = parent.childNodes[newPos]; + break; + } + } + if (stack.length === 0) { + return null; + } + } + } + } + + dump(buffer) { + if (this.nodeName === "#text") { + buffer.push(encodeToXmlString(this.nodeValue)); + return; + } + + buffer.push(`<${this.nodeName}`); + if (this.attributes) { + for (const attribute of this.attributes) { + buffer.push( + ` ${attribute.name}="${encodeToXmlString(attribute.value)}"` + ); + } + } + if (this.hasChildNodes()) { + buffer.push(">"); + for (const child of this.childNodes) { + child.dump(buffer); + } + buffer.push(``); + } else if (this.nodeValue) { + buffer.push(`>${encodeToXmlString(this.nodeValue)}`); + } else { + buffer.push("/>"); + } + } +} + +class SimpleXMLParser extends XMLParserBase { + constructor({ hasAttributes = false, lowerCaseName = false }) { + super(); + this._currentFragment = null; + this._stack = null; + this._errorCode = XMLParserErrorCode.NoError; + this._hasAttributes = hasAttributes; + this._lowerCaseName = lowerCaseName; + } + + parseFromString(data) { + this._currentFragment = []; + this._stack = []; + this._errorCode = XMLParserErrorCode.NoError; + + this.parseXml(data); + + if (this._errorCode !== XMLParserErrorCode.NoError) { + return undefined; // return undefined on error + } + + // We should only have one root. + const [documentElement] = this._currentFragment; + if (!documentElement) { + return undefined; // Return undefined if no root was found. + } + return { documentElement }; + } + + onText(text) { + if (isWhitespaceString(text)) { + return; + } + const node = new SimpleDOMNode("#text", text); + this._currentFragment.push(node); + } + + onCdata(text) { + const node = new SimpleDOMNode("#text", text); + this._currentFragment.push(node); + } + + onBeginElement(name, attributes, isEmpty) { + if (this._lowerCaseName) { + name = name.toLowerCase(); + } + const node = new SimpleDOMNode(name); + node.childNodes = []; + if (this._hasAttributes) { + node.attributes = attributes; + } + this._currentFragment.push(node); + if (isEmpty) { + return; + } + this._stack.push(this._currentFragment); + this._currentFragment = node.childNodes; + } + + onEndElement(name) { + this._currentFragment = this._stack.pop() || []; + const lastElement = this._currentFragment.at(-1); + if (!lastElement) { + return null; + } + for (const childNode of lastElement.childNodes) { + childNode.parentNode = lastElement; + } + return lastElement; + } + + onError(code) { + this._errorCode = code; + } +} + +export { SimpleDOMNode, SimpleXMLParser, XMLParserBase, XMLParserErrorCode }; diff --git a/frontend/public/pdf.js/src/core/xref.js b/frontend/public/pdf.js/src/core/xref.js new file mode 100644 index 0000000..4a02f5e --- /dev/null +++ b/frontend/public/pdf.js/src/core/xref.js @@ -0,0 +1,1031 @@ +/* Copyright 2021 Mozilla Foundation + * + * 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 { + assert, + bytesToString, + FormatError, + info, + InvalidPDFException, + warn, +} from "../shared/util.js"; +import { CIRCULAR_REF, Cmd, Dict, isCmd, Ref, RefSet } from "./primitives.js"; +import { Lexer, Parser } from "./parser.js"; +import { + MissingDataException, + ParserEOFException, + XRefEntryException, + XRefParseException, +} from "./core_utils.js"; +import { BaseStream } from "./base_stream.js"; +import { CipherTransformFactory } from "./crypto.js"; + +class XRef { + #firstXRefStmPos = null; + + constructor(stream, pdfManager) { + this.stream = stream; + this.pdfManager = pdfManager; + this.entries = []; + this._xrefStms = new Set(); + this._cacheMap = new Map(); // Prepare the XRef cache. + this._pendingRefs = new RefSet(); + this._newPersistentRefNum = null; + this._newTemporaryRefNum = null; + this._persistentRefsCache = null; + } + + getNewPersistentRef(obj) { + // When printing we don't care that much about the ref number by itself, it + // can increase for ever and it allows to keep some re-usable refs. + if (this._newPersistentRefNum === null) { + this._newPersistentRefNum = this.entries.length || 1; + } + const num = this._newPersistentRefNum++; + this._cacheMap.set(num, obj); + return Ref.get(num, 0); + } + + getNewTemporaryRef() { + // When saving we want to have some minimal numbers. + // Those refs are only created in order to be written in the final pdf + // stream. + if (this._newTemporaryRefNum === null) { + this._newTemporaryRefNum = this.entries.length || 1; + if (this._newPersistentRefNum) { + this._persistentRefsCache = new Map(); + for ( + let i = this._newTemporaryRefNum; + i < this._newPersistentRefNum; + i++ + ) { + // We *temporarily* clear the cache, see `resetNewTemporaryRef` below, + // to avoid any conflict with the refs created during saving. + this._persistentRefsCache.set(i, this._cacheMap.get(i)); + this._cacheMap.delete(i); + } + } + } + return Ref.get(this._newTemporaryRefNum++, 0); + } + + resetNewTemporaryRef() { + // Called once saving is finished. + this._newTemporaryRefNum = null; + if (this._persistentRefsCache) { + for (const [num, obj] of this._persistentRefsCache) { + this._cacheMap.set(num, obj); + } + } + this._persistentRefsCache = null; + } + + setStartXRef(startXRef) { + // Store the starting positions of xref tables as we process them + // so we can recover from missing data errors + this.startXRefQueue = [startXRef]; + } + + parse(recoveryMode = false) { + let trailerDict; + if (!recoveryMode) { + trailerDict = this.readXRef(); + } else { + warn("Indexing all PDF objects"); + trailerDict = this.indexObjects(); + } + trailerDict.assignXref(this); + this.trailer = trailerDict; + + let encrypt; + try { + encrypt = trailerDict.get("Encrypt"); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(`XRef.parse - Invalid "Encrypt" reference: "${ex}".`); + } + if (encrypt instanceof Dict) { + const ids = trailerDict.get("ID"); + const fileId = ids?.length ? ids[0] : ""; + // The 'Encrypt' dictionary itself should not be encrypted, and by + // setting `suppressEncryption` we can prevent an infinite loop inside + // of `XRef_fetchUncompressed` if the dictionary contains indirect + // objects (fixes issue7665.pdf). + encrypt.suppressEncryption = true; + this.encrypt = new CipherTransformFactory( + encrypt, + fileId, + this.pdfManager.password + ); + } + + // Get the root dictionary (catalog) object, and do some basic validation. + let root; + try { + root = trailerDict.get("Root"); + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(`XRef.parse - Invalid "Root" reference: "${ex}".`); + } + if (root instanceof Dict) { + try { + const pages = root.get("Pages"); + if (pages instanceof Dict) { + this.root = root; + return; + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + warn(`XRef.parse - Invalid "Pages" reference: "${ex}".`); + } + } + + if (!recoveryMode) { + throw new XRefParseException(); + } + // Even recovery failed, there's nothing more we can do here. + throw new InvalidPDFException("Invalid Root reference."); + } + + processXRefTable(parser) { + if (!("tableState" in this)) { + // Stores state of the table as we process it so we can resume + // from middle of table in case of missing data error + this.tableState = { + entryNum: 0, + streamPos: parser.lexer.stream.pos, + parserBuf1: parser.buf1, + parserBuf2: parser.buf2, + }; + } + + const obj = this.readXRefTable(parser); + + // Sanity check + if (!isCmd(obj, "trailer")) { + throw new FormatError( + "Invalid XRef table: could not find trailer dictionary" + ); + } + // Read trailer dictionary, e.g. + // trailer + // << /Size 22 + // /Root 20R + // /Info 10R + // /ID [ <81b14aafa313db63dbd6f981e49f94f4> ] + // >> + // The parser goes through the entire stream << ... >> and provides + // a getter interface for the key-value table + let dict = parser.getObj(); + + // The pdflib PDF generator can generate a nested trailer dictionary + if (!(dict instanceof Dict) && dict.dict) { + dict = dict.dict; + } + if (!(dict instanceof Dict)) { + throw new FormatError( + "Invalid XRef table: could not parse trailer dictionary" + ); + } + delete this.tableState; + + return dict; + } + + readXRefTable(parser) { + // Example of cross-reference table: + // xref + // 0 1 <-- subsection header (first obj #, obj count) + // 0000000000 65535 f <-- actual object (offset, generation #, f/n) + // 23 2 <-- subsection header ... and so on ... + // 0000025518 00002 n + // 0000025635 00000 n + // trailer + // ... + + const stream = parser.lexer.stream; + const tableState = this.tableState; + stream.pos = tableState.streamPos; + parser.buf1 = tableState.parserBuf1; + parser.buf2 = tableState.parserBuf2; + + // Outer loop is over subsection headers + let obj; + + while (true) { + if (!("firstEntryNum" in tableState) || !("entryCount" in tableState)) { + if (isCmd((obj = parser.getObj()), "trailer")) { + break; + } + tableState.firstEntryNum = obj; + tableState.entryCount = parser.getObj(); + } + + let first = tableState.firstEntryNum; + const count = tableState.entryCount; + if (!Number.isInteger(first) || !Number.isInteger(count)) { + throw new FormatError( + "Invalid XRef table: wrong types in subsection header" + ); + } + // Inner loop is over objects themselves + for (let i = tableState.entryNum; i < count; i++) { + tableState.streamPos = stream.pos; + tableState.entryNum = i; + tableState.parserBuf1 = parser.buf1; + tableState.parserBuf2 = parser.buf2; + + const entry = {}; + entry.offset = parser.getObj(); + entry.gen = parser.getObj(); + const type = parser.getObj(); + + if (type instanceof Cmd) { + switch (type.cmd) { + case "f": + entry.free = true; + break; + case "n": + entry.uncompressed = true; + break; + } + } + + // Validate entry obj + if ( + !Number.isInteger(entry.offset) || + !Number.isInteger(entry.gen) || + !(entry.free || entry.uncompressed) + ) { + throw new FormatError( + `Invalid entry in XRef subsection: ${first}, ${count}` + ); + } + + // The first xref table entry, i.e. obj 0, should be free. Attempting + // to adjust an incorrect first obj # (fixes issue 3248 and 7229). + if (i === 0 && entry.free && first === 1) { + first = 0; + } + + if (!this.entries[i + first]) { + this.entries[i + first] = entry; + } + } + + tableState.entryNum = 0; + tableState.streamPos = stream.pos; + tableState.parserBuf1 = parser.buf1; + tableState.parserBuf2 = parser.buf2; + delete tableState.firstEntryNum; + delete tableState.entryCount; + } + + // Sanity check: as per spec, first object must be free + if (this.entries[0] && !this.entries[0].free) { + throw new FormatError("Invalid XRef table: unexpected first object"); + } + return obj; + } + + processXRefStream(stream) { + if (!("streamState" in this)) { + // Stores state of the stream as we process it so we can resume + // from middle of stream in case of missing data error + const { dict, pos } = stream; + const byteWidths = dict.get("W"); + const range = dict.get("Index") || [0, dict.get("Size")]; + + this.streamState = { + entryRanges: range, + byteWidths, + entryNum: 0, + streamPos: pos, + }; + } + this.readXRefStream(stream); + delete this.streamState; + + return stream.dict; + } + + readXRefStream(stream) { + const streamState = this.streamState; + stream.pos = streamState.streamPos; + + const [typeFieldWidth, offsetFieldWidth, generationFieldWidth] = + streamState.byteWidths; + + const entryRanges = streamState.entryRanges; + while (entryRanges.length > 0) { + const [first, n] = entryRanges; + + if (!Number.isInteger(first) || !Number.isInteger(n)) { + throw new FormatError(`Invalid XRef range fields: ${first}, ${n}`); + } + if ( + !Number.isInteger(typeFieldWidth) || + !Number.isInteger(offsetFieldWidth) || + !Number.isInteger(generationFieldWidth) + ) { + throw new FormatError( + `Invalid XRef entry fields length: ${first}, ${n}` + ); + } + for (let i = streamState.entryNum; i < n; ++i) { + streamState.entryNum = i; + streamState.streamPos = stream.pos; + + let type = 0, + offset = 0, + generation = 0; + for (let j = 0; j < typeFieldWidth; ++j) { + const typeByte = stream.getByte(); + if (typeByte === -1) { + throw new FormatError("Invalid XRef byteWidths 'type'."); + } + type = (type << 8) | typeByte; + } + // if type field is absent, its default value is 1 + if (typeFieldWidth === 0) { + type = 1; + } + for (let j = 0; j < offsetFieldWidth; ++j) { + const offsetByte = stream.getByte(); + if (offsetByte === -1) { + throw new FormatError("Invalid XRef byteWidths 'offset'."); + } + offset = (offset << 8) | offsetByte; + } + for (let j = 0; j < generationFieldWidth; ++j) { + const generationByte = stream.getByte(); + if (generationByte === -1) { + throw new FormatError("Invalid XRef byteWidths 'generation'."); + } + generation = (generation << 8) | generationByte; + } + const entry = {}; + entry.offset = offset; + entry.gen = generation; + switch (type) { + case 0: + entry.free = true; + break; + case 1: + entry.uncompressed = true; + break; + case 2: + break; + default: + throw new FormatError(`Invalid XRef entry type: ${type}`); + } + if (!this.entries[first + i]) { + this.entries[first + i] = entry; + } + } + + streamState.entryNum = 0; + streamState.streamPos = stream.pos; + entryRanges.splice(0, 2); + } + } + + indexObjects() { + // Simple scan through the PDF content to find objects, + // trailers and XRef streams. + const TAB = 0x9, + LF = 0xa, + CR = 0xd, + SPACE = 0x20; + const PERCENT = 0x25, + LT = 0x3c; + + function readToken(data, offset) { + let token = "", + ch = data[offset]; + while (ch !== LF && ch !== CR && ch !== LT) { + if (++offset >= data.length) { + break; + } + token += String.fromCharCode(ch); + ch = data[offset]; + } + return token; + } + function skipUntil(data, offset, what) { + const length = what.length, + dataLength = data.length; + let skipped = 0; + // finding byte sequence + while (offset < dataLength) { + let i = 0; + while (i < length && data[offset + i] === what[i]) { + ++i; + } + if (i >= length) { + break; // sequence found + } + offset++; + skipped++; + } + return skipped; + } + const gEndobjRegExp = /\b(endobj|\d+\s+\d+\s+obj|xref|trailer\s*<<)\b/g; + const gStartxrefRegExp = /\b(startxref|\d+\s+\d+\s+obj)\b/g; + const objRegExp = /^(\d+)\s+(\d+)\s+obj\b/; + + const trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]); + const startxrefBytes = new Uint8Array([ + 115, 116, 97, 114, 116, 120, 114, 101, 102, + ]); + const xrefBytes = new Uint8Array([47, 88, 82, 101, 102]); + + // Clear out any existing entries, since they may be bogus. + this.entries.length = 0; + this._cacheMap.clear(); + + const stream = this.stream; + stream.pos = 0; + const buffer = stream.getBytes(), + bufferStr = bytesToString(buffer), + length = buffer.length; + let position = stream.start; + const trailers = [], + xrefStms = []; + while (position < length) { + let ch = buffer[position]; + if (ch === TAB || ch === LF || ch === CR || ch === SPACE) { + ++position; + continue; + } + if (ch === PERCENT) { + // %-comment + do { + ++position; + if (position >= length) { + break; + } + ch = buffer[position]; + } while (ch !== LF && ch !== CR); + continue; + } + const token = readToken(buffer, position); + let m; + if ( + token.startsWith("xref") && + (token.length === 4 || /\s/.test(token[4])) + ) { + position += skipUntil(buffer, position, trailerBytes); + trailers.push(position); + position += skipUntil(buffer, position, startxrefBytes); + } else if ((m = objRegExp.exec(token))) { + const num = m[1] | 0, + gen = m[2] | 0; + + const startPos = position + token.length; + let contentLength, + updateEntries = false; + if (!this.entries[num]) { + updateEntries = true; + } else if (this.entries[num].gen === gen) { + // Before overwriting an existing entry, ensure that the new one won't + // cause *immediate* errors when it's accessed (fixes issue13783.pdf). + try { + const parser = new Parser({ + lexer: new Lexer(stream.makeSubStream(startPos)), + }); + parser.getObj(); + updateEntries = true; + } catch (ex) { + if (ex instanceof ParserEOFException) { + warn(`indexObjects -- checking object (${token}): "${ex}".`); + } else { + // The error may come from the `Parser`-instance being initialized + // without an `XRef`-instance (we don't have a usable one yet). + updateEntries = true; + } + } + } + if (updateEntries) { + this.entries[num] = { + offset: position - stream.start, + gen, + uncompressed: true, + }; + } + + // Find the next "obj" string, rather than "endobj", to ensure that + // we won't skip over a new 'obj' operator in corrupt files where + // 'endobj' operators are missing (fixes issue9105_reduced.pdf). + gEndobjRegExp.lastIndex = startPos; + const match = gEndobjRegExp.exec(bufferStr); + + if (match) { + const endPos = gEndobjRegExp.lastIndex + 1; + contentLength = endPos - position; + + if (match[1] !== "endobj") { + warn( + `indexObjects: Found "${match[1]}" inside of another "obj", ` + + 'caused by missing "endobj" -- trying to recover.' + ); + contentLength -= match[1].length + 1; + } + } else { + contentLength = length - position; + } + const content = buffer.subarray(position, position + contentLength); + + // checking XRef stream suspect + // (it shall have '/XRef' and next char is not a letter) + const xrefTagOffset = skipUntil(content, 0, xrefBytes); + if (xrefTagOffset < contentLength && content[xrefTagOffset + 5] < 64) { + xrefStms.push(position - stream.start); + this._xrefStms.add(position - stream.start); // Avoid recursion + } + + position += contentLength; + } else if ( + token.startsWith("trailer") && + (token.length === 7 || /\s/.test(token[7])) + ) { + trailers.push(position); + + const startPos = position + token.length; + let contentLength; + // Attempt to handle (some) corrupt documents, where no 'startxref' + // operators are present (fixes issue15590.pdf). + gStartxrefRegExp.lastIndex = startPos; + const match = gStartxrefRegExp.exec(bufferStr); + + if (match) { + const endPos = gStartxrefRegExp.lastIndex + 1; + contentLength = endPos - position; + + if (match[1] !== "startxref") { + warn( + `indexObjects: Found "${match[1]}" after "trailer", ` + + 'caused by missing "startxref" -- trying to recover.' + ); + contentLength -= match[1].length + 1; + } + } else { + contentLength = length - position; + } + position += contentLength; + } else { + position += token.length + 1; + } + } + // reading XRef streams + for (const xrefStm of xrefStms) { + this.startXRefQueue.push(xrefStm); + this.readXRef(/* recoveryMode */ true); + } + + const trailerDicts = []; + // Pre-parsing the trailers to check if the document is possibly encrypted. + let isEncrypted = false; + for (const trailer of trailers) { + stream.pos = trailer; + const parser = new Parser({ + lexer: new Lexer(stream), + xref: this, + allowStreams: true, + recoveryMode: true, + }); + const obj = parser.getObj(); + if (!isCmd(obj, "trailer")) { + continue; + } + // read the trailer dictionary + const dict = parser.getObj(); + if (!(dict instanceof Dict)) { + continue; + } + trailerDicts.push(dict); + + if (dict.has("Encrypt")) { + isEncrypted = true; + } + } + + // finding main trailer + let trailerDict, trailerError; + for (const dict of [...trailerDicts, "genFallback", ...trailerDicts]) { + if (dict === "genFallback") { + if (!trailerError) { + break; // No need to fallback if there were no validation errors. + } + this._generationFallback = true; + continue; + } + // Do some basic validation of the trailer/root dictionary candidate. + let validPagesDict = false; + try { + const rootDict = dict.get("Root"); + if (!(rootDict instanceof Dict)) { + continue; + } + const pagesDict = rootDict.get("Pages"); + if (!(pagesDict instanceof Dict)) { + continue; + } + const pagesCount = pagesDict.get("Count"); + if (Number.isInteger(pagesCount)) { + validPagesDict = true; + } + // The top-level /Pages dictionary isn't obviously corrupt. + } catch (ex) { + trailerError = ex; + continue; + } + // taking the first one with 'ID' + if ( + validPagesDict && + (!isEncrypted || dict.has("Encrypt")) && + dict.has("ID") + ) { + return dict; + } + // The current dictionary is a candidate, but continue searching. + trailerDict = dict; + } + // No trailer with 'ID', taking last one (if exists). + if (trailerDict) { + return trailerDict; + } + // No trailer dictionary found, taking the "top"-dictionary (if exists). + if (this.topDict) { + return this.topDict; + } + + // When no trailer dictionary candidate exists, try picking the first + // dictionary that contains a /Root entry (fixes issue18986.pdf). + if (!trailerDicts.length) { + for (const [num, entry] of this.entries.entries()) { + if (!entry) { + continue; + } + const ref = Ref.get(num, entry.gen); + let obj; + + try { + obj = this.fetch(ref); + } catch { + continue; + } + if (obj instanceof BaseStream) { + obj = obj.dict; + } + if (obj instanceof Dict && obj.has("Root")) { + return obj; + } + } + } + + // nothing helps + throw new InvalidPDFException("Invalid PDF structure."); + } + + readXRef(recoveryMode = false) { + const stream = this.stream; + // Keep track of already parsed XRef tables, to prevent an infinite loop + // when parsing corrupt PDF files where e.g. the /Prev entries create a + // circular dependency between tables (fixes bug1393476.pdf). + const startXRefParsedCache = new Set(); + + while (this.startXRefQueue.length) { + try { + const startXRef = this.startXRefQueue[0]; + + if (startXRefParsedCache.has(startXRef)) { + warn("readXRef - skipping XRef table since it was already parsed."); + this.startXRefQueue.shift(); + continue; + } + startXRefParsedCache.add(startXRef); + + stream.pos = startXRef + stream.start; + + const parser = new Parser({ + lexer: new Lexer(stream), + xref: this, + allowStreams: true, + }); + let obj = parser.getObj(); + let dict; + + // Get dictionary + if (isCmd(obj, "xref")) { + // Parse end-of-file XRef + dict = this.processXRefTable(parser); + if (!this.topDict) { + this.topDict = dict; + } + + // Recursively get other XRefs 'XRefStm', if any + obj = dict.get("XRefStm"); + if (Number.isInteger(obj) && !this._xrefStms.has(obj)) { + // ignore previously loaded xref streams + // (possible infinite recursion) + this._xrefStms.add(obj); + this.startXRefQueue.push(obj); + this.#firstXRefStmPos ??= obj; + } + } else if (Number.isInteger(obj)) { + // Parse in-stream XRef + if ( + !Number.isInteger(parser.getObj()) || + !isCmd(parser.getObj(), "obj") || + !((obj = parser.getObj()) instanceof BaseStream) + ) { + throw new FormatError("Invalid XRef stream"); + } + dict = this.processXRefStream(obj); + if (!this.topDict) { + this.topDict = dict; + } + if (!dict) { + throw new FormatError("Failed to read XRef stream"); + } + } else { + throw new FormatError("Invalid XRef stream header"); + } + + // Recursively get previous dictionary, if any + obj = dict.get("Prev"); + if (Number.isInteger(obj)) { + this.startXRefQueue.push(obj); + } else if (obj instanceof Ref) { + // The spec says Prev must not be a reference, i.e. "/Prev NNN" + // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R" + this.startXRefQueue.push(obj.num); + } + } catch (e) { + if (e instanceof MissingDataException) { + throw e; + } + info("(while reading XRef): " + e); + } + this.startXRefQueue.shift(); + } + + if (this.topDict) { + return this.topDict; + } + if (recoveryMode) { + return undefined; + } + throw new XRefParseException(); + } + + get lastXRefStreamPos() { + return ( + this.#firstXRefStmPos ?? + (this._xrefStms.size > 0 ? Math.max(...this._xrefStms) : null) + ); + } + + getEntry(i) { + const xrefEntry = this.entries[i]; + if (xrefEntry && !xrefEntry.free && xrefEntry.offset) { + return xrefEntry; + } + return null; + } + + fetchIfRef(obj, suppressEncryption = false) { + if (obj instanceof Ref) { + return this.fetch(obj, suppressEncryption); + } + return obj; + } + + fetch(ref, suppressEncryption = false) { + if (!(ref instanceof Ref)) { + throw new Error("ref object is not a reference"); + } + const num = ref.num; + + // The XRef cache is populated with objects which are obtained through + // `Parser.getObj`, and indirectly via `Lexer.getObj`. Neither of these + // methods should ever return `undefined` (note the `assert` calls below). + const cacheEntry = this._cacheMap.get(num); + if (cacheEntry !== undefined) { + // In documents with Object Streams, it's possible that cached `Dict`s + // have not been assigned an `objId` yet (see e.g. issue3115r.pdf). + if (cacheEntry instanceof Dict && !cacheEntry.objId) { + cacheEntry.objId = ref.toString(); + } + return cacheEntry; + } + let xrefEntry = this.getEntry(num); + + if (xrefEntry === null) { + // The referenced entry can be free. + return xrefEntry; + } + // Prevent circular references, in corrupt PDF documents, from hanging the + // worker-thread. This relies, implicitly, on the parsing being synchronous. + if (this._pendingRefs.has(ref)) { + this._pendingRefs.remove(ref); + + warn(`Ignoring circular reference: ${ref}.`); + return CIRCULAR_REF; + } + this._pendingRefs.put(ref); + + try { + xrefEntry = xrefEntry.uncompressed + ? this.fetchUncompressed(ref, xrefEntry, suppressEncryption) + : this.fetchCompressed(ref, xrefEntry, suppressEncryption); + this._pendingRefs.remove(ref); + } catch (ex) { + this._pendingRefs.remove(ref); + throw ex; + } + if (xrefEntry instanceof Dict) { + xrefEntry.objId = ref.toString(); + } else if (xrefEntry instanceof BaseStream) { + xrefEntry.dict.objId = ref.toString(); + } + return xrefEntry; + } + + fetchUncompressed(ref, xrefEntry, suppressEncryption = false) { + const gen = ref.gen; + let num = ref.num; + if (xrefEntry.gen !== gen) { + const msg = `Inconsistent generation in XRef: ${ref}`; + // Try falling back to a *previous* generation (fixes issue15577.pdf). + if (this._generationFallback && xrefEntry.gen < gen) { + warn(msg); + return this.fetchUncompressed( + Ref.get(num, xrefEntry.gen), + xrefEntry, + suppressEncryption + ); + } + throw new XRefEntryException(msg); + } + const stream = this.stream.makeSubStream( + xrefEntry.offset + this.stream.start + ); + const parser = new Parser({ + lexer: new Lexer(stream), + xref: this, + allowStreams: true, + }); + const obj1 = parser.getObj(); + const obj2 = parser.getObj(); + const obj3 = parser.getObj(); + + if (obj1 !== num || obj2 !== gen || !(obj3 instanceof Cmd)) { + throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`); + } + if (obj3.cmd !== "obj") { + // some bad PDFs use "obj1234" and really mean 1234 + if (obj3.cmd.startsWith("obj")) { + num = parseInt(obj3.cmd.substring(3), 10); + if (!Number.isNaN(num)) { + return num; + } + } + throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`); + } + xrefEntry = + this.encrypt && !suppressEncryption + ? parser.getObj(this.encrypt.createCipherTransform(num, gen)) + : parser.getObj(); + if (!(xrefEntry instanceof BaseStream)) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + xrefEntry !== undefined, + 'fetchUncompressed: The "xrefEntry" cannot be undefined.' + ); + } + this._cacheMap.set(num, xrefEntry); + } + return xrefEntry; + } + + fetchCompressed(ref, xrefEntry, suppressEncryption = false) { + const tableOffset = xrefEntry.offset; + const stream = this.fetch(Ref.get(tableOffset, 0)); + if (!(stream instanceof BaseStream)) { + throw new FormatError("bad ObjStm stream"); + } + const first = stream.dict.get("First"); + const n = stream.dict.get("N"); + if (!Number.isInteger(first) || !Number.isInteger(n)) { + throw new FormatError("invalid first and n parameters for ObjStm stream"); + } + let parser = new Parser({ + lexer: new Lexer(stream), + xref: this, + allowStreams: true, + }); + const nums = new Array(n); + const offsets = new Array(n); + // read the object numbers to populate cache + for (let i = 0; i < n; ++i) { + const num = parser.getObj(); + if (!Number.isInteger(num)) { + throw new FormatError( + `invalid object number in the ObjStm stream: ${num}` + ); + } + const offset = parser.getObj(); + if (!Number.isInteger(offset)) { + throw new FormatError( + `invalid object offset in the ObjStm stream: ${offset}` + ); + } + nums[i] = num; + offsets[i] = offset; + } + + const start = (stream.start || 0) + first; + const entries = new Array(n); + // read stream objects for cache + for (let i = 0; i < n; ++i) { + const length = i < n - 1 ? offsets[i + 1] - offsets[i] : undefined; + if (length < 0) { + throw new FormatError("Invalid offset in the ObjStm stream."); + } + parser = new Parser({ + lexer: new Lexer( + stream.makeSubStream(start + offsets[i], length, stream.dict) + ), + xref: this, + allowStreams: true, + }); + + const obj = parser.getObj(); + entries[i] = obj; + if (obj instanceof BaseStream) { + continue; + } + const num = nums[i], + entry = this.entries[num]; + if (entry && entry.offset === tableOffset && entry.gen === i) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + obj !== undefined, + 'fetchCompressed: The "obj" cannot be undefined.' + ); + } + this._cacheMap.set(num, obj); + } + } + xrefEntry = entries[xrefEntry.gen]; + if (xrefEntry === undefined) { + throw new XRefEntryException(`Bad (compressed) XRef entry: ${ref}`); + } + return xrefEntry; + } + + async fetchIfRefAsync(obj, suppressEncryption) { + if (obj instanceof Ref) { + return this.fetchAsync(obj, suppressEncryption); + } + return obj; + } + + async fetchAsync(ref, suppressEncryption) { + try { + return this.fetch(ref, suppressEncryption); + } catch (ex) { + if (!(ex instanceof MissingDataException)) { + throw ex; + } + await this.pdfManager.requestRange(ex.begin, ex.end); + return this.fetchAsync(ref, suppressEncryption); + } + } + + getCatalogObj() { + return this.root; + } +} + +export { XRef }; diff --git a/frontend/public/pdf.js/src/display/annotation_layer.js b/frontend/public/pdf.js/src/display/annotation_layer.js new file mode 100644 index 0000000..8afd218 --- /dev/null +++ b/frontend/public/pdf.js/src/display/annotation_layer.js @@ -0,0 +1,3360 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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. + */ + +/** @typedef {import("./api").PDFPageProxy} PDFPageProxy */ +/** @typedef {import("./display_utils").PageViewport} PageViewport */ +// eslint-disable-next-line max-len +/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */ +// eslint-disable-next-line max-len +/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */ +/** @typedef {import("../../web/interfaces").IPDFLinkService} IPDFLinkService */ +// eslint-disable-next-line max-len +/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */ +// eslint-disable-next-line max-len +/** @typedef {import("../../web/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */ + +import { + AnnotationBorderStyleType, + AnnotationEditorType, + AnnotationPrefix, + AnnotationType, + FeatureTest, + LINE_FACTOR, + shadow, + unreachable, + Util, + warn, +} from "../shared/util.js"; +import { PDFDateString, setLayerDimensions } from "./display_utils.js"; +import { AnnotationStorage } from "./annotation_storage.js"; +import { ColorConverters } from "../shared/scripting_utils.js"; +import { DOMSVGFactory } from "./svg_factory.js"; +import { XfaLayer } from "./xfa_layer.js"; + +const DEFAULT_TAB_INDEX = 1000; +const DEFAULT_FONT_SIZE = 9; +const GetElementsByNameSet = new WeakSet(); + +/** + * @typedef {Object} AnnotationElementParameters + * @property {Object} data + * @property {HTMLDivElement} layer + * @property {IPDFLinkService} linkService + * @property {IDownloadManager} [downloadManager] + * @property {AnnotationStorage} [annotationStorage] + * @property {string} [imageResourcesPath] - Path for image resources, mainly + * for annotation icons. Include trailing slash. + * @property {boolean} renderForms + * @property {Object} svgFactory + * @property {boolean} [enableScripting] + * @property {boolean} [hasJSActions] + * @property {Object} [fieldObjects] + */ + +class AnnotationElementFactory { + /** + * @param {AnnotationElementParameters} parameters + * @returns {AnnotationElement} + */ + static create(parameters) { + const subtype = parameters.data.annotationType; + + switch (subtype) { + case AnnotationType.LINK: + return new LinkAnnotationElement(parameters); + + case AnnotationType.TEXT: + return new TextAnnotationElement(parameters); + + case AnnotationType.WIDGET: + const fieldType = parameters.data.fieldType; + + switch (fieldType) { + case "Tx": + return new TextWidgetAnnotationElement(parameters); + case "Btn": + if (parameters.data.radioButton) { + return new RadioButtonWidgetAnnotationElement(parameters); + } else if (parameters.data.checkBox) { + return new CheckboxWidgetAnnotationElement(parameters); + } + return new PushButtonWidgetAnnotationElement(parameters); + case "Ch": + return new ChoiceWidgetAnnotationElement(parameters); + case "Sig": + return new SignatureWidgetAnnotationElement(parameters); + } + return new WidgetAnnotationElement(parameters); + + case AnnotationType.POPUP: + return new PopupAnnotationElement(parameters); + + case AnnotationType.FREETEXT: + return new FreeTextAnnotationElement(parameters); + + case AnnotationType.LINE: + return new LineAnnotationElement(parameters); + + case AnnotationType.SQUARE: + return new SquareAnnotationElement(parameters); + + case AnnotationType.CIRCLE: + return new CircleAnnotationElement(parameters); + + case AnnotationType.POLYLINE: + return new PolylineAnnotationElement(parameters); + + case AnnotationType.CARET: + return new CaretAnnotationElement(parameters); + + case AnnotationType.INK: + return new InkAnnotationElement(parameters); + + case AnnotationType.POLYGON: + return new PolygonAnnotationElement(parameters); + + case AnnotationType.HIGHLIGHT: + return new HighlightAnnotationElement(parameters); + + case AnnotationType.UNDERLINE: + return new UnderlineAnnotationElement(parameters); + + case AnnotationType.SQUIGGLY: + return new SquigglyAnnotationElement(parameters); + + case AnnotationType.STRIKEOUT: + return new StrikeOutAnnotationElement(parameters); + + case AnnotationType.STAMP: + return new StampAnnotationElement(parameters); + + case AnnotationType.FILEATTACHMENT: + return new FileAttachmentAnnotationElement(parameters); + + default: + return new AnnotationElement(parameters); + } + } +} + +class AnnotationElement { + #updates = null; + + #hasBorder = false; + + #popupElement = null; + + constructor( + parameters, + { + isRenderable = false, + ignoreBorder = false, + createQuadrilaterals = false, + } = {} + ) { + this.isRenderable = isRenderable; + this.data = parameters.data; + this.layer = parameters.layer; + this.linkService = parameters.linkService; + this.downloadManager = parameters.downloadManager; + this.imageResourcesPath = parameters.imageResourcesPath; + this.renderForms = parameters.renderForms; + this.svgFactory = parameters.svgFactory; + this.annotationStorage = parameters.annotationStorage; + this.enableScripting = parameters.enableScripting; + this.hasJSActions = parameters.hasJSActions; + this._fieldObjects = parameters.fieldObjects; + this.parent = parameters.parent; + + if (isRenderable) { + this.container = this._createContainer(ignoreBorder); + } + if (createQuadrilaterals) { + this._createQuadrilaterals(); + } + } + + static _hasPopupData({ titleObj, contentsObj, richText }) { + return !!(titleObj?.str || contentsObj?.str || richText?.str); + } + + get _isEditable() { + return this.data.isEditable; + } + + get hasPopupData() { + return AnnotationElement._hasPopupData(this.data); + } + + updateEdited(params) { + if (!this.container) { + return; + } + + this.#updates ||= { + rect: this.data.rect.slice(0), + }; + + const { rect } = params; + + if (rect) { + this.#setRectEdited(rect); + } + + this.#popupElement?.popup.updateEdited(params); + } + + resetEdited() { + if (!this.#updates) { + return; + } + this.#setRectEdited(this.#updates.rect); + this.#popupElement?.popup.resetEdited(); + this.#updates = null; + } + + #setRectEdited(rect) { + const { + container: { style }, + data: { rect: currentRect, rotation }, + parent: { + viewport: { + rawDims: { pageWidth, pageHeight, pageX, pageY }, + }, + }, + } = this; + currentRect?.splice(0, 4, ...rect); + style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`; + style.top = `${(100 * (pageHeight - rect[3] + pageY)) / pageHeight}%`; + if (rotation === 0) { + style.width = `${(100 * /* width = */ (rect[2] - rect[0])) / pageWidth}%`; + style.height = `${(100 * /* height = */ (rect[3] - rect[1])) / pageHeight}%`; + } else { + this.setRotation(rotation); + } + } + + /** + * Create an empty container for the annotation's HTML element. + * + * @private + * @param {boolean} ignoreBorder + * @memberof AnnotationElement + * @returns {HTMLElement} A section element. + */ + _createContainer(ignoreBorder) { + const { + data, + parent: { page, viewport }, + } = this; + + const container = document.createElement("section"); + container.setAttribute("data-annotation-id", data.id); + if (!(this instanceof WidgetAnnotationElement)) { + container.tabIndex = DEFAULT_TAB_INDEX; + } + const { style } = container; + + // The accessibility manager will move the annotation in the DOM in + // order to match the visual ordering. + // But if an annotation is above an other one, then we must draw it + // after the other one whatever the order is in the DOM, hence the + // use of the z-index. + style.zIndex = this.parent.zIndex++; + + if (data.alternativeText) { + container.title = data.alternativeText; + } + + if (data.noRotate) { + container.classList.add("norotate"); + } + + if (!data.rect || this instanceof PopupAnnotationElement) { + const { rotation } = data; + if (!data.hasOwnCanvas && rotation !== 0) { + this.setRotation(rotation, container); + } + return container; + } + const { width, height } = this; + + if (!ignoreBorder && data.borderStyle.width > 0) { + style.borderWidth = `${data.borderStyle.width}px`; + + const horizontalRadius = data.borderStyle.horizontalCornerRadius; + const verticalRadius = data.borderStyle.verticalCornerRadius; + if (horizontalRadius > 0 || verticalRadius > 0) { + const radius = `calc(${horizontalRadius}px * var(--total-scale-factor)) / calc(${verticalRadius}px * var(--total-scale-factor))`; + style.borderRadius = radius; + } else if (this instanceof RadioButtonWidgetAnnotationElement) { + const radius = `calc(${width}px * var(--total-scale-factor)) / calc(${height}px * var(--total-scale-factor))`; + style.borderRadius = radius; + } + + switch (data.borderStyle.style) { + case AnnotationBorderStyleType.SOLID: + style.borderStyle = "solid"; + break; + + case AnnotationBorderStyleType.DASHED: + style.borderStyle = "dashed"; + break; + + case AnnotationBorderStyleType.BEVELED: + warn("Unimplemented border style: beveled"); + break; + + case AnnotationBorderStyleType.INSET: + warn("Unimplemented border style: inset"); + break; + + case AnnotationBorderStyleType.UNDERLINE: + style.borderBottomStyle = "solid"; + break; + + default: + break; + } + + const borderColor = data.borderColor || null; + if (borderColor) { + this.#hasBorder = true; + style.borderColor = Util.makeHexColor( + borderColor[0] | 0, + borderColor[1] | 0, + borderColor[2] | 0 + ); + } else { + // Transparent (invisible) border, so do not draw it at all. + style.borderWidth = 0; + } + } + + // Do *not* modify `data.rect`, since that will corrupt the annotation + // position on subsequent calls to `_createContainer` (see issue 6804). + const rect = Util.normalizeRect([ + data.rect[0], + page.view[3] - data.rect[1] + page.view[1], + data.rect[2], + page.view[3] - data.rect[3] + page.view[1], + ]); + const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims; + + style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`; + style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`; + + const { rotation } = data; + if (data.hasOwnCanvas || rotation === 0) { + style.width = `${(100 * width) / pageWidth}%`; + style.height = `${(100 * height) / pageHeight}%`; + } else { + this.setRotation(rotation, container); + } + + return container; + } + + setRotation(angle, container = this.container) { + if (!this.data.rect) { + return; + } + const { pageWidth, pageHeight } = this.parent.viewport.rawDims; + let { width, height } = this; + + if (angle % 180 !== 0) { + [width, height] = [height, width]; + } + container.style.width = `${(100 * width) / pageWidth}%`; + container.style.height = `${(100 * height) / pageHeight}%`; + + container.setAttribute("data-main-rotation", (360 - angle) % 360); + } + + get _commonActions() { + const setColor = (jsName, styleName, event) => { + const color = event.detail[jsName]; + const colorType = color[0]; + const colorArray = color.slice(1); + event.target.style[styleName] = + ColorConverters[`${colorType}_HTML`](colorArray); + this.annotationStorage.setValue(this.data.id, { + [styleName]: ColorConverters[`${colorType}_rgb`](colorArray), + }); + }; + + return shadow(this, "_commonActions", { + display: event => { + const { display } = event.detail; + // See scripting/constants.js for the values of `Display`. + // 0 = visible, 1 = hidden, 2 = noPrint and 3 = noView. + const hidden = display % 2 === 1; + this.container.style.visibility = hidden ? "hidden" : "visible"; + this.annotationStorage.setValue(this.data.id, { + noView: hidden, + noPrint: display === 1 || display === 2, + }); + }, + print: event => { + this.annotationStorage.setValue(this.data.id, { + noPrint: !event.detail.print, + }); + }, + hidden: event => { + const { hidden } = event.detail; + this.container.style.visibility = hidden ? "hidden" : "visible"; + this.annotationStorage.setValue(this.data.id, { + noPrint: hidden, + noView: hidden, + }); + }, + focus: event => { + setTimeout(() => event.target.focus({ preventScroll: false }), 0); + }, + userName: event => { + // tooltip + event.target.title = event.detail.userName; + }, + readonly: event => { + event.target.disabled = event.detail.readonly; + }, + required: event => { + this._setRequired(event.target, event.detail.required); + }, + bgColor: event => { + setColor("bgColor", "backgroundColor", event); + }, + fillColor: event => { + setColor("fillColor", "backgroundColor", event); + }, + fgColor: event => { + setColor("fgColor", "color", event); + }, + textColor: event => { + setColor("textColor", "color", event); + }, + borderColor: event => { + setColor("borderColor", "borderColor", event); + }, + strokeColor: event => { + setColor("strokeColor", "borderColor", event); + }, + rotation: event => { + const angle = event.detail.rotation; + this.setRotation(angle); + this.annotationStorage.setValue(this.data.id, { + rotation: angle, + }); + }, + }); + } + + _dispatchEventFromSandbox(actions, jsEvent) { + const commonActions = this._commonActions; + for (const name of Object.keys(jsEvent.detail)) { + const action = actions[name] || commonActions[name]; + action?.(jsEvent); + } + } + + _setDefaultPropertiesFromJS(element) { + if (!this.enableScripting) { + return; + } + + // Some properties may have been updated thanks to JS. + const storedData = this.annotationStorage.getRawValue(this.data.id); + if (!storedData) { + return; + } + + const commonActions = this._commonActions; + for (const [actionName, detail] of Object.entries(storedData)) { + const action = commonActions[actionName]; + if (action) { + const eventProxy = { + detail: { + [actionName]: detail, + }, + target: element, + }; + action(eventProxy); + // The action has been consumed: no need to keep it. + delete storedData[actionName]; + } + } + } + + /** + * Create quadrilaterals from the annotation's quadpoints. + * + * @private + * @memberof AnnotationElement + */ + _createQuadrilaterals() { + if (!this.container) { + return; + } + const { quadPoints } = this.data; + if (!quadPoints) { + return; + } + + const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(x => + Math.fround(x) + ); + + if (quadPoints.length === 8) { + const [trX, trY, blX, blY] = quadPoints.subarray(2, 6); + if ( + rectTrX === trX && + rectTrY === trY && + rectBlX === blX && + rectBlY === blY + ) { + // The quadpoints cover the whole annotation rectangle, so no need to + // create a quadrilateral. + return; + } + } + + const { style } = this.container; + let svgBuffer; + if (this.#hasBorder) { + const { borderColor, borderWidth } = style; + style.borderWidth = 0; + svgBuffer = [ + "url('data:image/svg+xml;utf8,", + ``, + ``, + ]; + this.container.classList.add("hasBorder"); + } + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { + this.container.classList.add("hasClipPath"); + } + + const width = rectTrX - rectBlX; + const height = rectTrY - rectBlY; + + const { svgFactory } = this; + const svg = svgFactory.createElement("svg"); + svg.classList.add("quadrilateralsContainer"); + svg.setAttribute("width", 0); + svg.setAttribute("height", 0); + const defs = svgFactory.createElement("defs"); + svg.append(defs); + const clipPath = svgFactory.createElement("clipPath"); + const id = `clippath_${this.data.id}`; + clipPath.setAttribute("id", id); + clipPath.setAttribute("clipPathUnits", "objectBoundingBox"); + defs.append(clipPath); + + for (let i = 2, ii = quadPoints.length; i < ii; i += 8) { + const trX = quadPoints[i]; + const trY = quadPoints[i + 1]; + const blX = quadPoints[i + 2]; + const blY = quadPoints[i + 3]; + const rect = svgFactory.createElement("rect"); + const x = (blX - rectBlX) / width; + const y = (rectTrY - trY) / height; + const rectWidth = (trX - blX) / width; + const rectHeight = (trY - blY) / height; + rect.setAttribute("x", x); + rect.setAttribute("y", y); + rect.setAttribute("width", rectWidth); + rect.setAttribute("height", rectHeight); + clipPath.append(rect); + svgBuffer?.push( + `` + ); + } + + if (this.#hasBorder) { + svgBuffer.push(`')`); + style.backgroundImage = svgBuffer.join(""); + } + + this.container.append(svg); + this.container.style.clipPath = `url(#${id})`; + } + + /** + * Create a popup for the annotation's HTML element. This is used for + * annotations that do not have a Popup entry in the dictionary, but + * are of a type that works with popups (such as Highlight annotations). + * + * @private + * @memberof AnnotationElement + */ + _createPopup() { + const { data } = this; + + const popup = (this.#popupElement = new PopupAnnotationElement({ + data: { + color: data.color, + titleObj: data.titleObj, + modificationDate: data.modificationDate, + contentsObj: data.contentsObj, + richText: data.richText, + parentRect: data.rect, + borderStyle: 0, + id: `popup_${data.id}`, + rotation: data.rotation, + }, + parent: this.parent, + elements: [this], + })); + this.parent.div.append(popup.render()); + } + + /** + * Render the annotation's HTML element(s). + * + * @public + * @memberof AnnotationElement + */ + render() { + unreachable("Abstract method `AnnotationElement.render` called"); + } + + /** + * @private + * @returns {Array} + */ + _getElementsByName(name, skipId = null) { + const fields = []; + + if (this._fieldObjects) { + const fieldObj = this._fieldObjects[name]; + if (fieldObj) { + for (const { page, id, exportValues } of fieldObj) { + if (page === -1) { + continue; + } + if (id === skipId) { + continue; + } + const exportValue = + typeof exportValues === "string" ? exportValues : null; + + const domElement = document.querySelector( + `[data-element-id="${id}"]` + ); + if (domElement && !GetElementsByNameSet.has(domElement)) { + warn(`_getElementsByName - element not allowed: ${id}`); + continue; + } + fields.push({ id, exportValue, domElement }); + } + } + return fields; + } + // Fallback to a regular DOM lookup, to ensure that the standalone + // viewer components won't break. + for (const domElement of document.getElementsByName(name)) { + const { exportValue } = domElement; + const id = domElement.getAttribute("data-element-id"); + if (id === skipId) { + continue; + } + if (!GetElementsByNameSet.has(domElement)) { + continue; + } + fields.push({ id, exportValue, domElement }); + } + return fields; + } + + show() { + if (this.container) { + this.container.hidden = false; + } + this.popup?.maybeShow(); + } + + hide() { + if (this.container) { + this.container.hidden = true; + } + this.popup?.forceHide(); + } + + /** + * Get the HTML element(s) which can trigger a popup when clicked or hovered. + * + * @public + * @memberof AnnotationElement + * @returns {Array|HTMLElement} An array of elements or an + * element. + */ + getElementsToTriggerPopup() { + return this.container; + } + + addHighlightArea() { + const triggers = this.getElementsToTriggerPopup(); + if (Array.isArray(triggers)) { + for (const element of triggers) { + element.classList.add("highlightArea"); + } + } else { + triggers.classList.add("highlightArea"); + } + } + + _editOnDoubleClick() { + if (!this._isEditable) { + return; + } + const { + annotationEditorType: mode, + data: { id: editId }, + } = this; + this.container.addEventListener("dblclick", () => { + this.linkService.eventBus?.dispatch("switchannotationeditormode", { + source: this, + mode, + editId, + }); + }); + } + + get width() { + return this.data.rect[2] - this.data.rect[0]; + } + + get height() { + return this.data.rect[3] - this.data.rect[1]; + } +} + +class LinkAnnotationElement extends AnnotationElement { + constructor(parameters, options = null) { + super(parameters, { + isRenderable: true, + ignoreBorder: !!options?.ignoreBorder, + createQuadrilaterals: true, + }); + this.isTooltipOnly = parameters.data.isTooltipOnly; + } + + render() { + const { data, linkService } = this; + const link = document.createElement("a"); + link.setAttribute("data-element-id", data.id); + let isBound = false; + + if (data.url) { + linkService.addLinkAttributes(link, data.url, data.newWindow); + isBound = true; + } else if (data.action) { + this._bindNamedAction(link, data.action); + isBound = true; + } else if (data.attachment) { + this.#bindAttachment(link, data.attachment, data.attachmentDest); + isBound = true; + } else if (data.setOCGState) { + this.#bindSetOCGState(link, data.setOCGState); + isBound = true; + } else if (data.dest) { + this._bindLink(link, data.dest); + isBound = true; + } else { + if ( + data.actions && + (data.actions.Action || + data.actions["Mouse Up"] || + data.actions["Mouse Down"]) && + this.enableScripting && + this.hasJSActions + ) { + this._bindJSAction(link, data); + isBound = true; + } + + if (data.resetForm) { + this._bindResetFormAction(link, data.resetForm); + isBound = true; + } else if (this.isTooltipOnly && !isBound) { + this._bindLink(link, ""); + isBound = true; + } + } + + this.container.classList.add("linkAnnotation"); + if (isBound) { + this.container.append(link); + } + + return this.container; + } + + #setInternalLink() { + this.container.setAttribute("data-internal-link", ""); + } + + /** + * Bind internal links to the link element. + * + * @private + * @param {Object} link + * @param {Object} destination + * @memberof LinkAnnotationElement + */ + _bindLink(link, destination) { + link.href = this.linkService.getDestinationHash(destination); + link.onclick = () => { + if (destination) { + this.linkService.goToDestination(destination); + } + return false; + }; + if (destination || destination === /* isTooltipOnly = */ "") { + this.#setInternalLink(); + } + } + + /** + * Bind named actions to the link element. + * + * @private + * @param {Object} link + * @param {Object} action + * @memberof LinkAnnotationElement + */ + _bindNamedAction(link, action) { + link.href = this.linkService.getAnchorUrl(""); + link.onclick = () => { + this.linkService.executeNamedAction(action); + return false; + }; + this.#setInternalLink(); + } + + /** + * Bind attachments to the link element. + * @param {Object} link + * @param {Object} attachment + * @param {str} [dest] + */ + #bindAttachment(link, attachment, dest = null) { + link.href = this.linkService.getAnchorUrl(""); + if (attachment.description) { + link.title = attachment.description; + } + link.onclick = () => { + this.downloadManager?.openOrDownloadData( + attachment.content, + attachment.filename, + dest + ); + return false; + }; + this.#setInternalLink(); + } + + /** + * Bind SetOCGState actions to the link element. + * @param {Object} link + * @param {Object} action + */ + #bindSetOCGState(link, action) { + link.href = this.linkService.getAnchorUrl(""); + link.onclick = () => { + this.linkService.executeSetOCGState(action); + return false; + }; + this.#setInternalLink(); + } + + /** + * Bind JS actions to the link element. + * + * @private + * @param {Object} link + * @param {Object} data + * @memberof LinkAnnotationElement + */ + _bindJSAction(link, data) { + link.href = this.linkService.getAnchorUrl(""); + const map = new Map([ + ["Action", "onclick"], + ["Mouse Up", "onmouseup"], + ["Mouse Down", "onmousedown"], + ]); + for (const name of Object.keys(data.actions)) { + const jsName = map.get(name); + if (!jsName) { + continue; + } + link[jsName] = () => { + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id: data.id, + name, + }, + }); + return false; + }; + } + + if (!link.onclick) { + link.onclick = () => false; + } + this.#setInternalLink(); + } + + _bindResetFormAction(link, resetForm) { + const otherClickAction = link.onclick; + if (!otherClickAction) { + link.href = this.linkService.getAnchorUrl(""); + } + this.#setInternalLink(); + + if (!this._fieldObjects) { + warn( + `_bindResetFormAction - "resetForm" action not supported, ` + + "ensure that the `fieldObjects` parameter is provided." + ); + if (!otherClickAction) { + link.onclick = () => false; + } + return; + } + + link.onclick = () => { + otherClickAction?.(); + + const { + fields: resetFormFields, + refs: resetFormRefs, + include, + } = resetForm; + + const allFields = []; + if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) { + const fieldIds = new Set(resetFormRefs); + for (const fieldName of resetFormFields) { + const fields = this._fieldObjects[fieldName] || []; + for (const { id } of fields) { + fieldIds.add(id); + } + } + for (const fields of Object.values(this._fieldObjects)) { + for (const field of fields) { + if (fieldIds.has(field.id) === include) { + allFields.push(field); + } + } + } + } else { + for (const fields of Object.values(this._fieldObjects)) { + allFields.push(...fields); + } + } + + const storage = this.annotationStorage; + const allIds = []; + for (const field of allFields) { + const { id } = field; + allIds.push(id); + switch (field.type) { + case "text": { + const value = field.defaultValue || ""; + storage.setValue(id, { value }); + break; + } + case "checkbox": + case "radiobutton": { + const value = field.defaultValue === field.exportValues; + storage.setValue(id, { value }); + break; + } + case "combobox": + case "listbox": { + const value = field.defaultValue || ""; + storage.setValue(id, { value }); + break; + } + default: + continue; + } + + const domElement = document.querySelector(`[data-element-id="${id}"]`); + if (!domElement) { + continue; + } else if (!GetElementsByNameSet.has(domElement)) { + warn(`_bindResetFormAction - element not allowed: ${id}`); + continue; + } + domElement.dispatchEvent(new Event("resetform")); + } + + if (this.enableScripting) { + // Update the values in the sandbox. + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id: "app", + ids: allIds, + name: "ResetForm", + }, + }); + } + + return false; + }; + } +} + +class TextAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: true }); + } + + render() { + this.container.classList.add("textAnnotation"); + + const image = document.createElement("img"); + image.src = + this.imageResourcesPath + + "annotation-" + + this.data.name.toLowerCase() + + ".svg"; + image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type"); + image.setAttribute( + "data-l10n-args", + JSON.stringify({ type: this.data.name }) + ); + + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + this.container.append(image); + return this.container; + } +} + +class WidgetAnnotationElement extends AnnotationElement { + render() { + // Show only the container for unsupported field types. + return this.container; + } + + showElementAndHideCanvas(element) { + if (this.data.hasOwnCanvas) { + if (element.previousSibling?.nodeName === "CANVAS") { + element.previousSibling.hidden = true; + } + element.hidden = false; + } + } + + _getKeyModifier(event) { + return FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey; + } + + _setEventListener(element, elementData, baseName, eventName, valueGetter) { + if (baseName.includes("mouse")) { + // Mouse events + element.addEventListener(baseName, event => { + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id: this.data.id, + name: eventName, + value: valueGetter(event), + shift: event.shiftKey, + modifier: this._getKeyModifier(event), + }, + }); + }); + } else { + // Non-mouse events + element.addEventListener(baseName, event => { + if (baseName === "blur") { + if (!elementData.focused || !event.relatedTarget) { + return; + } + elementData.focused = false; + } else if (baseName === "focus") { + if (elementData.focused) { + return; + } + elementData.focused = true; + } + + if (!valueGetter) { + return; + } + + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id: this.data.id, + name: eventName, + value: valueGetter(event), + }, + }); + }); + } + } + + _setEventListeners(element, elementData, names, getter) { + for (const [baseName, eventName] of names) { + if (eventName === "Action" || this.data.actions?.[eventName]) { + if (eventName === "Focus" || eventName === "Blur") { + elementData ||= { focused: false }; + } + this._setEventListener( + element, + elementData, + baseName, + eventName, + getter + ); + if (eventName === "Focus" && !this.data.actions?.Blur) { + // Ensure that elementData will have the correct value. + this._setEventListener(element, elementData, "blur", "Blur", null); + } else if (eventName === "Blur" && !this.data.actions?.Focus) { + this._setEventListener(element, elementData, "focus", "Focus", null); + } + } + } + } + + _setBackgroundColor(element) { + const color = this.data.backgroundColor || null; + element.style.backgroundColor = + color === null + ? "transparent" + : Util.makeHexColor(color[0], color[1], color[2]); + } + + /** + * Apply text styles to the text in the element. + * + * @private + * @param {HTMLDivElement} element + * @memberof TextWidgetAnnotationElement + */ + _setTextStyle(element) { + const TEXT_ALIGNMENT = ["left", "center", "right"]; + const { fontColor } = this.data.defaultAppearanceData; + const fontSize = + this.data.defaultAppearanceData.fontSize || DEFAULT_FONT_SIZE; + + const style = element.style; + + // TODO: If the font-size is zero, calculate it based on the height and + // width of the element. + // Not setting `style.fontSize` will use the default font-size for now. + + // We don't use the font, as specified in the PDF document, for the + // element. Hence using the original `fontSize` could look bad, which is why + // it's instead based on the field height. + // If the height is "big" then it could lead to a too big font size + // so in this case use the one we've in the pdf (hence the min). + let computedFontSize; + const BORDER_SIZE = 2; + const roundToOneDecimal = x => Math.round(10 * x) / 10; + if (this.data.multiLine) { + const height = Math.abs( + this.data.rect[3] - this.data.rect[1] - BORDER_SIZE + ); + const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1; + const lineHeight = height / numberOfLines; + computedFontSize = Math.min( + fontSize, + roundToOneDecimal(lineHeight / LINE_FACTOR) + ); + } else { + const height = Math.abs( + this.data.rect[3] - this.data.rect[1] - BORDER_SIZE + ); + computedFontSize = Math.min( + fontSize, + roundToOneDecimal(height / LINE_FACTOR) + ); + } + style.fontSize = `calc(${computedFontSize}px * var(--total-scale-factor))`; + + style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]); + + if (this.data.textAlignment !== null) { + style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; + } + } + + _setRequired(element, isRequired) { + if (isRequired) { + element.setAttribute("required", true); + } else { + element.removeAttribute("required"); + } + element.setAttribute("aria-required", isRequired); + } +} + +class TextWidgetAnnotationElement extends WidgetAnnotationElement { + constructor(parameters) { + const isRenderable = + parameters.renderForms || + parameters.data.hasOwnCanvas || + (!parameters.data.hasAppearance && !!parameters.data.fieldValue); + super(parameters, { isRenderable }); + } + + setPropertyOnSiblings(base, key, value, keyInStorage) { + const storage = this.annotationStorage; + for (const element of this._getElementsByName( + base.name, + /* skipId = */ base.id + )) { + if (element.domElement) { + element.domElement[key] = value; + } + storage.setValue(element.id, { [keyInStorage]: value }); + } + } + + render() { + const storage = this.annotationStorage; + const id = this.data.id; + + this.container.classList.add("textWidgetAnnotation"); + + let element = null; + if (this.renderForms) { + // NOTE: We cannot set the values using `element.value` below, since it + // prevents the AnnotationLayer rasterizer in `test/driver.js` + // from parsing the elements correctly for the reference tests. + const storedData = storage.getValue(id, { + value: this.data.fieldValue, + }); + let textContent = storedData.value || ""; + const maxLen = storage.getValue(id, { + charLimit: this.data.maxLen, + }).charLimit; + if (maxLen && textContent.length > maxLen) { + textContent = textContent.slice(0, maxLen); + } + + let fieldFormattedValues = + storedData.formattedValue || this.data.textContent?.join("\n") || null; + if (fieldFormattedValues && this.data.comb) { + fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, ""); + } + + const elementData = { + userValue: textContent, + formattedValue: fieldFormattedValues, + lastCommittedValue: null, + commitKey: 1, + focused: false, + }; + + if (this.data.multiLine) { + element = document.createElement("textarea"); + element.textContent = fieldFormattedValues ?? textContent; + if (this.data.doNotScroll) { + element.style.overflowY = "hidden"; + } + } else { + element = document.createElement("input"); + element.type = this.data.password ? "password" : "text"; + element.setAttribute("value", fieldFormattedValues ?? textContent); + if (this.data.doNotScroll) { + element.style.overflowX = "hidden"; + } + } + if (this.data.hasOwnCanvas) { + element.hidden = true; + } + GetElementsByNameSet.add(element); + element.setAttribute("data-element-id", id); + + element.disabled = this.data.readOnly; + element.name = this.data.fieldName; + element.tabIndex = DEFAULT_TAB_INDEX; + + this._setRequired(element, this.data.required); + + if (maxLen) { + element.maxLength = maxLen; + } + + element.addEventListener("input", event => { + storage.setValue(id, { value: event.target.value }); + this.setPropertyOnSiblings( + element, + "value", + event.target.value, + "value" + ); + elementData.formattedValue = null; + }); + + element.addEventListener("resetform", event => { + const defaultValue = this.data.defaultFieldValue ?? ""; + element.value = elementData.userValue = defaultValue; + elementData.formattedValue = null; + }); + + let blurListener = event => { + const { formattedValue } = elementData; + if (formattedValue !== null && formattedValue !== undefined) { + event.target.value = formattedValue; + } + // Reset the cursor position to the start of the field (issue 12359). + event.target.scrollLeft = 0; + }; + + if (this.enableScripting && this.hasJSActions) { + element.addEventListener("focus", event => { + if (elementData.focused) { + return; + } + const { target } = event; + if (elementData.userValue) { + target.value = elementData.userValue; + } + elementData.lastCommittedValue = target.value; + elementData.commitKey = 1; + if (!this.data.actions?.Focus) { + elementData.focused = true; + } + }); + + element.addEventListener("updatefromsandbox", jsEvent => { + this.showElementAndHideCanvas(jsEvent.target); + const actions = { + value(event) { + elementData.userValue = event.detail.value ?? ""; + storage.setValue(id, { value: elementData.userValue.toString() }); + event.target.value = elementData.userValue; + }, + formattedValue(event) { + const { formattedValue } = event.detail; + elementData.formattedValue = formattedValue; + if ( + formattedValue !== null && + formattedValue !== undefined && + event.target !== document.activeElement + ) { + // Input hasn't the focus so display formatted string + event.target.value = formattedValue; + } + storage.setValue(id, { + formattedValue, + }); + }, + selRange(event) { + event.target.setSelectionRange(...event.detail.selRange); + }, + charLimit: event => { + const { charLimit } = event.detail; + const { target } = event; + if (charLimit === 0) { + target.removeAttribute("maxLength"); + return; + } + + target.setAttribute("maxLength", charLimit); + let value = elementData.userValue; + if (!value || value.length <= charLimit) { + return; + } + value = value.slice(0, charLimit); + target.value = elementData.userValue = value; + storage.setValue(id, { value }); + + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id, + name: "Keystroke", + value, + willCommit: true, + commitKey: 1, + selStart: target.selectionStart, + selEnd: target.selectionEnd, + }, + }); + }, + }; + this._dispatchEventFromSandbox(actions, jsEvent); + }); + + // Even if the field hasn't any actions + // leaving it can still trigger some actions with Calculate + element.addEventListener("keydown", event => { + elementData.commitKey = 1; + // If the key is one of Escape, Enter then the data are committed. + // If we've a Tab then data will be committed on blur. + let commitKey = -1; + if (event.key === "Escape") { + commitKey = 0; + } else if (event.key === "Enter" && !this.data.multiLine) { + // When we've a multiline field, "Enter" key is a key as the other + // hence we don't commit the data (Acrobat behaves the same way) + // (see issue #15627). + commitKey = 2; + } else if (event.key === "Tab") { + elementData.commitKey = 3; + } + if (commitKey === -1) { + return; + } + const { value } = event.target; + if (elementData.lastCommittedValue === value) { + return; + } + elementData.lastCommittedValue = value; + // Save the entered value + elementData.userValue = value; + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id, + name: "Keystroke", + value, + willCommit: true, + commitKey, + selStart: event.target.selectionStart, + selEnd: event.target.selectionEnd, + }, + }); + }); + const _blurListener = blurListener; + blurListener = null; + element.addEventListener("blur", event => { + if (!elementData.focused || !event.relatedTarget) { + return; + } + if (!this.data.actions?.Blur) { + elementData.focused = false; + } + const { value } = event.target; + elementData.userValue = value; + if (elementData.lastCommittedValue !== value) { + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id, + name: "Keystroke", + value, + willCommit: true, + commitKey: elementData.commitKey, + selStart: event.target.selectionStart, + selEnd: event.target.selectionEnd, + }, + }); + } + _blurListener(event); + }); + + if (this.data.actions?.Keystroke) { + element.addEventListener("beforeinput", event => { + elementData.lastCommittedValue = null; + const { data, target } = event; + const { value, selectionStart, selectionEnd } = target; + + let selStart = selectionStart, + selEnd = selectionEnd; + + switch (event.inputType) { + // https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes + case "deleteWordBackward": { + const match = value + .substring(0, selectionStart) + .match(/\w*[^\w]*$/); + if (match) { + selStart -= match[0].length; + } + break; + } + case "deleteWordForward": { + const match = value + .substring(selectionStart) + .match(/^[^\w]*\w*/); + if (match) { + selEnd += match[0].length; + } + break; + } + case "deleteContentBackward": + if (selectionStart === selectionEnd) { + selStart -= 1; + } + break; + case "deleteContentForward": + if (selectionStart === selectionEnd) { + selEnd += 1; + } + break; + } + + // We handle the event ourselves. + event.preventDefault(); + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id, + name: "Keystroke", + value, + change: data || "", + willCommit: false, + selStart, + selEnd, + }, + }); + }); + } + + this._setEventListeners( + element, + elementData, + [ + ["focus", "Focus"], + ["blur", "Blur"], + ["mousedown", "Mouse Down"], + ["mouseenter", "Mouse Enter"], + ["mouseleave", "Mouse Exit"], + ["mouseup", "Mouse Up"], + ], + event => event.target.value + ); + } + + if (blurListener) { + element.addEventListener("blur", blurListener); + } + + if (this.data.comb) { + const fieldWidth = this.data.rect[2] - this.data.rect[0]; + const combWidth = fieldWidth / maxLen; + + element.classList.add("comb"); + element.style.letterSpacing = `calc(${combWidth}px * var(--total-scale-factor) - 1ch)`; + } + } else { + element = document.createElement("div"); + element.textContent = this.data.fieldValue; + element.style.verticalAlign = "middle"; + element.style.display = "table-cell"; + + if (this.data.hasOwnCanvas) { + element.hidden = true; + } + } + + this._setTextStyle(element); + this._setBackgroundColor(element); + this._setDefaultPropertiesFromJS(element); + + this.container.append(element); + return this.container; + } +} + +class SignatureWidgetAnnotationElement extends WidgetAnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: !!parameters.data.hasOwnCanvas }); + } +} + +class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: parameters.renderForms }); + } + + render() { + const storage = this.annotationStorage; + const data = this.data; + const id = data.id; + let value = storage.getValue(id, { + value: data.exportValue === data.fieldValue, + }).value; + if (typeof value === "string") { + // The value has been changed through js and set in annotationStorage. + value = value !== "Off"; + storage.setValue(id, { value }); + } + + this.container.classList.add("buttonWidgetAnnotation", "checkBox"); + + const element = document.createElement("input"); + GetElementsByNameSet.add(element); + element.setAttribute("data-element-id", id); + + element.disabled = data.readOnly; + this._setRequired(element, this.data.required); + element.type = "checkbox"; + element.name = data.fieldName; + if (value) { + element.setAttribute("checked", true); + } + element.setAttribute("exportValue", data.exportValue); + element.tabIndex = DEFAULT_TAB_INDEX; + + element.addEventListener("change", event => { + const { name, checked } = event.target; + for (const checkbox of this._getElementsByName(name, /* skipId = */ id)) { + const curChecked = checked && checkbox.exportValue === data.exportValue; + if (checkbox.domElement) { + checkbox.domElement.checked = curChecked; + } + storage.setValue(checkbox.id, { value: curChecked }); + } + storage.setValue(id, { value: checked }); + }); + + element.addEventListener("resetform", event => { + const defaultValue = data.defaultFieldValue || "Off"; + event.target.checked = defaultValue === data.exportValue; + }); + + if (this.enableScripting && this.hasJSActions) { + element.addEventListener("updatefromsandbox", jsEvent => { + const actions = { + value(event) { + event.target.checked = event.detail.value !== "Off"; + storage.setValue(id, { value: event.target.checked }); + }, + }; + this._dispatchEventFromSandbox(actions, jsEvent); + }); + + this._setEventListeners( + element, + null, + [ + ["change", "Validate"], + ["change", "Action"], + ["focus", "Focus"], + ["blur", "Blur"], + ["mousedown", "Mouse Down"], + ["mouseenter", "Mouse Enter"], + ["mouseleave", "Mouse Exit"], + ["mouseup", "Mouse Up"], + ], + event => event.target.checked + ); + } + + this._setBackgroundColor(element); + this._setDefaultPropertiesFromJS(element); + + this.container.append(element); + return this.container; + } +} + +class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: parameters.renderForms }); + } + + render() { + this.container.classList.add("buttonWidgetAnnotation", "radioButton"); + const storage = this.annotationStorage; + const data = this.data; + const id = data.id; + let value = storage.getValue(id, { + value: data.fieldValue === data.buttonValue, + }).value; + if (typeof value === "string") { + // The value has been changed through js and set in annotationStorage. + value = value !== data.buttonValue; + storage.setValue(id, { value }); + } + + if (value) { + // It's possible that multiple radio buttons are checked. + // So if this one is checked we just reset the other ones. + // (see bug 1864136). Then when the other ones will be rendered they will + // unchecked (because of their value in the storage). + // Consequently, the first checked radio button will be the only checked + // one. + for (const radio of this._getElementsByName( + data.fieldName, + /* skipId = */ id + )) { + storage.setValue(radio.id, { value: false }); + } + } + + const element = document.createElement("input"); + GetElementsByNameSet.add(element); + element.setAttribute("data-element-id", id); + + element.disabled = data.readOnly; + this._setRequired(element, this.data.required); + element.type = "radio"; + element.name = data.fieldName; + if (value) { + element.setAttribute("checked", true); + } + element.tabIndex = DEFAULT_TAB_INDEX; + + element.addEventListener("change", event => { + const { name, checked } = event.target; + for (const radio of this._getElementsByName(name, /* skipId = */ id)) { + storage.setValue(radio.id, { value: false }); + } + storage.setValue(id, { value: checked }); + }); + + element.addEventListener("resetform", event => { + const defaultValue = data.defaultFieldValue; + event.target.checked = + defaultValue !== null && + defaultValue !== undefined && + defaultValue === data.buttonValue; + }); + + if (this.enableScripting && this.hasJSActions) { + const pdfButtonValue = data.buttonValue; + element.addEventListener("updatefromsandbox", jsEvent => { + const actions = { + value: event => { + const checked = pdfButtonValue === event.detail.value; + for (const radio of this._getElementsByName(event.target.name)) { + const curChecked = checked && radio.id === id; + if (radio.domElement) { + radio.domElement.checked = curChecked; + } + storage.setValue(radio.id, { value: curChecked }); + } + }, + }; + this._dispatchEventFromSandbox(actions, jsEvent); + }); + + this._setEventListeners( + element, + null, + [ + ["change", "Validate"], + ["change", "Action"], + ["focus", "Focus"], + ["blur", "Blur"], + ["mousedown", "Mouse Down"], + ["mouseenter", "Mouse Enter"], + ["mouseleave", "Mouse Exit"], + ["mouseup", "Mouse Up"], + ], + event => event.target.checked + ); + } + + this._setBackgroundColor(element); + this._setDefaultPropertiesFromJS(element); + + this.container.append(element); + return this.container; + } +} + +class PushButtonWidgetAnnotationElement extends LinkAnnotationElement { + constructor(parameters) { + super(parameters, { ignoreBorder: parameters.data.hasAppearance }); + } + + render() { + // The rendering and functionality of a push button widget annotation is + // equal to that of a link annotation, but may have more functionality, such + // as performing actions on form fields (resetting, submitting, et cetera). + const container = super.render(); + container.classList.add("buttonWidgetAnnotation", "pushButton"); + + const linkElement = container.lastChild; + if (this.enableScripting && this.hasJSActions && linkElement) { + this._setDefaultPropertiesFromJS(linkElement); + + linkElement.addEventListener("updatefromsandbox", jsEvent => { + this._dispatchEventFromSandbox({}, jsEvent); + }); + } + + return container; + } +} + +class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: parameters.renderForms }); + } + + render() { + this.container.classList.add("choiceWidgetAnnotation"); + const storage = this.annotationStorage; + const id = this.data.id; + + const storedData = storage.getValue(id, { + value: this.data.fieldValue, + }); + + const selectElement = document.createElement("select"); + GetElementsByNameSet.add(selectElement); + selectElement.setAttribute("data-element-id", id); + + selectElement.disabled = this.data.readOnly; + this._setRequired(selectElement, this.data.required); + selectElement.name = this.data.fieldName; + selectElement.tabIndex = DEFAULT_TAB_INDEX; + + let addAnEmptyEntry = this.data.combo && this.data.options.length > 0; + + if (!this.data.combo) { + // List boxes have a size and (optionally) multiple selection. + selectElement.size = this.data.options.length; + if (this.data.multiSelect) { + selectElement.multiple = true; + } + } + + selectElement.addEventListener("resetform", event => { + const defaultValue = this.data.defaultFieldValue; + for (const option of selectElement.options) { + option.selected = option.value === defaultValue; + } + }); + + // Insert the options into the choice field. + for (const option of this.data.options) { + const optionElement = document.createElement("option"); + optionElement.textContent = option.displayValue; + optionElement.value = option.exportValue; + if (storedData.value.includes(option.exportValue)) { + optionElement.setAttribute("selected", true); + addAnEmptyEntry = false; + } + selectElement.append(optionElement); + } + + let removeEmptyEntry = null; + if (addAnEmptyEntry) { + const noneOptionElement = document.createElement("option"); + noneOptionElement.value = " "; + noneOptionElement.setAttribute("hidden", true); + noneOptionElement.setAttribute("selected", true); + selectElement.prepend(noneOptionElement); + + removeEmptyEntry = () => { + noneOptionElement.remove(); + selectElement.removeEventListener("input", removeEmptyEntry); + removeEmptyEntry = null; + }; + selectElement.addEventListener("input", removeEmptyEntry); + } + + const getValue = isExport => { + const name = isExport ? "value" : "textContent"; + const { options, multiple } = selectElement; + if (!multiple) { + return options.selectedIndex === -1 + ? null + : options[options.selectedIndex][name]; + } + return Array.prototype.filter + .call(options, option => option.selected) + .map(option => option[name]); + }; + + let selectedValues = getValue(/* isExport */ false); + + const getItems = event => { + const options = event.target.options; + return Array.prototype.map.call(options, option => ({ + displayValue: option.textContent, + exportValue: option.value, + })); + }; + + if (this.enableScripting && this.hasJSActions) { + selectElement.addEventListener("updatefromsandbox", jsEvent => { + const actions = { + value(event) { + removeEmptyEntry?.(); + const value = event.detail.value; + const values = new Set(Array.isArray(value) ? value : [value]); + for (const option of selectElement.options) { + option.selected = values.has(option.value); + } + storage.setValue(id, { + value: getValue(/* isExport */ true), + }); + selectedValues = getValue(/* isExport */ false); + }, + multipleSelection(event) { + selectElement.multiple = true; + }, + remove(event) { + const options = selectElement.options; + const index = event.detail.remove; + options[index].selected = false; + selectElement.remove(index); + if (options.length > 0) { + const i = Array.prototype.findIndex.call( + options, + option => option.selected + ); + if (i === -1) { + options[0].selected = true; + } + } + storage.setValue(id, { + value: getValue(/* isExport */ true), + items: getItems(event), + }); + selectedValues = getValue(/* isExport */ false); + }, + clear(event) { + while (selectElement.length !== 0) { + selectElement.remove(0); + } + storage.setValue(id, { value: null, items: [] }); + selectedValues = getValue(/* isExport */ false); + }, + insert(event) { + const { index, displayValue, exportValue } = event.detail.insert; + const selectChild = selectElement.children[index]; + const optionElement = document.createElement("option"); + optionElement.textContent = displayValue; + optionElement.value = exportValue; + + if (selectChild) { + selectChild.before(optionElement); + } else { + selectElement.append(optionElement); + } + storage.setValue(id, { + value: getValue(/* isExport */ true), + items: getItems(event), + }); + selectedValues = getValue(/* isExport */ false); + }, + items(event) { + const { items } = event.detail; + while (selectElement.length !== 0) { + selectElement.remove(0); + } + for (const item of items) { + const { displayValue, exportValue } = item; + const optionElement = document.createElement("option"); + optionElement.textContent = displayValue; + optionElement.value = exportValue; + selectElement.append(optionElement); + } + if (selectElement.options.length > 0) { + selectElement.options[0].selected = true; + } + storage.setValue(id, { + value: getValue(/* isExport */ true), + items: getItems(event), + }); + selectedValues = getValue(/* isExport */ false); + }, + indices(event) { + const indices = new Set(event.detail.indices); + for (const option of event.target.options) { + option.selected = indices.has(option.index); + } + storage.setValue(id, { + value: getValue(/* isExport */ true), + }); + selectedValues = getValue(/* isExport */ false); + }, + editable(event) { + event.target.disabled = !event.detail.editable; + }, + }; + this._dispatchEventFromSandbox(actions, jsEvent); + }); + + selectElement.addEventListener("input", event => { + const exportValue = getValue(/* isExport */ true); + const change = getValue(/* isExport */ false); + storage.setValue(id, { value: exportValue }); + + event.preventDefault(); + + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id, + name: "Keystroke", + value: selectedValues, + change, + changeEx: exportValue, + willCommit: false, + commitKey: 1, + keyDown: false, + }, + }); + }); + + this._setEventListeners( + selectElement, + null, + [ + ["focus", "Focus"], + ["blur", "Blur"], + ["mousedown", "Mouse Down"], + ["mouseenter", "Mouse Enter"], + ["mouseleave", "Mouse Exit"], + ["mouseup", "Mouse Up"], + ["input", "Action"], + ["input", "Validate"], + ], + event => event.target.value + ); + } else { + selectElement.addEventListener("input", function (event) { + storage.setValue(id, { value: getValue(/* isExport */ true) }); + }); + } + + if (this.data.combo) { + this._setTextStyle(selectElement); + } else { + // Just use the default font size... + // it's a bit hard to guess what is a good size. + } + this._setBackgroundColor(selectElement); + this._setDefaultPropertiesFromJS(selectElement); + + this.container.append(selectElement); + return this.container; + } +} + +class PopupAnnotationElement extends AnnotationElement { + constructor(parameters) { + const { data, elements } = parameters; + super(parameters, { isRenderable: AnnotationElement._hasPopupData(data) }); + this.elements = elements; + this.popup = null; + } + + render() { + this.container.classList.add("popupAnnotation"); + + const popup = (this.popup = new PopupElement({ + container: this.container, + color: this.data.color, + titleObj: this.data.titleObj, + modificationDate: this.data.modificationDate, + contentsObj: this.data.contentsObj, + richText: this.data.richText, + rect: this.data.rect, + parentRect: this.data.parentRect || null, + parent: this.parent, + elements: this.elements, + open: this.data.open, + })); + + const elementIds = []; + for (const element of this.elements) { + element.popup = popup; + element.container.ariaHasPopup = "dialog"; + elementIds.push(element.data.id); + element.addHighlightArea(); + } + + this.container.setAttribute( + "aria-controls", + elementIds.map(id => `${AnnotationPrefix}${id}`).join(",") + ); + + return this.container; + } +} + +class PopupElement { + #boundKeyDown = this.#keyDown.bind(this); + + #boundHide = this.#hide.bind(this); + + #boundShow = this.#show.bind(this); + + #boundToggle = this.#toggle.bind(this); + + #color = null; + + #container = null; + + #contentsObj = null; + + #dateObj = null; + + #elements = null; + + #parent = null; + + #parentRect = null; + + #pinned = false; + + #popup = null; + + #position = null; + + #rect = null; + + #richText = null; + + #titleObj = null; + + #updates = null; + + #wasVisible = false; + + constructor({ + container, + color, + elements, + titleObj, + modificationDate, + contentsObj, + richText, + parent, + rect, + parentRect, + open, + }) { + this.#container = container; + this.#titleObj = titleObj; + this.#contentsObj = contentsObj; + this.#richText = richText; + this.#parent = parent; + this.#color = color; + this.#rect = rect; + this.#parentRect = parentRect; + this.#elements = elements; + + // The modification date is shown in the popup instead of the creation + // date if it is available and can be parsed correctly, which is + // consistent with other viewers such as Adobe Acrobat. + this.#dateObj = PDFDateString.toDateObject(modificationDate); + + this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup()); + // Attach the event listeners to the trigger element. + for (const element of this.trigger) { + element.addEventListener("click", this.#boundToggle); + element.addEventListener("mouseenter", this.#boundShow); + element.addEventListener("mouseleave", this.#boundHide); + element.classList.add("popupTriggerArea"); + } + + // Attach the event listener to toggle the popup with the keyboard. + for (const element of elements) { + element.container?.addEventListener("keydown", this.#boundKeyDown); + } + + this.#container.hidden = true; + if (open) { + this.#toggle(); + } + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { + // Since the popup is lazily created, we need to ensure that it'll be + // created and displayed during reference tests. + this.#parent.popupShow.push(async () => { + if (this.#container.hidden) { + this.#show(); + } + }); + } + } + + render() { + if (this.#popup) { + return; + } + + const popup = (this.#popup = document.createElement("div")); + popup.className = "popup"; + + if (this.#color) { + const baseColor = (popup.style.outlineColor = Util.makeHexColor( + ...this.#color + )); + popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`; + } + + const header = document.createElement("span"); + header.className = "header"; + const title = document.createElement("h1"); + header.append(title); + ({ dir: title.dir, str: title.textContent } = this.#titleObj); + popup.append(header); + + if (this.#dateObj) { + const modificationDate = document.createElement("span"); + modificationDate.classList.add("popupDate"); + modificationDate.setAttribute( + "data-l10n-id", + "pdfjs-annotation-date-time-string" + ); + modificationDate.setAttribute( + "data-l10n-args", + JSON.stringify({ dateObj: this.#dateObj.valueOf() }) + ); + header.append(modificationDate); + } + + const html = this.#html; + if (html) { + XfaLayer.render({ + xfaHtml: html, + intent: "richText", + div: popup, + }); + popup.lastChild.classList.add("richText", "popupContent"); + } else { + const contents = this._formatContents(this.#contentsObj); + popup.append(contents); + } + this.#container.append(popup); + } + + get #html() { + const richText = this.#richText; + const contentsObj = this.#contentsObj; + if ( + richText?.str && + (!contentsObj?.str || contentsObj.str === richText.str) + ) { + return this.#richText.html || null; + } + return null; + } + + get #fontSize() { + return this.#html?.attributes?.style?.fontSize || 0; + } + + get #fontColor() { + return this.#html?.attributes?.style?.color || null; + } + + #makePopupContent(text) { + const popupLines = []; + const popupContent = { + str: text, + html: { + name: "div", + attributes: { + dir: "auto", + }, + children: [ + { + name: "p", + children: popupLines, + }, + ], + }, + }; + const lineAttributes = { + style: { + color: this.#fontColor, + fontSize: this.#fontSize + ? `calc(${this.#fontSize}px * var(--total-scale-factor))` + : "", + }, + }; + for (const line of text.split("\n")) { + popupLines.push({ + name: "span", + value: line, + attributes: lineAttributes, + }); + } + return popupContent; + } + + /** + * Format the contents of the popup by adding newlines where necessary. + * + * @private + * @param {Object} contentsObj + * @memberof PopupElement + * @returns {HTMLParagraphElement} + */ + _formatContents({ str, dir }) { + const p = document.createElement("p"); + p.classList.add("popupContent"); + p.dir = dir; + const lines = str.split(/(?:\r\n?|\n)/); + for (let i = 0, ii = lines.length; i < ii; ++i) { + const line = lines[i]; + p.append(document.createTextNode(line)); + if (i < ii - 1) { + p.append(document.createElement("br")); + } + } + return p; + } + + #keyDown(event) { + if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) { + return; + } + + if (event.key === "Enter" || (event.key === "Escape" && this.#pinned)) { + this.#toggle(); + } + } + + updateEdited({ rect, popupContent }) { + this.#updates ||= { + contentsObj: this.#contentsObj, + richText: this.#richText, + }; + if (rect) { + this.#position = null; + } + if (popupContent) { + this.#richText = this.#makePopupContent(popupContent); + this.#contentsObj = null; + } + this.#popup?.remove(); + this.#popup = null; + } + + resetEdited() { + if (!this.#updates) { + return; + } + ({ contentsObj: this.#contentsObj, richText: this.#richText } = + this.#updates); + this.#updates = null; + this.#popup?.remove(); + this.#popup = null; + this.#position = null; + } + + #setPosition() { + if (this.#position !== null) { + return; + } + const { + page: { view }, + viewport: { + rawDims: { pageWidth, pageHeight, pageX, pageY }, + }, + } = this.#parent; + + let useParentRect = !!this.#parentRect; + let rect = useParentRect ? this.#parentRect : this.#rect; + for (const element of this.#elements) { + if (!rect || Util.intersect(element.data.rect, rect) !== null) { + rect = element.data.rect; + useParentRect = true; + break; + } + } + + const normalizedRect = Util.normalizeRect([ + rect[0], + view[3] - rect[1] + view[1], + rect[2], + view[3] - rect[3] + view[1], + ]); + + const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5; + const parentWidth = useParentRect + ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION + : 0; + const popupLeft = normalizedRect[0] + parentWidth; + const popupTop = normalizedRect[1]; + this.#position = [ + (100 * (popupLeft - pageX)) / pageWidth, + (100 * (popupTop - pageY)) / pageHeight, + ]; + + const { style } = this.#container; + style.left = `${this.#position[0]}%`; + style.top = `${this.#position[1]}%`; + } + + /** + * Toggle the visibility of the popup. + */ + #toggle() { + this.#pinned = !this.#pinned; + if (this.#pinned) { + this.#show(); + this.#container.addEventListener("click", this.#boundToggle); + this.#container.addEventListener("keydown", this.#boundKeyDown); + } else { + this.#hide(); + this.#container.removeEventListener("click", this.#boundToggle); + this.#container.removeEventListener("keydown", this.#boundKeyDown); + } + } + + /** + * Show the popup. + */ + #show() { + if (!this.#popup) { + this.render(); + } + if (!this.isVisible) { + this.#setPosition(); + this.#container.hidden = false; + this.#container.style.zIndex = + parseInt(this.#container.style.zIndex) + 1000; + } else if (this.#pinned) { + this.#container.classList.add("focused"); + } + } + + /** + * Hide the popup. + */ + #hide() { + this.#container.classList.remove("focused"); + if (this.#pinned || !this.isVisible) { + return; + } + this.#container.hidden = true; + this.#container.style.zIndex = + parseInt(this.#container.style.zIndex) - 1000; + } + + forceHide() { + this.#wasVisible = this.isVisible; + if (!this.#wasVisible) { + return; + } + this.#container.hidden = true; + } + + maybeShow() { + if (!this.#wasVisible) { + return; + } + if (!this.#popup) { + this.#show(); + } + this.#wasVisible = false; + this.#container.hidden = false; + } + + get isVisible() { + return this.#container.hidden === false; + } +} + +class FreeTextAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + this.textContent = parameters.data.textContent; + this.textPosition = parameters.data.textPosition; + this.annotationEditorType = AnnotationEditorType.FREETEXT; + } + + render() { + this.container.classList.add("freeTextAnnotation"); + + if (this.textContent) { + const content = document.createElement("div"); + content.classList.add("annotationTextContent"); + content.setAttribute("role", "comment"); + for (const line of this.textContent) { + const lineSpan = document.createElement("span"); + lineSpan.textContent = line; + content.append(lineSpan); + } + this.container.append(content); + } + + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + this._editOnDoubleClick(); + + return this.container; + } +} + +class LineAnnotationElement extends AnnotationElement { + #line = null; + + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + } + + render() { + this.container.classList.add("lineAnnotation"); + + // Create an invisible line with the same starting and ending coordinates + // that acts as the trigger for the popup. Only the line itself should + // trigger the popup, not the entire container. + const { data, width, height } = this; + const svg = this.svgFactory.create( + width, + height, + /* skipDimensions = */ true + ); + + // PDF coordinates are calculated from a bottom left origin, so transform + // the line coordinates to a top left origin for the SVG element. + const line = (this.#line = this.svgFactory.createElement("svg:line")); + line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]); + line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]); + line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]); + line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]); + // Ensure that the 'stroke-width' is always non-zero, since otherwise it + // won't be possible to open/close the popup (note e.g. issue 11122). + line.setAttribute("stroke-width", data.borderStyle.width || 1); + line.setAttribute("stroke", "transparent"); + line.setAttribute("fill", "transparent"); + + svg.append(line); + this.container.append(svg); + + // Create the popup ourselves so that we can bind it to the line instead + // of to the entire container (which is the default). + if (!data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + return this.container; + } + + getElementsToTriggerPopup() { + return this.#line; + } + + addHighlightArea() { + this.container.classList.add("highlightArea"); + } +} + +class SquareAnnotationElement extends AnnotationElement { + #square = null; + + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + } + + render() { + this.container.classList.add("squareAnnotation"); + + // Create an invisible square with the same rectangle that acts as the + // trigger for the popup. Only the square itself should trigger the + // popup, not the entire container. + const { data, width, height } = this; + const svg = this.svgFactory.create( + width, + height, + /* skipDimensions = */ true + ); + + // The browser draws half of the borders inside the square and half of + // the borders outside the square by default. This behavior cannot be + // changed programmatically, so correct for that here. + const borderWidth = data.borderStyle.width; + const square = (this.#square = this.svgFactory.createElement("svg:rect")); + square.setAttribute("x", borderWidth / 2); + square.setAttribute("y", borderWidth / 2); + square.setAttribute("width", width - borderWidth); + square.setAttribute("height", height - borderWidth); + // Ensure that the 'stroke-width' is always non-zero, since otherwise it + // won't be possible to open/close the popup (note e.g. issue 11122). + square.setAttribute("stroke-width", borderWidth || 1); + square.setAttribute("stroke", "transparent"); + square.setAttribute("fill", "transparent"); + + svg.append(square); + this.container.append(svg); + + // Create the popup ourselves so that we can bind it to the square instead + // of to the entire container (which is the default). + if (!data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + return this.container; + } + + getElementsToTriggerPopup() { + return this.#square; + } + + addHighlightArea() { + this.container.classList.add("highlightArea"); + } +} + +class CircleAnnotationElement extends AnnotationElement { + #circle = null; + + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + } + + render() { + this.container.classList.add("circleAnnotation"); + + // Create an invisible circle with the same ellipse that acts as the + // trigger for the popup. Only the circle itself should trigger the + // popup, not the entire container. + const { data, width, height } = this; + const svg = this.svgFactory.create( + width, + height, + /* skipDimensions = */ true + ); + + // The browser draws half of the borders inside the circle and half of + // the borders outside the circle by default. This behavior cannot be + // changed programmatically, so correct for that here. + const borderWidth = data.borderStyle.width; + const circle = (this.#circle = + this.svgFactory.createElement("svg:ellipse")); + circle.setAttribute("cx", width / 2); + circle.setAttribute("cy", height / 2); + circle.setAttribute("rx", width / 2 - borderWidth / 2); + circle.setAttribute("ry", height / 2 - borderWidth / 2); + // Ensure that the 'stroke-width' is always non-zero, since otherwise it + // won't be possible to open/close the popup (note e.g. issue 11122). + circle.setAttribute("stroke-width", borderWidth || 1); + circle.setAttribute("stroke", "transparent"); + circle.setAttribute("fill", "transparent"); + + svg.append(circle); + this.container.append(svg); + + // Create the popup ourselves so that we can bind it to the circle instead + // of to the entire container (which is the default). + if (!data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + return this.container; + } + + getElementsToTriggerPopup() { + return this.#circle; + } + + addHighlightArea() { + this.container.classList.add("highlightArea"); + } +} + +class PolylineAnnotationElement extends AnnotationElement { + #polyline = null; + + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + + this.containerClassName = "polylineAnnotation"; + this.svgElementName = "svg:polyline"; + } + + render() { + this.container.classList.add(this.containerClassName); + + // Create an invisible polyline with the same points that acts as the + // trigger for the popup. Only the polyline itself should trigger the + // popup, not the entire container. + const { + data: { rect, vertices, borderStyle, popupRef }, + width, + height, + } = this; + if (!vertices) { + return this.container; + } + const svg = this.svgFactory.create( + width, + height, + /* skipDimensions = */ true + ); + + // Convert the vertices array to a single points string that the SVG + // polyline element expects ("x1,y1 x2,y2 ..."). PDF coordinates are + // calculated from a bottom left origin, so transform the polyline + // coordinates to a top left origin for the SVG element. + let points = []; + for (let i = 0, ii = vertices.length; i < ii; i += 2) { + const x = vertices[i] - rect[0]; + const y = rect[3] - vertices[i + 1]; + points.push(`${x},${y}`); + } + points = points.join(" "); + + const polyline = (this.#polyline = this.svgFactory.createElement( + this.svgElementName + )); + polyline.setAttribute("points", points); + // Ensure that the 'stroke-width' is always non-zero, since otherwise it + // won't be possible to open/close the popup (note e.g. issue 11122). + polyline.setAttribute("stroke-width", borderStyle.width || 1); + polyline.setAttribute("stroke", "transparent"); + polyline.setAttribute("fill", "transparent"); + + svg.append(polyline); + this.container.append(svg); + + // Create the popup ourselves so that we can bind it to the polyline + // instead of to the entire container (which is the default). + if (!popupRef && this.hasPopupData) { + this._createPopup(); + } + + return this.container; + } + + getElementsToTriggerPopup() { + return this.#polyline; + } + + addHighlightArea() { + this.container.classList.add("highlightArea"); + } +} + +class PolygonAnnotationElement extends PolylineAnnotationElement { + constructor(parameters) { + // Polygons are specific forms of polylines, so reuse their logic. + super(parameters); + + this.containerClassName = "polygonAnnotation"; + this.svgElementName = "svg:polygon"; + } +} + +class CaretAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + } + + render() { + this.container.classList.add("caretAnnotation"); + + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + return this.container; + } +} + +class InkAnnotationElement extends AnnotationElement { + #polylinesGroupElement = null; + + #polylines = []; + + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + + this.containerClassName = "inkAnnotation"; + + // Use the polyline SVG element since it allows us to use coordinates + // directly and to draw both straight lines and curves. + this.svgElementName = "svg:polyline"; + + this.annotationEditorType = + this.data.it === "InkHighlight" + ? AnnotationEditorType.HIGHLIGHT + : AnnotationEditorType.INK; + } + + #getTransform(rotation, rect) { + // PDF coordinates are calculated from a bottom left origin, so + // transform the polyline coordinates to a top left origin for the + // SVG element. + switch (rotation) { + case 90: + return { + transform: `rotate(90) translate(${-rect[0]},${rect[1]}) scale(1,-1)`, + width: rect[3] - rect[1], + height: rect[2] - rect[0], + }; + case 180: + return { + transform: `rotate(180) translate(${-rect[2]},${rect[1]}) scale(1,-1)`, + width: rect[2] - rect[0], + height: rect[3] - rect[1], + }; + case 270: + return { + transform: `rotate(270) translate(${-rect[2]},${rect[3]}) scale(1,-1)`, + width: rect[3] - rect[1], + height: rect[2] - rect[0], + }; + default: + return { + transform: `translate(${-rect[0]},${rect[3]}) scale(1,-1)`, + width: rect[2] - rect[0], + height: rect[3] - rect[1], + }; + } + } + + render() { + this.container.classList.add(this.containerClassName); + + // Create an invisible polyline with the same points that acts as the + // trigger for the popup. + const { + data: { rect, rotation, inkLists, borderStyle, popupRef }, + } = this; + const { transform, width, height } = this.#getTransform(rotation, rect); + + const svg = this.svgFactory.create( + width, + height, + /* skipDimensions = */ true + ); + const g = (this.#polylinesGroupElement = + this.svgFactory.createElement("svg:g")); + svg.append(g); + // Ensure that the 'stroke-width' is always non-zero, since otherwise it + // won't be possible to open/close the popup (note e.g. issue 11122). + g.setAttribute("stroke-width", borderStyle.width || 1); + g.setAttribute("stroke-linecap", "round"); + g.setAttribute("stroke-linejoin", "round"); + g.setAttribute("stroke-miterlimit", 10); + g.setAttribute("stroke", "transparent"); + g.setAttribute("fill", "transparent"); + g.setAttribute("transform", transform); + + for (let i = 0, ii = inkLists.length; i < ii; i++) { + const polyline = this.svgFactory.createElement(this.svgElementName); + this.#polylines.push(polyline); + polyline.setAttribute("points", inkLists[i].join(",")); + g.append(polyline); + } + + if (!popupRef && this.hasPopupData) { + this._createPopup(); + } + + this.container.append(svg); + this._editOnDoubleClick(); + + return this.container; + } + + updateEdited(params) { + super.updateEdited(params); + const { thickness, points, rect } = params; + const g = this.#polylinesGroupElement; + if (thickness >= 0) { + g.setAttribute("stroke-width", thickness || 1); + } + if (points) { + for (let i = 0, ii = this.#polylines.length; i < ii; i++) { + this.#polylines[i].setAttribute("points", points[i].join(",")); + } + } + if (rect) { + const { transform, width, height } = this.#getTransform( + this.data.rotation, + rect + ); + const root = g.parentElement; + root.setAttribute("viewBox", `0 0 ${width} ${height}`); + g.setAttribute("transform", transform); + } + } + + getElementsToTriggerPopup() { + return this.#polylines; + } + + addHighlightArea() { + this.container.classList.add("highlightArea"); + } +} + +class HighlightAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { + isRenderable: true, + ignoreBorder: true, + createQuadrilaterals: true, + }); + this.annotationEditorType = AnnotationEditorType.HIGHLIGHT; + } + + render() { + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + this.container.classList.add("highlightAnnotation"); + this._editOnDoubleClick(); + + return this.container; + } +} + +class UnderlineAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { + isRenderable: true, + ignoreBorder: true, + createQuadrilaterals: true, + }); + } + + render() { + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + this.container.classList.add("underlineAnnotation"); + return this.container; + } +} + +class SquigglyAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { + isRenderable: true, + ignoreBorder: true, + createQuadrilaterals: true, + }); + } + + render() { + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + this.container.classList.add("squigglyAnnotation"); + return this.container; + } +} + +class StrikeOutAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { + isRenderable: true, + ignoreBorder: true, + createQuadrilaterals: true, + }); + } + + render() { + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + + this.container.classList.add("strikeoutAnnotation"); + return this.container; + } +} + +class StampAnnotationElement extends AnnotationElement { + constructor(parameters) { + super(parameters, { isRenderable: true, ignoreBorder: true }); + this.annotationEditorType = AnnotationEditorType.STAMP; + } + + render() { + this.container.classList.add("stampAnnotation"); + this.container.setAttribute("role", "img"); + + if (!this.data.popupRef && this.hasPopupData) { + this._createPopup(); + } + this._editOnDoubleClick(); + + return this.container; + } +} + +class FileAttachmentAnnotationElement extends AnnotationElement { + #trigger = null; + + constructor(parameters) { + super(parameters, { isRenderable: true }); + + const { file } = this.data; + this.filename = file.filename; + this.content = file.content; + + this.linkService.eventBus?.dispatch("fileattachmentannotation", { + source: this, + ...file, + }); + } + + render() { + this.container.classList.add("fileAttachmentAnnotation"); + + const { container, data } = this; + let trigger; + if (data.hasAppearance || data.fillAlpha === 0) { + trigger = document.createElement("div"); + } else { + // Unfortunately it seems that it's not clearly specified exactly what + // names are actually valid, since Table 184 contains: + // Conforming readers shall provide predefined icon appearances for at + // least the following standard names: GraphPushPin, PaperclipTag. + // Additional names may be supported as well. Default value: PushPin. + trigger = document.createElement("img"); + trigger.src = `${this.imageResourcesPath}annotation-${ + /paperclip/i.test(data.name) ? "paperclip" : "pushpin" + }.svg`; + + if (data.fillAlpha && data.fillAlpha < 1) { + trigger.style = `filter: opacity(${Math.round( + data.fillAlpha * 100 + )}%);`; + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { + this.container.classList.add("hasFillAlpha"); + } + } + } + trigger.addEventListener("dblclick", this.#download.bind(this)); + this.#trigger = trigger; + + const { isMac } = FeatureTest.platform; + container.addEventListener("keydown", evt => { + if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) { + this.#download(); + } + }); + + if (!data.popupRef && this.hasPopupData) { + this._createPopup(); + } else { + trigger.classList.add("popupTriggerArea"); + } + + container.append(trigger); + return container; + } + + getElementsToTriggerPopup() { + return this.#trigger; + } + + addHighlightArea() { + this.container.classList.add("highlightArea"); + } + + /** + * Download the file attachment associated with this annotation. + */ + #download() { + this.downloadManager?.openOrDownloadData(this.content, this.filename); + } +} + +/** + * @typedef {Object} AnnotationLayerParameters + * @property {PageViewport} viewport + * @property {HTMLDivElement} div + * @property {Array} annotations + * @property {PDFPageProxy} page + * @property {IPDFLinkService} linkService + * @property {IDownloadManager} [downloadManager] + * @property {AnnotationStorage} [annotationStorage] + * @property {string} [imageResourcesPath] - Path for image resources, mainly + * for annotation icons. Include trailing slash. + * @property {boolean} renderForms + * @property {boolean} [enableScripting] - Enable embedded script execution. + * @property {boolean} [hasJSActions] - Some fields have JS actions. + * The default value is `false`. + * @property {Object> | null} [fieldObjects] + * @property {Map} [annotationCanvasMap] + * @property {TextAccessibilityManager} [accessibilityManager] + * @property {AnnotationEditorUIManager} [annotationEditorUIManager] + * @property {StructTreeLayerBuilder} [structTreeLayer] + */ + +/** + * Manage the layer containing all the annotations. + */ +class AnnotationLayer { + #accessibilityManager = null; + + #annotationCanvasMap = null; + + #editableAnnotations = new Map(); + + #structTreeLayer = null; + + constructor({ + div, + accessibilityManager, + annotationCanvasMap, + annotationEditorUIManager, + page, + viewport, + structTreeLayer, + }) { + this.div = div; + this.#accessibilityManager = accessibilityManager; + this.#annotationCanvasMap = annotationCanvasMap; + this.#structTreeLayer = structTreeLayer || null; + this.page = page; + this.viewport = viewport; + this.zIndex = 0; + this._annotationEditorUIManager = annotationEditorUIManager; + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { + // For testing purposes. + Object.defineProperty(this, "showPopups", { + value: async () => { + for (const show of this.popupShow) { + await show(); + } + }, + }); + this.popupShow = []; + } + } + + hasEditableAnnotations() { + return this.#editableAnnotations.size > 0; + } + + async #appendElement(element, id) { + const contentElement = element.firstChild || element; + const annotationId = (contentElement.id = `${AnnotationPrefix}${id}`); + const ariaAttributes = + await this.#structTreeLayer?.getAriaAttributes(annotationId); + if (ariaAttributes) { + for (const [key, value] of ariaAttributes) { + contentElement.setAttribute(key, value); + } + } + + this.div.append(element); + this.#accessibilityManager?.moveElementInDOM( + this.div, + element, + contentElement, + /* isRemovable = */ false + ); + } + + /** + * Render a new annotation layer with all annotation elements. + * + * @param {AnnotationLayerParameters} params + * @memberof AnnotationLayer + */ + async render(params) { + const { annotations } = params; + const layer = this.div; + setLayerDimensions(layer, this.viewport); + + const popupToElements = new Map(); + const elementParams = { + data: null, + layer, + linkService: params.linkService, + downloadManager: params.downloadManager, + imageResourcesPath: params.imageResourcesPath || "", + renderForms: params.renderForms !== false, + svgFactory: new DOMSVGFactory(), + annotationStorage: params.annotationStorage || new AnnotationStorage(), + enableScripting: params.enableScripting === true, + hasJSActions: params.hasJSActions, + fieldObjects: params.fieldObjects, + parent: this, + elements: null, + }; + + for (const data of annotations) { + if (data.noHTML) { + continue; + } + const isPopupAnnotation = data.annotationType === AnnotationType.POPUP; + if (!isPopupAnnotation) { + if (data.rect[2] === data.rect[0] || data.rect[3] === data.rect[1]) { + continue; // Ignore empty annotations. + } + } else { + const elements = popupToElements.get(data.id); + if (!elements) { + // Ignore popup annotations without a corresponding annotation. + continue; + } + elementParams.elements = elements; + } + elementParams.data = data; + const element = AnnotationElementFactory.create(elementParams); + + if (!element.isRenderable) { + continue; + } + + if (!isPopupAnnotation && data.popupRef) { + const elements = popupToElements.get(data.popupRef); + if (!elements) { + popupToElements.set(data.popupRef, [element]); + } else { + elements.push(element); + } + } + + const rendered = element.render(); + if (data.hidden) { + rendered.style.visibility = "hidden"; + } + await this.#appendElement(rendered, data.id); + + if (element._isEditable) { + this.#editableAnnotations.set(element.data.id, element); + this._annotationEditorUIManager?.renderAnnotationElement(element); + } + } + + this.#setAnnotationCanvasMap(); + } + + /** + * Add link annotations to the annotation layer. + * + * @param {Array} annotations + * @param {IPDFLinkService} linkService + * @memberof AnnotationLayer + */ + async addLinkAnnotations(annotations, linkService) { + const elementParams = { + data: null, + layer: this.div, + linkService, + svgFactory: new DOMSVGFactory(), + parent: this, + }; + for (const data of annotations) { + data.borderStyle ||= AnnotationLayer._defaultBorderStyle; + + elementParams.data = data; + const element = AnnotationElementFactory.create(elementParams); + + if (!element.isRenderable) { + continue; + } + const rendered = element.render(); + await this.#appendElement(rendered, data.id); + } + } + + /** + * Update the annotation elements on existing annotation layer. + * + * @param {AnnotationLayerParameters} viewport + * @memberof AnnotationLayer + */ + update({ viewport }) { + const layer = this.div; + this.viewport = viewport; + setLayerDimensions(layer, { rotation: viewport.rotation }); + + this.#setAnnotationCanvasMap(); + layer.hidden = false; + } + + #setAnnotationCanvasMap() { + if (!this.#annotationCanvasMap) { + return; + } + const layer = this.div; + for (const [id, canvas] of this.#annotationCanvasMap) { + const element = layer.querySelector(`[data-annotation-id="${id}"]`); + if (!element) { + continue; + } + + canvas.className = "annotationContent"; + const { firstChild } = element; + if (!firstChild) { + element.append(canvas); + } else if (firstChild.nodeName === "CANVAS") { + firstChild.replaceWith(canvas); + } else if (!firstChild.classList.contains("annotationContent")) { + firstChild.before(canvas); + } else { + firstChild.after(canvas); + } + + const editableAnnotation = this.#editableAnnotations.get(id); + if (!editableAnnotation) { + continue; + } + if (editableAnnotation._hasNoCanvas) { + // The canvas wasn't available when the annotation was created. + this._annotationEditorUIManager?.setMissingCanvas( + id, + element.id, + canvas + ); + editableAnnotation._hasNoCanvas = false; + } else { + editableAnnotation.canvas = canvas; + } + } + this.#annotationCanvasMap.clear(); + } + + getEditableAnnotations() { + return Array.from(this.#editableAnnotations.values()); + } + + getEditableAnnotation(id) { + return this.#editableAnnotations.get(id); + } + + /** + * @private + */ + static get _defaultBorderStyle() { + return shadow( + this, + "_defaultBorderStyle", + Object.freeze({ + width: 1, + rawWidth: 1, + style: AnnotationBorderStyleType.SOLID, + dashArray: [3], + horizontalCornerRadius: 0, + verticalCornerRadius: 0, + }) + ); + } +} + +export { + AnnotationLayer, + FreeTextAnnotationElement, + HighlightAnnotationElement, + InkAnnotationElement, + StampAnnotationElement, +}; diff --git a/frontend/public/pdf.js/src/display/annotation_storage.js b/frontend/public/pdf.js/src/display/annotation_storage.js new file mode 100644 index 0000000..9999f3b --- /dev/null +++ b/frontend/public/pdf.js/src/display/annotation_storage.js @@ -0,0 +1,324 @@ +/* Copyright 2020 Mozilla Foundation + * + * 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 { objectFromMap, shadow, unreachable } from "../shared/util.js"; +import { AnnotationEditor } from "./editor/editor.js"; +import { MurmurHash3_64 } from "../shared/murmurhash3.js"; + +const SerializableEmpty = Object.freeze({ + map: null, + hash: "", + transfer: undefined, +}); + +/** + * Key/value storage for annotation data in forms. + */ +class AnnotationStorage { + #modified = false; + + #modifiedIds = null; + + #storage = new Map(); + + constructor() { + // Callbacks to signal when the modification state is set or reset. + // This is used by the viewer to only bind on `beforeunload` if forms + // are actually edited to prevent doing so unconditionally since that + // can have undesirable effects. + this.onSetModified = null; + this.onResetModified = null; + this.onAnnotationEditor = null; + } + + /** + * Get the value for a given key if it exists, or return the default value. + * @param {string} key + * @param {Object} defaultValue + * @returns {Object} + */ + getValue(key, defaultValue) { + const value = this.#storage.get(key); + if (value === undefined) { + return defaultValue; + } + + return Object.assign(defaultValue, value); + } + + /** + * Get the value for a given key. + * @param {string} key + * @returns {Object} + */ + getRawValue(key) { + return this.#storage.get(key); + } + + /** + * Remove a value from the storage. + * @param {string} key + */ + remove(key) { + this.#storage.delete(key); + + if (this.#storage.size === 0) { + this.resetModified(); + } + + if (typeof this.onAnnotationEditor === "function") { + for (const value of this.#storage.values()) { + if (value instanceof AnnotationEditor) { + return; + } + } + this.onAnnotationEditor(null); + } + } + + /** + * Set the value for a given key + * @param {string} key + * @param {Object} value + */ + setValue(key, value) { + const obj = this.#storage.get(key); + let modified = false; + if (obj !== undefined) { + for (const [entry, val] of Object.entries(value)) { + if (obj[entry] !== val) { + modified = true; + obj[entry] = val; + } + } + } else { + modified = true; + this.#storage.set(key, value); + } + if (modified) { + this.#setModified(); + } + + if ( + value instanceof AnnotationEditor && + typeof this.onAnnotationEditor === "function" + ) { + this.onAnnotationEditor(value.constructor._type); + } + } + + /** + * Check if the storage contains the given key. + * @param {string} key + * @returns {boolean} + */ + has(key) { + return this.#storage.has(key); + } + + /** + * @returns {Object | null} + */ + getAll() { + return this.#storage.size > 0 ? objectFromMap(this.#storage) : null; + } + + /** + * @param {Object} obj + */ + setAll(obj) { + for (const [key, val] of Object.entries(obj)) { + this.setValue(key, val); + } + } + + get size() { + return this.#storage.size; + } + + #setModified() { + if (!this.#modified) { + this.#modified = true; + if (typeof this.onSetModified === "function") { + this.onSetModified(); + } + } + } + + resetModified() { + if (this.#modified) { + this.#modified = false; + if (typeof this.onResetModified === "function") { + this.onResetModified(); + } + } + } + + /** + * @returns {PrintAnnotationStorage} + */ + get print() { + return new PrintAnnotationStorage(this); + } + + /** + * PLEASE NOTE: Only intended for usage within the API itself. + * @ignore + */ + get serializable() { + if (this.#storage.size === 0) { + return SerializableEmpty; + } + const map = new Map(), + hash = new MurmurHash3_64(), + transfer = []; + const context = Object.create(null); + let hasBitmap = false; + + for (const [key, val] of this.#storage) { + const serialized = + val instanceof AnnotationEditor + ? val.serialize(/* isForCopying = */ false, context) + : val; + if (serialized) { + map.set(key, serialized); + + hash.update(`${key}:${JSON.stringify(serialized)}`); + hasBitmap ||= !!serialized.bitmap; + } + } + + if (hasBitmap) { + // We must transfer the bitmap data separately, since it can be changed + // during serialization with SVG images. + for (const value of map.values()) { + if (value.bitmap) { + transfer.push(value.bitmap); + } + } + } + + return map.size > 0 + ? { map, hash: hash.hexdigest(), transfer } + : SerializableEmpty; + } + + get editorStats() { + let stats = null; + const typeToEditor = new Map(); + for (const value of this.#storage.values()) { + if (!(value instanceof AnnotationEditor)) { + continue; + } + const editorStats = value.telemetryFinalData; + if (!editorStats) { + continue; + } + const { type } = editorStats; + if (!typeToEditor.has(type)) { + typeToEditor.set(type, Object.getPrototypeOf(value).constructor); + } + stats ||= Object.create(null); + const map = (stats[type] ||= new Map()); + for (const [key, val] of Object.entries(editorStats)) { + if (key === "type") { + continue; + } + let counters = map.get(key); + if (!counters) { + counters = new Map(); + map.set(key, counters); + } + const count = counters.get(val) ?? 0; + counters.set(val, count + 1); + } + } + for (const [type, editor] of typeToEditor) { + stats[type] = editor.computeTelemetryFinalData(stats[type]); + } + return stats; + } + + resetModifiedIds() { + this.#modifiedIds = null; + } + + /** + * @returns {{ids: Set, hash: string}} + */ + get modifiedIds() { + if (this.#modifiedIds) { + return this.#modifiedIds; + } + const ids = []; + for (const value of this.#storage.values()) { + if ( + !(value instanceof AnnotationEditor) || + !value.annotationElementId || + !value.serialize() + ) { + continue; + } + ids.push(value.annotationElementId); + } + return (this.#modifiedIds = { + ids: new Set(ids), + hash: ids.join(","), + }); + } +} + +/** + * A special `AnnotationStorage` for use during printing, where the serializable + * data is *frozen* upon initialization, to prevent scripting from modifying its + * contents. (Necessary since printing is triggered synchronously in browsers.) + */ +class PrintAnnotationStorage extends AnnotationStorage { + #serializable; + + constructor(parent) { + super(); + const { map, hash, transfer } = parent.serializable; + // Create a *copy* of the data, since Objects are passed by reference in JS. + const clone = structuredClone(map, transfer ? { transfer } : null); + + this.#serializable = { map: clone, hash, transfer }; + } + + /** + * @returns {PrintAnnotationStorage} + */ + // eslint-disable-next-line getter-return + get print() { + unreachable("Should not call PrintAnnotationStorage.print"); + } + + /** + * PLEASE NOTE: Only intended for usage within the API itself. + * @ignore + */ + get serializable() { + return this.#serializable; + } + + get modifiedIds() { + return shadow(this, "modifiedIds", { + ids: new Set(), + hash: "", + }); + } +} + +export { AnnotationStorage, PrintAnnotationStorage, SerializableEmpty }; diff --git a/frontend/public/pdf.js/src/display/api.js b/frontend/public/pdf.js/src/display/api.js new file mode 100644 index 0000000..944a9b7 --- /dev/null +++ b/frontend/public/pdf.js/src/display/api.js @@ -0,0 +1,3490 @@ +/* Copyright 2012 Mozilla Foundation + * + * 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. + */ + +/** + * @module pdfjsLib + */ + +import { + AbortException, + AnnotationMode, + assert, + FeatureTest, + getVerbosityLevel, + info, + isNodeJS, + RenderingIntentFlag, + setVerbosityLevel, + shadow, + stringToBytes, + unreachable, + warn, +} from "../shared/util.js"; +import { + AnnotationStorage, + PrintAnnotationStorage, + SerializableEmpty, +} from "./annotation_storage.js"; +import { FontFaceObject, FontLoader } from "./font_loader.js"; +import { + isDataScheme, + isValidFetchUrl, + PageViewport, + RenderingCancelledException, + StatTimer, +} from "./display_utils.js"; +import { MessageHandler, wrapReason } from "../shared/message_handler.js"; +import { + NodeCanvasFactory, + NodeCMapReaderFactory, + NodeFilterFactory, + NodeStandardFontDataFactory, + NodeWasmFactory, +} from "display-node_utils"; +import { CanvasGraphics } from "./canvas.js"; +import { DOMCanvasFactory } from "./canvas_factory.js"; +import { DOMCMapReaderFactory } from "display-cmap_reader_factory"; +import { DOMFilterFactory } from "./filter_factory.js"; +import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory"; +import { DOMWasmFactory } from "display-wasm_factory"; +import { GlobalWorkerOptions } from "./worker_options.js"; +import { Metadata } from "./metadata.js"; +import { OptionalContentConfig } from "./optional_content_config.js"; +import { PDFDataTransportStream } from "./transport_stream.js"; +import { PDFFetchStream } from "display-fetch_stream"; +import { PDFNetworkStream } from "display-network"; +import { PDFNodeStream } from "display-node_stream"; +import { TextLayer } from "./text_layer.js"; +import { XfaText } from "./xfa_text.js"; + +const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536 +const RENDERING_CANCELLED_TIMEOUT = 100; // ms + +/** + * @typedef { Int8Array | Uint8Array | Uint8ClampedArray | + * Int16Array | Uint16Array | + * Int32Array | Uint32Array | Float32Array | + * Float64Array + * } TypedArray + */ + +/** + * @typedef {Object} RefProxy + * @property {number} num + * @property {number} gen + */ + +/** + * Document initialization / loading parameters object. + * + * @typedef {Object} DocumentInitParameters + * @property {string | URL} [url] - The URL of the PDF. + * @property {TypedArray | ArrayBuffer | Array | string} [data] - + * Binary PDF data. + * Use TypedArrays (Uint8Array) to improve the memory usage. If PDF data is + * BASE64-encoded, use `atob()` to convert it to a binary string first. + * + * NOTE: If TypedArrays are used they will generally be transferred to the + * worker-thread. This will help reduce main-thread memory usage, however + * it will take ownership of the TypedArrays. + * @property {Object} [httpHeaders] - Basic authentication headers. + * @property {boolean} [withCredentials] - Indicates whether or not + * cross-site Access-Control requests should be made using credentials such + * as cookies or authorization headers. The default is `false`. + * @property {string} [password] - For decrypting password-protected PDFs. + * @property {number} [length] - The PDF file length. It's used for progress + * reports and range requests operations. + * @property {PDFDataRangeTransport} [range] - Allows for using a custom range + * transport implementation. + * @property {number} [rangeChunkSize] - Specify maximum number of bytes fetched + * per range request. The default value is {@link DEFAULT_RANGE_CHUNK_SIZE}. + * @property {PDFWorker} [worker] - The worker that will be used for loading and + * parsing the PDF data. + * @property {number} [verbosity] - Controls the logging level; the constants + * from {@link VerbosityLevel} should be used. + * @property {string} [docBaseUrl] - The base URL of the document, used when + * attempting to recover valid absolute URLs for annotations, and outline + * items, that (incorrectly) only specify relative URLs. + * @property {string} [cMapUrl] - The URL where the predefined Adobe CMaps are + * located. Include the trailing slash. + * @property {boolean} [cMapPacked] - Specifies if the Adobe CMaps are binary + * packed or not. The default value is `true`. + * @property {Object} [CMapReaderFactory] - The factory that will be used when + * reading built-in CMap files. + * The default value is {DOMCMapReaderFactory}. + * @property {boolean} [useSystemFonts] - When `true`, fonts that aren't + * embedded in the PDF document will fallback to a system font. + * The default value is `true` in web environments and `false` in Node.js; + * unless `disableFontFace === true` in which case this defaults to `false` + * regardless of the environment (to prevent completely broken fonts). + * @property {string} [standardFontDataUrl] - The URL where the standard font + * files are located. Include the trailing slash. + * @property {Object} [StandardFontDataFactory] - The factory that will be used + * when reading the standard font files. + * The default value is {DOMStandardFontDataFactory}. + * @property {string} [wasmUrl] - The URL where the wasm files are located. + * Include the trailing slash. + * @property {Object} [WasmFactory] - The factory that will be used + * when reading the wasm files. + * The default value is {DOMWasmFactory}. + * @property {boolean} [useWorkerFetch] - Enable using the Fetch API in the + * worker-thread when reading CMap and standard font files. When `true`, + * the `CMapReaderFactory`, `StandardFontDataFactory`, and `WasmFactory` + * options are ignored. + * The default value is `true` in web environments and `false` in Node.js. + * @property {boolean} [stopAtErrors] - Reject certain promises, e.g. + * `getOperatorList`, `getTextContent`, and `RenderTask`, when the associated + * PDF data cannot be successfully parsed, instead of attempting to recover + * whatever possible of the data. The default value is `false`. + * @property {number} [maxImageSize] - The maximum allowed image size in total + * pixels, i.e. width * height. Images above this value will not be rendered. + * Use -1 for no limit, which is also the default value. + * @property {boolean} [isEvalSupported] - Determines if we can evaluate strings + * as JavaScript. Primarily used to improve performance of PDF functions. + * The default value is `true`. + * @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use + * `OffscreenCanvas` in the worker. Primarily used to improve performance of + * image conversion/rendering. + * The default value is `true` in web environments and `false` in Node.js. + * @property {boolean} [isImageDecoderSupported] - Determines if we can use + * `ImageDecoder` in the worker. Primarily used to improve performance of + * image conversion/rendering. + * The default value is `true` in web environments and `false` in Node.js. + * + * NOTE: Also temporarily disabled in Chromium browsers, until we no longer + * support the affected browser versions, because of various bugs: + * + * - Crashes when using the BMP decoder with huge images, e.g. issue6741.pdf; + * see https://issues.chromium.org/issues/374807001 + * + * - Broken images when using the JPEG decoder with images that have custom + * colour profiles, e.g. GitHub discussion 19030; + * see https://issues.chromium.org/issues/378869810 + * + * @property {number} [canvasMaxAreaInBytes] - The integer value is used to + * know when an image must be resized (uses `OffscreenCanvas` in the worker). + * If it's -1 then a possibly slow algorithm is used to guess the max value. + * @property {boolean} [disableFontFace] - By default fonts are converted to + * OpenType fonts and loaded via the Font Loading API or `@font-face` rules. + * If disabled, fonts will be rendered using a built-in font renderer that + * constructs the glyphs with primitive path commands. + * The default value is `false` in web environments and `true` in Node.js. + * @property {boolean} [fontExtraProperties] - Include additional properties, + * which are unused during rendering of PDF documents, when exporting the + * parsed font data from the worker-thread. This may be useful for debugging + * purposes (and backwards compatibility), but note that it will lead to + * increased memory usage. The default value is `false`. + * @property {boolean} [enableXfa] - Render Xfa forms if any. + * The default value is `false`. + * @property {HTMLDocument} [ownerDocument] - Specify an explicit document + * context to create elements with and to load resources, such as fonts, + * into. Defaults to the current document. + * @property {boolean} [disableRange] - Disable range request loading of PDF + * files. When enabled, and if the server supports partial content requests, + * then the PDF will be fetched in chunks. The default value is `false`. + * @property {boolean} [disableStream] - Disable streaming of PDF file data. + * By default PDF.js attempts to load PDF files in chunks. The default value + * is `false`. + * @property {boolean} [disableAutoFetch] - Disable pre-fetching of PDF file + * data. When range requests are enabled PDF.js will automatically keep + * fetching more data even if it isn't needed to display the current page. + * The default value is `false`. + * + * NOTE: It is also necessary to disable streaming, see above, in order for + * disabling of pre-fetching to work correctly. + * @property {boolean} [pdfBug] - Enables special hooks for debugging PDF.js + * (see `web/debugger.js`). The default value is `false`. + * @property {Object} [CanvasFactory] - The factory that will be used when + * creating canvases. The default value is {DOMCanvasFactory}. + * @property {Object} [FilterFactory] - The factory that will be used to + * create SVG filters when rendering some images on the main canvas. + * The default value is {DOMFilterFactory}. + * @property {boolean} [enableHWA] - Enables hardware acceleration for + * rendering. The default value is `false`. + */ + +/** + * This is the main entry point for loading a PDF and interacting with it. + * + * NOTE: If a URL is used to fetch the PDF data a standard Fetch API call (or + * XHR as fallback) is used, which means it must follow same origin rules, + * e.g. no cross-domain requests without CORS. + * + * @param {string | URL | TypedArray | ArrayBuffer | DocumentInitParameters} + * src - Can be a URL where a PDF file is located, a typed array (Uint8Array) + * already populated with data, or a parameter object. + * @returns {PDFDocumentLoadingTask} + */ +function getDocument(src = {}) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + if (typeof src === "string" || src instanceof URL) { + src = { url: src }; + } else if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) { + src = { data: src }; + } + } + const task = new PDFDocumentLoadingTask(); + const { docId } = task; + + const url = src.url ? getUrlProp(src.url) : null; + const data = src.data ? getDataProp(src.data) : null; + const httpHeaders = src.httpHeaders || null; + const withCredentials = src.withCredentials === true; + const password = src.password ?? null; + const rangeTransport = + src.range instanceof PDFDataRangeTransport ? src.range : null; + const rangeChunkSize = + Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 + ? src.rangeChunkSize + : DEFAULT_RANGE_CHUNK_SIZE; + let worker = src.worker instanceof PDFWorker ? src.worker : null; + const verbosity = src.verbosity; + // Ignore "data:"-URLs, since they can't be used to recover valid absolute + // URLs anyway. We want to avoid sending them to the worker-thread, since + // they contain the *entire* PDF document and can thus be arbitrarily long. + const docBaseUrl = + typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) + ? src.docBaseUrl + : null; + const cMapUrl = getFactoryUrlProp(src.cMapUrl); + const cMapPacked = src.cMapPacked !== false; + const CMapReaderFactory = + src.CMapReaderFactory || + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS + ? NodeCMapReaderFactory + : DOMCMapReaderFactory); + const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl); + const StandardFontDataFactory = + src.StandardFontDataFactory || + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS + ? NodeStandardFontDataFactory + : DOMStandardFontDataFactory); + const wasmUrl = getFactoryUrlProp(src.wasmUrl); + const WasmFactory = + src.WasmFactory || + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS + ? NodeWasmFactory + : DOMWasmFactory); + const ignoreErrors = src.stopAtErrors !== true; + const maxImageSize = + Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 + ? src.maxImageSize + : -1; + const isEvalSupported = src.isEvalSupported !== false; + const isOffscreenCanvasSupported = + typeof src.isOffscreenCanvasSupported === "boolean" + ? src.isOffscreenCanvasSupported + : !isNodeJS; + const isImageDecoderSupported = + // eslint-disable-next-line no-nested-ternary + typeof src.isImageDecoderSupported === "boolean" + ? src.isImageDecoderSupported + : // eslint-disable-next-line no-nested-ternary + typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL") + ? true + : typeof PDFJSDev !== "undefined" && PDFJSDev.test("CHROME") + ? false + : !isNodeJS && (FeatureTest.platform.isFirefox || !globalThis.chrome); + const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) + ? src.canvasMaxAreaInBytes + : -1; + const disableFontFace = + typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS; + const fontExtraProperties = src.fontExtraProperties === true; + const enableXfa = src.enableXfa === true; + const ownerDocument = src.ownerDocument || globalThis.document; + const disableRange = src.disableRange === true; + const disableStream = src.disableStream === true; + const disableAutoFetch = src.disableAutoFetch === true; + const pdfBug = src.pdfBug === true; + const CanvasFactory = + src.CanvasFactory || + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS + ? NodeCanvasFactory + : DOMCanvasFactory); + const FilterFactory = + src.FilterFactory || + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS + ? NodeFilterFactory + : DOMFilterFactory); + const enableHWA = src.enableHWA === true; + + // Parameters whose default values depend on other parameters. + const length = rangeTransport ? rangeTransport.length : (src.length ?? NaN); + const useSystemFonts = + typeof src.useSystemFonts === "boolean" + ? src.useSystemFonts + : !isNodeJS && !disableFontFace; + const useWorkerFetch = + typeof src.useWorkerFetch === "boolean" + ? src.useWorkerFetch + : (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || + !!( + CMapReaderFactory === DOMCMapReaderFactory && + StandardFontDataFactory === DOMStandardFontDataFactory && + WasmFactory === DOMWasmFactory && + cMapUrl && + standardFontDataUrl && + wasmUrl && + isValidFetchUrl(cMapUrl, document.baseURI) && + isValidFetchUrl(standardFontDataUrl, document.baseURI) && + isValidFetchUrl(wasmUrl, document.baseURI) + ); + + // Parameters only intended for development/testing purposes. + const styleElement = + typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING") + ? src.styleElement + : null; + + // Set the main-thread verbosity level. + setVerbosityLevel(verbosity); + + // Ensure that the various factories can be initialized, when necessary, + // since the user may provide *custom* ones. + const transportFactory = { + canvasFactory: new CanvasFactory({ ownerDocument, enableHWA }), + filterFactory: new FilterFactory({ docId, ownerDocument }), + cMapReaderFactory: + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || + useWorkerFetch + ? null + : new CMapReaderFactory({ baseUrl: cMapUrl, isCompressed: cMapPacked }), + standardFontDataFactory: + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || + useWorkerFetch + ? null + : new StandardFontDataFactory({ baseUrl: standardFontDataUrl }), + wasmFactory: + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || + useWorkerFetch + ? null + : new WasmFactory({ baseUrl: wasmUrl }), + }; + + if (!worker) { + const workerParams = { + verbosity, + port: GlobalWorkerOptions.workerPort, + }; + // Worker was not provided -- creating and owning our own. If message port + // is specified in global worker options, using it. + worker = workerParams.port + ? PDFWorker.fromPort(workerParams) + : new PDFWorker(workerParams); + task._worker = worker; + } + + const docParams = { + docId, + apiVersion: + typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING") + ? PDFJSDev.eval("BUNDLE_VERSION") + : null, + data, + password, + disableAutoFetch, + rangeChunkSize, + length, + docBaseUrl, + enableXfa, + evaluatorOptions: { + maxImageSize, + disableFontFace, + ignoreErrors, + isEvalSupported, + isOffscreenCanvasSupported, + isImageDecoderSupported, + canvasMaxAreaInBytes, + fontExtraProperties, + useSystemFonts, + cMapUrl: useWorkerFetch ? cMapUrl : null, + standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null, + wasmUrl: useWorkerFetch ? wasmUrl : null, + }, + }; + const transportParams = { + disableFontFace, + fontExtraProperties, + ownerDocument, + pdfBug, + styleElement, + loadingParams: { + disableAutoFetch, + enableXfa, + }, + }; + + worker.promise + .then(function () { + if (task.destroyed) { + throw new Error("Loading aborted"); + } + if (worker.destroyed) { + throw new Error("Worker was destroyed"); + } + + const workerIdPromise = worker.messageHandler.sendWithPromise( + "GetDocRequest", + docParams, + data ? [data.buffer] : null + ); + + let networkStream; + if (rangeTransport) { + networkStream = new PDFDataTransportStream(rangeTransport, { + disableRange, + disableStream, + }); + } else if (!data) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + throw new Error("Not implemented: NetworkStream"); + } + if (!url) { + throw new Error("getDocument - no `url` parameter provided."); + } + let NetworkStream; + + if ( + typeof PDFJSDev !== "undefined" && + PDFJSDev.test("GENERIC") && + isNodeJS + ) { + if (isValidFetchUrl(url)) { + if ( + typeof fetch === "undefined" || + typeof Response === "undefined" || + !("body" in Response.prototype) + ) { + throw new Error( + "getDocument - the Fetch API was disabled in Node.js, see `--no-experimental-fetch`." + ); + } + NetworkStream = PDFFetchStream; + } else { + NetworkStream = PDFNodeStream; + } + } else { + NetworkStream = isValidFetchUrl(url) + ? PDFFetchStream + : PDFNetworkStream; + } + + networkStream = new NetworkStream({ + url, + length, + httpHeaders, + withCredentials, + rangeChunkSize, + disableRange, + disableStream, + }); + } + + return workerIdPromise.then(workerId => { + if (task.destroyed) { + throw new Error("Loading aborted"); + } + if (worker.destroyed) { + throw new Error("Worker was destroyed"); + } + + const messageHandler = new MessageHandler(docId, workerId, worker.port); + const transport = new WorkerTransport( + messageHandler, + task, + networkStream, + transportParams, + transportFactory + ); + task._transport = transport; + messageHandler.send("Ready", null); + }); + }) + .catch(task._capability.reject); + + return task; +} + +function getUrlProp(val) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + return null; // The 'url' is unused with `PDFDataRangeTransport`. + } + if (val instanceof URL) { + return val.href; + } + try { + // The full path is required in the 'url' field. + return new URL(val, window.location).href; + } catch { + if ( + typeof PDFJSDev !== "undefined" && + PDFJSDev.test("GENERIC") && + isNodeJS && + typeof val === "string" + ) { + return val; // Use the url as-is in Node.js environments. + } + } + throw new Error( + "Invalid PDF url data: " + + "either string or URL-object is expected in the url property." + ); +} + +function getDataProp(val) { + // Converting string or array-like data to Uint8Array. + if ( + typeof PDFJSDev !== "undefined" && + PDFJSDev.test("GENERIC") && + isNodeJS && + typeof Buffer !== "undefined" && // eslint-disable-line no-undef + val instanceof Buffer // eslint-disable-line no-undef + ) { + throw new Error( + "Please provide binary data as `Uint8Array`, rather than `Buffer`." + ); + } + if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) { + // Use the data as-is when it's already a Uint8Array that completely + // "utilizes" its underlying ArrayBuffer, to prevent any possible + // issues when transferring it to the worker-thread. + return val; + } + if (typeof val === "string") { + return stringToBytes(val); + } + if ( + val instanceof ArrayBuffer || + ArrayBuffer.isView(val) || + (typeof val === "object" && !isNaN(val?.length)) + ) { + return new Uint8Array(val); + } + throw new Error( + "Invalid PDF binary data: either TypedArray, " + + "string, or array-like object is expected in the data property." + ); +} + +function getFactoryUrlProp(val) { + if (typeof val !== "string") { + return null; + } + if (val.endsWith("/")) { + return val; + } + throw new Error(`Invalid factory url: "${val}" must include trailing slash.`); +} + +function isRefProxy(ref) { + return ( + typeof ref === "object" && + Number.isInteger(ref?.num) && + ref.num >= 0 && + Number.isInteger(ref?.gen) && + ref.gen >= 0 + ); +} + +/** + * @typedef {Object} OnProgressParameters + * @property {number} loaded - Currently loaded number of bytes. + * @property {number} total - Total number of bytes in the PDF file. + */ + +/** + * The loading task controls the operations required to load a PDF document + * (such as network requests) and provides a way to listen for completion, + * after which individual pages can be rendered. + */ +class PDFDocumentLoadingTask { + static #docId = 0; + + constructor() { + this._capability = Promise.withResolvers(); + this._transport = null; + this._worker = null; + + /** + * Unique identifier for the document loading task. + * @type {string} + */ + this.docId = `d${PDFDocumentLoadingTask.#docId++}`; + + /** + * Whether the loading task is destroyed or not. + * @type {boolean} + */ + this.destroyed = false; + + /** + * Callback to request a password if a wrong or no password was provided. + * The callback receives two parameters: a function that should be called + * with the new password, and a reason (see {@link PasswordResponses}). + * @type {function} + */ + this.onPassword = null; + + /** + * Callback to be able to monitor the loading progress of the PDF file + * (necessary to implement e.g. a loading bar). + * The callback receives an {@link OnProgressParameters} argument. + * @type {function} + */ + this.onProgress = null; + } + + /** + * Promise for document loading task completion. + * @type {Promise} + */ + get promise() { + return this._capability.promise; + } + + /** + * Abort all network requests and destroy the worker. + * @returns {Promise} A promise that is resolved when destruction is + * completed. + */ + async destroy() { + this.destroyed = true; + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + await this._transport?.destroy(); + } else { + try { + if (this._worker?.port) { + this._worker._pendingDestroy = true; + } + await this._transport?.destroy(); + } catch (ex) { + if (this._worker?.port) { + delete this._worker._pendingDestroy; + } + throw ex; + } + } + this._transport = null; + + this._worker?.destroy(); + this._worker = null; + } +} + +/** + * Abstract class to support range requests file loading. + * + * NOTE: The TypedArrays passed to the constructor and relevant methods below + * will generally be transferred to the worker-thread. This will help reduce + * main-thread memory usage, however it will take ownership of the TypedArrays. + */ +class PDFDataRangeTransport { + /** + * @param {number} length + * @param {Uint8Array|null} initialData + * @param {boolean} [progressiveDone] + * @param {string} [contentDispositionFilename] + */ + constructor( + length, + initialData, + progressiveDone = false, + contentDispositionFilename = null + ) { + this.length = length; + this.initialData = initialData; + this.progressiveDone = progressiveDone; + this.contentDispositionFilename = contentDispositionFilename; + + this._rangeListeners = []; + this._progressListeners = []; + this._progressiveReadListeners = []; + this._progressiveDoneListeners = []; + this._readyCapability = Promise.withResolvers(); + } + + /** + * @param {function} listener + */ + addRangeListener(listener) { + this._rangeListeners.push(listener); + } + + /** + * @param {function} listener + */ + addProgressListener(listener) { + this._progressListeners.push(listener); + } + + /** + * @param {function} listener + */ + addProgressiveReadListener(listener) { + this._progressiveReadListeners.push(listener); + } + + /** + * @param {function} listener + */ + addProgressiveDoneListener(listener) { + this._progressiveDoneListeners.push(listener); + } + + /** + * @param {number} begin + * @param {Uint8Array|null} chunk + */ + onDataRange(begin, chunk) { + for (const listener of this._rangeListeners) { + listener(begin, chunk); + } + } + + /** + * @param {number} loaded + * @param {number|undefined} total + */ + onDataProgress(loaded, total) { + this._readyCapability.promise.then(() => { + for (const listener of this._progressListeners) { + listener(loaded, total); + } + }); + } + + /** + * @param {Uint8Array|null} chunk + */ + onDataProgressiveRead(chunk) { + this._readyCapability.promise.then(() => { + for (const listener of this._progressiveReadListeners) { + listener(chunk); + } + }); + } + + onDataProgressiveDone() { + this._readyCapability.promise.then(() => { + for (const listener of this._progressiveDoneListeners) { + listener(); + } + }); + } + + transportReady() { + this._readyCapability.resolve(); + } + + /** + * @param {number} begin + * @param {number} end + */ + requestDataRange(begin, end) { + unreachable("Abstract method PDFDataRangeTransport.requestDataRange"); + } + + abort() {} +} + +/** + * Proxy to a `PDFDocument` in the worker thread. + */ +class PDFDocumentProxy { + constructor(pdfInfo, transport) { + this._pdfInfo = pdfInfo; + this._transport = transport; + + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + // For testing purposes. + Object.defineProperty(this, "getNetworkStreamName", { + value: () => this._transport.getNetworkStreamName(), + }); + Object.defineProperty(this, "getXFADatasets", { + value: () => this._transport.getXFADatasets(), + }); + Object.defineProperty(this, "getXRefPrevValue", { + value: () => this._transport.getXRefPrevValue(), + }); + Object.defineProperty(this, "getStartXRefPos", { + value: () => this._transport.getStartXRefPos(), + }); + Object.defineProperty(this, "getAnnotArray", { + value: pageIndex => this._transport.getAnnotArray(pageIndex), + }); + } + } + + /** + * @type {AnnotationStorage} Storage for annotation data in forms. + */ + get annotationStorage() { + return this._transport.annotationStorage; + } + + /** + * @type {Object} The canvas factory instance. + */ + get canvasFactory() { + return this._transport.canvasFactory; + } + + /** + * @type {Object} The filter factory instance. + */ + get filterFactory() { + return this._transport.filterFactory; + } + + /** + * @type {number} Total number of pages in the PDF file. + */ + get numPages() { + return this._pdfInfo.numPages; + } + + /** + * @type {Array} A (not guaranteed to be) unique ID to identify + * the PDF document. + * NOTE: The first element will always be defined for all PDF documents, + * whereas the second element is only defined for *modified* PDF documents. + */ + get fingerprints() { + return this._pdfInfo.fingerprints; + } + + /** + * @type {boolean} True if only XFA form. + */ + get isPureXfa() { + return shadow(this, "isPureXfa", !!this._transport._htmlForXfa); + } + + /** + * NOTE: This is (mostly) intended to support printing of XFA forms. + * + * @type {Object | null} An object representing a HTML tree structure + * to render the XFA, or `null` when no XFA form exists. + */ + get allXfaHtml() { + return this._transport._htmlForXfa; + } + + /** + * @param {number} pageNumber - The page number to get. The first page is 1. + * @returns {Promise} A promise that is resolved with + * a {@link PDFPageProxy} object. + */ + getPage(pageNumber) { + return this._transport.getPage(pageNumber); + } + + /** + * @param {RefProxy} ref - The page reference. + * @returns {Promise} A promise that is resolved with the page index, + * starting from zero, that is associated with the reference. + */ + getPageIndex(ref) { + return this._transport.getPageIndex(ref); + } + + /** + * @returns {Promise>>} A promise that is resolved + * with a mapping from named destinations to references. + * + * This can be slow for large documents. Use `getDestination` instead. + */ + getDestinations() { + return this._transport.getDestinations(); + } + + /** + * @param {string} id - The named destination to get. + * @returns {Promise | null>} A promise that is resolved with all + * information of the given named destination, or `null` when the named + * destination is not present in the PDF file. + */ + getDestination(id) { + return this._transport.getDestination(id); + } + + /** + * @returns {Promise | null>} A promise that is resolved with + * an {Array} containing the page labels that correspond to the page + * indexes, or `null` when no page labels are present in the PDF file. + */ + getPageLabels() { + return this._transport.getPageLabels(); + } + + /** + * @returns {Promise} A promise that is resolved with a {string} + * containing the page layout name. + */ + getPageLayout() { + return this._transport.getPageLayout(); + } + + /** + * @returns {Promise} A promise that is resolved with a {string} + * containing the page mode name. + */ + getPageMode() { + return this._transport.getPageMode(); + } + + /** + * @returns {Promise} A promise that is resolved with an + * {Object} containing the viewer preferences, or `null` when no viewer + * preferences are present in the PDF file. + */ + getViewerPreferences() { + return this._transport.getViewerPreferences(); + } + + /** + * @returns {Promise} A promise that is resolved with an {Array} + * containing the destination, or `null` when no open action is present + * in the PDF. + */ + getOpenAction() { + return this._transport.getOpenAction(); + } + + /** + * @returns {Promise} A promise that is resolved with a lookup table + * for mapping named attachments to their content. + */ + getAttachments() { + return this._transport.getAttachments(); + } + + /** + * @returns {Promise} A promise that is resolved with + * an {Object} with the JavaScript actions: + * - from the name tree. + * - from A or AA entries in the catalog dictionary. + * , or `null` if no JavaScript exists. + */ + getJSActions() { + return this._transport.getDocJSActions(); + } + + /** + * @typedef {Object} OutlineNode + * @property {string} title + * @property {boolean} bold + * @property {boolean} italic + * @property {Uint8ClampedArray} color - The color in RGB format to use for + * display purposes. + * @property {string | Array | null} dest + * @property {string | null} url + * @property {string | undefined} unsafeUrl + * @property {boolean | undefined} newWindow + * @property {number | undefined} count + * @property {Array} items + */ + + /** + * @returns {Promise>} A promise that is resolved with an + * {Array} that is a tree outline (if it has one) of the PDF file. + */ + getOutline() { + return this._transport.getOutline(); + } + + /** + * @typedef {Object} GetOptionalContentConfigParameters + * @property {string} [intent] - Determines the optional content groups that + * are visible by default; valid values are: + * - 'display' (viewable groups). + * - 'print' (printable groups). + * - 'any' (all groups). + * The default value is 'display'. + */ + + /** + * @param {GetOptionalContentConfigParameters} [params] - Optional content + * config parameters. + * @returns {Promise} A promise that is resolved with + * an {@link OptionalContentConfig} that contains all the optional content + * groups (assuming that the document has any). + */ + getOptionalContentConfig({ intent = "display" } = {}) { + const { renderingIntent } = this._transport.getRenderingIntent(intent); + + return this._transport.getOptionalContentConfig(renderingIntent); + } + + /** + * @returns {Promise | null>} A promise that is resolved with + * an {Array} that contains the permission flags for the PDF document, or + * `null` when no permissions are present in the PDF file. + */ + getPermissions() { + return this._transport.getPermissions(); + } + + /** + * @returns {Promise<{ info: Object, metadata: Metadata }>} A promise that is + * resolved with an {Object} that has `info` and `metadata` properties. + * `info` is an {Object} filled with anything available in the information + * dictionary and similarly `metadata` is a {Metadata} object with + * information from the metadata section of the PDF. + */ + getMetadata() { + return this._transport.getMetadata(); + } + + /** + * @typedef {Object} MarkInfo + * Properties correspond to Table 321 of the PDF 32000-1:2008 spec. + * @property {boolean} Marked + * @property {boolean} UserProperties + * @property {boolean} Suspects + */ + + /** + * @returns {Promise} A promise that is resolved with + * a {MarkInfo} object that contains the MarkInfo flags for the PDF + * document, or `null` when no MarkInfo values are present in the PDF file. + */ + getMarkInfo() { + return this._transport.getMarkInfo(); + } + + /** + * @returns {Promise} A promise that is resolved with a + * {Uint8Array} containing the raw data of the PDF document. + */ + getData() { + return this._transport.getData(); + } + + /** + * @returns {Promise} A promise that is resolved with a + * {Uint8Array} containing the full data of the saved document. + */ + saveDocument() { + return this._transport.saveDocument(); + } + + /** + * @returns {Promise<{ length: number }>} A promise that is resolved when the + * document's data is loaded. It is resolved with an {Object} that contains + * the `length` property that indicates size of the PDF data in bytes. + */ + getDownloadInfo() { + return this._transport.downloadInfoCapability.promise; + } + + /** + * Cleans up resources allocated by the document on both the main and worker + * threads. + * + * NOTE: Do not, under any circumstances, call this method when rendering is + * currently ongoing since that may lead to rendering errors. + * + * @param {boolean} [keepLoadedFonts] - Let fonts remain attached to the DOM. + * NOTE: This will increase persistent memory usage, hence don't use this + * option unless absolutely necessary. The default value is `false`. + * @returns {Promise} A promise that is resolved when clean-up has finished. + */ + cleanup(keepLoadedFonts = false) { + return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa); + } + + /** + * Destroys the current document instance and terminates the worker. + */ + destroy() { + return this.loadingTask.destroy(); + } + + /** + * @param {RefProxy} ref - The page reference. + * @returns {number | null} The page number, if it's cached. + */ + cachedPageNumber(ref) { + return this._transport.cachedPageNumber(ref); + } + + /** + * @type {DocumentInitParameters} A subset of the current + * {DocumentInitParameters}, which are needed in the viewer. + */ + get loadingParams() { + return this._transport.loadingParams; + } + + /** + * @type {PDFDocumentLoadingTask} The loadingTask for the current document. + */ + get loadingTask() { + return this._transport.loadingTask; + } + + /** + * @returns {Promise> | null>} A promise that is + * resolved with an {Object} containing /AcroForm field data for the JS + * sandbox, or `null` when no field data is present in the PDF file. + */ + getFieldObjects() { + return this._transport.getFieldObjects(); + } + + /** + * @returns {Promise} A promise that is resolved with `true` + * if some /AcroForm fields have JavaScript actions. + */ + hasJSActions() { + return this._transport.hasJSActions(); + } + + /** + * @returns {Promise | null>} A promise that is resolved with an + * {Array} containing IDs of annotations that have a calculation + * action, or `null` when no such annotations are present in the PDF file. + */ + getCalculationOrderIds() { + return this._transport.getCalculationOrderIds(); + } +} + +/** + * Page getViewport parameters. + * + * @typedef {Object} GetViewportParameters + * @property {number} scale - The desired scale of the viewport. + * @property {number} [rotation] - The desired rotation, in degrees, of + * the viewport. If omitted it defaults to the page rotation. + * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset. + * The default value is `0`. + * @property {number} [offsetY] - The vertical, i.e. y-axis, offset. + * The default value is `0`. + * @property {boolean} [dontFlip] - If true, the y-axis will not be + * flipped. The default value is `false`. + */ + +/** + * Page getTextContent parameters. + * + * @typedef {Object} getTextContentParameters + * @property {boolean} [includeMarkedContent] - When true include marked + * content items in the items array of TextContent. The default is `false`. + * @property {boolean} [disableNormalization] - When true the text is *not* + * normalized in the worker-thread. The default is `false`. + */ + +/** + * Page text content. + * + * @typedef {Object} TextContent + * @property {Array} items - Array of + * {@link TextItem} and {@link TextMarkedContent} objects. TextMarkedContent + * items are included when includeMarkedContent is true. + * @property {Object} styles - {@link TextStyle} objects, + * indexed by font name. + * @property {string | null} lang - The document /Lang attribute. + */ + +/** + * Page text content part. + * + * @typedef {Object} TextItem + * @property {string} str - Text content. + * @property {string} dir - Text direction: 'ttb', 'ltr' or 'rtl'. + * @property {Array} transform - Transformation matrix. + * @property {number} width - Width in device space. + * @property {number} height - Height in device space. + * @property {string} fontName - Font name used by PDF.js for converted font. + * @property {boolean} hasEOL - Indicating if the text content is followed by a + * line-break. + */ + +/** + * Page text marked content part. + * + * @typedef {Object} TextMarkedContent + * @property {string} type - Either 'beginMarkedContent', + * 'beginMarkedContentProps', or 'endMarkedContent'. + * @property {string} id - The marked content identifier. Only used for type + * 'beginMarkedContentProps'. + */ + +/** + * Text style. + * + * @typedef {Object} TextStyle + * @property {number} ascent - Font ascent. + * @property {number} descent - Font descent. + * @property {boolean} vertical - Whether or not the text is in vertical mode. + * @property {string} fontFamily - The possible font family. + */ + +/** + * Page annotation parameters. + * + * @typedef {Object} GetAnnotationsParameters + * @property {string} [intent] - Determines the annotations that are fetched, + * can be 'display' (viewable annotations), 'print' (printable annotations), + * or 'any' (all annotations). The default value is 'display'. + */ + +/** + * Page render parameters. + * + * @typedef {Object} RenderParameters + * @property {CanvasRenderingContext2D} canvasContext - A 2D context of a DOM + * Canvas object. + * @property {PageViewport} viewport - Rendering viewport obtained by calling + * the `PDFPageProxy.getViewport` method. + * @property {string} [intent] - Rendering intent, can be 'display', 'print', + * or 'any'. The default value is 'display'. + * @property {number} [annotationMode] Controls which annotations are rendered + * onto the canvas, for annotations with appearance-data; the values from + * {@link AnnotationMode} should be used. The following values are supported: + * - `AnnotationMode.DISABLE`, which disables all annotations. + * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus + * it also depends on the `intent`-option, see above). + * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain + * interactive form elements (those will be rendered in the display layer). + * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations + * (as above) but where interactive form elements are updated with data + * from the {@link AnnotationStorage}-instance; useful e.g. for printing. + * The default value is `AnnotationMode.ENABLE`. + * @property {Array} [transform] - Additional transform, applied just + * before viewport transform. + * @property {CanvasGradient | CanvasPattern | string} [background] - Background + * to use for the canvas. + * Any valid `canvas.fillStyle` can be used: a `DOMString` parsed as CSS + * value, a `CanvasGradient` object (a linear or radial gradient) or + * a `CanvasPattern` object (a repetitive image). The default value is + * 'rgb(255,255,255)'. + * + * NOTE: This option may be partially, or completely, ignored when the + * `pageColors`-option is used. + * @property {Object} [pageColors] - Overwrites background and foreground colors + * with user defined ones in order to improve readability in high contrast + * mode. + * @property {Promise} [optionalContentConfigPromise] - + * A promise that should resolve with an {@link OptionalContentConfig} + * created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`, + * the configuration will be fetched automatically with the default visibility + * states set. + * @property {Map} [annotationCanvasMap] - Map some + * annotation ids with canvases used to render them. + * @property {PrintAnnotationStorage} [printAnnotationStorage] + * @property {boolean} [isEditing] - Render the page in editing mode. + */ + +/** + * Page getOperatorList parameters. + * + * @typedef {Object} GetOperatorListParameters + * @property {string} [intent] - Rendering intent, can be 'display', 'print', + * or 'any'. The default value is 'display'. + * @property {number} [annotationMode] Controls which annotations are included + * in the operatorList, for annotations with appearance-data; the values from + * {@link AnnotationMode} should be used. The following values are supported: + * - `AnnotationMode.DISABLE`, which disables all annotations. + * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus + * it also depends on the `intent`-option, see above). + * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain + * interactive form elements (those will be rendered in the display layer). + * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations + * (as above) but where interactive form elements are updated with data + * from the {@link AnnotationStorage}-instance; useful e.g. for printing. + * The default value is `AnnotationMode.ENABLE`. + * @property {PrintAnnotationStorage} [printAnnotationStorage] + * @property {boolean} [isEditing] - Render the page in editing mode. + */ + +/** + * Structure tree node. The root node will have a role "Root". + * + * @typedef {Object} StructTreeNode + * @property {Array} children - Array of + * {@link StructTreeNode} and {@link StructTreeContent} objects. + * @property {string} role - element's role, already mapped if a role map exists + * in the PDF. + */ + +/** + * Structure tree content. + * + * @typedef {Object} StructTreeContent + * @property {string} type - either "content" for page and stream structure + * elements or "object" for object references. + * @property {string} id - unique id that will map to the text layer. + */ + +/** + * PDF page operator list. + * + * @typedef {Object} PDFOperatorList + * @property {Array} fnArray - Array containing the operator functions. + * @property {Array} argsArray - Array containing the arguments of the + * functions. + */ + +/** + * Proxy to a `PDFPage` in the worker thread. + */ +class PDFPageProxy { + #pendingCleanup = false; + + constructor(pageIndex, pageInfo, transport, pdfBug = false) { + this._pageIndex = pageIndex; + this._pageInfo = pageInfo; + this._transport = transport; + this._stats = pdfBug ? new StatTimer() : null; + this._pdfBug = pdfBug; + /** @type {PDFObjects} */ + this.commonObjs = transport.commonObjs; + this.objs = new PDFObjects(); + + this._intentStates = new Map(); + this.destroyed = false; + } + + /** + * @type {number} Page number of the page. First page is 1. + */ + get pageNumber() { + return this._pageIndex + 1; + } + + /** + * @type {number} The number of degrees the page is rotated clockwise. + */ + get rotate() { + return this._pageInfo.rotate; + } + + /** + * @type {RefProxy | null} The reference that points to this page. + */ + get ref() { + return this._pageInfo.ref; + } + + /** + * @type {number} The default size of units in 1/72nds of an inch. + */ + get userUnit() { + return this._pageInfo.userUnit; + } + + /** + * @type {Array} An array of the visible portion of the PDF page in + * user space units [x1, y1, x2, y2]. + */ + get view() { + return this._pageInfo.view; + } + + /** + * @param {GetViewportParameters} params - Viewport parameters. + * @returns {PageViewport} Contains 'width' and 'height' properties + * along with transforms required for rendering. + */ + getViewport({ + scale, + rotation = this.rotate, + offsetX = 0, + offsetY = 0, + dontFlip = false, + } = {}) { + return new PageViewport({ + viewBox: this.view, + userUnit: this.userUnit, + scale, + rotation, + offsetX, + offsetY, + dontFlip, + }); + } + + /** + * @param {GetAnnotationsParameters} [params] - Annotation parameters. + * @returns {Promise>} A promise that is resolved with an + * {Array} of the annotation objects. + */ + getAnnotations({ intent = "display" } = {}) { + const { renderingIntent } = this._transport.getRenderingIntent(intent); + + return this._transport.getAnnotations(this._pageIndex, renderingIntent); + } + + /** + * @returns {Promise} A promise that is resolved with an + * {Object} with JS actions. + */ + getJSActions() { + return this._transport.getPageJSActions(this._pageIndex); + } + + /** + * @type {Object} The filter factory instance. + */ + get filterFactory() { + return this._transport.filterFactory; + } + + /** + * @type {boolean} True if only XFA form. + */ + get isPureXfa() { + return shadow(this, "isPureXfa", !!this._transport._htmlForXfa); + } + + /** + * @returns {Promise} A promise that is resolved with + * an {Object} with a fake DOM object (a tree structure where elements + * are {Object} with a name, attributes (class, style, ...), value and + * children, very similar to a HTML DOM tree), or `null` if no XFA exists. + */ + async getXfa() { + return this._transport._htmlForXfa?.children[this._pageIndex] || null; + } + + /** + * Begins the process of rendering a page to the desired context. + * + * @param {RenderParameters} params - Page render parameters. + * @returns {RenderTask} An object that contains a promise that is + * resolved when the page finishes rendering. + */ + render({ + canvasContext, + viewport, + intent = "display", + annotationMode = AnnotationMode.ENABLE, + transform = null, + background = null, + optionalContentConfigPromise = null, + annotationCanvasMap = null, + pageColors = null, + printAnnotationStorage = null, + isEditing = false, + }) { + this._stats?.time("Overall"); + + const intentArgs = this._transport.getRenderingIntent( + intent, + annotationMode, + printAnnotationStorage, + isEditing + ); + const { renderingIntent, cacheKey } = intentArgs; + // If there was a pending destroy, cancel it so no cleanup happens during + // this call to render. + this.#pendingCleanup = false; + + optionalContentConfigPromise ||= + this._transport.getOptionalContentConfig(renderingIntent); + + let intentState = this._intentStates.get(cacheKey); + if (!intentState) { + intentState = Object.create(null); + this._intentStates.set(cacheKey, intentState); + } + + // Ensure that a pending `streamReader` cancel timeout is always aborted. + if (intentState.streamReaderCancelTimeout) { + clearTimeout(intentState.streamReaderCancelTimeout); + intentState.streamReaderCancelTimeout = null; + } + + const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT); + + // If there's no displayReadyCapability yet, then the operatorList + // was never requested before. Make the request and create the promise. + if (!intentState.displayReadyCapability) { + intentState.displayReadyCapability = Promise.withResolvers(); + intentState.operatorList = { + fnArray: [], + argsArray: [], + lastChunk: false, + separateAnnots: null, + }; + + this._stats?.time("Page Request"); + this._pumpOperatorList(intentArgs); + } + + const complete = error => { + intentState.renderTasks.delete(internalRenderTask); + + // Attempt to reduce memory usage during *printing*, by always running + // cleanup immediately once rendering has finished. + if (intentPrint) { + this.#pendingCleanup = true; + } + this.#tryCleanup(); + + if (error) { + internalRenderTask.capability.reject(error); + + this._abortOperatorList({ + intentState, + reason: error instanceof Error ? error : new Error(error), + }); + } else { + internalRenderTask.capability.resolve(); + } + + if (this._stats) { + this._stats.timeEnd("Rendering"); + this._stats.timeEnd("Overall"); + + if (globalThis.Stats?.enabled) { + globalThis.Stats.add(this.pageNumber, this._stats); + } + } + }; + + const internalRenderTask = new InternalRenderTask({ + callback: complete, + // Only include the required properties, and *not* the entire object. + params: { + canvasContext, + viewport, + transform, + background, + }, + objs: this.objs, + commonObjs: this.commonObjs, + annotationCanvasMap, + operatorList: intentState.operatorList, + pageIndex: this._pageIndex, + canvasFactory: this._transport.canvasFactory, + filterFactory: this._transport.filterFactory, + useRequestAnimationFrame: !intentPrint, + pdfBug: this._pdfBug, + pageColors, + }); + + (intentState.renderTasks ||= new Set()).add(internalRenderTask); + const renderTask = internalRenderTask.task; + + Promise.all([ + intentState.displayReadyCapability.promise, + optionalContentConfigPromise, + ]) + .then(([transparency, optionalContentConfig]) => { + if (this.destroyed) { + complete(); + return; + } + this._stats?.time("Rendering"); + + if (!(optionalContentConfig.renderingIntent & renderingIntent)) { + throw new Error( + "Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + + "and `PDFDocumentProxy.getOptionalContentConfig` methods." + ); + } + internalRenderTask.initializeGraphics({ + transparency, + optionalContentConfig, + }); + internalRenderTask.operatorListChanged(); + }) + .catch(complete); + + return renderTask; + } + + /** + * @param {GetOperatorListParameters} params - Page getOperatorList + * parameters. + * @returns {Promise} A promise resolved with an + * {@link PDFOperatorList} object that represents the page's operator list. + */ + getOperatorList({ + intent = "display", + annotationMode = AnnotationMode.ENABLE, + printAnnotationStorage = null, + isEditing = false, + } = {}) { + if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) { + throw new Error("Not implemented: getOperatorList"); + } + function operatorListChanged() { + if (intentState.operatorList.lastChunk) { + intentState.opListReadCapability.resolve(intentState.operatorList); + + intentState.renderTasks.delete(opListTask); + } + } + + const intentArgs = this._transport.getRenderingIntent( + intent, + annotationMode, + printAnnotationStorage, + isEditing, + /* isOpList = */ true + ); + let intentState = this._intentStates.get(intentArgs.cacheKey); + if (!intentState) { + intentState = Object.create(null); + this._intentStates.set(intentArgs.cacheKey, intentState); + } + let opListTask; + + if (!intentState.opListReadCapability) { + opListTask = Object.create(null); + opListTask.operatorListChanged = operatorListChanged; + intentState.opListReadCapability = Promise.withResolvers(); + (intentState.renderTasks ||= new Set()).add(opListTask); + intentState.operatorList = { + fnArray: [], + argsArray: [], + lastChunk: false, + separateAnnots: null, + }; + + this._stats?.time("Page Request"); + this._pumpOperatorList(intentArgs); + } + return intentState.opListReadCapability.promise; + } + + /** + * NOTE: All occurrences of whitespace will be replaced by + * standard spaces (0x20). + * + * @param {getTextContentParameters} params - getTextContent parameters. + * @returns {ReadableStream} Stream for reading text content chunks. + */ + streamTextContent({ + includeMarkedContent = false, + disableNormalization = false, + } = {}) { + const TEXT_CONTENT_CHUNK_SIZE = 100; + + return this._transport.messageHandler.sendWithStream( + "GetTextContent", + { + pageIndex: this._pageIndex, + includeMarkedContent: includeMarkedContent === true, + disableNormalization: disableNormalization === true, + }, + { + highWaterMark: TEXT_CONTENT_CHUNK_SIZE, + size(textContent) { + return textContent.items.length; + }, + } + ); + } + + /** + * NOTE: All occurrences of whitespace will be replaced by + * standard spaces (0x20). + * + * @param {getTextContentParameters} params - getTextContent parameters. + * @returns {Promise} A promise that is resolved with a + * {@link TextContent} object that represents the page's text content. + */ + getTextContent(params = {}) { + if (this._transport._htmlForXfa) { + // TODO: We need to revisit this once the XFA foreground patch lands and + // only do this for non-foreground XFA. + return this.getXfa().then(xfa => XfaText.textContent(xfa)); + } + const readableStream = this.streamTextContent(params); + + return new Promise(function (resolve, reject) { + function pump() { + reader.read().then(function ({ value, done }) { + if (done) { + resolve(textContent); + return; + } + textContent.lang ??= value.lang; + Object.assign(textContent.styles, value.styles); + textContent.items.push(...value.items); + pump(); + }, reject); + } + + const reader = readableStream.getReader(); + const textContent = { + items: [], + styles: Object.create(null), + lang: null, + }; + pump(); + }); + } + + /** + * @returns {Promise} A promise that is resolved with a + * {@link StructTreeNode} object that represents the page's structure tree, + * or `null` when no structure tree is present for the current page. + */ + getStructTree() { + return this._transport.getStructTree(this._pageIndex); + } + + /** + * Destroys the page object. + * @private + */ + _destroy() { + this.destroyed = true; + + const waitOn = []; + for (const intentState of this._intentStates.values()) { + this._abortOperatorList({ + intentState, + reason: new Error("Page was destroyed."), + force: true, + }); + + if (intentState.opListReadCapability) { + // Avoid errors below, since the renderTasks are just stubs. + continue; + } + for (const internalRenderTask of intentState.renderTasks) { + waitOn.push(internalRenderTask.completed); + internalRenderTask.cancel(); + } + } + this.objs.clear(); + this.#pendingCleanup = false; + + return Promise.all(waitOn); + } + + /** + * Cleans up resources allocated by the page. + * + * @param {boolean} [resetStats] - Reset page stats, if enabled. + * The default value is `false`. + * @returns {boolean} Indicates if clean-up was successfully run. + */ + cleanup(resetStats = false) { + this.#pendingCleanup = true; + const success = this.#tryCleanup(); + + if (resetStats && success) { + this._stats &&= new StatTimer(); + } + return success; + } + + /** + * Attempts to clean up if rendering is in a state where that's possible. + * @returns {boolean} Indicates if clean-up was successfully run. + */ + #tryCleanup() { + if (!this.#pendingCleanup || this.destroyed) { + return false; + } + for (const { renderTasks, operatorList } of this._intentStates.values()) { + if (renderTasks.size > 0 || !operatorList.lastChunk) { + return false; + } + } + this._intentStates.clear(); + this.objs.clear(); + this.#pendingCleanup = false; + return true; + } + + /** + * @private + */ + _startRenderPage(transparency, cacheKey) { + const intentState = this._intentStates.get(cacheKey); + if (!intentState) { + return; // Rendering was cancelled. + } + this._stats?.timeEnd("Page Request"); + + // TODO Refactor RenderPageRequest to separate rendering + // and operator list logic + intentState.displayReadyCapability?.resolve(transparency); + } + + /** + * @private + */ + _renderPageChunk(operatorListChunk, intentState) { + // Add the new chunk to the current operator list. + for (let i = 0, ii = operatorListChunk.length; i < ii; i++) { + intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); + intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]); + } + intentState.operatorList.lastChunk = operatorListChunk.lastChunk; + intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots; + + // Notify all the rendering tasks there are more operators to be consumed. + for (const internalRenderTask of intentState.renderTasks) { + internalRenderTask.operatorListChanged(); + } + + if (operatorListChunk.lastChunk) { + this.#tryCleanup(); + } + } + + /** + * @private + */ + _pumpOperatorList({ + renderingIntent, + cacheKey, + annotationStorageSerializable, + modifiedIds, + }) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + Number.isInteger(renderingIntent) && renderingIntent > 0, + '_pumpOperatorList: Expected valid "renderingIntent" argument.' + ); + } + const { map, transfer } = annotationStorageSerializable; + + const readableStream = this._transport.messageHandler.sendWithStream( + "GetOperatorList", + { + pageIndex: this._pageIndex, + intent: renderingIntent, + cacheKey, + annotationStorage: map, + modifiedIds, + }, + transfer + ); + const reader = readableStream.getReader(); + + const intentState = this._intentStates.get(cacheKey); + intentState.streamReader = reader; + + const pump = () => { + reader.read().then( + ({ value, done }) => { + if (done) { + intentState.streamReader = null; + return; + } + if (this._transport.destroyed) { + return; // Ignore any pending requests if the worker was terminated. + } + this._renderPageChunk(value, intentState); + pump(); + }, + reason => { + intentState.streamReader = null; + + if (this._transport.destroyed) { + return; // Ignore any pending requests if the worker was terminated. + } + if (intentState.operatorList) { + // Mark operator list as complete. + intentState.operatorList.lastChunk = true; + + for (const internalRenderTask of intentState.renderTasks) { + internalRenderTask.operatorListChanged(); + } + this.#tryCleanup(); + } + + if (intentState.displayReadyCapability) { + intentState.displayReadyCapability.reject(reason); + } else if (intentState.opListReadCapability) { + intentState.opListReadCapability.reject(reason); + } else { + throw reason; + } + } + ); + }; + pump(); + } + + /** + * @private + */ + _abortOperatorList({ intentState, reason, force = false }) { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + assert( + reason instanceof Error, + '_abortOperatorList: Expected valid "reason" argument.' + ); + } + + if (!intentState.streamReader) { + return; + } + // Ensure that a pending `streamReader` cancel timeout is always aborted. + if (intentState.streamReaderCancelTimeout) { + clearTimeout(intentState.streamReaderCancelTimeout); + intentState.streamReaderCancelTimeout = null; + } + + if (!force) { + // Ensure that an Error occurring in *only* one `InternalRenderTask`, e.g. + // multiple render() calls on the same canvas, won't break all rendering. + if (intentState.renderTasks.size > 0) { + return; + } + // Don't immediately abort parsing on the worker-thread when rendering is + // cancelled, since that will unnecessarily delay re-rendering when (for + // partially parsed pages) e.g. zooming/rotation occurs in the viewer. + if (reason instanceof RenderingCancelledException) { + let delay = RENDERING_CANCELLED_TIMEOUT; + if (reason.extraDelay > 0 && reason.extraDelay < /* ms = */ 1000) { + // Above, we prevent the total delay from becoming arbitrarily large. + delay += reason.extraDelay; + } + + intentState.streamReaderCancelTimeout = setTimeout(() => { + intentState.streamReaderCancelTimeout = null; + this._abortOperatorList({ intentState, reason, force: true }); + }, delay); + return; + } + } + intentState.streamReader + .cancel(new AbortException(reason.message)) + .catch(() => { + // Avoid "Uncaught promise" messages in the console. + }); + intentState.streamReader = null; + + if (this._transport.destroyed) { + return; // Ignore any pending requests if the worker was terminated. + } + // Remove the current `intentState`, since a cancelled `getOperatorList` + // call on the worker-thread cannot be re-started... + for (const [curCacheKey, curIntentState] of this._intentStates) { + if (curIntentState === intentState) { + this._intentStates.delete(curCacheKey); + break; + } + } + // ... and force clean-up to ensure that any old state is always removed. + this.cleanup(); + } + + /** + * @type {StatTimer | null} Returns page stats, if enabled; returns `null` + * otherwise. + */ + get stats() { + return this._stats; + } +} + +class LoopbackPort { + #listeners = new Map(); + + #deferred = Promise.resolve(); + + postMessage(obj, transfer) { + const event = { + data: structuredClone(obj, transfer ? { transfer } : null), + }; + + this.#deferred.then(() => { + for (const [listener] of this.#listeners) { + listener.call(this, event); + } + }); + } + + addEventListener(name, listener, options = null) { + let rmAbort = null; + if (options?.signal instanceof AbortSignal) { + const { signal } = options; + if (signal.aborted) { + warn("LoopbackPort - cannot use an `aborted` signal."); + return; + } + const onAbort = () => this.removeEventListener(name, listener); + rmAbort = () => signal.removeEventListener("abort", onAbort); + + signal.addEventListener("abort", onAbort); + } + this.#listeners.set(listener, rmAbort); + } + + removeEventListener(name, listener) { + const rmAbort = this.#listeners.get(listener); + rmAbort?.(); + + this.#listeners.delete(listener); + } + + terminate() { + for (const [, rmAbort] of this.#listeners) { + rmAbort?.(); + } + this.#listeners.clear(); + } +} + +/** + * @typedef {Object} PDFWorkerParameters + * @property {string} [name] - The name of the worker. + * @property {Worker} [port] - The `workerPort` object. + * @property {number} [verbosity] - Controls the logging level; + * the constants from {@link VerbosityLevel} should be used. + */ + +/** + * PDF.js web worker abstraction that controls the instantiation of PDF + * documents. Message handlers are used to pass information from the main + * thread to the worker thread and vice versa. If the creation of a web + * worker is not possible, a "fake" worker will be used instead. + * + * @param {PDFWorkerParameters} params - The worker initialization parameters. + */ +class PDFWorker { + static #fakeWorkerId = 0; + + static #isWorkerDisabled = false; + + static #workerPorts; + + static { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + if (isNodeJS) { + // Workers aren't supported in Node.js, force-disabling them there. + this.#isWorkerDisabled = true; + + GlobalWorkerOptions.workerSrc ||= PDFJSDev.test("LIB") + ? "../pdf.worker.js" + : "./pdf.worker.mjs"; + } + + // Check if URLs have the same origin. For non-HTTP based URLs, returns + // false. + this._isSameOrigin = (baseUrl, otherUrl) => { + let base; + try { + base = new URL(baseUrl); + if (!base.origin || base.origin === "null") { + return false; // non-HTTP url + } + } catch { + return false; + } + const other = new URL(otherUrl, base); + return base.origin === other.origin; + }; + + this._createCDNWrapper = url => { + // We will rely on blob URL's property to specify origin. + // We want this function to fail in case if createObjectURL or Blob do + // not exist or fail for some reason -- our Worker creation will fail + // anyway. + const wrapper = `await import("${url}");`; + return URL.createObjectURL( + new Blob([wrapper], { type: "text/javascript" }) + ); + }; + } + } + + constructor({ + name = null, + port = null, + verbosity = getVerbosityLevel(), + } = {}) { + this.name = name; + this.destroyed = false; + this.verbosity = verbosity; + + this._readyCapability = Promise.withResolvers(); + this._port = null; + this._webWorker = null; + this._messageHandler = null; + + if ( + (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) && + port + ) { + if (PDFWorker.#workerPorts?.has(port)) { + throw new Error("Cannot use more than one PDFWorker per port."); + } + (PDFWorker.#workerPorts ||= new WeakMap()).set(port, this); + this._initializeFromPort(port); + return; + } + this._initialize(); + } + + /** + * Promise for worker initialization completion. + * @type {Promise} + */ + get promise() { + return this._readyCapability.promise; + } + + #resolve() { + this._readyCapability.resolve(); + // Send global setting, e.g. verbosity level. + this._messageHandler.send("configure", { + verbosity: this.verbosity, + }); + } + + /** + * The current `workerPort`, when it exists. + * @type {Worker} + */ + get port() { + return this._port; + } + + /** + * The current MessageHandler-instance. + * @type {MessageHandler} + */ + get messageHandler() { + return this._messageHandler; + } + + _initializeFromPort(port) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + throw new Error("Not implemented: _initializeFromPort"); + } + this._port = port; + this._messageHandler = new MessageHandler("main", "worker", port); + this._messageHandler.on("ready", function () { + // Ignoring "ready" event -- MessageHandler should already be initialized + // and ready to accept messages. + }); + this.#resolve(); + } + + _initialize() { + // If worker support isn't disabled explicit and the browser has worker + // support, create a new web worker and test if it/the browser fulfills + // all requirements to run parts of pdf.js in a web worker. + // Right now, the requirement is, that an Uint8Array is still an + // Uint8Array as it arrives on the worker. + if ( + PDFWorker.#isWorkerDisabled || + PDFWorker.#mainThreadWorkerMessageHandler + ) { + this._setupFakeWorker(); + return; + } + let { workerSrc } = PDFWorker; + + try { + // Wraps workerSrc path into blob URL, if the former does not belong + // to the same origin. + if ( + typeof PDFJSDev !== "undefined" && + PDFJSDev.test("GENERIC") && + !PDFWorker._isSameOrigin(window.location.href, workerSrc) + ) { + workerSrc = PDFWorker._createCDNWrapper( + new URL(workerSrc, window.location).href + ); + } + + const worker = new Worker(workerSrc, { type: "module" }); + const messageHandler = new MessageHandler("main", "worker", worker); + const terminateEarly = () => { + ac.abort(); + messageHandler.destroy(); + worker.terminate(); + if (this.destroyed) { + this._readyCapability.reject(new Error("Worker was destroyed")); + } else { + // Fall back to fake worker if the termination is caused by an + // error (e.g. NetworkError / SecurityError). + this._setupFakeWorker(); + } + }; + + const ac = new AbortController(); + worker.addEventListener( + "error", + () => { + if (!this._webWorker) { + // Worker failed to initialize due to an error. Clean up and fall + // back to the fake worker. + terminateEarly(); + } + }, + { signal: ac.signal } + ); + + messageHandler.on("test", data => { + ac.abort(); + if (this.destroyed || !data) { + terminateEarly(); + return; + } + this._messageHandler = messageHandler; + this._port = worker; + this._webWorker = worker; + + this.#resolve(); + }); + + messageHandler.on("ready", data => { + ac.abort(); + if (this.destroyed) { + terminateEarly(); + return; + } + try { + sendTest(); + } catch { + // We need fallback to a faked worker. + this._setupFakeWorker(); + } + }); + + const sendTest = () => { + const testObj = new Uint8Array(); + // Ensure that we can use `postMessage` transfers. + messageHandler.send("test", testObj, [testObj.buffer]); + }; + + // It might take time for the worker to initialize. We will try to send + // the "test" message immediately, and once the "ready" message arrives. + // The worker shall process only the first received "test" message. + sendTest(); + return; + } catch { + info("The worker has been disabled."); + } + // Either workers are not supported or have thrown an exception. + // Thus, we fallback to a faked worker. + this._setupFakeWorker(); + } + + _setupFakeWorker() { + if (!PDFWorker.#isWorkerDisabled) { + warn("Setting up fake worker."); + PDFWorker.#isWorkerDisabled = true; + } + + PDFWorker._setupFakeWorkerGlobal + .then(WorkerMessageHandler => { + if (this.destroyed) { + this._readyCapability.reject(new Error("Worker was destroyed")); + return; + } + const port = new LoopbackPort(); + this._port = port; + + // All fake workers use the same port, making id unique. + const id = `fake${PDFWorker.#fakeWorkerId++}`; + + // If the main thread is our worker, setup the handling for the + // messages -- the main thread sends to it self. + const workerHandler = new MessageHandler(id + "_worker", id, port); + WorkerMessageHandler.setup(workerHandler, port); + + this._messageHandler = new MessageHandler(id, id + "_worker", port); + this.#resolve(); + }) + .catch(reason => { + this._readyCapability.reject( + new Error(`Setting up fake worker failed: "${reason.message}".`) + ); + }); + } + + /** + * Destroys the worker instance. + */ + destroy() { + this.destroyed = true; + + // We need to terminate only web worker created resource. + this._webWorker?.terminate(); + this._webWorker = null; + + PDFWorker.#workerPorts?.delete(this._port); + this._port = null; + + this._messageHandler?.destroy(); + this._messageHandler = null; + } + + /** + * @param {PDFWorkerParameters} params - The worker initialization parameters. + */ + static fromPort(params) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + throw new Error("Not implemented: fromPort"); + } + if (!params?.port) { + throw new Error("PDFWorker.fromPort - invalid method signature."); + } + const cachedPort = this.#workerPorts?.get(params.port); + if (cachedPort) { + if (cachedPort._pendingDestroy) { + throw new Error( + "PDFWorker.fromPort - the worker is being destroyed.\n" + + "Please remember to await `PDFDocumentLoadingTask.destroy()`-calls." + ); + } + return cachedPort; + } + return new PDFWorker(params); + } + + /** + * The current `workerSrc`, when it exists. + * @type {string} + */ + static get workerSrc() { + if (GlobalWorkerOptions.workerSrc) { + return GlobalWorkerOptions.workerSrc; + } + throw new Error('No "GlobalWorkerOptions.workerSrc" specified.'); + } + + static get #mainThreadWorkerMessageHandler() { + try { + return globalThis.pdfjsWorker?.WorkerMessageHandler || null; + } catch { + return null; + } + } + + // Loads worker code into the main-thread. + static get _setupFakeWorkerGlobal() { + const loader = async () => { + if (this.#mainThreadWorkerMessageHandler) { + // The worker was already loaded using e.g. a ` + + + + + + + + + + +
+ +
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+
+ + +
+
+
+ +
+
+ + diff --git a/frontend/public/pdf.js/web/viewer-geckoview.js b/frontend/public/pdf.js/web/viewer-geckoview.js new file mode 100644 index 0000000..2b9fd1a --- /dev/null +++ b/frontend/public/pdf.js/web/viewer-geckoview.js @@ -0,0 +1,83 @@ +/* Copyright 2016 Mozilla Foundation + * + * 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 { RenderingStates, ScrollMode, SpreadMode } from "./ui_utils.js"; +import { AppOptions } from "./app_options.js"; +import { LinkTarget } from "./pdf_link_service.js"; +import { PDFViewerApplication } from "./app.js"; + +/* eslint-disable-next-line no-unused-vars */ +const pdfjsVersion = + typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_VERSION") : void 0; +/* eslint-disable-next-line no-unused-vars */ +const pdfjsBuild = + typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_BUILD") : void 0; + +const AppConstants = + typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC") + ? { LinkTarget, RenderingStates, ScrollMode, SpreadMode } + : null; + +window.PDFViewerApplication = PDFViewerApplication; +window.PDFViewerApplicationConstants = AppConstants; +window.PDFViewerApplicationOptions = AppOptions; + +function getViewerConfiguration() { + const mainContainer = document.getElementById("viewerContainer"); + return { + appContainer: document.body, + mainContainer, + viewerContainer: document.getElementById("viewer"), + toolbar: { + mainContainer, + container: document.getElementById("floatingToolbar"), + download: document.getElementById("download"), + }, + + passwordOverlay: { + dialog: document.getElementById("passwordDialog"), + label: document.getElementById("passwordText"), + input: document.getElementById("password"), + submitButton: document.getElementById("passwordSubmit"), + cancelButton: document.getElementById("passwordCancel"), + }, + printContainer: document.getElementById("printContainer"), + }; +} + +function webViewerLoad() { + const config = getViewerConfiguration(); + + PDFViewerApplication.run(config); +} + +// Block the "load" event until all pages are loaded, to ensure that printing +// works in Firefox; see https://bugzilla.mozilla.org/show_bug.cgi?id=1618553 +document.blockUnblockOnload?.(true); + +if ( + document.readyState === "interactive" || + document.readyState === "complete" +) { + webViewerLoad(); +} else { + document.addEventListener("DOMContentLoaded", webViewerLoad, true); +} + +export { + PDFViewerApplication, + AppConstants as PDFViewerApplicationConstants, + AppOptions as PDFViewerApplicationOptions, +}; diff --git a/frontend/public/pdf.js/web/viewer-snippet-chrome-extension.html b/frontend/public/pdf.js/web/viewer-snippet-chrome-extension.html new file mode 100644 index 0000000..5c8b404 --- /dev/null +++ b/frontend/public/pdf.js/web/viewer-snippet-chrome-extension.html @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/pdf.js/web/viewer-snippet-chrome-overlays.html b/frontend/public/pdf.js/web/viewer-snippet-chrome-overlays.html new file mode 100644 index 0000000..1089963 --- /dev/null +++ b/frontend/public/pdf.js/web/viewer-snippet-chrome-overlays.html @@ -0,0 +1,30 @@ + +
+ +

+ Click on + "Allow access to file URLs" + at + chrome://extensions +
+ to view this PDF file. +

+
+
+

+ or select the file again: + +

+
+
diff --git a/frontend/public/pdf.js/web/viewer-snippet-firefox-extension.html b/frontend/public/pdf.js/web/viewer-snippet-firefox-extension.html new file mode 100644 index 0000000..aa6cf5d --- /dev/null +++ b/frontend/public/pdf.js/web/viewer-snippet-firefox-extension.html @@ -0,0 +1,2 @@ + + diff --git a/frontend/public/pdf.js/web/viewer-snippet.html b/frontend/public/pdf.js/web/viewer-snippet.html new file mode 100644 index 0000000..3baf947 --- /dev/null +++ b/frontend/public/pdf.js/web/viewer-snippet.html @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/pdf.js/web/viewer.css b/frontend/public/pdf.js/web/viewer.css new file mode 100644 index 0000000..2560e46 --- /dev/null +++ b/frontend/public/pdf.js/web/viewer.css @@ -0,0 +1,1759 @@ +/* Copyright 2014 Mozilla Foundation + * + * 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 url(pdf_viewer.css); + +:root { + --dir-factor: 1; + /*#if !MOZCENTRAL*/ + --inline-start: left; + --inline-end: right; + /*#endif*/ + + --sidebar-width: 200px; + --sidebar-transition-duration: 200ms; + --sidebar-transition-timing-function: ease; + + --toolbar-height: 32px; + --toolbar-horizontal-padding: 1px; + --toolbar-vertical-padding: 2px; + --icon-size: 16px; + + --toolbar-icon-opacity: 0.7; + --doorhanger-icon-opacity: 0.9; + --doorhanger-height: 8px; + + --main-color: rgb(12 12 13); + --body-bg-color: rgb(212 212 215); + --progressBar-color: rgb(10 132 255); + --progressBar-bg-color: rgb(221 221 222); + --progressBar-blend-color: rgb(116 177 239); + --scrollbar-color: auto; + --scrollbar-bg-color: auto; + --toolbar-icon-bg-color: rgb(0 0 0); + --toolbar-icon-hover-bg-color: rgb(0 0 0); + + --sidebar-narrow-bg-color: rgb(212 212 215 / 0.9); + --sidebar-toolbar-bg-color: rgb(245 246 247); + --toolbar-bg-color: rgb(249 249 250); + --toolbar-border-color: rgb(184 184 184); + --toolbar-box-shadow: 0 1px 0 var(--toolbar-border-color); + --toolbar-border-bottom: none; + --toolbarSidebar-box-shadow: + inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25), + 0 1px 0 rgb(0 0 0 / 0.15), 0 0 1px rgb(0 0 0 / 0.1); + --toolbarSidebar-border-bottom: none; + --button-hover-color: color-mix(in srgb, currentColor 17%, transparent); + --toggled-btn-color: rgb(0 0 0); + --toggled-btn-bg-color: rgb(0 0 0 / 0.3); + --toggled-hover-active-btn-color: rgb(0 0 0 / 0.4); + --toggled-hover-btn-outline: none; + --dropdown-btn-bg-color: rgb(215 215 219); + --dropdown-btn-border: none; + --separator-color: rgb(0 0 0 / 0.3); + --field-color: rgb(6 6 6); + --field-bg-color: rgb(255 255 255); + --field-border-color: rgb(187 187 188); + --treeitem-color: rgb(0 0 0 / 0.8); + --treeitem-bg-color: rgb(0 0 0 / 0.15); + --treeitem-hover-color: rgb(0 0 0 / 0.9); + --treeitem-selected-color: rgb(0 0 0 / 0.9); + --treeitem-selected-bg-color: rgb(0 0 0 / 0.25); + --thumbnail-hover-color: rgb(0 0 0 / 0.1); + --thumbnail-selected-color: rgb(0 0 0 / 0.2); + --doorhanger-bg-color: rgb(255 255 255); + --doorhanger-border-color: rgb(12 12 13 / 0.2); + --doorhanger-hover-color: rgb(12 12 13); + --doorhanger-separator-color: rgb(222 222 222); + --dialog-button-border: none; + --dialog-button-bg-color: rgb(12 12 13 / 0.1); + --dialog-button-hover-bg-color: rgb(12 12 13 / 0.3); + + --loading-icon: url(images/loading.svg); + --treeitem-expanded-icon: url(images/treeitem-expanded.svg); + --treeitem-collapsed-icon: url(images/treeitem-collapsed.svg); + --toolbarButton-editorFreeText-icon: url(images/toolbarButton-editorFreeText.svg); + --toolbarButton-editorHighlight-icon: url(images/toolbarButton-editorHighlight.svg); + --toolbarButton-editorInk-icon: url(images/toolbarButton-editorInk.svg); + --toolbarButton-editorStamp-icon: url(images/toolbarButton-editorStamp.svg); + --toolbarButton-editorSignature-icon: url(images/toolbarButton-editorSignature.svg); + --toolbarButton-menuArrow-icon: url(images/toolbarButton-menuArrow.svg); + --toolbarButton-sidebarToggle-icon: url(images/toolbarButton-sidebarToggle.svg); + --toolbarButton-secondaryToolbarToggle-icon: url(images/toolbarButton-secondaryToolbarToggle.svg); + --toolbarButton-pageUp-icon: url(images/toolbarButton-pageUp.svg); + --toolbarButton-pageDown-icon: url(images/toolbarButton-pageDown.svg); + --toolbarButton-zoomOut-icon: url(images/toolbarButton-zoomOut.svg); + --toolbarButton-zoomIn-icon: url(images/toolbarButton-zoomIn.svg); + --toolbarButton-presentationMode-icon: url(images/toolbarButton-presentationMode.svg); + --toolbarButton-print-icon: url(images/toolbarButton-print.svg); + /*#if GENERIC*/ + --toolbarButton-openFile-icon: url(images/toolbarButton-openFile.svg); + /*#endif*/ + --toolbarButton-download-icon: url(images/toolbarButton-download.svg); + --toolbarButton-bookmark-icon: url(images/toolbarButton-bookmark.svg); + --toolbarButton-viewThumbnail-icon: url(images/toolbarButton-viewThumbnail.svg); + --toolbarButton-viewOutline-icon: url(images/toolbarButton-viewOutline.svg); + --toolbarButton-viewAttachments-icon: url(images/toolbarButton-viewAttachments.svg); + --toolbarButton-viewLayers-icon: url(images/toolbarButton-viewLayers.svg); + --toolbarButton-currentOutlineItem-icon: url(images/toolbarButton-currentOutlineItem.svg); + --toolbarButton-search-icon: url(images/toolbarButton-search.svg); + --findbarButton-previous-icon: url(images/findbarButton-previous.svg); + --findbarButton-next-icon: url(images/findbarButton-next.svg); + --secondaryToolbarButton-firstPage-icon: url(images/secondaryToolbarButton-firstPage.svg); + --secondaryToolbarButton-lastPage-icon: url(images/secondaryToolbarButton-lastPage.svg); + --secondaryToolbarButton-rotateCcw-icon: url(images/secondaryToolbarButton-rotateCcw.svg); + --secondaryToolbarButton-rotateCw-icon: url(images/secondaryToolbarButton-rotateCw.svg); + --secondaryToolbarButton-selectTool-icon: url(images/secondaryToolbarButton-selectTool.svg); + --secondaryToolbarButton-handTool-icon: url(images/secondaryToolbarButton-handTool.svg); + --secondaryToolbarButton-scrollPage-icon: url(images/secondaryToolbarButton-scrollPage.svg); + --secondaryToolbarButton-scrollVertical-icon: url(images/secondaryToolbarButton-scrollVertical.svg); + --secondaryToolbarButton-scrollHorizontal-icon: url(images/secondaryToolbarButton-scrollHorizontal.svg); + --secondaryToolbarButton-scrollWrapped-icon: url(images/secondaryToolbarButton-scrollWrapped.svg); + --secondaryToolbarButton-spreadNone-icon: url(images/secondaryToolbarButton-spreadNone.svg); + --secondaryToolbarButton-spreadOdd-icon: url(images/secondaryToolbarButton-spreadOdd.svg); + --secondaryToolbarButton-spreadEven-icon: url(images/secondaryToolbarButton-spreadEven.svg); + --secondaryToolbarButton-imageAltTextSettings-icon: var( + --toolbarButton-editorStamp-icon + ); + --secondaryToolbarButton-documentProperties-icon: url(images/secondaryToolbarButton-documentProperties.svg); + --editorParams-stampAddImage-icon: url(images/toolbarButton-zoomIn.svg); +} + +:root:dir(rtl) { + --dir-factor: -1; + /*#if !MOZCENTRAL*/ + --inline-start: right; + --inline-end: left; + /*#endif*/ +} + +@media (prefers-color-scheme: dark) { + :root { + --main-color: rgb(249 249 250); + --body-bg-color: rgb(42 42 46); + --progressBar-color: rgb(0 96 223); + --progressBar-bg-color: rgb(40 40 43); + --progressBar-blend-color: rgb(20 68 133); + --scrollbar-color: rgb(121 121 123); + --scrollbar-bg-color: rgb(35 35 39); + --toolbar-icon-bg-color: rgb(255 255 255); + --toolbar-icon-hover-bg-color: rgb(255 255 255); + + --sidebar-narrow-bg-color: rgb(42 42 46 / 0.9); + --sidebar-toolbar-bg-color: rgb(50 50 52); + --toolbar-bg-color: rgb(56 56 61); + --toolbar-border-color: rgb(12 12 13); + --toggled-btn-color: rgb(255 255 255); + --toggled-btn-bg-color: rgb(0 0 0 / 0.3); + --toggled-hover-active-btn-color: rgb(0 0 0 / 0.4); + --dropdown-btn-bg-color: rgb(74 74 79); + --separator-color: rgb(0 0 0 / 0.3); + --field-color: rgb(250 250 250); + --field-bg-color: rgb(64 64 68); + --field-border-color: rgb(115 115 115); + --treeitem-color: rgb(255 255 255 / 0.8); + --treeitem-bg-color: rgb(255 255 255 / 0.15); + --treeitem-hover-color: rgb(255 255 255 / 0.9); + --treeitem-selected-color: rgb(255 255 255 / 0.9); + --treeitem-selected-bg-color: rgb(255 255 255 / 0.25); + --thumbnail-hover-color: rgb(255 255 255 / 0.1); + --thumbnail-selected-color: rgb(255 255 255 / 0.2); + --doorhanger-bg-color: #42414d; + --doorhanger-border-color: rgb(39 39 43); + --doorhanger-hover-color: rgb(249 249 250); + --doorhanger-separator-color: rgb(92 92 97); + --dialog-button-bg-color: rgb(92 92 97); + --dialog-button-hover-bg-color: rgb(115 115 115); + } +} + +@media screen and (forced-colors: active) { + :root { + --button-hover-color: Highlight; + --toolbar-icon-opacity: 1; + --toolbar-icon-bg-color: ButtonText; + --toolbar-icon-hover-bg-color: ButtonFace; + --toggled-hover-active-btn-color: ButtonText; + --toggled-hover-btn-outline: 2px solid ButtonBorder; + --toolbar-border-color: CanvasText; + --toolbar-border-bottom: 1px solid var(--toolbar-border-color); + --toolbar-box-shadow: none; + --toggled-btn-color: HighlightText; + --toggled-btn-bg-color: LinkText; + --doorhanger-hover-color: ButtonFace; + --doorhanger-border-color-whcm: 1px solid ButtonText; + --doorhanger-triangle-opacity-whcm: 0; + --dialog-button-border: 1px solid Highlight; + --dialog-button-hover-bg-color: Highlight; + --dialog-button-hover-color: ButtonFace; + --dropdown-btn-border: 1px solid ButtonText; + --field-border-color: ButtonText; + --main-color: CanvasText; + --separator-color: GrayText; + --doorhanger-separator-color: GrayText; + --toolbarSidebar-box-shadow: none; + --toolbarSidebar-border-bottom: 1px solid var(--toolbar-border-color); + } +} + +@media screen and (prefers-reduced-motion: reduce) { + :root { + --sidebar-transition-duration: 0; + } +} + +@keyframes progressIndeterminate { + 0% { + transform: translateX(calc(-142px * var(--dir-factor))); + } + + 100% { + transform: translateX(0); + } +} + +html { + &[data-toolbar-density="compact"] { + --toolbar-height: 30px; + } + &[data-toolbar-density="touch"] { + --toolbar-height: 44px; + } +} + +html, +body { + height: 100%; + width: 100%; +} + +body { + margin: 0; + background-color: var(--body-bg-color); + scrollbar-color: var(--scrollbar-color) var(--scrollbar-bg-color); + + &.wait::before { + content: ""; + position: fixed; + width: 100%; + height: 100%; + z-index: 100000; + cursor: wait; + } +} + +.hidden, +[hidden] { + display: none !important; +} + +#viewerContainer.pdfPresentationMode:fullscreen { + top: 0; + background-color: rgb(0 0 0); + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; + user-select: none; +} + +.pdfPresentationMode:fullscreen section:not([data-internal-link]) { + pointer-events: none; +} + +.pdfPresentationMode:fullscreen .textLayer span { + cursor: none; +} + +.pdfPresentationMode.pdfPresentationModeControls > *, +.pdfPresentationMode.pdfPresentationModeControls .textLayer span { + cursor: default; +} + +#outerContainer { + width: 100%; + height: 100%; + position: relative; + margin: 0; +} + +#sidebarContainer { + position: absolute; + inset-block: var(--toolbar-height) 0; + inset-inline-start: calc(-1 * var(--sidebar-width)); + width: var(--sidebar-width); + visibility: hidden; + z-index: 1; + font: message-box; + border-top: 1px solid transparent; + border-inline-end: var(--doorhanger-border-color-whcm); + transition-property: inset-inline-start; + transition-duration: var(--sidebar-transition-duration); + transition-timing-function: var(--sidebar-transition-timing-function); +} + +#outerContainer:is(.sidebarMoving, .sidebarOpen) #sidebarContainer { + visibility: visible; +} + +#outerContainer.sidebarOpen #sidebarContainer { + inset-inline-start: 0; +} + +#mainContainer { + position: absolute; + inset: 0; + min-width: 350px; + margin: 0; + display: flex; + flex-direction: column; +} + +#sidebarContent { + inset-block: var(--toolbar-height) 0; + inset-inline-start: 0; + overflow: auto; + position: absolute; + width: 100%; + box-shadow: inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25); +} + +#viewerContainer { + overflow: auto; + position: absolute; + inset: var(--toolbar-height) 0 0; + outline: none; + z-index: 0; +} + +#viewerContainer:not(.pdfPresentationMode) { + transition-duration: var(--sidebar-transition-duration); + transition-timing-function: var(--sidebar-transition-timing-function); +} + +#outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode) { + inset-inline-start: var(--sidebar-width); + transition-property: inset-inline-start; +} + +#sidebarContainer :is(input, button, select) { + font: message-box; +} + +.toolbar { + z-index: 2; +} + +#toolbarSidebar { + width: 100%; + height: var(--toolbar-height); + background-color: var(--sidebar-toolbar-bg-color); + box-shadow: var(--toolbarSidebar-box-shadow); + border-bottom: var(--toolbarSidebar-border-bottom); + padding: var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding); + justify-content: space-between; + + #toolbarSidebarLeft { + width: auto; + height: 100%; + + #viewThumbnail::before { + mask-image: var(--toolbarButton-viewThumbnail-icon); + } + + #viewOutline::before { + mask-image: var(--toolbarButton-viewOutline-icon); + transform: scaleX(var(--dir-factor)); + } + + #viewAttachments::before { + mask-image: var(--toolbarButton-viewAttachments-icon); + } + + #viewLayers::before { + mask-image: var(--toolbarButton-viewLayers-icon); + } + } + + #toolbarSidebarRight { + width: auto; + height: 100%; + padding-inline-end: 2px; + } +} + +#sidebarResizer { + position: absolute; + inset-block: 0; + inset-inline-end: -6px; + width: 6px; + z-index: 200; + cursor: ew-resize; +} + +#outerContainer.sidebarOpen #loadingBar { + inset-inline-start: var(--sidebar-width); +} + +#outerContainer.sidebarResizing + :is(#sidebarContainer, #viewerContainer, #loadingBar) { + /* Improve responsiveness and avoid visual glitches when the sidebar is resized. */ + transition-duration: 0s; +} + +.doorHanger, +.doorHangerRight { + border-radius: 2px; + box-shadow: + 0 1px 5px var(--doorhanger-border-color), + 0 0 0 1px var(--doorhanger-border-color); + border: var(--doorhanger-border-color-whcm); + background-color: var(--doorhanger-bg-color); + inset-block-start: calc(100% + var(--doorhanger-height) - 2px); + + &::after, + &::before { + bottom: 100%; + border-style: solid; + border-color: transparent; + content: ""; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + opacity: var(--doorhanger-triangle-opacity-whcm); + } + + &::before { + border-width: calc(var(--doorhanger-height) + 2px); + border-bottom-color: var(--doorhanger-border-color); + } + + &::after { + border-width: var(--doorhanger-height); + } +} + +.doorHangerRight { + inset-inline-end: calc(50% - var(--doorhanger-height) - 1px); + + &::before { + inset-inline-end: -1px; + } + + &::after { + border-bottom-color: var(--doorhanger-bg-color); + inset-inline-end: 1px; + } +} + +.doorHanger { + inset-inline-start: calc(50% - var(--doorhanger-height) - 1px); + + &::before { + inset-inline-start: -1px; + } + + &::after { + border-bottom-color: var(--toolbar-bg-color); + inset-inline-start: 1px; + } +} + +.dialogButton { + border: none; + background: none; + width: 28px; + height: 28px; + outline: none; +} + +.dialogButton:is(:hover, :focus-visible) { + background-color: var(--dialog-button-hover-bg-color); +} + +.dialogButton:is(:hover, :focus-visible) > span { + color: var(--dialog-button-hover-color); +} + +.splitToolbarButtonSeparator { + float: var(--inline-start); + width: 0; + height: 62%; + border-left: 1px solid var(--separator-color); + border-right: none; +} + +.dialogButton { + min-width: 16px; + margin: 2px 1px; + padding: 2px 6px 0; + border: none; + border-radius: 2px; + color: var(--main-color); + font-size: 12px; + line-height: 14px; + user-select: none; + cursor: default; + box-sizing: border-box; +} + +.treeItemToggler::before { + /* All matching images have a size of 16x16 + * All relevant containers have a size of 28x28 */ + position: absolute; + display: inline-block; + width: 16px; + height: 16px; + + content: ""; + background-color: var(--toolbar-icon-bg-color); + mask-size: cover; +} + +#sidebarToggleButton::before { + mask-image: var(--toolbarButton-sidebarToggle-icon); + transform: scaleX(var(--dir-factor)); +} + +#secondaryToolbarToggleButton::before { + mask-image: var(--toolbarButton-secondaryToolbarToggle-icon); + transform: scaleX(var(--dir-factor)); +} + +#previous::before { + mask-image: var(--toolbarButton-pageUp-icon); +} + +#next::before { + mask-image: var(--toolbarButton-pageDown-icon); +} + +#zoomOutButton::before { + mask-image: var(--toolbarButton-zoomOut-icon); +} + +#zoomInButton::before { + mask-image: var(--toolbarButton-zoomIn-icon); +} + +#presentationMode::before { + mask-image: var(--toolbarButton-presentationMode-icon); +} + +#editorFreeTextButton::before { + mask-image: var(--toolbarButton-editorFreeText-icon); +} + +#editorHighlightButton::before { + mask-image: var(--toolbarButton-editorHighlight-icon); +} + +#editorInkButton::before { + mask-image: var(--toolbarButton-editorInk-icon); +} + +#editorStampButton::before { + mask-image: var(--toolbarButton-editorStamp-icon); +} + +#editorSignatureButton::before { + mask-image: var(--toolbarButton-editorSignature-icon); +} + +#printButton::before { + mask-image: var(--toolbarButton-print-icon); +} + +/*#if GENERIC*/ +#secondaryOpenFile::before { + mask-image: var(--toolbarButton-openFile-icon); +} +/*#endif*/ + +#downloadButton::before { + mask-image: var(--toolbarButton-download-icon); +} + +#viewBookmark::before { + mask-image: var(--toolbarButton-bookmark-icon); +} + +#currentOutlineItem::before { + mask-image: var(--toolbarButton-currentOutlineItem-icon); + transform: scaleX(var(--dir-factor)); +} + +#viewFindButton::before { + mask-image: var(--toolbarButton-search-icon); +} + +.pdfSidebarNotification::after { + position: absolute; + display: inline-block; + top: 2px; + inset-inline-end: 2px; + /* Create a filled circle, with a diameter of 9 pixels, using only CSS: */ + content: ""; + background-color: rgb(112 219 85); + height: 9px; + width: 9px; + border-radius: 50%; +} + +.verticalToolbarSeparator { + display: block; + margin-inline: 2px; + width: 0; + height: 80%; + border-left: 1px solid var(--separator-color); + border-right: none; + box-sizing: border-box; +} + +.horizontalToolbarSeparator { + display: block; + margin: 6px 0; + border-top: 1px solid var(--doorhanger-separator-color); + border-bottom: none; + height: 0; + width: 100%; +} + +.toggleButton { + display: inline; + + &:has(> input:checked) { + color: var(--toggled-btn-color); + background-color: var(--toggled-btn-bg-color); + } + + &:is(:hover, :has(> input:focus-visible)) { + color: var(--toggled-btn-color); + background-color: var(--button-hover-color); + } + + & > input { + position: absolute; + top: 50%; + left: 50%; + opacity: 0; + width: 0; + height: 0; + } +} + +.toolbarField { + padding: 4px 7px; + margin: 3px 0; + border-radius: 2px; + background-color: var(--field-bg-color); + background-clip: padding-box; + border: 1px solid var(--field-border-color); + box-shadow: none; + color: var(--field-color); + font-size: 12px; + line-height: 16px; + outline: none; + + &:focus { + border-color: #0a84ff; + } +} + +#pageNumber { + -moz-appearance: textfield; /* hides the spinner in moz */ + text-align: end; + width: 40px; + background-size: 0 0; + transition-property: none; + + /*#if !MOZCENTRAL*/ + &::-webkit-inner-spin-button { + -webkit-appearance: none; + } + /*#endif*/ + + .loadingInput:has(> &.loading)::after { + display: inline; + visibility: visible; + + transition-property: visibility; + transition-delay: var(--loading-icon-delay); + } +} + +.loadingInput { + position: relative; + + &::after { + position: absolute; + visibility: hidden; + display: none; + width: var(--icon-size); + height: var(--icon-size); + + content: ""; + background-color: var(--toolbar-icon-bg-color); + mask-size: cover; + mask-image: var(--loading-icon); + } + + &.start::after { + inset-inline-start: 4px; + } + + &.end::after { + inset-inline-end: 4px; + } +} + +#thumbnailView, +#outlineView, +#attachmentsView, +#layersView { + position: absolute; + width: calc(100% - 8px); + inset-block: 0; + padding: 4px 4px 0; + overflow: auto; + user-select: none; +} + +#thumbnailView { + width: calc(100% - 60px); + padding: 10px 30px 0; +} + +#thumbnailView > a:is(:active, :focus) { + outline: 0; +} + +.thumbnail { + /* Define these variables here, and not in :root, since the individual + thumbnails may have different sizes. */ + --thumbnail-width: 0; + --thumbnail-height: 0; + + float: var(--inline-start); + width: var(--thumbnail-width); + height: var(--thumbnail-height); + margin: 0 10px 5px; + padding: 1px; + border: 7px solid transparent; + border-radius: 2px; +} + +#thumbnailView > a:last-of-type > .thumbnail { + margin-bottom: 10px; +} + +a:focus > .thumbnail, +.thumbnail:hover { + border-color: var(--thumbnail-hover-color); +} + +.thumbnail.selected { + border-color: var(--thumbnail-selected-color) !important; +} + +.thumbnailImage { + width: var(--thumbnail-width); + height: var(--thumbnail-height); + opacity: 0.9; +} + +a:focus > .thumbnail > .thumbnailImage, +.thumbnail:hover > .thumbnailImage { + opacity: 0.95; +} + +.thumbnail.selected > .thumbnailImage { + opacity: 1 !important; +} + +.thumbnail:not([data-loaded]) > .thumbnailImage { + width: calc(var(--thumbnail-width) - 2px); + height: calc(var(--thumbnail-height) - 2px); + border: 1px dashed rgb(132 132 132); +} + +.treeWithDeepNesting > .treeItem, +.treeItem > .treeItems { + margin-inline-start: 20px; +} + +.treeItem > a { + text-decoration: none; + display: inline-block; + /* Subtract the right padding (left, in RTL mode) of the container: */ + min-width: calc(100% - 4px); + height: auto; + margin-bottom: 1px; + padding: 2px 0 5px; + padding-inline-start: 4px; + border-radius: 2px; + color: var(--treeitem-color); + font-size: 13px; + line-height: 15px; + user-select: none; + white-space: normal; + cursor: pointer; +} + +#layersView .treeItem > a * { + cursor: pointer; +} + +#layersView .treeItem > a > label { + padding-inline-start: 4px; +} + +#layersView .treeItem > a > label > input { + float: var(--inline-start); + margin-top: 1px; +} + +.treeItemToggler { + position: relative; + float: var(--inline-start); + height: 0; + width: 0; + color: rgb(255 255 255 / 0.5); +} + +.treeItemToggler::before { + inset-inline-end: 4px; + mask-image: var(--treeitem-expanded-icon); +} + +.treeItemToggler.treeItemsHidden::before { + mask-image: var(--treeitem-collapsed-icon); + transform: scaleX(var(--dir-factor)); +} + +.treeItemToggler.treeItemsHidden ~ .treeItems { + display: none; +} + +.treeItem.selected > a { + background-color: var(--treeitem-selected-bg-color); + color: var(--treeitem-selected-color); +} + +.treeItemToggler:hover, +.treeItemToggler:hover + a, +.treeItemToggler:hover ~ .treeItems, +.treeItem > a:hover { + background-color: var(--treeitem-bg-color); + background-clip: padding-box; + border-radius: 2px; + color: var(--treeitem-hover-color); +} + +#outlineOptionsContainer { + display: none; + + #sidebarContainer:has(#outlineView:not(.hidden)) & { + display: inline flex; + } +} + +.dialogButton { + width: auto; + margin: 3px 4px 2px !important; + padding: 2px 11px; + color: var(--main-color); + background-color: var(--dialog-button-bg-color); + border: var(--dialog-button-border) !important; +} + +dialog { + margin: auto; + padding: 15px; + border-spacing: 4px; + color: var(--main-color); + font: message-box; + font-size: 12px; + line-height: 14px; + background-color: var(--doorhanger-bg-color); + border: 1px solid rgb(0 0 0 / 0.5); + border-radius: 4px; + box-shadow: 0 1px 4px rgb(0 0 0 / 0.3); +} + +dialog::backdrop { + background-color: rgb(0 0 0 / 0.2); +} + +dialog > .row { + display: table-row; +} + +dialog > .row > * { + display: table-cell; +} + +dialog .toolbarField { + margin: 5px 0; +} + +dialog .separator { + display: block; + margin: 4px 0; + height: 0; + width: 100%; + border-top: 1px solid var(--separator-color); + border-bottom: none; +} + +dialog .buttonRow { + text-align: center; + vertical-align: middle; +} + +dialog :link { + color: rgb(255 255 255); +} + +#passwordDialog { + text-align: center; +} + +#passwordDialog .toolbarField { + width: 200px; +} + +#documentPropertiesDialog { + text-align: left; +} + +#documentPropertiesDialog .row > * { + min-width: 100px; + text-align: start; +} + +#documentPropertiesDialog .row > span { + width: 125px; + word-wrap: break-word; +} + +#documentPropertiesDialog .row > p { + max-width: 225px; + word-wrap: break-word; +} + +#documentPropertiesDialog .buttonRow { + margin-top: 10px; +} + +.grab-to-pan-grab { + cursor: grab !important; +} + +.grab-to-pan-grab + *:not(input):not(textarea):not(button):not(select):not(:link) { + cursor: inherit !important; +} + +.grab-to-pan-grab:active, +.grab-to-pan-grabbing { + cursor: grabbing !important; +} + +.grab-to-pan-grabbing { + position: fixed; + background: rgb(0 0 0 / 0); + display: block; + inset: 0; + overflow: hidden; + z-index: 50000; /* should be higher than anything else in PDF.js! */ +} + +.toolbarButton { + height: 100%; + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + color: var(--main-color); + outline: none; + border-radius: 2px; + box-sizing: border-box; + font: message-box; + flex: none; + position: relative; + padding: 0; + + > span { + display: inline-block; + width: 0; + height: 0; + overflow: hidden; + } + + &::before { + opacity: var(--toolbar-icon-opacity); + display: inline-block; + width: var(--icon-size); + height: var(--icon-size); + content: ""; + background-color: var(--toolbar-icon-bg-color); + mask-size: cover; + mask-position: center; + } + + &.toggled { + background-color: var(--toggled-btn-bg-color); + color: var(--toggled-btn-color); + + &::before { + background-color: var(--toggled-btn-color); + } + + &:hover { + outline: var(--toggled-hover-btn-outline) !important; + + &:active { + background-color: var(--toggled-hover-active-btn-color); + } + } + } + + &:is(:hover, :focus-visible) { + background-color: var(--button-hover-color); + + &::before { + background-color: var(--toolbar-icon-hover-bg-color); + } + } + + &:is([disabled="disabled"], [disabled]) { + opacity: 0.5; + pointer-events: none; + } + + &.labeled { + width: 100%; + min-height: var(--menuitem-height); + justify-content: flex-start; + gap: 8px; + padding-inline-start: 12px; + aspect-ratio: unset; + text-align: start; + white-space: normal; + cursor: default; + + &:is(a) { + text-decoration: none; + + &[href="#"] { + opacity: 0.5; + pointer-events: none; + } + } + + &::before { + opacity: var(--doorhanger-icon-opacity); + } + + &:is(:hover, :focus-visible) { + color: var(--doorhanger-hover-color); + } + + > span { + display: inline-block; + width: max-content; + height: auto; + } + } +} + +.toolbarButtonWithContainer { + height: 100%; + aspect-ratio: 1; + display: inline-block; + position: relative; + flex: none; + + > .toolbarButton { + width: 100%; + height: 100%; + } + + .menu { + padding-block: 5px; + } + + .menuContainer { + width: 100%; + height: auto; + max-height: calc( + var(--viewer-container-height) - var(--toolbar-height) - + var(--doorhanger-height) + ); + display: flex; + flex-direction: column; + box-sizing: border-box; + overflow-y: auto; + } + + .editorParamsToolbar { + height: auto; + width: 220px; + position: absolute; + z-index: 30000; + cursor: default; + + :is(#editorStampAddImage, #editorSignatureAddSignature)::before { + mask-image: var(--editorParams-stampAddImage-icon); + } + + .editorParamsLabel { + flex: none; + font: menu; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 150%; + width: fit-content; + inset-inline-start: 0; + color: var(--main-color); + } + + button:is(:hover, :focus-visible) .editorParamsLabel { + color: var(--doorhanger-hover-color); + } + + .editorParamsToolbarContainer { + width: 100%; + height: auto; + display: flex; + flex-direction: column; + box-sizing: border-box; + padding-inline: 10px; + padding-block: 10px; + + > .editorParamsSetter { + min-height: 26px; + display: flex; + align-items: center; + justify-content: space-between; + } + + .editorParamsColor { + width: 32px; + height: 32px; + flex: none; + padding: 0; + } + + .editorParamsSlider { + background-color: transparent; + width: 90px; + flex: 0 1 0; + font: message-box; + + &::-moz-range-progress { + background-color: black; + } + + /*#if !MOZCENTRAL*/ + &::-webkit-slider-runnable-track, + /*#endif*/ + &::-moz-range-track { + background-color: black; + } + + /*#if !MOZCENTRAL*/ + &::-webkit-slider-thumb, + /*#endif*/ + &::-moz-range-thumb { + background-color: white; + } + } + } + } +} + +#secondaryToolbar { + height: auto; + width: 220px; + position: absolute; + z-index: 30000; + cursor: default; + min-height: 26px; + max-height: calc(var(--viewer-container-height) - 40px); + + #secondaryToolbarButtonContainer { + /*#if GENERIC*/ + #secondaryOpenFile::before { + mask-image: var(--toolbarButton-openFile-icon); + } + /*#endif*/ + + #secondaryPrint::before { + mask-image: var(--toolbarButton-print-icon); + } + + #secondaryDownload::before { + mask-image: var(--toolbarButton-download-icon); + } + + #presentationMode::before { + mask-image: var(--toolbarButton-presentationMode-icon); + } + + #viewBookmark::before { + mask-image: var(--toolbarButton-bookmark-icon); + } + + #firstPage::before { + mask-image: var(--secondaryToolbarButton-firstPage-icon); + } + + #lastPage::before { + mask-image: var(--secondaryToolbarButton-lastPage-icon); + } + + #pageRotateCcw::before { + mask-image: var(--secondaryToolbarButton-rotateCcw-icon); + } + + #pageRotateCw::before { + mask-image: var(--secondaryToolbarButton-rotateCw-icon); + } + + #cursorSelectTool::before { + mask-image: var(--secondaryToolbarButton-selectTool-icon); + } + + #cursorHandTool::before { + mask-image: var(--secondaryToolbarButton-handTool-icon); + } + + #scrollPage::before { + mask-image: var(--secondaryToolbarButton-scrollPage-icon); + } + + #scrollVertical::before { + mask-image: var(--secondaryToolbarButton-scrollVertical-icon); + } + + #scrollHorizontal::before { + mask-image: var(--secondaryToolbarButton-scrollHorizontal-icon); + } + + #scrollWrapped::before { + mask-image: var(--secondaryToolbarButton-scrollWrapped-icon); + } + + #spreadNone::before { + mask-image: var(--secondaryToolbarButton-spreadNone-icon); + } + + #spreadOdd::before { + mask-image: var(--secondaryToolbarButton-spreadOdd-icon); + } + + #spreadEven::before { + mask-image: var(--secondaryToolbarButton-spreadEven-icon); + } + + #imageAltTextSettings::before { + mask-image: var(--secondaryToolbarButton-imageAltTextSettings-icon); + } + + #documentProperties::before { + mask-image: var(--secondaryToolbarButton-documentProperties-icon); + } + } +} + +#findbar { + --input-horizontal-padding: 4px; + --findbar-padding: 2px; + + width: max-content; + max-width: 90vw; + min-height: var(--toolbar-height); + height: auto; + position: absolute; + z-index: 30000; + cursor: default; + padding: 0; + min-width: 300px; + background-color: var(--toolbar-bg-color); + box-sizing: border-box; + flex-wrap: wrap; + justify-content: flex-start; + + > * { + height: var(--toolbar-height); + padding: var(--findbar-padding); + } + + #findInputContainer { + margin-inline-start: 2px; + + #findPreviousButton::before { + mask-image: var(--findbarButton-previous-icon); + } + + #findNextButton::before { + mask-image: var(--findbarButton-next-icon); + } + + #findInput { + width: 200px; + padding: 5px var(--input-horizontal-padding); + + /*#if !MOZCENTRAL*/ + &::-webkit-input-placeholder { + color: rgb(191 191 191); + } + /*#endif*/ + &::placeholder { + font-style: normal; + } + + .loadingInput:has(> &[data-status="pending"])::after { + display: inline; + visibility: visible; + inset-inline-end: calc(var(--input-horizontal-padding) + 1px); + } + + &[data-status="notFound"] { + background-color: rgb(255 102 102); + } + } + } + + #findbarMessageContainer { + display: none; + gap: 4px; + + &:has(> :is(#findResultsCount, #findMsg):not(:empty)) { + display: inline flex; + } + + #findResultsCount { + background-color: rgb(217 217 217); + color: rgb(82 82 82); + padding-block: 4px; + + &:empty { + display: none; + } + } + + #findMsg { + &[data-status="notFound"] { + font-weight: bold; + } + + &:empty { + display: none; + } + } + } + + &.wrapContainers { + flex-direction: column; + align-items: flex-start; + height: max-content; + + .toolbarLabel { + margin: 0 4px; + } + + #findbarMessageContainer { + flex-wrap: wrap; + flex-flow: column nowrap; + align-items: flex-start; + height: max-content; + + #findResultsCount { + height: calc(var(--toolbar-height) - 2 * var(--findbar-padding)); + } + + #findMsg { + min-height: var(--toolbar-height); + } + } + } +} + +@page { + margin: 0; +} + +#printContainer { + display: none; +} + +@media print { + body { + background: rgb(0 0 0 / 0) none; + } + + body[data-pdfjsprinting] #outerContainer { + display: none; + } + + body[data-pdfjsprinting] #printContainer { + display: block; + } + + #printContainer { + height: 100%; + } + + /* wrapper around (scaled) print canvas elements */ + #printContainer > .printedPage { + page-break-after: always; + page-break-inside: avoid; + + /* The wrapper always cover the whole page. */ + height: 100%; + width: 100%; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + #printContainer > .xfaPrintedPage .xfaPage { + position: absolute; + } + + #printContainer > .xfaPrintedPage { + page-break-after: always; + page-break-inside: avoid; + width: 100%; + height: 100%; + position: relative; + } + + #printContainer > .printedPage :is(canvas, img) { + /* The intrinsic canvas / image size will make sure that we fit the page. */ + max-width: 100%; + max-height: 100%; + + direction: ltr; + display: block; + } +} + +.visibleMediumView { + display: none !important; +} + +.toolbarLabel { + width: max-content; + min-width: 16px; + height: 100%; + padding-inline: 4px; + margin: 2px; + border-radius: 2px; + color: var(--main-color); + font-size: 12px; + line-height: 14px; + text-align: left; + user-select: none; + cursor: default; + box-sizing: border-box; + + display: inline flex; + flex-direction: column; + align-items: center; + justify-content: center; + + > label { + width: 100%; + } +} + +.toolbarHorizontalGroup { + height: 100%; + display: inline flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 1px; + box-sizing: border-box; +} + +.dropdownToolbarButton { + display: inline flex; + flex-direction: row; + align-items: center; + justify-content: center; + position: relative; + + width: fit-content; + min-width: 140px; + padding: 0; + background-color: var(--dropdown-btn-bg-color); + border: var(--dropdown-btn-border); + border-radius: 2px; + color: var(--main-color); + font-size: 12px; + line-height: 14px; + user-select: none; + cursor: default; + box-sizing: border-box; + outline: none; + + &:hover { + background-color: var(--button-hover-color); + } + + > select { + appearance: none; + width: inherit; + min-width: inherit; + height: 28px; + font: message-box; + font-size: 12px; + color: var(--main-color); + margin: 0; + padding-block: 1px 2px; + padding-inline: 6px 38px; + border: none; + outline: none; + background-color: var(--dropdown-btn-bg-color); + + > option { + background: var(--doorhanger-bg-color); + color: var(--main-color); + } + + &:is(:hover, :focus-visible) { + background-color: var(--button-hover-color); + color: var(--toggled-btn-color); + } + } + + &::after { + /* All matching images have a size of 16x16 + * All relevant containers have a size of 28x28 */ + position: absolute; + display: inline; + width: var(--icon-size); + height: var(--icon-size); + + content: ""; + background-color: var(--toolbar-icon-bg-color); + mask-size: cover; + + inset-inline-end: 4px; + pointer-events: none; + mask-image: var(--toolbarButton-menuArrow-icon); + } + + &:is(:hover, :focus-visible, :active)::after { + background-color: var(--toolbar-icon-hover-bg-color); + } +} + +#toolbarContainer { + --menuitem-height: calc(var(--toolbar-height) - 6px); + + width: 100%; + height: var(--toolbar-height); + padding: var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding); + position: relative; + box-sizing: border-box; + font: message-box; + background-color: var(--toolbar-bg-color); + box-shadow: var(--toolbar-box-shadow); + border-bottom: var(--toolbar-border-bottom); + + #toolbarViewer { + width: 100%; + height: 100%; + justify-content: space-between; + + > * { + flex: none; + } + + input { + font: message-box; + } + + .toolbarButtonSpacer { + width: 30px; + display: block; + height: 1px; + } + + #toolbarViewerLeft #numPages.toolbarLabel { + padding-inline-start: 3px; + flex: none; + } + } + + #loadingBar { + /* Define these variables here, and not in :root, to avoid reflowing the + entire viewer when updating progress (see issue 15958). */ + --progressBar-percent: 0%; + --progressBar-end-offset: 0; + + position: absolute; + top: var(--toolbar-height); + inset-inline: 0 var(--progressBar-end-offset); + height: 4px; + background-color: var(--progressBar-bg-color); + border-bottom: 1px solid var(--toolbar-border-color); + transition-property: inset-inline-start; + transition-duration: var(--sidebar-transition-duration); + transition-timing-function: var(--sidebar-transition-timing-function); + + .progress { + position: absolute; + top: 0; + inset-inline-start: 0; + width: 100%; + transform: scaleX(var(--progressBar-percent)); + transform-origin: calc(50% - 50% * var(--dir-factor)) 0; + height: 100%; + background-color: var(--progressBar-color); + overflow: hidden; + transition: transform 200ms; + } + + &.indeterminate .progress { + transform: none; + background-color: var(--progressBar-bg-color); + transition: none; + + .glimmer { + position: absolute; + top: 0; + inset-inline-start: 0; + height: 100%; + width: calc(100% + 150px); + background: repeating-linear-gradient( + 135deg, + var(--progressBar-blend-color) 0, + var(--progressBar-bg-color) 5px, + var(--progressBar-bg-color) 45px, + var(--progressBar-color) 55px, + var(--progressBar-color) 95px, + var(--progressBar-blend-color) 100px + ); + animation: progressIndeterminate 1s linear infinite; + } + } + } +} + +#secondaryToolbar { + #firstPage::before { + mask-image: var(--secondaryToolbarButton-firstPage-icon); + } + + #lastPage::before { + mask-image: var(--secondaryToolbarButton-lastPage-icon); + } + + #pageRotateCcw::before { + mask-image: var(--secondaryToolbarButton-rotateCcw-icon); + } + + #pageRotateCw::before { + mask-image: var(--secondaryToolbarButton-rotateCw-icon); + } + + #cursorSelectTool::before { + mask-image: var(--secondaryToolbarButton-selectTool-icon); + } + + #cursorHandTool::before { + mask-image: var(--secondaryToolbarButton-handTool-icon); + } + + #scrollPage::before { + mask-image: var(--secondaryToolbarButton-scrollPage-icon); + } + + #scrollVertical::before { + mask-image: var(--secondaryToolbarButton-scrollVertical-icon); + } + + #scrollHorizontal::before { + mask-image: var(--secondaryToolbarButton-scrollHorizontal-icon); + } + + #scrollWrapped::before { + mask-image: var(--secondaryToolbarButton-scrollWrapped-icon); + } + + #spreadNone::before { + mask-image: var(--secondaryToolbarButton-spreadNone-icon); + } + + #spreadOdd::before { + mask-image: var(--secondaryToolbarButton-spreadOdd-icon); + } + + #spreadEven::before { + mask-image: var(--secondaryToolbarButton-spreadEven-icon); + } + + #documentProperties::before { + mask-image: var(--secondaryToolbarButton-documentProperties-icon); + } +} + +@media all and (max-width: 840px) { + #sidebarContainer { + background-color: var(--sidebar-narrow-bg-color); + } + #outerContainer.sidebarOpen #viewerContainer { + inset-inline-start: 0 !important; + } +} + +@media all and (max-width: 750px) { + #outerContainer .hiddenMediumView { + display: none !important; + } + #outerContainer .visibleMediumView:not(.hidden, [hidden]) { + display: inline-block !important; + } +} + +@media all and (max-width: 690px) { + .hiddenSmallView, + .hiddenSmallView * { + display: none !important; + } + + #toolbarContainer #toolbarViewer .toolbarButtonSpacer { + width: 0; + } +} + +@media all and (max-width: 560px) { + #scaleSelectContainer { + display: none; + } +} diff --git a/frontend/public/pdf.js/web/viewer.html b/frontend/public/pdf.js/web/viewer.html new file mode 100644 index 0000000..87dfd37 --- /dev/null +++ b/frontend/public/pdf.js/web/viewer.html @@ -0,0 +1,841 @@ +๏ปฟ + + + + + + + + + PDF.js viewer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + + +
+
+ +
+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ +
+ +
+
+ + + + +
+
+
+
+ +
+ +
+ + + +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ + + +
+ +
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+
+ + +
+
+ +
+ File name: +

-

+
+
+ File size: +

-

+
+
+
+ Title: +

-

+
+
+ Author: +

-

+
+
+ Subject: +

-

+
+
+ Keywords: +

-

+
+
+ Creation Date: +

-

+
+
+ Modification Date: +

-

+
+
+ Creator: +

-

+
+
+
+ PDF Producer: +

-

+
+
+ PDF Version: +

-

+
+
+ Page Count: +

-

+
+
+ Page Size: +

-

+
+
+
+ Fast Web View: +

-

+
+
+ +
+
+ +
+
+ Choose an option + + Alt text (alternative text) helps when people canโ€™t see the image or when it doesnโ€™t load. + +
+
+
+
+ + +
+
+ + Aim for 1-2 sentences that describe the subject, setting, or actions. + +
+
+
+ +
+
+
+
+
+ + +
+
+ + This is used for ornamental images, like borders or watermarks. + +
+
+
+
+ + +
+
+
+ +
+
+ Edit alt text (image description) +
+
+
+
+
+
+ +
+ Short description for people who canโ€™t see the image or when the image doesnโ€™t load. +
This alt text was created automatically and may be inaccurate. Learn more
+
+
+ + +
+ +
+
+
+
+
+
+ Couldnโ€™t create alt text automatically + Please write your own alt text or try again later. +
+ +
+
+
+ + + +
+
+
+ + +
+
+ Image alt text settings +
+
+ Automatic alt text +
+
+
+ + +
+
+ Suggests descriptions to help people who canโ€™t see the image or when the image doesnโ€™t load. Learn more +
+
+
+
+ Alt text AI model (180MB) +
+ Runs locally on your device so your data stays private. Required for automatic alt text. +
+
+ + +
+
+
+
+
+ Alt text editor +
+
+ + +
+
+ Helps you make sure all your images have alt text. +
+
+
+
+ +
+
+
+ + + + This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use. + +
+
+ Add a signature +
+
+ + + +
+
+
+ +
+
+ + Draw your signature +
+
+ + +
+
+
+
+ +
+ Drag a file here to upload + + +
+
+
+
+
+ + + + + +
+ +
+
+ + + + Youโ€™ve reached the limit of 5 saved signatures. Remove one to save more. +
+
+ +
+ + +
+
+
+
+ + +
+
+ Edit description +
+
+
+ + + + + +
+ +
+
+ + +
+
+
+ + + +
+ Preparing document for printingโ€ฆ +
+
+ + 0% +
+
+ +
+
+ + + + +
+ + + +
+
+ + diff --git a/frontend/public/pdf.js/web/viewer.js b/frontend/public/pdf.js/web/viewer.js new file mode 100644 index 0000000..d3e99af --- /dev/null +++ b/frontend/public/pdf.js/web/viewer.js @@ -0,0 +1,318 @@ +/* Copyright 2016 Mozilla Foundation + * + * 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 { RenderingStates, ScrollMode, SpreadMode } from "./ui_utils.js"; +import { AppOptions } from "./app_options.js"; +import { LinkTarget } from "./pdf_link_service.js"; +import { PDFViewerApplication } from "./app.js"; + +/* eslint-disable-next-line no-unused-vars */ +const pdfjsVersion = + typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_VERSION") : void 0; +/* eslint-disable-next-line no-unused-vars */ +const pdfjsBuild = + typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_BUILD") : void 0; + +const AppConstants = + typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC") + ? { LinkTarget, RenderingStates, ScrollMode, SpreadMode } + : null; + +window.PDFViewerApplication = PDFViewerApplication; +window.PDFViewerApplicationConstants = AppConstants; +window.PDFViewerApplicationOptions = AppOptions; + +function getViewerConfiguration() { + return { + appContainer: document.body, + principalContainer: document.getElementById("mainContainer"), + mainContainer: document.getElementById("viewerContainer"), + viewerContainer: document.getElementById("viewer"), + toolbar: { + container: document.getElementById("toolbarContainer"), + numPages: document.getElementById("numPages"), + pageNumber: document.getElementById("pageNumber"), + scaleSelect: document.getElementById("scaleSelect"), + customScaleOption: document.getElementById("customScaleOption"), + previous: document.getElementById("previous"), + next: document.getElementById("next"), + zoomIn: document.getElementById("zoomInButton"), + zoomOut: document.getElementById("zoomOutButton"), + print: document.getElementById("printButton"), + editorFreeTextButton: document.getElementById("editorFreeTextButton"), + editorFreeTextParamsToolbar: document.getElementById( + "editorFreeTextParamsToolbar" + ), + editorHighlightButton: document.getElementById("editorHighlightButton"), + editorHighlightParamsToolbar: document.getElementById( + "editorHighlightParamsToolbar" + ), + editorHighlightColorPicker: document.getElementById( + "editorHighlightColorPicker" + ), + editorInkButton: document.getElementById("editorInkButton"), + editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"), + editorStampButton: document.getElementById("editorStampButton"), + editorStampParamsToolbar: document.getElementById( + "editorStampParamsToolbar" + ), + editorSignatureButton: document.getElementById("editorSignatureButton"), + editorSignatureParamsToolbar: document.getElementById( + "editorSignatureParamsToolbar" + ), + download: document.getElementById("downloadButton"), + }, + secondaryToolbar: { + toolbar: document.getElementById("secondaryToolbar"), + toggleButton: document.getElementById("secondaryToolbarToggleButton"), + presentationModeButton: document.getElementById("presentationMode"), + openFileButton: + typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC") + ? document.getElementById("secondaryOpenFile") + : null, + printButton: document.getElementById("secondaryPrint"), + downloadButton: document.getElementById("secondaryDownload"), + viewBookmarkButton: document.getElementById("viewBookmark"), + firstPageButton: document.getElementById("firstPage"), + lastPageButton: document.getElementById("lastPage"), + pageRotateCwButton: document.getElementById("pageRotateCw"), + pageRotateCcwButton: document.getElementById("pageRotateCcw"), + cursorSelectToolButton: document.getElementById("cursorSelectTool"), + cursorHandToolButton: document.getElementById("cursorHandTool"), + scrollPageButton: document.getElementById("scrollPage"), + scrollVerticalButton: document.getElementById("scrollVertical"), + scrollHorizontalButton: document.getElementById("scrollHorizontal"), + scrollWrappedButton: document.getElementById("scrollWrapped"), + spreadNoneButton: document.getElementById("spreadNone"), + spreadOddButton: document.getElementById("spreadOdd"), + spreadEvenButton: document.getElementById("spreadEven"), + imageAltTextSettingsButton: document.getElementById( + "imageAltTextSettings" + ), + imageAltTextSettingsSeparator: document.getElementById( + "imageAltTextSettingsSeparator" + ), + documentPropertiesButton: document.getElementById("documentProperties"), + }, + sidebar: { + // Divs (and sidebar button) + outerContainer: document.getElementById("outerContainer"), + sidebarContainer: document.getElementById("sidebarContainer"), + toggleButton: document.getElementById("sidebarToggleButton"), + resizer: document.getElementById("sidebarResizer"), + // Buttons + thumbnailButton: document.getElementById("viewThumbnail"), + outlineButton: document.getElementById("viewOutline"), + attachmentsButton: document.getElementById("viewAttachments"), + layersButton: document.getElementById("viewLayers"), + // Views + thumbnailView: document.getElementById("thumbnailView"), + outlineView: document.getElementById("outlineView"), + attachmentsView: document.getElementById("attachmentsView"), + layersView: document.getElementById("layersView"), + // View-specific options + currentOutlineItemButton: document.getElementById("currentOutlineItem"), + }, + findBar: { + bar: document.getElementById("findbar"), + toggleButton: document.getElementById("viewFindButton"), + findField: document.getElementById("findInput"), + highlightAllCheckbox: document.getElementById("findHighlightAll"), + caseSensitiveCheckbox: document.getElementById("findMatchCase"), + matchDiacriticsCheckbox: document.getElementById("findMatchDiacritics"), + entireWordCheckbox: document.getElementById("findEntireWord"), + findMsg: document.getElementById("findMsg"), + findResultsCount: document.getElementById("findResultsCount"), + findPreviousButton: document.getElementById("findPreviousButton"), + findNextButton: document.getElementById("findNextButton"), + }, + passwordOverlay: { + dialog: document.getElementById("passwordDialog"), + label: document.getElementById("passwordText"), + input: document.getElementById("password"), + submitButton: document.getElementById("passwordSubmit"), + cancelButton: document.getElementById("passwordCancel"), + }, + documentProperties: { + dialog: document.getElementById("documentPropertiesDialog"), + closeButton: document.getElementById("documentPropertiesClose"), + fields: { + fileName: document.getElementById("fileNameField"), + fileSize: document.getElementById("fileSizeField"), + title: document.getElementById("titleField"), + author: document.getElementById("authorField"), + subject: document.getElementById("subjectField"), + keywords: document.getElementById("keywordsField"), + creationDate: document.getElementById("creationDateField"), + modificationDate: document.getElementById("modificationDateField"), + creator: document.getElementById("creatorField"), + producer: document.getElementById("producerField"), + version: document.getElementById("versionField"), + pageCount: document.getElementById("pageCountField"), + pageSize: document.getElementById("pageSizeField"), + linearized: document.getElementById("linearizedField"), + }, + }, + altTextDialog: { + dialog: document.getElementById("altTextDialog"), + optionDescription: document.getElementById("descriptionButton"), + optionDecorative: document.getElementById("decorativeButton"), + textarea: document.getElementById("descriptionTextarea"), + cancelButton: document.getElementById("altTextCancel"), + saveButton: document.getElementById("altTextSave"), + }, + newAltTextDialog: { + dialog: document.getElementById("newAltTextDialog"), + title: document.getElementById("newAltTextTitle"), + descriptionContainer: document.getElementById( + "newAltTextDescriptionContainer" + ), + textarea: document.getElementById("newAltTextDescriptionTextarea"), + disclaimer: document.getElementById("newAltTextDisclaimer"), + learnMore: document.getElementById("newAltTextLearnMore"), + imagePreview: document.getElementById("newAltTextImagePreview"), + createAutomatically: document.getElementById( + "newAltTextCreateAutomatically" + ), + createAutomaticallyButton: document.getElementById( + "newAltTextCreateAutomaticallyButton" + ), + downloadModel: document.getElementById("newAltTextDownloadModel"), + downloadModelDescription: document.getElementById( + "newAltTextDownloadModelDescription" + ), + error: document.getElementById("newAltTextError"), + errorCloseButton: document.getElementById("newAltTextCloseButton"), + cancelButton: document.getElementById("newAltTextCancel"), + notNowButton: document.getElementById("newAltTextNotNow"), + saveButton: document.getElementById("newAltTextSave"), + }, + altTextSettingsDialog: { + dialog: document.getElementById("altTextSettingsDialog"), + createModelButton: document.getElementById("createModelButton"), + aiModelSettings: document.getElementById("aiModelSettings"), + learnMore: document.getElementById("altTextSettingsLearnMore"), + deleteModelButton: document.getElementById("deleteModelButton"), + downloadModelButton: document.getElementById("downloadModelButton"), + showAltTextDialogButton: document.getElementById( + "showAltTextDialogButton" + ), + altTextSettingsCloseButton: document.getElementById( + "altTextSettingsCloseButton" + ), + closeButton: document.getElementById("altTextSettingsCloseButton"), + }, + addSignatureDialog: { + dialog: document.getElementById("addSignatureDialog"), + panels: document.getElementById("addSignatureActionContainer"), + typeButton: document.getElementById("addSignatureTypeButton"), + typeInput: document.getElementById("addSignatureTypeInput"), + drawButton: document.getElementById("addSignatureDrawButton"), + drawSVG: document.getElementById("addSignatureDraw"), + drawPlaceholder: document.getElementById("addSignatureDrawPlaceholder"), + drawThickness: document.getElementById("addSignatureDrawThickness"), + imageButton: document.getElementById("addSignatureImageButton"), + imageSVG: document.getElementById("addSignatureImage"), + imagePlaceholder: document.getElementById("addSignatureImagePlaceholder"), + imagePicker: document.getElementById("addSignatureFilePicker"), + imagePickerLink: document.getElementById("addSignatureImageBrowse"), + description: document.getElementById("addSignatureDescription"), + clearButton: document.getElementById("clearSignatureButton"), + saveContainer: document.getElementById("addSignatureSaveContainer"), + saveCheckbox: document.getElementById("addSignatureSaveCheckbox"), + errorBar: document.getElementById("addSignatureError"), + errorCloseButton: document.getElementById("addSignatureErrorCloseButton"), + cancelButton: document.getElementById("addSignatureCancelButton"), + addButton: document.getElementById("addSignatureAddButton"), + }, + editSignatureDialog: { + dialog: document.getElementById("editSignatureDescriptionDialog"), + description: document.getElementById("editSignatureDescription"), + editSignatureView: document.getElementById("editSignatureView"), + cancelButton: document.getElementById("editSignatureCancelButton"), + updateButton: document.getElementById("editSignatureUpdateButton"), + }, + annotationEditorParams: { + editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"), + editorFreeTextColor: document.getElementById("editorFreeTextColor"), + editorInkColor: document.getElementById("editorInkColor"), + editorInkThickness: document.getElementById("editorInkThickness"), + editorInkOpacity: document.getElementById("editorInkOpacity"), + editorStampAddImage: document.getElementById("editorStampAddImage"), + editorSignatureAddSignature: document.getElementById( + "editorSignatureAddSignature" + ), + editorFreeHighlightThickness: document.getElementById( + "editorFreeHighlightThickness" + ), + editorHighlightShowAll: document.getElementById("editorHighlightShowAll"), + }, + printContainer: document.getElementById("printContainer"), + editorUndoBar: { + container: document.getElementById("editorUndoBar"), + message: document.getElementById("editorUndoBarMessage"), + undoButton: document.getElementById("editorUndoBarUndoButton"), + closeButton: document.getElementById("editorUndoBarCloseButton"), + }, + }; +} + +function webViewerLoad() { + const config = getViewerConfiguration(); + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) { + // Give custom implementations of the default viewer a simpler way to + // set various `AppOptions`, by dispatching an event once all viewer + // files are loaded but *before* the viewer initialization has run. + const event = new CustomEvent("webviewerloaded", { + bubbles: true, + cancelable: true, + detail: { + source: window, + }, + }); + try { + // Attempt to dispatch the event at the embedding `document`, + // in order to support cases where the viewer is embedded in + // a *dynamically* created