Căn đều đầy đủ là bố cục làm cho một cột văn bản căn thẳng cả cạnh trái và cạnh phải, vẻ ngoài bạn mong đợi từ một cuốn sách in hoặc một báo cáo chính thức. Dễ mô tả và đáng ngạc nhiên dễ làm sai, vì câu trả lời cho câu hỏi "khoảng trắng thêm đi đâu" không giống nhau cho tiếng Anh và tiếng Nhật, và vì cách ngây thơ để đo mỗi dòng biến một trang nhanh thành một trang chậm. HotPDF cung cấp cho bạn căn đều nhận biết script qua một lệnh gọi bố cục hộp duy nhất, và bên dưới lệnh gọi đó là một bản sửa hiệu suất sách giáo khoa đáng hiểu theo cách riêng của nó
Bài viết này đi qua cả hai. Đầu tiên, quy tắc sắp chữ quyết định cách slack được phân phối cho các script có khoảng cách từ so với các script không có. Thứ hai, thay đổi đo lường cắt giảm chi phí mỗi trang của căn đều khoảng tám mươi lần mà không có sự khác biệt nào trong đầu ra. Cả hai đều quan trọng nếu bạn tạo tài liệu hàng loạt và muốn chúng đọc như sắp chữ thực thay vì đầu ra monospaced được kéo dài cho vừa
Căn đều đầy đủ thực sự yêu cầu gì
Một dòng văn bản được vẽ ở chiều rộng tự nhiên của nó hầu như không bao giờ đạt đến cạnh phải của cột. Luôn có một phần dư, slack, giữa nơi glyph cuối cùng kết thúc và nơi ranh giới cột nằm. Căn trái để slack đó ở bên phải. Căn phải di chuyển nó sang trái. Căn giữa chia đôi nó. Căn đều đầy đủ loại bỏ nó bằng cách mở rộng dòng cho đến khi cả hai cạnh gặp hộp, và cách trung thực duy nhất để làm điều đó là đẩy các glyph ra xa nhau từ bên trong
Quy tắc phân biệt căn đều tốt và căn đều xấu là nơi bạn đặt slack. Một script viết các từ với khoảng cách giữa chúng, như tiếng Anh và phần còn lại của họ Latin, có các đường nối tự nhiên tại mỗi khoảng cách giữa từ. Mở rộng những khoảng cách đó không nhìn thấy được vì người đọc đã chấp nhận rằng khoảng cách từ thay đổi. Một script viết không có khoảng cách từ, như chữ Hán Trung Quốc, kana Nhật Bản, hoặc Hangul Hàn Quốc, không có những đường nối như vậy. Ở đó slack phải được trải đều giữa các glyph kề nhau, đây là nguyên tắc mà các nhà sắp chữ Nhật gọi là kintou-waritsuke, khoảng cách đều. Đặt kéo dài khoảng cách từ kiểu Latin lên một dòng CJK, hoặc nhét tất cả slack vào một nơi mà một dòng CJK tình cờ chứa một khoảng cách, tạo ra các sông và khoảng cách đánh dấu đầu ra nghiệp dư
Cách HotPDF quyết định khoảng cách đi đâu
HotPDF đưa ra quyết định đó mỗi khoảng cách, không phải mỗi dòng. Khi nó căn đều một dòng, nó duyệt mỗi cặp glyph kề nhau và hỏi liệu có ranh giới có thể kéo dài giữa chúng không. Một ranh giới có thể kéo dài khi một trong hai phía là khoảng cách hoặc tab, trường hợp Latin, hoặc khi cả hai phía là các ký tự CJK có thể ngắt, trường hợp khoảng cách đều. Nó đếm các ranh giới đó, chia slack của dòng đều cho chúng, và thêm phần đó vào mỗi khoảng cách đủ điều kiện
Hệ quả rơi ra một cách tự nhiên. Một dòng tiếng Anh chỉ có các ranh giới có thể kéo dài tại các khoảng cách từ, vì vậy tất cả slack đến đó và các từ trải rộng trong khi các chữ cái bên trong mỗi từ giữ khoảng cách tự nhiên. Một dòng Han hoặc kana có ranh giới có thể kéo dài giữa hầu hết mọi cặp glyph, vì vậy slack phân phối đều qua toàn bộ dòng, chính xác là khoảng cách giữa glyph đều mà các script đó yêu cầu. Một dòng là một từ Latin dài không có khoảng cách bên trong không có ranh giới có thể kéo dài nào cả, vì vậy HotPDF để nó ở chiều rộng tự nhiên thay vì xé từ ra từng chữ. Logic tương tự xử lý các dãy Latin và CJK hỗn hợp trong một dòng mà không cần trường hợp đặc biệt, vì quyết định là cục bộ tại mỗi ranh giới
Một ranh giới bị loại trừ có chủ ý ở khắp nơi. Vị trí sau glyph cuối cùng của một dòng không bao giờ được coi là khoảng cách, vì kéo dài ở đó sẽ chỉ tái tạo lại phần dư bên phải, đây là điều ngược lại của căn đều
Tại sao dòng cuối cùng được để nguyên
Dòng cuối cùng của một đoạn văn là đặc biệt, và làm sai nó là lỗi căn đều phổ biến nhất. Dòng cuối của một đoạn thường ngắn, thường chỉ một vài từ, và kéo dài nó đến chiều rộng cột đầy đủ kéo những từ đó qua trang thành một hàng thưa thớt, bị vỡ. Sắp chữ đúng để dòng cuối ở chiều rộng tự nhiên của nó, căn trái
HotPDF phát hiện dòng cuối theo vị trí. Khi nó bọc văn bản thành các dòng, nó biết khi dòng vừa tách ra đến cuối chuỗi được cung cấp. Dòng cuối đó được phát với căn trái thuần và giữ chiều rộng tự nhiên. Mọi dòng trước nó được căn đều đến cả hai cạnh. Ngắt dòng cứng bạn viết vào văn bản được tuân theo như đã viết, vì vậy một dòng ngắn có chủ ý không bao giờ bị kéo dài. Người đọc thấy một khối văn bản hình chữ nhật gọn gàng mà dòng cuối kết thúc tự nhiên, đây là điều mắt mong đợi
Chi phí đo lường khiến căn đều chậm
Để căn đều một dòng bạn phải biết chiều rộng chính xác của nó, và bạn phải biết advance của mỗi glyph để bạn có thể đặt khoảng trắng thêm chính xác. Triển khai đầu tiên nhận được những con số đó theo cách rõ ràng. Nó đo toàn bộ dòng với một truy vấn chiều rộng Unicode đầy đủ, sau đó đo từng tiền tố để phục hồi advance của mỗi glyph bằng cách lấy hiệu. Đối với một dòng N glyph thì N+1 lần gọi vào engine đo lường, và mỗi lần gọi là một chuyến đi khứ hồi GDI đầy đủ, yêu cầu hệ điều hành shape và đo văn bản và trả lời lại
Mỗi dòng nghe có vẻ rẻ. Trên một trang thì không. Lấy một trang A4 dày đặc văn bản nội dung, khoảng bốn mươi lăm dòng mỗi dòng khoảng tám mươi ký tự. Tại N+1 chuyến đi mỗi dòng thì khoảng 81 chuyến đi cho mỗi dòng và khoảng 3.645 cho trang, hầu hết trong số đó đều dành để đo lại văn bản mà engine đã xem xét trước đó. Trên một công việc batch tạo ra hàng nghìn trang, chi phí đó chiếm ưu thế trong thời gian bố cục, và mỗi chuyến đi khứ hồi vượt qua ranh giới giữa quy trình của bạn và hệ thống đồ họa
Một lần gọi thay vì N cộng một
Cách sửa là loại thay đổi trông nhỏ và trả lợi nhuận lớn. GDI đã có thể báo cáo tổng chiều rộng của một chuỗi và vị trí của mỗi glyph trong một truy vấn duy nhất. HotPDF hiển thị điều đó qua GetWideCharAdvances, điền một mảng với advance tự nhiên của mỗi glyph, bao gồm kerning, và trả về tổng chiều rộng, trong một lần gọi thay vì N+1. Thủ tục căn đều, _HPDFEmitJustifiedWideLine nội bộ, yêu cầu tất cả các advance một lần, tính toán slack, phân phối nó qua các ranh giới có thể kéo dài, và phát dòng
Đối với cùng một trang A4, đo lường mỗi dòng giảm từ khoảng 81 chuyến đi xuống còn một, vì vậy trang giảm từ khoảng 3.645 chuyến đi xuống còn khoảng 45, gần tám mươi lần giảm. Đầu ra byte-cho-byte giống hệt nhau, vì không có gì về phép đo thay đổi ngoại trừ số lần nó được yêu cầu. Cùng engine GDI, cùng số liệu font, cùng thông tin kerning cung cấp cùng số. Chỉ có số chuyến đi khứ hồi giảm. Khi một phép đo đã đúng, tối ưu hóa đúng là ngừng yêu cầu nó nhiều lần, không phải xấp xỉ nó
Cách dòng đến trang
Khi slack được phân bổ, HotPDF phát dòng với ExtTextOut và một mảng advance mỗi glyph, mảng Dx. Mỗi mục là khoảng cách từ gốc của một glyph đến glyph tiếp theo, là advance tự nhiên của glyph đó cộng với phần slack của nó khi một ranh giới có thể kéo dài theo sau nó. Điều này ánh xạ trực tiếp lên mô hình hình ảnh PDF. Văn bản được định vị được viết với toán tử TJ, một mảng xen kẽ các dãy glyph với các điều chỉnh ngang rõ ràng, và các giá trị Dx trở thành chính xác những điều chỉnh đó. Đó là lý do tại sao khoảng trắng thêm đến giữa các glyph ở các vị trí sub-point chính xác thay vì được giả mạo bằng các ký tự đệm, và tại sao một dòng HotPDF căn đều đo đúng nếu một công cụ hạ nguồn đọc lại nó
Bạn không gọi ExtTextOut cho các đoạn văn căn đều. Điểm vào là WideTextOutBox, bọc một chuỗi Unicode vào một hộp và áp dụng căn chỉnh bạn yêu cầu. Nó chia văn bản thành các dòng vừa chiều rộng hộp, đặt mỗi dòng xuống chiều cao hộp, và trả về số ký tự nó quản lý để vừa trước khi hết chỗ dọc. Căn chỉnh được chọn bởi enum căn đều
type
THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);
Ba cái đầu là căn trái, căn giữa, và căn phải tự giải thích. Cái thứ tư, jtJustify, là căn đều cả hai cạnh đầy đủ được mô tả ở đây, và nó là giá trị mà WideTextOutBox đọc để bật khoảng cách nhận biết script
Căn đều một đoạn trong thực tế
Một ví dụ hoàn chỉnh tạo một tài liệu, đặt font, và đổ một đoạn vào một hộp với căn đều đầy đủ. Code tương tự căn đều văn bản Latin và CJK mà không cần thay đổi cờ, vì nhận biết script nằm bên dưới API
uses
HPDFDoc;
procedure JustifyParagraph;
var
Pdf: THotPDF;
Body: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Justified.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', 11);
Body :=
'Full justification spreads the slack on each filled line so both ' +
'edges meet the column, while the last line keeps its natural width. ' +
'For scripts with word gaps the space lands between words; for ' +
'scripts without them it spreads evenly between glyphs.';
// X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Để vẽ cùng khối căn trái, căn giữa, hoặc căn phải, chỉ thay đổi đối số cuối cùng thành jtLeft, jtCenter, hoặc jtRight. Bọc, đặt dòng, và giá trị trả về giữ nguyên. Chiều rộng đo được điều khiển tất cả bốn đường dẫn đến từ GetWideTextWidth, truy vấn chiều rộng nhận biết Unicode đo một WideString đúng cách nơi phép đo theo byte cũ hơn sẽ định kích thước sai bất cứ thứ gì ngoài Latin-1, đây là thứ làm cho hộp bọc văn bản CJK và cặp surrogate ở đúng chỗ ngay từ đầu
Căn đều là một lớp của một ngăn xếp shaping văn bản lớn hơn. Khi một dòng chứa các script sắp xếp lại hoặc nối các glyph, các quyết định khoảng cách ở đây nằm trên công việc được mô tả trong bài viết của chúng tôi về shaping văn bản script phức tạp, và khi một font mang các biến thể sắp chữ bạn muốn chọn, xem cách điều khiển các hình thay thế kiểu chữ GSUB OpenType. Tất cả đều đi kèm trong HotPDF Component cho Delphi và C++Builder, cùng với các API văn bản, bố cục, và tài liệu rộng hơn được đề cập trên blog này