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

Рендиране на PDF tiling шарки в Delphi с HotPDF

Щриховка, която се рендира като един плосък сив блок, е класическият провал при tiling шарка. HotPDF, нативният VCL PDF компонент за Delphi и C++Builder, рисува PatternType 1, като превръща текущия път във временно изрязване и преиграва потока със съдържание на шарката веднъж за всяка видима плочка, като изборът на шарка се пази в графичното състояние и се възстановява от q и Q

Симптомите пристигат в две форми, и изглеждат несвързани, докато не знаете причината. CAD чертеж губи щриховката на сечението си и се връща като плътни запълвания, защото рендерът е разрешил шарката до среден цвят и е нарисувал него. Или щриховката изтича: заглавен блок, който трябва да е обикновено бял, поема диагоналните линии от детайлен изглед два пътя по-рано. И двете са проблеми със състоянието на шарката, и само единият от тях изобщо е свързан с рисуването на плочки

Защо tiling шарка изтича върху следващия път?

Защото избраното име на шарка е част от графичното състояние, не свойство на оператора, който я е използвал. ISO 32000-1 §8.6.6.2 дефинира цветово пространство Pattern като такова, чиято стойност на цвета е име на шарка, подадено на scn или SCN, а всеки друг компонент на състоянието на цвета се запазва от q и възстановява от Q. Името на шарката трябва да следва същото правило. HotPDF го пази в записа на състоянието като FillPatternName и StrokePatternName, заедно със семейството цветово пространство за запълване и обвод, така че Q връща предишния избор обратно точно както връща предишния CTM

Съхранете това име в локална променлива вътре в диспечера на оператори вместо това, и то преживява всеки Q в потока. Провалът тогава се проявява някъде неочаквано: Form XObject, нарисуван след пътя с шарка, наследява избор на шарка, който собственият му поток със съдържание никога не е направил, и запълванията му излизат щриховани. Вложените форми влошават нещата, защото всяко ниво на вложеност бута и изважда състояние, което заблудената променлива игнорира. Задаването на цветово пространство, което не е шарка, с cs или CS, или издаването на обикновен g / rg / k, също трябва да изчисти името на шарката, иначе остарелият избор надживява цветовото пространство, което му е дало смисъл

q
  /Pattern cs              % pattern colour space, ISO 32000-1 8.6.6.2
  /P1 scn                  % coloured tiling pattern, PaintType 1
  10 10 200 120 re f       % this rectangle is hatched
Q
0 0 300 200 re f           % must be black again, not hatched

q
  /Cs2 cs                  % [/Pattern /DeviceCMYK] array
  0 0.6 1 0 /P2 scn        % uncoloured pattern plus its underlying colour
  20 20 160 90 re f*
Q

Шарка се рисува през изрязване, никога като запълване

Правилният модел е субтрактивен: ограничете изрязването на устройството до формата, която се рисува, после изпълнете съдържанието на шарката вътре в него. HotPDF никога не рисува плътно приближение първо и не го прерисува, защото междинното плътно запълване би било видимо през пролуките между плочките и би се борило с всякаква прозрачност в съдържанието на плочката. §8.7.3.2 описва tiling шарка като поток със съдържание, повтарян на фиксирани хоризонтални и вертикални интервали, а повтарянето има смисъл само спрямо изрязване, което вече има правилната форма. За запълвания преобразуването е директно: HPDFSelectFillPathClip задава режима на запълване на полигон на ALTERNATE за f*, B* и b* и на WINDING за нулевите варианти, изгражда GDI пътя и го пресича в изрязването с SelectClipPath. Точно този ред е това, което кара запълване с шарка по правило even-odd да оставя същите дупки като плътно запълване по even-odd, точно каквото има нужда област с щриховка във формата на поничка

Обводите са частта, лесна за объркване. Обведен път няма вътрешност, така че пресичането на самия път в изрязването дава празна област и нищо не се рисува. HPDFSelectStrokePathClip затова изгражда геометрична писалка от текущото състояние първо, използвайки PS_GEOMETRIC с крайната шапка от J, съединението от j, лимита на митрата от M, и PS_USERSTYLE, когато масив за пунктир е активен, после извиква WidenPath, за да превърне обведения контур в запълваема област преди изрязването. Поведението на шапката, съединението, митрата и пунктира по обведен път с шарка тогава съвпада с нормален обвод по конструкция, а не чрез втора имплементация. Две честни граници живеят тук: ширини на линия под една единица на устройството се прикачат до един пиксел, а масивът за пунктир се съкращава до шестнайсет записа, което е таванът, приеман от ExtCreatePen

Кои плочки всъщност са видими?

Видимият диапазон идва от пускане на трансформацията назад. Поставянето на плочки се случва в пространството на шарката, но единственото нещо, което знае колко от страницата се докосва, е кутията за изрязване на устройството, която е в пространството на устройството. HotPDF съставя BaseMatrix := CTM * PatternMatrix, обръща я и картографира четирите ъгъла на GDI кутията за изрязване обратно през обратната матрица. Подравнените с осите граници на тези четири картографирани ъгъла дават правоъгълника в пространството на шарката, който евентуално може да бъде покрит, а разделянето на този правоъгълник на XStep и YStep спрямо BBox на шарката дава затворени диапазони от индекси. Всяка клетка после се рендира с CTM от CTM * PatternMatrix * Translate(i * XStep, j * YStep), и бива изрязана втори път до собствения си трансформиран полигон BBox. Това второ изрязване има значение, когато XStep е по-малко от ширината на ограждащата кутия, което е начинът, по който се изразяват препокриващи се дизайни на плочки; без него съседни клетки биха рисували една над друга извън декларирания си обхват. Ако изрязването за отделна клетка се върне като NULLREGION, клетката се пропуска без токенизиране или изпълнение на каквото и да е

// Map the device clip box back into pattern space through the inverse of
// CTM * PatternMatrix, then convert those bounds into tile index ranges.
BaseMatrix := HPDFMatMul(FGSStack.State.CTM, PatternMatrix);
if not HPDFMatInvert(BaseMatrix, InverseMatrix) then Exit;   // singular: refuse
if GetClipBox(FDC, ClipRect) = ERROR then Exit;

// MinX..MaxY are the axis-aligned bounds of the four mapped clip corners.
I0 := Floor((MinX - BBox[2]) / StepXAbs);
I1 := Ceil ((MaxX - BBox[0]) / StepXAbs);
J0 := Floor((MinY - BBox[3]) / StepYAbs);
J1 := Ceil ((MaxY - BBox[1]) / StepYAbs);

PlannedTiles := Int64(I1 - I0 + 1) * Int64(J1 - J0 + 1);
if (PlannedTiles <= 0) or (PlannedTiles > FPatternTilesRemaining) then Exit;
Dec(FPatternTilesRemaining, Integer(PlannedTiles));

Безцветни шарки и цветът, който идва отвън

Шарка PaintType 2 носи форма, но не и цвят, а цветът пристига с името на шарката. §8.7.3.2 указва, че безцветна шарка се използва само с цветово пространство Pattern, което декларира подлежащо пространство, така че scn получава стойностите на компонентите първо, а името на шарката последно. HotPDF разрешава тези компоненти през подлежащото пространство, съхранено в записа на цветовото пространство на шарката, което означава, че безцветна щриховка може да бъде оцветена с мастило Separation или комбинация DeviceN точно както всяко друго запълване; механиката на това разрешаване е разгледана в рендиране на спот цветове Separation и DeviceN. Вътре в плочката двата типа на рисуване се разминават рязко. За PaintType 2 рендерът задава флаг за потискане на цветови оператори за времетраенето на плочката, така че всеки g, rg, k или scn в съдържанието на шарката се игнорира и всяка марка взема външно подадения цвят. За PaintType 1 важи обратното: състоянието на запълване и обвод се нулира до PDF стойностите по подразбиране, DeviceGray черно с идентично цветово пространство, и плочката се оцветява сама. Пропускането на това нулиране позволява цвета, който случайно е бил текущ при оператора f, да изтече в шарка, която е трябвало да бъде самоописваща се

Защо дълбочината на стека на графичното състояние трябва да бъде възстановявана след всяка плочка?

Защото потокът със съдържание на шарка е позволено да е небалансиран, и щетата се натрупва през клетките. Плочка, чийто поток съдържа три оператора q и два оператора Q, оставя стека едно ниво по-дълбоко, отколкото е започнал. Възстановете само текущия запис на състоянието между клетките и дълбочината продължава да расте, така че клетка номер двеста се изпълнява от рамка на стека, принадлежаща на клетка номер сто деветдесет и девет, с каквито CTM и изрязване тази рамка е носила. HotPDF затова прави снимка на записа на състоянието и дълбочината на стека преди цикъла на плочките и извиква RestoreSnapshot в началото на всяка итерация, което съкращава стека обратно до запазената дължина и преинсталира запазеното състояние в една стъпка. Речникът с Resources на страницата и флагът за потискане на цветовите оператори се възстановяват на същата граница, тъй като плочка може да реферира собствените си ресурси и не бива да ги подава на съседа си. Състоянието на изрязване на GDI получава същото третиране чрез двойка SaveDC / RestoreDC около всяка клетка, така че плочка, инсталираща собствено изрязване W n, не може да смали областта, налична за следващата

Бюджети, откази и какво рендерът няма да нарисува

Tiling шарките са най-лесното място в PDF за писане на файл за отказ на услуга, така че лимитите са твърди числа, не евристики. Влагането на шарки е ограничено на дълбочина 4, същата защита, използвана за рекурсия на Form XObject, която спира шарка, реферираща сама себе си през собствения си речник с ресурси. Единично рисуване на път може да изпълни най-много 16 384 плочки общо, броени надолу през вложени шарки и нулирани само когато рисуването на най-външната шарка започва. Мрежа от плочки, чийто планиран брой клетки надвишава оставащото от този бюджет, се отхвърля направо, преди дори една клетка да се изпълни

Изродена геометрия се отказва, вместо да се приближава. Липсваща или с нулева площ BBox, XStep или YStep, чиято величина е под 1e-6, произведение CTM * PatternMatrix без обратна матрица, картографирани координати за изрязване отвъд 1e9, или величина на индекс отвъд един милион — всички те карат рисуването на шарката да се върне без да рисува. Резултатът е непозиран регион вместо блокирала нишка за рендиране, което е компромисът, който искате в пакетен конвертор. Производителността идва от едно решение: потокът на шарката се токенизира веднъж на рисуване с HPDFTokenizeContentStream, а масивът с токени се преизползва във всяка видима клетка, така че броят плочки умножава разхода за изпълнение, но никога разхода за лексиране

Рендиране на страница с шарки от Delphi

Нищо в поддръжката на шарки не променя извикващия код. Заредете документа, поискайте страница, и работата с плочките се случва вътре в интерпретатора на потока със съдържание, който рендирането на страница в bitmap вече задвижва. Същият интерпретатор захранва контексти на устройства за bitmap, метафайл и принтер, така че щрихован чертеж, който изглежда правилно в мостра за преглед, се разпечатва със същата геометрия на плочките. Shading шарките PatternType 2 вземат различен клон, който споделя пътя си на оценка с голия оператор sh, описан подробно в рендиране на аксиални и радиални shading

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('assembly-drawing.pdf') > 0 then
    begin
      // Section hatching that previously flattened to a solid block now
      // replays the tile content once per visible cell.
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 200);
      if Assigned(Bmp) then
      try
        Bmp.SaveToFile('sheet1.bmp');
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Когато шарена област все още изглежда грешно, проверете трите класа провали поред. Област, която е изцяло празна, обикновено означава отказ: инспектирайте XStep, YStep и BBox за изродени стойности, или пребройте плочките, от които мрежата би имала нужда, спрямо тавана от 16 384. Област, изрисувана в единичен плосък цвят, означава, че името на шарката никога не е достигнало оператора за рисуване, което сочи към подредбата на cs и scn в потока. Шарка, появяваща се там, където не принадлежи, означава възстановяване на състоянието, и мястото, което да проверите, е обработката на q / Q около формата или пътя, който я е наследил

Tiling шарките са една от онези функции на PDF, които остават невидими, докато файлът, който се нуждае от тях, не кацне във входящата ви поща, и тогава те са цялата работа. Ако изграждате визуализатори на чертежи, конвертори на инженерни документи или рендери на отчети на Delphi или C++Builder, пълният компонент и API-то му за рендиране са документирани на страницата на HotPDF Delphi PDF компонент