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.