C dla każdego (cz. 9.)

Pisania ciąg dalszy

Zakończymy dziś rozpoczęte przed miesiącem rozważania na temat czcionek. Listing, który dziś zaprezentujemy, nie powinien być nowością. Myślę, że pracowity Czytelnik po lekturze poprzedniego odcinka ma już jakieś pojęcie o otwieraniu czcionek.

Funkcja OpenDiskFont() otwiera czcionkę najbardziej zbliżoną do opisu, jednak nie zawsze dostępna na dysku czcionka odpowiada naszym wymaganiom. W takim wypadku możliwe jest wygenerowanie żądanych atrybutów. Służy do tego funkcja:

ULONG SetSoftStyle( struct RastPort *rp, ULONG style, ULONG enable );

Funkcja zmienia styl czcionki używanej przez RastPort "rp" na styl opisany za pomocą parametru "style" (możliwe pochylenie, pogrubienie i podkreślenie -- patrz poprzednia część). Funkcja zwraca flagi stylu, który udało się uzyskać. Argument "enable" jest maską bitową, zezwalającą na zastosowanie poszczególnych atrybutów. W zależności od upodobań twórcy czcionki nie wszystkie atrybuty mogą być programowo generowane (bo na przykład czcionka została od razu zaprojektowana jako pochylona), argument "enable" powinien mieć ustawione te bity, które odpowiadają możliwym do generowania trybom. Aby uzyskać informacje, które atrybuty są dozwolone, należy skorzystać z funkcji:

ULONG AskSoftStyle( struct RastPort *rp );

Funkcja ta zwraca wartość, która jest maską bitową wszystkich możliwych atrybutów czcionki, używanej aktualnie w podanym jako argument RastPorcie.
Informacje o stylach, które zostały wygenerowane, można znaleźć w polu "AlgoStyle" struktury "RastPort".
Kwestię otwarcia czcionki i ustalenia jej stylu mamy już za sobą. Najwyższy czas wziąć się do pisania.
Najprostszą funkcją, piszącą po RastPorcie, jest zaprezentowana już w części szóstej funkcja z "graphics.library":

LONG Text( struct RastPort *rp, STRPTR string, ULONG count );

Funkcja ta uznaje ustawienia RastPortu, takie jak kolory, tryb rysowania i, rzecz jasna, czcionkę. Ponieważ istnieją czcionki proporcjonalne, określenie długości napisu wykonanego taką czcionką może być kłopotliwe. W tym celu biblioteka graficzna została wyposażona w funkcje, pozwalające wyliczyć rozmiary napisu. Pierwszą i jednocześnie najstarszą jest:

WORD TextLength( struct RastPort *rp, STRPTR string, ULONG count );

Funkcja ta określa, o ile punktów w poziomie przesunie się kursor graficzny po wypisaniu w RastPorcie napisu, wskazywanego przez "string", o długości odnotowanej w argumencie "count". Informacja ta nie zawsze jest w pełni wystarczająca, ponieważ w wypadku czcionek pochylonych występują rozbieżności pomiędzy ostatnim zajętym przez napis punktem a położeniem kursora po umieszczeniu napisu (patrz rysunek -- czerwonymi krzyżykami oznaczono kursor graficzny przed i po umieszczeniu tekstu w RastPorcie). Kolejną funkcją, pomocną w określeniu rozmiarów napisu, jest TextExtent(). Pojawiła się ona w systemie operacyjnym 2.0:

void TextExtent( struct RastPort *rp, STRPTR string, long count, struct TextExtent *textExtent );

Trzy pierwsze argumenty są analogiczne do argumentów funkcji TextLength(). Czwarty argument jest wskaˇnikiem na strukturę "TextExtent", zdefiniowaną w "graphics/text.h", w której zostaną umieszczone rezultaty.

struct TextExtent
{
UWORD te_Width;
UWORD te_Height;
struct Rectangle te_Extent;
};

Po wywołaniu funkcji struktura ta zostanie wypełniona w sposób następujący:

"te_Width" -- zawiera tę samą wartość, którą zwraca funkcja TextLength(), czyli przesunięcie kursora graficznego, jakie spowoduje wypisanie napisu.

"te_Height" -- zawiera wysokość napisu, czyli "tf_YSize" czcionki.

Kolejne pole jest strukturą "Rectangle", przeznaczoną do opisu prostokąta:

Struct Rectangle { WORD MinX, MinY; WORD MaxX, MaxY; };

W naszym wypadku struktura ta opisuje obszar, który ulegnie zmianie po wypisaniu napisu. Wszystkie współrzędne są liczone względem obecnego położenia kursora graficznego w RastPorcie, oznaczają więc:

"te_Extent.MinX" -- odległość pomiędzy pozycją "cp_x" kursora w RastPorcie a lewą krawędzią prostokąta, zawierającego napis -- wartość ta bywa ujemna;

"te_Extent.MinY" -- odległość pomiędzy pozycją "cp_y" kursora (a więc linią bazową) a górną krawędzią prostokąta -- również bywa ujemna;

"te_Extent.MaxX" -- odległość pomiędzy pozycją "cp_x" kursora a prawą krawędzią prostokąta;

"te_Extent.MaxY" -- odległość pomiędzy pozycją "cp_y" kursora (a więc linią bazową) a dolną krawędzią prostokąta.

W szczególnym wypadku stosowania czcionek "normalnych" pole "te_Extent.MinX" wynosi 0.

Wiem, że nie jest to oczywiste, więc proponuję przeanalizować rysunek. Kolorem zielonym zaznaczone są współrzędne, otrzymane po przesunięciu kursora graficznego o wartości zapisane w strukturze "Rectangle". Linia górna jest wynikiem przesunięcia kursora w pionie o wartość "MinY", linia dolna -- "MaxY", lewa -- "MinX", prawa -- "MaxX". Linia koloru niebieskiego jest wynikiem przesunięcia kursora o wartość "te_Width".

Całkowita długość prostokąta, zawierającego napis, wynosi więc te_Extent.MaxX-te_Extent.MinX+1. Z wysokością jest analogicznie, można również skorzystać z pola "tf_YSize" czcionki.

Jeśli ktoś chciałby narysować ramkę na zewnątrz napisu, to wystarczy zmniejszyć o 1 pola "MinX" i "MinY" oraz zwiększyć o 1 "MaxX" i "MaxY". Dzięki zmianie wartości tych pól ramka będzie narysowana na zewnątrz prostokąta zawierającego napis. Wykonanie ramki jest przedstawione w przykładzie (funkcja border()).

Za pomocą funkcji TextExtent() można sprawdzić, ile liter danego napisu zmieści się w RastPorcie. Wystarczy umieścić tę funkcję w pętli zmniejszającej przy każdym obrocie wartość "count" i wykonującej się aż do chwili, gdy napis zmieści się w żądanym obszarze. Ponieważ potrzeba taka zachodzi dość często, powstało do tego celu narzędzie systemowe. Funkcja TextFit(), bo o niej mowa, jako wynik działania zwraca liczbę znaków, którą można wpisać do podanego obszaru. Wypełnia ona również podaną strukturę "TextExtent", która zawiera dokładnie te same informacje, co w wypadku poprzedniej funkcji. Jedyna różnica polega na tym, że informacje te dotyczą napisu o długości równej wartości zwróconej przez funkcję.

ULONG TextFit( struct RastPort *rp, STRPTR string, ULONG strLen, struct TextExtent *textExtent, struct TextExtent *constrainingExtent, long strDirection, ULONG constrainingBitWidth, ULONG constrainingBitHeight );

Argumenty "rp", "string", "strLen" oraz "textExtent" są analogiczne do argumentów poprzedniej funkcji. Argument "strDirection" ustala, w którą stronę mają być naliczane znaki -- zwykle podaje się 1, wtedy funkcja informuje, ile pierwszych liter napisu "string" się zmieści. Gdy poda się -1, a jako "string" koniec napisu, wtedy funkcja wyliczy, ile ostatnich liter napisu się zmieści.

Do funkcji należy przekazać wymiary obszaru, na którym ma być umieszczony napis. Wedle posiadanej przez nas dokumentacji wymiary te można przekazać na dwa sposoby: albo podając wypełnioną strukturę "TextExtend" jako parametr "constrainingExtent", albo podając tam NULL, a szerokość i wysokość w polach "constrainingBitWidth" i "constrainingBitHeight". Drugi sposób działa bez zarzutu, jednak pierwszy jest zupełnie nie udokumentowany. Darek usiłował rozszyfrować, "co jest grane", eksperymentując, ale doszedł do dziwacznych wniosków, których nie zamierzamy tu prezentować. Polecamy więc używać tylko drugiego sposobu, który zresztą w praktyce w pełni wystarcza.

Listing nr 10

Najwyższa pora zaprezentować przykład. W dzisiejszym listingu po raz pierwszy skorzystamy z biblioteki pochodzącej z dysku. W takim wypadku koniecznie trzeba wziąć pod uwagę możliwość zwrócenia NULL przez OpenLibrary() (bo np. użytkownik mógł bibliotekę skasować z dysku). Prawdziwy program użytkowy powinien poinformować o jej braku, nam w przykładzie szkoda na to miejsca.

W wypadku bibliotek, znajdujących się w ROM-ie, takich jak "intuition" czy "graphics", można z czystym sumieniem zrezygnować ze sprawdzania, czy próba ich otwarcia została uwieńczona sukcesem, co też czynić w przykładzie.

Przykład oczekuje jednego parametru w linii argumentowej -- nazwy czcionki, którą będzie "maltretował".

W jednym z poprzednich odcinków obiecałem, że pokażę, jak obejść się bez "WFLG_GIMMEZEROZERO", właśnie to robić:

Struktura "Window" zawiera pola, w których zapisana jest szerokość poszczególnych ramek okna. Pola "BorderLeft", "BorderTop", "BorderRight", "BorderBottom" zawierają szerokości odpowiednio lewej, górnej, prawej i dolnej ramki. Aby nie "paprać" po ramkach, wystarczy pamiętać, że "dozwolony" obszar zaczyna się poniżej górnej i na prawo od lewej ramki, oraz powyżej dolnej i na lewo od prawej. W przykładzie korzystam ze struktury "Rectangle", w której na początku odnotowuję wolny obszar okna, a następnie sukcesywnie zmniejszam go, umieszczając w RastPorcie napisy.

Może Was zastanawiać, dlaczego sprawdzając, czy jest cokolwiek do napisania, sprawdzamy zarówno, czy TextFit() zwróciła wartość niezerową, jak i to, czy za napisem będzie jeszcze przewidziane przez nas wolne miejsce. Powinien właściwie wystarczyć ten pierwszy test. Wygląda jednak na to, że funkcja TextFit() ma błąd -- gdy "da" się jej zbyt mało miejsca, to czasami "wydaje" jej się, że ma go w bród i "udziela nieprawdziwych informacji", należy więc zweryfikować otrzymane wyniki.

W pętli do-while zastosowałem skrót, pozwalający cyklicznie zmieniać atrybuty czcionki. Skrót ten jest dokładnie taki sam, jak w poprzednim listingu. Tamten dotyczył trybu rysowania w RastPorcie.

Na koniec przypominam, że polecenie preprocesora "#undef" powoduje "zapomnienie" o poprzedniej definicji makra.