HotPDF eksportuje stronę dowolnego wczytanego dokumentu PDF do samodzielnego znacznika SVG za pomocą jednego wywołania, BuildLoadedPageSVG, które zwraca kompletny dokument SVG jako ciąg znaków. Wyeksportowany znacznik zawiera geometrię strony, tekst jako prawdziwe elementy SVG text, osadzone obrazy rastrowe oraz stan obrysu, jaki operatory PDF ustaliły przy każdej operacji rysowania
Właśnie w tym miejscu większość domowych konwerterów po cichu się rozpada. Zamiana strony PDF na SVG wygląda jak problem współrzędnych, a okazuje się problemem stanu. PDF to maszyna stosowa, której stan grafiki zmienia się w trakcie interpretacji strumienia treści; SVG to drzewo deklaratywne, którego każdy element niesie własne atrybuty prezentacji. Wszystko, czego interpreter nie zarejestruje w chwili emitowania elementu, po prostu znika z wyniku, a błąd jest cichy: otrzymujesz poprawny SVG, który renderuje nieznacznie błędną stronę
Dlaczego strona PDF nie konwertuje się do SVG bezpośrednio?
Trzy niezgodności sprawiają, że konwersja nie jest trywialna, a wszystkie trzy dają wynik, który wygląda wiarygodnie, dopóki nie porówna się go bezpośrednio z oryginałem. Pierwszą jest oś y. Przestrzeń użytkownika PDF rośnie w górę od lewego dolnego rogu strony; SVG rośnie w dół od lewego górnego rogu. Pojedyncze odbicie na poziomie strony naprawia współrzędne rysowania, po czym psuje każdy glif, ponieważ odbicie całego płótna lustrzanie odwraca również kształty liter
Drugą niezgodnością jest dziedziczenie. W PDF operatory q i Q odkładają na stos i zdejmują ze stosu stan grafiki obejmujący szerokość linii, zakończenie linii, złączenie linii, limit ścięcia, tablicę kreskowania, fazę kreskowania i alfa. W SVG element, który nie podaje jakiegoś atrybutu, dziedziczy go po grupie nadrzędnej, co jest zupełnie inną regułą zasięgu. Eksporter, który śledzi tylko bieżącą macierz transformacji i zapomina o stanie obrysu, pozwala, by stan przywrócony po Q przeciekł do elementów następujących po nim
Trzecią jest to, że PDF wyraża pewne rzeczy poprzez konwencję, a nie poprzez wartość. Zakończenia i złączenia linii są liczbami całkowitymi, zerowa szerokość linii oznacza linię włosową w przestrzeni urządzenia, a nie linię niewidoczną, a warianty operatorów malowania z gwiazdką zmieniają regułę wypełniania zamiast koloru. Każda z tych rzeczy wymaga tłumaczenia, a nie kopiowania
Jedno wywołanie dla typowego przypadku
Do zwykłego zadania eksportu stron na potrzeby przeglądarki internetowej, narzędzia do porównywania różnic albo przekazania projektu, powierzchnia API sprowadza się do jednej funkcji. BuildLoadedPageSVG przyjmuje liczony od zera indeks strony w aktualnie wczytanym dokumencie i zwraca dokument SVG jako AnsiString:
var
Pdf: THotPDF;
I: Integer;
Svg: AnsiString;
Output: TFileStream;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('statements.pdf', '') <= 0 then
Exit; // LoadFromFile zwraca liczbę stron
for I := 0 to Pdf.LoadedPageCount - 1 do
begin
Svg := Pdf.BuildLoadedPageSVG(I);
if Length(Svg) = 0 then
Continue;
Output := TFileStream.Create(Format('page-%d.svg', [I + 1]), fmCreate);
try
Output.WriteBuffer(Svg[1], Length(Svg));
finally
Output.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Ten sam eksport jest dostępny w narzędziu wiersza poleceń HotPDF jako polecenie export-svg, co jest przydatne w potokach budowania i skryptach regresyjnych, gdzie potrzebna jest reprezentacja strony możliwa do porównania tekstowego bez pisania choćby linijki Pascala. Ponieważ SVG jest tekstem, stanowi naturalne uzupełnienie ścieżki rastrowej opisanej w renderowaniu strony PDF do mapy bitowej: mapa bitowa pokazuje, jak strona wygląda, a SVG pokazuje, z czego się składa
Jak tekst PDF jest mapowany na elementy tekstowe SVG?
HotPDF składa łańcuch macierzy tekstu jako prefiks razy CTM razy macierz tekstu razy odbicie glifu, gdzie odbicie glifu to mnożenie z prawej strony przez matrix(1,0,0,-1,0,0). Ten prawostronny czynnik istnieje wyłącznie po to, by zniwelować pionowe odbicie na poziomie strony dla kształtów glifów, ponieważ tekst SVG narysowany w odbitym układzie lokalnym w przeciwnym razie wyglądałby do góry nogami. Umieszczenie tej korekty w macierzy zamiast w kodzie obsługującym przypadki specjalne oznacza, że tekst obrócony, odbity lustrzanie i pochylony zawsze wychodzi poprawnie, bez dodatkowych rozgałęzień
Pozycjonowanie poziome wykorzystuje wielowartościową składnię x elementu SVG text, po jednej współrzędnej na znak, sumowaną z przyrostu każdego glifu plus obowiązujący w danej chwili odstęp między znakami Tc i odstęp między słowami Tw. Skalowanie poziome Tz jest wtopione w kolumny a i c macierzy tekstu zamiast być emitowane osobno, dzięki czemu przeglądarka ignorująca egzotyczne atrybuty tekstu nadal umieszcza każdy glif tam, gdzie umieścił go PDF. Tekst wyprodukowany przez złożone kształtowanie, opisany w kształtowaniu tekstu w złożonych pismach, przechodzi tę samą ścieżkę, ponieważ moduł kształtujący do momentu interpretacji strumienia treści już rozwiązał klastry na pozycjonowane glify
Obrót i obrazy: dwa odbicia, które łatwo pomylić kierunkiem
Strona z niezerowym wpisem /Rotate wymaga wstępnej transformacji złożonej z odbicia względem wysokości obróconego płótna oraz obrotu wyrażonego w przestrzeni wyświetlania z osią y skierowaną w górę. Trzy macierze obrotu to (0,-1,1,0,0,W) dla 90 stopni, (-1,0,0,-1,W,H) dla 180 i (0,1,-1,0,H,0) dla 270, gdzie W i H to wymiary strony sprzed obrotu. Wyprowadzanie ich ręcznie zaprasza błędy znaku dokładnie w trzech miejscach, więc eksporter składa je za pomocą tej samej procedury mnożenia macierzy, która obsługuje każdą inną transformację
Osadzone obrazy wymagają własnego odbicia, ponieważ przestrzeń obrazu PDF umieszcza pierwszy wiersz próbek przy górnej krawędzi kwadratu jednostkowego, podczas gdy element SVG image ma lokalny układ z osią y skierowaną w dół. Emitowana transformacja to zatem CTM pomnożone z prawej strony przez matrix(1,0,0,-1,0,1). Pomylenie tego kierunku daje pionowo odbite lustrzanie fotografie na poza tym idealnej stronie, czyli defekt, który recenzent zauważa natychmiast, a automatyczny test często nie
Co dokładnie zachowuje urządzenie stanu grafiki?
HotPDF przekazuje operatory stanu obrysu w, J, j, M i d przez osobny, opcjonalny interfejs urządzenia, dzięki czemu wierność obrysu dodano bez zmiany vtable istniejącego urządzenia treści i bez łamania kompatybilności binarnej dla kodu zbudowanego na wcześniejszych wersjach. W praktyce eksportowany SVG otrzymuje przetłumaczone słowa kluczowe zamiast surowych liczb całkowitych PDF:
// Całkowitoliczbowe wyliczenia PDF stają się atrybutami słów kluczowych SVG
// line cap 0, 1, 2 -> butt, round, square
// line join 0, 1, 2 -> miter, round, bevel
//
// Zerowa szerokość linii oznacza w PDF linię włosową w przestrzeni urządzenia, więc
// eksporter emituje vector-effect="non-scaling-stroke", aby obrys pozostał
// widoczny i miał szerokość bliską jednego piksela urządzenia po zastosowaniu CTM
//
// f* B* b* wybierają regułę even-odd i emitują fill-rule="evenodd",
// natomiast f B b zachowują domyślną w SVG regułę nonzero
Przywracanie stanu przy Q obejmuje razem krycie, szerokość linii, zakończenie, złączenie, limit ścięcia, tablicę kreskowania i fazę kreskowania. Zagnieżdżone Form XObject rejestrują i przywracają ten sam pełny zestaw na swoich granicach, dzięki czemu przerywana ramka zdefiniowana wewnątrz pieczątki nie może przeciekać swoim wzorem do treści strony, która następuje później. Jeśli z innych powodów już śledzisz zachowanie przycinania i CTM, jest to ten sam model stanu, który pojawia się w imporcie wektorowym EMF i WMF, działający w przeciwnym kierunku
Ograniczenia, które warto znać, zanim wdrożysz to produkcyjnie
Eksporter jest uczciwy co do swojego zakresu, a poznanie jego granic z góry jest tańsze niż odkrywanie ich na produkcji. Kolor dociera do urządzenia SVG przez operatory rg, RG, g i G. Wypełnienia ustalone przez przestrzeń kolorów plus scn, czyli sposób, w jaki malowane są kolory Separation, DeviceN i ICCBased, nie docierają do urządzenia jako rozwiązana trójka RGB, więc strony korzystające w ten sposób z kolorów dodatkowych eksportują swoją geometrię, ale nie te kolory. Dla źródeł zorientowanych na druk lepiej zamiast tego zrasteryzować albo najpierw spłaszczyć kolory dodatkowe; sam model malowania jest opisany w renderowaniu kolorów dodatkowych Separation i DeviceN
Dwie mniejsze uwagi oszczędzają czas debugowania. Szesnastkowe literały kolorów są emitowane wielkimi literami, więc test sprawdzający #ff0000 zawiedzie wobec całkowicie poprawnego #FF0000. Ponadto urządzenie SVG jest zliczane przez referencje za pośrednictwem swojego interfejsu, co oznacza, że jego zwolnienie polega na wyjściu interfejsu poza zakres, a nie na wywołaniu Free na obiekcie — rozróżnienie istotne, jeśli rozszerzasz urządzenie o emitowanie własnego znacznika obok treści strony
Eksport SVG naturalnie łączy się z porównaniem strukturalnym, gdy trzeba wiedzieć, czy wygenerowany dokument rzeczywiście zmienił się między dwiema kompilacjami. Szerszy zestaw narzędzi wokół wczytanych dokumentów, od renderowania przez edycję po eksport, jest opisany na stronie komponentu PDF HotPDF dla Delphi