public PDFCatalog(PDFOutlines outlines, PDFPages pages) { _outlines = outlines; _pages = pages; }
/// <summary> /// Opens an existing PDF document. /// </summary> public static PDFDocument Open(Stream stream, string password, PDFDocumentOpenMode openmode, PDFPasswordProvider passwordProvider) { PDFDocument document; try { Lexer lexer = new Lexer(stream); document = new PDFDocument(lexer); document.State |= DocumentState.Imported; document.OpenMode = openmode; document.FileSize = stream.Length; // Get file version. byte[] header = new byte[1024]; stream.Position = 0; stream.Read(header, 0, 1024); document._version = GetPDFFileVersion(header); if (document._version == 0) { throw new InvalidOperationException(PSSR.InvalidPDF); } document.IrefTable.IsUnderConstruction = true; Parser parser = new Parser(document); // Read all trailers or cross-reference streams, but no objects. document.Trailer = parser.ReadTrailer(); if (document.Trailer == null) { ParserDiagnostics.ThrowParserException("Invalid PDF file: no trailer found."); // TODO L10N using PSSR. } Debug.Assert(document.IrefTable.IsUnderConstruction); document.IrefTable.IsUnderConstruction = false; // Is document encrypted? PDFReference xrefEncrypt = document.Trailer.Elements[PDFTrailer.Keys.Encrypt] as PDFReference; if (xrefEncrypt != null) { //xrefEncrypt.Value = parser.ReadObject(null, xrefEncrypt.ObjectID, false); PDFObject encrypt = parser.ReadObject(null, xrefEncrypt.ObjectID, false, false); encrypt.Reference = xrefEncrypt; xrefEncrypt.Value = encrypt; PDFStandardSecurityHandler securityHandler = document.SecurityHandler; TryAgain: PasswordValidity validity = securityHandler.ValidatePassword(password); if (validity == PasswordValidity.Invalid) { if (passwordProvider != null) { PDFPasswordProviderArgs args = new PDFPasswordProviderArgs(); passwordProvider(args); if (args.Abort) { return(null); } password = args.Password; goto TryAgain; } else { if (password == null) { throw new PDFReaderException(PSSR.PasswordRequired); } else { throw new PDFReaderException(PSSR.InvalidPassword); } } } else if (validity == PasswordValidity.UserPassword && openmode == PDFDocumentOpenMode.Modify) { if (passwordProvider != null) { PDFPasswordProviderArgs args = new PDFPasswordProviderArgs(); passwordProvider(args); if (args.Abort) { return(null); } password = args.Password; goto TryAgain; } else { throw new PDFReaderException(PSSR.OwnerPasswordRequired); } } } else { if (password != null) { // Password specified but document is not encrypted. // ignore } } PDFReference[] irefs2 = document.IrefTable.AllReferences; int count2 = irefs2.Length; // 3rd: Create iRefs for all compressed objects. Dictionary <int, object> objectStreams = new Dictionary <int, object>(); for (int idx = 0; idx < count2; idx++) { PDFReference iref = irefs2[idx]; if (iref.Value is PDFCrossReferenceStream xrefStream) { for (int idx2 = 0; idx2 < xrefStream.Entries.Count; idx2++) { PDFCrossReferenceStream.CrossReferenceStreamEntry item = xrefStream.Entries[idx2]; // Is type xref to compressed object? if (item.Type == 2) { //PDFReference irefNew = parser.ReadCompressedObject(new PDFObjectID((int)item.Field2), (int)item.Field3); //document._irefTable.Add(irefNew); int objectNumber = (int)item.Field2; if (!objectStreams.ContainsKey(objectNumber)) { objectStreams.Add(objectNumber, null); PDFObjectID objectID = new PDFObjectID((int)item.Field2); parser.ReadIRefsFromCompressedObject(objectID); } } } } } // 4th: Read compressed objects. for (int idx = 0; idx < count2; idx++) { PDFReference iref = irefs2[idx]; if (iref.Value is PDFCrossReferenceStream xrefStream) { for (int idx2 = 0; idx2 < xrefStream.Entries.Count; idx2++) { PDFCrossReferenceStream.CrossReferenceStreamEntry item = xrefStream.Entries[idx2]; // Is type xref to compressed object? if (item.Type == 2) { PDFReference irefNew = parser.ReadCompressedObject(new PDFObjectID((int)item.Field2), (int)item.Field3); Debug.Assert(document.IrefTable.Contains(iref.ObjectID)); //document._irefTable.Add(irefNew); } } } } PDFReference[] irefs = document.IrefTable.AllReferences; int count = irefs.Length; // Read all indirect objects. for (int idx = 0; idx < count; idx++) { PDFReference iref = irefs[idx]; if (iref.Value == null) { #if DEBUG_ if (iref.ObjectNumber == 1074) { iref.GetType(); } #endif try { Debug.Assert(document.IrefTable.Contains(iref.ObjectID)); PDFObject pdfObject = parser.ReadObject(null, iref.ObjectID, false, false); Debug.Assert(pdfObject.Reference == iref); pdfObject.Reference = iref; Debug.Assert(pdfObject.Reference.Value != null, "Something went wrong."); } catch (Exception ex) { Debug.WriteLine(ex.Message); // 4STLA rethrow exception to notify caller. throw; } } else { Debug.Assert(document.IrefTable.Contains(iref.ObjectID)); //iref.GetType(); } // Set maximum object number. document.IrefTable._maxObjectNumber = Math.Max(document.IrefTable._maxObjectNumber, iref.ObjectNumber); } // Encrypt all objects. if (xrefEncrypt != null) { document.SecurityHandler.EncryptDocument(); } // Fix references of trailer values and then objects and irefs are consistent. document.Trailer.Finish(); #if DEBUG_ // Some tests... PDFReference[] reachables = document.xrefTable.TransitiveClosure(document.trailer); reachables.GetType(); reachables = document.xrefTable.AllXRefs; document.xrefTable.CheckConsistence(); #endif if (openmode == PDFDocumentOpenMode.Modify) { // Create new or change existing document IDs. if (document.Internals.SecondDocumentID == "") { document.Trailer.CreateNewDocumentIDs(); } else { byte[] agTemp = Guid.NewGuid().ToByteArray(); document.Internals.SecondDocumentID = PDFEncoders.RawEncoding.GetString(agTemp, 0, agTemp.Length); } // Change modification date document.Info.ModificationDate = DateTime.Now; // Remove all unreachable objects int removed = document.IrefTable.Compact(); if (removed != 0) { Debug.WriteLine("Number of deleted unreachable objects: " + removed); } // Force flattening of page tree PDFPages pages = document.Pages; Debug.Assert(pages != null); //bool b = document.irefTable.Contains(new PDFObjectID(1108)); //b.GetType(); document.IrefTable.CheckConsistence(); document.IrefTable.Renumber(); document.IrefTable.CheckConsistence(); } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } return(document); }
internal PDFFormXObject(PDFDocument thisDocument, PDFImportedObjectTable importedObjectTable, XPDFForm form) : base(thisDocument) { Debug.Assert(importedObjectTable != null); Debug.Assert(ReferenceEquals(thisDocument, importedObjectTable.Owner)); Elements.SetName(Keys.Type, "/XObject"); Elements.SetName(Keys.Subtype, "/Form"); if (form.IsTemplate) { Debug.Assert(importedObjectTable == null); // TODO more initialization here??? return; } XPDFForm pdfForm = form; // Get import page PDFPages importPages = importedObjectTable.ExternalDocument.Pages; if (pdfForm.PageNumber < 1 || pdfForm.PageNumber > importPages.Count) { PSSR.ImportPageNumberOutOfRange(pdfForm.PageNumber, importPages.Count, form._path); } PDFPage importPage = importPages[pdfForm.PageNumber - 1]; // Import resources PDFItem res = importPage.Elements["/Resources"]; if (res != null) // unlikely but possible { #if true // Get root object PDFObject root = res is PDFReference ? ((PDFReference)res).Value : (PDFDictionary)res; root = ImportClosure(importedObjectTable, thisDocument, root); // If the root was a direct object, make it indirect. if (root.Reference == null) { thisDocument.IrefTable.Add(root); } Debug.Assert(root.Reference != null); Elements["/Resources"] = root.Reference; #else // Get transitive closure PDFObject[] resources = importPage.Owner.Internals.GetClosure(resourcesRoot); int count = resources.Length; #if DEBUG_ for (int idx = 0; idx < count; idx++) { Debug.Assert(resources[idx].XRef != null); Debug.Assert(resources[idx].XRef.Document != null); Debug.Assert(resources[idx].Document != null); if (resources[idx].ObjectID.ObjectNumber == 12) { GetType(); } } #endif // 1st step. Already imported objects are reused and new ones are cloned. for (int idx = 0; idx < count; idx++) { PDFObject obj = resources[idx]; if (importedObjectTable.Contains(obj.ObjectID)) { // external object was already imported PDFReference iref = importedObjectTable[obj.ObjectID]; Debug.Assert(iref != null); Debug.Assert(iref.Value != null); Debug.Assert(iref.Document == Owner); // replace external object by the already clone counterpart resources[idx] = iref.Value; } else { // External object was not imported earlier and must be cloned PDFObject clone = obj.Clone(); Debug.Assert(clone.Reference == null); clone.Document = Owner; if (obj.Reference != null) { // add it to this (the importer) document Owner.irefTable.Add(clone); Debug.Assert(clone.Reference != null); // save old object identifier importedObjectTable.Add(obj.ObjectID, clone.Reference); //Debug.WriteLine("Cloned: " + obj.ObjectID.ToString()); } else { // The root object (the /Resources value) is not an indirect object Debug.Assert(idx == 0); // add it to this (the importer) document Owner.irefTable.Add(clone); Debug.Assert(clone.Reference != null); } // replace external object by its clone resources[idx] = clone; } } #if DEBUG_ for (int idx = 0; idx < count; idx++) { Debug.Assert(resources[idx].XRef != null); Debug.Assert(resources[idx].XRef.Document != null); Debug.Assert(resources[idx].Document != null); if (resources[idx].ObjectID.ObjectNumber == 12) { GetType(); } } #endif // 2nd step. Fix up indirect references that still refers to the import document. for (int idx = 0; idx < count; idx++) { PDFObject obj = resources[idx]; Debug.Assert(obj.Owner != null); FixUpObject(importedObjectTable, importedObjectTable.Owner, obj); } // Set resources key to the root of the clones Elements["/Resources"] = resources[0].Reference; #endif } // Take /Rotate into account. PDFRectangle rect = importPage.Elements.GetRectangle(PDFPage.InheritablePageKeys.MediaBox); // Reduce rotation to 0, 90, 180, or 270. int rotate = (importPage.Elements.GetInteger(PDFPage.InheritablePageKeys.Rotate) % 360 + 360) % 360; //rotate = 0; if (rotate == 0) { // Set bounding box to media box. Elements["/BBox"] = rect; } else { // TODO: Have to adjust bounding box? (I think not, but I'm not sure -> wait for problem) Elements["/BBox"] = rect; // Rotate the image such that it is upright. XMatrix matrix = new XMatrix(); double width = rect.Width; double height = rect.Height; matrix.RotateAtPrepend(-rotate, new XPoint(width / 2, height / 2)); // Translate the image such that its center lies on the center of the rotated bounding box. double offset = (height - width) / 2; if (rotate == 90) { // TODO It seems we can simplify this as the sign of offset changes too. if (height > width) { matrix.TranslatePrepend(offset, offset); // Tested. } else { matrix.TranslatePrepend(offset, offset); // TODO Test case. } } else if (rotate == 270) { // TODO It seems we can simplify this as the sign of offset changes too. if (height > width) { matrix.TranslatePrepend(-offset, -offset); // Tested. } else { matrix.TranslatePrepend(-offset, -offset); // Tested. } } //string item = "[" + PDFEncoders.ToString(matrix) + "]"; //Elements[Keys.Matrix] = new PDFLiteral(item); Elements.SetMatrix(Keys.Matrix, matrix); } // Preserve filter because the content keeps unmodified. PDFContent content = importPage.Contents.CreateSingleContent(); #if !DEBUG content.Compressed = true; #endif PDFItem filter = content.Elements["/Filter"]; if (filter != null) { Elements["/Filter"] = filter.Clone(); } // (no cloning needed because the bytes keep untouched) Stream = content.Stream; // new PDFStream(bytes, this); Elements.SetInteger("/Length", content.Stream.Value.Length); }