2 điểm bởi GN⁺ 2023-09-05 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nếu muốn thay đổi nội dung văn bản trong PDF mà không dùng công cụ PDF thương mại, bạn cần giải nén các stream đã nén, lần theo mã hóa theo từng phông chữ, rồi thay văn bản gốc và đoạn thay thế theo cùng một cách
  • Nhiều PDF không lưu văn bản thực của tài liệu dưới dạng plain text, mà dưới dạng stream nén và các chuỗi trông giống hệ thập lục phân; có thể dùng qpdf --qdf --object-streams=disable để giải nén sang dạng gần với có thể chỉnh sửa
  • Các khối văn bản gồm những lệnh PDF như BT, Tf, Td, Tj, ET, và các giá trị như <002a004800570003003600480057> Tj có thể không phải là các code point Unicode đơn giản
  • Nếu PDF có ánh xạ ToUnicode theo từng phông chữ, bạn có thể dùng pdffonts để kiểm tra embuni có phải yes hay không, rồi lần theo tham chiếu như /ToUnicode 19 0 R để tìm bảng mã hóa
  • Do độ phức tạp của chuẩn PDF, cách này khó khái quát cho mọi tệp, nhưng với một số PDF cụ thể, bạn có thể tìm mã hóa của văn bản gốc và thay thế văn bản bằng find-and-replace

Giải nén stream văn bản PDF đã nén

  • Khi mở PDF bằng trình soạn thảo văn bản, bạn có thể đọc được một phần cấu trúc, nhưng không thấy văn bản thực của tài liệu
  • Nhiều PDF lưu dữ liệu văn bản ở dạng đã nén
  • Có thể dùng qpdf để giải nén và kiểm tra các stream văn bản đã nén
    • Trên Mac có homebrew formula
    • Trên các bản phân phối Linux cũng có thể dùng các công cụ liên quan
qpdf --qdf --object-streams=disable in.pdf out.pdf
  • Sau khi chỉnh sửa, bạn có thể nén lại stream
qpdf out-edited.pdf out-recompressed.pdf
  • Lệnh nén lại đã báo lỗi, nhưng tệp PDF kết quả vẫn đọc được trong Preview

Tìm văn bản bên trong các lệnh PDF

  • Ngay cả sau khi giải nén, văn bản vẫn nằm trong các khối lệnh PDF
  • Một khối văn bản cơ bản có dạng như sau
BT
  /Font_0 12 Tf
  288 720 Td
  <002a004800570003003600480057> Tj
ET
  • Theo PDF Reference, khối này thực hiện lần lượt các thao tác sau
    • Bắt đầu một đối tượng văn bản
    • Thiết lập phông chữ và kích thước sẽ dùng
    • Chỉ định vị trí bắt đầu trên trang
    • Vẽ glyph của chuỗi tại vị trí đó
    • Kết thúc đối tượng văn bản

Vì sao văn bản không đọc được ngay

  • Các giá trị như <002a004800570003003600480057> trông giống chuỗi thập lục phân, nhưng có thể không phải là danh sách code point Unicode đơn giản
  • PDF có nhiều cách chỉ định mã hóa, và có thể chứa mã hóa tùy chỉnh bên trong tệp
  • Những mã hóa như vậy phần lớn có thể được ánh xạ sang code point Unicode
  • Quy trình này giả định rằng PDF bạn đang xử lý có chứa mã hóa nhúng
    • Không đề cập cách xử lý các trường hợp khác

Kiểm tra phông chữ và ánh xạ Unicode

  • Mã hóa văn bản của PDF gắn với một phông chữ cụ thể
  • pdffonts hiển thị thông tin liên quan đến phông chữ và mã hóa bên trong PDF
$ pdffonts sample.pdf
name                                 type              emb sub uni prob object ID
------------------------------------ ----------------- --- --- --- ---- ---------
CLDQZB+TrebuchetMS,Bold              CID TrueType      yes yes yes           9  0
YQBAIZ+TrebuchetMS                   CID TrueType      yes yes yes          10  0
  • Các trường cần kiểm tra ở đây là
    • emb: nghĩa là phông chữ đã được nhúng vào PDF
    • uni: nghĩa là có ánh xạ Unicode
  • Nếu cả hai giá trị đều là yes, có khả năng tìm được ánh xạ cần thiết
  • Các định danh phông chữ như /Font_0 trong ví dụ có thể tương ứng với tên phông chữ trong kết quả của pdffonts, tùy trường hợp
    • Trong trường hợp đó, /Font_0 trỏ tới CLDQZB+TrebuchetMS,Bold

Lần theo bảng ánh xạ ToUnicode

  • Sau khi tìm được tên đầy đủ của phông chữ dùng trong văn bản, hãy tìm tên đó bên trong PDF
  • Ở một vị trí nhất định, bạn có thể tìm thấy tham chiếu như sau
/ToUnicode 19 0 R
  • Giá trị này trỏ tới ID đối tượng của bảng mã hóa
  • Sau đó, khi tìm 19 0 obj, có thể tìm được bảng tương ứng

Thay thế văn bản bằng bảng mã hóa

  • Phần cốt lõi của bảng ánh xạ có thể có dạng như sau
38 beginbfrange^M
<0036><0036><0053>^M
<0057><0057><0074>^M
<0044><0044><0061>^M
<0048><0048><0065>^M
<0050><0050><006D>^M
...
  • Ví dụ này ánh xạ phạm vi các điểm mã hóa tùy chỉnh sang các điểm Unicode
    • Ở đây, phạm vi chỉ gồm một ký tự
    • 0036 được ánh xạ sang điểm Unicode 0053
  • Nếu định dạng bảng khác, có thể tham khảo ToUnicode mapping file tutorial
  • Để chuyển đổi tự động, hãy dùng Python để biến bảng thành dictionary và viết các hàm mã hóa/giải mã đơn giản
  • Luồng thay thế cuối cùng như sau
    • Xác định giá trị mã hóa tùy chỉnh của văn bản hiện có cần thay
    • Mã hóa văn bản thay thế theo cùng cách mã hóa tùy chỉnh
    • Dùng find-and-replace trong PDF để thay giá trị cũ

1 bình luận

 
GN⁺ 2023-09-05
Ý kiến trên Hacker News
  • Đặc tả PDF khá là gồ ghề. Mẩu kiến thức lặt vặt mình thích nhất gần đây là khi render các phần tử chồng lên nhau, nó hỗ trợ tất cả chế độ hòa trộn layer của Photoshop
    Điều mình thích thứ hai là nội dung được nối thêm phía sau có thể sửa đổi nội dung phía trước, nên về mặt pháp chứng phải xem xét tất cả các phiên bản khác nhau được biểu diễn trong cùng một tệp
    Đây cũng là một ví dụ cho thấy sự phù phiếm của DRM. Đặc tả có mã hóa dựa trên mật khẩu, có thể đặt riêng mật khẩu “chủ sở hữu” và “người dùng”, đồng thời cũng có các trường bit như “cấm in”, “cấm sao chép văn bản”. Nhưng để đọc tài liệu thì cuối cùng vẫn phải giải mã, nên nếu mở PDF đã mã hóa bằng mật khẩu “người dùng” trong một công cụ không tuân thủ rồi lưu thành phiên bản không mã hóa, bạn sẽ có một bản tương đương có thể chỉnh sửa
    [1] Mục “More than just transparency” của https://blog.adobe.com/en/publish/2022/01/31/20-years-of-tra...
    [2] https://blog.didierstevens.com/2008/05/07/solving-a-little-p...
    [3] Trang 61 của https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
    [4] Ví dụ như một script dùng thư viện pypdf

    • Trong bối cảnh nó vốn là định dạng độc quyền, không được công khai rộng rãi cho mọi người, và được tạo ra vào thời mã hóa bị luật xuất khẩu kiểm soát chặt chẽ, kiểu bảo mật dựa vào che giấu như vậy rất phổ biến
      Một trong những hướng dẫn crack nổi tiếng thời đó là vá hàm kiểm tra quyền để gỡ DRM của trình đọc chính thức
    • Cách làm: https://www.cyberciti.biz/faq/removing-password-from-pdf-on-...
    • Các chế độ hòa trộn được hỗ trợ chẳng phải chỉ là chế độ tổng hợp Porter-Duff sao? Có thể trông hơi quá mức, nhưng nó tương ứng rất tốt với những gì các pipeline render khác cung cấp, và khá hữu ích trong việc giảm khối lượng công việc khi tạo PDF
    • Nếu đào sâu vào trường quyền hạn, bạn sẽ rơi vào một cái hố thỏ về cách một số trình tạo PDF không tuân thủ đặc tả, cũng như các cơ chế xử lý vòng tránh có thể có hoặc không có tùy từng trình đọc PDF hay thư viện
    • Công bằng mà nói, nếu muốn ngăn sao chép văn bản thì cách dễ nhất là bỏ ánh xạ ToUnicode của font. Khi đó mọi người sẽ phải tự tay dựng lại
  • Chủ đề này xuất hiện định kỳ; đa số nghĩ PDF là một định dạng nhị phân khó chọc vào, nhưng thực ra không phải vậy
    PDF là một đồ thị đối tượng gồm nhiều kiểu khác nhau, và bản thân các kiểu được mô tả rõ trong đặc tả chính thức. Có một thú vui khổ sở là đọc nó cho vui
    Điều tôi luôn khuyên là, giống tác giả, hãy chuyển PDF sang phiên bản không có dữ liệu nén. Công cụ ưa thích là mutool, dùng mutool clean -d in.pdf out.pdf là được. Sau đó khi lục lọi, bạn sẽ ngạc nhiên vì có thể lần theo nhiều phần hơn tưởng tượng
    Bài viết đã bỏ qua bước xem đối tượng trang và kiểm tra tài nguyên. Đó là nơi tên font dùng trong content stream được liên kết với đối tượng con thực tế
    Một phần quan trọng khác bị bỏ sót là hầu hết font được nhúng vào PDF dưới dạng tập con. Chỉ các glyph cần thiết được giữ lại trong font. Việc tái mã hóa có vẻ thường diễn ra ở đây, còn ToUnicode được giữ để cho phép sao chép văn bản hoặc tìm kiếm trong PDF. Đây là thứ tốt cho người dùng nếu có; theo kinh nghiệm của tôi thì nhìn chung nó tồn tại và khá đúng

    • Nếu “đọc cho vui” thì gần với masochist hơn là sadist. Nếu bạn tham gia viết đặc tả hoặc bắt người khác đọc nó thì mới là sadist
    • Thật đáng tiếc khi Adobe thiết kế một định dạng khó xử lý đến mức mọi người lại ngạc nhiên khi ai đó làm được một việc lẽ ra phải là cơ bản
      Ngay từ đầu, triết lý thiết kế muốn tạo ra một định dạng chỉ đọc đã sai rồi. Chẳng phải tính năng đầu tiên mọi người sẽ yêu cầu là quá rõ sao
    • Nếu thấy vui khi chịu đau thì là masochist, còn thích gây đau cho người khác thì là sadist. Người này có vẻ đang cố giúp mọi người, nên gần với vế trước hơn. Cảm ơn vì lời khuyên về mutool
    • Tôi đang phụ thuộc nhiều vào Amazon Textract để phân tích cú pháp PDF
      Không biết có ai có góc nhìn nào khác về cách xử lý tốt theo kiểu native mà không cần nhà cung cấp cloud không. Đặc biệt là khi xử lý bảng
  • Có thể làm như sau:
    Chuyển sang PostScript a.ps bằng pdf2ps a.pdf
    Chỉnh sửa trực tiếp PostScript bằng vim a.ps
    Chuyển lại sang PDF bằng ps2pdf a.ps
    Với các PDF phức tạp có JavaScript nhúng hay animation chẳng hạn, sau vòng chuyển đổi qua lại này đôi khi chúng không hoạt động đúng. Dù vậy, nếu là tài liệu bình thường thì cách này hoạt động khá tốt. Việc gỡ watermark, thay đổi từ ngữ và con số có thể làm dễ dàng, còn chỉnh khoảng cách thì khó hơn. Tất nhiên bạn cần biết PostScript ở một mức độ nào đó.

    • Ở một công ty xử lý thư tín nơi tôi từng làm việc trước đây, một phần công đoạn chuẩn bị in gần như được làm theo cách này. Thông thường chúng tôi tạo tài liệu để in bằng công cụ đúng nghĩa, nhưng đôi khi khách hàng nghĩ họ biết rõ hơn và gửi PDF tới; thường thì việc đó tốn công hơn và cũng dễ lỗi hơn
      Dù đầu ra có đúng đi nữa, chúng tôi vẫn cần sắp xếp lại trang, áp dụng barcode cho máy xử lý thư và phân loại thư, rồi tạo báo cáo. Thường chúng tôi dùng Perl và các công cụ khác để cào văn bản từ trang nhằm lấy địa chỉ; PS nhìn chung dễ hơn PDF rất nhiều, nhưng cũng có những trường hợp cực kỳ mong manh, chẳng hạn khi PDF “bảo mật” không có ánh xạ glyph đúng
      Trường hợp tệ nhất là khách hàng gửi các PDF tài liệu đơn lẻ, và khi gộp chúng lại thì số lượng font subset trong bản in tăng vọt, lấp đầy bộ nhớ máy in và làm nó chết. Khi tôi nghỉ việc đó, hình như vẫn chưa có công cụ hữu ích nào để hợp nhất/gộp các font subset xuất phát từ cùng một font gốc. Lẽ ra điều đó phải khả thi và sẽ rất hữu ích, nhưng tôi không có thời gian hay kiến thức để tìm hiểu
    • Nếu có thể đưa JavaScript và animation vào PDF, thì điều gì ngăn người ta tạo frontend bên trong nó? Tôi tò mò ranh giới những gì có thể làm với PDF
      Thành thật mà nói, có vẻ như chỉ những người viết malware mới hưởng lợi từ độ phức tạp của PDF
  • Có vẻ như một điểm quan trọng đã bị bỏ sót. Ở cuối PDF có một bảng lưu byte offset của nhiều đối tượng trong file, tức bảng tham chiếu chéo
    Nếu sửa bên trong file, các offset này thường sẽ thay đổi và làm hỏng file. Bài viết có vẻ chỉ quan tâm đến việc thay một chữ số bằng một chữ số khác, nên vị trí có thể đã không đổi
    Nhưng nói chung, nếu thêm/xóa/sửa ở giữa file thì phải tính lại bảng xref, vì vậy dùng thư viện sẽ dễ hơn rất nhiều so với chỉnh trực tiếp bằng trình soạn thảo văn bản

    • Vì thế mới decode bằng qpdf rồi encode lại sau đó. Khi ấy qpdf sẽ xử lý phần đó
      qpdf dường như tái cấu trúc lại cấu trúc PDF gốc và cố giữ nguyên cả số hiệu object, nhưng các offset thì được tính lại hoàn toàn
    • Tôi cho đó là phần kỳ lạ nhất trong đặc tả PDF. Binary và text bị trộn lẫn, còn byte offset lại được ghi bằng text
      Nếu một ngày nào đó các tác giả kể lại, tôi muốn đọc vì sao định dạng này lại thành ra như vậy. Có vẻ ban đầu họ định làm nó hoàn toàn ở dạng text, rồi sau đó quyết định binary hiệu quả hơn. Tuy nhiên, việc phải cố định độ dài các mục trong bảng xref cũng ăn khớp với giả định đó theo một cách hơi kỳ lạ
    • Theo kinh nghiệm của tôi, cách dễ nhất là cứ làm hỏng bảng xref rồi chạy thứ gì đó như mutool clean để sửa. Vì nó có thể được suy ra hoàn toàn từ nội dung nên an toàn
    • Nó khá giống với việc chỉnh sửa binary đã biên dịch
      Miễn là giữ nguyên độ dài, bạn có thể sửa binary tùy ý
      Một phần mềm tôi từng dùng trước đây có xác thực với server, nhưng mọi xử lý đều diễn ra ở client. Kiểu như client chạy SQL trực tiếp trên server, còn server chỉ kiểm tra xem client này có vượt quá số license đã mua hay không
      Tôi có thể đưa nó vào disassembler, tìm đoạn kiểm tra, đổi thẳng đoạn đó thành JMP rồi lấp phần trống còn lại bằng NOP
  • Có vẻ như một bước quan trọng bị thiếu khi dùng chế độ --qdf của qpdf. Sau khi chỉnh sửa xong, cần cho file đi qua tiện ích fix-pdf để tính lại toàn bộ object offset và dựng lại bảng tham chiếu chéo ở cuối file. Ngoại lệ là nếu bạn chỉ thay đổi tại chỗ mà không thêm hay xóa byte nào
    Top 3 sự thật thú vị về PDF là như sau

    1. Tài liệu PDF thường là file binary 8-bit, nhưng nếu dùng bộ lọc ASCII85 thì có thể tạo PDF văn bản thuần UTF-8 hợp lệ, kể cả có ảnh
    2. PDF cho phép cực kỳ nhiều tính năng kỳ quặc như đối tượng 3D, JavaScript, video trong đối tượng Flash nhúng, chú thích vô hình, v.v. PDF/A là một subset lành mạnh và an toàn hơn nhiều
    3. Đặc tả PDF cho phép viết widget, chẳng hạn form control, bằng rich text vốn là một subset của XHTML và CSS. Tuy nhiên tính năng này rất hiếm được hỗ trợ bên ngoài Adobe Reader chính thức
      [0] Ví dụ: https://lab6.com/2
    • Sau khi chỉnh sửa xong, chỉ cần cho nó đi qua qpdf không kèm tham số như đã giải thích ở phần đầu bài viết. Khi đó nó sẽ nén lại dữ liệu và tạo lại cả bảng xref. Không cần công cụ nào khác
  • Nếu muốn học thêm trong hành trình với PDF, bạn có thể đọc tài liệu mổ xẻ file mà tôi mới tổng hợp gần đây:
    https://pdfsyntax.dev/introduction_pdf_syntax.html

  • Điều mọi người thường bỏ qua về PDF là, ở một khía cạnh nào đó, nó gần với định dạng hình ảnh hơn là tài liệu Word. Trong biên tập tài liệu, tài liệu Word, PDF và hình ảnh giống như dự án DAW, MIDI và MP3 trong âm nhạc; cũng tương tự mã nguồn Java, bytecode JVM và mã máy x86 thuần trong phần mềm
    Mục đích chính của tệp PDF là cho biết một cách hoàn toàn rõ ràng cần hiển thị hoặc in gì, với số byte ít hơn rất nhiều so với một hình ảnh thực. Nó tận dụng việc tác giả tài liệu biết các mẫu cấu trúc của tài liệu, để nếu biểu diễn đúng cách thì có thể nén tốt hơn nhiều so với các thuật toán nén ảnh thực sự. Ví dụ, nếu có thể truy cập phông chữ thật, thì thay vì đưa mọi lần xuất hiện của ký tự vào như một phần của ảnh rồi trông chờ thuật toán nén tự giảm phần lặp lại, tốt hơn là nói “đặt các ký tự này ở các tọa độ này với khoảng cách này”. Ký tự nào thuộc về từ nào, thậm chí codepoint Unicode nào tương ứng với glyph nào của phông chữ, về cơ bản không quan trọng nếu mục tiêu chỉ là truyền tải hình ảnh tài liệu một cách hiệu quả
    Với một tài liệu có thể chỉnh sửa, không chỉ cách biểu diễn mà cấu trúc ngữ nghĩa của nội dung trở nên quan trọng hơn nhiều. Việc một chỗ ngắt cụ thể trong văn bản là nhiều dấu cách, là cột tiếp theo của bảng, hay là bố cục trang kỳ lạ do hình ảnh gây ra đều quan trọng. Việc phần văn bản ở cuối mỗi trang là do tác giả chèn nhiều lần, hay chỉ nhập một lần rồi đặt làm chân trang, cũng quan trọng. Nếu thêm một đoạn mới khiến bố cục trang phải thay đổi, thì việc đoạn cuối trang này là chú thích cuối trang nên không được tràn sang trang sau là điều quan trọng. Điều quan trọng là khi tiêu đề mục chuyển sang trang khác thì mục lục phải tự động cập nhật, chứ không được chỉ là văn bản do tác giả nhập thủ công
    Với máy in hoặc màn hình thì những điều này chẳng liên quan gì. Chỉ cần in hoặc hiển thị theo chỉ thị là được. Trong PDF, chú thích cuối trang, tiêu đề mục, chân trang và mục lục không cần phải là thứ gì đặc biệt; chúng cũng có thể chỉ là văn bản được định dạng vô nghĩa. Vì vậy, nếu cố dùng PDF cho mục đích không phải hiển thị hay in ấn thì không thể đạt độ chính xác 100%. Dĩ nhiên có những nỗ lực để bù đắp điều này, và chương trình tạo PDF có thể tự do đưa metadata mong muốn vào, nhưng đó không phải là bắt buộc
    Đây có thể không phải là mô hình tinh thần mà những người tạo ra PDF dự định, nhưng là một góc nhìn hữu ích để hiểu vì sao PDF có hình dạng như vậy

    • Mục tiêu ban đầu của PDF là tạo ra một bản sao trung thực để in có tính di động. Đúng nghĩa WYSIWYG. Dù gửi tệp PDF tới máy in laser, máy Linotype hay màn hình thì nó đều phải trông giống nhau
      Khi in bằng máy in PostScript thì nó trông chính xác như nhau, và nếu dùng phông Type 1 thì đôi khi còn tốt hơn
  • Người muốn làm việc này đang bỏ lỡ điểm cốt lõi của PDF. PDF là định dạng mô tả trang, nên nó không biểu diễn cấu trúc tài liệu mà chỉ biểu diễn các dấu vết hiển thị trên trang
    Đừng cố chỉnh sửa PDF; hãy chỉnh sửa tài liệu gốc đã tạo ra PDF

    • Khi nào mọi người ngừng gửi cho tôi những PDF mà tôi muốn chỉnh sửa, thì tôi cũng sẽ ngừng chỉnh sửa PDF
      Không hiểu sao việc gửi thẳng tài liệu được tạo ra để chỉnh sửa lại trở thành chuyện “không chuyên nghiệp”, và cuối cùng ta rơi vào tình cảnh này
    • PDF cũng hỗ trợ chức năng chứa thông tin cấu trúc tài liệu logic. Nó được gọi là Tagged PDF
      Đây là tùy chọn, nhưng được khuyến nghị vì khả năng tiếp cận. Ví dụ có PDF/UA. Xem các chương 14.7–14.8 của [1]
      [1] https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
    • Ở đây nói “không nên làm” cũng chẳng có nhiều ý nghĩa. Ngoài đời có rất nhiều tình huống buộc phải chỉnh sửa PDF bằng cách này hay cách khác
    • Một khó khăn là đánh dấu các hiệu đính cần được đưa ngược về tài liệu gốc. Bạn nhận bản in thử từ người dàn trang và phải đánh dấu để họ sửa
      Không thể trực tiếp thay đổi văn bản PDF. Vì người dàn trang sẽ không thấy thay đổi đó. Công cụ markup của Acrobat không đến nỗi tệ, nhưng không bằng thời dùng giấy và bút chì đỏ. Trừ khi dùng công cụ “pencil” của Acrobat. Tôi mong phần này được cải thiện
    • Nếu nó là “định dạng mô tả trang” và chỉ biểu diễn những gì hiển thị trên trang chứ không phải cấu trúc tài liệu, thì lẽ ra tên phải là Page Description Format chứ không phải Portable Document Format nhỉ
  • 20 năm trước tôi từng làm phụ trách bản in ở một tòa soạn báo. Chúng tôi có hai “máy in” bản kẽm Kodak trị giá một triệu đô, nhưng gọi là máy in thì không chính xác. Nếu tôi nhớ đúng, nhũ tương trên bản kẽm bị chiếu tia cực tím rồi tan trong bể hóa chất
    Thiết bị Kodak thường xuyên lỗi, và sếp tôi hay mở trực tiếp các tệp PostScript hoặc EPS để sửa phần header hoặc chỗ sai mà phần mềm dàn trang gửi ra. Sau đó mọi thứ lại chạy tốt. Nhân tiện, chiếc máy in offset cuộn khổng lồ của Đức của chúng tôi chạy Linux
    Tôi nhớ sếp tên là Bill. Ông ấy là một người tốt, từng đưa tôi, khi đó 17 tuổi, đi xem buổi diễn của Sigur Ros. Thế là có tới hai chuyện chẳng liên quan gì đến PDF rồi

  • Bài viết hay. Khoảng 5 năm qua, khi xây dựng DocSpring [1], tôi đã đọc khá nhiều đặc tả PDF nhưng vẫn có cảm giác mới chỉ chạm nhẹ bề mặt. qpdf là một công cụ tuyệt vời, và một công cụ khác tôi thích là RUPS [2], cho phép đào sâu vào cấu trúc PDF
    [1] https://docspring.com
    [2] https://github.com/itext/i7j-rups