Mỗi chuỗi văn bản hiển thị trong tài liệu HotPDF đều được xuất ra thông qua một lời gọi hàm: TextOut(X, Y, angle, Text). Ví dụ Hello World sử dụng nó ở dạng thức nguyên bản nhất, phông chữ (font) được thiết lập một lần và bốn đối số (arguments) giữ nguyên các thiết lập mặc định hợp lý. Sang tới các trang tiếp theo, chính bốn đối số ấy phải gánh trọn trọng trách bố cục (layout). Đối số thứ ba làm nhiệm vụ xoay hướng đoạn văn. Lời gọi chọn phông chữ đứng liền ngay trước đó sẽ quyết định cỡ chữ (size) và kiểu dáng chữ (style). Còn cặp (X, Y), được đo đạc từ góc đáy trang giấy với đơn vị tính là điểm (points), chính là điểm tựa duy nhất giữ cho một bản báo cáo ngăn nắp không bị vỡ bố cục, hay là chữ bị tràn (overlap), bị cắt mất (clips), hoặc bị trôi tụt (drifts) đi một dòng thấp hơn khi được in bằng máy in của người khác. Đó là lúc TextOut khẳng định giá trị của nó, và cũng là lúc các giá trị mặc định trở nên không còn đủ dùng nữa
Phần chữ ký hàm (signature) đáng được ghi nhớ trước khi thực hiện bất cứ bước nào khác: X và Y là kiểu Single dưới dạng điểm (points), angle là Extended theo đơn vị độ (degrees), và Text là WideString, cho nên ký tự Unicode dễ dàng chui lọt qua mà không cần gọi hàm nào khác tách biệt. Cấu trúc nạp chồng hàm (overload) thứ hai chấp nhận một PWORD đi kèm thêm tham số chiều dài nhằm dành cho những lúc bạn đã giữ sẵn các mã ký tự tạo hình (glyph codes) trong tay, nhưng đối với những chuỗi ký tự bình thường thì định dạng truyền WideString là kiểu bạn sẽ dùng đến
Kích thước và kiểu dáng đến từ SetFont, chứ không phải TextOut
TextOut không có tham số kích thước (size). Kích thước, độ đậm (weight), độ nghiêng (slant), tất cả những thứ đó nằm trong lệnh gọi SetFont đứng trước đoạn xuất văn bản, và nó duy trì hiệu lực cho đến khi SetFont tiếp theo thay thế nó. Đó là sự thật duy nhất giải thích phần lớn những bối rối của ngày đầu tiên: một dòng chữ in ra bị in đậm vì ba lệnh gọi trước đó một hàm nào đó đã thiết lập [fsBold] và không có cái gì xóa nó đi
Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report'); // 24pt thường
Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue'); // 12pt in đậm
Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands'); // 11pt in nghiêng
Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, ' +18.4% YoY'); // kết hợp kiểu dáng
Đối số thứ hai là một tập hợp TFontStyles, vì vậy [fsBold, fsItalic] là in đậm in nghiêng và [] là chữ thường. Kích thước tính bằng điểm, cùng một đơn vị với tọa độ, điều này làm cho khoảng cách theo chiều dọc dễ lý luận hơn: một dòng 12 điểm muốn có khoảng 14 đến 16 điểm bước nhảy theo chiều dọc để thở, vì vậy việc giảm Y đi 14 điểm mỗi dòng là một khoảng giãn dòng (leading) khởi đầu hợp lý. Không có tính năng tự động xuống dòng. Bạn tự tính toán mỗi đường cơ sở (baseline), điều này thật tẻ nhạt đối với một đoạn văn nhưng lại chính xác đối với một biểu mẫu, nơi mỗi trường dữ liệu nằm ở một tọa độ cố định
Hai lưu ý thực tế về tên phông chữ. Nó được giải quyết bằng các phông chữ được cài đặt trên máy dựng (build machine), và bất cứ thứ gì hệ điều hành trả lại đều là thứ được nhúng vào, vì vậy một tên gọi hoạt động được trên máy tính của bạn và một tên gọi hoạt động được trên máy chủ dựng có thể không đảm bảo là cùng một kiểu chữ (face). Và phông chữ phải bao phủ các bộ chữ (scripts) trong chuỗi. Một đoạn văn bản Cyrillic hoặc CJK nằm dưới một phông chữ chỉ có chữ Latin sẽ hiển thị dưới dạng các ô vuông (missing-glyph boxes) mà không báo lỗi, đó là lý do mà trang Hello World lại tìm đến một bộ phông chữ có hỗ trợ dải mã Unicode rộng lớn mỗi khi nó dùng kết hợp nhiều ngôn ngữ với nhau
Đối số góc xoay xung quanh điểm neo
Đối số thứ ba là thứ mà hầu hết các mã lập trình luôn luôn để ở số không. Truyền vào một giá trị khác không và đoạn chữ sẽ xoay ngược chiều kim đồng hồ quanh điểm neo (anchor) (X, Y) của chính nó, nằm ở góc dưới cùng bên trái của văn bản, theo từng ấy độ (degrees). Bản thân điểm neo không di chuyển, nên cùng tọa độ từng dùng để đặt một nhãn ngang giờ sẽ đặt được bản sao (twin) bị xoay của nó; chỉ thay đổi mỗi hướng hành quân của các ký tự (glyphs) thôi
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);
// Một nhãn trục dọc chạy dọc theo lề trái: 90 độ đọc từ dưới lên trên.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');
// Chữ mờ DRAFT chạy chéo qua thân trang.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');
// Nhãn cột nghiêng 60 độ để các nhãn dài vừa vặn trong một bảng hẹp.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');
Trường hợp phổ biến là 90 độ, một nhãn văn bản dựng đứng chạy bên cạnh của một biểu đồ hay một dòng tiêu đề trên gáy sách (spine). Góc xoay 45 độ xử lý các tiêu đề cột bị nghiêng, thủ thuật cho phép một nhãn rộng nằm phía trên một cột hẹp mà không tràn sang các cột lân cận. Phép xoay không làm thay đổi cách diễn giải điểm neo, điều mà hay làm nhiều người vấp phải: một chuỗi văn bản xoay 90 độ vẫn bắt đầu tại (X, Y) và phát triển hướng lên trên từ đó, vì vậy để căn giữa một nhãn đã xoay, bạn phải điều chỉnh điểm neo chứ không phải góc quay. Khi nhiều dải văn bản bị xoay cùng chia sẻ một đường cơ sở, hãy gán cho chúng cùng một tọa độ Y và bước nhảy tọa độ X (step X), hoàn toàn giống với cách bạn áp dụng bước nhảy tọa độ Y cho các dòng ngang xếp chồng lên nhau
Đặt tọa độ không cần phỏng đoán
Tọa độ là phần sống sót sau đánh giá mã hoặc âm thầm làm nó thất bại. HotPDF đo lường từ góc dưới bên trái của trang giấy, trục Y thì hướng lên trên, bằng đơn vị điểm (points) với 72 điểm mỗi inch. Một trang khổ US Letter có kích thước 612 x 792 điểm; khổ A4 là 595 x 842. Do đó, lề trên 1 inch trên khổ Letter sẽ đặt đường cơ sở đầu tiên của bạn ở gần Y = 792 trừ đi 72 rồi trừ đi kích thước phông chữ, chứ không phải ở một số nhỏ nào đó gần phía trên cùng. Bất kỳ ai đến từ thế giới tọa độ màn hình, nơi Y tăng dần xuống dưới từ 0, đều sẽ viết dòng đầu tiên văng ra khỏi mép dưới và dành mười phút để tự hỏi nó đi đâu mất rồi
Hãy xem xét bố cục (layout) dưới dạng bài toán số học dựa trên các điểm neo (anchors) có định danh (named anchors) thay vì một cột những con số ma thuật (magic numbers). Một khoảng lề trái (left margin), một đường cơ sở đang chạy giảm dần theo từng dòng (running baseline decremented per line), và khoảng giãn dòng (leading) cố định biến một khối nhãn mác (labels) thành một vòng lặp nhỏ thay vì một mảng chứa đầy dữ kiện thô ráp dồn dập rành rành:
const
LeftMargin = 72; // canh vô 1 inch
TopBaseline = 720; // dòng đầu tiên, cách đỉnh khoảng 1 inch trên khổ Letter
Leading = 16; // bước nhảy dọc giữa các dòng
var
Y: Single;
Line: string;
begin
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := TopBaseline;
for Line in ReportLines do
begin
Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
Y := Y - Leading;
if Y < 72 then // chạm đến lề dưới
begin
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 11); // phông chữ đặt lại trên trang mới
Y := TopBaseline;
end;
end;
end;
Phần mã bảo vệ ngắt trang (page-break guard) là dòng mã tất cả mọi người hay quên nhất và là dòng mã thực địa dễ vấp nhất. Không có bố cục luồng chảy (flow layout) nào bên dưới TextOut. Tiếp tục giảm giá trị vượt qua lề dưới cùng và văn bản tiếp tục vẽ tràn ra ngoài lề, ra khỏi trang, vào hư không, mà chẳng có cảnh báo nào. Vì vậy bạn phải tự theo dõi giá trị Y, gọi hàm AddPage khi nó vượt qua đáy giới hạn, và thiết lập lại đường cơ sở (baseline). Lời gọi hàm SetFont nằm ngay sau AddPage không phải là một chi tiết đệm thừa (optional padding): phông chữ hiện tại không thể sống sót sau một lần ngắt trang (page break), và nếu bạn bỏ qua bước đó thì đoạn chạy văn bản đầu tiên ở trang mới sẽ hiện ra ở phông chữ mặc định của trình xem PDF (viewer's default face)
Khoảng cách ký tự và từ vựng để vừa vặn và căn chỉnh
Thỉnh thoảng một chuỗi ký tự chính xác nhưng bị sai chiều rộng: một dòng tiêu đề (header) cần trải rộng khắp một thước đường nét (rule) cố định, một đoạn mã số (code) lẽ ra dễ đọc hơn với các con số giãn thoáng, một thân cột chằng trịt cần đẩy nhẹ các giá trị của nó để căn chỉnh (align). PDF mang theo hai toán tử trạng thái văn bản (text-state operators) để xử lý việc này: khoảng cách ký tự (character spacing - Tc, khoảng trắng thừa chèn thêm vào sau mỗi hình tượng - glyph) và khoảng cách từ vựng (word spacing - Tw, khoảng trắng thừa chèn vào tại mỗi ký tự khoảng trắng (space character)), và cả hai đều được biểu diễn dưới dạng đơn vị không gian văn bản không qua chia tỷ lệ (unscaled text-space units), hiệu quả tương đương với số điểm ở kích thước phông chữ hiện hành. Chúng là các trạng thái, không phải là đối số cấp cho lệnh hàm TextOut, vậy nên bạn phải đặt giá trị cho chúng, vẽ, và phải phục hồi chúng trở về lại (set them back)
// Dãn lỏng kí tự (Letter-space) cho một tựa đề (heading) ngắn để nó phủ ôm trọn dải kẻ ngang
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0); // thả khóa thu về gốc trước khi viết văn bản thân bài (body text) thông thường
// Nới rộng các khe trống phân cách các từ (words) trên một dòng văn bản trải ngang dài.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name Department Extension');
Pdf.CurrentPage.SetWordSpacing(0);
Khoảng trống dãn từ (Word spacing) chỉ có tác động đè cấn lên mỗi ký tự khoảng trắng (space character - mã code 32), điều này kéo theo một hệ lụy cần lưu ý: nó hoàn toàn vô dụng bên trong một dải chạy (run) văn bản CJK không mang theo các dấu khoảng trắng ASCII nào, và nó hoạt động khá kỳ quặc (oddly) cùng với mảng văn bản được mã hóa (encoded) dưới dạng các chỉ số (indices) ký tự hình (glyph) thay vì dạng từng byte. Đối với kết xuất dữ liệu bảng dạng Latinh (Latin tabular output), nó là giải pháp ít tốn kém (cheap way) để nới rộng các khe phân cách mà không phải thao tác gõ lại chuỗi văn bản đó. Khoảng cách ký tự (Character spacing) mới là thứ công cụ tốt hơn cho một đoạn tiêu đề (heading) cần đạt đến một chiều rộng mục tiêu (target width), bởi vì nó dàn đều sự điều chỉnh đó lên khắp mọi hình ký tự thay vì dồn ứ ngập tại các vị trí khoảng trắng
Lệnh thiết lập lại (reset) là toàn bộ sự kỷ luật. Khoảng cách (Spacing), cũng như kiểu font, là một phần của trạng thái vẽ của trang văn bản, và trạng thái này vẫn tiếp tục duy trì hiệu lực cho đến khi bạn thay đổi nó. Nếu bạn giãn ký tự (Letter-space) cho một tiêu đề và quên đưa nó về không (zero), thì tất cả các đoạn văn bản (paragraph) bên dưới nó đều thừa hưởng nguyên sự kéo giãn đó, khiến cho chúng hiện ra mang theo một sự lỗi hỏng tinh vi, khó định vị rành mạch, có thể dễ dàng qua mặt trong lần soát lỗi lướt (casual proofread) nhưng sẽ rơi rụng khi bị kiểm tra cẩn thận. Việc luyện tạo thói quen đáng tin cậy (reliable habit) là phải đặt ra một mức giãn khoảng cách, vẽ các nét chữ đang cần, và lập tức phục hồi (set it back) về số 0 ở ngay dòng tiếp theo sau, nhờ thế các mã lập trình ở phần sau chẳng cần bận tâm về những gì phần chạy trước (earlier section) đó đã từng làm nữa
Kiểm tra đầu ra tại điểm mà nó thực sự bị vỡ
Bố cục văn bản (Text layout) bị thất bại ở chiếc máy thứ hai, chứ không phải chiếc đầu tiên, vì vậy các bước kiểm tra (checks) quan trọng sẽ diễn ra tại nơi khác chứ không phải ngay tại bàn làm việc của bạn. Hãy mở tệp đã được tạo ra trên một hệ thống không cài đặt bộ phông chữ dành cho nhà phát triển của bạn (developer font set) và xác nhận rằng các phông chữ được nhúng (embedded faces) vẫn hiển thị đúng, bao gồm các ký tự Latin có dấu, mọi bộ chữ phi Latin (non-Latin scripts), và các dấu câu (punctuation), trong cùng một đợt chạy rà soát toàn bộ thay vì chỉ kiểm tra xác suất ngẫu nhiên một vài ký tự dễ. Bôi đen chọn rồi sao chép một vài dòng chữ để khẳng định lại đoạn văn kia là chữ văn bản thật sự (real text) chứ không phải nét phác vẽ đường bao ngoài (outlines), yếu tố này mang tính quyết định ngay tại khoảnh khắc mà chức năng tìm kiếm (search) hoặc trích xuất (extraction) đưa vào sử dụng. Hãy nạp cho bố cục mảng dữ liệu mang tính đại diện (representative data), kiểu nhãn tiếng Đức dài dằng dặc nhất với con số có chiều rộng bự nhất, chứ đừng nhồi một giá trị trám chỗ ảo tạm bợ (placeholder), vì tại duyên cớ dải chạy (run) gây vỡ trào khỏi một trường dữ liệu (field) lúc nào cũng chính là chuỗi mà bạn đã không tự tay gõ. Và giả sử mặt trang đang thiết kế cần phải được in đè lên một bản mẫu đã được in phôi sẵn từ trước (preprinted form), hãy thao tác in thử (print) hoặc xử lý ảnh bitmap (rasterize) ra một mẫu thử (sample) và để đặt so sát đối chiếu chồng khớp cùng bản gốc (original); việc đường ranh ngang của chữ bị chệch trượt dẫu cỡ chừng chỉ cỡ 1/4 milimét (quarter-millimeter baseline drift) thì thường ẩn hình rất kín ở trên mặt màn chiếu màn hình (screen) vậy mà lại dễ ngó trừng soi ra hiển nhiên trên mặt bản giấy (paper)
Nếu bạn còn chưa kịp viết ra một trang giấy bìa đơn lẻ nào, đà khởi đầu từ ví dụ mẫu Hello World của HotPDF, với tính năng hỗ trợ cài đặt bố cục cho văn bản (document), phông chữ (font), cùng với hệ tọa độ hướng góc dưới cùng bên trái đóng vai trò điểm quy chiếu cho mọi thao tác phía trên ngả. Đám lệnh TextOut, SetFont, và cụm gọi cài đặt đệm lề (spacing calls) bày ở đây trực thuộc vào Thành phần HotPDF (HotPDF Component) dành tặng cõi lập trình Delphi với C++Builder