HotPDF vẽ đồ họa vector bằng cách xây dựng một đường dẫn (path) trên trang hiện tại và sau đó yêu cầu tô màu nó. Không có bước xử lý ảnh bitmap trung gian nào. Một đoạn thẳng bạn vẽ bằng MoveTo và LineTo sẽ trở thành các toán tử đường dẫn của PDF bên trong luồng nội dung, vì vậy nó vẫn là một vector thực sự: sắc nét ở mức thu phóng 50%, sắc nét ở mức 1600%, và có kích thước chỉ bằng một phần nhỏ so với phiên bản raster hóa. Đối với lưới đồ thị, đường kẻ bảng, trục biểu đồ và trang trí biểu mẫu, đó chính là thứ bạn cần, và API hỗ trợ nó đủ nhỏ để học trong một lần ngồi
Toàn bộ bề mặt vẽ nằm trên THotPDF.CurrentPage. Giữa BeginDoc và EndDoc, bạn thiết lập màu sắc và độ dày nét vẽ trên đối tượng trang đó, vạch ra dạng hình học và gọi một toán tử tô màu để chốt nó. Bốn hàm nguyên thủy bạn sẽ sử dụng nhiều nhất là MoveTo và LineTo cho các đường dẫn tùy ý, Rectangle cho các hộp, Circle cho các hình tròn và hai toán tử tô màu là Stroke và Fill
Hệ tọa độ góc dưới bên trái
Đây là điều duy nhất khiến những người chuyển từ VCL sang dễ bị vấp. TCanvas mà bạn vẽ các điều khiển (controls) đặt gốc tọa độ ở góc trên cùng bên trái và trục Y hướng xuống dưới. PDF thì ngược lại. HotPDF đo từ góc dưới cùng bên trái của trang theo đơn vị điểm (1/72 inch), với Y tăng lên khi bạn đi lên. Điểm ở Y := 720 nằm gần đỉnh của trang US Letter (cao 792 điểm), và Y := 50 nằm gần đáy. Nếu bản vẽ đầu tiên của bạn bị lộn ngược theo chiều dọc, thì đây là lý do: mã chuyển từ đồ họa màn hình giả định sai hướng và chạy ra khỏi mép dưới
Quy ước tương tự cũng chi phối TextOut, vì vậy văn bản và hình dạng có chung một mô hình để bạn tư duy sau khi đã nắm rõ. Lập kế hoạch bố cục bằng cách quyết định vị trí đáy của mỗi phần tử, không phải đỉnh, và các phần còn lại sẽ theo sau
Đường dẫn: MoveTo, LineTo, Stroke
Đường dẫn có nét (stroked path) giống như việc nhấc bút lên, đặt xuống và kéo đi. MoveTo nhấc bút và đặt điểm bắt đầu mà không vẽ gì cả. Mỗi lệnh LineTo kéo dài đường dẫn hiện tại đến một điểm mới. Không có gì xuất hiện trên trang cho đến khi bạn gọi Stroke, hàm này vẽ đường dẫn đã tích lũy bằng màu nét và độ dày nét hiện tại, sau đó xóa đường dẫn để MoveTo tiếp theo bắt đầu lại từ đầu
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'DrawPaths.pdf';
Pdf.BeginDoc;
// Chiều rộng nét được tính bằng point và áp dụng cho đến khi bạn thay đổi nó.
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
// Một đường kẻ ngang gần đỉnh trang (Y đo từ dưới lên).
Pdf.CurrentPage.MoveTo(72, 720);
Pdf.CurrentPage.LineTo(523, 720);
Pdf.CurrentPage.Stroke; // chốt đường dẫn; không có gì vẽ ra trước lệnh này
// Đường gấp khúc (polyline) dày hơn: ba đoạn trong một đường dẫn.
Pdf.CurrentPage.SetLineWidth(3);
Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
Pdf.CurrentPage.MoveTo(72, 640);
Pdf.CurrentPage.LineTo(172, 690);
Pdf.CurrentPage.LineTo(272, 620);
Pdf.CurrentPage.LineTo(372, 680);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Hai chi tiết giúp tiết kiệm thời gian gỡ lỗi thực tế. Chiều rộng nét là một trạng thái, không phải đối số: SetLineWidth được đặt một lần và mọi lệnh Stroke tiếp theo sẽ sử dụng giá trị đó cho đến khi bạn thay đổi, đó là lý do đường gấp khúc phía trên dày hơn đường thẳng. Và đường dẫn tự đặt lại sau mỗi lần gọi Stroke, vì vậy nếu quên Stroke, hình dạng bạn vẽ sẽ không bao giờ xuất hiện. Nếu thiếu một hình dạng trong đầu ra, lệnh tô màu là nơi đầu tiên cần kiểm tra
Tọa độ là số điểm, và điểm là phân số. MoveTo và LineTo nhận các giá trị Single, nên một nét vẽ mỏng ở 0.5 điểm hoặc vị trí ở 72.25 đều hợp lệ và có nghĩa, không bị làm tròn thành số nguyên gần nhất. Độ chính xác đó quan trọng theo hai hướng trái ngược nhau. Độ dày nét dưới khoảng 0.5 có thể hiển thị dưới dạng nét mỏng nhất tùy thuộc vào thiết bị, biến mất trên màn hình và xuất hiện lại khi in, vì vậy nếu muốn đường kẻ có thể nhìn thấy, bạn nên đặt độ dày có chủ đích thay vì dùng mặc định. Mặt khác, việc bám các đường kẻ bảng và lưới vào tọa độ điểm nguyên giúp một lưới dày đặc không bị lệch vị trí, nơi các đường lân cận bị làm tròn khác nhau. Hãy quyết định khoảng cách lưới tính bằng điểm ngay từ đầu và phần còn lại của bố cục sẽ tuân theo
Hình khối được tô và màu sắc
Các hình nguyên thủy khép kín có thể được tô màu (fill) thay vì chỉ vẽ viền. Rectangle nhận vào vị trí và kích thước, Circle nhận tâm và bán kính, và cả hai đều được chốt lại bằng Fill (tô bên trong bằng màu tô hiện tại), hoặc bằng Stroke (chỉ vẽ đường viền). Màu tô và màu nét là các trạng thái tách biệt, được thiết lập bằng SetRGBFillColor và SetRGBStrokeColor, cả hai đều nhận giá trị TColor. Điều đó có nghĩa là bạn có thể sử dụng trực tiếp các hằng số màu của Delphi và hàm trợ giúp RGB
// Rectangle(X, Y, Width, Height): X và Y là góc dưới cùng bên trái.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;
// Circle(X, Y, Radius): X và Y là tâm.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;
// Chỉ vẽ viền: đặt màu nét, độ dày nét, sau đó gọi Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;
Hãy chú ý đến hình dạng đối số của Rectangle. Nó là tọa độ cộng với kích thước, X, Y, Width, Height, không phải là tọa độ hai góc đối diện. Đối tượng TCanvas.Rectangle mà các lập trình viên Delphi hay dùng nhận vào (Left, Top, Right, Bottom), do đó thói quen sẽ khiến bạn truyền cho HotPDF một góc thứ hai trong khi nó đang đợi độ rộng và độ cao, dẫn đến cái hộp bị sai kích thước. Cặp (X, Y) là góc dưới cùng bên trái, đồng nhất với gốc của trang. Đối với hình tròn, (X, Y) là tâm và tham số thứ ba là bán kính tính bằng point
Lựa chọn màu sắc mà ví dụ ban đầu đã làm sai
Một phiên bản cũ hơn của ví dụ này đã tạo màu sắc ngẫu nhiên bằng Random($FFFFFF) trên mọi hình khối. Nó trông sống động, nhưng đó là một bản năng sai lầm đối với các tài liệu được tạo. Một tệp PDF do mã tạo ra thường là thứ bạn cũng muốn kiểm tra, và các màu tô ngẫu nhiên khiến đầu ra không thể so sánh được giữa các lần chạy: việc so sánh (diff) theo từng byte với một tệp chuẩn (known-good) sẽ luôn thất bại mà không có lý do thực sự nào. Hãy chọn các màu sắc rõ ràng. Khi bạn muốn có sự đa dạng trong một loạt các hình khối, hãy điều khiển nó từ dữ liệu của bạn hoặc từ một mảng bảng màu cố định, để cùng một đầu vào luôn tạo ra cùng một tệp. Tính tất định giá trị hơn sự mới mẻ khi sản phẩm di chuyển qua quy trình phát hành
Ghép các nguyên thủy lại với nhau: một hộp callout
Mỗi lệnh vẽ nguyên thủy đều đơn giản khi đứng riêng lẻ; sự hiệu quả thể hiện khi một nhóm các lệnh kết hợp lại thành những gì một báo cáo thực sự cần. Hộp callout, loại hộp có viền dùng để chỉ vào một hình ảnh và giải thích nó, sử dụng mọi thứ được đề cập cho đến nay: một hình chữ nhật được tô màu cùng với đường viền, một đoạn nét vẽ để trỏ, một chấm tròn để neo vào đối tượng, và văn bản được bố trí bên trong hộp sử dụng cùng hệ tọa độ dưới cùng bên trái. FillAndStroke thể hiện giá trị của nó ở đây, bằng việc tô bên trong và vẽ đường viền của một đường dẫn chỉ trong một lần thay vì phải tạo hình chữ nhật hai lần
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'Callout.pdf';
Pdf.BeginDoc;
// 1. Cái hộp: tô màu nhạt và đường viền rõ nét, một đường dẫn, một lần chốt.
// Hình chữ nhật có điểm dưới trái cộng kích thước, Y đo từ đáy
Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214)); // màu hổ phách nhạt
Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40)); // viền đậm hơn
Pdf.CurrentPage.SetLineWidth(1);
Pdf.CurrentPage.Rectangle(90, 600, 240, 70);
Pdf.CurrentPage.FillAndStroke;
// 2. Con trỏ: một đoạn nét từ cạnh hộp chỉ xuống
// hướng về phía phần được chú thích
Pdf.CurrentPage.SetLineWidth(1.5);
Pdf.CurrentPage.MoveTo(90, 615); // cạnh trái của hộp
Pdf.CurrentPage.LineTo(66, 546);
Pdf.CurrentPage.Stroke;
// 3. Chấm tròn để neo con trỏ tại mục tiêu của nó
Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
Pdf.CurrentPage.Circle(64, 542, 3);
Pdf.CurrentPage.Fill;
// 4. Nhãn văn bản, được định vị tương đối so với góc dưới trái của hộp.
// Văn bản và hình vẽ dùng chung một hệ tọa độ, vì vậy phần lệch (offset)
// chỉ là phép tính số học thuần túy so với điểm (90, 600)
Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
Pdf.CurrentPage.TextOut(102, 645, 0, 'Check this total');
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(102, 628, 0, 'The rounding rule changed in the');
Pdf.CurrentPage.TextOut(102, 616, 0, 'June release; verify against v2.1');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Lưu ý cách cụm kết hợp này đòi hỏi rất ít sự quản lý trạng thái. Màu tô, màu viền và độ dày viền đều được thiết lập ngay trước khối hình sử dụng chúng, vì vậy mọi khối vẽ đều được đọc như một đơn vị khép kín và có thể được sắp xếp lại hoặc trích xuất thành một hàm trợ giúp mà không phải kéo theo các trạng thái ẩn. Gói toàn bộ nội dung này vào một thủ tục nhận điểm neo và các chuỗi, và bạn có một chú thích sơ đồ có thể tái sử dụng chỉ với chi phí bốn mươi dòng mã
Khi nào thì vẽ vector thể hiện sự đáng giá, và khi nào thì không
Hãy sử dụng các lệnh đường dẫn và hình vẽ khi phần hình học được tạo ra (tính toán): lưới đồ thị, các cột, đường kẻ của một bảng hóa đơn, các hộp callout trên một sơ đồ, một logo gồm một vài nét vẽ. Tất cả đều thay đổi tỷ lệ mà không bị mờ và hầu như không tăng kích thước tệp, bởi vì một hình chữ nhật chỉ chứa một vài con số thay vì hàng ngàn pixel. Mặt trái của điều này cũng rất thực tế. Nếu những gì bạn có thực sự là một bức ảnh hoặc ảnh chụp màn hình, hãy vẽ nó bằng cách dùng lệnh AddImage và ShowImage thay thế; việc đồ lại một ảnh bitmap bằng các lệnh vector không đem lại bất cứ lợi ích gì. Các đoạn thẳng, hình chữ nhật và hình tròn ở trên đã thực hiện phần lớn khối lượng công việc báo cáo trong thực tế, và ba cải tiến tiếp theo mà các nhà phát triển hay hỏi đến, đường cong (curves), nét đứt (dash patterns), và độ trong suốt (transparency), cũng nằm trên cùng một đối tượng trang
Đường cong, nét đứt và độ trong suốt nói ngắn gọn
Các đường cong dạng tự do kế thừa cùng một cơ chế đường dẫn mà bạn đã có sẵn. CurveToC(X1, Y1, X2, Y2, X3, Y3) thêm vào một đoạn Bezier bậc ba từ điểm hiện tại tới điểm (X3, Y3), bị uốn theo hai điểm điều khiển, và các phiên bản viết tắt như CurveToV và CurveToY sẽ được dùng trong các trường hợp một trong hai điểm điều khiển trùng với một điểm kết nối (endpoint). Một đường dẫn có thể kết hợp tự do các lệnh LineTo và CurveToC trước khi được chốt bằng một lệnh Stroke hoặc Fill duy nhất, đây là cách mà các góc bo tròn và các đường biểu đồ mượt mà được tạo ra
Đường viền nét đứt (dashed strokes) là một trạng thái, giống như độ dày đường nét. SetDash([3, 3], 0) chuyển tất cả các nét tiếp theo sang mẫu hiển thị nét đứt dài 3 point, ngắt 3 point. Mảng truyền vào giúp thể hiện độ dài nét hiển thị/ngắt bằng point và đối số thứ hai định vị pha (chu kỳ) bắt đầu; NoDash đưa bút quay về đường nét liền. Hãy thiết lập nó, dùng nét vẽ cho các đường kẻ lưới đang cần, và thiết lập lại về mặc định trước khi vẽ nét liền tiếp theo, nếu không kiểu nét đứt sẽ âm thầm lây nhiễm sang mọi thứ đi theo sau
Độ trong suốt được xử lý thông qua một trạng thái đồ họa được đặt tên thay vì là một tham số màu, bởi vì tham số alpha (độ đục) trong PDF là một thuộc tính của từ điển trạng thái đồ họa (graphics state dictionary). Hãy đăng ký một trạng thái trên tài liệu bằng RegisterExtGState, truyền vào tham số alpha cho màu tô (fill) và nét viền (stroke) nằm trong khoảng từ 0 đến 1, sau đó áp dụng tên (name) mà nó trả về bằng hàm CurrentPage.SetGraphicsState; mọi nét tô và vẽ viền kể từ thời điểm đó sẽ được vẽ ở độ mờ đã đăng ký. Điều này tốn nhiều công sức hơn là thiết lập màu sắc, nhưng nó lại xứng đáng cho lần đầu tiên một vệt highlight phải đè lên phần văn bản mà không làm che lấp nó
Thói quen cuối cùng đáng được duy trì chính là quá trình xác minh (verification). Hình khối được sinh ra có thể hoạt động hoàn hảo trên máy của bạn nhưng lại thất bại trên máy tính khách hàng, thường là do vấn đề thế chỗ phông chữ (font substitution) với mọi văn bản bạn đưa vào hoặc một giả định về kích thước trang (page-size assumption) không được đảm bảo. Hãy mở tệp đã hoàn thiện ở một vài cấp độ phóng to khác nhau để đảm bảo các cạnh vẫn hiển thị rõ ràng và kiểm tra lại xem mọi hình dạng có được đặt chuẩn bên trong hộp lề mong muốn hay không. Cùng với cơ chế chọn màu chắc chắn, việc kiểm tra đó hoàn toàn có thể tự động bằng cách so sánh đối chiếu với một bản gốc PDF tham khảo thay vì phải dùng sức quan sát bằng mắt thường
Các lệnh MoveTo, LineTo, Stroke, Fill và các lệnh màu sắc hiển thị ở đây là một phần của Thành phần HotPDF dành cho Delphi và C++Builder