PDFlibPas cho lập trình viên Delphi và C++Builder ba loại hành động để điều hướng rời khỏi trang hiện tại: GoToR (Go To Remote) mở một trang cụ thể trong một file PDF khác, GoToE (Go To Embedded) mở một file PDF được nhúng bên trong tài liệu hiện tại, và Launch chạy một chương trình ngoài hay mở một file thông qua shell hệ điều hành. Cả ba đều sống trong ISO 32000-1 §12.6.4, mục Action Types cũng định nghĩa hành động GoTo thường ngày, và mỗi cái mang cạm bẫy riêng của nó cho người bất cẩn: một số trang có ý nghĩa khác nhau tùy theo lệnh gọi nào xây dựng nó, một đích là một tên chứ không phải một đường dẫn file, và một cặp tham số chuỗi trông giống hệt nhau nhưng phục vụ hai trình xem khác nhau
Không điều nào trong số này là giả thuyết. Một gói tài liệu tham khảo kỹ thuật — một hướng dẫn chính, một PDF thông số kỹ thuật một nhà phân phối cập nhật theo lịch trình riêng, một tiện ích hiệu chuẩn được cài đặt cạnh cả hai — dựa chính xác vào loại kết nối xuyên tài liệu này: một tham chiếu chéo phải đáp xuống trang 5 của file thông số, một bảng dữ liệu đáng gửi kèm bên trong hướng dẫn thay vì bên cạnh nó, một liên kết chuyển thẳng đến công cụ hiệu chuẩn. Bài viết này là hình ảnh gương của đọc lại các hành động bookmark và chú thích từ một PDF hiện có: bài viết đó nói về việc tiêu thụ một hành động GoToR, Launch, hay GoToE mà một nhà sản xuất khác đã viết sẵn vào một file; bài viết này nói về việc xây dựng chính ba loại hành động đó từ đầu, gồm cả các quy tắc cấp trường mà PDFlibPas thực thi trước khi nó commit một byte nào
Ba cách để một hành động PDF rời khỏi trang hiện tại
PDFlibPas tách biệt điều hướng cục bộ khỏi mọi thứ khác tại khóa /S của hành động, và GoToR, GoToE, và Launch là ba subtype có đích nằm ngoài trang hiện tại: GoToR theo ISO 32000-1 §12.6.4.3, GoToE theo §12.6.4.4, và Launch theo §12.6.4.5, tất cả bên trong mục §12.6.4 Action Types rộng hơn cũng định nghĩa hành động GoTo thường ngày. Đích của một hành động GoTo thuần túy nêu tên một đối tượng trang đã tồn tại sẵn bên trong tài liệu, nên PDFlibPas có thể xác thực nó ngay lập tức; GoToR và GoToE không thể làm điều đó theo cùng cách, vì file ngoài thậm chí có thể không tồn tại trên máy này và số trang của một file nhúng không phải thứ tài liệu host theo dõi, nên cả hai mang theo một tham chiếu chưa giải quyết thay vì một liên kết cứng — một đặc tả file cộng một đích cho GoToR, một tên file nhúng cộng một trang đích cho GoToE — trong khi Launch hoàn toàn bỏ khái niệm đích và chỉ nêu tên thứ gì đó cho hệ điều hành chạy hoặc mở. Sự tách bạch đó thể hiện thành hai họ lệnh gọi ở phía ghi: các bộ dựng cấp cao, một-lần như AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, và AddLinkToLocalFile tạo một chú thích liên kết điểm-nóng trang và hành động của nó cùng nhau, bao phủ hầu hết các bố cục thực tế — một dòng văn bản hay một biểu tượng người đọc nhấp vào — trong khi các bộ thiết lập cấp thấp hơn như SetActionRemoteDestinationEx, SetActionLaunchOptions, và các đối tác AddActionNext* của chúng gắn hoặc thay thế một hành động trên thứ gì đó bạn đã giữ sẵn một handle: một bookmark hiện có, một trigger trường biểu mẫu, hay một sự kiện vòng đời cấp tài liệu hay cấp trang. Cả hai họ đều kết thúc bằng việc viết cùng hình dạng từ điển; sự khác biệt là bạn đang đứng ở đâu khi gọi chúng, và, như mục tiếp theo nói đến, một số trang có nghĩa là gì khi bạn làm vậy
Làm sao để xây dựng một liên kết GoToR mở một trang trong một file PDF khác?
Một hành động GoToR cần hai thứ — một đặc tả file và một đích bên trong file đó — và PDFlibPas phơi bày hai lệnh gọi khác nhau để cung cấp phần thứ hai, mỗi lệnh gọi có quy ước đánh số trang riêng của nó. AddLinkToFile và AddLinkToFileEx, các bộ dựng điểm-nóng trang cấp cao, xác thực tham số Page hoặc DestPage của chúng là lớn hơn không, cùng cách đánh số bắt-đầu-từ-1 mà PDFlibPas dùng ở khắp mọi nơi khác, bao gồm SelectPage. SetActionRemoteDestinationEx, bộ thiết lập cấp thấp hơn dùng để gắn hay thay thế một hành động GoToR trên thứ gì đó bạn đã có sẵn một handle, thay vào đó xác thực DestPage là lớn hơn hoặc bằng không và viết nó thẳng vào mảng đích tường minh của hành động không có điều chỉnh: nó muốn chỉ số trang thô, bắt-đầu-từ-không của tài liệu đích, cách đánh số ISO 32000-1 chỉ định cho một đích tường minh từ xa. Gọi bộ thiết lập cấp thấp với cùng số bạn sẽ đưa cho bộ dựng cấp cao và liên kết mở sớm một trang
var
Lib: TPDFlib;
ActionID: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('manual.pdf', '') = 1 then
begin
Lib.SelectPage(12);
// Page is 1-based here, same as SelectPage above: this opens
// the fifth page of specs.pdf.
Lib.AddLinkToFile(72, 700, 200, 16, 'specs.pdf', 5, 0, 0, 0);
// A later maintenance pass repoints the same link at a
// reorganized file. SetActionRemoteDestinationEx edits the
// action directly, and DestPage here is the zero-based index
// PDF itself uses for a remote explicit destination -- "the
// fifth page" is now 4, not 5.
ActionID := Lib.GetAnnotActionID(1);
Lib.SetActionRemoteDestinationEx(ActionID, 'specs-2026.pdf',
4, Ord(dkFit), 0, 0, 0, 0, 0, 0, -1);
end;
finally
Lib.Free;
end;
end;
Phần còn lại của các tham số SetActionRemoteDestinationEx cũng chữ nghĩa không kém. ValueMask là một tập bit — 1 cho trái, 2 cho trên, 4 cho phải, 8 cho dưới, 16 cho zoom — và PDFlibPas kiểm tra nó theo DestType trước khi viết bất cứ thứ gì: một đích dkFitR phải cung cấp đúng 15 (cả bốn cạnh, không zoom), dkFit và dkFitB phải cung cấp 0, và dkFitH/dkFitV chỉ chấp nhận một tọa độ liên quan của chúng. Các bit bạn để chưa đặt trong một mask khác hợp lệ không bị bỏ khỏi mảng; chúng được viết như một null PDF tường minh, thứ ISO 32000-1 coi là "giữ bất cứ giá trị nào trình xem đã có" cho tọa độ đó — một cách hợp pháp để nói "nhảy đến trang này, để yên zoom" thay vì một sơ suất. Bản thân zoom được lưu như một phân số của giá trị bạn truyền, nên một lệnh gọi yêu cầu 150 phần trăm đưa cho mảng một giá trị đã lưu 1.5, và phạm vi đầu vào hợp lệ là 0 đến 6400
Làm sao để liên kết đến một PDF được nhúng bên trong tài liệu của chính bạn?
AddLinkToEmbeddedPDF xây dựng hành động GoToE, và tham số đích của nó, EmbeddedFileName, là một tên chứ không phải một đường dẫn: nó phải khớp với chuỗi Title đã được truyền cho EmbedFile khi file đính kèm được tạo, vì title đó là khóa chữ nghĩa mà PDFlibPas lưu trong cây tên /EmbeddedFiles của tài liệu, và GoToE giải quyết bằng cách tra cứu tên đó, không bằng cách đụng vào hệ thống file lần nữa. Hàm chỉ kiểm tra rằng EmbeddedFileName không rỗng và TargetPage ít nhất là 1 — truyền một tên chưa bao giờ thực sự được nhúng và lệnh gọi vẫn trả về thành công, hành động vẫn được viết, và liên kết đơn giản thất bại giải quyết cho mỗi người đọc nhấp vào nó
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.NewPage;
// The Title argument becomes the key PDFlibPas stores in the
// document's EmbeddedFiles name tree -- that string, not
// "datasheet.pdf", is the target GoToE resolves against.
if Lib.EmbedFile('Datasheet', 'datasheet.pdf', 'application/pdf') = 1 then
Lib.AddLinkToEmbeddedPDF(72, 700, 200, 16, 'Datasheet', 3, 0, 0);
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Có hai sàn phiên bản xếp chồng ở đây, không phải một. EmbedFile cần PDF 1.4 cho cây tên /EmbeddedFiles, và AddLinkToEmbeddedPDF riêng biệt nâng sàn lên PDF 1.6 cho chính loại hành động GoToE, nên mức tối thiểu hiệu lực cho bất kỳ tài liệu nào dùng tính năng này là 1.6, không phải 1.4. Cũng lưu ý rằng TargetPage ở đây bắt-đầu-từ-1, quy ước PDFlibPas thông thường — một sự tương phản có chủ đích với DestPage bắt-đầu-từ-không mà mục trước vừa nói đến, và một lời nhắc rằng lược đồ đánh số trang nào áp dụng phụ thuộc vào loại hành động và lệnh gọi cụ thể, không phải theo một quy tắc chung. Từ điển đích của hành động cũng có thể mang một mục /R là C cho con hoặc P cho cha, hỗ trợ một chuỗi hai bước vào một file nhúng hay quay ngược ra container của nó, dù AddLinkToEmbeddedPDF chỉ bao giờ xây dựng hướng con, vì đó là hướng có ý nghĩa từ một tài liệu đang thực hiện việc nhúng thay vì đang bị nhúng
Hành động Launch: một FileName, hai đích chuỗi không thể thay thế cho nhau
SetActionLaunchOptions viết đích file của một hành động Launch vào hai khóa khác nhau từ một tham số FileName duy nhất, và hai khóa đó giữ hai loại chuỗi khác nhau. Khóa /F cấp cao nhận một từ điển đặc tả file, được xây dựng qua cùng phép chuyển đổi đường dẫn mà PDFlibPas dùng cho GoToR, đó là dạng khả chuyển mà ISO 32000-1 §7.11.3 định nghĩa cho một từ điển đặc tả file. Từ điển con /Win, khi PDFlibPas viết một, nhận khóa /F riêng của nó được đặt thành giá trị FileName thô đúng như được truyền vào, hoàn toàn không chuyển đổi, vì /Win /F được ghi chép trong ISO 32000-1 §12.6.4.5 như một chuỗi đường dẫn Windows thuần túy chỉ dành cho một trình xem Windows đọc. Truyền một đường dẫn khả chuyển, đã chuyển đổi sẵn mong đợi cả hai khóa kết thúc giống hệt nhau và bản sao /Win sẽ mang bất cứ thứ gì bạn đưa cho hàm, không chạm tới
var
Lib: TPDFlib;
ActionID: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('manual.pdf', '') = 1 then
begin
Lib.SelectPage(1);
Lib.AddLinkToLocalFile(72, 660, 220, 16, 'calibrate.exe', 0);
ActionID := Lib.GetAnnotActionID(1);
// Operation 0 leaves this as a normal open -- pass 1 to ask a
// Windows viewer to print instead. Parameters and
// DefaultDirectory only ever reach /Win /P and /Win /D, never
// the top-level /F.
Lib.SetActionLaunchOptions(ActionID, 'calibrate.exe',
'/silent /profile:default', 'C:\Tools\Calibration', 0, -1);
end;
finally
Lib.Free;
end;
end;
Hãy coi Launch là hành động ma sát cao nhất trong ba, vì toàn bộ mục đích của nó là chạy một chương trình hay mở một file bên ngoài sandbox PDF, và mọi trình xem chính thống đều đối xử với nó tương ứng. Enhanced Security của Adobe Acrobat chặn hoặc hỏi trước các hành động Launch theo mặc định trừ khi đích nằm trong một vị trí được tin cậy tường minh, và hầu hết các triển khai Acrobat doanh nghiệp để sự bảo vệ đó bật sẵn. Một hành động Launch trong một tài liệu đưa cho công chúng do đó không phải một trigger đáng tin cậy: lên kế hoạch cho việc nó bị chặn, được hỏi, hay âm thầm bị bỏ qua bởi bất cứ trình xem nào mở file, và để dành nó cho các môi trường đóng nơi bạn cũng kiểm soát các thiết lập tin cậy của trình xem — một kiosk nội bộ, một lượt triển khai doanh nghiệp được kiểm soát, một tài liệu không bao giờ rời khỏi một máy bạn quản lý
Cổng PDF/A: vì sao các lệnh gọi GoToR và Launch có thể trả về không
SetActionRemoteDestinationEx và SetActionLaunchOptions đều từ chối thẳng thừng khi tài liệu đích ở bất kỳ chế độ tuân thủ PDF/A nào: cả hai kiểm tra chế độ PDF/A của tài liệu như điều kiện đầu tiên của chúng và thoát với kết quả 0 trước khi đụng đến hành động, không ngoại lệ nào được ném ra. Điều này có chủ đích. Các giới hạn của PDF/A trên hành động tương tác loại trừ Launch cụ thể, vì việc trao cho một file lưu trữ khả năng chạy một chương trình tùy ý chính xác là loại hành vi phụ thuộc-môi-trường mà các định dạng lưu trữ dài hạn tồn tại để ngăn chặn, và PDFlibPas áp dụng cùng cổng bảo thủ đó cho bộ thiết lập go-to từ xa trong cùng đường code. Hệ quả thực tế dễ bị bỏ sót trong lúc phát triển: cùng một lệnh gọi hoạt động trên một PDF thông thường sẽ biên dịch, chạy, và âm thầm không làm gì trên một tài liệu được nạp với một mức tuân thủ PDF/A đã đặt, nên hãy kiểm tra giá trị trả về thay vì giả định thành công — một 0 ở đây không phải một lỗi đầu vào-lỗi-định-dạng, đó là thư viện từ chối một yêu cầu xung đột với tuyên bố tuân thủ riêng của tài liệu
GoToR, GoToE, và Launch khớp vào đâu trong một luồng công việc PDFlibPas lớn hơn
Ba loại hành động trong bài viết này không phải tất cả đều đến cùng nơi. Bài viết đồng hành về các trigger hành động vòng đời tài liệu và trang nói đến SetDocumentAction và SetPageAction, có thể gắn một hành động GoToR hay Launch vào một trigger như WillClose thông qua các hằng số PDF_ACTION_BUILDER_REMOTE_DESTINATION và PDF_ACTION_BUILDER_LAUNCH dùng chung — cùng bộ dựng cũng bao phủ một trigger URI hay JavaScript thuần túy. GoToE không có hằng số như vậy và hoàn toàn không có đường vào bộ dựng chung đó; AddLinkToEmbeddedPDF là cách duy nhất PDFlibPas xây dựng một cái, khiến nó hoàn toàn là một hành động điểm-nóng trang, không bao giờ là một trigger cấp tài liệu hay cấp trang. Nơi GoToR và Launch đến được bộ dựng chung, sự đánh đổi là quyền kiểm soát: nó xây dựng một GoToR chỉ trỏ vào một đích từ xa có tên và một hành động Launch chỉ với một tên file và tham số, trong khi việc đánh địa chỉ trang-và-loại-fit tường minh và các tùy chọn launch đặc thù Windows được nói đến trong bài viết này chỉ đến được thông qua SetActionRemoteDestinationEx và SetActionLaunchOptions trực tiếp
Có một thuộc tính an toàn đáng biết trước khi xây dựng một công cụ bảo trì quanh các bộ thiết lập này. SetActionRemoteDestinationEx và SetActionLaunchOptions xây dựng toàn bộ hành động thay thế trong một từ điển nháp trước, và chỉ xóa và copy các khóa /F, /D hay /Win, và /NewWindow lên hành động sống một khi bản sao nháp đó xác thực được — nên một lệnh gọi thất bại xác thực, dù từ một ValueMask ngoài phạm vi hay một FileName rỗng, để nguyên hành động gốc, và bất kỳ chuỗi /Next nào đã treo trên nó, hoàn toàn không đụng tới thay vì bị ghi đè nửa vời. Điều đó quan trọng vì các hành động GoToR và Launch đều có thể nằm bên trong một chuỗi /Next được xây dựng bằng AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, hay AddActionNextEx tổng quát hơn, cho phép một trigger duy nhất kích hoạt một mục log JavaScript rồi đến một bước nhảy từ xa theo trình tự. Việc xây dựng GoToR, GoToE, và Launch như mô tả ở đây là một phần của PDFlibPas, thư viện PDF gốc dành cho Delphi và C++Builder