Bài viết kỹ thuật

Font Biến Thiên trong PDF: Tạo Thực Thể Tĩnh trong Delphi

PDF không có khái niệm về font biến thiên. Một font được nhúng vào file PDF là tập hợp đường nét cố định với số đo cố định, nên một font biến thiên phải được rút gọn thành một thực thể tĩnh trước khi có thể đưa vào tài liệu. HotPDF thực hiện việc tạo thực thể này ở bên trong: bạn kiểm tra các trục của font biến thiên, chọn tọa độ chẳng hạn như độ đậm 620 hoặc độ rộng 87.5, và thư viện sẽ ghi cố định các giá trị đó vào một chương trình font hoàn chỉnh, độc lập mà bất kỳ trình đọc PDF tuân thủ chuẩn nào cũng có thể render được

Lý do điều này quan trọng mang tính thực tế hơn là lý thuyết. Các xưởng đúc chữ ngày càng chuyển sang phát hành một file biến thiên thay vì hàng chục độ đậm tĩnh, và đội ngũ thiết kế lại chọn những giá trị mà không có thực thể được đặt tên nào cung cấp sẵn. Nếu không tạo thực thể, một bộ tạo báo cáo hoặc phải quay về thực thể mặc định, làm mất đi quyết định thiết kế, hoặc nhúng cả font biến thiên và hy vọng trình xem tôn trọng tọa độ trục mà nó không có cách nào biết đến, điều mà không trình đọc nào bắt buộc phải làm

Tạo thực thể thực chất phải dựng lại những gì?

Một font biến thiên OpenType lưu một đường nét mặc định cho mỗi glyph cộng với một tập hợp delta được đánh chỉ mục theo vị trí trong không gian thiết kế. Áp dụng một tọa độ trục không đơn giản là ghi một con số vào header; nó có nghĩa là duyệt qua bảng gvar, nội suy delta cho vị trí được yêu cầu, di chuyển các điểm, rồi tính lại mọi thứ được suy ra từ những điểm đó. HotPDF dựng lại đường nét glyph, bảng loca dạng dài, toàn bộ số đo ngang và dọc, hộp bao (bounding box) tổng thể của font và giá trị điều chỉnh checksum của sfnt

Điều quan trọng không kém là những gì bị loại bỏ. Một thực thể tĩnh không được giữ lại fvar, avar, gvar, HVAR, VVAR, MVAR, STAT hay cvar, và một DSIG đã lỗi thời cũng phải bị loại bỏ, vì các byte đã ký không còn tồn tại. Để sót lại bất kỳ cái nào trong số đó sẽ tạo ra một font tự nhận là biến thiên trong khi mang đường nét đã bị di chuyển từ trước, và những trình đọc thực sự áp dụng biến thể sẽ áp dụng chúng lần thứ hai

Điểm ma và cái bẫy áp dụng hai lần

Quy tắc tinh vi nhất trong toàn bộ quá trình liên quan đến số đo. Trong gvar, số lượng điểm của một glyph bao gồm các điểm đường nét, hoặc điểm thành phần đối với glyph tổng hợp, cộng thêm bốn điểm ma mã hóa khoảng cách bên trái (left side bearing), độ rộng bước tiến, và các giá trị tương đương theo chiều dọc của chúng. Bản thân những điểm ma này cũng chịu tác động của delta

Vì vậy khi một font có bảng gvar, HotPDF suy ra số đo ngang và dọc từ các điểm ma đã nội suy và không áp dụng thêm HVAR hay VVAR. Áp dụng cả hai là lỗi kinh điển: cùng một biến thể bị áp dụng hai lần và mọi độ rộng bước tiến ra kết quả rộng hơn một chút, biểu hiện thành văn bản dần trôi sang phải trên một dòng canh đều. Chỉ khi một font không có gvar thì thư viện mới ghi cố định kho lưu biến thể số đo trực tiếp vào hmtx hoặc vmtx

Hai chi tiết nữa giữ cho hình học luôn chính xác. Điểm ma không bao giờ tham gia vào nội suy đường viền, nên các điểm không được liệt kê rõ ràng cho một glyph đơn giản được suy ra bằng IUP theo từng đường viền, với các điểm ma bị loại trừ. Và glyph tổng hợp có delta của chúng được áp dụng vào offset thành phần dùng tham số XY, sau đó biên của thành phần con được tính lại theo đệ quy. Đệ quy đó bị giới hạn về độ sâu và được kiểm tra chu trình, vì một đồ thị thành phần độc hại hoặc chỉ đơn thuần bị hỏng có thể đệ quy vô hạn nếu không có giới hạn đó

Kiểm tra không gian thiết kế trước khi chọn

Lệnh gọi đầu tiên trong bất kỳ quy trình tạo thực thể nào là InspectVariableFont, hàm này báo cáo các trục và các thực thể được đặt tên mà xưởng đúc chữ đã định nghĩa. Bản ghi trục mang thẻ bốn byte, giá trị tối thiểu, mặc định và tối đa, cờ và một ID tên; thực thể được đặt tên mang một ID tên họ phụ (subfamily), cờ, một ID tên PostScript tùy chọn và một tọa độ cho mỗi trục:

var
  Pdf: THotPDF;
  Axes: THPDFVariableFontAxisArray;
  Instances: THPDFVariableFontNamedInstanceArray;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.InspectVariableFont('C:\Fonts\Inter.ttf', Axes, Instances) then
    begin
      for I := 0 to High(Axes) do
        Writeln(Format('%s  min=%.1f default=%.1f max=%.1f',
          [string(Axes[I].Tag), Axes[I].MinimumValue,
           Axes[I].DefaultValue, Axes[I].MaximumValue]));
      Writeln(Format('%d named instance(s) defined', [Length(Instances)]));
    end
    else
      Writeln('not a variable font - embed it as an ordinary TrueType face');
  finally
    Pdf.Free;
  end;
end;

Báo cáo khoảng giá trị trục rất quan trọng vì giá trị trục bị giới hạn (clamp) theo khoảng mà font khai báo, chứ không theo khoảng mà giao diện người dùng của bạn cung cấp. Một thanh trượt cho phép người dùng yêu cầu độ đậm 1000 trên một font có trục wght chỉ dừng ở 900 nên được sửa ở giao diện, không phải âm thầm sửa ở tầng font, nếu không đầu ra bản in sẽ khác với bản xem trước

Chọn tọa độ và phát hành tài liệu

Việc chọn trục có trạng thái và áp dụng cho những font được đăng ký sau đó. SetVariableFontAxis nhận một thẻ ASCII in được bốn byte và một giá trị hữu hạn, và từ chối bất cứ thứ gì khác bằng một ngoại lệ thay vì âm thầm bỏ qua. ClearVariableFontAxes đặt lại lựa chọn, và GetVariableFontAxisSelections báo cáo những gì đang chờ xử lý, điều này đáng ghi log trong các bộ máy tạo báo cáo nơi nhiều luồng mã có thể đã chạm vào cùng một đối tượng tài liệu. Bản thân họ font được chọn theo tên qua SetFont, giống hệt như bất kỳ mặt chữ TrueType nhúng nào khác:

begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;

    Pdf.SetVariableFontAxis('wght', 620);   // semibold, không phải thực thể được đặt tên
    Pdf.SetVariableFontAxis('wdth', 87.5);  // hơi thu hẹp
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // quay về thực thể mặc định
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Sự kiện OnVariableFontInstance được kích hoạt mỗi khi một thực thể được tạo ra và báo cáo các giá trị trục đã dùng, đây là cách rẻ nhất để chứng minh trong log rằng một file PDF cụ thể thực sự chứa những gì. Vì mỗi tập tọa độ riêng biệt tạo ra một chương trình font riêng biệt, hãy coi lựa chọn trục là một phần của khóa cache font; cơ chế cache được mô tả trong cache tập con font bền vững

Tạo thực thể tương tác thế nào với việc tạo tập con và tạo hình?

Tạo thực thể chạy trước khi tạo tập con, và thứ tự đó là đúng. Font đã tạo thực thể là một mặt chữ TrueType tĩnh bình thường, nên bộ tạo tập con thông thường xử lý nó như bất kỳ font nào khác: nó tính bao đóng glyph (glyph closure), giữ lại các glyph mà tài liệu thực sự dùng và loại bỏ phần còn lại. Điều cần lưu ý về tương tác là hai lựa chọn trục khác nhau của cùng một họ font là hai chương trình font khác nhau, nên một tài liệu pha trộn độ đậm 400 và độ đậm 620 sẽ nhúng hai tập con, không phải một mặt chữ dùng chung với hai thực thể

Về nguyên tắc, việc tạo hình không bị ảnh hưởng và đáng để kiểm tra trong thực tế. Các tính năng bố cục nằm trong GSUBGPOS, những bảng mà việc tạo thực thể giữ nguyên, nên ligature và biến thể kiểu chữ tiếp tục hoạt động như được mô tả trong biến thể kiểu chữ GSUB của OpenType. Điều thay đổi là vị trí đặt chữ: một thực thể thu hẹp có bước tiến hẹp hơn thực thể mặc định, nên bất kỳ bố cục nào đo văn bản trước khi tạo thực thể đều đã đo sai độ rộng. Đo với cùng lựa chọn trục mà bạn sẽ render, và sự sai lệch sẽ biến mất

Một lưu ý phòng vệ cuối cùng từ quá trình triển khai, hữu ích cho ai muốn mở rộng đường dẫn này. Các font không có số đo dọc vẫn đánh giá các đối số mảng động tại vị trí gọi trong Delphi, nên các mảng tại thời điểm phân tích luôn được cấp phát thay vì dựa vào kiểm tra HasVerticalMetrics để bỏ qua sớm một chỉ mục rỗng. Đây là loại chi tiết ở cấp ngôn ngữ có thể biến một nhánh trông có vẻ được bảo vệ thành lỗi truy cập bộ nhớ (access violation) đúng trên những font mà bạn chưa từng thử nghiệm

Hỗ trợ font biến thiên nằm trong cùng pipeline font với nhúng, tạo tập con và bao đóng glyph, được mô tả sâu hơn trong bao đóng tập con font và glyph đã tạo hình. Toàn bộ bộ tính năng typography cho Delphi và C++Builder được liệt kê trên trang thành phần HotPDF Delphi PDF