Obsah

Struktura adresáře links*

Položky zakončené lomítkem jsou adresáře, ostatní položky jsou soubory.

Zdrojáky

Header soubory

Rozhraní mezi jednotlivými kusy Linkse

Rozhraní grafických driverů

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
Jméno driveru.
unsigned char *(*init_driver)(unsigned char *param)
Inicializuje driver. Vrátí NULL při úspěchu nebo alokovaný řetězec obsahující popis chyby při neúspěchu. Na začátku programu se může zavolat init_driver, a na konci se pak musí shutdown_driver. Po shutdown_driver může následovat další cyklus. Při přechodu do dalšího cyklu musí být všechny devicy seshutdownované.
struct graphics_device *(*init_device)(void)
Vytvoří nové zařízení. Může se zavolat víckrat init_device, ale pak se pro každé device musí zavolat shutdown_device dříve, než se zavolá shutdown_driver.
void (*shutdown_device)(struct graphics_device *dev)
Ukončí zařízení.
void (*shutdown_driver)(void)
Ukončí činnost celého driveru. Předpokládá se, že při volání této funkce jsou už všechna zařízení ukončena a v driveru nejsou registrovány žádné bitmapy ani barvy.
unsigned char *(*get_driver_param)(void)
Vrací poitner na řetězec s parametrem funkce init_driver nebo NULL (funkce init_driver by se měla příště zavolat s tímto parametrem, pokud uživatel neřekne jinak). Používá se k zapamatování grafického módu, velikosti okna a podobně.
int (*get_empty_bitmap)(struct bitmap *dest)
Před voláním této funkce jsou vyplněny položky x a y v struct bitmap. Obě musí být >0. V ostatních jsou odpadky. Funkce alokuje místo na bitmapu, nastaví hodnoty skip a data, ponechá hodnotu user, může nastavit flags. Můžeme předpokládat, že po volání get_empty_bitmap bude zavoláno register_bitmap. Po get_empty_bitmap uživatel nesmí šahat na x, y, skip, data, flags. Do user může uživatel hrabat jak chce - je to jeho.

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)
Před voláním této funkce jsou vyplněny položky x a y v struct bitmap. Obě musí být >0. V ostatních jsou odpadky. Funkce alokuje místo na bitmapu, nastaví hodnoty skip a data, ponechá hodnotu user, může nastavit flags. Bitmapa bude od výroby už zaregistrovaná a bude vyplněna vzorkem pattern. Patern ukazuje na pole o velikosti n_bytes, bitmapa bude temito byty vyplnena. Muze se (snad) predpokladat, ze n_bytes je stejna hodnota, jako nastavi graficky driver pro pocet bitu v pixelu. Bitmapa bude už zaregistrová, takže dovolené operace na ní jsou: prepare_strip, unregister_bitmap, draw_bitmap, draw_bitmaps.

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)
Registruje vyplněnou bitmapu. Může (ale nemusí) přenést data bitmapy do videoram a odalokovat je. Čili po register_bitmap je už pointer ve struct bitmap neplatný!
void *(*prepare_strip)(struct bitmap *bmp, int top, int lines)
Připraví bitmapu na zápis vodorovného pruhu co je přes celou šířku bitmapy. bmp musí být zaregistrovaná bitmapa. top je první řádek co se bude měnit. length je počet měněnych řádků, musí být >0, jinak to spadne na segfault. Pruh nesmí být mimo bitmapu ani trčet nahoře ani dole z bitmapy jinak to má nárok spadnout na segfault. Pointer co tahle funkce vrátí je pointer na který se má začít zapisovat data a při zápisu celého řádku se skočí o bmp->skip. Po zavolání 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)
Commitne změny do bitmapy. bmp musí být připravena pomocí prepare_strip() a top a lines musí být stejné jako v prepare_strip() jináč to má nárok spadnout. Po commitnutí se už do bitmapy zase nesmí hrabat.
void (*unregister_bitmap)(struct bitmap *bmp)
Uvolní bitmapu. Nesahá na bmp->x a bmp->y.
void (*draw_bitmap)(struct graphics_device *dev, struct bitmap *hndl, int x, int y)
Nakreslí bitmapu na dané zařízení na danou pozici.
void (*draw_bitmaps)(struct graphics_device *dev, struct bitmap **hndls, int n, int x, int y)
Nakreslí několik bitmap za sebe. Všechny bitmapy musejí mít stejnou výšku. Funkce je tu proto, aby se snížil overhead při volání draw_bitmap.
long (*get_color)(int rgb)
Alokuje barvu. Parametr je ve tvaru R*65536+G*256+B, kde R, G, B jsou čísla 0 az 255. Číslo 0 reprezentuje 0 (žádné elektrony do monitoru), Číslo 255 reprezentuje maximum elektronů do monitoru, co se dá vytřískat z videokarty. Vrátí handle barvy. Handle je specifický pro daný driver. Handle může být předáván funkcím pro kreslení čar a plnění nebo uvolněn pomocí následující funkce. Každý handle musí být uvolněn před ukončením driveru pomocí free_color.
void (*free_color)(long color)
Uvolní barvu.
void (*fill_area)(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
Vyplní daný obdélník danou barvou. Budou vyplněny všechny pixely o souřadnicích [x,y], které leží uvnitř ořezávací oblasti a splňují podmínku: (x>=x1)&&(x<x2)&&(y>=y1)&&(y<y2). Tedy v případě, že x1<x2 a y1<y2, x1,y1 v obdélníku bude a x2,y2 tam už nebude.
void (*draw_hline)(struct graphics_device *dev, int left, int y, int right, long color)
Nakreslí horizontální čáru. Bod left,y na ní leží, bod right,y na ní neleží. Pokud left<=right, pak je čára prázdná.
void (*draw_vline)(struct graphics_device *dev, int x, int top, int bottom, long color)
Nakreslí vertikální čáru. Bod x,top na ní leží, bod x,bottom na ní neleží. Pokud je top>=bottom, pak je čára prázdná.
int (*hscroll)(struct graphics_device *dev, struct rect_set **set, int sc)
Scroll. Posune aktuální ořezávanou oblast o sc pixelu doprava (eventuálně doleva, pokud je sc záporné). Oblast odkryta scrollováním je nedefinovaná. Návratová hodnota:
0 - program nemusí překreslovat odkrytou oblast, bude zavolána funkce redraw (viz níže).
1 - program by měl překreslit odkrytou oblast.
Typicky by se hodnota 1 měla vracet na ne-okenních systémech (svgalib, framebuffer, dos), kde máme jistotu, že po překreslení odkrytého obdélníku bude obrazovka konzistentní. Na druhou stranu v okenních systémech (X, OS/2) může být okno, které scrollujeme, překryto jiným oknem a program neví, které části má překreslit. Musí mu být tedy grafickým driverem zaslán požadavek redraw.

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)
Totéž jako hscroll, ale scrolluje oblast nahoru (když je sc záporné) nebo dolů.
void (*set_clip_area)(struct graphics_device *dev, struct rect *r)
Nastaví oblast pro ořezávání. Veškeré funkce budou pracovat pouze na této oblasti. struct rect má položky x1, x2, y1, y2, což jsou souřadnice oblasti. x1,y1 patří do clip_area, x2,y2 tam již nepatří.
int (*block)(struct graphics_device *dev)
Vrati puvodni textovy videomod, vrati handlovani mysi a klavesnice a zajisti, ze kreslici funkce nebudou uz nic kreslit (to jde zajistit celkem jednoduse -- mas tam uz makro TEST_INACTIVITY a promennou current_virtual_device).

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)
Obnovi zpet graficky mod, klavesnici a mys, a nakonec zavola redraw_handler -- cili prekresleni cele obrazovky. Musi se brat v potaz, ze graphics_device, na kretem se ten externi program pustil (a tedy to, co dostanes jako parametr) muze byt jiny, nez aktualni grapgics_device. Takze budes prekreslovat radsi current_virtual_device, pokud je nenulovy.
int depth
Barevná hloubka:
bity 0-2 - počet bytů na pixel. Pixely chodí do bitmapového zobrazovače tak, že každý pixel okupuje celý počet bytů, a žádný byte není okupován dvěma pixely. Proto např. 1 bit na pixel se nekóduje jako 8 pixelů na byte, ale každý byte obsahuje jeden pixel a tudíž 7 bitů v byte je pak nevyužitých. V případě, že v obrazové paměti jsou pixely v bytech nějak úchylně nasekány, musí je zobrazovač překódovávat. bity 3-7 - počet bitů na pixel - 1, 4, 8, 15, 16, 24
bit 8 - misordered - to je flag ve svgalibě, který říká, že karta místo layoutu RGBRGBRGB má BGRBGRBGR, a některé ostatní layouty mohou být také podobně otočeny. Interní informace grafického driveru, kterou používá pro správnou konstrukci barevných dat z r,g,b. bit 9 - misordered2. Pro 4 byty na pixel jsou 3 různé možné paměťové organizace. Tento bit je zde proto, že jen bit 8 by to nemohl rozlišit.
Zde je definice paměťové organizace pro jednotlivé podporované depth. depth, které nejsou v tabulce, jsou nepodoporované. Všechny depth v tabulce jsou podporované dither.c. V tabulce pokud například červená má barevnou hloubku 5, pak R0 je nejméně významný bit a R4 nejvíce významný bit červeného kanálu.

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
RGB 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
330x21121 R0G1G0B0
650x41332 R2R1R0G2 G1G0B1B0
1220x7A555 G2G1G0B4 B3B2B1B0 R4R3R2 R1R0G4G3
1300x82565 G2G1G0B4 B3B2B1B0 R4R3R2R1 R0G5G4G3
1950xc3888 B7B6B5B4 B3B2B1B0 G7G6G5G4 G3G2G1G0 R7R6R5R4 R3R2R1R0
1960xc4888 B7B6B5B4 B3B2B1B0 G7G6G5G4 G3G2G1G0 R7R6R5R4 R3R2R1R0 0000 0000
4510x1c3888 R7R6R5R4 R3R2R1R0 G7G6G5G4 G3G2G1G0 B7B6B5B4 B3B2B1B0
4520x1c4888 0000 0000 B7B6B5B4 B3B2B1B0 G7G6G5G4 G3G2G1G0 R7R6R5R4 R3R2R1R0
7080x2c4888 0000 0000
R7R6R5R4 R3R2R1R0 G7G6G5G4 G3G2G1G0 B7B6B5B4 B3B2B1B0
int x, y
Velikost obrazovky - pouze pro drivery, které používají virtuální zařízení. Protože se u okenního systému může stát, že každé okno bude jinak veliké, neboť uživatel může okénka resizovat.
int flags
Flagy, které nastaví grafický driver v init_driver. Vzniknou zORováním některých z následujících konstant:
int codepage
Kódová stránka klávesnice.

Struktura graphics_device má následující položky:

struct rect size
Velikost zařízení. size.x1 == 0, size.y1 == 0, size.x2 a size.y2 obsahují aktuální velikost okna.
struct rect clip
Aktuální ořezávací oblast. Program může tuto položku číst, ale nesmí tam zapisovat. Změna ořezávací oblasti se dělá přes set_clip_area.
struct graphics_driver *drv
Driver, ke kterému zařízení náleží.
void *driver_data
Data soukromá pro grafický driver. Program by na to neměl sahat.
void *user_data
Data soukromá pro program. Grafický driver by na to neměl sahat.
void (*redraw_handler)(struct graphics_device *dev, struct rect *r)
Sem si program uloží pointer na funkci, kterou driver zavolá, když bude potřeba překreslit část obrazovky. redraw se nevolá při inicializaci okna. Jestli se redraw volá při přepnutí grafické konzole, to nevím, to ví Mikuláš.
void (*resize_handler)(struct graphics_device *dev)
Sem program uloží funkci, kterou driver zavolá, když je změněna velikost zařízení.
void (*keyboard_handler)(struct graphics_device *dev, int key, int flags)
Sem program uloží funkci, kterou driver zavolá, když je zmáčknuta klávesa. Parametr key je konstanta typu KBD_xxx nebo UNICODE kód klávesy. KBD_CTRL_C se posílá při stisknutí ctrl+c v programu, ne však při zavření okna v okenním systému (windowmanagerem). Při KBD_CTRL_C se může objevit dialog, jestli chce uživatel opravdu končit. KBD_CLOSE je poslán při požadavku o zavření okna (z windowmanageru), program se ukončí bez dalšího ptaní lusera. flags obsahuje zORované následující konstanty: KBD_SHIFT, KBD_CTRL, KBD_ALT. KBD_SHIFT se posilá pouze pro speciální klávesy (enter, šipky ...), ne pro písmena nebo ascii znaky.
void (*mouse_handler)(struct graphics_device *dev, int x, int y, int buttons)
Sem program uloží funkci, kterou driver zavolá, když je pohnuto s myší. buttons obsahuje zakódovaná tlačítka. Tlačítka se kódují ORováním jedné konstanty z každé následující skupiny. B_LEFT, B_MIDDLE, B_RIGHT, B_WHEELUP, B_WHEELDOWN, B_WHEELUP1, B_WHEELDOWN1 - tlačítka. U kolečka se posílá vždy akce B_MOVE. B_WHEELUP1 a B_WHEELDOWN1 znamená posunutí o 1 řádek (16 pixelů), používá se na OS/2, kde lze nastavit počet eventů na otočení kolečka. B_WHEELUP a B_WHEELDOWN znamená scroll kolečkem o více řádků (64 pixelů), používá se v X a SVGAlib. B_DOWN, B_UP, B_DRAG, B_MOVE - akce: tlačítko bylo zmáčknuto; tlačítko bylo puštěno; tlačítko je zmáčklé a pohnula se myš; myš se pohybuje (v tomto případě se kód tlačítka ignoruje), . Pro přístup k jednotlivým skupinám slouží masky BM_BUTT a BM_ACT. Jestliže je zmáčknuto více tlačítek, posílají se sekvenčně v pořadí v jakém přišly driveru eventy, v případě B_DRAG platí vždy poslední zmáčklé tlačítko.

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.

Rozhraní virtuálních deviců

Virtuální devicy jsou metoda, jak na jednu obrazovku fyzickou umístit několik obrazovek virtuálních přepínaných pomocí nějakých kláves (na svgalib je to Alt-1 až Alt-0). Používají se u neokenních systémů (svgalib), protože uživatel typicky chce browsit v několika oknech současně. Tvůrce grafického driveru nemusí virtuální devicy používat, ale pak si bude muset funkce pro správu deviců napsat sám.
int init_virtual_devices(struct graphics_driver *drv, int n)
Zavolá se typicky z init_driver. Naalokuje n virtuálních zařízení. Návratová hodnota: 0 - OK.
void shutdown_virtual_devices()
Zavolá se z shutdown_driver. Odalokuje paměť pro virtuálni devicy
struct graphics_device *init_virtual_device()
Dá se jako funkce init_device do struktury graphics_driver.
void shutdown_virtual_device(struct graphics_device *dev)
Dá se jako funkce shutdown_device do struktury graphics_driver.
void switch_virtual_device(int i)
Přepne virtuální device na device s číslem i. Nastaví current_virtual_device a pak tomuto devicu pošle redraw. Typicky se tato funkce volá z handleru klávesnice grafickeho driveru. Pokud driver zjistí, že stisknutá klávesa má přepnout virtual device, klávesu nepředává dál programu, ale zavolá switch_virtual_device.
struct graphics_device *current_virtual_device
Proměnná ukazuje na virtual device, který je aktuálně zvolený (může být i NULL). V každé funkci grafického driveru, která něco kreslí, by se mělo zjistit, zda device, na který se má kreslit == current_virtual_device, a pouze v takovém případě kreslení provést. Jinak se budou do obrazu prolínat kusy grafiky z neviditelných deviců, což může, ale nemusí působit esteticky.

Formáty obrazových dat

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).

Pomocné programy a soubory

arrow

Tento program vyrábí z arrow.png (který je ve formátu PNG, kde barvy číslo 0,1,2 udávají šipečku, okolí a průhlednost) do arrow.inc, který obsahuje řady 32-bitových čísel, pro šipečku i pro její průhlednostní masku. Šířka šipečky musí být 32 pixelů.

calibrate

Tento program vyrábí černě až bíle vybarvenou obrazovku, v 256 stupních. Pracuje v truecolor režimu 640x480x16M. Používá se k měření gamma monitoru pomocí měřiče světla (hardwarového to zařízení).

colour.html

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.

clip

Ořeže boxy z pngček - písmenek pro linkse. Volá pomocný program improcess.

generate_font

Vezme soubory v adresáři font a vyrobí z nich soubor font_include.c, který se přikompiluje do linkse a obsahuje interní strukturu obsahující font ve formátu PNG. Tato data jsou tím pádem uložena ve spustitelném souboru links a uživatel není obtěžován žádnými dodatečnými datovými adresáři, čímž se silně zvyšuje přenositelnost celého systému (nebylo jasné, kam by se měly tyto soubory umisťovat, neboť takováto místa vhodná pro umisťování těchto souborů jsou silně závislá na konkrétním typu systému a vznikaly by s tím jen problémy). Fonty se do systému přidávají tak, že se přidá do adresáře font, pak se pustí generate_font a pak se překompiluje links příkazem make.

genps

Je určen k výrobě linksových fontů z fontů v Ghostscriptu. Vyrobí soubor letters.ps, který obsahuje všechna písmena od 0 do 255 tohoto fontu, přičemž kolem nich jsou boxy pro správné oříznutí. Tento program není určen pro ruční spouštění.

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:

genps specifying image

paper_height musí odpovídat výšce papíru, která je nastavena v pdf2html, tedy A4 (není důvod to měnit), což je 297. rozměrem těchto všech proměnných je 1 milimetr.

improcess

Program improcess představuje nástroj pro základní manipulace s černobílými obrázky PNG. Program nahraje obrázek, provádí na něm unární operace (není tedy možno kombinovat dva obrázky do sebe) a nakonec jej uloží do souboru. syntaxe je improcess infile cmdfile outfile, kde infile je původní obrázek, outfile nový a cmdfile soubor s příkazy. infile může být totožné s outfile, k destrukci dat při tom nedojde. cmdfile obsahuje řádky, kde každý řádek definuje jeden příkaz. Příkazy jsou tvaru příkaz argument argument ..., kde argument může být pouze znaménkové decimální nebo hexadecimální (s 0x prefixem) číslo. Šedé tóny jsou reprezentovány čísly typu int, kde 0 je černá, 0xffffff bílá a při operacích je možno vyjet až do rozsahu -0x80000000 až 0x7fffffff, přičemž dále dojde k přetečení a nedefinovanému stupni šedé. Čísla v argumentu udávající barvu se řídí také touto konvencí. Program umí následující příkazy:

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.

makefont

Je shellový skript, který vyrobí font do adresáře ./font/, přičemž typ fontu je definován v souboru Fontmap. Musí se spouštět z adresáře, ve kterém jsou zdrojáky linkse. Je určen k ručnímu spouštění. Na systému musí být nainstalován Ghostscript.

pbm2png

Vyrábí z toku dat ve formátu více zkonkatenovaných souborů pbm (což je přesně formát který teče z ghostscripta ve skriptu pdf2html) sérii pngček 17-násobným vodorovně a 15-násobným svisle převzorkováním. Není určen pro ruční spouštění.

pdf2html

Je skript, který vyrobí z *.pdf nebo *.ps sérii pngček. Provádí převzorkování 15-násobné svisle a 17-násobné vodorovně do 8-bitové stupnice šedé. Výstupní gamma je 1.0. Vystupni obrazek obsahuje chunk gAMA (informace o gamma obrazku) a tento je nastaven na 1.0. Volá program pbm2png.

purge

Smaže tohle.

wb02links

Slouží k převodu souboru font_cache.dat z wb0 na soubor pngček vhodných jako font pro Linkse. Musí se nainstalovat wb0, pak pustit wb0 -h100 nebo kolik chceme (takhle to bude vysoké 100 pixelů) a wb0 vytvoří ve svém pomocném adresáři soubor font_cache.dat.

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/.

Které soubory mazat

Když pustíte purge, smaže se všechno tohle, kromě těch *.swp.

Které soubory budou v distribuci

Externí bugy a rádobybugy

Javascript

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í

Obecně o programování Linkse

Toto by si měl přečíst každý, kdo do Linkse bude něco programovat.

C jazyk

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):

C++ komentáře
// tohle v cc neprojde
/* je potřeba používat tyto komentáře */
Label na konci bloku
fn()
{
	nějaký kód....
	label:
}
Je třeba nahradit
fn()
{
	nějaký kód....
	label:;
}
Inicializace struktur nekonstantními výrazy
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
make
Pak se to bude kompilovat ANSI C místo GNU C. Vypise to spoustu warningů, ale výsledný kód je funkční.

Přidávání zdrojáku

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".

Používání autoconfu

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:

AC_DEFINE(SYMBOL) AC_DEFINE(SYMBOL, HODNOTA)
definuje SYMBOL v config.h (např. AC_DEFINE(CHCEME_FLEXI_LIBU)). Symbol se také musí vyskytovat v souboru acconfig.h (nevím proč).
AC_TRY_LINK(includy, tělo_main, úspěch-skript, neúspěch-skript)
vyrobí program, který bude na začátku obsahovat includy, pak main(){ a tělo main a pak ukončovací }. Pokusí se program slinkovat, pokud se to povede, vykoná úspěch-skript, jinak neúspěch-skript.
AC_CACHE_CHECK(hláška, ac_cv_proměnná, skript)
pokud je ac_cv_proměnná v config.cache, tak ji nahraje z té cache; jinak vykoná skript. Předpokládá se, že skript nastaví ac_cv_proměnnou. Ta je uložena do config.cache a při dalším puštění se už skript nevyvolává.
Další makra - viz info autoconf.

Velikosti dat

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.

Alokace paměti

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.

Chyby

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, ...)
Syntax je jako u printf. Vypíše chybové hlašení. Používá se k ošetřování různých vnějších chyb, jako třeba, že došla paměť (je voláno přímo z mem_realloc). Kód pokračuje za funkcí.
void debug(unsigned char *str, ...)
Vypíše DEBUG MESSAGE at file:line: str... a zastaví běh na jednu sekundu, aby bylo možno zprávu přecíst. Syntax je jako printf. Používá se při debugování, neměla by se vyskytnout nikde ve výsledném kódu.
void internal(unsigned char *str, ...)
Pro ošetřování "can't happen" situací. Funkce vypíše hlášení INTERNAL ERROR at file:line: str..., zastaví browser a způsobí core dump. Syntax je jako printf. Pokud si je člověk jist, že nějaká podmínka má platit, ale že by platit nemusela, pokud se někde vyskytl bug, měl by tuto podmínku otestovat, a pokud neplatí zavolat internal. Čím dřív se bug zachytí, tím líp se hledá, proto by se tato funkce měla hojně používat. Funkce se může (a měla by se) vyskytovat ve výsledném hotovém kódu. Možná bysme ji mohli odstranit leda tak v té verzi k obhajobě.
void do_not_optimize_here(void *p)
Nedělá vůbec nic. Slouží jen k oblbnutí překladace, aby neprováděl optimalizace. Ano - i gcc 2.7.2.1 má bugy v optimalizaci.

Řetězce

unsigned char upcase(unsigned char chr)
Převede znak na velká písmena.
int casecmp(unsigend char *s1, unsigned char *s2, int len)
Jako memcmp, ale ignoruje velikost písmen. Je taky zaručeno (což v memcmp není!), že nebude sahat za první neshodující se byte, takže je možné použít k porovnávání začátků řetězců.
unsigned char *stracpy(unsigned char *str)
Zkopíruje řetězec do nově alokovaného místa. Výsledek je nutno po použití uvolnit pomocí mem_free.
unsigned char *memacpy(unsigned char *str, int n)
Zkopíruje n bytů ze str do nově alokované paměti, přida nulu na konec a vrátí pointer.
void add_to_strn(unsigned char **str1, unsigned char *str2)
Realokuje řetězec str1 a zkopíruje na jeho konec str2. str1 je pointer na pointer na řetězec - může se změnit při realokaci. Není to moc rychlé, lepší použít následující funkce.

Řetězce s prealokací

Následující funkce patří k sobě a mohou být používány pouze na řetězcích vytvořených pomocí init_str().

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()
Alokuje řetězec pro použití dalších funkcí. Vrácený pointer reprezentuje prázdný řetězec.
void add_to_str(unsigned char **str, int *len, unsigned char *str2)
Přidá řetězec str2 na konec řetězce str. Řetězec str musel být alokován pomocí init_str().
void add_bytes_to_str(unsigned char **str, int *len, unsigned char *str2, int len2)
Jako add_to_str, ale přidá len2 bytů z adresy str2. Byty z adresy str2 nebudou odalokovány.
void add_char_to_str(unsigned char **str, int *len, unsigned char chr)
Přidá jeden znak.
void add_num_to_str(unsigned char **str, int *len, int num)
Přidá číslo v dekadickém zápise.
void add_knum_to_str(unsigned char **str, int *len, int num)
Přidá číslo v "lidsky čitelném zápise", čili s použitím písmenek "k" (kilo) a "M" (mega). Kilo vyjadřuje 1024 a mega vyjadřuje 1048576 (což je 1024*1024). Jinak je to stejné jako add_num_to_str.
Typické použití těchto funkcí vypadá asi takto. Je to poměrne efektivní, protože funkce dělají prealokaci (exponencialni) a tak nevolají realloc při každém přidání.
{
	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.

Seznamy

Pokud nějakou strukturu chceme řetězit v seznamu, musíme zajistit, aby první dva prvky té struktury byly pointery na next a prev. Pokud budou položky next a prev na jiném místě, bude se do struktury střílet!
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)
Přidá položku do seznamu.
add_at_pos(prvek_v_seznamu, položka_seznamu)
prvek_v_seznamu je už zařazen v nějakém seznamu, makro přidá položka_seznamu do seznamu za příslušný prvek.
foreach(proměnná, hlava_seznamu)
Expanduje se jako for cyklus, který pro proměnnou projde všechny prvky seznamu. Tělo cyklu nesmí smazat aktuální prvek.
foreachback(proměnna, hlava_seznamu)
Jako foreach, ale projde seznam pozpátku.
free_list(hlava_seznamu)
Zavolá mem_free na všecky prvky seznamu a seznam vyprázdní.
int list_empty(list_head)
Vrátí 1, pokud je seznam prázdný. Jinak vrátí nulu.

Jak používat grafiku

Textový / grafický mód

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)
Vrátí hodnotu x, pokud běží browser v textovém módu, y, pokud běží v grafickém.
NO_GFX
Zavolá interní chybu, pokud běží browser v textovém módu. Lepší zavolat interní chybu, než pak spadnout při sahání na neinicializované struktury...
NO_TXT
Zavolá interní chybu, pokud běží browser v grafickém módu.

Přidávání fontů z ghostscripta

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:

Kterak postupovat jako uživatel

Přidávání fontů z tištěné předlohy

Pokud chceme přidat do prohlížeče unicode znaky, které jsou pro nás důležité, ale nemáme od nich soubor čitelný Ghostscriptem, ale pouze papírovou předlohu a scanner, stačí písmo nascannovat, nahrát do GIMPu, invertovat, oříznout tak, aby účaří bylo na 5/24 výšky (počítáno odspoda), vhodně stranově oříznout, pomocí image -> colors -> levels oříznout zašuměné bílé a černé partie (bez změny gammy, předpokládáme, že scanner jako většina scannerů generuje výstup s gammou 1.0) a uložit do souboru ????.png, kde ???? je hexadecimální kód Unicode znaku.

Jak co funguje

Zde se popisuje, jak fungují (jak jsou naprogramovány, jaké jsou invarianty, jakými principy se řídí) různé činnosti linkse.

Fonty

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í.

Gamma

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
0lx1.333333333333
15lx1.111111111111
64lx1.000000000000
>=200lx0.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:

Vzhledem k tomu, že nejrozumnější (a nejběžnější) je PNG a JPG obrázky ukládat s gammou rovnou 0.45454545 (gamma sRGB standardu), nevzniká v řetězci degradace, která by vznikla, přenášely-li by se někde 8-bitová data s gammou 1 (tedy úměrná osvětlení scény).

Select smyčka

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)
Registruje handler na čtení, zápis a chybu příslušného file descriptoru. Až bude z fd možno číst, zavolá se funkce read_handler, až bude možno zapisovat, zavolá se write_handler, při chybě se zavola error_handler. Funkce se zavolají s parametrem data. Funkce můžou být NULL - v tom případě se na danou událost přestane čekat.
int install_timer(ttime t, void (*fn)(void *), void *data)
t je doba v milisekundách. Za danou dobu zavolá funkci fn a předá ji parametr data. Vrátí handle timeru, který je možno použít k předčasnému ukončení. Pokud vrátí -1, došlo k neúspěchu.
void kill_timer(int timer_handle)
Předčasně ukončí timer s příslušným handlem. Pokud handle není platný timer, vyvolá INTERNAL ERROR a dumpne core.
void install_signal_handler(int signal, void (*fn)(void *), void *data, int immediate)
Čeká na signál. Při přijmutí signálu zavolá funkci fn s parametrem data. Pokud je immediate 0, funkce je zavolána až se kód vrátí do select_loop. Pokud je parametr immediate nastaven na 1, funkce je zavolána ihned - v takovém případě můžou být všechny struktury v nekonzistentním stavu, proto na ně není dobré sahat ani volat funkce, co na ně sahaji (nesmí se třeba ani alokovat pamět). fn == NULL znamená odinstalace handleru.
ttime get_time()
Vrátí čas v milisekundách. Vrácená hodnota se plynule zvětšuje, ale nemůžeme z ní usuzovat, jaký je reálný čas. Funkce je použitelná pro měření, jak dlouho nějaká akce trvala.
int register_bottom_half(void (*)(void *), void *)
Způsobí, že daná funkce bude zavolána okamžitě, až se program vrátí do select smyčky. Jakmile byl bottom half registrován, nejde ho zrušit (pozor na to, když je objekt, který byl předán funkci jako parametr, mezitím odalokován). Když je registrována stejná funkce se stejným parametrem vícekrát, zavolá se pouze jednou. Bottom halfy se typicky používají pro odalokování ruzných struktur (jako třeba terminál).
Podobného efektu lze docílit registrováním timeru s časem nula. Rozdíl je v tom, že když registrujeme funkci se stejným parametrem vícekrát, taky bude vícekrát zavolána. Timer jde taky předčasně zrušit, proto je ve většině případů lepší, než bottom halfy.

Stahování dokumentů ze sítě

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)
Stáhne objekt ze sítě. 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
Čtením object_request->state lze zjišťovat stav natahování:
Živé spojení:
O_WAITING – ještě se nezačalo nic stahovat.
O_LOADING – už se stahuje, je stažena část dokumentu.
Spojení se už ukončilo:
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.
Přesnější popis chyby je možno získat z 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)
Tato funkce uvolní object request. Pokud se ještě něco stahuje, tak se to zruší, případně odloží na pozadí, a cachová položka je odemčena, takže může být uvolněna. Zavoláním této funkcí, se nelze na data objektu nijak dostat.

Interpret javascriptu - vývojová dokumentace

Interpret sestává z těchto částí:

  1. lexikální analyzátor
  2. syntaktický analyzátor
  3. generátor interkódu
  4. interpret interkódu

1. Lexikální analyzátor

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...).

2. Syntaktický analyzátor

Syntaktická analýza se děje automatem vygenerovaným programem bison. Zdrojový kód lze nalézt v souboru javascript.y. Syntaktická analýza dostává na vstupu jednotlivé tokeny a má "říct, jak spolu souvisejí", tedy postavit syntaktický strom podle pravidel gramatiky. Tuto gramatiku jsme téměř opsali opět z normy javascript 1.1. Lexikální i syntaktická analýza předcházejí vlastní interpretaci, jelikož interpretování "z čisté vody", tedy z čistého zdrojového kódu právě interpretovaného javascriptu by bylo značně zdlouhavé, navíc bison neposkytuje dostatečně komfortní prostředí pro samotnou interpretaci - bylo by nutné udržovat pohromadě syntaktický analyzér (který je už sám o sobě dost dlouhý) v jednom souboru s interpretačními pravidly (ta jsou ještě delší). Syntaktický analyzér pouze podle pravidel opsaných z gramatiky z posloupnosti tokenů postaví syntaktický strom tvaru:

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).

3. Generátor mezikódu

Generování interkódu probíhá při syntaktické analýze. Celý výpočet javascriptu je zavřen ve struktuře struct js_context, která obsahuje zejména:

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).

4. Interpret mezikódu

Interpret mezikódu je nejnáročnější částí celého interpretu. Jeho hlavní část lze najít v souboru ipret.c. Interpretace probíhá tak, že funkce ipret postupně a organizovaně prohlíží strom. Jediné, co potřebuje vědět, je, v kterém vrcholu teď stojíme, kolikátý argument daného operátora zpracováváme a jaké argumenty už máme spočítané. Když má operátor vypočteny všechny argumenty, spočítá svou hodnotu a vrátí řízení "nadřazenému uzlu", který jeho výpočet použije jako jeden ze svých argumentů. Důležité je, že argumenty se udržují na zásobníku argumentů a vlastnosti zásobníku zaručují, že ve chvíli, kdy se budeme shánět po svých argumentech je tam budeme mít v pořadí: poslední, předposlední,... první (pokud nechceme jinak). Stejně tak teoretické vlastnosti zásobníku zaručují, že vždy po ukončení výpočtu v současném uzlu se vrátíme do bezprostředně předcházejícího uzlu, který má ještě zájem počítat (zájem počítat už např. nemá operátor program, který již zavolal svůj druhý argument. Funkce program vznikla z pravidla:
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í.

Interface

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ů.

Debugovani javascriptu

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.

Obecné seznamy

Popis

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.

Ovládání uživatelem

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:

Datové struktury a funkce

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í.

Stromový seznam

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.

Přidávání a editace položek - funkce edit_item

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).

Přístup do seznamu z více oken (instancí linksu)

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

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í:

Přesný popis rozhraní

Popis struct cached_image

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
012389101112131415
Stav před přechodem0NicSoubor je celý staženJe znam typ souboru
1count2 se změnilNic
2NicSoubor skončilJe znám typ souboru
3count2 se změnilNic
8count2 se změnilNicSoubor skončilPřečetla se hlavička
9count2 se změnilNic
10count2 se změnilNicSoubor skončilPřečetla se hlavička
11zvětšil se count2Nic
12count2 se zvětšilNicSkončil soubor
13count2 se zvětšilNic
14count2 se zvětšilNicSoubor skončil
15count2 se zvětšilNic

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_pixelObsah paměti (po charech)
3RGB
4RGBA
Pro 16-bitové kanály má buffer následující paměťovou organizaci, složky jsou uloženy v unsigned shortech:
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).

Reakce obrázků na změny od uživatele

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á.

Ditherování a barvy

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.

Vydání nové verze Linkse

Vydání nové verze Linkse pomocí skriptů