Внутреннее устройство PDF Processing Engine

Внутреннее устройство PDF Processing Engine

Внутреннее устройство PDF-движка

Document, Page и Annotation классы, которые вы используете для повседневной обработки PDF, являются фасадом над более низкоуровневым пакетом aspose_pdf.engine. Движок реализует настоящую механику PDF: модель объектов COS (Carousel Object Structure), из которой построен каждый PDF-файл, парсер и писатель, преобразующие объекты COS в PDF-байты и обратно, синтез внешнего вида аннотаций, растеризацию страниц, внутренности кодеков шрифтов и изображений, а также криптографические примитивы, лежащие в основе шифрования документов и цифровых подписей. Большинству приложений никогда не требуется импортировать напрямую из aspose_pdf.engine — но это правильное место для поиска, когда нужны пользовательские инструменты, судебный анализ PDF или поведение, которое не раскрывается на высоком уровне API.


Создание внешнего вида для одной аннотации

Интерактивные аннотации, такие как квадраты, круги и штампы, не содержат автоматически обычный поток внешнего вида (/AP /N). Вызов Annotation.generate_appearance активирует внутренние механизмы синтеза внешнего вида движка, чтобы построить его по требованию из свойств аннотации.

from aspose_pdf import Document

doc = Document()
doc.pages.add()
ann = doc.pages[0].annotations.add(
    "Square", (100, 100, 200, 200), "", properties={"C": [1, 0, 0], "IC": [0, 1, 0]}
)

print(ann.has_appearance)  # False -- no appearance stream yet
ann.generate_appearance()
print(ann.has_appearance)  # True -- the engine synthesised one
print(b"1 0 0 RG" in ann.appearance_normal)  # True -- red stroke operator
print(b"0 1 0 rg" in ann.appearance_normal)  # True -- green fill operator

Пакетная генерация внешних видов по страницам и документам

AnnotationCollection.generate_appearances синтезирует внешние виды для каждой подходящей аннотации на странице за один вызов, пропуская подтипы, которые движок не умеет отрисовывать (например, Text):

from aspose_pdf import Document

doc = Document()
doc.pages.add()
page = doc.pages[0]
page.annotations.add("Square", (0, 0, 50, 50), "")
page.annotations.add("Circle", (60, 0, 110, 50), "")
page.annotations.add("Text", (0, 60, 20, 80), "")  # unsupported subtype -> skipped

print(page.annotations.generate_appearances())  # 2

Document.generate_appearances делает то же самое на каждой странице документа и является идемпотентным — второй вызов не оказывает эффекта, если внешние виды уже существуют:

from aspose_pdf import Document

doc = Document()
doc.pages.add()
doc.pages.add()
doc.pages[0].annotations.add("Square", (0, 0, 50, 50), "")
doc.pages[1].annotations.add(
    "Line", (0, 0, 50, 50), "", properties={"L": [0, 0, 50, 50]}
)

print(doc.generate_appearances())  # 2 -- one per page
print(doc.generate_appearances())  # 0 -- already generated, no-op

Преобразование аннотаций в статическое содержимое страницы

Document.flatten() рисует внешний вид каждой аннотации непосредственно в поток содержимого своей страницы (как вызов Do XObject) и затем удаляет сам объект аннотации, поэтому страница отображается идентично в просмотрщиках, полностью игнорирующих аннотации:

from aspose_pdf import Document

doc = Document()
doc.pages.add()
doc.pages[0].annotations.add(
    "Square", (100, 100, 200, 200), "", properties={"C": [0, 0, 0]}
)

doc.flatten()

После этого вызова поток содержимого страницы становится длиннее, чем был ранее (теперь он содержит встроенный квадрат), и doc.pages[0].annotations больше не содержит уплощённую аннотацию.


Получение ключей шифрования на основе пароля (Ревизия 4 / AES-128)

EncryptionUtils реализует вывод ключей обработчика безопасности PDF-стандарта и AES-CBC примитивов напрямую, независимо от Document API. Это полезно для пользовательских инструментов или судебного анализа зашифрованных PDF-файлов:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

file_id = os.urandom(16)
user_pwd = "mypassword"

# Derive the owner (O) and user (U) key material for Revision 4 (128-bit AES)
o_value = EncryptionUtils.compute_owner_key_v4("owner", user_pwd, 16, 4)
u_value, enc_key = EncryptionUtils.compute_user_key_v4(
    user_pwd, o_value, -4, file_id, 16, 4
)

# Encrypt data with the derived file-encryption key
plaintext = b"Confidential PDF content"
ciphertext = EncryptionUtils.encrypt_aes_cbc(enc_key, plaintext)

# Re-derive the key from the password before trusting it to decrypt
verified_key = EncryptionUtils.verify_password_v4(
    user_pwd, u_value, o_value, -4, file_id, 16, 4
)
print(verified_key is not None)  # True -- password matches

decrypted = EncryptionUtils.decrypt_aes_cbc(verified_key, ciphertext)
print(decrypted == plaintext)  # True

Шифрование необработанного содержимого с помощью AES-CBC

Для низкоуровневых задач EncryptionUtils.encrypt_aes_cbc() и decrypt_aes_cbc() работают напрямую с любым 16-, 24- или 32-байтовым ключом, полностью обходя вывод ключа на основе пароля:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

key = os.urandom(32)  # AES-256; 16 and 24-byte keys are also accepted
plaintext = b"Hello, PDF AES 256!"

ciphertext = EncryptionUtils.encrypt_aes_cbc(key, plaintext)
decrypted = EncryptionUtils.decrypt_aes_cbc(key, ciphertext)
print(decrypted == plaintext)  # True

Советы и лучшие практики

  • Предпочитайте высокоуровневый фасад Document, Page и Annotation для повседневной обработки документов. Пакет aspose_pdf.engine является внутренней реализацией, на которой построены эти классы— используйте его только когда вам нужны пользовательские инструменты, судебный анализ или поведение, которое фасад не раскрывает.
  • Согласуйте аргумент revision с обработчиком безопасности, который вы используете: compute_owner_key_v4/compute_user_key_v4 охватывают версии 2–4 (40- и 128-битные RC4/AES), тогда как compute_hash_v5 реализует алгоритм версии 5/6, используемый AES-256. Смешивание версий и длины ключа без предупреждения приводит к неверному ключу.
  • Document.generate_appearances и AnnotationCollection.generate_appearances являются идемпотентными— вызывайте их предосторожно перед рендерингом или уплощением документа, который вы не создавали.
  • Document.flatten() разрушителен: он удаляет каждую аннотацию, которую вставляет в содержимое страницы. Завершите редактирование остальных аннотаций сначала или работайте с копией.
  • Не каждый подтип аннотации имеет встроенный синтезатор внешнего вида— Text и Popup являются распространёнными примерами. Проверьте has_appearance после вызова generate_appearance(), вместо того чтобы предполагать, что он завершился успешно.

Распространённые проблемы

ПроблемаПричинаИсправление
EncryptionUtils.encrypt_aes_cbc/decrypt_aes_cbc вызывает ошибку “AES key must be 16, 24, or 32 bytes”Был предоставлен ключ неверной длиныСгенерируйте ключ с помощью os.urandom(16), os.urandom(24) или os.urandom(32)
EncryptionUtils.verify_password_v4 возвращает None вместо возбуждения ошибкиУказанный пароль не совпадает с полученными из документа значениями U/OЯвно проверьте наличие None перед передачей результата в decrypt_aes_cbc
Annotation.generate_appearance возвращает FalseПодтип аннотации не имеет встроенного синтезатора внешнего вида (например, Text или Popup)Предоставьте свои собственные байты appearance_normal, либо примите рендеринг по умолчанию в просмотрщике
Второй вызов Document.generate_appearances возвращает 0Вызов идемпотентен — аннотации, у которых уже есть has_appearance == True, пропускаютсяОжидаемое поведение, а не ошибка

FAQ

Нужно ли импортировать из aspose_pdf.engine для повседневной обработки документов?

Нет. Классы Document, Page и Annotation охватывают стандартные рабочие процессы с документами. Слой движка— это место, где реализовано поведение этих классов, и он наиболее полезен для пользовательских инструментов или прямого изучения внутренностей PDF.

В чем разница между Annotation.generate_appearance и AnnotationCollection.generate_appearances?

Первый синтезирует поток отображения для одной аннотации и возвращает bool. Второй делает то же самое для каждой подходящей аннотации в коллекции (аннотации страницы или, через Document.generate_appearances, каждую страницу в документе) и возвращает количество созданных отображений.

Почему методы вывода ключа принимают аргумент revision?

Обработчик безопасности стандарта PDF эволюционировал в различных версиях ISO 32000— Revision 2 использует 40-битный RC4, Revision 3/4 поддерживают 128-битный RC4 или AES, а Revision 5/6 (используется для AES-256) полностью используют другой алгоритм хеширования (compute_hash_v5). Аргумент revision выбирает, какую производную выполняют методы EncryptionUtils.

Могу ли я напрямую исследовать или создавать необработанные объекты PDF?

Да. aspose_pdf.engine.cos раскрывает модель объектов COS— PdfObject, PdfDictionary, PdfArray, PdfStream, PdfName и связанные типы— которые PdfCosWriter и PdfCosParser сериализуют в байты PDF и разбирают из них.

Где происходит рендеринг страниц в изображения?

Document.render_page возвращает RasterizedPage, объект уровня движка с методами to_png(), to_tiff() и save() для преобразования отрисованной страницы в файл изображения.


API Reference Сводка

Класс / МетодОписание:
Annotation.generate_appearance(force) -> boolСинтезировать поток обычного отображения для одной аннотации по запросу
AnnotationCollection.generate_appearances(force) -> intПакетно генерировать отображения для каждой подходящей аннотации на странице
Document.generate_appearances(force) -> intПакетно генерировать отображения для каждой подходящей аннотации в документе
Document.flatten() -> DocumentВстроить отображения аннотаций в содержимое страницы и удалить аннотации
Document.render_page(page_index, dpi, scale, background, antialias) -> RasterizedPageРастеризовать страницу через конвейер рендеринга движка
RasterizedPageОтрисованная страница в упакованном формате RGB, с to_png(), to_tiff() и save()
GeneratedAppearanceВнутренний результат синтеза внешнего вида: байты контента плюс любые требуемые ресурсы ExtGState/font
EncryptionUtilsAES-CBC/RC4 шифрование и вывод ключа стандартного обработчика безопасности PDF (ревизии 2–6)
PdfObjectАбстрактный базовый класс для каждого объекта COS (Carousel Object Structure)
PdfDictionary / PdfArray / PdfStreamКонкретные типы контейнеров COS, составляющие низкоуровневое дерево документа
PdfName / PdfNumber / PdfString / PdfBoolean / PdfNullПримитивные типы значений COS
PdfIndirectReferenceКосвенная ссылка COS (n g R) на другой объект
PdfCosWriterСериализует в памяти объект COS PdfDocument в PDF-байты
PdfCosParser / LazyPdfObjectStoreРазбирает PDF-байты в объекты COS, материализуя их по запросу
IncrementalUpdate / IncrementalWriterДобавляет секцию инкрементного обновления к существующему PDF вместо его полной перезаписи
SimplePdfНативное Python низкоуровневое представление документа, на котором построен высокоуровневый Document API
TextFragmentAbsorber / TextFragmentCollectionНизкоуровневое извлечение фрагментов текста над экземпляром SimplePdf
ImagePlacementAbsorber / ImagePlacementНайдите, сохраните, замените или скройте растровые изображения, размещённые на странице
SigningUtilsСоздавайте самоподписанные сертификаты и подписи PKCS#7/CAdES для цифровой подписи
DssMaterial / ChainResult / RevocationResult / TimestampInfoПоддержка проверки подписи: материалы DSS, результаты цепочки сертификатов, проверки отзыва и проверка тайм-стампа RFC3161
StandardFontsМетрики и кодировки для 14 стандартных шрифтов PDF
CidTextCodecКодировать и декодировать show-strings для составных (Type0) шрифтов
ShadingПример RGB-цвета для осевых, радиальных и основанных на функции затенений
Color / MatrixНизкоуровневые примитивы цвета и 2-D аффинных преобразований, используемые по всему движку

См. также:

 Русский