Bài viết kỹ thuật

PDFlibPas: Chuyển PDF sang Markdown và DOCX trong Delphi

PDFlibPas chuyển đổi nội dung PDF sang hai định dạng có thể chỉnh sửa mà không cần tự động hóa Office. ExportPageMarkdownExportDocumentMarkdown trả về Markdown ngữ nghĩa với tiêu đề được suy luận, danh sách có thứ tự và không thứ tự, cùng bảng dạng pipe, trong khi SaveDOCXToFileSaveDOCXToStream ghi ra một gói WordprocessingML chứa đoạn văn, tiêu đề, đánh số danh sách gốc, bảng được nhận diện, định dạng phông chữ, ngắt trang và ảnh PNG đã định vị

Cả hai đều chạy hoàn toàn bằng Pascal, trên máy chủ, không cần cài Word và không cần COM. Ràng buộc đó chính là lý do tính năng này tồn tại trong một thư viện PDF thay vì trong một công cụ desktop

Vì sao "PDF sang Word" thực sự khó?

Vì một trang PDF không chứa các đoạn văn. Nó chứa các toán tử hiển thị văn bản đặt các chuỗi glyph tại các tọa độ, theo bất kỳ thứ tự nào mà trình tạo đã ghi ra, không hề có nghĩa vụ chỉ ra rằng hai chuỗi thuộc cùng một câu, chứ đừng nói đến cùng một mục danh sách. Định dạng này được thiết kế để mô tả chính xác một trang in, và nó làm được điều đó bằng cách loại bỏ đi cấu trúc đã tạo ra trang đó

Vì vậy mọi bộ chuyển đổi đều phải tái dựng lại những gì trình tạo đã vứt bỏ. Việc gom dòng dựa vào khoảng cách theo chiều dọc và sự căn chỉnh theo đường cơ sở. Ranh giới đoạn văn dựa vào thay đổi khoảng cách và thụt lề. Một tiêu đề là một dòng có phông lớn hơn hoặc đậm hơn phần thân và tách biệt hẳn với phần tiếp theo. Một danh sách là một chuỗi đoạn văn bắt đầu bằng ký tự bullet hoặc mẫu số thứ tự. Một bảng là một lưới các khối văn bản có cạnh thẳng hàng theo cả hàng lẫn cột. Mỗi điều trên đều là một suy luận, và suy luận nghĩa là kết quả tốt trên các tài liệu tuân theo quy ước typography thông thường, và kết quả tầm thường trên các tài liệu không tuân theo

PDF có gắn thẻ là trường hợp ngoại lệ, và là một ngoại lệ lớn. Khi tài liệu mang theo cây cấu trúc, các vai trò đoạn văn, tiêu đề, danh sách và bảng được ghi lại thay vì bị đoán, đó là lý do công việc về khả năng truy cập được mô tả trong cấu trúc khả năng truy cập của PDF có gắn thẻ cũng mang lại lợi ích cho chất lượng chuyển đổi. Nếu bạn kiểm soát trình tạo, gắn thẻ cho đầu ra của mình là việc có đòn bẩy cao nhất bạn có thể làm cho bất kỳ ai sau này phải chuyển đổi nó

Xuất Markdown, từng trang một

Đường xử lý Markdown là lựa chọn nên dùng khi đích đến là một pipeline văn bản: một trang tài liệu, một chỉ mục tìm kiếm, một kho ngữ liệu truy xuất cho một trợ lý. Các tùy chọn là một bit mask: PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, với PDF_MARKDOWN_DEFAULT kết hợp cả ba

var
  Pdf: TPDFlib;
  Md: WideString;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.LoadFromFile('handbook.pdf', '');

    // Một trang, dưới dạng chuỗi
    Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);

    // Một dải trang, ghi luồng ra đĩa dưới dạng UTF-8 không có BOM
    Pdf.SaveMarkdownToFile('1-40',
      PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
      'handbook.md');
  finally
    Pdf.Free;
  end;
end;

Các mốc trang phát huy giá trị trong công việc truy xuất. Một đoạn văn bản mang theo số trang nó xuất phát có thể được trích dẫn chính xác, và một người đọc theo trích dẫn sẽ đến đúng nơi tuyên bố đó thực sự nằm. Hãy tắt chúng đi khi Markdown dành cho con người đọc, nơi ranh giới trang từ bố cục gốc chỉ là nhiễu

Các điểm vào dạng streaming quan trọng đối với tài liệu lớn. SaveMarkdownToStreamSaveMarkdownToFile ghi UTF-8 từng trang một và không lưu đệm toàn bộ đầu ra, nên một tài liệu hướng dẫn 900 trang không biến thành một chuỗi 900 trang trong bộ nhớ trước. Việc không có dấu thứ tự byte cũng là chủ ý: một BOM trên tệp Markdown gây rối cho không ít trình tạo trang tĩnh và công cụ diff

DOCX mà không cần Office trên máy

Bộ ghi DOCX tự tạo ra gói: các mục ZIP được ghi dưới dạng Deflate thô có kiểm tra CRC, các phần WordprocessingML, và các mối quan hệ liên kết chúng lại. Không có gì gọi vào Word, nghĩa là quá trình chuyển đổi chạy được trên một máy chủ headless, bên trong một tài khoản dịch vụ, trong một container, ở tất cả những nơi mà tự động hóa Office hoặc không được cấp phép, hoặc không ổn định, hoặc bị cấm

var
  Pdf: TPDFlib;
  Target: TFileStream;
begin
  Pdf := TPDFlib.Create;
  Target := TFileStream.Create('handbook.docx', fmCreate);
  try
    Pdf.LoadFromFile('handbook.pdf', '');
    Pdf.SaveDOCXToStream('1-40',
      PDF_DOCX_INCLUDE_IMAGES or PDF_DOCX_DETECT_HEADINGS or
      PDF_DOCX_PRESERVE_STYLES or PDF_DOCX_PRESERVE_PAGE_BREAKS,
      Target);
  finally
    Target.Free;
    Pdf.Free;
  end;
end;

Dữ liệu ảnh được ghi ra ngay khi từng trang được xử lý thay vì được gom lại rồi ghép vào cuối, nên bộ nhớ đỉnh chỉ tương ứng với một trang thay vì cả tài liệu. Thứ tự trang tường minh được giữ nguyên, và trang PDF đang được chọn sẽ được khôi phục sau đó, điều này quan trọng khi việc xuất chỉ là một bước trong một tác vụ dài hơn mà đã có sẵn một trang được chọn vì lý do khác

Đóng gói tất định mang lại lợi ích gì?

Khả năng tái tạo từng byte một. Hai lần chuyển đổi cùng một đầu vào với cùng tùy chọn sẽ tạo ra cùng một gói, nghĩa là bạn có thể băm đầu ra để phát hiện thay đổi, so sánh hai bản build của một tài liệu được tạo tự động, và cache một cách mạnh dạn mà không lo rằng cùng một đầu vào lại tạo ra một tạo phẩm khác nhau

Tự động hóa Office không thể hứa hẹn điều đó. Nó nhúng dấu thời gian, mã định danh phiên bản và siêu dữ liệu phụ thuộc vào máy, nên cùng một tài liệu được chuyển đổi hai lần lại khác nhau theo cách khiến việc băm trở nên vô nghĩa. Cùng lý lẽ đó cũng chi phối mã định danh tệp PDF tất định được bàn trong mã ID PDF tất định cho các bản build có thể tái tạo: khi đầu ra có thể tái tạo, việc xác minh trở thành một phép so sánh thay vì một cuộc kiểm tra

Đầu ra tốt ở đâu, và không tốt ở đâu

Hãy trung thực với người dùng của bạn về điều này, vì chất lượng chuyển đổi biến thiên theo đầu vào nhiều hơn là theo bộ chuyển đổi. PDF có gắn thẻ và các tài liệu kinh doanh được tạo sạch sẽ, hóa đơn, báo cáo, hợp đồng, chuyển đổi tốt: tiêu đề trở thành tiêu đề, bảng được giữ nguyên, danh sách đánh số lại đúng trong Word. Các bố cục học thuật hai cột chuyển đổi khá ổn nếu hình học cột đều đặn. Các bảng trải dài qua nhiều trang được ghép lại bằng suy luận và đôi khi bị tách rời. Các tài liệu quảng cáo được thiết kế công phu, nơi văn bản được đặt vì hiệu ứng thị giác chứ không theo thứ tự đọc, chuyển đổi kém, và không lượng suy luận nào khắc phục được điều đó

Tài liệu quét là một trường hợp hoàn toàn riêng biệt. Một trang chỉ là một ảnh lớn duy nhất không chứa đối tượng văn bản nào, nên không có gì để xuất cho đến khi tồn tại một lớp văn bản; đường xử lý OCR để tạo ra lớp đó là điều kiện tiên quyết, không phải một tùy chọn. Trước khi chạy một lô lớn, hãy lấy mẫu vài chục tệp đại diện và xem xét đầu ra, và cân nhắc liệt kê các phần tử trang trước, như mô tả trong tìm kiếm văn bản và liệt kê phần tử trang, để xem các trang đó thực sự chứa gì

Đối với các pipeline trợ lý và truy xuất, đường xử lý Markdown thường là đích tốt hơn: tiêu đề trở thành ranh giới đoạn cắt, bảng vẫn dễ đọc dưới dạng bảng pipe, và các mốc trang cho mỗi đoạn cắt một vị trí có thể trích dẫn. Đối với chỉnh sửa của con người, DOCX là câu trả lời, vì điều người dùng muốn không phải là văn bản mà là khả năng thay đổi nó

PDFlibPas là thư viện PDF cho Delphi, C++Builder và Lazarus, đi kèm giao diện DLL và ActiveX tương ứng, nên các lệnh xuất tương tự cũng khả dụng từ C#, C++ hoặc các trình host kịch bản. Tài liệu đầy đủ và bản dùng thử có tại trang thư viện PDF PDFlibPas cho Delphi