Bỏ qua để đến nội dung
getnextpdf.com

Kinh tế học của kích thước tệp PDF

Spec: ISO 32000-2, §7.5.7

Hai tệp PDF có thể trông giống nhau từng-điểm-ảnh trên màn hình mà chênh nhau gấp mười lần trên đĩa. Khác biệt gần như không bao giờ là nội dung bạn thấy; nó là cách tệp được lắp ráp bên dưới. Trang này là một chuyến tham quan kinh-tế-kích-thước: các byte của một tệp PDF thực sự đi đâu, và bốn đòn bẩy mà một tác giả chi ngân sách byte vào.

Nó là bạn đồng hành vì sao các tệp lớn của Streams and filters, trang bao quát cách một filter giải mã. Trang này thì bám vào ngân sách.

Kích thước tệp hiếm khi là một chỉ số phù phiếm. Nó là băng thông trên mỗi lần tải về, dung lượng lưu trữ trên mỗi lần lưu, và độ trễ trên mỗi lần xem trước. Một hóa đơn 12 MB lẽ ra nên là 400 KB không phải một vấn đề trang trí khi bạn tạo ra một triệu cái mỗi tháng — nó là một hóa đơn gấp ba mươi lần.

Phần bực bội là sự phình to thường vô hình. Tài liệu hiển thị đúng, mở tốt, in tốt. Chẳng có gì nói cho bạn biết rằng cùng cái hiện-vật đó lẽ ra có thể chỉ bằng một phần nhỏ kích thước, vì các byte bị lãng phí là thuộc cấu trúc, không phải thị giác. Để tìm chúng bạn phải nhìn vào ngân sách, không phải trang.

Hãy nghĩ về một tệp PDF như một ngân sách bạn chi vào bốn khoản mục.

  • Chi phí phụ trên mỗi đối tượng. Mỗi indirect object mang theo một phong bì N G obj / endobj, và được theo dõi bởi một cross-reference entry. Một tài liệu nhiều-trang có hàng nghìn đối tượng tí hon, và các phong bì cộng dồn lại. Object stream bỏ đi cái phong bì đó cho cả một nhóm đối tượng; mỗi đối tượng được đóng gói vẫn giữ cross-reference entry của riêng nó, nhưng dưới dạng một type-2 entry gọn nhẹ.
  • Byte ảnh. Với bất kỳ tài liệu nào có ảnh chụp hay ảnh quét, các ảnh chiếm ưu thế, và đòn bẩy lớn nhất duy nhất là lựa chọn filter — một codec không-mất-mát so với một codec ảnh có-mất-mát là khác biệt giữa megabyte và kilobyte.
  • Byte font. Một font được nhúng đầy đủ là hàng trăm kilobyte glyph mà bạn không bao giờ dùng. Subsetting chỉ giữ lại các glyph mà tài liệu thực sự vẽ.
  • Chỉ mục. Cross-reference table cho phép một trình đọc tìm mọi đối tượng tự thân nó có thể là một stream được nén thay vì văn bản thuần.

Làm đúng cả bốn và tệp nhỏ. Bỏ sót một và nó lấn át mọi thứ khác mà bạn đã làm tốt.

Bộ ghi của NextPDF là một bộ serialize streaming một-lượt: nó nối thêm các byte của mỗi đối tượng khi chúng được tạo ra và ghi một cross-reference entry in-use cổ điển cho mỗi cái. Mặc định đó nhanh, có thể đoán trước, và tạo ra một tệp byte-ổn-định — nhưng nó không phải bố cục nhỏ nhất có thể, và NextPDF trung thực về điều đó.

Một tệp PDF là một đồ thị các indirect object. Hầu hết chúng là các dictionary nhỏ: page node, annotation dictionary, structure-tree element, outline entry. Mỗi cái trả một khoản thuế cố định — các từ khóa obj / endobj, các số đối tượng và generation, và một cross-reference entry định vị nó. Trên một tài liệu có hàng nghìn đối tượng nhỏ, khoản thuế đó là một lát cắt đáng kể của tệp.

Một object stream gom nhiều trong số các đối tượng không-stream nhỏ đó vào một stream duy nhất và nén chúng cùng nhau (Spec: ISO 32000-2, §7.5.7). Phong bì obj / endobj được bỏ đi cho cả nhóm; các giá trị bên trong được lưu liền-kề-nhau không có từ khóa trên-mỗi-đối-tượng, rồi được deflate như một khối đơn lẻ — cái này cũng nén tốt hơn, vì bộ nén loại-bỏ-trùng-lặp giờ thấy tất cả các dictionary tương tự đó cùng một lúc. Cross-reference entry không biến mất — mỗi đối tượng được đóng gói vẫn cần một cái — nhưng nó co lại thành một type-2 entry nhị phân gọn nhẹ trong cross-reference stream (thêm về điều đó trong Đòn bẩy 2).

Trong NextPDF cái này được cung cấp bởi ObjectStreamPacker, một bộ hậu-xử-lý độc lập nhận một tệp PDF cross-reference-stream đã hoàn tất và ghi lại các đối tượng đủ điều kiện thành một /Type /ObjStm duy nhất. Các quy tắc nó tuân theo đến thẳng từ tiêu chuẩn: một đối tượng đủ điều kiện là một đối tượng generation-zero, không-stream, sau khi loại trừ các đối tượng đặc biệt phải vẫn định-địa-chỉ-trực-tiếp được. §7.5.7 cấm lưu một đối tượng stream bên trong một object stream, nên các đối tượng stream — content, font, ảnh — giữ các entry của riêng chúng; và ObjectStreamPacker còn từ chối thêm đối tượng cross-reference stream của chính tài liệu (nó được ghi lại) và /Encrypt dictionary, cả hai đều phải vẫn định-địa-chỉ-trực-tiếp được. Mọi thứ khác generation-zero và không-stream đều được đóng gói.

Object stream packing (ObjStm) — edition availability
EditionAvailability
CoreFull support in the open-source core via ObjectStreamPacker. It is opt-in: the single-pass writer’s default emits classic in-use entries, so output stays byte-identical unless you enable packing. The packer is deterministic, with its own reproducible golden baseline.
ProNot in this edition
EnterpriseNot in this edition

Opt-in là một lập trường có chủ ý, không phải một giới hạn đang trốn tránh. Đầu ra mặc định là byte-ổn-định và khớp với các golden baseline sẵn có; bật packing lên là chọn vào một bố cục khác, nhỏ hơn, tất định ngang nhau. Bạn chọn sự đánh đổi, và engine không bao giờ tự chọn sau lưng bạn.

Đòn bẩy 2 — chỉ mục cũng có thể là một stream

Phần tiêu đề “Đòn bẩy 2 — chỉ mục cũng có thể là một stream”

Một khi các đối tượng sống bên trong một object stream, chỉ mục trỏ vào chúng đổi hình dạng. Một trình đọc tìm một đối tượng được đóng gói qua một cross-reference entry được nén — một type-2 entry đặt tên cho object stream và chỉ số bên trong nó (Spec: ISO 32000-2, §7.5.8.3). Vì cả cross-reference tự thân là một /Type /XRef stream, chỉ mục cho hàng nghìn đối tượng được đóng-gói-nhị-phân và deflate thay vì được viết như các hàng văn bản thuần. Bản đồ co lại cùng với lãnh thổ.

ObjectStreamPacker xây lại đúng cái này: nó phát ra object stream đơn lẻ, rồi một cross-reference stream được ghi lại mang theo một type-1 entry gọn nhẹ cho mỗi đối tượng được giữ lại và một type-2 entry cho mỗi đối tượng được đóng gói, bảo toàn mọi số đối tượng để các reference sẵn có vẫn hợp lệ.

Với bất kỳ tài liệu nào có ảnh thật, đòn bẩy này lấn át các đòn bẩy khác. Các byte là cùng một bức ảnh; codec là ngân sách. Các tên filter sống trong bộ filter tiêu chuẩn (Spec: ISO 32000-2, §7.4), và lựa chọn giữa chúng là một quyết định về kích thước:

  • FlateDecode là không-mất-mát. Hoàn hảo cho line art, ảnh chụp màn hình, và bất cứ thứ gì có màu phẳng — và tai hại cho một ảnh chụp, nơi không-mất-mát nghĩa là từng byte của bản gốc.
  • DCTDecode là JPEG: có-mất-mát, và với ảnh chụp là lựa chọn đúng một cách áp đảo, thường giảm gấp mười lần cho một sụt giảm chất lượng chẳng ai để ý.
  • JPXDecode là JPEG 2000: nén wavelet với một đường cong chất-lượng/kích-thước khác, nhưng hỗ trợ không đồng đều và bị một số profile lưu trữ cấm.

Đây chính xác là nơi cách một filter giải mã quan trọng, và đó là việc của bài Streams and filters. Điểm về kinh tế thì hẹp hơn: một ảnh chụp được lưu không-mất-mát là nguyên nhân đơn lẻ phổ biến nhất của một tệp PDF to một cách không cần thiết, và không lượng object-stream packing nào cứu được một tệp mà trọng lượng thật của nó là một ảnh quét chưa-được-JPEG-hóa.

Một font được nhúng là một chương trình. Một font đầy đủ có thể là vài trăm kilobyte, vì nó mang theo mọi glyph mà nhà thiết kế typeface từng vẽ — hàng nghìn ký tự xuyên các chữ viết mà bạn sẽ không bao giờ dùng trong tài liệu này. Subsetting chỉ nhúng các glyph mà tài liệu thực sự vẽ, biến chương trình đó thành một phần nhỏ của chính nó. Một lá thư một-trang không cần cả một font có-khả-năng-CJK; nó cần vài chục glyph mà nó đặt. Cơ chế của việc các glyph được chọn và đánh-lại-chỉ-số là chủ đề riêng của chúng — xem Fonts: the hard part.

  1. Image bytesFor media-heavy files, the largest line item. The filter choice — lossless Flate vs a lossy image codec like DCT (or JPX in a lossy mode) — is the dominant size lever.
  2. Font bytesA full embedded font is mostly glyphs you never draw. Subsetting keeps only the used glyphs, often shrinking the program by an order of magnitude.
  3. Per-object overheadThousands of small dictionaries each pay an obj/endobj envelope plus a cross-reference entry. Object streams (ObjStm) drop the envelope for the whole group; each object keeps a compact type-2 cross-reference entry.
  4. The indexA compressed cross-reference stream with type-2 entries binary-packs and deflates the map of every object, replacing plaintext index rows.
Where the bytes go: the four levers an author spends a PDF's size budget on, in the order they typically dominate a real file.

Không có một API bịa đặt nào để trình bày ở đây, vì các quyết định kích thước quan trọng nhất được đưa ra trước khi các byte tới được bộ ghi — và đòn bẩy cấu trúc duy nhất mà NextPDF phơi ra là một opt-in đơn lẻ. Về mặt khái niệm, ngân sách đọc như thế này:

  • Đưa ảnh chụp vào dưới dạng JPEG để chúng nằm dưới DCTDecode, không phải được mã hóa lại không-mất-mát. Cái thắng lớn nhất là một lựa chọn về dữ liệu nguồn, không phải một cờ của bộ ghi.
  • Để bộ ghi subset các font được nhúng để chỉ các glyph được vẽ mới ship.
  • Với một tài liệu có nhiều đối tượng nhỏ, hãy chọn vào object-stream packing, nó định tuyến tệp đã hoàn tất qua ObjectStreamPacker để gom các đối tượng không-stream nhỏ và ghi lại cross-reference stream.

Đường mặc định — các entry in-use cổ điển, không ObjStm — là đường cơ sở đúng: tất định, byte-ổn-định, và dễ xác minh. Packing là sự nâng cấp được cân nhắc khi số đối tượng, chứ không phải trọng lượng ảnh, là cái đang làm phình tệp.

Cái bẫy là với lấy một nút “nén tệp PDF” và mong đợi nó sửa mọi thứ. Nén không phải một đòn bẩy; nó là bốn, và chúng không thay thế cho nhau. Object-stream packing không thể làm nhỏ một ảnh chụp — đó là việc của filter ảnh. Một JPEG hoàn hảo không thể bù cho một font bạn quên subset. Và không cái nào trong số đó giúp được nếu sự phình to thực sự là một ảnh quét 10 MB được lưu không-mất-mát vì chẳng ai chọn DCTDecode cho nó.

Hiểu lầm thứ hai là nhỏ hơn thì luôn luôn nghiêm ngặt là tốt hơn. Không phải vậy. Object stream không tương thích với linearization — bố cục fast-web-view ghim vị trí tuyệt đối của đối tượng để trang đầu stream vào sớm. Và một số profile lưu trữ hạn chế cả việc những filter ảnh nào được phép. Kích thước là một trục. Nó đánh đổi với streaming, sự tuân thủ lưu trữ, và tính tái lập, và điểm đúng trên đường cong phụ thuộc vào tài liệu dùng để làm gì.

Bốn đòn bẩy là kinh tế cấu trúc của kích thước tệp. Chúng không phải một bảo đảm “làm nó nhỏ hơn” phổ quát, và NextPDF không giả vờ là một bộ tái-tối-ưu cho các tệp PDF đến từ bên ngoài tùy ý.

ObjectStreamPacker là một tối ưu hóa nỗ-lực-tốt-nhất không bao giờ liều với tính đúng đắn. Nó từ chối — trả về đầu vào không đổi — khi tệp không phải là một tệp PDF cross-reference-stream, khi nó được mã hóa, khi nó mang một chữ ký số (việc bố trí lại các đối tượng sẽ dịch chuyển các dải byte mà một chữ ký bảo vệ), khi nó đã chứa các object stream, hoặc khi không có đối tượng nào đủ điều kiện để đóng gói. Nó phát ra đúng một object stream cho cả tài liệu; nó không chia thành một bộ /Extends, vốn nằm ngoài phạm vi và tuân thủ với các kích thước tài liệu mà NextPDF tạo ra.

Các đòn bẩy ảnh và font phần lớn là các quyết định về đầu vào. Bộ ghi của NextPDF không ngầm chuyển mã một bitmap không-mất-mát thành một image stream có-mất-mát — mã hóa lại một ảnh chụp thành DCTDecode hay JPXDecode là một quyết định mã-hóa-ảnh ở thượng nguồn được nêu rõ, không phải thứ bộ serialize làm sau lưng bạn — và nó cũng không thu hồi các glyph từ một font mà bên gọi yêu cầu nhúng đầy đủ. Các thắng lợi kích thước lớn nhất được đưa ra ở thượng nguồn của bộ serialize byte; việc của engine là không lãng phí ngân sách bạn mang tới cho nó.

  • Streams and filters — bạn đồng hành cách một filter giải mã; trang này cố ý bổ sung cho nó, không sao chép nó.
  • What a PDF actually is — mô hình indirect-object mà chi phí phụ trên-mỗi-đối-tượng của nó được đòn bẩy object-stream làm giảm.
  • The anatomy of a PDF file — cấu trúc cross-reference trở thành một stream được nén.
  • Fonts: the hard part — cách subsetting chọn và đánh-lại-chỉ-số các glyph mà một tài liệu vẽ.
  • Object stream (ObjStm) — một stream đơn lẻ giữ nhiều indirect object không-stream nhỏ, được nén cùng nhau, để phong bì obj / endobj được bỏ đi cho cả nhóm. Mỗi đối tượng được đóng gói vẫn giữ cross-reference entry của riêng nó, dưới dạng một type-2 entry gọn nhẹ. Opt-in trong NextPDF.
  • Indirect object — một đối tượng được đánh số trong đồ thị của một tệp PDF, được bọc trong một phong bì obj / endobj và được theo dõi bởi cross-reference index. Phong bì là chi phí phụ trên-mỗi-đối-tượng.
  • Cross-reference stream/Type /XRef stream lập chỉ mục cho mọi đối tượng, được đóng-gói-nhị-phân và deflate thay vì được viết như các hàng văn bản thuần.
  • Compressed (type-2) entry — một cross-reference entry trỏ vào một đối tượng sống bên trong một object stream, đặt tên cho stream và chỉ số bên trong nó.
  • Font subsetting — nhúng chỉ các glyph mà một tài liệu thực sự vẽ, thay vì cả typeface, làm nhỏ chương trình font được nhúng.
  • Lossless vs lossy filter — một codec không-mất-mát (FlateDecode) tái tạo từng byte; một codec ảnh có-mất-mát (DCTDecode, hay JPXDecode ở một chế độ có-mất-mát) loại bỏ chi tiết không-cảm-nhận-được để có một kết quả nhỏ hơn rất nhiều. (JPEG 2000 / JPX cũng có thể được cấu hình không-mất-mát.) Lựa chọn là đòn bẩy chiếm ưu thế trên một tệp nhiều-media.