Кожен видимий рядок у документі HotPDF з'являється завдяки одному виклику: TextOut(X, Y, angle, Text). Приклад "Hello World" використовує його в найпростішому вигляді: шрифт встановлюється один раз, а чотири аргументи залишаються з розумними значеннями за замовчуванням. Але за межами цієї першої сторінки ті самі чотири аргументи несуть на собі всю вагу компонування. Третій аргумент обертає текст. Шрифт, встановлений безпосередньо перед цим, визначає розмір і стиль. А пара X, Y, яка вимірюється від кута сторінки в пунктах, - це єдине, що стоїть між чистим звітом і текстом, який накладається, обрізається або з'їжджає на рядок нижче на чужому принтері. Саме тут TextOut відпрацьовує своє, і саме тут значень за замовчуванням стає недостатньо
Перш за все, варто запам'ятати сигнатуру: X та Y - це значення типу Single у пунктах, angle (кут) - це Extended у градусах, а Text - це WideString, завдяки чому Unicode проходить без окремого виклику. Друге перевантаження методу приймає PWORD плюс довжину для випадків, коли ви вже маєте коди гліфів, але для звичайних рядків вам потрібен саме варіант з WideString
Розмір і стиль беруться з SetFont, а не з TextOut
TextOut не має параметра розміру. Розмір, насиченість, нахил - усе це живе у виклику SetFont, який передує виводу тексту, і залишається чинним доти, доки наступний SetFont його не замінить. Цей єдиний факт пояснює більшість непорозумінь першого дня: рядок виходить жирним, тому що три виклики тому щось встановило [fsBold] і ніщо його не скасувало
Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report'); // 24pt regular
Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue'); // 12pt bold
Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands'); // 11pt italic
Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, ' +18.4% YoY'); // styles combine
Другий аргумент - це набір TFontStyles, тому [fsBold, fsItalic] означає жирний курсив, а [] - звичайний текст. Розмір задається в пунктах - тих самих одиницях, що й координати, що полегшує міркування про вертикальний інтервал: 12-пунктовому рядку для "дихання" потрібен вертикальний крок приблизно від 14 до 16 пунктів, тому зменшення Y на 14 на кожен рядок є розумним початковим інтерліньяжем. Автоматичного переходу на новий рядок немає. Ви обчислюєте кожну базову лінію (baseline) самостійно, що виснажливо для абзацу, але ідеально точно для форми, де кожне поле лежить на фіксованій координаті
Дві практичні нотатки щодо назви шрифту. Вона розпізнається на основі шрифтів, встановлених на машині, де відбувається збирання, і те, що повертає операційна система, є тим, що буде вбудовано. Тому назва, яка працює на вашому комп'ютері, і назва, яка працює на сервері збирання, не гарантують однакового накреслення. Крім того, шрифт має покривати системи письма (scripts), присутні в рядку. Текст кирилицею чи CJK (китайська, японська, корейська) під шрифтом, який містить лише латиницю, відрендериться як квадратики відсутніх гліфів без жодних повідомлень про помилку, саме тому сторінка "Hello World" тягнеться до широкого Unicode-шрифту, коли змішує мови
Аргумент кута обертає текст навколо якоря
Третій аргумент - це той, який більшість коду назавжди залишає нульовим. Передайте ненульове значення, і текст обернеться проти годинникової стрілки навколо власного (X, Y) якоря - лівого нижнього кута тексту - на вказану кількість градусів. Сам якір при цьому не рухається, тому та сама координата, яка розміщувала горизонтальну мітку, розміщує її повернутого двійника; змінюється лише напрямок, у якому марширують гліфи
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);
// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');
// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');
// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');
Дев'яносто градусів - це типовий випадок для мітки, яка йде вздовж краю діаграми, або для заголовка на корінці. Сорок п'ять градусів вирішують проблему нахилених заголовків стовпців - трюк, який дозволяє широкій мітці сидіти над вузьким стовпцем, не наїжджаючи на сусідів. Обертання не змінює того, як інтерпретується якір, і це часто збиває людей з пантелику: текст, повернутий на 90 градусів, усе ще починається з (X, Y) і росте звідти вгору, тому щоб відцентрувати повернуту мітку, ви коригуєте якір, а не кут. Коли кілька повернутих текстів поділяють спільну базову лінію, дайте їм однаковий Y і крокуйте по X, точнісінько так само, як ви крокували б по Y для складених один на одний горизонтальних рядків
Розміщення координат без вгадування
Координати - це та частина, яка витримує перевірку або тихо провалює її. HotPDF вимірює координати від лівого нижнього кута сторінки, причому Y росте вгору, у пунктах, 72 на дюйм. Сторінка формату US Letter має розмір 612 на 792 пункти; A4 - 595 на 842. Тому верхнє поле в один дюйм на форматі Letter розміщує вашу першу базову лінію поблизу Y = 792 мінус 72 мінус розмір шрифту, а не на якомусь малому числі біля верхнього краю. Кожен, хто приходить з екранних координат, де Y росте донизу від нуля, пише перший рядок за нижнім краєм і витрачає десять хвилин на роздуми про те, куди він подівся
Ставтеся до компонування як до арифметики відносно іменованих якорів, а не як до стовпця магічних чисел. Ліве поле, поточна базова лінія, яку ви зменшуєте на кожен рядок, та фіксований інтерліньяж перетворюють блок міток на короткий цикл замість стіни літералів:
const
LeftMargin = 72; // 1 inch in
TopBaseline = 720; // first line, ~1 inch down on Letter
Leading = 16; // vertical step between lines
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 // bottom margin reached
begin
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 11); // font resets on a new page
Y := TopBaseline;
end;
end;
end;
Перевірка на розрив сторінки - це рядок, який усі забувають у першу чергу, і через який застосунок страждає найсильніше. Під TextOut немає потокового компонування (flow layout). Якщо ви зменшите Y за межі нижнього поля, текст продовжить малюватися на обрізі, за межами сторінки, в нікуди, без жодного попередження. Тож ви самі стежите за Y, викликаєте AddPage, коли він перетинає "підлогу", і скидаєте базову лінію. Виклик SetFont після AddPage - це не необов'язкове перестраховування: поточний шрифт не переживає розриву сторінки, і перший рядок на новій сторінці вийде у шрифті переглядача за замовчуванням, якщо ви його пропустите
Інтервали між символами та словами для підгонки і вирівнювання
Іноді рядок правильний, але має не ту ширину: заголовок, який повинен розтягнутися на фіксовану лінію, код, який має читатися з просторішими цифрами, стовпець, який потребує зсуву значень для вирівнювання. PDF має два оператори стану тексту для цього: інтервал між символами (Tc - додатковий простір, що додається після кожного гліфа) та інтервал між словами (Tw - додатковий простір, що додається на кожному символі пробілу). Обидва виражаються в нерозмасштабованих одиницях текстового простору - фактично в пунктах при поточному розмірі шрифту. Це стан, а не аргументи для TextOut, тому ви встановлюєте їх, малюєте текст, а потім повертаєте все назад
// Letter-space a short heading so it stretches across a rule.
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); // reset before normal body text
// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name Department Extension');
Pdf.CurrentPage.SetWordSpacing(0);
Інтервал між словами діє лише на символ пробілу (код 32), що має один наслідок, який варто знати: він нічого не робить у тексті CJK, який не має ASCII-пробілів, і дивно взаємодіє з текстом, закодованим як індекси гліфів, а не байти. Для виведення латинських таблиць - це дешевий спосіб розширити проміжки без необхідності переписувати рядок. Інтервал між символами є кращим інструментом для заголовка, який має досягти цільової ширини, оскільки він рівномірно розподіляє коригування між усіма гліфами, а не накопичує його на пробілах
Скидання налаштувань - це основа дисципліни. Інтервали, як і шрифт, є частиною стану малювання сторінки, і стан зберігається, поки ви його не зміните. Задайте інтервал між символами одному заголовку і забудьте обнулити його, і кожен абзац нижче успадкує це розтягування, що сприйматиметься як ледь помітна, невловима помилка, яка проходить побіжне вичитування і провалюється на ретельному. Надійна звичка - встановити значення інтервалу, намалювати текст, який його потребує, і повернути його до нуля в наступному рядку, щоб жодному подальшому коду не доводилося думати, що там наробив попередній розділ
Перевірка виводу там, де він насправді ламається
Компонування тексту ламається на другій машині, а не на першій, тому важливі перевірки відбуваються не за вашим робочим столом. Відкрийте згенерований файл на системі, де не встановлено ваш набір шрифтів розробника, і переконайтеся, що вбудовані шрифти досі відмальовуються, включно з акцентованою латиницею, будь-якими нелатинськими системами письма та пунктуацією - за один прохід, замість точкової перевірки простих символів. Виділіть і скопіюйте кілька рядків, щоб переконатися, що текст є справжнім текстом, а не контурами, що має критичне значення, щойно на порядку денному з'являється пошук або вилучення даних. Годуйте компонуванню репрезентативні дані - найдовшу німецьку мітку та найширше число, а не акуратний заповнювач, тому що текст, який вилазить за межі поля - це завжди той текст, який ви не вводили вручну. І якщо сторінка має лягти на заздалегідь надрукований бланк, роздрукуйте або раструйте один зразок і накладіть його на оригінал; зсув базової лінії на чверть міліметра непомітний на екрані і кидається в очі на папері
Якщо ви ще не написали жодної сторінки, почніть із прикладу HotPDF Hello World, який налаштовує документ, шрифт і систему координат від лівого нижнього кута, від яких залежить усе вищесказане. Виклики TextOut, SetFont та функцій для роботи з інтервалами, показані тут, є частиною HotPDF Component для Delphi та C++Builder