PDFlibPas перетворює розширені метафайли на справжній вміст сторінок PDF запис за записом, а не растеризує їх — саме тому імпортована діаграма чи CAD-креслення залишаються чіткими за будь-якого масштабування. Той конвертер налічує близько 6500 рядків і був написаний проти VCL, тож коли бібліотека отримала ціль Free Pascal, його класифікували як непереносний і замінили заглушкою. Ця класифікація була помилковою, а спосіб, у який вона була помилковою, — корисний урок про те, як перевіряти залежність, перш ніж вирішувати переписувати навколо неї
Фактична поверхня VCL у тих 6500 рядках виявилася малою: клас бітової карти, що використовується заради піксельного формату, збереження в потік, дескриптора, канви та рядків розгортки; клас метафайла заради ширини, висоти та дескриптора; і тип кольору з двома константами. Кожне з цього вже надавав власний графічний модуль бібліотеки, створений саме для того, щоб збірка без VCL мала еквіваленти. Конвертер узагалі не блокувався на VCL. Він блокувався на модулі Windows у Free Pascal
Розділяйте за віссю, від якої код справді залежить
Тож зміною була не реімплементація. Це була одна умовна компіляція: з «компілювати заглушку, коли збирається без VCL» на «компілювати заглушку, коли збирається не для Windows». Це правильна вісь, і формулювання причини робить різницю очевидною. Розширений метафайл — це контейнер Windows. Конвертер — це парсер записів GDI Windows від початку до кінця. Те, чи використовує хост-застосунок VCL, інший набір віджетів або жодного, не має жодного стосунку до того, чи можна інтерпретувати ці записи; те, чи ціллю є Windows, має до цього весь стосунок
Наслідки правильного вибору осі дістаються безкоштовно. Збірки C++Builder, які знімають символи платформи Windows у цій бібліотеці, лишають заглушку, що викидає виняток, і поводяться точно як досі. macOS лишає заглушку, правильно, бо там немає записів GDI для розбору. Збірки Delphi VCL не чіпані. А збірка під Windows із набором віджетів без VCL отримує векторний імпорт EMF як побічний ефект, який нікому не довелося реалізовувати. Умова, узгоджена зі справжньою залежністю, перетворює платформну роботу на однорядкову зміну; умова, узгоджена з неправильною, перетворює її на переписування, яке ніколи не потрапить у план
Пропущеним у Free Pascal були оголошення, а не логіка
Насправді бракувало оголошень Win32, які надає модуль Windows у Delphi і не надає модуль Windows у Free Pascal. Зібрати їх в один сумісний модуль, замість того щоб розсипати умовні компіляції конвертером, зберегло читабельність парсера. Список повчальний, бо показує, наскільки нерівномірне покриття заголовків між двома RTL: 113 констант типів записів метафайла, два прапорці розширеного текстового виводу, три константи режимів градієнтного заливання, тип вказівника на таблицю дескрипторів, псевдоніми для записів градієнтних вершин і примітивів та три типи записів, які Free Pascal взагалі не оголошує, — вони покривають альфа-змішування, прозоре блітування та режим керування кольором
Окремо кожне з цього нецікаве. Усе воно має бути правильним, перш ніж парсер скомпілюється, а сумісний модуль — природний дім для нього, бо його можна звіряти з документацією заголовків як одне ціле
Той, що мовчки малює неправильну картину
Два з тих оголошень не просто відсутні — вони присутні й неправильні для цієї цілі, і це та частина, яку варто запам'ятати, навіть якщо ви ніколи не торкатиметеся метафайла
Free Pascal оголошує запис створення пензля з вбудованою структурою пензля часу виконання, а запис розширеного пера — з вбудованою структурою пера часу виконання. Обидві ті структури часу виконання оголошують свій член hatch як ціле число розміру вказівника, бо в живому виклику GDI цей член може нести дескриптор. Метафайл, однак, завжди зберігає 32-бітову форму, бо розкладка запису є частиною серіалізованого формату файла і не змінюється з розрядністю процесу
На 32-бітових збірках обидва збігаються, і нічого не відбувається. На Win64 член розміру вказівника — вісім байт там, де файл має чотири, тож кожне поле після члена hatch читається з неправильного зміщення. Ні винятку, ні помилки розбору, ні попередження. Метафайл просто малюється неправильно: кольори з чужих байтів, товщини пера з чужих байтів, а картина виглядає як вада рендерингу, а не як вада розкладки структури. Delphi постачає явно 32-бітові варіанти обох структур саме з цієї причини, і сумісний модуль оголошує їх так само
// Неправильно на Win64: Hatch має розмір вказівника, файл зберігає
// 32 біти, і кожне наступне поле зсувається на чотири байти без помилки
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 байтів у 64-бітовому процесі
end;
// Правильно: серіалізована розкладка фіксованої ширини незалежно
// від розрядності
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // завжди 4 байти, як у метафайлі
end;
Загальне правило: будь-яка структура, що з'являється і як аргумент API часу виконання, і як серіалізована розкладка полів, потребує двох оголошень, а серіалізоване має всюди використовувати типи фіксованої ширини. Члени розміру вказівника у форматі файла — це завжди вада, що чекає на 64-бітову збірку
Відмінності сигнатур належать обгортці, а не кожному місцю виклику
Решта відмінностей були звичайними невідповідностями сигнатур, і спосіб їх поглинути — обгортка-переадресовувач, а не умова в кожному місці виклику. Функція поєднання трансформацій приймає вказівники під Free Pascal там, де Delphi приймає параметри-посилання, тож обгортка приймає посилання і передає адреси. Вона також спершу копіює обидва вихідні аргументи в локальні змінні, бо конвертер має місця виклику, де матриця призначення водночас є однією з джерел, а передача тієї самої адреси двічі функції, що пише під час читання, дає трансформацію, тонко неправильну так, що це проявляється лише на повернутому вмісті
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Спершу копіюємо: викликачі цілком законно передають Dest як A або B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Типи прямокутника й точки — інший випадок. Free Pascal вважає записи прямокутника й точки метафайла окремими типами щодо загальних графічних, тож вісім місць присвоєння потребували явного приведення між записами однакової розкладки. Обидва компілятори приймають форму приведення, тож ті місця взагалі не несуть умовної компіляції, а це варте невеликої негарноти
Що це змінює для розгортання на Free Pascal
Векторний імпорт EMF працює на Windows під Free Pascal і дає той самий вміст сторінок, що й збірка Delphi: шляхи як шляхи, градієнти як вміст шаблонів, текст як текст. Поза Windows відповіддю лишається растровий шлях, і це обмеження формату, а не порту. Координатний стан і стан відсікання, у який конвертер записує, описано в статті про трекер CTM і відсікання потоку вмісту, а векторні примітиви, які він випромінює, охоплені в векторній графіці, шейдерах і градієнтах
Якщо ви перевіряєте власну кодову базу на таку саму нагоду, корисна вправа — та, з якої все почалося: перелічіть члени, які ви справді використовуєте з фреймворка, від якого, як вам здається, залежите. Відповідь часто значно коротша, ніж навіює список імпорту, а справжнє обмеження зазвичай десь зовсім інше. Шляхи імпорту на основі контексту пристрою загалом описані в статті про попередній перегляд друку та контекст пристрою, а покриття платформ і інструментаріїв наведене на сторінці продукту losLab PDF Developer Library