VimeoVimeo
RSSRSS
5.00 / 001

TCPDF - UTF-8 i polskie czcionki

Banner

Do generowania dokumentów PDF w PHP wykorzystuję popularną klasę TCPDF. Niestety ma ona szereg przeszkód, jakie musimy pokonać, aby uzyskać poprawnie działające i wyglądające czcionki z ogonkami. Naturalnie TCPDF  obsługę kodowania UTF-8, ale nie jest ona do końca idealna. Przede wszystkim musimy posiadać czcionki Unicode, które osadzone w dokumencie PDF znacząco zwiększają jego rozmiar. Pewne rozwiązanie problemu opublikował Karol Nowacki na swoim blogu, jednak nie do końca trafia ono w sedno problemu.

Od wersji TCPDF 5.2 pojawiła się możliwość osadzania podzbioru znaków (subsetting) wykorzystanej czcionki. Chodzi o to, że do dokumentu PDF dołączane są jedynie te znaki, które wykorzystaliśmy przy jego tworzeniu, ograniczając rozmiar pliku wynikowego. Rozwiązanie dobre, ale nie zawsze.

W przypadku zastosowania owego podzbioru, ograniczamy możliwość edycji pliku PDF osobom, które nie posiadają w systemie wykorzystanej przez nas czcionki. Po drugie, czas generowania dokumentu z wykorzystaniem subsettingu jest koszmarnie długi, dodatkowo uzależniony od ilości tekstu.

Rozmiar pliku czy szybkość generowania?

Do testów wykorzystamy fragment Pana Tadeusza, zapisanego w UTF-8 oraz czcionki DejaVuSans Unicode.

  1. "Litwo! Ojczyzno moja! Ty jesteś jak zdrowie. Ile cię stracił. Dziś człowieka rodu, obyczajów! Dość, że niecierpliwa młodzież nieraz jego wiernym ludem! Jak ów Wespazyjanus nie odmówi. To jedno puste miejsce, jak pieniądze Żydzi. To mówiąc spojrzał zyzem, gdzie mieszkał, dzieckiem będąc, przed młodzieżą o autorów pytała Tadeusza wsparła się tajemnie, Ścigany od baśni historyje gadał."

Przygotowana czcionka (dejavusans.z) dla TCPDF waży 339kB. Tak jak wspomniałem wcześniej, możemy osadzić ja całkowicie lub tylko jej podzbiór. Na początek osadzimy ją całkowicie :

  1. <?php
  2.  
  3.     $pdf = new TCPDFPL(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8');
  4.     $pdf->setFont('dejavusans', '', 10, '', false); // osadzamy całkowicie
  5.     $pdf->addPage();
  6.     $pdf->writeHTMLCell(100, 100, 10,50, $utf8);
  7.     $pdf->Output('document.pdf', 'D');
  8.  
  9. ?>

Dokument PDF generował się około 100ms, osiągając rozmiar 372kB. Zobaczmy jak to będzie wyglądało w przypadku osadzenia podzbioru czcionki :

  1. <?php
  2.  
  3.     $pdf = new TCPDFPL(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8');
  4.     $pdf->setFont('dejavusans', '', 10, '', true); // osadzamy podzbiór
  5.     $pdf->addPage();
  6.     $pdf->writeHTMLCell(100, 100, 10,50, $utf8);
  7.     $pdf->Output('document.pdf', 'D');
  8.  
  9. ?>

Tym razem dokument osiągnął rozmiar 50kB, jednak generował się ponad 5 sekund. Czas potrzebny na wygenerowanie dokumentu wzrasta zarówno w przypadku zwiększenia ilości tekstu jak i ilości wykorzystanych czcionek. Mamy zatem do wyboru mały rozmiar dokumentu, albo przyzwoity czas generowania pliku. Niestety - oba rozwiązania wciąż wymagają posiadania czcionki Unicode. Co więc w przypadku tradycyjnych czcionek TrueType (Arial, Helvetica, Times itp.) ?

Odchudzanie czcionek Unicode i nie tylko

Tutaj pojawia się pomysł Karola Nowackiego - możemy taką czcionkę odpowiednio odchudzić stosując jedynie mapę kodową ISO-8859-2. Zarówno dla czcionek TrueType Unicode jak i TrueType. Problem w tym, że TCPDF (jak słusznie zauważył autor) nie radzi sobie z tym kodowaniem i trzeba zrobić mały "myk".

Na początek postępujemy identycznie :

1. Tworzymy odchudzoną czcionkę dla TCPDF

ttf2ufm -b -L iso-8859-2.map DejaVuSans.ttf dejavusans
php -q makefont.php dejavusans.pfg dejavusans.afm iso-8859-2

2. Ponieważ polskie znaki nie różnią się szerokością od wersji bezogonkowych, dopisujemy ich szerokości wykorzystując szerokości ich odpowiedników do pliku dejavusans.php

  1. <?php
  2.  
  3.    $cw[260] = $cw[65]; // Ą = A
  4.    $cw[261] = $cw[97]; // ą = a
  5.    $cw[262] = $cw[67]; // Ć = C
  6.    $cw[263] = $cw[99]; // ć = c
  7.    $cw[280] = $cw[69]; // Ę = E
  8.    $cw[281] = $cw[101]; // ę = e
  9.    $cw[321] = $cw[76]; // Ł = L
  10.    $cw[322] = $cw[108]; // Ł = l
  11.    $cw[323] = $cw[78]; // Ń = N
  12.    $cw[324] = $cw[110]; // ń = n
  13.    $cw[211] = $cw[79]; // Ó = O
  14.    $cw[243] = $cw[111]; // ó = o
  15.    $cw[346] = $cw[83]; // Ś = S
  16.    $cw[347] = $cw[115]; // ś = s
  17.    $cw[377] = $cw[90]; // Ż = Z
  18.    $cw[378] = $cw[122]; // ż = z
  19.    $cw[379] = $cw[90]; // Ź = Z
  20.    $cw[380] = $cw[122]; // ź = z
  21.  
  22. ?>

3. Tworzymy klasę dziedziczącą TCPDF, nadpisując jedną z metod

  1. <?php
  2.  
  3.     class TCPDFPL extends TCPDF
  4.     {
  5.         protected function UTF8ToLatin1($str)
  6.         {
  7.             if (!$this->isunicode)
  8.             {
  9.                 return $str;
  10.             }
  11.             return iconv("UTF-8", "ISO-8859-2", $str);
  12.         }                       
  13.     }
  14.  
  15. ?>

4. Testujemy

  1. <?php
  2.  
  3.     $pdf = new TCPDFPL(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8');
  4.     $pdf->setFont('dejavusans', '', 10, '', false); // osadzamy całkowicie
  5.     $pdf->addPage();
  6.     $pdf->writeHTMLCell(100, 100, 10,50, $utf8);
  7.     $pdf->Output('document.pdf', 'D');
  8.  
  9. ?>

Tym razem przygotowana czcionka (dejvausans.z) zajmuje tylko 51kB. Bez problemu osadziliśmy ją całkowicie w dokumencie PDF przy czasie generowania zaledwie 60ms.

Wszystko pięknie, ale ...

Metoda Karola działa prawie prawidłowo, rozmiar dokumentu jest sporo mniejszy przy zachowaniu szybkości generowania PDF-a. Jednak dodane szerokości polski znaków nie zostały uwzględnione, co skutkuje niewłaściwym justowaniem tekstu lub wyrównaniem do prawego marginesu.

Zaradzić temu możemy poprzez nadpisanie jeszcze jedenej metody w klasie TCPDFPL :

  1. <?php
  2.  
  3.     class TCPDFPL extends TCPDF
  4.     {
  5.         protected function UTF8ArrToLatin1($unicode)
  6.         {
  7.             if ((!$this->isunicode) || $this->isUnicodeFont())
  8.             {
  9.                 return $unicode;
  10.             }
  11.                 
  12.             $outarr = array();
  13.                             
  14.             foreach ($unicode as $char)
  15.             {
  16.                 if (isset($this->CurrentFont['cw'][$char]))
  17.                 {
  18.                     $outarr[] = $char;
  19.                 } else
  20.                 if (array_key_exists($char, $this->unicode->uni_utf8tolatin))
  21.                 {
  22.                     $outarr[] = $this->unicode->uni_utf8tolatin[$char];
  23.                 } elseif ($char == 0xFFFD)
  24.                 {
  25.                 } else
  26.                 {
  27.                     $outarr[] = 63;
  28.                 }
  29.             }
  30.             
  31.             return $outarr;
  32.         }
  33.     
  34.         protected function UTF8ToLatin1($str)
  35.         {
  36.             if (!$this->isunicode)
  37.             {
  38.                 return $str;
  39.             }
  40.             return iconv("UTF-8", "ISO-8859-2", $str);
  41.         }                       
  42.     }
  43.  
  44. ?>

Po tym zabiegu, justowanie i pozycjonowanie tekstu znów działa prawidłowo.

Alternatywne rozwiązanie

W przypadku gdy nie chcemy bawić się w dopisywanie definicji szerokości dla polskich znaków w każdej czcionce, możemy użyć jeszcze jednej modyfikacji :

  1. <?php
  2.  
  3.     class TCPDFPL extends TCPDF
  4.     {
  5.         protected  function unicode2ascii($unicode)
  6.         {
  7.             switch ($unicode)
  8.             {
  9.                 case 260: return 65;
  10.                 case 261: return 97;
  11.                 case 262: return 67;
  12.                 case 263: return 99;
  13.                 case 280: return 69;
  14.                 case 281: return 101;
  15.                 case 321: return 76;
  16.                 case 322: return 108;
  17.                 case 323: return 78;
  18.                 case 324: return 110;
  19.                 case 211: return 79;
  20.                 case 243: return 111;
  21.                 case 346: return 83;
  22.                 case 347: return 115;
  23.                 case 377: return 90;
  24.                 case 378: return 122;
  25.                 case 379: return 90;
  26.                 case 380: return 122;
  27.                 default: return $unicode;
  28.             }
  29.         }
  30.         
  31.         protected function UTF8ArrToLatin1($unicode)
  32.         {
  33.             if ((!$this->isunicode) || $this->isUnicodeFont())
  34.             {
  35.                 return $unicode;
  36.             }
  37.                             
  38.             $outarr = array();
  39.                                         
  40.             foreach ($unicode as $char)
  41.             {
  42.                 $char = $this->unicode2ascii($char);
  43.                 
  44.                 if ($char < 256)
  45.                 {
  46.                     $outarr[] = $char;
  47.                 } else
  48.                 if (array_key_exists($char, $this->unicode->uni_utf8tolatin))
  49.                 {
  50.                     $outarr[] = $this->unicode->uni_utf8tolatin[$char];
  51.                 } else
  52.                 if ($char == 0xFFFD)
  53.                 {
  54.                 } else
  55.                 {
  56.                     $outarr[] = 63;
  57.                 }
  58.             }
  59.             
  60.             return $outarr;
  61.         }
  62.     
  63.         protected function UTF8ToLatin1($str)
  64.         {
  65.             if (!$this->isunicode)
  66.             {
  67.                 return $str;
  68.             }
  69.             return iconv("UTF-8", "ISO-8859-2", $str);
  70.         }                       
  71.     }
  72.  
  73. ?>

I tyle - mam nadzieję, że komuś się przyda :)

Podziel się tym wpisem : TCPDF - UTF-8 i polskie czcionki

Komentarze (12)

Gravatar

b00rt00s / 23 mar 2011 / 17:04

Sorry za offtop, ale na firefoksie4 trochę się twój blog rozjeżdża.
» link «

Czcionki są z grupy nimbus, rozmiar 13. Czcionki nimbus są małe i w standardowym rozmiarze 12pt. na wszystkich innych stronach (np. onet.pl) czcionki są mikroskopijne.

Tak dla porównania wygląda onet przy czcionce 13:
» link «

Mozilla Firefox 4.0 / Linux / Warszawa (mazowieckie)

Korneliusz / 23 mar 2011 / 18:44 Strona www «

Również używam FF4 od pierwszych wersji beta i nie zauważyłem. Myślę, że to kwestia rodziny czcionek o której piszesz. Możesz mi wyłożyć, skąd pomysł wykorzystywania innych krojów / rodzaju czcionek niż użyte na stronie ? Domyślam się, że wyłączyłeś opcję "Pozwalaj stronom stosować innych czcionek ..." wymuszając używanie własnych domyślnych ?

Mozilla Firefox 4.0 / Linux / Katowice (śląskie) Registered user
Gravatar

b00rt00s / 23 mar 2011 / 19:23

Po prostu nie miałem zainstalowanej czcionki arial. Dlatego firefox dopasowywał wybraną przeze mnie czcionkę typu sans. Jak doinstalowałem czcionkę arial wszystko wygląda należycie.

Swoją drogą jest to ciekawe, bo w operze pomimo braku czcionki arial wszystko wyświetlane było poprawnie...

Mozilla Firefox 4.0 / Linux / Warszawa (mazowieckie)

Korneliusz / 23 mar 2011 / 19:37 Strona www «

A spróbuj tak ( ja również nie mam Ariala ) : » link «

Mozilla Firefox 4.0 / Linux / Katowice (śląskie) Registered user
Gravatar

gordon / 23 mar 2011 / 21:53

U mnie wszystko dziala jak nalezy w ff4.

Chrome 11.0 / Linux / Kraków (małopolskie)
Gravatar

b00rt00s / 23 mar 2011 / 22:19

No i wiem w czym był problem. Miałem ustawioną minimalną czcionkę na 12 (to są chyba ustawienia domyślne firefoksa). Ustawiłem tak jak Ty i teraz jest ok.

P.S. A nie wiesz z czego może wynikać taka bladość czcionek?
» link «

Gdy czcionką jest arial takich cudów nie ma.

Mozilla Firefox 4.0 / Linux / Warszawa (mazowieckie)

Korneliusz / 23 mar 2011 / 22:46 Strona www «

Trochę dziwne, że miałeś minimalny rozmiar ustawiony na 12. To by znaczyło, że wszystkie o mniejszym rozmiarze były zwiększane do dwunastki O_o

Co do bladości, może hinting, źle dobrany w wygładzaniu? » link « a rozmiary mam takie » link «

Mozilla Firefox 4.0 / Linux / Katowice (śląskie) Registered user
Gravatar

b00rt00s / 24 mar 2011 / 00:09

Dobra, nie będę zawracał wam głowy. To nie temat na komentarz w tym wpisie. Będę musiał coś z tymi czcionkami pokombinować. Swoją drogą, to tylko na stronie dobreprogramy.pl jest problem. Po powiększeniu strony czcionki wyglądają ładnie. Są jakby za cienkie. Ale kończmy OT. GSAm poszukam rozwiązania.

Mozilla Firefox 4.0 / Linux / Warszawa (mazowieckie)
Gravatar

Mateusz / 25 mar 2011 / 09:06

Osobiście wolę korzystać z rozwiązań które generują PDF ze źródła w formacie HTML - dompdf, mpdf. Czas generowania dokumentu jest nieco dłuższy, za to drastycznie skraca się czas naszej pracy.
Ostatnio bawiłem się z tym: » link « - projekt renderuje PDF przy pomocy silnika WebKit, co daje ogromne możliwości.

Chrome 10.0 / Windows Seven / Poznań (wielkopolskie)

Korneliusz / 25 mar 2011 / 15:16 Strona www «

@Mateusz - tcpdf również generuje z HTMLa. Z wielką chęcią zobaczę dwa pozostałe projekty - przyznam się, że nie miałem okazji do nich jeszcze zaglądać. Ciekawe, czy oprócz konwertowania HTMLa potrafią samodzielną budowę dokumentów (to jest dla mnie bardziej kluczowy wymóg). Jak będę miał chwilę, na pewno zobaczę. Dzięki za ten cenny komentarz.

Mozilla Firefox 4.0 / Linux Registered user
Gravatar

canis_lupus / 02 kwi 2011 / 13:45

Polecam generowanie PDF z PHP wykorzystując do tego latexa. Bardzo prosto można uzyskać znakomite wizualnie efekty. A PDF wychodzi malutki.

Mozilla Firefox 4.0 / Linux / Poznań (wielkopolskie)
Gravatar

ptb / 29 sie 2011 / 15:02

Generalnie działa OK. W niektórych (rzadko) przypadkach wyrzuca mi taki błąd:
[ Warning ]: array_key_exists() [function.array-key-exists]: The second argument should be either an array or an object
Błąd wyskakuje w funkcji: protected function UTF8ArrToLatin1($unicode) przylinach:
else
if (array_key_exists($char, $this->unicode->uni_utf8tolatin))
{
$outarr[] = $this->unicode->uni_utf8tolatin[$char];
}
Szukałem w źródłowym TCPDF ale nie mogłem znaleźć co to jest za:
$this->unicode->uni_utf8tolatin
Czy macie jakieś sugestie dotyczące rozwiązania tego problemu?

Mozilla Firefox 6.0 / Windows Seven / Warszawa (mazowieckie)

Napisz komentarz

Jeśli piszesz po raz pierwszy, komentarz pojawi się dopiero po akceptacji. Kolejne komentarze będą umieszczane automatycznie. Proszę o komentarze wyłącznie związane z tematyką wpisów. Zauważone błedy proszę kierować na adres e-mail, który można znaleźć na stronie "Kontakt". Zastrzegam sobie prawo do kasowania lub nie akceptacji komentarzy, które naruszają ogólne zasady dobrego wychowania lub nie dotyczą publikowanej treści. Twój adres e-mail nie będzie wyświetlany w komentarzach.

Podstrony

Blog Fotografia Czytelnicy Kontakt Changelog Geolokalizacja TrackIP

Z prawej strony

  • 26 kwi 2012Paczki KDE 4.8.2 dla Slackware

    Dostępne są paczki KDE 4.8.2 dla Slackware 13.37. Źródła i binarki można pobarć z tego miejsca.

  • 29 wrz 2011Paczki KDE 4.6.5 dla Slackware

    Dostępne są paczki KDE 4.6.5 dla Slackware 13.37. Źródła i binarki można pobarć z tego miejsca.

  • 29 wrz 2011GNOME 3.2 już dostępny

    Informacje o wydaniu GNOME 3.2

Reklama

Popularne wpisy (ostatnie 7 dni)

Kategorie wpisów

Nuta tygodnia

Nuta tygodnia #9

Poprzednie nuty

Facebook

GooglePlus

Lubię odwiedzać

Warte odnotowania

Software monitor

2012-05-12 : Kernel (3.x) 3.3.6 75,3 MB 2012-05-11 : Wine 1.5.4 19,3 MB 2012-04-15 : Apache 2.4.2 3,9 MB 2011-08-04 : Kernel (2.6) 2.6.39.4 72,6 MB

Czytelnicy online

  • Aktualna liczba czytelników : 9