Shaping văn bản trong thành phần PDFium đi qua một đối tượng cài được duy nhất. ConfigureTextShaper cài shaper mà mọi điểm vào shaping đều dẫn qua, thay thế và giải phóng thứ đang có ở đó; ActiveTextShaper trả về shaper đã cài và tạo mặc định nền tảng ở lần dùng đầu; ActiveTextShaperName báo backend nào đang sống; ClearTextShaper bỏ cài đặt và cho phép mặc định được tạo lại. Trên Windows, mặc định là TPdfUniscribeTextShaper. Dưới Free Pascal có TPdfHarfBuzzTextShaper, hàm gắn libharfbuzz lúc chạy để một thư viện còn thiếu là một tình trạng được báo cáo chứ không phải một thất bại nạp
Một giao diện, hai backend chia việc theo cách hoàn toàn khác nhau. Việc hiểu sự bất đối xứng đó là điều ngăn con đường di động tạo ra văn bản được shaping đúng nhưng định vị sai
Vì sao backend Windows là một lớp còn bản di động là ba mảnh?
Vì Uniscribe là bốn API giả làm một. ScriptItemize phân đoạn một chuỗi theo script và phân giải các mức hai chiều; ScriptShape ánh xạ ký tự thành glyph; ScriptPlace tính advance và offset; ScriptLayout xếp các run kết quả vào thứ tự thị giác. Một backend dựng trên nó vì vậy chẳng còn gì để thêm, đó là lý do shaper Windows là một lớp với một phương thức
HarfBuzz phủ hai cái giữa. Nó shaping và định vị một run mà caller đã quyết định sẵn hướng và script, và nó không có ý kiến gì về cách một đoạn văn tách thành các run hay các run đó xuất hiện theo thứ tự nào. Vì vậy backend di động cung cấp phần còn lại: thuật toán hai chiều phân giải các mức nhúng, các hàm Unicode HarfBuzz phân đoạn văn bản theo script, và các run được xếp theo thứ tự thị giác mà quy tắc L2 của UAX #9 tạo ra. Nửa hai chiều đồ sộ đủ để là một unit riêng, được mô tả trong bài viết về các mức nhúng UAX #9
Shaper không phân giải phông, và điều đó là có chủ đích
Uniscribe đọc nhị phân phông từ một device context GDI. Chẳng có đối tượng tương đương di động của điều đó, và việc bịa một cái bên trong một unit shaping nghĩa là quyết định, thay mặt mọi ứng dụng, rằng phông đến từ fontconfig, từ CoreText, từ một thư mục phông ứng dụng, hay từ một cơ sở dữ liệu. Vì vậy backend HarfBuzz nhận một resolver: một callback ánh xạ một tên phông tới các byte TrueType hay OpenType. Trả về False làm thất bại yêu cầu shaping giống như một phông GDI không đọc được làm thất bại nó trên Windows
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// Chính sách của bạn: fontconfig, CoreText, một thư mục phông
// ứng dụng, một CSDL
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// Quyền sở hữu chuyển cho unit; gọi một lần lúc khởi động,
// trước khi bất cứ thứ gì shaping văn bản
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Trên Delphi, mặc định nền tảng (Uniscribe) được tạo theo yêu cầu,
// nên chẳng cần cài đặt gì cả
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
Giữ việc khám phá phông bên ngoài shaper có một lợi ích thứ hai lộ ra trên máy chủ: cùng một tiến trình có thể shaping với một tập phông nhúng chẳng liên quan gì tới thứ được cài trên máy, đúng thứ bạn muốn khi đầu ra phải tái tạo được từng byte giữa các máy chủ. Thành phần cũng phơi một provider phông hệ thống máy chủ cho các trường hợp bạn thực sự muốn phông đã cài, được đề cập trong bài viết về provider phông hệ thống
Record kết quả trung lập với backend, và cluster là lý do
Cả hai backend đều điền cùng một TPdfShapedText: văn bản nguồn, tên phông, cỡ, byte phông, một mảng run, tổng bề rộng, số glyph và số ký tự logic. Mỗi TPdfShapedRun mang đoạn của nó trong văn bản nguồn, vị trí X thị giác, bề rộng, mức hai chiều và một cờ phải-sang-trái, cộng với các glyph của nó. Mỗi TPdfShapedGlyph mang một định danh glyph, một advance, các offset X và Y, và cluster mà nó thuộc về dưới dạng một điểm bắt đầu và một độ dài trong văn bản nguồn
Những trường cluster đó là điều khiến bản ghi dùng được chứ không chỉ có tính thông báo. Shaping không phải một ánh xạ một-một: một âm tiết Devanagari thành một glyph từ bốn ký tự, một ligature Ả Rập gộp hai ký tự, và một ký tự đơn có thể tạo ra vài dấu phụ. Không có đoạn cluster, bạn không thể đặt một caret, hit-test một cú nhấp, hay tô sáng một vùng chọn, vì bạn không thể nói glyph thuộc về những ký tự nào. Có chúng, phép tính là cục bộ và cùng một mã chạy cho cả hai backend
var
Shaped: TPdfShapedText;
R, G: Integer;
begin
if ShapePdfText(Line, 'Noto Sans Arabic', 14, ptdAuto, Shaped) then
for R := 0 to High(Shaped.Runs) do
begin
// Các run đã đến theo thứ tự thị giác với VisualX được điền sẵn
X := Shaped.Runs[R].VisualX;
for G := 0 to High(Shaped.Runs[R].Glyphs) do
begin
EmitGlyph(Shaped.Runs[R].Glyphs[G].GlyphID,
X + Shaped.Runs[R].Glyphs[G].OffsetX,
Shaped.Runs[R].Glyphs[G].OffsetY);
X := X + Shaped.Runs[R].Glyphs[G].Advance;
end;
end;
end;
Ngân sách thuộc về record tùy chọn
TPdfTextShapingOptions mang một hướng cộng ba trần: ký tự tối đa, glyph tối đa và run tối đa, với một hàm lớp Default điền các giá trị hợp lý. Các trần không phải hoang tưởng về đầu vào dị dạng; chúng là phép toán. Shaping mở rộng: một phông với phép thay thế ngữ cảnh hung hãn có thể phát nhiều glyph hơn ký tự đầu vào, và một đoạn văn so le script mỗi vài ký tự tạo ra một run cho mỗi lần chuyển. Một tài liệu được lắp ráp để tối đa hóa cả hai biến một chuỗi khiêm tốn thành một lần cấp phát lớn, và một dịch vụ shaping văn bản từ các PDF không tin cậy cần một giới hạn mà nó chọn chứ không phải một giới hạn mà máy áp đặt
Đặt hướng tường minh thay vì để chế độ tự động đáng làm bất cứ khi nào bạn đã biết nó. Chế độ tự động áp các quy tắc hướng đoạn để đoán từ ký tự mạnh đầu tiên, điều đúng cho văn bản tự do và sai cho một trường biểu mẫu mà hướng là thuộc tính của trường chứ không phải của giá trị ai đó gõ vào
Gắn lúc chạy, không phải phụ thuộc build
Backend HarfBuzz nạp thư viện một cách động. Đó là một quyết định triển khai với hậu quả thật: một binary chạy trên máy có HarfBuzz và trên máy không có, báo năng lực giảm trong trường hợp thứ hai thay vì không khởi động nổi. Với một thư viện phát hành cho các lập trình viên khác, đó là cách sắp xếp duy nhất khả thi, vì bạn không thể đòi hỏi mọi người tiêu dùng của một thành phần PDF phải sắm và khớp phiên bản một thư viện shaping mà họ có thể không cần
Quy tắc tương ứng cho caller là kiểm tra. ActiveTextShaper trả về nil khi nền tảng không có mặc định và chẳng cái nào được cấu hình, và điểm vào shaping báo điều đó như một shaper không khả dụng chứ không phải một thất bại shaping. Đó là hai vấn đề khác nhau và đáng nhận các thông điệp khác nhau: một là khoảng trống triển khai, kia là vấn đề phông hay văn bản
Cài một lần, trước khi bất cứ thứ gì shaping
Việc cài đặt thay thế và giải phóng shaper trước đó, nên gọi nó lặp đi lặp lại là an toàn nhưng vô nghĩa, còn gọi nó trong khi một thread khác đang shaping thì chẳng an toàn chút nào. Hãy làm lúc khởi động. Nếu bạn cần quay về mặc định nền tảng sau đó, hãy truyền nil, cũng là cách bạn gỡ một test double ở cuối một phép thử
Một khi backend đã được cài, đo lường và ngắt dòng hành xử như nhau trên cả hai nền tảng, vì chúng tiêu thụ các số đo run và glyph thay vì gọi nền tảng trực tiếp; mô hình ngắt dòng được mô tả trong bài viết về đo văn bản và ngắt từ. Các nền tảng và toolchain được hỗ trợ của thành phần được liệt kê trên trang sản phẩm PDFium Delphi component