(artykuł)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
dział serwisuArtykuły
kategoriaInne artykuły
artykuł[C, C++] Generowanie plików PDF - biblioteka LibHaru
Autor: Grzegorz 'baziorek' Bazior

[C, C++] Generowanie plików PDF - biblioteka LibHaru

[artykuł]

Intro

Format PDF jest obecnie najpopularniejszym do prezentacji dokumentów, coraz więcej programów oferuję generację plików PDF, dlatego warto aby w polskiej literaturze internetowej znalazł się opis biblioteki dla C/C++ umożliwiającej właśnie generację dokumentów PDF.
Chcąc napisać artykuł wybrałem bibliotekę LibHaru poleconą na stackOverflow.

LibHaru co to jest

LibHaru jest to darmowa, przenośna między systemami, open source'owa biblioteka do generowania plików PDF (jak na razie nie umożliwia edycji już istniejących dokumentów PDF), napisana w ANSI C.

Bibliotek daje następujące możliwości:
  • Generowanie PDFów z liniami, tekstem, obrazkami,
  • Obrysowywanie, adnotacje tekstowe, adnotacje do linków
  • Outline, text annotation, link annotation.
  • Kompresowanie dokumentów
  • Zagnieżdżanie obrazków PNG oraz JPEG
  • Zagnieżdżanie czcionek
  • Tworzenie zaszyfrowanych plików PDF
  • Używanie różnych kodowań (ISO8859-1~16, MSCP1250~8, KOI8-R)
  • Wspieranie czcionek i kodowania CJS (Chinese, Japanese, Korean)
Kolejnym plusem biblioteki jest to że aby się nią posługiwać nie trzeba znać struktury plików PDF

Instalacja

Aby zacząć trzeba najpierw pobrać biblioteki na odpowiedni system operacyjny z tąd.

Linux

Po pobraniu odpowiedniej wersji (ja używam wersji libharu-2.2.1 na Linuxa) należy plik rozpakować, wejść do katalogu i zainstalować (najlepiej z poziomu roota) w ten sposób:

tar -xvzf libharu-2.2.1.tar.gz
cd libharu-2.2.1
sudo ./configure && make && make install
Do opcji ./configure można dodać opcjonalne parametry: --prefix=/usr/local --with-zlib --with-png --with-libdir=lib
Po jakimś czasie instalacja dobiegnie końca i będzie można skompilować pierwszy program. Kompilujemy dodając odpowiednie flagi linkera:
-lhpdf jest wymagana gdy używamy biblioteki
-lpng gdy dodajemy do naszych PDFów plików PND
-mno-cygwin ta opcja jest wymagana gdy chcemy używać MinGW na kompilatorze cygwin
ponadto jeżeli kompilator lub linker nie może znaleźć bibliotek musimy dodać jeszcze 2 opcje -L../../ -I../include
-lstdc++ jest to dodatkowa flaga potrzebna gdy kompilujemy nasz program używając tej biblioteki z poziomu C++ (a nie z poziomu C)
Gdyby mimo wszystkich tych zabiegów program nie będzie chciał się skompilować można ustawić zmienną środowiskową na odpowiednią ścieżkę:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib ; export LD_LIBRARY_PATH

Windows

Instrukcja instalacji dla Windowsa znajduje się na tej stronie.
Ponadto dostępne są również instrukcje jak kompilować na różnych kompilatorach.

Prosta generacja PDFów

Zanim pokażę nawet najprostrzy przykład muszę niestety opisać parę funkcji. W tym też punkcie umieszczam linka do oficjalnej dokumentacji

Opis podstawowych funkcji do tworzenia PDFów

HPDF_Doc HPDF_New(HPDF_Error_Handler funkcjaWRazieBledow, void *dane_uzytkownika); - tworzy instancje obiektu HPDF_Doc i ją inicjalizuje, funkcjaWRazieBledow jest to funkcje używana podczas obsługi błędów, mająca np. taką strukturę:
C/C++
void funkcjaWRazieBledow( HPDF_STATUS nr_bledu, HPDF_STATUS numer_szczegolow, void * dane_uzytkownika ) {
    printf( "BLAD!!!: nr_bledu=%04X, numer_szczegolow=%u\n",( HPDF_UINT ) nr_bledu,( HPDF_UINT ) numer_szczegolow );
}
dane_uzytkownika - zdefiniowany przez użytkownika wskaźnik używany podczas obsługi błędów

void HPDF_Free(HPDF_Doc pdf); - funkcja ta czyści pamięć i wszystkie zasoby potrzebne do pracy z dokumentem PDF, jako argument przyjmuje strukturę zaalokowaną przez HPDF_New().

HPDF_Page HPDF_AddPage(HPDF_Doc pdf); - dodaje stronę do dokumentu podanego jako argument funkcji, funkcja zwraca uchwyt do tej strony. Możliwe błędy:
HPDF_INVALID_DOCUMENT - podano niewłaściwy uchwyt do dokumentu
HPDF_FAILD_TO_ALLOC_MEM - błąd alokacji pamięci

HPDF_Page HPDF_InsertPage(HPDF_Doc pdf, HPDF_Page strona_istniejaca); - wstawia nową stronę przed istniejącą  i zwraca uchwyt do nowo utworznej strony. Możliwe błędy jak wyżej, a ponadto:
HPDF_INVALID_PAGE - podano niewłaściwy uchwyt do strony.

HPDF_STATUS HPDF_SaveToFile(HPDF_Doc pdf, const char *nazwa_pliku); - zapisuje obecny dokument do pliku, jeżeli się uda zwracany jest HPDF_OK, w innym przypadku możliwe są kody błędów:
HPDF_INVALID_DOCUMENT - podano niewłaściwy uchwyt do dokumentu
HPDF_FAILD_TO_ALLOC_MEM - błąd alokacji pamięci
HPDF_FILE_IO_ERROR - błąd w trakcie opecacji wejścia/wyjścia

Do operowania na dokumencie (z pominięciem wpisywania doń treści) wystarczą powyższe funkcje, natomiast dla dociekliwych dołączam opis innych, przydatnych funkcji do operowania na dokumencie:

HPDF_Doc HPDF_NewEx(HPDF_Error_Handler funkcjaWRazieBledow, HPDF_Alloc_Func funkcjaAllokujaca, HPDF_Free_Func funkcjaZwalniajacaPamiec, HPDF_UINT rozmiar_puli_pamieci, void *dane_uzytkownika); - jest to funkcja, która podobnie jak HPDF_New() tworzy i inicjalizuje obiekt dokumentu PDF. Parametery:
funkcjaWRazieBledow - zdefiniowana prez użytkownika funkcja do obsługi błędów, wywoławana automatyczniem w razie wystąpienia błędu
funkcjaAllokujaca - zdefiniowana przez użytkownika funkcja allokująca pamięć, gdy podamy NULL wedy malloc() jest używany
funkcjaZwalniajacaPamiec - zdefiniowana przez użytkownika funkcja zwalniająca pamięć, gdy NULL wedy free() jest używany
rozmiar_puli_pamieci - libHaru domyślnie nie używa puli pamięci, natomiast jeżeli ten parametr nie jest 0 zostanie używa pula pamięci.
dane_uzytkownika - zdefiniowany przez użytkownika wskaźnik używany podczas obsługi błędów

HPDF_STATUS HPDF_NewDoc(HPDF_Doc pdf); - tworzy nowy dokument. Jeżeli HPDF_Doc zawiera już dokument, pamięć z tego dokumentu jest wpierw zwalniana. Możliwe błędy: HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM.

void HPDF_FreeDocAll(HPDF_Doc pdf); - zwalnia zasoby zajęte przez dokument podany w argumencie.

void HPDF_FreeDoc(HPDF_Doc pdf); - zwalnia zasoby zajęte przez dokument podany w argumencie. W przeciwieństwie do powyższej funkcji HPDF_FreeDoc zachowuje informacja o m.in. czcionkach i kodowaniu.

HPDF_STATUS HPDF_SetErrorHandler(HPDF_Doc pdf, HPDF_Error_Handler funkcjaWRazieBledow); - ustanawia zdefiniowaną przez użytkownika funkcję do obsługi błędów, wywołaną automatycznie w razie błędu. Możliwy do zwrócenia błąd tej funkcji: HPDF_INVALID_DOCUMENT.

HPDF_STATUS HPDF_GetError(HPDF_Doc pdf); - zwraca ostatni kod błędu, lub HPDF_OK, gdy takowego nie było.

void HPDF_ResetError(HPDF_Doc pdf); - funkcja czyści ostatni kod błędu. Jest to potrzebne, ponieważ po pojaweniu się jakiegokolwiek błędu funkcje wyjścia/wejścia "nie zechcą zadziałać" gdy nie wywołamy wpierw funkcji HPDF_ResetError().

HPDF_BOOL HPDF_HasDoc(HPDF_Doc pdf); - jeżeli mamy poprawny uchwyt do struktury dokumentu zostanie zwrócone HPDF_TRUE, w innym przypadku HPDF_INVALID_DOCUMENT

Przykład na generację PDFa w oparciu o powyższe funkcje

Powyżej opisałem funkcje do operowania na strukturze dokumentu PDF biblioteki LibHaru, żeby dobrze posługiwać się tą biblioteką potrzebne są jeszcze funkcje zapisujące do PDFa różne treści, zanim je jednak opiszę umieszczam poniżej przykładowy kod który utworzy dwu-stronowy, pusty dokument PDF (wygenerowany przez program dokument):
C/C++
#include <stdio.h>
#include <hpdf.h>

void opiszBlad( HPDF_STATUS status ) {
    if( status != HPDF_OK )
    switch( status ) {
    case HPDF_INVALID_DOCUMENT: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do dokumentu\n" ); break;
    case HPDF_FAILD_TO_ALLOC_MEM: printf( "!!!BLAD!!! Nie można zaalokować pamięci\n" ); break;
    case HPDF_INVALID_PAGE: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do strony\n" ); break;
    case HPDF_FILE_IO_ERROR: printf( "!!!BLAD!!! problem w trakcie opecacji wejścia/wyjścia \n" ); break;
        default: printf( "!!!BLAD!!! nieznany o kodzie: %ld\n", status ); break;
    };
}

void funkcjaWRazieBledow( HPDF_STATUS nr_bledu, HPDF_STATUS numer_szczegolow, void * dane_uzytkownika ) {
    printf( "BLAD: nr_bledu=%04X, numer_szczegolow=%u\t",( HPDF_UINT ) nr_bledu,( HPDF_UINT ) numer_szczegolow );
    opiszBlad( nr_bledu );
}

int main() {
    HPDF_Page strona1;
    HPDF_Doc pdf = HPDF_New( funkcjaWRazieBledow, NULL );
   
    if( !pdf )
         printf( "!!!BLAD!!! Nie mozna utworzyc dokumentu PDF\n" );
   
    strona1 = HPDF_AddPage( pdf );
   
    HPDF_InsertPage( pdf, strona1 );
   
    HPDF_SaveToFile( pdf, "pdf1.pdf" );
   
    HPDF_Free( pdf );
} //    gcc -o pdf1 -lhpdf  pdf1.c && ./pdf1

Funkcje do zarządzania wyświetlaniem stron PDFa

Zanim zaczniemy wpisywać jakąś treść do naszego dokumentu PDF warto byłoby co nie co ustawić parametry strony, do tego właśnie służą poniższe funkcje:
HPDF_STATUS HPDF_SetPageMode(HPDF_Doc pdf, HPDF_PageMode tryb); - ustawia w jaki sposób dokument powinien zostać wyświetlany, tryby do wyboru:
  • HPDF_PAGE_MODE_USE_NONE - wyświetla dokument bez spisu treści i miniatur
  • HPDF_PAGE_MODE_USE_OUTLINE - wyświetla ze spidem treści
  • HPDF_PAGE_MODE_USE_THUMBS - wyświetla z miniaturami
  • HPDF_PAGE_MODE_FULL_SCREEN - wyświetla dokument ze spisem treści i miniaturami
funkcja w razie błędu zwróci: HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_PAGE_MODE_OUT_OF_RANGE (ostatni błąd pojawia się gdy zły tryb strony zostanie podany)

HPDF_PageMode HPDF_GetPageMode(HPDF_Doc pdf); - wyświetla obecny tryb strony

HPDF_Page HPDF_GetCurrentPage(HPDF_Doc pdf); - zwraca uchwyt do obecnej strony w dokumencie (gdy się uda), w razie niepowodzenia zwraca NULL

HPDF_STATUS HPDF_SetOpenAction(HPDF_Doc pdf, HPDF_Destination akcja_docelowa); - ustawia daną akcję po otwarciu dokumentu (np. ustawienie w pewnym miejscu tekstu). Możliwe błędy: HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_INVALID_DESTINATION (podano niewłaściwą akcję docelową).

HPDF_STATUS HPDF_SetPageLayout(HPDF_Doc pdf, HPDF_PageLayout wyglad_stronicowy); - ustawia w jaki sposób strony powinny zostać wyświetlone, gdy nie użyjemy tej funkcji pozostaje domyślny sposób wyświetlania w programie do przeglądania PDFów. Możliwe sposoby wyświetlania stron:
  • HPDF_PAGE_LAYOUT_SINGLE - tylko jedna strona jest wyświetlana
  • HPDF_PAGE_LAYOUT_ONE_COLUMN - wyświetlanie stron w jednej kolumnie
  • HPDF_PAGE_LAYOUT_TWO_COLUMN_LEFT - wyświetlanie w dwóch kolumnach, nieparzyste strony po lewej
  • HPDF_PAGE_LAYOUT_TWO_COLUMN_RIGHT - wyświetlanie w dwóch kolumnach, nieparzyste strony po prawej
Możliwe błędy:  HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_PAGE_LAYOUT_OUT_OF_RANGE (podano zły sposób wyświetlania strony)

HPDF_PageLayout HPDF_GetPageLayout(HPDF_Doc pdf); - zwraca informacje o sposobie wyświetania stron w dokumencie

HPDF_STATUS HPDF_SetPagesConfiguration(HPDF_Doc pdf, HPDF_UINT strony_na_strone); - ta funkcja jest bardziej skomplikowana, ale warto o niej napisać, w razie gdyby ktoś chciał w dokumencie PDF zapisać więcej niż 8191 stron. Żeby ją opisać muszę najpierw doprecyzować o co chodzi z tą liczbą stron - domyślnie HPDF_Doc ma jeden "root" na którym są wszystkie strony tworzone, maksymalnie może być ich 8191. Ponadto bardzo duża stron pod jednym obiektem jest pewnym obciążeniem dla aplikacji wyświetlającej, dlatego wywołując tą funkcję można wprowadzić dwu-poziomową zależność, dzięki której można pomieścić w jednym dokumencie stron 8191*strony_na_strone. Dodam że aby użyć tej funkcji dokument jeszcze nie może posiadać stron.

Zapis tekstu do PDFów

Zanim pokażę w jaki sposób zapisywać tekst do dokumentów muszę przedstawić parę funkcji:

Funkcje służące do odpowiedniego zapisywania tekstów do plików PDF

HPDF_REAL HPDF_Page_GetHeight(HPDF_Page strona); i HPDF_REAL HPDF_Page_GetWidth(HPDF_Page strona); - funkcje zwracają odpowiednio wysokość i szerokość strony, w razie błędu zwracają 0.

HPDF_Font HPDF_GetFont(HPDF_Doc pdf, const char *nazwa_czcionki, const char *nazwa_kodowania); - zwraca uchwyt do chcianej czcionki (informacje o dostępnych kodowaniach, informacje o dostępnych czcionkach), nazwa kodowania może być NULL.
Możliwe błędy: HPDF_FAILD_TO_ALLOC_MEM, HPDF_INVALID_DOCUMENT, HPDF_INVALID_FONT_NAME (podano niewłaściwą nazwę czcionki), HPDF_INVALID_ENCODING_NAME (podano niewłaściwą nazwę kodowania), HPDF_UNSUPPORTED_FONT_TYPE (niewspierany typ lub zbiór czcionek).

HPDF_STATUS HPDF_Page_SetFontAndSize(HPDF_Page strona, HPDF_Font czcionka, HPDF_REAL rozmiar_czcionki); - ustawia typ i rozmiar czcionki

HPDF_STATUS HPDF_Page_BeginText(HPDF_Page strona); - rozpoczyna pisanie tekstu na stronie i ustawie pozycję pisania na 0,0.

HPDF_STATUS HPDF_Page_EndText(HPDF_Page strona); - kończy pisanie tekstu na stronie

HPDF_REAL HPDF_Page_TextWidth(HPDF_Page strona, const char *text); - zwraca aktualną szerokość podanego tekstu z uwzględnieniem wielkości czcionki, odstępów między literami i między wyrazami. Przydatna jest gdy chcemy wyśrodkować tekst, lub po prostu zmieścić go w linijce.

HPDF_STATUS HPDF_Page_TextOut(HPDF_Page strona, HPDF_REAL pozycjaX, HPDF_REAL pozycjaY, const char *text); - zapisuje podany tekst w podanej pozycji na stronie.

HPDF_STATUS HPDF_Page_MoveTextPos(HPDF_Page strona, HPDF_REAL x, HPDF_REAL y); - przestawia pozycję tekstu o x, y

HPDF_STATUS HPDF_Page_MoveTextPos2(HPDF_Page strona, HPDF_REAL x, HPDF_REAL y); - przestawia pozycję tekstu o x, y; ponadto prowadzący tekst jest ustawione na -y (w oryginale: "Also, the text-leading is set to -y")

HPDF_STATUS HPDF_Page_ShowText(HPDF_Page strona, const char *text); - zapisuje tekst na bieżącej pozycji na stronie

HPDF_Point HPDF_Page_GetCurrentTextPos(HPDF_Page strona); - zwraca aktualną pozycję tekstu na stronie, możliwe jest sprawdzenie jedynie gdy pracujemy w trybie: HPDF_GMODE_TEXT_OBJECT. W razie błędu zwrócony punkt wyniesie {0, 0}.

Powyższe funkcje powinny wystarczyć do zapisywania przeróżnych tekstów w różnych wielkościach, formatach i miejscach, warto abym dopisał parę dodatkowych funkcji:

HPDF_STATUS HPDF_Page_MoveToNextLine(HPDF_Page strona); - przesuwa tekst na stronie do początku linii na której piszemy

HPDF_STATUS HPDF_Page_MoveTo(HPDF_Page strona, HPDF_REAL x, HPDF_REAL y); - przesuwa punkt startowy pisania na punkt X,Y

HPDF_STATUS HPDF_Page_SetTextRenderingMode(HPDF_Page strona, HPDF_TextRenderingMode mode); - ustawia sposób renderowania tekstu spośród: HPDF_FILL (domyślny), HPDF_STROKE, HPDF_FILL_THEN_STROKE, HPDF_INVISIBLE, HPDF_FILL_CLIPPING, HPDF_STROKE_CLIPPING, HPDF_FILL_STROKE_CLIPPING, HPDF_CLIPPING. Nie ma sensu ich wszystkich opisywać, jak wyglądają zobaczymy tutaj.

HPDF_STATUS HPDF_Page_SetTextRise(HPDF_Page strona, HPDF_REAL wartosc); - ustawia pozycje tekstu wkierunku pionowym zależnie od podanej wartości.

HPDF_STATUS HPDF_Page_SetWordSpace(HPDF_Page strona, HPDF_REAL value); - ustawia odstępy między słowami

HPDF_STATUS HPDF_Page_ShowTextNextLine(HPDF_Page strona, const char *text); - przestawia pozycje pisania tekstu na początek nowej linii po czym wpisuje tekst

HPDF_STATUS HPDF_Page_ShowTextNextLineEx(HPDF_Page strona, HPDF_REAL odstep_miedzy_slowami, HPDF_REAL odstep_miedzy_literami, const char *text); - przesuwa pozycję pisania tekstu na początek nowej linii, następnie ustawia odstępy między wyrazami i literami, po czym wpisuje tekst

HPDF_STATUS HPDF_Page_TextRect(HPDF_Page strona, HPDF_REAL zLewej, HPDF_REAL zGory, HPDF_REAL zPrawej, HPDF_REAL zDolu, const char *text, HPDF_TextAlignment wyrownanie_tekstu, HPDF_UINT *ilosc_liter); - wpisuje tekst w określonym obszarze (prostokącie), umiejscowionym między współrzędnymi z..., jeśli ilosc_liter nie jest NULL wtedy ilość liter zapisanych w danym obszarze zostanie zwrócona. Możliwe wyrównania takstu:
HPDF_TALIGN_LEFT, HPDF_TALIGN_RIGHT, HPDF_TALIGN_CENTER, HPDF_TALIGN_JUSTIFY

HPDF_UINT HPDF_Font_GetXHeight(HPDF_Font czcionka); - funkcja zwraca aktualny odstęp między linijkami dla małych liter aktualnej czcionki. W razie błędu zwracane jest 0.

HPDF_UINT HPDF_Font_GetCapHeight(HPDF_Font czcionka); - funkcja zwraca aktualny odstęp między linijkami dla wielkich liter aktualnej czcionki. W razie błędu zwracane jest 0.

const char* HPDF_Font_GetFontName(HPDF_Font czcionka); - zwraca napis zawierający nazwę czcionki

Przykład zapisu tekstu do PDFa

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <hpdf.h>

void opiszBlad( HPDF_STATUS status ) {
    if( status != HPDF_OK )
    switch( status ) {
    case HPDF_INVALID_DOCUMENT: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do dokumentu\n" ); break;
    case HPDF_FAILD_TO_ALLOC_MEM: printf( "!!!BLAD!!! Nie można zaalokować pamięci\n" ); break;
    case HPDF_INVALID_PAGE: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do strony\n" ); break;
    case HPDF_FILE_IO_ERROR: printf( "!!!BLAD!!! problem w trakcie opecacji wejścia/wyjścia\n" ); break;
    case HPDF_INVALID_FONT_NAME: printf( "!!!BLAD!!! podano niewłaściwą nazwę czcionki\n" ); break;
    case HPDF_INVALID_ENCODING_NAME: printf( "!!!BLAD!!! podano niewłaściwą nazwę kodowania\n" ); break;
    case HPDF_UNSUPPORTED_FONT_TYPE: printf( "!!!BLAD!!! niewspierany typ/zbior czcionek\n" ); break;
        default: printf( "!!!BLAD!!! nieznany o kodzie: %ld\n", status ); break;
    };
}

void funkcjaWRazieBledow( HPDF_STATUS nr_bledu, HPDF_STATUS numer_szczegolow, void * dane_uzytkownika ) {
    printf( "BLAD: nr_bledu=%04X, numer_szczegolow=%u\t",( HPDF_UINT ) nr_bledu,( HPDF_UINT ) numer_szczegolow );
    opiszBlad( nr_bledu );
}

const char * lista_czcionek[] = {
    NULL,
    "Courier",
    "Courier-Bold",
    "Courier-Oblique",
    "Courier-BoldOblique",
    "Helvetica",
    "Helvetica-Bold",
    "Helvetica-Oblique",
    "Helvetica-BoldOblique",
    "Times-Roman",
    "Times-Bold",
    "Times-Italic",
    "Times-BoldItalic",
    "Symbol",
    "ZapfDingbats"
};

const char * randomTextArray( unsigned rozmiar ) {
    static char text[ 256 ]; // (1)
    unsigned i = 0;
    for(; i < rozmiar; ++i )
         text[ i ] = rand() %( 'z' - 'a' ) + 'a';
   
    text[ i ] = '\0';
    return text;
}

int main() {
    HPDF_Page strona1;
    HPDF_REAL wysokosc_strony, szerokosc_strony;
    HPDF_Font czcionka;
    HPDF_Doc pdf = HPDF_New( funkcjaWRazieBledow, NULL );
   
    if( !pdf )
         printf( "!!!BLAD!!! Nie mozna utworzyc dokumentu PDF\n" );
   
    czcionka = HPDF_GetFont( pdf, lista_czcionek[ 9 ], "ISO8859-2" );
    HPDF_SetPageLayout( pdf, HPDF_PAGE_LAYOUT_TWO_COLUMN_LEFT );
    strona1 = HPDF_AddPage( pdf );
   
    wysokosc_strony = HPDF_Page_GetHeight( strona1 );
    szerokosc_strony = HPDF_Page_GetWidth( strona1 );
   
    HPDF_Page_SetFontAndSize( strona1, czcionka, 24.5 );
   
    HPDF_Page_BeginText( strona1 );
    { // (2)
        const char * text = "Ala juz nie ma kota";
        const char * text1 = "tekst przesuniety zwyczajnie";
        const char * text2 = "tekst przesuniety nadzwyczajnie";
        const char * text3 = "do nowej linii";
        const char * text4 = "do jeszcze kolejnej nowej linii";
       
        HPDF_REAL szerokosc_tekstu = HPDF_Page_TextWidth( strona1, text );
        HPDF_Page_TextOut( strona1,( szerokosc_strony - szerokosc_tekstu ) / 2., 50, text );
       
        HPDF_Page_MoveTextPos( strona1, 50, 100 );
        HPDF_Page_ShowText( strona1, text1 );
       
        HPDF_Page_MoveTextPos2( strona1, 0, 30 );
        HPDF_Page_ShowText( strona1, text2 );
       
        HPDF_Page_MoveToNextLine( strona1 );
        HPDF_Page_SetWordSpace( strona1, 2 );
        HPDF_Page_ShowText( strona1, text3 );
       
        HPDF_Page_ShowTextNextLine( strona1, text4 );
       
        HPDF_Page_SetFontAndSize( strona1, czcionka, 44 );
        HPDF_Page_TextRect( strona1, 0, wysokosc_strony, szerokosc_strony, wysokosc_strony / 3.* 2., randomTextArray( 20 ), HPDF_TALIGN_CENTER, NULL );
    }
    HPDF_Page_EndText( strona1 );
    HPDF_SaveToFile( pdf, "pdf2.pdf" );
    HPDF_Free( pdf );
} //    gcc -o pdf2 -lhpdf  pdf2.c && ./pdf2
Wygenerowany przez powyższy program dokument.
Na większą uwagę zasługują 2 miejsca w programie:
  • 1
    static char text[ 256 ];
    funkcja ma zwracać jako rezultat tablicę losowych znaków, dzięki słówku static tablica jest wciąż dostępna nawet po wyjściu z funkcji, podobny rezultat mogłem osiągnąć poprzez allokację pamięci, natomiast potem musiał bym ją zwalniać
  • 2
    C/C++
    { // (2)
    używam "sztucznego" bloku gdyż piszę w C, a nie lubię wszystkiego deklarować na samej górze funkcji main(), dzięki zastosowaniy "wąsów" mogę prawie wszędzie definiować zmienne
Program wygeneruje następujący plik PDF.

Wykorzystanie grafiki w dokumentach

Zanim zaczniemy używanie zewnętrznych plików graficznych warto poświęcić parę chwil wewnętrznym mechanizmom graficznym.

Funkcje rysujące

HPDF_STATUS HPDF_Page_MoveTo(HPDF_Page strona, HPDF_REAL start_X, HPDF_REAL start_Y); - ustawia punkt startowy nowej ścieżki i przesuwa tam wspaźnik. Funkcja ta jest pierwszym etapem rysowania figur i linii.

HPDF_STATUS HPDF_Page_LineTo(HPDF_Page strona, HPDF_REAL cel_X, HPDF_REAL cel_Y); - rysuje linię na obecnej ścieżce od punktu bieżącego do końcowego (cel_X i cel_Y)

HPDF_STATUS HPDF_Page_Rectangle(HPDF_Page strona, HPDF_REAL dolny_lewy_rogX, HPDF_REAL dolny_lewy_rogY, HPDF_REAL szerokosc, HPDF_REAL wysokosc); - funkcja ta rysuje prostokąt na obecnej ścieżce

HPDF_STATUS HPDF_Page_CurveTo3(HPDF_Page strona, HPDF_REAL x1, HPDF_REAL y1, HPDF_REAL koniec_X, HPDF_REAL koniec_Y); - funkcja rysuje krzywą Beziera od punktu bieżącego na ścieżce idąc w kierunku punktu (x1, y1) i kończąc w punkcie (koniec_).

HPDF_STATUS HPDF_Page_CurveTo2(HPDF_Page strona, HPDF_REAL x2, HPDF_REAL y2, HPDF_REAL koniec_X, HPDF_REAL koniec_Y); - jest to krzywa Beziera idąca od obecnegu punktu ścieżki w kierunku punktu (x2, y2) i kończąca w punkcie koniec_

HPDF_STATUS HPDF_Page_CurveTo(HPDF_Page strona, HPDF_REAL x1, HPDF_REAL y1, HPDF_REAL x2, HPDF_REAL y2, HPDF_REAL koniec_X, HPDF_REAL koniec_Y); - rysuje krzywą Beziera od bieżącego punktu w kierunku punktów 1,2,3; kończąc w punkcie (koniec_).

Krzywe ciężko jest opisać, dlatego zachęcam do zobaczenia ich wyglądu w dokumencacji.

HPDF_STATUS HPDF_Page_Circle(HPDF_Page strona, HPDF_REAL srodek_X, HPDF_REAL srodek_Y, HPDF_REAL promien); - funkcja rysuje koło o środku w punkcie (srodek_) i podanym w argumencie promieniu.

HPDF_STATUS HPDF_Page_Arc(HPDF_Page strona, HPDF_REAL srodek_X, HPDF_REAL srodek_Y, HPDF_REAL promien, HPDF_REAL kat_od, HPDF_REAL kat_do); - funkcja rysuje łuk o środku w punkcie (srodek_), podanym promieniu, mieszczącym się między kątami (kat_od, kat_do); kat_do musi być większy niż kat_od.

HPDF_STATUS HPDF_Page_Ellipse(HPDF_Page strona, HPDF_REAL srodek_X, HPDF_REAL srodek_Y, HPDF_REAL promien1, HPDF_REAL promien2); - rysowanie elipsy na bieżącej ścieżce, o środku w punkcie (srodek_) i promieniach promien1 (poziomo), promien2(pionowo).

HPDF_STATUS HPDF_Page_Stroke(HPDF_Page strona); - rysuje obecną ścieżkę, czyli wszystko co zawarliśmy do narysowania

HPDF_STATUS HPDF_Page_Fill(HPDF_Page strona); - wypełnia obecną ścieżkę

HPDF_STATUS HPDF_Page_FillStroke(HPDF_Page strona); - rysuje daną ścieżkę, oraz wypełnia ją. Na uwagę zasługuje fakt że inne style/kolory wypełnienia i rysowania sa możliwe.

HPDF_STATUS HPDF_Page_ClosePath(HPDF_Page strona); - zamyka ścieżkę, czyli rysuje linię między obecnym punktem i punktem początku ścieżki. Obecny punkt staje się punktem początku nowej ścieżki.

HPDF_STATUS HPDF_Page_ClosePathStroke(HPDF_Page strona); - funkcja zamyka bieżącą ścieżkę i ją rysuję

HPDF_STATUS HPDF_Page_EndPath(HPDF_Page strona); - kończy obecną ścieżkę bez rysowania jej

void print_grid(HPDF_Doc pdf, HPDF_Page strona); - rysowanie siatki, wbudowanej, wyglądającej podobnie jak w zeszytach kratkowanych

Funkcje ustawiające rysunek

Mamy już wiedzę jak rysowac różne kształty, ale fajnie by było wiedzieć jak ustawić np grubość linii, lub przynajmniej jej kolor. Dlatego zanim zarzucę przykładem muszę opisac jeszcze parę funkcji:

HPDF_STATUS HPDF_Page_GSave(HPDF_Page strona); - funkcja zapamiętuje bieżące ustawienia takie jak czcionka, obrót, skalowanie strony, grubość linii itp... Zapamiętywanie odbywa się na stosie, więc można wiele razy zapisywać stan, a potem go przywracać. Funkcja jest przydatna żebyśmy przywrócili pewien stan (lista rzeczy które możemy zapamiętywać) gdy bardzo pozmieniamy parametry rysowania.

HPDF_STATUS HPDF_Page_GRestore(HPDF_Page strona); - przywraca zapamiętany wcześniej przez funkcję HPDF_Page_GSave() stan ustawień.

HPDF_STATUS HPDF_Page_SetLineWidth(HPDF_Page strona, HPDF_REAL szerokość_linii); - funkcja ustawiająca szerokość linii, domyślnie 1.

HPDF_STATUS HPDF_Page_SetDash(HPDF_Page strona, const HPDF_UINT16 *tablica_wzorow_linii, HPDF_UINT ilosc_wzorow, HPDF_UINT zaczecie_kreski); - ustawia wzór kreskowania linii na stronie. Jako argument tej funkcji podajemy tablice zawierająca liczby odzwierciedlające na przemian długość kreski i długość odstępu; jako że to tablica musimy podać w kolejnym argumencie jej rozmiar. Ostatni parametr mówi od którego momentu ma się zacząć kreskowanie (możemy zacząć od pełnej kreski, od fragmentu kreski lub nawet od odstępu). Warto zobaczyć w dokumentacji jak to wygląda. Domyślnie mamy tablica_wzorow_linii=NULL, ilosc_wzorow=0, zaczecie_kreski=0.

HPDF_STATUS HPDF_Page_SetHorizontalScalling(HPDF_Page strona, HPDF_REAL skala); - funkcja ustawia poziome skalowanie o podaną wartość

HPDF_STATUS HPDF_Page_SetLineCap(HPDF_Page strona, HPDF_LineCap line_cap); - ustawienie zakończenia linii, do wyboru opcje:
  • HPDF_BUTT_END - kanciaste zakończenia linii w miejscu zakończenia ścieżki (co do pixela)
  • HPDF_ROUND_END - półkoliste zakończenia linii, środek półkoli jest na końcu ścieżki
  • HPDF_PROJECTING_SCUARE_END - kanciaste zakończenia, lecz sięgające poza zakończenie ścieżki o połowę szerokości linii
.

HPDF_STATUS HPDF_Page_SetLineJoin(HPDF_Page strona, HPDF_LineJoin typ_laczenia); - funkcja ustawiająca łączenie linii, do wyboru:
  • HPDF_MITER_JOIN - ostre zakończenia (domyślne)
  • HPDF_ROUND_JOIN - zaokrąglone zakończenia
  • HPDF_BEVEL_JOIN - ścięte zakończenia

Funkcje ustawiające kolor wypełnienia i konturu

HPDF_STATUS HPDF_Page_SetGrayFill(HPDF_Page page, HPDF_REAL skala_szarosci); - ustawia szarość wypełnienia w przedziale 0-1
HPDF_STATUS HPDF_Page_SetGrayStroke(HPDF_Page page, HPDF_REAL skala_szarosci); - ustawia szarość konturu przedziale 0-1

HPDF_STATUS HPDF_Page_SetRGBStroke(HPDF_Page strona, HPDF_REAL czerwien, HPDF_REAL zielen, HPDF_REAL blekit); - ustawia kolor konturu przyjmująć za parametry intensywność 3 kolorów w przedziale 0-1

HPDF_STATUS HPDF_Page_SetRGBFill(HPDF_Page strona, HPDF_REAL czerwien, HPDF_REAL zielen, HPDF_REAL blekit); - ustawia kolor wypełnienia przyjmująć za parametry intensywność 3 kolorów w przedziale 0-1. Te 2 funkcje ustawiają kolory w przestrzenie barw http://pl.wikipedia.org/wiki/RGB, z założenia składa się ona z 3 kolorów podstawowych, z których można otrzymać wszystkie (z założenia) inne barwy.

HPDF_STATUS HPDF_Page_SetCMYKFill(HPDF_Page strona, HPDF_REAL cyan, HPDF_REAL maganta, HPDF_REAL yellow, HPDF_REAL czern); - ustawia kolor wypełnienia przyjmująć za parametry intensywność 4 kolorów w przedziale 0-1

HPDF_STATUS HPDF_Page_SetCMYKStroke(HPDF_Page strona, HPDF_REAL cyan, HPDF_REAL maganta, HPDF_REAL yellow, HPDF_REAL czern); - ustawia kolor konturu przyjmująć za parametry intensywność czterech kolorów, każdy w przedziale 0-1.

Te dwie, powyższe funkcje ustawiają kolory w przestrzeni barw http://pl.wikipedia.org/wiki/CMYK, każdy z tych kolorów odpowiada kolorowi rzeczywistemu w drukarce, jeżeli zależy nam na idealnym odwzorowaniu PDFa na papierze (maksymalnie zbliżonym do tego co widzimy na monitorze) możemy się posłużyć tą przestrzenią.

Przykład użycia powyższych funkcji

Opisałem większość funkcji służących do rysowania, oraz ustawiania jak rysować, teraz trzeba pokazać przykład. Wyjątkowo jednak zamiast pisać własny przykład zachęcam do obejrzenia przykładu z oficjalnej dokumentacji, który idealnie demonstruje użycie większośći powyższych funkcji. Dostępny jest przykład w C (również w Rubym, C#) i wygenerowany PDF. Są również dostępne inne przykłady m.in. ciekawie zmieniające kolor tekstu.

Operowanie na grafice zewnętrznej grafice-funkcje

Najwyższy czas aby pokazać w jaki sposób wrzucać zewnętrzną grafikę w różnych formatach do naszego PDFa. Zacznę od plików PNG. Przypominam że aby używać plików PNG musimy dodać flage do linkera -lpng; jako dygresję dodam że dzięki przprocesorowi można sprawdić obecność tej flagi: 
#ifndef HPDF_NOPNGLIB
-jeśli w preprocesorze jest to zdefiniowane znaczy że użyliśmy tej flagi.

HPDF_Image HPDF_LoadPngImageFromFile(HPDF_Doc pdf, const char *nazwa_pliku); - funkcja ta bierze plik PNG i zwraca w formie użytecznej dla naszej biblioteki. Przy udanym pobraniu pliku funkcja zwraca uchwyt do obrazka, w przeciwnym razie zwraca NULL, oraz przenosi się do funkcji obsługi błędu. Możliwe flagi błędów po wywołaniu tej funkcji:
  • HPDF_INVALID_DOCUMENT - niewłaściwy uchwyt do dokumentu
  • HPDF_FAILD_TO_ALLOC_MEM - błąd alokacji pamięci
  • HPDF_UNSUPPORTED_FUNC - biblioteka nie jest zkonfigurowana do używania plików PNG
  • HPDF_LIBPNG_ERROR - błąd podczas wywoływania funkcji z biblioteki do obsługi PNG
  • HPDF_INVALID_PNG_IMAGE - niewłaściwy plik PNG

HPDF_Image HPDF_LoadPngImageFromFile2(HPDF_Doc pdf, const char *nazwa_pliku); - funkcja jak wyżej z tą różnicą że nie ładuje całego pliku od razu (jedynie informacje o rozmiarze i kolorze), ładowanie całego pliku do pamięci następuje dopiero przed zapisaniem pliku PDF i od razu po tym z pamięci podręcznej usuwany jest ten plik - funkcja jest więc mniej pamięciożerna od poprzedniej. Możliwe flagi błędów j.w.

HPDF_Image HPDF_LoadRawImageFromFile(HPDF_Doc pdf, const char *nazwa_pliku, HPDF_UINT szerokosc, HPDF_UINT wysokosc, HPDF_ColorSpace przestrzen_kolorow); - funkcja ładuje plik graficzny bez jakichkolwiek konwersji, w związku z tym jest wiele szybsza, niż gdyby używać funkcji wykonujących konwersje. Flagi błędów j.w. Możliwe przestrzenie kolorów:
  • HPDF_CS_DEVICE_GRAY -  8-bitowa skala szarości. Skala szarości opisuje każdy pixel jednym bajtem, opis każdego bajta mieści się od 0X00 (najciemniejszy) do 0XFF (najjaśniejszy). Rozmiar danych obrazka wynosi wysokosc*szerokosc bajtów.
  • HPDF_CS_DEVICE_RGB - 24bitowy obrazek w przestrzeni RGB. W tej przestrzeni kolorów każdy pixel jest opisywany przez 3 bajty w przedziałach od 0X00 (najciemniejszy) do 0XFF (najjaśniejszy). Rozmiar obrazka to wysokosc*szerokosc*3 bajtów.
  • HPDF_CS_DEVICE_CMYK - 32bitowy obrazek w przestrzeni CMYK. W tej przestrzeni kolorów każdy pixel jest opisywany przez 4 bajty w przedziałach od 0X00 (najciemniejszy) do 0XFF (najjaśniejszy). Rozmiar obrazka to wysokosc*szerokosc*4 bajtów.

HPDF_Image HPDF_LoadJpegImageFromFile(HPDF_Doc pdf, const char *nazwa_pliku); - ładuje plik JPG do pamięci i zwraca uchwyt do niego w razie powodzenia, w przeciwnym razie zwraca NULL, oraz pojawia się odpowiednia flaga błędów z następujących: HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_UNSUPPORTED_JPEG_FORMAT (niewłaściwy plik JPEG).

HPDF_Point HPDF_Image_GetSize(HPDF_Image grafika); - funkcja zwraca rozmiary (x, y) grafiki, zwracana jest struktura zawierająca obydwie wartości. W razie błędu zwrócony zostanie punkt (0, 0).
HPDF_UINT HPDF_Image_GetWidth(HPDF_Image grafika); - funkcja zwraca szerokość grafiki, w razie błędu zwróci 0.
HPDF_UINT HPDF_Image_GetHeight(HPDF_Image grafika); - funkcja zwraca wysokość grafiki, w razie błędu zwróci 0.

HPDF_STATUS HPDF_Image_SetMaskImage(HPDF_Image grafika, HPDF_Image grafika_maskujaca); - ustawia maskę grafiki inną grafiką. Działa to podobnie jak maski warstwy w programie Photoshop. Możliwe błędy: HPDF_INVALID_IMAGE (niewłaściwy uchwyt do obrazka), HPDF_INVALID_BIT_PER_COMPONENT (niewłaściwa ilość bitów na składową), HPDF_FAILD_TO_ALLOC_MEM.

HPDF_STATUS HPDF_Image_SetColorMask(HPDF_Image grafika, HPDF_UINT rmin, HPDF_UINT rmax, HPDF_UINT gmin, HPDF_UINT gmax, HPDF_UINT bmin, HPDF_UINT bmax); - ustawia przeźroczyste fragmenty obrazka jeżeli składowe kolorów mieszczą się w zakresie Xmin-Xmax. Grafika musi być w przestrzeni barw RGB. Możliwe błędy: HPDF_INVALID_IMAGE, HPDF_INVALID_COLOR_SPACE (obrazek w innej przestrzeni barw niż RGB), HPDF_FAILD_TO_ALLOC_MEM, HPDF_INVALID_PARAMETER (podano niewłaściwe zakresy barw).

HPDF_UINT HPDF_Image_GetBitsPerComponent(HPDF_Image grafika); - funkcja zwraca liczbę bitów użytą aby opisać każdą składową. W razie błędu zwracane jest 0.

const char* HPDF_Image_GetColorSpace(HPDF_Image grafika); - funkcja zwraca tekstową nazwę przestrzeni kolorów grafiki.

Czasami możemy chcieć wygenerować jakiś obrazek, lub użyć go wielokrotnie, wtedy funkcje ładowania z pliku moga okazać się niewystarczające, albo raczej mało wydajne, na szczęście jest możliwość ładowania obrazków z bufora pamięci, tę funkcjonalność umozliwiają następujące funkcje:

HPDF_Image HPDF_LoadPngImageFromMem(HPDF_Doc pdf, const HPDF_BYTE *bufor, HPDF_UINT rozmiar_bufora); - funkcja ładuje plik PNG z bufora. Możliwe błędy: HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_INVALID_PNG_IMAGE.

HPDF_Image HPDF_LoadJpegImageFromMem(HPDF_Doc pdf, const HPDF_BYTE *bufor, HPDF_UINT rozmiar_bufora); - funkcja ładuje plik JPEG z bufora. Możliwe błędy: HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_INVALID_JPEG_DATA (podano niewłaściwą grafikę JPEG), HPDF_UNSUPPORTED_JPEG_FORMAT (podano niewłaściwą/niewspieraną grafikę JPEG).

HPDF_Image HPDF_LoadRawImageFromMem(HPDF_Doc pdf, const HPDF_BYTE *bufor, HPDF_UINT szerokosc, HPDF_UINT wysokosc, HPDF_ColorSpace przestrzen_kolorow, HPDF_UINT rozmiar_w_bitach_kazdego_koloru); - funkcja ładuje "surowy" plik graficzny z bufora, dane nie podlegają konwersją, przez co funkcje ta jest o wiele szybsza, od poprzednich dwóch. Możliwe przestrzenie kolorów (opisane powyżej): HPDF_CS_DEVICE_GRAY, HPDF_CS_DEVICE_RGB, HPDF_CS_DEVICE_CMYK. rozmiar_w_bitach_kazdego_koloru może być jedną z wartości: 1, 2, 4, 8. Moliwe błędy:      HPDF_INVALID_DOCUMENT, HPDF_FAILD_TO_ALLOC_MEM, HPDF_INVALID_COLOR_SPACE (niewłaściwa przestrzeń kolorów), HPDF_INVALID_IMAGE (niewłaściwy rozmiar grafiki podano).

HPDF_STATUS HPDF_Page_Concat(HPDF_Page strona, HPDF_REAL a, HPDF_REAL b, HPDF_REAL c, HPDF_REAL d, HPDF_REAL x, HPDF_REAL y); - "łączy obecną macierz transformacji z macierzą transformacji powstałą z podanych parametrów". A po ludzku umożliwia m.in. obrót o 45 stopni:
C/C++
float rad1 = 45 / 180 * 3.141592;
HPDF_Page_Concat( page, cos( rad1 ), sin( rad1 ), - sin( rad1 ), cos( rad1 ), 220, 350 );
Przed użyciem tej funkcji warto użyć HPDF_Page_GSave().

Przykładowe programy operujące na grafice

Dobre przykłady na operacje na grafice są na tej samej stronie co dokumentacja. Zacznę od paru słów odnośnie przykładów operujących na plikach PNG i JPEG: umieszczają one różne pliki w PDFach, nie robią nic skomplikowanego. Kolejny przykład na ładowanie "surowych obrazków" jest troszeczkę bardziej skomplikowany, ciekawą demonstracją jest używanie bufora, którego zawartość jest widoczna w PDFie jako obrazek. Najbardziej kombinujący z grafiką jest jeszcze inny przykład, który wykorzystuje maski, skalowanie i obroty.

Operowanie na strumieniu pliku PDF

W LibHaru mamy możliwość oprócz zapisania pliku wynikowego dostać się do tekstowej zawartości PDFa, w celu m.in. zapisania pliku PDF drugim sposobem. Zapewne, drogi czytelniku, zastanawiasz się po co kombinować bawiąc się w strumienie skoro możemy normalnie wygenerować plik -powodów może być wiele, najbardziej przekonywującym wydaje mi się potrzeba zapisania wygenerowanego PDFa do bazy danych, o wiele szybciej jest zapisać tekst bezpośrednio do bazy niż generować plik, który następnie jest wgrywany do bazy, a na koniec usuwany z postaci wolno-stojącego. Kolejnym zastosowaniem strumienia dokumentu jest użycie http://pl.wikipedia.org/wiki/Common_Gateway_Interface, a co za tym idzie potrzeba wyświetlenia wygenerowanego dokumentu klientowi zamiast zapis na serwerze i umieszczanie linka do pliku.

Opis funkcji strumieni

HPDF_STATUS HPDF_SaveToStream(HPDF_Doc pdf); - żeby możliwe było odczytywanie ze strumienia, wpierw należy ten strumień utworzyć, do tego właśni służy ta funkcja. Możliwe błędy: HPDF_INVALID_DOCUMENT i HPDF_FAILD_TO_ALLOC_MEM.

HPDF_UINT32 HPDF_GetStreamSize(HPDF_Doc pdf); - funkcja zwraca aktualny rozmiar strumienia. Możliwy błąd: HPDF_INVALID_DOCUMENT. W razie błędu jest zwracane 0.

HPDF_STATUS HPDF_ReadFromStream(HPDF_Doc pdf, HPDF_BYTE *bufor, HPDF_UINT32 *rozmiar_bufora); - funkcja czyta ze strumienia podaną w trzecim parametrze liczbę bitów do podanego bufora; po każdym odczytaniu "wewnętrzny wskaźnik strumienia" jest przesuwany, przez co można wielokrotnie wywołując tą funkcję odczytać cały strumień. Podczas odczytu ze strumienia jego część jest zapisywana w buforze, pod rozmiar_bufora jest zapisywana liczba wczytanych bajtów. Gdy wczytamy całość jest zwracany przez funkcję HPDF_STREAM_EOF. Możliwe kody błędów: HPDF_INVALID_DOCUMENT, HPDF_INVALID_OPERATION (w buforze nie ma żadnych informacji), HPDF_INVALID_PARAMETER  (niewłaściwy parametr reprezentujący rozmiar bufora).

HPDF_STATUS HPDF_ResetStream(HPDF_Doc pdf); - przewija wskaźnik czytania strumienia na jego początek. Możliwe kody błędów: HPDF_INVALID_DOCUMENT, HPDF_INVALID_OPERATION.

Przykładowy kod wykorzystujący operację na strumieniach

C/C++
#include <stdio.h>
#include <stdlib.h>
#include <hpdf.h>

void opiszBlad( HPDF_STATUS status ) {
    if( status != HPDF_OK )
    switch( status ) {
    case HPDF_INVALID_DOCUMENT: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do dokumentu\n" ); break;
    case HPDF_FAILD_TO_ALLOC_MEM: printf( "!!!BLAD!!! Nie można zaalokować pamięci\n" ); break;
    case HPDF_INVALID_PAGE: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do strony\n" ); break;
    case HPDF_FILE_IO_ERROR: printf( "!!!BLAD!!! problem w trakcie opecacji wejścia/wyjścia\n" ); break;
    case HPDF_INVALID_FONT_NAME: printf( "!!!BLAD!!! podano niewłaściwą nazwę czcionki\n" ); break;
    case HPDF_INVALID_ENCODING_NAME: printf( "!!!BLAD!!! podano niewłaściwą nazwę kodowania\n" ); break;
    case HPDF_UNSUPPORTED_FONT_TYPE: printf( "!!!BLAD!!! niewspierany typ/zbior czcionek\n" ); break;
    case HPDF_INVALID_OPERATION: printf( "!!!BLAD!!! w buforze nie ma żadnych informacji\n" ); break;
    case HPDF_INVALID_PARAMETER: printf( "!!!BLAD!!! niewłaściwy parametr reprezentujący rozmiar bufora\n" ); break;
        default: printf( "!!!BLAD!!! nieznany o kodzie: %ld\n", status ); break;
    };
}

void funkcjaWRazieBledow( HPDF_STATUS nr_bledu, HPDF_STATUS numer_szczegolow, void * dane_uzytkownika ) {
    printf( "BLAD: nr_bledu=%04X, numer_szczegolow=%u\t",( HPDF_UINT ) nr_bledu,( HPDF_UINT ) numer_szczegolow );
    opiszBlad( nr_bledu );
}

int main() {
    HPDF_Page strona1;
    HPDF_REAL szerokosc_strony;
   
    HPDF_REAL szerokosc_tekstu;
    HPDF_Font czcionka;
    const char * text = "Ciekawe ile wazy ten tekst?";
   
    FILE * plik;
   
    HPDF_Doc pdf = HPDF_New( funkcjaWRazieBledow, NULL );
   
    if( !pdf )
         printf( "!!!BLAD!!! Nie mozna utworzyc dokumentu PDF\n" );
   
    czcionka = HPDF_GetFont( pdf, "Times-Bold", "ISO8859-2" );
    HPDF_SetPageLayout( pdf, HPDF_PAGE_LAYOUT_TWO_COLUMN_LEFT );
    strona1 = HPDF_AddPage( pdf );
   
    szerokosc_strony = HPDF_Page_GetWidth( strona1 );
   
    HPDF_Page_SetFontAndSize( strona1, czcionka, 24.5 );
   
    HPDF_Page_BeginText( strona1 );
   
    szerokosc_tekstu = HPDF_Page_TextWidth( strona1, text );
    HPDF_Page_TextOut( strona1,( szerokosc_strony - szerokosc_tekstu ) / 2., 50, text );
   
    HPDF_Page_EndText( strona1 );
   
    HPDF_SaveToStream( pdf );
    printf( "rozmiar danych = %d bajtow\n", HPDF_GetStreamSize( pdf ) );
   
    plik = fopen( "pdf4.pdf", "w" );
    while( 1 ) {
        HPDF_BYTE bufor[ 4096 ];
        HPDF_UINT32 rozmiar_bufora = 4096;
        HPDF_STATUS ret = HPDF_ReadFromStream( pdf, bufor, & rozmiar_bufora );
       
        if( rozmiar_bufora == 0 )
             break;
       
        if( fwrite( bufor, rozmiar_bufora, 1, plik ) != 1 ) {
            fprintf( stderr, "nie mozna zapisac w standardowym wejsciu bufora %d\n", rozmiar_bufora );
            break;
        }
    }
    fclose( plik );
   
    HPDF_Free( pdf );
} //    gcc -o pdf4 -lhpdf  pdf4.c && ./pdf4
Wygenerowany przez powyższy program PDF

Polskie znaki

Nadchodzi taki czas że trzeba pomyśleć o polskich znakach. Na szczęście LibHaru umożliwia zapis polskich znaków do PDFów, niestety gdy zapiszemy
const char * napis = "AĄBCĆDEĘ";
 wcale w PDFie nie będziemy mieli takiego napisu. Mamy pewną, długą listę dostępnych kodowań, nas interesuje: ISO8859-2, ale nawet po włączeniu odpowiedniego kodowania kwestia polskich znaków nie zostanie rozwiązana, dzieje się tak dlatego że inaczej polskie "ogonki" są zakodowane w ASCI, a inaczej w ISO8859-2. Rozwiązać tą kwestię można dokonując konwersji znaków, mojej roboty funkcja znajdze się w poniższym programiku
(dla zainteresowanych przykładowy program wyświetlający wszystkie znaki w każdym z dostępnych kodowań)
C/C++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hpdf.h>
#include <ctype.h>

void opiszBlad( HPDF_STATUS status ) {
    if( status != HPDF_OK )
    switch( status ) {
    case HPDF_INVALID_DOCUMENT: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do dokumentu\n" ); break;
    case HPDF_FAILD_TO_ALLOC_MEM: printf( "!!!BLAD!!! Nie można zaalokować pamięci\n" ); break;
    case HPDF_INVALID_PAGE: printf( "!!!BLAD!!! Podano niewłaściwy uchwyt do strony\n" ); break;
    case HPDF_FILE_IO_ERROR: printf( "!!!BLAD!!! problem w trakcie opecacji wejścia/wyjścia\n" ); break;
    case HPDF_INVALID_FONT_NAME: printf( "!!!BLAD!!! podano niewłaściwą nazwę czcionki\n" ); break;
    case HPDF_INVALID_ENCODING_NAME: printf( "!!!BLAD!!! podano niewłaściwą nazwę kodowania\n" ); break;
    case HPDF_UNSUPPORTED_FONT_TYPE: printf( "!!!BLAD!!! niewspierany typ/zbior czcionek\n" ); break;
    case HPDF_INVALID_OPERATION: printf( "!!!BLAD!!! w buforze nie ma żadnych informacji\n" ); break;
    case HPDF_INVALID_PARAMETER: printf( "!!!BLAD!!! niewłaściwy parametr reprezentujący rozmiar bufora\n" ); break;
        default: printf( "!!!BLAD!!! nieznany o kodzie: %ld\n", status ); break;
    };
}

void funkcjaWRazieBledow( HPDF_STATUS nr_bledu, HPDF_STATUS numer_szczegolow, void * dane_uzytkownika ) {
    printf( "BLAD: nr_bledu=%04X, numer_szczegolow=%u\t",( HPDF_UINT ) nr_bledu,( HPDF_UINT ) numer_szczegolow );
    opiszBlad( nr_bledu );
}

typedef struct {
    char c;
    int i;
} para;

const static para odpowiedniki_polskich[] = {
    { 'Ą', 161 },
    { 'Ć', 198 },
    { 'Ę', 202 },
    { 'Ł', 163 },
    { 'Ń', 209 },
    { 'Ó', 211 },
    { 'Ś', 166 },
    { 'Ź', 172 },
    { 'Ż', 175 },
    { 'ą', 177 },
    { 'ć', 230 },
    { 'ę', 234 },
    { 'ł', 181 },
    { 'ń', 241 },
    { 'ó', 243 },
    { 'ś', 182 },
    { 'ź', 188 },
    { 'ż', 191 }
};

const int LICZBA_POLSKICH_ZNAKOW = sizeof( odpowiedniki_polskich ) / sizeof( para );

void zamienZnakWCiagu( unsigned char * wejsciowy, unsigned char znak_obecny, unsigned char znak_docelowy ) {
    unsigned char * pch = strchr( wejsciowy, znak_obecny );
    unsigned pozycja = 0;
   
    while( pch != NULL ) {
        pozycja = pch - wejsciowy;
        wejsciowy[ pozycja ] =( char ) znak_docelowy;
        pch = strchr( pch + 1, znak_obecny );
    }
}

void usunZNapisu( unsigned char * wejsciowy, unsigned pozycja ) {
    unsigned i;
    for( i = pozycja; i < strlen( wejsciowy ); ++i )
         wejsciowy[ i ] = wejsciowy[ i + 1 ];
   
    wejsciowy[ i ] = '\0';
}

char * dodajOgonki( const unsigned char * wejsciowy ) {
    unsigned char * tymczasowy = malloc( sizeof( char ) * strlen( wejsciowy ) + 3 );
    const unsigned char * cos = "a";
    unsigned char * wyjsciowy = malloc( sizeof( char ) * strlen( wejsciowy ) + 3 );
    unsigned i = 0;
   
    wyjsciowy[ 0 ] = 'a';
    wyjsciowy[ 1 ] = '\0';
    strcat( wyjsciowy, wejsciowy ); // konkatenuje napisy
   
    for( i; i < LICZBA_POLSKICH_ZNAKOW; ++i )
         zamienZnakWCiagu( wyjsciowy, odpowiedniki_polskich[ i ].c, odpowiedniki_polskich[ i ].i );
   
    for( i = 0; i < strlen( wejsciowy ); ++i ) // usuwam pierwsza litere z ciagu, ktora dodalem na poczatku
         wyjsciowy[ i ] = wyjsciowy[ i + 1 ];
   
    wyjsciowy[ i + 2 ] = '\0';
   
    for( i = 0; i < strlen( wyjsciowy ); ++i )
    if( !isalpha( wyjsciowy[ i ] ) )
         usunZNapisu( wyjsciowy, i );
   
    return wyjsciowy;
}

int main() {
    HPDF_Doc pdf = HPDF_New( funkcjaWRazieBledow, NULL );
   
    HPDF_Page strona;
    HPDF_REAL szerokosc_strony, wysokosc_strony;
   
    HPDF_REAL szerokosc_tekstu;
    HPDF_Font czcionka = HPDF_GetFont( pdf, "Times-Roman", NULL );
   
    unsigned i = 0;
    const unsigned char * polski_alfabet_duze = { "AĄBCĆDEĘFGHIJKLŁMNŃOÓPRSŚTUWYZŹŻ" };
    char unsigned * tymczasowe;
   
   
    if( !pdf )
         printf( "!!!BLAD!!! Nie mozna utworzyc dokumentu PDF\n" );
   
    strona = HPDF_AddPage( pdf );
   
    szerokosc_strony = HPDF_Page_GetWidth( strona );
    wysokosc_strony = HPDF_Page_GetHeight( strona );
   
    printf( "Alfabet duzych polskich liter\t'%s'\n", polski_alfabet_duze );
   
    HPDF_Page_BeginText( strona );
   
    HPDF_Page_SetFontAndSize( strona, czcionka, 12 );
   
   
    HPDF_Page_MoveTextPos( strona, 1, wysokosc_strony - 20 );
    HPDF_Page_ShowText( strona, "bez kodowania: ISO8859-2:" );
   
    HPDF_Page_TextOut( strona,( szerokosc_strony - HPDF_Page_TextWidth( strona, "duze litery:" ) ) / 2, wysokosc_strony - 100, "duze litery:" );
    HPDF_Page_TextOut( strona, 10, wysokosc_strony - 120, polski_alfabet_duze );
   
    czcionka = HPDF_GetFont( pdf, "Times-Roman", "ISO8859-2" );
    HPDF_Page_SetFontAndSize( strona, czcionka, 12 );
   
    HPDF_Page_MoveTextPos( strona, szerokosc_strony - szerokosc_strony / 2, 20 );
   
    HPDF_Page_TextOut( strona, szerokosc_strony / 2, wysokosc_strony - 20, "kodowanie: ISO8859-2:" );
    HPDF_Page_TextOut( strona, szerokosc_strony / 2, wysokosc_strony - 120, polski_alfabet_duze );
   
    HPDF_Page_TextOut( strona,( szerokosc_strony - HPDF_Page_TextWidth( strona, "polskie znaki po zamianie:" ) ) / 2, wysokosc_strony - 200, "polskie znaki po zamianie:" );
   
    tymczasowe = dodajOgonki( polski_alfabet_duze );
   
    HPDF_Page_TextOut( strona,( szerokosc_strony - HPDF_Page_TextWidth( strona, tymczasowe ) ) / 2, wysokosc_strony - 250, tymczasowe );
   
    free( tymczasowe );
    HPDF_Page_EndText( strona );
   
    HPDF_SaveToFile( pdf, "pdf5kodowanie.pdf" );
   
    HPDF_Free( pdf );
} //    gcc -o pdf5kodowanie -lhpdf  pdf5kodowanie.c && ./pdf5kodowanie
W powyższym kodzie nie robię prawie nic nadzwyczajnego (wygenerowany PDF tutaj; zapisuję ciąg polskich znaków z tablicy znakowej do PDFa bez ustawiania kodowania, potem po ustawieniu kodowania dopisuję ponownie tą samą treść do dokumentu i na końcu po użyciu napisanej przeze mnie funkcji zamieniającej znaki znowu dopisuje do PDFa. Chcąc wykorzystać napisaną przeze mnie funkcję trzeba skopiować do swojego programu:
C/C++
typedef struct {
    char c;
    int i;
} para;

const static para odpowiedniki_polskich[] = {
    { 'Ą', 161 },
    { 'Ć', 198 },
    { 'Ę', 202 },
    { 'Ł', 163 },
    { 'Ń', 209 },
    { 'Ó', 211 },
    { 'Ś', 166 },
    { 'Ź', 172 },
    { 'Ż', 175 },
    { 'ą', 177 },
    { 'ć', 230 },
    { 'ę', 234 },
    { 'ł', 181 },
    { 'ń', 241 },
    { 'ó', 243 },
    { 'ś', 182 },
    { 'ź', 188 },
    { 'ż', 191 }
};

const int LICZBA_POLSKICH_ZNAKOW = sizeof( odpowiedniki_polskich ) / sizeof( para );

void zamienZnakWCiagu( unsigned char * wejsciowy, unsigned char znak_obecny, unsigned char znak_docelowy ) {
    unsigned char * pch = strchr( wejsciowy, znak_obecny );
    unsigned pozycja = 0;
   
    while( pch != NULL ) {
        pozycja = pch - wejsciowy;
        wejsciowy[ pozycja ] =( char ) znak_docelowy;
        pch = strchr( pch + 1, znak_obecny );
    }
}

void usunZNapisu( unsigned char * wejsciowy, unsigned pozycja ) {
    unsigned i;
    for( i = pozycja; i < strlen( wejsciowy ); ++i )
         wejsciowy[ i ] = wejsciowy[ i + 1 ];
   
    wejsciowy[ i ] = '\0';
}

char * dodajOgonki( const unsigned char * wejsciowy ) {
    unsigned char * tymczasowy = malloc( sizeof( char ) * strlen( wejsciowy ) + 3 );
    const unsigned char * cos = "a";
    unsigned char * wyjsciowy = malloc( sizeof( char ) * strlen( wejsciowy ) + 3 );
    unsigned i = 0;
   
    wyjsciowy[ 0 ] = 'a';
    wyjsciowy[ 1 ] = '\0';
    strcat( wyjsciowy, wejsciowy ); // konkatenuje napisy
   
    for( i; i < LICZBA_POLSKICH_ZNAKOW; ++i )
         zamienZnakWCiagu( wyjsciowy, odpowiedniki_polskich[ i ].c, odpowiedniki_polskich[ i ].i );
   
    for( i = 0; i < strlen( wejsciowy ); ++i ) // usuwam pierwsza litere z ciagu, ktora dodalem na poczatku
         wyjsciowy[ i ] = wyjsciowy[ i + 1 ];
   
    wyjsciowy[ i + 2 ] = '\0';
   
    for( i = 0; i < strlen( wyjsciowy ); ++i )
    if( !isalpha( wyjsciowy[ i ] ) )
         usunZNapisu( wyjsciowy, i );
   
    return wyjsciowy;
}
Powyższa funkcja musi być troszeczkę skomplikowana z paru powodów.
Po pierwsze jeżeli polski "ogonek" jest na pierwszej pozycji funkcja strchr() zwraca 0, czyli często to samo co NULL (NULL jest zwracane w razie nie znalezienia znaku), dlatego też na początek wrzucam literę 'a', którą muszę później usunąć.
Kolejną rzeczą jest to że zamieniając pozycje z polskim znakiem w tablicy przed tym znakiem dodaje się dodatkowy, dziwny znaczek (nie wiem jakim cudem tak się dzieje) i dlatego muszę go usunąć z tablicy, wykorzystuję do sprawdzania funkcji isalpha().

Inne możliwości biblioteki

Jak widać z powyższej treści biblioteka dostarcza nam bardzo wielu możliwości, nawet więcej niż potrzebujemy. Są jeszcze pewne dodatkowe możliwości, z przykładami na stronie domowej: