Техническа статия

EMF векторен импорт на Free Pascal с PDFlibPas

PDFlibPas превръща enhanced metafile в истинско PDF съдържание на страница запис по запис, вместо да ги растеризира, което е това, което държи импортирана диаграма или CAD чертеж остър при всяко мащабиране; Този конвертор е около 6500 реда и беше написан срещу VCL, така че когато библиотеката придоби Free Pascal цел, той беше класифициран като непреносим и беше замазан със заглушка; Класификацията беше грешна, а начинът, по който беше грешна, е полезен урок за това как да одитирате зависимост, преди да решите да пренапишете около нея

Реалната VCL повърхност на тези 6500 реда се оказа малка: клас за bitmap, използван за неговия пикселен формат, записване в поток, handle, canvas и scanlines; клас за metafile, използван за неговата ширина, височина и handle; и типът цвят с две константи; Всяко едно от тях вече се предоставяше от собствения graphics модул на библиотеката, който съществува именно така, че non-VCL компилацията да има еквиваленти; Конверторът изобщо не беше блокиран от VCL; Той беше блокиран от Free Pascal Windows модула

Разделяне по оста, от която кодът реално зависи

Така че промяната не беше нова имплементация; Тя беше един условен израз: от „компилирай заглушката, когато се компилира без VCL“ към „компилирай заглушката, когато не се компилира за Windows“; Това е правилната ос, а изясняването защо прави разликата очевидна; Enhanced metafile е Windows контейнер; Конверторът е парсер за Windows GDI записи от горе до долу; Дали хост приложението използва VCL, друг набор от widget-и или изобщо никакъв набор няма нищо общо с това дали тези записи могат да бъдат интерпретирани; дали целта е Windows има всичко общо с това

Последиците от избора на правилната ос изтичат безплатно; C++Builder компилациите, които отдефинират символа на Windows платформата в тази библиотека, запазват хвърлящата заглушка и се държат точно както преди; macOS запазва заглушката, правилно, защото там няма GDI записи за парсване; Delphi VCL компилациите са недокоснати; И Windows компилация с non-VCL набор от widget-и придобива векторен EMF импорт като страничен ефект, което никой не е трябвало да имплементира; Условен израз, съгласуван с истинската зависимост, превръща работата по платформата в промяна от един ред; условен израз, съгласуван с грешната, я превръща в преписване, което никога не влиза в графика

Условният израз на EMF импорта е преориентиран от принадлежност към VCL към Windows платформата, запазвайки заглушки другаде и давайки векторен импорт на non-VCL Windows компилации
Преориентирането на условието за заглушка към Windows платформата запазва всяко съществуващо поведение на компилациите и дава на non-VCL Windows целите EMF векторен импорт безплатно

Пропастта на Free Pascal беше декларации, не логика

Това, което действително липсваше, бяха Win32 декларациите, които Delphi Windows модулът предоставя, а Free Pascal не; Събирането им в единичен compatibility модул, вместо разпръскване на условни изрази из конвертора, запази парсера четим; Списъкът е поучителен, защото показва колко неравномерно е покритието на headers между двете RTL: 113 константи за типове metafile записи, два флага за разширено текстово извеждане, три константи за режим на gradient fill, тип указател за handle таблица, псевдоними за gradient vertex и primitive записите и три типа записи, които Free Pascal изобщо не декларира, обхващащи alpha blending, transparent blitting и режим на управление на цветовете

Нито едно от това не е интересно поотделно; Всичко трябва да е право, преди парсерът да се компилира, а compatibility модулът е естествен дом, защото може да се диферира срещу header документацията като единица

Win32 декларации, липсващи от Free Pascal Windows модула, събрани в един compatibility модул за EMF към PDF векторния конвертор
Константи на записи, флагове, псевдоними и три отсъстващи типа записи живеят в един compatibility модул, който може да се диферира срещу header документацията

Онова, което мълшиво рисува грешната картина

Две от тези декларации не са просто липсващи, те са налични и грешни за тази цел, а това е частта, която си заслужава да запомните, дори никога да не докоснете metafile

Free Pascal декларира записа за създаване на четка със вградената run-time структура на четката, а записа за разширеното перо с вградената run-time структура на перото; И двете run-time структури декларират своя hatch член като цяло число с размера на указател, защото в живо GDI извикване този член може да носи handle; Metafile обаче винаги съхранява 32-битовата форма, защото оформлението на записа е част от сериализирания файлов формат и не се променя с битовата ширина на процеса

При 32-битови компилации двете съвпадат и нищо не се случва; На Win64 членът с размера на указател е осем байта, докато файлът има четири, така че всяко поле след hatch члена се чете от грешния offset; Няма изключение, няма грешка при парсване и няма предупреждение; Metafile просто се изобразява грешно: цветове от грешните байтове, ширини на перото от грешните байтове и картина, която изглежда като дефект при изобразяване, а не като дефект на struct оформлението; Delphi доставя изрично 32-битови варианти на двете структури именно поради тази причина, а compatibility модулът ги редекларира по същия начин

Байтово оформление на EMF записа на четката, показващо hatch поле с размера на указател, което измества следващите полета с четири байта на Win64 спрямо фиксираното 32-битово оформление
Сериализираният запис винаги съхранява 4-байтов hatch, така че run-time структурата с размера на указател мълшиво чете погрешно всяко следващо поле на Win64
// Грешно на 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 байта, както е съхранено в metafile
  end;

Общото правило: всяка структура, която се появява едновременно като run-time API аргумент и като сериализирано оформление на полета се нуждае от две декларации, а сериализираната трябва да използва фиксирани по ширина типове навсякъде; Членове с размера на указател във файлов формат са винаги дефект, чакащ 64-битова компилация

Разлики в сигнатурите принадлежат на wrapper, не на всяко място на извикване

Останалите разлики бяха обикновени несъответствия в сигнатурите, а начинът да се погълнат е forwarding wrapper, а не условен израз на всяко от местата за извикване; Функцията за съчетаване на трансформации приема указатели под Free Pascal, докато Delphi приема reference параметри, така че wrapper-ът приема референции и предава адреси; Той също копира и двата изходни аргумента в локални променливи първо, защото конверторът има места за извикване, където матрицата-приемник е едновременно един от източниците, и подаването на един и същ адрес два пъти на функция, която пише, докато чете, произвежда трансформация, която е тънко грешна по начин, който се проявява само върху завъртяно съдържание

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;

Типовете rectangle и point са другият случай; Free Pascal третира metafile записите за rectangle и point като отделни типове от общите graphics такива, така че осем места за присвояване се нуждаеха от изричен cast между записи с идентично оформление; И двата компилатора приемат cast формата, така че тези места не носят изобщо условен израз, което си струва малко грознота

Какво това променя за внедряване на Free Pascal

Векторният EMF импорт работи на Windows под Free Pascal, произвеждайки същото съдържание на страница като Delphi компилацията: paths като paths, градиенти като pattern съдържание, текст като текст; Извън Windows растерният път остава отговорът и това е ограничение на формата, а не на преноса; Координатното и clipping състоянието, в което конверторът захранва, е описано в статията за content stream CTM и clipping тракера, а векторните примитиви, които той излъчва, са разгледани в векторна графика, шейдъри и градиенти

Ако одитирате собствения си код за същата възможност, полезното упражнение е онова, което стартира това: избройте членовете, които действително използвате от фреймуърка, от който мислите, че зависите; Отговорът често е много по-кратък, отколкото списъкът за импорт подсказва, а истинското ограничение обикновено е някъде съвсем другаде; Пътищата за импорт, базирани на device context, изобщо са описани в статията за print preview и device context, а покритието на платформи и toolchain е изброено на продуктовата страница losLab PDF Developer Library