PDFium Component điều khiển PDF optional content layer (OCG) trong Delphi qua hai method của TPdf: InspectOptionalContent liệt kê mọi layer cùng với visibility mà PDFium thật sự sẽ render, và SaveAsOptionalContentConfigured ghi một bản sao đã kiểm chứng trong đó các layer bạn chọn được bật hay tắt. Method thứ hai còn vô hiệu hóa các luật Usage và /AS mà nếu không sẽ lặng lẽ Undo mất chỉnh sửa của bạn. Cả hai làm việc trên tài liệu đang mở sẵn trong TPdf, nên không có parser thứ hai nào phải giữ đồng bộ với những gì viewer hiển thị
Yêu cầu thường đến từ một xưởng CAD hay GIS: bộ bản vẽ đi kèm kích thước, chú thích và khối tiêu đề trên các layer riêng, và khách muốn một bản sao ẩn phần kích thước trước khi chuyển cho nhà cung cấp. PDFium render optional content chuẩn xác, nhưng ABI công khai của nó không có hàm nào để liệt kê OCG, chọn một configuration hay lật trạng thái một layer. Vậy bạn xuống cấp object, sửa /OCProperties, lưu, nạp lại, và layer vẫn nguyên ở đó. Nguyên nhân nằm ở logic visibility của PDFium, và đáng hiểu nó trước khi đụng vào bất kỳ byte nào
Vì sao sửa /ON và /OFF không đổi những gì PDFium render?
Sửa hai mảng /ON và /OFF của dictionary configuration là chưa đủ, vì PDFium cho một trạng thái tường minh nằm trong dictionary /Usage của chính OCG thắng hai mảng đó, và một luật auto-state /AS lại có thể đè lên cả hai. ISO 32000-1 §8.11.4 mô tả configuration và usage dictionary như hai cơ chế tách rời; renderer của PDFium gộp chúng vào một quyết định duy nhất, và InspectOptionalContent tái hiện nó theo thứ tự này:
- Xuất phát từ
/BaseStatecủa configuration, nơi/ONvà/Unchangedcùng được tính là hiện hữu và chỉ/OFFlà ẩn - Áp mảng
/ONcủa configuration, rồi tới mảng/OFF, nên một group có mặt trong cả hai kết thúc bằng ẩn - Áp trạng thái Usage tường minh của group cho usage được yêu cầu, chẳng hạn
/Usage << /View << /ViewState /OFF >> >>, thứ đè lên mọi thứ phía trên - Coi một group mà
/Intentkhông chứa/Viewlẫn/Alllà hiện hữu, vì nó không tham gia vào visibility theo view intent - Cuối cùng chạy mảng
/AScủa configuration được chọn, mà các entry của nó cho sự kiện khớp đặt trạng thái của các group mà chúng liệt kê
Bước thứ ba là bước làm người ta bỏng tay. Một file được công cụ layout lưu thường mang /ViewState /ON trên mọi OCG, và PDFium khi đó phớt lờ mảng /OFF mà bạn chỉnh công phu: lần lưu thành công, file mở lại sạch sẽ, và layer vẫn tô lên trang. Với Print và Export, OcExplicitUsageState đọc PrintState hay ExportState trước và fallback về ViewState khi entry riêng không có mặt, nên một mình ViewState /ON cũng ghim layer cho cả in ấn. Marked content tham chiếu một OCMD (§8.11.2.2) sau đó được phân giải chống lại các kết quả per-group này, qua chính sách /P hay, khi có mặt, biểu thức visibility /VE
Làm sao liệt kê các layer mà PDFium thật sự hiển thị?
TPdf.InspectOptionalContent trả về một TPdfOptionalContentInventory trong đó mảng Groups mang số object, tên, intents, ba trạng thái Usage, ngôn ngữ, vùng zoom, cờ Locked, chỉ mục radio-group và EffectiveVisible đã tính của từng OCG. Method trước hết nhờ PDFium lưu tài liệu in-memory hiện tại, trải các object stream, rồi quét kết quả, nên các chỉnh sửa trước đó trong phiên cũng được phản ánh. Chỉ mục configuration 0 luôn là dictionary /D mặc định và các entry của /Configs theo từ chỉ mục 1; đối số mặc định -1 chọn chỉ mục 0. Một tài liệu thiếu /OCProperties khiến method trả False với lý do nằm trong ErrorMessage thay vì raise
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage mặc định là ocuView; -1 chọn configuration 0, dictionary /D
if not Pdf.InspectOptionalContent(Inv) then
begin
Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
Exit;
end;
Memo1.Lines.Add(Format('Configuration %d: %s',
[Inv.SelectedConfigurationIndex,
string(Inv.Configurations[Inv.SelectedConfigurationIndex].Name)]));
for G in Inv.Groups do
Memo1.Lines.Add(Format('obj %d %s visible=%s locked=%s radio=%d',
[G.ObjectNumber, string(G.Name),
BoolToStr(G.EffectiveVisible, True),
BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;
Mảng Memberships báo mọi OCMD cùng Policy của nó (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), text VisibilityExpression thô và EffectiveVisible riêng của nó. Vài luật biên là có chủ ý. /P mặc định là /AnyOn, và một OCMD không có group nào được tính là hiện hữu. Một tham chiếu tới số object không phải OCG đã biết được coi là hiện hữu thay vì làm hỏng cả biểu thức. Phép đánh giá /VE dừng ở độ sâu lồng 32 và coi mọi thứ sâu hơn là ẩn, điều giữ một biểu thức thù địch hay tự tham chiếu khỏi biến việc soi vào một stack overflow
Ghi trạng thái layer mới bằng SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured nhận một mảng record TPdfOptionalContentStateChange (số object group cộng Visible) và ghi một tài liệu mà configuration được chọn sinh ra đúng trạng thái đó. Configuration được chọn nhận /BaseState /ON cùng các mảng /ON và /OFF trọn vẹn phủ mọi group, và mỗi OCG đã có sẵn Usage dictionary nhận một ViewState tường minh (hay PrintState / ExportState, theo Options.Usage) khớp trạng thái mới của nó. Với TPdfOptionalContentConfigureOptions.Default, khóa /AS của configuration được chọn bị gỡ bỏ để một sự kiện open, print hay export không lật các layer quay lại
procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer);
var
Changes: TPdfOptionalContentStateChanges;
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
SetLength(Changes, 2);
Changes[0].GroupObjectNumber := DimensionsObj;
Changes[0].Visible := False;
Changes[1].GroupObjectNumber := NotesObj;
Changes[1].Visible := True;
// Configuration 0, ocuView, DisableAutomaticState và EnforceRadioGroups True
Options := TPdfOptionalContentConfigureOptions.Default;
if not Pdf.SaveAsOptionalContentConfigured('C:\Out\Drawing-NoDims.pdf',
Changes, Options, Report) then
raise Exception.Create('Layer update rejected: ' + Report.ErrorMessage);
Log(Format('%d of %d groups changed, %d Usage states rewritten, /AS removed: %s',
[Report.ChangedGroupCount, Report.GroupCount,
Report.UpdatedUsageStateCount,
BoolToStr(Report.RemovedAutomaticState, True)]));
end;
Đường ghi giữ output tự lưu của PDFium làm tiền tố từng-byte-một và chỉ nối thêm phần chủ configuration được viết lại cùng các object OCG mang Usage dictionary, theo sau là một section xref và trailer mới. Trước khi một byte nào chạm đích, kết quả được mở lại trong một TPdf riêng rẽ dưới chính sách nạp nghiêm ngặt, và method thất bại nếu bảng cross-reference không kiểm chứng được. Overload file đi xa hơn một bước: nó ghi vào một file tạm bên cạnh đích và chỉ thay thế đích sau khi kiểm chứng thành công, nên một cập nhật bị từ chối chẳng bao giờ để lại một bản vẽ viết dở. Đó là cùng cách sửa đổi tăng dần có kiểm chứng mà trình soạn name tree và number tree PDF trong PDFium Component dùng
Lần lưu configured từ chối làm những gì?
Lần lưu configured từ chối mọi thay đổi mà chính tài liệu cấm hay không thể biểu diễn an toàn, và mọi lần từ chối xảy ra trước khi đích bị đụng tới. Một số object không nằm trong /OCGs thất bại trắng trợn. Việc đổi một group nằm trong mảng /Locked của configuration thất bại, dù việc nhắc lại giá trị hiện tại của nó là được phép. Với EnforceRadioGroups bật, bất kỳ bộ /RBGroups nào sẽ kết thúc với nhiều hơn một thành viên hiện hữu bị từ chối thay vì lặng lẽ tắt các thành viên còn lại. Tài liệu được mã hóa bị từ chối vì các object tăng dần dạng plaintext không thể mang security handler đang kích hoạt. Tài liệu có chữ ký raise EPdfError trừ khi bạn truyền AllowSignedDocument = True, vì thay đổi những gì một trang hiển thị có thể làm vỡ vùng phủ chữ ký hay một chính sách chứng nhận
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // ghi /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // entry đầu của /Configs, không phải /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument vẫn là False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // file có chữ ký: chẳng gì được ghi vào Target
Result := False;
end;
end;
end;
Hãy nắm các đánh đổi trước khi nối nó vào một batch job. Bản sửa đổi được nối nằm trên đỉnh lần re-save trọn vẹn của PDFium, chứ không phải các byte file gốc của bạn, và đó chính là lý do input có chữ ký cần sự đồng ý tường minh. Phép viết lại cũng chuẩn hóa configuration được chọn về /BaseState /ON, nên một baseline /Unchanged hay /OFF của tác giả bị thay bằng các mảng tường minh cho cùng visibility kết quả. Việc bỏ /AS xóa luôn các mẹo chỉ-in-ấn như một layer watermark chỉ xuất hiện trên giấy; đặt DisableAutomaticState thành False để giữ các luật đó, chấp nhận rằng chúng có thể đè trạng thái bạn yêu cầu cho sự kiện đó. Mặt tích cực thì PDF/A-2 (ISO 19005-2 clause 6.9) và PDF/UA (ISO 14289-1 clause 7.10) đều cấm /AS trong configuration dictionary, nên output mặc định gỡ luôn một vấn đề mà PDF/A preflight validation với PDFium Component của bạn lẽ ra phải báo
Điều khiển layer đứng ở đâu trong một PDF viewer Delphi
Trong một viewer, điều khiển layer là một checklist dẫn dắt bởi inventory cộng một lần nạp lại kết quả đã lưu. Đổ checklist từ Groups, vô hiệu các entry đang Locked, coi các thành viên chung RadioGroupIndex là loại trừ lẫn nhau, và khi apply thì ghi ra một TMemoryStream rồi nạp ngược stream đó vào TPdf để view tô trạng thái mới. Phần nối giữa TPdf và TPdfView được trình bày trong dựng một PDF viewer giàu tính năng với PDFium VCL trong Delphi. Giấy phép, bản tải dùng thử và phần còn lại của bộ tính năng nằm trên trang sản phẩm PDFium Component for Delphi