(72, 72) noktasına çizilen bir dize, sayfanın üst kenarının bir inç altına düşer; bu, PDFi içerik akışı tarafından öğrenen herkesi şaşırtır, çünkü orada y = 72, alt kenarın bir inç üstünde durur. İkinci sürpriz birkaç yüz satır sonra ortaya çıkar. US Letter için düzenlenmiş bir rapor, 612 × 792 noktalık bir ilk sayfa ve sonrasındaki her sayfayı 595 × 842 nokta olarak döndürür; çünkü kağıt boyutunu ayarlayan satır tam olarak bir kez çalışmıştır
İki etki de aynı köke çıkar. Bir PDFde metin, kısa bir durum değişikliği dizisinden (font ve boyut, karakter aralığı, kelime aralığı, konum ve döndürme için bir text matrix) ve ardından gelen tek bir show-text operatöründen ibarettir; üst düzey bir metin çağrısı bu dizinin ince bir sarmalayıcısıdır. Çağrı durumu okur. Onu sizin için asla sıfırlamaz ve yeni bir sayfa, önceki sayfanın bıraktığı yerden değil, sabit bir öntanımlı setten başlar
TextOut, (X, Y)yi sayfada nereye koyar?
HotPDF metni THPDFPage.TextOut(X, Y: Single; angle: Extended; Text: WideString) ile çizer; Pdf.CurrentPage.TextOut üzerinden ulaşılır. X ve Y, sayfanın sol üst köşesinden ölçülür, Y aşağı doğru büyür; öntanımlı Resolution 72 iken bir birim bir point, yani bir inçin 1/72sidir. Sayfa, her Yi, aşağıdan yukarı akan PDF user spaceine dönüştürmek için sayfa yüksekliğinden çıkarır. Açı derece cinsindendir ve metin bir WideString olduğundan Unicode doğrudan geçer. İkinci bir overload, UTF-16 tamponları zaten elinde tutan kod için bir PWORD artı bir uzunluk alır
Sayfa geometrisi de sabittir. BeginDoc ilk sayfayı ekler ve BeginDoc ya da AddPagein ürettiği her sayfa, geçerli font Arial 12 olacak şekilde A4, yani 595 × 842 nokta olarak başlar. US Letter (612 × 792), CurrentPage.Size := Letter üzerinden tek bir atamadır; ama o atama sayfaya aittir, bu yüzden her yeni sayfa için yeniden yapılmalıdır
Öntanımlı olarak verdiğiniz Y, metnin üst kenarıdır ve baseline onun bir font ascent aşağısında durur. TextFromBaseLine(True), sayfayı baseline konumlandırmasına geçirir. Tek satırda boyut karıştıran her şey için onu açın. Üst kenar modunda, 14 pointlik bir tutarın yanındaki 9 pointlik para birimi kodunun üstü, tutarın üstüyle hizalanır; yani görünür biçimde yukarıda kalmaktadır. Baseline modunda ise iki dizi de aynı satırda, dizgicinin koyacağı yerde durur. Sonraki örneğin son bloğu tam olarak bunu yapar
Boyut ve stil SetFontta yaşar
TextOutun boyut ya da stil parametresi yoktur. SetFont(FontName, FontStyle, ASize, FontCharset, IsVertical) hepsini taşır; son iki argüman ANSI_CHARSET ve yatay metne öntanımlıdır ve SetFontun koyduğu her şey, bir sonraki SetFonta kadar onu izleyen her TextOut için geçerli kalır. İlk gündeki kafa karışıklıklarının çoğu buradan doğar. Bir satır bold çıkar, çünkü üç çağrı önce biri [fsBold] kurmuş ve onu temizleyen olmamıştır
var
W: Single;
begin
Pdf.BeginDoc; // ilk sayfa: A4, Arial 12
Pdf.CurrentPage.TextFromBaseLine(True); // aşağıdaki her Y bir baseline
Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 72, 0, 'Quarterly Report'); // 24 pt normal
Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 104, 0, 'Revenue'); // 12 pt bold
Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 120, 0, 'figures in thousands'); // 11 pt italic
Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsUnderline], 10);
Pdf.CurrentPage.TextOut(72, 136, 0, '+18.4% YoY'); // stiller birleşir
// Tek baseline üzerinde karışık boyutlar: 14 pt yüz aktifken ölçün
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
W := Pdf.CurrentPage.GetWideTextWidth('1,284.50');
Pdf.CurrentPage.TextOut(72, 160, 0, '1,284.50');
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(72 + W + 3, 160, 0, 'EUR'); // 3 pt boşluk
...
end;
Stil argümanı sıradan bir TFontStyles setidir; [] düz, [fsBold, fsItalic] bold italic demektir. fsUnderline ve fsStrikeOut, dizinin altına ya da içine ince, dolu çubuklar olarak çizilir ve dizi döndürüldüğünde onu izlerler. Boyut, koordinatlarla aynı birim olan point cinsindendir; bu, dikey aritmetiği basit tutar: 16 pointlik adımla akan 11 pointlik gövde metni, satırlar arasında yaklaşık beş point boşluk bırakır. Otomatik satır ilerlemesi yoktur. Her Yi hesaplamak size düşer; bu, bir paragraf için sıkıcı ama alanları sabit konumlarda duran bir form için tam isabetlidir
GetWideTextWidth, çağrı anında aktif olan hangi font varsa onunla ölçer; bu yüzden son blokte ölçüm, 14 pointlik bold yüz hâlâ kurulu iken yapılmalıdır. Onu ikinci SetFontun altına taşırsanız, 9 pointlik sonek tutarın son rakamlarının üstüne biner
Font adı, PDFi üreten makinede kurulu fontlara göre çözülür; geliştiricinin masaüstünde çözülen bir yüz, derleme sunucusunda bulunmayabilir, dolayısıyla biten dosyadaki yüz hangi makinenin ürettiğine bağlıdır. Script kapsamı işin diğer yarısıdır. Yalnızca Latin kapsayan bir yüze Kiril ya da CJK çizdirilirse eksik glif kutuları üretilir ve bu, çağrının değil font dosyasının özelliğidir. Tek bir dize, hiçbir yüzün tek başına kapsamadığı scriptleri karıştırıyorsa TextOutWithFallback devreye girer: aynı dört argümanı alır, birincil fontun eksik bıraktığı karakterler için yüzleri dizi dizi değiştirir ve iş bitince özgün fontu geri yükler
Açı argümanı metni nasıl döndürür?
Sıfırdan farklı bir açı, diziyi çapası etrafında o kadar derece saat yönünün tersine döndürür ve çapa, metnin kendi çerçevesinde metnin sol üst köşesidir. Çapa kıpırdamaz. Değişen yalnızca gliflerin yürüdüğü yöndür; bu yüzden yatay bir etiketi yerleştiren koordinat, döndürülmüş ikizini de yerleştirir. Döndürülmüş diziler, sayfa baseline modunda bile o sol üst çapasını korur; TextFromBaseLine yalnızca yatay dizileri kaydırır
var
W, AxisMidY: Single;
begin
AxisMidY := 400; // grafiğin Y ekseninin ortası
// 90 derecelik bir dizi aşağıdan yukarıya okunur ve Yden yukarı doğru büyür;
// eksen üzerinde ortalamak, yarısını orta noktanın altından başlatmak demektir
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);
W := Pdf.CurrentPage.GetWideTextWidth('Units sold');
Pdf.CurrentPage.TextOut(40, AxisMidY + W / 2, 90, 'Units sold');
// Çapraz bir filigran
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');
// 60 derece eğik sütun başlıkları: aynı Y, adımlı X
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');
end;
Doksan derece eksen etiketlerini ve sırt başlıklarını karşılar. 90 derecede glifler, çapanın Xinin sağında, bir satır yüksekliği genişliğinde durur ve dizi, kendi genişliği kadar sayfanın tepesine tırmanır. Ortalamak için Yye genişliğin yarısının eklendiğinin ve çapa tam orta noktanın üzerindeyken etiketin tümüyle üstünde kaldığının sebebi budur. Altmış derece sütun başlıklarına uyar; uzun bir etiketin dar bir sütunun üzerinde, komşusuna taşmadan durmasını sağlar. Kırk beş derece ise her zamanki filigran açısıdır. Aynı baselineı paylaşan diziler aynı Yi alır ve X adımlanır; üst üste yatay satırların bir X paylaşıp Yyi adımlaması gibi
İkinci sayfa neden A4 ve Arial 12 olarak çıkar?
AddPage yeni sayfanın indeksini döndürür ve onu geçerli yapar; yeni sayfa öntanımlarlardan başlar: A4, Arial 12, üst kenar konumlandırması, sıfır karakter ve kelime aralığı. Önceki sayfadan hiçbir şey taşınmaz. Bu tasarım her sayfayı kendi içinde tam tutar; çünkü bir PDF sayfasının içerik akışı zaten taze bir graphics state ile başlar. Ama belgenin en üstünde bir kez sayfayı yapılandıran her düzen kodunun ikinci sayfada sessizce kağıt boyutunu ve yazı yüzünü değiştireceği anlamına da gelir. Çözüm, sayfa başına her ayarı tek bir routine koymak ve onu BeginDoc sonrasında ve her AddPage sonrasında çağırmaktır
procedure StartReportPage(Pdf: THotPDF);
begin
Pdf.CurrentPage.Size := Letter; // yeni bir sayfa A4 olarak başlar
Pdf.CurrentPage.TextFromBaseLine(True); // yeni bir sayfa üst kenar modunda başlar
Pdf.CurrentPage.SetFont('Arial', [], 11); // yeni bir sayfa Arial 12 ile başlar
end;
procedure WriteReport(Pdf: THotPDF; Lines: TStrings);
const
LeftMargin = 72; // 1 inç
TopLine = 72; // ilk baseline, 1 inç aşağıda
BottomMargin = 72;
Leading = 16;
var
I: Integer;
Y: Single;
begin
StartReportPage(Pdf);
Y := TopLine;
for I := 0 to Lines.Count - 1 do
begin
if Y > Pdf.CurrentPage.Height - BottomMargin then // Letterda 720
begin
Pdf.AddPage;
StartReportPage(Pdf);
Y := TopLine;
end;
Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Lines[I]);
Y := Y + Leading;
end;
end;
TextOutun altında bir akış düzeni yoktur ve sayfanın altının ötesindeki bir Y, içerik akışı açısından hâlâ geçerli bir koordinattır: metin yazılır, yalnızca media boxın dışına düşer ve hiçbir görüntüleyici göstermez. Bu yüzden döngü her satırdan önce Yi kontrol eder ve gerektiğinde kendisi yeni bir sayfa açar. Sınırı 720 olarak koda gömmek yerine CurrentPage.Heightten okumak, biri ileride yardımcıyı A4e döndürürse döngünün doğru kalmasını sağlar; aynı bir inçlik kenar boşluğu orada sınırı 770e koyar
En çabuk kontrol, okuyucunun yazdırma penceresinin yapacağı olandır. Çıktıyı açın, ikinci sayfaya gidin ve belge özelliklerine bakın: Letter bir sayfa 8,5 × 11 inç, A4 yaklaşık 8,27 × 11,69 inç bildirir. İlk sayfa ile ikincisi uyuşmuyorsa, sayfa başına bir ayar yardımcının dışında kalmıştır
Karakter ve kelime aralığı da sayfa durumudur
Kimi zaman dize doğrudur ama genişlik yanlıştır: sabit bir çizgiye yaslanması gereken bir başlık, rakamları daha gevşek daha iyi okunan bir kod, boşluklarının açılması gereken bir etiket satırı. PDFin ISO 32000-1de bu iş için iki text-state parametresi vardır: her gliften sonra eklenen karakter aralığı Tc (§9.3.2) ve her boşlukta eklenen kelime aralığı Tw (§9.3.3). HotPDF bunları SetCharacterSpacing(Spacing: Single) ve SetWordSpacing(Spacing: Single) ile, öntanımlı çözünürlükte point cinsinden kurar; ve font gibi, değiştirilene kadar geçerli kalırlar
Kelime aralığının, şartnameye yazılmış bir tuzağı vardır. §9.3.3, Twyi yalnızca tek baytlık 32 karakter koduna uygular ve çok baytlı bir kodun içindeki 32 baytına açıkça uygulamaz; yani operatör kendi başına, iki baytlık glif kodlarıyla yazılan metin için hiçbir şey yapmaz — gömülü Unicode fontları normalde öyle yazılır. HotPDF de Unicode dizilerini öyle yazar ve o yolda kelime aralığını, her boşluk glifinden sonra açık konum ayarlamaları olarak uygular; dolayısıyla SetWordSpacing boşlukları iki durumda da genişletir. Tetikleyici yine düz boşluktur, U+0020. ASCII boşluğu olmayan bir Çince ya da Japonca dize hiç ek alan almaz ve ideografik boşluk (U+3000) sıradan bir glif sayılır
Bir başlığın hedef bir genişliğe ulaşması gerektiğinde doğru araç karakter aralığıdır; çünkü değişimi boşluklarda biriktirmek yerine eşit dağıtır. GetWideTextWidth, o an geçerli olan karakter ve kelime aralığını da hesaba katar; bu yüzden ikisi sıfırken ölçün ve aralığı türetin:
const
Heading = 'SUMMARY';
Target = 200; // başlığın yaslanması gereken çizginin genişliği, point cinsinden
var
Natural, Tc: Single;
begin
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.SetCharacterSpacing(0); // artan aralık olmadan ölçün
Natural := Pdf.CurrentPage.GetWideTextWidth(Heading);
// Tc her gliften sonra gelir ama yalnızca glifler arası boşluklar görünürdür
Tc := (Target - Natural) / (Length(Heading) - 1);
Pdf.CurrentPage.SetCharacterSpacing(Tc); // negatif değerler sıkılaştırır
Pdf.CurrentPage.TextOut(72, 72, 0, Heading);
Pdf.CurrentPage.SetCharacterSpacing(0); // gövde metninden önce sıfırlayın
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 100, 0, 'Name Department Extension');
Pdf.CurrentPage.SetWordSpacing(0);
end;
Reset satırları, kuran satırlardan daha önemlidir. Bir başlığın ardından 4 pointlik karakter aralığını yerinde bırakın, altındaki her paragraf o gerilmeyi miras alır; bu, hızlı bir prova okumasında gözden kaçan ve sonra basılı kopyada beliren belirsiz bir yanlışlık olarak okunur. Değeri kurun, ihtiyaç duyan tek diziyi çizin ve bir sonraki satırda sıfırlayın; sonraki hiçbir kodun, önceki bir bölümün ne yaptığını bilmesi gerekmesin. Aynı sayfa, bir diziyi yüksekliğini bozmadan sıkıştıran SetHorizontalScalingi (Tz, öntanımlı 100) ve trStroke gibi değerlerle konturlu metin çizen SetTextRenderingModeu da sunar; ikisi de tam olarak aynı şekilde kalıcıdır
Bu sizin ilk çıktı sayfanızsa, HotPDF Hello World örneği belge kurulumunu ve kaydetmeyi anlatır. Bir sütunu kenardan kenara doldurması gereken metinler için PDF metninde tam iki yana hizalamanın kendi anlatımı vardır; yukarıdan aşağıya CJK ise HotPDFde dikey metin düzeninde işlenir. TextOut, SetFont, yukarıdaki aralık çağrıları ve sayfa boyutu ayarlarının tümü, Delphi ve C++Builder için HotPDF Delphi Component ile birlikte gelir