Položky zakončené lomítkem jsou adresáře, ostatní položky jsou soubory.
Vzhledem k tomu, že ditherování se provádí v 16-bitovém režimu (tedy 48 bitů na pixel), a samozřejmě v prostoru úměrném světlu, je to super-ultra-delikatesně kvalitní, nezávisle na tom, jak crappy rozlišení si nastavíme.
Aby program mohl pracovat s rozličnými grafickými zařízeními, bylo navrženo rozhraní pro grafické drivery. Každý grafický driver obsluhuje jeden grafický subsystém (SVGAlib, OS/2, X). Na grafickém driveru může být vytvořeno několik grafických zařízení. V okennich systémech je každé grafické zařízení jedno okno; v neokenních systémech jsou použita virtuální zařízení - třeba na SVGAlib je možno virtuální zařízení přepínat pomocí Alt-1 - Alt-0. Za běhu programu může být aktivní pouze jeden driver, na který ukazuje proměnná drv. Každý driver je popsán strukturou struct graphics_driver, která má následující položky:
unsigned char *name
unsigned char *(*init_driver)(unsigned char *param)
struct graphics_device *(*init_device)(void)
void (*shutdown_device)(struct graphics_device *dev)
void (*shutdown_driver)(void)
unsigned char *(*get_driver_param)(void)
int (*get_empty_bitmap)(struct bitmap *dest)
Návratová hodnota je 0 pokud je bitmapa naalokovaná na heapu, 1 pokud je ve videopaměti a 2 pokud je v X serveru.
int (*get_filled_bitmap)(struct bitmap *dest, unsgned char *pattern, int n_bytes)
Návratová hodnota je 0 pokud je bitmapa naalokovaná na heapu, 1 pokud je ve videopaměti a 2 pokud je v X serveru.
void (*register_bitmap)(struct bitmap *bmp)
void *(*prepare_strip)(struct bitmap *bmp, int top, int lines)
prepare_strip()
musí být zavolán právě jednou odpovídající commit_strip()
.
void (*commit_strip)(struct bitmap *bmp, int top, int lines)
void (*unregister_bitmap)(struct bitmap *bmp)
void (*draw_bitmap)(struct graphics_device *dev, struct bitmap *hndl, int
x, int y)
void (*draw_bitmaps)(struct graphics_device *dev, struct bitmap **hndls,
int n, int x, int y)
long (*get_color)(int rgb)
void (*free_color)(long color)
void (*fill_area)(struct graphics_device *dev, int x1, int y1, int x2, int
y2, long color)
void (*draw_hline)(struct graphics_device *dev, int left, int y, int
right, long color)
void (*draw_vline)(struct graphics_device *dev, int x, int top, int
bottom, long color)
int (*hscroll)(struct graphics_device *dev, struct rect_set **set, int sc)
Pokud set není rovna NULL, obsahuje obdélníky, které je potřeba překreslit (oblast, která byla přikrytá jiným oknem). Program musí tuto oblast překreslit sám, grafický driver to neumí.
Kdyby nebylo jasno, co je posunout oblast doprava, tak to je načíst klipovací obdélník, nakreslit ho na místo které je více vpravo než kde obdélník původně byl. A při kreslení se samozřejmě klipuje na nastavenou oblast, takže část dat se při tom kreslení zahodí. A část plochy zůstane původní pro libovolný obsah obdélníku. Toto je odkrytá oblast.
int (*vscroll)(struct graphics_device *dev, struct rect_set **set, int sc)
void (*set_clip_area)(struct graphics_device *dev, struct rect *r)
int (*block)(struct graphics_device *dev)
Pokud se ti zavola block a ty jses uz zablokovany (predtim bylo zavolano block a jeste nebylo zavolano unblock), tak nedelas nic a vratis 1. Jinak vratis 0.
Pro drivery, kde neni potreba blokovat terminal pri pousteni externich programu (X, Pmshell), tahle funkce vraci 0 a nedela nic.
void (*unblock)(struct graphics_device *dev)
int depth
Bity v bajtu jsou číslovány: 0 má váhu 1, 7 má váhu 128.
depth deka | depth hexa | Barevná hloubka | Paměťová organizace: offset bytu v paměti | |||||||||||||||||||||||||||||||||
+0 | +1 | +2 | +3 | |||||||||||||||||||||||||||||||||
R | G | B | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ||
33 | 0x21 | 1 | 2 | 1 | R0 | G1 | G0 | B0 | ||||||||||||||||||||||||||||
65 | 0x41 | 3 | 3 | 2 | R2 | R1 | R0 | G2 | G1 | G0 | B1 | B0 | ||||||||||||||||||||||||
122 | 0x7A | 5 | 5 | 5 | G2 | G1 | G0 | B4 | B3 | B2 | B1 | B0 | R4 | R3 | R2 | R1 | R0 | G4 | G3 | |||||||||||||||||
130 | 0x82 | 5 | 6 | 5 | G2 | G1 | G0 | B4 | B3 | B2 | B1 | B0 | R4 | R3 | R2 | R1 | R0 | G5 | G4 | G3 | ||||||||||||||||
195 | 0xc3 | 8 | 8 | 8 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | G7 | G6 | G5 | G4 | G3 | G2 | G1 | G0 | R7 | R6 | R5 | R4 | R3 | R2 | R1 | R0 | ||||||||
196 | 0xc4 | 8 | 8 | 8 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | G7 | G6 | G5 | G4 | G3 | G2 | G1 | G0 | R7 | R6 | R5 | R4 | R3 | R2 | R1 | R0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
451 | 0x1c3 | 8 | 8 | 8 | R7 | R6 | R5 | R4 | R3 | R2 | R1 | R0 | G7 | G6 | G5 | G4 | G3 | G2 | G1 | G0 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | ||||||||
452 | 0x1c4 | 8 | 8 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | G7 | G6 | G5 | G4 | G3 | G2 | G1 | G0 | R7 | R6 | R5 | R4 | R3 | R2 | R1 | R0 |
708 | 0x2c4 | 8 | 8 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | R7 | R6 | R5 | R4 | R3 | R2 | R1 | R0 | G7 | G6 | G5 | G4 | G3 | G2 | G1 | G0 | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
int x, y
int flags
int codepage
Struktura graphics_device má následující položky:
struct rect size
struct rect clip
struct graphics_driver *drv
void *driver_data
void *user_data
void (*redraw_handler)(struct graphics_device *dev, struct rect *r)
void (*resize_handler)(struct graphics_device *dev)
void (*keyboard_handler)(struct graphics_device *dev, int key, int
flags)
void (*mouse_handler)(struct graphics_device *dev, int x, int y, int
buttons)
Poslední 4 funkce volá driver, když došlo k nějaké události. Aby se předešlo
race-conditionům, musí být funkce volány z bezpečného místa - t.j. z
handlerů
vytvořených v modulu select.c.
Grafický driver nesmí tyto funkce volat ze svých funkcí volaných z programu.
Například, když program zavolá hscroll, grafický driver nesmí
volat redraw rovnou z kontextu hscroll, ale musí si na to registrovat buď
bottom half nebo timer s časem 0. Kdyby to volal rovnou, tak by v programu
vzniklo rekurzivní volání, a to by nevedlo k ničemu dobrému.
int init_virtual_devices(struct graphics_driver *drv, int n)
void shutdown_virtual_devices()
struct graphics_device *init_virtual_device()
void shutdown_virtual_device(struct graphics_device *dev)
void switch_virtual_device(int i)
struct graphics_device *current_virtual_device
Formát obrazových dat je charakterizován jednotlivými přítomnými kanály, jejich hloubkou, paměťovou organizací a gammou (alpha nemá gammu).
Obsahuje texty o různých barvách. Vhodné pro testování linkse v grafickém režimu, zejména ditherování, zmenšování písmenek a správné gammy.
Uvnitř zdrojáku genps.c uživatel může nastavit proměnné
h_margin, v_margin, font_pos, font_height, paper_height. Viz následující
obrázek:
Neplatný příkaz bude ignorován a bude se pokračovat v provádění. Před uložením do souboru musejí být všechny pixely mezi černou a bílou, budou-li mimo tento rozsah, jejich hodnota v souboru bude nedefinovaná.
Typické použití programu je pro ztluštění nebo ztenčení písmenek, které se provede gaussovským rozmazáním, vynásobením, přičtením a ořezáním.
Pak pustíme wb02links, přičemž font_cache.dat musí být v aktuálním adresáři. Vygenerují se nám pngčka do adresáře ./font/.
Když pustíte purge, smaže se všechno tohle, kromě těch *.swp.
Javascript může hackovat do objektů - a to zejména do jakéhokoliv html
tagu (tzn. změny barev, linků pod rukama... lze vytvářet nová okna).
Jak je to s vytvářením nových linků jsem zatím nenašel.
Méně příjemná věc, kterou jsem zpozoroval je, že skript může hackovat do
okna - přes objekty document a window. Bude potřeba důkladně navrhnout, jak
javascriptu umožníme zapisovat do dokumentu přes
document.write("<A href="odkaz.html">text odkazu</a>");
vytvořit nový link a pak pod něj zapisovat.
Prostě ten text, co se bude vypisovat přes write, se musí protáhnout html
parserem!
Scriptové příkazy mimo funkci, tj. napsané jako main v Pascalu se provádějí sekvenčně (v pořadí, v jakém jsou v dokumentu za sebou), neukončeny skript v Netscapovi neproběhne (musí se začínat <script> a skončit </script>). Jakmile to druhé chybí, tak se ten kus skriptu neprovede.
Objekt navigátor - bude mít vlastnosti spojené s navigací, zařídí PerM. Vložené funkce se při parsování nejvýše syntakticky a sémanticky rozeberou - neexistuje nic jako funkce autoexec, ale místo toho se provádí kód mimo funkční definice, tudíž by bylo patrně nejlepší udělat parsování skriptů rovnou při nahrávání (rychlejší interpretace a pak můžu účinněji zasáhnout když zjistím malér). Kromě toho: Je třeba funkce Settimer, která za nějaký čásek pošle údaj o tom, že přišla událost vyprchání času... Budu tedy muset být schopen rozhodnout, jestli přišel timeout, nebo jestli přišla jiná událost jako třeba mouseclick... Ještě je třeba chytat události na objektech - kdy se kde pohybuje myš, kdy se s ní cvaklo... kdy se dvojclicklo...
Objekt history - archivuje, kde jsme byli - nemělo by být těžké to udělat: Je možno zavolat Go back.
Javascript umí měnit i obsahy různých framů, tedy:
Je otázka, jestli se mi mají posílat všechny události, které se staly,
nebo jestli si mám já říct o události, které mě zajímají a nechat
si ohlásit jenom ty. Druhé řešení by možná bylo lepší, protože by nebylo
kvůli nevýznamným událostem nutné volat mé handlery.
Javascripty umí taky procmailovat. V normě to však není, takže to patrně nepodpoříme.
Reagovat lze na Abort - onAbort, onBlur,onChange - textová políčka, seznam vúběrů..., onClick,onDragDrop,onError,onFocus - windows and all. Aktuální seznam není nikde snadno k dispozici, zkusíme vydumpovat kde co je v nějaké verzi Netscape Navigatora a položky, které se podaří zdokumentovat implementujeme. Norma Javascript 1.1 popisuje úplně jiné rozhraní, než je ve skutečnosti implementováno. Tudíž bude potřeba asi udělat něco mezi tím.
Multitask javascriptu je kooperativní, tzn. že dokud skript běží a nevolá nějakou funkci, tak je nepřerušitelný - viz PerMova kalkulačka vylepšená někde na PerMových stránkách. Ve chvíli, kdy je spuštěna funkce Settimeout, je skript pozastavený a tudíž je možno spustit něco jiného. Pak přijde událost, nasadí proces zpátky na koleje.
Netscape Navigator má zajímavý option, že když se spustí
program:
<script language="javascript">
while(1);
</script>
tak je potřeba celého Navigatora zabít, jelikož javascript v Netscape
Navigatorovi není přerušitelný. To je poněkud pofidérní vlastnost,
tu bych odstranil zaschedulováním po každém statementu (pokud se to ukáže
jako moc časté, tak třeba jednou za 100 statementů).
Jinými slovy my uděláme multitasking "částečně preemptivní", tedy tak, aby nešlo dvakrát spustit stejný skript přes sebe, ale dva různé skripty ano. O multitaskingu se v normě javascript 1.1 nic nepíše, tudíž předpokládám, že si jej lze udělat dle vlastního uvážení
Toto by si měl přečíst každý, kdo do Linkse bude něco programovat.
Links je psán celý v C, C++ se nepoužívá. Links je psán tak, aby byl zkompilovatelný klasickým ANSI C bez GNU rozšíření. Je třeba dát pozor na následující rozšíření, která sice projdou v gcc, ale neprojdou v ANSI C (a kterých byl původní links plný a dostával jsem na to stížnosti):
// tohle v cc neprojde /* je potřeba používat tyto komentáře */
fn() { nějaký kód.... label: }Je třeba nahradit
fn() { nějaký kód.... label:; }
struct bla { int a, b; }; int a1, a2; fn() { struct bla x = {a1, a2}; }
K otestování přenositelnosti linksu na ANSI C je třeba ho nahrát na Solarisy nebo Irixy na Malé Straně a napsat (za současného modlení aby na Irixu nespadl slabostí kernel)
export CC=cc ./configure makePak se to bude kompilovat ANSI C místo GNU C. Vypise to spoustu warningů, ale výsledný kód je funkční.
Napíšeme foo.c. Přidáme do Makefile.am do řádku links_SOURCES foo.c a na začátek foo.c dáme #include "cfg.h" a #include "links.h".
configure.in je shell script, ve kterém jsou makra. Makra jsou procesorem m4 expandována na další příkazy shellu (jak psát portabilní shell skripty se dočtete v info autoconf). Řetězce se v m4 dávají do [ hranatých závorek ]
Nejčastěji používaná makra:
Nesmí se předpokládat, že sizeof(int) == sizeof(void *). Neplatí to na Alphě. Je možno předpokládat, že sizeof(int) <= sizeof(void *) <= sizeof(long).
char je na některých systémech signed a na některých (třeba ty IRIXy na Malé Straně) unsigned. Aby se zabránilo chybám (jaké v původním linksu taky skutečně byly), tak se před každý char bude psát unsigned. char bez specifikace se v kódu vyskytnout nesmí, signed char se může použít, když je znaménko potřeba.
Nepoužívat malloc, realloc, free. Místo toho používat mem_alloc, mem_realloc, mem_free. Tyto funkce mají navíc kontrolu na memory leaky. Když se vyskytne leak, tak se při ukončení napíše soubor a řádek, kde se příslušná paměť alokovala, a dumpne se core.
mem_alloc nikdy nevrátí NULL. Vstup nesmí být 0.
mem_calloc - je jako mem_alloc, ale vynuluje blok.
mem_realloc - je místo reallocu.
mem_free - nesmí se volat s argumentem 0.
mem_alloc, mem_realloc, mem_free není možno používat v threadech.
Následující funkce je třeba volat při různých chybových stavech. f/printf by se pokud možno nemělo používat vůbec - třeba jednoho dne bude potřeba zpracovávání chyb změnit.
void error(unsigned char *str, ...)
void debug(unsigned char *str, ...)
void internal(unsigned char *str, ...)
void do_not_optimize_here(void *p)
unsigned char upcase(unsigned char chr)
int casecmp(unsigend char *s1, unsigned char *s2, int len)
unsigned char *stracpy(unsigned char *str)
unsigned char *memacpy(unsigned char *str, int n)
void add_to_strn(unsigned char **str1, unsigned char *str2)
Datová struktura reprezentuje posloupnost znaků, z nichž žádný není nulový (tedy to, co se mysli pod pojmem řetězec v jazyce C). Instance této struktury je definována ukazatelem a délkou. Délka je vždy >=0, a vyjadřuje počet těch znaků, které jsou reprezentovány. Ukazatel je vždy !=NULL. Podíváme-li se do paměti, na kterou ukazuje ukazatel, uvidíme tam obsah této datové struktury, za kterým bude nulový znak (terminátor), a na další obsah této paměti se již nesmí koukat. Ukazatel vždy vznikl z mem_alloc nebo mem_realloc. Destrukce struktury se provádí tak, že se zavolá mem_free na ukazatel. Konstrukce se nesmí dělat pokoutně, musí se použít init_str(). Datová struktura je implementována exponenciálni prealokací o základu 2. Kód těchto funkcí je v souboru links.h.
unsigned char *init_str()
void add_to_str(unsigned char **str, int *len, unsigned char *str2)
void add_bytes_to_str(unsigned char **str, int *len, unsigned char *str2,
int len2)
void add_char_to_str(unsigned char **str, int *len, unsigned char
chr)
void add_num_to_str(unsigned char **str, int *len, int num)
void add_knum_to_str(unsigned char **str, int *len, int num)
{ int l = 0; unsigned char *str = init_str(); add_to_str(&str, &l, "Text:"); add_chr_to_str(&str, &l, ' '); add_num_to_str(&str, &l, 10); printf(str); mem_free(str); }Toto je špatně:
{ int l = 5; unsigned char *str = stracpy("12345"); add_to_str(&str, &l, "bla"); }Funkce stracpy nedělá potřebnou prealokaci, takže to bude střílet do paměti.
struct polozka_seznamu { struct polozka_seznamu *next; struct polozka_seznamu *prev; /* a tady jsou uz dalsi volitelne polozky */ int a, b; unsigned char c; }Hlavu seznamu deklarujeme takto:
struct list_head hlava_seznamu = { &hlava_seznamu, &hlava_seznamu };
add_to_list(hlava_seznamu, polozka_seznamu)
add_at_pos(prvek_v_seznamu, položka_seznamu)
foreach(proměnná, hlava_seznamu)
foreachback(proměnna, hlava_seznamu)
free_list(hlava_seznamu)
int list_empty(list_head)
Links má běžet v textovém i grafickém módu. Zkompilovat jde buď jen pro textový mód nebo pro textový i grafický mód (./configure --enable-graphics). V grafickém módu se pouští s parametrem -g.
Kód by měl být psán tak, aby uživatele, kteří chcou pouze textový prohlížeč
nebyli zatěžováni spoustou grafických funkcí. Textový prohlížeč by měl zůstat
rozumně malý.
Pokud je links zkompilován pro textový i grafický mód, je definováno makro G.
Proměnná F určuje, zda links právě běží v textovém(0) nebo grafickém(1) módu.
Pokud byl links zkompilován pouze pro textový mód, je F makro, které je
definované na hodnotu 0. Typický kód, mající se rozdvojit podle módu, vypadá
takto:
if (!F) { kód pro textový mód... #ifdef G } else { kód pro grafický mód... #endif }Existují další makra pro usnadnění rozhodování:
gf_val(x, y)
NO_GFX
NO_TXT
Tento odstavec popisuje, jak font ve formátu *.pfb, *.afm, *.pfa (nebo jeho části) přidat do prohlížeče Links. Vstupem procedury je font ve formátu, který je schopen přečíst Ghostscript a výstupem sada obrázků ve formátu PNG, které se přidají do adresáře graphics/font/<jméno fontu>.
Přidávání fontů je poloautomatické s použitím několika scriptů a pomocných programů, které jsou přibaleny v CVS vydání Linkse. Tyto programy nejsou v běžné distribuci, protože pro instalaci a běh prohlížeče nejsou potřeba. Programy se kompilují také bez použití configure, a vyžadují ke svému běhu striktnější verzi libpng než samotný prohlížeč (nejsou tam workaroundy na schopnosti libpng, které chybějí ve starých verzích). Procedura také vyžaduje, aby na systému byl nainstalován Imagemagick (program convert). Během přidání je možno vygenerovat agregované symboly, kde už stačí jen jednoduchá editace GIMPem, a můžeme tak dodělat chybějící akcenty z fontů, které nemají příslušné symboly.
Celý proces probíhá následovně: Uživatel zadá do Fontmap, který chce font, a pak pustí skript makefont. Výstupem makefontu bude hromada souborů ve formátu PNG v adresáři font/new. Program makefont provádí následující akce:
V genps.c můžeme provést nastavení, ale to jen v extrémním případě, a defaultní hodnoty v komentářích necháme, aby se vědělo, co tam patří. Celý systém je totiž seřízený na následující podmínky:
float font_pos=300; float font_height=392.9619; float h_margin=100; float v_margin=120; float paper_height=842;
/Links-generated (c059016l.pfb) ; /Links-generated (/usr/local/share/ghostscript/fonts/c059016l.pfb) ; /Links-generated /CenturySchL-Bold ;
Komentáře se dělají pomocí % na začátku řádku. Buď se tam může napsat rovnou jméno souboru s fontem (pokud je v adresáři kde ho Ghostscript najde, 1. řádek), nebo cesta (2. řádek) a nebo jméno fontu který už je v Ghostscriptu nadefinovaný (3. řádek). Vybereme si 1 z nich, modifikujeme podle našeho fontu a ostatní zakomentujeme. přidá Když máme font pro ghostscript (většinou *.pfb a *.afm, to *.pfb jsou písmenka a *.afm je metrika), tak se dá pomocí pomocných programů a skriptů přidat.
Zkontrolujeme, jestli v clip.c máme napsanou překladovou tabulku pro daný font. Máme-li například japonské písmo Hiragana, podíváme se kde je #ifdef HIRAGANA a tam je tabulka copy[] a merge[]. Copy říká která písmenka se z fontu mají okopírovat rovnou a merge říká ty akcentované co se pak budou lepit gimpem. v copy[] je vždy po sobě číslo znaku ve fontu ghostscriptu (0-255) a číslo znaku v unicode (0-65535). V merge jsou trojice: číslo znaku v ghostscriptu, číslo příslušného akcentu v ghostscriptu, a číslo v unicode na který se to má zmergovat. Pokud v clip.c pro příslušné písmo tyto tabulky nejsou, tak si přidáme #define a připíšeme si je. Můžeme stávající tabulky také opravit, když zjistíme že je v nich chyba. Nejlépe se tabulky píšou tak, že si dáme v Xech do 1 okna gv letters.ps a do vedlejšího prohlížeč s unikódovými tabulkami z http://www.unicode.org. Do třetího okna dáme vi clip.c a vizuálně hledáme ke každému unikódovému znaku odpovídající znak v letters.ps a rovnou to píšeme do zdrojáku clip.c.
V makefontu je možno nastavit ořezávání zhora a zdola, a rozlišení. Ale nenastavujte to, jen v případě, že nebude zbytí. Nechte defaultní hodnoty:
export hundred_dpi=1703 export top_promile=198 export bottom_promile=238
Pomocí makefont se vygenerují do adresáře font/new/ písmenka která jsou ve fontu přímo. Písmenka s akcentem která tam nejsou, ale je tam to písmenko a akcent se vygenerují že v tom výsledném obrázku je vlevo písmenko, mezi tím takový kostkovaný pruh a vpravo akcent. Pak se vezme gimp a akcent se přendá nad písmenko do esteticky hodnotné polohy a odřízne se kostkované pruh a to, co je vpravo od kostkovaného pruhu. Pak se to uloží do obrázku zpět. Při troše zručnosti takovéto akcentování jde celkem rychle.
Fonty, to jsou ty čtverečky a obdélníčky na kterých je namalováno písmenko které je vidět na obrazovce. Při jejich tištění se nepoužívají žádné fontovací fuknce X, svgalib, pmshell ani ničeho podobného. Používají se pouze funkce pro tištění bitmap. Bitmapy jsou renderovány linksem (čímž je zaručeno, že bude moci být 100% kontrolováno, jak budou vypadat) přímo ve formátu vhodném pro dané výstupní zařízení (záleží na aspect ratio, barevné hloubce, schopnosti zobrazovat barevně nebo černobíle atd.) a pak jsou kresleny na obrazovku.
Základem pro fonty jsou obrázky písmenek, tak jak mají vypadat. Souvislý text se skládá z řádek, což jsou stejně vysoké pruhy textu. V obrázku písmenka (toho v adresáři font) je horní okraj (horní hrana nejsvrchnější pixelové řady) ztotožněn s hraniční přímkou těchto pruhů, a dolní hrana (dolní okraj nejspodnější pixelové řady) je stotožněna s hraniční přímkou o jednu řadu níže. Hraniční přímka je útvar o nulové tloušťce.
Překrývání ani ligatury se nepodporují z důvodu, že přinášejí málo vizuálniho zlepšení za cenu zavlečení obtížných problémů do sázení, tištění, scrollování, rozsvěcování části textu a podobně. Italické písmo se nepodporuje z důvodu, že by mezera mezi písmeny z důvodu překryvu byla někdy zaměnitelná s mezerou mezi slovy.
Partie, které jsou v obrázcích bílé, budou kresleny "inkoustem", partie černé budou kresleny "papírem". Části s barvou mezi tím budou kresleny lineárně (ve fotonovém prostoru) mezi tím (tedy např. je-li png 8-bitové a barva z rozsahu 0 (černá) - 255 (bílá) je 12, pak bude smícháno 12/255 inkoustu a (255-12)/255 papíru). Když jsou písmenka barevná (což se nedoporučuje, protože to má za následek zbytečné zvětšení PNG), tak se zkonvertují na černobílé podle aproximační formulky vyjadřující přibližný jas vnímaný člověkem.
Předlohy písmenek jsou hodně vysoké obrázky, například 100 pixelů. Pro kreslení řekněme 16 pixelů vysokého písmenka je třeba bitmapu zmenšit. Na to se použije algoritmus, který namapuje výstupní pixely na vstupní (oboje pixely bere jako obdélníky) a v každém výstupním pixelu vypočítá průměrnou barvu na základě informací o pixelech vstupních. Dělá se to převzorkováním v jednom směru a následným převzorkováním v druhém směru. Pořadí se volí tak, aby mezivýsledek měl tu menší plochu z obou možných variant.
Písmenka, která jsou moc titěrná, takto vyjdou správně - tedy tak, jak by se jevila, kdyby byla natištěna na papíře a snímána idealizovanou televizní kamerou. Ovšem, jak je mozno vidět v televizi při záběru na dokument s malým písmem (a jak také vyplývá z naměřených funkcí vnímání kontrastu v závislosti na prostorové frekvenci u lidského oka), když jsou písmenka malá, vypadají šedivě a nevýrazně. Je to způsobeno faktem, že malé černé a bílé detaily vytvoří šedivou, která způsobuje subjektivní vjem sníženého kontrastu. Tomuto se předchází následnou korekcí, která slučuje filtr pro zvýšení konstrastu (s oříznutím na černou a bílou, samozřejmě) a filtr pro zvýšení ostrosti. Parametry filtru byly voleny empiricky, aby to dobře vypadalo. Byly dělány také pokusy s převzorkováním ostrou dolní propustí namísto mapování obdélníků, ale výsledek byl špatný (ačkoliv metoda byla ta jediná matematicky správná pro převzorkování velkého obrazu na malý tak, aby se reprezentovatelné frekvence zachovaly beze změny amplitudy i fáze).
Pro tento kombinovaný filtr je použita matice 3x3 bodů natvrdo zakódovaná do algoritmu (a optimalizovaná na základě symetrie). Filtr se používá do výšky písmenek 32 pixelů, dále se již nepoužívá.
Zdrojové PNG soubory jsou uloženy ve spustitelném souboru. Program generate_font slouží pro výrobu font_include.c, jenž obsahuje pole bajtů zapsané v syntaxi jazyka C, ve kterém jsou uloženy po sobě v pořadí podle čísla znaků všechny potřebné PNG, a pomocnou tabulku umožňující nalézt písmeno a zjistit, jak je jeho PNG dlouhé. PNG se v paměti rozkóduje pomocí libpng a putuje dále do zpracování.
Výhody celého tohoto přístupu jsou následující: pro kompletní funkci prohlížeče je potřeba jeden soubor a příslušné nainstalované knihovny (v případě, že je zkompilován staticky, je třeba jen jeden spustitelný soubor, který může být puštěn i misto initu), nevznikají tedy potíže s tím, kam umístit data na různých operačních systémech. Vzhled a podporované fonty nejsou platformně a konfiguračně závislé. Písmenka se dají snadno přidávat (stačí získat vzor písmenka ve tvaru obrázku bez omezení na výšku a šířku, ten je možno získat i z tištěného materiálu nascanováním, úpravou obrazu GIMPem a uložením ve formátu PNG) pouhým překompilováním prohlížeče (což je standarní instalační procedura). Písmenka jsou dobře čitelná i při malé pixelové výšce (na rozdíl od neantialiasovaných fontů, které jsou při nízkých výškách téměř nebo zcela nečitelné).
Písmenka z PNG jsou standardně uložena 8-bitová šedá s gammou 1.0 což odpovídá tomu, že jsou přímo úměrná světlu vycházejícímu z obrazkovky. Použijeme-li je jako alpha masku na 16-bitové barvy, vzniknou nám čísla 0 až 255*255*257. K tomuto číslu přičteme 127 a vydělíme to 255, čímž dostaneme číslo v rozsahu 0-255*257 (65535).
V případě, že je za písmenky obrázek, použije se místo jednolité pozaďové 16-bitové barvy 16-bitový obrázek. U pozaďového obrázku se alpha ignoruje.
Ruzné druhy písma jsou dělány tak, že každý druh písma má
extra sadu obrázků. Podtržené písmo se dělá přikreslováním podtrhávací
čáry přes font: při kreslení znaků se nastaví clip nad podtrhovací čáru,
pak se nakreslí podtrhovací čára a clip se nastaví pod podtrhovací
čáru a nakreslí se písmenka znovu.
Jinak má každý font jméno ve formátu family-weight-slant-adstyl-spacing. Family
může být libovolné jméno (fonty ho mají malými písmeny, nehledí se na
velká a malá písmena) složené jen z písmen a podtržítek. weight je
"bold" nebo "medium", slant je "italic" nebo
"roman", adstyl je "sans" nebo "serif",
spacing je "mono" nebo "vari".
Když se hledá vhodný font, tak se napřed chce, aby seděly všechny položky co označují font. Tím se najde v souboru font/catalogue jediný font, který se dá na první místo a vyškrtne se. Pak se zruší postupně položky family, adstyl, weight, spacing, slant. Po zrušení každé položky se projde katalog a vyhovující fonty se nasází na další místa. Nakonec je průchod se zrušenými všemi položkami, a tam musí projít všechny fonty, takže se všechny fonty tímto seřadí do žebříčku oblíbenosti pro zadaný požadavek na font. To se nacpe do struktury struct font, a když se hledá písmenko, tak se po hitparádě jde zezhora dolů a dokavaď se nenajde, tak se jde. Když se nenajde ani na posledním místě, tak se vrátí prázdné písmenko s rozměry 0 x 0 nebo něco podobného, což vyústí v to, že se místo písmenka vůbec nic nezobrazí - prostě v textu chybí.
Do monitoru vstupuje elektrický signál a vystupuje z něj optický signál. Elektrický signál je přímo úměrný počtu elektronů a optický signál počtu fotonů. Co je foton a elektron snad každý ví. Všechny elektrony jsou stejně velké. Všechny fotony jedné vlnové délky jsou stejně velké.
Počet fotonů není přímo úměrný počtu elektronů na vstupu. Platí například, že fotony=elektrony^2.2. To 2.2 je gamma toho monitoru. Obdobné kalkulace platí i pro jiná zařízení, pokud jsme schopni se dohodnout, v jakých jednotkách se měří vstupní a výstupní veličiny zařízení. Ne každý monitor má gammu stejnou. A gamma jedoho exempláře monitoru se může lišit i pro jednotlivé kanály red, green, blue. Nejlepší je proto gammu změřit pomocí testovacího obrazce a nastavit ji do prohlížeče, protože pak dostaneme nejdokonalejší obraz. Při zadání nesprávných hodnot gamma mohou vzniknout závady zobrazování jako barevné závoje při ditherování, příliš kontrastní jedny partie obrazu a nedostatečně kontrastní ostatní partie obrazu, případně nesprávné podání barevného tónu v určitých partiích. V případě, že není možno určit gammu, lze použít aproximaci, kdy se všechny tři gammy nastaví stejně, a to na hodnotu 2.2.
Základní podmínka pro to, abychom očekávali vůbec věrný obraz, je nastavit správně jas monitoru. S kontrastem si pak budeme moct kroutit jak chceme, kvalitu obrazu neovlivní, jen jeho intenzitu. Nastavovací procedura následuje: nastavte kontrast na minimum a jas na maximum. Zmenšete obraz abyste jasně viděli rozdíl mezi rámečkem obrazu ještě zasaženým elektronovým paprskem a krajem skla paprskem nezasaženým. Pak snižte jas dokud nepřestane být vidět toto rozhraní. Pro zlepšení viditelnosti je vhodně zhasnout, zatáhnout záclony a podobně. Jakmile rozhraní přestane být vidět, jas se již nesmí dále ubírat. V tomto okamžiku velmi opatrně nalepte kus lepicí pásky na knoflík jasu aby se již nedal nikdy více otáčet nebo se zapřísahejte na Bibli, že do elektronického menu již nikdy na jas nesáhnete. Pak si nastavte kontrast dle libosti a zvětšete obraz zpět do původní velikosti. V případě, že nejste proceduru schopni provést přesne, je lepší nechat jas trochu větší než trochu menší. Pro přesný popis této procedury se podívejte semhle.
Display gamma je gamma exponent mezi hodnotou vstupující do grafického ovladače (například do paměti grafické karty, do X protokolu, atd.) a počtem fotonů vystupujících z luminoforu obrazovky následkem ozáření elektronovým paprskem.
Jaký má display gamma exponent, to říkají proměnné display_red_gamma, display_green_gamma a display_blue gamma. Protože gammy se mohou lišit pro různé barevné kanály, tak jsou tam tři. Cílem zobrazení linksu je, aby počet fotonů dopadajícího do oka uživatele při osvětlení 64 luxů byl stejný jako počet fotonů dopadajícího do oka uživatele, sedícího před monitorem s gammou monitoru 2.2 a gammou obrázku 1/2.2 (obrázek podle sRGB standardu) při osvětlení 64 luxů.
V případě, že máme obrázek sRGB a monitor s gammou 2.2 a osvětlení 64 luxů, postupuje links následovně: vezme raw data z obrázku a šoupne je do obrazovky. Pokud je osvětlení jiné, hodí se před šoupnutím obrázek umocnit na následující čísla:
Osvětlení | Umocnit na |
0lx | 1.333333333333 |
15lx | 1.111111111111 |
64lx | 1.000000000000 |
>=200lx | 0.888888888888 |
Tato magická čísla označme jako user_gamma a nechme je nastavit uživatele.
V případě, že gammy monitoru jsou jiné, a gamma obrázku je jiná, dělají se tyto akce:
Proto je ve zdrojovém kódu dip.c wanted_red_gamma, wanted_green_gamma a wanted_blue gamma. Jsou to gammy, které jsou zařazeny do obrazového řetězce jako exponenciální funkce x^-wanted_red_gamma, x^-wanted_green_gamma, x^-wanted_blue_gamma.
V přenosovém řetězci uvnitř linksu se dělají následující procedury:
V případě zobrazování obrázku z png, jpg, a podobně, se dělají náledující procedury:
Celý život linkse se odehrává ve funkci select_loop, která pomocí funkce select čeká na ruzné události. Pomocí následujících funkcí si můžeme objednat čekání na néjakou událost, vždy je třeba specifikovat funkci, která se zavolá, až daná událost nastane, a pointer, ktery se jí předá:
void set_handlers(int fd, void (*read_handler)(void *), void
(*write_handler)(void *), void (*error_handler)(void *), void *data)
int install_timer(ttime t, void (*fn)(void *), void *data)
void kill_timer(int timer_handle)
void install_signal_handler(int signal, void (*fn)(void *), void *data,
int immediate)
ttime get_time()
int register_bottom_half(void (*)(void *), void *)
Ke stahování dokumentu slouží object requester. Je možno ho nalézt v objreq.c.
void request_object(struct terminal *term, unsigned char *url, int pri, int
cache, void (*upcall)(struct object_request *, void *), void *data, struct
object_request **rqp)
term
je terminál, na který se budou
vypisovat otázky ohledně stahování. url
je url, které se má stáhnout.
pri
je priorita (konstanta PRI_
xxx).
cache
je úroveň, s jakou se má používat cache. Je definována makry
NC_
xxx. Typicky se používá NC_CACHE
.
upcall
je funkce, která se zavolá s parametrem data
.
Upcall se volá periodicky, každou 0.1s až 1s po dobu, po kterou je spojení živé.
Na místo rqp
se uloží pointer na request.
struct object_request
object_request->state
lze zjišťovat stav natahování:
O_WAITING
– ještě se nezačalo nic stahovat.
O_LOADING
– už se stahuje, je stažena část dokumentu.
O_FAILED
– nepodařilo se nic stáhnout.
O_INCOMPLETE
– stáhla se část a pak spojení spadlo.
O_OK
– stáhl se celý dokument.
object_request->stat.state
.
To je některá z konstant S_
xxx, která určuje přímo typ chyby, ke
které došlo, nebo stav, ve kterém se spojení právě nachází.
object_request->ce
ukazuje na položku v cachi, kam se stahuje. Může
být NULL, pokud se zatím nic nestáhlo. Tato položka je platná pouze ve stavech
O_LOADING, O_INCOMPLETE a O_OK
. V struct cache_entry
je uložena
HTTP hlavička a seznam fragmentu. Pomocí funkce defrag_entry
lze
položku kdykoli defragmentovat a fragmenty spojit do jednoho, nebo to lze
nechat tak, a rozebírat jednotlivé fragmenty.
void release_object(struct object_request **rqq)
Interpret sestává z těchto částí:
K napsání lexikálního analyzátoru byl použit prostředek flex. Zdrojové texty jsou v souboru javascr.l. Úkolem lexikální analýzy je rozbit vstupní řetězec do sady tokenů, které postupují dále k syntaktické analýze. Tokeny mohou být nemálo typů. Lexikální pravidla byla opsána z normy javascript 1.1 od Netscape Corporation a poněkud upravena s vyhlídkou na snazší syntaktickou analýzu. Např. každe řídící slovo má svůj vlastní typ tokenu (while, for,...). K syntaktické analýze postupují tokeny číslované typem long (možná by stačil int, ale jelikož při jeho psaní nebylo jasné, jestli nebude potřeba místo čísla vracet pointer, byl vybran typ, který je konvertovatelný na pointer.
Typy tokenu: Řidícím slovům stačí pouze long říkající vše. Jedná-li se o nějaký literál, pak pro boolské literály jsou použity dva různé tokeny (TRUE a FALSE), pro nullový literál token NULL. Numerický literál odesílá ještě v proměnné yylval buďto hodnotu celého čísla, nebo pointer na necelé. yylval je typu long kvůli přetypovatelnosti na integer i pointer. Řetězce posílají v proměnné yylval ukazatel na místo v paměti, kde se řetězec nachází.
Identifikátory:
Každemu identifikátoru je již při lexikální analýze
přiřazen klíč, pod kterým bude v celém programu vystupovat (tento klíč
je jednoznačný i pro "vnořené" identifikátory, tedy ať už se identifikátor
vyskytuje jako proměnná a stejně se jmenuje kupř. nějaká metoda nějakého
objektu, budou mít tyto klíče stejné. Kde se objekt identifikátorem odkazovaný
v paměti nachází se zjišťuje z "adresného prostoru", který je až záležitostí
interpretu mezikódu. Při lexikální analýze tedy vzniká jakýsi "namespace",
který přiřazuje každému identifikátoru nějaký klíč. Indexování proměnných
klíčem bylo vybráno proto, že porovnat dva longy je podstatně rychlejší,
než testovat shodu dvou řetězců (zdržení se realizuje za každý výskyt
proměnné ve zdrojovém textu, nikoliv za každý dotaz na ni při interpretaci).
Lexikální analýza též odstraňuje komentáře a tomu podobné pro význam programu nepotřebné věci (konce řádku, zbytečné mezery...).
Operátor, ukazatel na první argument, ukazatel na druhý argument,... ukaza- tel na šestý argument.
Šest argumentů není nikdy použito (maximum jsou čtyři, návrh natvrdo sestrojit sedmice je podložen těmito argumenty: Sice je v určitém smyslu poněkud marnotratný (průměrný počet potomků ve stromě je mezi dvěma a třemi), navíc není zcela pružný (změna gramatiky může někde vynutit třeba 7 synů), zato je ale poměrně jednoduchý, není potřeba udržovat počet synů a komplikovaně pro ně alokovat a odalokovavat paměť, navíc změna gramatiky by reprezentovala takový zásah do interpretu, že přidání pár slotů na syny by byla nevinná dětská hra (i když samotné přidání nějakých pravidel by rovněž problémem nebylo - postačovalo by přidat příslušná pravidla do javascript.y, eventuálně nové tokeny do javascr.l a změny interpretující funkce do ipret.c).
Fosilie: Funkce terminál a neterminál. V minulosti se jevilo jako dobrý nápad rozlišovat, co je terminál (nemá potomky typu pointer, ale hodnota) a co neterminál (rodič nějakých terminálů nebo neterminálu). Posléze se však ukázalo, že uzle jsou natolik heterogenní, že takovéto dělení je úplně zbytečné. Až bude trocha času na civilizování kódu, tak jednu z těchto funkcí vymažu (možná už jsem to udělal, ale nepamatuju se).
program -> program program,tedy pouze řetezí jednotlivé bloky programu.
Vztahy k okolí:
Interpretace začíná zavoláním js_create_context, která vytvoří strukturu
js_context (popsanou výše). V budoucnu v této funkci budou do namespaců
a addrspaců doplňovány vestavěne funkce. Jejich podpora ještě není napsána,
ale dle mého názoru by neměla patřit mezi těžké partie.
Když je potřeba interpretování zastavit (dočasně ukončit), zavolám callback. Po tomto zavolání mohu být ještě probuzen znovu dalším požadavkem.
Je-li potřeba s příslušným skriptem již definitivně skoncovat, zavolá se funkce js_destroy_context, která zruší kontext a tím i všechna data se skriptem spojená.
Závěr:
Pokouším se napsat interpret javascriptu podle dostupných informací.
Ukázalo se, jak jsem podle různých tvrzení vyučujících překladačů předpokládal,
že se jedná o poměrně náročné, zato však zajímavé programování s ohledem na
to, že je potřeba ošetřovat různé speciální případy a navrhovat různé věci,
které nepatří mezi zcela obvyklé partie programování a které vyžadují
relativně abstraktní a teoretické uvažování.
Pro upcally navrhuju tento interface:
upcall(long upcall_id,int id_volajícího_dokumentu,struct upcall_arg *argumenty);
Za odallokování argumentu si ručí příjemce, vracet se bude typ long a to povětšinou jako pointer na řetězec. Nula znamená void. Bude-li potřeba, bude se vracet struct upcall_arg*.
struct upcall_arg { char* argument; struct arg* next; }
Původně jsem chtěl předávat char**, ale byla by tu nutnost znát předem počet argumentů.
Podle definovanosti konstant DEBUZIM a BRUTALDEBUG se vypisuji nebo nevypisuji hlasky o tom, jak probiha analyza. Zavislost na nicem neni. Podobne funguji i makra debug (puvodni debug oddefinovano v ipret.c a ns.c) a idebug (v builtin.c, context.c). Budto jsou definovana jako printf, nebo se misto nich vrazi prazdne misto, normalne jsou vypnute. Pak jsou tu konstanty DEBUGMEMORY - podle ni se nastavuje komentar k argumentum ulozenym na bufferu, DEBUZ_KRACHY - pri internalu a js_erroru vysype do links_debug.err udaje o miste, kde se to stalo. Konstanta DEBUZ_KRACHY je ponekud nebezpecna a v normalnim provozu by nemela byt definovana!
U konstanty DEBUGMEMORY je dulezite, aby byla nastavena jenom kdyz se alokuje pomoci debug_mem_alloc a freeuje pomoci debug_mem_free, proto je kolem nich v struct.h ifdef na to, jestli je DEBUGLEVEL 2.
V souboru listedit.c jsou funkce a datové struktury umožňující jednoduše nadefinovat nějaký seznam, se kterým uživatel linksu bude pracovat. S využitím funkcí z listedit.c se dají vytvořit velice jednoduchým přídavným kódem například bookmarky, asociace, přípony, ...
Seznam může být buď plochý, nebo stromový. Plochý seznam je lineární seznam, kde jedna položka následuje druhou. Ve stromovém seznamu jsou krom položek ještě adresáře. V adresářích mohou být položky nebo další adresáře.
Obecné seznamy neřeší vyrábění seznamů, jejich ukládání na disk, čtení z disku. To si musí implementátor obstarat sám.
Při zavolání funkce create_list_window se uživateli Linksu zobrazí okno se seznamem. Po seznamu se může pohybovat klávesami nahoru, dolů, home, end, page-up, page-down, tím se vybírá položka seznamu. Klávesami doleva, doprava se pohybuje po tlačítkách ve spodní části okna, klávesou enter se "zmáčkne" příslušné tlačítko ve spodní části okna. Navíc u stromového seznamu se klávesami + / - otevře/zavře adresář, mezerníkem se otevřený adresář zavře, zavřený se otevře.
Uživatel také může užít myši. Pravé tlačítko+pohyb scrolluje seznamem nahoru/dolů. Pokud se klikne levým tlačítkem na popis položky, položka se vybere. Navíc u stromového seznamu levé tlačítko na symbolu adresáře otevře/zavře adresář.
V hlavním okně jsou tato tlačítka:
Seznamy používají dvě významné datové struktury. První z nich je struct list. Tato struktura obsahuje vlastní seznam.
Obsahuje tyto části:
Druhá struktura je struct list_description, obsahuje popis seznamu. Pro každou implementaci seznamu je potřeba vytvořit jednu takovouto strukturu, ta se pak předává všem funkcím pracujícím se seznamy.
Struktura obashuje tyto položky:
Jediné funkce viditelné navenek listedit.c jsou
extern int create_list_window(struct list_description *,struct list *,struct terminal *,struct session *);Tato funkce vytvoří hlavní okno se seznamem.
extern void redraw_list_window(struct list_description *ld);Překreslí obsah okna se seznamem.
extern void reinit_list_window(struct list_description *ld);Znovu inicialisuje okno do stavu jako po spuštění create_list_window. To znamená přesune kurzor a výhled na seznam na začátek a překreslí.
Každý člen seznamu má u sebe proměnné: hloubka, typ (adresář/položka), flag otevřeno/zavřeno, ukazatel na otce.
Hloubka je číslo od 0 výše, hlava má jako jediná hloubku -1. Stromový seznam je interně uložen jako lineární seznam. Adresář je uložen tak, že je nejprve adresář a za ním následuje jeho obsah. Obsah adresáře je tedy po adresáři nasledující souvislý blok položek, ktere mají hloubku větší než hloubka dotyčného adresáře.
Typ může být buď 1 (adresář), nebo 0 (položka). Flag otevřeno/zavřeno se používá jen u adresáře. U položky se ignoruje.
Ukazatel na otce slouží k urychlení vykreslování seznamu. Když jsou některé adresáře zavřené, tak je potřeba jejich obsah přeskočit.
Když uživatel zmáčkne tlačítko add, vyrobí se nová položka (zavolá se funkce new_item), která se nepřidá do seznamu. Poté se zavolá funkce edit_item na editaci položky. Po úspěšné editaci položky (zmáčknutí "OK") se teprve položka přidá do seznamu. Při zmáčknutí "cancel" se položka smaže.
Při editování položky (uživatel zmáčkne tlačítko "edit") se vytvoří nová položka, zkopíruje se do ní obsah té původní (zavolá se copy_item) a opět se zavolá edit_item. Jestliže uživatel zruší editaci tlačítkem "cancel", položka se smaže. V opačném případě se obsah položky zkopíruje zpět do původní (opět pomocí copy_item) a položka se smaže.
Při zmáčknutí "cancel" tedy funkce edit_item musí položku smazat. Při zmáčknutí "OK" je zavolána funkce, kterou edit_item dostane jako argument (viz výše).
Struktury nejsou odolné proti více paralelním přístupům (například ve struct list_description je pozice kurzoru v okně atd.), proto je přístup z více oken zakázán. Funkce create_list_window nejprve nastaví ve struct list_description proměnnou open na 1, při zavření hlavního okna se proměnná open nastaví na 0. Pokud se při zavolání funkce create_list_window zjistí, že proměnná open je nastavena na 1, uživateli se objeví upozornění, že okno je již jednou otevřeno a že ho nejprve musí zavřít. Pochopitelně toto platí pro stejný seznam. Není problém mít otevřeno více oken, každé od jiného seznamu.
Použitý princip umožňuje závodění. V "ideálním případě" se totiž může stát, že výše popsaný test selže a tedy bude okno seznamu otevřeno vícekrát. Jelikož je vysoce nepravděpodobné, že se toto uživateli podaří (kliknout ve více oknech současně na otevření okna se seznamem, a ještě mít stěstí, že operační systém naschedulujuje oba procesy ve "vhodném" pořadí), tak by vynaložené úsilí na implementaci precisního zamykání bylo neadekvátní k výsledku. Proto se zvolila jednodušší varianta.
Obrázky jsou v img.c a imgcache.c, gif.c, xbm.c, png.c, jpeg.c a tiff.c . Rozhraní je následující:
image->name=stracpy(im->name); image->src=stracpy(im->url);Pokud je insert_flag v image_description nenulový, musí insert_image ještě obrázek vložit do seznamu obrázků ve f_data:
add_to_list(current_f_data->images,&image->image_list);. Jinak musí provést:
image->image_list.prev=NULL; image->image_list.next=NULL;
insert_image může zavolat af=request_additional_file(). Pokud se dá zjistit rozměr obrázku z již stažených dat ze sítě, musí ho zjistit a podle toho nastavit xw a yw. Pokud nedá (a hrozí že se později rozměr změní), musí nastavit af->need_reparse na 1. need_reparse se smí jen nastavit na 1 a nesmí se nulovat. V tom případě se při zavolání img_draw_image obrázek nemusí nakreslit korektně - může být velký a uřízlý nebo malý a doplněný pozadím. Stejně se to má chovat, když se obrázek reloadne a bude mít pak jiné rozměry. img_draw nesmí měnit xw a yw. xw a yw nastavuje insert_image na začátku při vytvoření struct g_object_image.
Jestli se stáhlo něco nového tak se to musí zpracovat před nakreslením obrázku.
Funkce nebude nikterak sahat na rozměry g_object_image. Pokud obrázek na daném URL bude menší, doplní se v g_object_image pozadím. Pokud bude větší, v g_object_image bude výřez od levého horního rohu. Funkce bude volána ze select smyčky. Z funkce se musí zavolat request_additional_file a refresh_image, které zajistí, že se obrázek bude automaticky periodicky překreslovat během natahování.
if (goi->name)mem_free(goi->name); if (goi->src)mem_free(goi->src); release_image_map(goi->map); del_from_list(&goi->image_list);a musí zcela vylikvidovat struct g_object_image *goi a zavolat mem_free(goi).
Struct cached_image může být v rozličných roztodivných stavech. Hlavní stavová proměnná je state. Proměnná state může mít hodnotu 0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 14, nebo 15. Představuje stavy ve kterých se nacacheovaný obrázek nachází a zhruba znamená toto:
Stav | Ví se typ souboru? | Hlavička obrázku (určující width a height) byla přečtena | wanted_xw i wanted_yw urceno, obojí současně | Obrazový proud skončil - buď skončilo spojení nebo dekodér řekl, že už dál dekódovat nebude, ať už proto, že obrázek skončil, nebo že se v něm narazilo na chybu) | img_draw_image nareslí (a uživatel uvidí) | bitmapa | Obrazová data (buffer, rows_added) | Obrazové informace (width, height, buffer_bytes_per_pixel, red_gamma, green_gamma, blue_gamma) | dregs | Odkud se bere xww a yww | decoder, last_length | image_type | gamma_stamp | need_reparse | gamma_table | Stav |
0 | 0 | 0 | 0 | 0 | Rám | Neplatné | Neplatné | Neplatné | Neplatné | xww= wanted_xw< 0? scale(32): wanted_xw; yww= wanted_yw< 0? scale(32): wanted_yw) | Neplatné | Neplatné | Neplatí | 1 | Neplatí | 0 |
1 | 0 | 0 | 0 | 1 | Rozbitý rám | Neplatné | Neplatné | Neplatné | Neplatné | xww= wanted_xw< 0? scale(32): wanted_xw; yww= wanted_yw< 0? scale(32): wanted_yw | Neplatné | Neplatné | Neplatí | 0 | Neplatí | 1 |
2 | 0 | 0 | 1 | 0 | Rám | Neplatné | Neplatné | Neplatné | Neplatné | xww=wanted_xw, yww=wanted_yw | Neplatné | Neplatné | Neplatí | 0 | Neplatí | 2 |
3 | 0 | 0 | 1 | 1 | Rozbitý rám | Neplatné | Neplatné | Neplatné | Neplatné | xww=wanted_xw, yww=wanted_yw | Neplatné | Neplatné | Neplatí | 0 | Neplatí | 3 |
4 | 0 | 1 | 0 | 0 | Tohle jsou nesmyslné stavy které nikdy nesmějí nastat. | 4 | ||||||||||
5 | 0 | 1 | 0 | 1 | 5 | |||||||||||
6 | 0 | 1 | 1 | 0 | 6 | |||||||||||
7 | 0 | 1 | 1 | 1 | 7 | |||||||||||
8 | 1 | 0 | 0 | 0 | Rám | Neplatné | Neplatné | Neplatné | Neplatné | xww= wanted_xw< 0? scale(32): wanted_xw; yww= wanted_yw< 0? scale(32): wanted_yw | Běží | Platný | Neplatí | 1 | Neplatí | 8 |
9 | 1 | 0 | 0 | 1 | Rozbitý rám | Neplatné | Neplatné | Neplatné | Neplatné | xww= wanted_xw< 0? scale(32): wanted_xw; yww= wanted_yw< 0? scale(32): wanted_yw | Neplatné | Neplatné | Neplatí | 0 | Neplatí | 9 |
10 | 1 | 0 | 1 | 0 | Rám | Neplatné | Neplatné | Neplatné | Neplatné | xww=wanted_xw, yww=wanted_yw | Běží | Platný | Neplatí | 0 | Neplatí | 10 |
11 | 1 | 0 | 1 | 1 | Rozbitý rám | Neplatné | Neplatné | Neplatné | Neplatné | xww=wanted_xw, yww=wanted_yw | Neplatné | Neplatné | Neplatí | 0 | Neplatí | 11 |
12 | 1 | 1 | 0 | 0 | Obrázek | bmp->user? obrázek: nic | strip_optimized ? neplatí : platí | Platí | strip_optimized ? platí nebo NULL: neplatí | Pokud (wanted_xw< 0&& wanted_yw< 0), tak xww=scale(width) a yww=scale(height). Pokud (wanted_xw>= 0&& wanted_yw< 0), pak xww=wanted_xw a yww= (xww* height+ (width>> 1))/ width. Pokud (wanted_yw>= 0&& wanted_xw< 0), pak yww=wanted_yw a xww= (yww* width+ (height>> 1))/ height. | Běží | Platný | Platí | 0 | NULL nebo naalokována na umocnění na user_gamma/cimg->*_cimg_gamma | 12 |
13 | 1 | 1 | 0 | 1 | Obrázek | Obrázek | Neplatné | Neplatné | Neplatné | Pokud (wanted_xw< 0&& wanted_yw< 0), tak xww=scale(width) a yww=scale(height). Pokud (wanted_xw>= 0&& wanted_yw< 0), pak xww=wanted_xw a yww= (xww* height+ (width>> 1))/ width. Pokud (wanted_yw>= 0&& wanted_xw< 0), pak yww=wanted_yw a xww= (yww* width+ (height>> 1))/ height. width a height jsou rozměry které měl obrázek při dekódování, nikoliv již aktuální stav položky v cimg (tam už můžou být odpadky) | Neplatné | Neplatné | Platí | 0 | Neplatí | 13 |
14 | 1 | 1 | 1 | 0 | Obrázek | bmp->user? obrázek: nic | strip_optimized ? neplatí : platí | Platí | strip_optimized ? platí nebo NULL : neplatí | xww=wanted_xw, yww=wanted_yw | Běží | Platný | Platí | 0 | NULL nebo naalokována na umocnění na user_gamma/cimg->*_cimg_gamma | 14 |
15 | 1 | 1 | 1 | 1 | Obrázek | Obrázek | Neplatné | Neplatné | Neplatné | xww=wanted_xw, yww=wanted_yw | Neplatné | Platí | Platí | 0 | Neplatí | 15 |
Uvedené hodnoty jsou zaručeny pouze mimo funkce obrázků. Funkce obrázků mohou být v různých polovičatých stavech a položky používat na různé pomocné úkony takže tam to zaručeno není.
scale(něco) může představovat hodnotu odpovídající staršímu nastavení global_fdatac->ses->ds.image_scale. Což ovšem nevadí, protože jak si jednou obrázek "nadiktuje" svoje rozměry, tak se tyto rozměry předají při návratu insert_image sázeči, a tomu je jedno, co v nich je.
Následující tabulka říká, při jakých příležitostech dochází k přechodům mezi jednotlivými stavy:
Stav po přechodu | |||||||||||||
0 | 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ||
Stav před přechodem | 0 | Nic | Soubor je celý stažen | Je znam typ souboru | |||||||||
1 | count2 se změnil | Nic | |||||||||||
2 | Nic | Soubor skončil | Je znám typ souboru | ||||||||||
3 | count2 se změnil | Nic | |||||||||||
8 | count2 se změnil | Nic | Soubor skončil | Přečetla se hlavička | |||||||||
9 | count2 se změnil | Nic | |||||||||||
10 | count2 se změnil | Nic | Soubor skončil | Přečetla se hlavička | |||||||||
11 | zvětšil se count2 | Nic | |||||||||||
12 | count2 se zvětšil | Nic | Skončil soubor | ||||||||||
13 | count2 se zvětšil | Nic | |||||||||||
14 | count2 se zvětšil | Nic | Soubor skončil | ||||||||||
15 | count2 se zvětšil | Nic |
wanted_xw, wanted_yw Jsou požadované rozměry obrázku vyjádřené v pixelech displaye. Jsou to tedy hodnoty z tagů width a height naškálované na img->scale. Pokud tag nebyl specifikován, je v příslušné proměnné -1. ani wanted_xw ani wanted_yw nesmí nabývat hodnoty 0.
scale je škálování pro které položka z cache platí. Při hledání položek v cachi se ignoruje v případě, že wanted_x a wanted_y jsou obě specifikované (aby se mohly ztotožnit obrázky které se neliší ve výsledné velikosti).
xww a yww jsou výsledný rozměr obrázku podle aktuálních vědomostí, vyjádřený v pixelech displaye. V případě, že se nějaký rozměr nedá nijak určit, dá se tam provizorně scale(32). xww i yww musí být >=1.
image_type pokud platí, definuje, o jaký druh obrázku se jedná z hlediska Content-Type. Zjištění typu, pro který není do Linksu vestavěn dekodér, se považuje za chybu v kódovém proudu a interpretuje se, jako kdyby soubor skončil, proto v proměnné image_type je v případě platnosti vždy hodnota poukazující na konkrétní typ toku obrazového kódu. Pokud se identifikuje nějaký známý typ, do image_type se přiřadí jedna z konstant IM_PNG, IM_MNG, IM_JPG, IM_PCX, IM_BMP, IM_TIF, IM_GIF.
rows_added když platí a je nula tak to znamená, že bitmapa zobrazuje přesně to samé, co je v bufferu a také to znamená, že bitmapa je platná. Pokud rows_added platí a je 1, pak to znamená, že do bufferu bylo zapsáno a nebyla updatována bitmapa, tudíž bitmapu nelze kreslit, neboť by zobrazovala starý stav, a tudíž je nutno před kreslením bitmapy nebo likvidací bufferu nutno buffer předělat do bitmapy. platné rows_added a rows_added==1 neznamená nutně, že bitmapa je neplatná. Když je rows_added platné a bitmapa neplatná, je vždy nastaveno rows_added na 1.
buffer má paměťovou organizaci pro 8-bitové kanály podle následující tabulky. R, G, B jsou barevné složky, A je alpha. Složky jsou uloženy v charech.
buffer_bytes_per_pixel | Obsah paměti (po charech) |
3 | RGB |
4 | RGBA |
3*sizeof(unsigned short) | RGB |
4*sizeof(unsigned short) | RGBA |
strip_optimized signalizuje, že se nepoužívá buffer, a namísto toho se dekódovaný proužek rovnou ditheruje (v restartovatelné ditherovací engině s pomocnou chybovou řádkou dregs). V takovém případě buffer ani rows_added není platné. Ostatní opbrazové informace platné jsou protože jsou k ditherování potřeba. Hodnota této proměnné se rozhoduje konkrétní dekódovací funkcí určitého formátu (JPEG, PNG, TIFF, GIF, XBM) před zavoláním header_dimensions_known(). Pokud je strip_optimized nastaveno když se obrázek bude škálovat a je zavoláno header_dimensions_known(), header_dimwnsions_known() stirp_optimized automaticky shodí, protože to nejde aby se kreslilo po kusech a současně škálovalo.
width a height určují (v případě, že jsou platné, tedy spolu s bufferem) rozměry bufferu. Musí platit: width>=1, height>=1.
"dregs jsou NULL nebo platné" znamená, že v okamžiku, kdy se tato položka "vyplňuje", tak když je nastaveno dither_images, tak se naalokuje, jinak se dá na NULL.
dregs je chybový radek z minuleho behu ditherovaci enginy. Ma smysl jen ve stavech 12 a 14 a to jeste, kdyz je zapnuto strip_optimized. Tento zpusob ditherovani se da pouzit jedine kdyz obrazek neni prokladany, takze se kresli zezhora dolu v jednom kuse, a soucasne kdyz se obrazek nezvetsuje ani nezmensuje (na to se musi totiz cely dekodovat a pak naraz zmensit nebo zvetsit).
gamma_table se vyskytuje jen když obrázek má dost pixelů (vyplatí se) a když nemá 16 bitů na složku (pak by tabulka byla moc velká). Mohla by se udělat pro 16-bitové obrázky kdyby měl obrázek víc nebo rovno než řekněme 131072 pixelů, ale 16-bitové obrázky jsou poměrně vzácné. Navíc by zabírala v paměti asi 300kB. gamma_table má paměťovou organizaci 3*256 unsigned shortů. Prvních 256 je pro červenou, další pro zelenou, a posledních 256 pro modrou složku. Může přítomna jen ve stavech 12 a 14 a v ostatních je odalokovaná. Když není ve stavech 12 a 14 přítomna, je na jejím místě NULL, aby se to poznalo.
Pomocná položka bmp->user se zde používá jako indikátor, zda bitmapa tam je nebo není. Pokud je bmp platná a user je NULL, pak zbytek struktury bmp není platný a nic není alokováno a bmp se bere jako prázdná. Pokud je bmp platná a user není NULL, pak zbytek struktury bmp je platný a bitmapa je alokována a je v ní obrázek který se může kreslit. žádný rozměr bitmapy nemůže být nula. To je ošetřeno již na začátku funkcí insert_image a img_draw, že se hned vrací a nic se nedělá, takže se to sem vůbec nedostane.
last_length, pokud platí, říká, kde jsme naposled skončili při lití dat do dekodéru. Z této definice je již jasné, že je platný pouze ve stavech, kdy dekodér běží, a že se nastavuje na nulu při startu dekodéru.
last_count2 je platný vždy. Slouží ke zjištění, zda se count2 změnilo (což by naznačovalo reload).
Změní-li se gamma, zvětší se globální proměnná gamma_stamp a zavolá se na všechny obrázky redraw. Ten zjistí, že se změnila globální proměnná gamma_stamp, a v případě že cimg->gamma_stamp je platná, otestuje tyto dvě proměnné proti sobě a zachová se, jako by se změnil count2 (úplný reload, protože se zcela změní vzhled obrázku).
Změní-li se škálování (v global_fdatac->ses->ds.image_scale), pak se přeparsuje a přesází celý dokument, takže se změní i příslušné požadované rozměry, a v cachi se začne hledat něco jiného.
need_reparse znamená, že kromě případu reloadu se nemůže změnit rozměr místa, který obrázek zabírá.
Provádí se v dither.c a dip.c. dither.c je nutno nejdříve nainicializovat voláním init_dither(), čímž se vygenerují ditherovací tabulky. Ditherovací tabulky se jmenují red_table, green_table a blue_table. Každá z nich má 65536 položek typu int, protože vstup do ditherovače má 16 bitů na barevný kanál. Celkově tedy tabulky zabírají v paměti prohlížeče 0.75MB při velikosti intu 4 byty. Teoreticky by se mohly samotné tabulky udělat např. 12-bitové (zabíraly by pak jen 49kB) a ditherování jako takové nechat 16-bitové, ale není to kritické a raději s tím počkáme, než abychom to zabugovali. Jediný problém zde činí fakt, že tabulky se počítají v plovoucí čárce a staré procesory i486 bez matematické emulace se při startu prohlížeče v grafickém režimu skutečně zapotí.
Vstup do ditherovací tabulky je 16-bitové číslo 0-65535 značící lineární hodnotu světla, kterou se snažíme reprezentovat. Tabulka nám pak sdělí, jaký kód máme vyplodit na výstup (do bitmapy grafického driveru) a jaký skutečný světelný tok vyplozenému kódu odpovídá. Skutečnou hodnotu odečteme od požadované a získáme chybu, kterou rozdistribuujeme do pixelů, které budou zpracovávány později. 7/16 půjde do pixelu vpravou, 1/16 do pixelu vpravo dole, 5/16 do pixelu dole a 3/16 do pixelu vlevo dole.
Tato metoda se nazývá jednosměrný Floyd-Steinberg a byla vybrána na základě testovacích obrázků v přednášce Počítačová Grafika I. Josefa Pelikána. Dělá se samozřejmě v prostoru přímo úměrném světlu vycházejícímu z monitoru, protože jedině v takovém prostoru dává smysl a optimální výsledky. Proto je také nutno prohlížeč kalibraovat, aby ditherování a jiné operace pracovaly s fyzikálně a fyziologicky podloženými čísly a nikoliv s brambory a švestkami.
Jiný možný pohled na použitou metodu je jako na sigma-delta modulaci. Ditherovací algoritmus funguje jako sigma-delta modulátor a posouvá kvantizační šum do vyšších frekvencí, na které je optická soustava méně citlivá (díky rozmazanosti monitoru a zobrazovacím vadám lidského oka) čímž se subjektivně zlepšuje dojem z obrazu.
Kromě ditherovacích tabulek jsou v prohlížeči ješte zaokrouhlovací tabulky round_red_table, round_green_table a round_blue_table. Tyto mají každá 256 položek typu unsigned short, takže zabírají celkem 1536 bytů. Vstupem je sRGB hodnota 0-255 a výstupem je 16-bitová lineární světelná hodnota odpovídající barvě, která se zobrazí, nastaví-li se tato hodnota jako HTML pozadí. Vzhledem k tomu, že pozadí se nikdy neditheruje, jsou tyto tabulky potřeba také na to, aby se pozadí u transparentních obrázků vyplnilo barvou, která bude korespondovat s pozadím stránky. Pozadí stránky se totiž zaokrouhlí na nejbližší barvu, která je zobrazitelná bez ditherování.
Ditherování obrazu se dělá voláním funkce dither() a zaokrouhlování se dělá funkcí (*round_fn). Jedna z těchto dvou možností se provádí podle toho, jestli si uživatel zapnul nebo vypnul ditherování obrázku a písmenek. Barva pozadí se grafickému driveru nastaví podle toho, co vrátí dip_get_color_sRGB (ten vrací už přímo barvu pro driver). Je to ještě cachované, skutečný výpočet dělá real_get_color_sRGB.
Popřeďové barvy písmenek se nezaokrouhlují, pouze pokud jsou příliš podobné pozadí, vygeneruje se kontrastní barva a dosadí se do popředí, aby to bylo čitelné. To dělá funkce separate_fg_bg. Pozadí písmenek se stejně jako pozadí obrázků zaokrouhluje, aby sedělo s pozadím stránky.
Ditherovací tabulky se vypočítají ve funkci make_16_table. Buňka ditherovací tabulky obsahuje číslo typu int (předpokládáme, že int podrží aspoň 32 bitů). 16 horních bitů obsahuje kód pro grafický driver, který se pošle do bitmapy, 16 dolních číslo 0-65535 popisující přesně světlo, které vychází z monitoru při zobrazení tohoto kódu.
Pokud paměťová organizace obrazovky má 8 bitů na barevný kanál, pak je těcho 8 bitů uloženo v bitech 16-23 položky ditherovací tabulky. Pokud paměťová organizace má 16 bitů na pixel (a to ať už 15 nebo 16 významných), pak v případě, že existuje datový typ t2c 2-charový datový typ), je v bitech 16-31 uložen obsah pixelu tak, že se vezme pixel (s nastavenými jen těmi barevnými bity, od kterého barevného kanálu je to tabulka), načte se do t2c, a toto se uloží do bitů 16-31 ditherovací tabulky. Pokud zabírá pixel 2 byty a není definován t2c, je v bitech 16-23 uložen obsah bajtu paměti s nižší adresou a 24-31 bajtu s vyšší adresou. Pokud je 1 byte na pixel, pak je tento byte (s nastavenými jen těmi bity, pro který kanál je ta ditherovací tabulka) uložen v bitech 16-23 položky ditherovací tabulky.
Pokud tato podmínka není splněna dosáhne se jí tak, že se nainstaluje pkg-config.