diff --git a/.gitignore b/.gitignore index 23fde71..30de5ee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ project/Windows/x64/ project/x64/ project/DOS/capture/ .vscode +MICROWEB.SWP diff --git a/CGA.dat b/CGA.dat new file mode 100644 index 0000000..e8c628e Binary files /dev/null and b/CGA.dat differ diff --git a/Default.dat b/Default.dat new file mode 100644 index 0000000..e346f8d Binary files /dev/null and b/Default.dat differ diff --git a/EGA.dat b/EGA.dat new file mode 100644 index 0000000..337cc89 Binary files /dev/null and b/EGA.dat differ diff --git a/LowRes.dat b/LowRes.dat new file mode 100644 index 0000000..ec5ad33 Binary files /dev/null and b/LowRes.dat differ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..482666d --- /dev/null +++ b/Makefile @@ -0,0 +1,81 @@ +bin = MicroWeb.exe +SRC_PATH = src +OBJDIR=obj +objects = MicroWeb.obj App.obj Parser.obj Render.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.obj Font.obj Interfac.obj DOSInput.obj DOSNet.obj Page.obj +memory_model = -ml +CC = wpp +CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) +LD = wlink + +# begin mTCP stuff +tcp_h_dir = lib\mTCP\TCPINC\ +tcp_c_dir = lib\mTCP\TCPLIB\ + +tcpobjs = packet.obj arp.obj eth.obj ip.obj tcp.obj tcpsockm.obj udp.obj utils.obj dns.obj timer.obj ipasm.obj trace.obj + +tcp_compile_options = -0 $(memory_model) -DCFG_H="tcp.cfg" -oh -ok -ot -s -oa -ei -zp2 -zpw -we -ob -ol+ -oi+ +tcp_compile_options += -i=$(tcp_h_dir) + +.cpp : $(tcp_c_dir) + +.asm : $(tcp_c_dir) + +.asm.obj : + wasm -0 $(memory_model) $[* + +.cpp.obj : + wpp $[* $(tcp_compile_options) +# end mTCP stuff + +$(bin): $(objects) $(tcpobjs) + $(LD) system dos name $@ file { $(objects) $(tcpobjs) } + + +MicroWeb.obj: $(SRC_PATH)\MicroWeb.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +App.obj: $(SRC_PATH)\App.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Parser.obj: $(SRC_PATH)\Parser.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Render.obj: $(SRC_PATH)\Render.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Tags.obj: $(SRC_PATH)\Tags.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Page.obj: $(SRC_PATH)\Page.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Platform.obj: $(SRC_PATH)\DOS\Platform.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Font.obj: $(SRC_PATH)\Font.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Interfac.obj: $(SRC_PATH)\Interfac.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +CGA.obj: $(SRC_PATH)\DOS\CGA.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Hercules.obj: $(SRC_PATH)\DOS\Hercules.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +EGA.obj: $(SRC_PATH)\DOS\EGA.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +TextMode.obj: $(SRC_PATH)\DOS\TextMode.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +DOSInput.obj: $(SRC_PATH)\DOS\DOSInput.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +DOSNet.obj: $(SRC_PATH)\DOS\DOSNet.cpp + $(CC) -fo=$@ $(CFLAGS) -i=$(tcp_h_dir) -DCFG_H="tcp.cfg" $< + +clean: .symbolic + del *.obj + del $(bin) \ No newline at end of file diff --git a/README.md b/README.md index 5909dc3..91e5c66 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MicroWeb DOS web browser -![Screenshot](screenshot.png) +MicroWeb is a web browser for DOS that runs as a 16-bit real mode application and is designed to run on minimal hardware. -MicroWeb is a web browser for DOS! It is a 16-bit real mode application, designed to run on minimal hardware. +![Screenshot](screenshot.gif) ## Minimum requirements To run you will need: @@ -9,45 +9,61 @@ To run you will need: * CGA, EGA, VGA or Hercules compatible graphics card * A network interface (it is possible to use your machine's serial port with the EtherSLIP driver) * A mouse is desirable but not 100% required -* 640k RAM is desirable. EMS/XMS are not required +* 640K RAM is desirable but can run with less +* EMS can be used if available and is recommended for loading heavier web pages and images ## Limitations -* Text only (this may change in a later release) -* HTTP only (no HTTPS support) +* HTTP only (See HTTPS limitations below) +* Only GIF images are rendered, although PNG and JPEG dimensions are loaded for layout purposes * No CSS or Javascript * Very long pages may be truncated if there is not enough RAM available * Mouse cursor is currently not visible in Hercules mode ## Keyboard shortcuts -* Escape : Exit -* F2 : Invert screen (useful for LCD displays) -* F6 / Ctrl+L : Select address bar -* Tab / Shift+Tab : Cycle through selectable page elements -* Enter : Follow link / press button -* Backspace : Back in history +| Key | Shortcut | +|-------------------|-------------------------------------------------| +| Escape | Exit | +| F2 | Invert screen (useful for old LCD displays) | +| F3 | Toggle title bar / status bar visibility | +| F5 | Reload page | +| F6 / Ctrl+L | Focus address bar | +| Tab / Shift+Tab | Cycle through selectable page elements | +| Enter | Follow link / press button | +| Cursor up/down | Scroll page | +| Page up/down | Scroll page in large increments | +| Home | Jump to start of page | +| End | Jump to end of page | -The page position can be scrolled with cursor keys, Page up, Page down, Home and End +## Supported video modes +MicroWeb supports a wide range of different video modes. You will be asked on startup to select a mode and one will be suggested based on your detected hardware. +* 640x200 monochrome (CGA) +* 640x200 inverse monochrome (Palmtop CGA) +* 320x200 4 colours (CGA) +* 320x200 16 colours (Composite CGA) +* 640x200 16 colours (EGA) +* 640x350 monochrome (EGA) +* 640x350 16 colours (EGA) +* 640x480 monochrome (VGA) +* 640x480 16 colours (VGA) +* 320x200 256 colours (VGA) +* 720x348 monochrome (Hercules) +* 640x400 monochrome (Olivetti M24) +* 640x400 monochrome (Toshiba T3100) +* 240x128 monochrome (HP 95LX) ## Command line options You can use a URL as an argument to load a specific page on startup. This can also be a path to a local html file. -MicroWeb will try to automatically choose the most appropriate display mode on startup, but it is possible to manually select a video mode by using a command line switch - -Option | Effect -------- |------- - -c | Force to run in 640x200 CGA mode - -h | Force to run in 720x348 Hercules mode - -e | Force to run in 640x350 EGA mode - -v | Force to run in 640x480 VGA mode - -o | Run in 640x400 Olivetti M24 mode - -t3100 | Run in 640x400 Toshiba T3100 mode - -i | Start with inverted screen colours (useful for some LCD monitors) +| Option | Effect +|-----------|------- +| -i | Start with inverted screen colours (useful for some LCD monitors) +| -noems | Disable EMS memory usage +| -noimages | Disables image decoders - useful for very low memory setups -For example `MICROWEB -c http://68k.news` will start in CGA 640x200 mode and load the 68k.news website +For example `MICROWEB -noems http://68k.news` will load the 68k.news website on startup but disable the EMS routines ## HTTPS limitations -Unfortunately older machines just don't have the processing power to handle HTTPS but there are a few options available: -* Browse sites that still allow HTTP +TLS encryption is currently not supported which means that only HTTP servers can be accessed directly. There are some options for HTTPS sites: * Use a proxy server such as [retro-proxy](https://github.com/DrKylstein/retro-proxy) which converts HTTPS to HTTP. You can configure a proxy server by setting the HTTP_PROXY environment variable before running MicroWeb. e.g. `SET HTTP_PROXY=192.168.0.50:8000` * Use the [FrogFind!](http://www.frogfind.com) web service to view a stripped down version of a site. If MicroWeb is redirected to an HTTPS site then it will generate a FrogFind link for your convenience. @@ -55,9 +71,9 @@ Unfortunately older machines just don't have the processing power to handle HTTP Check out the [releases page](https://github.com/jhhoward/MicroWeb/releases) which will include a pre-built binary. Also available are FreeDOS boot disk images for 360K and 720K floppies, which are configured to work with a NE2000 network adapter. These boot images can be used in an emulator such as [PCem](https://pcem-emulator.co.uk/). ## Network setup -MicroWeb uses Michael Brutman's [mTCP networking library](http://www.brutman.com/mTCP/) for the network stack. You will need a DOS packet driver relevant to your network interface. You can read more about configuring DOS networking [here](http://www.brutman.com/Dos_Networking/dos_networking.html) +MicroWeb uses Michael Brutman's [mTCP networking library](http://www.brutmanlabs.org/mTCP/) for the network stack. You will need a DOS packet driver relevant to your network interface. You can read more about configuring DOS networking [here](http://brutmanlabs.org/Dos_Networking/dos_networking.html) ## Build instructions To build you will need the [OpenWatcom 1.9 C++ compiler](https://sourceforge.net/projects/openwatcom/files/open-watcom-1.9/). -Use OpenWatcom's wmake to build the makefile in the project/DOS folder +Use OpenWatcom's wmake to build the makefile in the project/DOS folder. Currently only builds in a Windows environment. diff --git a/assets/CGA/Cour1.png b/assets/CGA/Cour1.png new file mode 100644 index 0000000..52b7905 Binary files /dev/null and b/assets/CGA/Cour1.png differ diff --git a/assets/CGA/Cour2.png b/assets/CGA/Cour2.png new file mode 100644 index 0000000..2676c52 Binary files /dev/null and b/assets/CGA/Cour2.png differ diff --git a/assets/CGA/Cour3.png b/assets/CGA/Cour3.png new file mode 100644 index 0000000..8032611 Binary files /dev/null and b/assets/CGA/Cour3.png differ diff --git a/assets/CGA/Helv1.png b/assets/CGA/Helv1.png new file mode 100644 index 0000000..ab4abb0 Binary files /dev/null and b/assets/CGA/Helv1.png differ diff --git a/assets/CGA/Helv2.png b/assets/CGA/Helv2.png new file mode 100644 index 0000000..dbea7d0 Binary files /dev/null and b/assets/CGA/Helv2.png differ diff --git a/assets/CGA/Helv3.png b/assets/CGA/Helv3.png new file mode 100644 index 0000000..daed967 Binary files /dev/null and b/assets/CGA/Helv3.png differ diff --git a/assets/CGA/broken-image-icon.png b/assets/CGA/broken-image-icon.png new file mode 100644 index 0000000..c3e3bdc Binary files /dev/null and b/assets/CGA/broken-image-icon.png differ diff --git a/assets/CGA/checkbox-ticked.png b/assets/CGA/checkbox-ticked.png new file mode 100644 index 0000000..c862836 Binary files /dev/null and b/assets/CGA/checkbox-ticked.png differ diff --git a/assets/CGA/checkbox.png b/assets/CGA/checkbox.png new file mode 100644 index 0000000..51f5321 Binary files /dev/null and b/assets/CGA/checkbox.png differ diff --git a/assets/CGA/down-icon.png b/assets/CGA/down-icon.png new file mode 100644 index 0000000..a149fa9 Binary files /dev/null and b/assets/CGA/down-icon.png differ diff --git a/assets/CGA/mouse-link.png b/assets/CGA/mouse-link.png index ce3df9d..2a0ea0b 100644 Binary files a/assets/CGA/mouse-link.png and b/assets/CGA/mouse-link.png differ diff --git a/assets/CGA/mouse-select.png b/assets/CGA/mouse-select.png index 9e8a2b0..f9e5647 100644 Binary files a/assets/CGA/mouse-select.png and b/assets/CGA/mouse-select.png differ diff --git a/assets/CGA/mouse.png b/assets/CGA/mouse.png index 9734239..22e058a 100644 Binary files a/assets/CGA/mouse.png and b/assets/CGA/mouse.png differ diff --git a/assets/CGA/radio-selected.png b/assets/CGA/radio-selected.png new file mode 100644 index 0000000..7f06a36 Binary files /dev/null and b/assets/CGA/radio-selected.png differ diff --git a/assets/CGA/radio.png b/assets/CGA/radio.png new file mode 100644 index 0000000..8eac466 Binary files /dev/null and b/assets/CGA/radio.png differ diff --git a/assets/Default/Cour1.png b/assets/Default/Cour1.png new file mode 100644 index 0000000..781d067 Binary files /dev/null and b/assets/Default/Cour1.png differ diff --git a/assets/Default/Cour2.png b/assets/Default/Cour2.png new file mode 100644 index 0000000..2bf5bf6 Binary files /dev/null and b/assets/Default/Cour2.png differ diff --git a/assets/Default/Cour3.png b/assets/Default/Cour3.png new file mode 100644 index 0000000..200439a Binary files /dev/null and b/assets/Default/Cour3.png differ diff --git a/assets/Default/Helv1.png b/assets/Default/Helv1.png new file mode 100644 index 0000000..90e3a33 Binary files /dev/null and b/assets/Default/Helv1.png differ diff --git a/assets/Default/Helv2.png b/assets/Default/Helv2.png new file mode 100644 index 0000000..213d744 Binary files /dev/null and b/assets/Default/Helv2.png differ diff --git a/assets/Default/Helv3.png b/assets/Default/Helv3.png new file mode 100644 index 0000000..dbd553b Binary files /dev/null and b/assets/Default/Helv3.png differ diff --git a/assets/Default/broken-image-icon.png b/assets/Default/broken-image-icon.png new file mode 100644 index 0000000..2fedd96 Binary files /dev/null and b/assets/Default/broken-image-icon.png differ diff --git a/assets/Default/checkbox-ticked.png b/assets/Default/checkbox-ticked.png new file mode 100644 index 0000000..9b526ca Binary files /dev/null and b/assets/Default/checkbox-ticked.png differ diff --git a/assets/Default/checkbox.png b/assets/Default/checkbox.png new file mode 100644 index 0000000..c6e0b3c Binary files /dev/null and b/assets/Default/checkbox.png differ diff --git a/assets/Default/down-icon.png b/assets/Default/down-icon.png new file mode 100644 index 0000000..6ae84d8 Binary files /dev/null and b/assets/Default/down-icon.png differ diff --git a/assets/Default/mouse-link.png b/assets/Default/mouse-link.png index e474807..660a2f6 100644 Binary files a/assets/Default/mouse-link.png and b/assets/Default/mouse-link.png differ diff --git a/assets/Default/mouse-select.png b/assets/Default/mouse-select.png index a6e44c5..df8f192 100644 Binary files a/assets/Default/mouse-select.png and b/assets/Default/mouse-select.png differ diff --git a/assets/Default/mouse.png b/assets/Default/mouse.png index b5bf9b4..82decbd 100644 Binary files a/assets/Default/mouse.png and b/assets/Default/mouse.png differ diff --git a/assets/Default/radio-selected.png b/assets/Default/radio-selected.png new file mode 100644 index 0000000..9f08deb Binary files /dev/null and b/assets/Default/radio-selected.png differ diff --git a/assets/Default/radio.png b/assets/Default/radio.png new file mode 100644 index 0000000..f623c17 Binary files /dev/null and b/assets/Default/radio.png differ diff --git a/assets/EGA/Cour1.png b/assets/EGA/Cour1.png new file mode 100644 index 0000000..cb42a5d Binary files /dev/null and b/assets/EGA/Cour1.png differ diff --git a/assets/EGA/Cour2.png b/assets/EGA/Cour2.png new file mode 100644 index 0000000..04b7b74 Binary files /dev/null and b/assets/EGA/Cour2.png differ diff --git a/assets/EGA/Cour3.png b/assets/EGA/Cour3.png new file mode 100644 index 0000000..6bc8ac7 Binary files /dev/null and b/assets/EGA/Cour3.png differ diff --git a/assets/EGA/Helv1.png b/assets/EGA/Helv1.png new file mode 100644 index 0000000..21f9a1a Binary files /dev/null and b/assets/EGA/Helv1.png differ diff --git a/assets/EGA/Helv2.png b/assets/EGA/Helv2.png new file mode 100644 index 0000000..cf769c4 Binary files /dev/null and b/assets/EGA/Helv2.png differ diff --git a/assets/EGA/Helv3.png b/assets/EGA/Helv3.png new file mode 100644 index 0000000..eb4b657 Binary files /dev/null and b/assets/EGA/Helv3.png differ diff --git a/assets/EGA/broken-image-icon.png b/assets/EGA/broken-image-icon.png new file mode 100644 index 0000000..d384701 Binary files /dev/null and b/assets/EGA/broken-image-icon.png differ diff --git a/assets/EGA/bullet.png b/assets/EGA/bullet.png new file mode 100644 index 0000000..6a540bc Binary files /dev/null and b/assets/EGA/bullet.png differ diff --git a/assets/EGA/checkbox-ticked.png b/assets/EGA/checkbox-ticked.png new file mode 100644 index 0000000..6b96241 Binary files /dev/null and b/assets/EGA/checkbox-ticked.png differ diff --git a/assets/EGA/checkbox.png b/assets/EGA/checkbox.png new file mode 100644 index 0000000..b19cec9 Binary files /dev/null and b/assets/EGA/checkbox.png differ diff --git a/assets/EGA/down-icon.png b/assets/EGA/down-icon.png new file mode 100644 index 0000000..f1d316f Binary files /dev/null and b/assets/EGA/down-icon.png differ diff --git a/assets/EGA/font-large.png b/assets/EGA/font-large.png new file mode 100644 index 0000000..76ab94d Binary files /dev/null and b/assets/EGA/font-large.png differ diff --git a/assets/EGA/font-mono-large.png b/assets/EGA/font-mono-large.png new file mode 100644 index 0000000..e75f792 Binary files /dev/null and b/assets/EGA/font-mono-large.png differ diff --git a/assets/EGA/font-mono-small.png b/assets/EGA/font-mono-small.png new file mode 100644 index 0000000..7140d73 Binary files /dev/null and b/assets/EGA/font-mono-small.png differ diff --git a/assets/EGA/font-mono.png b/assets/EGA/font-mono.png new file mode 100644 index 0000000..0b692c5 Binary files /dev/null and b/assets/EGA/font-mono.png differ diff --git a/assets/EGA/font-small.png b/assets/EGA/font-small.png new file mode 100644 index 0000000..7cc93c0 Binary files /dev/null and b/assets/EGA/font-small.png differ diff --git a/assets/EGA/font.png b/assets/EGA/font.png new file mode 100644 index 0000000..2286aa8 Binary files /dev/null and b/assets/EGA/font.png differ diff --git a/assets/EGA/image-icon.png b/assets/EGA/image-icon.png new file mode 100644 index 0000000..c700da4 Binary files /dev/null and b/assets/EGA/image-icon.png differ diff --git a/assets/EGA/mouse-link.png b/assets/EGA/mouse-link.png new file mode 100644 index 0000000..91e15ff Binary files /dev/null and b/assets/EGA/mouse-link.png differ diff --git a/assets/EGA/mouse-select.png b/assets/EGA/mouse-select.png new file mode 100644 index 0000000..a9db917 Binary files /dev/null and b/assets/EGA/mouse-select.png differ diff --git a/assets/EGA/mouse.png b/assets/EGA/mouse.png new file mode 100644 index 0000000..c99cdac Binary files /dev/null and b/assets/EGA/mouse.png differ diff --git a/assets/EGA/radio-selected.png b/assets/EGA/radio-selected.png new file mode 100644 index 0000000..216da0e Binary files /dev/null and b/assets/EGA/radio-selected.png differ diff --git a/assets/EGA/radio.png b/assets/EGA/radio.png new file mode 100644 index 0000000..7525598 Binary files /dev/null and b/assets/EGA/radio.png differ diff --git a/assets/Fonts/COURA.FON b/assets/Fonts/COURA.FON new file mode 100644 index 0000000..4f0b94a Binary files /dev/null and b/assets/Fonts/COURA.FON differ diff --git a/assets/Fonts/COURB.FON b/assets/Fonts/COURB.FON new file mode 100644 index 0000000..b0ef8d1 Binary files /dev/null and b/assets/Fonts/COURB.FON differ diff --git a/assets/Fonts/COURC.FON b/assets/Fonts/COURC.FON new file mode 100644 index 0000000..d28ba79 Binary files /dev/null and b/assets/Fonts/COURC.FON differ diff --git a/assets/Fonts/COURD.FON b/assets/Fonts/COURD.FON new file mode 100644 index 0000000..29ae469 Binary files /dev/null and b/assets/Fonts/COURD.FON differ diff --git a/assets/Fonts/COURE.FON b/assets/Fonts/COURE.FON new file mode 100644 index 0000000..ece8771 Binary files /dev/null and b/assets/Fonts/COURE.FON differ diff --git a/assets/Fonts/HELVA.FON b/assets/Fonts/HELVA.FON new file mode 100644 index 0000000..8f03d36 Binary files /dev/null and b/assets/Fonts/HELVA.FON differ diff --git a/assets/Fonts/HELVB.FON b/assets/Fonts/HELVB.FON new file mode 100644 index 0000000..81750c0 Binary files /dev/null and b/assets/Fonts/HELVB.FON differ diff --git a/assets/Fonts/HELVC.FON b/assets/Fonts/HELVC.FON new file mode 100644 index 0000000..4bfb251 Binary files /dev/null and b/assets/Fonts/HELVC.FON differ diff --git a/assets/Fonts/HELVD.FON b/assets/Fonts/HELVD.FON new file mode 100644 index 0000000..0b3e7d9 Binary files /dev/null and b/assets/Fonts/HELVD.FON differ diff --git a/assets/Fonts/HELVE.FON b/assets/Fonts/HELVE.FON new file mode 100644 index 0000000..a65a450 Binary files /dev/null and b/assets/Fonts/HELVE.FON differ diff --git a/assets/Fonts/fonts.txt b/assets/Fonts/fonts.txt new file mode 100644 index 0000000..dc30ba5 --- /dev/null +++ b/assets/Fonts/fonts.txt @@ -0,0 +1,15 @@ +Fonts are ISO 8859-1 (Latin-1) + +HELVA.FON, Helv 8,10,12,14,18,24 (CGA resolution) +HELVB.FON, Helv 8,10,12,14,18,24 (EGA resolution) +HELVC.FON, Helv 8,10,12,14,18,24 (60 dpi) +HELVD.FON, Helv 8,10,12,14,18,24 (120 dpi) +HELVE.FON, Helv 8,10,12,14,18,24 (VGA resolution) +HELVF.FON, Helv 8,10,12,14,18,24 (8514/a resolution) + +COURA.FON, Courier 10,12,15 (CGA resolution) +COURB.FON, Courier 10,12,15 (EGA resolution) +COURC.FON, Courier 10,12,15 (60 dpi) +COURD.FON, Courier 10,12,15 (120 dpi) +COURE.FON, Courier 10,12,15 (VGA resolution) +COURF.FON, Courier 10,12,15 (8514/a resolution) \ No newline at end of file diff --git a/assets/LowRes/Cour1.png b/assets/LowRes/Cour1.png new file mode 100644 index 0000000..de0ba77 Binary files /dev/null and b/assets/LowRes/Cour1.png differ diff --git a/assets/LowRes/Cour2.png b/assets/LowRes/Cour2.png new file mode 100644 index 0000000..9e610a0 Binary files /dev/null and b/assets/LowRes/Cour2.png differ diff --git a/assets/LowRes/Cour3.png b/assets/LowRes/Cour3.png new file mode 100644 index 0000000..6c42f11 Binary files /dev/null and b/assets/LowRes/Cour3.png differ diff --git a/assets/LowRes/Helv1.png b/assets/LowRes/Helv1.png new file mode 100644 index 0000000..a9ee588 Binary files /dev/null and b/assets/LowRes/Helv1.png differ diff --git a/assets/LowRes/Helv2.png b/assets/LowRes/Helv2.png new file mode 100644 index 0000000..7712322 Binary files /dev/null and b/assets/LowRes/Helv2.png differ diff --git a/assets/LowRes/Helv3.png b/assets/LowRes/Helv3.png new file mode 100644 index 0000000..4689ed5 Binary files /dev/null and b/assets/LowRes/Helv3.png differ diff --git a/assets/LowRes/broken-image-icon.png b/assets/LowRes/broken-image-icon.png new file mode 100644 index 0000000..77ae646 Binary files /dev/null and b/assets/LowRes/broken-image-icon.png differ diff --git a/assets/LowRes/bullet.png b/assets/LowRes/bullet.png new file mode 100644 index 0000000..53e9945 Binary files /dev/null and b/assets/LowRes/bullet.png differ diff --git a/assets/LowRes/checkbox-ticked.png b/assets/LowRes/checkbox-ticked.png new file mode 100644 index 0000000..018df02 Binary files /dev/null and b/assets/LowRes/checkbox-ticked.png differ diff --git a/assets/LowRes/checkbox.png b/assets/LowRes/checkbox.png new file mode 100644 index 0000000..3060707 Binary files /dev/null and b/assets/LowRes/checkbox.png differ diff --git a/assets/LowRes/down-icon.png b/assets/LowRes/down-icon.png new file mode 100644 index 0000000..cce7973 Binary files /dev/null and b/assets/LowRes/down-icon.png differ diff --git a/assets/LowRes/image-icon.png b/assets/LowRes/image-icon.png new file mode 100644 index 0000000..9126e12 Binary files /dev/null and b/assets/LowRes/image-icon.png differ diff --git a/assets/LowRes/mouse-link.png b/assets/LowRes/mouse-link.png new file mode 100644 index 0000000..0b8865b Binary files /dev/null and b/assets/LowRes/mouse-link.png differ diff --git a/assets/LowRes/mouse-select.png b/assets/LowRes/mouse-select.png new file mode 100644 index 0000000..fb5bbff Binary files /dev/null and b/assets/LowRes/mouse-select.png differ diff --git a/assets/LowRes/mouse.png b/assets/LowRes/mouse.png new file mode 100644 index 0000000..e926416 Binary files /dev/null and b/assets/LowRes/mouse.png differ diff --git a/assets/LowRes/radio-selected.png b/assets/LowRes/radio-selected.png new file mode 100644 index 0000000..8b7cf5d Binary files /dev/null and b/assets/LowRes/radio-selected.png differ diff --git a/assets/LowRes/radio.png b/assets/LowRes/radio.png new file mode 100644 index 0000000..887fa45 Binary files /dev/null and b/assets/LowRes/radio.png differ diff --git a/examples/68knews.htm b/examples/68knews.htm new file mode 100644 index 0000000..67bf168 --- /dev/null +++ b/examples/68knews.htm @@ -0,0 +1,212 @@ + + + + + + + 68k.news: Headlines From the Future + + +

68k.news: Headlines from the Future

+
+
Basic HTML Google News for vintage computers. Built by Action Retro on YouTube. Tested on Netscape 1.1 through 4 on a Mac SE/30.
+ +

+

TOP WORLD NATION BUSINESS TECHNOLOGY ENTERTAINMENT SPORTS SCIENCE HEALTH
+ -=-=-=-=-=-=-=-=-=-=-=-=-=- +
US Edition (Change)
+

+
+ +

Uber and Lyft to provide free rides to vaccination sites as part of new White House partnership - CNN

+

  1. Uber and Lyft to provide free rides to vaccination sites as part of new White House partnership  CNN
  2. Biden to announce partnership with Uber, Lyft for free rides to COVID vaccination sites  Fox News
  3. McDonald's and Uber to help encourage vaccine-hesitant Americans  The Guardian
  4. Uber, Lyft to Provide Free Rides to Covid-19 Vaccine Sites Until July 4  The Wall Street Journal
  5. Biden showcases vaccination best practices amid July 4 push  KXAN.com

+

Posted on 11 May 2021 | 1:44 pm

+ + +

Broadway shows 'Lion King,' 'Hamilton' and 'Wicked' set reopening dates - CNN

+

  1. Broadway shows 'Lion King,' 'Hamilton' and 'Wicked' set reopening dates  CNN
  2. Broadway shows to reopen in September  Good Morning America
  3. Cue the lights: Broadway is back Sept. 14 with 'Hamilton,' 'The Lion King' and more  The Washington Post
  4. 'Hamilton,' 'Wicked' and 'The Lion King' return to Broadway in September  New York Post
  5. 'Hamilton,' 'Wicked' and 'The Lion King' to kick off Broadway reopening on Sept. 14  CNBC

+

Posted on 11 May 2021 | 1:33 pm

+ + +

Feds grant approval to 84-turbine Vineyard Wind project off coast of Massachusetts - WCVB Boston

+

  1. Feds grant approval to 84-turbine Vineyard Wind project off coast of Massachusetts  WCVB Boston
  2. Biden Administration Issues Final Approval For First Major U.S. Wind Farm To Be Built Off Martha's V  CBS Boston
  3. Biden Administration Approves Nation's First Major Offshore Wind Farm  The New York Times
  4. Opinion: Hurdles ahead, but US will get there on offshore wind  Windpower Monthly
  5. Biden administration grants Vineyard Wind its final major permit  BetaBoston

+

Posted on 11 May 2021 | 1:25 pm

+ + +

FBI says Darkside hacking group responsible for pipeline cyberattack - CNET

+

  1. FBI says Darkside hacking group responsible for pipeline cyberattack  CNET
  2. Colonial Pipeline CEO warns of possible fuel shortages following cyberattack  Fox Business
  3. Gasoline demand spikes in several states after pipeline hack  CNN
  4. The Real Infrastructure Problem  The Wall Street Journal
  5. Cyberattack on fuel pipeline causes gas shortages in multiple states l GMA  Good Morning America

+

Posted on 11 May 2021 | 1:09 pm

+ + +

India Struggles to Keep Pace With Coronavirus Variants - The Wall Street Journal

+

  1. India Struggles to Keep Pace With Coronavirus Variants  The Wall Street Journal
  2. WHO elevates Indian coronavirus strain to 'variant of concern'  Fox News
  3. WHO labels a Covid strain in India as a 'variant of concern' — here's what we know  CNBC
  4. Covid: Targeted testing in Nottingham after Indian variant rise  BBC News

+

Posted on 11 May 2021 | 12:56 pm

+ + +

Stocks tumble in broad sell-off, with Dow down 450 points - The Washington Post

+

  1. Stocks tumble in broad sell-off, with Dow down 450 points  The Washington Post
  2. Nasdaq erases 2% loss as tech stocks rebound from morning washout  CNBC
  3. Two experts on what was behind Monday's Big Tech sell-off  CNBC Television
  4. Dow slides over 500 points Home Depot, American Express drags  Fox Business
  5. Inflation fears push S&P 500 to one-month low  Reuters

+

Posted on 11 May 2021 | 12:55 pm

+ + +

Schumer invokes Cheney in debate over Dems' sweeping voting bill she calls 'unconstitutional' - Fox News

+

  1. Schumer invokes Cheney in debate over Dems' sweeping voting bill she calls 'unconstitutional'  Fox News
  2. Senate rules committee takes up bill on voting access and elections  CBS News
  3. Schumer, McConnell Clash Over Voting Rights Bill In Senate | NBC News  NBC News
  4. It's time for Democrats to force Joe Manchin to show his hand  The Washington Post
  5. Sen. Shelley Moore Capito: Democrats' election power grab - S1 bill not For the People. Here's why  Fox News

+

Posted on 11 May 2021 | 12:50 pm

+ + +

Bob Baffert comes clean in Medina Spirit scandal after wild urine theory - New York Post

+

  1. Bob Baffert comes clean in Medina Spirit scandal after wild urine theory  New York Post
  2. Kentucky Derby winner Medina Spirit was treated with ointment including betamethasone, Bob Baffert reveals  Fox News
  3. No, failed Derby drug test is not 'cancel culture.' But racing needs culture change.  Lexington Herald Leader
  4. Bob Baffert admits he treated Medina Spirit with ointment that contained betamethasone  Yahoo Sports
  5. Say no to dope? There's just no hope  Chicago Sun-Times

+

Posted on 11 May 2021 | 12:39 pm

+ + +

3 men who chased, killed Ahmaud Arbery due in federal court Tuesday - USA TODAY

+

  1. 3 men who chased, killed Ahmaud Arbery due in federal court Tuesday  USA TODAY
  2. Georgia repeals citizen's arrest law ahead of a federal hearing in the death of Ahmaud Arbery  CNN
  3. Men who chased, killed Ahmaud Arbery to appear in federal court  11Alive
  4. Ahmaud Arbery murder suspects plead not guilty in federal case  New York Daily News
  5. Ahmaud Arbery killing: Defendants to appear in US federal court  Al Jazeera English

+

Posted on 11 May 2021 | 12:27 pm

+ + +

LA County could see coronavirus herd immunity by July: health official - Fox News

+

  1. LA County could see coronavirus herd immunity by July: health official  Fox News
  2. L.A. County expected to hit COVID-19 herd immunity by end of July  Yahoo News
  3. L.A. County projected to reach herd immunity by late July if vaccination rate keeps up current pace  KTLA Los Angeles
  4. As vaccination eligibility expands, LA County could reach herd immunity by this summer, Ferrer says  KABC-TV
  5. Los Angeles County could reach herd immunity by end of July, health officials say  Business Insider India

+

Posted on 11 May 2021 | 12:14 pm

+ + +

Perseverance's Robotic Arm Starts Conducting Science - NASA's Mars Exploration Program - NASA Mars Exploration

+

  1. Perseverance's Robotic Arm Starts Conducting Science - NASA's Mars Exploration Program  NASA Mars Exploration
  2. Could humans have contaminated Mars with life?  BBC News
  3. The first sounds of the Mars helicopter have made it to Earth  Yahoo Entertainment
  4. Mastcam-Z Views Santa Cruz on Mars  Jet Propulsion Laboratory
  5. Perseverance Rover Successfully Pulls Breathable Oxygen From The Red Planet's Atmosphere  DOGOnews

+

Posted on 11 May 2021 | 12:11 pm

+ + +

The Chromebook at 10: How these stripped-down computers went mainstream - CNET

+

  1. The Chromebook at 10: How these stripped-down computers went mainstream  CNET
  2. My favorite Chromebook classroom accessories  Chrome Unboxed
  3. What is a Chromebook? Here is everything you wanted to ask  The Indian Express
  4. It turns out Google's handwriting recognition for Chromebooks needs no network to work  Chrome Unboxed

+

Posted on 11 May 2021 | 12:00 pm

+ + +

HTC unveils new Vive Pro 2 with 5K resolution display and 120Hz refresh rate - The Verge

+

  1. HTC unveils new Vive Pro 2 with 5K resolution display and 120Hz refresh rate  The Verge
  2. HTC's newest headsets signal end of Vive's 5-year "VR for the home" mission  Ars Technica
  3. HTC Reveals Vive Pro 2 & Focus 3  UploadVR
  4. HTC Hopes Its Long-Awaited 5K Vive Pro 2 Headset Won't Make You Sick  Gizmodo
  5. VIVE Focus 3 | VIVE  HTC VIVE

+

Posted on 11 May 2021 | 12:00 pm

+ + +

Russia School Shooting: At Least 9 Dead - NPR

+

  1. Russia School Shooting: At Least 9 Dead  NPR
  2. At Least 7 Killed And 22 Injured In Russian School Shooting | NBC News NOW  NBC News
  3. At least 7 children killed in Russian school shooting  CNN
  4. Russia school shooting: Children and teacher killed in Kazan  BBC News
  5. Children killed, many wounded, in Russian school shooting  CNBC

+

Posted on 11 May 2021 | 11:48 am

+ + +

4th Wave Of COVID-19 Hospitalizations Hits Washington State - NPR

+

  1. 4th Wave Of COVID-19 Hospitalizations Hits Washington State  NPR
  2. COVID-19 vaccinations appear to be driving down infections in Washington state  KING 5
  3. Coronavirus daily news updates, May 11: What to know today about COVID-19 in the Seattle area, Washington state and the world  The Seattle Times
  4. COVID-19 Vaccine | The scramble for vaccines  eNCA
  5. Parents, schools prepare to give COVID-19 vaccines to kids 12 and up in Washington  KING5.com

+

Posted on 11 May 2021 | 11:44 am

+ + +

Uber and Lyft will offer free rides to vaccination sites in White House partnership - CNBC

+

  1. Uber and Lyft will offer free rides to vaccination sites in White House partnership  CNBC
  2. Biden reaches agreements with Uber and Lyft to give free rides to vaccine sites  Axios
  3. Governors meeting with President Biden to focus on new COVID-19 vaccination strategy  WCVB Channel 5 Boston
  4. White House to announce deal for free vaccination rides from Uber, Lyft | TheHill  The Hill
  5. Uber, Lyft to provide free rides to COVID-19 vaccination sites in new White House partnership  WABC-TV

+

Posted on 11 May 2021 | 11:31 am

+ + +

John Mulaney jokes about rehab during first stand-up show after treatment - Page Six

+

  1. John Mulaney jokes about rehab during first stand-up show after treatment  Page Six
  2. John Mulaney & Wife Split After 6 Years of Marriage | E! News  E! News
  3. Comedian John Mulaney and wife Anna Marie Tendler call it quits as he completes rehab and returns to the stage  Yahoo Entertainment
  4. John Mulaney and wife Anna Marie Tendler are divorcing after his rehab stay  Page Six
  5. John Mulaney and Wife Annamarie Tendler Split After 6 Years of Marriage | PEOPLE  People

+

Posted on 11 May 2021 | 11:09 am

+ + +

Voyager spacecraft detects 'persistent hum' beyond our solar system - CNN

+

  1. Voyager spacecraft detects 'persistent hum' beyond our solar system  CNN
  2. Voyager 1 detects 'hum' while in interstellar space: report  Fox News
  3. Humanity's most distant space probe captures a strange sound  Big Think
  4. In the emptiness of space, Voyager 1 detects plasma 'hum'  Phys.org
  5. Faraway NASA probe detects the eerie hum of interstellar space  Yahoo News

+

Posted on 11 May 2021 | 10:43 am

+ + +

Davante Adams has no gut feeling on how the Aaron Rodgers situation will play out - NBC Sports

+

  1. Davante Adams has no gut feeling on how the Aaron Rodgers situation will play out  NBC Sports
  2. Proud of 'connection' with QB, Green Bay Packers WR Davante Adams would do 'extra thinking' on future if Aaron Rodgers leaves  ESPN
  3. Opinion: Could the Buccaneers Model Help the Green Bay Packers Keep Aaron Rodgers Happy?  EssentiallySports
  4. Broncos will pursue Aaron Rodgers if he becomes available: report  Fox News
  5. There's only one way Aaron Rodgers remains with the Packers - Booger McFarland | First Take  ESPN

+

Posted on 11 May 2021 | 10:37 am

+ + +

Dan Gainor: NBC races to drop Golden Globes over diversity, but MSNBC blasts Scott as 'token,' 'tap dancer' - Fox News

+

  1. Dan Gainor: NBC races to drop Golden Globes over diversity, but MSNBC blasts Scott as 'token,' 'tap dancer'  Fox News
  2. NBC won't air the Golden Globes in 2022. Good. Let's ditch the other awards shows, too.  The Washington Post
  3. It's not just racism and sexism. The Golden Globes have been sunk by sheer stupidity  The Guardian
  4. The only way the Golden Globes will get a Hollywood ending  CNN
  5. After Tom Cruise's snub, is this the end of the Golden Globes?  NME

+

Posted on 11 May 2021 | 10:36 am

+ + +

Elon Musk asks Twitter followers if Tesla should accept Dogecoin - Al Jazeera English

+

  1. Elon Musk asks Twitter followers if Tesla should accept Dogecoin  Al Jazeera English
  2. Does Elon Musk Really Think Dogecoin Is 'A Hustle?'  Yahoo Finance
  3. WATCH: Elon Musk reveals he has Asperger's syndrome on 'SNL'  MarketWatch
  4. Dogecoin gives away the crypto game  Financial Times
  5. Dogecoin and Elon Musk on SNL: It's a Hustle, But It's the People's Hustle  Bloomberg

+

Posted on 11 May 2021 | 10:31 am

+ + +

The 17 Best Mineral Sunscreen 2021 That Won't Leave a White Cast - Glamour

+

  1. The 17 Best Mineral Sunscreen 2021 That Won't Leave a White Cast  Glamour
  2. May is Skin Cancer Awareness Month  WJHL
  3. Why Jennifer Hudson Swears By This Drugstore Suncreen  InStyle
  4. Ten things you can do to prevent skin cancer this summer  abc4utah
  5. Protecting yourself from skin cancer  WWLP.com

+

Posted on 11 May 2021 | 10:29 am

+ + +

'VA hospital serial killer faces victims' families at sentencing for seven murders - USA TODAY

+

'VA hospital serial killer faces victims' families at sentencing for seven murders  USA TODAY

+

Posted on 11 May 2021 | 10:26 am

+ + +

Scores of dead bodies found floating in India's Ganges River - The Associated Press

+

  1. Scores of dead bodies found floating in India's Ganges River  The Associated Press
  2. India Covid: Dozens of bodies wash up on banks of Ganges river  BBC News
  3. Dozens of bodies wash up on the banks of Ganges River in India  CNN
  4. COVID-19 bodies being disposed of in India rivers as cremation costs rise | TheHill  The Hill
  5. The bloated, decaying bodies of COVID victims are washing up on the banks of India's Ganges River  Yahoo News

+

Posted on 11 May 2021 | 10:25 am

+ + +

Washington Post Picks Sally Buzbee as Top Editor - The New York Times

+

  1. Washington Post Picks Sally Buzbee as Top Editor  The New York Times
  2. Sally Buzbee of the Associated Press named executive editor of The Washington Post, the first woman to lead the newsroom  The Washington Post
  3. Washington Post hires AP's Buzbee as executive editor  POLITICO
  4. Washington Post names AP's Sally Buzbee as executive editor  Axios
  5. Sally Buzbee will be first woman to lead Washington Post as new executive editor  CNN

+

Posted on 11 May 2021 | 9:57 am

+ + +

NBA roundup: Wizards' Russell Westbrook breaks Oscar Robertson's triple-double record; favorites fall on road - CBS Sports

+

  1. NBA roundup: Wizards' Russell Westbrook breaks Oscar Robertson's triple-double record; favorites fall on road  CBS Sports
  2. First Take reacts to Russell Westbrook breaking Oscar Robertson's triple-double record  ESPN
  3. Russell Westbrook is the NBA's most underappreciated player  Yahoo Sports
  4. Russell Westbrook breaks Oscar Robertson's NBA record for most career triple-doubles  CBS Sports
  5. Here are some sports records that were not meant to be easily broken  USA TODAY

+

Posted on 11 May 2021 | 9:20 am

+ + +

Matt Damon talks about Ben Affleck and Jennifer Lopez on TODAY show - Today.com

+

  1. Matt Damon talks about Ben Affleck and Jennifer Lopez on TODAY show  Today.com
  2. Alex Rodriguez 'shocked' by Jennifer Lopez, Ben Affleck reunion following split: report  Fox News
  3. Kevin Smith Wants to Remind Everybody He Coined 'Bennifer' the First Time Around  Vulture
  4. Kevin Smith reminds the world he created the term 'Bennifer' amid work on 2004's Jersey Girl  Daily Mail
  5. Jennifer Lopez and Ben Affleck 'Spent Several Days' Together in Montana, Source Says  PEOPLE

+

Posted on 11 May 2021 | 8:49 am

+ + +

Everything You Need to Know About the 2020 NBA Hall of Fame Class - The Ringer

+

  1. Everything You Need to Know About the 2020 NBA Hall of Fame Class  The Ringer
  2. Lakers star LeBron James likely to return vs. Knicks on Tuesday  ESPN
  3. What Will It Take For Lakers To Move Up To 6th? How Everything Could Change For L.A.  Lakers Nation
  4. Anthony Davis' return to form is the biggest reason to fear the Lakers in the postseason  The Athletic
  5. LeBron James' Play-In Nightmare: Every Lakers scenario to escape play-in  ClutchPoints

+

Posted on 11 May 2021 | 8:47 am

+ + +

India's coronavirus doctors report 'black fungus' infections among some patients - Fox News

+

  1. India's coronavirus doctors report 'black fungus' infections among some patients  Fox News
  2. COVID-19 patients in India are developing deadly 'black fungus' infections that can lead to blindness  Yahoo News
  3. Gravitas: What is Black Fungus or Mucormycosis?  WION
  4. What is the deadly 'black fungus' seen in Covid patients in India?  The Guardian
  5. 'Black fungus' complication adds to India's COVID woes  Reuters India

+

Posted on 11 May 2021 | 8:46 am

+ + +

Queen Elizabeth II doesn't wear a mask or crown as she opens U.K. parliament and outlines Boris Johnson's agenda - The Washington Post

+

  1. Queen Elizabeth II doesn't wear a mask or crown as she opens U.K. parliament and outlines Boris Johnson's agenda  The Washington Post
  2. Queen carries out first major royal duty since Philip's death  BBC News
  3. Queen avoids full robes in COVID hit scaled-back ceremony during the state opening of parliament  Yahoo News
  4. The Queen opens UK Parliament in her first major event since Philip's death  CNN
  5. British PM seeks to bolster economy, union in new parliament  San Francisco Chronicle

+

Posted on 11 May 2021 | 8:46 am

+ + +

Republican Joni Ernst accuses party of cancel culture over Liz Cheney ousting - The Guardian

+

  1. Republican Joni Ernst accuses party of cancel culture over Liz Cheney ousting  The Guardian
  2. Kinzinger: Cheney 'is being run out for one thing: her consistency'  CNN
  3. The collapse of the GOP? It's just wishful thinking  The Week
  4. This is the most repulsive GOP scam about Liz Cheney yet  The Washington Post
  5. The GOP's new orthodoxy: 'Big Lie' or bust | COMMENTARY  Baltimore Sun

+

Posted on 11 May 2021 | 8:30 am

+ + +

Efforts to Weed Out Extremists in Law Enforcement Meet Resistance - Yahoo News

+

  1. Efforts to Weed Out Extremists in Law Enforcement Meet Resistance  Yahoo News
  2. Law enforcement memorial service  WYFF News 4
  3. 3 Ozarks officers honored for service at National Law Enforcement Officers Memorial  KY3
  4. How friendly is Kansas for police officers? Study says state ranks low  KSN-TV
  5. Report: Washington among top 10 best states to be a police officer  MyNorthwest.com

+

Posted on 11 May 2021 | 8:15 am

+ + +

Republicans cheer on Andrew Yang after pro-Israel tweet - Business Insider

+

  1. Republicans cheer on Andrew Yang after pro-Israel tweet  Business Insider
  2. Palestinians fight eviction from homes in East Jerusalem  CNN
  3. Hamas Fires Rockets at Tel Aviv as Israel Hits Gaza With Airstrikes  The New York Times
  4. Opinion | Israelis, Palestinians and Their Neighbors Worry: Is This the Big One?  The New York Times
  5. Biden didn't want to be dragged into the Israel-Palestine issue. But he doesn't have a choice.  The Washington Post

+

Posted on 11 May 2021 | 8:06 am

+ + +

Nvidia New Mobile GPUs Brings Ray Tracing to Cheap Gaming Laptops - IGN - IGN

+

  1. Nvidia New Mobile GPUs Brings Ray Tracing to Cheap Gaming Laptops - IGN  IGN
  2. NVIDIA DLSS | GeForce RTX 3050 Ti Laptop Gameplay  NVIDIA GeForce
  3. Nvidia's RTX 3050 Ti can deliver 60fps gameplay in more budget-friendly laptops  The Verge
  4. Nvidia's Newest Graphics Cards for Gaming Laptops Are Here  Gizmodo
  5. Nvidia Broadcast waves goodbye to cats and dogs in latest update  Techradar

+

Posted on 11 May 2021 | 7:03 am

+ + +

Dell's latest XPS laptops pack ray-traced graphics into the same slim frame - Engadget

+

  1. Dell's latest XPS laptops pack ray-traced graphics into the same slim frame  Engadget
  2. Dell's new XPS 15 and XPS 17 get upgraded with Intel's long-awaited 11th Gen H-series chips  The Verge
  3. Dell XPS 15 9510 gets a 3.5K OLED touch option along with ray tracing and DLSS support thanks to RTX 3050 and RTX 3050 Ti Laptop GPUs  Notebookcheck.net
  4. Alienware teases new X-Series laptops with exotic cooling solution  Engadget
  5. Alienware Refreshes m15, Teases Thin New X-Series  Tom's Hardware

+

Posted on 11 May 2021 | 7:02 am

+ + +

Colorado Springs shooting: Domestic violence eyed in investigation, report says - Fox News

+

  1. Colorado Springs shooting: Domestic violence eyed in investigation, report says  Fox News
  2. Victims of Colorado Springs birthday party shooting to be identified  ABC News
  3. Teodoro Macias Jr. 28, known as 'Junior' killed six in Colorado shooting  Daily Mail
  4. Police to give update on shooting at Colorado Springs birthday party that left 7 dead  9News.com KUSA
  5. Man Kills 6, Including Girlfriend, at Colorado Birthday Party Before Shooting Himself, Police Say  PEOPLE

+

Posted on 11 May 2021 | 2:49 am

+ + +

NASA's OSIRIS-REx Spacecraft Heads for Earth with Asteroid Sample - NASA

+

  1. NASA's OSIRIS-REx Spacecraft Heads for Earth with Asteroid Sample  NASA
  2. NASA's OSIRIS-REx spacecraft heading back to Earth after retrieving asteroid sample  Fox News
  3. NASA spacecraft begins 2-year trip home with asteroid rubble  KXAN.com
  4. NASA spacecraft carrying history-making asteroid sample now heading toward Earth  CNN
  5. Bye bye, Bennu: Arizona-led mission begins long journey back to Earth  Arizona Daily Star

+

Posted on 10 May 2021 | 4:59 pm

+ + +

SpaceX rocket makes record 10th flight while Elon Musk parties post-SNL - CNET

+

  1. SpaceX rocket makes record 10th flight while Elon Musk parties post-SNL  CNET
  2. String of satellites baffles residents, bugs astronomers  NBC News
  3. SpaceX launches Starlink satellite rocket  CBS 17
  4. Starlink in advanced talks for Nigerian operating licence  Total Telecom
  5. Tech Tuesday: SpaceX makes record 10th launch of Falcon 9  KOIN 6

+

Posted on 10 May 2021 | 1:18 pm

+ +

v1.0 Powered by Mozilla Readability (Andres Rey PHP Port) and SimplePie

+ +
\ No newline at end of file diff --git a/examples/simple.htm b/examples/simple.htm new file mode 100644 index 0000000..a503562 --- /dev/null +++ b/examples/simple.htm @@ -0,0 +1,90 @@ + + + + +

H1 small header?

+

H1 header?

+ Test text + Strong text + + Hello world!
+ The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. + + + + + diff --git a/examples/sq.htm b/examples/sq.htm new file mode 100644 index 0000000..449bc1f --- /dev/null +++ b/examples/sq.htm @@ -0,0 +1,214 @@ + + + + + + + Space Quest - Wikipedia + + +

+

+ Back to FrogFind! | Browsing URL: + +
+

+
+

Space Quest - Wikipedia

+

+

+ +

+

Space Quest is a series of six comic science fiction adventure games released between 1986 and 1995. The games follow the adventures of a hapless janitor named Roger Wilco as he campaigns through the galaxy for "truth, justice and really clean floors". +

Initially created for Sierra On-Line by Mark Crowe and Scott Murphy (who called themselves the "Two Guys from Andromeda"), the games parodied both science fiction properties such as Star Wars and Star Trek (the theme song itself is a parody of the Star Wars theme), as well as pop-culture phenomena from McDonald's to Microsoft. The series featured a silly sense of humor heavily reliant on puns and wacky storylines. Roger Wilco, a perpetual loser, is often depicted as the underdog who repeatedly saves the universe (often by accident) - only to be either ignored or punished for violating minor regulations in the process. +

+ + +

Development[edit]

+

Scott Murphy and Mark Crowe, who had already worked together on the Sierra game The Black Cauldron, wanted to create a humorous science fiction adventure game. They also wanted it to star a janitor (a choice possibly inspired by the mop-wielding main character from Infocom's humorous sci-fi text adventure Planetfall). +

Murphy commented that "Sierra was in a mindset where everything was medieval and it was all fairly serious. I wanted to do a game that was more fun. We even liked the idea of 'fun death'! I mean, if the player is gonna die or fail, they should at least get a laugh out of it. So we came up with the idea of making death amusing. Let's face it, most adventure games involve a good deal of frustration for the player. But we felt that if we made failure fun, to an extent, you might have players actually going back and looking for new ways to die, just to see what happens!"[citation needed] +

Crowe noted, "We wanted to do two things for the player. One, we wanted him to feel as if he were in a movie, where he could just sort of kick back and enjoy the scenery. We also wanted the player to feel as if he really was the character on the screen."[citation needed] +

Although skeptical, Ken Williams gave the idea a shot. Scott and Mark created a short demo, which ended up becoming the first four rooms of Space Quest I, at which point Ken gave the project a green-light. +

Both Space Quest I and II were developed in Adventure Game Interpreter, Sierra's own programming language. Space Quest III was written in Sierra's Creative Interpreter (SCI), which had 3-D capabilities. Space Quest IV marked an evolution in terms of graphics by increasing the number of colors from 16 to 256 colors.[1] +

+

Roger Wilco[edit]

+

Roger Wilco is a fictional character and the protagonist of the Space Quest series, introduced in Space Quest: The Sarien Encounter in 1986. Roger is a bumbling if well-meaning everyman character, a spacefaring janitor who has a tendency to attract trouble and stumble into dangerous or interesting situations. Despite saving the universe on multiple occasions, he seems unable to gain any respect from society, and works as a "sanitation engineer" (in one form or another) throughout the series. +

The character's name is a reference to voice procedure, one of many puns in the series (it means "receiving you, will comply"). The first two Space Quest games allowed the player to choose the character's name, which defaulted to Roger Wilco if left blank. This feature was later removed in the remake of the first game. +

Roger is originally a janitor from the planet Xenon of the Earnon system. We first meet him as the janitor and sole survivor of the scientific research ship Arcada, which was overrun by the apparently hostile Sariens. After several extremely deadly adventures and a bit of janitorial work, he enters the StarCon Academy. Graduating in Space Quest V, he is promoted from a janitor to captain of the garbage scow SCS Eureka. He also meets Beatrice Creakworm Wankmeister, with whom he becomes romantically involved. In Space Quest 6, his spot in the limelight ends as he is busted back down to janitor and assigned to the backwoods of the cosmos. +

According to Space Quest IV, Roger would eventually marry Beatrice and they would have a son (Roger Wilco Jr.) who would later travel back in time to save Roger's life. Beatrice is absent from Space Quest 6, but she is mentioned in the game's closing credits and by Roger himself. By the time of the fictional Space Quest XII, when Roger Jr. would be a young adult, Roger would be "unavailable" for some reason. The details are never disclosed. +

While Roger retains his basic appearance and sustains no lasting damage from his swashbucklings and repeated near-mutilations, his hair begins the series brown and changes to blonde in the upgrade between parts III and IV. (The same has happened to fellow adventure protagonists Guybrush Threepwood and Devon Aidendale, in Devon's case to the other direction.) While this retcon is never addressed in the game itself, it spawned a full-fledged fangame, Space Quest: The Lost Chapter.[2] +

Including him on the 2004 list of "top ten working class heroes", Retro Gamer opined that "for a hero that Ken Williams (co-founder of Sierra) was initially unimpressed with, Roger Wilco has become a classic cult figure."[3] +

+

Games[edit]

+ +

Space Quest: The Sarien Encounter[edit]

+ +

The original Space Quest game was released in October 1986 and quickly became a hit, selling in excess of 100,000 copies (sales are believed to be around 200,000 to date, not including the many compilations it has been included in). A remake was released in 1991 as Space Quest I: Roger Wilco in the Sarien Encounter. +

+

Space Quest II: Vohaul's Revenge[edit]

+ +

Released in 1987; Roger, with his newfound status of Hero, is transferred to the Xenon Orbital Station 4 and promoted to head (and only) janitor. All is quiet until he is abducted by Sludge Vohaul, who was behind the original Sarien attack of the Arcada. As Roger is being transported to the Labion labour mines as punishment for thwarting Sludge's original plan, the prison ship crash-lands in a nearby jungle upon the planet. Our hero manages to escape his pursuers and the dangers of the Labion jungle and soon reaches Sludge's asteroid base. Once again, it's up to Roger alone to stop Vohaul's evil plan: to eradicate sentient life from Xenon by launching millions of cloned insurance salesmen at the planet. +

+

Space Quest III: The Pirates of Pestulon[edit]

+ +

Released in 1989; Roger's escape pod from the end of SQII is captured by an automated garbage freighter. He escapes the robot-controlled scow by repairing an old ship, the Aluminum Mallard (a play on Howard Hughes' "Spruce Goose" and Star Wars' Millennium Falcon). He eventually discovers the sinister activities of a video game company known as ScumSoft run by the "Pirates of Pestulon". +

+

Space Quest IV: Roger Wilco and the Time Rippers[edit]

+ +

Released in 1991; in this installment, Roger embarks on a time-travel adventure through Space Quest games both real and fictional. A reborn Sludge Vohaul from the fictional Space Quest XII: Vohaul's Revenge II chases Roger through time in an attempt to finally kill him. Roger also visits settings from the fictional Space Quest X: Latex Babes of Estros (whose title is a parody of Infocom's game Leather Goddesses of Phobos) and from Space Quest I; in the latter, the graphics and music revert to the style of the original game and Roger is threatened by a group of monochromatic bikers who consider Roger's 256 colors pretentious (or comment on other graphics modes if played in EGA or monochrome). +

The games Space Quest XII: Vohaul's Revenge II and Space Quest X: Latex Babes of Estros were never actually developed or released as full games, they exist only internally in Space Quest IV. +

+

Space Quest V: Roger Wilco - The Next Mutation[edit]

+ +

Released in 1993; in Space Quest V, Roger is now a cadet in the StarCon academy. He graduates (or rather, cheats through the final exam) and is appointed captain of his own spacecraft (actually a space garbage scow). The main plot is to stop a mutagenic disease that is spreading through the galaxy by discovering its source, and fighting everyone that got infected. In the end, the disease infected the crew members of the SCS Goliath, a powerful warship, whose commander, Raemes T. Quirk (a rather blatant spoof of Captain Kirk), subsequently attacks the Eureka. In the end, Roger sacrifices his ship to get rid of the plague - and suddenly, if temporarily, becomes the commander of the fleet's flagship. +

Roger's cheating is, along with Raemes T. Quirk, an homage to William Shatner's Star Trek character, who cheated on his own Starfleet exam by reprogramming a "no-win" scenario so that he could successfully complete it. In a typical twist of luck, however, Roger's exam scores are still achieved by accident. +

This entry was the first in the Space Quest series where only one of the two guys from andromeda, Mark Crowe, lead a Space Quest project, due to the fact that Scott Murphy was working on other projects. +

+

Space Quest 6: Roger Wilco in The Spinal Frontier[edit]

+ +

Released in 1995, this game was the last to be released in the Space Quest series. Having defeated the diabolical pukoid mutants in Space Quest V, Captain Roger Wilco triumphantly returns to StarCon headquarters - only to be court-martialed due to breaking StarCon regulations while saving the galaxy. He's busted down to Janitor Second Class, and assigned to the SCS DeepShip 86 (a parody of Star Trek: Deep Space Nine), commanded by Commander Kielbasa, a Cowardly Lion look-alike whose name is taken from the Polish sausage as well as being a play on the names of both the feline Kilrathi from the video game series Wing Commander and of the character Mufasa from the animated motion picture The Lion King. His voice is a parody of Captain Jean-Luc Picard from Star Trek: The Next Generation. The main villain in the game is a wrinkly old lady named Sharpei, a pun on the dog Shar Pei, a wrinkly dog. +

The game's subtitle comes from the final portion, in which Roger has to undergo miniaturization and enter the body of a shipmate and romantic interest. (This segment also provided the game's original subtitle, Where in Corpsman Santiago is Roger Wilco?, which was not used due to legal threats from the makers of the Carmen Sandiego products.)[citation needed] +

Sadly, once again, only one of the Two Guys from Andromeda worked on this game. This time though, it was Scott Murphy who sat in the director's chair, more or less. Scott was actually a co-director of Space Quest 6 with another Sierra employee, Josh Mandel, who'd worked on many of the behind-the-scenes aspects of Space Quest IV and V, as well as helping create the SCI remake of King's Quest I. Josh actually worked on and created the majority of Space Quest 6, and had to step out when the project was already near completion, and that's when Scott Murphy just stepped in and did the rest. +

+

Space Quest 6: The Spinal Frontier Interactive Demo[edit]

+ +

The demo for Space Quest 6 is actually a short game unto itself. It uses the SQ6 engine and takes place aboard the SCS DeepShip 86 but is a stand-alone adventure. The ship is taken over by Borg-like invaders called the Bjorn, and Wilco must defeat them. +

+

In-fiction future sequels[edit]

+

In Space Quest IV, Roger travels into both the past and future of the game's timeline. Even in-game characters are conscious of living in a video game, and refer to eras with sequel numbers, not temporal units (such as years), even though specific years are named elsewhere in the Space Quest canon. Portions of the game took place in the time frames of the following "sequels": +

+ +

These games were never actually created, and only exist within the plot of Space Quest IV. Scott Murphy has stated that he did intend to use these titles if the series had made it that far and the storyline still permitted it.[4] +

+

Roger Wilco's Spaced Out Game Pack[edit]

+

Budget software including several mini-games taken from the Space Quest series. Including hoverspeeder, Monolith Burger maker, and Ms. Astro Chicken. +

+

Planet Pinball[edit]

+

Planet Pinball is a series of three Space Quest IV themed pinball boards in Take a Break! Pinball. The boards include; Level One: Planet Xenon in the Beginning, Level Two: Spaced Travel, Level Three: Reformation Day. +

+

Hoyle Book of Games[edit]

+

Roger Wilco appears as an opponent in Hoyle's Official Book of Games, Volume I. He has conversations with the other opponents, talking about his adventures in the first three Space Quest games. Roger Wilco is trapped in the Hoyle game, and is trying to find a way to escape back to his game world. +

Roger Wilco returns in Hoyle 3, along with bad guy characters, Arnoid and Vohaul, but the characters are limited to talking about the game itself. +

Roger also appears as an opponent in Hoyle Classic Card Games, the fourth game in the series. Again interaction is limited to the game only. +

+

Cancelled games[edit]

+

Space Quest VII: Return to Roman Numerals[edit]

+

Sierra tried on several occasions to revive the series for another episode, with a working subtitle of The Return to Roman Numerals, since the previous game was titled Space Quest 6, not Space Quest VI. +

Development of Space Quest VII was underway in 1996 when Sierra released The Space Quest Collection, which consisted of Space Quest I through 6 and included a brief trailer of Space Quest VII (consisting of Roger strapping a giant rocket to his back and using it to push himself forward on roller skates in a scene reminiscent of Wile E. Coyote). Little was released regarding story line, interface, et cetera, although there was speculation that the game would introduce a multiplayer aspect. Scott Murphy said during development that Space Quest VII would contain some 3D elements, but would not require the use of a 3D accelerator card. Due to poor sales of Grim Fandango, a high-profile adventure game by LucasArts, there was a perception that humorous adventure games were no longer viable, so when Vivendi took over Sierra, Space Quest VII was cancelled. +

This project was eventually restarted in 1999, and pitched to management, but ultimately did not have enough support to continue within the company. Few details are known about the SQVII relaunch, save that there was one very ardent supporter, who later left Vivendi. +

+

Space Quest[edit]

+

Another Space Quest began development by Escape Factory for the Microsoft Xbox video game console in 2002, entitled simply Space Quest. +

This attempt at creating a new Space Quest was announced on February 7, 2002. Development proceeded for almost a year and a half before the project was cancelled. According to Space Quest 6 designer Josh Mandel, the SQVII designers were forbidden from using story elements from the original Space Quest games or from even playing the games. This is disputable, since other sources claimed the developers had played the games before. Website FYI.com, also claimed that this "gutted" SQVII would not have been an adventure game at all and would have been released only on game console platforms such as the Xbox rather than the PC. Since then the Vivendi's Product Manager Bruce Goodwill, has confirmed that the title was going to be released only on console platforms. +

The game was planned as a departure from the main Space Quest series, rumors it starred a new character named "Wilger", although Roger Wilco was playable (as seen in a production video). Though it would have maintained a comedic theme in space, no plan was made to connect it to the original series. It was cancelled around 2003. +

+

Collections[edit]

+ +

Collection bonus material[edit]

+ +

Fall 2006 releases[edit]

+

Vivendi Universal has re-released the Space Quest Collection (originally named Space Quest Compilation) that is compatible with Windows XP. The collection was released September 15, 2006.[5] +

The Space Quest games were made compatible by the licensing of DOSBox, a free program that allows users to play old DOS games on Windows XP. Valve's digital distribution platform re-released the XP/Vista compatible Space Quest Collection on July 23, 2009. It is so far unavailable in Australia and New Zealand. +

+

Other media[edit]

+

Space Quest merchandise included the Space Quest III VHS tape and pin, Space Quest 6 mug, calling card and patch and an autographed picture of Roger Wilco.[6][7] +

Two strategy guides were released that contained novelizations of the first five games from Roger Wilco's perspective. +

The first of these included The Space Quest Companion by Peter and Jeremy Spear. The book is similar to Peter Spear's The King's Quest Companion and The Official Uncensored Leisure Suit Larry Bedside Companion. The first edition covered the first four games (with a preview of SQ5), and the second added the fifth game. It was written from the perspective of Roger Wilco sending journals on disks back into the past, so that his adventures could be made into video games so that his great grand parents (x-times removed) would have a chance to meet each other and fall in love through their mutual love of the games. Thus by inspiring the game designers to create the games, he insured his own future existence. Each story began with Roger's daydreams and his fantasies of marrying Cornucopia Agricorp and later Beatrice Wankmeister. +

The other was The Official Guide To Roger Wilco's Space Adventures by Jill Champion. It is similar to her The Official Book of Police Quest. It came in two editions as well. The book contains two interviews with Roger Wilco (one just after events of SQIV, and the other after SQV). The novels themselves are written as Roger's running monologues during his adventures. The first edition covers SQ EGA to SQIV, and the second edition covers SQI remake to SQV. The novel of SQ1 in the first edition is based on the original SQ1, and the version in the second edition is based on the remake of SQ1. +

Adventure Comics (a division of Malibu Comics) released three issues in 1992 of a comic based on Space Quest I under the name The Adventures of Roger Wilco. The first was written by John Shaw and was in full colour. The other two were written by Paul O'Connor and were black and white. The print run was very small and the books are very hard to find now. +

+

Legacy[edit]

+

Thy Dungeonman II, a text adventure game from the creators of Homestar Runner, uses cover art that depicts the title character holding a mop in the same way Roger Wilco does on the Space Quest box art. He is also described as a "custodial knight" and the mop is also used to defeat enemies in a maze portion of the game.[8] +

+

Fan-made games[edit]

+

The series has remained popular with Sierra fans, and several fan sites are still active and maintain a community dedicated to the games. There have been several attempts to create a Space Quest fan game, such as the now-canceled SQ7.org project, and several fan games have actually been released. +

Games set in the Space Quest universe: +

+ +

Games influenced by Space Quest: +

+ +

SpaceVenture[edit]

+

On April 14, 2012, Mark Crowe and Scott Murphy announced they had reunited and were planning an original adventure game set in space.[15] They established a new game development company called Two Guys from Andromeda for that purpose. Co-founder of the company Chris Pope (dubbed as "Space Pope" from fans) works to operate its marketing and interact directly with fans as well as Executive Producer.[16] A Kickstarter project was launched to fund the development of the new game or SpaceVenture, with plans to feature the voice of Gary Owens (narrator of Space Quest IV and 6),[17] prior to his death in 2015.[18] +

As of June 13, 2012, they achieved their goal of $500,000 eventually raising $539,768 from their Kickstarter campaign, and have begun work on the game.[15] As of September 2012, enough funding has been achieved to enable them to translate the game into German, Spanish, French and Italian. +

The game acts as a spiritual successor to the Space Quest series, including the use of a blue-collar worker (now a repairman rather than a janitor) as its protagonist. The hero in this case is named "Ace Hardway" after the home improvement chain Ace Hardware. Another character is "Cluck Y'egger", after test pilot Chuck Yeager. He is a chicken superhero based on the Astro Chicken running gag in the Space Quest series. The game will be rendered in CGI, but a "retro graphics" feature has been proposed. +

In June 2013, it was announced that there would be a playable Alpha demo at the 2013 San Diego Comicon.[19] +

In January 2014, the game had a projected late 2015 release date.[20][better source needed] In October 2015, the Two Guys of Andromeda promised to continue development and to release the game in November 2016.[21][better source needed] The game remains in development, but has still (as of July 2020, over eight years after reaching its funding goal) not been given a release date. On June 2, 2019, an update was released on the Two Guys from Andromeda Twitter and Kickstarter pages revealing the box art for the game.[citation needed] +

On July 31st, 2020, a complete beta was released to Kickstarter backers.[22] +

+

See also[edit]

+ +

References[edit]

+ +
    +
  1. ^ Champion, Jill; Leinecker, Richard C. (1991). The Official Guide to Roger Wilco Space Adventures. Compute Books. ISBN 0-87455-237-0. +
  2. +
  3. ^ Neal Roger Tringham (10 September 2014). Science Fiction Video Games. CRC Press. pp. 119-. ISBN 978-1-4822-0389-9. +
  4. +
  5. ^ Retro Gamer, p. 35. +
  6. +
  7. ^ Murphy, Scott (2019-03-29). "Thank you! Yes, unless we'd effed it up so badly in subsequent sequels that they'd already been designed out, but I did indeed imagine this". @slashvohaul. Retrieved 2019-03-30. +
  8. +
  9. ^ More info can be found at Sierra's Space Quest Collection +
  10. +
  11. ^ "Space Quest Rarities". Wiw.org. Retrieved 2012-05-25. +
  12. +
  13. ^ "Goodies". SpaceQuest.Net. Retrieved 2012-05-25. +
  14. +
  15. ^ "Comes with 3 volume set of Thy Encyclopedias and Reference Guide!". Videlectrix.com. Retrieved 2012-11-12. +
  16. +
  17. ^ "Space Quest 0: Replicated - Downloads, Hints, and More!". Wiw.org. Retrieved 2012-11-12. +
  18. +
  19. ^ "Space Quest IV.5 Roger Wilco and The Voyage Home". Spacequestiv5.pytalhost.at. Retrieved 2012-11-12. +
  20. +
  21. ^ "Infamous Adventures". Infamous Adventures. Retrieved 2019-11-10. +
  22. +
  23. ^ "Teasing Cats since 1997". Box of Mystery. 2012-01-11. Retrieved 2012-11-12. +
  24. +
  25. ^ "Space Quest: Roger Wilco Not Over And Out". Rock, Paper, Shotgun. Retrieved 2012-11-12. +
  26. +
  27. ^ "PC Adventure Game. Official Website. Good Old Adventure Games". Cosmos Quest. Retrieved 2012-11-12. +
  28. +
  29. ^ a b "The Two Guys from Andromeda have returned!". Retrieved 14 April 2012. +
  30. +
  31. ^ Adam Rosenberg (May 9, 2012). "Space Quest Creators Turn To Kickstarter For SpaceVenture". G4tv.com. Retrieved August 1, 2012. +
  32. +
  33. ^ "Two Guys SpaceVenture by the Creators of Space Quest". Retrieved August 9, 2012. +
  34. +
  35. ^ "Gary Owens, Droll Announcer on 'Laugh-In,' Dies at 80". Retrieved 22 July 2015. +
  36. +
  37. ^ Kruse, Cord (18 June 2013). "Two Guys SpaceVenture Demo Status Update". Inside Mac Games. Retrieved 1 October 2014. +
  38. +
  39. ^ January 1, 2014 SpaceVenture Update. +
  40. +
  41. ^ September 30, 2015 SpaceVenture Update. +
  42. +
  43. ^ https://www.kickstarter.com/projects/spaceventure/two-guys-spaceventure-by-the-creators-of-space-que/posts/2909932 +
  44. +
+ + + + + + + + +

+ + \ No newline at end of file diff --git a/examples/win1250.htm b/examples/win1250.htm new file mode 100644 index 0000000..81d1620 --- /dev/null +++ b/examples/win1250.htm @@ -0,0 +1,141 @@ + + + + + + Windows-1250 Extended Characters + + +

Windows-1250 Extended Characters

+
+     EURO SIGN (U+20AC)
+    UNUSED
+     SINGLE LOW-9 QUOTATION MARK (U+201A)
+    UNUSED
+     DOUBLE LOW-9 QUOTATION MARK (U+201E)
+     HORIZONTAL ELLIPSIS (U+2026)
+     DAGGER (U+2020)
+     DOUBLE DAGGER (U+2021)
+    UNUSED
+     PER MILLE SIGN (U+2030)
+     LATIN CAPITAL LETTER S WITH CARON (U+0160)
+     SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)
+     LATIN CAPITAL LETTER S WITH ACUTE (U+015A)
+     LATIN CAPITAL LETTER T WITH CARON (U+0164)
+     LATIN CAPITAL LETTER Z WITH CARON (U+017D)
+     LATIN CAPITAL LETTER Z WITH ACUTE (U+0179)
+    UNUSED
+     LEFT SINGLE QUOTATION MARK (U+2018)
+     RIGHT SINGLE QUOTATION MARK (U+2019)
+     LEFT DOUBLE QUOTATION MARK (U+201C)
+     RIGHT DOUBLE QUOTATION MARK (U+201D)
+     BULLET (U+2022)
+     EN DASH (U+2013)
+     EM DASH (U+2014)
+    UNUSED
+     TRADE MARK SIGN (U+2122)
+     LATIN SMALL LETTER S WITH CARON (U+0161)
+     SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)
+     LATIN SMALL LETTER S WITH ACUTE (U+015B)
+     LATIN SMALL LETTER T WITH CARON (U+0165)
+     LATIN SMALL LETTER Z WITH CARON (U+017E)
+     LATIN SMALL LETTER Z WITH ACUTE (U+017A)
+     NO-BREAK SPACE (U+00A0)
+     CARON (U+02C7)
+     BREVE (U+02D8)
+     LATIN CAPITAL LETTER L WITH STROKE (U+0141)
+     CURRENCY SIGN (U+00A4)
+     LATIN CAPITAL LETTER A WITH OGONEK (U+0104)
+     BROKEN BAR (U+00A6)
+     SECTION SIGN (U+00A7)
+     DIAERESIS (U+00A8)
+     COPYRIGHT SIGN (U+00A9)
+     LATIN CAPITAL LETTER S WITH CEDILLA (U+015E)
+     LEFT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00AB)
+     NOT SIGN (U+00AC)
+    SOFT HYPHEN (U+00AD)
+     REGISTERED SIGN (U+00AE)
+     LATIN CAPITAL LETTER Z WITH DOT ABOVE (U+017B)
+     DEGREE SIGN (U+00B0)
+     PLUS-MINUS SIGN (U+00B1)
+     OGONEK (U+02DB)
+     LATIN SMALL LETTER L WITH STROKE (U+0142)
+     ACUTE ACCENT (U+00B4)
+     MICRO SIGN (U+00B5)
+     PILCROW SIGN (U+00B6)
+     MIDDLE DOT (U+00B7)
+     CEDILLA (U+00B8)
+     LATIN SMALL LETTER A WITH OGONEK (U+0105)
+     LATIN SMALL LETTER S WITH CEDILLA (U+015F)
+     RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00BB)
+     LATIN CAPITAL LETTER L WITH CARON (U+013D)
+     DOUBLE ACUTE ACCENT (U+02DD)
+     LATIN SMALL LETTER L WITH CARON (U+013E)
+     LATIN SMALL LETTER Z WITH DOT ABOVE (U+017C)
+     LATIN CAPITAL LETTER R WITH ACUTE (U+0154)
+     LATIN CAPITAL LETTER A WITH ACUTE (U+00C1)
+     LATIN CAPITAL LETTER A WITH CIRCUMFLEX (U+00C2)
+     LATIN CAPITAL LETTER A WITH BREVE (U+0102)
+     LATIN CAPITAL LETTER A WITH DIAERESIS (U+00C4)
+     LATIN CAPITAL LETTER L WITH ACUTE (U+0139)
+     LATIN CAPITAL LETTER C WITH ACUTE (U+0106)
+     LATIN CAPITAL LETTER C WITH CEDILLA (U+00C7)
+     LATIN CAPITAL LETTER C WITH CARON (U+010C)
+     LATIN CAPITAL LETTER E WITH ACUTE (U+00C9)
+     LATIN CAPITAL LETTER E WITH OGONEK (U+0118)
+     LATIN CAPITAL LETTER E WITH DIAERESIS (U+00CB)
+     LATIN CAPITAL LETTER E WITH CARON (U+011A)
+     LATIN CAPITAL LETTER I WITH ACUTE (U+00CD)
+     LATIN CAPITAL LETTER I WITH CIRCUMFLEX (U+00CE)
+     LATIN CAPITAL LETTER D WITH CARON (U+010E)
+     LATIN CAPITAL LETTER D WITH STROKE (U+0110)
+     LATIN CAPITAL LETTER N WITH ACUTE (U+0143)
+     LATIN CAPITAL LETTER N WITH CARON (U+0147)
+     LATIN CAPITAL LETTER O WITH ACUTE (U+00D3)
+     LATIN CAPITAL LETTER O WITH CIRCUMFLEX (U+00D4)
+     LATIN CAPITAL LETTER O WITH DOUBLE ACUTE (U+0150)
+     LATIN CAPITAL LETTER O WITH DIAERESIS (U+00D6)
+     MULTIPLICATION SIGN (U+00D7)
+     LATIN CAPITAL LETTER R WITH CARON (U+0158)
+     LATIN CAPITAL LETTER U WITH RING ABOVE (U+016E)
+     LATIN CAPITAL LETTER U WITH ACUTE (U+00DA)
+     LATIN CAPITAL LETTER U WITH DOUBLE ACUTE (U+0170)
+     LATIN CAPITAL LETTER U WITH DIAERESIS (U+00DC)
+     LATIN CAPITAL LETTER Y WITH ACUTE (U+00DD)
+     LATIN CAPITAL LETTER T WITH CEDILLA (U+0162)
+     LATIN SMALL LETTER SHARP S (U+00DF)
+     LATIN SMALL LETTER R WITH ACUTE (U+0155)
+     LATIN SMALL LETTER A WITH ACUTE (U+00E1)
+     LATIN SMALL LETTER A WITH CIRCUMFLEX (U+00E2)
+     LATIN SMALL LETTER A WITH BREVE (U+0103)
+     LATIN SMALL LETTER A WITH DIAERESIS (U+00E4)
+     LATIN SMALL LETTER L WITH ACUTE (U+013A)
+     LATIN SMALL LETTER C WITH ACUTE (U+0107)
+     LATIN SMALL LETTER C WITH CEDILLA (U+00E7)
+     LATIN SMALL LETTER C WITH CARON (U+010D)
+     LATIN SMALL LETTER E WITH ACUTE (U+00E9)
+     LATIN SMALL LETTER E WITH OGONEK (U+0119)
+     LATIN SMALL LETTER E WITH DIAERESIS (U+00EB)
+     LATIN SMALL LETTER E WITH CARON (U+011B)
+     LATIN SMALL LETTER I WITH ACUTE (U+00ED)
+     LATIN SMALL LETTER I WITH CIRCUMFLEX (U+00EE)
+     LATIN SMALL LETTER D WITH CARON (U+010F)
+     LATIN SMALL LETTER D WITH STROKE (U+0111)
+     LATIN SMALL LETTER N WITH ACUTE (U+0144)
+     LATIN SMALL LETTER N WITH CARON (U+0148)
+     LATIN SMALL LETTER O WITH ACUTE (U+00F3)
+     LATIN SMALL LETTER O WITH CIRCUMFLEX (U+00F4)
+     LATIN SMALL LETTER O WITH DOUBLE ACUTE (U+0151)
+     LATIN SMALL LETTER O WITH DIAERESIS (U+00F6)
+     DIVISION SIGN (U+00F7)
+     LATIN SMALL LETTER R WITH CARON (U+0159)
+     LATIN SMALL LETTER U WITH RING ABOVE (U+016F)
+     LATIN SMALL LETTER U WITH ACUTE (U+00FA)
+     LATIN SMALL LETTER U WITH DOUBLE ACUTE (U+0171)
+     LATIN SMALL LETTER U WITH DIAERESIS (U+00FC)
+     LATIN SMALL LETTER Y WITH ACUTE (U+00FD)
+     LATIN SMALL LETTER T WITH CEDILLA (U+0163)
+     DOT ABOVE (U+02D9)
+
+ + \ No newline at end of file diff --git a/floppy/freedos.boot.disk.360K.img b/floppy/freedos.boot.disk.360K.img index fccb7cc..a97a539 100644 Binary files a/floppy/freedos.boot.disk.360K.img and b/floppy/freedos.boot.disk.360K.img differ diff --git a/floppy/freedos.boot.disk.720K.img b/floppy/freedos.boot.disk.720K.img index 3533c89..ca476ca 100644 Binary files a/floppy/freedos.boot.disk.720K.img and b/floppy/freedos.boot.disk.720K.img differ diff --git a/project/AssetGen/AssetGen.vcxproj b/project/AssetGen/AssetGen.vcxproj index cbebb86..49ccc20 100644 --- a/project/AssetGen/AssetGen.vcxproj +++ b/project/AssetGen/AssetGen.vcxproj @@ -144,6 +144,7 @@ + diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 1f9535d..ec8c7ba 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -96,45 +96,60 @@ - + + + - + - - - + + + + + + + + + + + - + - + - + - + + + + + + + + - - diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 36cf959..6539e1f 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,10 +1,11 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Style.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj ListItem.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj CheckBox.obj Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Surf1512.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj Jpeg.obj Png.obj MemBlock.obj Memory.obj EMS.obj memory_model = -ml CC = wpp -CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) +CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h +# CFLAGS = -zq -0 -oh -ok -ot -oa -s -ei -ol+ -oi+ -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h LD = wlink # begin mTCP stuff @@ -21,63 +22,159 @@ tcp_compile_options += -i=$(tcp_h_dir) .asm : $(tcp_c_dir) .asm.obj : - wasm -0 $(memory_model) $[* + wasm -0 $(memory_model) $[* .cpp.obj : - wpp $[* $(tcp_compile_options) + wpp $[* $(tcp_compile_options) # end mTCP stuff $(bin): $(objects) $(tcpobjs) - $(LD) system dos name $@ file { $(objects) $(tcpobjs) } + $(LD) system dos name $@ file { $(objects) $(tcpobjs) } MicroWeb.obj: $(SRC_PATH)\MicroWeb.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< App.obj: $(SRC_PATH)\App.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< Parser.obj: $(SRC_PATH)\Parser.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< -Renderer.obj: $(SRC_PATH)\Renderer.cpp - $(CC) -fo=$@ $(CFLAGS) $< +Render.obj: $(SRC_PATH)\Render.cpp + $(CC) -fo=$@ $(CFLAGS) $< Tags.obj: $(SRC_PATH)\Tags.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< Page.obj: $(SRC_PATH)\Page.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< Platform.obj: $(SRC_PATH)\DOS\Platform.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< + +HTTP.obj: $(SRC_PATH)\HTTP.cpp + $(CC) -fo=$@ $(CFLAGS) $< Font.obj: $(SRC_PATH)\Font.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< + +Style.obj: $(SRC_PATH)\Style.cpp + $(CC) -fo=$@ $(CFLAGS) $< Interface.obj: $(SRC_PATH)\Interface.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< -CGA.obj: $(SRC_PATH)\DOS\CGA.cpp - $(CC) -fo=$@ $(CFLAGS) $< +Layout.obj: $(SRC_PATH)\Layout.cpp + $(CC) -fo=$@ $(CFLAGS) $< -Hercules.obj: $(SRC_PATH)\DOS\Hercules.cpp - $(CC) -fo=$@ $(CFLAGS) $< +Node.obj: $(SRC_PATH)\Node.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +ImgNode.obj: $(SRC_PATH)\Nodes\ImgNode.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +StyNode.obj: $(SRC_PATH)\Nodes\StyNode.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +LinkNode.obj: $(SRC_PATH)\Nodes\LinkNode.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Block.obj: $(SRC_PATH)\Nodes\Block.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Break.obj: $(SRC_PATH)\Nodes\Break.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +CheckBox.obj: $(SRC_PATH)\Nodes\CheckBox.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +ListItem.obj: $(SRC_PATH)\Nodes\ListItem.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Section.obj: $(SRC_PATH)\Nodes\Section.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Text.obj: $(SRC_PATH)\Nodes\Text.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Form.obj: $(SRC_PATH)\Nodes\Form.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Status.obj: $(SRC_PATH)\Nodes\Status.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Scroll.obj: $(SRC_PATH)\Nodes\Scroll.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Field.obj: $(SRC_PATH)\Nodes\Field.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Table.obj: $(SRC_PATH)\Nodes\Table.cpp + $(CC) -fo=$@ $(CFLAGS) $< -Olivetti.obj: $(SRC_PATH)\DOS\Olivetti.cpp - $(CC) -fo=$@ $(CFLAGS) $< +Button.obj: $(SRC_PATH)\Nodes\Button.cpp + $(CC) -fo=$@ $(CFLAGS) $< -EGA.obj: $(SRC_PATH)\DOS\EGA.cpp - $(CC) -fo=$@ $(CFLAGS) $< +Select.obj: $(SRC_PATH)\Nodes\Select.cpp + $(CC) -fo=$@ $(CFLAGS) $< -TextMode.obj: $(SRC_PATH)\DOS\TextMode.cpp - $(CC) -fo=$@ $(CFLAGS) $< +DataPack.obj: $(SRC_PATH)\DataPack.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Decoder.obj: $(SRC_PATH)\Image\Decoder.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Gif.obj: $(SRC_PATH)\Image\Gif.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Png.obj: $(SRC_PATH)\Image\Png.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Jpeg.obj: $(SRC_PATH)\Image\Jpeg.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +MemBlock.obj: $(SRC_PATH)\Memory\MemBlock.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Memory.obj: $(SRC_PATH)\Memory\Memory.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +EMS.obj: $(SRC_PATH)\DOS\EMS.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +VidModes.obj: $(SRC_PATH)\VidModes.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Colour.obj: $(SRC_PATH)\Colour.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +BIOSVid.obj: $(SRC_PATH)\DOS\BIOSVid.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Hercules.obj: $(SRC_PATH)\DOS\Hercules.cpp + $(CC) -fo=$@ $(CFLAGS) $< DOSInput.obj: $(SRC_PATH)\DOS\DOSInput.cpp - $(CC) -fo=$@ $(CFLAGS) $< + $(CC) -fo=$@ $(CFLAGS) $< DOSNet.obj: $(SRC_PATH)\DOS\DOSNet.cpp - $(CC) -fo=$@ $(CFLAGS) -i=$(tcp_h_dir) -DCFG_H="tcp.cfg" $< + $(CC) -fo=$@ $(CFLAGS) -i=$(tcp_h_dir) -DCFG_H="tcp.cfg" $< + +Surf1bpp.obj: $(SRC_PATH)\Draw\Surf1bpp.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Surf2bpp.obj: $(SRC_PATH)\Draw\Surf2bpp.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Surf4bpp.obj: $(SRC_PATH)\DOS\Surf4bpp.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Surf8bpp.obj: $(SRC_PATH)\Draw\Surf8bpp.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Surf1512.obj: $(SRC_PATH)\DOS\Surf1512.cpp + $(CC) -fo=$@ $(CFLAGS) $< clean: .symbolic del *.obj diff --git a/project/DOS/RESULT2.HTM b/project/DOS/RESULT2.HTM new file mode 100644 index 0000000..e81394f --- /dev/null +++ b/project/DOS/RESULT2.HTM @@ -0,0 +1,58 @@ +test - Google Search
Find out how get a test to check if you have coronavirus (COVID-19), what testing involves and understand your test result. +Test at home or at a test site Test results How to do a rapid lateral flow test
NHS information about what to do if you're told by the NHS Test and Trace service that you've been in contact with someone who has coronavirus (COVID- 19)...
Choose a drive-through or walk-through test centre for a quick test, or order a home test kit.
Use Speedtest on all your devices with our free desktop and mobile apps.
3 hours ago Day Two Report | Live scoreboard from second Test; England 37 runs ahead with only one wicket remaining after familiar failings cause...
How fast is your download speed? In seconds, FAST.com's simple internet speed test will estimate your ISP speed.
Test and Protect is an approach designed to prevent the spread of coronavirus ( COVID-19) in the community in Scotland.
Common questions
For informational purposes only. Consult your local medical authority for health advice.
Test My Site. Improve your mobile site to boost your business. error_outline Sorry ! We couldn't find that domain. Please double check that you entered a valid...
Test(s), testing, or TEST may refer to: Test (assessment), an educational assessment intended to measure the respondents' knowledge or other abilities...
\ No newline at end of file diff --git a/project/DOS/TCP.CFG b/project/DOS/TCP.CFG index ccfc4f2..78c01b2 100644 --- a/project/DOS/TCP.CFG +++ b/project/DOS/TCP.CFG @@ -90,6 +90,7 @@ #undef TCP_MAX_SOCKETS #define PACKET_BUFFERS (20) // Number of incoming buffers -#define TCP_MAX_SOCKETS (3) // Maximum number of sockets to use +//#define TCP_MAX_SOCKETS (3) // Maximum number of sockets to use +#define TCP_MAX_SOCKETS (2) // Maximum number of sockets to use #endif diff --git a/project/DOS/hp95lx.mak b/project/DOS/hp95lx.mak index 0d05ecb..4e10c05 100644 --- a/project/DOS/hp95lx.mak +++ b/project/DOS/hp95lx.mak @@ -4,7 +4,7 @@ OBJDIR=obj objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj HP95LX.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj memory_model = -ml CC = wpp -CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -dHP95LX +CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -dHP95LX -fi=$(SRC_PATH)\Defines.h LD = wlink # begin mTCP stuff diff --git a/project/DOS/rebuild.bat b/project/DOS/rebuild.bat index 5e1c939..db04c26 100644 --- a/project/DOS/rebuild.bat +++ b/project/DOS/rebuild.bat @@ -6,3 +6,5 @@ set WIPFC=c:\watcom\wipfc set LIBDOS=c:\watcom\lib286\dos;c:\watcom\lib286 wmake.exe clean wmake.exe +rem wmake -f hp95lx.mak clean +rem wmake -f hp95lx.mak diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 6040a5a..f349e79 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -23,7 +23,7 @@ Win32Proj {05554dc2-eb0d-4107-856c-c5be0836f410} Windows - 10.0 + 10.0.18362.0 @@ -42,8 +42,8 @@ Application true - v142 Unicode + v142 Application @@ -114,7 +114,7 @@ Level3 true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _DEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32_LEAN_AND_MEAN true @@ -128,7 +128,7 @@ true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + NDEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32_LEAN_AND_MEAN true
@@ -140,32 +140,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + diff --git a/screenshot.gif b/screenshot.gif new file mode 100644 index 0000000..4c5dae8 Binary files /dev/null and b/screenshot.gif differ diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index c9581ce..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/screenshot_vga.png b/screenshot_vga.png deleted file mode 100644 index 91c812f..0000000 Binary files a/screenshot_vga.png and /dev/null differ diff --git a/src/App.cpp b/src/App.cpp index d93f99b..1162179 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -15,34 +15,47 @@ #include #include "App.h" #include "Platform.h" +#include "HTTP.h" +#include "Image/Decoder.h" + +App* App::app; +AppConfig App::config; App::App() - : page(*this), renderer(*this), parser(page), ui(*this) + : page(*this), pageRenderer(*this), parser(page), ui(*this) { + app = this; requestedNewPage = false; - pageHistorySize = 0; - pageHistoryPos = -1; + + memset(pageHistoryBuffer, 0, MAX_PAGE_HISTORY_BUFFER_SIZE); + pageHistoryPtr = pageHistoryBuffer; } App::~App() { - + app = NULL; } void App::ResetPage() { + StylePool::Get().Reset(); page.Reset(); - renderer.Reset(); parser.Reset(); + pageRenderer.Reset(); ui.Reset(); + pageRenderer.RefreshAll(); } void App::Run(int argc, char* argv[]) { running = true; + char* targetURL = nullptr; - renderer.Init(); + config.loadImages = true; + config.dumpPage = false; + config.useSwap = false; + config.useEMS = true; if (argc > 1) { @@ -50,68 +63,167 @@ void App::Run(int argc, char* argv[]) { if (*argv[n] != '-') { - OpenURL(argv[n]); + targetURL = argv[n]; break; } + else if (!stricmp(argv[n], "-noimages")) + { + config.loadImages = false; + } + else if (!stricmp(argv[n], "-dumppage")) + { + config.dumpPage = true; + } + else if (!stricmp(argv[n], "-i")) + { + config.invertScreen = true; + } + else if (!stricmp(argv[n], "-useswap")) + { + config.useSwap = true; + } + else if (!stricmp(argv[n], "-noems")) + { + config.useEMS = false; + } } } + MemoryManager::pageBlockAllocator.Init(); + + if (config.loadImages) + { + ImageDecoder::Allocate(); + } + + StylePool::Get().Init(); + ui.Init(); + page.Reset(); + pageRenderer.Init(); + + if (targetURL) + { + OpenURL(targetURL); + } + else + { + ui.FocusNode(ui.addressBarNode); + } + while (running) { Platform::Update(); - renderer.Update(); - if (loadTask.HasContent()) + if (pageLoadTask.HasContent()) { if (requestedNewPage) { ResetPage(); requestedNewPage = false; - page.pageURL = loadTask.GetURL(); + page.pageURL = pageLoadTask.GetURL(); ui.UpdateAddressBar(page.pageURL); + loadTaskTargetNode = page.GetRootNode(); + ui.SetStatusMessage("Parsing page content...", StatusBarNode::GeneralStatus); } - static char buffer[256]; - - size_t bytesRead = loadTask.GetContent(buffer, 256); + size_t bytesRead = pageLoadTask.GetContent(loadBuffer, APP_LOAD_BUFFER_SIZE); if (bytesRead) { - parser.Parse(buffer, bytesRead); + if (pageLoadTask.debugDumpFile) + { + fwrite(loadBuffer, 1, bytesRead, pageLoadTask.debugDumpFile); + } + + parser.Parse(loadBuffer, bytesRead); } } else { if (requestedNewPage) { - if (loadTask.type == LoadTask::RemoteFile) + if (pageLoadTask.type == LoadTask::RemoteFile) { - if (!loadTask.request) + if (!pageLoadTask.request) { - ShowErrorPage("No network interface available"); + if (Platform::network->IsConnected()) + { + ShowErrorPage("Failed to make network request"); + } + else + { + ShowErrorPage("No network interface available"); + } requestedNewPage = false; } - else if (loadTask.request->GetStatus() == HTTPRequest::Error) + else if (pageLoadTask.request->GetStatus() == HTTPRequest::Error) { - ShowErrorPage(loadTask.request->GetStatusString()); + ShowErrorPage(pageLoadTask.request->GetStatusString()); requestedNewPage = false; } - else if (loadTask.request->GetStatus() == HTTPRequest::UnsupportedHTTPS) + else if (pageLoadTask.request->GetStatus() == HTTPRequest::UnsupportedHTTPS) { ShowNoHTTPSPage(); requestedNewPage = false; } - } + } + else if (pageLoadTask.type == LoadTask::LocalFile) + { + ui.UpdateAddressBar(pageLoadTask.GetURL()); + ShowErrorPage("File not found"); + requestedNewPage = false; + } + } + else if (!parser.IsFinished()) + { + parser.Finish(); + } + } + + if (pageContentLoadTask.HasContent()) + { + size_t bytesRead = pageContentLoadTask.GetContent(loadBuffer, APP_LOAD_BUFFER_SIZE); + if (bytesRead) + { + bool stillProcessing = loadTaskTargetNode->Handler().ParseContent(loadTaskTargetNode, loadBuffer, bytesRead); + if (!stillProcessing) + { + pageContentLoadTask.Stop(); + } + } + } + else if(!pageContentLoadTask.IsBusy()) + { + if (loadTaskTargetNode) + { + loadTaskTargetNode->Handler().FinishContent(loadTaskTargetNode, pageContentLoadTask); + loadTaskTargetNode = page.ProcessNextLoadTask(loadTaskTargetNode, pageContentLoadTask); + + if (!loadTaskTargetNode && page.layout.IsFinished()) + { + if (MemoryManager::pageAllocator.GetError()) + { + page.GetApp().ui.SetStatusMessage("Out of memory when loading page", StatusBarNode::GeneralStatus); + } + else + { + page.GetApp().ui.ClearStatusMessage(StatusBarNode::GeneralStatus); + } + } } } - if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) - renderer.SetStatus(loadTask.request->GetStatusString()); + //if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) + // ui.SetStatusMessage(loadTask.request->GetStatusString()); + page.layout.Update(); + pageRenderer.Update(); ui.Update(); } } void LoadTask::Load(const char* targetURL) { + Stop(); + url = targetURL; // Check for protocol substring @@ -127,6 +239,9 @@ void LoadTask::Load(const char* targetURL) else if (strstr(url.url, "https://") == url.url) { type = LoadTask::RemoteFile; + + // Bit of a hack: try forcing http:// first + strcpy(url.url + 4, url.url + 5); } else if (strstr(url.url, "://")) { @@ -147,28 +262,50 @@ void LoadTask::Load(const char* targetURL) } else { - // Assume this should be http:// - type = LoadTask::RemoteFile; - strcpy(url.url, "http://"); - strcpy(url.url + 7, targetURL); + // did this start with X:\ ? + if (targetURL[0] >= 'A' && targetURL[0] <= 'z' && targetURL[1] == ':' && targetURL[2] == '\\') + { + type = LoadTask::LocalFile; + fs = nullptr; + } + else + { + // Assume this should be http:// + type = LoadTask::RemoteFile; + strcpy(url.url, "http://"); + strcpy(url.url + 7, targetURL); + } } } + url.CleanUp(); + if (type == LoadTask::RemoteFile) { request = Platform::network->CreateRequest(url.url); + + if (App::config.dumpPage && this == &App::Get().pageLoadTask) + { + debugDumpFile = fopen("dump.htm", "wb"); + } } } void LoadTask::Stop() { + if (debugDumpFile) + { + fclose(debugDumpFile); + debugDumpFile = NULL; + } + switch (type) { case LoadTask::LocalFile: if (fs) { fclose(fs); - fs = NULL; + fs = nullptr; } break; case LoadTask::RemoteFile: @@ -176,7 +313,7 @@ void LoadTask::Stop() { request->Stop(); Platform::network->DestroyRequest(request); - request = NULL; + request = nullptr; } break; } @@ -191,6 +328,12 @@ const char* LoadTask::GetURL() return url.url; } +bool LoadTask::IsBusy() +{ + bool isStillConnecting = type == LoadTask::RemoteFile && request && request->GetStatus() == HTTPRequest::Connecting; + return isStillConnecting || HasContent(); +} + bool LoadTask::HasContent() { if (type == LoadTask::LocalFile) @@ -208,16 +351,29 @@ size_t LoadTask::GetContent(char* buffer, size_t count) { if (type == LoadTask::LocalFile) { - if (fs && !feof(fs)) + if (fs) { - return fread(buffer, 1, count, fs); + if (!feof(fs)) + { + return fread(buffer, 1, count, fs); + } + fclose(fs); } } else if (type == LoadTask::RemoteFile) { - if (request && request->GetStatus() == HTTPRequest::Downloading) - { - return request->ReadData(buffer, count); + if (request) + { + switch (request->GetStatus()) + { + case HTTPRequest::Downloading: + return request->ReadData(buffer, count); + case HTTPRequest::Error: + case HTTPRequest::Finished: + case HTTPRequest::Stopped: + Stop(); + break; + } } } return 0; @@ -225,53 +381,74 @@ size_t LoadTask::GetContent(char* buffer, size_t count) void App::RequestNewPage(const char* url) { + if (!url || !*url) + return; + StopLoad(); - loadTask.Load(url); + pageLoadTask.Load(url); requestedNewPage = true; + loadTaskTargetNode = nullptr; //ui.UpdateAddressBar(loadTask.url); + + if (pageLoadTask.type == LoadTask::RemoteFile && pageLoadTask.request) + { + ui.SetStatusMessage("Connecting to server...", StatusBarNode::GeneralStatus); + } } void App::OpenURL(const char* url) { RequestNewPage(url); - pageHistoryPos++; - if (pageHistoryPos >= MAX_PAGE_URL_HISTORY) + size_t urlStringLength = strlen(url) + 1; + + if (*pageHistoryPtr) + { + pageHistoryPtr += strlen(pageHistoryPtr) + 1; + } + + // If not enough space in the buffer then move to previous space + while (pageHistoryPtr + urlStringLength > pageHistoryBuffer + MAX_PAGE_HISTORY_BUFFER_SIZE) { - for (int n = 0; n < MAX_PAGE_URL_HISTORY - 1; n++) + int firstLength = strlen(pageHistoryBuffer); + + if (!firstLength) { - pageHistory[n] = pageHistory[n + 1]; + // This shouldn't happen + pageHistoryPtr = pageHistoryBuffer; + break; } - pageHistoryPos--; + + memmove(pageHistoryBuffer, pageHistoryBuffer + firstLength + 1, MAX_PAGE_HISTORY_BUFFER_SIZE - firstLength - 1); + pageHistoryPtr -= firstLength + 1; } - pageHistory[pageHistoryPos] = loadTask.url; - pageHistorySize = pageHistoryPos + 1; + strcpy(pageHistoryPtr, url); + memset(pageHistoryPtr + urlStringLength, 0, MAX_PAGE_HISTORY_BUFFER_SIZE - (pageHistoryPtr + urlStringLength - pageHistoryBuffer)); } void App::StopLoad() { - loadTask.Stop(); + pageLoadTask.Stop(); + pageContentLoadTask.Stop(); } void App::ShowErrorPage(const char* message) { - loadTask.Stop(); + StopLoad(); ResetPage(); - page.BreakLine(2); - page.PushStyle(WidgetStyle(FontStyle::Bold, 2)); - page.AppendText("Error"); - page.PopStyle(); - page.BreakLine(2); - page.AppendText(message); - page.FinishSection(); - page.SetTitle("Error"); - //page.pageURL = "about:error"; - //ui.UpdateAddressBar(page.pageURL); - //renderer.DrawAddress(page.pageURL.url); + page.pageURL = pageLoadTask.GetURL(); + ui.UpdateAddressBar(page.pageURL); + + parser.Write(""); + parser.Write("

Error loading page

"); + parser.Write("
"); + parser.Write(message); + parser.Write(""); + parser.Finish(); } static const char* frogFindURL = "http://frogfind.com/read.php?a="; @@ -281,46 +458,75 @@ void App::ShowNoHTTPSPage() { ResetPage(); - page.BreakLine(2); - page.PushStyle(WidgetStyle(FontStyle::Bold, 2)); - page.AppendText("HTTPS unsupported"); - page.PopStyle(); - page.BreakLine(2); - page.AppendText("Sorry this browser does not support HTTPS!"); - page.BreakLine(); - page.PushStyle(WidgetStyle(FontStyle::Underline)); - - page.pageURL = frogFindURL; - strncpy(page.pageURL.url + FROG_FIND_URL_LENGTH, loadTask.GetURL(), MAX_URL_LENGTH - FROG_FIND_URL_LENGTH); - page.SetWidgetURL(page.pageURL.url); - - - page.AppendText("Visit this site via FrogFind"); - page.ClearWidgetURL(); - page.PopStyle(); - page.FinishSection(); - page.SetTitle("HTTPS unsupported"); - page.pageURL = loadTask.GetURL(); + page.pageURL = pageLoadTask.GetURL(); ui.UpdateAddressBar(page.pageURL); - loadTask.Stop(); + StopLoad(); + + page.pageURL = frogFindURL; + strncpy(page.pageURL.url + FROG_FIND_URL_LENGTH, pageLoadTask.GetURL(), MAX_URL_LENGTH - FROG_FIND_URL_LENGTH); + + parser.Write(""); + parser.Write("

HTTPS unsupported

"); + parser.Write("
"); + parser.Write("Sorry this browser does not support HTTPS!
"); + parser.Write("Visit this site via FrogFind"); + parser.Write(""); + parser.Finish(); } void App::PreviousPage() { - if (pageHistoryPos > 0) + if (pageHistoryPtr > pageHistoryBuffer) { - pageHistoryPos--; - RequestNewPage(pageHistory[pageHistoryPos].url); + do + { + pageHistoryPtr--; + } while (pageHistoryPtr > pageHistoryBuffer && pageHistoryPtr[-1]); + + RequestNewPage(pageHistoryPtr); } } void App::NextPage() { - if (pageHistoryPos + 1 < pageHistorySize) + if (*pageHistoryPtr) + { + char* next = pageHistoryPtr + strlen(pageHistoryPtr) + 1; + if (next < pageHistoryBuffer + MAX_PAGE_HISTORY_BUFFER_SIZE && *next) + { + pageHistoryPtr = next; + RequestNewPage(pageHistoryPtr); + } + } +} + +void App::ReloadPage() +{ + if (*pageHistoryPtr) + { + RequestNewPage(pageHistoryPtr); + } +} + +void App::LoadImageNodeContent(Node* node) +{ + loadTaskTargetNode = node; + node->Handler().LoadContent(node, pageContentLoadTask); +} + +void VideoDriver::InvertVideoOutput() +{ + if (drawSurface->format == DrawSurface::Format_1BPP) { - pageHistoryPos++; - RequestNewPage(pageHistory[pageHistoryPos].url); + App::config.invertScreen = !App::config.invertScreen; + + DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); + Platform::input->HideMouse(); + context.surface->InvertRect(context, 0, 0, screenWidth, screenHeight); + Platform::input->ShowMouse(); } } diff --git a/src/App.h b/src/App.h index dfdc7ca..b1cf281 100644 --- a/src/App.h +++ b/src/App.h @@ -12,26 +12,29 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _APP_H_ +#define _APP_H_ #include #include "Parser.h" #include "Page.h" -#include "Renderer.h" #include "URL.h" #include "Interface.h" +#include "Render.h" -#define MAX_PAGE_URL_HISTORY 5 +#define MAX_PAGE_HISTORY_BUFFER_SIZE MAX_URL_LENGTH +#define APP_LOAD_BUFFER_SIZE 256 class HTTPRequest; struct LoadTask { - LoadTask() : type(LocalFile), fs(NULL) {} + LoadTask() : type(LocalFile), fs(NULL), debugDumpFile(NULL) {} void Load(const char* url); void Stop(); bool HasContent(); + bool IsBusy(); size_t GetContent(char* buffer, size_t count); const char* GetURL(); @@ -49,10 +52,21 @@ struct LoadTask FILE* fs; HTTPRequest* request; }; + + FILE* debugDumpFile; }; struct Widget; +struct AppConfig +{ + bool loadImages : 1; + bool dumpPage : 1; + bool invertScreen : 1; + bool useSwap : 1; + bool useEMS : 1; +}; + class App { public: @@ -67,13 +81,22 @@ class App void NextPage(); void StopLoad(); + void ReloadPage(); void ShowErrorPage(const char* message); + static App& Get() { return *app; } + Page page; - Renderer renderer; + PageRenderer pageRenderer; HTMLParser parser; AppInterface ui; + static AppConfig config; + + LoadTask pageLoadTask; + LoadTask pageContentLoadTask; + + void LoadImageNodeContent(Node* node); private: void ResetPage(); @@ -82,10 +105,16 @@ class App void ShowNoHTTPSPage(); bool requestedNewPage; - LoadTask loadTask; + Node* loadTaskTargetNode; bool running; - URL pageHistory[MAX_PAGE_URL_HISTORY]; - int pageHistorySize; - int pageHistoryPos; + char pageHistoryBuffer[MAX_PAGE_HISTORY_BUFFER_SIZE]; + char* pageHistoryPtr; + + static App* app; + + char loadBuffer[APP_LOAD_BUFFER_SIZE]; }; + + +#endif diff --git a/src/Colour.cpp b/src/Colour.cpp new file mode 100644 index 0000000..afe84df --- /dev/null +++ b/src/Colour.cpp @@ -0,0 +1,62 @@ +#include "Colour.h" +#include "Palettes.inc" + +ColourScheme monochromeColourScheme = +{ + // Page + 1, + // Text + 0, + // Link + 0, + // Button + 1 +}; + +ColourScheme cgaColourScheme = +{ + // Page + 0xff, + // Text + 0, + // Link + 0x55, + // Button + 0xff +}; + +ColourScheme compositeCgaColourScheme = +{ + // Page + 0xff, + // Text + 0, + // Link + 0x22, + // Button + 0x55 +}; + +ColourScheme egaColourScheme = +{ + // Page + 15, + // Text + 0, + // Link + 1, + // Button + 7 +}; + +ColourScheme colourScheme666 = +{ + // Page + RGB666(0xff, 0xff, 0xff), + // Text + RGB666(0, 0, 0), + // Link + RGB666(0, 0, 0xee), + // Button + RGB666(0xcc, 0xcc, 0xcc), +}; diff --git a/src/Colour.h b/src/Colour.h new file mode 100644 index 0000000..d1d41a8 --- /dev/null +++ b/src/Colour.h @@ -0,0 +1,36 @@ +#ifndef _COLOUR_H_ +#define _COLOUR_H_ + +#include + +#define RGB332(red, green, blue) (((red) & 0xe0) | (((green) & 0xe0) >> 3) | (((blue) & 0xc0) >> 6)) +#define RGB_TO_GREY(red, green, blue) (((red) * 76 + (green) * 150 + (blue) * 30) >> 8) +#define RGB666(red, green, blue) ( ( ( (red) * 5) / 255) * 36) + ( ( ( (green) * 5) / 255) * 6) + ( ( ( (blue) * 5) / 255) ) + 16 + +#define TRANSPARENT_COLOUR_VALUE 0xff + +struct ColourScheme +{ + uint8_t pageColour; + uint8_t textColour; + uint8_t linkColour; + uint8_t buttonColour; +}; + +struct NamedColour +{ + const char* name; + uint8_t colour; +}; + +extern ColourScheme monochromeColourScheme; +extern ColourScheme egaColourScheme; +extern ColourScheme cgaColourScheme; +extern ColourScheme compositeCgaColourScheme; +extern ColourScheme colourScheme666; + +extern uint8_t cgaPaletteLUT[]; +extern uint8_t egaPaletteLUT[]; +extern uint8_t compositeCgaPaletteLUT[]; + +#endif diff --git a/src/Cursor.h b/src/Cursor.h index 19887a1..6e066b6 100644 --- a/src/Cursor.h +++ b/src/Cursor.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _CURSOR_H_ +#define _CURSOR_H_ #include struct MouseCursor @@ -28,5 +29,7 @@ struct MouseCursor struct MouseCursorData { uint16_t data[32]; - int hotSpotX, hotSpotY; + int16_t hotSpotX, hotSpotY; }; + +#endif diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp new file mode 100644 index 0000000..55839b0 --- /dev/null +++ b/src/DOS/BIOSVid.cpp @@ -0,0 +1,216 @@ +// +// Copyright (C) 2021 James Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// + +#include +#include +#include +#include +#include +#include +#include "../Image/Image.h" +#include "BIOSVid.h" +#include "../Interface.h" +#include "../DataPack.h" +#include "../Draw/Surf1bpp.h" +#include "../Draw/Surf2bpp.h" +#include "../Draw/Surf4bpp.h" +#include "../Draw/Surf8bpp.h" +#include "Surf1512.h" +#include "../VidModes.h" + +BIOSVideoDriver::BIOSVideoDriver() +{ + startingScreenMode = -1; +} + +void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) +{ + videoMode = inVideoModeInfo; + startingScreenMode = GetScreenMode(); + + screenWidth = videoMode->screenWidth; + screenHeight = videoMode->screenHeight; + + if (!SetScreenMode(videoMode->biosVideoMode)) + { + Platform::FatalError("Could not set video mode: %d", videoMode->biosVideoMode); + return; + } + + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) + { + SetScreenMode(6); + outp(0x3D8, 0x1a); + Assets.LoadPreset(videoMode->dataPackIndex); + } + else + { + Assets.LoadPreset(videoMode->dataPackIndex); + } + + int screenPitch = 0; + + switch(videoMode->surfaceFormat) + { + case DrawSurface::Format_1BPP: + drawSurface = new DrawSurface_1BPP(screenWidth, screenHeight); + screenPitch = screenWidth / 8; + colourScheme = monochromeColourScheme; + paletteLUT = nullptr; + break; + case DrawSurface::Format_2BPP: + drawSurface = new DrawSurface_2BPP(screenWidth, screenHeight); + screenPitch = screenWidth / 4; + + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) + { + paletteLUT = compositeCgaPaletteLUT; + colourScheme = compositeCgaColourScheme; + } + else + { + paletteLUT = cgaPaletteLUT; + colourScheme = cgaColourScheme; + } + break; + case DrawSurface::Format_4BPP_EGA: + drawSurface = new DrawSurface_4BPP(screenWidth, screenHeight); + screenPitch = screenWidth / 8; + colourScheme = egaColourScheme; + paletteLUT = egaPaletteLUT; + break; + case DrawSurface::Format_8BPP: + drawSurface = new DrawSurface_8BPP(screenWidth, screenHeight); + screenPitch = screenWidth; + colourScheme = colourScheme666; + paletteLUT = new uint8_t[256]; + if (!paletteLUT) + { + Platform::FatalError("Could not allocate memory for paletteLUT"); + } + + for (int n = 0; n < 256; n++) + { + int r = (n & 0xe0); + int g = (n & 0x1c) << 3; + int b = (n & 3) << 6; + + int rgbBlue = ((long)b * 255) / 0xc0; + int rgbGreen = ((long)g * 255) / 0xe0; + int rgbRed = ((long)r * 255) / 0xe0; + + paletteLUT[n] = RGB666(rgbRed, rgbGreen, rgbBlue); + } + + // Set palette to RGB666 + outp(0x03C6, 0xff); + outp(0x03C8, 16); + + for (int r = 0; r < 6; r++) + { + for (int g = 0; g < 6; g++) + { + for (int b = 0; b < 6; b++) + { + outp(0x03C9, (r * 63) / 5); + outp(0x03C9, (g * 63) / 5); + outp(0x03C9, (b * 63) / 5); + } + } + } + break; + + case DrawSurface::Format_4BPP_PC1512: + drawSurface = new DrawSurface_4BPP_PC1512(screenWidth, screenHeight); + screenPitch = screenWidth / 8; + colourScheme = egaColourScheme; + paletteLUT = egaPaletteLUT; + break; + + default: + Platform::FatalError("Unsupported video format"); + break; + } + + if (!drawSurface || !drawSurface->lines) + { + Platform::FatalError("Could not allocate memory for draw surface"); + } + + if (videoMode->vramPage3) + { + // 4 page interlaced memory layout + int offset = 0; + for (int y = 0; y < screenHeight; y += 4) + { + drawSurface->lines[y] = (uint8_t*) MK_FP(videoMode->vramPage1, offset); + drawSurface->lines[y + 1] = (uint8_t*) MK_FP(videoMode->vramPage2, offset); + drawSurface->lines[y + 2] = (uint8_t*) MK_FP(videoMode->vramPage3, offset); + drawSurface->lines[y + 3] = (uint8_t*) MK_FP(videoMode->vramPage4, offset); + offset += screenPitch; + } + } + else if (videoMode->vramPage2) + { + // 2 page interlaced memory layout + int offset = 0; + for (int y = 0; y < screenHeight; y += 2) + { + drawSurface->lines[y] = (uint8_t*)MK_FP(videoMode->vramPage1, offset); + drawSurface->lines[y + 1] = (uint8_t*)MK_FP(videoMode->vramPage2, offset); + offset += screenPitch; + } + } + else + { + // No interlacing + int offset = 0; + for (int y = 0; y < screenHeight; y++) + { + drawSurface->lines[y] = (uint8_t*)MK_FP(videoMode->vramPage1, offset); + offset += screenPitch; + } + } +} + +void BIOSVideoDriver::Shutdown() +{ + if (startingScreenMode != -1) + { + SetScreenMode(startingScreenMode); + } +} + +int BIOSVideoDriver::GetScreenMode() +{ + union REGS inreg, outreg; + inreg.h.ah = 0xf; + + int86(0x10, &inreg, &outreg); + + return (int)outreg.h.al; +} + +bool BIOSVideoDriver::SetScreenMode(int screenMode) +{ + union REGS inreg, outreg; + inreg.h.ah = 0; + inreg.h.al = (unsigned char)screenMode; + + int86(0x10, &inreg, &outreg); + + return GetScreenMode() == screenMode; +} + + diff --git a/src/DOS/BIOSVid.h b/src/DOS/BIOSVid.h new file mode 100644 index 0000000..9c3bf26 --- /dev/null +++ b/src/DOS/BIOSVid.h @@ -0,0 +1,37 @@ +// +// Copyright (C) 2021 James Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// + +#ifndef _BIOSVID_H_ +#define _BIOSVID_H_ + +#include "../Platform.h" + +struct Image; + +class BIOSVideoDriver : public VideoDriver +{ +public: + BIOSVideoDriver(); + + virtual void Init(VideoModeInfo* inVideoModeInfo); + virtual void Shutdown(); + +private: + int GetScreenMode(); + bool SetScreenMode(int screenMode); + + int startingScreenMode; +}; + +#endif diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp deleted file mode 100644 index e28d73b..0000000 --- a/src/DOS/CGA.cpp +++ /dev/null @@ -1,767 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#include -#include -#include -#include -#include -#include -#include "../Image.h" -#include "CGA.h" -#include "CGAData.inc" -#include "../Interface.h" - -#define CGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) - -#define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT 200 - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT 12 - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 32 - -#define ADDRESS_BAR_X 60 -#define ADDRESS_BAR_Y 10 -#define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - 64) -#define ADDRESS_BAR_HEIGHT 12 -#define TITLE_BAR_HEIGHT 8 -#define STATUS_BAR_HEIGHT 8 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) - -#define WINDOW_TOP 24 -#define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define SCROLL_BAR_WIDTH 16 - -#define WINDOW_VRAM_TOP_EVEN (BYTES_PER_LINE * (WINDOW_TOP / 2)) -#define WINDOW_VRAM_TOP_ODD (0x2000 + BYTES_PER_LINE * (WINDOW_TOP / 2)) -#define WINDOW_VRAM_BOTTOM_EVEN (BYTES_PER_LINE * (WINDOW_BOTTOM / 2)) -#define WINDOW_VRAM_BOTTOM_ODD (0x2000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 2)) -#define BYTES_PER_LINE 80 - -CGADriver::CGADriver() -{ - screenWidth = SCREEN_WIDTH; - screenHeight = SCREEN_HEIGHT; - windowWidth = screenWidth - 16; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - scissorY2 = SCREEN_HEIGHT; - invertScreen = false; - clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &CGA_ImageIcon; - bulletImage = &CGA_Bullet; - isTextMode = false; -} - -void CGADriver::Init() -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(6); -} - -void CGADriver::Shutdown() -{ - SetScreenMode(startingScreenMode); -} - -int CGADriver::GetScreenMode() -{ - union REGS inreg, outreg; - inreg.h.ah = 0xf; - - int86(0x10, &inreg, &outreg); - - return (int)outreg.h.al; -} - -void CGADriver::SetScreenMode(int screenMode) -{ - union REGS inreg, outreg; - inreg.h.ah = 0; - inreg.h.al = (unsigned char)screenMode; - - int86(0x10, &inreg, &outreg); -} - -void CGADriver::InvertScreen() -{ - int count = 0x4000; - unsigned char far* VRAM = CGA_BASE_VRAM_ADDRESS; - while (count--) - { - *VRAM ^= 0xff; - VRAM++; - } - - invertScreen = !invertScreen; - clearMask = invertScreen ? 0 : 0xffff; -} - -void CGADriver::ClearScreen() -{ - uint8_t clearValue = (uint8_t)(clearMask & 0xff); - FastMemSet(CGA_BASE_VRAM_ADDRESS, clearValue, 0x4000); - // White out main page - //FastMemSet(CGA_BASE_VRAM_ADDRESS + BYTES_PER_LINE * TITLE_BAR_HEIGHT / 2, 0xff, BYTES_PER_LINE * (SCREEN_HEIGHT - TITLE_BAR_HEIGHT - STATUS_BAR_HEIGHT) / 2); - //FastMemSet(CGA_BASE_VRAM_ADDRESS + 0x2000 + BYTES_PER_LINE * TITLE_BAR_HEIGHT / 2, 0xff, BYTES_PER_LINE * (SCREEN_HEIGHT - TITLE_BAR_HEIGHT - STATUS_BAR_HEIGHT) / 2); -} - -void CGADriver::DrawImage(Image* image, int x, int y) -{ - int imageHeight = image->height; - - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + imageHeight < scissorY1) - { - return; - } - if (y + imageHeight > scissorY2) - { - imageHeight = (scissorY2 - y); - } - - uint16_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - VRAM += (y >> 1) * BYTES_PER_LINE; - - if (y & 1) - { - VRAM += 0x2000; - } - - uint16_t imageWidthBytes = image->width >> 3; - - // Check if rounds to a byte, pad if necessary - if ((imageWidthBytes << 3) != image->width) - { - imageWidthBytes++; - } - - uint8_t* imageData = image->data + firstLine * imageWidthBytes; - uint8_t far* VRAMptr = VRAM + (x >> 3); - bool oddLine = (y & 1); - - for (uint8_t j = firstLine; j < imageHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - for (uint8_t i = 0; i < imageWidthBytes; i++) - { - uint8_t glyphPixels = *imageData++; - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - if (oddLine) - { - VRAMptr -= (0x2000 - BYTES_PER_LINE); - } - else - { - VRAMptr += 0x2000; - } - oddLine = !oddLine; - } -} - -void CGADriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ - Font* font = GetFont(size, style); - - int startX = x; - uint8_t glyphHeight = font->glyphHeight; - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + glyphHeight > scissorY2) - { - glyphHeight = (uint8_t)(scissorY2 - y); - } - if (y + glyphHeight < scissorY1) - { - return; - } - - uint8_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAM += (y >> 1) * BYTES_PER_LINE; - - if (y & 1) - { - VRAM += 0x2000; - } - - while (*text) - { - char c = *text++; - if (c < 32 || c >= 128) - { - continue; - } - - char index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; - - if (glyphWidth == 0) - { - continue; - } - - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - - glyphData += (firstLine * font->glyphWidthBytes); - - bool oddLine = (y & 1); - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < glyphHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) - { - writeOffset++; - } - - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - if (oddLine) - { - VRAMptr -= (0x2000 - BYTES_PER_LINE); - } - else - { - VRAMptr += 0x2000; - } - oddLine = !oddLine; - } - - x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; - } - - if (x >= scissorX2) - { - break; - } - } - - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < scissorY2) - { - HLine(startX, y - firstLine + font->glyphHeight - 1, x - startX); - } -} - -Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) - { - switch (fontSize) - { - case 0: - return &CGA_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &CGA_LargeFont_Monospace; - default: - return &CGA_RegularFont_Monospace; - } - } - - switch (fontSize) - { - case 0: - return &CGA_SmallFont; - case 2: - case 3: - case 4: - return &CGA_LargeFont; - default: - return &CGA_RegularFont; - } -} - -void CGADriver::HLine(int x, int y, int count) -{ - if (invertScreen) - { - ClearHLine(x, y, count); - } - else - { - HLineInternal(x, y, count); - } -} - -void CGADriver::HLineInternal(int x, int y, int count) -{ - if (y < scissorY1 || y >= scissorY2) - return; - - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 1) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - if (y & 1) - { - VRAMptr += 0x2000; - } - - uint8_t data = *VRAMptr; - uint8_t mask = ~(0x80 >> (x & 7)); - - while (count--) - { - data &= mask; - x++; - mask = (mask >> 1) | 0x80; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0; - count -= 8; - } - mask = ~0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void CGADriver::ClearHLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 1) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - if (y & 1) - { - VRAMptr += 0x2000; - } - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data |= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void CGADriver::ClearRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } -} - -void CGADriver::InvertLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 1) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - if (y & 1) - { - VRAMptr += 0x2000; - } - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data ^= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ ^= 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -bool CGADriver::ApplyScissor(int& y, int& height) -{ - if (y + height < scissorY1) - return false; - if (y >= scissorY2) - return false; - if (y < scissorY1) - { - height -= (scissorY1 - y); - y = scissorY1; - } - if (y + height >= scissorY2) - { - height = scissorY2 - y; - } - return true; -} - -void CGADriver::InvertRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - for (int j = 0; j < height; j++) - { - InvertLine(x, y + j, width); - } -} - -void CGADriver::FillRect(int x, int y, int width, int height) -{ - if (x == 0 && width == SCREEN_WIDTH && !(height & 1) && !(y & 1)) - { - y >>= 1; - height >>= 1; - uint8_t fillValue = ~(clearMask & 0xff); - FastMemSet(CGA_BASE_VRAM_ADDRESS + BYTES_PER_LINE * y, fillValue, BYTES_PER_LINE * height); - FastMemSet(CGA_BASE_VRAM_ADDRESS + 0x2000 + BYTES_PER_LINE * y, fillValue, BYTES_PER_LINE * height); - } - else - { - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } - } -} - -void CGADriver::VLine(int x, int y, int count) -{ - if (y < scissorY1) - { - count -= (scissorY1 - y); - y = scissorY1; - } - if (y >= scissorY2) - { - return; - } - if (y + count >= scissorY2) - { - count = scissorY2 - y; - } - if (count <= 0) - { - return; - } - - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - uint8_t mask = ~(0x80 >> (x & 7)); - - VRAMptr += (y >> 1) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - bool oddLine = (y & 1); - if (oddLine) - { - VRAMptr += 0x2000; - } - - if (invertScreen) - { - mask ^= 0xff; - - while (count--) - { - *VRAMptr |= mask; - if (oddLine) - { - VRAMptr -= (0x2000 - BYTES_PER_LINE); - } - else - { - VRAMptr += 0x2000; - } - oddLine = !oddLine; - } - } - else - { - while (count--) - { - *VRAMptr &= mask; - if (oddLine) - { - VRAMptr -= (0x2000 - BYTES_PER_LINE); - } - else - { - VRAMptr += 0x2000; - } - oddLine = !oddLine; - } - } -} - -MouseCursorData* CGADriver::GetCursorGraphic(MouseCursor::Type type) -{ - switch (type) - { - default: - case MouseCursor::Pointer: - return &CGA_MouseCursor; - case MouseCursor::Hand: - return &CGA_MouseCursorHand; - case MouseCursor::TextSelect: - return &CGA_MouseCursorTextSelect; - } -} - -int CGADriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) -{ - Font* font = GetFont(fontSize, style); - if (c >= 32 && c < 128) - { - int width = font->glyphWidth[c - 32]; - if (style & FontStyle::Bold) - { - width++; - } - return width; - } - return 0; -} - -int CGADriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return GetFont(fontSize, style)->glyphHeight + 1; -} - -void CGADriver::DrawScrollBar(int position, int size) -{ - position >>= 1; - size >>= 1; - - uint8_t* VRAM = CGA_BASE_VRAM_ADDRESS + WINDOW_TOP / 2 * BYTES_PER_LINE + (BYTES_PER_LINE - 2); - - if (invertScreen) - { - DrawScrollBarBlockInverted(VRAM, position, size, (WINDOW_HEIGHT / 2) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x2000, position, size, (WINDOW_HEIGHT / 2) - position - size); - } - else - { - DrawScrollBarBlock(VRAM, position, size, (WINDOW_HEIGHT / 2) - position - size); - DrawScrollBarBlock(VRAM + 0x2000, position, size, (WINDOW_HEIGHT / 2) - position - size); - } -} - -void CGADriver::DrawRect(int x, int y, int width, int height) -{ - HLine(x, y, width); - HLine(x, y + height - 1, width); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void CGADriver::DrawButtonRect(int x, int y, int width, int height) -{ - HLine(x + 1, y, width - 2); - HLine(x + 1, y + height - 1, width - 2); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void CGADriver::ScrollWindow(int amount) -{ - amount &= ~1; - - if (amount > 0) - { - int lines = (WINDOW_HEIGHT - amount) >> 1; - int offset = amount * (BYTES_PER_LINE >> 1); - ScrollRegionUp(WINDOW_VRAM_TOP_EVEN, WINDOW_VRAM_TOP_EVEN + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_ODD, WINDOW_VRAM_TOP_ODD + offset, lines); - - //ClearRegion(0x1ef0 - offset, (WINDOW_HEIGHT / 2) - lines); - //ClearRegion(0x3ef0 - offset, (WINDOW_HEIGHT / 2) - lines); - - ClearRegion(WINDOW_VRAM_BOTTOM_EVEN - offset, (WINDOW_HEIGHT / 2) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_ODD - offset, (WINDOW_HEIGHT / 2) - lines, clearMask); - } - else if (amount < 0) - { - int lines = (WINDOW_HEIGHT + amount) >> 1; - int offset = amount * (BYTES_PER_LINE >> 1); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_EVEN - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_EVEN - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_ODD - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_ODD - BYTES_PER_LINE + offset, lines); - - ClearRegion(WINDOW_VRAM_TOP_EVEN, (WINDOW_HEIGHT / 2) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_ODD, (WINDOW_HEIGHT / 2) - lines, clearMask); - } -} - -void CGADriver::ClearWindow() -{ - ClearRegion(WINDOW_VRAM_TOP_EVEN, (WINDOW_HEIGHT / 2), clearMask); - ClearRegion(WINDOW_VRAM_TOP_ODD, (WINDOW_HEIGHT / 2), clearMask); -} - -void CGADriver::SetScissorRegion(int y1, int y2) -{ - scissorY1 = y1; - scissorY2 = y2; -} - -void CGADriver::ClearScissorRegion() -{ - scissorY1 = 0; - scissorY2 = screenHeight; -} - -void CGADriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT; - app.statusBar.width = SCREEN_WIDTH; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 1; - app.titleBar.width = SCREEN_WIDTH; - app.titleBar.height = TITLE_BAR_HEIGHT; -} - -void CGADriver::ScaleImageDimensions(int& width, int& height) -{ - // Scale to 4:3 - height = (height * 5) / 12; -} diff --git a/src/DOS/CGA.h b/src/DOS/CGA.h deleted file mode 100644 index d4ac372..0000000 --- a/src/DOS/CGA.h +++ /dev/null @@ -1,225 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#pragma once - -#include "../Platform.h" - -struct Image; - -class CGADriver : public VideoDriver -{ -public: - CGADriver(); - - virtual void Init(); - virtual void Shutdown(); - virtual void ClearScreen(); - virtual void InvertScreen(); - - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app); - - - virtual void ClearWindow(); - virtual void ScrollWindow(int delta); - virtual void SetScissorRegion(int y1, int y2); - virtual void ClearScissorRegion(); - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void DrawRect(int x, int y, int width, int height); - virtual void DrawButtonRect(int x, int y, int width, int height); - virtual void DrawImage(Image* image, int x, int y); - virtual void ClearRect(int x, int y, int width, int height); - virtual void FillRect(int x, int y, int width, int height); - virtual void InvertRect(int x, int y, int width, int height); - - virtual void HLine(int x, int y, int count); - virtual void VLine(int x, int y, int count); - virtual void ScaleImageDimensions(int& width, int& height); - - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - - virtual void DrawScrollBar(int position, int size); - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); - -protected: - int GetScreenMode(); - void SetScreenMode(int screenMode); - bool ApplyScissor(int& y, int& height); - - bool invertScreen; - uint16_t clearMask; - int startingScreenMode; - int scissorX1, scissorY1, scissorX2, scissorY2; - -private: - virtual void ClearHLine(int x, int y, int count); - virtual void HLineInternal(int x, int y, int count); - virtual void InvertLine(int x, int y, int count); -}; - -class OlivettiDriver : public CGADriver -{ -public: - OlivettiDriver(int _videoMode); - - virtual void Init(); - - virtual void ClearScreen(); - virtual void InvertScreen(); - - virtual void ArrangeAppInterfaceWidgets(AppInterface& app); - - virtual void DrawImage(Image* image, int x, int y); - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void FillRect(int x, int y, int width, int height); - virtual void VLine(int x, int y, int count); - virtual void DrawScrollBar(int position, int size); - - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); - - virtual void ClearWindow(); - virtual void ScrollWindow(int delta); - - virtual void ScaleImageDimensions(int& width, int& height); - -private: - virtual void ClearHLine(int x, int y, int count); - virtual void HLineInternal(int x, int y, int count); - virtual void InvertLine(int x, int y, int count); - - int videoMode; -}; - -void FastMemSet(void far* mem, uint8_t value, unsigned int count); -#pragma aux FastMemSet = \ - "rep stosb" \ - modify [di cx] \ - parm[es di][al][cx]; - -void DrawScrollBarBlock(uint8_t far* ptr, int top, int middle, int bottom); -#pragma aux DrawScrollBarBlock = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov ax, 0xfe7f" \ - "_loopTop:" \ - "stosw" \ - "add di, 78" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov ax, 0x0660" \ - "_loopMiddle:" \ - "stosw" \ - "add di, 78" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0xfe7f" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosw" \ - "add di, 78" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -void DrawScrollBarBlockInverted(uint8_t far* ptr, int top, int middle, int bottom); -#pragma aux DrawScrollBarBlockInverted = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov ax, 0x0180" \ - "_loopTop:" \ - "stosw" \ - "add di, 78" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov ax, 0xf99f" \ - "_loopMiddle:" \ - "stosw" \ - "add di, 78" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0x0180" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosw" \ - "add di, 78" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -void ScrollRegionUp(int dest, int src, int count); -#pragma aux ScrollRegionUp = \ - "push ds" \ - "push es" \ - "mov ax, 0xb800" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 39" \ - "rep movsw" \ - "add di, 2" \ - "add si, 2" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -void ScrollRegionDown(int dest, int src, int count); -#pragma aux ScrollRegionDown = \ - "push ds" \ - "push es" \ - "mov ax, 0xb800" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 39" \ - "rep movsw" \ - "sub di, 158" \ - "sub si, 158" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -void ClearRegion(int offset, int count, uint16_t clearMask); -#pragma aux ClearRegion = \ - "push es" \ - "mov ax, 0xb800" \ - "mov es, ax" \ - "mov ax, bx" \ - "_loopLine:" \ - "mov cx, 39" \ - "rep stosw" \ - "add di, 2" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - modify [cx di ax cx dx] \ - parm [di] [dx] [bx] diff --git a/src/DOS/CGAData.inc b/src/DOS/CGAData.inc deleted file mode 100644 index b553eed..0000000 --- a/src/DOS/CGAData.inc +++ /dev/null @@ -1,1269 +0,0 @@ -// CGA resources -// This file is auto generated - -// Fonts: -static unsigned char CGA_RegularFont_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0xd8, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x66, 0x00, 0xff, 0x00, 0x66, 0x00, 0x66, 0x00, 0xff, 0x00, 0x66, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x30, 0x00, 0x7c, 0x00, 0xb0, 0x00, 0x78, 0x00, 0x34, 0x00, 0xf8, 0x00, 0x30, 0x00, - // '%' - 0x00, 0x00, 0x71, 0x80, 0xdb, 0x00, 0x76, 0x00, 0x0d, 0xc0, 0x1b, 0x60, 0x31, 0xc0, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x70, 0x00, 0xd8, 0x00, 0x30, 0x00, 0x6d, 0x80, 0xc3, 0x00, 0x7c, 0xc0, 0x00, 0x00, - // ''' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x60, 0x00, - // ')' - 0x00, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xc0, 0x00, - // '*' - 0x00, 0x00, 0x66, 0x00, 0x3c, 0x00, 0xff, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0xff, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x30, 0x00, 0x30, 0x00, 0x60, 0x00, 0x60, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x30, 0x00, 0xf0, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0xf8, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xfc, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0xf8, 0x00, 0x0c, 0x00, 0x38, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0xf8, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x38, 0x00, 0x78, 0x00, 0xd8, 0x00, 0xfc, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0xf8, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0xf8, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x78, 0x00, 0xc0, 0x00, 0xf8, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0xfc, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x7c, 0x00, 0x0c, 0x00, 0x78, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0x18, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x1f, 0x80, 0x60, 0x60, 0xcf, 0x30, 0xd9, 0xb0, 0xce, 0xe0, 0x60, 0x00, 0x1f, 0xc0, - // 'A' - 0x00, 0x00, 0x18, 0x00, 0x3c, 0x00, 0x66, 0x00, 0xff, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0xfc, 0x00, 0xc3, 0x00, 0xfe, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xfc, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x7e, 0x00, 0xc3, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0xfe, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xfe, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0xfe, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xfe, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0xfe, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x7f, 0x00, 0xc1, 0x80, 0xc0, 0x00, 0xc3, 0x80, 0xc1, 0x80, 0x7f, 0x80, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0xc1, 0x80, 0xc1, 0x80, 0xff, 0x80, 0xc1, 0x80, 0xc1, 0x80, 0xc1, 0x80, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0xc3, 0x00, 0xcc, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xcc, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0xe1, 0xc0, 0xf3, 0xc0, 0xde, 0xc0, 0xcc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0xe1, 0x80, 0xf1, 0x80, 0xd9, 0x80, 0xcd, 0x80, 0xc7, 0x80, 0xc3, 0x80, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x7f, 0x00, 0xc1, 0x80, 0xc1, 0x80, 0xc1, 0x80, 0xc1, 0x80, 0x7f, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0xfe, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xfe, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x7f, 0x00, 0xc1, 0x80, 0xc1, 0x80, 0xcd, 0x80, 0xc7, 0x80, 0x7f, 0x00, 0x01, 0x80, - // 'R' - 0x00, 0x00, 0xfe, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xfc, 0x00, 0xc6, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x7e, 0x00, 0xc0, 0x00, 0x7e, 0x00, 0x03, 0x00, 0x03, 0x00, 0xfe, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0xfc, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x18, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0xc0, 0x30, 0xc0, 0x30, 0xc6, 0x30, 0x6f, 0x60, 0x79, 0xe0, 0x30, 0xc0, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x38, 0x00, 0x6c, 0x00, 0xc6, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0xc3, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0xfe, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xfe, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xf0, 0x00, - // '\' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0xf0, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0xf0, 0x00, - // '^' - 0x30, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, - // '`' - 0xc0, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x0c, 0x00, 0x7c, 0x00, 0xcc, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xfc, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xc0, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x06, 0x00, 0x7e, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0xf0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7e, 0x00, 0x06, 0x00, 0xfc, 0x00, - // 'h' - 0x00, 0x00, 0xc0, 0x00, 0xf8, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x00, 0x00, - // 'i' - 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 'j' - 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0xc0, 0x00, - // 'k' - 0x00, 0x00, 0xc0, 0x00, 0xcc, 0x00, 0xd8, 0x00, 0xf0, 0x00, 0xd8, 0x00, 0xcc, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0xf6, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xfc, 0x00, 0xc0, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7e, 0x00, 0x06, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xc0, 0x00, 0x70, 0x00, 0x18, 0x00, 0xf0, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x60, 0x00, 0xf0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x78, 0x00, 0xcc, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0xe0, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xfc, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x38, 0x00, 0x60, 0x00, 0x60, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x60, 0x00, 0x38, 0x00, - // '|' - 0x00, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc0, 0x00, - // '}' - 0x00, 0x00, 0xe0, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x30, 0x00, 0x30, 0x00, 0xe0, 0x00, - // '~' - 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font CGA_RegularFont = { - // Glyph widths - { 3,3,6,9,7,12,11,3,4,4,9,9,3,4,3,5,7,5,7,7,7,7,7,7,7,7,3,3,7,8,7,7,13,9,9,9,9,8,8,10,10,3,7,9,7,11,10,10,9,10,9,9,7,9,9,13,8,9,8,5,5,5,6,8,5,7,8,7,8,7,5,8,7,3,4,7,3,9,7,8,8,8,5,6,5,7,7,9,7,7,7,6,3,6,8}, - 2, // Byte width - 8, // Glyph height - 16, // Glyph stride - CGA_RegularFont_Data -}; - -static unsigned char CGA_SmallFont_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - // '"' - 0xa0, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x50, 0x00, 0xf8, 0x00, 0x50, 0x00, 0xf8, 0x00, 0x50, 0x00, 0x00, 0x00, - // '$' - 0x20, 0x00, 0x70, 0x00, 0xc0, 0x00, 0x30, 0x00, 0xe0, 0x00, 0x40, 0x00, - // '%' - 0xc8, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x98, 0x00, 0x00, 0x00, - // '&' - 0x38, 0x00, 0x44, 0x00, 0x38, 0x00, 0xc6, 0x00, 0x79, 0x00, 0x00, 0x00, - // ''' - 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, - // ')' - 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '*' - 0x00, 0x00, 0xa8, 0x00, 0x70, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x10, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - // '/' - 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, - // '0' - 0x60, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, 0x00, - // '1' - 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // '2' - 0xe0, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00, 0x00, - // '3' - 0xe0, 0x00, 0x10, 0x00, 0x60, 0x00, 0x10, 0x00, 0xe0, 0x00, 0x00, 0x00, - // '4' - 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xf0, 0x00, 0x20, 0x00, 0x00, 0x00, - // '5' - 0xf0, 0x00, 0x80, 0x00, 0xe0, 0x00, 0x10, 0x00, 0xe0, 0x00, 0x00, 0x00, - // '6' - 0x70, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '7' - 0xf8, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '8' - 0x70, 0x00, 0x88, 0x00, 0x70, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '9' - 0x70, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, 0x70, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, - // '<' - 0x0c, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0xc0, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x00, 0x00, - // '?' - 0x70, 0x00, 0x88, 0x00, 0x30, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - // '@' - 0x7c, 0x00, 0x82, 0x00, 0xba, 0x00, 0xbe, 0x00, 0x80, 0x00, 0x7c, 0x00, - // 'A' - 0x30, 0x00, 0x48, 0x00, 0x84, 0x00, 0xfc, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'B' - 0xf8, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'C' - 0x7c, 0x00, 0x82, 0x00, 0x80, 0x00, 0x82, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'D' - 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'E' - 0xf8, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'F' - 0xf8, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'G' - 0x7e, 0x00, 0x80, 0x00, 0x8e, 0x00, 0x82, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'H' - 0x82, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, - // 'I' - 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'J' - 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'K' - 0x84, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'L' - 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x00, - // 'M' - 0x80, 0x80, 0xc1, 0x80, 0xa2, 0x80, 0x94, 0x80, 0x88, 0x80, 0x00, 0x00, - // 'N' - 0xc2, 0x00, 0xa2, 0x00, 0x92, 0x00, 0x8a, 0x00, 0x86, 0x00, 0x00, 0x00, - // 'O' - 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'P' - 0xf8, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'Q' - 0x7c, 0x00, 0x82, 0x00, 0x8a, 0x00, 0x86, 0x00, 0x7a, 0x00, 0x00, 0x00, - // 'R' - 0xf8, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x88, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'S' - 0x7c, 0x00, 0x80, 0x00, 0x78, 0x00, 0x04, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'T' - 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // 'U' - 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'V' - 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, 0x00, - // 'W' - 0x84, 0x20, 0x84, 0x20, 0x8a, 0x20, 0x51, 0x40, 0x20, 0x80, 0x00, 0x00, - // 'X' - 0x88, 0x00, 0x50, 0x00, 0x20, 0x00, 0x50, 0x00, 0x88, 0x00, 0x00, 0x00, - // 'Y' - 0x88, 0x00, 0x50, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // 'Z' - 0xf8, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0xf8, 0x00, 0x00, 0x00, - // '[' - 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xe0, 0x00, 0x00, 0x00, - // '\' - 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, - // ']' - 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x00, 0x00, - // '^' - 0x60, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, - // '`' - 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x70, 0x00, 0x08, 0x00, 0xd8, 0x00, 0x68, 0x00, 0x00, 0x00, - // 'b' - 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x70, 0x00, 0x80, 0x00, 0x80, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'd' - 0x08, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x70, 0x00, 0xd8, 0x00, 0x80, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'f' - 0x40, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x78, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, 0xf8, 0x00, - // 'h' - 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, - // 'i' - 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'j' - 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // 'k' - 0x80, 0x00, 0x90, 0x00, 0xe0, 0x00, 0x90, 0x00, 0x88, 0x00, 0x00, 0x00, - // 'l' - 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0xf7, 0x00, 0x88, 0x80, 0x88, 0x80, 0x88, 0x80, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x80, 0x00, - // 'q' - 0x00, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, - // 'r' - 0x00, 0x00, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x70, 0x00, 0xc0, 0x00, 0x30, 0x00, 0xe0, 0x00, 0x00, 0x00, - // 't' - 0x80, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x50, 0x00, 0x20, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x92, 0x00, 0x92, 0x00, 0x6c, 0x00, 0x44, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x90, 0x00, 0x60, 0x00, 0x60, 0x00, 0x90, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x50, 0x00, 0x20, 0x00, 0xc0, 0x00, - // 'z' - 0x00, 0x00, 0xf0, 0x00, 0x20, 0x00, 0x40, 0x00, 0xf0, 0x00, 0x00, 0x00, - // '{' - 0x18, 0x00, 0x20, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x18, 0x00, - // '|' - 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // '}' - 0xc0, 0x00, 0x20, 0x00, 0x18, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, - // '~' - 0x68, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font CGA_SmallFont = { - // Glyph widths - { 2,2,4,6,5,6,9,2,4,3,6,7,3,3,2,4,5,5,5,5,5,5,6,6,6,6,1,3,7,8,7,6,8,7,7,8,7,6,6,8,8,2,6,7,5,10,8,8,7,8,7,7,6,8,8,12,6,6,6,4,4,4,4,6,5,6,6,5,6,6,3,6,6,2,3,6,2,10,6,6,6,6,4,5,3,6,6,8,5,6,5,6,2,6,5,1}, - 2, // Byte width - 6, // Glyph height - 12, // Glyph stride - CGA_SmallFont_Data -}; - -static unsigned char CGA_LargeFont_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0xcc, 0x00, 0x00, 0xcc, 0x00, 0x00, 0xcc, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x01, 0x98, 0x00, 0x01, 0x10, 0x00, 0x03, 0x30, 0x00, 0x7f, 0xfe, 0x00, 0x04, 0x40, 0x00, 0xff, 0xfc, 0x00, 0x19, 0x80, 0x00, 0x11, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x04, 0x00, 0x00, 0x7f, 0x80, 0x00, 0xc4, 0xc0, 0x00, 0xe4, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x05, 0xc0, 0x00, 0xc4, 0x60, 0x00, 0x64, 0xc0, 0x00, 0x3f, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '%' - 0x3c, 0x06, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x38, 0x00, 0x3c, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x01, 0x8f, 0x00, 0x07, 0x30, 0xc0, 0x0c, 0x30, 0xc0, 0x18, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x1f, 0x80, 0x00, 0x30, 0xc0, 0x00, 0x31, 0x80, 0x00, 0x1e, 0x00, 0x00, 0x63, 0x98, 0x00, 0xc0, 0xf0, 0x00, 0xc0, 0x60, 0x00, 0x61, 0xd8, 0x00, 0x3f, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x10, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x10, 0x00, 0x00, - // ')' - 0x80, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x00, - // '*' - 0x18, 0x00, 0x00, 0xdb, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x06, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x3f, 0x80, 0x00, 0x60, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0x60, 0xc0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x0c, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x3f, 0x80, 0x00, 0x60, 0xe0, 0x00, 0xc0, 0x60, 0x00, 0x00, 0xe0, 0x00, 0x03, 0x80, 0x00, 0x1e, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x3f, 0x00, 0x00, 0x61, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0x01, 0xc0, 0x00, 0x1f, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0xe1, 0xc0, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0xc0, 0x00, 0x03, 0xc0, 0x00, 0x0c, 0xc0, 0x00, 0x38, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0xff, 0xf0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0xff, 0x80, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0x00, 0x00, 0xc1, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0xc1, 0xc0, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x3f, 0x80, 0x00, 0x60, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0xdf, 0x80, 0x00, 0xe0, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0x60, 0xc0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0xff, 0xe0, 0x00, 0x00, 0xc0, 0x00, 0x01, 0x80, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x3f, 0x80, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x60, 0xc0, 0x00, 0x3f, 0x80, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x3f, 0x80, 0x00, 0x60, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0x60, 0xe0, 0x00, 0x3f, 0x60, 0x00, 0x00, 0x60, 0x00, 0x60, 0xc0, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x1f, 0x00, 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x70, 0x00, 0x00, 0x70, 0x00, 0x0f, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x3f, 0x00, 0x00, 0xe1, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0x03, 0x80, 0x00, 0x0e, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0xff, 0xc0, 0x07, 0x80, 0x70, 0x18, 0x7d, 0x88, 0x21, 0xc3, 0x0c, 0x63, 0x03, 0x0c, 0xc3, 0x02, 0x0c, 0xc3, 0x06, 0x18, 0xc1, 0xfb, 0xe0, 0x60, 0x00, 0x00, 0x38, 0x00, 0xc0, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, - // 'A' - 0x07, 0x00, 0x00, 0x0d, 0x80, 0x00, 0x18, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x30, 0x60, 0x00, 0x7f, 0xf0, 0x00, 0x60, 0x30, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0xff, 0xe0, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xff, 0xe0, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x0f, 0xf0, 0x00, 0x38, 0x1c, 0x00, 0x60, 0x06, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x06, 0x00, 0x38, 0x1c, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0xff, 0xc0, 0x00, 0xc0, 0x70, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x0c, 0x00, 0xc0, 0x0c, 0x00, 0xc0, 0x0c, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x70, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0xff, 0xf0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0xff, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x0f, 0xf0, 0x00, 0x38, 0x1c, 0x00, 0x60, 0x06, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0xfe, 0x00, 0x60, 0x06, 0x00, 0x38, 0x1e, 0x00, 0x0f, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xff, 0xf8, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0xc0, 0x70, 0x00, 0xc1, 0xc0, 0x00, 0xc7, 0x00, 0x00, 0xde, 0x00, 0x00, 0xf3, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0xe0, 0x07, 0x00, 0xf0, 0x0f, 0x00, 0xd8, 0x1b, 0x00, 0xd8, 0x1b, 0x00, 0xcc, 0x33, 0x00, 0xc6, 0x63, 0x00, 0xc6, 0x63, 0x00, 0xc3, 0xc3, 0x00, 0xc1, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0xc0, 0x18, 0x00, 0xf0, 0x18, 0x00, 0xd8, 0x18, 0x00, 0xcc, 0x18, 0x00, 0xc7, 0x18, 0x00, 0xc1, 0x98, 0x00, 0xc0, 0xd8, 0x00, 0xc0, 0x78, 0x00, 0xc0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x0f, 0xf0, 0x00, 0x38, 0x1c, 0x00, 0x60, 0x06, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x60, 0x06, 0x00, 0x38, 0x1c, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0xff, 0xe0, 0x00, 0xc0, 0x70, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x70, 0x00, 0xff, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x0f, 0xf0, 0x00, 0x38, 0x1c, 0x00, 0x60, 0x06, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x60, 0x36, 0x00, 0x38, 0x1c, 0x00, 0x0f, 0xf6, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'R' - 0xff, 0xf0, 0x00, 0xc0, 0x38, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x38, 0x00, 0xff, 0xe0, 0x00, 0xc0, 0x38, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x3f, 0xc0, 0x00, 0xe0, 0x70, 0x00, 0xc0, 0x18, 0x00, 0xf8, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0xf8, 0x00, 0xc0, 0x18, 0x00, 0x70, 0x38, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0xff, 0xfc, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x70, 0x70, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0xc0, 0x18, 0x00, 0x60, 0x30, 0x00, 0x60, 0x30, 0x00, 0x30, 0x60, 0x00, 0x30, 0x60, 0x00, 0x18, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x0d, 0x80, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0xc0, 0x60, 0x30, 0xc0, 0xf0, 0x30, 0x61, 0x98, 0x60, 0x61, 0x98, 0x60, 0x33, 0x0c, 0xc0, 0x33, 0x0c, 0xc0, 0x1a, 0x05, 0x80, 0x1e, 0x07, 0x80, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0xc0, 0x30, 0x00, 0x60, 0x60, 0x00, 0x30, 0xc0, 0x00, 0x19, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x19, 0x80, 0x00, 0x30, 0xc0, 0x00, 0x60, 0x60, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0xc0, 0x0c, 0x00, 0x60, 0x18, 0x00, 0x38, 0x70, 0x00, 0x0c, 0xc0, 0x00, 0x07, 0x80, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x7f, 0xf8, 0x00, 0x00, 0x18, 0x00, 0x00, 0x70, 0x00, 0x01, 0xc0, 0x00, 0x07, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0xf0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '\' - 0xc0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ']' - 0xf0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '^' - 0x0f, 0x00, 0x00, 0x39, 0xc0, 0x00, 0xe0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, - // '`' - 0xe0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0xc0, 0x00, 0x3f, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc1, 0xc0, 0x00, 0x7e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xdf, 0x80, 0x00, 0xe0, 0x60, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xe0, 0x60, 0x00, 0xdf, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x60, 0x60, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x60, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x1f, 0xb0, 0x00, 0x60, 0x70, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0x60, 0x70, 0x00, 0x1f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x60, 0x60, 0x00, 0xff, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x60, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x1c, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xb0, 0x00, 0x60, 0x70, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0x60, 0x70, 0x00, 0x1f, 0xb0, 0x00, 0x00, 0x30, 0x00, 0x60, 0x70, 0x00, 0x3f, 0xc0, 0x00, - // 'h' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xdf, 0x00, 0x00, 0xe1, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xe0, 0x00, 0x00, - // 'k' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc1, 0xc0, 0x00, 0xc7, 0x00, 0x00, 0xdc, 0x00, 0x00, 0xe6, 0x00, 0x00, 0xc3, 0x80, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x3c, 0x00, 0xe3, 0xc7, 0x00, 0xc1, 0x83, 0x00, 0xc1, 0x83, 0x00, 0xc1, 0x83, 0x00, 0xc1, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x00, 0x00, 0xe1, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x60, 0x60, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0x60, 0x60, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x80, 0x00, 0xe0, 0x60, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xe0, 0x60, 0x00, 0xdf, 0x80, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xb0, 0x00, 0x60, 0x70, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0x60, 0x70, 0x00, 0x1f, 0xb0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0xe0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xc3, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x0f, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xe1, 0xc0, 0x00, 0x3e, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x60, 0x00, 0x60, 0xc0, 0x00, 0x31, 0x80, 0x00, 0x1b, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xc1, 0x80, 0x61, 0x43, 0x00, 0x23, 0x62, 0x00, 0x32, 0x26, 0x00, 0x1e, 0x3c, 0x00, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x36, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x60, 0x00, 0x60, 0xc0, 0x00, 0x31, 0x80, 0x00, 0x1b, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0xe0, 0x00, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x01, 0xc0, 0x00, 0x07, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x1c, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x1c, 0x00, 0x00, - // '|' - 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, - // '}' - 0xe0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xe0, 0x00, 0x00, - // '~' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x10, 0x00, 0xce, 0x30, 0x00, 0x83, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font CGA_LargeFont = { - // Glyph widths - { 7,4,8,17,13,20,16,4,6,6,10,14,4,7,4,9,13,8,13,12,14,12,13,13,13,13,4,4,14,14,14,12,24,15,14,17,16,14,13,17,15,4,12,14,13,18,15,18,14,18,15,15,16,15,15,22,14,16,15,5,8,5,12,13,4,12,13,12,13,12,7,13,11,3,5,11,3,17,11,13,13,13,7,10,7,11,12,18,10,11,12,7,4,7,13}, - 3, // Byte width - 12, // Glyph height - 36, // Glyph stride - CGA_LargeFont_Data -}; - -static unsigned char CGA_RegularFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - // '"' - 0x66, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x66, 0x00, 0xff, 0x00, 0x66, 0x00, 0x66, 0x00, 0xff, 0x00, 0x66, 0x00, 0x00, 0x00, - // '$' - 0x18, 0x00, 0x3e, 0x00, 0x60, 0x00, 0x3c, 0x00, 0x06, 0x00, 0x7c, 0x00, 0x18, 0x00, - // '%' - 0xc6, 0x00, 0xcc, 0x00, 0x18, 0x00, 0x30, 0x00, 0x66, 0x00, 0xc6, 0x00, 0x00, 0x00, - // '&' - 0x38, 0x00, 0x60, 0x00, 0x33, 0x00, 0x7e, 0x00, 0xce, 0x00, 0x7b, 0x00, 0x00, 0x00, - // ''' - 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, - // ')' - 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, - // '*' - 0x66, 0x00, 0x3c, 0x00, 0xff, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x30, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - // '/' - 0x06, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x00, 0x00, 0x00, - // '0' - 0x3c, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x00, 0x00, - // '1' - 0x78, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, - // '2' - 0x3c, 0x00, 0x66, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x60, 0x00, 0x7e, 0x00, 0x00, 0x00, - // '3' - 0x3c, 0x00, 0x66, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x00, 0x00, - // '4' - 0x0e, 0x00, 0x1e, 0x00, 0x36, 0x00, 0x66, 0x00, 0x7e, 0x00, 0x06, 0x00, 0x00, 0x00, - // '5' - 0x7e, 0x00, 0x60, 0x00, 0x7c, 0x00, 0x06, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x00, 0x00, - // '6' - 0x3c, 0x00, 0x60, 0x00, 0x7c, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x00, 0x00, - // '7' - 0x7e, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, - // '8' - 0x3c, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x00, 0x00, - // '9' - 0x3c, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3e, 0x00, 0x06, 0x00, 0x3c, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x30, 0x00, - // '<' - 0x00, 0x00, 0x06, 0x00, 0x18, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x18, 0x00, 0x60, 0x00, 0x00, 0x00, - // '?' - 0x3c, 0x00, 0x66, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - // '@' - 0x3c, 0x00, 0xc3, 0x00, 0xcf, 0x00, 0xdb, 0x00, 0xcf, 0x00, 0xc0, 0x00, 0x3e, 0x00, - // 'A' - 0x18, 0x00, 0x18, 0x00, 0x3c, 0x00, 0x66, 0x00, 0x7e, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'B' - 0xfe, 0x00, 0x63, 0x00, 0x7e, 0x00, 0x63, 0x00, 0x63, 0x00, 0xfe, 0x00, 0x00, 0x00, - // 'C' - 0x7d, 0x00, 0xc3, 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xc1, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'D' - 0xfe, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0xfe, 0x00, 0x00, 0x00, - // 'E' - 0xff, 0x00, 0x61, 0x00, 0x7c, 0x00, 0x60, 0x00, 0x61, 0x00, 0xff, 0x00, 0x00, 0x00, - // 'F' - 0xff, 0x00, 0x61, 0x00, 0x7c, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'G' - 0x7d, 0x00, 0xc3, 0x00, 0xc0, 0x00, 0xcf, 0x00, 0xc3, 0x00, 0x7f, 0x00, 0x00, 0x00, - // 'H' - 0xc3, 0x00, 0xc3, 0x00, 0xff, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'I' - 0x7e, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'J' - 0x1f, 0x00, 0x06, 0x00, 0x06, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'K' - 0xe7, 0x00, 0x6c, 0x00, 0x78, 0x00, 0x6c, 0x00, 0x66, 0x00, 0xf3, 0x00, 0x00, 0x00, - // 'L' - 0xf8, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x61, 0x00, 0xff, 0x00, 0x00, 0x00, - // 'M' - 0xc3, 0x00, 0xe7, 0x00, 0xff, 0x00, 0xdb, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'N' - 0xe3, 0x00, 0xf3, 0x00, 0xdb, 0x00, 0xcf, 0x00, 0xc7, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'O' - 0x7e, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'P' - 0xfe, 0x00, 0x63, 0x00, 0x63, 0x00, 0x7e, 0x00, 0x60, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'Q' - 0x7e, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x3b, 0x00, - // 'R' - 0xfe, 0x00, 0x63, 0x00, 0x63, 0x00, 0x7c, 0x00, 0x66, 0x00, 0xf3, 0x00, 0x00, 0x00, - // 'S' - 0x7e, 0x00, 0xc3, 0x00, 0x78, 0x00, 0x0e, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'T' - 0xff, 0x00, 0x99, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'U' - 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'V' - 0xc3, 0x00, 0xc3, 0x00, 0x66, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x18, 0x00, 0x00, 0x00, - // 'W' - 0xc3, 0x00, 0xc3, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00, - // 'X' - 0xc3, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x66, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x00, 0x00, - // 'Y' - 0xc3, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x18, 0x00, 0x18, 0x00, 0x3c, 0x00, 0x00, 0x00, - // 'Z' - 0xff, 0x00, 0x86, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x61, 0x00, 0xff, 0x00, 0x00, 0x00, - // '[' - 0x3c, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3c, 0x00, - // '\' - 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, - // ']' - 0x3c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x3c, 0x00, - // '^' - 0x3c, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, - // '`' - 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x7c, 0x00, 0x06, 0x00, 0x7e, 0x00, 0xc6, 0x00, 0x7b, 0x00, 0x00, 0x00, - // 'b' - 0xe0, 0x00, 0x7e, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0xde, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x7f, 0x00, 0xc3, 0x00, 0xc0, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'd' - 0x0e, 0x00, 0x76, 0x00, 0xce, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7b, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x7e, 0x00, 0xc3, 0x00, 0xff, 0x00, 0xc0, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'f' - 0x1e, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x30, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x7b, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7e, 0x00, 0x06, 0x00, 0x3c, 0x00, - // 'h' - 0xe0, 0x00, 0x6c, 0x00, 0x76, 0x00, 0x66, 0x00, 0x66, 0x00, 0xf7, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x78, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x7e, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x7c, 0x00, - // 'k' - 0xe0, 0x00, 0x63, 0x00, 0x6e, 0x00, 0x78, 0x00, 0x6e, 0x00, 0xe3, 0x00, 0x00, 0x00, - // 'l' - 0x78, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0xb6, 0x00, 0xff, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0xec, 0x00, 0x76, 0x00, 0x66, 0x00, 0x66, 0x00, 0xf7, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x7e, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0xde, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x7e, 0x00, 0xe0, 0x00, - // 'q' - 0x00, 0x00, 0x7b, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0x7e, 0x00, 0x07, 0x00, - // 'r' - 0x00, 0x00, 0xde, 0x00, 0x63, 0x00, 0x60, 0x00, 0x60, 0x00, 0xf0, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x7e, 0x00, 0xc0, 0x00, 0x7e, 0x00, 0x03, 0x00, 0x7e, 0x00, 0x00, 0x00, - // 't' - 0x30, 0x00, 0xff, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x1f, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0xee, 0x00, 0x66, 0x00, 0x66, 0x00, 0x6e, 0x00, 0x37, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x18, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0xc3, 0x00, 0xdb, 0x00, 0xdb, 0x00, 0x66, 0x00, 0x66, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0xe7, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x66, 0x00, 0xe7, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0xc3, 0x00, 0xc3, 0x00, 0x66, 0x00, 0x3c, 0x00, 0x18, 0x00, 0xf0, 0x00, - // 'z' - 0x00, 0x00, 0xff, 0x00, 0x86, 0x00, 0x18, 0x00, 0x61, 0x00, 0xff, 0x00, 0x00, 0x00, - // '{' - 0x0e, 0x00, 0x18, 0x00, 0x18, 0x00, 0x70, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0e, 0x00, - // '|' - 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, - // '}' - 0x70, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x18, 0x00, 0x70, 0x00, - // '~' - 0x00, 0x00, 0x7b, 0x00, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font CGA_RegularFont_Monospace = { - // Glyph widths - { 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9}, - 2, // Byte width - 7, // Glyph height - 14, // Glyph stride - CGA_RegularFont_Monospace_Data -}; - -static unsigned char CGA_SmallFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x30, 0x30, 0x30, 0x00, 0x30, 0x00, - // '"' - 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x00, - // '$' - 0x30, 0x7c, 0xf0, 0x3c, 0xf8, 0x30, - // '%' - 0xcc, 0x18, 0x30, 0x60, 0xcc, 0x00, - // '&' - 0x70, 0xc0, 0x76, 0xcc, 0x76, 0x00, - // ''' - 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x18, 0x30, 0x30, 0x30, 0x30, 0x18, - // ')' - 0x30, 0x18, 0x18, 0x18, 0x18, 0x30, - // '*' - 0x00, 0x38, 0xfe, 0x6c, 0x00, 0x00, - // '+' - 0x00, 0x30, 0xfc, 0x30, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x30, 0x20, - // '-' - 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, - // '/' - 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, - // '0' - 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, - // '1' - 0x70, 0x30, 0x30, 0x30, 0xfc, 0x00, - // '2' - 0x7c, 0xc6, 0x18, 0x60, 0xfe, 0x00, - // '3' - 0xfc, 0x06, 0x3c, 0x06, 0xfc, 0x00, - // '4' - 0x1c, 0x3c, 0x6c, 0xfe, 0x0c, 0x00, - // '5' - 0xfc, 0xc0, 0xfc, 0x06, 0xfc, 0x00, - // '6' - 0x78, 0xc0, 0xfc, 0xc6, 0x7c, 0x00, - // '7' - 0xfe, 0x0c, 0x18, 0x30, 0x60, 0x00, - // '8' - 0x7c, 0xc6, 0x7c, 0xc6, 0x7c, 0x00, - // '9' - 0x7c, 0xc6, 0x7e, 0x06, 0x3c, 0x00, - // ':' - 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, - // ';' - 0x00, 0x30, 0x00, 0x00, 0x30, 0x20, - // '<' - 0x0e, 0x38, 0xe0, 0x38, 0x0e, 0x00, - // '=' - 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x00, - // '>' - 0xe0, 0x38, 0x0e, 0x38, 0xe0, 0x00, - // '?' - 0x78, 0x0c, 0x38, 0x00, 0x30, 0x00, - // '@' - 0x7c, 0x82, 0xba, 0xbe, 0x80, 0x78, - // 'A' - 0x38, 0x6c, 0xc6, 0xfe, 0xc6, 0x00, - // 'B' - 0xfc, 0x66, 0x7c, 0x66, 0xfc, 0x00, - // 'C' - 0x7e, 0xc6, 0xc0, 0xc6, 0x7c, 0x00, - // 'D' - 0xfc, 0x66, 0x66, 0x66, 0xfc, 0x00, - // 'E' - 0xfe, 0x60, 0x78, 0x60, 0xfe, 0x00, - // 'F' - 0xfe, 0x60, 0x78, 0x60, 0xf0, 0x00, - // 'G' - 0x7e, 0xc0, 0xde, 0xc6, 0x7e, 0x00, - // 'H' - 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0x00, - // 'I' - 0xfc, 0x30, 0x30, 0x30, 0xfc, 0x00, - // 'J' - 0x3e, 0x0c, 0x0c, 0xcc, 0x78, 0x00, - // 'K' - 0xe6, 0x6c, 0x78, 0x6c, 0xe6, 0x00, - // 'L' - 0xf0, 0x60, 0x60, 0x60, 0xfe, 0x00, - // 'M' - 0x82, 0xc6, 0xee, 0xd6, 0xc6, 0x00, - // 'N' - 0xc6, 0xe6, 0xd6, 0xce, 0xc6, 0x00, - // 'O' - 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, - // 'P' - 0xfc, 0x66, 0x7c, 0x60, 0xf0, 0x00, - // 'Q' - 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x36, - // 'R' - 0xfc, 0x66, 0x7c, 0x6c, 0xf6, 0x00, - // 'S' - 0x7c, 0xc0, 0x7c, 0x06, 0x7c, 0x00, - // 'T' - 0xfc, 0xb4, 0x30, 0x30, 0x78, 0x00, - // 'U' - 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, - // 'V' - 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, - // 'W' - 0xc6, 0xc6, 0xd6, 0x7c, 0x28, 0x00, - // 'X' - 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00, - // 'Y' - 0xc6, 0x6c, 0x38, 0x10, 0x38, 0x00, - // 'Z' - 0xfe, 0x0c, 0x38, 0x60, 0xfe, 0x00, - // '[' - 0x3c, 0x30, 0x30, 0x30, 0x30, 0x3c, - // '\' - 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, - // ']' - 0x78, 0x18, 0x18, 0x18, 0x18, 0x78, - // '^' - 0x30, 0x78, 0xcc, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - // '`' - 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x78, 0xcc, 0xcc, 0x76, 0x00, - // 'b' - 0xe0, 0x7c, 0x66, 0x66, 0xfc, 0x00, - // 'c' - 0x00, 0x7e, 0xc0, 0xc0, 0x7e, 0x00, - // 'd' - 0x0c, 0x7c, 0xcc, 0xcc, 0x7e, 0x00, - // 'e' - 0x00, 0x7c, 0xee, 0xc0, 0x7c, 0x00, - // 'f' - 0x1c, 0x30, 0x7c, 0x30, 0x7c, 0x00, - // 'g' - 0x00, 0x7e, 0xcc, 0x7c, 0x0c, 0x38, - // 'h' - 0xc0, 0xdc, 0xe6, 0xc6, 0xc6, 0x00, - // 'i' - 0x30, 0xf0, 0x30, 0x30, 0xfc, 0x00, - // 'j' - 0x18, 0x78, 0x18, 0x18, 0x18, 0x70, - // 'k' - 0xe0, 0x6c, 0x78, 0x6c, 0xe6, 0x00, - // 'l' - 0xf0, 0x30, 0x30, 0x30, 0xfc, 0x00, - // 'm' - 0x00, 0xec, 0xd6, 0xd6, 0xd6, 0x00, - // 'n' - 0x00, 0xdc, 0xe6, 0xc6, 0xc6, 0x00, - // 'o' - 0x00, 0x7c, 0xc6, 0xc6, 0x7c, 0x00, - // 'p' - 0x00, 0xfc, 0x66, 0x66, 0x7c, 0xe0, - // 'q' - 0x00, 0x7e, 0xcc, 0xcc, 0x7c, 0x0e, - // 'r' - 0x00, 0xee, 0x76, 0x60, 0xf8, 0x00, - // 's' - 0x00, 0x3c, 0x60, 0x1c, 0x78, 0x00, - // 't' - 0x30, 0xfc, 0x30, 0x30, 0x1e, 0x00, - // 'u' - 0x00, 0xc6, 0xc6, 0xce, 0x76, 0x00, - // 'v' - 0x00, 0xc6, 0x6c, 0x38, 0x10, 0x00, - // 'w' - 0x00, 0xc6, 0xd6, 0x7c, 0x28, 0x00, - // 'x' - 0x00, 0xc6, 0x38, 0x6c, 0xc6, 0x00, - // 'y' - 0x00, 0xc6, 0x6c, 0x38, 0x30, 0xe0, - // 'z' - 0x00, 0xfe, 0x0c, 0x70, 0xfe, 0x00, - // '{' - 0x0c, 0x18, 0x30, 0x18, 0x18, 0x0c, - // '|' - 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, - // '}' - 0x60, 0x30, 0x18, 0x30, 0x30, 0x60, - // '~' - 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, -}; - -Font CGA_SmallFont_Monospace = { - // Glyph widths - { 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8}, - 1, // Byte width - 6, // Glyph height - 6, // Glyph stride - CGA_SmallFont_Monospace_Data -}; - -static unsigned char CGA_LargeFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '"' - 0x33, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x0c, 0xc0, 0x19, 0x80, 0x7f, 0xc0, 0x33, 0x00, 0xff, 0x80, 0x66, 0x00, 0xcc, 0x00, 0x00, 0x00, - // '$' - 0x0c, 0x00, 0x3f, 0x00, 0x61, 0x80, 0x3c, 0x00, 0x0f, 0x00, 0x61, 0x80, 0x3f, 0x00, 0x0c, 0x00, - // '%' - 0x71, 0x80, 0xdb, 0x00, 0x76, 0x00, 0x0c, 0x00, 0x1b, 0x80, 0x36, 0xc0, 0x63, 0x80, 0x00, 0x00, - // '&' - 0x3e, 0x00, 0x63, 0x00, 0x30, 0x00, 0x79, 0x80, 0xcd, 0x80, 0xc7, 0x00, 0x7d, 0xc0, 0x00, 0x00, - // ''' - 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x06, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x06, 0x00, - // ')' - 0x18, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x18, 0x00, - // '*' - 0x00, 0x00, 0x33, 0x00, 0x1e, 0x00, 0xff, 0xc0, 0x1e, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0xff, 0xc0, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x18, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '/' - 0x01, 0x80, 0x03, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x00, - // '0' - 0x3f, 0x00, 0x61, 0x80, 0x61, 0x80, 0x61, 0x80, 0x61, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x00, 0x00, - // '1' - 0x3c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x7f, 0x80, 0x00, 0x00, - // '2' - 0x3f, 0x00, 0x61, 0x80, 0x01, 0x80, 0x03, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x7f, 0x80, 0x00, 0x00, - // '3' - 0x3f, 0x00, 0x61, 0x80, 0x01, 0x80, 0x0f, 0x00, 0x01, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x00, 0x00, - // '4' - 0x0f, 0x00, 0x1b, 0x00, 0x33, 0x00, 0x63, 0x00, 0x7f, 0x80, 0x03, 0x00, 0x0f, 0x80, 0x00, 0x00, - // '5' - 0x7f, 0x00, 0x60, 0x00, 0x6f, 0x00, 0x71, 0x80, 0x01, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x00, 0x00, - // '6' - 0x1f, 0x00, 0x30, 0x00, 0x60, 0x00, 0x7f, 0x00, 0x61, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x00, 0x00, - // '7' - 0x7f, 0x80, 0x61, 0x80, 0x03, 0x00, 0x03, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '8' - 0x3f, 0x00, 0x61, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x61, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x00, 0x00, - // '9' - 0x3f, 0x00, 0x61, 0x80, 0x61, 0x80, 0x3f, 0x80, 0x01, 0x80, 0x03, 0x00, 0x3e, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x18, 0x00, - // '<' - 0x01, 0x80, 0x06, 0x00, 0x18, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x01, 0x80, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x01, 0x80, 0x06, 0x00, 0x18, 0x00, 0x60, 0x00, 0x00, 0x00, - // '?' - 0x3f, 0x00, 0x61, 0x80, 0x01, 0x80, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '@' - 0x1f, 0x80, 0x60, 0x60, 0x67, 0xe0, 0x6c, 0x60, 0x67, 0xe0, 0x60, 0x00, 0x1f, 0x80, 0x00, 0x00, - // 'A' - 0x1e, 0x00, 0x06, 0x00, 0x0f, 0x00, 0x19, 0x80, 0x3f, 0xc0, 0x60, 0x60, 0xf0, 0xf0, 0x00, 0x00, - // 'B' - 0x7f, 0x80, 0x30, 0xc0, 0x30, 0xc0, 0x3f, 0x80, 0x30, 0x60, 0x30, 0x60, 0x7f, 0xc0, 0x00, 0x00, - // 'C' - 0x0f, 0x20, 0x30, 0xe0, 0x60, 0x60, 0x60, 0x00, 0x60, 0x00, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, - // 'D' - 0x7f, 0x00, 0x30, 0xc0, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x30, 0xc0, 0x7f, 0x00, 0x00, 0x00, - // 'E' - 0x7f, 0xe0, 0x30, 0x60, 0x33, 0x00, 0x3f, 0x00, 0x33, 0x00, 0x30, 0x60, 0x7f, 0xe0, 0x00, 0x00, - // 'F' - 0x7f, 0xe0, 0x30, 0x60, 0x33, 0x00, 0x3f, 0x00, 0x33, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'G' - 0x0f, 0x20, 0x30, 0xe0, 0x60, 0x60, 0x60, 0x00, 0x61, 0xf0, 0x30, 0x60, 0x0f, 0xa0, 0x00, 0x00, - // 'H' - 0x79, 0xe0, 0x30, 0xc0, 0x30, 0xc0, 0x3f, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x79, 0xe0, 0x00, 0x00, - // 'I' - 0x3f, 0xc0, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x3f, 0xc0, 0x00, 0x00, - // 'J' - 0x0f, 0xe0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x61, 0x80, 0x61, 0x80, 0x3f, 0x00, 0x00, 0x00, - // 'K' - 0x79, 0xe0, 0x31, 0x80, 0x33, 0x00, 0x3e, 0x00, 0x33, 0x00, 0x31, 0x80, 0x78, 0xe0, 0x00, 0x00, - // 'L' - 0x7c, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x60, 0x7f, 0xe0, 0x00, 0x00, - // 'M' - 0xe0, 0x70, 0x70, 0xe0, 0x79, 0xe0, 0x6f, 0x60, 0x66, 0x60, 0x60, 0x60, 0xf0, 0xf0, 0x00, 0x00, - // 'N' - 0x71, 0xe0, 0x38, 0xc0, 0x3c, 0xc0, 0x36, 0xc0, 0x33, 0xc0, 0x31, 0xc0, 0x78, 0xc0, 0x00, 0x00, - // 'O' - 0x0f, 0x00, 0x30, 0xc0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x30, 0xc0, 0x0f, 0x00, 0x00, 0x00, - // 'P' - 0x7f, 0x80, 0x30, 0x60, 0x30, 0x60, 0x3f, 0x80, 0x30, 0x00, 0x30, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'Q' - 0x0f, 0x00, 0x30, 0xc0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x30, 0xc0, 0x0f, 0x00, 0x19, 0xc0, - // 'R' - 0x7f, 0x80, 0x30, 0x60, 0x30, 0x60, 0x3f, 0x80, 0x31, 0x80, 0x30, 0xc0, 0x78, 0x70, 0x00, 0x00, - // 'S' - 0x3f, 0xc0, 0x60, 0x60, 0x70, 0x00, 0x0f, 0x00, 0x00, 0xe0, 0x60, 0x60, 0x3f, 0xc0, 0x00, 0x00, - // 'T' - 0x7f, 0xe0, 0x66, 0x60, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x1f, 0x80, 0x00, 0x00, - // 'U' - 0x79, 0xe0, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x1f, 0x80, 0x00, 0x00, - // 'V' - 0xf0, 0xf0, 0x60, 0x60, 0x30, 0xc0, 0x30, 0xc0, 0x19, 0x80, 0x0f, 0x00, 0x06, 0x00, 0x00, 0x00, - // 'W' - 0xf9, 0xf0, 0x60, 0x60, 0x66, 0x60, 0x66, 0x60, 0x3f, 0xc0, 0x19, 0x80, 0x19, 0x80, 0x00, 0x00, - // 'X' - 0x79, 0xe0, 0x19, 0x80, 0x0f, 0x00, 0x06, 0x00, 0x0f, 0x00, 0x19, 0x80, 0x79, 0xe0, 0x00, 0x00, - // 'Y' - 0x79, 0xe0, 0x30, 0xc0, 0x19, 0x80, 0x0f, 0x00, 0x06, 0x00, 0x06, 0x00, 0x1f, 0x80, 0x00, 0x00, - // 'Z' - 0x7f, 0xe0, 0x60, 0xe0, 0x03, 0x80, 0x06, 0x00, 0x1c, 0x00, 0x70, 0x60, 0x7f, 0xe0, 0x00, 0x00, - // '[' - 0x1e, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x1e, 0x00, - // '\' - 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x03, 0x00, 0x01, 0x80, 0x00, 0x00, - // ']' - 0x1e, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x1e, 0x00, - // '^' - 0x06, 0x00, 0x0f, 0x00, 0x19, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, - // '`' - 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0xc0, 0x3f, 0xc0, 0x60, 0xc0, 0x3f, 0x60, 0x00, 0x00, - // 'b' - 0x70, 0x00, 0x30, 0x00, 0x3f, 0xc0, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x6f, 0xc0, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x60, 0x60, 0x60, 0x00, 0x60, 0x60, 0x3f, 0xc0, 0x00, 0x00, - // 'd' - 0x01, 0xc0, 0x00, 0xc0, 0x3f, 0xc0, 0x60, 0xc0, 0x60, 0xc0, 0x60, 0xc0, 0x3f, 0x60, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x60, 0x60, 0x7f, 0xe0, 0x60, 0x00, 0x3f, 0xc0, 0x00, 0x00, - // 'f' - 0x07, 0xc0, 0x0c, 0x00, 0x3f, 0x80, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x3f, 0x80, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0x60, 0x60, 0xc0, 0x60, 0xc0, 0x3f, 0xc0, 0x00, 0xc0, 0x1f, 0x80, - // 'h' - 0x70, 0x00, 0x30, 0x00, 0x37, 0x80, 0x38, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x79, 0xe0, 0x00, 0x00, - // 'i' - 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x3f, 0xc0, 0x00, 0x00, - // 'j' - 0x01, 0x80, 0x00, 0x00, 0x1f, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x3f, 0x00, - // 'k' - 0x70, 0x00, 0x30, 0x00, 0x31, 0xc0, 0x33, 0x00, 0x3e, 0x00, 0x33, 0x80, 0x70, 0xe0, 0x00, 0x00, - // 'l' - 0x1e, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x3f, 0xc0, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0xdd, 0xc0, 0x66, 0x60, 0x66, 0x60, 0x66, 0x60, 0xf7, 0x70, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x77, 0x80, 0x38, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x79, 0xe0, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x3f, 0xc0, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x6f, 0xc0, 0x30, 0x60, 0x30, 0x60, 0x30, 0x60, 0x3f, 0xc0, 0x70, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0x60, 0x60, 0xc0, 0x60, 0xc0, 0x60, 0xc0, 0x3f, 0xc0, 0x00, 0xe0, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x3b, 0xc0, 0x1c, 0x60, 0x18, 0x00, 0x18, 0x00, 0x3e, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x60, 0x00, 0x3f, 0xc0, 0x00, 0x60, 0x3f, 0xc0, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x0c, 0x00, 0x3f, 0xc0, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x07, 0xc0, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x71, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x31, 0xc0, 0x1e, 0xe0, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x79, 0xe0, 0x30, 0xc0, 0x19, 0x80, 0x0f, 0x00, 0x06, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf0, 0x60, 0x60, 0x36, 0xc0, 0x3f, 0xc0, 0x19, 0x80, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x79, 0xe0, 0x19, 0x80, 0x0f, 0x00, 0x19, 0x80, 0x79, 0xe0, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x79, 0xe0, 0x30, 0xc0, 0x19, 0x80, 0x0f, 0x00, 0x06, 0x00, 0x7c, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x31, 0x80, 0x06, 0x00, 0x18, 0xc0, 0x3f, 0xc0, 0x00, 0x00, - // '{' - 0x03, 0x80, 0x06, 0x00, 0x06, 0x00, 0x1c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x80, - // '|' - 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, - // '}' - 0x1c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x03, 0x80, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x1c, 0x00, - // '~' - 0x00, 0x00, 0x1e, 0xc0, 0x37, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font CGA_LargeFont_Monospace = { - // Glyph widths - { 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12}, - 2, // Byte width - 8, // Glyph height - 16, // Glyph stride - CGA_LargeFont_Monospace_Data -}; - - -// Mouse cursors: -MouseCursorData CGA_MouseCursor = { - { - 0x1fff,0x07ff,0x01ff,0x007f,0x001f,0x0007,0x0001,0x0000,0x001f,0x0c0f,0x1e07,0xff0f,0xffff,0xffff,0xffff,0xffff, - 0xe000,0x9800,0x8600,0x8180,0x8060,0x8018,0x8006,0x807f,0x8c20,0x9210,0xe108,0x00f0,0x0000,0x0000,0x0000,0x0000, - }, - // Hot spot - 0, 0 -}; - -MouseCursorData CGA_MouseCursorHand = { - { - 0xf9ff,0xf0ff,0xf0ff,0xf0ff,0xf007,0xf001,0x9000,0x0000,0x0000,0x8000,0xc000,0xe001,0xf003,0xf807,0xffff,0xffff, - 0x0000,0x0600,0x0600,0x0600,0x0600,0x06d8,0x06da,0x67fe,0x77fe,0x3ffe,0x1ffe,0x0ffc,0x07f8,0x0000,0x0000,0x0000, - }, - // Hot spot - 6, 1 -}; - -MouseCursorData CGA_MouseCursorTextSelect = { - { - 0xf9cf,0xf087,0xf80f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xf80f,0xf087,0xf9cf,0xffff,0xffff,0xffff, - 0x0630,0x0948,0x06b0,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x06b0,0x0948,0x0630,0x0000,0x0000,0x0000, - }, - // Hot spot - 8, 6 -}; - - -// Images: -static unsigned char CGA_ImageIcon_Data[] = { - 0xff, 0xe0, 0x80, 0x50, 0x98, 0x78, 0x81, 0x88, 0x9b, 0xc8, 0xbf, 0xe8, 0x80, 0x08, 0xff, 0xf8, -}; - -Image CGA_ImageIcon = { - // Dimensions - 13, 8, - CGA_ImageIcon_Data -}; - -static unsigned char CGA_Bullet_Data[] = { - 0x00, 0x78, 0xfc, 0xfc, 0x78, 0x00, 0x00, -}; - -Image CGA_Bullet = { - // Dimensions - 6, 7, - CGA_Bullet_Data -}; - diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index c4e4c81..aa857fb 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -20,6 +20,7 @@ #include #include "DOSInput.h" #include "../Keycodes.h" +#include "../DataPack.h" void DOSInputDriver::Init() { @@ -30,8 +31,7 @@ void DOSInputDriver::Init() currentCursor = MouseCursor::Hand; SetMouseCursor(MouseCursor::Pointer); - lastMouseButtons = 0; - mouseVisible = false; + mouseHideCount = 1; ShowMouse(); } @@ -45,12 +45,14 @@ void DOSInputDriver::ShowMouse() { if (!hasMouse) return; - if (mouseVisible) + + mouseHideCount--; + if (mouseHideCount > 0) return; + union REGS inreg, outreg; inreg.x.ax = 1; int86(0x33, &inreg, &outreg); - mouseVisible = true; } void DOSInputDriver::SetMousePosition(int x, int y) @@ -64,12 +66,56 @@ void DOSInputDriver::SetMousePosition(int x, int y) int86(0x33, &inreg, &outreg); } +bool DOSInputDriver::GetMouseButtonPress(int& x, int& y) +{ + if (!hasMouse) + return false; + + union REGS inreg, outreg; + inreg.x.ax = 5; + inreg.x.bx = 0; + int86(0x33, &inreg, &outreg); + x = outreg.x.cx; + y = outreg.x.dx; + + if (Platform::video->screenWidth == 320) + { + x /= 2; + } + + return (outreg.x.bx > 0); +} + +bool DOSInputDriver::GetMouseButtonRelease(int& x, int& y) +{ + if (!hasMouse) + return false; + + union REGS inreg, outreg; + inreg.x.ax = 6; + inreg.x.bx = 0; + int86(0x33, &inreg, &outreg); + x = outreg.x.cx; + y = outreg.x.dx; + + if (Platform::video->screenWidth == 320) + { + x /= 2; + } + + return (outreg.x.bx > 0); +} + + void DOSInputDriver::HideMouse() { if (!hasMouse) return; - if (!mouseVisible) + + mouseHideCount++; + if (mouseHideCount > 1) return; + union REGS inreg, outreg; inreg.x.ax = 2; int86(0x33, &inreg, &outreg); @@ -92,7 +138,7 @@ void DOSInputDriver::SetMouseCursor(MouseCursor::Type type) { return; } - MouseCursorData* cursor = Platform::video->GetCursorGraphic(type); + MouseCursorData* cursor = Assets.GetMouseCursorData(type); if (cursor) { SetMouseCursorASM(cursor->data, cursor->hotSpotX, cursor->hotSpotY); @@ -135,10 +181,9 @@ void DOSInputDriver::GetMouseStatus(int& buttons, int& x, int& y) y = outreg.x.dx; buttons = outreg.x.bx; - if (Platform::video->isTextMode) + if (Platform::video->screenWidth == 320) { - x >>= 3; - y >>= 3; + x /= 2; } } @@ -154,23 +199,5 @@ InputButtonCode DOSInputDriver::GetKeyPress() return keyPress; } - int buttons, mouseX, mouseY; - GetMouseStatus(buttons, mouseX, mouseY); - - int oldButtons = lastMouseButtons; - lastMouseButtons = buttons; - - if (buttons != oldButtons) - { - if ((buttons & 1) && !(oldButtons & 1)) - { - return KEYCODE_MOUSE_LEFT; - } - if ((buttons & 2) && !(oldButtons & 2)) - { - return KEYCODE_MOUSE_RIGHT; - } - } - return 0; } diff --git a/src/DOS/DOSInput.h b/src/DOS/DOSInput.h index 228e4f2..b4a9bf1 100644 --- a/src/DOS/DOSInput.h +++ b/src/DOS/DOSInput.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _DOSINPUT_H_ +#define _DOSINPUT_H_ #include "../Platform.h" class DOSInputDriver : public InputDriver @@ -27,11 +28,16 @@ class DOSInputDriver : public InputDriver virtual void GetMouseStatus(int& buttons, int& x, int& y); virtual void SetMousePosition(int x, int y); + virtual bool GetMouseButtonPress(int& x, int& y); + virtual bool GetMouseButtonRelease(int& x, int& y); + virtual InputButtonCode GetKeyPress(); private: MouseCursor::Type currentCursor; - int lastMouseButtons; bool mouseVisible; bool hasMouse; + int mouseHideCount; }; + +#endif diff --git a/src/DOS/DOSNet.cpp b/src/DOS/DOSNet.cpp index a760b6e..512aa8f 100644 --- a/src/DOS/DOSNet.cpp +++ b/src/DOS/DOSNet.cpp @@ -13,6 +13,7 @@ // #include "DOSNet.h" +#include "../HTTP.h" #include #include @@ -52,23 +53,32 @@ void DOSNetworkDriver::Init() //printf("Init network driver\n"); if (Utils::parseEnv() != 0) { - fprintf(stderr, "\nFailed in parseEnv()\n"); - //exit(1); + printf("Failed in parseEnv()\n"); return; } //printf("Init network stack\n"); if (Utils::initStack(MAX_CONCURRENT_HTTP_REQUESTS, TCP_SOCKET_RING_SIZE, ctrlBreakHandler, ctrlBreakHandler)) { - fprintf(stderr, "\nFailed to initialize TCP/IP - exiting\n"); - //exit(1); + printf("Failed to initialize TCP/IP\n"); return; } for (int n = 0; n < MAX_CONCURRENT_HTTP_REQUESTS; n++) { - requests[n] = new DOSHTTPRequest(); + requests[n] = new HTTPRequest(); + if (!requests[n]) + { + Platform::FatalError("Could not allocate memory for HTTP request"); + } + + sockets[n] = new DOSTCPSocket(); + if (!sockets[n]) + { + Platform::FatalError("Could not allocate memory for TCP socket"); + } } + printf("Network interface initialised\n"); isConnected = true; } @@ -122,447 +132,91 @@ void DOSNetworkDriver::DestroyRequest(HTTPRequest* request) } } -DOSHTTPRequest::DOSHTTPRequest() : status(HTTPRequest::Stopped), sock(NULL) -{ -} - -void DOSHTTPRequest::Reset() +// Returns zero on success, negative number is error +int DOSNetworkDriver::ResolveAddress(const char* name, NetworkAddress address, bool sendRequest) { - lineBufferSize = 0; - lineBufferSendPos = -1; -} - -void DOSHTTPRequest::WriteLine(char* fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - int maxWriteSize = LINE_BUFFER_SIZE - lineBufferSize - 2; - int vsrc = vsnprintf(lineBuffer + lineBufferSize, maxWriteSize, fmt, ap); - va_end(ap); - - if ((vsrc < 0) || (vsrc >= maxWriteSize)) - { - Error(WriteLineError); - lineBufferSendPos = -1; - } - else - { - if (lineBufferSendPos == -1) - { - lineBufferSendPos = 0; - } - lineBufferSize += vsrc; - lineBuffer[lineBufferSize++] = '\r'; - lineBuffer[lineBufferSize++] = '\n'; - } + return Dns::resolve(name, address, sendRequest ? 1 : 0); } -bool DOSHTTPRequest::SendPendingWrites() +NetworkTCPSocket* DOSNetworkDriver::CreateSocket() { - if (sock) + for (int n = 0; n < MAX_CONCURRENT_HTTP_REQUESTS; n++) { - if (lineBufferSendPos < 0) - { - return false; - } - - int rc = sock->send((uint8_t*)(lineBuffer + lineBufferSendPos), lineBufferSize - lineBufferSendPos); - if (rc > 0) + if (sockets[n]->GetSock() == NULL) { - lineBufferSendPos += rc; - if (lineBufferSendPos >= lineBufferSize) + TcpSocket* sock = TcpSocketMgr::getSocket(); + if (sock) { - lineBufferSendPos = -1; - lineBufferSize = 0; + sockets[n]->SetSock(sock); + return sockets[n]; } + return NULL; } - else if (rc < 0) - { - Error(WriteLineError); - lineBufferSendPos = -1; - } - return lineBufferSendPos >= 0; } - return false; + + return NULL; } -void DOSHTTPRequest::Open(char* inURL) +void DOSNetworkDriver::DestroySocket(NetworkTCPSocket* socket) { - url = inURL; - Reset(); - - if (strnicmp(url.url, "http://", 7) == 0) { - - char* hostnameStart = url.url + 7; - - // Scan ahead for another slash; if there is none then we - // only have a server name and we should fetch the top - // level directory. - - char* proxy = getenv("HTTP_PROXY"); - if (proxy == NULL) { - - char* pathStart = strchr(hostnameStart, '/'); - if (pathStart == NULL) { - - strncpy(hostname, hostnameStart, HOSTNAME_LEN); - hostname[HOSTNAME_LEN - 1] = 0; - - path[0] = '/'; - path[1] = 0; - - } - else { - - strncpy(hostname, hostnameStart, pathStart - hostnameStart); - hostname[pathStart - hostnameStart] = '\0'; - hostname[HOSTNAME_LEN - 1] = 0; - - strncpy(path, pathStart, PATH_LEN); - path[PATH_LEN - 1] = 0; - - } - - } - else { - - strncpy(hostname, proxy, HOSTNAME_LEN); - hostname[HOSTNAME_LEN - 1] = 0; - - strncpy(path, url.url, PATH_LEN); - path[PATH_LEN - 1] = 0; - - } - - serverPort = 80; - char* portStart = strchr(hostname, ':'); - - if (portStart != NULL) { - serverPort = atoi(portStart + 1); - if (serverPort == 0) { - Error(InvalidPort); - return; - } + socket->Close(); +} - // Truncate hostname early - *portStart = 0; - } +DOSTCPSocket::DOSTCPSocket() +{ + sock = NULL; +} - status = HTTPRequest::Connecting; - internalStatus = QueuedDNSRequest; - } - else if (strnicmp(url.url, "https://", 8) == 0) { - status = HTTPRequest::UnsupportedHTTPS; - } - else { - // Need to specify a URL starting with http:// - Error(InvalidProtocol); +void DOSTCPSocket::SetSock(TcpSocket* inSock) +{ + sock = inSock; + if (sock) + { + sock->rcvBuffer = recvBuffer; + sock->rcvBufSize = TCP_RECV_BUFFER_SIZE; } } -size_t DOSHTTPRequest::ReadData(char* buffer, size_t count) +int DOSTCPSocket::Send(uint8_t* data, int length) { - if (status == HTTPRequest::Downloading && sock) + if (!sock) { - int16_t rc = sock->recv((unsigned char*) buffer, count); - if (rc < 0) - { - Error(ContentReceiveError); - } - else - { - return (size_t)(rc); - } + return -1; } - return 0; + return sock->send(data, length); } -void DOSHTTPRequest::Stop() +int DOSTCPSocket::Receive(uint8_t* buffer, int length) { - if(sock) + if (!sock) { - sock->closeNonblocking(); - TcpSocketMgr::freeSocket(sock); - sock = NULL; + return -1; } - status = HTTPRequest::Stopped; + return sock->recv(buffer, length); } -void DOSHTTPRequest::Error(InternalStatus statusError) +int DOSTCPSocket::Connect(NetworkAddress address, int port) { - status = HTTPRequest::Error; - internalStatus = statusError; + uint16_t localport = 2048 + rand(); + return sock->connectNonBlocking(localport, address, port); } -void DOSHTTPRequest::Update() +bool DOSTCPSocket::IsConnectComplete() { - if (SendPendingWrites()) - { - return; - } - - switch (status) - { - case HTTPRequest::Connecting: - { - switch (internalStatus) - { - case QueuedDNSRequest: - { - int8_t rc = Dns::resolve(hostname, hostAddr, 1); - if (rc == 1) - { - internalStatus = WaitingDNSResolve; - } - else if (rc == 0) - { - internalStatus = OpeningSocket; - - //printf("Host %s resolved to %d.%d.%d.%d\n", hostname, hostAddr[0], hostAddr[1], hostAddr[2], hostAddr[3]); - //status = HTTPRequest::Stopped; - } - } - break; - case WaitingDNSResolve: - { - int8_t rc = Dns::resolve(hostname, hostAddr, 0); - if (rc == 0) - { - internalStatus = OpeningSocket; - //printf("Host %s resolved to %d.%d.%d.%d\n", hostname, hostAddr[0], hostAddr[1], hostAddr[2], hostAddr[3]); - //status = HTTPRequest::Stopped; - } - else - { - //printf("Resolve host \"%s\"", hostname); - //getchar(); - } - } - break; - case OpeningSocket: - { - sock = TcpSocketMgr::getSocket(); - if (!sock) - { - Error(SocketCreationError); - } - sock->rcvBuffer = recvBuffer; - sock->rcvBufSize = TCP_RECV_BUFFER_SIZE; - - uint16_t localport = 2048 + rand(); - - if (sock->connectNonBlocking(localport, hostAddr, serverPort)) - { - Error(SocketCreationError); - break; - } - internalStatus = ConnectingSocket; - } - break; - case ConnectingSocket: - { - if (sock->isConnectComplete()) - { - internalStatus = SendHeaders; - break; - } - else if(sock->isClosed()) - { - Error(SocketConnectionError); - break; - } - } - break; - case SendHeaders: - { - char* agent = getenv("HTTP_USER_AGENT"); - WriteLine("GET %s HTTP/1.0", path); - if (agent == NULL) { - WriteLine("User-Agent: MicroWeb " __DATE__); - } - else { - WriteLine("User-Agent: MicroWeb %s %s", __DATE__, agent); - } - WriteLine("Host: %s", hostname); - WriteLine("Connection: close"); - WriteLine(); - internalStatus = ReceiveHeaderResponse; - } - break; - case ReceiveHeaderResponse: - { - if (ReadLine()) - { - if ((strncmp(lineBuffer, "HTTP/1.0", 8) != 0) && (strncmp(lineBuffer, "HTTP/1.1", 8) != 0)) { - Error(UnsupportedHTTPError); - return; - } - - // Skip past HTTP version number - char* s = lineBuffer + 8; - char* s2 = s; - - // Skip past whitespace - while (*s) { - if (*s != ' ' && *s != '\t') break; - s++; - } - - if ((s == s2) || (*s == 0) || (sscanf(s, "%3d", &responseCode) != 1)) { - Error(MalformedHTTPVersionLineError); - return; - } - - //printf("Response code: %d", responseCode); - //getchar(); - internalStatus = ReceiveHeaderContent; - } - } - break; - case ReceiveHeaderContent: - { - if (ReadLine()) - { - if (lineBuffer[0] == '\0') - { - // Header has finished - status = Downloading; - internalStatus = ReceiveContent; - break; - } - - if (!strncmp(lineBuffer, "Location: ", 10)) - { - if (responseCode == RESPONSE_MOVED_PERMANENTLY || responseCode == RESPONSE_MOVED_TEMPORARILY || responseCode == RESPONSE_TEMPORARY_REDIRECTION || responseCode == RESPONSE_PERMANENT_REDIRECT) - { - //printf("Redirecting to %s", lineBuffer + 10); - //getchar(); - Stop(); - Open(lineBuffer + 10); - break; - } - } - - //printf("Header: %s -- ", lineBuffer); - //getchar(); - } - } - break; - } - } - break; - - case HTTPRequest::Downloading: - { - if (sock->isRemoteClosed()) - { - //status = HTTPRequest::Finished; - } - } - break; - } + return sock && sock->isConnectComplete(); } -bool DOSHTTPRequest::ReadLine() +bool DOSTCPSocket::IsClosed() { - while (1) - { - int rc = sock->recv((unsigned char*) lineBuffer + lineBufferSize, 1); - if (rc == 0) - { - // Need to wait for new packets to be received, defer - return false; - } - else if (rc < 0) - { - printf("Receive error\n"); - getchar(); - Error(ContentReceiveError); - return false; - } - - if (lineBufferSize >= LINE_BUFFER_SIZE) - { - // Line was too long - lineBuffer[LINE_BUFFER_SIZE - 1] = '\0'; - Error(ContentReceiveError); - return false; - } - - if (lineBuffer[lineBufferSize] == '\n') - { - lineBuffer[lineBufferSize] = '\0'; - if (lineBufferSize >= 1) - { - if (lineBuffer[lineBufferSize - 1] == '\r') - { - lineBuffer[lineBufferSize - 1] = '\0'; - } - } - - lineBufferSize = 0; - return true; - } - - if (lineBuffer[lineBufferSize] == '\0') - { - // Found terminated string - lineBufferSize = 0; - return true; - } - - lineBufferSize++; - } + return !sock || sock->isClosed(); } -const char* DOSHTTPRequest::GetStatusString() +void DOSTCPSocket::Close() { - switch (status) + if (sock) { - case HTTPRequest::Error: - switch (internalStatus) - { - case InvalidPort: - return "Invalid port"; - case InvalidProtocol: - return "Invalid protocol"; - case SocketCreationError: - return "Socket creation error"; - case SocketConnectionError: - return "Socket connection error"; - case HeaderSendError: - return "Error sending HTTP header"; - case ContentReceiveError: - return "Error receiving HTTP content"; - case UnsupportedHTTPError: - return "Unsupported HTTP version"; - case MalformedHTTPVersionLineError: - return "Malformed HTTP version line"; - case WriteLineError: - return "Error writing headers"; - } - break; - - case HTTPRequest::Connecting: - switch (internalStatus) - { - case QueuedDNSRequest: - case WaitingDNSResolve: - return "Resolving host name via DNS"; - case OpeningSocket: - return "Connecting to server"; - case ConnectingSocket: - case SendHeaders: - return "Sending headers"; - case ReceiveHeaderResponse: - case ReceiveHeaderContent: - return "Receiving headers"; - case ReceiveContent: - return "Receiving content"; - } - break; - default: - break; + sock->closeNonblocking(); + TcpSocketMgr::freeSocket(sock); + sock = NULL; } - return ""; } diff --git a/src/DOS/DOSNet.h b/src/DOS/DOSNet.h index cd9f9e6..210784e 100644 --- a/src/DOS/DOSNet.h +++ b/src/DOS/DOSNet.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _DOSNET_H_ +#define _DOSNET_H_ #include #include "../Platform.h" @@ -23,85 +24,32 @@ #define MAX_CONCURRENT_HTTP_REQUESTS 1 #else #define TCP_RECV_BUFFER_SIZE (16384) -#define MAX_CONCURRENT_HTTP_REQUESTS 3 +#define MAX_CONCURRENT_HTTP_REQUESTS 2 #endif -#define HOSTNAME_LEN (80) -#define PATH_LEN (MAX_URL_LENGTH) -#define LINE_BUFFER_SIZE 512 -#define RESPONSE_MOVED_PERMANENTLY 301 -#define RESPONSE_MOVED_TEMPORARILY 302 -#define RESPONSE_TEMPORARY_REDIRECTION 307 -#define RESPONSE_PERMANENT_REDIRECT 308 - -typedef uint8_t IpAddr_t[4]; // An IPv4 address is 4 bytes struct TcpSocket; -class DOSHTTPRequest : public HTTPRequest +class DOSTCPSocket : public NetworkTCPSocket { public: - DOSHTTPRequest(); - - void Open(char* url); + DOSTCPSocket(); + TcpSocket* GetSock() { return sock; } + void SetSock(TcpSocket* sock); - virtual HTTPRequest::Status GetStatus() { return status; } - virtual size_t ReadData(char* buffer, size_t count); - virtual void Stop(); - void Update(); - virtual const char* GetStatusString(); - virtual const char* GetURL() { return url.url; } + virtual int Send(uint8_t* data, int length) override; + virtual int Receive(uint8_t* buffer, int length) override; + virtual int Connect(NetworkAddress address, int port) override; + virtual bool IsConnectComplete() override; + virtual bool IsClosed() override; + virtual void Close() override; private: - enum InternalStatus - { - // Errors - InvalidPort, - InvalidProtocol, - SocketCreationError, - SocketConnectionError, - HeaderSendError, - ContentReceiveError, - UnsupportedHTTPError, - MalformedHTTPVersionLineError, - WriteLineError, - - // Connection states - QueuedDNSRequest, - WaitingDNSResolve, - OpeningSocket, - ConnectingSocket, - SendHeaders, - ReceiveHeaderResponse, - ReceiveHeaderContent, - ReceiveContent - }; - - void Error(InternalStatus statusError); - bool ReadLine(); - void WriteLine(char* fmt = "", ...); - bool SendPendingWrites(); - - void Reset(); - - HTTPRequest::Status status; - InternalStatus internalStatus; - - URL url; - char hostname[HOSTNAME_LEN]; - char path[PATH_LEN]; - IpAddr_t hostAddr; - uint16_t serverPort; TcpSocket* sock; - uint16_t responseCode; - uint8_t recvBuffer[TCP_RECV_BUFFER_SIZE]; - char lineBuffer[LINE_BUFFER_SIZE]; - int lineBufferSize; - int lineBufferSendPos; }; class DOSNetworkDriver : public NetworkDriver -{ +{ public: virtual void Init(); virtual void Shutdown(); @@ -109,10 +57,20 @@ class DOSNetworkDriver : public NetworkDriver virtual bool IsConnected() { return isConnected; } + // Returns zero on success, negative number is error + virtual int ResolveAddress(const char* name, NetworkAddress address, bool sendRequest) override; + virtual HTTPRequest* CreateRequest(char* url); virtual void DestroyRequest(HTTPRequest* request); + virtual NetworkTCPSocket* CreateSocket() override; + virtual void DestroySocket(NetworkTCPSocket* socket) override; + private: - DOSHTTPRequest* requests[MAX_CONCURRENT_HTTP_REQUESTS]; + HTTPRequest* requests[MAX_CONCURRENT_HTTP_REQUESTS]; + DOSTCPSocket* sockets[MAX_CONCURRENT_HTTP_REQUESTS]; + bool isConnected; }; + +#endif diff --git a/src/DOS/DefData.h b/src/DOS/DefData.h deleted file mode 100644 index 714e13b..0000000 --- a/src/DOS/DefData.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -extern Font Default_RegularFont; -extern Font Default_SmallFont; -extern Font Default_LargeFont; - -extern Font Default_SmallFont_Monospace; -extern Font Default_RegularFont_Monospace; -extern Font Default_LargeFont_Monospace; - -extern MouseCursorData Default_MouseCursor; -extern MouseCursorData Default_MouseCursorHand; -extern MouseCursorData Default_MouseCursorTextSelect; - -extern Image Default_ImageIcon; -extern Image Default_Bullet; \ No newline at end of file diff --git a/src/DOS/DefData.inc b/src/DOS/DefData.inc deleted file mode 100644 index 85579f2..0000000 --- a/src/DOS/DefData.inc +++ /dev/null @@ -1,1267 +0,0 @@ -// Default resources -// This file is auto generated - -// Fonts: -static unsigned char Default_RegularFont_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x7e, 0x00, 0x28, 0x00, 0x28, 0x00, 0xfc, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x20, 0x00, 0x70, 0x00, 0xa8, 0x00, 0xa0, 0x00, 0x70, 0x00, 0x28, 0x00, 0xa8, 0x00, 0x70, 0x00, 0x20, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x60, 0x80, 0x91, 0x00, 0x92, 0x00, 0x64, 0x00, 0x09, 0x80, 0x12, 0x40, 0x22, 0x40, 0x41, 0x80, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x50, 0x00, 0x8a, 0x00, 0x84, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, - // ')' - 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '*' - 0x00, 0x00, 0x20, 0x00, 0xa8, 0x00, 0x70, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x28, 0x00, 0x48, 0x00, 0x88, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x80, 0x00, 0xb0, 0x00, 0xc8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x98, 0x00, 0x68, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x0f, 0x00, 0x30, 0xc0, 0x40, 0x20, 0x47, 0x20, 0x89, 0x10, 0x89, 0x10, 0x89, 0x10, 0x46, 0xe0, 0x40, 0x00, 0x30, 0x60, 0x0f, 0x80, - // 'A' - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0xfc, 0x00, 0x82, 0x00, 0x82, 0x00, 0xfc, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x81, 0x00, 0x80, 0x00, 0x80, 0x00, 0x81, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0xfc, 0x00, 0x82, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x82, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0xfc, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0xfc, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x81, 0x00, 0x80, 0x00, 0x87, 0x00, 0x81, 0x00, 0x43, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0xff, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x84, 0x00, 0x88, 0x00, 0x90, 0x00, 0xa0, 0x00, 0xd0, 0x00, 0x88, 0x00, 0x84, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x40, 0x40, 0x60, 0xc0, 0x51, 0x40, 0x4a, 0x40, 0x44, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x81, 0x00, 0xc1, 0x00, 0xa1, 0x00, 0x91, 0x00, 0x89, 0x00, 0x85, 0x00, 0x83, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0xfe, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x85, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0xfe, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0xfe, 0x00, 0x81, 0x00, 0x81, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x70, 0x00, 0x0c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0xff, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x81, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x82, 0x08, 0x82, 0x08, 0x45, 0x10, 0x45, 0x10, 0x28, 0xa0, 0x28, 0xa0, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x81, 0x00, 0x42, 0x00, 0x24, 0x00, 0x18, 0x00, 0x18, 0x00, 0x24, 0x00, 0x42, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x80, 0x80, 0x41, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0xfc, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xe0, 0x00, - // '\' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xe0, 0x00, - // '^' - 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, - // '`' - 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x7c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0x84, 0x00, 0x84, 0x00, 0xc4, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x80, 0x00, 0x80, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x74, 0x00, 0x8c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x8c, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0xfc, 0x00, 0x80, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0xe0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x8c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x8c, 0x00, 0x74, 0x00, 0x04, 0x00, 0x78, 0x00, - // 'h' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xb0, 0x00, 0xc8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // 'k' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x88, 0x00, 0x90, 0x00, 0xa0, 0x00, 0xd0, 0x00, 0x88, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x00, 0xcc, 0x80, 0x88, 0x80, 0x88, 0x80, 0x88, 0x80, 0x88, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x00, 0xc8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0x84, 0x00, 0x84, 0x00, 0xc4, 0x00, 0xb8, 0x00, 0x80, 0x00, 0x80, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x8c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x8c, 0x00, 0x74, 0x00, 0x04, 0x00, 0x04, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x60, 0x00, 0x10, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0xe0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x98, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x50, 0x00, 0x50, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x80, 0x88, 0x80, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x48, 0x00, 0x30, 0x00, 0x30, 0x00, 0x48, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0xc0, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x30, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x30, 0x00, - // '|' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, - // '}' - 0x00, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, - // '~' - 0x00, 0x00, 0x64, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_RegularFont = { - // Glyph widths - { 3,2,4,8,6,11,7,2,3,3,6,6,3,5,2,5,6,4,6,6,6,6,6,6,6,6,2,3,5,6,6,6,13,8,8,9,9,8,8,9,9,2,6,8,6,11,9,9,9,9,10,8,10,9,8,14,9,10,7,4,5,4,6,8,3,8,7,7,7,7,4,7,6,2,3,7,2,10,6,7,7,7,4,6,4,6,6,10,7,7,5,5,2,5,7}, - 2, // Byte width - 11, // Glyph height - 22, // Glyph stride - Default_RegularFont_Data -}; - -static unsigned char Default_SmallFont_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0xfc, 0x00, 0x48, 0x00, 0xfc, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, - // '$' - 0x20, 0x00, 0x70, 0x00, 0xa8, 0x00, 0xa0, 0x00, 0x70, 0x00, 0x28, 0x00, 0xa8, 0x00, 0x70, 0x00, 0x20, 0x00, - // '%' - 0x00, 0x00, 0x62, 0x00, 0x94, 0x00, 0x68, 0x00, 0x10, 0x00, 0x2c, 0x00, 0x52, 0x00, 0x8c, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x40, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0x40, 0x00, 0xa8, 0x00, 0x90, 0x00, 0x68, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, - // ')' - 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '*' - 0x00, 0x00, 0xa0, 0x00, 0x40, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0xf8, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x50, 0x00, 0x90, 0x00, 0xf8, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x30, 0x00, 0xc0, 0x00, 0x30, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x60, 0x00, 0x18, 0x00, 0x60, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x3f, 0x00, 0x40, 0x80, 0x8e, 0x40, 0x92, 0x40, 0x92, 0x40, 0x8d, 0x80, 0x40, 0x00, 0x3f, 0x00, - // 'A' - 0x00, 0x00, 0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x80, 0x00, 0x9c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x7c, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xfc, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x88, 0x00, 0x90, 0x00, 0xa0, 0x00, 0xe0, 0x00, 0x90, 0x00, 0x88, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x82, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0x92, 0x00, 0x92, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x84, 0x00, 0xc4, 0x00, 0xa4, 0x00, 0xa4, 0x00, 0x94, 0x00, 0x8c, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x94, 0x00, 0x78, 0x00, 0x04, 0x00, - // 'R' - 0x00, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x80, 0x00, 0x70, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x44, 0x40, 0x2a, 0x80, 0x2a, 0x80, 0x11, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xc0, 0x00, - // '\' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xc0, 0x00, - // '^' - 0x20, 0x00, 0x50, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, - // '`' - 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x08, 0x00, 0x78, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, 0xf0, 0x00, - // 'h' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, - // 'k' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x90, 0x00, 0xa0, 0x00, 0xe0, 0x00, 0x90, 0x00, 0x90, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x80, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x80, 0x00, 0x60, 0x00, 0x10, 0x00, 0xe0, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x50, 0x00, 0x20, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x00, 0x92, 0x00, 0xaa, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x90, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x20, 0x00, 0xc0, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x60, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x00, - // '{' - 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, - // '|' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // '}' - 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '~' - 0x00, 0x00, 0x64, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_SmallFont = { - // Glyph widths - { 3,2,6,7,6,8,6,2,3,3,4,6,3,3,3,4,6,4,6,6,6,6,6,6,6,6,2,3,6,6,6,6,11,8,6,7,7,6,6,7,7,2,5,7,6,8,7,7,7,7,7,6,6,7,8,12,8,8,6,3,5,3,5,7,3,6,6,5,6,6,3,6,6,2,2,5,2,8,6,6,6,6,3,5,3,6,6,8,5,6,5,4,2,4,7}, - 2, // Byte width - 9, // Glyph height - 18, // Glyph stride - Default_SmallFont_Data -}; - -static unsigned char Default_LargeFont_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0xd8, 0x00, 0x00, 0xd8, 0x00, 0x00, 0xd8, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x12, 0x00, 0x00, 0x12, 0x00, 0x00, 0x12, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x48, 0x00, 0x00, 0x48, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x08, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x6b, 0x00, 0x00, 0xc9, 0x80, 0x00, 0x68, 0x00, 0x00, 0x38, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x00, 0x00, 0xc9, 0x80, 0x00, 0x6b, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '%' - 0x78, 0x18, 0x00, 0xcc, 0x30, 0x00, 0xcc, 0x60, 0x00, 0xcc, 0xc0, 0x00, 0x79, 0x80, 0x00, 0x03, 0x00, 0x00, 0x06, 0x78, 0x00, 0x0c, 0xcc, 0x00, 0x18, 0xcc, 0x00, 0x30, 0xcc, 0x00, 0x60, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x1e, 0x00, 0x00, 0x33, 0x00, 0x00, 0x33, 0x00, 0x00, 0x33, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x36, 0x00, 0x00, 0x63, 0x60, 0x00, 0xc1, 0xc0, 0x00, 0xc1, 0xc0, 0x00, 0x63, 0x60, 0x00, 0x3e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x10, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x10, 0x00, 0x00, - // ')' - 0x80, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x00, - // '*' - 0x92, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x38, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x18, 0x00, 0x00, 0x10, 0x00, 0x00, 0x30, 0x00, 0x00, 0x20, 0x00, 0x00, 0x60, 0x00, 0x00, 0x40, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x18, 0x00, 0x00, 0x38, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x01, 0x80, 0x00, 0x07, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x16, 0x00, 0x00, 0x26, 0x00, 0x00, 0x46, 0x00, 0x00, 0x86, 0x00, 0x00, 0xff, 0x80, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0xff, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xde, 0x00, 0x00, 0xf3, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc0, 0x00, 0x00, 0xde, 0x00, 0x00, 0xe3, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0xff, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x80, 0x00, 0x3d, 0x80, 0x00, 0x01, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x0f, 0x00, 0x00, 0x38, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x38, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x07, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x07, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x3c, 0x00, 0x00, 0x66, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x18, 0x0c, 0x00, 0x60, 0x02, 0x00, 0x41, 0xdb, 0x00, 0xc2, 0x31, 0x00, 0x86, 0x31, 0x00, 0x8c, 0x63, 0x00, 0xcc, 0x62, 0x00, 0x47, 0xbc, 0x00, 0x60, 0x00, 0x00, 0x38, 0x18, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, - // 'A' - 0x07, 0x00, 0x00, 0x0d, 0x80, 0x00, 0x0d, 0x80, 0x00, 0x18, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x30, 0x60, 0x00, 0x3f, 0xe0, 0x00, 0x60, 0x30, 0x00, 0x60, 0x30, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0xff, 0x00, 0x00, 0xc1, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc1, 0xc0, 0x00, 0xff, 0x00, 0x00, 0xc1, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc1, 0xc0, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x1f, 0xc0, 0x00, 0x70, 0x70, 0x00, 0x60, 0x30, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x30, 0x00, 0x70, 0x70, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0xff, 0x00, 0x00, 0xc1, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0xc0, 0x00, 0xc1, 0xc0, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0xff, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0xff, 0xe0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0xc0, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x1f, 0xc0, 0x00, 0x70, 0x70, 0x00, 0x60, 0x30, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc3, 0xf0, 0x00, 0xc0, 0x30, 0x00, 0x60, 0x30, 0x00, 0x70, 0xf0, 0x00, 0x1f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xff, 0xf0, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0xc3, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0xc1, 0x80, 0x00, 0xc3, 0x00, 0x00, 0xc6, 0x00, 0x00, 0xcc, 0x00, 0x00, 0xd8, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xe6, 0x00, 0x00, 0xc3, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0xe0, 0x1c, 0x00, 0xf0, 0x3c, 0x00, 0xf0, 0x3c, 0x00, 0xd8, 0x6c, 0x00, 0xd8, 0x6c, 0x00, 0xcc, 0xcc, 0x00, 0xcc, 0xcc, 0x00, 0xc7, 0x8c, 0x00, 0xc7, 0x8c, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0xc0, 0x30, 0x00, 0xe0, 0x30, 0x00, 0xf0, 0x30, 0x00, 0xd8, 0x30, 0x00, 0xcc, 0x30, 0x00, 0xc6, 0x30, 0x00, 0xc3, 0x30, 0x00, 0xc1, 0xb0, 0x00, 0xc0, 0xf0, 0x00, 0xc0, 0x70, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x1f, 0xc0, 0x00, 0x70, 0x70, 0x00, 0x60, 0x30, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x60, 0x30, 0x00, 0x70, 0x70, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0xff, 0x80, 0x00, 0xc0, 0xe0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0xe0, 0x00, 0xff, 0x80, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x1f, 0xc0, 0x00, 0x70, 0x70, 0x00, 0x60, 0x30, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x61, 0xb0, 0x00, 0x70, 0xf0, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'R' - 0xff, 0x80, 0x00, 0xc0, 0xe0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0xe0, 0x00, 0xff, 0x80, 0x00, 0xc0, 0xc0, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x3f, 0x00, 0x00, 0xe1, 0x80, 0x00, 0xc0, 0xc0, 0x00, 0xe0, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0xc0, 0xc0, 0x00, 0xe1, 0xc0, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0xff, 0xf0, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0xc0, 0x60, 0x00, 0x60, 0xc0, 0x00, 0x71, 0xc0, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0xc0, 0x18, 0x00, 0xc0, 0x18, 0x00, 0x60, 0x30, 0x00, 0x60, 0x30, 0x00, 0x30, 0x60, 0x00, 0x30, 0x60, 0x00, 0x18, 0xc0, 0x00, 0x18, 0xc0, 0x00, 0x0d, 0x80, 0x00, 0x0d, 0x80, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x61, 0xe1, 0x80, 0x61, 0xe1, 0x80, 0x63, 0x31, 0x80, 0x33, 0x33, 0x00, 0x33, 0x33, 0x00, 0x1e, 0x1e, 0x00, 0x1e, 0x1e, 0x00, 0x0c, 0x0c, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0xc0, 0x30, 0x00, 0x60, 0x60, 0x00, 0x30, 0xc0, 0x00, 0x19, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x19, 0x80, 0x00, 0x30, 0xc0, 0x00, 0x60, 0x60, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0xc0, 0x30, 0x00, 0x60, 0x60, 0x00, 0x30, 0xc0, 0x00, 0x19, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x7f, 0xf0, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x01, 0x80, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0xf0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '\' - 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x60, 0x00, 0x00, 0x20, 0x00, 0x00, 0x30, 0x00, 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ']' - 0xf0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '^' - 0x38, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, - // '`' - 0xe0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x03, 0x00, 0x00, 0x3f, 0x00, 0x00, 0xc3, 0x00, 0x00, 0xc3, 0x00, 0x00, 0xc7, 0x00, 0x00, 0x79, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xde, 0x00, 0x00, 0xe3, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xe3, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x3d, 0x80, 0x00, 0x63, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x80, 0x00, 0x3d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xff, 0x80, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x61, 0x80, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x80, 0x00, 0x63, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x80, 0x00, 0x3d, 0x80, 0x00, 0x01, 0x80, 0x00, 0xc3, 0x00, 0x00, 0x7e, 0x00, 0x00, - // 'h' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xdf, 0x00, 0x00, 0xe1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, - // 'k' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc6, 0x00, 0x00, 0xcc, 0x00, 0x00, 0xd8, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xcc, 0x00, 0x00, 0xc6, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xf8, 0x00, 0xe3, 0x8c, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdf, 0x00, 0x00, 0xe1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0xe3, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xe3, 0x00, 0x00, 0xde, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x80, 0x00, 0x63, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x80, 0x00, 0x3d, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0xe0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0xc6, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x70, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x06, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0xc3, 0x80, 0x00, 0x7d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x63, 0x00, 0x00, 0x36, 0x00, 0x00, 0x36, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x0c, 0x00, 0xc3, 0x0c, 0x00, 0x67, 0x98, 0x00, 0x67, 0x98, 0x00, 0x3c, 0xf0, 0x00, 0x3c, 0xf0, 0x00, 0x18, 0x60, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x36, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x36, 0x00, 0x00, 0x63, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x00, 0xc1, 0x80, 0x00, 0x63, 0x00, 0x00, 0x63, 0x00, 0x00, 0x36, 0x00, 0x00, 0x36, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x70, 0x00, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x60, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x38, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // '|' - 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '}' - 0xe0, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0x30, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, - // '~' - 0x79, 0x80, 0x00, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_LargeFont = { - // Glyph widths - { 6,4,7,10,10,16,14,3,6,6,9,12,4,7,4,7,11,7,11,11,11,10,11,11,11,11,4,4,12,12,12,10,18,15,12,14,13,13,13,14,14,4,10,13,11,16,14,15,13,15,13,12,14,13,15,20,14,14,14,6,7,6,8,11,5,10,10,10,10,10,5,10,10,3,4,9,3,15,10,10,10,10,6,8,5,10,10,15,10,10,10,6,3,6,10}, - 3, // Byte width - 14, // Glyph height - 42, // Glyph stride - Default_LargeFont_Data -}; - -static unsigned char Default_RegularFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x09, 0x00, 0x09, 0x00, 0x3f, 0x80, 0x12, 0x00, 0x12, 0x00, 0x7f, 0x00, 0x24, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x08, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x40, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '%' - 0x30, 0x80, 0x49, 0x00, 0x32, 0x00, 0x04, 0x00, 0x08, 0x00, 0x13, 0x00, 0x24, 0x80, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x1c, 0x00, 0x22, 0x00, 0x20, 0x00, 0x10, 0x00, 0x28, 0x80, 0x45, 0x00, 0x42, 0x00, 0x3d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - // ')' - 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '*' - 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x1c, 0x00, 0x7f, 0x00, 0x1c, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x7f, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x12, 0x00, 0x22, 0x00, 0x3f, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x3f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x0e, 0x00, 0x10, 0x00, 0x20, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x3f, 0x00, 0x21, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x4e, 0x80, 0x52, 0x80, 0x52, 0x80, 0x4d, 0x00, 0x20, 0x00, 0x1f, 0x00, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x14, 0x00, 0x14, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x7c, 0x00, 0x22, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x22, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x7f, 0x00, 0x21, 0x00, 0x24, 0x00, 0x3c, 0x00, 0x24, 0x00, 0x20, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x7f, 0x00, 0x21, 0x00, 0x24, 0x00, 0x3c, 0x00, 0x24, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x40, 0x00, 0x47, 0x80, 0x41, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x3e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x0f, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x24, 0x00, 0x28, 0x00, 0x34, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x78, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0xc1, 0x80, 0x63, 0x00, 0x55, 0x00, 0x49, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0xc7, 0x80, 0x41, 0x00, 0x61, 0x00, 0x51, 0x00, 0x49, 0x00, 0x45, 0x00, 0x43, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x33, 0x00, - // 'R' - 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x3d, 0x00, 0x43, 0x00, 0x41, 0x00, 0x38, 0x00, 0x06, 0x00, 0x41, 0x00, 0x61, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0xff, 0x80, 0x88, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x49, 0x00, 0x49, 0x00, 0x2a, 0x00, 0x36, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x7f, 0x00, 0x42, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x0e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0e, 0x00, 0x00, 0x00, - // '\' - 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x00, 0x00, - // '^' - 0x00, 0x00, 0x0c, 0x00, 0x12, 0x00, 0x21, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, - // '`' - 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x2c, 0x00, 0x33, 0x00, 0x21, 0x00, 0x21, 0x00, 0x33, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x63, 0x00, 0x40, 0x00, 0x40, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x61, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x0e, 0x00, 0x11, 0x00, 0x10, 0x00, 0x3c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x46, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x02, 0x00, 0x42, 0x00, 0x3c, 0x00, - // 'h' - 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, - // 'k' - 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x27, 0x00, 0x24, 0x00, 0x28, 0x00, 0x34, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x6d, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0xed, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x32, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x63, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x33, 0x00, 0x21, 0x00, 0x21, 0x00, 0x33, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x78, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x0f, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x18, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x38, 0x00, 0x06, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x26, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x80, 0x49, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x60, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x42, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, 0x00, 0x00, - // '|' - 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, - // '}' - 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00, - // '~' - 0x00, 0x00, 0x31, 0x00, 0x49, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_RegularFont_Monospace = { - // Glyph widths - { 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9}, - 2, // Byte width - 11, // Glyph height - 22, // Glyph stride - Default_RegularFont_Monospace_Data -}; - -static unsigned char Default_SmallFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, - // '"' - 0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x0a, 0x0a, 0x3f, 0x14, 0x7e, 0x28, 0x28, 0x00, - // '$' - 0x00, 0x08, 0x1e, 0x20, 0x1c, 0x02, 0x3c, 0x08, 0x00, - // '%' - 0x00, 0x21, 0x52, 0x24, 0x08, 0x12, 0x25, 0x42, 0x00, - // '&' - 0x00, 0x18, 0x20, 0x10, 0x29, 0x46, 0x44, 0x3b, 0x00, - // ''' - 0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x08, 0x04, - // ')' - 0x00, 0x10, 0x08, 0x04, 0x04, 0x04, 0x04, 0x08, 0x10, - // '*' - 0x00, 0x00, 0x36, 0x1c, 0x7f, 0x1c, 0x36, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, - // '/' - 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, - // '0' - 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, - // '1' - 0x00, 0x08, 0x38, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, - // '2' - 0x00, 0x1c, 0x22, 0x02, 0x04, 0x08, 0x10, 0x3e, 0x00, - // '3' - 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x02, 0x22, 0x1c, 0x00, - // '4' - 0x00, 0x04, 0x0c, 0x14, 0x24, 0x3e, 0x04, 0x0e, 0x00, - // '5' - 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x02, 0x22, 0x1c, 0x00, - // '6' - 0x00, 0x0c, 0x10, 0x20, 0x3c, 0x22, 0x22, 0x1c, 0x00, - // '7' - 0x00, 0x3e, 0x22, 0x04, 0x08, 0x08, 0x10, 0x10, 0x00, - // '8' - 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00, - // '9' - 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x04, 0x18, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x10, - // '<' - 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, - // '?' - 0x00, 0x1c, 0x22, 0x02, 0x04, 0x08, 0x00, 0x08, 0x00, - // '@' - 0x00, 0x3e, 0x41, 0x4d, 0x55, 0x5e, 0x40, 0x3c, 0x00, - // 'A' - 0x00, 0x38, 0x08, 0x14, 0x14, 0x3e, 0x22, 0x77, 0x00, - // 'B' - 0x00, 0x7e, 0x21, 0x21, 0x3e, 0x21, 0x21, 0x7e, 0x00, - // 'C' - 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x21, 0x1e, 0x00, - // 'D' - 0x00, 0x7c, 0x22, 0x21, 0x21, 0x21, 0x22, 0x7c, 0x00, - // 'E' - 0x00, 0x7f, 0x21, 0x20, 0x3c, 0x20, 0x21, 0x7f, 0x00, - // 'F' - 0x00, 0x7f, 0x21, 0x20, 0x3c, 0x20, 0x20, 0x78, 0x00, - // 'G' - 0x00, 0x1e, 0x21, 0x40, 0x40, 0x47, 0x21, 0x1e, 0x00, - // 'H' - 0x00, 0x77, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x77, 0x00, - // 'I' - 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, - // 'J' - 0x00, 0x1e, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, - // 'K' - 0x00, 0x73, 0x22, 0x24, 0x38, 0x24, 0x22, 0x73, 0x00, - // 'L' - 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x11, 0x7f, 0x00, - // 'M' - 0x00, 0x63, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x77, 0x00, - // 'N' - 0x00, 0x67, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x72, 0x00, - // 'O' - 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x00, - // 'P' - 0x00, 0x7e, 0x21, 0x21, 0x3e, 0x20, 0x20, 0x78, 0x00, - // 'Q' - 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x1b, - // 'R' - 0x00, 0x7e, 0x21, 0x21, 0x3e, 0x24, 0x22, 0x73, 0x00, - // 'S' - 0x00, 0x3e, 0x41, 0x40, 0x3e, 0x01, 0x41, 0x3e, 0x00, - // 'T' - 0x00, 0x7f, 0x49, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, - // 'U' - 0x00, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, - // 'V' - 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x00, - // 'W' - 0x00, 0x77, 0x22, 0x22, 0x2a, 0x2a, 0x14, 0x14, 0x00, - // 'X' - 0x00, 0x77, 0x22, 0x14, 0x08, 0x14, 0x22, 0x77, 0x00, - // 'Y' - 0x00, 0x77, 0x22, 0x14, 0x08, 0x08, 0x08, 0x1c, 0x00, - // 'Z' - 0x00, 0x7f, 0x42, 0x04, 0x08, 0x10, 0x21, 0x7f, 0x00, - // '[' - 0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1c, - // '\' - 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, - // ']' - 0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1c, - // '^' - 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - // '`' - 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x3c, 0x02, 0x3e, 0x42, 0x3d, 0x00, - // 'b' - 0x00, 0x60, 0x20, 0x3e, 0x21, 0x21, 0x21, 0x7e, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x3e, 0x41, 0x40, 0x41, 0x3e, 0x00, - // 'd' - 0x00, 0x06, 0x02, 0x3e, 0x42, 0x42, 0x42, 0x3f, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x3e, 0x41, 0x7f, 0x40, 0x3e, 0x00, - // 'f' - 0x00, 0x0c, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x3c, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x3e, 0x02, 0x3c, - // 'h' - 0x00, 0x60, 0x20, 0x2c, 0x32, 0x22, 0x22, 0x77, 0x00, - // 'i' - 0x00, 0x08, 0x00, 0x38, 0x08, 0x08, 0x08, 0x3e, 0x00, - // 'j' - 0x00, 0x04, 0x00, 0x3c, 0x04, 0x04, 0x04, 0x04, 0x38, - // 'k' - 0x00, 0x60, 0x20, 0x26, 0x24, 0x38, 0x24, 0x63, 0x00, - // 'l' - 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x74, 0x2a, 0x2a, 0x2a, 0x6b, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x6c, 0x32, 0x22, 0x22, 0x77, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x20, - // 'q' - 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x42, 0x3e, 0x02, - // 'r' - 0x00, 0x00, 0x00, 0x76, 0x19, 0x10, 0x10, 0x7c, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x3f, 0x40, 0x3e, 0x01, 0x7e, 0x00, - // 't' - 0x00, 0x00, 0x10, 0x3c, 0x10, 0x10, 0x12, 0x0c, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x66, 0x22, 0x22, 0x26, 0x1b, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x08, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x77, 0x22, 0x2a, 0x14, 0x14, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x36, 0x14, 0x08, 0x14, 0x36, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x08, 0x30, - // 'z' - 0x00, 0x00, 0x00, 0x7e, 0x44, 0x18, 0x22, 0x7e, 0x00, - // '{' - 0x00, 0x06, 0x08, 0x08, 0x30, 0x08, 0x08, 0x06, 0x00, - // '|' - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - // '}' - 0x00, 0x30, 0x08, 0x08, 0x06, 0x08, 0x08, 0x30, 0x00, - // '~' - 0x00, 0x31, 0x49, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_SmallFont_Monospace = { - // Glyph widths - { 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8}, - 1, // Byte width - 9, // Glyph height - 9, // Glyph stride - Default_SmallFont_Monospace_Data -}; - -static unsigned char Default_LargeFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x04, 0x40, 0x1f, 0xe0, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x3f, 0xc0, 0x11, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0f, 0x40, 0x10, 0xc0, 0x10, 0x40, 0x0e, 0x00, 0x01, 0x80, 0x10, 0x40, 0x18, 0x40, 0x17, 0x80, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x20, 0x44, 0x40, 0x44, 0x80, 0x39, 0x00, 0x02, 0x00, 0x04, 0xe0, 0x09, 0x10, 0x11, 0x10, 0x20, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x20, 0x21, 0x40, 0x20, 0x80, 0x1f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, - // ')' - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, - // '*' - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x12, 0x40, 0x0f, 0x80, 0x07, 0x00, 0x05, 0x00, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3f, 0xe0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x40, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x00, 0x40, 0x03, 0x80, 0x00, 0x40, 0x00, 0x40, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x02, 0x80, 0x04, 0x80, 0x08, 0x80, 0x10, 0x80, 0x1f, 0xc0, 0x00, 0x80, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x10, 0x00, 0x10, 0x00, 0x1f, 0x00, 0x10, 0x80, 0x00, 0x40, 0x00, 0x40, 0x10, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x06, 0x00, 0x08, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x10, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x0f, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0xc0, 0x0f, 0x40, 0x00, 0x40, 0x00, 0x80, 0x03, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x23, 0xe0, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x23, 0xe0, 0x20, 0x00, 0x10, 0x00, 0x0f, 0x80, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x80, 0x1f, 0xc0, 0x10, 0x40, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x1f, 0xe0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x00, 0x00, 0x07, 0xd0, 0x18, 0x30, 0x10, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x10, 0x18, 0x30, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x60, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x10, 0x60, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x80, 0x1f, 0x80, 0x10, 0x80, 0x10, 0x20, 0x10, 0x20, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x80, 0x1f, 0x80, 0x10, 0x80, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x30, 0x60, 0x20, 0x20, 0x40, 0x00, 0x41, 0xf0, 0x40, 0x20, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x20, 0x80, 0x20, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x00, 0x00, 0x73, 0xc0, 0x21, 0x00, 0x22, 0x00, 0x24, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x21, 0x00, 0x20, 0x80, 0x70, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x10, 0x20, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x30, 0x60, 0x28, 0xa0, 0x25, 0x20, 0x22, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x00, 0x00, 0x60, 0xf0, 0x30, 0x20, 0x28, 0x20, 0x24, 0x20, 0x22, 0x20, 0x21, 0x20, 0x20, 0xa0, 0x20, 0x60, 0x78, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x1f, 0xc0, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x08, 0x20, 0x1f, 0xc0, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x1f, 0xc0, 0x11, 0x00, 0x10, 0x80, 0x10, 0x40, 0x7c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa0, 0x20, 0x60, 0x20, 0x20, 0x18, 0x00, 0x07, 0x00, 0x00, 0xc0, 0x20, 0x20, 0x30, 0x20, 0x2f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf0, 0x42, 0x10, 0x42, 0x10, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x42, 0x10, 0x42, 0x10, 0x45, 0x10, 0x25, 0x20, 0x28, 0xa0, 0x28, 0xa0, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x00, 0x00, 0x78, 0xf0, 0x10, 0x40, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x80, 0x10, 0x40, 0x78, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x20, 0x40, 0x20, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x20, 0x10, 0x20, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x07, 0x80, 0x00, 0x00, - // '\' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, - // ']' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, - // '^' - 0x02, 0x00, 0x05, 0x00, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, - // '`' - 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x1f, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x1f, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x60, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x18, 0x60, 0x37, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xa0, 0x18, 0x60, 0x10, 0x20, 0x10, 0x00, 0x10, 0x00, 0x18, 0x20, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x40, 0x0f, 0x40, 0x30, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x30, 0xc0, 0x0f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x3f, 0xe0, 0x20, 0x00, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x01, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x0f, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x60, 0x30, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x30, 0xc0, 0x0f, 0x40, 0x00, 0x40, 0x00, 0x80, 0x1f, 0x00, - // 'h' - 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x1c, 0x00, - // 'k' - 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x13, 0xc0, 0x11, 0x00, 0x12, 0x00, 0x1e, 0x00, 0x11, 0x00, 0x10, 0x80, 0x31, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xc0, 0x33, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x73, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x80, 0x18, 0x60, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x18, 0x60, 0x17, 0x80, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x60, 0x30, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x30, 0xc0, 0x0f, 0x40, 0x00, 0x40, 0x00, 0x40, 0x01, 0xe0, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xc0, 0x0c, 0x20, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa0, 0x20, 0x60, 0x30, 0x20, 0x0f, 0x80, 0x20, 0x60, 0x30, 0x20, 0x2f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1f, 0xc0, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x40, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0xc0, 0x0f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x22, 0x20, 0x12, 0x40, 0x15, 0x40, 0x08, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xe0, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x80, 0x3d, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x7c, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x10, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x40, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, - // '|' - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, - // '}' - 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x00, 0x00, - // '~' - 0x00, 0x00, 0x00, 0x00, 0x1c, 0x20, 0x22, 0x20, 0x21, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_LargeFont_Monospace = { - // Glyph widths - { 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12}, - 2, // Byte width - 14, // Glyph height - 28, // Glyph stride - Default_LargeFont_Monospace_Data -}; - - -// Mouse cursors: -MouseCursorData Default_MouseCursor = { - { - 0x3fff,0x1fff,0x0fff,0x07ff,0x03ff,0x01ff,0x00ff,0x007f,0x003f,0x003f,0x01ff,0x00ff,0x30ff,0xf87f,0xf87f,0xfcff, - 0x0000,0x4000,0x6000,0x7000,0x7800,0x7c00,0x7e00,0x7f00,0x7f80,0x7c00,0x6c00,0x4600,0x0600,0x0300,0x0300,0x0000, - }, - // Hot spot - 0, 0 -}; - -MouseCursorData Default_MouseCursorHand = { - { - 0xf9ff,0xf0ff,0xf0ff,0xf0ff,0xf00f,0xf003,0x9001,0x0001,0x0001,0x8001,0xc001,0xc003,0xe003,0xf007,0xf807,0xf807, - 0x0000,0x0600,0x0600,0x0600,0x0600,0x06d0,0x06d4,0x67fc,0x77fc,0x3ffc,0x1ffc,0x1ff8,0x0ff8,0x07f0,0x03f0,0x0000, - }, - // Hot spot - 6, 1 -}; - -MouseCursorData Default_MouseCursorTextSelect = { - { - 0xf9cf,0xf087,0xf80f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xfe3f,0xf80f,0xf087,0xf9cf, - 0x0630,0x0948,0x06b0,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x0140,0x06b0,0x0948,0x0630, - }, - // Hot spot - 8, 6 -}; - - -// Images: -static unsigned char Default_ImageIcon_Data[] = { - 0xff, 0xc0, 0x80, 0xa0, 0x80, 0x90, 0xb0, 0xf0, 0xb0, 0x10, 0x81, 0x10, 0x81, 0x10, 0x83, 0x90, 0x9b, 0x90, 0x9f, 0xd0, 0xbf, 0xd0, 0xbf, 0xd0, 0x80, 0x10, 0xff, 0xf0, -}; - -Image Default_ImageIcon = { - // Dimensions - 12, 14, - Default_ImageIcon_Data -}; - -static unsigned char Default_Bullet_Data[] = { - 0x00, 0x00, 0x78, 0xfc, 0xfc, 0xfc, 0xfc, 0x78, 0x00, 0x00, 0x00, -}; - -Image Default_Bullet = { - // Dimensions - 6, 11, - Default_Bullet_Data -}; - diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp deleted file mode 100644 index 80c46a4..0000000 --- a/src/DOS/EGA.cpp +++ /dev/null @@ -1,796 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#include -#include -#include -#include -#include -#include -#include "../Image.h" -#include "EGA.h" -#include "DefData.h" -#include "../Interface.h" - -#define EGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) - -#define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT (screenHeight) - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 32 - -#define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - 64) -#define ADDRESS_BAR_HEIGHT 15 -#define TITLE_BAR_HEIGHT 12 -#define STATUS_BAR_HEIGHT 12 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) -#define ADDRESS_BAR_X 60 -#define ADDRESS_BAR_Y (TITLE_BAR_HEIGHT + 1) - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT ADDRESS_BAR_HEIGHT - -#define WINDOW_TOP (TITLE_BAR_HEIGHT + ADDRESS_BAR_HEIGHT + 3) -#define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define SCROLL_BAR_WIDTH 16 - -#define WINDOW_VRAM_TOP (BYTES_PER_LINE * (WINDOW_TOP)) -#define WINDOW_VRAM_BOTTOM (BYTES_PER_LINE * (WINDOW_BOTTOM)) -#define BYTES_PER_LINE 80 - -EGADriver::EGADriver() -{ - SetupVars(0x10, 350); -} - -void EGADriver::SetupVars(int inScreenMode, int inScreenHeight) -{ - screenModeToUse = inScreenMode; - screenWidth = SCREEN_WIDTH; - screenHeight = inScreenHeight; - windowWidth = screenWidth - 16; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - scissorY2 = SCREEN_HEIGHT; - invertScreen = false; - clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; - isTextMode = false; -} - -void EGADriver::Init() -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(screenModeToUse); -} - -void EGADriver::Shutdown() -{ - SetScreenMode(startingScreenMode); -} - -int EGADriver::GetScreenMode() -{ - union REGS inreg, outreg; - inreg.h.ah = 0xf; - - int86(0x10, &inreg, &outreg); - - return (int)outreg.h.al; -} - -void EGADriver::SetScreenMode(int screenMode) -{ - union REGS inreg, outreg; - inreg.h.ah = 0; - inreg.h.al = (unsigned char)screenMode; - - int86(0x10, &inreg, &outreg); -} - -static void FastMemSet(void far* mem, uint8_t value, unsigned int count); -#pragma aux FastMemSet = \ - "rep stosb" \ - modify [di cx] \ - parm[es di][al][cx]; - -void EGADriver::InvertScreen() -{ - int count = (SCREEN_HEIGHT * BYTES_PER_LINE); - unsigned char far* VRAM = EGA_BASE_VRAM_ADDRESS; - while (count--) - { - *VRAM ^= 0xff; - VRAM++; - } - - invertScreen = !invertScreen; - clearMask = invertScreen ? 0 : 0xffff; -} - -void EGADriver::ClearScreen() -{ - uint8_t clearValue = (uint8_t)(clearMask & 0xff); - FastMemSet(EGA_BASE_VRAM_ADDRESS, clearValue, (SCREEN_HEIGHT * BYTES_PER_LINE)); -} - -void EGADriver::DrawImage(Image* image, int x, int y) -{ - int imageHeight = image->height; - - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + imageHeight < scissorY1) - { - return; - } - if (y + imageHeight > scissorY2) - { - imageHeight = (scissorY2 - y); - } - - uint16_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) EGA_BASE_VRAM_ADDRESS; - VRAM += y * BYTES_PER_LINE; - - uint16_t imageWidthBytes = image->width >> 3; - - // Check if rounds to a byte, pad if necessary - if ((imageWidthBytes << 3) != image->width) - { - imageWidthBytes++; - } - - uint8_t* imageData = image->data + firstLine * imageWidthBytes; - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < imageHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - for (uint8_t i = 0; i < imageWidthBytes; i++) - { - uint8_t glyphPixels = *imageData++; - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - VRAMptr += BYTES_PER_LINE; - } -} - -void EGADriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ - Font* font = GetFont(size, style); - - int startX = x; - uint8_t glyphHeight = font->glyphHeight; - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + glyphHeight > scissorY2) - { - glyphHeight = (uint8_t)(scissorY2 - y); - } - if (y + glyphHeight < scissorY1) - { - return; - } - - uint8_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) EGA_BASE_VRAM_ADDRESS; - - VRAM += y * BYTES_PER_LINE; - - while (*text) - { - char c = *text++; - if (c < 32 || c >= 128) - { - continue; - } - - char index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; - - if (glyphWidth == 0) - { - continue; - } - - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - - glyphData += (firstLine * font->glyphWidthBytes); - - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < glyphHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) - { - writeOffset++; - } - - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - VRAMptr += BYTES_PER_LINE; - } - - x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; - } - - if (x >= scissorX2) - { - break; - } - } - - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < scissorY2) - { - HLine(startX, y - firstLine + font->glyphHeight - 1, x - startX); - } -} - -Font* EGADriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) - { - switch (fontSize) - { - case 0: - return &Default_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &Default_LargeFont_Monospace; - default: - return &Default_RegularFont_Monospace; - } - } - - switch (fontSize) - { - case 0: - return &Default_SmallFont; - case 2: - case 3: - case 4: - return &Default_LargeFont; - default: - return &Default_RegularFont; - } -} - -void EGADriver::HLine(int x, int y, int count) -{ - if (invertScreen) - { - ClearHLine(x, y, count); - } - else - { - HLineInternal(x, y, count); - } -} - -void EGADriver::HLineInternal(int x, int y, int count) -{ - if (y < scissorY1 || y >= scissorY2) - return; - - uint8_t far* VRAMptr = (uint8_t far*) EGA_BASE_VRAM_ADDRESS; - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - uint8_t data = *VRAMptr; - uint8_t mask = ~(0x80 >> (x & 7)); - - while (count--) - { - data &= mask; - x++; - mask = (mask >> 1) | 0x80; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0; - count -= 8; - } - mask = ~0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void EGADriver::ClearHLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) EGA_BASE_VRAM_ADDRESS; - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data |= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void EGADriver::ClearRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } -} - -void EGADriver::InvertLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) EGA_BASE_VRAM_ADDRESS; - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data ^= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ ^= 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -bool EGADriver::ApplyScissor(int& y, int& height) -{ - if (y + height < scissorY1) - return false; - if (y >= scissorY2) - return false; - if (y < scissorY1) - { - height -= (scissorY1 - y); - y = scissorY1; - } - if (y + height >= scissorY2) - { - height = scissorY2 - y; - } - return true; -} - -void EGADriver::InvertRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - for (int j = 0; j < height; j++) - { - InvertLine(x, y + j, width); - } -} - -void EGADriver::FillRect(int x, int y, int width, int height) -{ - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } -} - -void EGADriver::VLine(int x, int y, int count) -{ - if (y < scissorY1) - { - count -= (scissorY1 - y); - y = scissorY1; - } - if (y >= scissorY2) - { - return; - } - if (y + count >= scissorY2) - { - count = scissorY2 - y; - } - if (count <= 0) - { - return; - } - - uint8_t far* VRAMptr = (uint8_t far*) EGA_BASE_VRAM_ADDRESS; - uint8_t mask = ~(0x80 >> (x & 7)); - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - if (invertScreen) - { - mask ^= 0xff; - - while (count--) - { - *VRAMptr |= mask; - VRAMptr += BYTES_PER_LINE; - } - } - else - { - while (count--) - { - *VRAMptr &= mask; - VRAMptr += BYTES_PER_LINE; - } - } -} - -MouseCursorData* EGADriver::GetCursorGraphic(MouseCursor::Type type) -{ - switch (type) - { - default: - case MouseCursor::Pointer: - return &Default_MouseCursor; - case MouseCursor::Hand: - return &Default_MouseCursorHand; - case MouseCursor::TextSelect: - return &Default_MouseCursorTextSelect; - } -} - -int EGADriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) -{ - Font* font = GetFont(fontSize, style); - if (c >= 32 && c < 128) - { - int width = font->glyphWidth[c - 32]; - if (style & FontStyle::Bold) - { - width++; - } - return width; - } - return 0; -} - -int EGADriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return GetFont(fontSize, style)->glyphHeight + 1; -} - -void DrawScrollBarBlock(uint8_t far* ptr, int top, int middle, int bottom); -#pragma aux DrawScrollBarBlock = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov ax, 0xfe7f" \ - "_loopTop:" \ - "stosw" \ - "add di, 78" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov ax, 0x0660" \ - "_loopMiddle:" \ - "stosw" \ - "add di, 78" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0xfe7f" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosw" \ - "add di, 78" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -void DrawScrollBarBlockInverted(uint8_t far* ptr, int top, int middle, int bottom); -#pragma aux DrawScrollBarBlockInverted = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov ax, 0x0180" \ - "_loopTop:" \ - "stosw" \ - "add di, 78" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov ax, 0xf99f" \ - "_loopMiddle:" \ - "stosw" \ - "add di, 78" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0x0180" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosw" \ - "add di, 78" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - - -void EGADriver::DrawScrollBar(int position, int size) -{ - uint8_t* VRAM = EGA_BASE_VRAM_ADDRESS + WINDOW_TOP * BYTES_PER_LINE + (BYTES_PER_LINE - 2); - - if (invertScreen) - { - DrawScrollBarBlockInverted(VRAM, position, size, WINDOW_HEIGHT - position - size); - } - else - { - DrawScrollBarBlock(VRAM, position, size, WINDOW_HEIGHT - position - size); - } -} - -void EGADriver::DrawRect(int x, int y, int width, int height) -{ - HLine(x, y, width); - HLine(x, y + height - 1, width); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void EGADriver::DrawButtonRect(int x, int y, int width, int height) -{ - HLine(x + 1, y, width - 2); - HLine(x + 1, y + height - 1, width - 2); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void ScrollRegionUp(int dest, int src, int count); -#pragma aux ScrollRegionUp = \ - "push ds" \ - "push es" \ - "mov ax, 0xa000" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 39" \ - "rep movsw" \ - "add di, 2" \ - "add si, 2" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -void ScrollRegionDown(int dest, int src, int count); -#pragma aux ScrollRegionDown = \ - "push ds" \ - "push es" \ - "mov ax, 0xa000" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 39" \ - "rep movsw" \ - "sub di, 158" \ - "sub si, 158" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -void ClearRegion(int offset, int count, uint16_t clearMask); -#pragma aux ClearRegion = \ - "push es" \ - "mov ax, 0xa000" \ - "mov es, ax" \ - "mov ax, bx" \ - "_loopLine:" \ - "mov cx, 39" \ - "rep stosw" \ - "add di, 2" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - modify [cx di ax cx dx] \ - parm [di] [dx] [bx] - -void EGADriver::ScrollWindow(int amount) -{ - if (amount > 0) - { - int lines = (WINDOW_HEIGHT - amount); - int offset = amount * BYTES_PER_LINE; - ScrollRegionUp(WINDOW_VRAM_TOP, WINDOW_VRAM_TOP + offset, lines); - - ClearRegion(WINDOW_VRAM_BOTTOM - offset, (WINDOW_HEIGHT) - lines, clearMask); - } - else if (amount < 0) - { - int lines = (WINDOW_HEIGHT + amount); - int offset = amount * BYTES_PER_LINE; - ScrollRegionDown(WINDOW_VRAM_BOTTOM - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM - BYTES_PER_LINE + offset, lines); - - ClearRegion(WINDOW_VRAM_TOP, WINDOW_HEIGHT - lines, clearMask); - } -} - -void EGADriver::ClearWindow() -{ - ClearRegion(WINDOW_VRAM_TOP, WINDOW_HEIGHT, clearMask); -} - -void EGADriver::SetScissorRegion(int y1, int y2) -{ - scissorY1 = y1; - scissorY2 = y2; -} - -void EGADriver::ClearScissorRegion() -{ - scissorY1 = 0; - scissorY2 = screenHeight; -} - -void EGADriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT; - app.statusBar.width = SCREEN_WIDTH; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 1; - app.titleBar.width = SCREEN_WIDTH; - app.titleBar.height = TITLE_BAR_HEIGHT; -} - -void EGADriver::ScaleImageDimensions(int& width, int& height) -{ - // Scale to 4:3 - height = (height * 35) / 48; -} diff --git a/src/DOS/EGA.h b/src/DOS/EGA.h deleted file mode 100644 index d03768f..0000000 --- a/src/DOS/EGA.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#pragma once - -#include "../Platform.h" - -struct Image; - -class EGADriver : public VideoDriver -{ -public: - EGADriver(); - - virtual void Init(); - virtual void Shutdown(); - virtual void ClearScreen(); - virtual void InvertScreen(); - - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app); - - - virtual void ClearWindow(); - virtual void ScrollWindow(int delta); - virtual void SetScissorRegion(int y1, int y2); - virtual void ClearScissorRegion(); - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void DrawRect(int x, int y, int width, int height); - virtual void DrawButtonRect(int x, int y, int width, int height); - virtual void DrawImage(Image* image, int x, int y); - virtual void ClearRect(int x, int y, int width, int height); - virtual void FillRect(int x, int y, int width, int height); - virtual void InvertRect(int x, int y, int width, int height); - - virtual void HLine(int x, int y, int count); - virtual void VLine(int x, int y, int count); - virtual void ScaleImageDimensions(int& width, int& height); - - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - - virtual void DrawScrollBar(int position, int size); - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - void ClearHLine(int x, int y, int count); - void HLineInternal(int x, int y, int count); - void InvertLine(int x, int y, int count); - bool ApplyScissor(int& y, int& height); - - bool invertScreen; - uint16_t clearMask; - int startingScreenMode; - int scissorX1, scissorY1, scissorX2, scissorY2; - -protected: - void SetupVars(int inScreenMode, int inScreenHeight); - - int screenModeToUse; -}; - -class VGADriver : public EGADriver -{ -public: - VGADriver() - { - SetupVars(0x11, 480); - } - virtual void ScaleImageDimensions(int& width, int& height) {} -}; diff --git a/src/DOS/EMS.cpp b/src/DOS/EMS.cpp new file mode 100644 index 0000000..3c205d2 --- /dev/null +++ b/src/DOS/EMS.cpp @@ -0,0 +1,146 @@ +#include "EMS.h" +#include +#include +#include +#include + +#define EMS_INTERRUPT_NUMBER 0x67 + +void EMSManager::Init() +{ + union REGS inregs, outregs; + struct SREGS sregs; + + // Check for EMS driver + inregs.h.ah = 0x35; + inregs.h.al = EMS_INTERRUPT_NUMBER; + int86x(0x21, &inregs, &outregs, &sregs); + char* emm = (char*) MK_FP(sregs.es, 0xa); + + if(memcmp(emm, "EMMXXXX0", 8)) + { + // No EMS present + return; + } + + // Check for EMS version 4 + inregs.h.ah = 0x46; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + if ((outregs.h.al & 0xf0) < 0x40) { + // Incorrect version + return; + } + + // Get the page address + inregs.h.ah = 0x41; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + pageAddressSegment = outregs.x.bx; + + // Get the number of unallocated pages + inregs.h.ah = 0x42; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + int numAvailablePages = outregs.x.bx; + + // Allocate the pages + inregs.h.ah = 0x43; + inregs.x.bx = numAvailablePages; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + + if (outregs.h.ah) + { + // Allocation failed + return; + } + + numAllocatedPages = numAvailablePages; + allocationHandle = outregs.x.dx; + + allocationPageIndex = 0; + allocationPageUsed = 0; + + for (int n = 0; n < NUM_MAPPABLE_PAGES; n++) + { + mappedPages[n] = 0xffff; + } + nextPageToMap = 0; + + isAvailable = true; +} + +void EMSManager::Reset() +{ + allocationPageIndex = 0; + allocationPageUsed = 0; +} + +void EMSManager::Shutdown() +{ + if (isAvailable) + { + union REGS inregs, outregs; + + // Free allocated pages + inregs.h.ah = 0x45; + inregs.x.dx = allocationHandle; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + } +} + +MemBlockHandle EMSManager::Allocate(size_t size) +{ + MemBlockHandle result; + + if (allocationPageIndex < numAllocatedPages) + { + if (size + allocationPageUsed > EMS_PAGE_SIZE) + { + allocationPageIndex++; + allocationPageUsed = 0; + } + + if (allocationPageIndex < numAllocatedPages) + { + result.emsPage = allocationPageIndex; + result.emsPageOffset = allocationPageUsed; + result.type = MemBlockHandle::EMS; + allocationPageUsed += size; + } + } + + return result; +} + +void* EMSManager::MapBlock(MemBlockHandle& handle) +{ + if (isAvailable && handle.type == MemBlockHandle::EMS) + { + // Check if this page is already mapped first + for (int n = 0; n < NUM_MAPPABLE_PAGES; n++) + { + if (mappedPages[n] == handle.emsPage) + { + return MK_FP(pageAddressSegment + n * EMS_PAGE_SEGMENT_SPACING, handle.emsPageOffset); + } + } + + int mappedPageIndex = nextPageToMap; + + nextPageToMap++; + if (nextPageToMap >= NUM_MAPPABLE_PAGES) + nextPageToMap = 0; + + { + union REGS inregs, outregs; + inregs.h.ah = 0x44; + inregs.h.al = mappedPageIndex; + inregs.x.bx = handle.emsPage; + inregs.x.dx = allocationHandle; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + mappedPages[mappedPageIndex] = handle.emsPage; + } + + return MK_FP(pageAddressSegment + mappedPageIndex * EMS_PAGE_SEGMENT_SPACING, handle.emsPageOffset); + } + + return nullptr; +} diff --git a/src/DOS/EMS.h b/src/DOS/EMS.h new file mode 100644 index 0000000..244e6b0 --- /dev/null +++ b/src/DOS/EMS.h @@ -0,0 +1,42 @@ +#ifndef _EMS_H_ +#define _EMS_H_ + +#include +#include "../Memory/MemBlock.h" + +#define EMS_PAGE_SIZE (16 * 1024l) +#define NUM_MAPPABLE_PAGES 4 +#define EMS_PAGE_SEGMENT_SPACING (1024) + +class EMSManager +{ +public: + EMSManager() : isAvailable(false) {} + + void Init(); + void Reset(); + + bool IsAvailable() { return isAvailable; } + + MemBlockHandle Allocate(size_t size); + void* MapBlock(MemBlockHandle& handle); + + void Shutdown(); + + long TotalAllocated() { return numAllocatedPages * EMS_PAGE_SIZE; } + long TotalUsed() { return allocationPageIndex * EMS_PAGE_SIZE + allocationPageUsed; } + +private: + bool isAvailable; + int numAllocatedPages; + uint16_t pageAddressSegment; + uint16_t allocationHandle; + + uint16_t allocationPageIndex; + uint16_t allocationPageUsed; + + uint16_t mappedPages[NUM_MAPPABLE_PAGES]; + uint8_t nextPageToMap; +}; + +#endif diff --git a/src/DOS/HP95LX.cpp b/src/DOS/HP95LX.cpp deleted file mode 100644 index 05b166f..0000000 --- a/src/DOS/HP95LX.cpp +++ /dev/null @@ -1,942 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#include -#include -#include -#include -#include -#include -#include "../Image.h" -#include "HP95LX.h" -#include "DefData.h" -#include "../Interface.h" - -#define USE_EGA_PREVIS 0 - -#if USE_EGA_PREVIS -#define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) -#else -#define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) -#endif - -#define SCREEN_WIDTH 240 -#define SCREEN_HEIGHT 128 - -#define ADDRESS_BAR_HEIGHT 13 -#define TITLE_BAR_HEIGHT 8 -#define STATUS_BAR_HEIGHT 0 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT ADDRESS_BAR_HEIGHT - -#define BACK_BUTTON_X 0 -#define FORWARD_BUTTON_X (BACK_BUTTON_X + NAVIGATION_BUTTON_WIDTH + 1) -#define ADDRESS_BAR_X (FORWARD_BUTTON_X + NAVIGATION_BUTTON_WIDTH + 1) -#define ADDRESS_BAR_Y (TITLE_BAR_HEIGHT + 1) -#define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - ADDRESS_BAR_X - 1) - -#define WINDOW_TOP (TITLE_BAR_HEIGHT + ADDRESS_BAR_HEIGHT + 2) -#define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define SCROLL_BAR_WIDTH 8 - -#define WINDOW_WIDTH (SCREEN_WIDTH - SCROLL_BAR_WIDTH) - -#if USE_EGA_PREVIS -#define BYTES_PER_LINE 80 -#else -#define BYTES_PER_LINE 30 -#endif - -#define WINDOW_VRAM_TOP (BYTES_PER_LINE * (WINDOW_TOP)) -#define WINDOW_VRAM_BOTTOM (BYTES_PER_LINE * (WINDOW_BOTTOM)) - - - -HP95LXVideoDriver::HP95LXVideoDriver() -{ - screenWidth = SCREEN_WIDTH; - screenHeight = SCREEN_HEIGHT; - windowWidth = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = SCREEN_WIDTH; - scissorY2 = SCREEN_HEIGHT; - invertScreen = true; - clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; - isTextMode = false; -} - -void HP95LXVideoDriver::Init() -{ - startingScreenMode = GetScreenMode(); -#if USE_EGA_PREVIS - SetScreenMode(0x10); -#else - SetScreenMode(0x20); -#endif -} - -void HP95LXVideoDriver::Shutdown() -{ - SetScreenMode(startingScreenMode); -} - -int HP95LXVideoDriver::GetScreenMode() -{ - union REGS inreg, outreg; - inreg.h.ah = 0xf; - - int86(0x10, &inreg, &outreg); - - return (int)outreg.h.al; -} - -void HP95LXVideoDriver::SetScreenMode(int screenMode) -{ - union REGS inreg, outreg; - inreg.h.ah = 0; - inreg.h.al = (unsigned char)screenMode; - - int86(0x10, &inreg, &outreg); -} - -static void FastMemSet(void far* mem, uint8_t value, unsigned int count); -#pragma aux FastMemSet = \ - "rep stosb" \ - modify [di cx] \ - parm[es di][al][cx]; - -void HP95LXVideoDriver::InvertScreen() -{ - int count = (SCREEN_HEIGHT * BYTES_PER_LINE); - unsigned char far* VRAM = BASE_VRAM_ADDRESS; - while (count--) - { - *VRAM ^= 0xff; - VRAM++; - } - - invertScreen = !invertScreen; - clearMask = invertScreen ? 0 : 0xffff; -} - -void HP95LXVideoDriver::ClearScreen() -{ - uint8_t clearValue = (uint8_t)(clearMask & 0xff); - FastMemSet(BASE_VRAM_ADDRESS, clearValue, (SCREEN_HEIGHT * BYTES_PER_LINE)); -} - -void HP95LXVideoDriver::DrawImage(Image* image, int x, int y) -{ - int imageHeight = image->height; - - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + imageHeight < scissorY1) - { - return; - } - if (y + imageHeight > scissorY2) - { - imageHeight = (scissorY2 - y); - } - - uint16_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) BASE_VRAM_ADDRESS; - VRAM += y * BYTES_PER_LINE; - - uint16_t imageWidthBytes = image->width >> 3; - - // Check if rounds to a byte, pad if necessary - if ((imageWidthBytes << 3) != image->width) - { - imageWidthBytes++; - } - - uint8_t* imageData = image->data + firstLine * imageWidthBytes; - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < imageHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - for (uint8_t i = 0; i < imageWidthBytes; i++) - { - uint8_t glyphPixels = *imageData++; - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - VRAMptr += BYTES_PER_LINE; - } -} - -void HP95LXVideoDriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ - Font* font = GetFont(size, style); - - int startX = x; - uint8_t glyphHeight = font->glyphHeight; - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + glyphHeight > scissorY2) - { - glyphHeight = (uint8_t)(scissorY2 - y); - } - if (y + glyphHeight < scissorY1) - { - return; - } - - uint8_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAM += y * BYTES_PER_LINE; - - while (*text) - { - char c = *text++; - if (c < 32 || c >= 128) - { - continue; - } - - char index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; - - if (glyphWidth == 0) - { - continue; - } - - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - - glyphData += (firstLine * font->glyphWidthBytes); - - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < glyphHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) - { - writeOffset++; - } - - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - VRAMptr += BYTES_PER_LINE; - } - - x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; - } - - if (x >= scissorX2) - { - break; - } - } - - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < scissorY2) - { - HLine(startX, y - firstLine + font->glyphHeight - 1, x - startX); - } -} - -Font* HP95LXVideoDriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) - { - switch (fontSize) - { - default: - return &Default_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &Default_RegularFont_Monospace; - } - } - - switch (fontSize) - { - default: - return &Default_SmallFont; - case 2: - case 3: - case 4: - return &Default_RegularFont; - } -} - -void HP95LXVideoDriver::HLine(int x, int y, int count) -{ - if (invertScreen) - { - ClearHLine(x, y, count); - } - else - { - HLineInternal(x, y, count); - } -} - -void HP95LXVideoDriver::HLineInternal(int x, int y, int count) -{ - if (x >= scissorX2) - { - return; - } - if (x + count >= scissorX2) - { - count = scissorX2 - x; - } - if (y < scissorY1 || y >= scissorY2) - return; - - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - uint8_t data = *VRAMptr; - uint8_t mask = ~(0x80 >> (x & 7)); - - while (count--) - { - data &= mask; - x++; - mask = (mask >> 1) | 0x80; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0; - count -= 8; - } - mask = ~0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void HP95LXVideoDriver::ClearHLine(int x, int y, int count) -{ - if (x >= scissorX2) - { - return; - } - if (x + count >= scissorX2) - { - count = scissorX2 - x; - } - - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data |= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void HP95LXVideoDriver::ClearRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } -} - -void HP95LXVideoDriver::InvertLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data ^= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ ^= 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -bool HP95LXVideoDriver::ApplyScissor(int& y, int& height) -{ - if (y + height < scissorY1) - return false; - if (y >= scissorY2) - return false; - if (y < scissorY1) - { - height -= (scissorY1 - y); - y = scissorY1; - } - if (y + height >= scissorY2) - { - height = scissorY2 - y; - } - return true; -} - -void HP95LXVideoDriver::InvertRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - for (int j = 0; j < height; j++) - { - InvertLine(x, y + j, width); - } -} - -void HP95LXVideoDriver::FillRect(int x, int y, int width, int height) -{ - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } -} - -void HP95LXVideoDriver::VLine(int x, int y, int count) -{ - if (x >= scissorX2) - { - return; - } - if (y < scissorY1) - { - count -= (scissorY1 - y); - y = scissorY1; - } - if (y >= scissorY2) - { - return; - } - if (y + count >= scissorY2) - { - count = scissorY2 - y; - } - if (count <= 0) - { - return; - } - - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - uint8_t mask = ~(0x80 >> (x & 7)); - - VRAMptr += y * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - if (invertScreen) - { - mask ^= 0xff; - - while (count--) - { - *VRAMptr |= mask; - VRAMptr += BYTES_PER_LINE; - } - } - else - { - while (count--) - { - *VRAMptr &= mask; - VRAMptr += BYTES_PER_LINE; - } - } -} - -MouseCursorData* HP95LXVideoDriver::GetCursorGraphic(MouseCursor::Type type) -{ - switch (type) - { - default: - case MouseCursor::Pointer: - return &Default_MouseCursor; - case MouseCursor::Hand: - return &Default_MouseCursorHand; - case MouseCursor::TextSelect: - return &Default_MouseCursorTextSelect; - } -} - -int HP95LXVideoDriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) -{ - Font* font = GetFont(fontSize, style); - if (c >= 32 && c < 128) - { - int width = font->glyphWidth[c - 32]; - if (style & FontStyle::Bold) - { - width++; - } - return width; - } - return 0; -} - -int HP95LXVideoDriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return GetFont(fontSize, style)->glyphHeight + 1; -} - -void DrawScrollBarBlock(uint8_t far* ptr, int top, int middle, int bottom); -void DrawScrollBarBlockInverted(uint8_t far* ptr, int top, int middle, int bottom); - -#if USE_EGA_PREVIS -#pragma aux DrawScrollBarBlock = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov al, 0x7e" \ - "_loopTop:" \ - "stosb" \ - "add di, 79" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov al, 0x42" \ - "_loopMiddle:" \ - "stosb" \ - "add di, 79" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov al, 0x7e" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosb" \ - "add di, 79" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -#pragma aux DrawScrollBarBlockInverted = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov al, 0x81" \ - "_loopTop:" \ - "stosb" \ - "add di, 79" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov al, 0xbd" \ - "_loopMiddle:" \ - "stosb" \ - "add di, 79" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0x81" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosb" \ - "add di, 79" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -#else -#pragma aux DrawScrollBarBlock = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov al, 0x7e" \ - "_loopTop:" \ - "stosb" \ - "add di, 29" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov al, 0x42" \ - "_loopMiddle:" \ - "stosb" \ - "add di, 29" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov al, 0x7e" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosb" \ - "add di, 29" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -#pragma aux DrawScrollBarBlockInverted = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov al, 0x81" \ - "_loopTop:" \ - "stosb" \ - "add di, 29" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov al, 0xbd" \ - "_loopMiddle:" \ - "stosb" \ - "add di, 29" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov al, 0x81" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosb" \ - "add di, 29" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; -#endif - -void HP95LXVideoDriver::DrawScrollBar(int position, int size) -{ -// uint8_t* VRAM = BASE_VRAM_ADDRESS + WINDOW_TOP * BYTES_PER_LINE + (BYTES_PER_LINE - 2); - uint8_t* VRAM = BASE_VRAM_ADDRESS + WINDOW_TOP * BYTES_PER_LINE + ((SCREEN_WIDTH / 8) - 1); - - if (invertScreen) - { - DrawScrollBarBlockInverted(VRAM, position, size, WINDOW_HEIGHT - position - size); - } - else - { - DrawScrollBarBlock(VRAM, position, size, WINDOW_HEIGHT - position - size); - } -} - -void HP95LXVideoDriver::DrawRect(int x, int y, int width, int height) -{ - HLine(x, y, width); - HLine(x, y + height - 1, width); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void HP95LXVideoDriver::DrawButtonRect(int x, int y, int width, int height) -{ - HLine(x + 1, y, width - 2); - HLine(x + 1, y + height - 1, width - 2); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void ScrollRegionUp(int dest, int src, int count); -void ScrollRegionDown(int dest, int src, int count); -void ClearRegion(int offset, int count, uint16_t clearMask); - -#if USE_EGA_PREVIS -#pragma aux ScrollRegionUp = \ - "push ds" \ - "push es" \ - "mov ax, 0xa000" /* b000*/ \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 29" \ - "rep movsb" \ - "add di, 51" /* 1 */ \ - "add si, 51" /* 1 */ \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -#pragma aux ScrollRegionDown = \ - "push ds" \ - "push es" \ - "mov ax, 0xa000" /* b000*/ \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 29" \ - "rep movsb" \ - "sub di, 109" /* 160 - 2 = 158 -> 58 */ \ - "sub si, 109" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -#pragma aux ClearRegion = \ - "push es" \ - "mov ax, 0xa000" /* b000 */ \ - "mov es, ax" \ - "mov ax, bx" \ - "_loopLine:" \ - "mov cx, 29" \ - "rep stosb" \ - "add di, 51" /* 2 */ \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - modify [cx di ax cx dx] \ - parm [di] [dx] [bx] -#else -#pragma aux ScrollRegionUp = \ - "push ds" \ - "push es" \ - "mov ax, 0xb000" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 29" \ - "rep movsb" \ - "add di, 1" \ - "add si, 1" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -#pragma aux ScrollRegionDown = \ - "push ds" \ - "push es" \ - "mov ax, 0xb000" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 29" \ - "rep movsb" \ - "sub di, 59" \ - "sub si, 59" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -#pragma aux ClearRegion = \ - "push es" \ - "mov ax, 0xb000" \ - "mov es, ax" \ - "mov ax, bx" \ - "_loopLine:" \ - "mov cx, 29" \ - "rep stosb" \ - "add di, 1" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - modify [cx di ax cx dx] \ - parm [di] [dx] [bx] -#endif - -void HP95LXVideoDriver::ScrollWindow(int amount) -{ - if (amount > 0) - { - int lines = (WINDOW_HEIGHT - amount); - int offset = amount * BYTES_PER_LINE; - ScrollRegionUp(WINDOW_VRAM_TOP, WINDOW_VRAM_TOP + offset, lines); - - ClearRegion(WINDOW_VRAM_BOTTOM - offset, (WINDOW_HEIGHT) - lines, clearMask); - } - else if (amount < 0) - { - int lines = (WINDOW_HEIGHT + amount); - int offset = amount * BYTES_PER_LINE; - ScrollRegionDown(WINDOW_VRAM_BOTTOM - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM - BYTES_PER_LINE + offset, lines); - - ClearRegion(WINDOW_VRAM_TOP, WINDOW_HEIGHT - lines, clearMask); - } -} - -void HP95LXVideoDriver::ClearWindow() -{ - ClearRegion(WINDOW_VRAM_TOP, WINDOW_HEIGHT, clearMask); -} - -void HP95LXVideoDriver::SetScissorRegion(int y1, int y2) -{ - scissorY1 = y1; - scissorY2 = y2; - scissorX1 = 0; - scissorX2 = WINDOW_WIDTH; -} - -void HP95LXVideoDriver::ClearScissorRegion() -{ - scissorY1 = 0; - scissorY2 = screenHeight; - scissorX1 = 0; - scissorX2 = SCREEN_WIDTH; -} - -void HP95LXVideoDriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT; - app.statusBar.width = SCREEN_WIDTH; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 0; - app.titleBar.width = SCREEN_WIDTH; - app.titleBar.height = TITLE_BAR_HEIGHT; -} - -void HP95LXVideoDriver::ScaleImageDimensions(int& width, int& height) -{ -} diff --git a/src/DOS/HP95LX.h b/src/DOS/HP95LX.h deleted file mode 100644 index 98828b5..0000000 --- a/src/DOS/HP95LX.h +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#pragma once - -#include "../Platform.h" - -struct Image; - -class HP95LXVideoDriver : public VideoDriver -{ -public: - HP95LXVideoDriver(); - - virtual void Init(); - virtual void Shutdown(); - virtual void ClearScreen(); - virtual void InvertScreen(); - - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app); - - - virtual void ClearWindow(); - virtual void ScrollWindow(int delta); - virtual void SetScissorRegion(int y1, int y2); - virtual void ClearScissorRegion(); - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void DrawRect(int x, int y, int width, int height); - virtual void DrawButtonRect(int x, int y, int width, int height); - virtual void DrawImage(Image* image, int x, int y); - virtual void ClearRect(int x, int y, int width, int height); - virtual void FillRect(int x, int y, int width, int height); - virtual void InvertRect(int x, int y, int width, int height); - - virtual void HLine(int x, int y, int count); - virtual void VLine(int x, int y, int count); - virtual void ScaleImageDimensions(int& width, int& height); - - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - - virtual void DrawScrollBar(int position, int size); - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - void ClearHLine(int x, int y, int count); - void HLineInternal(int x, int y, int count); - void InvertLine(int x, int y, int count); - bool ApplyScissor(int& y, int& height); - - bool invertScreen; - uint16_t clearMask; - int startingScreenMode; - int scissorX1, scissorY1, scissorX2, scissorY2; - -}; - diff --git a/src/DOS/Hercules.cpp b/src/DOS/Hercules.cpp index f136d3a..cfebc54 100644 --- a/src/DOS/Hercules.cpp +++ b/src/DOS/Hercules.cpp @@ -18,69 +18,20 @@ #include #include #include -#include "../Image.h" +#include "../Image/Image.h" #include "Hercules.h" -#include "DefData.h" +#include "../DataPack.h" #include "../Interface.h" +#include "../Draw/Surf1bpp.h" #define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) -#define SCREEN_WIDTH 720 -#define SCREEN_HEIGHT 348 - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 32 - -#define ADDRESS_BAR_X 60 -#define ADDRESS_BAR_Y 12 -#define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - 64) -#define ADDRESS_BAR_HEIGHT 14 -#define TITLE_BAR_HEIGHT 11 -#define STATUS_BAR_HEIGHT 12 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT ADDRESS_BAR_HEIGHT - -#define WINDOW_TOP 28 -#define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define SCROLL_BAR_WIDTH 16 - -#define WINDOW_VRAM_TOP_PAGE1 (BYTES_PER_LINE * (WINDOW_TOP / 4)) -#define WINDOW_VRAM_TOP_PAGE2 (0x2000 + BYTES_PER_LINE * (WINDOW_TOP / 4)) -#define WINDOW_VRAM_TOP_PAGE3 (0x4000 + BYTES_PER_LINE * (WINDOW_TOP / 4)) -#define WINDOW_VRAM_TOP_PAGE4 (0x6000 + BYTES_PER_LINE * (WINDOW_TOP / 4)) - -#define WINDOW_VRAM_BOTTOM_PAGE1 (BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) -#define WINDOW_VRAM_BOTTOM_PAGE2 (0x2000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) -#define WINDOW_VRAM_BOTTOM_PAGE3 (0x4000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) -#define WINDOW_VRAM_BOTTOM_PAGE4 (0x6000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) - -#define WINDOW_VRAM_TOP_EVEN (BYTES_PER_LINE * (WINDOW_TOP / 2)) -#define WINDOW_VRAM_TOP_ODD (0x2000 + BYTES_PER_LINE * (WINDOW_TOP / 2)) -#define WINDOW_VRAM_BOTTOM_EVEN (BYTES_PER_LINE * (WINDOW_BOTTOM / 2)) -#define WINDOW_VRAM_BOTTOM_ODD (0x2000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 2)) #define BYTES_PER_LINE 90 HerculesDriver::HerculesDriver() { - screenWidth = SCREEN_WIDTH; - screenHeight = SCREEN_HEIGHT; - windowWidth = screenWidth - 16; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - scissorY2 = SCREEN_HEIGHT; - invertScreen = false; - clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; - isTextMode = false; + screenWidth = 720; + screenHeight = 348; } // graphics mode CRTC register values @@ -90,9 +41,24 @@ static uint8_t graphicsModeCRTC[] = { 0x35, 0x2d, 0x2e, 0x07, 0x5b, 0x02, 0x57, static uint8_t textModeCRTC[] = { 0x61, 0x50, 0x52, 0x0f, 0x19, 0x06, 0x19, 0x19, 0x02, 0x0d, 0x0b, 0x0c }; -void HerculesDriver::Init() +void HerculesDriver::Init(VideoModeInfo* inVideoMode) { + videoMode = inVideoMode; SetGraphicsMode(); + + Assets.Load("EGA.DAT"); + + DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); + for (int y = 0; y < screenHeight; y += 4) + { + int offset = BYTES_PER_LINE * (y / 4); + drawSurface1BPP->lines[y] = (BASE_VRAM_ADDRESS) + offset; + drawSurface1BPP->lines[y + 1] = (BASE_VRAM_ADDRESS) + offset + 0x2000; + drawSurface1BPP->lines[y + 2] = (BASE_VRAM_ADDRESS) + offset + 0x4000; + drawSurface1BPP->lines[y + 3] = (BASE_VRAM_ADDRESS) + offset + 0x6000; + } + drawSurface = drawSurface1BPP; + colourScheme = monochromeColourScheme; } void HerculesDriver::Shutdown() @@ -132,773 +98,3 @@ void HerculesDriver::SetTextMode() outp(0x03B8, 0x08); FastMemSet(BASE_VRAM_ADDRESS, 0, 0x4000); } - -void HerculesDriver::InvertScreen() -{ - int count = 0x8000; - unsigned char far* VRAM = BASE_VRAM_ADDRESS; - while (count--) - { - *VRAM ^= 0xff; - VRAM++; - } - - invertScreen = !invertScreen; - clearMask = invertScreen ? 0 : 0xffff; -} - -void HerculesDriver::ClearScreen() -{ - uint8_t clearValue = (uint8_t)(clearMask & 0xff); - FastMemSet(BASE_VRAM_ADDRESS, clearValue, 0x8000); - // White out main page - //FastMemSet(CGA_BASE_VRAM_ADDRESS + BYTES_PER_LINE * TITLE_BAR_HEIGHT / 2, 0xff, BYTES_PER_LINE * (SCREEN_HEIGHT - TITLE_BAR_HEIGHT - STATUS_BAR_HEIGHT) / 2); - //FastMemSet(CGA_BASE_VRAM_ADDRESS + 0x2000 + BYTES_PER_LINE * TITLE_BAR_HEIGHT / 2, 0xff, BYTES_PER_LINE * (SCREEN_HEIGHT - TITLE_BAR_HEIGHT - STATUS_BAR_HEIGHT) / 2); -} - -void HerculesDriver::DrawImage(Image* image, int x, int y) -{ - int imageHeight = image->height; - - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + imageHeight < scissorY1) - { - return; - } - if (y + imageHeight > scissorY2) - { - imageHeight = (scissorY2 - y); - } - - uint16_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) BASE_VRAM_ADDRESS; - VRAM += (y >> 2) * BYTES_PER_LINE; - - uint16_t imageWidthBytes = image->width >> 3; - - // Check if rounds to a byte, pad if necessary - if ((imageWidthBytes << 3) != image->width) - { - imageWidthBytes++; - } - - uint8_t* imageData = image->data + firstLine * imageWidthBytes; - uint8_t far* VRAMptr = VRAM + (x >> 3); - - int interlace = (y & 3); - VRAMptr += 0x2000 * interlace; - - for (uint8_t j = firstLine; j < imageHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - for (uint8_t i = 0; i < imageWidthBytes; i++) - { - uint8_t glyphPixels = *imageData++; - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - - interlace++; - if (interlace == 4) - { - VRAMptr -= (0x6000 - BYTES_PER_LINE); - interlace = 0; - } - else - { - VRAMptr += 0x2000; - } - } -} - -void HerculesDriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ - Font* font = GetFont(size, style); - - int startX = x; - uint8_t glyphHeight = font->glyphHeight; - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + glyphHeight > scissorY2) - { - glyphHeight = (uint8_t)(scissorY2 - y); - } - if (y + glyphHeight < scissorY1) - { - return; - } - - uint8_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAM += (y >> 2) * BYTES_PER_LINE; - int interlace = (y & 3); - VRAM += 0x2000 * interlace; - - while (*text) - { - char c = *text++; - if (c < 32 || c >= 128) - { - continue; - } - - char index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; - - if (glyphWidth == 0) - { - continue; - } - - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - - glyphData += (firstLine * font->glyphWidthBytes); - - interlace = (y & 3); - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < glyphHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) - { - writeOffset++; - } - - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - interlace++; - - if (interlace == 4) - { - VRAMptr -= (0x6000 - BYTES_PER_LINE); - interlace = 0; - } - else - { - VRAMptr += 0x2000; - } - } - - x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; - } - - if (x >= scissorX2) - { - break; - } - } - - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < scissorY2) - { - HLine(startX, y - firstLine + font->glyphHeight - 1, x - startX); - } -} - -Font* HerculesDriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) - { - switch (fontSize) - { - case 0: - return &Default_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &Default_LargeFont_Monospace; - default: - return &Default_RegularFont_Monospace; - } - } - - switch (fontSize) - { - case 0: - return &Default_SmallFont; - case 2: - case 3: - case 4: - return &Default_LargeFont; - default: - return &Default_RegularFont; - } -} - -void HerculesDriver::HLine(int x, int y, int count) -{ - if (invertScreen) - { - ClearHLine(x, y, count); - } - else - { - HLineInternal(x, y, count); - } -} - -void HerculesDriver::HLineInternal(int x, int y, int count) -{ - if (y < scissorY1 || y >= scissorY2) - return; - - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - int interlace = y & 3; - VRAMptr += interlace * 0x2000; - - uint8_t data = *VRAMptr; - uint8_t mask = ~(0x80 >> (x & 7)); - - while (count--) - { - data &= mask; - x++; - mask = (mask >> 1) | 0x80; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0; - count -= 8; - } - mask = ~0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void HerculesDriver::ClearHLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - int interlace = y & 3; - VRAMptr += interlace * 0x2000; - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data |= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void HerculesDriver::ClearRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } -} - -void HerculesDriver::InvertLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - int interlace = y & 3; - VRAMptr += interlace * 0x2000; - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data ^= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ ^= 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -bool HerculesDriver::ApplyScissor(int& y, int& height) -{ - if (y + height < scissorY1) - return false; - if (y >= scissorY2) - return false; - if (y < scissorY1) - { - height -= (scissorY1 - y); - y = scissorY1; - } - if (y + height >= scissorY2) - { - height = scissorY2 - y; - } - return true; -} - -void HerculesDriver::InvertRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - for (int j = 0; j < height; j++) - { - InvertLine(x, y + j, width); - } -} - -void HerculesDriver::FillRect(int x, int y, int width, int height) -{ - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } -} - -void HerculesDriver::VLine(int x, int y, int count) -{ - if (y < scissorY1) - { - count -= (scissorY1 - y); - y = scissorY1; - } - if (y >= scissorY2) - { - return; - } - if (y + count >= scissorY2) - { - count = scissorY2 - y; - } - if (count <= 0) - { - return; - } - - uint8_t far* VRAMptr = (uint8_t far*) BASE_VRAM_ADDRESS; - uint8_t mask = ~(0x80 >> (x & 7)); - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (x >> 3); - - int interlace = (y & 3); - VRAMptr += 0x2000 * interlace; - - if (invertScreen) - { - mask ^= 0xff; - - while (count--) - { - *VRAMptr |= mask; - - interlace++; - if (interlace == 4) - { - VRAMptr -= (0x6000 - BYTES_PER_LINE); - interlace = 0; - } - else - { - VRAMptr += 0x2000; - } - } - } - else - { - while (count--) - { - *VRAMptr &= mask; - - interlace++; - if (interlace == 4) - { - VRAMptr -= (0x6000 - BYTES_PER_LINE); - interlace = 0; - } - else - { - VRAMptr += 0x2000; - } - } - } -} - -MouseCursorData* HerculesDriver::GetCursorGraphic(MouseCursor::Type type) -{ - switch (type) - { - default: - case MouseCursor::Pointer: - return &Default_MouseCursor; - case MouseCursor::Hand: - return &Default_MouseCursorHand; - case MouseCursor::TextSelect: - return &Default_MouseCursorTextSelect; - } -} - -int HerculesDriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) -{ - Font* font = GetFont(fontSize, style); - if (c >= 32 && c < 128) - { - int width = font->glyphWidth[c - 32]; - if (style & FontStyle::Bold) - { - width++; - } - return width; - } - return 0; -} - -int HerculesDriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return GetFont(fontSize, style)->glyphHeight + 1; -} - -void DrawScrollBarBlock(uint8_t far* ptr, int top, int middle, int bottom); -#pragma aux DrawScrollBarBlock = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov ax, 0xfe7f" \ - "_loopTop:" \ - "stosw" \ - "add di, 88" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov ax, 0x0660" \ - "_loopMiddle:" \ - "stosw" \ - "add di, 88" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0xfe7f" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosw" \ - "add di, 88" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - -void DrawScrollBarBlockInverted(uint8_t far* ptr, int top, int middle, int bottom); -#pragma aux DrawScrollBarBlockInverted = \ - "cmp bx, 0" \ - "je _startMiddle" \ - "mov ax, 0x0180" \ - "_loopTop:" \ - "stosw" \ - "add di, 88" \ - "dec bx"\ - "jnz _loopTop" \ - "_startMiddle:" \ - "mov ax, 0xf99f" \ - "_loopMiddle:" \ - "stosw" \ - "add di, 88" \ - "dec cx"\ - "jnz _loopMiddle" \ - "mov ax, 0x0180" \ - "cmp dx, 0" \ - "je _end" \ - "_loopBottom:" \ - "stosw" \ - "add di, 88" \ - "dec dx"\ - "jnz _loopBottom" \ - "_end:" \ - modify [di ax bx cx dx] \ -parm[es di][bx][cx][dx]; - - -void HerculesDriver::DrawScrollBar(int position, int size) -{ - position >>= 2; - size >>= 2; - - uint8_t* VRAM = BASE_VRAM_ADDRESS + WINDOW_TOP / 4 * BYTES_PER_LINE + (BYTES_PER_LINE - 2); - - if (invertScreen) - { - DrawScrollBarBlockInverted(VRAM, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x2000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x4000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x6000, position, size, (WINDOW_HEIGHT / 4) - position - size); - } - else - { - DrawScrollBarBlock(VRAM, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlock(VRAM + 0x2000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlock(VRAM + 0x4000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlock(VRAM + 0x6000, position, size, (WINDOW_HEIGHT / 4) - position - size); - } -} - -void HerculesDriver::DrawRect(int x, int y, int width, int height) -{ - HLine(x, y, width); - HLine(x, y + height - 1, width); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void HerculesDriver::DrawButtonRect(int x, int y, int width, int height) -{ - HLine(x + 1, y, width - 2); - HLine(x + 1, y + height - 1, width - 2); - VLine(x, y + 1, height - 2); - VLine(x + width - 1, y + 1, height - 2); -} - -void ScrollRegionUp(int dest, int src, int count); -#pragma aux ScrollRegionUp = \ - "push ds" \ - "push es" \ - "mov ax, 0xb000" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 44" \ - "rep movsw" \ - "add di, 2" \ - "add si, 2" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -void ScrollRegionDown(int dest, int src, int count); -#pragma aux ScrollRegionDown = \ - "push ds" \ - "push es" \ - "mov ax, 0xb000" \ - "mov ds, ax" \ - "mov es, ax" \ - "_loopLine:" \ - "mov cx, 44" \ - "rep movsw" \ - "sub di, 178" \ - "sub si, 178" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - "pop ds" \ - modify [ax cx dx di si] \ - parm [di][si][dx] - -void ClearRegion(int offset, int count, uint16_t clearMask); -#pragma aux ClearRegion = \ - "push es" \ - "mov ax, 0xb000" \ - "mov es, ax" \ - "mov ax, bx" \ - "_loopLine:" \ - "mov cx, 44" \ - "rep stosw" \ - "add di, 2" \ - "dec dx" \ - "jnz _loopLine" \ - "pop es" \ - modify [cx di ax cx dx] \ - parm [di] [dx] [bx] - -void HerculesDriver::ScrollWindow(int amount) -{ - amount &= ~3; - - if (amount > 0) - { - int lines = (WINDOW_HEIGHT - amount) >> 2; - //int offset = amount * (BYTES_PER_LINE >> 1); - int offset = (amount * BYTES_PER_LINE) >> 2; - ScrollRegionUp(WINDOW_VRAM_TOP_PAGE1, WINDOW_VRAM_TOP_PAGE1 + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_PAGE2, WINDOW_VRAM_TOP_PAGE2 + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_PAGE3, WINDOW_VRAM_TOP_PAGE3 + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_PAGE4, WINDOW_VRAM_TOP_PAGE4 + offset, lines); - - ClearRegion(WINDOW_VRAM_BOTTOM_PAGE1 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_PAGE2 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_PAGE3 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_PAGE4 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - } - else if (amount < 0) - { - int lines = (WINDOW_HEIGHT + amount) >> 2; - int offset = (amount * BYTES_PER_LINE) >> 2; - ScrollRegionDown(WINDOW_VRAM_BOTTOM_PAGE1 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_PAGE1 - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_PAGE2 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_PAGE2 - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_PAGE3 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_PAGE3 - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_PAGE4 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_PAGE4 - BYTES_PER_LINE + offset, lines); - // - ClearRegion(WINDOW_VRAM_TOP_PAGE1, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_PAGE2, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_PAGE3, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_PAGE4, (WINDOW_HEIGHT / 4) - lines, clearMask); - } -} - -void HerculesDriver::ClearWindow() -{ - ClearRegion(WINDOW_VRAM_TOP_PAGE1, (WINDOW_HEIGHT / 4), clearMask); - ClearRegion(WINDOW_VRAM_TOP_PAGE2, (WINDOW_HEIGHT / 4), clearMask); - ClearRegion(WINDOW_VRAM_TOP_PAGE3, (WINDOW_HEIGHT / 4), clearMask); - ClearRegion(WINDOW_VRAM_TOP_PAGE4, (WINDOW_HEIGHT / 4), clearMask); -} - -void HerculesDriver::SetScissorRegion(int y1, int y2) -{ - scissorY1 = y1; - scissorY2 = y2; -} - -void HerculesDriver::ClearScissorRegion() -{ - scissorY1 = 0; - scissorY2 = screenHeight; -} - -void HerculesDriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT; - app.statusBar.width = SCREEN_WIDTH; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 1; - app.titleBar.width = SCREEN_WIDTH; - app.titleBar.height = TITLE_BAR_HEIGHT; -} - -void HerculesDriver::ScaleImageDimensions(int& width, int& height) -{ - // Scale to 4:3 - height = (height * 29) / 45; -} diff --git a/src/DOS/Hercules.h b/src/DOS/Hercules.h index 9b8cee9..815eeec 100644 --- a/src/DOS/Hercules.h +++ b/src/DOS/Hercules.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _HERCULES_H_ +#define _HERCULES_H_ #include "../Platform.h" @@ -23,49 +24,12 @@ class HerculesDriver : public VideoDriver public: HerculesDriver(); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); - virtual void ClearScreen(); - virtual void InvertScreen(); - - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app); - - - virtual void ClearWindow(); - virtual void ScrollWindow(int delta); - virtual void SetScissorRegion(int y1, int y2); - virtual void ClearScissorRegion(); - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void DrawRect(int x, int y, int width, int height); - virtual void DrawButtonRect(int x, int y, int width, int height); - virtual void DrawImage(Image* image, int x, int y); - virtual void ClearRect(int x, int y, int width, int height); - virtual void FillRect(int x, int y, int width, int height); - virtual void InvertRect(int x, int y, int width, int height); - - virtual void HLine(int x, int y, int count); - virtual void VLine(int x, int y, int count); - virtual void ScaleImageDimensions(int& width, int& height); - - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - - virtual void DrawScrollBar(int position, int size); - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); private: void SetGraphicsMode(); void SetTextMode(); +}; - void ClearHLine(int x, int y, int count); - void HLineInternal(int x, int y, int count); - void InvertLine(int x, int y, int count); - bool ApplyScissor(int& y, int& height); - - bool invertScreen; - uint16_t clearMask; - int scissorX1, scissorY1, scissorX2, scissorY2; -}; \ No newline at end of file +#endif diff --git a/src/DOS/Olivetti.cpp b/src/DOS/Olivetti.cpp deleted file mode 100644 index 9a2ebe0..0000000 --- a/src/DOS/Olivetti.cpp +++ /dev/null @@ -1,604 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#include -#include -#include -#include -#include -#include -#include "../Image.h" -#include "CGA.h" -#include "DefData.h" -#include "../Interface.h" - -#define CGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) - -#define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT 400 - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 32 - -#define ADDRESS_BAR_X 60 -#define ADDRESS_BAR_Y 12 -#define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - 64) -#define ADDRESS_BAR_HEIGHT 14 -#define TITLE_BAR_HEIGHT 11 -#define STATUS_BAR_HEIGHT 12 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT ADDRESS_BAR_HEIGHT - -#define WINDOW_TOP 28 -#define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define SCROLL_BAR_WIDTH 16 - -#define WINDOW_VRAM_TOP_0 (BYTES_PER_LINE * (WINDOW_TOP / 4)) -#define WINDOW_VRAM_TOP_1 (0x2000 + BYTES_PER_LINE * (WINDOW_TOP / 4)) -#define WINDOW_VRAM_TOP_2 (0x4000 + BYTES_PER_LINE * (WINDOW_TOP / 4)) -#define WINDOW_VRAM_TOP_3 (0x6000 + BYTES_PER_LINE * (WINDOW_TOP / 4)) - -#define WINDOW_VRAM_BOTTOM_0 (BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) -#define WINDOW_VRAM_BOTTOM_1 (0x2000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) -#define WINDOW_VRAM_BOTTOM_2 (0x4000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) -#define WINDOW_VRAM_BOTTOM_3 (0x6000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 4)) - -#define BYTES_PER_LINE 80 - -OlivettiDriver::OlivettiDriver(int _videoMode) - : videoMode(_videoMode) -{ - screenHeight = SCREEN_HEIGHT; - windowHeight = WINDOW_HEIGHT; - windowY = WINDOW_TOP; - scissorY2 = SCREEN_HEIGHT; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; -} - -void OlivettiDriver::Init() -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(videoMode); -} - -void OlivettiDriver::InvertScreen() -{ - int count = 0x8000; - unsigned char far* VRAM = CGA_BASE_VRAM_ADDRESS; - while (count--) - { - *VRAM ^= 0xff; - VRAM++; - } - - invertScreen = !invertScreen; - clearMask = invertScreen ? 0 : 0xffff; -} - -void OlivettiDriver::ClearScreen() -{ - uint8_t clearValue = (uint8_t)(clearMask & 0xff); - FastMemSet(CGA_BASE_VRAM_ADDRESS, clearValue, 0x8000); -} - -void OlivettiDriver::DrawImage(Image* image, int x, int y) -{ - int imageHeight = image->height; - - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + imageHeight < scissorY1) - { - return; - } - if (y + imageHeight > scissorY2) - { - imageHeight = scissorY2 - y; - } - - uint16_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - uint8_t interlace = y & 3; - VRAM += (y >> 2) * BYTES_PER_LINE; - VRAM += 0x2000 * interlace; - - uint16_t imageWidthBytes = image->width >> 3; - - // Check if rounds to a byte, pad if necessary - if ((imageWidthBytes << 3) != image->width) - { - imageWidthBytes++; - } - - uint8_t* imageData = image->data + firstLine * imageWidthBytes; - uint8_t far* VRAMptr = VRAM + (x >> 3); - - for (uint8_t j = firstLine; j < imageHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - for (uint8_t i = 0; i < imageWidthBytes; i++) - { - uint8_t glyphPixels = *imageData++; - - VRAMptr[i] ^= glyphPixels >> writeOffset; - VRAMptr[i + 1] ^= glyphPixels << (8 - writeOffset); - } - - if (interlace == 3) - { - VRAMptr -= 0x6000 - BYTES_PER_LINE; - } - else - { - VRAMptr += 0x2000; - } - interlace = (interlace + 1) & 3; - } -} - -void OlivettiDriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ - Font* font = GetFont(size, style); - - int startX = x; - uint8_t glyphHeight = font->glyphHeight; - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) - { - return; - } - if (y + glyphHeight > scissorY2) - { - glyphHeight = (uint8_t)(scissorY2 - y); - } - if (y + glyphHeight < scissorY1) - { - return; - } - - uint8_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } - - uint8_t far* VRAM = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - uint8_t interlace = y & 3; - VRAM += (y >> 2) * BYTES_PER_LINE; - VRAM += 0x2000 * interlace; - - while (*text) - { - char c = *text++; - if (c < 32 || c >= 128) - { - continue; - } - - char index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; - - if (glyphWidth == 0) - { - continue; - } - - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - - glyphData += (firstLine * font->glyphWidthBytes); - - uint8_t far* VRAMptr = VRAM + (x >> 3); - interlace = y & 3; - - for (uint8_t j = firstLine; j < glyphHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) - { - writeOffset++; - } - - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - - VRAMptr[i] ^= (glyphPixels >> writeOffset); - VRAMptr[i + 1] ^= (glyphPixels << (8 - writeOffset)); - } - - if (interlace == 3) - { - VRAMptr -= 0x6000 - BYTES_PER_LINE; - } - else - { - VRAMptr += 0x2000; - } - interlace = (interlace + 1) & 3; - } - - x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; - } - - if (x >= scissorX2) - { - break; - } - } - - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < scissorY2) - { - HLine(startX, y - firstLine + font->glyphHeight - 1, x - startX); - } -} - -Font* OlivettiDriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) - { - switch (fontSize) - { - case 0: - return &Default_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &Default_LargeFont_Monospace; - default: - return &Default_RegularFont_Monospace; - } - } - - switch (fontSize) - { - case 0: - return &Default_SmallFont; - case 2: - case 3: - case 4: - return &Default_LargeFont; - default: - return &Default_RegularFont; - } -} - -void OlivettiDriver::HLineInternal(int x, int y, int count) -{ - if (y < scissorY1 || y >= scissorY2) - return; - - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (y & 3) * 0x2000; - VRAMptr += x >> 3; - - uint8_t data = *VRAMptr; - uint8_t mask = ~(0x80 >> (x & 7)); - - while (count--) - { - data &= mask; - x++; - mask = (mask >> 1) | 0x80; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0; - count -= 8; - } - mask = ~0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void OlivettiDriver::ClearHLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (y & 3) * 0x2000; - VRAMptr += x >> 3; - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data |= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ = 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void OlivettiDriver::InvertLine(int x, int y, int count) -{ - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += (y & 3) * 0x2000; - VRAMptr += x >> 3; - - uint8_t data = *VRAMptr; - uint8_t mask = (0x80 >> (x & 7)); - - while (count--) - { - data ^= mask; - x++; - mask >>= 1; - if ((x & 7) == 0) - { - *VRAMptr++ = data; - while (count > 8) - { - *VRAMptr++ ^= 0xff; - count -= 8; - } - mask = 0x80; - data = *VRAMptr; - } - } - - *VRAMptr = data; -} - -void OlivettiDriver::FillRect(int x, int y, int width, int height) -{ - if (invertScreen) - { - for (int j = 0; j < height; j++) - { - ClearHLine(x, y + j, width); - } - } - else - { - for (int j = 0; j < height; j++) - { - HLineInternal(x, y + j, width); - } - } -} - -void OlivettiDriver::VLine(int x, int y, int count) -{ - if (y < scissorY1) - { - count -= (scissorY1 - y); - y = scissorY1; - } - if (y >= scissorY2) - { - return; - } - if (y + count >= scissorY2) - { - count = scissorY2 - y; - } - if (count <= 0) - { - return; - } - - uint8_t far* VRAMptr = (uint8_t far*) CGA_BASE_VRAM_ADDRESS; - uint8_t interlace = y & 3; - uint8_t mask = ~(0x80 >> (x & 7)); - - VRAMptr += (y >> 2) * BYTES_PER_LINE; - VRAMptr += 0x2000 * interlace; - VRAMptr += x >> 3; - - if (invertScreen) - { - mask ^= 0xff; - - while (count--) - { - *VRAMptr |= mask; - if (interlace == 3) - { - VRAMptr -= (0x6000 - BYTES_PER_LINE); - } - else - { - VRAMptr += 0x2000; - } - interlace = (interlace + 1) & 3; - } - } - else - { - while (count--) - { - *VRAMptr &= mask; - if (interlace == 3) - { - VRAMptr -= (0x6000 - BYTES_PER_LINE); - } - else - { - VRAMptr += 0x2000; - } - interlace = (interlace + 1) & 3; - } - } -} - -MouseCursorData* OlivettiDriver::GetCursorGraphic(MouseCursor::Type type) -{ - switch (type) - { - default: - case MouseCursor::Pointer: - return &Default_MouseCursor; - case MouseCursor::Hand: - return &Default_MouseCursorHand; - case MouseCursor::TextSelect: - return &Default_MouseCursorTextSelect; - } -} - -void OlivettiDriver::DrawScrollBar(int position, int size) -{ - position >>= 2; - size >>= 2; - - uint8_t* VRAM = CGA_BASE_VRAM_ADDRESS + WINDOW_TOP / 4 * BYTES_PER_LINE + (BYTES_PER_LINE - 2); - - if (invertScreen) - { - DrawScrollBarBlockInverted(VRAM, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x2000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x4000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlockInverted(VRAM + 0x6000, position, size, (WINDOW_HEIGHT / 4) - position - size); - } - else - { - DrawScrollBarBlock(VRAM, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlock(VRAM + 0x2000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlock(VRAM + 0x4000, position, size, (WINDOW_HEIGHT / 4) - position - size); - DrawScrollBarBlock(VRAM + 0x6000, position, size, (WINDOW_HEIGHT / 4) - position - size); - } -} - -void OlivettiDriver::ScrollWindow(int amount) -{ - amount &= ~3; - - if (amount > 0) - { - int lines = (WINDOW_HEIGHT - amount) >> 2; - int offset = (amount * BYTES_PER_LINE) >> 2; - ScrollRegionUp(WINDOW_VRAM_TOP_0, WINDOW_VRAM_TOP_0 + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_1, WINDOW_VRAM_TOP_1 + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_2, WINDOW_VRAM_TOP_2 + offset, lines); - ScrollRegionUp(WINDOW_VRAM_TOP_3, WINDOW_VRAM_TOP_3 + offset, lines); - - ClearRegion(WINDOW_VRAM_BOTTOM_0 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_1 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_2 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_BOTTOM_3 - offset, (WINDOW_HEIGHT / 4) - lines, clearMask); - } - else if (amount < 0) - { - int lines = (WINDOW_HEIGHT + amount) >> 2; - int offset = (amount * BYTES_PER_LINE) >> 2; - ScrollRegionDown(WINDOW_VRAM_BOTTOM_0 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_0 - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_1 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_1 - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_2 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_2 - BYTES_PER_LINE + offset, lines); - ScrollRegionDown(WINDOW_VRAM_BOTTOM_3 - BYTES_PER_LINE, WINDOW_VRAM_BOTTOM_3 - BYTES_PER_LINE + offset, lines); - - ClearRegion(WINDOW_VRAM_TOP_0, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_1, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_2, (WINDOW_HEIGHT / 4) - lines, clearMask); - ClearRegion(WINDOW_VRAM_TOP_3, (WINDOW_HEIGHT / 4) - lines, clearMask); - } -} - -void OlivettiDriver::ClearWindow() -{ - ClearRegion(WINDOW_VRAM_TOP_0, WINDOW_HEIGHT / 4, clearMask); - ClearRegion(WINDOW_VRAM_TOP_1, WINDOW_HEIGHT / 4, clearMask); - ClearRegion(WINDOW_VRAM_TOP_2, WINDOW_HEIGHT / 4, clearMask); - ClearRegion(WINDOW_VRAM_TOP_3, WINDOW_HEIGHT / 4, clearMask); -} - -void OlivettiDriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT; - app.statusBar.width = SCREEN_WIDTH; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 1; - app.titleBar.width = SCREEN_WIDTH; - app.titleBar.height = TITLE_BAR_HEIGHT; -} - -void OlivettiDriver::ScaleImageDimensions(int& width, int& height) -{ - // Scale to 4:3 - height = (height * 5) / 6; -} diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 0eb2be1..82d8ac2 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -13,29 +13,24 @@ // #include +#include #include "../Platform.h" -#ifdef HP95LX -#include "HP95LX.h" -#else -#include "CGA.h" -#include "EGA.h" #include "Hercules.h" -#include "TextMode.h" -#endif +#include "../VidModes.h" +#include "BIOSVid.h" #include "DOSInput.h" #include "DOSNet.h" -#include "../Image.h" +#include "../Image/Image.h" #include "../Cursor.h" #include "../Font.h" -#include "DefData.inc" +#include "../Draw/Surface.h" +#include "../Memory/Memory.h" +#include "../App.h" -static DOSInputDriver DOSinput; -static DOSNetworkDriver DOSNet; - -VideoDriver* Platform::video = NULL; -InputDriver* Platform::input = &DOSinput; -NetworkDriver* Platform::network = &DOSNet; +VideoDriver* Platform::video = nullptr; +InputDriver* Platform::input = nullptr; +NetworkDriver* Platform::network = nullptr; /* Find6845 @@ -93,32 +88,39 @@ bool DetectHercules(); modify [ax cx dx] \ value [al] -static void AutoDetectVideoDriver() +static const int HP95LX = 13; +static const int Hercules = 10; +static const int CGA = 0; +static const int CGAPalmtop = 1; +static const int EGA = 6; +static const int VGA = 8; + +static int AutoDetectVideoMode() { union REGS inreg, outreg; -#ifdef HP95LX + + // Look for HP 95LX inreg.x.ax = 0x4dd4; int86(0x15, &inreg, &outreg); if (outreg.x.bx == 0x4850) { - Platform::video = new HP95LXVideoDriver(); - printf("Detected HP 95LX\n"); - return; + if (outreg.x.cx == 0x0101) + { + return HP95LX; + } + else if (outreg.x.cx == 0x0102) + { + return CGAPalmtop; + } } - fprintf(stderr, "This build only works with the HP 95LX, 100LX and 200LX\n"); - exit(1); -#else // First try detect presence of VGA card inreg.x.ax = 0x1200; inreg.h.bl = 0x32; int86(0x10, &inreg, &outreg); - if (outreg.h.al == 0x12) { - Platform::video = new VGADriver(); - printf("Detected VGA\n"); - return; + return VGA; } // Attempt to detect EGA card @@ -128,18 +130,14 @@ static void AutoDetectVideoDriver() if (outreg.h.bl < 4) { - Platform::video = new EGADriver(); - printf("Detected EGA\n"); - return; + return EGA; } // Attempt to detect CGA // Note we are preferring CGA over Hercules because some CGA devices are errorneously reporting Hercules support if (Find6845(0x3d4)) { - Platform::video = new CGADriver(); - printf("Detected CGA\n"); - return; + return CGA; } bool isMono = Find6845(0x3b4); @@ -147,91 +145,66 @@ static void AutoDetectVideoDriver() // Attempt to detect Hercules if (isMono && DetectHercules()) { - Platform::video = new HerculesDriver(); - printf("Detected Hercules\n"); - return; + return Hercules; } - if (isMono) + return CGA; +} + +#include "../Node.h" +#include +#include + +bool Platform::Init(int argc, char* argv[]) +{ + network = new DOSNetworkDriver(); + if (network) { - Platform::video = new MDATextModeDriver(); - printf("Detected MDA\n"); - return; - //fprintf(stderr, "MDA cards not supported!\n"); + network->Init(); } - else + else FatalError("Could not create network driver"); + + int suggestedMode = AutoDetectVideoMode(); + VideoModeInfo* videoMode = ShowVideoModePicker(suggestedMode); + if (!videoMode) { - fprintf(stderr, "Could not detect video card!\n"); + return false; } - exit(1); -#endif -} -void Platform::Init(int argc, char* argv[]) -{ - bool inverse = false; - video = NULL; + if (videoMode == &VideoModeList[HP95LX] || videoMode == &VideoModeList[CGAPalmtop]) + { + App::config.invertScreen = true; + } - for (int n = 1; n < argc; n++) + if (videoMode->biosVideoMode == HERCULES_MODE) { -#ifndef HP95LX - if (!stricmp(argv[n], "-v") && !video) - { - video = new VGADriver(); - } - if (!stricmp(argv[n], "-e") && !video) - { - video = new EGADriver(); - } - if (!stricmp(argv[n], "-c") && !video) - { - video = new CGADriver(); - } - if (!stricmp(argv[n], "-o") && !video) - { - video = new OlivettiDriver(0x40); - } - if (!stricmp(argv[n], "-t3100") && !video) - { - video = new OlivettiDriver(0x74); - } - if (!stricmp(argv[n], "-h") && !video) - { - video = new HerculesDriver(); - } - if (!stricmp(argv[n], "-t") && !video) - { - video = new CGATextModeDriver(); - } - if (!stricmp(argv[n], "-m") && !video) - { - video = new MDATextModeDriver(); - } -#endif - if (!stricmp(argv[n], "-i")) - { - inverse = true; - } + video = new HerculesDriver(); + } + else + { + video = new BIOSVideoDriver(); } if (!video) { - AutoDetectVideoDriver(); + FatalError("Could not create video driver"); } - network->Init(); - video->Init(); - video->ClearScreen(); - input->Init(); + video->Init(videoMode); - if (inverse) + input = new DOSInputDriver(); + if (input) { - video->InvertScreen(); + input->Init(); } + else FatalError("Could not create input driver"); + + return true; } void Platform::Shutdown() { + MemoryManager::pageBlockAllocator.Shutdown(); input->Shutdown(); video->Shutdown(); network->Shutdown(); @@ -244,3 +217,23 @@ void Platform::Update() network->Update(); input->Update(); } + +void Platform::FatalError(const char* message, ...) +{ + va_list args; + + if (video) + { + video->Shutdown(); + } + + va_start(args, message); + vfprintf(stderr, message, args); + printf("\n"); + va_end(args); + + MemoryManager::pageBlockAllocator.Shutdown(); + + exit(1); +} + diff --git a/src/DOS/Source.cpp b/src/DOS/Source.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/DOS/Surf1512.cpp b/src/DOS/Surf1512.cpp new file mode 100644 index 0000000..9cbfe5d --- /dev/null +++ b/src/DOS/Surf1512.cpp @@ -0,0 +1,822 @@ +#include +#include "Surf1512.h" +#include "../Font.h" +#include "../Image/Image.h" +#include "../Memory/MemBlock.h" +#include "../Platform.h" +#include "../App.h" + +static uint8_t planeBits[4] = { 1, 2, 4, 8 }; + +static void SetColourSelect(uint8_t mask); +#pragma aux SetColourSelect = \ + "mov dx, 0x3d9" \ + "out dx, al" \ + modify[dx] \ + parm[al]; + +static void SetBorderColour(uint8_t colour); +#pragma aux SetBorderColour = \ + "mov dx, 0x3df" \ + "out dx, al" \ + modify[dx] \ + parm[al]; + +static void SetPlaneWriteMask(uint8_t mask); +#pragma aux SetPlaneWriteMask = \ + "mov dx, 0x3dd" \ + "out dx, al" \ + modify [dx] \ + parm[al]; + +static void SetPlaneRead(uint8_t plane); +#pragma aux SetPlaneRead = \ + "mov dx, 0x3de" \ + "out dx, al" \ + modify [dx] \ + parm[al]; + +DrawSurface_4BPP_PC1512::DrawSurface_4BPP_PC1512(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; + format = DrawSurface::Format_4BPP_PC1512; +} + +void DrawSurface_4BPP_PC1512::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (y < context.clipTop || y >= context.clipBottom) + { + return; + } + if (x < context.clipLeft) + { + count -= (context.clipLeft - x); + x = context.clipLeft; + } + if (x + count >= context.clipRight) + { + count = context.clipRight - x; + } + if (count < 0) + { + return; + } + + uint8_t* VRAM = lines[y]; + VRAM += (x >> 3); + + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + uint8_t writeMask = planeBits[plane]; + SetPlaneWriteMask(writeMask); + uint8_t* VRAMptr = VRAM; + uint8_t data = *VRAMptr; + int xCount = count; + + if (colour & writeMask) + { + uint8_t mask = (0x80 >> (x & 7)); + + while (xCount--) + { + data |= mask; + mask >>= 1; + if (!mask) + { + *VRAMptr++ = data; + while (xCount > 8) + { + *VRAMptr++ = 0xff; + xCount -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + else + { + uint8_t mask = ~(0x80 >> (x & 7)); + + while (xCount--) + { + data &= mask; + mask = (mask >> 1) | 0x80; + if (mask == 0xff) + { + *VRAMptr++ = data; + while (xCount > 8) + { + *VRAMptr++ = 0; + xCount -= 8; + } + mask = ~0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + } +} + +void DrawSurface_4BPP_PC1512::VLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x >= context.clipRight || x < context.clipLeft) + { + return; + } + if (y < context.clipTop) + { + count -= (context.clipTop - y); + y = context.clipTop; + } + if (y >= context.clipBottom) + { + return; + } + if (y + count > context.clipBottom) + { + count = context.clipBottom - y; + } + if (count <= 0) + { + return; + } + + uint8_t mask = (0x80 >> (x & 7)); + int index = x >> 3; + + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + uint8_t planeMask = planeBits[plane]; + SetPlaneWriteMask(planeMask); + int yCount = count; + int outY = y; + + if (colour & planeMask) + { + while (yCount--) + { + (lines[outY])[index] |= mask; + outY++; + } + } + else + { + uint8_t andMask = mask ^ 0xff; + while (yCount--) + { + (lines[outY])[index] &= andMask; + outY++; + } + } + } +} + +void DrawSurface_4BPP_PC1512::FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + while (height--) + { + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + uint8_t planeMask = planeBits[plane]; + SetPlaneWriteMask(planeMask); + + int count = width; + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + uint8_t data = *VRAMptr; + + if (colour & planeMask) + { + uint8_t mask = (0x80 >> (x & 7)); + + while (count--) + { + data |= mask; + mask >>= 1; + if (!mask) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ = 0xff; + count -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + else + { + uint8_t mask = ~(0x80 >> (x & 7)); + + while (count--) + { + data &= mask; + mask = (mask >> 1) | 0x80; + if (mask == 0xff) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ = 0; + count -= 8; + } + mask = ~0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + } + y++; + } +} + +void DrawSurface_4BPP_PC1512::DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + int startX = x; + uint8_t glyphHeight = font->glyphHeight; + + if (x >= context.clipRight) + { + return; + } + if (y >= context.clipBottom) + { + return; + } + if (y + glyphHeight > context.clipBottom) + { + glyphHeight = (uint8_t)(context.clipBottom - y); + } + if (y + glyphHeight < context.clipTop) + { + return; + } + + uint8_t firstLine = 0; + if (y < context.clipTop) + { + firstLine += context.clipTop - y; + y += firstLine; + } + + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int index = c - 32; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; + + if (glyphWidth == 0) + { + continue; + } + + glyphWidth += bold; + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphDataPtr = font->glyphData + font->glyphs[index].offset; + glyphDataPtr += (firstLine * glyphWidthBytes); + + for (int plane = 0; plane < 4; plane++) + { + int outY = y; + uint8_t* VRAMptr = lines[y] + (x >> 3); + uint8_t* glyphData = glyphDataPtr; + + SetPlaneRead(plane); + + uint8_t writeMask = planeBits[plane]; + SetPlaneWriteMask(writeMask); + + if (!(writeMask & colour)) + { + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + uint8_t writeOffset = (uint8_t)(x) & 0x7; + + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + writeOffset++; + } + + uint8_t boldCarry = 0; + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + if (bold) + { + if (boldCarry) + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1) | 0x80; + } + else + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1); + } + } + + VRAMptr[i] &= ~(glyphPixels >> writeOffset); + VRAMptr[i + 1] &= ~(glyphPixels << (8 - writeOffset)); + } + + outY++; + VRAMptr = lines[outY] + (x >> 3); + } + } + else + { + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + uint8_t writeOffset = (uint8_t)(x) & 0x7; + + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + writeOffset++; + } + + uint8_t boldCarry = 0; + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + if (bold) + { + if (boldCarry) + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1) | 0x80; + } + else + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1); + } + } + + VRAMptr[i] |= (glyphPixels >> writeOffset); + VRAMptr[i + 1] |= (glyphPixels << (8 - writeOffset)); + } + + outY++; + VRAMptr = lines[outY] + (x >> 3); + } + } + } + + x += glyphWidth; + + //if (x >= context.clipRight) + //{ + // break; + //} + } + + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + { + HLine(context, startX, y - firstLine + font->glyphHeight - 1 - context.drawOffsetY, x - startX - context.drawOffsetX, colour); + } + +} + +void DrawSurface_4BPP_PC1512::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + if (!image->lines.IsAllocated() ) + return; + + x += context.drawOffsetX; + y += context.drawOffsetY; + + int srcWidth = image->width; + int srcHeight = image->height; + int startX = 0; + int srcX = 0; + int srcY = 0; + + // Calculate the destination width and height to copy, considering clipping region + int destWidth = srcWidth; + int destHeight = srcHeight; + + if (x < context.clipLeft) + { + srcX += (context.clipLeft - x); + destWidth -= (context.clipLeft - x); + x = context.clipLeft; + } + + if (x + destWidth > context.clipRight) + { + destWidth = context.clipRight - x; + } + + if (y < context.clipTop) + { + srcY += (context.clipTop - y); + destHeight -= (context.clipTop - y); + y = context.clipTop; + } + + if (y + destHeight > context.clipBottom) + { + destHeight = context.clipBottom - y; + } + + if (destWidth <= 0 || destHeight <= 0) + { + return; // Nothing to draw if fully outside the clipping region. + } + + if (image->bpp == 1) + { + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[j + srcY]; + + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + SetPlaneWriteMask(planeBits[plane]); + + uint8_t* src = imageLine.Get() + (srcX >> 3); + uint8_t* dest = lines[y + j] + (x >> 3); + uint8_t srcMask = 0x80 >> (srcX & 7); + uint8_t destMask = 0x80 >> (x & 7); + uint8_t srcBuffer = (*src++); + uint8_t destBuffer = *dest; + + for (int i = 0; i < destWidth; i++) + { + if (srcBuffer & srcMask) + { + destBuffer |= destMask; + } + else + { + destBuffer &= ~destMask; + } + srcMask >>= 1; + if (!srcMask) + { + srcMask = 0x80; + srcBuffer = (*src++); + } + destMask >>= 1; + if (!destMask) + { + *dest++ = destBuffer; + destBuffer = *dest; + destMask = 0x80; + } + } + *dest = destBuffer; + } + } + } + else + { + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[j + srcY]; + + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + uint8_t planeMask = planeBits[plane]; + SetPlaneWriteMask(planeMask); + + uint8_t* src = imageLine.Get() + (srcX >> 3); + uint8_t* dest = lines[y + j] + (x >> 3); + uint8_t destMask = 0x80 >> (x & 7); + uint8_t srcBuffer = (*src++); + uint8_t destBuffer = *dest; + + int count = destWidth; + + while(count--) + { + uint8_t colour = *src++; + + if (colour != TRANSPARENT_COLOUR_VALUE) + { + if (colour & planeMask) + { + destBuffer |= destMask; + } + else + { + destBuffer &= ~destMask; + } + } + + destMask >>= 1; + if (!destMask) + { + *dest++ = destBuffer; + destBuffer = *dest; + + while (count >= 8) + { + // Unrolled 8 pixels at a time + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x80; + else + destBuffer &= ~0x80; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x40; + else + destBuffer &= ~0x40; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x20; + else + destBuffer &= ~0x20; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x10; + else + destBuffer &= ~0x10; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x8; + else + destBuffer &= ~0x8; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x4; + else + destBuffer &= ~0x4; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x2; + else + destBuffer &= ~0x2; + colour = *src++; + if (colour != TRANSPARENT_COLOUR_VALUE) + if (colour & planeMask) + destBuffer |= 0x1; + else + destBuffer &= ~0x1; + + *dest++ = destBuffer; + destBuffer = *dest; + count -= 8; + } + + destMask = 0x80; + } + } + *dest = destBuffer; + } + } + } +} + + +void DrawSurface_4BPP_PC1512::InvertRect(DrawContext& context, int x, int y, int width, int height) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + while (height--) + { + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + uint8_t planeMask = planeBits[plane]; + SetPlaneWriteMask(planeMask); + + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + int count = width; + uint8_t data = *VRAMptr; + uint8_t mask = (0x80 >> (x & 7)); + + while (count--) + { + data ^= mask; + //mask = (mask >> 1) | 0x80; + mask >>= 1; + if (!mask) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ ^= 0xff; + count -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + y++; + } +} + +void DrawSurface_4BPP_PC1512::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) +{ + const uint16_t widgetEdge = 0x0660; + const uint16_t widgetInner = 0xfa5f; + const uint16_t grab = 0x0a50; + const uint16_t edge = 0; + const uint16_t inner = 0xfe7f; + + SetPlaneWriteMask(0xf); + + x += context.drawOffsetX; + y += context.drawOffsetY; + int startY = y; + + x >>= 3; + const int grabSize = 7; + const int minWidgetSize = grabSize + 4; + const int widgetPaddingSize = size - minWidgetSize; + int topPaddingSize = widgetPaddingSize >> 1; + int bottomPaddingSize = widgetPaddingSize - topPaddingSize; + int bottomSpacing = height - position - size; + + while (position--) + { + *(uint16_t*)(&lines[y++][x]) = inner; + } + + *(uint16_t*)(&lines[y++][x]) = inner; + *(uint16_t*)(&lines[y++][x]) = widgetEdge; + + + while (topPaddingSize--) + { + *(uint16_t*)(&lines[y++][x]) = widgetInner; + } + + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + + while (bottomPaddingSize--) + { + *(uint16_t*)(&lines[y++][x]) = widgetInner; + } + + *(uint16_t*)(&lines[y++][x]) = widgetEdge; + *(uint16_t*)(&lines[y++][x]) = inner; + + while (bottomSpacing--) + { + *(uint16_t*)(&lines[y++][x]) = inner; + } +} + +void DrawSurface_4BPP_PC1512::Clear() +{ + int widthBytes = width >> 3; + uint8_t clear = 0xff; + +// SetBorderColour(0xf); + SetColourSelect(0xf); + SetPlaneWriteMask(0xf); + + for (int y = 0; y < height; y++) + { + memset(lines[y], clear, widthBytes); + } +} + +void DrawSurface_4BPP_PC1512::ScrollScreen(int top, int bottom, int width, int amount) +{ + width >>= 3; + + if (amount > 0) + { + for (int y = top; y < bottom; y++) + { + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + SetPlaneWriteMask(planeBits[plane]); + memcpy(lines[y], lines[y + amount], width); + } + } + } + else if (amount < 0) + { + for (int y = bottom - 1; y >= top; y--) + { + for (int plane = 0; plane < 4; plane++) + { + SetPlaneRead(plane); + SetPlaneWriteMask(planeBits[plane]); + memcpy(lines[y], lines[y + amount], width); + } + } + } +} + diff --git a/src/DOS/Surf1512.h b/src/DOS/Surf1512.h new file mode 100644 index 0000000..b2316e6 --- /dev/null +++ b/src/DOS/Surf1512.h @@ -0,0 +1,23 @@ +#ifndef _SURF1512_H_ +#define _SURF1512_H_ + +#include +#include "../Draw/Surface.h" + +class DrawSurface_4BPP_PC1512 : public DrawSurface +{ +public: + DrawSurface_4BPP_PC1512(int inWidth, int inHeight); + + virtual void Clear(); + virtual void HLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void VLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour); + virtual void DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style = FontStyle::Regular); + virtual void BlitImage(DrawContext& context, Image* image, int x, int y); + virtual void InvertRect(DrawContext& context, int x, int y, int width, int height); + virtual void VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size); + virtual void ScrollScreen(int top, int bottom, int width, int amount); +}; + +#endif diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp new file mode 100644 index 0000000..28347af --- /dev/null +++ b/src/DOS/Surf4bpp.cpp @@ -0,0 +1,799 @@ +#include +#include +#include +#include "../Draw/Surf4bpp.h" +#include "../Font.h" +#include "../Image/Image.h" +#include "../Memory/MemBlock.h" +#include "../Colour.h" + +#define USE_ASM_ROUTINES 1 + +#define GC_INDEX 0x3ce +#define GC_DATA 0x3cf + +#define GC_SET_RESET 0 +#define GC_MODE 0x5 +#define GC_ROTATE 3 +#define GC_BITMASK 8 + +#define SC_INDEX 0x3C4 +#define SC_MAPMASK 2 + +#define GC_XOR 0x18 + +static uint8_t pixelBitmasks[8] = +{ + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static uint8_t pixelEndBitmasks[8] = +{ + 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe +}; + +inline void SetPenColour(uint8_t colour) +{ + uint8_t temp; + + outp(GC_INDEX, GC_SET_RESET); + temp = inp(GC_DATA); + temp &= 0xf0; + temp |= colour; + outp(GC_DATA, temp); +} + +DrawSurface_4BPP::DrawSurface_4BPP(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; + format = DrawSurface::Format_4BPP_EGA; +} + +void DrawSurface_4BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (y < context.clipTop || y >= context.clipBottom) + { + return; + } + if (x < context.clipLeft) + { + count -= (context.clipLeft - x); + x = context.clipLeft; + } + if (x + count >= context.clipRight) + { + count = context.clipRight - x; + } + if (count < 0) + { + return; + } + + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + SetPenColour(colour); + + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + + // Start pixels + uint8_t mask = 0; + while (count--) + { + mask |= pixelBitmasks[x & 7]; + x++; + if (!(x & 7)) + break; + } + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr++ |= 0xff; + + if (!count) + return; + + // Middle bytes + outp(GC_DATA, 0xff); + while (count >= 8) + { + count -= 8; + *VRAMptr++ |= 0xff; + } + + // End byte + if (count > 0) + { + mask = pixelEndBitmasks[count]; + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + } +} + +void DrawSurface_4BPP::VLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x >= context.clipRight || x < context.clipLeft) + { + return; + } + if (y < context.clipTop) + { + count -= (context.clipTop - y); + y = context.clipTop; + } + if (y >= context.clipBottom) + { + return; + } + if (y + count > context.clipBottom) + { + count = context.clipBottom - y; + } + if (count <= 0) + { + return; + } + + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + SetPenColour(colour); + + uint8_t mask = pixelBitmasks[x & 7]; + int index = x >> 3; + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + while (count--) + { + (lines[y])[index] |= mask; + y++; + } +} + +void DrawSurface_4BPP::FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + SetPenColour(colour); + + while (height) + { + int count = width; + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + + // Start pixels + uint8_t mask = 0; + int workX = x; + while (count--) + { + mask |= pixelBitmasks[workX & 7]; + workX++; + if (!(workX & 7)) + break; + } + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr++ |= 0xff; + + if (count) + { + // Middle bytes + outp(GC_DATA, 0xff); + while (count >= 8) + { + count -= 8; + *VRAMptr++ |= 0xff; + } + + // End byte + if (count > 0) + { + mask = pixelEndBitmasks[count]; + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + } + } + + height--; + y++; + } +} + +void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + int startX = x; + uint8_t glyphHeight = font->glyphHeight; + + if (x >= context.clipRight) + { + return; + } + if (y >= context.clipBottom) + { + return; + } + if (y + glyphHeight > context.clipBottom) + { + glyphHeight = (uint8_t)(context.clipBottom - y); + } + if (y + glyphHeight < context.clipTop) + { + return; + } + + uint8_t firstLine = 0; + if (y < context.clipTop) + { + firstLine += context.clipTop - y; + y += firstLine; + } + + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + SetPenColour(colour); + + // Set bit mask + outp(GC_INDEX, GC_BITMASK); + + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int index = c - 32; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; + + if (glyphWidth == 0) + { + continue; + } + + glyphWidth += bold; + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; + + glyphData += (firstLine * glyphWidthBytes); + + int outY = y; + uint8_t* VRAMptr = lines[y] + (x >> 3); + + { + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + uint8_t writeOffset = (uint8_t)(x) & 0x7; + + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + writeOffset++; + } + + uint8_t boldCarry = 0; + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + if (bold) + { + if (boldCarry) + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1) | 0x80; + } + else + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1); + } + } + + outp(GC_DATA, glyphPixels >> writeOffset); + VRAMptr[i] |= 0xff; + outp(GC_DATA, glyphPixels << (8 - writeOffset)); + VRAMptr[i + 1] |= 0xff; + + //VRAMptr[i] |= (glyphPixels >> writeOffset); + //VRAMptr[i + 1] |= (glyphPixels << (8 - writeOffset)); + } + + outY++; + VRAMptr = lines[outY] + (x >> 3); + } + } + + x += glyphWidth; + } + + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + { + HLine(context, startX, y - firstLine + font->glyphHeight - 1 - context.drawOffsetY, x - startX - context.drawOffsetX, colour); + } + +} + +static void BlitLineASM(uint8_t* srcPtr, uint8_t* destLine, uint8_t destMask, int count); +#pragma aux BlitLineASM = \ + "push ds" \ + "mov ds, dx" /* ds:si = src */ \ + "next_pixel:" \ + "mov dx, 0x3ce" /* dx = GC_INDEX */ \ + "mov al, 0" /* GC_SET_RESET */ \ + "out dx, al" \ + "lodsb" \ + "cmp al, 0xff" \ + "je pixel_done" /* Skip if transparent (0xff) */ \ + "inc dx" /* dx = GC_DATA */ \ + "out dx, al" /* Write colour register */ \ + "mov dx, 0x3ce" /* dx = GC_INDEX */ \ + "mov al, 8" /* GC_BITMASK */ \ + "out dx, al" \ + "inc dx" /* dx = GC_DATA */ \ + "mov al, bl" \ + "out dx, al" /* Write bitmask register */ \ + "mov al, [es:di]" /* Read latches */ \ + "or [es:di], 0xff" /* Write */ \ + "pixel_done:" \ + "ror bl, 1" /* Rotate bitmask */ \ + "cmp bl, 0x80" \ + "jne rotated_mask" \ + "inc di" /* Mask fully rotated, move to next byte */ \ + "rotated_mask:" \ + "dec cx" \ + "jnz next_pixel" \ + "pop ds" \ + modify [cx ax si di dx] \ + parm[dx si][es di][bl][cx]; + +void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + if (!image->lines.IsAllocated()) + return; + x += context.drawOffsetX; + y += context.drawOffsetY; + + int srcWidth = image->width; + int srcHeight = image->height; + int srcPitch = image->pitch; + int srcX = 0; + int srcY = 0; + + // Calculate the destination width and height to copy, considering clipping region + int destWidth = srcWidth; + int destHeight = srcHeight; + + if (x < context.clipLeft) + { + if (image->bpp == 1) + { + srcX += ((context.clipLeft - x) >> 3); + } + else + { + srcX += (context.clipLeft - x); + } + destWidth -= (context.clipLeft - x); + x = context.clipLeft; + } + + if (x + destWidth > context.clipRight) + { + destWidth = context.clipRight - x; + } + + if (y < context.clipTop) + { + srcY += (context.clipTop - y); + destHeight -= (context.clipTop - y); + y = context.clipTop; + } + + if (y + destHeight > context.clipBottom) + { + destHeight = context.clipBottom - y; + } + + if (destWidth <= 0 || destHeight <= 0) + { + return; // Nothing to draw if fully outside the clipping region. + } + + if (image->bpp == 8) + { + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + uint8_t startDestMask = 0x80 >> (x & 7); + int destOffset = (x >> 3); + + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[srcY + j]; + uint8_t* src = imageLine.Get() + srcX; + +#if USE_ASM_ROUTINES + uint8_t* dest = lines[y + j] + destOffset; + BlitLineASM(src, dest, startDestMask, destWidth); +#else + uint8_t* destRow = lines[y + j] + destOffset; + uint8_t destMask = startDestMask; + + for (int i = 0; i < destWidth; i++) + { + uint8_t colour = *src++; + + if (colour != TRANSPARENT_COLOUR_VALUE) + { + outp(GC_INDEX, GC_SET_RESET); + outp(GC_DATA, colour); + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, destMask); + + *destRow |= 0xff; + } + + destMask >>= 1; + if (!destMask) + { + destMask = 0x80; + destRow++; + } + } +#endif + } + } + else + { + // Set write mode + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x0); + + outp(GC_INDEX, GC_ROTATE); + outp(GC_DATA, 0); + + outp(GC_INDEX, GC_SET_RESET); + outp(GC_DATA, 0x0); + + // Set bit mask + outp(GC_INDEX, GC_BITMASK); + + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + outp(GC_DATA, 0xff); + + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[j + srcY]; + uint8_t* src = imageLine.Get() + (srcX >> 3); + uint8_t* dest = lines[y + j] + (x >> 3); + uint8_t srcMask = 0x80 >> (srcX & 7); + uint8_t destMask = 0x80 >> (x & 7); + uint8_t srcBuffer = *src++; + uint8_t writeBitMask = 0; + uint8_t destBuffer = *dest; + + outp(GC_DATA, writeBitMask); + + for (int i = 0; i < destWidth; i++) + { + writeBitMask |= destMask; + if ((srcBuffer & srcMask)) + { + destBuffer |= destMask; + } + else + { + destBuffer &= ~destMask; + } + srcMask >>= 1; + if (!srcMask) + { + srcMask = 0x80; + srcBuffer = *src++; + } + destMask >>= 1; + if (!destMask) + { + outp(GC_DATA, writeBitMask); + *dest++ = destBuffer; + destBuffer = *dest; + destMask = 0x80; + writeBitMask = 0; + } + } + + if (writeBitMask) + { + outp(GC_DATA, writeBitMask); + *dest++ = destBuffer; + } + } + } +} + + +void DrawSurface_4BPP::InvertRect(DrawContext& context, int x, int y, int width, int height) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + outp(GC_INDEX, GC_ROTATE); + outp(GC_DATA, GC_XOR); + + SetPenColour(0xf); + + while (height) + { + int count = width; + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + + // Start pixels + uint8_t mask = 0; + int workX = x; + while (count--) + { + mask |= pixelBitmasks[workX & 7]; + workX++; + if (!(workX & 7)) + break; + } + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr++ |= 0xff; + + if (count) + { + // Middle bytes + outp(GC_DATA, 0xff); + while (count >= 8) + { + count -= 8; + *VRAMptr++ |= 0xff; + } + + // End byte + if (count > 0) + { + mask = pixelEndBitmasks[count]; + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + } + } + + height--; + y++; + } + + outp(GC_INDEX, GC_ROTATE); + outp(GC_DATA, 0); + +} + +void DrawSurface_4BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + int startY = y; + + x >>= 3; + const int grabSize = 7; + const int minWidgetSize = grabSize + 4; + const int widgetPaddingSize = size - minWidgetSize; + int topPaddingSize = widgetPaddingSize >> 1; + int bottomPaddingSize = widgetPaddingSize - topPaddingSize; + const uint16_t edge = 0; + const uint16_t inner = 0xfe7f; + int bottomSpacing = height - position - size; + + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x0); + + SetPenColour(0xff); + + // Set bit mask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, 0xff); + + while (position--) + { + *(uint16_t*)(&lines[y++][x]) = inner; + } + + const uint16_t widgetEdge = 0x0660; + *(uint16_t*)(&lines[y++][x]) = inner; + *(uint16_t*)(&lines[y++][x]) = widgetEdge; + + const uint16_t widgetInner = 0xfa5f; + const uint16_t grab = 0x0a50; + + while (topPaddingSize--) + { + *(uint16_t*)(&lines[y++][x]) = widgetInner; + } + + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + + while (bottomPaddingSize--) + { + *(uint16_t*)(&lines[y++][x]) = widgetInner; + } + + *(uint16_t*)(&lines[y++][x]) = widgetEdge; + *(uint16_t*)(&lines[y++][x]) = inner; + + while (bottomSpacing--) + { + *(uint16_t*)(&lines[y++][x]) = inner; + } +} + +void DrawSurface_4BPP::Clear() +{ + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + SetPenColour(0xf); + + // Set bit mask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, 0xff); + + int widthBytes = width >> 3; + for (int y = 0; y < height; y++) + { + memset(lines[y], 0xff, widthBytes); + } +} + +// Implementation of memcpy that copies one byte at a time +// Needed when copying video memory since plane values get +// stored in VGA latches. Using standard memcpy() will +// copy with words for speed but ends up corrupting the +// plane information +static void memcpy_bytes(void far* dest, void far* src, unsigned int count); +#pragma aux memcpy_bytes = \ + "push ds" \ + "mov ds, dx" \ + "rep movsb" \ + "pop dx" \ + modify [si di cx] \ + parm[es di][dx si][cx]; + + +void DrawSurface_4BPP::ScrollScreen(int top, int bottom, int width, int amount) +{ + width >>= 3; + + // Set write mode 1 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x1); + + outp(GC_INDEX, GC_ROTATE); + outp(GC_DATA, 0); + + // Set bit mask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, 0xff); + + if (amount > 0) + { + for (int y = top; y < bottom; y++) + { + memcpy_bytes(lines[y], lines[y + amount], width); + } + } + else if (amount < 0) + { + for (int y = bottom - 1; y >= top; y--) + { + memcpy_bytes(lines[y], lines[y + amount], width); + } + } +} diff --git a/src/DOS/TextData.inc b/src/DOS/TextData.inc deleted file mode 100644 index 19cf353..0000000 --- a/src/DOS/TextData.inc +++ /dev/null @@ -1,14 +0,0 @@ -// Text mode resources -// This file is auto generated - -// Fonts: -Font TextMode_Font = { - // Glyph widths - { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}, - 0, // Byte width - 1, // Glyph height - 0, // Glyph stride - NULL -}; - - diff --git a/src/DOS/TextMode.cpp b/src/DOS/TextMode.cpp deleted file mode 100644 index 7fe6d2f..0000000 --- a/src/DOS/TextMode.cpp +++ /dev/null @@ -1,371 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#include -#include -#include -#include -#include -#include -#include "../Image.h" -#include "TextMode.h" -#include "TextData.inc" -#include "../Interface.h" - -#define NAVIGATION_BUTTON_WIDTH 3 -#define NAVIGATION_BUTTON_HEIGHT 1 - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 6 - -#define ADDRESS_BAR_X 7 -#define ADDRESS_BAR_Y 1 -#define ADDRESS_BAR_WIDTH 64 -#define ADDRESS_BAR_HEIGHT 1 -#define TITLE_BAR_HEIGHT 1 -#define STATUS_BAR_HEIGHT 1 -#define STATUS_BAR_Y (screenHeight - STATUS_BAR_HEIGHT) - -#define WINDOW_TOP 3 -#define WINDOW_HEIGHT (screenHeight - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define SCROLL_BAR_WIDTH 1 - -//#define WINDOW_VRAM_TOP_EVEN (BYTES_PER_LINE * (WINDOW_TOP / 2)) -//#define WINDOW_VRAM_TOP_ODD (0x2000 + BYTES_PER_LINE * (WINDOW_TOP / 2)) -//#define WINDOW_VRAM_BOTTOM_EVEN (BYTES_PER_LINE * (WINDOW_BOTTOM / 2)) -//#define WINDOW_VRAM_BOTTOM_ODD (0x2000 + BYTES_PER_LINE * (WINDOW_BOTTOM / 2)) -#define BYTES_PER_LINE 160 - -static Image dummyImage = -{ - 0, 0, NULL -}; - -TextModeDriver::TextModeDriver(int inScreenMode, uint8_t* inVideoBaseAddress, uint8_t* inTextAttributeMap) -{ - screenWidth = 80; - screenHeight = 25; - windowWidth = 79; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = screenWidth - SCROLL_BAR_WIDTH; - scissorY2 = screenHeight; - invertScreen = false; - clearMask = invertScreen ? 0 : 0xffff; - - videoBaseAddress = inVideoBaseAddress; - textAttributeMap = inTextAttributeMap; - screenMode = inScreenMode; - - imageIcon = &dummyImage; - bulletImage = &dummyImage; - isTextMode = true; - //imageIcon = &CGA_ImageIcon; - //bulletImage = &CGA_Bullet; -} - -void TextModeDriver::Init() -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(screenMode); - - bool disableBlinking = true; - if (disableBlinking) - { - // For CGA cards - outp(0x3D8, 9); - - // For EGA and above - union REGS inreg, outreg; - inreg.h.ah = 0x10; - inreg.h.al = 0x3; - inreg.h.bl = 0x0; - int86(0x10, &inreg, &outreg); - } -} - -void TextModeDriver::Shutdown() -{ - SetScreenMode(startingScreenMode); -} - -int TextModeDriver::GetScreenMode() -{ - union REGS inreg, outreg; - inreg.h.ah = 0xf; - - int86(0x10, &inreg, &outreg); - - return (int)outreg.h.al; -} - -void TextModeDriver::SetScreenMode(int screenMode) -{ - union REGS inreg, outreg; - inreg.h.ah = 0; - inreg.h.al = (unsigned char)screenMode; - - int86(0x10, &inreg, &outreg); -} - -static void FastMemSet(void far* mem, uint16_t value, unsigned int count); -#pragma aux FastMemSet = \ - "rep stosw" \ - modify [di cx] \ - parm[es di][ax][cx]; - -void TextModeDriver::InvertScreen() -{ - int count = 0x1000; - unsigned char far* VRAM = videoBaseAddress; - while (count--) - { - *VRAM ^= 0xff; - VRAM += 2; - } - - invertScreen = !invertScreen; - clearMask = invertScreen ? 0 : 0xffff; -} - -void TextModeDriver::ClearScreen() -{ - uint16_t clearValue = textAttributeMap[FontStyle::Regular] << 8; - FastMemSet(videoBaseAddress, clearValue, 0x1000); - // White out main page - //FastMemSet(CGA_BASE_VRAM_ADDRESS + BYTES_PER_LINE * TITLE_BAR_HEIGHT / 2, 0xff, BYTES_PER_LINE * (SCREEN_HEIGHT - TITLE_BAR_HEIGHT - STATUS_BAR_HEIGHT) / 2); - //FastMemSet(CGA_BASE_VRAM_ADDRESS + 0x2000 + BYTES_PER_LINE * TITLE_BAR_HEIGHT / 2, 0xff, BYTES_PER_LINE * (SCREEN_HEIGHT - TITLE_BAR_HEIGHT - STATUS_BAR_HEIGHT) / 2); - - FastMemSet(videoBaseAddress + (BYTES_PER_LINE * 2), clearValue | 0xc4, 80); - FastMemSet(videoBaseAddress + (BYTES_PER_LINE * 24), 0x0f00, 80); -} - -void TextModeDriver::DrawImage(Image* image, int x, int y) -{ -} - -void TextModeDriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ - if (y < scissorY1 || y >= scissorY2) - return; - - unsigned char far* VRAM = videoBaseAddress; - uint8_t attribute = textAttributeMap[style]; - - VRAM += (y * screenWidth + x) * 2; - - while (*text && x < screenWidth) - { - *VRAM++ = *text; - *VRAM++ = attribute; - text++; - x++; - } -} - -Font* TextModeDriver::GetFont(int fontSize, FontStyle::Type style) -{ - return &TextMode_Font; -} - -void TextModeDriver::HLine(int x, int y, int count) -{ -} - -void TextModeDriver::ClearRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; - - uint8_t* VRAM = videoBaseAddress + (y * screenWidth + x) * 2; - uint16_t clearValue = textAttributeMap[FontStyle::Regular] << 8; - - while (height) - { - FastMemSet(VRAM, clearValue, width); - VRAM += BYTES_PER_LINE; - height--; - } -} - -bool TextModeDriver::ApplyScissor(int& y, int& height) -{ - if (y + height < scissorY1) - return false; - if (y >= scissorY2) - return false; - if (y < scissorY1) - { - height -= (scissorY1 - y); - y = scissorY1; - } - if (y + height >= scissorY2) - { - height = scissorY2 - y; - } - return true; -} - -void TextModeDriver::InvertRect(int x, int y, int width, int height) -{ - if (!ApplyScissor(y, height)) - return; -} - -void TextModeDriver::FillRect(int x, int y, int width, int height) -{ -} - -void TextModeDriver::VLine(int x, int y, int count) -{ -} - -MouseCursorData* TextModeDriver::GetCursorGraphic(MouseCursor::Type type) -{ - return NULL; -} - -int TextModeDriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) -{ - return 1; -} - -int TextModeDriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return 1; -} - -void TextModeDriver::DrawScrollBar(int position, int size) -{ - uint8_t* VRAM = videoBaseAddress; - VRAM += (BYTES_PER_LINE * WINDOW_TOP) + (screenWidth - 1) * 2; - - for (int y = 0; y < WINDOW_HEIGHT; y++) - { - if (y >= position && y <= position + size) - { - *VRAM = '\xdb'; - } - else - { - *VRAM = '\xb1'; - } - VRAM += BYTES_PER_LINE; - } -} - -void TextModeDriver::DrawRect(int x, int y, int width, int height) -{ -} - -void TextModeDriver::DrawButtonRect(int x, int y, int width, int height) -{ -} - -void TextModeDriver::ScrollWindow(int amount) -{ -} - -void TextModeDriver::ClearWindow() -{ - uint8_t* VRAM = videoBaseAddress; - VRAM += (BYTES_PER_LINE * WINDOW_TOP); - uint16_t clearValue = textAttributeMap[FontStyle::Regular] << 8; - - for (int y = 0; y < WINDOW_HEIGHT; y++) - { - FastMemSet(VRAM, clearValue, BYTES_PER_LINE - 2); - VRAM += BYTES_PER_LINE; - } -} - -void TextModeDriver::SetScissorRegion(int y1, int y2) -{ - scissorY1 = y1; - scissorY2 = y2; -} - -void TextModeDriver::ClearScissorRegion() -{ - scissorY1 = 0; - scissorY2 = screenHeight; -} - -void TextModeDriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = screenWidth - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = screenHeight - STATUS_BAR_HEIGHT; - app.statusBar.width = screenWidth; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 0; - app.titleBar.width = screenWidth; - app.titleBar.height = TITLE_BAR_HEIGHT; -} - -void TextModeDriver::ScaleImageDimensions(int& width, int& height) -{ - width >>= 3; - height >>= 4; -} - -#define MDA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) - -uint8_t MDATextModeDriver::mdaAttributeMap[16] = -{ - 0x7, 0xa, 0xa, 0xa, 0x1, 0x9, 0x9, 0x9, - 0x7, 0xa, 0xa, 0xa, 0x1, 0x9, 0x9, 0x9, -}; - -MDATextModeDriver::MDATextModeDriver() : TextModeDriver(7, MDA_BASE_VRAM_ADDRESS, mdaAttributeMap) -{ -} - -#define CGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) - -uint8_t CGATextModeDriver::cgaAttributeMap[16] = -{ - 0x07, 0x0f, 0x0e, 0x07, 0x09, 0x07, 0x07, 0x07, - 0x07, 0x0f, 0x0e, 0x07, 0x09, 0x07, 0x07, 0x07, -}; - -CGATextModeDriver::CGATextModeDriver() : TextModeDriver(3, CGA_BASE_VRAM_ADDRESS, cgaAttributeMap) -{ -} diff --git a/src/DOS/TextMode.h b/src/DOS/TextMode.h deleted file mode 100644 index e5c6dbe..0000000 --- a/src/DOS/TextMode.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#pragma once - -#include "../Platform.h" - -struct Image; - -class TextModeDriver : public VideoDriver -{ -public: - TextModeDriver(int screenMode, uint8_t* inVideoBaseAddress, uint8_t* inTextAttributeMap); - - virtual void Init(); - virtual void Shutdown(); - virtual void ClearScreen(); - virtual void InvertScreen(); - - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app); - - - virtual void ClearWindow(); - virtual void ScrollWindow(int delta); - virtual void SetScissorRegion(int y1, int y2); - virtual void ClearScissorRegion(); - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void DrawRect(int x, int y, int width, int height); - virtual void DrawButtonRect(int x, int y, int width, int height); - virtual void DrawImage(Image* image, int x, int y); - virtual void ClearRect(int x, int y, int width, int height); - virtual void FillRect(int x, int y, int width, int height); - virtual void InvertRect(int x, int y, int width, int height); - - virtual void HLine(int x, int y, int count); - virtual void VLine(int x, int y, int count); - virtual void ScaleImageDimensions(int& width, int& height); - - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - - virtual void DrawScrollBar(int position, int size); - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - bool ApplyScissor(int& y, int& height); - - bool invertScreen; - uint16_t clearMask; - int startingScreenMode; - int screenMode; - int scissorX1, scissorY1, scissorX2, scissorY2; - uint8_t* videoBaseAddress; - uint8_t* textAttributeMap; -}; - -class MDATextModeDriver : public TextModeDriver -{ -public: - MDATextModeDriver(); -private: - static uint8_t mdaAttributeMap[16]; -}; - -class CGATextModeDriver : public TextModeDriver -{ -public: - CGATextModeDriver(); -private: - static uint8_t cgaAttributeMap[16]; -}; diff --git a/src/DataPack.cpp b/src/DataPack.cpp new file mode 100644 index 0000000..a202d36 --- /dev/null +++ b/src/DataPack.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include "DataPack.h" +#include "Platform.h" +#include + +DataPack Assets; + +#pragma warning(disable:4996) + +const char* DataPack::datapackFilenames[] = +{ + "CGA.DAT", + "EGA.DAT", + "DEFAULT.DAT", + "LOWRES.DAT" +}; + +bool DataPack::LoadPreset(DataPack::Preset preset) +{ + return Load(datapackFilenames[preset]); +} + + +bool DataPack::Load(const char* path) +{ + FILE* fs = fopen(path, "rb"); + + if (!fs) + { + Platform::FatalError("Could not load %s", path); + return false; + } + + DataPackHeader header; + + fread(&header.numEntries, sizeof(uint16_t), 1, fs); + header.entries = new DataPackEntry[header.numEntries]; + + if (!header.entries) + { + Platform::FatalError("Could not allocate memory for data pack header from %s", path); + return false; + } + + fread(header.entries, sizeof(DataPackEntry), header.numEntries, fs); + + pointerCursor = (MouseCursorData*)LoadAsset(fs, header, "CMOUSE"); + linkCursor = (MouseCursorData*)LoadAsset(fs, header, "CLINK"); + textSelectCursor = (MouseCursorData*)LoadAsset(fs, header, "CTEXT"); + + imageIcon = LoadImageAsset(fs, header, "IIMG"); + brokenImageIcon = LoadImageAsset(fs, header, "IBROKEN"); + checkbox = LoadImageAsset(fs, header, "ICHECK1"); + checkboxTicked = LoadImageAsset(fs, header, "ICHECK2"); + radio = LoadImageAsset(fs, header, "IRADIO1"); + radioSelected = LoadImageAsset(fs, header, "IRADIO2"); + downIcon = LoadImageAsset(fs, header, "IDOWN"); + + fonts[0] = (Font*)LoadAsset(fs, header, "FHELV1"); + fonts[1] = (Font*)LoadAsset(fs, header, "FHELV2"); + fonts[2] = (Font*)LoadAsset(fs, header, "FHELV3"); + monoFonts[0] = (Font*)LoadAsset(fs, header, "FCOUR1"); + monoFonts[1] = (Font*)LoadAsset(fs, header, "FCOUR2"); + monoFonts[2] = (Font*)LoadAsset(fs, header, "FCOUR3"); + + delete[] header.entries; + fclose(fs); + + return true; +} + +int DataPack::FontSizeToIndex(int fontSize) +{ + switch (fontSize) + { + case 0: + return 0; + case 2: + case 3: + case 4: + return 2; + default: + return 1; + } +} + +Font* DataPack::GetFont(int fontSize, FontStyle::Type fontStyle) +{ + if (fontStyle & FontStyle::Monospace) + { + return monoFonts[FontSizeToIndex(fontSize)]; + } + else + { + return fonts[FontSizeToIndex(fontSize)]; + } +} + +MouseCursorData* DataPack::GetMouseCursorData(MouseCursor::Type type) +{ + switch (type) + { + case MouseCursor::Hand: + return linkCursor; + case MouseCursor::Pointer: + return pointerCursor; + case MouseCursor::TextSelect: + return textSelectCursor; + } + return NULL; +} + +Image* DataPack::LoadImageAsset(FILE* fs, DataPackHeader& header, const char* entryName) +{ + void* asset = LoadAsset(fs, header, entryName); + if (asset) + { + Image* image = new Image(); + if (!image) + { + Platform::FatalError("Could not allocate memory for data pack image %s", entryName); + } + memcpy(image, asset, sizeof(ImageMetadata)); + uint8_t* data = ((uint8_t*) asset) + sizeof(ImageMetadata); + MemBlockHandle* lines = new MemBlockHandle[image->height]; + if (!lines) + { + Platform::FatalError("Could not allocate memory for data pack image %s", entryName); + } + for (int y = 0; y < image->height; y++) + { + lines[y] = MemBlockHandle(data + y * image->pitch); + } + image->lines = MemBlockHandle(lines); + + return image; + } + return nullptr; +} + +void* DataPack::LoadAsset(FILE* fs, DataPackHeader& header, const char* entryName, void* buffer) +{ + DataPackEntry* entry = NULL; + + for (int n = 0; n < header.numEntries; n++) + { + if (!stricmp(header.entries[n].name, entryName)) + { + entry = &header.entries[n]; + break; + } + } + + if (!entry) + { + return NULL; + } + + fseek(fs, entry->offset, SEEK_SET); + int32_t length = entry[1].offset - entry[0].offset; + + if (!buffer) + { + buffer = malloc(length); + } + if (!buffer) + { + Platform::FatalError("Could not allocate memory for data pack asset %s", entryName); + } + + fread(buffer, length, 1, fs); + + return buffer; +} + diff --git a/src/DataPack.h b/src/DataPack.h index a86358a..db03429 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -1,24 +1,92 @@ -#pragma once +#ifndef _DATAPACK_H_ +#define _DATAPACK_H_ -#include "Image.h" -#include "Font.h" +#include +#include #include "Cursor.h" +#include "Image/Image.h" +#include "Font.h" + +/* + +Asset data pack format + +Header: (all offsets relative to end of the header) +uint16 smallFontOffset +uint16 mediumFontOffset +uint16 largeFontOffset +uint16 smallMonoFontOffset +uint16 mediumMonoFontOffset +uint16 largeMonoFontOffset +uint16 pointerCursorOffset +uint16 linkCursorOffset +uint16 textSelectCursorOffset + +Mouse cursor: +uint16 hotspotX +uint16 hotspotY +uint16 cursorData[32] + +Font data: +uint8 glyphWidth[256 - 32] +uint8 glyphWidthBytes +uint8 glyphHeight +uint8 glyphDataStride +uint8 glyphData[variable] + +*/ + +#define NUM_FONT_SIZES 3 + +struct DataPackEntry +{ + char name[8]; + uint32_t offset; +}; + +struct DataPackHeader +{ + uint16_t numEntries; + DataPackEntry* entries; +}; struct DataPack { - MouseCursorData cursor; - MouseCursorData hand; - MouseCursorData select; - Image imageIcon; - Font small; - Font medium; - Font large; - - void Fixup() + enum Preset { - imageIcon.data += (uint8_t*)(this); - small.glyphData += (uint8_t*)(this); - medium.glyphData += (uint8_t*)(this); - large.glyphData += (uint8_t*)(this); - } + CGA, + EGA, + Default, + Lowres + }; + + MouseCursorData* pointerCursor; + MouseCursorData* linkCursor; + MouseCursorData* textSelectCursor; + Image* imageIcon; + Image* brokenImageIcon; + + Image* checkbox; + Image* checkboxTicked; + Image* radio; + Image* radioSelected; + Image* downIcon; + + Font* fonts[NUM_FONT_SIZES]; + Font* monoFonts[NUM_FONT_SIZES]; + + bool LoadPreset(Preset preset); + bool Load(const char* path); + Font* GetFont(int fontSize, FontStyle::Type fontStyle); + MouseCursorData* GetMouseCursorData(MouseCursor::Type type); + +private: + void* LoadAsset(FILE* fs, DataPackHeader& header, const char* entryName, void* buffer = NULL); + Image* LoadImageAsset(FILE* fs, DataPackHeader& header, const char* entryName); + int FontSizeToIndex(int fontSize); + static const char* datapackFilenames[]; }; + +extern DataPack Assets; + +#endif diff --git a/src/Defines.h b/src/Defines.h new file mode 100644 index 0000000..5ab1c1f --- /dev/null +++ b/src/Defines.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef __DOS__ +// Define modern C++ keywords that aren't supported by OpenWatcom +#define override + +#ifndef nullptr +#ifdef __cplusplus +#if !defined(_M_I86) || defined(__SMALL__) || defined(__MEDIUM__) +#define nullptr 0 +#else +#define nullptr 0L +#endif +#else +#define nullptr ((void *)0) +#endif +#endif + +#endif diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp new file mode 100644 index 0000000..c540a53 --- /dev/null +++ b/src/Draw/Surf1bpp.cpp @@ -0,0 +1,642 @@ +#include +#include "Surf1bpp.h" +#include "../Font.h" +#include "../Image/Image.h" +#include "../Memory/MemBlock.h" +#include "../Platform.h" +#include "../App.h" + +DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; + format = DrawSurface::Format_1BPP; +} + +void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (y < context.clipTop || y >= context.clipBottom) + { + return; + } + if (x < context.clipLeft) + { + count -= (context.clipLeft - x); + x = context.clipLeft; + } + if (x + count >= context.clipRight) + { + count = context.clipRight - x; + } + if (count < 0) + { + return; + } + + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + + uint8_t data = *VRAMptr; + + if (App::config.invertScreen) + colour = !colour; + + if (colour) + { + uint8_t mask = (0x80 >> (x & 7)); + + while (count--) + { + data |= mask; + x++; + mask >>= 1; + if (!mask) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ = 0xff; + count -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + else + { + uint8_t mask = ~(0x80 >> (x & 7)); + + while (count--) + { + data &= mask; + x++; + mask = (mask >> 1) | 0x80; + if ((x & 7) == 0) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ = 0; + count -= 8; + } + mask = ~0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } +} + +void DrawSurface_1BPP::VLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x >= context.clipRight || x < context.clipLeft) + { + return; + } + if (y < context.clipTop) + { + count -= (context.clipTop - y); + y = context.clipTop; + } + if (y >= context.clipBottom) + { + return; + } + if (y + count > context.clipBottom) + { + count = context.clipBottom - y; + } + if (count <= 0) + { + return; + } + + uint8_t mask = (0x80 >> (x & 7)); + int index = x >> 3; + + if (App::config.invertScreen) + colour = !colour; + + if (colour) + { + while (count--) + { + (lines[y])[index] |= mask; + y++; + } + } + else + { + mask ^= 0xff; + while (count--) + { + (lines[y])[index] &= mask; + y++; + } + } +} + +void DrawSurface_1BPP::FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + if (App::config.invertScreen) + colour = !colour; + + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + int count = width; + int workX = x; + uint8_t data = *VRAMptr; + + if (colour) + { + uint8_t mask = (0x80 >> (x & 7)); + + while (count--) + { + data |= mask; + mask >>= 1; + if (!mask) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ = 0xff; + count -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + else + { + uint8_t mask = ~(0x80 >> (x & 7)); + + while (count--) + { + data &= mask; + workX++; + mask = (mask >> 1) | 0x80; + if ((workX & 7) == 0) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ = 0; + count -= 8; + } + mask = ~0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + } + + height--; + y++; + } +} + +void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + int startX = x; + uint8_t glyphHeight = font->glyphHeight; + + if (x >= context.clipRight) + { + return; + } + if (y >= context.clipBottom) + { + return; + } + if (y + glyphHeight > context.clipBottom) + { + glyphHeight = (uint8_t)(context.clipBottom - y); + } + if (y + glyphHeight < context.clipTop) + { + return; + } + + uint8_t firstLine = 0; + if (y < context.clipTop) + { + firstLine += context.clipTop - y; + y += firstLine; + } + + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + + if (App::config.invertScreen) + colour = !colour; + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int index = c - 32; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; + + if (glyphWidth == 0) + { + continue; + } + + glyphWidth += bold; + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; + + glyphData += (firstLine * glyphWidthBytes); + + int outY = y; + uint8_t* VRAMptr = lines[y] + (x >> 3); + + if (!colour) + { + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + uint8_t writeOffset = (uint8_t)(x) & 0x7; + + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + writeOffset++; + } + + uint8_t boldCarry = 0; + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + if (bold) + { + if (boldCarry) + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1) | 0x80; + } + else + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1); + } + } + + VRAMptr[i] &= ~(glyphPixels >> writeOffset); + VRAMptr[i + 1] &= ~(glyphPixels << (8 - writeOffset)); + } + + outY++; + VRAMptr = lines[outY] + (x >> 3); + } + } + else + { + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + uint8_t writeOffset = (uint8_t)(x) & 0x7; + + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + writeOffset++; + } + + uint8_t boldCarry = 0; + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + if (bold) + { + if (boldCarry) + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1) | 0x80; + } + else + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1); + } + } + + VRAMptr[i] |= (glyphPixels >> writeOffset); + VRAMptr[i + 1] |= (glyphPixels << (8 - writeOffset)); + } + + outY++; + VRAMptr = lines[outY] + (x >> 3); + } + } + + x += glyphWidth; + + //if (x >= context.clipRight) + //{ + // break; + //} + } + + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + { + HLine(context, startX, y - firstLine + font->glyphHeight - 1 - context.drawOffsetY, x - startX - context.drawOffsetX, colour); + } + +} + +void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + if (!image->lines.IsAllocated() || image->bpp != 1) + return; + + x += context.drawOffsetX; + y += context.drawOffsetY; + + int srcWidth = image->width; + int srcHeight = image->height; + int startX = 0; + int srcX = 0; + int srcY = 0; + + // Calculate the destination width and height to copy, considering clipping region + int destWidth = srcWidth; + int destHeight = srcHeight; + + if (x < context.clipLeft) + { + srcX += (context.clipLeft - x); + destWidth -= (context.clipLeft - x); + x = context.clipLeft; + } + + if (x + destWidth > context.clipRight) + { + destWidth = context.clipRight - x; + } + + if (y < context.clipTop) + { + srcY += (context.clipTop - y); + destHeight -= (context.clipTop - y); + y = context.clipTop; + } + + if (y + destHeight > context.clipBottom) + { + destHeight = context.clipBottom - y; + } + + if (destWidth <= 0 || destHeight <= 0) + { + return; // Nothing to draw if fully outside the clipping region. + } + + uint8_t invertMask = App::config.invertScreen ? 0xff : 0; + + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[j + srcY]; + uint8_t* src = imageLine.Get() + (srcX >> 3); + uint8_t* dest = lines[y + j] + (x >> 3); + uint8_t srcMask = 0x80 >> (srcX & 7); + uint8_t destMask = 0x80 >> (x & 7); + uint8_t srcBuffer = (*src++) ^ invertMask; + uint8_t destBuffer = *dest; + + for (int i = 0; i < destWidth; i++) + { + if (srcBuffer & srcMask) + { + destBuffer |= destMask; + } + else + { + destBuffer &= ~destMask; + } + srcMask >>= 1; + if (!srcMask) + { + srcMask = 0x80; + srcBuffer = (*src++) ^ invertMask; + } + destMask >>= 1; + if (!destMask) + { + *dest++ = destBuffer; + destBuffer = *dest; + destMask = 0x80; + } + } + *dest = destBuffer; + } +} + + +void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, int height) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + int count = width; + uint8_t data = *VRAMptr; + uint8_t mask = (0x80 >> (x & 7)); + + while (count--) + { + data ^= mask; + //mask = (mask >> 1) | 0x80; + mask >>= 1; + if (!mask) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ ^= 0xff; + count -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + + height--; + y++; + } +} + +void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) +{ + uint16_t inverseMask = App::config.invertScreen ? 0xffff : 0; + const uint16_t widgetEdge = 0x0660 ^ inverseMask; + const uint16_t widgetInner = 0xfa5f ^ inverseMask; + const uint16_t grab = 0x0a50 ^ inverseMask; + const uint16_t edge = 0 ^ inverseMask; + const uint16_t inner = 0xfe7f ^ inverseMask; + + x += context.drawOffsetX; + y += context.drawOffsetY; + int startY = y; + + x >>= 3; + const int grabSize = 7; + const int minWidgetSize = grabSize + 4; + const int widgetPaddingSize = size - minWidgetSize; + int topPaddingSize = widgetPaddingSize >> 1; + int bottomPaddingSize = widgetPaddingSize - topPaddingSize; + int bottomSpacing = height - position - size; + + while (position--) + { + *(uint16_t*)(&lines[y++][x]) = inner; + } + + *(uint16_t*)(&lines[y++][x]) = inner; + *(uint16_t*)(&lines[y++][x]) = widgetEdge; + + + while (topPaddingSize--) + { + *(uint16_t*)(&lines[y++][x]) = widgetInner; + } + + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + *(uint16_t*)(&lines[y++][x]) = grab; + *(uint16_t*)(&lines[y++][x]) = widgetInner; + + while (bottomPaddingSize--) + { + *(uint16_t*)(&lines[y++][x]) = widgetInner; + } + + *(uint16_t*)(&lines[y++][x]) = widgetEdge; + *(uint16_t*)(&lines[y++][x]) = inner; + + while (bottomSpacing--) + { + *(uint16_t*)(&lines[y++][x]) = inner; + } +} + +void DrawSurface_1BPP::Clear() +{ + int widthBytes = width >> 3; + uint8_t clear = App::config.invertScreen ? 0 : 0xff; + + for (int y = 0; y < height; y++) + { + memset(lines[y], clear, widthBytes); + } +} + +void DrawSurface_1BPP::ScrollScreen(int top, int bottom, int width, int amount) +{ + width >>= 3; + + if (amount > 0) + { + for (int y = top; y < bottom; y++) + { + memcpy(lines[y], lines[y + amount], width); + } + } + else if (amount < 0) + { + for (int y = bottom - 1; y >= top; y--) + { + memcpy(lines[y], lines[y + amount], width); + } + } +} + diff --git a/src/Draw/Surf1bpp.h b/src/Draw/Surf1bpp.h new file mode 100644 index 0000000..ab60f1b --- /dev/null +++ b/src/Draw/Surf1bpp.h @@ -0,0 +1,23 @@ +#ifndef _SURF1BPP_H_ +#define _SURF1BPP_H_ + +#include +#include "Surface.h" + +class DrawSurface_1BPP : public DrawSurface +{ +public: + DrawSurface_1BPP(int inWidth, int inHeight); + + virtual void Clear(); + virtual void HLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void VLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour); + virtual void DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style = FontStyle::Regular); + virtual void BlitImage(DrawContext& context, Image* image, int x, int y); + virtual void InvertRect(DrawContext& context, int x, int y, int width, int height); + virtual void VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size); + virtual void ScrollScreen(int top, int bottom, int width, int amount); +}; + +#endif diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp new file mode 100644 index 0000000..1374981 --- /dev/null +++ b/src/Draw/Surf2bpp.cpp @@ -0,0 +1,578 @@ +#include +#include "Surf2BPP.h" +#include "../Font.h" +#include "../Image/Image.h" +#include "../Memory/MemBlock.h" +#include "../Colour.h" + +static uint8_t bitmaskTable[] = +{ + 0xc0, 0x30, 0x0c, 0x03 +}; + +DrawSurface_2BPP::DrawSurface_2BPP(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; + format = DrawSurface::Format_2BPP; +} + +void DrawSurface_2BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (y < context.clipTop || y >= context.clipBottom) + { + return; + } + if (x < context.clipLeft) + { + count -= (context.clipLeft - x); + x = context.clipLeft; + } + if (x + count >= context.clipRight) + { + count = context.clipRight - x; + } + if (count < 0) + { + return; + } + + colour |= (colour << 4); + + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 2); + + uint8_t data = *VRAMptr; + uint8_t mask = bitmaskTable[x & 3]; + + while (count--) + { + data = (data & (~mask)) | (colour & mask); + x++; + mask >>= 2; + if (!mask) + { + *VRAMptr++ = data; + while (count > 4) + { + *VRAMptr++ = colour; + count -= 4; + } + mask = 0xc0; + data = *VRAMptr; + } + } + + *VRAMptr = data; +} + +void DrawSurface_2BPP::VLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x >= context.clipRight || x < context.clipLeft) + { + return; + } + if (y < context.clipTop) + { + count -= (context.clipTop - y); + y = context.clipTop; + } + if (y >= context.clipBottom) + { + return; + } + if (y + count > context.clipBottom) + { + count = context.clipBottom - y; + } + if (count <= 0) + { + return; + } + + colour |= (colour << 4); + + uint8_t mask = bitmaskTable[x & 3]; + uint8_t andMask = ~mask; + uint8_t orMask = mask & colour; + int index = x >> 2; + + while (count--) + { + (lines[y])[index] = ((lines[y])[index] & andMask) | orMask; + y++; + } +} + +void DrawSurface_2BPP::FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + colour |= (colour << 4); + + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 2); + + uint8_t data = *VRAMptr; + uint8_t mask = bitmaskTable[x & 3]; + int count = width; + + while (count--) + { + data = (data & (~mask)) | (colour & mask); + mask >>= 2; + if (!mask) + { + *VRAMptr++ = data; + while (count > 4) + { + *VRAMptr++ = colour; + count -= 4; + } + mask = 0xc0; + data = *VRAMptr; + } + } + + *VRAMptr = data; + + height--; + y++; + } +} + +void DrawSurface_2BPP::DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + int startX = x; + uint8_t glyphHeight = font->glyphHeight; + + if (x >= context.clipRight) + { + return; + } + if (y >= context.clipBottom) + { + return; + } + if (y + glyphHeight > context.clipBottom) + { + glyphHeight = (uint8_t)(context.clipBottom - y); + } + if (y + glyphHeight < context.clipTop) + { + return; + } + + colour |= (colour << 4); + + uint8_t firstLine = 0; + if (y < context.clipTop) + { + firstLine += context.clipTop - y; + y += firstLine; + } + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int index = c - 32; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; + + if (glyphWidth == 0) + { + continue; + } + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; + + glyphData += (firstLine * glyphWidthBytes); + + int outY = y; + + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + uint8_t* VRAMptr = lines[outY] + (x >> 2); + uint8_t writeData = *VRAMptr; + uint8_t writeMask = bitmaskTable[x & 3]; + uint8_t writeOffset = (uint8_t)(x) & 0x7; + + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + writeOffset++; + } + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + for (uint8_t k = 0; k < 8; k++) + { + if (glyphPixels & (0x80 >> k)) + { + writeData = (writeData & (~writeMask)) | (writeMask & colour); + } + + writeMask >>= 2; + if (!writeMask) + { + *VRAMptr++ = writeData; + writeData = *VRAMptr; + writeMask = 0xc0; + } + } + } + + *VRAMptr = writeData; + + outY++; + } + + x += glyphWidth; + + //if (x >= context.clipRight) + //{ + // break; + //} + } + + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + { + HLine(context, startX, y - firstLine + font->glyphHeight - 1 - context.drawOffsetY, x - startX - context.drawOffsetX, colour); + } +} + +void DrawSurface_2BPP::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + if (!image->lines.IsAllocated()) + return; + + x += context.drawOffsetX; + y += context.drawOffsetY; + + int srcWidth = image->width; + int srcHeight = image->height; + int startX = 0; + int srcX = 0; + int srcY = 0; + + // Calculate the destination width and height to copy, considering clipping region + int destWidth = srcWidth; + int destHeight = srcHeight; + + if (x < context.clipLeft) + { + srcX += (context.clipLeft - x); + destWidth -= (context.clipLeft - x); + x = context.clipLeft; + } + + if (x + destWidth > context.clipRight) + { + destWidth = context.clipRight - x; + } + + if (y < context.clipTop) + { + srcY += (context.clipTop - y); + destHeight -= (context.clipTop - y); + y = context.clipTop; + } + + if (y + destHeight > context.clipBottom) + { + destHeight = context.clipBottom - y; + } + + if (destWidth <= 0 || destHeight <= 0) + { + return; // Nothing to draw if fully outside the clipping region. + } + + if (image->bpp == 8) + { + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[j + srcY]; + uint8_t* src = imageLine.Get() + srcX; + uint8_t* dest = lines[y + j] + (x >> 2); + uint8_t destMask = bitmaskTable[x & 3]; + uint8_t destBuffer = *dest; + + for (int i = 0; i < destWidth; i++) + { + uint8_t srcBuffer = *src; + if (srcBuffer != TRANSPARENT_COLOUR_VALUE) + { + srcBuffer |= (srcBuffer << 4); + destBuffer = (destBuffer & (~destMask)) | ((srcBuffer)&destMask); + } + src++; + destMask >>= 2; + if (!destMask) + { + *dest++ = destBuffer; + destBuffer = *dest; + destMask = 0xc0; + } + } + *dest = destBuffer; + } + } + else if (image->bpp == 1) + { + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[j + srcY]; + uint8_t* src = imageLine.Get() + (srcX >> 3); + uint8_t* dest = lines[y + j] + (x >> 2); + uint8_t srcMask = 0x80 >> (srcX & 7); + uint8_t destMask = bitmaskTable[x & 3]; + uint8_t destBuffer = *dest; + uint8_t srcBuffer = *src++; + + for (int i = 0; i < destWidth; i++) + { + if ((srcBuffer & srcMask)) + { + destBuffer |= destMask; + } + else + { + destBuffer &= ~destMask; + } + srcMask >>= 1; + if (!srcMask) + { + srcMask = 0x80; + srcBuffer = *src++; + } + + destMask >>= 2; + if (!destMask) + { + *dest++ = destBuffer; + destBuffer = *dest; + destMask = 0xc0; + } + } + *dest = destBuffer; + } + } +} + + +void DrawSurface_2BPP::InvertRect(DrawContext& context, int x, int y, int width, int height) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 2); + int count = width; + uint8_t data = *VRAMptr; + uint8_t mask = bitmaskTable[x & 3]; + + while (count--) + { + data ^= mask; + mask >>= 2; + if (!mask) + { + *VRAMptr++ = data; + while (count > 4) + { + *VRAMptr++ ^= 0xff; + count -= 4; + } + mask = 0xc0; + data = *VRAMptr; + } + } + + *VRAMptr = data; + + height--; + y++; + } +} + +void DrawSurface_2BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) +{ +#if 1 + const uint16_t edge = 0; + const uint16_t inner1 = 0xff3f; + const uint16_t inner2 = 0xfcff; + const uint16_t widgetEdge1 = 0x003f; + const uint16_t widgetEdge2 = 0xfc00; + const uint16_t widgetInner1 = 0xff3c; + const uint16_t widgetInner2 = 0x3cff; + const uint16_t grab1 = 0xc03c; + const uint16_t grab2 = 0x3c03; + + x += context.drawOffsetX; + y += context.drawOffsetY; + int startY = y; + + x >>= 2; + const int grabSize = 7; + const int minWidgetSize = grabSize + 4; + const int widgetPaddingSize = size - minWidgetSize; + int topPaddingSize = widgetPaddingSize >> 1; + int bottomPaddingSize = widgetPaddingSize - topPaddingSize; + int bottomSpacing = height - position - size; + + while (position--) + { + ((uint16_t*)(&lines[y][x]))[0] = inner1; + ((uint16_t*)(&lines[y++][x]))[1] = inner2; + } + + ((uint16_t*)(&lines[y][x]))[0] = inner1; + ((uint16_t*)(&lines[y++][x]))[1] = inner2; + ((uint16_t*)(&lines[y][x]))[0] = widgetEdge1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetEdge2; + + + while (topPaddingSize--) + { + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; + } + + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; + ((uint16_t*)(&lines[y][x]))[0] = grab1; + ((uint16_t*)(&lines[y++][x]))[1] = grab2; + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; + ((uint16_t*)(&lines[y][x]))[0] = grab1; + ((uint16_t*)(&lines[y++][x]))[1] = grab2; + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; + ((uint16_t*)(&lines[y][x]))[0] = grab1; + ((uint16_t*)(&lines[y++][x]))[1] = grab2; + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; + + while (bottomPaddingSize--) + { + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; + } + + ((uint16_t*)(&lines[y][x]))[0] = widgetEdge1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetEdge2; + ((uint16_t*)(&lines[y][x]))[0] = inner1; + ((uint16_t*)(&lines[y++][x]))[1] = inner2; + + while (bottomSpacing--) + { + ((uint16_t*)(&lines[y][x]))[0] = inner1; + ((uint16_t*)(&lines[y++][x]))[1] = inner2; + } +#endif +} + +void DrawSurface_2BPP::Clear() +{ + int widthBytes = width >> 2; + for (int y = 0; y < height; y++) + { + memset(lines[y], 0xff, widthBytes); + } +} + + +void DrawSurface_2BPP::ScrollScreen(int top, int bottom, int width, int amount) +{ + width >>= 2; + + if (amount > 0) + { + for (int y = top; y < bottom; y++) + { + memcpy(lines[y], lines[y + amount], width); + } + } + else if (amount < 0) + { + for (int y = bottom - 1; y >= top; y--) + { + memcpy(lines[y], lines[y + amount], width); + } + } +} diff --git a/src/Draw/Surf2bpp.h b/src/Draw/Surf2bpp.h new file mode 100644 index 0000000..18fd439 --- /dev/null +++ b/src/Draw/Surf2bpp.h @@ -0,0 +1,23 @@ +#ifndef _SURF2BPP_H_ +#define _SURF2BPP_H_ + +#include +#include "Surface.h" + +class DrawSurface_2BPP : public DrawSurface +{ +public: + DrawSurface_2BPP(int inWidth, int inHeight); + + virtual void Clear(); + virtual void HLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void VLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour); + virtual void DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style = FontStyle::Regular); + virtual void BlitImage(DrawContext& context, Image* image, int x, int y); + virtual void InvertRect(DrawContext& context, int x, int y, int width, int height); + virtual void VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size); + virtual void ScrollScreen(int top, int bottom, int width, int amount); +}; + +#endif diff --git a/src/Draw/Surf4bpp.h b/src/Draw/Surf4bpp.h new file mode 100644 index 0000000..bc9bf9b --- /dev/null +++ b/src/Draw/Surf4bpp.h @@ -0,0 +1,23 @@ +#ifndef _SURF4BPP_H_ +#define _SURF4BPP_H_ + +#include +#include "Surface.h" + +class DrawSurface_4BPP : public DrawSurface +{ +public: + DrawSurface_4BPP(int inWidth, int inHeight); + + virtual void Clear(); + virtual void HLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void VLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour); + virtual void DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style = FontStyle::Regular); + virtual void BlitImage(DrawContext& context, Image* image, int x, int y); + virtual void InvertRect(DrawContext& context, int x, int y, int width, int height); + virtual void VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size); + virtual void ScrollScreen(int top, int bottom, int width, int amount); +}; + +#endif diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp new file mode 100644 index 0000000..d7c318f --- /dev/null +++ b/src/Draw/Surf8bpp.cpp @@ -0,0 +1,579 @@ +#include +#include "Surf8bpp.h" +#include "../Font.h" +#include "../Image/Image.h" +#include "../Memory/MemBlock.h" +#include "../Colour.h" +#include "../Platform.h" + +DrawSurface_8BPP::DrawSurface_8BPP(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; + format = DrawSurface::Format_8BPP; +} + +void DrawSurface_8BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (y < context.clipTop || y >= context.clipBottom) + { + return; + } + if (x < context.clipLeft) + { + count -= (context.clipLeft - x); + x = context.clipLeft; + } + if (x + count >= context.clipRight) + { + count = context.clipRight - x; + } + if (count < 0) + { + return; + } + + uint8_t* VRAMptr = lines[y]; + VRAMptr += x; + + while (count--) + { + *VRAMptr++ = colour; + } +} + +void DrawSurface_8BPP::VLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x >= context.clipRight || x < context.clipLeft) + { + return; + } + if (y < context.clipTop) + { + count -= (context.clipTop - y); + y = context.clipTop; + } + if (y >= context.clipBottom) + { + return; + } + if (y + count > context.clipBottom) + { + count = context.clipBottom - y; + } + if (count <= 0) + { + return; + } + + while (count--) + { + (lines[y])[x] = colour; + y++; + } +} + +void DrawSurface_8BPP::FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += x; + int count = width; + + while (count--) + { + *VRAMptr++ = colour; + } + + height--; + y++; + } +} + +void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + int startX = x; + uint8_t glyphHeight = font->glyphHeight; + + if (x >= context.clipRight) + { + return; + } + if (y >= context.clipBottom) + { + return; + } + if (y + glyphHeight > context.clipBottom) + { + glyphHeight = (uint8_t)(context.clipBottom - y); + } + if (y + glyphHeight < context.clipTop) + { + return; + } + + uint8_t firstLine = 0; + if (y < context.clipTop) + { + firstLine += context.clipTop - y; + y += firstLine; + } + + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int index = c - 32; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; + + if (glyphWidth == 0) + { + continue; + } + + glyphWidth += bold; + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; + + glyphData += (firstLine * glyphWidthBytes); + + int outY = y; + uint8_t* VRAMptr = lines[y] + x; + + for (uint8_t j = firstLine; j < glyphHeight; j++) + { + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + { + VRAMptr++; + } + + uint8_t boldCarry = 0; + + for (uint8_t i = 0; i < glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + if (bold) + { + if (boldCarry) + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1) | 0x80; + } + else + { + boldCarry = glyphPixels & 1; + glyphPixels |= (glyphPixels >> 1); + } + } + + for (uint8_t k = 0; k < 8; k++) + { + if (glyphPixels & (0x80 >> k)) + { + *VRAMptr = colour; + } + VRAMptr++; + } + } + + outY++; + VRAMptr = lines[outY] + x; + } + + x += glyphWidth; + + //if (x >= context.clipRight) + //{ + // break; + //} + } + + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + { + HLine(context, startX, y - firstLine + font->glyphHeight - 1 - context.drawOffsetY, x - startX - context.drawOffsetX, colour); + } + +} + +void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + if (!image->lines.IsAllocated()) + return; + + x += context.drawOffsetX; + y += context.drawOffsetY; + + int srcWidth = image->width; + int srcHeight = image->height; + int srcPitch = image->pitch; + int srcX = 0; + int srcY = 0; + + // Calculate the destination width and height to copy, considering clipping region + int destWidth = srcWidth; + int destHeight = srcHeight; + + if (x < context.clipLeft) + { + srcX += (context.clipLeft - x); + destWidth -= (context.clipLeft - x); + x = context.clipLeft; + } + + if (x + destWidth > context.clipRight) + { + destWidth = context.clipRight - x; + } + + if (y < context.clipTop) + { + srcY += (context.clipTop - y); + destHeight -= (context.clipTop - y); + y = context.clipTop; + } + + if (y + destHeight > context.clipBottom) + { + destHeight = context.clipBottom - y; + } + + if (destWidth <= 0 || destHeight <= 0) + { + return; // Nothing to draw if fully outside the clipping region. + } + + if (image->bpp == 8) + { + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[srcY + j]; + uint8_t* src = imageLine.Get() + srcX; + uint8_t* destRow = lines[y + j] + x; + + for (int i = 0; i < destWidth; i++) + { + uint8_t pixel = *src++; + + if (pixel != TRANSPARENT_COLOUR_VALUE) + { + *destRow = pixel; + } + destRow++; + } + } + } + else if (image->bpp == 1) + { + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[srcY + j]; + uint8_t* src = imageLine.Get() + (srcX >> 3); + uint8_t srcMask = 0x80 >> (srcX & 7); + uint8_t* destRow = lines[y + j] + x; + uint8_t black = 0; + uint8_t white = 0xf; + uint8_t buffer = *src++; + + for (int i = 0; i < destWidth; i++) + { + if (buffer & srcMask) + { + *destRow++ = white; + } + else + { + *destRow++ = black; + } + srcMask >>= 1; + if (!srcMask) + { + srcMask = 0x80; + buffer = *src++; + } + } + } + } + +#if 0 + x += context.drawOffsetX; + y += context.drawOffsetY; + + int srcWidth = image->width; + int srcHeight = image->height; + int srcPitch = image->pitch; + uint8_t* srcData = image->data; + + // Calculate the destination width and height to copy, considering clipping region + int destWidth = srcWidth; + int destHeight = srcHeight; + + if (x < context.clipLeft) + { + srcData += ((context.clipLeft - x) >> 3); + destWidth -= (context.clipLeft - x); + x = context.clipLeft; + } + + if (x + destWidth > context.clipRight) + { + destWidth = context.clipRight - x; + } + + if (y < context.clipTop) + { + srcData += (context.clipTop - y) * srcPitch; + destHeight -= (context.clipTop - y); + y = context.clipTop; + } + + if (y + destHeight > context.clipBottom) + { + destHeight = context.clipBottom - y; + } + + if (destWidth <= 0 || destHeight <= 0) + { + return; // Nothing to draw if fully outside the clipping region. + } + + int srcByteWidth = (srcWidth + 7) >> 3; // Calculate the width of the source image in bytes + int destByteWidth = (destWidth + 7) >> 3; // Calculate the width of the destination image in bytes + + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) + { + uint8_t* srcRow = srcData + (j * srcPitch); + uint8_t* destRow = lines[y + j] + (x >> 3); + int xBits = 7 - (x & 0x7); // Number of bits in the first destination byte to skip + + // Handle the first destination byte separately with proper masking + if (xBits == 0) + { + // If x is on a byte boundary, copy the whole byte from the source + for (int i = 0; i < destByteWidth; i++) + { + destRow[i] = srcRow[i]; + } + } + else + { + // Copy the first destination byte with proper masking + destRow[0] = (destRow[0] & (0xFF << xBits)) | (srcRow[0] >> (8 - xBits)); + + // Copy the remaining bytes + for (int i = 1; i < destByteWidth; i++) + { + destRow[i] = (srcRow[i - 1] << xBits) | (srcRow[i] >> (8 - xBits)); + } + } + + // Handle the case when the destination image width is not a multiple of 8 bits + int lastBit = 7 - ((x + destWidth) & 0x7); + if (lastBit > 0) + { + int mask = 0xFF << lastBit; + destRow[destByteWidth - 1] = (destRow[destByteWidth - 1] & ~mask) | (srcRow[destByteWidth] & mask); + } + } +#endif +} + + +void DrawSurface_8BPP::InvertRect(DrawContext& context, int x, int y, int width, int height) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + + if (x < context.clipLeft) + { + width -= (context.clipLeft - x); + x = context.clipLeft; + } + if (y < context.clipTop) + { + height -= (context.clipTop - y); + y = context.clipTop; + } + if (x + width > context.clipRight) + { + width = context.clipRight - x; + } + if (y + height > context.clipBottom) + { + height = context.clipBottom - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += x; + int count = width; + + while (count--) + { + *VRAMptr++ ^= 0xf; + } + + height--; + y++; + } +} + +static uint8_t edge[16] = +{ + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 +}; + +static uint8_t widgetEdge[16] = +{ + 0x0,0xf,0xf,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xf,0x0 +}; + +static uint8_t inner[16] = +{ + 0x0,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x0 +}; + +static uint8_t widgetInner[16] = +{ + 0x0,0xf,0x0,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x0,0xf,0x0 +}; + +static uint8_t grab[16] = +{ + 0x0,0xf,0x0,0xf,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x0,0xf,0x0 +}; + +void DrawSurface_8BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) +{ + x += context.drawOffsetX; + y += context.drawOffsetY; + int startY = y; + + const int grabSize = 7; + const int minWidgetSize = grabSize + 4; + const int widgetPaddingSize = size - minWidgetSize; + int topPaddingSize = widgetPaddingSize >> 1; + int bottomPaddingSize = widgetPaddingSize - topPaddingSize; + const uint16_t edge = 0; + int bottomSpacing = height - position - size; + + while (position--) + { + memcpy(&lines[y++][x], inner, 16); + } + + memcpy(&lines[y++][x], inner, 16); + memcpy(&lines[y++][x], widgetEdge, 16); + + while (topPaddingSize--) + { + memcpy(&lines[y++][x], widgetInner, 16); + } + + memcpy(&lines[y++][x], widgetInner, 16); + memcpy(&lines[y++][x], grab, 16); + memcpy(&lines[y++][x], widgetInner, 16); + memcpy(&lines[y++][x], grab, 16); + memcpy(&lines[y++][x], widgetInner, 16); + memcpy(&lines[y++][x], grab, 16); + memcpy(&lines[y++][x], widgetInner, 16); + + while (bottomPaddingSize--) + { + memcpy(&lines[y++][x], widgetInner, 16); + } + + memcpy(&lines[y++][x], widgetEdge, 16); + memcpy(&lines[y++][x], inner, 16); + + while (bottomSpacing--) + { + memcpy(&lines[y++][x], inner, 16); + } +} + +void DrawSurface_8BPP::Clear() +{ + int widthBytes = width; + for (int y = 0; y < height; y++) + { + memset(lines[y], Platform::video->colourScheme.pageColour, widthBytes); + } +} + +void DrawSurface_8BPP::ScrollScreen(int top, int bottom, int width, int amount) +{ + if (amount > 0) + { + for (int y = top; y < bottom; y++) + { + memcpy(lines[y], lines[y + amount], width); + } + } + else if (amount < 0) + { + for (int y = bottom - 1; y >= top; y--) + { + memcpy(lines[y], lines[y + amount], width); + } + } +} diff --git a/src/Draw/Surf8bpp.h b/src/Draw/Surf8bpp.h new file mode 100644 index 0000000..8aba796 --- /dev/null +++ b/src/Draw/Surf8bpp.h @@ -0,0 +1,23 @@ +#ifndef _SURF8BPP_H_ +#define _SURF8BPP_H_ + +#include +#include "Surface.h" + +class DrawSurface_8BPP : public DrawSurface +{ +public: + DrawSurface_8BPP(int inWidth, int inHeight); + + virtual void Clear(); + virtual void HLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void VLine(DrawContext& context, int x, int y, int count, uint8_t colour); + virtual void FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour); + virtual void DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style = FontStyle::Regular); + virtual void BlitImage(DrawContext& context, Image* image, int x, int y); + virtual void InvertRect(DrawContext& context, int x, int y, int width, int height); + virtual void VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size); + virtual void ScrollScreen(int top, int bottom, int width, int amount); +}; + +#endif diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h new file mode 100644 index 0000000..dbebf64 --- /dev/null +++ b/src/Draw/Surface.h @@ -0,0 +1,67 @@ +#ifndef _SURFACE_H_ +#define _SURFACE_H_ + +#include "../Font.h" +struct Font; +struct Image; +class DrawSurface; + +struct DrawContext +{ + DrawContext() {} + DrawContext(DrawSurface* inSurface, int inClipLeft, int inClipTop, int inClipRight, int inClipBottom) + : surface(inSurface), clipLeft(inClipLeft), clipTop(inClipTop), clipRight(inClipRight), clipBottom(inClipBottom), drawOffsetX(0), drawOffsetY(0) + { + + } + + void Restrict(int left, int top, int right, int bottom) + { + left += drawOffsetX; + right += drawOffsetX; + top += drawOffsetY; + bottom += drawOffsetY; + if (left > clipLeft) + clipLeft = left; + if (right < clipRight) + clipRight = right; + if (top > clipTop) + clipTop = top; + if (bottom < clipBottom) + clipBottom = bottom; + } + + DrawSurface* surface; + int clipLeft, clipTop, clipRight, clipBottom; + int drawOffsetX, drawOffsetY; +}; + +class DrawSurface +{ +public: + enum Format + { + Format_1BPP, + Format_2BPP, + Format_8BPP, + Format_4BPP_EGA, + Format_4BPP_PC1512, + }; + + DrawSurface(int inWidth, int inHeight) : width(inWidth), height(inHeight) {} + virtual void Clear() = 0; + virtual void HLine(DrawContext& context, int x, int y, int count, uint8_t colour) = 0; + virtual void VLine(DrawContext& context, int x, int y, int count, uint8_t colour) = 0; + virtual void FillRect(DrawContext& context, int x, int y, int width, int height, uint8_t colour) = 0; + virtual void InvertRect(DrawContext& context, int x, int y, int width, int height) = 0; + virtual void DrawString(DrawContext& context, Font* font, const char* text, int x, int y, uint8_t colour, FontStyle::Type style = FontStyle::Regular) = 0; + virtual void BlitImage(DrawContext& context, Image* image, int x, int y) = 0; + virtual void VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) = 0; + virtual void ScrollScreen(int top, int bottom, int width, int amount) {} + + uint8_t** lines; + int width, height; + Format format; +}; + +#endif diff --git a/src/Event.h b/src/Event.h new file mode 100644 index 0000000..31210b1 --- /dev/null +++ b/src/Event.h @@ -0,0 +1,31 @@ +#pragma once +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include "Platform.h" + +class App; + +struct Event +{ + enum Type + { + MouseClick, + MouseRelease, + MouseDrag, + KeyPress, + Focus, + Unfocus + }; + + Event(App& inApp, Type inType) : app(inApp), type(inType), key(0), x(0), y(0) {} + Event(App& inApp, Type inType, InputButtonCode inKey) : app(inApp), type(inType), key(inKey), x(0), y(0) {} + Event(App& inApp, Type inType, int inX, int inY) : app(inApp), type(inType), key(0), x(inX), y(inY) {} + + App& app; + Type type; + InputButtonCode key; + int x, y; +}; + +#endif diff --git a/src/Font.cpp b/src/Font.cpp index 5224de0..48e3463 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -18,22 +18,41 @@ int Font::CalculateWidth(const char* text, FontStyle::Type style) { int result = 0; + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + while (*text) { char c = *text++; - if (c < 32 || c >= 128) + + int index = (unsigned char)c - FIRST_FONT_GLYPH; + if (index < 0) { continue; } - char index = c - 32; - result += glyphWidth[index]; - - if (style & FontStyle::Bold) + uint8_t width = glyphs[index].width; + if (width) { - result++; + result += width + bold; } } return result; } + +int Font::GetGlyphWidth(char c, FontStyle::Type style) +{ + int index = (unsigned char)(c)-FIRST_FONT_GLYPH; + if (index < 0) + { + return 0; + } + int result = glyphs[index].width; + + if (result) + { + if (style & FontStyle::Bold) + result++; + } + return result; +} diff --git a/src/Font.h b/src/Font.h index d406f43..8c360ca 100644 --- a/src/Font.h +++ b/src/Font.h @@ -12,10 +12,15 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _FONT_H_ +#define _FONT_H_ #include +#define FIRST_FONT_GLYPH 32 +#define LAST_FONT_GLYPH 255 +#define NUM_GLYPH_ENTRIES (LAST_FONT_GLYPH + 1 - FIRST_FONT_GLYPH) + struct FontStyle { enum Type @@ -28,14 +33,22 @@ struct FontStyle }; }; -struct Font +struct Font { - uint8_t glyphWidth[96]; - uint8_t glyphWidthBytes; +#pragma pack(push, 1) + struct Glyph + { + uint8_t width; + uint16_t offset; + }; +#pragma pack(pop) + + Glyph glyphs[NUM_GLYPH_ENTRIES]; uint8_t glyphHeight; - uint8_t glyphDataStride; - uint8_t* glyphData; + uint8_t glyphData[1]; int CalculateWidth(const char* text, FontStyle::Type style = FontStyle::Regular); + int GetGlyphWidth(char c, FontStyle::Type style = FontStyle::Regular); }; +#endif diff --git a/src/HTTP.cpp b/src/HTTP.cpp new file mode 100644 index 0000000..0261025 --- /dev/null +++ b/src/HTTP.cpp @@ -0,0 +1,563 @@ +#include +#include +#include +#include "HTTP.h" + +HTTPRequest::HTTPRequest() : status(HTTPRequest::Stopped), sock(NULL) +{ + contentType[0] = '\0'; +} + +void HTTPRequest::Reset() +{ + lineBufferSize = 0; + lineBufferSendPos = -1; + contentType[0] = '\0'; +} + +void HTTPRequest::WriteLine(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int maxWriteSize = LINE_BUFFER_SIZE - lineBufferSize - 2; + int vsrc = vsnprintf(lineBuffer + lineBufferSize, maxWriteSize, fmt, ap); + va_end(ap); + + if ((vsrc < 0) || (vsrc >= maxWriteSize)) + { + MarkError(WriteLineError); + lineBufferSendPos = -1; + } + else + { + if (lineBufferSendPos == -1) + { + lineBufferSendPos = 0; + } + lineBufferSize += vsrc; + lineBuffer[lineBufferSize++] = '\r'; + lineBuffer[lineBufferSize++] = '\n'; + } +} + +bool HTTPRequest::SendPendingWrites() +{ + if (sock) + { + if (lineBufferSendPos < 0) + { + return false; + } + + int rc = sock->Send((uint8_t*)(lineBuffer + lineBufferSendPos), lineBufferSize - lineBufferSendPos); + if (rc > 0) + { + lineBufferSendPos += rc; + if (lineBufferSendPos >= lineBufferSize) + { + lineBufferSendPos = -1; + lineBufferSize = 0; + } + } + else if (rc < 0) + { + MarkError(WriteLineError); + lineBufferSendPos = -1; + } + return lineBufferSendPos >= 0; + } + return false; +} + +void HTTPRequest::ResetTimeOutTimer() +{ + timeout = clock() + HTTP_RESPONSE_TIMEOUT; +} + +void HTTPRequest::Open(char* inURL) +{ + url = inURL; + Reset(); + + if (strnicmp(url.url, "http://", 7) == 0) { + + char* hostnameStart = url.url + 7; + + // Scan ahead for another slash; if there is none then we + // only have a server name and we should fetch the top + // level directory. + + char* proxy = getenv("HTTP_PROXY"); + //const char* proxy = "192.168.56.1:8888"; + if (proxy == NULL) { + + char* pathStart = strchr(hostnameStart, '/'); + if (pathStart == NULL) { + + strncpy(hostname, hostnameStart, HOSTNAME_LEN); + hostname[HOSTNAME_LEN - 1] = 0; + + path[0] = '/'; + path[1] = 0; + + } + else { + + strncpy(hostname, hostnameStart, pathStart - hostnameStart); + hostname[pathStart - hostnameStart] = '\0'; + hostname[HOSTNAME_LEN - 1] = 0; + + strncpy(path, pathStart, PATH_LEN); + path[PATH_LEN - 1] = 0; + + } + + } + else { + + strncpy(hostname, proxy, HOSTNAME_LEN); + hostname[HOSTNAME_LEN - 1] = 0; + + strncpy(path, url.url, PATH_LEN); + path[PATH_LEN - 1] = 0; + + } + + // If there is a # in the URL, remove from the path + char* hashPathPtr = strstr(path, "#"); + if (hashPathPtr) + { + *hashPathPtr = '\0'; + } + + serverPort = 80; + char* portStart = strchr(hostname, ':'); + + if (portStart != NULL) { + serverPort = atoi(portStart + 1); + if (serverPort == 0) { + MarkError(InvalidPort); + return; + } + + // Truncate hostname early + *portStart = 0; + } + + status = HTTPRequest::Connecting; + internalStatus = QueuedDNSRequest; + + ResetTimeOutTimer(); + } + else if (strnicmp(url.url, "https://", 8) == 0) { + status = HTTPRequest::UnsupportedHTTPS; + } + else { + // Need to specify a URL starting with http:// + MarkError(InvalidProtocol); + } +} + +size_t HTTPRequest::ReadData(char* buffer, size_t count) +{ + if (status == HTTPRequest::Downloading && sock && internalStatus == ReceiveContent) + { + if (usingChunkedTransfer && count > chunkSizeRemaining) + { + count = chunkSizeRemaining; + } + + int16_t rc = sock->Receive((unsigned char*)buffer, count); + if (rc < 0) + { + MarkError(ContentReceiveError); + } + else if(rc > 0) + { + ResetTimeOutTimer(); + + size_t bytesRead = (size_t)(rc); + if (contentRemaining > 0) + { + contentRemaining -= bytesRead; + if (contentRemaining <= 0) + { + Stop(); + return bytesRead; + } + } + + if (usingChunkedTransfer) + { + chunkSizeRemaining -= bytesRead; + if (!chunkSizeRemaining) + { + internalStatus = ParseChunkHeader; + } + } + + return bytesRead; + } + } + return 0; +} + +void HTTPRequest::Stop() +{ + if (sock) + { + sock->Close(); + Platform::network->DestroySocket(sock); + sock = NULL; + } + status = HTTPRequest::Stopped; +} + +void HTTPRequest::MarkError(InternalStatus statusError) +{ + status = HTTPRequest::Error; + internalStatus = statusError; +} + +void HTTPRequest::Update() +{ + if ((status == HTTPRequest::Connecting || status == HTTPRequest::Downloading) && clock() > timeout) + { + MarkError(TimedOut); + return; + } + + if (SendPendingWrites()) + { + return; + } + + switch (status) + { + case HTTPRequest::Connecting: + { + switch (internalStatus) + { + case QueuedDNSRequest: + { + int rc = Platform::network->ResolveAddress(hostname, hostAddr, true); + if(rc > 0) + { + internalStatus = WaitingDNSResolve; + } + else if(rc == 0) + { + internalStatus = OpeningSocket; + } + else + { + MarkError(HostNameResolveError); + } + } + break; + case WaitingDNSResolve: + { + int8_t rc = Platform::network->ResolveAddress(hostname, hostAddr, false); + if (rc == 0) + { + internalStatus = OpeningSocket; + } + else if(rc < 0) + { + MarkError(HostNameResolveError); + } + } + break; + case OpeningSocket: + { + sock = Platform::network->CreateSocket(); + if (!sock) + { + MarkError(SocketCreationError); + } + + if (sock->Connect(hostAddr, serverPort)) + { + MarkError(SocketCreationError); + break; + } + internalStatus = ConnectingSocket; + ResetTimeOutTimer(); + } + break; + case ConnectingSocket: + { + if (sock->IsConnectComplete()) + { + internalStatus = SendHeaders; + ResetTimeOutTimer(); + break; + } + else if (sock->IsClosed()) + { + MarkError(SocketConnectionError); + break; + } + } + break; + case SendHeaders: + { + WriteLine("GET %s HTTP/1.1", path); + WriteLine("User-Agent: MicroWeb " __DATE__); + WriteLine("Host: %s", hostname); + WriteLine("Accept-Encoding: identity"); + WriteLine("Connection: close"); + WriteLine(""); + internalStatus = ReceiveHeaderResponse; + } + break; + case ReceiveHeaderResponse: + { + if (ReadLine()) + { + if ((strncmp(lineBuffer, "HTTP/1.0", 8) != 0) && (strncmp(lineBuffer, "HTTP/1.1", 8) != 0)) { + MarkError(UnsupportedHTTPError); + return; + } + + // Skip past HTTP version number + char* s = lineBuffer + 8; + char* s2 = s; + + // Skip past whitespace + while (*s) { + if (*s != ' ' && *s != '\t') break; + s++; + } + + if ((s == s2) || (*s == 0) || (sscanf(s, "%3d", &responseCode) != 1)) { + MarkError(MalformedHTTPVersionLineError); + return; + } + + //printf("Response code: %d", responseCode); + //getchar(); + internalStatus = ReceiveHeaderContent; + + contentRemaining = -1; + usingChunkedTransfer = false; + contentType[0] = '\0'; + } + } + break; + case ReceiveHeaderContent: + { + if (ReadLine()) + { + if (lineBuffer[0] == '\0') + { + if (contentRemaining == 0) + { + // Received header with zero content + MarkError(ContentReceiveError); + } + else + { + // Header has finished + if (usingChunkedTransfer) + { + internalStatus = ParseChunkHeader; + } + else + { + status = Downloading; + internalStatus = ReceiveContent; + } + } + break; + } + else if (!strnicmp(lineBuffer, "Location: ", 10)) + { + if (responseCode == RESPONSE_MOVED_PERMANENTLY || responseCode == RESPONSE_MOVED_TEMPORARILY || responseCode == RESPONSE_TEMPORARY_REDIRECTION || responseCode == RESPONSE_PERMANENT_REDIRECT) + { + //printf("Redirecting to %s", lineBuffer + 10); + //getchar(); + Stop(); + + char* redirectedAddress = lineBuffer + 10; + if (!strnicmp(redirectedAddress, "https://", 8) && !strnicmp(url.url, "http://", 7)) + { + // Check if the redirected address is http -> https + if (!strcmp(url.url + 7, redirectedAddress + 8)) + { + url = redirectedAddress; + status = HTTPRequest::UnsupportedHTTPS; + break; + } + else + { + // Attempt to change this to http:// instead + strcpy(redirectedAddress + 4, redirectedAddress + 5); + } + } + + Open(redirectedAddress); + break; + } + } + else if (!strnicmp(lineBuffer, "Content-Length:", 15)) + { + contentRemaining = strtol(lineBuffer + 15, NULL, 10); + } + else if (!stricmp(lineBuffer, "Transfer-Encoding: chunked")) + { + usingChunkedTransfer = true; + } + else if (!strnicmp(lineBuffer, "Content-Type:", 13)) + { + strncpy(contentType, lineBuffer + 14, MAX_CONTENT_TYPE_LENGTH); + } + + //printf("Header: %s -- \n", lineBuffer); + //getchar(); + } + } + break; + } + } + break; + + case HTTPRequest::Downloading: + { + //if (sock->isRemoteClosed()) + //{ + // //status = HTTPRequest::Finished; + //} + } + break; + } + + if (internalStatus == ParseChunkHeader) + { + if (ReadLine()) + { + chunkSizeRemaining = strtol(lineBuffer, NULL, 16); + + if (chunkSizeRemaining) + { + status = Downloading; + internalStatus = ReceiveContent; + } + } + } +} + +bool HTTPRequest::ReadLine() +{ + bool allowBufferTruncation = true; + + if (!sock) + return false; + + while (1) + { + int rc = sock->Receive((unsigned char*)lineBuffer + lineBufferSize, 1); + if (rc == 0) + { + // Need to wait for new packets to be received, defer + return false; + } + else if (rc < 0) + { + printf("Receive error\n"); + MarkError(ContentReceiveError); + return false; + } + + if (lineBufferSize >= LINE_BUFFER_SIZE) + { + // Line was too long + lineBuffer[LINE_BUFFER_SIZE - 1] = '\0'; + MarkError(ContentReceiveError); + return false; + } + + if (lineBuffer[lineBufferSize] == '\n') + { + lineBuffer[lineBufferSize] = '\0'; + if (lineBufferSize >= 1) + { + if (lineBuffer[lineBufferSize - 1] == '\r') + { + lineBuffer[lineBufferSize - 1] = '\0'; + } + } + + lineBufferSize = 0; + return true; + } + + if (lineBuffer[lineBufferSize] == '\0') + { + // Found terminated string + lineBufferSize = 0; + return true; + } + + if (!allowBufferTruncation || lineBufferSize < LINE_BUFFER_SIZE - 1) + { + lineBufferSize++; + } + } +} + +const char* HTTPRequest::GetStatusString() +{ + switch (status) + { + case HTTPRequest::Error: + switch (internalStatus) + { + case InvalidPort: + return "Invalid port"; + case InvalidProtocol: + return "Invalid protocol"; + case SocketCreationError: + return "Socket creation error"; + case SocketConnectionError: + return "Socket connection error"; + case HeaderSendError: + return "Error sending HTTP header"; + case ContentReceiveError: + return "Error receiving HTTP content"; + case UnsupportedHTTPError: + return "Unsupported HTTP version"; + case MalformedHTTPVersionLineError: + return "Malformed HTTP version line"; + case WriteLineError: + return "Error writing headers"; + case HostNameResolveError: + return "Error resolving host name"; + case TimedOut: + return "Connection timed out"; + } + break; + + case HTTPRequest::Connecting: + switch (internalStatus) + { + case QueuedDNSRequest: + case WaitingDNSResolve: + return "Resolving host name via DNS"; + case OpeningSocket: + return "Connecting to server"; + case ConnectingSocket: + case SendHeaders: + return "Sending headers"; + case ReceiveHeaderResponse: + case ReceiveHeaderContent: + return "Receiving headers"; + case ReceiveContent: + return "Receiving content"; + } + break; + default: + break; + } + return ""; +} diff --git a/src/HTTP.h b/src/HTTP.h new file mode 100644 index 0000000..f96be01 --- /dev/null +++ b/src/HTTP.h @@ -0,0 +1,109 @@ +#ifndef HTTP_H_ +#define HTTP_H_ + +#include +#include "Platform.h" +#include "URL.h" + +#define HOSTNAME_LEN (80) +#define PATH_LEN (MAX_URL_LENGTH) +#define LINE_BUFFER_SIZE 512 + +#define RESPONSE_MOVED_PERMANENTLY 301 +#define RESPONSE_MOVED_TEMPORARILY 302 +#define RESPONSE_TEMPORARY_REDIRECTION 307 +#define RESPONSE_PERMANENT_REDIRECT 308 + +#define MAX_CONTENT_TYPE_LENGTH 32 + +#define HTTP_RESPONSE_TIMEOUT_SECONDS 20 +#define HTTP_RESPONSE_TIMEOUT (HTTP_RESPONSE_TIMEOUT_SECONDS * CLOCKS_PER_SEC) + +class HTTPRequest +{ +public: + enum Status + { + Stopped, + Connecting, + Downloading, + Finished, + Error, + UnsupportedHTTPS + }; + + HTTPRequest(); + + void Open(char* url); + + HTTPRequest::Status GetStatus() { return status; } + size_t ReadData(char* buffer, size_t count); + void Stop(); + void Update(); + const char* GetStatusString(); + const char* GetURL() { return url.url; } + const char* GetContentType() { return contentType; } + +private: + enum InternalStatus + { + // Errors + InvalidPort, + InvalidProtocol, + SocketCreationError, + SocketConnectionError, + HeaderSendError, + ContentReceiveError, + UnsupportedHTTPError, + MalformedHTTPVersionLineError, + WriteLineError, + TimedOut, + HostNameResolveError, + + // Connection states + QueuedDNSRequest, + WaitingDNSResolve, + OpeningSocket, + ConnectingSocket, + SendHeaders, + ReceiveHeaderResponse, + ReceiveHeaderContent, + ReceiveContent, + ParseChunkHeader + }; + + void MarkError(InternalStatus statusError); + bool ReadLine(); + void WriteLine(const char* fmt, ...); + bool SendPendingWrites(); + + void Reset(); + void ResetTimeOutTimer(); + + HTTPRequest::Status status; + InternalStatus internalStatus; + + URL url; + char hostname[HOSTNAME_LEN]; + char path[PATH_LEN]; + NetworkAddress hostAddr; + uint16_t serverPort; + NetworkTCPSocket* sock; + int responseCode; + char contentType[MAX_CONTENT_TYPE_LENGTH]; + + char lineBuffer[LINE_BUFFER_SIZE]; + int lineBufferSize; + int lineBufferSendPos; + + long contentRemaining; + + long chunkSizeRemaining; + bool usingChunkedTransfer; + + clock_t timeout; +}; + + +#endif + diff --git a/src/Image.h b/src/Image.h deleted file mode 100644 index 4a64ccd..0000000 --- a/src/Image.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -// Images are 1bpp -struct Image -{ - uint16_t width; - uint16_t height; - uint8_t* data; -}; diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp new file mode 100644 index 0000000..878424c --- /dev/null +++ b/src/Image/Decoder.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include "Gif.h" +#include "Jpeg.h" +#include "Png.h" +#include "Decoder.h" +#include "../Platform.h" +#include "../VidModes.h" +#include "Image.h" +#include "../Draw/Surface.h" +#pragma warning(disable:4996) + +#include +#include + +typedef union +{ + char gif[sizeof(GifDecoder)]; + char png[sizeof(PngDecoder)]; + char jpeg[sizeof(JpegDecoder)]; + char buffer[1]; +} ImageDecoderUnion; + +static ImageDecoderUnion* imageDecoderUnion = nullptr; + +#define COLDITH(x) ((x * 4) - 32) + +const int8_t ImageDecoder::colourDitherMatrix[16] = +{ +// 0, 8, 2, 10, +// 12, 4, 14, 6, +// 3, 11, 1, 9, +// 15, 7, 13, 5 + COLDITH(0), COLDITH(8), COLDITH(2), COLDITH(10), + COLDITH(12), COLDITH(4), COLDITH(14), COLDITH(6), + COLDITH(3), COLDITH(11), COLDITH(1), COLDITH(9), + COLDITH(15), COLDITH(7), COLDITH(13), COLDITH(5) +}; + +const uint8_t ImageDecoder::greyDitherMatrix[256] = +{ + 0, 128, 32, 160, 8, 136, 40, 168, 2, 130, 34, 162, 10, 138, 42, 170, + 192, 64, 224, 96, 200, 72, 232, 104, 194, 66, 226, 98, 202, 74, 234, 106, + 48, 176, 16, 144, 56, 184, 24, 152, 50, 178, 18, 146, 58, 186, 26, 154, + 240, 112, 208, 80, 248, 120, 216, 88, 242, 114, 210, 82, 250, 122, 218, 90, + 12, 140, 44, 172, 4, 132, 36, 164, 14, 142, 46, 174, 6, 134, 38, 166, + 204, 76, 236, 108, 196, 68, 228, 100, 206, 78, 238, 110, 198, 70, 230, 102, + 60, 188, 28, 156, 52, 180, 20, 148, 62, 190, 30, 158, 54, 182, 22, 150, + 252, 124, 220, 92, 244, 116, 212, 84, 254, 126, 222, 94, 246, 118, 214, 86, + 3, 131, 35, 163, 11, 139, 43, 171, 1, 129, 33, 161, 9, 137, 41, 169, + 195, 67, 227, 99, 203, 75, 235, 107, 193, 65, 225, 97, 201, 73, 233, 105, + 51, 179, 19, 147, 59, 187, 27, 155, 49, 177, 17, 145, 57, 185, 25, 153, + 243, 115, 211, 83, 251, 123, 219, 91, 241, 113, 209, 81, 249, 121, 217, 89, + 15, 143, 47, 175, 7, 135, 39, 167, 13, 141, 45, 173, 5, 133, 37, 165, + 207, 79, 239, 111, 199, 71, 231, 103, 205, 77, 237, 109, 197, 69, 229, 101, + 63, 191, 31, 159, 55, 183, 23, 151, 61, 189, 29, 157, 53, 181, 21, 149, + 254, 127, 223, 95, 247, 119, 215, 87, 253, 125, 221, 93, 245, 117, 213, 85 +}; + +void ImageDecoder::Allocate() +{ + if (!imageDecoderUnion) + { + imageDecoderUnion = new ImageDecoderUnion; + + + //printf("Decoder size: %u bytes", (long) sizeof(ImageDecoderUnion)); + //getch(); + } + if (!imageDecoderUnion) + { + Platform::FatalError("Could not allocate memory for image decoder"); + } +} + +ImageDecoder* ImageDecoder::Get() +{ + return (ImageDecoder*)(imageDecoderUnion->buffer); +} + +ImageDecoder* ImageDecoder::Create(DecoderType type) +{ + switch (type) + { + case ImageDecoder::Jpeg: + return new (imageDecoderUnion->buffer) JpegDecoder(); + case ImageDecoder::Gif: + return new (imageDecoderUnion->buffer) GifDecoder(); + case ImageDecoder::Png: + return new (imageDecoderUnion->buffer) PngDecoder(); + default: + return nullptr; + } +} + +ImageDecoder* ImageDecoder::CreateFromMIME(const char* type) +{ + if (!stricmp(type, "image/gif")) + { + return Create(ImageDecoder::Gif); + } + if (!stricmp(type, "image/png")) + { + return Create(ImageDecoder::Png); + } + if (!stricmp(type, "image/jpeg")) + { + return Create(ImageDecoder::Jpeg); + } + return nullptr; +} + +ImageDecoder* ImageDecoder::CreateFromExtension(const char* path) +{ + const char* extension = path + strlen(path); + while (extension > path) + { + extension--; + if (*extension == '.') + { + extension++; + if (!stricmp(extension, "gif")) + { + return Create(ImageDecoder::Gif); + } + if (!stricmp(extension, "png")) + { + return Create(ImageDecoder::Png); + } + if (!stricmp(extension, "jpeg") || !stricmp(extension, "jpg")) + { + return Create(ImageDecoder::Jpeg); + } + + return nullptr; + } + } + + return nullptr; +} + +bool ImageDecoder::FillStruct(uint8_t** data, size_t& dataLength, void* dest, size_t size) +{ + size_t bytesLeft = size - structFillPosition; + if (bytesLeft <= dataLength) + { + memcpy((uint8_t*)dest + structFillPosition, *data, bytesLeft); + *data += bytesLeft; + dataLength -= bytesLeft; + structFillPosition = 0; + return true; + } + else + { + memcpy((uint8_t*)dest + structFillPosition, *data, dataLength); + *data += dataLength; + structFillPosition += dataLength; + dataLength = 0; + return false; + } +} + +bool ImageDecoder::SkipBytes(uint8_t** data, size_t& dataLength, size_t size) +{ + size_t bytesLeft = size - structFillPosition; + if (bytesLeft <= dataLength) + { + *data += bytesLeft; + dataLength -= bytesLeft; + structFillPosition = 0; + return true; + } + else + { + *data += dataLength; + structFillPosition += dataLength; + dataLength = 0; + return false; + } +} + +void ImageDecoder::Begin(Image* image, bool dimensionsOnly) +{ + onlyDownloadDimensions = dimensionsOnly; + state = ImageDecoder::Decoding; + outputImage = image; + outputImage->bpp = Platform::video->drawSurface->format == DrawSurface::Format_1BPP ? 1 : 8; +} + +void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) +{ + VideoModeInfo* modeInfo = Platform::video->GetVideoModeInfo(); + + if (modeInfo->aspectRatio != 100) + { + sourceHeight = ((long)sourceHeight * 100) / modeInfo->aspectRatio; + } + if (modeInfo->zoom != 100) + { + sourceWidth = ((long)modeInfo->zoom * sourceWidth) / 100; + sourceHeight = ((long)modeInfo->zoom * sourceHeight) / 100; + } + + if (sourceHeight < 1) + sourceHeight = 1; + if (sourceWidth < 1) + sourceWidth = 1; + + int calculatedWidth = sourceWidth; + int calculatedHeight = sourceHeight; + + if (outputImage->width != 0) + { + // Specified by layout + calculatedWidth = outputImage->width; + + if (outputImage->height == 0) + { + calculatedHeight = ((long)sourceHeight * outputImage->width) / sourceWidth; + if (calculatedHeight <= 0) + calculatedHeight = 1; + } + } + if (outputImage->height != 0) + { + // Specified by layout + calculatedHeight = outputImage->height; + + if (outputImage->width == 0) + { + calculatedWidth = ((long)sourceWidth * outputImage->height) / sourceHeight; + if (calculatedWidth <= 0) + calculatedWidth = 1; + } + } + + outputImage->width = calculatedWidth; + outputImage->height = calculatedHeight; +} diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h new file mode 100644 index 0000000..464647a --- /dev/null +++ b/src/Image/Decoder.h @@ -0,0 +1,103 @@ +#ifndef _DECODER_H_ +#define _DECODER_H_ + +#include +#include +struct Image; +class LinearAllocator; + +#pragma pack(push, 1) +struct uint16_be +{ + uint16_be() {} + uint16_be(uint16_t x) : highByte((uint8_t)(x >> 8)), lowByte((uint8_t)x) {} + + uint8_t highByte; + uint8_t lowByte; + + operator uint16_t () + { + return (highByte << 8) | lowByte; + } +}; + +struct uint32_be +{ + uint32_be() {} + uint32_be(uint32_t x) + { + bytes[0] = (uint8_t)(x & 0xff); + bytes[1] = (uint8_t)((x >> 8) & 0xff); + bytes[2] = (uint8_t)((x >> 16) & 0xff); + bytes[3] = (uint8_t)((x >> 24) & 0xff); + } + + uint8_t bytes[4]; + + operator uint32_t () + { + return + ((uint32_t)bytes[0] << 24) | + ((uint32_t)bytes[1] << 16) | + ((uint32_t)bytes[2] << 8) | + ((uint32_t)bytes[3]); + } +}; +#pragma pack(pop) + +class ImageDecoder +{ +public: + enum State + { + Stopped, + Decoding, + Success, + Error + }; + + enum DecoderType + { + Gif, + Png, + Jpeg + }; + + ImageDecoder() : outputImage(NULL), state(Stopped), structFillPosition(0) {} + + void Begin(Image* image, bool dimensionsOnly); + virtual void Process(uint8_t* data, size_t dataLength) = 0; + State GetState() { return state; } + + static void Allocate(); + static ImageDecoder* Get(); + static ImageDecoder* Create(DecoderType type); + static ImageDecoder* CreateFromExtension(const char* path); + static ImageDecoder* CreateFromMIME(const char* type); + +protected: + bool FillStruct(uint8_t** data, size_t& dataLength, void* dest, size_t size); + uint8_t NextByte(uint8_t** data, size_t& dataLength) + { + uint8_t result = **data; + (*data)++; + dataLength--; + return result; + } + bool SkipBytes(uint8_t** data, size_t& dataLength, size_t size); + size_t structFillPosition; + + void CalculateImageDimensions(int sourceWidth, int sourceHeight); + + Image* outputImage; + ImageDecoder::State state; + bool onlyDownloadDimensions; + + static const uint8_t greyDitherMatrix[256]; + static const int8_t colourDitherMatrix[16]; + +}; + + + +#endif diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp new file mode 100644 index 0000000..9fae3d2 --- /dev/null +++ b/src/Image/Gif.cpp @@ -0,0 +1,783 @@ +#include "Gif.h" +#include "Image.h" +#include "../Platform.h" +#include "../Colour.h" +#include "../Memory/Memory.h" +#include "../Page.h" +#include "../App.h" +#include "../Draw/Surface.h" +#include + +#ifdef _WIN32 +#define DEBUG_MESSAGE(...) printf(__VA_ARGS__); +#else +#define DEBUG_MESSAGE(...) +#endif + +#define BLOCK_TYPE_EXTENSION_INTRODUCER 0x21 +#define BLOCK_TYPE_IMAGE_DESCRIPTOR 0x2C +#define BLOCK_TYPE_TRAILER 0x3B +#define BLOCK_TYPE_GRAPHIC_CONTROL_EXTENSION 0xF9 + +GifDecoder::GifDecoder() +{ + internalState = ParseHeader; + lineBufferSkipCount = 0; + transparentColourIndex = -1; +} + +void GifDecoder::Process(uint8_t* data, size_t dataLength) +{ + if(state != ImageDecoder::Decoding) + { + return; + } + + while(dataLength > 0) + { + switch(internalState) + { + case ParseHeader: + { + if(FillStruct(&data, dataLength, &header, sizeof(Header))) + { + // Header structure is complete + if(memcmp(header.versionTag, "GIF89a", 6) && memcmp(header.versionTag, "GIF87a", 6)) + { + // Not a GIF89a + state = ImageDecoder::Error; + return; + } + + // If the image width is wider than the buffer, we will skip every N pixels + lineBufferDivider = 1; + while (header.width / lineBufferDivider > GIF_LINE_BUFFER_MAX_SIZE) + { + lineBufferDivider++; + } + + CalculateImageDimensions(header.width, header.height); + + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; + } + + if (outputImage->bpp == 1) + { + outputImage->pitch = (outputImage->width + 7) / 8; + } + else // 8bpp + { + outputImage->pitch = outputImage->width; + } + + outputImage->lines = MemoryManager::pageBlockAllocator.Allocate(sizeof(MemBlockHandle) * outputImage->height); + if(!outputImage->lines.IsAllocated()) + { + // Allocation error + DEBUG_MESSAGE("Could not allocate!\n"); + state = ImageDecoder::Error; + return; + } + + MemBlockHandle* lines = outputImage->lines.Get(); + + for (int j = 0; j < outputImage->height; j++) + { + lines[j] = MemoryManager::pageBlockAllocator.Allocate(outputImage->pitch); + + if (!lines[j].IsAllocated()) + { + // Allocation error + DEBUG_MESSAGE("Could not allocate!\n"); + outputImage->lines.type = MemBlockHandle::Unallocated; + state = ImageDecoder::Error; + return; + } + } + + outputImage->lines.Commit(); + for (int j = 0; j < outputImage->height; j++) + { + lines = outputImage->lines.Get(); + MemBlockHandle line = lines[j]; + void* pixels = line.GetPtr(); + if (pixels) + { + memset(pixels, TRANSPARENT_COLOUR_VALUE, outputImage->pitch); + line.Commit(); + } + } + + backgroundColour = header.backgroundColour; + + DEBUG_MESSAGE("Image is %d x %d\n", outputImage->width, outputImage->height); + + if(header.fields & (1 << 7)) + { + paletteSize = (1 << ((header.fields & 0x7) + 1)); + DEBUG_MESSAGE("Image has a palette of %d colours\n", (int) paletteSize); + + internalState = ParsePalette; + paletteIndex = 0; + } + else + { + internalState = ParseDataBlock; + } + } + } + break; + + case ParsePalette: + { + if(FillStruct(&data, dataLength, rgb, 3)) + { + //DEBUG_MESSAGE("RGB index %d: %x %x %x\n", paletteIndex, (int)(rgb[0]), (int)(rgb[1]), (int)(rgb[2])); + + //uint8_t grey = (uint8_t)(((uint16_t)rgb[0] * 76 + (uint16_t)rgb[1] * 150 + (uint16_t)rgb[2] * 30) >> 8); + //palette[paletteIndex++] = grey; + palette[paletteIndex * 3] = rgb[0]; + palette[paletteIndex * 3 + 1] = rgb[1]; + palette[paletteIndex * 3 + 2] = rgb[2]; + paletteIndex++; + + if(paletteIndex == paletteSize) + { + if (outputImage->bpp == 8) + { + for (int n = 0; n < paletteSize; n++) + { + paletteLUT[n] = Platform::video->paletteLUT[RGB332(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2])]; + } + } + else + { + for (int n = 0; n < paletteSize; n++) + { + paletteLUT[n] = RGB_TO_GREY(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2]); + } + } + + if (transparentColourIndex >= 0) + { + paletteLUT[transparentColourIndex] = TRANSPARENT_COLOUR_VALUE; + } + + internalState = ParseDataBlock; + } + } + } + break; + + case ParseDataBlock: + { + uint8_t blockType = NextByte(&data, dataLength); + + switch(blockType) + { + case BLOCK_TYPE_IMAGE_DESCRIPTOR: + internalState = ParseImageDescriptor; + break; + + case BLOCK_TYPE_TRAILER: + // End of GIF + state = ImageDecoder::Success; + return; + + case BLOCK_TYPE_EXTENSION_INTRODUCER: + internalState = ParseExtension; + break; + + default: + DEBUG_MESSAGE("Invalid block type: %x\n", (int)(blockType)); + state = ImageDecoder::Error; + return; + } + } + break; + + case ParseImageDescriptor: + { + if(FillStruct(&data, dataLength, &imageDescriptor, sizeof(ImageDescriptor))) + { + DEBUG_MESSAGE("Image: %d, %d %d, %d\n", imageDescriptor.x, imageDescriptor.y, imageDescriptor.width, imageDescriptor.height); + + drawX = drawY = 0; + outputLine = 0; + + linesProcessed = 0; + lineBufferSize = 0; + lineBufferFlushCount = 0; + + if (imageDescriptor.fields & 0x80) + { + internalState = ParseLocalColourTable; + paletteIndex = 0; + localColourTableLength = 1 << ((imageDescriptor.fields & 7) + 1); + } + else + { + internalState = ParseLZWCodeSize; + } + } + } + break; + + case ParseLocalColourTable: + { + if (FillStruct(&data, dataLength, &rgb, 3)) + { + palette[paletteIndex * 3] = rgb[0]; + palette[paletteIndex * 3 + 1] = rgb[1]; + palette[paletteIndex * 3 + 2] = rgb[2]; + + paletteIndex++; + + if (paletteIndex == localColourTableLength) + { + if (outputImage->bpp == 8) + { + for (int n = 0; n < localColourTableLength; n++) + { + paletteLUT[n] = Platform::video->paletteLUT[RGB332(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2])]; + } + } + else + { + for (int n = 0; n < localColourTableLength; n++) + { + paletteLUT[n] = RGB_TO_GREY(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2]); + } + } + + if (transparentColourIndex >= 0) + { + paletteLUT[transparentColourIndex] = TRANSPARENT_COLOUR_VALUE; + } + + internalState = ParseLZWCodeSize; + } + } + } + break; + + case ParseLZWCodeSize: + { + lzwCodeSize = NextByte(&data, dataLength); + DEBUG_MESSAGE("LZW code size: %d\n", lzwCodeSize); + + // Init LZW vars + code = 0; + clearCode = 1 << lzwCodeSize; + stopCode = clearCode + 1; + codeLength = resetCodeLength = lzwCodeSize + 1; + codeBit = 0; + prev = -1; + ClearDictionary(); + + internalState = ParseImageSubBlockSize; + } + break; + + case ParseImageSubBlockSize: + { + imageSubBlockSize = NextByte(&data, dataLength); + DEBUG_MESSAGE("Sub block size: %d bytes\n", imageSubBlockSize); + if(imageSubBlockSize) + { + internalState = ParseImageSubBlock; + } + else + { + internalState = ParseDataBlock; + + // HACK: Finish decoding after first frame + state = ImageDecoder::Success; + return; + } + } + break; + + case ParseImageSubBlock: + { + if(imageSubBlockSize) + { + imageSubBlockSize--; + uint8_t dataByte = NextByte(&data, dataLength); + //DEBUG_MESSAGE("-Data: %x\n", dataByte); + + for(int n = 0; n < 8; n++) + { + if(dataByte & (1 << n)) + { + code |= (1 << codeBit); + } + codeBit++; + + if (codeBit == codeLength) + { + //DEBUG_MESSAGE("code: %x [len=%d]\n", code, codeLength); + + // Code complete + if (code == clearCode) + { + DEBUG_MESSAGE("CLEAR\n"); + codeLength = resetCodeLength; + ClearDictionary(); + prev = -1; + codeBit = 0; + code = 0; + continue; + } + else if (code == stopCode) + { + DEBUG_MESSAGE("STOP\n"); + if (imageSubBlockSize) + { + DEBUG_MESSAGE("Malformed GIF\n"); + // FIXME + //state = ImageDecoder::Success; + //return; + //state = ImageDecoder::Error; + } + continue; + } + + if (prev > -1 && codeLength <= 12) + { + if (code > dictionaryIndex) + { + DEBUG_MESSAGE("Error: code = %x, but dictionaryIndex = %x\n", code, dictionaryIndex); + state = ImageDecoder::Error; + return; + } + + int ptr = (code == dictionaryIndex) ? prev : code; + while (dictionary[ptr].prev != -1) + { + ptr = dictionary[ptr].prev; + } + dictionary[dictionaryIndex].byte = dictionary[ptr].byte; + + dictionary[dictionaryIndex].prev = prev; + if (prev != -1) + { + /*if (dictionary[prev].len == 255) + { + DEBUG_MESSAGE("Dictionary entry length too long"); + state = ImageDecoder::Error; + return; + }*/ + + dictionary[dictionaryIndex].len = dictionary[prev].len + 1; + } + else + { + dictionary[dictionaryIndex].len = 1; + } + + dictionaryIndex++; + + if(dictionaryIndex == (1 << (codeLength)) && codeLength < 12) + { + codeLength++; + //DEBUG_MESSAGE("Code length: %d\n", codeLength); + } + } + + prev = code; + + { + uint8_t stack[1024]; + int stackSize = 0; + + while(code != -1) + { + if (stackSize >= 1024) + { + Platform::FatalError("Stack overflow in GIF decoding"); + } + + stack[stackSize++] = dictionary[code].byte; + //DEBUG_MESSAGE(" value: %x\n", dictionary[code].byte); + + code = dictionary[code].prev; + } + + while (stackSize) + { + if (lineBufferSkipCount == lineBufferDivider - 1) + { + lineBuffer[lineBufferSize++] = stack[stackSize - 1]; + lineBufferSkipCount = 0; + } + else + { + lineBufferSkipCount++; + } + + lineBufferFlushCount++; + stackSize--; + + if (lineBufferFlushCount == imageDescriptor.width) + { + ProcessLineBuffer(); + lineBufferSize = 0; + lineBufferFlushCount = 0; + } + } + } + + codeBit = 0; + code = 0; + } + } + } + else + { + //DEBUG_MESSAGE("--\n"); + internalState = ParseImageSubBlockSize; + } + } + break; + + case ParseExtension: + { + if(FillStruct(&data, dataLength, &extensionHeader, sizeof(ExtensionHeader))) + { + DEBUG_MESSAGE("Extension: %x Size: %d\n", (int) extensionHeader.code, (int) extensionHeader.size); + + if (extensionHeader.code == BLOCK_TYPE_GRAPHIC_CONTROL_EXTENSION) + { + internalState = ParseGraphicControlExtension; + } + else + { + internalState = ParseExtensionContents; + } + } + } + break; + + case ParseExtensionContents: + { + if(SkipBytes(&data, dataLength, extensionHeader.size)) + { + internalState = ParseExtensionSubBlockSize; + } + } + break; + + case ParseGraphicControlExtension: + { + if (FillStruct(&data, dataLength, &graphicControlExtension, sizeof(GraphicControlExtension))) + { + if (graphicControlExtension.packedFields & 1) + { + transparentColourIndex = graphicControlExtension.transparentColourIndex; + paletteLUT[transparentColourIndex] = TRANSPARENT_COLOUR_VALUE; + } + internalState = ParseExtensionSubBlockSize; + } + } + break; + + case ParseExtensionSubBlockSize: + { + extensionSubBlockSize = NextByte(&data, dataLength); + if(extensionSubBlockSize > 0) + { + internalState = ParseExtensionSubBlock; + } + else + { + internalState = ParseDataBlock; + } + } + break; + + case ParseExtensionSubBlock: + { + if(SkipBytes(&data, dataLength, extensionSubBlockSize)) + { + internalState = ParseExtensionSubBlockSize; + } + } + break; + } + } +} + +void GifDecoder::ClearDictionary() +{ + for(dictionaryIndex = 0; dictionaryIndex < (1 << lzwCodeSize); dictionaryIndex++) + { + dictionary[dictionaryIndex].byte = (uint8_t) dictionaryIndex; + dictionary[dictionaryIndex].prev = -1; + dictionary[dictionaryIndex].len = 1; + } + + dictionaryIndex += 2; +} + +// Compute output index of y-th input line, in frame of height h. +int GifDecoder::CalculateLineIndex(int y) +{ + int p; /* number of lines in current pass */ + + p = (header.height - 1) / 8 + 1; + if (y < p) /* pass 1 */ + return y * 8; + y -= p; + p = (header.height - 5) / 8 + 1; + if (y < p) /* pass 2 */ + return y * 8 + 4; + y -= p; + p = (header.height - 3) / 4 + 1; + if (y < p) /* pass 3 */ + return y * 4 + 2; + y -= p; + /* pass 4 */ + return y * 2 + 1; +} + +void GifDecoder::ProcessLineBuffer() +{ + int outputY = linesProcessed; + + if (imageDescriptor.fields & GIF_INTERLACE_BIT) + { + outputY = CalculateLineIndex(linesProcessed); + } + + if (outputImage->height == header.height) + { + EmitLine(outputY); + } + else + { + int first = outputY * (long)outputImage->height / header.height; + int last = (outputY + 1) * (long)outputImage->height / header.height; + + for (int y = first; y < last; y++) + { + EmitLine(y); + } + } + + linesProcessed++; +} + +void GifDecoder::EmitLine(int y) +{ + MemBlockHandle* lines = outputImage->lines.Get(); + MemBlockHandle lineOutput = lines[y]; + uint8_t* output = lineOutput.Get(); + + if (outputImage->bpp == 8) + { + bool useColourDithering = true; + //if (Platform::video->paletteLUT == compositeCgaPaletteLUT) + //{ + // useColourDithering = false; + //} + + if (useColourDithering) + { + const int8_t* ditherPattern = colourDitherMatrix + 4 * (y & 3); + int ditherIndex = 0; + + if (outputImage->width == lineBufferSize) + { + for (int i = 0; i < lineBufferSize; i++) + { + int8_t offset = ditherPattern[ditherIndex]; + ditherIndex = (ditherIndex + 1) & 3; + + if (lineBuffer[i] == transparentColourIndex) + { + output[i] = TRANSPARENT_COLOUR_VALUE; + continue; + } + + int index = lineBuffer[i] * 3; + int red = palette[index] + offset; + if (red > 255) + red = 255; + else if (red < 0) + red = 0; + int green = palette[index + 1] + offset; + if (green > 255) + green = 255; + else if (green < 0) + green = 0; + int blue = palette[index + 2] + offset; + if (blue > 255) + blue = 255; + else if (blue < 0) + blue = 0; + + output[i] = Platform::video->paletteLUT[RGB332(red, green, blue)]; + } + } + else + { + int dy = lineBufferSize; + int dx = outputImage->width; + int D = 2 * dy - dx; + int x = 0; + + for (int i = 0; i < outputImage->width; i++) + { + int offset = ditherPattern[ditherIndex]; + ditherIndex = (ditherIndex + 1) & 3; + + if (lineBuffer[x] == transparentColourIndex) + { + output[i] = TRANSPARENT_COLOUR_VALUE; + } + else + { + int index = lineBuffer[x] * 3; + int red = palette[index] + offset; + if (red > 255) + red = 255; + else if (red < 0) + red = 0; + int green = palette[index + 1] + offset; + if (green > 255) + green = 255; + else if (green < 0) + green = 0; + int blue = palette[index + 2] + offset; + if (blue > 255) + blue = 255; + else if (blue < 0) + blue = 0; + + output[i] = Platform::video->paletteLUT[RGB332(red, green, blue)]; + } + + while (D > 0) + { + x++; + D -= 2 * dx; + } + D += 2 * dy; + } + } + + } + else + { + if (outputImage->width == lineBufferSize) + { + for (int i = 0; i < lineBufferSize; i++) + { + output[i] = paletteLUT[lineBuffer[i]]; + } + } + else + { + int dy = lineBufferSize; + int dx = outputImage->width; + int D = 2 * dy - dx; + int x = 0; + + for (int i = 0; i < outputImage->width; i++) + { + output[i] = paletteLUT[lineBuffer[x]]; + while (D > 0) + { + x++; + D -= 2 * dx; + } + D += 2 * dy; + } + } + } + } + else + { + const uint8_t* ditherPattern = greyDitherMatrix + 16 * (y & 15); + uint8_t buffer = 0; + uint8_t mask = 0x80; + int ditherIndex = 0; + + if (outputImage->width == lineBufferSize) + { + for (int i = 0; i < lineBufferSize; i++) + { + uint8_t value = paletteLUT[lineBuffer[i]]; + uint8_t threshold = ditherPattern[ditherIndex]; + + if (value > threshold) + { + buffer |= mask; + } + + ditherIndex = (ditherIndex + 1) & 15; + + mask >>= 1; + if (!mask) + { + *output++ = buffer; + buffer = 0; + mask = 0x80; + } + } + + if (mask != 0x80) + { + *output = buffer; + } + } + else + { + + int dy = lineBufferSize; + int dx = outputImage->width; + int D = 2 * dy - dx; + int x = 0; + + for (int i = 0; i < outputImage->width; i++) + { + uint8_t value = paletteLUT[lineBuffer[x]]; + uint8_t threshold = ditherPattern[ditherIndex]; + + if (value > threshold) + { + buffer |= mask; + } + + ditherIndex = (ditherIndex + 1) & 15; + + mask >>= 1; + if (!mask) + { + *output++ = buffer; + buffer = 0; + mask = 0x80; + } + + while (D > 0) + { + x++; + D -= 2 * dx; + } + D += 2 * dy; + } + + if (mask != 0x80) + { + *output = buffer; + } + } + } + + lineOutput.Commit(); +} + diff --git a/src/Image/Gif.h b/src/Image/Gif.h new file mode 100644 index 0000000..ade0965 --- /dev/null +++ b/src/Image/Gif.h @@ -0,0 +1,145 @@ +#ifndef _GIF_H_ +#define _GIF_H_ + +#include +#include "Decoder.h" + +#define GIF_MAX_LZW_CODE_LENGTH 12 +#define GIF_MAX_DICTIONARY_ENTRIES (1 << (GIF_MAX_LZW_CODE_LENGTH + 1)) + +#define GIF_INTERLACE_BIT 0x40 +#define GIF_LINE_BUFFER_MAX_SIZE 640 + +class GifDecoder : public ImageDecoder +{ +public: + GifDecoder(); + + virtual void Process(uint8_t* data, size_t dataLength); + +private: + + void ClearDictionary(); + + int CalculateLineIndex(int y); + void ProcessLineBuffer(); + void EmitLine(int y); + + + enum InternalState + { + ParseHeader, + ParsePalette, + ParseImageDescriptor, + ParseLocalColourTable, + ParseLZWCodeSize, + ParseDataBlock, + ParseImageSubBlockSize, + ParseImageSubBlock, + ParseExtension, + ParseExtensionContents, + ParseExtensionSubBlockSize, + ParseExtensionSubBlock, + ParseGraphicControlExtension + }; + + #pragma pack(push, 1) + struct Header + { + char versionTag[6]; // Should be GIF89a + uint16_t width; + uint16_t height; + uint8_t fields; + uint8_t backgroundColour; + uint8_t aspectRatio; + }; + + struct ImageDescriptor + { + uint16_t x, y; + uint16_t width, height; + uint8_t fields; + }; + + struct ExtensionHeader + { + uint8_t code; + uint8_t size; + }; + + struct DictionaryEntry + { + uint8_t byte; + int16_t prev; + uint16_t len; + }; + + struct GraphicControlExtension + { + uint8_t packedFields; + uint16_t delayTime; + uint8_t transparentColourIndex; + }; + #pragma pack(pop) + + InternalState internalState; + + uint8_t palette[256*3]; // RGB values + uint8_t paletteLUT[256]; // GIF palette colour to video mode palette colour + int paletteSize; + uint8_t backgroundColour; + int transparentColourIndex; + uint8_t lzwCodeSize; + + DictionaryEntry dictionary[GIF_MAX_DICTIONARY_ENTRIES]; + +// union + //{ + struct + { + // ParseHeader temporary vars + Header header; + }; + struct + { + // ParsePalette temporary vars + unsigned int paletteIndex; + uint8_t rgb[3]; + int localColourTableLength; + }; + struct + { + // ParseImageDescriptor temporary vars + ImageDescriptor imageDescriptor; + uint8_t imageSubBlockSize; + + int codeLength; + int resetCodeLength; + int clearCode; + int stopCode; + int code; + int prev; + int dictionaryIndex; + int codeBit; + + int drawX, drawY; + int outputLine; + + uint8_t lineBuffer[GIF_LINE_BUFFER_MAX_SIZE]; + int lineBufferSize; + int linesProcessed; + int lineBufferDivider; + int lineBufferSkipCount; + int lineBufferFlushCount; + }; + struct + { + // ParseExtension temporary vars + ExtensionHeader extensionHeader; + uint8_t extensionSubBlockSize; + GraphicControlExtension graphicControlExtension; + }; + //}; +}; + +#endif diff --git a/src/Image/Image.h b/src/Image/Image.h new file mode 100644 index 0000000..df52974 --- /dev/null +++ b/src/Image/Image.h @@ -0,0 +1,25 @@ +#ifndef _IMAGE_H_ +#define _IMAGE_H_ + +#include + +#include "../Memory/MemBlock.h" + +struct ImageMetadata +{ + uint16_t width; + uint16_t height; + uint16_t pitch; + uint8_t bpp; +}; + +struct Image : ImageMetadata +{ + Image() + { + width = height = pitch = bpp = 0; + } + MemBlockHandle lines; +}; + +#endif diff --git a/src/Image/Jpeg.cpp b/src/Image/Jpeg.cpp new file mode 100644 index 0000000..f028aec --- /dev/null +++ b/src/Image/Jpeg.cpp @@ -0,0 +1,114 @@ +#include "Jpeg.h" +#include "Image.h" +#include "../Platform.h" + +// Start of Frame markers, non-differential, Huffman coding +const uint8_t SOF0 = 0xC0; // Baseline DCT +const uint8_t SOF1 = 0xC1; // Extended sequential DCT +const uint8_t SOF2 = 0xC2; // Progressive DCT +const uint8_t SOF3 = 0xC3; // Lossless (sequential) + +// Start of Frame markers, differential, Huffman coding +const uint8_t SOF5 = 0xC5; // Differential sequential DCT +const uint8_t SOF6 = 0xC6; // Differential progressive DCT +const uint8_t SOF7 = 0xC7; // Differential lossless (sequential) + +// Start of Frame markers, non-differential, arithmetic coding +const uint8_t SOF9 = 0xC9; // Extended sequential DCT +const uint8_t SOF10 = 0xCA; // Progressive DCT +const uint8_t SOF11 = 0xCB; // Lossless (sequential) + +// Start of Frame markers, differential, arithmetic coding +const uint8_t SOF13 = 0xCD; // Differential sequential DCT +const uint8_t SOF14 = 0xCE; // Differential progressive DCT +const uint8_t SOF15 = 0xCF; // Differential lossless (sequential) + +const uint8_t SOI = 0xD8; // Start of Image +const uint8_t EOI = 0xD9; // End of Image + +JpegDecoder::JpegDecoder() + : internalState(ParseStartMarker) +{ +} + +void JpegDecoder::Process(uint8_t* data, size_t dataLength) +{ + if (state != ImageDecoder::Decoding) + { + return; + } + + while (dataLength > 0) + { + switch (internalState) + { + case ParseStartMarker: + if (FillStruct(&data, dataLength, &marker, sizeof(marker))) + { + if (marker.highByte != 0xff || marker.lowByte != SOI) + { + state = ImageDecoder::Error; + return; + } + internalState = ParseMarker; + } + break; + case ParseMarker: + if (FillStruct(&data, dataLength, &marker, sizeof(marker))) + { + if (marker.highByte != 0xff) + { + state = ImageDecoder::Error; + return; + } + + switch (marker.lowByte) + { + case EOI: + state = ImageDecoder::Success; + return; + case SOF0: + case SOF2: + internalState = ParseStartOfFrame; + break; + default: + internalState = ParseSegmentLength; + break; + } + } + break; + case ParseSegmentLength: + if (FillStruct(&data, dataLength, &segmentLength, sizeof(segmentLength))) + { + segmentLength = segmentLength - 2; // Reduce by 2 uint8_ts for the length value + internalState = SkipSegment; + } + break; + case SkipSegment: + if(SkipBytes(&data, dataLength, segmentLength)) + { + internalState = ParseMarker; + } + break; + case ParseStartOfFrame: + if (FillStruct(&data, dataLength, &frameHeader, sizeof(FrameHeader))) + { + if (outputImage->width == 0 || outputImage->height == 0) + { + CalculateImageDimensions(frameHeader.width, frameHeader.height); + + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; + } + } + + state = ImageDecoder::Success; + return; + } + break; + } + } +} + diff --git a/src/Image/Jpeg.h b/src/Image/Jpeg.h new file mode 100644 index 0000000..d857dfa --- /dev/null +++ b/src/Image/Jpeg.h @@ -0,0 +1,41 @@ +#ifndef _JPEG_H_ +#define _JPEG_H_ + +#include "Decoder.h" + +class JpegDecoder : public ImageDecoder +{ +public: + JpegDecoder(); + virtual void Process(uint8_t* data, size_t dataLength) override; + +private: + enum InternalState + { + ParseStartMarker, + ParseMarker, + ParseSegmentLength, + SkipSegment, + ParseStartOfFrame + }; + +#pragma pack(push, 1) + struct FrameHeader + { + uint16_be length; + uint8_t bpp; + uint16_be height; + uint16_be width; + uint8_t numComponents; + }; +#pragma pack(pop) + + InternalState internalState; + + uint16_be marker; + uint16_be segmentLength; + + FrameHeader frameHeader; +}; + +#endif diff --git a/src/Image/Png.cpp b/src/Image/Png.cpp new file mode 100644 index 0000000..83d2937 --- /dev/null +++ b/src/Image/Png.cpp @@ -0,0 +1,83 @@ +#include +#include "Png.h" +#include "Image.h" +#include "../Platform.h" + +static uint8_t pngSignature[PNG_SIGNATURE_LENGTH] = +{ + 137, 80, 78, 71, 13, 10, 26, 10 +}; + +PngDecoder::PngDecoder() + : internalState(ParseSignature) +{ +} + +void PngDecoder::Process(uint8_t* data, size_t dataLength) +{ + if (state != ImageDecoder::Decoding) + { + return; + } + + while (dataLength > 0) + { + switch (internalState) + { + case ParseSignature: + if (FillStruct(&data, dataLength, signature, PNG_SIGNATURE_LENGTH)) + { + if(memcmp(signature, pngSignature, PNG_SIGNATURE_LENGTH)) + { + state = ImageDecoder::Error; + return; + } + internalState = ParseChunkHeader; + } + break; + case ParseChunkHeader: + if (FillStruct(&data, dataLength, &chunkHeader, sizeof(ChunkHeader))) + { + if (!memcmp(chunkHeader.type, "IEND", 4)) + { + state = ImageDecoder::Success; + return; + } + if (!memcmp(chunkHeader.type, "IHDR", 4)) + { + internalState = ParseImageHeader; + } + else + { + internalState = SkipChunk; + } + } + break; + case SkipChunk: + if (SkipBytes(&data, dataLength, chunkHeader.length + 4)) // +4 as we are just going to skip CRC check + { + internalState = ParseChunkHeader; + } + break; + case ParseImageHeader: + if (FillStruct(&data, dataLength, &imageHeader, sizeof(ImageHeader))) + { + if (outputImage->width == 0 || outputImage->height == 0) + { + CalculateImageDimensions(imageHeader.width, imageHeader.height); + + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; + } + } + + state = ImageDecoder::Success; + return; + } + break; + } + } +} + diff --git a/src/Image/Png.h b/src/Image/Png.h new file mode 100644 index 0000000..b932e1b --- /dev/null +++ b/src/Image/Png.h @@ -0,0 +1,49 @@ +#ifndef _PNG_H_ +#define _PNG_H_ + +#include "Decoder.h" + +#define PNG_SIGNATURE_LENGTH 8 + +class PngDecoder : public ImageDecoder +{ +public: + PngDecoder(); + virtual void Process(uint8_t* data, size_t dataLength) override; + +private: + enum InternalState + { + ParseSignature, + ParseChunkHeader, + SkipChunk, + ParseImageHeader, + }; + +#pragma pack(push, 1) + struct ChunkHeader + { + uint32_be length; + char type[4]; + }; + + struct ImageHeader + { + uint32_be width; + uint32_be height; + uint8_t bitDepth; + uint8_t colourType; + uint8_t compressionMethod; + uint8_t filterMethod; + uint8_t interlaceMode; + }; +#pragma pack(pop) + + InternalState internalState; + + uint8_t signature[PNG_SIGNATURE_LENGTH]; + ChunkHeader chunkHeader; + ImageHeader imageHeader; +}; + +#endif diff --git a/src/Interface.cpp b/src/Interface.cpp index d7239bb..ab7119f 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -16,117 +16,169 @@ #include "Interface.h" #include "App.h" #include "KeyCodes.h" - -#define MIN_SCROLL_WIDGET_SIZE 8 +#include "Nodes/Section.h" +#include "Nodes/Button.h" +#include "Nodes/Field.h" +#include "Nodes/LinkNode.h" +#include "Nodes/Text.h" +#include "Nodes/Status.h" +#include "Nodes/ImgNode.h" +#include "Nodes/Scroll.h" +#include "Draw/Surface.h" +#include "DataPack.h" +#include "Event.h" +#include "Memory/Memory.h" AppInterface::AppInterface(App& inApp) : app(inApp) { - GenerateWidgets(); - - hoverWidget = NULL; oldMouseX = -1; oldMouseY = -1; oldButtons = 0; - textFieldCursorPosition = 0; - clickingButton = false; + + hoverNode = nullptr; + focusedNode = nullptr; + jumpTagName = nullptr; + jumpNode = nullptr; +} + +void AppInterface::Init() +{ + GenerateInterfaceNodes(); + + SetTitle("MicroWeb"); + + DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); + DrawInterfaceNodes(context); } void AppInterface::Reset() { + scrollPositionY = 0; oldPageHeight = 0; - if (activeWidget && !activeWidget->isInterfaceWidget) + + if (focusedNode && !IsInterfaceNode(focusedNode)) { - activeWidget = NULL; + focusedNode = nullptr; } - if (hoverWidget && !hoverWidget->isInterfaceWidget) + focusedNode = nullptr; + if (hoverNode && !IsInterfaceNode(hoverNode)) { - hoverWidget = NULL; + hoverNode = nullptr; + Platform::input->SetMouseCursor(MouseCursor::Pointer); } -} + jumpTagName = nullptr; + jumpNode = nullptr; -void AppInterface::DrawInterfaceWidgets() -{ - for (int n = 0; n < NUM_APP_INTERFACE_WIDGETS; n++) - { - app.renderer.RenderWidget(appInterfaceWidgets[n]); - } - - // Page top line divider - Platform::video->HLine(0, Platform::video->windowY - 1, Platform::video->screenWidth); + ClearStatusMessage(StatusBarNode::HoverStatus); + ClearStatusMessage(StatusBarNode::GeneralStatus); + UpdatePageScrollBar(); } void AppInterface::Update() { - if (app.page.GetPageHeight() != oldPageHeight) - { - oldPageHeight = app.page.GetPageHeight(); - UpdatePageScrollBar(); - } - int buttons, mouseX, mouseY; Platform::input->GetMouseStatus(buttons, mouseX, mouseY); - Widget* oldHoverWidget = hoverWidget; + Node* oldHoverNode = hoverNode; - if (hoverWidget && !app.renderer.IsOverWidget(hoverWidget, mouseX, mouseY)) + if (hoverNode && !IsOverNode(hoverNode, mouseX, mouseY)) { - hoverWidget = PickWidget(mouseX, mouseY); + hoverNode = PickNode(mouseX, mouseY); } - else if (!hoverWidget && (mouseX != oldMouseX || mouseY != oldMouseY)) + else if (!hoverNode && (mouseX != oldMouseX || mouseY != oldMouseY)) { - hoverWidget = PickWidget(mouseX, mouseY); + hoverNode = PickNode(mouseX, mouseY); } - if ((buttons & 1) && !(oldButtons & 1)) + int clickX, clickY; + + if (Platform::input->GetMouseButtonPress(clickX, clickY)) { + hoverNode = PickNode(clickX, clickY); HandleClick(mouseX, mouseY); } - else if (!(buttons & 1) && (oldButtons & 1)) + + if ((buttons & 1) && (oldButtons & 1) && (mouseX != oldMouseX || mouseY != oldMouseY)) { - HandleRelease(); + HandleDrag(mouseX, mouseY); } - if (activeWidget == &scrollBar) + if (Platform::input->GetMouseButtonRelease(clickX, clickY)) { - if (mouseY != oldMouseY) - { - scrollBar.scrollBar->position = mouseY - scrollBar.y - scrollBarRelativeClickPositionY; - if (scrollBar.scrollBar->position < 0) - { - scrollBar.scrollBar->position = 0; - } - if (scrollBar.scrollBar->position + scrollBar.scrollBar->size > scrollBar.height) - { - scrollBar.scrollBar->position = scrollBar.height - scrollBar.scrollBar->size; - } - - Platform::input->HideMouse(); - app.renderer.RedrawScrollBar(); - Platform::input->ShowMouse(); - //Platform::input->SetMousePosition(scrollBar.x + scrollBarRelativeClickPositionX, scrollBar.y + scrollBar.scrollBar->position + scrollBarRelativeClickPositionY); - } + HandleRelease(clickX, clickY); } oldMouseX = mouseX; oldMouseY = mouseY; oldButtons = buttons; - if (hoverWidget != oldHoverWidget) + if (hoverNode != oldHoverNode) { - if (hoverWidget && hoverWidget->GetLinkURL()) - { - app.renderer.SetStatus(URL::GenerateFromRelative(app.page.pageURL.url, hoverWidget->GetLinkURL()).url); - Platform::input->SetMouseCursor(MouseCursor::Hand); - } - else if (hoverWidget && hoverWidget->type == Widget::TextField) + bool hasHoverStatusMessage = false; + + if (hoverNode) { - Platform::input->SetMouseCursor(MouseCursor::TextSelect); + switch (hoverNode->type) + { + case Node::Link: + Platform::input->SetMouseCursor(MouseCursor::Hand); + { + LinkNode::Data* linkData = static_cast(hoverNode->data); + if (linkData && linkData->url) + { + SetStatusMessage(URL::GenerateFromRelative(app.page.pageURL.url, linkData->url).url, StatusBarNode::HoverStatus); + hasHoverStatusMessage = true; + } + } + break; + case Node::TextField: + Platform::input->SetMouseCursor(MouseCursor::TextSelect); + break; + case Node::Image: + { + ImageNode::Data* imageData = static_cast(hoverNode->data); + if (imageData && imageData->altText) + { + SetStatusMessage(imageData->altText, StatusBarNode::HoverStatus); + hasHoverStatusMessage = true; + } + Platform::input->SetMouseCursor(MouseCursor::Pointer); + } + break; + default: + Platform::input->SetMouseCursor(MouseCursor::Pointer); + break; + } } else { - app.renderer.SetStatus(""); Platform::input->SetMouseCursor(MouseCursor::Pointer); } + + if (!hasHoverStatusMessage) + { + ClearStatusMessage(StatusBarNode::HoverStatus); + } + + if(0) + { + // For debugging picking + Platform::input->HideMouse(); + if (hoverNode) + { + DrawContext context; + app.pageRenderer.GenerateDrawContext(context, hoverNode); + context.surface->InvertRect(context, hoverNode->anchor.x, hoverNode->anchor.y, hoverNode->size.x, hoverNode->size.y); + } + if (oldHoverNode) + { + DrawContext context; + app.pageRenderer.GenerateDrawContext(context, oldHoverNode); + context.surface->InvertRect(context, oldHoverNode->anchor.x, oldHoverNode->anchor.y, oldHoverNode->size.x, oldHoverNode->size.y); + } + Platform::input->ShowMouse(); + } + } @@ -135,12 +187,9 @@ void AppInterface::Update() while (keyPress = Platform::input->GetKeyPress()) { - if (activeWidget) + if (focusedNode && focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::KeyPress, keyPress))) { - if (HandleActiveWidget(keyPress)) - { - continue; - } + continue; } switch (keyPress) @@ -158,47 +207,64 @@ void AppInterface::Update() scrollDelta += 8; break; case KEYCODE_PAGE_UP: - scrollDelta -= (Platform::video->windowHeight - 24); + scrollDelta -= (windowRect.height - 24); break; case KEYCODE_PAGE_DOWN: - scrollDelta += (Platform::video->windowHeight - 24); + scrollDelta += (windowRect.height - 24); break; case KEYCODE_HOME: - app.renderer.ScrollTo(0); + ScrollAbsolute(0); break; case KEYCODE_END: - app.renderer.ScrollTo(app.renderer.GetMaxScrollPosition()); + { + int end = app.pageRenderer.GetVisiblePageHeight() - windowRect.height; + if (end > 0) + { + ScrollAbsolute(end); + } + } break; case KEYCODE_BACKSPACE: app.PreviousPage(); break; case KEYCODE_F2: { - Platform::input->HideMouse(); - Platform::video->InvertScreen(); - Platform::input->ShowMouse(); + Platform::video->InvertVideoOutput(); } break; case KEYCODE_CTRL_L: case KEYCODE_F6: - ActivateWidget(&addressBar); + FocusNode(addressBarNode); + break; + case KEYCODE_F5: + app.ReloadPage(); + break; + case KEYCODE_F3: + ToggleStatusAndTitleBar(); break; case KEYCODE_TAB: - CycleWidgets(1); + CycleNodes(1); break; case KEYCODE_SHIFT_TAB: - CycleWidgets(-1); + CycleNodes(-1); break; case 'm': { - char tempMessage[50]; - snprintf(tempMessage, 50, "Allocated: %dK Used: %dK\n", (int)(app.page.allocator.TotalAllocated() / 1024), (int)(app.page.allocator.TotalUsed() / 1024)); - app.renderer.SetStatus(tempMessage); + char tempMessage[100]; + MemoryManager::GenerateMemoryReport(tempMessage); + SetStatusMessage(tempMessage, StatusBarNode::GeneralStatus); } break; + case 'n': + { +#ifdef _WIN32 + app.page.DebugDumpNodeGraph(); +#endif + } + default: // printf("%x\n", keyPress); break; @@ -207,623 +273,429 @@ void AppInterface::Update() if (scrollDelta) { - app.renderer.Scroll(scrollDelta); + ScrollRelative(scrollDelta); } -} - -void AppInterface::GenerateWidgets() -{ - appInterfaceWidgets[0] = &addressBar; - appInterfaceWidgets[1] = &scrollBar; - appInterfaceWidgets[2] = &backButton; - appInterfaceWidgets[3] = &forwardButton; - - addressBar.type = Widget::TextField; - addressBar.textField = &addressBarData; - addressBarData.buffer = addressBarURL.url; - addressBarData.bufferLength = MAX_URL_LENGTH - 1; - addressBar.style = WidgetStyle(FontStyle::Regular); - addressBar.isInterfaceWidget = true; - - scrollBar.type = Widget::ScrollBar; - scrollBar.isInterfaceWidget = true; - scrollBar.scrollBar = &scrollBarData; - scrollBarData.position = 0; - scrollBarData.size = Platform::video->windowHeight; - - backButton.isInterfaceWidget = true; - backButtonData.text = (char*) "<"; - backButtonData.form = NULL; - forwardButton.isInterfaceWidget = true; - forwardButtonData.text = (char*) ">"; - forwardButtonData.form = NULL; - - backButton.type = Widget::Button; - backButton.button = &backButtonData; - backButton.style = WidgetStyle(FontStyle::Bold); - forwardButton.type = Widget::Button; - forwardButton.button = &forwardButtonData; - forwardButton.style = WidgetStyle(FontStyle::Bold); - - titleBar.isInterfaceWidget = true; - statusBar.isInterfaceWidget = true; - - Platform::video->ArrangeAppInterfaceWidgets(*this); -} -Widget* AppInterface::PickWidget(int x, int y) -{ - for (int n = 0; n < NUM_APP_INTERFACE_WIDGETS; n++) + if (jumpNode) { - Widget* widget = appInterfaceWidgets[n]; - if (x >= widget->x && y >= widget->y && x < widget->x + widget->width && y < widget->y + widget->height) + Node* node = App::Get().ui.jumpNode; + while (node && node->size.IsZero()) { - return widget; + node = node->GetNextInTree(); } - } - - return app.renderer.PickPageWidget(x, y); -} -void AppInterface::DeactivateWidget() -{ - if (activeWidget) - { - if (activeWidget->type == Widget::Button) - { - if (clickingButton) - { - app.renderer.InvertWidget(activeWidget); - } - else - { - Widget* widget = activeWidget; - activeWidget = NULL; - app.renderer.RedrawWidget(widget); - } - } - if (activeWidget->type == Widget::TextField) + if (node) { - if (textFieldCursorPosition == -1) - { - app.renderer.InvertWidget(activeWidget); - } - else + int jumpPosition = node->anchor.y; + + if (app.page.layout.IsFinished() || jumpPosition + windowRect.height < app.pageRenderer.GetVisiblePageHeight()) { - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - } + ScrollAbsolute(jumpPosition); + jumpNode = nullptr; + } } - else if (activeWidget->GetLinkURL()) - { - InvertWidgetsWithLinkURL(activeWidget->GetLinkURL()); - } - activeWidget = NULL; } } -void AppInterface::ActivateWidget(Widget* widget) +bool AppInterface::IsOverNode(Node* node, int x, int y) { - if (activeWidget && activeWidget != widget) + if (!node) { - DeactivateWidget(); + return false; } - - activeWidget = widget; - - if (activeWidget) + if (IsInterfaceNode(node)) + { + return node->IsPointInsideChildren(x, y); + } + else { - switch (activeWidget->type) + if (x >= windowRect.x && y >= windowRect.y && x < windowRect.x + windowRect.width && y < windowRect.y + windowRect.height) { - case Widget::Button: - if (clickingButton) - { - app.renderer.InvertWidget(activeWidget); - } - else - { - app.renderer.RedrawWidget(activeWidget); - } - break; - case Widget::TextField: - if (activeWidget == &addressBar && strlen(activeWidget->textField->buffer) > 0) - { - textFieldCursorPosition = -1; - app.renderer.InvertWidget(activeWidget); - } - else - { - textFieldCursorPosition = strlen(activeWidget->textField->buffer); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - break; - default: - if (widget->GetLinkURL()) - { - InvertWidgetsWithLinkURL(widget->GetLinkURL()); - app.renderer.SetStatus(URL::GenerateFromRelative(app.page.pageURL.url, widget->GetLinkURL()).url); - } - break; + x -= windowRect.x; + y -= windowRect.y - scrollPositionY; + return node->IsPointInsideChildren(x, y); } + return false; } } -void AppInterface::InvertWidgetsWithLinkURL(const char* url) +Node* AppInterface::PickNode(int x, int y) { - for (int n = app.renderer.GetPageTopWidgetIndex(); n < app.page.numFinishedWidgets; n++) + Node* interfaceNode = rootInterfaceNode->Handler().Pick(rootInterfaceNode, x, y); + + if (interfaceNode) { - Widget* pageWidget = &app.page.widgets[n]; - if (pageWidget->y > app.renderer.GetScrollPosition() + Platform::video->windowHeight) - { - break; - } - if (pageWidget->GetLinkURL() == url) - { - app.renderer.InvertWidget(pageWidget); - } + return interfaceNode; + } + + if (x >= windowRect.x && y >= windowRect.y && x < windowRect.x + windowRect.width && y < windowRect.y + windowRect.height) + { + int pageX = x - windowRect.x; + int pageY = y - windowRect.y + scrollPositionY; + + Node* pageRootNode = app.page.GetRootNode(); + return pageRootNode->Handler().Pick(pageRootNode, pageX, pageY); } + + return nullptr; } void AppInterface::HandleClick(int mouseX, int mouseY) { - if (activeWidget && activeWidget != hoverWidget) + if (hoverNode) { - DeactivateWidget(); + hoverNode->Handler().HandleEvent(hoverNode, Event(app, Event::MouseClick, mouseX, mouseY)); } + else if (focusedNode) + { + FocusNode(nullptr); + } +} - if (hoverWidget) +void AppInterface::HandleDrag(int mouseX, int mouseY) +{ + if (focusedNode) { - if (hoverWidget->GetLinkURL()) - { - app.OpenURL(URL::GenerateFromRelative(app.page.pageURL.url, hoverWidget->GetLinkURL()).url); - } - else if (hoverWidget->type == Widget::TextField) - { - bool shouldPick = hoverWidget != &addressBar || hoverWidget == activeWidget; - if (hoverWidget != activeWidget) - { - ActivateWidget(hoverWidget); - } + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseDrag, mouseX, mouseY)); + } +} - if(shouldPick) - { - if (hoverWidget == activeWidget && textFieldCursorPosition == -1) - { - app.renderer.InvertWidget(activeWidget); - } - activeWidget = hoverWidget; +void AppInterface::HandleRelease(int mouseX, int mouseY) +{ + if (focusedNode) + { + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseRelease, mouseX, mouseY)); + } - int pickX = activeWidget->x + 3; - int pickIndex = 0; - for (char* str = activeWidget->textField->buffer; *str; str++, pickIndex++) - { - if (mouseX < pickX) - { - break; - } - pickX += Platform::video->GetGlyphWidth(*str); - } +} - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - textFieldCursorPosition = pickIndex; - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - } - else if (hoverWidget->type == Widget::Button) - { - clickingButton = true; - ActivateWidget(hoverWidget); - } - else if (hoverWidget->type == Widget::ScrollBar) - { - //activeWidget = hoverWidget; - int relPos = mouseY - hoverWidget->y; +void AppInterface::UpdateAddressBar(const URL& url) +{ + addressBarURL = url; + addressBarNode->Redraw(); - if (relPos < hoverWidget->scrollBar->position) - { - app.renderer.Scroll(-(Platform::video->windowHeight - 24)); - } - else if (relPos > hoverWidget->scrollBar->position + hoverWidget->scrollBar->size) - { - app.renderer.Scroll(Platform::video->windowHeight - 24); - } - else - { - activeWidget = hoverWidget; - scrollBarRelativeClickPositionX = mouseX - hoverWidget->x; - scrollBarRelativeClickPositionY = relPos - hoverWidget->scrollBar->position; - } - } - } + jumpTagName = strstr(url.url, "#"); } -void AppInterface::HandleButtonClicked(Widget* widget) +void AppInterface::UpdatePageScrollBar() { - if(widget->type == Widget::Button) - { - if (widget->button->form) - { - SubmitForm(widget->button->form); - } - else if (widget == &backButton) - { - app.PreviousPage(); - } - else if (widget == &forwardButton) - { - app.NextPage(); - } - } + ScrollBarNode::Data* data = static_cast(scrollBarNode->data); + int maxScrollHeight = app.pageRenderer.GetVisiblePageHeight() - app.ui.windowRect.height; + if (maxScrollHeight < 0) + maxScrollHeight = 0; + data->scrollPosition = scrollPositionY; + data->maxScroll = maxScrollHeight; + scrollBarNode->Redraw(); } -void AppInterface::HandleRelease() +void AppInterface::GenerateInterfaceNodes() { - if (activeWidget && activeWidget->type == Widget::Button) - { - if (clickingButton) - { - if (hoverWidget == activeWidget) - { - HandleButtonClicked(activeWidget); - } - DeactivateWidget(); + Allocator& allocator = MemoryManager::interfaceAllocator; + ElementStyle rootInterfaceStyle; + rootInterfaceStyle.alignment = ElementAlignment::Left; + rootInterfaceStyle.fontSize = 1; + rootInterfaceStyle.fontStyle = FontStyle::Regular; + rootInterfaceStyle.fontColour = Platform::video->colourScheme.textColour; - activeWidget = NULL; - clickingButton = false; - } + rootInterfaceNode = SectionElement::Construct(allocator, SectionElement::Interface); + rootInterfaceNode->SetStyle(rootInterfaceStyle); + + Font* interfaceFont = Assets.GetFont(1, FontStyle::Regular); + Font* smallInterfaceFont = Assets.GetFont(0, FontStyle::Regular); + + { + titleBuffer[0] = '\0'; + TextElement::Data* titleNodeData = allocator.Alloc(MemBlockHandle (titleBuffer)); + titleNode = allocator.Alloc(Node::Text, titleNodeData); + titleNode->anchor.Clear(); + titleNode->size.x = Platform::video->screenWidth; + titleNode->size.y = interfaceFont->glyphHeight; + titleNode->styleHandle = rootInterfaceNode->styleHandle; + rootInterfaceNode->AddChild(titleNode); } - else if (activeWidget == &scrollBar) + + backButtonNode = ButtonNode::Construct(allocator, " < ", OnBackButtonPressed); + backButtonNode->styleHandle = rootInterfaceNode->styleHandle; + backButtonNode->size = ButtonNode::CalculateSize(backButtonNode); + backButtonNode->anchor.x = 1; + backButtonNode->anchor.y = titleNode->size.y; + rootInterfaceNode->AddChild(backButtonNode); + + forwardButtonNode = ButtonNode::Construct(allocator, " > ", OnForwardButtonPressed); + forwardButtonNode->styleHandle = rootInterfaceNode->styleHandle; + forwardButtonNode->size = ButtonNode::CalculateSize(forwardButtonNode); + forwardButtonNode->anchor.x = backButtonNode->anchor.x + backButtonNode->size.x + 2; + forwardButtonNode->anchor.y = titleNode->size.y; + rootInterfaceNode->AddChild(forwardButtonNode); + + addressBarNode = TextFieldNode::Construct(allocator, addressBarURL.url, MAX_URL_LENGTH - 1, OnAddressBarSubmit); + addressBarNode->styleHandle = rootInterfaceNode->styleHandle; + addressBarNode->anchor.x = forwardButtonNode->anchor.x + forwardButtonNode->size.x + 2; + addressBarNode->anchor.y = titleNode->size.y; + addressBarNode->size.x = Platform::video->screenWidth - addressBarNode->anchor.x - 1; + addressBarNode->size.y = backButtonNode->size.y; + rootInterfaceNode->AddChild(addressBarNode); + + statusBarNode = StatusBarNode::Construct(allocator); + statusBarNode->size.x = Platform::video->screenWidth; + statusBarNode->size.y = smallInterfaceFont->glyphHeight + 2; + statusBarNode->anchor.x = 0; + statusBarNode->anchor.y = Platform::video->screenHeight - statusBarNode->size.y; + rootInterfaceNode->AddChild(statusBarNode); + + ElementStyle statusBarStyle = rootInterfaceStyle; + statusBarStyle.fontSize = 0; + statusBarNode->SetStyle(statusBarStyle); + + scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight, OnScrollBarMoved); + scrollBarNode->styleHandle = rootInterfaceNode->styleHandle; + scrollBarNode->anchor.y = backButtonNode->anchor.y + backButtonNode->size.y + 2; + scrollBarNode->size.x = 16; + scrollBarNode->size.y = Platform::video->screenHeight - scrollBarNode->anchor.y - statusBarNode->size.y; + scrollBarNode->anchor.x = Platform::video->screenWidth - scrollBarNode->size.x; + rootInterfaceNode->AddChild(scrollBarNode); + + windowRect.x = 0; + windowRect.y = backButtonNode->anchor.y + backButtonNode->size.y + 2; + windowRect.width = Platform::video->screenWidth - scrollBarNode->size.x; + windowRect.height = Platform::video->screenHeight - windowRect.y - statusBarNode->size.y; + + pageHeightForDimensionScaling = windowRect.height; + + StylePool::Get().MarkInterfaceStylesComplete(); + + for (Node* node = rootInterfaceNode; node; node = node->GetNextInTree()) { - if (scrollBar.scrollBar->size < scrollBar.height) - { - int targetScroll = ((int32_t) app.renderer.GetMaxScrollPosition() * scrollBar.scrollBar->position) / (scrollBar.height - scrollBar.scrollBar->size); - app.renderer.ScrollTo(targetScroll); - } - activeWidget = NULL; + node->isLayoutComplete = true; } } -bool AppInterface::HandleActiveWidget(InputButtonCode keyPress) +void AppInterface::DrawInterfaceNodes(DrawContext& context) { - switch (activeWidget->type) - { - case Widget::TextField: - { - if(activeWidget->textField && activeWidget->textField->buffer) - { - if (keyPress >= 32 && keyPress < 128) - { - if (textFieldCursorPosition == -1) - { - activeWidget->textField->buffer[0] = '\0'; - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = 0; - } + Platform::input->HideMouse(); + Platform::video->drawSurface->Clear(); - if (textFieldCursorPosition < activeWidget->textField->bufferLength) - { - int len = strlen(activeWidget->textField->buffer); - for (int n = len; n >= textFieldCursorPosition; n--) - { - activeWidget->textField->buffer[n + 1] = activeWidget->textField->buffer[n]; - } - activeWidget->textField->buffer[textFieldCursorPosition++] = (char)(keyPress); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition - 1, true); - app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition - 1); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - else if (keyPress == KEYCODE_BACKSPACE) - { - if (textFieldCursorPosition == -1) - { - activeWidget->textField->buffer[0] = '\0'; - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = 0; - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - else if (textFieldCursorPosition > 0) - { - int len = strlen(activeWidget->textField->buffer); - for (int n = textFieldCursorPosition - 1; n < len; n++) - { - activeWidget->textField->buffer[n] = activeWidget->textField->buffer[n + 1]; - } - textFieldCursorPosition--; - app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - else if (keyPress == KEYCODE_DELETE) - { - int len = strlen(activeWidget->textField->buffer); - if (textFieldCursorPosition == -1) - { - activeWidget->textField->buffer[0] = '\0'; - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = 0; - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - else if (textFieldCursorPosition < len) - { - for (int n = textFieldCursorPosition; n < len; n++) - { - activeWidget->textField->buffer[n] = activeWidget->textField->buffer[n + 1]; - } - app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - else if (keyPress == KEYCODE_ENTER) - { - if (activeWidget == &addressBar) - { - app.OpenURL(addressBarURL.url); - } - else if (activeWidget->textField->form) - { - SubmitForm(activeWidget->textField->form); - } - DeactivateWidget(); - return true; - } - else if (keyPress == KEYCODE_ARROW_LEFT) - { - if (textFieldCursorPosition == -1) - { - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = strlen(activeWidget->textField->buffer); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - else if (textFieldCursorPosition > 0) - { - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - textFieldCursorPosition--; - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - else if (keyPress == KEYCODE_ARROW_RIGHT) - { - if (textFieldCursorPosition == -1) - { - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = strlen(activeWidget->textField->buffer); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - else if (textFieldCursorPosition < strlen(activeWidget->textField->buffer)) - { - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - textFieldCursorPosition++; - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - else if (keyPress == KEYCODE_END) - { - if (textFieldCursorPosition == -1) - { - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = strlen(activeWidget->textField->buffer); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - else - { - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - textFieldCursorPosition = strlen(activeWidget->textField->buffer); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - else if (keyPress == KEYCODE_HOME) - { - if (textFieldCursorPosition == -1) - { - app.renderer.RedrawWidget(activeWidget); - textFieldCursorPosition = strlen(activeWidget->textField->buffer); - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - else - { - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - textFieldCursorPosition = 0; - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); - } - return true; - } - } - } - break; - case Widget::Button: - if (clickingButton) - { - return true; - } - if (keyPress == KEYCODE_ENTER) - { - HandleButtonClicked(activeWidget); - } - break; - default: - if (keyPress == KEYCODE_ENTER && activeWidget->GetLinkURL()) - { - app.OpenURL(URL::GenerateFromRelative(app.page.pageURL.url, activeWidget->GetLinkURL()).url); - return true; - } - break; - } + app.pageRenderer.DrawAll(context, rootInterfaceNode); - return false; + uint8_t dividerColour = Platform::video->colourScheme.textColour; + context.surface->HLine(context, 0, windowRect.y - 1, Platform::video->screenWidth, dividerColour); + Platform::input->ShowMouse(); } -void AppInterface::SubmitForm(WidgetFormData* form) +void AppInterface::SetTitle(const char* title) { - if (form->method == WidgetFormData::Get) + strncpy(titleBuffer, title, MAX_TITLE_LENGTH); + titleBuffer[MAX_TITLE_LENGTH - 1] = '\0'; + Font* font = titleNode->GetStyleFont(); + int titleWidth = font->CalculateWidth(titleBuffer, titleNode->GetStyle().fontStyle); + titleNode->anchor.x = Platform::video->screenWidth / 2 - titleWidth / 2; + if (titleNode->anchor.x < 0) { - char* address = addressBarURL.url; - strcpy(address, form->action); - int numParams = 0; + titleNode->anchor.x = 0; + } - for (int n = 0; n < app.page.numFinishedWidgets; n++) - { - Widget& widget = app.page.widgets[n]; + DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); + Platform::input->HideMouse(); + uint8_t fillColour = Platform::video->colourScheme.pageColour; + context.surface->FillRect(context, 0, titleNode->anchor.y, Platform::video->screenWidth, titleNode->size.y, fillColour); + titleNode->Handler().Draw(context, titleNode); + Platform::input->ShowMouse(); +} - switch (widget.type) - { - case Widget::TextField: - if (widget.textField->form == form && widget.textField->name && widget.textField->buffer) - { - if (numParams == 0) - { - strcat(address, "?"); - } - else - { - strcat(address, "&"); - } - strcat(address, widget.textField->name); - strcat(address, "="); - strcat(address, widget.textField->buffer); - numParams++; - } - break; - } +bool AppInterface::IsInterfaceNode(Node* node) +{ + return node == rootInterfaceNode || (node && node->parent == rootInterfaceNode); +} + +bool AppInterface::FocusNode(Node* node) +{ + if (node != focusedNode) + { + if (focusedNode) + { + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::Unfocus)); } - // Replace any spaces with + - for (char* p = address; *p; p++) + focusedNode = node; + + if (focusedNode) { - if (*p == ' ') + if (!focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::Focus))) { - *p = '+'; + focusedNode = nullptr; + return false; } } + } + + return true; +} + +void AppInterface::OnBackButtonPressed(Node* node) +{ + App::Get().PreviousPage(); +} - app.OpenURL(URL::GenerateFromRelative(app.page.pageURL.url, address).url); +void AppInterface::OnForwardButtonPressed(Node* node) +{ + App::Get().NextPage(); +} + +void AppInterface::OnAddressBarSubmit(Node* node) +{ + App& app = App::Get(); + TextFieldNode::Data* data = static_cast(node->data); + app.OpenURL(data->buffer); + app.ui.FocusNode(nullptr); +} + +void AppInterface::SetStatusMessage(const char* message, StatusBarNode::StatusType type) +{ + if (message) + { + StatusBarNode::SetStatus(statusBarNode, message, type); } + else ClearStatusMessage(type); } -void AppInterface::UpdateAddressBar(const URL& url) +void AppInterface::ClearStatusMessage(StatusBarNode::StatusType type) { - addressBarURL = url; - app.renderer.RedrawWidget(&addressBar); + StatusBarNode::SetStatus(statusBarNode, "", type); } -void AppInterface::UpdatePageScrollBar() +void AppInterface::ScrollRelative(int delta) { - int pageHeight = app.page.GetPageHeight(); - int windowHeight = Platform::video->windowHeight; - int scrollWidgetSize = pageHeight < windowHeight ? windowHeight : (int)(((int32_t)windowHeight * windowHeight) / pageHeight); - if (scrollWidgetSize < MIN_SCROLL_WIDGET_SIZE) - scrollWidgetSize = MIN_SCROLL_WIDGET_SIZE; - int maxWidgetPosition = windowHeight - scrollWidgetSize; + int oldScrollPositionY = scrollPositionY; - int maxScroll = app.renderer.GetMaxScrollPosition(); - int widgetPosition = maxScroll == 0 ? 0 : (int32_t)maxWidgetPosition * app.renderer.GetScrollPosition() / maxScroll; + scrollPositionY += delta; + if (scrollPositionY < 0) + scrollPositionY = 0; - scrollBar.scrollBar->size = scrollWidgetSize; + int maxScrollY = app.pageRenderer.GetVisiblePageHeight() - windowRect.height; + if (maxScrollY < 0) + maxScrollY = 0; - if (activeWidget != &scrollBar) + if (scrollPositionY > maxScrollY) { - scrollBar.scrollBar->position = widgetPosition; + scrollPositionY = maxScrollY; } - app.renderer.RedrawScrollBar(); + delta = scrollPositionY - oldScrollPositionY; + + UpdatePageScrollBar(); + + app.pageRenderer.OnPageScroll(delta); } -void AppInterface::CycleWidgets(int direction) +void AppInterface::ScrollAbsolute(int position) { - if (app.page.numFinishedWidgets == 0) + int delta = position - scrollPositionY; + if (delta) { - return; + ScrollRelative(delta); } +} - int pageScrollBottom = app.renderer.GetScrollPosition() + Platform::video->windowHeight; +void AppInterface::CycleNodes(int direction) +{ + Node* node = focusedNode; - // If there is an active widget, find index and check if it is on screen - int currentPos = -1; - if (activeWidget && !activeWidget->isInterfaceWidget) + if (!node) { - for (int n = 0; n < app.page.numFinishedWidgets; n++) - { - Widget* widget = &app.page.widgets[n]; - if (activeWidget == widget) - { - if (app.renderer.GetScrollPosition() < widget->y + widget->height && pageScrollBottom > widget->y) - { - currentPos = n; - } - break; - } - } + node = app.page.GetRootNode(); + } + + bool isFocusedNodeVisible = false; + if (focusedNode) + { + Rect nodeRect; + node->CalculateEncapsulatingRect(nodeRect); + bool offPage = (nodeRect.y + nodeRect.height < scrollPositionY || nodeRect.y > scrollPositionY + windowRect.height); + isFocusedNodeVisible = !offPage; + if (!isFocusedNodeVisible) + node = app.page.GetRootNode(); } - // If there is no active widget or not on screen, find the first widget on screen - if (currentPos == -1) + if (node) { - if (direction > 0) + if (!IsInterfaceNode(node)) { - for (currentPos = app.renderer.GetPageTopWidgetIndex(); currentPos < app.page.numFinishedWidgets; currentPos++) + while (node) { - Widget* widget = &app.page.widgets[currentPos]; - if (widget->y >= app.renderer.GetScrollPosition()) + if (direction > 0) { - break; + node = node->GetNextInTree(); } - } - - if (currentPos == app.page.numFinishedWidgets) - { - currentPos = 0; - } - } - else - { - for (currentPos = app.page.numFinishedWidgets - 1; currentPos >= 0; currentPos--) - { - Widget* widget = &app.page.widgets[currentPos]; - if (widget->y + widget->height < pageScrollBottom) + else { - break; + node = node->GetPreviousInTree(); } - } - if (currentPos < 0) - { - currentPos = app.page.numFinishedWidgets - 1; + if (node && node->Handler().CanPick(node)) + { + Rect nodeRect; + node->CalculateEncapsulatingRect(nodeRect); + + if (!isFocusedNodeVisible && (nodeRect.y + nodeRect.height < scrollPositionY || nodeRect.y > scrollPositionY + windowRect.height)) + { + // Not visible on page and we haven't selected anything yet, try find something else to focus on + continue; + } + + if (nodeRect.y < scrollPositionY) + { + ScrollAbsolute(nodeRect.y); + } + else if (nodeRect.y + nodeRect.height > scrollPositionY + windowRect.height) + { + ScrollAbsolute(nodeRect.y + nodeRect.height - windowRect.height); + } + + if(FocusNode(node)) + return; + } } } } - int startPos = currentPos; + FocusNode(nullptr); +} - while (1) - { - Widget* widget = &app.page.widgets[currentPos]; - if (widget != activeWidget) - { - if ((widget->type == Widget::TextField) || (widget->type == Widget::Button) || (widget->GetLinkURL() && widget->GetLinkURL() != activeWidget->GetLinkURL())) - { - ActivateWidget(widget); - app.renderer.ScrollTo(widget); - return; - } - } +void AppInterface::OnScrollBarMoved(Node* node) +{ + ScrollBarNode::Data* data = static_cast(node->data); + AppInterface& ui = App::Get().ui; + ui.ScrollRelative(data->scrollPosition - ui.scrollPositionY); +} - currentPos += direction; - if (currentPos >= app.page.numFinishedWidgets) - { - currentPos = 0; - } - if (currentPos < 0) - { - currentPos = app.page.numFinishedWidgets - 1; - } - if (currentPos == startPos) - { - break; - } +void AppInterface::ToggleStatusAndTitleBar() +{ + int upperShift = titleNode->size.y; + int lowerShift = statusBarNode->size.y; + + + if (titleNode->anchor.y < 0) + { + upperShift = -upperShift; + lowerShift = -lowerShift; } + + titleNode->anchor.y -= upperShift; + windowRect.y -= upperShift; + windowRect.height += upperShift; + backButtonNode->anchor.y -= upperShift; + forwardButtonNode->anchor.y -= upperShift; + addressBarNode->anchor.y -= upperShift; + scrollBarNode->anchor.y -= upperShift; + scrollBarNode->size.y += upperShift; + statusBarNode->anchor.y += lowerShift; + scrollBarNode->size.y += lowerShift; + windowRect.height += lowerShift; + + Platform::input->HideMouse(); + DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + DrawInterfaceNodes(context); + app.pageRenderer.RefreshAll(); + Platform::input->ShowMouse(); } diff --git a/src/Interface.h b/src/Interface.h index dfbb8cc..bdff03e 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -12,68 +12,96 @@ // GNU General Public License for more details. // -#pragma once -#include "Widget.h" +#ifndef _INTERFACE_H_ +#define _INTERFACE_H_ #include "Platform.h" #include "URL.h" +#include "Node.h" +#include "Nodes/Status.h" -#define NUM_APP_INTERFACE_WIDGETS 4 +#define MAX_TITLE_LENGTH 80 class App; +class Node; +struct DrawContext; class AppInterface { public: AppInterface(App& inApp); + void Init(); void Reset(); void Update(); - void DrawInterfaceWidgets(); + void DrawInterfaceNodes(DrawContext& context); void UpdateAddressBar(const URL& url); + void SetStatusMessage(const char* message, StatusBarNode::StatusType type); + void ClearStatusMessage(StatusBarNode::StatusType type); + void UpdatePageScrollBar(); - Widget* GetActiveWidget() { return activeWidget; } - int GetTextFieldCursorPosition() { return textFieldCursorPosition; } - Widget scrollBar; - Widget addressBar; - Widget backButton; - Widget forwardButton; + void SetTitle(const char* title); + + bool FocusNode(Node* node); + Node* GetFocusedNode() { return focusedNode; } + Node* GetHoverNode() { return hoverNode; } + Node* GetRootInterfaceNode() { return rootInterfaceNode; } + bool IsInterfaceNode(Node* node); + bool IsOverNode(Node* node, int x, int y); - Widget titleBar; - Widget statusBar; + int GetScrollPositionY() { return scrollPositionY; } + void ScrollRelative(int delta); + void ScrollAbsolute(int position); + int GetPageHeightForDimensionScaling() { return pageHeightForDimensionScaling; } + + Node* addressBarNode; + URL addressBarURL; + + Rect windowRect; + + // For jumping to #name in the page + const char* jumpTagName; + Node* jumpNode; private: - void GenerateWidgets(); - Widget* PickWidget(int x, int y); + void GenerateInterfaceNodes(); + + Node* PickNode(int x, int y); + void HandleClick(int mouseX, int mouseY); - void HandleRelease(); - bool HandleActiveWidget(InputButtonCode keyPress); - void SubmitForm(WidgetFormData* form); - void CycleWidgets(int direction); - void InvertWidgetsWithLinkURL(const char* url); - void HandleButtonClicked(Widget* widget); + void HandleRelease(int mouseX, int mouseY); + void HandleDrag(int mouseX, int mouseY); + + void CycleNodes(int direction); - void ActivateWidget(Widget* widget); - void DeactivateWidget(); + void ToggleStatusAndTitleBar(); + + static void OnBackButtonPressed(Node* node); + static void OnForwardButtonPressed(Node* node); + static void OnAddressBarSubmit(Node* node); + static void OnScrollBarMoved(Node* node); - Widget* appInterfaceWidgets[NUM_APP_INTERFACE_WIDGETS]; App& app; - Widget* activeWidget; - Widget* hoverWidget; + Node* focusedNode; + Node* hoverNode; int oldButtons; int oldMouseX, oldMouseY; - URL addressBarURL; - int scrollBarRelativeClickPositionX; - int scrollBarRelativeClickPositionY; int oldPageHeight; - int textFieldCursorPosition; - bool clickingButton; - ButtonWidgetData backButtonData; - ButtonWidgetData forwardButtonData; - TextFieldWidgetData addressBarData; - ScrollBarData scrollBarData; + Node* rootInterfaceNode; + Node* titleNode; + Node* backButtonNode; + Node* forwardButtonNode; + Node* statusBarNode; + Node* scrollBarNode; + + int scrollPositionY; + int pageHeightForDimensionScaling; + + char titleBuffer[MAX_TITLE_LENGTH]; }; + +#endif diff --git a/src/KeyCodes.h b/src/KeyCodes.h index 8ad5d81..6c04e19 100644 --- a/src/KeyCodes.h +++ b/src/KeyCodes.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _KEYCODES_H_ +#define _KEYCODES_H_ #define KEYCODE_ARROW_UP 0x4800 #define KEYCODE_ARROW_DOWN 0x5000 @@ -26,8 +27,8 @@ #define KEYCODE_HOME 0x4700 #define KEYCODE_END 0x4f00 -#define KEYCODE_MOUSE_LEFT 0xfe00 -#define KEYCODE_MOUSE_RIGHT 0xff00 +//#define KEYCODE_MOUSE_LEFT 0xfe00 +//#define KEYCODE_MOUSE_RIGHT 0xff00 #define KEYCODE_DELETE 0x5300 @@ -49,4 +50,6 @@ #define KEYCODE_F7 0x4100 #define KEYCODE_F8 0x4200 #define KEYCODE_F9 0x4300 -#define KEYCODE_F10 0x4400 \ No newline at end of file +#define KEYCODE_F10 0x4400 + +#endif diff --git a/src/Layout.cpp b/src/Layout.cpp new file mode 100644 index 0000000..7d5adb4 --- /dev/null +++ b/src/Layout.cpp @@ -0,0 +1,369 @@ +#include "VidModes.h" +#include "Layout.h" +#include "Page.h" +#include "Platform.h" +#include "App.h" +#include "Render.h" +#include "Nodes/ImgNode.h" + +Layout::Layout(Page& inPage) + : page(inPage), cursorStack(MemoryManager::pageAllocator), paramStack(MemoryManager::pageAllocator) +{ +} + +void Layout::Reset() +{ + paramStack.Reset(); + cursorStack.Reset(); + currentLineHeight = 0; + lineStartNode = nullptr; + lastNodeContext = nullptr; + currentNodeToProcess = nullptr; + Cursor().Clear(); + tableDepth = 0; + isFinished = false; + + LayoutParams& params = GetParams(); + params.marginLeft = 0; + params.marginRight = page.GetApp().ui.windowRect.width; +} + +void Layout::Update() +{ + while (currentNodeToProcess && currentNodeToProcess != lastNodeToProcess) + { + currentNodeToProcess->Handler().BeginLayoutContext(*this, currentNodeToProcess); + + if (currentNodeToProcess->type == Node::Image && App::config.loadImages) + { + ImageNode::Data* imageData = static_cast(currentNodeToProcess->data); + if (!imageData->HasDimensions()) + { + // Waiting to first determine image size + if (!App::Get().pageContentLoadTask.IsBusy()) + { + App::Get().LoadImageNodeContent(currentNodeToProcess); + } + return; + } + } + + currentNodeToProcess->Handler().GenerateLayout(*this, currentNodeToProcess); + + if (currentNodeToProcess->firstChild) + { + currentNodeToProcess = currentNodeToProcess->firstChild; + } + else + { + currentNodeToProcess->Handler().EndLayoutContext(*this, currentNodeToProcess); + + if (!tableDepth && !lineStartNode) + { + page.GetApp().pageRenderer.MarkNodeLayoutComplete(currentNodeToProcess); + } + + if (currentNodeToProcess->next) + { + currentNodeToProcess = currentNodeToProcess->next; + } + else if(currentNodeToProcess->parent) + { + while (currentNodeToProcess) + { + currentNodeToProcess = currentNodeToProcess->parent; + + if (currentNodeToProcess) + { + currentNodeToProcess->Handler().EndLayoutContext(*this, currentNodeToProcess); + + if (!tableDepth && !lineStartNode) + { + page.GetApp().pageRenderer.MarkNodeLayoutComplete(currentNodeToProcess); + } + + if (currentNodeToProcess->next) + { + currentNodeToProcess = currentNodeToProcess->next; + break; + } + } + } + } + } + + } + + if (!isFinished && App::Get().parser.IsFinished() && !currentNodeToProcess) + { + if (!MemoryManager::pageAllocator.GetError()) + { + page.GetApp().pageRenderer.MarkPageLayoutComplete(); + } + + // Layout has finished so now we can load image content + App::Get().LoadImageNodeContent(page.GetRootNode()); + page.GetApp().ui.SetStatusMessage("Loading images...", StatusBarNode::GeneralStatus); + isFinished = true; + } +} + +void Layout::BreakNewLine() +{ + // Recenter items if required + //if (lineStartNode && lineStartNode->style.alignment == ElementAlignment::Center) + //{ + // int shift = AvailableWidth() / 2; + // TranslateNodes(lineStartNode, shift, 0, true); + //} + + if (lineStartNode) + { + if (lineStartNode->GetStyle().alignment == ElementAlignment::Center) + { + int shift = AvailableWidth() / 2; + TranslateNodes(lineStartNode, lastNodeContext, shift, 0); + } + else if (lineStartNode->GetStyle().alignment == ElementAlignment::Right) + { + int shift = AvailableWidth(); + TranslateNodes(lineStartNode, lastNodeContext, shift, 0); + } + } + + if (lastNodeContext && !tableDepth) + { + //page.GetApp().pageRenderer.MarkNodeLayoutComplete(lastNodeContext); + } + + Cursor().x = GetParams().marginLeft; + Cursor().y += currentLineHeight; + currentLineHeight = 0; + lineStartNode = nullptr; +} + +void Layout::ProgressCursor(Node* nodeContext, int width, int lineHeight) +{ + // Horrible hack to add padding / spacing between nodes. FIXME + if (width) + { + width += 2; + } + + if (!lineStartNode) + { + lineStartNode = nodeContext; + } + + lastNodeContext = nodeContext; + + if (lineHeight > currentLineHeight) + { + // Line height has increased so move everything down accordingly + int deltaY = lineHeight - currentLineHeight; +// TranslateNodes(lineStartNode, 0, deltaY, true); + TranslateNodes(lineStartNode, nodeContext, 0, deltaY); + currentLineHeight = lineHeight; + } + + Cursor().x += width; +} + +void Layout::TranslateNodes(Node* start, Node* end, int deltaX, int deltaY) +{ + for (Node* node = start; node; node = node->GetNextInTree()) + { + node->anchor.x += deltaX; + node->anchor.y += deltaY; + + if (node == end) + { + break; + } + } +} + +void Layout::OnNodeEmitted(Node* node) +{ + if (!currentNodeToProcess) + { + currentNodeToProcess = page.GetRootNode(); + } + + lastNodeToProcess = node; + +// if (!lineStartNode) +// { +// lineStartNode = node; +// } +// +// node->Handler().GenerateLayout(*this, node); +} + +void Layout::PadHorizontal(int left, int right) +{ + LayoutParams& params = GetParams(); + if (params.marginLeft + left >= params.marginRight - right) + { + params.marginLeft = (params.marginLeft + left + params.marginRight - right) / 2; + params.marginRight = params.marginLeft + 1; + } + else + { + params.marginLeft += left; + params.marginRight -= right; + } + if (Cursor().x < params.marginLeft) + { + Cursor().x = params.marginLeft; + } +} + +void Layout::RestrictHorizontal(int maxWidth) +{ + LayoutParams& params = GetParams(); + int marginRight = Cursor().x + maxWidth; + if (marginRight < params.marginRight) + { + params.marginRight = marginRight; + } +} + +void Layout::PadVertical(int down) +{ + Cursor().x = GetParams().marginLeft; + Cursor().y += down; + +} + +void Layout::RecalculateLayoutForNode(Node* targetNode) +{ + targetNode->Handler().BeginLayoutContext(*this, targetNode); + targetNode->Handler().GenerateLayout(*this, targetNode); + + for (Node* node = targetNode->firstChild; node; node = node->next) + { + RecalculateLayoutForNode(node); + } + + targetNode->Handler().EndLayoutContext(*this, targetNode); + +#if 0 + Node* node = targetNode; + bool checkChildren = true; + + node->Handler().BeginLayoutContext(*this, node); + node->Handler().GenerateLayout(*this, node); + + while (node) + { + if (checkChildren && node->firstChild) + { + node = node->firstChild; + } + else if (node->next) + { + if (checkChildren) + { + // ??? + node->Handler().EndLayoutContext(*this, node); + } + + node = node->next; + checkChildren = true; + } + else + { + node = node->parent; + if (node) + { + node->Handler().EndLayoutContext(*this, node); + } + + if (node == targetNode) + { + break; + } + checkChildren = false; + continue; + } + + if (node) + { + node->Handler().BeginLayoutContext(*this, node); + node->Handler().GenerateLayout(*this, node); + } + } +#endif +} + +void Layout::RecalculateLayout() +{ + Reset(); + + Node* node = page.GetRootNode(); + RecalculateLayoutForNode(node); + + page.GetApp().pageRenderer.MarkPageLayoutComplete(); + page.GetApp().pageRenderer.RefreshAll(); + +#ifdef _WIN32 + //page.DebugDumpNodeGraph(page.GetRootNode()); +#endif +} + +void Layout::MarkParsingComplete() +{ + lastNodeToProcess = nullptr; +} + +int Layout::CalculateWidth(ExplicitDimension explicitWidth) +{ + if (explicitWidth.IsSet()) + { + if (explicitWidth.IsPercentage()) + { + return ((long)MaxAvailableWidth() * explicitWidth.Value()) / 100; + } + else + { + int result = explicitWidth.Value(); + VideoModeInfo* modeInfo = Platform::video->GetVideoModeInfo(); + + if (modeInfo->zoom != 100) + { + result = ((long)modeInfo->zoom * result) / 100; + } + return result; + } + } + return 0; +} + +int Layout::CalculateHeight(ExplicitDimension explicitHeight) +{ + if (explicitHeight.IsSet()) + { + if (explicitHeight.IsPercentage()) + { + return ((long)App::Get().ui.GetPageHeightForDimensionScaling() * explicitHeight.Value()) / 100; + } + else + { + int result = explicitHeight.Value(); + VideoModeInfo* modeInfo = Platform::video->GetVideoModeInfo(); + + if (modeInfo->aspectRatio != 100) + { + result = ((long)result * 100) / modeInfo->aspectRatio; + } + if (modeInfo->zoom != 100) + { + result = ((long)modeInfo->zoom * result) / 100; + } + return result; + } + } + return 0; + +} diff --git a/src/Layout.h b/src/Layout.h new file mode 100644 index 0000000..46f8fc7 --- /dev/null +++ b/src/Layout.h @@ -0,0 +1,85 @@ +#pragma once + +#include "Node.h" +#include "Stack.h" + +class Page; +class Node; + +struct LayoutParams +{ + int marginLeft, marginRight; +}; + +class Layout +{ +public: + + Layout(Page& page); + + void Reset(); + void Update(); + + Page& page; + + void BreakNewLine(); + + void PushLayout() { paramStack.Push(); } + void PopLayout() { paramStack.Pop(); } + + void PadHorizontal(int left, int right); + void PadVertical(int down); + void RestrictHorizontal(int maxWidth); + + void OnNodeEmitted(Node* node); + void MarkParsingComplete(); + void ProgressCursor(Node* nodeContext, int width, int lineHeight); + + void RecalculateLayout(); + void RecalculateLayoutForNode(Node* node); + + int CalculateWidth(ExplicitDimension explicitWidth); + int CalculateHeight(ExplicitDimension explicitHeight); + + Coord GetCursor(int lineHeight = 0) + { + Coord result = Cursor(); + result.y += currentLineHeight - lineHeight; + return result; + } + LayoutParams& GetParams() { return paramStack.Top(); } + int AvailableWidth() { return GetParams().marginRight - Cursor().x; } + int MaxAvailableWidth() { return GetParams().marginRight - GetParams().marginLeft; } + + bool IsFinished() { return isFinished; } + + Node* lineStartNode; + Node* lastNodeContext; + + Node* currentNodeToProcess; + Node* lastNodeToProcess; + + Coord& Cursor() + { + return cursorStack.Top(); + } + void PushCursor() { cursorStack.Push(); } + void PopCursor() { cursorStack.Pop(); } + + Stack cursorStack; + + int currentLineHeight; + int tableDepth; + + Stack paramStack; + + void TranslateNodes(Node* start, Node* end, int deltaX, int deltaY); + + bool isFinished; +}; + +/* + + + +*/ diff --git a/src/Memory/Alloc.h b/src/Memory/Alloc.h new file mode 100644 index 0000000..a612834 --- /dev/null +++ b/src/Memory/Alloc.h @@ -0,0 +1,103 @@ +#ifndef _ALLOC_H_ +#define _ALLOC_H_ + +#include +#include +#include +#include +#include +#pragma warning(disable:4996) + +class Allocator +{ +public: + virtual void* Allocate(size_t numBytes) = 0; + + char* AllocString(const char* inString) + { + char* result = (char*)Allocate(strlen(inString) + 1); + if (result) + { + strcpy(result, inString); + } + return result; + } + + char* AllocString(const char* inString, size_t length) + { + char* result = (char*)Allocate(length + 1); + if (result) + { + memcpy(result, inString, length); + result[length] = '\0'; + } + return result; + } + + template + T* Alloc() + { + void* mem = Allocate(sizeof(T)); + if (mem) + { + return new (mem) T(); + } + return nullptr; + } + + template + T* Alloc(A a) + { + void* mem = Allocate(sizeof(T)); + if (mem) + { + return new (mem) T(a); + } + return nullptr; + } + + template + T* Alloc(A a, B b) + { + void* mem = Allocate(sizeof(T)); + if (mem) + { + return new (mem) T(a, b); + } + return nullptr; + } + + template + T* Alloc(A a, B b, C c) + { + void* mem = Allocate(sizeof(T)); + if (mem) + { + return new (mem) T(a, b, c); + } + return nullptr; + } + + template + T* Alloc(A a, B b, C c, D d) + { + void* mem = Allocate(sizeof(T)); + if (mem) + { + return new (mem) T(a, b, c, d); + } + return nullptr; + } +}; + +class MallocWrapper : public Allocator +{ +public: + virtual void* Allocate(size_t numBytes) + { + return malloc(numBytes); + } + +}; + +#endif diff --git a/src/LinAlloc.h b/src/Memory/LinAlloc.h similarity index 70% rename from src/LinAlloc.h rename to src/Memory/LinAlloc.h index aad96c5..b42eed2 100644 --- a/src/LinAlloc.h +++ b/src/Memory/LinAlloc.h @@ -12,18 +12,20 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _LINALLOC_H_ +#define _LINALLOC_H_ #include #include #include #include +#include "Alloc.h" #pragma warning(disable:4996) -// 8K chunk size including next chunk pointer -#define CHUNK_DATA_SIZE (8 * 1024 - sizeof(struct Chunk*)) +// 16K chunk size including next chunk pointer +#define CHUNK_DATA_SIZE (16 * 1024 - sizeof(struct Chunk*)) -class LinearAllocator +class LinearAllocator : public Allocator { struct Chunk { @@ -63,28 +65,7 @@ class LinearAllocator errorFlag = Error_None; } - char* AllocString(const char* inString) - { - char* result = (char*) Alloc(strlen(inString) + 1); - if (result) - { - strcpy(result, inString); - } - return result; - } - - char* AllocString(const char* inString, size_t length) - { - char* result = (char*)Alloc(length + 1); - if (result) - { - memcpy(result, inString, length); - result[length] = '\0'; - } - return result; - } - - void* Alloc(size_t numBytes) + virtual void* Allocate(size_t numBytes) { if (numBytes >= CHUNK_DATA_SIZE) { @@ -92,6 +73,12 @@ class LinearAllocator return NULL; } + if (!currentChunk) + { + errorFlag = Error_OutOfMemory; + return nullptr; + } + uint8_t* result = ¤tChunk->data[allocOffset]; if (allocOffset + numBytes > CHUNK_DATA_SIZE) @@ -101,6 +88,7 @@ class LinearAllocator if (!currentChunk->next) { currentChunk->next = new Chunk(); + if (!currentChunk->next) { errorFlag = Error_OutOfMemory; @@ -114,19 +102,13 @@ class LinearAllocator result = ¤tChunk->data[allocOffset]; } - totalBytesUsed += numBytes; + totalBytesUsed += (long) numBytes; allocOffset += numBytes; return result; } - template - T* Alloc() - { - return new (Alloc(sizeof(T))) T(); - } - - uint32_t TotalAllocated() { return (uint32_t)numAllocatedChunks * sizeof(Chunk); } - uint32_t TotalUsed() { return totalBytesUsed; } + long TotalAllocated() { return numAllocatedChunks * sizeof(Chunk); } + long TotalUsed() { return totalBytesUsed; } AllocationError GetError() { return errorFlag; } private: @@ -135,7 +117,9 @@ class LinearAllocator Chunk* currentChunk; size_t allocOffset; - size_t numAllocatedChunks; - uint32_t totalBytesUsed; // Bytes actually used for data + long numAllocatedChunks; + long totalBytesUsed; // Bytes actually used for data AllocationError errorFlag; }; + +#endif diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp new file mode 100644 index 0000000..0cc3f44 --- /dev/null +++ b/src/Memory/MemBlock.cpp @@ -0,0 +1,205 @@ +#include +#include "MemBlock.h" +#include "LinAlloc.h" +#include "Memory.h" +#include "../Platform.h" +#include "../App.h" + +#ifdef __DOS__ +#include +#include "../DOS/EMS.h" + +EMSManager ems; +#endif + +void* MemBlockHandle::GetPtr() +{ + switch (type) + { + case MemBlockHandle::Conventional: + return conventionalPointer; + case MemBlockHandle::DiskSwap: + { + return MemoryManager::pageBlockAllocator.AccessSwap(*this); + } +#ifdef __DOS__ + case MemBlockHandle::EMS: + { + return ems.MapBlock(*this); + } +#endif + default: + + Platform::FatalError("Invalid pointer type: %d\n", type); + return nullptr; + } +} + +void MemBlockHandle::Commit() +{ + switch (type) + { + case MemBlockHandle::DiskSwap: + MemoryManager::pageBlockAllocator.CommitSwap(*this); + break; + + } +} + +MemBlockAllocator::MemBlockAllocator() + : swapFile(nullptr) + , swapFileLength(0) + , swapBuffer(nullptr) + , lastSwapRead(-1) + , maxSwapSize(0) +{ +} + +void MemBlockAllocator::Init() +{ + if (App::config.useSwap) + { + swapFile = fopen("Microweb.swp", "wb+"); + } + + if (swapFile) + { + swapBuffer = malloc(MAX_SWAP_ALLOCATION); + lastSwapRead = -1; + swapFileLength = 0; + maxSwapSize = MAX_SWAP_SIZE; + } + +#ifdef __DOS__ + if (App::config.useEMS) + { + ems.Init(); + } +#endif +} + +void MemBlockAllocator::Shutdown() +{ +#ifdef __DOS__ + ems.Shutdown(); +#endif + + if (swapFile) + { + fclose(swapFile); + swapFile = NULL; + } +} + +MemBlockHandle MemBlockAllocator::AllocString(const char* inString) +{ + MemBlockHandle result = Allocate(strlen(inString) + 1); + if (result.IsAllocated()) + { + strcpy(result.Get(), inString); + result.Commit(); + } + return result; +} + +MemBlockHandle MemBlockAllocator::Allocate(uint16_t size) +{ + MemBlockHandle result; + long conventionalMemoryAvailable = 0; + +#ifdef __DOS__ + if (ems.IsAvailable()) + { + result = ems.Allocate(size); + if (result.IsAllocated()) + { + totalAllocated += size; + return result; + } + } + + conventionalMemoryAvailable += _memmax(); +#endif + + conventionalMemoryAvailable += MemoryManager::pageAllocator.TotalAllocated() - MemoryManager::pageAllocator.TotalUsed(); + + if (swapFile && conventionalMemoryAvailable < 16 * 1024) // If we have less than 16K available, fall back to disk + { + uint16_t sizeNeededForSwap = size + sizeof(uint16_t); + + if (sizeNeededForSwap <= MAX_SWAP_ALLOCATION && swapFileLength + sizeNeededForSwap + sizeof(uint16_t) < maxSwapSize) + { + result.swapFilePosition = swapFileLength; + fseek(swapFile, swapFileLength, SEEK_SET); + + fwrite(&size, sizeof(uint16_t), 1, swapFile); + + char empty = 0xaa; + size_t bytesLeft = size; + while (bytesLeft > 0) + { + if(!fwrite(&empty, 1, 1, swapFile)) + { + // Out of disk space? + result.type = MemBlockHandle::Unallocated; + return result; + } + bytesLeft--; + } + + swapFileLength += sizeNeededForSwap; + result.type = MemBlockHandle::DiskSwap; + totalAllocated += sizeNeededForSwap; + return result; + } + } + + //if(0) + { + result.conventionalPointer = MemoryManager::pageAllocator.Allocate(size); + if (result.conventionalPointer) + { + result.type = MemBlockHandle::Conventional; + totalAllocated += size; + } + } + + return result; +} + +void* MemBlockAllocator::AccessSwap(MemBlockHandle& handle) +{ + if (swapFile && lastSwapRead != handle.swapFilePosition) + { + fseek(swapFile, handle.swapFilePosition, SEEK_SET); + uint16_t allocatedSize = 0; + fread(&allocatedSize, sizeof(uint16_t), 1, swapFile); + fread(swapBuffer, 1, allocatedSize, swapFile); + lastSwapRead = handle.swapFilePosition; + } + + return swapBuffer; +} + +void MemBlockAllocator::CommitSwap(MemBlockHandle& handle) +{ + if (swapFile) + { + fseek(swapFile, handle.swapFilePosition, SEEK_SET); + uint16_t allocatedSize = 0; + fread(&allocatedSize, sizeof(uint16_t), 1, swapFile); + fseek(swapFile, handle.swapFilePosition + sizeof(uint16_t), SEEK_SET); + fwrite(swapBuffer, 1, allocatedSize, swapFile); + } +} + +void MemBlockAllocator::Reset() +{ + swapFileLength = 0; + lastSwapRead = -1; + totalAllocated = 0; + +#ifdef __DOS__ + ems.Reset(); +#endif +} diff --git a/src/Memory/MemBlock.h b/src/Memory/MemBlock.h new file mode 100644 index 0000000..df333d7 --- /dev/null +++ b/src/Memory/MemBlock.h @@ -0,0 +1,80 @@ +#ifndef _MEMBLOCK_H_ +#define _MEMBLOCK_H_ + +#include +#include + +#define MAX_SWAP_ALLOCATION (1024) +#define MAX_SWAP_SIZE (1024l * 1024l) + +// Abstract way of allocating a chunk of memory from conventional memory, EMS, disk swap + +#pragma pack(push, 1) +struct MemBlockHandle +{ + enum Type + { + Unallocated, + Conventional, + EMS, + DiskSwap + }; + + Type type : 8; + + MemBlockHandle() : type(Unallocated) {} + MemBlockHandle(void* buffer) : type(Conventional), conventionalPointer(buffer) {} + void* GetPtr(); + + template + inline T Get() { return (T)GetPtr(); } + void Commit(); + + bool IsAllocated() { return type != Unallocated; } + + union + { + void* conventionalPointer; + long swapFilePosition; + + struct + { + uint16_t emsPage; + uint16_t emsPageOffset; + }; + }; +}; +#pragma pack(pop) + +class LinearAllocator; + +class MemBlockAllocator +{ +public: + MemBlockAllocator(); + + void Init(); + void Shutdown(); + + MemBlockHandle Allocate(uint16_t size); + MemBlockHandle AllocString(const char* inString); + + long TotalAllocated() { return totalAllocated; } + + void Reset(); + +private: + friend struct MemBlockHandle; + void* AccessSwap(MemBlockHandle& handle); + void CommitSwap(MemBlockHandle& handle); + + FILE* swapFile; + long swapFileLength; + void* swapBuffer; + long lastSwapRead; + long maxSwapSize; + long totalAllocated; +}; + + +#endif diff --git a/src/Memory/Memory.cpp b/src/Memory/Memory.cpp new file mode 100644 index 0000000..b5e49b8 --- /dev/null +++ b/src/Memory/Memory.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "Memory.h" +#ifdef _DOS +#include +#include "../DOS/EMS.h" +extern EMSManager ems; +#endif + +LinearAllocator MemoryManager::pageAllocator; +MallocWrapper MemoryManager::interfaceAllocator; +MemBlockAllocator MemoryManager::pageBlockAllocator; + +void MemoryManager::GenerateMemoryReport(char* outString) +{ +#ifdef _DOS +// int DOSavailable = 0; +// union REGS inreg, outreg; +// inreg.x.ax = 0x4800; +// inreg.x.bx = 0; +// intdos(&inreg, &outreg); +// DOSavailable = outreg.x.bx / 64; + int EMSallocated = ems.TotalAllocated() / 1024; + int EMSused = ems.TotalUsed() / 1024; + int DOSavailable = _memmax() / 1024; + snprintf(outString, 100, "Conv: Alloc: %dK Used: %dK DOS free: %dK EMS: Alloc: %dK Used: %dK Block: %dK Err: %d\n", + (int)(MemoryManager::pageAllocator.TotalAllocated() / 1024), + (int)(MemoryManager::pageAllocator.TotalUsed() / 1024), + DOSavailable, + EMSallocated, + EMSused, + (int)(MemoryManager::pageBlockAllocator.TotalAllocated() / 1024), + MemoryManager::pageAllocator.GetError()); +#else + snprintf(outString, 100, "Conv: Alloc: %dK Used: %dK Block allocation: %dK\n", + (int)(MemoryManager::pageAllocator.TotalAllocated() / 1024), + (int)(MemoryManager::pageAllocator.TotalUsed() / 1024), + (int)(MemoryManager::pageBlockAllocator.TotalAllocated() / 1024)); +#endif + +} diff --git a/src/Memory/Memory.h b/src/Memory/Memory.h new file mode 100644 index 0000000..f0e6fd7 --- /dev/null +++ b/src/Memory/Memory.h @@ -0,0 +1,20 @@ +#ifndef _MEMORY_H_ +#define _MEMORY_H_ + +#include "Alloc.h" +#include "LinAlloc.h" +#include "MemBlock.h" + +class MemoryManager +{ +public: + static LinearAllocator pageAllocator; + static MallocWrapper interfaceAllocator; + + static MemBlockAllocator pageBlockAllocator; + + static void GenerateMemoryReport(char* outString); +}; + + +#endif diff --git a/src/Microweb.cpp b/src/Microweb.cpp index f34202f..17ac2bf 100644 --- a/src/Microweb.cpp +++ b/src/Microweb.cpp @@ -20,14 +20,16 @@ int main(int argc, char* argv[]) { - Platform::Init(argc, argv); + if (!Platform::Init(argc, argv)) + { + return 0; + } App* app = new App(); if (!app) { - Platform::Shutdown(); - fprintf(stderr, "Not enough memory\n"); + Platform::FatalError("Error allocating memory for application"); return 0; } diff --git a/src/Node.cpp b/src/Node.cpp new file mode 100644 index 0000000..5443fb9 --- /dev/null +++ b/src/Node.cpp @@ -0,0 +1,354 @@ +#include "Page.h" +#include "App.h" +#include "Draw/Surface.h" +#include "DataPack.h" +#include "Node.h" +#include "Nodes/Text.h" +#include "Nodes/Section.h" +#include "Nodes/ImgNode.h" +#include "Nodes/Break.h" +#include "Nodes/StyNode.h" +#include "Nodes/LinkNode.h" +#include "Nodes/Block.h" +#include "Nodes/Button.h" +#include "Nodes/Field.h" +#include "Nodes/Form.h" +#include "Nodes/Status.h" +#include "Nodes/Scroll.h" +#include "Nodes/Table.h" +#include "Nodes/Select.h" +#include "Nodes/ListItem.h" +#include "Nodes/CheckBox.h" + +NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = +{ + new SectionElement(), + new TextElement(), + new SubTextElement(), + new ImageNode(), + new BreakNode(), + new StyleNode(), + new LinkNode(), + new BlockNode(), + new ButtonNode(), + new TextFieldNode(), + new FormNode(), + new StatusBarNode(), + new ScrollBarNode(), + new TableNode(), + new TableRowNode(), + new TableCellNode(), + new SelectNode(), + new OptionNode(), + new ListNode(), + new ListItemNode(), + new CheckBoxNode() +}; + +Node::Node(Type inType, void* inData) + : type(inType) + , isLayoutComplete(false) + , parent(nullptr) + , next(nullptr) + , firstChild(nullptr) + , data(inData) +{ + anchor.Clear(); + size.Clear(); +} + +void Node::AddChild(Node* child) +{ + child->parent = this; + child->styleHandle = styleHandle; + + if(!firstChild) + { + firstChild = child; + } + else + { + Node* lastChild; + for(lastChild = firstChild; lastChild->next; lastChild = lastChild->next) {} + lastChild->next = child; + } +} + +void Node::InsertSibling(Node* sibling) +{ + sibling->styleHandle = styleHandle; + sibling->parent = parent; + sibling->next = next; + next = sibling; +} + +void Node::CalculateEncapsulatingRect(Rect& rect) +{ + rect.Clear(); + Node* node = this; + bool checkChildren = true; + + if (!firstChild) + { + rect.x = anchor.x; + rect.y = anchor.y; + rect.width = size.x; + rect.height = size.y; + return; + } + + while (node) + { + if (checkChildren && node->firstChild) + { + node = node->firstChild; + } + else if (node->next) + { + node = node->next; + checkChildren = true; + } + else + { + node = node->parent; + if (node == this) + { + break; + } + checkChildren = false; + } + + if (node->size.x == 0 || node->size.y == 0) + continue; + + if (rect.width == 0 && rect.height == 0) + { + rect.x = node->anchor.x; + rect.y = node->anchor.y; + rect.width = node->size.x; + rect.height = node->size.y; + continue; + } + + if (node->anchor.x < rect.x) + { + rect.x = node->anchor.x; + } + if (node->anchor.y < rect.y) + { + rect.y = node->anchor.y; + } + if (node->anchor.x + node->size.x > rect.x + rect.width) + { + rect.width = node->anchor.x + node->size.x - rect.x; + } + if (node->anchor.y + node->size.y > rect.y + rect.height) + { + rect.height = node->anchor.y + node->size.y - rect.y; + } + } +} + + +bool Node::IsPointInsideNode(int x, int y) +{ + return x >= anchor.x && y >= anchor.y && x < anchor.x + size.x && y < anchor.y + size.y; +} + +bool Node::IsPointInsideChildren(int x, int y) +{ + if (!size.IsZero() && !IsPointInsideNode(x, y)) + { + return false; + } + + if (firstChild == nullptr) + { + return IsPointInsideNode(x, y); + } + + for (Node* it = firstChild; it; it = it->next) + { + if (it->IsPointInsideChildren(x, y)) + { + return true; + } + } + + return false; +} + +Node* NodeHandler::Pick(Node* node, int x, int y) +{ + if (!node || (!node->size.IsZero() && !node->IsPointInsideNode(x, y))) + { + return nullptr; + } + + if (CanPick(node) && node->isLayoutComplete) + { + // Node is pickable, but it's dimensions could be based on encapsulated children so check + // it is over the child nodes and not just in the bounding box + if (node->IsPointInsideChildren(x, y)) + { + return node; + } + + return nullptr; + } + + for (Node* it = node->firstChild; it; it = it->next) + { + Node* result = it->Handler().Pick(it, x, y); + if (result) + { + return result; + } + } + + return nullptr; +} + +void NodeHandler::EndLayoutContext(Layout& layout, Node* node) +{ +} + +Node* Node::FindParentOfType(Node::Type searchType) +{ + for(Node* node = parent; node; node = node->parent) + { + if (node->type == searchType) + { + return node; + } + } + return NULL; +} + +void Node::Redraw() +{ + DrawContext context; + + Platform::input->HideMouse(); + App::Get().pageRenderer.GenerateDrawContext(context, this); + Handler().Draw(context, this); + + Platform::input->ShowMouse(); +} + +Node* Node::GetPreviousInTree() +{ + if (parent) + { + if (parent->firstChild == this) + { + return parent; + } + + for (Node* child = parent->firstChild; child; child = child->next) + { + if (child->next == this) + { + Node* node = child; + + while (node->firstChild) + { + node = node->firstChild; + + while (node->next) + { + node = node->next; + } + } + + return node; + } + } + + // Shouldn't ever get here + return nullptr; + } + else + { + // Top of the tree + return nullptr; + } +} + +Node* Node::GetNextInTree() +{ + Node* node = this; + bool checkChildren = true; + + while (node) + { + if (checkChildren && node->firstChild) + { + return node->firstChild; + } + else if (node->next) + { + return node->next; + } + else + { + node = node->parent; + checkChildren = false; + } + } + + return nullptr; +} + +bool Node::IsChildOf(Node* potentialParent) +{ + for(Node* node = parent; node; node = node->parent) + { + if (node == potentialParent) + { + return true; + } + } + + return false; +} + +ExplicitDimension ExplicitDimension::Parse(const char* str) +{ + ExplicitDimension result; + + char* unit; + long value = strtol(str, &unit, 10); + if (value) + { + if (unit && *unit == '%') + { + result.value = (int16_t)(-value); + } + else + { + result.value = (int16_t) value; + } + } + + return result; +} + +const ElementStyle& Node::GetStyle() +{ + return StylePool::Get().GetStyle(styleHandle); +} + +void Node::SetStyle(const ElementStyle& style) +{ + //if(memcmp(&style, &StylePool::Get().GetStyle(styleHandle), sizeof(ElementStyle))) + { + styleHandle = StylePool::Get().AddStyle(style); + } +} + +Font* Node::GetStyleFont() +{ + const ElementStyle& style = GetStyle(); + return Assets.GetFont(style.fontSize, style.fontStyle); +} diff --git a/src/Node.h b/src/Node.h new file mode 100644 index 0000000..25ee612 --- /dev/null +++ b/src/Node.h @@ -0,0 +1,149 @@ +#pragma once +#ifndef _NODE_H_ +#define _NODE_H_ + +#include +#include "Style.h" +#include "Event.h" +#include "Defines.h" +#include "Memory/Alloc.h" + +class App; +class Page; +class Node; +class Layout; +struct DrawContext; + +class NodeHandler +{ +public: + virtual void Draw(DrawContext& context, Node* element) {} + virtual void GenerateLayout(Layout& layout, Node* node) {} + virtual void BeginLayoutContext(Layout& layout, Node* node) {} + virtual void EndLayoutContext(Layout& layout, Node* node); + virtual void ApplyStyle(Node* node) {} + virtual Node* Pick(Node* node, int x, int y); + virtual bool CanPick(Node* node) { return false; } + virtual bool HandleEvent(Node* node, const Event& event) { return false; } + + virtual void LoadContent(Node* node, struct LoadTask& loadTask) {} + virtual bool ParseContent(Node* node, char* buffer, size_t count) { return false; } + virtual void FinishContent(Node* node, struct LoadTask& loadTask) {} + +}; + +struct Coord +{ + int16_t x, y; + + void Clear() { x = y = 0; } + bool IsZero() const { return x == 0 && y == 0; } +}; + +struct Rect +{ + int16_t x, y, width, height; + + void Clear() { x = y = width = height = 0; } +}; + +// For when width / height are set on an element +// percentage is stored internally as a negative number, px as positive, zero is no value set +struct ExplicitDimension +{ + static ExplicitDimension Parse(const char* str); + + ExplicitDimension() : value(0) {} + bool IsSet() { return value != 0; } + bool IsPercentage() { return value < 0; } + int16_t Value() { return value < 0 ? -value : value; } + +private: + int16_t value; +}; + +#pragma pack(push, 1) +class Node +{ +public: + enum Type + { + Section, + Text, + SubText, + Image, + Break, + Style, + Link, + Block, + Button, + TextField, + Form, + StatusBar, + ScrollBar, + Table, + TableRow, + TableCell, + Select, + Option, + List, + ListItem, + CheckBox, + NumNodeTypes + }; + + NodeHandler& Handler() const + { + return *nodeHandlers[type]; + } + + Node(Type inType, void* inData); + void AddChild(Node* child); + void InsertSibling(Node* sibling); + void CalculateEncapsulatingRect(Rect& rect); + bool IsPointInsideNode(int x, int y); + bool IsPointInsideChildren(int x, int y); + Node* FindParentOfType(Node::Type searchType); + template + T* FindParentDataOfType(Node::Type searchType) + { + Node* parent = FindParentOfType(searchType); + if (parent) + { + return static_cast(parent->data); + } + return nullptr; + } + bool IsChildOf(Node* node); + + const ElementStyle& GetStyle(); + void SetStyle(const ElementStyle& style); + Font* GetStyleFont(); + + void Redraw(); + + Node* GetPreviousInTree(); + Node* GetNextInTree(); + + ElementStyleHandle styleHandle; + Type type : 7; + bool isLayoutComplete : 1; + + Coord anchor; // Top left page position + Coord size; // Rectangle size that encapsulates node and its children + + Node* parent; + Node* next; + Node* firstChild; + + void* data; + +protected: + static NodeHandler* nodeHandlers[Node::NumNodeTypes]; +}; +#pragma pack(pop) + +typedef void(*NodeCallbackFunction)(Node* node); + + +#endif diff --git a/src/Nodes/Block.cpp b/src/Nodes/Block.cpp new file mode 100644 index 0000000..8028550 --- /dev/null +++ b/src/Nodes/Block.cpp @@ -0,0 +1,40 @@ +#include +#include "../Layout.h" +#include "../Memory/Memory.h" +#include "Block.h" + +Node* BlockNode::Construct(Allocator& allocator, int horizontalPadding, int verticalPadding) +{ + BlockNode::Data* data = allocator.Alloc(horizontalPadding, verticalPadding); + if (data) + { + return allocator.Alloc(Node::Block, data); + } + return nullptr; +} + +void BlockNode::BeginLayoutContext(Layout& layout, Node* node) +{ + BlockNode::Data* data = static_cast(node->data); + + layout.BreakNewLine(); + node->anchor = layout.GetCursor(); + + layout.PadVertical(data->verticalPadding); + layout.PushLayout(); + layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); +} + +void BlockNode::EndLayoutContext(Layout& layout, Node* node) +{ + + //NodeHandler::EndLayoutContext(layout, node); + + BlockNode::Data* data = static_cast(node->data); + + node->size.x = layout.MaxAvailableWidth(); + layout.PopLayout(); + layout.BreakNewLine(); + layout.PadVertical(data->verticalPadding); + node->size.y = layout.GetCursor().y - node->anchor.y; +} diff --git a/src/Nodes/Block.h b/src/Nodes/Block.h new file mode 100644 index 0000000..3bf35c5 --- /dev/null +++ b/src/Nodes/Block.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Node.h" + +class BlockNode : public NodeHandler +{ +public: + class Data + { + public: + Data(int inHorizontalPadding, int inVerticalPadding) : horizontalPadding(inHorizontalPadding), verticalPadding(inVerticalPadding) {} + int horizontalPadding; + int verticalPadding; + }; + + static Node* Construct(Allocator& allocator, int horizontalPadding = 0, int verticalPadding = 0); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; +}; diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp new file mode 100644 index 0000000..b0c9fd1 --- /dev/null +++ b/src/Nodes/Break.cpp @@ -0,0 +1,61 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../App.h" +#include "Break.h" + +Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayBreakLine, bool onlyPadEmptyLines) +{ + BreakNode::Data* data = allocator.Alloc(breakPadding, displayBreakLine, onlyPadEmptyLines); + if (data) + { + return allocator.Alloc(Node::Break, data); + } + return nullptr; +} + +void BreakNode::Draw(DrawContext& context, Node* node) +{ + BreakNode::Data* data = static_cast(node->data); + + if (data->displayBreakLine) + { + uint8_t outlineColour = App::Get().page.colourScheme.textColour; + context.surface->HLine(context, node->anchor.x, node->anchor.y + node->size.y / 2, node->size.x, outlineColour); + } +} + + +void BreakNode::GenerateLayout(Layout& layout, Node* node) +{ + BreakNode::Data* data = static_cast(node->data); + + int lineHeight = layout.currentLineHeight; + layout.BreakNewLine(); + + int breakPadding = data->breakPadding; + + if (data->onlyPadEmptyLines && lineHeight != 0) + { + breakPadding = 0; + } + + if (breakPadding) + { + layout.PadVertical(breakPadding); + } + + if (data->displayBreakLine) + { + node->anchor = layout.GetCursor(); + node->anchor.x += 8; + node->anchor.y -= breakPadding; + node->size.x = layout.AvailableWidth() - 16; + node->size.y = breakPadding; + if (!breakPadding) + { + node->size.y = 1; + } + } +} diff --git a/src/Nodes/Break.h b/src/Nodes/Break.h new file mode 100644 index 0000000..afa8e60 --- /dev/null +++ b/src/Nodes/Break.h @@ -0,0 +1,23 @@ +#ifndef _BREAK_H_ +#define _BREAK_H_ + +#include "../Node.h" + +class BreakNode : public NodeHandler +{ +public: + class Data + { + public: + Data(int inBreakPadding, bool inDisplayBreakLine, bool inOnlyPadEmptyLines) : breakPadding((uint8_t)inBreakPadding), displayBreakLine(inDisplayBreakLine), onlyPadEmptyLines(inOnlyPadEmptyLines) {} + uint8_t breakPadding : 6; + bool displayBreakLine : 1; + bool onlyPadEmptyLines : 1; + }; + + static Node* Construct(Allocator& allocator, int breakPadding = 0, bool displayBreakLine = false, bool onlyPadEmptyLines = false); + virtual void Draw(DrawContext& context, Node* element) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; +}; + +#endif diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp new file mode 100644 index 0000000..b6788b6 --- /dev/null +++ b/src/Nodes/Button.cpp @@ -0,0 +1,169 @@ + +#include "Button.h" +#include "../DataPack.h" +#include "../Draw/Surface.h" +#include "../Memory/LinAlloc.h" +#include "../Layout.h" +#include "../Event.h" +#include "../App.h" +#include "../KeyCodes.h" + +void ButtonNode::Draw(DrawContext& context, Node* node) +{ + ButtonNode::Data* data = static_cast(node->data); + + if (data->buttonText) + { + Font* font = node->GetStyleFont(); + uint8_t textColour = Platform::video->colourScheme.textColour; + uint8_t buttonOutlineColour = Platform::video->colourScheme.textColour; + uint8_t buttonColour = Platform::video->colourScheme.buttonColour; + context.surface->FillRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 2, buttonColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 1, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 2, node->size.x - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 1, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + context.surface->DrawString(context, font, data->buttonText, node->anchor.x + 8, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + + if (App::Get().ui.GetFocusedNode() == node) + { + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 3, node->size.x - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 2, node->size.y - 5, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 2, node->size.y - 5, buttonOutlineColour); + } + } +} + +Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, NodeCallbackFunction callback) +{ + char* buttonText = NULL; + if (inButtonText) + { + buttonText = allocator.AllocString(inButtonText); + if (!buttonText) + { + return nullptr; + } + HTMLParser::ReplaceAmpersandEscapeSequences(buttonText); + } + + ButtonNode::Data* data = allocator.Alloc(buttonText, callback); + if (data) + { + return allocator.Alloc(Node::Button, data); + } + + return nullptr; +} + +Coord ButtonNode::CalculateSize(Node* node) +{ + ButtonNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + int labelHeight = font->glyphHeight; + int labelWidth = 0; + + if (data->buttonText) + { + labelWidth = font->CalculateWidth(data->buttonText, node->GetStyle().fontStyle); + } + + Coord result; + result.x = labelWidth + 16; + result.y = labelHeight + 4; + return result; +} + +void ButtonNode::GenerateLayout(Layout& layout, Node* node) +{ + //ButtonNode::Data* data = static_cast(node->data); + + node->size = CalculateSize(node); + + if (layout.AvailableWidth() < node->size.x) + { + layout.BreakNewLine(); + } + + node->anchor = layout.GetCursor(node->size.y); + layout.ProgressCursor(node, node->size.x, node->size.y); +} + +bool ButtonNode::HandleEvent(Node* node, const Event& event) +{ + AppInterface& ui = App::Get().ui; + ButtonNode::Data* data = static_cast(node->data); + + switch (event.type) + { + case Event::MouseClick: + { + focusingFromMouseClick = true; + ui.FocusNode(node); + InvertButton(node); + } + return true; + case Event::MouseRelease: + { + InvertButton(node); + ui.FocusNode(nullptr); + if (ui.IsOverNode(node, event.x, event.y)) + { + if (data->onClick) + { + data->onClick(node); + } + } + } + return true; + case Event::KeyPress: + if (event.key == KEYCODE_ENTER) + { + if (data->onClick) + { + data->onClick(node); + } + return true; + } + return false; + case Event::Focus: + if (!focusingFromMouseClick) + { + HighlightButton(node, Platform::video->colourScheme.textColour); + } + else focusingFromMouseClick = false; + return true; + case Event::Unfocus: + HighlightButton(node, Platform::video->colourScheme.buttonColour); + return true; + default: + break; + } + + return false; +} + +void ButtonNode::InvertButton(Node* node) +{ + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Platform::input->HideMouse(); + context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); + Platform::input->ShowMouse(); +} + +void ButtonNode::HighlightButton(Node* node, uint8_t colour) +{ + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Platform::input->HideMouse(); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, colour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 3, node->size.x - 2, colour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 2, node->size.y - 5, colour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 2, node->size.y - 5, colour); + Platform::input->ShowMouse(); +} diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h new file mode 100644 index 0000000..97f0330 --- /dev/null +++ b/src/Nodes/Button.h @@ -0,0 +1,32 @@ +#ifndef _BUTTON_H_ +#define _BUTTON_H_ + +#include "../Node.h" + +class ButtonNode : public NodeHandler +{ +public: + class Data + { + public: + Data(const char* inButtonText, NodeCallbackFunction inOnClick) : buttonText(inButtonText), onClick(inOnClick) {} + const char* buttonText; + NodeCallbackFunction onClick; + }; + + ButtonNode() : focusingFromMouseClick(false) {} + + static Node* Construct(Allocator& allocator, const char* buttonText, NodeCallbackFunction onClick); + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* node) override; + virtual bool CanPick(Node* node) { return true; } + virtual bool HandleEvent(Node* node, const Event& event); + + static Coord CalculateSize(Node* node); + void InvertButton(Node* node); + void HighlightButton(Node* node, uint8_t colour); + + bool focusingFromMouseClick; +}; + +#endif diff --git a/src/Nodes/CheckBox.cpp b/src/Nodes/CheckBox.cpp new file mode 100644 index 0000000..a46b5d3 --- /dev/null +++ b/src/Nodes/CheckBox.cpp @@ -0,0 +1,283 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" +#include "../App.h" +#include "CheckBox.h" +#include "../KeyCodes.h" + +Node* CheckBoxNode::Construct(Allocator& allocator, const char* name, const char* value, bool isRadio, bool isChecked) +{ + name = allocator.AllocString(name); + value = allocator.AllocString(value); + + CheckBoxNode::Data* data = allocator.Alloc(name, value, isRadio, isChecked); + if (data) + { + return allocator.Alloc(Node::CheckBox, data); + } + return nullptr; +} + +void CheckBoxNode::Draw(DrawContext& context, Node* node) +{ + CheckBoxNode::Data* data = static_cast(node->data); + + if (data) + { + if (data->isRadio) + { + if (data->isChecked) + { + context.surface->BlitImage(context, Assets.radioSelected, node->anchor.x, node->anchor.y); + } + else + { + context.surface->BlitImage(context, Assets.radio, node->anchor.x, node->anchor.y); + } + } + else + { + if (data->isChecked) + { + context.surface->BlitImage(context, Assets.checkboxTicked, node->anchor.x, node->anchor.y); + } + else + { + context.surface->BlitImage(context, Assets.checkbox, node->anchor.x, node->anchor.y); + } + } + } + + if (App::Get().ui.GetFocusedNode() == node) + { + uint8_t outlineColour = Platform::video->colourScheme.textColour; + + context.surface->HLine(context, node->anchor.x, node->anchor.y, node->size.x, outlineColour); + context.surface->HLine(context, node->anchor.x, node->anchor.y + node->size.y - 1, node->size.x, outlineColour); + context.surface->VLine(context, node->anchor.x, node->anchor.y + 1, node->size.y - 2, outlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 1, node->anchor.y + 1, node->size.y - 2, outlineColour); + } + +} + + +void CheckBoxNode::GenerateLayout(Layout& layout, Node* node) +{ + CheckBoxNode::Data* data = static_cast(node->data); + Image* image = data->isRadio ? Assets.radio : Assets.checkbox; + + Font* font = node->GetStyleFont(); + + node->size.x = image->width; + node->size.y = image->height; + + node->anchor = layout.GetCursor(node->size.y); + layout.ProgressCursor(node, node->size.x, node->size.y); +} + +Node* CheckBoxNode::FindPreviousRadioNode(Node* node) +{ + Node* n = node; + Node* next = FindNextRadioNode(node); + + while (next != node) + { + n = next; + next = FindNextRadioNode(n); + } + + return n; +} + +Node* CheckBoxNode::FindNextRadioNode(Node* node) +{ + for (Node* n = node->GetNextInTree(); n; n = n->GetNextInTree()) + { + if (IsPartOfRadioSet(node, n)) + { + return n; + } + } + + for (Node* n = App::Get().page.GetRootNode(); n && n != node; n = n->GetNextInTree()) + { + if (IsPartOfRadioSet(node, n)) + { + return n; + } + } + + return node; +} + +bool CheckBoxNode::IsPartOfRadioSet(Node* contextNode, Node* node) +{ + if (node->type == Node::CheckBox) + { + Node* formNode = contextNode->FindParentOfType(Node::Form); + CheckBoxNode::Data* contextData = static_cast(contextNode->data); + + CheckBoxNode::Data* data = static_cast(node->data); + Node* otherFormNode = node->FindParentOfType(Node::Form); + + if (data && data->isRadio && formNode == otherFormNode && data->name && !strcmp(contextData->name, data->name)) + { + return true; + } + } + + return false; +} + +bool CheckBoxNode::HandleEvent(Node* node, const Event& event) +{ + AppInterface& ui = App::Get().ui; + CheckBoxNode::Data* data = static_cast(node->data); + + switch (event.type) + { + case Event::MouseClick: + { + if (data->isRadio) + { + if (!data->isChecked) + { + data->isChecked = true; + + if (data->name) + { + Node* formNode = node->FindParentOfType(Node::Form); + + for (Node* n = App::Get().page.GetRootNode(); n; n = n->GetNextInTree()) + { + if (n != node && IsPartOfRadioSet(node, n)) + { + CheckBoxNode::Data* otherData = static_cast(n->data); + otherData->isChecked = false; + n->Redraw(); + } + } + } + } + } + else + { + data->isChecked = !data->isChecked; + } + node->Redraw(); + ui.FocusNode(nullptr); + } + return true; + + case Event::Focus: + { + bool shouldFocus = !data->isRadio || data->isChecked; + + if (!shouldFocus) + { + shouldFocus = true; + + // If this is a radio type and no option is selected, allow focus + for (Node* n = App::Get().page.GetRootNode(); n; n = n->GetNextInTree()) + { + if (IsPartOfRadioSet(node, n)) + { + CheckBoxNode::Data* otherData = static_cast(n->data); + if (otherData->isChecked) + { + shouldFocus = false; + break; + } + } + } + } + + if (shouldFocus) + { + DrawHighlight(node, Platform::video->colourScheme.textColour); + return true; + } + else + { + return false; + } + } + + case Event::Unfocus: + DrawHighlight(node, Platform::video->colourScheme.pageColour); + return true; + + case Event::KeyPress: + if (data->isRadio) + { + switch (event.key) + { + case KEYCODE_ARROW_UP: + case KEYCODE_ARROW_LEFT: + { + Node* next = FindPreviousRadioNode(node); + if (next && next != node) + { + data->isChecked = false; + CheckBoxNode::Data* nextData = static_cast(next->data); + nextData->isChecked = true; + node->Redraw(); + next->Redraw(); + ui.FocusNode(next); + } + } + return true; + case KEYCODE_ARROW_DOWN: + case KEYCODE_ARROW_RIGHT: + { + Node* next = FindNextRadioNode(node); + if (next && next != node) + { + data->isChecked = false; + CheckBoxNode::Data* nextData = static_cast(next->data); + nextData->isChecked = true; + node->Redraw(); + next->Redraw(); + ui.FocusNode(next); + } + } + return true; + case ' ': + if (!data->isChecked) + { + data->isChecked = true; + node->Redraw(); + } + return true; + } + } + else + { + switch (event.key) + { + case ' ': + data->isChecked = !data->isChecked; + node->Redraw(); + return true; + } + } + break; + } + + return false; +} + +void CheckBoxNode::DrawHighlight(Node* node, uint8_t colour) +{ + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Platform::input->HideMouse(); + context.surface->HLine(context, node->anchor.x, node->anchor.y, node->size.x, colour); + context.surface->HLine(context, node->anchor.x, node->anchor.y + node->size.y - 1, node->size.x, colour); + context.surface->VLine(context, node->anchor.x, node->anchor.y + 1, node->size.y - 2, colour); + context.surface->VLine(context, node->anchor.x + node->size.x - 1, node->anchor.y + 1, node->size.y - 2, colour); + Platform::input->ShowMouse(); +} diff --git a/src/Nodes/CheckBox.h b/src/Nodes/CheckBox.h new file mode 100644 index 0000000..24096d9 --- /dev/null +++ b/src/Nodes/CheckBox.h @@ -0,0 +1,34 @@ +#ifndef _CHECKBOX_H_ +#define _CHECKBOX_H_ + +#include "../Node.h" + +class CheckBoxNode : public NodeHandler +{ +public: + class Data + { + public: + Data(const char* inName, const char* inValue, bool inIsRadio, bool inIsChecked) : + name(inName), value(inValue), isRadio(inIsRadio), isChecked(inIsChecked) {} + const char* name; + const char* value; + bool isRadio; + bool isEnabled; + bool isChecked; + }; + + static Node* Construct(Allocator& allocator, const char* name, const char* value, bool isRadio, bool isChecked); + virtual void Draw(DrawContext& context, Node* element) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual bool CanPick(Node* node) { return true; } + virtual bool HandleEvent(Node* node, const Event& event); + + void DrawHighlight(Node* node, uint8_t colour); + Node* FindNextRadioNode(Node* node); + Node* FindPreviousRadioNode(Node* node); + bool IsPartOfRadioSet(Node* context, Node* node); + +}; + +#endif diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp new file mode 100644 index 0000000..66ae2b4 --- /dev/null +++ b/src/Nodes/Field.cpp @@ -0,0 +1,568 @@ +#include "Field.h" +#include "../DataPack.h" +#include "../Draw/Surface.h" +#include "../Memory/Memory.h" +#include "../Layout.h" +#include "../Platform.h" +#include "../KeyCodes.h" +#include "../App.h" + +#define PASSWORD_CHARACTER '\x95' +#define PASSWORD_CHARACTER_STRING "\x95" + +#define LEFT_PADDING 4 + +void TextFieldNode::Draw(DrawContext& context, Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + + Font* font = node->GetStyleFont(); + uint8_t textColour = Platform::video->colourScheme.textColour; + uint8_t buttonOutlineColour = Platform::video->colourScheme.textColour; + uint8_t clearColour = Platform::video->colourScheme.pageColour; + context.surface->FillRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 2, clearColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 1, node->size.x - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 1, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + + DrawContext subContext = context; + subContext.clipRight = node->anchor.x + node->size.x - 2; + + if (node == App::Get().ui.GetFocusedNode()) + { + if (data->isPassword) + { + DrawPasswordString(subContext, font, data->buffer + shiftPosition, node->anchor.x + LEFT_PADDING, node->anchor.y + 2, textColour); + } + else + { + subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + LEFT_PADDING, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } + + if (selectionLength > 0) + { + DrawSelection(subContext, node); + } + else + { + DrawCursor(context, node); + } + + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 2, node->size.x - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + } + else + { + if (data->isPassword) + { + DrawPasswordString(subContext, font, data->buffer, node->anchor.x + LEFT_PADDING, node->anchor.y + 2, textColour); + } + else + { + subContext.surface->DrawString(subContext, font, data->buffer, node->anchor.x + LEFT_PADDING, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } + } +} + +void TextFieldNode::DrawHighlight(Node* node, uint8_t colour) +{ + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Platform::input->HideMouse(); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, colour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 2, node->size.x - 2, colour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.y - 2, colour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 1, node->size.y - 2, colour); + Platform::input->ShowMouse(); +} + +void TextFieldNode::DrawPasswordString(DrawContext& context, Font* font, const char* str, int x, int y, uint8_t colour) +{ + int length = strlen(str); + int glyphWidth = font->GetGlyphWidth(PASSWORD_CHARACTER); + while (length--) + { + context.surface->DrawString(context, font, PASSWORD_CHARACTER_STRING, x, y, colour); + x += glyphWidth; + } +} + +Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue, NodeCallbackFunction onSubmit) +{ + char* buffer = (char*) allocator.Allocate(DEFAULT_TEXT_FIELD_BUFFER_SIZE); + if (!buffer) + { + return nullptr; + } + + TextFieldNode::Data* data = allocator.Alloc(buffer, DEFAULT_TEXT_FIELD_BUFFER_SIZE, onSubmit); + if (data) + { + if (inValue) + { + strncpy(buffer, inValue, DEFAULT_TEXT_FIELD_BUFFER_SIZE); + buffer[DEFAULT_TEXT_FIELD_BUFFER_SIZE - 1] = '\0'; + } + else + { + buffer[0] = '\0'; + } + return allocator.Alloc(Node::TextField, data); + } + + return nullptr; +} + +Node* TextFieldNode::Construct(Allocator& allocator, char* buffer, int bufferLength, NodeCallbackFunction onSubmit) +{ + TextFieldNode::Data* data = allocator.Alloc(buffer, bufferLength, onSubmit); + if (data) + { + return allocator.Alloc(Node::TextField, data); + } + + return nullptr; +} + +void TextFieldNode::GenerateLayout(Layout& layout, Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + + if (data->explicitWidth.IsSet()) + { + node->size.x = layout.CalculateWidth(data->explicitWidth); + } + else + { + node->size.x = Platform::video->screenWidth / 3; + } + node->size.y = font->glyphHeight + 4; + + if (layout.MaxAvailableWidth() < node->size.x) + { + node->size.x = layout.MaxAvailableWidth(); + } + + if (layout.AvailableWidth() < node->size.x) + { + layout.BreakNewLine(); + } + + node->anchor = layout.GetCursor(node->size.y); + layout.ProgressCursor(node, node->size.x, node->size.y); +} + +bool TextFieldNode::HandleEvent(Node* node, const Event& event) +{ + TextFieldNode::Data* data = static_cast(node->data); + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + switch (event.type) + { + case Event::Focus: + shiftPosition = 0; + ShiftIntoView(node); + + if (App::Get().ui.IsInterfaceNode(node) && strlen(data->buffer) > 0) + { + // This is the address bar + cursorPosition = strlen(data->buffer); + selectionStartPosition = 0; + selectionLength = cursorPosition; + DrawSelection(context, node); + } + else + { + selectionStartPosition = selectionLength = 0; + cursorPosition = pickedPosition != -1 ? pickedPosition : strlen(data->buffer); + DrawCursor(context, node); + } + DrawHighlight(node, Platform::video->colourScheme.textColour); + return true; + case Event::Unfocus: + pickedPosition = -1; + if (shiftPosition > 0) + { + shiftPosition = 0; + selectionLength = 0; + cursorPosition = -1; + node->Redraw(); + } + else + { + if (selectionLength > 0) + { + DrawSelection(context, node); + } + else + { + DrawCursor(context, node); + } + } + DrawHighlight(node, Platform::video->colourScheme.pageColour); + return true; + case Event::MouseClick: + pickedPosition = PickPosition(node, event.x, event.y); + if (App::Get().ui.GetFocusedNode() != node) + { + App::Get().ui.FocusNode(node); + } + else + { + ClearSelection(node); + MoveCursorPosition(node, pickedPosition); + } + break; + case Event::MouseDrag: + if(pickedPosition != -1) + { + int releasedPickPosition = PickPosition(node, event.x, event.y); + if (pickedPosition != releasedPickPosition) + { + if (selectionLength > 0) + { + DrawSelection(context, node); + } + else + { + DrawCursor(context, node); + } + + if (pickedPosition > releasedPickPosition) + { + selectionStartPosition = releasedPickPosition; + selectionLength = pickedPosition - releasedPickPosition; + } + else + { + selectionStartPosition = pickedPosition; + selectionLength = releasedPickPosition - pickedPosition; + } + + DrawSelection(context, node); + } + } + //cursorPosition = -1; + break; + case Event::KeyPress: + pickedPosition = -1; + if (event.key >= 32 && event.key < 128) + { + if (selectionLength > 0) + { + DeleteSelectionContents(node); + } + + if (cursorPosition < data->bufferSize) + { + int len = strlen(data->buffer); + for (int n = len; n >= cursorPosition; n--) + { + data->buffer[n + 1] = data->buffer[n]; + } + data->buffer[cursorPosition] = (char)(event.key); + MoveCursorPosition(node, cursorPosition + 1); + RedrawModified(node, cursorPosition - 1); + } + ShiftIntoView(node); + return true; + } + else if (event.key == KEYCODE_BACKSPACE) + { + if (selectionLength > 0) + { + DeleteSelectionContents(node); + } + else if (cursorPosition > 0) + { + int len = strlen(data->buffer); + for (int n = cursorPosition - 1; n < len; n++) + { + data->buffer[n] = data->buffer[n + 1]; + } + MoveCursorPosition(node, cursorPosition - 1); + RedrawModified(node, cursorPosition); + } + ShiftIntoView(node); + return true; + } + else if (event.key == KEYCODE_DELETE) + { + int len = strlen(data->buffer); + if (selectionLength > 0) + { + DeleteSelectionContents(node); + } + else if (cursorPosition < len) + { + for (int n = cursorPosition; n < len; n++) + { + data->buffer[n] = data->buffer[n + 1]; + } + RedrawModified(node, cursorPosition); + } + ShiftIntoView(node); + return true; + } + else if (event.key == KEYCODE_ENTER) + { + if (data->onSubmit) + { + data->onSubmit(node); + } + return true; + } + else if (event.key == KEYCODE_ARROW_LEFT) + { + if (selectionLength > 0) + { + ClearSelection(node); + } + else if (cursorPosition > 0) + { + MoveCursorPosition(node, cursorPosition - 1); + } + ShiftIntoView(node); + return true; + } + else if (event.key == KEYCODE_ARROW_RIGHT) + { + if (selectionLength > 0) + { + ClearSelection(node); + } + else if (cursorPosition < strlen(data->buffer)) + { + MoveCursorPosition(node, cursorPosition + 1); + } + ShiftIntoView(node); + return true; + } + else if (event.key == KEYCODE_END) + { + if (selectionLength > 0) + { + ClearSelection(node); + } + else + { + MoveCursorPosition(node, strlen(data->buffer)); + } + ShiftIntoView(node); + return true; + } + else if (event.key == KEYCODE_HOME) + { + if (selectionLength > 0) + { + ClearSelection(node); + } + else + { + MoveCursorPosition(node, 0); + } + ShiftIntoView(node); + return true; + } + break; + default: + break; + } + + return false; +} + +int TextFieldNode::GetBufferPixelWidth(Node* node, int start, int end) +{ + TextFieldNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + int x = 0; + + for (int n = 0; n < end; n++) + { + if (!data->buffer[n]) + break; + + if (n >= start) + { + if (data->isPassword) + { + x += font->GetGlyphWidth(PASSWORD_CHARACTER); + } + else + { + x += font->GetGlyphWidth(data->buffer[n]); + } + } + } + + return x; +} + +void TextFieldNode::DrawCursor(DrawContext& context, Node* node) +{ + if (cursorPosition < 0) + return; + + TextFieldNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + + int x = node->anchor.x + LEFT_PADDING; + int height = font->glyphHeight; + + x += GetBufferPixelWidth(node, shiftPosition, cursorPosition); + + if (x >= node->anchor.x + node->size.x - 1) + { + return; + } + + int y = node->anchor.y + 2; + + context.surface->InvertRect(context, x, y, 1, height); +} + +void TextFieldNode::MoveCursorPosition(Node* node, int newPosition) +{ + Platform::input->HideMouse(); + + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + DrawCursor(context, node); + cursorPosition = newPosition; + DrawCursor(context, node); + + Platform::input->ShowMouse(); +} + +void TextFieldNode::DrawSelection(DrawContext& context, Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + + Font* font = node->GetStyleFont(); + int selectionX1 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition); + int selectionX2 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition + selectionLength); + + Platform::input->HideMouse(); + context.surface->InvertRect(context, selectionX1 + node->anchor.x + LEFT_PADDING, node->anchor.y + 2, selectionX2 - selectionX1, font->glyphHeight); + Platform::input->ShowMouse(); +} + +void TextFieldNode::RedrawModified(Node* node, int position) +{ + TextFieldNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + DrawContext context; + uint8_t textColour = Platform::video->colourScheme.textColour; + uint8_t clearColour = Platform::video->colourScheme.pageColour; + context.clipRight = node->anchor.x + node->size.x - 2; + + int drawPosition = GetBufferPixelWidth(node, shiftPosition, position) + LEFT_PADDING; + int clearWidth = node->size.x - 2 - drawPosition; + drawPosition += node->anchor.x; + + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Platform::input->HideMouse(); + context.surface->FillRect(context, drawPosition, node->anchor.y + 2, clearWidth, node->size.y - 4, clearColour); + if (data->isPassword) + { + DrawPasswordString(context, font, data->buffer + position, drawPosition, node->anchor.y + 2, textColour); + } + else + { + context.surface->DrawString(context, font, data->buffer + position, drawPosition, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } + DrawCursor(context, node); + Platform::input->ShowMouse(); +} + +void TextFieldNode::ShiftIntoView(Node* node) +{ + bool needsRedraw = false; + + if (cursorPosition < 0) + { + cursorPosition = 0; + } + + if (cursorPosition < shiftPosition) + { + shiftPosition = cursorPosition; + needsRedraw = true; + } + else + { + int cursorPixelPosition = GetBufferPixelWidth(node, shiftPosition, cursorPosition); + + while (cursorPixelPosition > node->size.x - 4) + { + needsRedraw = true; + shiftPosition++; + cursorPixelPosition = GetBufferPixelWidth(node, shiftPosition, cursorPosition); + } + } + + if (needsRedraw) + { + RedrawModified(node, shiftPosition); + } +} + +void TextFieldNode::DeleteSelectionContents(Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + + strcpy(data->buffer + selectionStartPosition, data->buffer + selectionStartPosition + selectionLength); + cursorPosition = selectionStartPosition; + selectionLength = 0; + RedrawModified(node, cursorPosition); +} + +void TextFieldNode::ClearSelection(Node* node) +{ + if (selectionLength > 0) + { + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + DrawSelection(context, node); + selectionLength = 0; + DrawCursor(context, node); + } +} + +int TextFieldNode::PickPosition(Node* node, int x, int y) +{ + x -= node->anchor.x + LEFT_PADDING; + int result = shiftPosition; + + TextFieldNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + + while(x > 0) + { + if (!data->buffer[result]) + { + break; + } + + if (data->isPassword) + { + x -= font->GetGlyphWidth(PASSWORD_CHARACTER); + } + else + { + x -= font->GetGlyphWidth(data->buffer[result]); + } + result++; + } + + return result; +} diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h new file mode 100644 index 0000000..9e0ff55 --- /dev/null +++ b/src/Nodes/Field.h @@ -0,0 +1,60 @@ +#ifndef _FIELD_H_ +#define _FIELD_H_ + +#include "../Node.h" + +#define DEFAULT_TEXT_FIELD_BUFFER_SIZE 80 + +class TextFieldNode : public NodeHandler +{ +public: + TextFieldNode() : + shiftPosition(0), + cursorPosition(0), + selectionStartPosition(0), + selectionLength(0), + pickedPosition(0) + { + + } + + class Data + { + public: + Data(char* inBuffer, int inBufferSize, NodeCallbackFunction inOnSubmit) : buffer(inBuffer), bufferSize(inBufferSize), name(NULL), isPassword(false), onSubmit(inOnSubmit) {} + char* buffer; + int bufferSize; + char* name; + bool isPassword; + NodeCallbackFunction onSubmit; + ExplicitDimension explicitWidth; + }; + + static Node* Construct(Allocator& allocator, const char* text, NodeCallbackFunction onSubmit = NULL); + static Node* Construct(Allocator& allocator, char* buffer, int bufferLength, NodeCallbackFunction onSubmit = NULL); + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* node) override; + virtual bool CanPick(Node* node) override { return true; } + virtual bool HandleEvent(Node* node, const Event& event) override; + +private: + int shiftPosition; + int cursorPosition; + int selectionStartPosition; + int selectionLength; + int pickedPosition; + + void ShiftIntoView(Node* node); + void DrawCursor(DrawContext& context, Node* node); + void DrawSelection(DrawContext& context, Node* node); + void MoveCursorPosition(Node* node, int newPosition); + void RedrawModified(Node* node, int position); + int GetBufferPixelWidth(Node* node, int start, int end); + void DeleteSelectionContents(Node* node); + void ClearSelection(Node* node); + int PickPosition(Node* node, int x, int y); + void DrawPasswordString(DrawContext& context, Font* font, const char* str, int x, int y, uint8_t colour); + void DrawHighlight(Node* node, uint8_t colour); +}; + +#endif diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp new file mode 100644 index 0000000..cb5fc9c --- /dev/null +++ b/src/Nodes/Form.cpp @@ -0,0 +1,128 @@ +#include "Form.h" +#include "../Memory/Memory.h" +#include "../App.h" +#include "../Interface.h" +#include "Field.h" +#include "CheckBox.h" +#include "Select.h" + +Node* FormNode::Construct(Allocator& allocator) +{ + FormNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Form, data); + } + + return nullptr; +} + +void FormNode::AppendParameter(char* address, const char* name, const char* value, int& numParams) +{ + if (!name) + return; + + if (numParams == 0) + { + strcat(address, "?"); + } + else + { + strcat(address, "&"); + } + strcat(address, name); + strcat(address, "="); + if (value) + { + strcat(address, value); + } + numParams++; + +} + +void FormNode::BuildAddressParameterList(Node* node, char* address, int& numParams) +{ + switch(node->type) + { + case Node::TextField: + { + TextFieldNode::Data* fieldData = static_cast(node->data); + + if (fieldData->name && fieldData->buffer) + { + AppendParameter(address, fieldData->name, fieldData->buffer, numParams); + } + } + break; + case Node::CheckBox: + { + CheckBoxNode::Data* checkboxData = static_cast(node->data); + + if (checkboxData && checkboxData->isChecked && checkboxData->name && checkboxData->value) + { + AppendParameter(address, checkboxData->name, checkboxData->value, numParams); + } + } + break; + case Node::Select: + { + SelectNode::Data* selectData = static_cast(node->data); + + if (selectData && selectData->selected) + { + AppendParameter(address, selectData->name, selectData->selected->text, numParams); + } + } + break; + } + + for (node = node->firstChild; node; node = node->next) + { + BuildAddressParameterList(node, address, numParams); + } +} + +void FormNode::SubmitForm(Node* node) +{ + FormNode::Data* data = static_cast(node->data); + App& app = App::Get(); + + if (data->method == FormNode::Data::Get) + { + char* address = app.ui.addressBarURL.url; + if (data->action) + { + strcpy(address, data->action); + } + int numParams = 0; + + // Remove anything after existing ? + char* questionMark = strstr(address, "?"); + if (questionMark) + { + *questionMark = '\0'; + } + + BuildAddressParameterList(node, address, numParams); + + // Replace any spaces with + + for (char* p = address; *p; p++) + { + if (*p == ' ') + { + *p = '+'; + } + } + + app.OpenURL(URL::GenerateFromRelative(app.page.pageURL.url, address).url); + } +} + +void FormNode::OnSubmitButtonPressed(Node* node) +{ + Node* formNode = node->FindParentOfType(Node::Form); + if (formNode) + { + SubmitForm(formNode); + } +} diff --git a/src/Nodes/Form.h b/src/Nodes/Form.h new file mode 100644 index 0000000..7f65ee9 --- /dev/null +++ b/src/Nodes/Form.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef _FORM_H_ +#define _FORM_H_ + + +#include +#include "../Node.h" + +class FormNode : public NodeHandler +{ +public: + class Data + { + public: + enum MethodType + { + Get, + Post + }; + char* action; + MethodType method; + + Data() : action(NULL), method(Get) {} + }; + + static Node* Construct(Allocator& allocator); + + static void SubmitForm(Node* node); + + static void OnSubmitButtonPressed(Node* node); + +private: + static void BuildAddressParameterList(Node* node, char* address, int& numParams); + static void AppendParameter(char* address, const char* name, const char* value, int& numParams); +}; + +#endif diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp new file mode 100644 index 0000000..5cd2fb8 --- /dev/null +++ b/src/Nodes/ImgNode.cpp @@ -0,0 +1,264 @@ +#include "../Platform.h" +#include "../Page.h" +#include "ImgNode.h" +#include "../Memory/Memory.h" +#include "../Layout.h" +#include "../Draw/Surface.h" +#include "../App.h" +#include "../Image/Decoder.h" +#include "../DataPack.h" +#include "../HTTP.h" +#include "Text.h" + +void ImageNode::Draw(DrawContext& context, Node* node) +{ + ImageNode::Data* data = static_cast(node->data); + //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); + uint8_t outlineColour = App::Get().page.colourScheme.textColour; + + if (data->state == ImageNode::FinishedDownloadingContent && data->image.lines.IsAllocated()) + { + context.surface->BlitImage(context, &data->image, node->anchor.x, node->anchor.y); + } + else + { + Image* image = data->state == ImageNode::ErrorDownloading ? Assets.brokenImageIcon : Assets.imageIcon; + + if (data->IsBrokenImageWithoutDimensions()) + { + context.surface->BlitImage(context, image, node->anchor.x, node->anchor.y); + } + else + { + context.surface->HLine(context, node->anchor.x, node->anchor.y, node->size.x, outlineColour); + context.surface->HLine(context, node->anchor.x, node->anchor.y + node->size.y - 1, node->size.x, outlineColour); + context.surface->VLine(context, node->anchor.x, node->anchor.y + 1, node->size.y - 2, outlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 1, node->anchor.y + 1, node->size.y - 2, outlineColour); + + DrawContext croppedContext = context; + croppedContext.Restrict(node->anchor.x + 1, node->anchor.y + 1, node->anchor.x + node->size.x - 1, node->anchor.y + node->size.y - 1); + croppedContext.surface->BlitImage(croppedContext, image, node->anchor.x + 2, node->anchor.y + 2); + + if (data->altText) + { + Font* font = node->GetStyleFont(); + uint8_t textColour = App::Get().page.colourScheme.textColour; + croppedContext.surface->DrawString(croppedContext, font, data->altText, node->anchor.x + image->width + 4, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } + } + } + + Node* focusedNode = App::Get().ui.GetFocusedNode(); + if (focusedNode && node->IsChildOf(focusedNode)) + { + context.surface->InvertRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y); + } +} + +bool ImageNode::Data::IsBrokenImageWithoutDimensions() +{ + return state == ImageNode::ErrorDownloading && image.width == Assets.brokenImageIcon->width && image.height == Assets.brokenImageIcon->height; +} + + +Node* ImageNode::Construct(Allocator& allocator) +{ + ImageNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Image, data); + } + + return nullptr; +} + +void ImageNode::BeginLayoutContext(Layout& layout, Node* node) +{ + ImageNode::Data* data = static_cast(node->data); + + if (!data->AreDimensionsLocked()) + { + if (data->explicitWidth.IsSet()) + { + data->image.width = layout.CalculateWidth(data->explicitWidth); + if (data->image.width <= 0) + data->image.width = 1; + } + if(data->explicitHeight.IsSet()) + { + data->image.height = layout.CalculateHeight(data->explicitHeight); + if (data->image.height <= 0) + data->image.height = 1; + } + } +} + +void ImageNode::GenerateLayout(Layout& layout, Node* node) +{ + ImageNode::Data* data = static_cast(node->data); + + if (!data->AreDimensionsLocked()) + { + if (data->image.width > layout.MaxAvailableWidth()) + { + int imageWidth = data->image.width; + data->image.width = layout.MaxAvailableWidth(); + data->image.height = (uint16_t)(((long)data->image.height * data->image.width) / imageWidth); + } + } + + node->size.x = data->image.width; + node->size.y = data->image.height; + + if (layout.AvailableWidth() < node->size.x) + { + layout.BreakNewLine(); + } + + node->anchor = layout.GetCursor(node->size.y); + + layout.ProgressCursor(node, node->size.x, node->size.y); +} + +void ImageNode::LoadContent(Node* node, LoadTask& loadTask) +{ + if (!App::config.loadImages) + { + return; + } + + ImageNode::Data* data = static_cast(node->data); + if (data && data->state != ImageNode::ErrorDownloading && data->state != ImageNode::FinishedDownloadingContent) + { + if (data->HasDimensions() && !App::Get().page.layout.IsFinished()) + { + return; + } + + if (!data->source) + { + ImageLoadError(node); + } + else + { + bool loadDimensionsOnly = !data->HasDimensions(); + if (!loadDimensionsOnly && App::Get().pageLoadTask.HasContent()) + return; + + loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); + data->state = ImageNode::DeterminingFormat; + } + } +} + +void ImageNode::FinishContent(Node* node, struct LoadTask& loadTask) +{ + ImageNode::Data* data = static_cast(node->data); + + if (data) + { + switch (data->state) + { + case ImageNode::DownloadingDimensions: + case ImageNode::DownloadingContent: + case ImageNode::DeterminingFormat: + ImageLoadError(node); + break; + } + } +} + +void ImageNode::ImageLoadError(Node* node) +{ + ImageNode::Data* data = static_cast(node->data); + + if (data->state != ImageNode::ErrorDownloading) + { + if (!data->HasDimensions()) + { + data->image.width = Assets.brokenImageIcon->width; + data->image.height = Assets.brokenImageIcon->height; + + if (data->altText) + { + Node* altTextNode = TextElement::Construct(MemoryManager::pageAllocator, data->altText); + if (altTextNode) + { + node->InsertSibling(altTextNode); + altTextNode->Handler().ApplyStyle(altTextNode); + } + } + } + data->state = ImageNode::ErrorDownloading; + } +} + + +bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) +{ + ImageNode::Data* data = static_cast(node->data); + + if (data->state == ImageNode::DeterminingFormat) + { + bool loadDimensionsOnly = !data->HasDimensions(); + LoadTask& loadTask = App::Get().pageContentLoadTask; + + if((loadTask.type == LoadTask::RemoteFile && ImageDecoder::CreateFromMIME(loadTask.request->GetContentType())) + || ImageDecoder::CreateFromExtension(data->source)) + { + ImageDecoder::Get()->Begin(&data->image, loadDimensionsOnly); + data->state = loadDimensionsOnly ? ImageNode::DownloadingDimensions : ImageNode::DownloadingContent; + } + else + { + // Unsupported image format + ImageLoadError(node); + return false; + } + } + + ImageDecoder* decoder = ImageDecoder::Get(); + + decoder->Process((uint8_t*) buffer, count); + if (decoder->GetState() == ImageDecoder::Success) + { + if (data->state == ImageNode::DownloadingDimensions) + { + data->state = ImageNode::FinishedDownloadingDimensions; + } + else + { + data->state = ImageNode::FinishedDownloadingContent; + App::Get().pageRenderer.MarkNodeDirty(node); + } + + // Loop through image nodes in case this image is used multiple times + for (Node* n = node; n; n = n->GetNextInTree()) + { + if (n->type == Node::Image) + { + ImageNode::Data* otherData = static_cast(n->data); + + if (otherData->source && !strcmp(data->source, otherData->source) && n != node) + { + if (!otherData->HasDimensions() || (otherData->image.width == data->image.width && otherData->image.height == data->image.height)) + { + otherData->image = data->image; + otherData->state = data->state; + + if (data->state == ImageNode::FinishedDownloadingContent) + { + App::Get().pageRenderer.MarkNodeDirty(n); + } + } + } + } + } + } + else if (decoder->GetState() != ImageDecoder::Decoding) + { + ImageLoadError(node); + } + + return decoder->GetState() == ImageDecoder::Decoding; +} diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h new file mode 100644 index 0000000..cd85987 --- /dev/null +++ b/src/Nodes/ImgNode.h @@ -0,0 +1,51 @@ +#ifndef _IMGNODE_H_ +#define _IMGNODE_H_ + +#include "../Node.h" +#include "../Image/Image.h" + +class ImageNode: public NodeHandler +{ +public: + enum State + { + WaitingToDownload, + DeterminingFormat, + DownloadingDimensions, + FinishedDownloadingDimensions, + DownloadingContent, + FinishedDownloadingContent, + ErrorDownloading + }; + + class Data + { + public: + Data() : source(nullptr), altText(nullptr), state(WaitingToDownload) {} + bool HasDimensions() { return image.width > 0 && image.height > 0; } + bool AreDimensionsLocked() { return state == DownloadingContent || state == FinishedDownloadingContent || state == ErrorDownloading; } + bool IsBrokenImageWithoutDimensions(); + Image image; + const char* source; + char* altText; + State state; + + ExplicitDimension explicitWidth; + ExplicitDimension explicitHeight; + }; + + static Node* Construct(Allocator& allocator); + virtual void Draw(DrawContext& context, Node* element) override; + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; + + virtual void LoadContent(Node* node, struct LoadTask& loadTask) override; + virtual bool ParseContent(Node* node, char* buffer, size_t count) override; + virtual void FinishContent(Node* node, struct LoadTask& loadTask) override; + + virtual bool CanPick(Node* node) override { return true; } + + void ImageLoadError(Node* node); +}; + +#endif diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp new file mode 100644 index 0000000..ac12d84 --- /dev/null +++ b/src/Nodes/LinkNode.cpp @@ -0,0 +1,134 @@ +#include "LinkNode.h" +#include "../Memory/Memory.h" +#include "../Event.h" +#include "../App.h" +#include "../KeyCodes.h" + +void LinkNode::ApplyStyle(Node* node) +{ + ElementStyle style = node->GetStyle(); + style.fontStyle = (FontStyle::Type)(style.fontStyle | FontStyle::Underline); + style.fontColour = App::Get().page.colourScheme.linkColour; + node->SetStyle(style); +} + +Node* LinkNode::Construct(Allocator& allocator, char* url) +{ + LinkNode::Data* data = allocator.Alloc(url); + if (data) + { + return allocator.Alloc(Node::Link, data); + } + return nullptr; +} + +bool LinkNode::HandleEvent(Node* node, const Event& event) +{ + LinkNode::Data* data = static_cast(node->data); + + switch (event.type) + { + case Event::Focus: + { + HighlightChildren(node); + if (data->url) + { + App::Get().ui.SetStatusMessage(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url, StatusBarNode::HoverStatus); + } + } + return true; + case Event::Unfocus: + { + HighlightChildren(node); + if (data->url) + { + App::Get().ui.ClearStatusMessage(StatusBarNode::HoverStatus); + } + } + return true; + case Event::MouseClick: + { + if (data->url) + { + App::Get().OpenURL(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url); + } + return true; + } + case Event::KeyPress: + { + if (event.key == KEYCODE_ENTER) + { + if (data->url) + { + App::Get().OpenURL(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url); + } + return true; + } + return false; + } + default: + break; + } + + return false; +} + +void LinkNode::HighlightChildren(Node* node) +{ + Node* child = node->firstChild; + if (!child) + return; + + bool isDescendingTree = true; + + while (child != node) + { + if (isDescendingTree) + { + bool shouldHighlight = false; + + switch (child->type) + { + case Node::Text: + shouldHighlight = child->firstChild == nullptr; + break; + case Node::Image: + case Node::SubText: + shouldHighlight = true; + break; + } + + if (shouldHighlight) + { + App::Get().pageRenderer.InvertNode(child); + } + + if (child->firstChild) + { + child = child->firstChild; + } + else if(child->next) + { + child = child->next; + } + else + { + isDescendingTree = false; + child = child->parent; + } + } + else + { + if (child->next) + { + child = child->next; + isDescendingTree = true; + } + else + { + child = child->parent; + } + } + } + +} diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h new file mode 100644 index 0000000..a1edce7 --- /dev/null +++ b/src/Nodes/LinkNode.h @@ -0,0 +1,23 @@ +#pragma once + +#include "../Node.h" + +class LinkNode : public NodeHandler +{ +public: + class Data + { + public: + Data(char* inURL) : url(inURL) {} + char* url; + }; + + virtual void ApplyStyle(Node* node) override; + virtual bool CanPick(Node* node) override { return true; } + virtual bool HandleEvent(Node* node, const Event& event) override; + + static Node* Construct(Allocator& allocator, char* url); + + void HighlightChildren(Node* node); + +}; \ No newline at end of file diff --git a/src/Nodes/ListItem.cpp b/src/Nodes/ListItem.cpp new file mode 100644 index 0000000..518534d --- /dev/null +++ b/src/Nodes/ListItem.cpp @@ -0,0 +1,90 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" +#include "ListItem.h" + +#define BULLET_CHARACTER '\x95' +#define BULLET_CHARACTER_STRING "\x95" + +Node* ListNode::Construct(Allocator& allocator) +{ + ListNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::List, data); + } + return nullptr; +} + +void ListNode::BeginLayoutContext(Layout& layout, Node* node) +{ + ListNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + + layout.BreakNewLine(); + layout.PadVertical(font->glyphHeight / 2); + layout.PushLayout(); + layout.PadHorizontal(16, 0); +} + +void ListNode::EndLayoutContext(Layout& layout, Node* node) +{ + ListNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + + layout.PopLayout(); + layout.BreakNewLine(); + layout.PadVertical(font->glyphHeight / 2); +} + +Node* ListItemNode::Construct(Allocator& allocator) +{ + ListItemNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::ListItem, data); + } + return nullptr; +} + +void ListItemNode::Draw(DrawContext& context, Node* node) +{ + ListItemNode::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + ElementStyle style = node->GetStyle(); + + if (data) + { + context.surface->DrawString(context, font, BULLET_CHARACTER_STRING, node->anchor.x, node->anchor.y, style.fontColour); + //context.surface->BlitImage(context, Assets.bulletIcon, node->anchor.x, node->anchor.y); + } +} + + +void ListItemNode::BeginLayoutContext(Layout& layout, Node* node) +{ + ListItemNode::Data* data = static_cast(node->data); + + Font* font = node->GetStyleFont(); + + layout.BreakNewLine(); + node->anchor = layout.GetCursor(); + //node->anchor.y += (font->glyphHeight - Assets.bulletIcon->height) / 2; + node->size.x = layout.AvailableWidth(); + layout.PushLayout(); + + int margin = font->GetGlyphWidth(BULLET_CHARACTER) * 2; + layout.PadHorizontal(margin, 0); +} + +void ListItemNode::EndLayoutContext(Layout& layout, Node* node) +{ + ListItemNode::Data* data = static_cast(node->data); + + layout.PopLayout(); + layout.BreakNewLine(); + + node->size.y = layout.Cursor().y - node->anchor.y; +} diff --git a/src/Nodes/ListItem.h b/src/Nodes/ListItem.h new file mode 100644 index 0000000..9fa1951 --- /dev/null +++ b/src/Nodes/ListItem.h @@ -0,0 +1,37 @@ +#ifndef _LISTITEM_H_ +#define _LISTITEM_H_ + +#include "../Node.h" + +class ListItemNode : public NodeHandler +{ +public: + class Data + { + public: + Data() {} + int index; + }; + + static Node* Construct(Allocator& allocator); + virtual void Draw(DrawContext& context, Node* element) override; + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; +}; + +class ListNode : public NodeHandler +{ +public: + class Data + { + public: + Data() {} + char type; + }; + + static Node* Construct(Allocator& allocator); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; +}; + +#endif diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp new file mode 100644 index 0000000..9358073 --- /dev/null +++ b/src/Nodes/Scroll.cpp @@ -0,0 +1,127 @@ +#include "Scroll.h" +#include "../Memory/Memory.h" +#include "../DataPack.h" +#include "../App.h" +#include "../Interface.h" +#include "../Draw/Surface.h" + +Node* ScrollBarNode::Construct(Allocator& allocator, int scrollPosition, int maxScroll, NodeCallbackFunction onScroll) +{ + ScrollBarNode::Data* data = allocator.Alloc(scrollPosition, maxScroll, onScroll); + if (data) + { + return allocator.Alloc(Node::ScrollBar, data); + } + + return nullptr; +} + +void ScrollBarNode::CalculateWidgetParams(Node* node, int& outPosition, int& outSize) +{ + ScrollBarNode::Data* data = static_cast(node->data); + const int minWidgetSize = 15; + const int maxWidgetSize = node->size.y; + int scrollPosition = data->scrollPosition; + + if (node == App::Get().ui.GetFocusedNode()) + { + scrollPosition = draggingScrollPosition; + } + + if (data->maxScroll == 0) + { + outPosition = 0; + outSize = maxWidgetSize; + } + else + { + outSize = (int)(((int32_t)node->size.y * node->size.y) / (data->maxScroll + node->size.y)); + if (outSize < minWidgetSize) + { + outSize = minWidgetSize; + } + if (outSize > maxWidgetSize) + { + outSize = maxWidgetSize; + } + int maxWidgetPosition = node->size.y - outSize; + outPosition = (int32_t)maxWidgetPosition * scrollPosition / data->maxScroll; + if (outPosition < 0) + outPosition = 0; + if (outPosition > maxWidgetPosition) + outPosition = maxWidgetPosition; + } +} + +void ScrollBarNode::Draw(DrawContext& context, Node* node) +{ + int widgetPosition, widgetSize; + CalculateWidgetParams(node, widgetPosition, widgetSize); + context.surface->VerticalScrollBar(context, node->anchor.x, node->anchor.y, node->size.y, widgetPosition, widgetSize); +} + +bool ScrollBarNode::HandleEvent(Node* node, const Event& event) +{ + AppInterface& ui = App::Get().ui; + ScrollBarNode::Data* data = static_cast(node->data); + int widgetPosition, widgetSize; + CalculateWidgetParams(node, widgetPosition, widgetSize); + int maxWidgetPosition = node->size.y - widgetSize; + + switch (event.type) + { + case Event::Focus: + case Event::Unfocus: + return true; + case Event::MouseClick: + { + if (event.y < widgetPosition + node->anchor.y) + { + ui.ScrollRelative(-(ui.windowRect.height - 24)); + } + else if (event.y >= widgetPosition + widgetSize + node->anchor.y) + { + ui.ScrollRelative((ui.windowRect.height - 24)); + } + else + { + ui.FocusNode(node); + startDragOffset = (event.y - node->anchor.y) - widgetPosition; + draggingScrollPosition = data->scrollPosition; + } + } + return true; + case Event::MouseRelease: + { + ui.FocusNode(nullptr); + + data->scrollPosition = draggingScrollPosition; + if (data->onScroll) + { + data->onScroll(node); + } + } + return true; + case Event::MouseDrag: + { + if (maxWidgetPosition > 0) + { + int dragPosition = (event.y - node->anchor.y) - startDragOffset; + int newScrollPosition = ((long)dragPosition * data->maxScroll) / maxWidgetPosition; + if (newScrollPosition < 0) + newScrollPosition = 0; + else if (newScrollPosition > data->maxScroll) + newScrollPosition = data->maxScroll; + + if (newScrollPosition != draggingScrollPosition) + { + draggingScrollPosition = newScrollPosition; + node->Redraw(); + } + } + } + return true; + } + return false; +} + diff --git a/src/Nodes/Scroll.h b/src/Nodes/Scroll.h new file mode 100644 index 0000000..a931841 --- /dev/null +++ b/src/Nodes/Scroll.h @@ -0,0 +1,29 @@ +#ifndef _SCROLL_H_ +#define _SCROLL_H_ + +#include "../Node.h" + +class ScrollBarNode : public NodeHandler +{ +public: + class Data + { + public: + Data(int inScrollPosition, int inMaxScroll, NodeCallbackFunction inOnScroll) : scrollPosition(inScrollPosition), maxScroll(inMaxScroll), onScroll(inOnScroll) { } + int scrollPosition; + int maxScroll; + NodeCallbackFunction onScroll; + }; + + static Node* Construct(Allocator& allocator, int scrollPosition = 0, int maxScroll = 0, NodeCallbackFunction onScroll = nullptr); + virtual void Draw(DrawContext& context, Node* node) override; + virtual bool HandleEvent(Node* node, const Event& event) override; + virtual bool CanPick(Node* node) { return true; } + + void CalculateWidgetParams(Node* node, int& outPosition, int& outSize); + + int startDragOffset; + int draggingScrollPosition; +}; + +#endif diff --git a/src/Nodes/Section.cpp b/src/Nodes/Section.cpp new file mode 100644 index 0000000..cc16838 --- /dev/null +++ b/src/Nodes/Section.cpp @@ -0,0 +1,16 @@ + +#include "Section.h" +#include "../Memory/Memory.h" +#include "../Layout.h" + +Node* SectionElement::Construct(Allocator& allocator, SectionElement::Type sectionType) +{ + SectionElement::Data* data = allocator.Alloc(sectionType); + + if (data) + { + return allocator.Alloc(Node::Section, data); + } + return nullptr; +} + diff --git a/src/Nodes/Section.h b/src/Nodes/Section.h new file mode 100644 index 0000000..5913cb0 --- /dev/null +++ b/src/Nodes/Section.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../Node.h" + +class SectionElement : public NodeHandler +{ +public: + enum Type + { + Document, + HTML, + Head, + Body, + Script, + Style, + Title, + Interface + }; + + class Data + { + public: + Data(SectionElement::Type inType) : type(inType) { } + SectionElement::Type type; + }; + + static Node* Construct(Allocator& allocator, SectionElement::Type sectionType); +}; + diff --git a/src/Nodes/Select.cpp b/src/Nodes/Select.cpp new file mode 100644 index 0000000..3c99981 --- /dev/null +++ b/src/Nodes/Select.cpp @@ -0,0 +1,405 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" +#include "../App.h" +#include "../KeyCodes.h" +#include "Select.h" + +Node* SelectNode::Construct(Allocator& allocator, const char* name) +{ + SelectNode::Data* data = allocator.Alloc(allocator.AllocString(name)); + if (data) + { + return allocator.Alloc(Node::Select, data); + } + return nullptr; +} + +void SelectNode::ApplyStyle(Node* node) +{ + ElementStyle style = node->GetStyle(); + style.fontStyle = FontStyle::Regular; + style.fontSize = 1; + node->SetStyle(style); +} + + +void SelectNode::Draw(DrawContext& context, Node* node) +{ + SelectNode::Data* data = static_cast(node->data); + + Font* font = node->GetStyleFont(); + uint8_t textColour = Platform::video->colourScheme.textColour; + uint8_t buttonOutlineColour = Platform::video->colourScheme.textColour; + uint8_t clearColour = Platform::video->colourScheme.pageColour; + context.surface->FillRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 2, clearColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 1, node->size.x - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 1, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + + if (data->selected && data->selected->text) + { + context.surface->DrawString(context, font, data->selected->text, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } + context.surface->BlitImage(context, Assets.downIcon, node->anchor.x + node->size.x - Assets.downIcon->width - 2, node->anchor.y + (node->size.y - Assets.downIcon->height) / 2); + + if (App::Get().ui.GetFocusedNode() == node) + { + if (node == dropDownMenu.activeNode) + { + DrawDropDownMenu(); + } + else + { + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, buttonOutlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 2, node->size.x - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 1, node->size.y - 2, buttonOutlineColour); + } + } +} + +void SelectNode::DrawHighlight(Node* node, uint8_t colour) +{ + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Platform::input->HideMouse(); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, colour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 2, node->size.x - 2, colour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.y - 2, colour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 1, node->size.y - 2, colour); + Platform::input->ShowMouse(); +} + + +void SelectNode::EndLayoutContext(Layout& layout, Node* node) +{ + SelectNode::Data* data = static_cast(node->data); + + Font* font = node->GetStyleFont(); + + node->size.y = font->glyphHeight + 4; + node->size.x = node->size.y; + + for (OptionNode::Data* option = data->firstOption; option; option = option->next) + { + if (option->node->size.x > node->size.x) + { + node->size.x = option->node->size.x; + } + } + + node->size.x += 8 + Assets.downIcon->width; + + if (layout.AvailableWidth() < node->size.x) + { + layout.BreakNewLine(); + } + + node->anchor = layout.GetCursor(node->size.y); + layout.ProgressCursor(node, node->size.x, node->size.y); +} + +Node* OptionNode::Construct(Allocator& allocator) +{ + OptionNode::Data* data = allocator.Alloc(); + if (data) + { + Node* result = allocator.Alloc(Node::Option, data); + data->node = result; + return result; + } + return nullptr; +} + +void OptionNode::GenerateLayout(Layout& layout, Node* node) +{ + OptionNode::Data* data = static_cast(node->data); + if (!data->addedToSelectNode) + { + SelectNode::Data* select = node->FindParentDataOfType(Node::Select); + if (select) + { + if (!select->firstOption) + { + select->firstOption = data; + } + else + { + for (OptionNode::Data* option = select->firstOption; option; option = option->next) + { + if (!option->next) + { + option->next = data; + break; + } + } + } + + if(!select->selected) + { + select->selected = data; + } + + data->addedToSelectNode = true; + } + + Font* font = node->GetStyleFont(); + + if (data->text) + { + node->size.x = font->CalculateWidth(data->text, node->GetStyle().fontStyle); + } + } +} + +bool SelectNode::HandleEvent(Node* node, const Event& event) +{ + AppInterface& ui = App::Get().ui; + SelectNode::Data* data = static_cast(node->data); + + switch (event.type) + { + case Event::MouseClick: + { + if (node == dropDownMenu.activeNode) + { + int clickX = event.x; + int clickY = event.y + ui.GetScrollPositionY() - ui.windowRect.y; + if (node->IsPointInsideNode(clickX, clickY)) + { + ui.FocusNode(nullptr); + } + else + { + if (clickX >= dropDownMenu.rect.x && clickX < dropDownMenu.rect.x + dropDownMenu.rect.width + && clickY >= dropDownMenu.rect.y && clickY < dropDownMenu.rect.y + dropDownMenu.rect.height) + { + Font* font = node->GetStyleFont(); + int index = (clickY - dropDownMenu.rect.y + 1) / font->glyphHeight; + + for (OptionNode::Data* option = data->firstOption; option; option = option->next) + { + if (!index) + { + data->selected = option; + break; + } + index--; + } + + ui.FocusNode(nullptr); + node->Redraw(); + } + } + } + else + { + ShowDropDownMenu(node); + ui.FocusNode(node); + } + //if (data->selected) + //{ + // data->selected = data->selected->next; + // if (!data->selected) + // { + // data->selected = data->firstOption; + // } + // node->Redraw(); + //} + } + return true; + case Event::Focus: + if (dropDownMenu.activeNode == node) + { + DrawDropDownMenu(); + } + else + { + DrawHighlight(node, Platform::video->colourScheme.textColour); + } + return true; + case Event::Unfocus: + if (dropDownMenu.activeNode == node) + { + CloseDropDownMenu(); + } + else + { + DrawHighlight(node, Platform::video->colourScheme.pageColour); + } + return true; + + case Event::KeyPress: + switch (event.key) + { + case KEYCODE_ARROW_UP: + case KEYCODE_ARROW_LEFT: + if (data->selected) + { + if (data->selected == data->firstOption) + { + while (data->selected->next) + { + data->selected = data->selected->next; + } + } + else + { + for (OptionNode::Data* option = data->firstOption; option; option = option->next) + { + if (option->next == data->selected) + { + data->selected = option; + break; + } + } + } + node->Redraw(); + } + return true; + case KEYCODE_ARROW_DOWN: + case KEYCODE_ARROW_RIGHT: + if (data->selected) + { + data->selected = data->selected->next; + if (!data->selected) + { + data->selected = data->firstOption; + } + node->Redraw(); + } + return true; + + case KEYCODE_PAGE_UP: + case KEYCODE_PAGE_DOWN: + case KEYCODE_HOME: + case KEYCODE_END: + return node == dropDownMenu.activeNode; + } + break; + + } + + return false; +} + +Node* SelectNode::Pick(Node* node, int x, int y) +{ + if (node && node == dropDownMenu.activeNode) + { + if (x >= dropDownMenu.rect.x && y >= dropDownMenu.rect.y && x < dropDownMenu.rect.x + dropDownMenu.rect.width && y < dropDownMenu.rect.y + dropDownMenu.rect.height) + { + return node; + } + } + + if (node && (!node->size.IsZero() && node->IsPointInsideNode(x, y))) + { + return node; + } + + return nullptr; +} + +void SelectNode::DrawDropDownMenu() +{ + Font* font = dropDownMenu.activeNode->GetStyleFont(); + SelectNode::Data* data = static_cast(dropDownMenu.activeNode->data); + + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, dropDownMenu.activeNode); + + Platform::input->HideMouse(); + uint8_t clearColour = Platform::video->colourScheme.pageColour; + uint8_t textColour = Platform::video->colourScheme.textColour; + + context.surface->FillRect(context, dropDownMenu.rect.x, dropDownMenu.rect.y, dropDownMenu.rect.width, dropDownMenu.rect.height, clearColour); + context.surface->HLine(context, dropDownMenu.rect.x, dropDownMenu.rect.y, dropDownMenu.rect.width, textColour); + context.surface->HLine(context, dropDownMenu.rect.x, dropDownMenu.rect.y + dropDownMenu.rect.height - 1, dropDownMenu.rect.width, textColour); + context.surface->VLine(context, dropDownMenu.rect.x, dropDownMenu.rect.y + 1, dropDownMenu.rect.height - 2, textColour); + context.surface->VLine(context, dropDownMenu.rect.x + dropDownMenu.rect.width - 1, dropDownMenu.rect.y + 1, dropDownMenu.rect.height - 2, textColour); + + int optionY = dropDownMenu.rect.y + 1; + for (OptionNode::Data* option = data->firstOption; option; option = option->next) + { + if (option == data->selected) + { + context.surface->FillRect(context, dropDownMenu.rect.x + 1, optionY, dropDownMenu.rect.width - 2, font->glyphHeight, textColour); + context.surface->DrawString(context, font, option->text, dropDownMenu.rect.x + 2, optionY, clearColour); + } + else + { + context.surface->DrawString(context, font, option->text, dropDownMenu.rect.x + 2, optionY, textColour); + } + optionY += font->glyphHeight; + } + + Platform::input->ShowMouse(); +} + +void SelectNode::ShowDropDownMenu(Node *node) +{ + dropDownMenu.activeNode = node; + dropDownMenu.numOptions = 0; + + SelectNode::Data* data = static_cast(dropDownMenu.activeNode->data); + + for (OptionNode::Data* option = data->firstOption; option; option = option->next) + { + dropDownMenu.numOptions++; + } + + AppInterface& ui = App::Get().ui; + Rect& windowRect = ui.windowRect; + Font* font = node->GetStyleFont(); + dropDownMenu.rect.height = font->glyphHeight * dropDownMenu.numOptions + 2; + dropDownMenu.rect.width = dropDownMenu.activeNode->size.x; + dropDownMenu.rect.x = dropDownMenu.activeNode->anchor.x; + dropDownMenu.rect.y = dropDownMenu.activeNode->anchor.y + dropDownMenu.activeNode->size.y - 1; + + int offsetY = windowRect.y - ui.GetScrollPositionY(); + if (dropDownMenu.rect.y + dropDownMenu.rect.height + offsetY > windowRect.y + windowRect.height) + { + // Cut off the bottom of the screen + + int topPosition = dropDownMenu.activeNode->anchor.y - dropDownMenu.rect.height + 1; + if (topPosition + offsetY < windowRect.y) + { + // Still cut off + int topCutoff = windowRect.y - (topPosition + offsetY); + int bottomCutoff = (dropDownMenu.rect.y + dropDownMenu.rect.height + offsetY) - (windowRect.y + windowRect.height); + + // TODO: add scroll bars and fit to window + if (topCutoff < bottomCutoff) + { + dropDownMenu.rect.y = topPosition; + } + else + { + + } + } + else + { + dropDownMenu.rect.y = topPosition; + } + } + + App::Get().pageRenderer.SetPaused(true); +} + +void SelectNode::CloseDropDownMenu() +{ + if (dropDownMenu.activeNode) + { + int offsetY = App::Get().ui.windowRect.y - App::Get().ui.GetScrollPositionY(); + App::Get().pageRenderer.MarkScreenRegionDirty(dropDownMenu.rect.x, dropDownMenu.rect.y + offsetY, dropDownMenu.rect.x + dropDownMenu.rect.width, dropDownMenu.rect.y + dropDownMenu.rect.height + offsetY); + dropDownMenu.activeNode = nullptr; + App::Get().pageRenderer.SetPaused(false); + } +} diff --git a/src/Nodes/Select.h b/src/Nodes/Select.h new file mode 100644 index 0000000..6e5fda5 --- /dev/null +++ b/src/Nodes/Select.h @@ -0,0 +1,60 @@ +#ifndef _SELET_H_ +#define _SELET_H_ + +#include "../Node.h" + +class OptionNode : public NodeHandler +{ +public: + class Data + { + public: + Data() : node(nullptr), text(nullptr), next(nullptr), addedToSelectNode(false) {} + Node* node; + const char* text; + Data* next; + bool addedToSelectNode; + }; + + static Node* Construct(Allocator& allocator); + virtual void GenerateLayout(Layout& layout, Node* node) override; +}; + +class SelectNode : public NodeHandler +{ +public: + class Data + { + public: + Data(const char* inName) : name(inName), firstOption(nullptr), selected(nullptr) {} + const char* name; + OptionNode::Data* firstOption; + OptionNode::Data* selected; + }; + + virtual void ApplyStyle(Node* node) override; + static Node* Construct(Allocator& allocator, const char* inName); + virtual void Draw(DrawContext& context, Node* element) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; + + virtual bool CanPick(Node* node) { return true; } + virtual bool HandleEvent(Node* node, const Event& event); + virtual Node* Pick(Node* node, int x, int y) override; + + void DrawHighlight(Node* node, uint8_t colour); + + void DrawDropDownMenu(); + void ShowDropDownMenu(Node *node); + void CloseDropDownMenu(); + + struct DropDownMenu + { + Node* activeNode; + int numOptions; + Rect rect; + }; + + DropDownMenu dropDownMenu; +}; + +#endif diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp new file mode 100644 index 0000000..35bc342 --- /dev/null +++ b/src/Nodes/Status.cpp @@ -0,0 +1,46 @@ +#include "Status.h" +#include "../Memory/Memory.h" +#include "../DataPack.h" +#include "../App.h" +#include "../Interface.h" +#include "../Draw/Surface.h" + +Node* StatusBarNode::Construct(Allocator& allocator) +{ + StatusBarNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::StatusBar, data); + } + + return nullptr; +} + +void StatusBarNode::Draw(DrawContext& context, Node* node) +{ + StatusBarNode::Data* data = static_cast(node->data); + + Font* font = node->GetStyleFont(); + uint8_t textColour = Platform::video->colourScheme.textColour; + uint8_t clearColour = Platform::video->colourScheme.pageColour; + context.surface->HLine(context, node->anchor.x, node->anchor.y, node->size.x, textColour); + context.surface->FillRect(context, node->anchor.x, node->anchor.y + 1, node->size.x, node->size.y - 1, clearColour); + + const char* message = data->messages[1].HasMessage() ? data->messages[1].message : data->messages[0].message; + context.surface->DrawString(context, font, message, node->anchor.x + 1, node->anchor.y + 1, textColour, node->GetStyle().fontStyle); +} + +void StatusBarNode::SetStatus(Node* node, const char* message, StatusType type) +{ + StatusBarNode::Data* data = static_cast(node->data); + + if (message) + { + strncpy(data->messages[type].message, message, MAX_STATUS_BAR_MESSAGE_LENGTH); + } + else + { + data->messages[type].Clear(); + } + node->Redraw(); +} diff --git a/src/Nodes/Status.h b/src/Nodes/Status.h new file mode 100644 index 0000000..c735b5b --- /dev/null +++ b/src/Nodes/Status.h @@ -0,0 +1,46 @@ +#ifndef _STATUS_H_ +#define _STATUS_H_ + +#include "../Node.h" + +#define STATUS_MESSAGE_BUFFER_SIZE 100 +#define MAX_STATUS_BAR_MESSAGE_LENGTH (STATUS_MESSAGE_BUFFER_SIZE - 4) + +class StatusBarNode : public NodeHandler +{ +public: + enum StatusType + { + GeneralStatus, + HoverStatus, + NumStatusTypes + }; + + class Data + { + public: + struct Message + { + Message() + { + message[0] = '\0'; + message[STATUS_MESSAGE_BUFFER_SIZE - 1] = '\0'; + message[STATUS_MESSAGE_BUFFER_SIZE - 2] = '.'; + message[STATUS_MESSAGE_BUFFER_SIZE - 3] = '.'; + message[STATUS_MESSAGE_BUFFER_SIZE - 4] = '.'; + } + void Clear() { message[0] = '\0'; } + bool HasMessage() { return message[0] != '\0'; } + + char message[STATUS_MESSAGE_BUFFER_SIZE]; + }; + Message messages[NumStatusTypes]; + }; + + static Node* Construct(Allocator& allocator); + virtual void Draw(DrawContext& context, Node* node) override; + + static void SetStatus(Node* node, const char* message, StatusType type); +}; + +#endif diff --git a/src/Nodes/StyNode.cpp b/src/Nodes/StyNode.cpp new file mode 100644 index 0000000..645d6f1 --- /dev/null +++ b/src/Nodes/StyNode.cpp @@ -0,0 +1,67 @@ +#include "StyNode.h" +#include "../Memory/Memory.h" +#include "../Layout.h" + +void StyleNode::ApplyStyle(Node* node) +{ + StyleNode::Data* data = static_cast(node->data); + ElementStyle style = node->GetStyle(); + data->styleOverride.Apply(style); + node->SetStyle(style); +} + +void StyleNode::GenerateLayout(Layout& layout, Node* node) +{ + StyleNode::Data* data = static_cast(node->data); + if (data->styleOverride.overrideMask.alignment) + { + layout.BreakNewLine(); + } +} + +Node* StyleNode::Construct(Allocator& allocator) +{ + StyleNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Style, data); + } + return nullptr; +} + +Node* StyleNode::ConstructFontStyle(Allocator& allocator, FontStyle::Type fontStyle, int fontSize) +{ + StyleNode::Data* data = allocator.Alloc(); + if (data) + { + data->styleOverride.SetFontStyle(fontStyle); + if (fontSize >= 0) + { + data->styleOverride.SetFontSize(fontSize); + } + return allocator.Alloc(Node::Style, data); + } + return nullptr; +} + +Node* StyleNode::ConstructFontSize(Allocator& allocator, int fontSize) +{ + StyleNode::Data* data = allocator.Alloc(); + if (data) + { + data->styleOverride.SetFontSize(fontSize); + return allocator.Alloc(Node::Style, data); + } + return nullptr; +} + +Node* StyleNode::ConstructAlignment(Allocator& allocator, ElementAlignment::Type alignment) +{ + StyleNode::Data* data = allocator.Alloc(); + if (data) + { + data->styleOverride.SetAlignment(alignment); + return allocator.Alloc(Node::Style, data); + } + return nullptr; +} diff --git a/src/Nodes/StyNode.h b/src/Nodes/StyNode.h new file mode 100644 index 0000000..ce54667 --- /dev/null +++ b/src/Nodes/StyNode.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include "../Node.h" +#include "../Font.h" +#include "../Style.h" + +class StyleNode : public NodeHandler +{ +public: + class Data + { + public: + ElementStyleOverride styleOverride; + }; + + virtual void ApplyStyle(Node* node) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; + + static Node* Construct(Allocator& allocator); + static Node* ConstructFontStyle(Allocator& allocator, FontStyle::Type fontStyle, int fontSize = -1); + static Node* ConstructFontSize(Allocator& allocator, int fontSize); + static Node* ConstructAlignment(Allocator& allocator, ElementAlignment::Type alignment); +}; + diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp new file mode 100644 index 0000000..6cbf40c --- /dev/null +++ b/src/Nodes/Table.cpp @@ -0,0 +1,753 @@ +#include +#include "../Layout.h" +#include "../Memory/Memory.h" +#include "../Draw/Surface.h" +#include "../VidModes.h" +#include "Table.h" + +/* + * Table layout generation is done in 2 passes: + * 1) Each cell has its content generated with the maximum available width to + * work out the preferred size + * 2) Column and row dimensions are calculated based on preferred sizes + */ + +Node* TableNode::Construct(Allocator& allocator) +{ + TableNode::Data* data = allocator.Alloc(); + if (data) + { + data->cellPadding = 2; + data->cellSpacing = 2; + return allocator.Alloc(Node::Table, data); + } + return nullptr; +} + +void TableNode::Draw(DrawContext& context, Node* node) +{ + TableNode::Data* data = static_cast(node->data); + + int x = node->anchor.x; + int y = node->anchor.y; + int w = node->size.x; + int h = node->size.y; + bool needsFill = data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->format != DrawSurface::Format_1BPP; + + if (data->border) + { + uint8_t borderColour = Platform::video->colourScheme.textColour; + context.surface->HLine(context, x, y, w, borderColour); + context.surface->HLine(context, x, y + h - 1, w, borderColour); + context.surface->VLine(context, x, y + 1, h - 2, borderColour); + context.surface->VLine(context, x + w - 1, y + 1, h - 2, borderColour); + + if (needsFill) + { + context.surface->FillRect(context, x + 1, y + 1, w - 2, h - 2, data->bgColour); + } + } + else if (needsFill) + { + context.surface->FillRect(context, x, y, w, h, data->bgColour); + } +} + +void TableNode::BeginLayoutContext(Layout& layout, Node* node) +{ + TableNode::Data* data = static_cast(node->data); + + layout.BreakNewLine(); + layout.tableDepth++; + layout.PushCursor(); + layout.PushLayout(); + + node->anchor = layout.Cursor(); + int availableWidth = layout.AvailableWidth(); + + if (data->state == Data::FinishedLayout) + { + //if (availableWidth != data->lastAvailableWidth) + { + data->state = Data::GeneratingLayout; + } + } + + data->lastAvailableWidth = availableWidth; + + if (!data->IsGeneratingLayout()) + { + layout.PadHorizontal(data->cellSpacing, data->cellSpacing); + if (data->numRows > 0) + { + layout.PadVertical(data->cellSpacing); + } + } +} + +void TableNode::EndLayoutContext(Layout& layout, Node* node) +{ + TableNode::Data* data = static_cast(node->data); + + layout.PopLayout(); + layout.PopCursor(); + + if (data->IsGeneratingLayout()) + { + if (!data->HasGeneratedCellGrid()) + { + // Calculate number of rows and columns + data->numRows = data->numColumns = 0; + + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + { + int columnCount = 0; + for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) + { + columnCount += cell->columnSpan; + } + data->numRows++; + + if (columnCount > data->numColumns) + { + data->numColumns = columnCount; + } + } + + + if (!data->cells) + { + data->cells = (TableCellNode::Data**)MemoryManager::pageAllocator.Allocate(sizeof(TableCellNode::Data*) * data->numRows * data->numColumns); + } + + if (!data->columns) + { + data->columns = (TableNode::Data::ColumnInfo*)MemoryManager::pageAllocator.Allocate(sizeof(TableNode::Data::ColumnInfo) * data->numColumns); + } + + if (!data->cells || !data->columns) + { + // TODO: allocation error + return; + } + + for (int n = 0; n < data->numRows * data->numColumns; n++) + { + data->cells[n] = nullptr; + } + + // Fill a grid array with pointers to cells + int rowIndex = 0; + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + { + int columnIndex = 0; + + for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) + { + while (data->cells[rowIndex * data->numColumns + columnIndex] && columnIndex < data->numColumns) + { + columnIndex++; + } + if (columnIndex == data->numColumns) + { + break; + } + + for (int j = 0; j < cell->rowSpan; j++) + { + for (int i = 0; i < cell->columnSpan; i++) + { + data->cells[(rowIndex + j) * data->numColumns + columnIndex + i] = cell; + } + } + + cell->columnIndex = columnIndex; + cell->rowIndex = rowIndex; + columnIndex += cell->columnSpan; + } + rowIndex++; + } + } + + int minCellWidth = 16; + + for (int n = 0; n < data->numColumns; n++) + { + data->columns[n].Clear(); + //data->columns[n].preferredWidth = minCellWidth; + } + + // Cull unused columns + for (int n = data->numColumns - 1; n >= 0; n--) + { + bool isUsed = false; + + for (TableRowNode::Data* row = data->firstRow; row && !isUsed; row = row->nextRow) + { + for (TableCellNode::Data* cell = row->firstCell; cell && !isUsed; cell = cell->nextCell) + { + if (cell->columnIndex == n) + { + isUsed = true; + } + } + } + + if (isUsed) + { + break; + } + else + { + data->numColumns--; + } + } + + int maxConstrainedTableWidth = layout.MaxAvailableWidth(); + if (data->explicitWidth.IsSet()) + { + maxConstrainedTableWidth = layout.CalculateWidth(data->explicitWidth); + } + + // Find out preferred column widths in two passes: + // - First pass, check with cells of column span = 1 + // - Second pass for cells of column span > 1 + for (int pass = 0; pass < 2; pass++) + { + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + { + for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) + { + if (pass == 0 && cell->columnSpan > 1) + continue; + if (pass == 1 && cell->columnSpan == 1) + continue; + + int preferredWidth = (2 * data->cellPadding + cell->node->size.x); + int explicitWidth = 0; + int explicitWidthPercentage = 0; + + if (cell->explicitWidth.IsSet()) + { + if (cell->explicitWidth.IsPercentage()) + { + explicitWidthPercentage = cell->explicitWidth.Value(); + } + else + { + explicitWidth = ((long)cell->explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom) / 100; + } + } + + if (preferredWidth > maxConstrainedTableWidth) + { + preferredWidth = maxConstrainedTableWidth; + } + if (explicitWidth > maxConstrainedTableWidth) + { + explicitWidth = maxConstrainedTableWidth; + } + + if (pass == 0) + { + if (data->columns[cell->columnIndex].preferredWidth < preferredWidth) + { + data->columns[cell->columnIndex].preferredWidth = preferredWidth; + } + if (data->columns[cell->columnIndex].explicitWidthPercentage < explicitWidthPercentage) + { + data->columns[cell->columnIndex].explicitWidthPercentage = explicitWidthPercentage; + } + if (data->columns[cell->columnIndex].explicitWidthPixels < explicitWidth) + { + data->columns[cell->columnIndex].explicitWidthPixels = explicitWidth; + } + } + else + { + int columnsPreferredWidth = data->cellSpacing * (cell->columnSpan - 1); + int columnsExplicitWidthPercentage = 0; + int columnsExplicitWidthPixels = 0; + + for (int i = 0; i < cell->columnSpan; i++) + { + columnsPreferredWidth += data->columns[cell->columnIndex + i].preferredWidth; + columnsExplicitWidthPercentage += data->columns[cell->columnIndex + i].explicitWidthPercentage; + columnsExplicitWidthPixels += data->columns[cell->columnIndex + i].explicitWidthPixels; + } + + if (columnsPreferredWidth < preferredWidth) + { + for (int i = 0; i < cell->columnSpan; i++) + { + data->columns[cell->columnIndex + i].preferredWidth += (preferredWidth - columnsPreferredWidth) / cell->columnSpan; + } + } + if (columnsExplicitWidthPercentage < explicitWidthPercentage) + { + for (int i = 0; i < cell->columnSpan; i++) + { + data->columns[cell->columnIndex + i].explicitWidthPercentage += (explicitWidthPercentage - columnsExplicitWidthPercentage) / cell->columnSpan; + } + } + if (columnsExplicitWidthPixels < explicitWidth) + { + for (int i = 0; i < cell->columnSpan; i++) + { + data->columns[cell->columnIndex + i].explicitWidthPixels += (explicitWidth - columnsExplicitWidthPixels) / cell->columnSpan; + } + } + } + } + } + } + + data->totalWidth = 0; + int totalCellSpacing = (data->numColumns + 1) * data->cellSpacing; + + if (!data->explicitWidth.IsSet()) + { + // Need to calculate the width of the table as it wasn't specified + int totalPreferredWidth = 0; + int maxAvailableWidthForCells = layout.MaxAvailableWidth() - totalCellSpacing; + int widthRemaining = maxAvailableWidthForCells; + + for (int i = 0; i < data->numColumns; i++) + { + if (data->columns[i].explicitWidthPixels) + { + data->columns[i].calculatedWidth = data->columns[i].explicitWidthPixels; + } + else if (data->columns[i].explicitWidthPercentage) + { + data->columns[i].calculatedWidth = 0; + } + else + { + data->columns[i].calculatedWidth = data->columns[i].preferredWidth; + } + totalPreferredWidth += data->columns[i].calculatedWidth; + } + + // Enforce percentage constraints + for (int it = 0; it < data->numColumns; it++) + { + bool changesMade = false; + + for (int i = 0; i < data->numColumns; i++) + { + if (data->columns[i].explicitWidthPercentage) + { + int desiredWidth = (data->columns[i].explicitWidthPercentage * totalPreferredWidth) / 100; + + if (desiredWidth != data->columns[i].calculatedWidth) + { + totalPreferredWidth -= data->columns[i].calculatedWidth; + long z = data->columns[i].explicitWidthPercentage; + data->columns[i].calculatedWidth = (z * totalPreferredWidth) / (100 - z); + totalPreferredWidth += data->columns[i].calculatedWidth; + changesMade = true; + } + } + } + + if (!changesMade) + break; + } + + if (totalPreferredWidth <= maxAvailableWidthForCells) + { + data->totalWidth = totalPreferredWidth + totalCellSpacing; + node->size.x = data->totalWidth; + } + } + + if (data->explicitWidth.IsSet() || !data->totalWidth) + { + // Generate widths for columns based on a given table width + if (data->explicitWidth.IsSet()) + { + data->totalWidth = layout.CalculateWidth(data->explicitWidth); + } + else + { + data->totalWidth = layout.MaxAvailableWidth(); + } + node->size.x = data->totalWidth; + + int maxAvailableWidthForCells = node->size.x - totalCellSpacing; + int widthRemaining = maxAvailableWidthForCells; + int totalUnsetWidth = 0; + int minUnsetWidth = 0; + minCellWidth = data->numColumns ? data->totalWidth / (data->numColumns * 2) : 0; + + // First pass allocate widths to explicit pixels widths + for (int i = 0; i < data->numColumns; i++) + { + if (data->columns[i].explicitWidthPixels) + { + data->columns[i].calculatedWidth = data->columns[i].explicitWidthPixels; + } + if (data->columns[i].explicitWidthPercentage) + { + int calculatedWidth = (long)(data->columns[i].explicitWidthPercentage * maxAvailableWidthForCells) / 100; + if (calculatedWidth > data->columns[i].calculatedWidth) + { + data->columns[i].calculatedWidth = calculatedWidth; + } + } + + if (data->columns[i].calculatedWidth) + { + widthRemaining -= data->columns[i].calculatedWidth; + } + else + { + totalUnsetWidth += data->columns[i].preferredWidth; + + if(data->columns[i].preferredWidth) + minUnsetWidth += minCellWidth; + } + } + + int totalCellsWidth = 0; + + if (widthRemaining < minUnsetWidth) + { + int widthForSetCells = maxAvailableWidthForCells - minUnsetWidth; + int totalSetWidth = maxAvailableWidthForCells - widthRemaining; + + // Explicit cell widths too large to fit in table, readjust + for (int i = 0; i < data->numColumns; i++) + { + if (!data->columns[i].calculatedWidth) + { + if(data->columns[i].preferredWidth) + data->columns[i].calculatedWidth = minCellWidth; + } + else + { + data->columns[i].calculatedWidth = ((long)widthForSetCells * data->columns[i].calculatedWidth) / totalSetWidth; + } + totalCellsWidth += data->columns[i].calculatedWidth; + } + } + else + { + for (int i = 0; i < data->numColumns; i++) + { + if (!data->columns[i].calculatedWidth && totalUnsetWidth) + { + data->columns[i].calculatedWidth = ((long)widthRemaining * data->columns[i].preferredWidth) / totalUnsetWidth; + } + totalCellsWidth += data->columns[i].calculatedWidth; + } + } + + if (totalCellsWidth < maxAvailableWidthForCells && data->numColumns > 0) + { + data->columns[data->numColumns - 1].calculatedWidth += maxAvailableWidthForCells - totalCellsWidth; + } + } + + layout.PushCursor(); + layout.PushLayout(); + + int available = layout.AvailableWidth(); + if (data->totalWidth < available) + { + int alignmentPadding = 0; + + if (node->GetStyle().alignment == ElementAlignment::Center) + { + alignmentPadding = (available - data->totalWidth) / 2; + } + else if (node->GetStyle().alignment == ElementAlignment::Right) + { + alignmentPadding = (available - data->totalWidth); + } + if (alignmentPadding) + { + layout.PadHorizontal(alignmentPadding, 0); + node->anchor = layout.Cursor(); + } + } + + data->state = Data::FinalisingLayout; + layout.RecalculateLayoutForNode(node); + + layout.PopLayout(); + layout.PopCursor(); + + data->state = Data::FinishedLayout; + } + + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + { + if (!row->nextRow) + { + // Use last row's dimensions to calculate how tall the table should be + int bottom = row->node->anchor.y + row->node->size.y; + node->size.y = bottom - node->anchor.y + data->cellSpacing; + } + } + + layout.tableDepth--; + layout.PadVertical(node->size.y); + layout.BreakNewLine(); +} + +// Table row node + +Node* TableRowNode::Construct(Allocator& allocator) +{ + TableRowNode::Data* data = allocator.Alloc(); + if (data) + { + data->node = allocator.Alloc(Node::TableRow, data); + return data->node; + } + return nullptr; +} + +void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) +{ + TableRowNode::Data* data = static_cast(node->data); + + layout.BreakNewLine(); + + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + if (tableData) + { + if (tableData->IsGeneratingLayout()) + { + if (!tableData->HasGeneratedCellGrid()) + { + if (!tableData->firstRow) + { + tableData->firstRow = data; + } + else + { + for (TableRowNode::Data* row = tableData->firstRow; row; row = row->nextRow) + { + if (row->nextRow == nullptr) + { + row->nextRow = data; + break; + } + } + } + data->rowIndex = tableData->numRows; + tableData->numRows++; + } + } + else + { + node->anchor = layout.Cursor(); + node->size.x = tableData->totalWidth; + } + } + + layout.PushCursor(); + layout.PushLayout(); +} + +void TableRowNode::EndLayoutContext(Layout& layout, Node* node) +{ + TableRowNode::Data* data = static_cast(node->data); + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + + if (tableData) + { + layout.BreakNewLine(); + layout.PopLayout(); + layout.PopCursor(); + + if (!tableData->IsGeneratingLayout()) + { + node->size.y = 0; + for (TableCellNode::Data* cellData = data->firstCell; cellData; cellData = cellData->nextCell) + { + int cellBottom = cellData->node->anchor.y + cellData->node->size.y; + if (cellBottom > node->anchor.y + node->size.y) + { + node->size.y = cellBottom - node->anchor.y; + } + } + for (TableCellNode::Data* cellData = data->firstCell; cellData; cellData = cellData->nextCell) + { + cellData->node->size.y = node->size.y; + } + layout.PadVertical(node->size.y + tableData->cellSpacing); + } + } +} + +void TableRowNode::ApplyStyle(Node* node) +{ + ElementStyle style = node->GetStyle(); + style.alignment = ElementAlignment::Left; + node->SetStyle(style); +} + +// Table cell node + +Node* TableCellNode::Construct(Allocator& allocator, bool isHeader) +{ + TableCellNode::Data* data = allocator.Alloc(isHeader); + if (data) + { + Node* node = allocator.Alloc(Node::TableCell, data); + data->node = node; + return node; + } + return nullptr; +} + +void TableCellNode::ApplyStyle(Node* node) +{ + TableCellNode::Data* data = static_cast(node->data); + ElementStyle style = node->GetStyle(); + + if (data->isHeader) + { + style.alignment = ElementAlignment::Center; + style.fontStyle = FontStyle::Bold; + } + else + { + style.alignment = ElementAlignment::Left; + } + node->SetStyle(style); +} + + +void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) +{ + TableCellNode::Data* data = static_cast(node->data); + + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + TableRowNode::Data* rowData = node->FindParentDataOfType(Node::TableRow); + + layout.PushLayout(); + + if (tableData && rowData) + { + if (tableData->IsGeneratingLayout()) + { + if (!tableData->HasGeneratedCellGrid()) + { + if (!rowData->firstCell) + { + rowData->firstCell = data; + } + else + { + for (TableCellNode::Data* cell = rowData->firstCell; cell; cell = cell->nextCell) + { + if (!cell->nextCell) + { + cell->nextCell = data; + break; + } + } + } + + data->rowIndex = rowData->rowIndex; + data->columnIndex = rowData->numCells; + rowData->numCells++; + } + } + else + { + node->anchor = layout.Cursor(); + + node->size.x = tableData->columns[data->columnIndex].calculatedWidth; + for (int n = 1; n < data->columnSpan && n < tableData->numColumns; n++) + { + node->size.x += tableData->columns[data->columnIndex + n].calculatedWidth + tableData->cellSpacing; + } + + layout.RestrictHorizontal(node->size.x); + layout.PadHorizontal(tableData->cellPadding, tableData->cellPadding); + } + } + + layout.PushCursor(); + + if (tableData) + { + layout.PadVertical(tableData->cellPadding); + } +} + +void TableCellNode::EndLayoutContext(Layout& layout, Node* node) +{ + TableCellNode::Data* data = static_cast(node->data); + + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + TableRowNode::Data* rowData = node->FindParentDataOfType(Node::TableRow); + + if (tableData) + { + Rect rect; + node->CalculateEncapsulatingRect(rect); + node->size.y = rect.y + rect.height + tableData->cellPadding - node->anchor.y; + + if (tableData->IsGeneratingLayout()) + { + node->size.x = rect.width; + } + } + + layout.BreakNewLine(); + layout.PopCursor(); + layout.PopLayout(); + + if (tableData && !tableData->IsGeneratingLayout()) + { + layout.PadHorizontal(node->size.x + tableData->cellSpacing, 0); + } +} + +void TableCellNode::Draw(DrawContext& context, Node* node) +{ + uint8_t borderColour = Platform::video->colourScheme.textColour; + + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + Node* rowNode = node->FindParentOfType(Node::TableRow); + TableCellNode::Data* data = static_cast(node->data); + + if (tableData && !tableData->IsGeneratingLayout() && rowNode) + { + int x = node->anchor.x; + int y = node->anchor.y; + int w = node->size.x; + int h = node->size.y; + + bool needsFill = context.surface->format != DrawSurface::Format_1BPP + && data->bgColour != TRANSPARENT_COLOUR_VALUE + && data->bgColour != tableData->bgColour; + + if (tableData->border) + { + context.surface->HLine(context, x, y, w, borderColour); + context.surface->HLine(context, x, y + h - 1, w, borderColour); + context.surface->VLine(context, x, y + 1, h - 2, borderColour); + context.surface->VLine(context, x + w - 1, y + 1, h - 2, borderColour); + if (needsFill) + { + context.surface->FillRect(context, x + 1, y + 1, w - 2, h - 2, data->bgColour); + } + } + else if (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->format != DrawSurface::Format_1BPP) + { + if (needsFill) + { + context.surface->FillRect(context, x, y, w, h, data->bgColour); + } + } + } + +} diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h new file mode 100644 index 0000000..f7cbd1b --- /dev/null +++ b/src/Nodes/Table.h @@ -0,0 +1,106 @@ +#ifndef _TABLE_H_ +#define _TABLE_H + +#include "../Node.h" +#include "../Colour.h" + +class TableCellNode : public NodeHandler +{ +public: + class Data + { + public: + Data(bool inIsHeader) : node(nullptr), isHeader(inIsHeader), columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), bgColour(TRANSPARENT_COLOUR_VALUE), nextCell(nullptr) {} + Node* node; + bool isHeader; + int columnIndex; + int rowIndex; + int columnSpan; + int rowSpan; + uint8_t bgColour; + TableCellNode::Data* nextCell; + ExplicitDimension explicitWidth; + }; + + static Node* Construct(Allocator& allocator, bool isHeader); + virtual void ApplyStyle(Node* node); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* node) override; +}; + +class TableRowNode : public NodeHandler +{ +public: + class Data + { + public: + Data() : node(nullptr), rowIndex(0), numCells(0), nextRow(nullptr), firstCell(nullptr) {} + Node* node; + int rowIndex; + int numCells; + TableRowNode::Data* nextRow; + TableCellNode::Data* firstCell; + }; + + static Node* Construct(Allocator& allocator); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; + virtual void ApplyStyle(Node* node); +}; + +class TableNode : public NodeHandler +{ +public: + class Data + { + public: + struct ColumnInfo + { + void Clear() + { + preferredWidth = 0; + calculatedWidth = 0; + explicitWidthPixels = 0; + explicitWidthPercentage = 0; + } + int preferredWidth; + int calculatedWidth; + int explicitWidthPixels; + int explicitWidthPercentage; + }; + enum State + { + GeneratingLayout, + FinalisingLayout, + FinishedLayout + }; + + Data() : state(GeneratingLayout), numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), border(0), columns(nullptr), firstRow(nullptr), cells(nullptr), bgColour(TRANSPARENT_COLOUR_VALUE), lastAvailableWidth(-1) {} + + bool IsGeneratingLayout() { return state == GeneratingLayout; } + bool HasGeneratedCellGrid() { return cells != nullptr; } + + State state; + int numColumns; + int numRows; + int cellSpacing; + int cellPadding; + uint8_t border; + int totalWidth; + ColumnInfo* columns; + TableRowNode::Data* firstRow; + TableCellNode::Data** cells; + uint8_t bgColour; + int lastAvailableWidth; + ExplicitDimension explicitWidth; + }; + + static Node* Construct(Allocator& allocator); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* node) override; + +}; + +#endif diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp new file mode 100644 index 0000000..fc0ba7a --- /dev/null +++ b/src/Nodes/Text.cpp @@ -0,0 +1,285 @@ +#include "../Platform.h" +#include "../Page.h" +#include "Text.h" +#include "../Memory/Memory.h" +#include "../Layout.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" +#include "../Interface.h" +#include "../App.h" + +Node* TextElement::Construct(Allocator& allocator, const char* text) +{ + MemBlockHandle textHandle = MemoryManager::pageBlockAllocator.AllocString(text); + if (textHandle.IsAllocated()) + { + TextElement::Data* data = allocator.Alloc(textHandle); + if (data) + { + return allocator.Alloc(Node::Text, data); + } + } + + return nullptr; +} + +void TextElement::Draw(DrawContext& context, Node* node) +{ + TextElement::Data* data = static_cast(node->data); + + if (!node->firstChild && data->text.IsAllocated()) + { + Font* font = node->GetStyleFont(); + + uint8_t textColour = node->GetStyle().fontColour; + char* text = data->text.Get(); + + if (textColour == App::Get().page.colourScheme.pageColour) + { + if (context.surface->format == DrawSurface::Format_1BPP) + { + textColour = !textColour; + } + else + { + textColour ^= 0x08; + } + } + + context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->GetStyle().fontStyle); + + Node* focusedNode = App::Get().ui.GetFocusedNode(); + if (focusedNode && node->IsChildOf(focusedNode)) + { + context.surface->InvertRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y); + } + } +} + + +void TextElement::GenerateLayout(Layout& layout, Node* node) +{ + TextElement::Data* data = static_cast(node->data); + Font* font = node->GetStyleFont(); + int lineHeight = font->glyphHeight; + +#if 1 + // TODO: optimisation + if (data->lastAvailableWidth != -1) + { + // We must be regenerating this text element, see if we can just shuffle position rather than + // completely regenerating the whole layout + if (data->lastAvailableWidth == layout.AvailableWidth()) + { + if (node->firstChild) + { + for (Node* child = node->firstChild; child; child = child->next) + { + child->anchor = layout.GetCursor(lineHeight); + layout.ProgressCursor(child, child->size.x, child->size.y); + if (child->next) + { + layout.BreakNewLine(); + } + } + } + else + { + node->anchor = layout.GetCursor(lineHeight); + layout.ProgressCursor(node, node->size.x, node->size.y); + } + + return; + } + } + + data->lastAvailableWidth = layout.AvailableWidth(); +#endif + + + // Clear out SubTextElement children if we are regenerating the layout + for (Node* child = node->firstChild; child; child = child->next) + { + SubTextElement::Data* childData = static_cast(child->data); + childData->startIndex = 0; + childData->length = 0; + child->anchor = layout.GetCursor(); + child->size.Clear(); + } + + node->size.Clear(); + + char* text = data->text.Get(); + int charIndex = 0; + int startIndex = 0; + int lastBreakPoint = 0; + int lastBreakPointWidth = 0; + int width = 0; + Node* subTextNode = node->firstChild; + bool hasModified = false; + + for(charIndex = 0; ; charIndex++) + { + char c = text[charIndex]; + bool isEnd = text[charIndex + 1] == 0; + + if (c == ' ' || c == '\t') + { + lastBreakPoint = charIndex; + lastBreakPointWidth = width; + } + + if (c == '\x1f') + { + // Non breaking space + text[charIndex] = ' '; + hasModified = true; + } + + int glyphWidth = font->GetGlyphWidth(c, node->GetStyle().fontStyle); + width += glyphWidth; + + bool cannotFit = width > layout.AvailableWidth(); + + if (cannotFit && !lastBreakPoint && layout.AvailableWidth() < layout.MaxAvailableWidth()) + { + // Nothing could fit on the line before the break, just add a line break + layout.BreakNewLine(); + cannotFit = width > layout.AvailableWidth(); + } + + if (cannotFit || isEnd) + { + // Needs a line break + + int emitStartPosition = startIndex; + int emitLength; + int emitWidth; + int nextIndex; + + if (isEnd && !cannotFit) + { + // End of the line so just emit everything + emitLength = charIndex + 1 - emitStartPosition; + emitWidth = width; + nextIndex = -1; + + if (!node->firstChild) + { + // No line breaks so don't create sub text nodes + node->anchor = layout.GetCursor(lineHeight); + node->size.x = emitWidth; + node->size.y = lineHeight; + + layout.ProgressCursor(node, emitWidth, lineHeight); + break; + } + } + else if (lastBreakPoint) + { + // There was a space or tab that we can break at + emitLength = lastBreakPoint - emitStartPosition; + emitWidth = lastBreakPointWidth; + nextIndex = lastBreakPoint + 1; + } + else + { + // Need to break in the middle of a word + emitLength = charIndex - emitStartPosition; + emitWidth = width - glyphWidth; + nextIndex = charIndex; + } + + if (!subTextNode) + { + subTextNode = SubTextElement::Construct(MemoryManager::pageAllocator, emitStartPosition, emitLength); + node->AddChild(subTextNode); + } + else + { + SubTextElement::Data* subTextData = static_cast(subTextNode->data); + subTextData->startIndex = emitStartPosition; + subTextData->length = emitLength; + } + + subTextNode->anchor = layout.GetCursor(lineHeight); + subTextNode->size.x = emitWidth; + subTextNode->size.y = lineHeight; + + startIndex = nextIndex; + width -= emitWidth; + + layout.ProgressCursor(subTextNode, emitWidth, lineHeight); + subTextNode = subTextNode->next; + + if (isEnd) + { + break; + } + + lastBreakPoint = 0; + lastBreakPointWidth = 0; + + layout.BreakNewLine(); + } + } + + if (hasModified) + { + data->text.Commit(); + } +} + +Node* SubTextElement::Construct(Allocator& allocator, int startIndex, int length) +{ + SubTextElement::Data* data = allocator.Alloc(startIndex, length); + if (data) + { + return allocator.Alloc(Node::SubText, data); + } + + return nullptr; +} + +void SubTextElement::GenerateLayout(Layout& layout, Node* node) +{ + TextElement::Data* data = static_cast(node->data); +} + +void SubTextElement::Draw(DrawContext& context, Node* node) +{ + TextElement::Data* textData = static_cast(node->parent->data); + SubTextElement::Data* subTextData = static_cast(node->data); + + if (textData && subTextData && textData->text.IsAllocated()) + { + Font* font = node->GetStyleFont(); + uint8_t textColour = node->GetStyle().fontColour; + char* text = textData->text.Get() + subTextData->startIndex; + char temp = text[subTextData->length]; + text[subTextData->length] = 0; + + if (textColour == App::Get().page.colourScheme.pageColour) + { + if (context.surface->format == DrawSurface::Format_1BPP) + { + textColour = !textColour; + } + else + { + textColour ^= 0x08; + } + } + + context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->GetStyle().fontStyle); + + Node* focusedNode = App::Get().ui.GetFocusedNode(); + if (focusedNode && node->IsChildOf(focusedNode)) + { + context.surface->InvertRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y); + } + + text[subTextData->length] = temp; + //printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); + } +} diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h new file mode 100644 index 0000000..b1796a0 --- /dev/null +++ b/src/Nodes/Text.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../Node.h" +#include "../Memory/MemBlock.h" + +class TextElement : public NodeHandler +{ +public: + class Data + { + public: + Data(MemBlockHandle& inText) : text(inText), lastAvailableWidth(-1) {} + MemBlockHandle text; + int lastAvailableWidth; + }; + + static Node* Construct(Allocator& allocator, const char* text); + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* element) override; +}; + +class SubTextElement : public TextElement +{ +public: + class Data + { + public: + Data(int inStartIndex, int inLength) : startIndex(inStartIndex), length(inLength) {} + int startIndex; + int length; + }; + static Node* Construct(Allocator& allocator, int startIndex, int length); + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* element) override; +}; diff --git a/src/Page.cpp b/src/Page.cpp index 09d68de..32a44ec 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -18,588 +18,221 @@ #include "Platform.h" #include "Page.h" #include "App.h" -#include "Image.h" +#include "Image/Image.h" +#include "Nodes/Section.h" +#include "Nodes/Text.h" +#include "Nodes/Form.h" +#include "Nodes/StyNode.h" +#include "Nodes/Select.h" +#include "Nodes/LinkNode.h" +#include "Draw/Surface.h" +#include "Memory/Memory.h" #define TOP_MARGIN_PADDING 1 -Page::Page(App& inApp) : app(inApp), widgets(allocator) +Page::Page(App& inApp) : app(inApp), layout(*this) { - Reset(); } void Page::Reset() { - currentLineStartWidgetIndex = -1; - currentWidgetIndex = -1; - numFinishedWidgets = 0; - pageWidth = Platform::video->windowWidth; + pageWidth = app.ui.windowRect.width; pageHeight = 0; - widgets.Clear(); - needLeadingWhiteSpace = false; pendingVerticalPadding = 0; - widgetURL = NULL; textBufferSize = 0; leftMarginPadding = 1; cursorX = leftMarginPadding; cursorY = TOP_MARGIN_PADDING; - formData = NULL; + colourScheme = Platform::video->colourScheme; - styleStackSize = 1; - styleStack[0] = WidgetStyle(FontStyle::Regular, 1, false); - allocator.Reset(); -} + MemoryManager::pageAllocator.Reset(); + MemoryManager::pageBlockAllocator.Reset(); -WidgetStyle& Page::GetStyleStackTop() -{ - return styleStack[styleStackSize - 1]; -} + rootNode = SectionElement::Construct(MemoryManager::pageAllocator, SectionElement::Document); + ElementStyle rootStyle; + rootStyle.alignment = ElementAlignment::Left; + rootStyle.fontSize = 1; + rootStyle.fontStyle = FontStyle::Regular; + rootStyle.fontColour = colourScheme.textColour; + rootNode->SetStyle(rootStyle); -void Page::AdjustLeftMargin(int delta) -{ - bool adjustCursor = cursorX == leftMarginPadding; - - leftMarginPadding += delta; - if (leftMarginPadding < 1) - { - leftMarginPadding = 1; - } - else if (leftMarginPadding > Platform::video->windowWidth / 2) - { - leftMarginPadding = Platform::video->windowHeight / 2; - } - - if (adjustCursor) - { - cursorX = leftMarginPadding; - } + layout.Reset(); } -void Page::PushStyle(const WidgetStyle& style) +void Page::SetTitle(const char* inTitle) { - if (styleStackSize < MAX_PAGE_STYLE_STACK_SIZE) - { - styleStack[styleStackSize] = style; - styleStackSize++; - FinishCurrentWidget(); - } + app.ui.SetTitle(inTitle); } -void Page::PopStyle() +static const char* nodeTypeNames[] = { - if (styleStackSize > 1) - { - styleStackSize--; - } - FinishCurrentWidget(); -} + "Section", + "Text", + "SubText", + "Image", + "Break", + "Style", + "Link", + "Block", + "Button", + "TextField", + "Form", + "StatusBar", + "ScrollBar", + "Table", + "TableRow", + "TableCell", + "Select", + "Option", + "List", + "ListItem" +}; -void Page::AddHorizontalRule() +void Page::DebugDumpNodeGraph() { - BreakLine(1); - Widget* widget = CreateWidget(Widget::HorizontalRule); - if (widget) - { - widget->width = Platform::video->windowWidth - 8; - widget->x = 4; - widget->height = 1; - } - BreakLine(1); -} + DebugDumpNodeGraph(GetRootNode()); -void Page::AddButton(char* text) -{ - Widget* widget = CreateWidget(Widget::Button); - if (widget) - { - widget->button = allocator.Alloc(); - if (widget->button) - { - widget->button->form = formData; - widget->button->text = text; - widget->height = Platform::video->GetFont(1)->glyphHeight + 4; - widget->width = Platform::video->GetFont(1)->CalculateWidth(text) + 16; - } - } -} - -void Page::AddTextField(char* text, int bufferLength, char* name) -{ - Widget* widget = CreateWidget(Widget::TextField); - if (widget) - { - widget->textField = allocator.Alloc(); - if (widget->textField) - { - widget->textField->form = formData; - widget->textField->name = name; - widget->textField->buffer = NULL; - if (text) - { - int textLength = strlen(text); - if (textLength > bufferLength) - { - bufferLength = textLength; - } - } - widget->textField->buffer = (char*) allocator.Alloc(bufferLength + 1); - if (widget->textField->buffer) - { - if (text) - { - strcpy(widget->textField->buffer, text); - } - else - { - widget->textField->buffer[0] = '\0'; - } - } - widget->textField->bufferLength = bufferLength; + int nodeTypeCounts[Node::NumNodeTypes]; - widget->width = Platform::video->screenWidth / 3; - widget->height = Platform::video->GetFont(1)->glyphHeight + 4; - //widget->width = Platform::video->GetFont(1)->CalculateWidth(text) + 4; - } - } -} + memset(nodeTypeCounts, 0, sizeof(nodeTypeCounts)); + int totalCount = 0; -Widget* Page::CreateTextWidget() -{ - Widget* widget = CreateWidget(Widget::Text); - if (widget) + for (Node* node = GetRootNode(); node; node = node->GetNextInTree()) { - widget->text = allocator.Alloc(); - if (widget->text) - { - widget->text->linkURL = widgetURL; - widget->height = Platform::video->GetLineHeight(widget->style.fontSize); - widget->text->text = NULL; - } + nodeTypeCounts[node->type]++; + totalCount++; } - return widget; -} -Widget* Page::CreateWidget(Widget::Type type) -{ - if (allocator.GetError() == LinearAllocator::Error_OutOfMemory) + for (int n = 0; n < Node::NumNodeTypes; n++) { - return NULL; + printf("%s :\t%d\n", nodeTypeNames[n], nodeTypeCounts[n]); } + printf("Total: %d nodes\n", totalCount); - if (currentWidgetIndex != -1) - { - FinishCurrentWidget(); - } - - if (widgets.Allocate()) - { - currentWidgetIndex = widgets.Count() - 1; - - Widget& current = widgets[currentWidgetIndex]; - current.type = type; - - if (needLeadingWhiteSpace) - { - if (cursorX > leftMarginPadding) - { - cursorX += Platform::video->GetGlyphWidth(' '); - } - needLeadingWhiteSpace = false; - } - - cursorY += pendingVerticalPadding; - pendingVerticalPadding = 0; - - current.style = GetStyleStackTop(); - current.x = cursorX; - current.y = cursorY; - current.width = 0; - current.height = 0; - current.text = NULL; - - if (currentLineStartWidgetIndex == -1) - { - currentLineStartWidgetIndex = currentWidgetIndex; - } - - return ¤t; - } - else - { - currentWidgetIndex = -1; - return NULL; - } } -void Page::FinishCurrentWidget() +void Page::DebugDumpNodeGraph(Node* node, int depth) { - if (currentWidgetIndex != -1) + static const char* sectionTypeNames[] = { - Widget& current = widgets[currentWidgetIndex]; - cursorX += current.width; - - switch (current.type) - { - case Widget::Text: - current.text->text = allocator.AllocString(textBuffer, textBufferSize); - break; - } - - textBufferSize = 0; - - currentWidgetIndex = -1; - } -} + "Document", + "HTML", + "Head", + "Body", + "Script", + "Style", + "Title" + }; -void Page::FinishCurrentLine(bool includeCurrentWidget) -{ - if (includeCurrentWidget) + for (int i = 0; i < depth; i++) { - FinishCurrentWidget(); + printf(" "); } - - if (currentLineStartWidgetIndex != -1) + + switch (node->type) { - int lineWidth = 0; - int lineHeight = 0; - int lineEndWidget = widgets.Count(); - - // If there is a bullet point right before the line start then treat it as part of the line finishing shuffling: - if (currentLineStartWidgetIndex > 0 && widgets[currentLineStartWidgetIndex - 1].type == Widget::BulletPoint) - { - // Also potentially shift the bullet point down to match the position of the next element - widgets[currentLineStartWidgetIndex - 1].y = widgets[currentLineStartWidgetIndex].y; - currentLineStartWidgetIndex--; - } - - if (!includeCurrentWidget && currentWidgetIndex != -1) - { - lineEndWidget = widgets.Count() - 1; - } - - for (int n = currentLineStartWidgetIndex; n < lineEndWidget; n++) + case Node::Text: { - if (widgets[n].height > lineHeight) + TextElement::Data* data = static_cast(node->data); + if (node->firstChild) { - lineHeight = widgets[n].height; + printf("<%s> [%d,%d:%d,%d]\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); } - if (widgets[n].x + widgets[n].width > lineWidth) + else { - lineWidth = widgets[n].x + widgets[n].width; + printf("<%s> [%d,%d:%d,%d] %s\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y, data->text.Get()); } } - - int centerAdjust = (Platform::video->windowWidth - lineWidth) >> 1; - - for (int n = currentLineStartWidgetIndex; n < lineEndWidget; n++) + break; + case Node::SubText: { - widgets[n].y += lineHeight - widgets[n].height; - if (widgets[n].style.center) - { - widgets[n].x += centerAdjust; - } + TextElement::Data* data = static_cast(node->parent->data); + SubTextElement::Data* subData = static_cast(node->data); + char* text = data->text.Get() + subData->startIndex; + char temp = text[subData->length]; + text[subData->length] = 0; + printf("<%s> [%d,%d:%d,%d] %s\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y, text); + text[subData->length] = temp; } - - cursorY += lineHeight; - pageHeight = cursorY; - - numFinishedWidgets = lineEndWidget; - - if (includeCurrentWidget) + break; + case Node::Option: { - currentLineStartWidgetIndex = -1; + OptionNode::Data* data = static_cast(node->data); + printf("<%s> [%s]\n", nodeTypeNames[node->type], data->text); } - else + break; + case Node::Link: { - currentLineStartWidgetIndex = currentWidgetIndex; + LinkNode::Data* data = static_cast(node->data); + printf("<%s> [%d,%d:%d,%d] %s\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y, data->url); } - } - - cursorX = leftMarginPadding; -} - -void Page::AddImage(char* altText, int width, int height) -{ - const int maxImageWidth = Platform::video->windowWidth - leftMarginPadding - 2; - - while (width > maxImageWidth) - { - width /= 2; - height /= 2; - } - - Widget* widget = CreateWidget(Widget::Image); - if (widget) - { - if (widget->image = allocator.Alloc()) - { - widget->style = WidgetStyle(widgetURL ? FontStyle::Underline : FontStyle::Regular, 1, widget->style.center); - - if (altText) - { - widget->image->altText = allocator.AllocString(altText); - //int textWidth = Platform::video->GetFont(widget->style.fontSize)->CalculateWidth(altText, widget->style.fontStyle); - //int textHeight = Platform::video->GetFont(widget->style.fontSize)->glyphHeight; - //if (textWidth > 0 && width < textWidth + 4) - //{ - // width = textWidth + 4; - //} - //if (textWidth > 0 && height < textHeight + 4) - //{ - // height = textHeight + 4; - //} - } - else - { - widget->image->altText = NULL; - } - widget->image->linkURL = widgetURL; - } - widget->width = width; - widget->height = height; - - if (width > maxImageWidth) - { - width = maxImageWidth; - } - - if (widget->x + widget->width > Platform::video->windowWidth) + break; + case Node::Section: { - // Move to new line - FinishCurrentLine(false); - widget->x = cursorX; - widget->y = cursorY; + SectionElement::Data* data = static_cast(node->data); + printf("<%s> [%d,%d:%d,%d] %s\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y, sectionTypeNames[data->type]); } - } -} - -void Page::AddBulletPoint() -{ - Widget* widget = CreateWidget(Widget::BulletPoint); - if (widget) - { - FinishCurrentWidget(); - widget->style.fontStyle = FontStyle::Regular; - //widget->width = Platform::video->GetFont(widget->style.fontSize, widget->style.fontStyle)->CalculateWidth(" * ", widget->style.fontStyle); - //widget->height = Platform::video->GetLineHeight(widget->style.fontSize, widget->style.fontStyle); - widget->width = Platform::video->bulletImage->width; - widget->height = Platform::video->bulletImage->height; - currentLineStartWidgetIndex = -1; - currentWidgetIndex = -1; - } -} - -void Page::AppendText(const char* text) -{ - if (currentWidgetIndex == -1 || widgets[currentWidgetIndex].type != Widget::Text) - { - CreateTextWidget(); - } - - if (currentWidgetIndex != -1) - { - Widget* current = &widgets[currentWidgetIndex]; - Font* font = Platform::video->GetFont(current->style.fontSize, current->style.fontStyle); - - int addedWidth = 0; - int startIndex = 0; - - if (needLeadingWhiteSpace) + break; + case Node::Form: { - needLeadingWhiteSpace = false; - if (current->width > 0 && textBufferSize < MAX_TEXT_BUFFER_SIZE) - { - textBuffer[textBufferSize++] = ' '; - current->width += Platform::video->GetGlyphWidth(' ', current->style.fontSize, current->style.fontStyle); - } + FormNode::Data* data = static_cast(node->data); + printf("<%s> [%d,%d:%d,%d] action: %s\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y, data->action ? data->action : "NONE"); } - - for (int index = 0; text[index] != '\0'; index++) + break; + case Node::Style: { - if (text[index] < 32 || text[index] > 128) - continue; - - int glyphWidth = font->glyphWidth[text[index] - 32]; - if (current->style.fontStyle & FontStyle::Bold) + StyleNode::Data* data = static_cast(node->data); + printf("<%s> [%d,%d:%d,%d] ", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); + if (data->styleOverride.overrideMask.alignment) { - glyphWidth++; - } - - if (current->x + current->width + addedWidth + glyphWidth < Platform::video->windowWidth) - { - addedWidth += glyphWidth; - - if (text[index] == ' ') + switch (data->styleOverride.styleSettings.alignment) { - // Word boundary, so word fits, append now - for (int n = startIndex; n <= index; n++) - { - if (textBufferSize < MAX_TEXT_BUFFER_SIZE) - { - textBuffer[textBufferSize++] = text[n]; - } - } - startIndex = index + 1; - current->width += addedWidth; - addedWidth = 0; + case ElementAlignment::Center: + printf("align center "); + break; } } - else + if (data->styleOverride.overrideMask.fontStyle) { - if (current->width == 0) + if (data->styleOverride.styleSettings.fontStyle & FontStyle::Bold) { - if (current->x <= leftMarginPadding) - { - // Case with lots of text without spaces, so just break in middle of the word - for (int n = startIndex; n < index; n++) - { - if (textBufferSize < MAX_TEXT_BUFFER_SIZE) - { - textBuffer[textBufferSize++] = text[n]; - } - } - current->width += addedWidth; - startIndex = index + 1; - addedWidth = 0; - - FinishCurrentLine(); - - current = CreateTextWidget(); - } - else - { - // Case where widget is empty and needs to move to the next line - - // Move to new line - FinishCurrentLine(false); - current->x = cursorX; - current->y = cursorY; - } + printf("bold "); } - else + if (data->styleOverride.styleSettings.fontStyle & FontStyle::Italic) { - // Case where we need to make a new widget on the next line - FinishCurrentLine(); - current = CreateTextWidget(); + printf("italic "); } - - if (!current) + if (data->styleOverride.styleSettings.fontStyle & FontStyle::Underline) { - // Couldn't make a new widget so probably out of memory - return; + printf("underline "); } - - // Go back a character so it gets processed properly - index--; } - } - // Add whatever is left - if (addedWidth > 0) - { - for (int index = startIndex; text[index] != '\0'; index++) - { - if (textBufferSize < MAX_TEXT_BUFFER_SIZE) - { - textBuffer[textBufferSize++] = text[index]; - } - } - current->width += addedWidth; + printf("\n"); } + break; + default: + printf("<%s> [%d,%d:%d,%d]\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); + break; } -} - -void Page::BreakLine(int padding) -{ - FinishCurrentLine(); - - if (padding > pendingVerticalPadding) - { - pendingVerticalPadding = padding; - } -} - -void Page::BreakTextLine() -{ - int oldCursorY = cursorY; - FinishCurrentLine(); - - if (oldCursorY == cursorY) - { - cursorY += Platform::video->GetLineHeight(GetStyleStackTop().fontSize); - } -} - -void Page::SetTitle(const char* inTitle) -{ - app.renderer.SetTitle(inTitle); -} -Widget* Page::GetWidget(int x, int y) -{ - for (int n = 0; n < widgets.Count(); n++) + for (Node* child = node->firstChild; child; child = child->next) { - Widget* widget = &widgets[n]; - - if (x >= widget->x && y >= widget->y && x < widget->x + widget->width && y < widget->y + widget->height) - return widget; + DebugDumpNodeGraph(child, depth + 1); } - return NULL; -} - -void Page::SetWidgetURL(const char* url) -{ - widgetURL = allocator.AllocString(url); -} - -void Page::ClearWidgetURL() -{ - widgetURL = NULL; -} - -void Page::FinishSection() -{ - FinishCurrentLine(); -} - -void Page::SetFormData(WidgetFormData* inFormData) -{ - formData = inFormData; -} - -WidgetContainer::WidgetContainer(LinearAllocator& inAllocator) : allocator(inAllocator), numAllocated(0) -{ } -bool WidgetContainer::Allocate() +Node* Page::ProcessNextLoadTask(Node* lastNode, LoadTask& loadTask) { - int chunkIndex = WIDGET_INDEX_TO_CHUNK_INDEX(numAllocated); - int indexInChunk = WIDGET_INDEX_TO_INDEX_IN_CHUNK(numAllocated); - - if (chunkIndex > MAX_WIDGET_CHUNKS) - { - return false; - } - - if (indexInChunk == 0) + Node* node; + for (node = lastNode->GetNextInTree(); node; node = node->GetNextInTree()) { - // Need to allocate a new chunk - chunks[chunkIndex] = allocator.Alloc(); - if (chunks[chunkIndex] == NULL) + if (node && node->type == Node::Image) { - return false; + node->Handler().LoadContent(node, loadTask); + return node; } } - numAllocated++; - return true; -} - -void WidgetContainer::Clear() -{ - numAllocated = 0; -} - -Widget& WidgetContainer::operator[](int index) -{ - return chunks[WIDGET_INDEX_TO_CHUNK_INDEX(index)]->widgets[WIDGET_INDEX_TO_INDEX_IN_CHUNK(index)]; + return nullptr; } diff --git a/src/Page.h b/src/Page.h index 77b2462..adc599c 100644 --- a/src/Page.h +++ b/src/Page.h @@ -12,46 +12,18 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _PAGE_H_ +#define _PAGE_H_ -#include "Widget.h" -#include "LinAlloc.h" #include "URL.h" +#include "Layout.h" +#include "Colour.h" -#define MAX_PAGE_WIDGETS 2000 #define MAX_PAGE_STYLE_STACK_SIZE 32 #define MAX_TEXT_BUFFER_SIZE 128 -#define MAX_WIDGET_CHUNKS 256 -#define WIDGETS_PER_CHUNK 64 -#define WIDGET_INDEX_TO_CHUNK_INDEX(index) ((index) >> 6) -#define WIDGET_INDEX_TO_INDEX_IN_CHUNK(index) ((index) & 0x3f) - class App; - -class WidgetContainer -{ -public: - WidgetContainer(LinearAllocator& inAllocator); - - bool Allocate(); - void Clear(); - - int Count() const { return numAllocated; } - - Widget& operator[](int index); - -private: - LinearAllocator& allocator; - int numAllocated; - - struct Chunk - { - Widget widgets[WIDGETS_PER_CHUNK]; - }; - - Chunk* chunks[MAX_WIDGET_CHUNKS]; -}; +class Node; class Page { @@ -60,68 +32,41 @@ class Page void Reset(); - void AddHorizontalRule(); - void AddButton(char* text); - void AddTextField(char* text, int bufferLength, char* name); - void AppendText(const char* text); - void AddImage(char* altText, int width, int height); - void AddBulletPoint(); - void BreakLine(int padding = 0); - void BreakTextLine(); - void FlagLeadingWhiteSpace() { needLeadingWhiteSpace = true; } void SetTitle(const char* text); - void FinishSection(); - void SetFormData(WidgetFormData* formData); - void AdjustLeftMargin(int delta); - void SetWidgetURL(const char* url); - void ClearWidgetURL(); + Node* GetRootNode() { return rootNode; } - Widget* GetWidget(int x, int y); + Layout layout; - WidgetStyle& GetStyleStackTop(); - void PushStyle(const WidgetStyle& style); - void PopStyle(); + void DebugDumpNodeGraph(); - LinearAllocator allocator; + URL pageURL; - int GetPageHeight() { return pageHeight; } + App& GetApp() { return app; } - URL pageURL; + Node* ProcessNextLoadTask(Node* lastNode, struct LoadTask& loadTask); + + ColourScheme colourScheme; private: - friend class Renderer; friend class AppInterface; - Widget* CreateWidget(Widget::Type type); - Widget* CreateTextWidget(); - void FinishCurrentWidget(); - void FinishCurrentLine(bool includeCurrentWidget = true); + void DebugDumpNodeGraph(Node* node, int depth = 0); App& app; char* title; - int currentLineStartWidgetIndex; - int currentWidgetIndex; - int numFinishedWidgets; - int pageWidth, pageHeight; int cursorX, cursorY; int pendingVerticalPadding; int leftMarginPadding; - bool needLeadingWhiteSpace; - - WidgetContainer widgets; - - WidgetStyle styleStack[MAX_PAGE_STYLE_STACK_SIZE]; - uint8_t styleStackSize; - WidgetFormData* formData; + Node* rootNode; char textBuffer[MAX_TEXT_BUFFER_SIZE]; int textBufferSize; - - char* widgetURL; }; + +#endif diff --git a/src/Palettes.inc b/src/Palettes.inc new file mode 100644 index 0000000..cf08459 --- /dev/null +++ b/src/Palettes.inc @@ -0,0 +1,54 @@ +uint8_t egaPaletteLUT[] = { + 0, 0, 1, 1, 0, 0, 1, 1, 0, 8, 1, 9, 2, 8, 3, 9, + 2, 2, 3, 3, 2, 2, 3, 3, 2, 10, 3, 11, 2, 10, 3, 11, + 0, 0, 1, 1, 0, 8, 1, 9, 0, 8, 1, 9, 2, 8, 3, 9, + 2, 8, 3, 9, 2, 10, 3, 11, 2, 10, 3, 11, 2, 10, 3, 11, + 0, 8, 1, 9, 0, 8, 1, 9, 8, 8, 8, 9, 8, 8, 8, 9, + 2, 8, 3, 9, 2, 10, 3, 11, 2, 10, 3, 11, 10, 10, 10, 11, + 4, 8, 5, 9, 4, 8, 5, 9, 6, 8, 8, 9, 6, 8, 7, 9, + 6, 8, 7, 9, 2, 10, 7, 11, 10, 10, 7, 11, 10, 10, 10, 11, + 4, 4, 5, 5, 4, 8, 5, 9, 6, 8, 5, 9, 6, 8, 7, 9, + 6, 8, 7, 9, 6, 7, 7, 7, 10, 10, 7, 11, 10, 10, 7, 11, + 4, 4, 5, 5, 4, 12, 5, 13, 6, 12, 5, 13, 6, 12, 7, 13, + 6, 7, 7, 7, 6, 7, 7, 7, 14, 14, 7, 15, 14, 14, 7, 15, + 4, 12, 5, 13, 4, 12, 5, 13, 6, 12, 5, 13, 6, 12, 7, 13, + 6, 12, 7, 13, 6, 14, 7, 15, 14, 14, 7, 15, 14, 14, 14, 15, + 4, 12, 5, 13, 4, 12, 5, 13, 6, 12, 12, 13, 6, 12, 12, 13, + 6, 12, 7, 13, 14, 14, 7, 15, 14, 14, 14, 15, 14, 14, 14, 15 +}; +uint8_t cgaPaletteLUT[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, + 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, + 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, + 15, 15, 15, 15, 5, 5, 5, 15, 5, 5, 5, 5, 5, 5, 5, 5, + 10, 10, 10, 10, 10, 10, 0, 10, 10, 10, 0, 0, 10, 0, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 5, 5, 5, 5, 5, 5, 5, 5, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, 15, + 10, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 5, 5, 5, 5, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 15, 15, 10, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 15, 10, 10, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 +}; +uint8_t compositeCgaPaletteLUT[] = { + 0, 0, 2, 2, 0, 1, 2, 2, 8, 1, 3, 3, 1, 1, 3, 3, + 1, 1, 3, 3, 9, 1, 3, 3, 9, 9, 11, 3, 9, 9, 11, 11, + 0, 0, 2, 2, 0, 1, 2, 2, 8, 1, 2, 2, 8, 1, 3, 3, + 8, 1, 3, 3, 9, 1, 11, 3, 9, 9, 11, 11, 9, 9, 11, 11, + 0, 4, 2, 2, 8, 5, 2, 2, 8, 5, 5, 2, 8, 5, 5, 3, + 8, 5, 5, 3, 9, 5, 11, 3, 9, 11, 11, 11, 9, 11, 11, 11, + 4, 4, 2, 2, 8, 4, 5, 2, 8, 5, 5, 2, 8, 5, 5, 7, + 8, 5, 5, 7, 13, 5, 11, 7, 13, 5, 11, 11, 13, 11, 11, 11, + 4, 4, 4, 6, 4, 4, 5, 6, 4, 5, 5, 7, 12, 5, 5, 7, + 13, 5, 5, 7, 13, 5, 5, 7, 13, 13, 11, 7, 13, 13, 11, 11, + 4, 4, 6, 6, 4, 4, 6, 6, 12, 4, 14, 7, 12, 5, 14, 7, + 12, 5, 14, 7, 13, 5, 7, 7, 13, 13, 7, 7, 13, 13, 15, 15, + 4, 4, 6, 6, 12, 4, 6, 6, 12, 12, 14, 6, 12, 12, 14, 7, + 12, 12, 14, 7, 13, 13, 14, 7, 13, 13, 14, 15, 13, 13, 15, 15, + 4, 4, 6, 6, 12, 4, 6, 6, 12, 12, 14, 6, 12, 12, 14, 14, + 12, 12, 14, 7, 13, 14, 14, 15, 13, 13, 14, 15, 13, 13, 15, 15 +}; diff --git a/src/Parser.cpp b/src/Parser.cpp index 8187a6b..60ff27b 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -18,70 +18,152 @@ #include #include "Parser.h" #include "Tags.h" -#include "Renderer.h" #include "Page.h" #include "Unicode.inc" +#include "Nodes/Text.h" +#include "Nodes/ImgNode.h" +#include "Nodes/Break.h" +#include "Nodes/Select.h" +#include "Nodes/Button.h" +#include "Memory/Memory.h" +#include "App.h" + +// debug +#include "Platform.h" +#include "Draw/Surface.h" +#include "App.h" HTMLParser::HTMLParser(Page& inPage) : page(inPage) -, sectionStackSize(0) +, contextStack(MemoryManager::pageAllocator) +, contextStackSize(0) , parseState(ParseText) , textBufferSize(0) , parsingUnicode(false) , preformatted(0) { - sectionStack[0] = HTMLParseSection::Document; } void HTMLParser::Reset() { parseState = ParseText; textBufferSize = 0; - sectionStackSize = 0; preformatted = 0; SetTextEncoding(TextEncoding::UTF8); + + contextStack.Reset(); + contextStackSize = -1; + PushContext(page.GetRootNode(), nullptr); } -void HTMLParser::PushSection(HTMLParseSection::Type section) +HTMLParseContext* HTMLParser::FindContextInStack(Node::Type nodeType) { - if (sectionStackSize < MAX_PARSE_SECTION_STACK_SIZE - 1) + for (Stack::Entry* entry = contextStack.top; entry; entry = entry->prev) { - sectionStackSize++; - sectionStack[sectionStackSize] = section; + if (entry->obj.node && entry->obj.node->type == nodeType) + { + return &entry->obj; + } } - else + return nullptr; +} + +void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) +{ + if (!node) { - // TODO: ERROR + return; } + + if (contextStackSize >= 0) + { + Node* parentNode = CurrentContext().node; + parentNode->AddChild(node); + node->Handler().ApplyStyle(node); + } + + contextStack.Push(); + contextStackSize++; + + contextStack.Top().node = node; + contextStack.Top().tag = tag; + + if (node->type == Node::Section) + { + SectionElement::Data* data = static_cast(node->data); + contextStack.Top().parseSection = data->type; + } + + //node->Handler().BeginLayoutContext(page.layout, node); } -void HTMLParser::PopSection(HTMLParseSection::Type section) +void HTMLParser::PopContext(const HTMLTagHandler* tag) { - if (sectionStackSize > 0) + if (contextStackSize >= 0) { - if (CurrentSection() != section) + bool hasEntry = false; + + // Check that the context stack has this tag (in case of malformed HTML) + for (Stack::Entry* entry = contextStack.top; entry; entry = entry->prev) { - // TODO ERROR + if (entry->obj.tag == tag) + { + hasEntry = true; + break; + } } - sectionStackSize--; + if (!hasEntry) + { + return; + } } - else + + // Keep popping contexts until we get to the matching tag + while(contextStackSize >= 0) { - // TODO: ERROR + HTMLParseContext& parseContext = contextStack.Top(); + contextStack.Pop(); + contextStackSize--; + + //parseContext.node->Handler().EndLayoutContext(page.layout, parseContext.node); + + if (parseContext.tag == tag) + { + // If the stack is emptied then we have finished parsing the document + if (contextStackSize == 0 && parseContext.parseSection == SectionElement::HTML) + { +#ifdef WIN32 + page.DebugDumpNodeGraph(); +#endif + Finish(); + //page.GetApp().pageRenderer.RefreshAll(); + //page.GetApp().pageRenderer.MarkPageLayoutComplete(); + } + + return; + } } - page.FinishSection(); + Platform::FatalError("Error popping context in HTML parser"); } -#define NUM_AMPERSAND_ESCAPE_SEQUENCES 14 -const char* ampersandEscapeSequences[14 * 2] = +void HTMLParser::Finish() +{ + FlushTextBuffer(); + parseState = ParseFinished; + page.layout.MarkParsingComplete(); + page.GetApp().pageLoadTask.Stop(); +} + +#define NUM_AMPERSAND_ESCAPE_SEQUENCES (sizeof(ampersandEscapeSequences) / (2 * sizeof(const char*))) +const char* ampersandEscapeSequences[] = { "quot", "\"", "amp", "&", "lt", "<", "gt", ">", - "nbsp", " ", + "nbsp", "\x1f", "pound", "£", "brvbar", "¦", "uml", "\"", @@ -106,6 +188,68 @@ void HTMLParser::AppendTextBuffer(char c) } } +void HTMLParser::EmitText(const char* text) +{ + EmitNode(TextElement::Construct(MemoryManager::pageAllocator, text)); +} + +void HTMLParser::EmitNode(Node* node) +{ + if (!node) + { + return; + } + + // Check if this is part of a table - if so, make sure we are in a cell + Node* tableContainer = nullptr; + for (Node* n = CurrentContext().node; n; n = n->parent) + { + if (n->type == Node::Table) + { + tableContainer = n; + break; + } + } + if (tableContainer) + { + bool isInCell = false; + + for (Node* n = CurrentContext().node; n != tableContainer; n = n->parent) + { + if (n->type == Node::TableCell) + { + isInCell = true; + break; + } + } + + if (!isInCell) + { + // Don't emit the node. In other browsers the content gets emitted before the table? + return; + } + } + + Node* parentNode = CurrentContext().node; + parentNode->AddChild(node); + + node->Handler().ApplyStyle(node); + + page.layout.OnNodeEmitted(node); +} + +void HTMLParser::EmitImage(Image* image, int imageWidth, int imageHeight) +{ + Node* node = ImageNode::Construct(MemoryManager::pageAllocator); + if (node) + { + node->size.x = imageWidth; + node->size.y = imageHeight; + EmitNode(node); + } +} + + void HTMLParser::FlushTextBuffer() { textBuffer[textBufferSize] = '\0'; @@ -114,13 +258,39 @@ void HTMLParser::FlushTextBuffer() { case ParseText: { - if(CurrentSection() == HTMLParseSection::Body && textBufferSize > 0) + if(textBufferSize > 0) { - page.AppendText(textBuffer); - } - if (CurrentSection() == HTMLParseSection::Title && textBufferSize > 0) - { - page.SetTitle(textBuffer); + HTMLParseContext* optionContext = FindContextInStack(Node::Option); + HTMLParseContext* buttonContext = FindContextInStack(Node::Button); + + if (optionContext) + { + OptionNode::Data* option = static_cast(optionContext->node->data); + option->text = MemoryManager::pageAllocator.AllocString(textBuffer); + } + else if (buttonContext) + { + ButtonNode::Data* button = static_cast(buttonContext->node->data); + button->buttonText = MemoryManager::pageAllocator.AllocString(textBuffer); + } + else + { + switch (CurrentSection()) + { + case SectionElement::Title: + page.SetTitle(textBuffer); + break; + case SectionElement::Script: + case SectionElement::Style: + case SectionElement::Document: + break; + default: + case SectionElement::Body: + case SectionElement::HTML: + EmitText(textBuffer); + break; + } + } } } break; @@ -156,7 +326,7 @@ void HTMLParser::FlushTextBuffer() const HTMLTagHandler* tagHandler = DetermineTag(tagStr); // Special case when parsing - if (CurrentSection() == HTMLParseSection::Script) + if (CurrentSection() == SectionElement::Script) { if (!isCloseTag || stricmp(tagHandler->name, "script")) { @@ -170,51 +340,152 @@ void HTMLParser::FlushTextBuffer() } else { + HTMLParseContext& oldContext = CurrentContext(); tagHandler->Open(*this, attributeStr); + + if (&oldContext != &CurrentContext()) + { + AttributeParser attributes(attributeStr); + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "align")) + { + ElementStyle style = CurrentContext().node->GetStyle(); + if (!stricmp(attributes.Value(), "center")) + { + style.alignment = ElementAlignment::Center; + } + else if (!stricmp(attributes.Value(), "left")) + { + style.alignment = ElementAlignment::Left; + } + else if (!stricmp(attributes.Value(), "right")) + { + style.alignment = ElementAlignment::Right; + } + CurrentContext().node->SetStyle(style); + } + if (!stricmp(attributes.Key(), "name")) + { + if (App::Get().ui.jumpTagName && !stricmp(attributes.Value(), App::Get().ui.jumpTagName + 1)) + { + App::Get().ui.jumpNode = CurrentContext().node; + } + } + } + } } } } break; case ParseAmpersandEscape: { - if(textBufferSize == 0) + // Flush everything before the escape sequence started + if (escapeSequenceStartIndex) { - page.AppendText("&"); + textBufferSize = escapeSequenceStartIndex; + parseState = ParseText; + FlushTextBuffer(); + parseState = ParseAmpersandEscape; + textBuffer[escapeSequenceStartIndex] = '&'; + strcpy(textBuffer, textBuffer + escapeSequenceStartIndex); } - else + return; + } + break; + } + + textBufferSize = 0; + textBuffer[0] = '\0'; +} + +void HTMLParser::ReplaceAmpersandEscapeSequences(char* buffer, bool replaceNonBreakingSpace) +{ + while (*buffer) + { + if (*buffer == '&') + { + buffer++; + + // Find length of sequence + int escapeSequenceLength = 0; + while (buffer[escapeSequenceLength] && buffer[escapeSequenceLength] != ';' && !IsWhiteSpace(buffer[escapeSequenceLength])) { - for(int n = 0; n < NUM_AMPERSAND_ESCAPE_SEQUENCES; n++) + escapeSequenceLength++; + } + bool correctlyTerminated = buffer[escapeSequenceLength] == ';'; + char* nextBufferPosition = correctlyTerminated ? buffer + escapeSequenceLength + 1 : buffer + escapeSequenceLength; + + if(escapeSequenceLength > 0) + { + if (*buffer == '#') { - const char* escapeSequence = ampersandEscapeSequences[n * 2]; - bool matching = true; - - for(int i = 0; i < textBufferSize; i++) + int number = 0; + + // This is a entity number + if (buffer[1] == 'x' || buffer[1] == 'X') + { + // Hex number + number = strtol(buffer + 2, NULL, 16); + } + else + { + number = atoi(buffer + 1); + } + buffer--; + if (number > FIRST_FONT_GLYPH && number <= LAST_FONT_GLYPH) + { + *buffer = (char)(number); + strcpy(buffer + 1, nextBufferPosition); + } + else + { + strcpy(buffer, nextBufferPosition); + } + } + else + { + for (int n = 0; n < NUM_AMPERSAND_ESCAPE_SEQUENCES; n++) { - if(escapeSequence[i] == '\0') + const char* escapeSequence = ampersandEscapeSequences[n * 2]; + bool matching = true; + + for (int i = 0; i < escapeSequenceLength; i++) { - matching = false; - break; + if (escapeSequence[i] == '\0') + { + matching = false; + break; + } + if (tolower(buffer[i]) != escapeSequence[i]) + { + matching = false; + break; + } } - if(tolower(textBuffer[i]) != escapeSequence[i]) + + if (matching && escapeSequence[escapeSequenceLength] == '\0') { - matching = false; + buffer--; // One step backwards to write over the & + const char* replacementText = ampersandEscapeSequences[n * 2 + 1]; + int replacementTextLength = strlen(replacementText); + memcpy(buffer, replacementText, replacementTextLength); + strcpy(buffer + replacementTextLength, nextBufferPosition); + + if (replaceNonBreakingSpace && *buffer == '\x1f') + { + *buffer = ' '; + } + + buffer += replacementTextLength - 1; break; } } - - if(matching && escapeSequence[textBufferSize] == '\0') - { - page.AppendText(ampersandEscapeSequences[n * 2 + 1]); - break; - } } } } - break; + buffer++; } - - textBufferSize = 0; - textBuffer[0] = '\0'; } bool HTMLParser::IsWhiteSpace(char c) @@ -222,9 +493,14 @@ bool HTMLParser::IsWhiteSpace(char c) return c == ' ' || c == '\n' || c == '\t' || c == '\r'; } +void HTMLParser::Write(const char* str) +{ + Parse((char*) str, strlen(str)); +} + void HTMLParser::Parse(char* buffer, size_t count) { - while (count && page.allocator.GetError() == LinearAllocator::Error_None) + while (count && MemoryManager::pageAllocator.GetError() == LinearAllocator::Error_None) { char c = *buffer++; count--; @@ -330,6 +606,16 @@ void HTMLParser::Parse(char* buffer, size_t count) ParseChar(c); } } + + if (MemoryManager::pageAllocator.GetError() && contextStackSize >= 0) + { + // There was a memory error, possibly out of memory. Unwind the context stack + while (contextStackSize >= 0) + { + HTMLParseContext& parseContext = contextStack.Top(); + PopContext(parseContext.tag); + } + } } void HTMLParser::SetTextEncoding(TextEncoding::Type newType) @@ -362,8 +648,9 @@ void HTMLParser::ParseChar(char c) } else if(c == '&') { - FlushTextBuffer(); parseState = ParseAmpersandEscape; + escapeSequenceStartIndex = textBufferSize; + AppendTextBuffer(c); } else { @@ -374,7 +661,8 @@ void HTMLParser::ParseChar(char c) c = ' '; if (textBufferSize == 0) { - page.FlagLeadingWhiteSpace(); + // TODO-refactor + //page.FlagLeadingWhiteSpace(); break; } else @@ -391,7 +679,10 @@ void HTMLParser::ParseChar(char c) if (c == '\n') { FlushTextBuffer(); - page.BreakTextLine(); + + EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + // TODO-refactor + //page.BreakTextLine(); break; } else if (c == '\r') @@ -436,7 +727,7 @@ void HTMLParser::ParseChar(char c) } // Special case when parsing script tags : we just want to look for a script closing tag - if (CurrentSection() == HTMLParseSection::Script && textBufferSize >= 7 && strnicmp(textBuffer, "/script", 7)) + if (CurrentSection() == SectionElement::Script && textBufferSize >= 7 && strnicmp(textBuffer, "/script", 7)) { textBufferSize = 0; textBuffer[0] = '\0'; @@ -447,18 +738,14 @@ void HTMLParser::ParseChar(char c) break; case ParseAmpersandEscape: + AppendTextBuffer(c); if(c == ';' || IsWhiteSpace(c)) { - FlushTextBuffer(); + textBuffer[textBufferSize] = '\0'; + ReplaceAmpersandEscapeSequences(textBuffer + escapeSequenceStartIndex, false); + textBufferSize = strlen(textBuffer + escapeSequenceStartIndex) + escapeSequenceStartIndex; + parseState = ParseText; - if(IsWhiteSpace(c)) - { - AppendTextBuffer(' '); - } - } - else - { - AppendTextBuffer(c); } break; @@ -484,6 +771,17 @@ void HTMLParser::ParseChar(char c) } +// Making this static as we only ever have one attribute parser running at a time +// and we don't want to blow the stack with a huge buffer +char AttributeParser::attributeStringBuffer[MAX_ATTRIBUTE_STRING_LENGTH]; + +AttributeParser::AttributeParser(const char* inAttributeString) :key(NULL), value(NULL) +{ + strncpy(attributeStringBuffer, inAttributeString, MAX_ATTRIBUTE_STRING_LENGTH); + attributeStringBuffer[MAX_ATTRIBUTE_STRING_LENGTH - 1] = '\0'; + attributeString = attributeStringBuffer; +} + bool AttributeParser::Parse() { key = value = NULL; @@ -538,7 +836,9 @@ bool AttributeParser::Parse() { if(*attributeString == '\0') { - return false; + // Key but no value + value = attributeString; + return true; } if(*attributeString == '=') { @@ -556,7 +856,9 @@ bool AttributeParser::Parse() { if(*attributeString == '\0') { - return false; + // Key but no value + value = attributeString; + return true; } if(*attributeString == '=') { @@ -564,7 +866,9 @@ bool AttributeParser::Parse() } if(!IsWhiteSpace(*attributeString)) { - return false; + // Key but no value + value = key + strlen(key); + return true; } attributeString++; } @@ -633,3 +937,75 @@ bool AttributeParser::IsWhiteSpace(char c) return c == ' ' || c == '\n' || c == '\t'; } +NamedColour namedColours[] = +{ + { "black", RGB332(0x00, 0x00, 0x00) }, + { "white", RGB332(0xff, 0xff, 0xff) }, + { "gray", RGB332(0x80, 0x80, 0x80) }, + { "grey", RGB332(0x80, 0x80, 0x80) }, + // { "silver", RGB332(0xc0, 0xc0, 0xc0) }, + { "silver", RGB332(0xa0, 0xa0, 0xa0) }, + { "red", RGB332(0xff, 0x00, 0x00) }, + { "maroon", RGB332(0x80, 0x00, 0x00) }, + { "yellow", RGB332(0xff, 0xff, 0x00) }, + { "olive", RGB332(0x80, 0x80, 0x00) }, + { "lime", RGB332(0x00, 0xff, 0x00) }, + { "green", RGB332(0x00, 0x80, 0x00) }, + { "aqua", RGB332(0x00, 0xff, 0xff) }, + { "teal", RGB332(0x00, 0x80, 0x80) }, + { "blue", RGB332(0x00, 0x00, 0xff) }, + { "navy", RGB332(0x00, 0x00, 0x80) }, + { "fuchsia",RGB332(0xff, 0x00, 0xff) }, + { "purple", RGB332(0x80, 0x00, 0x80) }, + { "orange", RGB332(0xff, 0xa5, 0x00) } +}; + +#define NUM_NAMED_COLOURS (sizeof(namedColours) / sizeof(NamedColour)) + +uint8_t HTMLParser::ParseColourCode(const char* colourCode) +{ + if (colourCode[0] == '#') + { + uint8_t red = 0, green = 0, blue = 0; + int codeLength = strlen(colourCode + 1); + if (codeLength == 6 || codeLength == 8) + { + sscanf(colourCode, "#%02hhx%02hhx%02hhx", &red, &green, &blue); + } + else if (codeLength == 3 || codeLength == 4) + { + sscanf(colourCode, "#%1hhx%1hhx%1hhx", &red, &green, &blue); + red += red * 16; + green += green * 16; + blue += blue * 16; + } + + if (Platform::video->paletteLUT) + { + return Platform::video->paletteLUT[RGB332(red, green, blue)]; + } + + int grey = red + green + blue; + return grey > (127 * 3) ? 1 : 0; + } + else + { + for (int n = 0; n < NUM_NAMED_COLOURS; n++) + { + if (!stricmp(namedColours[n].name, colourCode)) + { + uint8_t rgb332 = namedColours[n].colour; + if (Platform::video->paletteLUT) + { + return Platform::video->paletteLUT[rgb332]; + } + else + { + return (rgb332 & 0xda) != 0; + } + } + } + } + return 0; +} + diff --git a/src/Parser.h b/src/Parser.h index afa459c..a5a3b3a 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -12,10 +12,17 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _PARSER_H_ +#define _PARSER_H_ #include +#include "Nodes/Section.h" +#include "Stack.h" + class Page; +class Node; +struct Image; +class HTMLTagHandler; struct TextEncoding { @@ -27,38 +34,34 @@ struct TextEncoding }; }; -struct HTMLParseSection +struct HTMLParseContext { - enum Type - { - Document, - Head, - Body, - Script, - Style, - Title - }; + Node* node; + const HTMLTagHandler* tag; + SectionElement::Type parseSection; }; +#define MAX_ATTRIBUTE_STRING_LENGTH 1024 + class AttributeParser { public: - AttributeParser(char* inAttributeString) : attributeString(inAttributeString), key(NULL), value(NULL) {} + AttributeParser(const char* inAttributeString); bool Parse(); const char* Key() { return key; } const char* Value() { return value; } + int ValueAsInt() { return atoi(value); } private: bool IsWhiteSpace(char c); - char* attributeString; + static char attributeStringBuffer[MAX_ATTRIBUTE_STRING_LENGTH]; char* key; char* value; + char* attributeString; }; -#define MAX_PARSE_SECTION_STACK_SIZE 32 - class HTMLParser { public: @@ -66,25 +69,41 @@ class HTMLParser void Reset(); void Parse(char* buffer, size_t count); + void Write(const char* str); Page& page; - void PushSection(HTMLParseSection::Type section); - void PopSection(HTMLParseSection::Type section); - HTMLParseSection::Type CurrentSection() { return sectionStack[sectionStackSize]; } + void PushContext(Node* node, const HTMLTagHandler* tag); + void PopContext(const HTMLTagHandler* tag); + HTMLParseContext& CurrentContext() { return contextStack.Top(); } + SectionElement::Type CurrentSection() { return CurrentContext().parseSection; } + HTMLParseContext* FindContextInStack(Node::Type nodeType); + + void EmitNode(Node* node); + void EmitText(const char* text); + void EmitImage(Image* image, int imageWidth, int imageHeight); void SetTextEncoding(TextEncoding::Type newType); void PushPreFormatted(); void PopPreFormatted(); + static uint8_t ParseColourCode(const char* colourCode); + + static void ReplaceAmpersandEscapeSequences(char* buffer, bool replaceNonBreakingSpace = true); + + void Finish(); + bool IsFinished() { return parseState == ParseFinished; } + private: void ParseChar(char c); //HTMLNode* CreateNode(HTMLNode::NodeType nodeType, HTMLNode* parentNode); void AppendTextBuffer(char c); void FlushTextBuffer(); - bool IsWhiteSpace(char c); + static bool IsWhiteSpace(char c); + + void DebugDumpNodeGraph(Node* node, int depth = 0); enum ParseState { @@ -92,15 +111,17 @@ class HTMLParser ParsePossibleTag, ParseTag, ParseAmpersandEscape, - ParseComment + ParseComment, + ParseFinished }; ParseState parseState; char textBuffer[2560]; size_t textBufferSize; - - HTMLParseSection::Type sectionStack[MAX_PARSE_SECTION_STACK_SIZE]; - unsigned int sectionStackSize; + int escapeSequenceStartIndex; + + Stack contextStack; + int contextStackSize; bool parsingUnicode; int unicodeByteCount; @@ -110,3 +131,5 @@ class HTMLParser unsigned int preformatted; }; + +#endif diff --git a/src/Platform.h b/src/Platform.h index 8474fde..97a17d8 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -12,80 +12,50 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _PLATFORM_H_ +#define _PLATFORM_H_ #include #include "Font.h" +#include "Colour.h" #include "Cursor.h" -#include "Widget.h" struct Image; +class DrawSurface; +struct VideoModeInfo; class VideoDriver { public: - virtual void Init() = 0; + virtual void Init(VideoModeInfo* videoMode) = 0; virtual void Shutdown() = 0; - virtual void ClearScreen() = 0; - virtual void InvertScreen() {} - - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app) = 0; - - virtual void ClearWindow() = 0; - virtual void ClearRect(int x, int y, int width, int height) = 0; - virtual void FillRect(int x, int y, int width, int height) = 0; - virtual void InvertRect(int x, int y, int width, int height) = 0; - virtual void ScrollWindow(int delta) = 0; - virtual void SetScissorRegion(int y1, int y2) = 0; - virtual void ClearScissorRegion() = 0; - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular) = 0; - virtual void DrawScrollBar(int position, int size) = 0; - virtual void DrawImage(Image* image, int x, int y) = 0; - - virtual void HLine(int x, int y, int count) = 0; - virtual void VLine(int x, int y, int count) = 0; - - virtual void ScaleImageDimensions(int& width, int& height) {} - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type) = 0; - - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular) = 0; - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular) = 0; - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular) = 0; + void InvertVideoOutput(); + VideoModeInfo* GetVideoModeInfo() { return videoMode; } int screenWidth; int screenHeight; - int windowWidth; - int windowHeight; - int windowX, windowY; - - Image* imageIcon; - Image* bulletImage; - - bool isTextMode; + DrawSurface* drawSurface; + ColourScheme colourScheme; + uint8_t* paletteLUT; + +protected: + VideoModeInfo* videoMode; }; -class HTTPRequest +typedef uint8_t NetworkAddress[4]; // An IPv4 address is 4 bytes +class HTTPRequest; + +class NetworkTCPSocket { public: - enum Status - { - Stopped, - Connecting, - Downloading, - Finished, - Error, - UnsupportedHTTPS - }; - - virtual Status GetStatus() = 0; - virtual size_t ReadData(char* buffer, size_t count) = 0; - virtual void Stop() = 0; - virtual const char* GetStatusString() { return ""; } - virtual const char* GetURL() { return ""; } + virtual int Send(uint8_t* data, int length) = 0; + virtual int Receive(uint8_t* buffer, int length) = 0; + virtual int Connect(NetworkAddress address, int port) = 0; + virtual bool IsConnectComplete() = 0; + virtual bool IsClosed() = 0; + virtual void Close() = 0; }; class NetworkDriver @@ -97,6 +67,12 @@ class NetworkDriver virtual bool IsConnected() { return false; } + // Returns zero on success, negative number is error + virtual int ResolveAddress(const char* name, NetworkAddress address, bool sendRequest) { return false; } + + virtual NetworkTCPSocket* CreateSocket() { return NULL; } + virtual void DestroySocket(NetworkTCPSocket* socket) {} + virtual HTTPRequest* CreateRequest(char* url) { return NULL; } virtual void DestroyRequest(HTTPRequest* request) {} }; @@ -115,17 +91,25 @@ class InputDriver virtual void SetMouseCursor(MouseCursor::Type type) = 0; virtual void GetMouseStatus(int& buttons, int& x, int& y) = 0; virtual void SetMousePosition(int x, int y) = 0; + + virtual bool GetMouseButtonPress(int& x, int& y) = 0; + virtual bool GetMouseButtonRelease(int& x, int& y) = 0; + virtual InputButtonCode GetKeyPress() { return 0; } }; class Platform { public: - static void Init(int argc, char* argv[]); + static bool Init(int argc, char* argv[]); static void Shutdown(); static void Update(); + static void FatalError(const char* message, ...); + static VideoDriver* video; static NetworkDriver* network; static InputDriver* input; }; + +#endif diff --git a/src/Render.cpp b/src/Render.cpp new file mode 100644 index 0000000..e0b2ea2 --- /dev/null +++ b/src/Render.cpp @@ -0,0 +1,579 @@ +#include "Render.h" +#include "Node.h" +#include "App.h" +#include "Interface.h" +#include "Draw/Surface.h" +#include "DataPack.h" +#include "Nodes/ImgNode.h" + +PageRenderer::PageRenderer(App& inApp) + : app(inApp) +{ +} + +void PageRenderer::Init() +{ + Reset(); +} + +void PageRenderer::InitContext(DrawContext& context) +{ + context.surface = Platform::video->drawSurface; + + Rect& windowRect = app.ui.windowRect; + context.clipLeft = windowRect.x; + context.clipRight = windowRect.x + windowRect.width; + context.clipTop = windowRect.y; + context.clipBottom = windowRect.y + windowRect.height; + context.drawOffsetX = windowRect.x; + context.drawOffsetY = windowRect.y; +} + +void PageRenderer::ClampContextToRect(DrawContext& context, Rect& rect) +{ + if (context.clipTop < rect.y) + context.clipTop = rect.y; + if (context.clipBottom < rect.y) + context.clipBottom = rect.y; + + int maxY = rect.y + rect.height; + if (context.clipTop > maxY) + context.clipTop = maxY; + if (context.clipBottom > maxY) + context.clipBottom = maxY; +} + +void PageRenderer::RefreshAll() +{ + Platform::input->HideMouse(); + + Rect& windowRect = app.ui.windowRect; + + DrawContext clearContext; + InitContext(clearContext); + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, app.page.colourScheme.pageColour); + + Platform::input->ShowMouse(); + + renderQueue.Reset(); + + FindOverlappingNodesInScreenRegion(windowRect.y, windowRect.y + windowRect.height); +} + +void PageRenderer::MarkScreenRegionDirty(int left, int top, int right, int bottom) +{ + Rect& windowRect = app.ui.windowRect; + if (top < windowRect.y) + top = windowRect.y; + if (bottom > windowRect.y + windowRect.height) + bottom = windowRect.y + windowRect.height; + if (left < windowRect.x) + left = windowRect.x; + if (right > windowRect.x + windowRect.width) + right = windowRect.x + windowRect.width; + + DrawContext clearContext; + InitContext(clearContext); + clearContext.drawOffsetY = 0; + + Platform::input->HideMouse(); + clearContext.surface->FillRect(clearContext, left, top, right - left, bottom - top, app.page.colourScheme.pageColour); + Platform::input->ShowMouse(); + + FindOverlappingNodesInScreenRegion(top, bottom); +} + + +void PageRenderer::OnPageScroll(int scrollDelta) +{ + if (scrollDelta == 0) + { + return; + } + + Rect& windowRect = app.ui.windowRect; + + int minWinY = windowRect.y; + int maxWinY = windowRect.y + windowRect.height; + int minWinX = windowRect.x; + int maxWinX = windowRect.x + windowRect.width; + + for (int i = renderQueue.head; i < renderQueue.tail; i++) + { + RenderQueue::Item* item = &renderQueue.items[i]; + item->lowerClip -= scrollDelta; + item->upperClip -= scrollDelta; + + if (item->upperClip < minWinY) + item->upperClip = minWinY; + if (item->lowerClip > maxWinY) + item->lowerClip = maxWinY; + + if (item->lowerClip <= item->upperClip) + { + // Scrolled off screen, can remove from queue + renderQueue.tail--; + for (int n = i; n < renderQueue.tail; n++) + { + renderQueue.items[n] = renderQueue.items[n + 1]; + } + i--; + } + } + + if (scrollDelta > 0) + { + int top = maxWinY - scrollDelta; + if (top < minWinY) + top = minWinY; + FindOverlappingNodesInScreenRegion(top, maxWinY); + } + else if (scrollDelta < 0) + { + int bottom = minWinY - scrollDelta; + if (bottom > maxWinY) + bottom = maxWinY; + FindOverlappingNodesInScreenRegion(minWinY, bottom); + } + + Platform::input->HideMouse(); + + DrawContext clearContext; + InitContext(clearContext); + + if (scrollDelta > 0) + { + Platform::video->drawSurface->ScrollScreen(minWinY, maxWinY - scrollDelta, windowRect.width, scrollDelta); + clearContext.clipTop = maxWinY - scrollDelta; + if (clearContext.clipTop < minWinY) + { + clearContext.clipTop = minWinY; + } + clearContext.clipBottom = maxWinY; + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, app.page.colourScheme.pageColour); + } + else + { + Platform::video->drawSurface->ScrollScreen(minWinY - scrollDelta, maxWinY, windowRect.width, scrollDelta); + clearContext.clipTop = minWinY; + clearContext.clipBottom = minWinY - scrollDelta; + if (clearContext.clipBottom > maxWinY) + { + clearContext.clipBottom = maxWinY; + } + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, app.page.colourScheme.pageColour); + } + + Platform::input->ShowMouse(); +} + +bool PageRenderer::IsRenderableNode(Node* node) +{ + if (node->size.IsZero()) + return false; + + switch (node->type) + { + case Node::Style: + case Node::Section: + case Node::Link: + case Node::Form: + return false; + default: + return true; + } +} + +int PageRenderer::GetDrawOffsetY() +{ + Rect& windowRect = app.ui.windowRect; + return windowRect.y - app.ui.GetScrollPositionY(); +} + +void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) +{ + Rect& windowRect = app.ui.windowRect; + int drawOffsetY = GetDrawOffsetY(); + + if (top < windowRect.y) + top = windowRect.y; + if (bottom > windowRect.y + windowRect.height) + bottom = windowRect.y + windowRect.height; + + if (!lastCompleteNode) + return; + + for(Node* node = app.page.GetRootNode(); node; node = node->GetNextInTree()) + { + if(IsRenderableNode(node)) + { + int nodeTop = node->anchor.y + drawOffsetY; + int nodeBottom = nodeTop + node->size.y; + + if (nodeTop < top) + nodeTop = top; + if (nodeBottom > bottom) + nodeBottom = bottom; + + if(nodeBottom - nodeTop > 0) + { + AddToQueue(node, nodeTop, nodeBottom); + } + } + + if (node == lastCompleteNode) + break; + } +} + +void PageRenderer::Reset() +{ + renderQueue.Reset(); + lastCompleteNode = nullptr; + visiblePageHeight = 0; + isPaused = false; +} + +bool PageRenderer::DoesOverlapWithContext(Node* node, DrawContext& context) +{ + if (node->anchor.y + context.drawOffsetY > context.clipBottom) + { + return false; + } + if (node->anchor.y + node->size.y + context.drawOffsetY < context.clipTop) + { + return false; + } + return true; +} + +void PageRenderer::Update() +{ +#ifdef _WIN32 + static int highWaterMark = 0; + if (renderQueue.Size() > highWaterMark) + { + highWaterMark = renderQueue.Size(); + printf("High water mark: %d\n", highWaterMark); + } + + //if (rand() % 256) + // return; +#endif + if (isPaused) + return; + + int itemsToRender = 5; + + DrawContext itemContext; + InitContext(itemContext); + itemContext.drawOffsetY = GetDrawOffsetY(); + + while(renderQueue.Size() && itemsToRender) + { + itemsToRender--; + + RenderQueue::Item* item = &renderQueue.items[renderQueue.head]; + Node* toRender = item->node; + bool finishedRendering = true; + + Platform::input->HideMouse(); + itemContext.clipTop = item->upperClip; + itemContext.clipBottom = item->lowerClip; + + if (toRender->type == Node::Image) + { + // Render images bit by bit + const int imageLinesToRenderPerUpdate = 8; + + ImageNode::Data* imageData = static_cast(toRender->data); + if (imageData->state == ImageNode::FinishedDownloadingContent && imageData->image.lines.IsAllocated()) + { + if (itemContext.clipBottom > itemContext.clipTop + imageLinesToRenderPerUpdate) + { + itemContext.clipBottom = itemContext.clipTop + imageLinesToRenderPerUpdate; + item->upperClip = itemContext.clipBottom; + finishedRendering = false; + } + } + } + + toRender->Handler().Draw(itemContext, toRender); + + Platform::input->ShowMouse(); + + if (finishedRendering) + { + renderQueue.Dequeue(); + itemsToRender = 0; + } + } +} + +void PageRenderer::AddToQueue(Node* node, int upperClip, int lowerClip) +{ + if (lowerClip <= upperClip) + { + return; + } + + int insertPosition = renderQueue.tail; + + // Check if in queue already + for (int i = renderQueue.head; i < renderQueue.tail; i++) + { + RenderQueue::Item* item = &renderQueue.items[i]; + if (item->node == node) + { + // Already in queue + if ((upperClip <= item->upperClip && lowerClip >= item->upperClip) + || (lowerClip >= item->lowerClip && upperClip <= item->lowerClip)) + { + // Grow clip region + if (upperClip < item->upperClip) + { + item->upperClip = upperClip; + } + if (lowerClip > item->lowerClip) + { + item->lowerClip = lowerClip; + } + return; + } + } + else if (item->node->IsChildOf(node)) + { + if (insertPosition > i) + { + insertPosition = i; + } + } + } + + if (renderQueue.Size() < MAX_RENDER_QUEUE_SIZE) + { + if (renderQueue.tail == MAX_RENDER_QUEUE_SIZE) + { + memcpy(renderQueue.items, renderQueue.items + renderQueue.head, renderQueue.Size() * sizeof(RenderQueue::Item)); + renderQueue.tail -= renderQueue.head; + insertPosition -= renderQueue.head; + renderQueue.head = 0; + } + + if (insertPosition != renderQueue.tail) + { + for (int i = renderQueue.tail; i > insertPosition; --i) + { + renderQueue.items[i] = renderQueue.items[i - 1]; + } + } + + RenderQueue::Item* item = &renderQueue.items[insertPosition]; + item->node = node; + item->upperClip = upperClip; + item->lowerClip = lowerClip; + renderQueue.tail++; + } + else + { + //printf("Error! Out of space in render queue!\n"); + } +} + +bool PageRenderer::IsInRenderQueue(Node* node) +{ + if (!node) + { + return false; + } + for (int i = renderQueue.head; i < renderQueue.tail; i++) + { + RenderQueue::Item* item = &renderQueue.items[i]; + if (item->node == node) + { + // Already in queue + return true; + } + } + return false; +} + +void PageRenderer::DrawAll(DrawContext& context, Node* node) +{ + Platform::input->HideMouse(); + while (node) + { + node->Handler().Draw(context, node); + + node = node->GetNextInTree(); + } + Platform::input->ShowMouse(); + + //context.surface->BlitImage(context, Assets.imageIcon, 0, 50); + //context.surface->BlitImage(context, Assets.imageIcon, 1, 100); + //context.surface->BlitImage(context, Assets.imageIcon, 2, 150); +} + +void PageRenderer::GenerateDrawContext(DrawContext& context, Node* node) +{ + context.surface = Platform::video->drawSurface; + + if (app.ui.IsInterfaceNode(node)) + { + context.clipLeft = 0; + context.clipRight = Platform::video->screenWidth; + context.clipTop = 0; + context.clipBottom = Platform::video->screenHeight; + context.drawOffsetX = 0; + context.drawOffsetY = 0; + } + else + { + Rect& windowRect = app.ui.windowRect; + context.clipLeft = windowRect.x; + context.clipRight = windowRect.x + windowRect.width; + context.clipTop = windowRect.y; + context.clipBottom = windowRect.y + windowRect.height; + context.drawOffsetX = windowRect.x; + context.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); + } +} + +void PageRenderer::MarkNodeLayoutComplete(Node* node) +{ + Rect& windowRect = app.ui.windowRect; + int drawOffsetY = GetDrawOffsetY(); + Node* startNode = lastCompleteNode ? lastCompleteNode->GetNextInTree() : app.page.GetRootNode(); + lastCompleteNode = node; + + int minWinY = windowRect.y; + int maxWinY = windowRect.y + windowRect.height; + + bool expandedPage = false; + + for (Node* node = startNode; node; node = node->GetNextInTree()) + { + node->isLayoutComplete = true; + + if (IsRenderableNode(node)) + { + int nodeTop = node->anchor.y + drawOffsetY; + int nodeBottom = nodeTop + node->size.y; + + if (node->anchor.y + node->size.y > visiblePageHeight) + { + visiblePageHeight = node->anchor.y + node->size.y; + expandedPage = true; + } + + if (nodeTop < minWinY) + nodeTop = minWinY; + if (nodeBottom > maxWinY) + nodeBottom = maxWinY; + if (nodeBottom > nodeTop) + { + AddToQueue(node, nodeTop, nodeBottom); + } + } + + if (node == lastCompleteNode) + break; + } + + if (expandedPage) + { + App::Get().ui.UpdatePageScrollBar(); + } +} + +void PageRenderer::MarkNodeDirty(Node* dirtyNode) +{ + // Check this is in a completed layout + for (Node* node = app.page.GetRootNode(); node; node = node->GetNextInTree()) + { + if (node == lastCompleteNode) + return; + + if (node == dirtyNode) + break; + } + + Rect& windowRect = app.ui.windowRect; + int drawOffsetY = GetDrawOffsetY(); + int minWinY = windowRect.y; + int maxWinY = windowRect.y + windowRect.height; + + int nodeTop = dirtyNode->anchor.y + drawOffsetY; + int nodeBottom = nodeTop + dirtyNode->size.y; + bool outsideOfWindow = (nodeTop > maxWinY) || (nodeBottom < minWinY); + + if (!outsideOfWindow) + { + if (nodeTop < minWinY) + nodeTop = minWinY; + if (nodeBottom > maxWinY) + nodeBottom = maxWinY; + AddToQueue(dirtyNode, nodeTop, nodeBottom); + + Platform::input->HideMouse(); + + Rect& windowRect = app.ui.windowRect; + + DrawContext clearContext; + InitContext(clearContext); + clearContext.clipBottom = windowRect.y + windowRect.height; + clearContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); + clearContext.surface->FillRect(clearContext, dirtyNode->anchor.x, dirtyNode->anchor.y, dirtyNode->size.x, dirtyNode->size.y, app.page.colourScheme.pageColour); + + Platform::input->ShowMouse(); + } +} + +void PageRenderer::MarkPageLayoutComplete() +{ + Node* node = lastCompleteNode; + + if (!lastCompleteNode) + { + node = app.page.GetRootNode(); + } + + while (node) + { + Node* next = node->GetNextInTree(); + if (next) + { + node = next; + } + else + { + MarkNodeLayoutComplete(node); + break; + } + } +} + +void PageRenderer::InvertNode(Node* node) +{ + Platform::input->HideMouse(); + Rect& windowRect = app.ui.windowRect; + DrawContext invertContext; + + InitContext(invertContext); + invertContext.clipBottom = windowRect.y + windowRect.height; + + //if (invertContext.clipTop < upperContext.clipBottom) + //{ + // invertContext.clipTop = upperContext.clipBottom; + //} + //if (invertContext.clipBottom > lowerContext.clipTop) + //{ + // invertContext.clipBottom = lowerContext.clipTop; + //} + + invertContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); + invertContext.surface->InvertRect(invertContext, node->anchor.x, node->anchor.y, node->size.x, node->size.y); + + Platform::input->ShowMouse(); +} + diff --git a/src/Render.h b/src/Render.h new file mode 100644 index 0000000..194abf6 --- /dev/null +++ b/src/Render.h @@ -0,0 +1,107 @@ +#ifndef _RENDER_H_ +#define _RENDER_H_ + +#include "Draw/Surface.h" +#include "Node.h" + +class App; +class Node; +struct DrawContext; +struct Rect; + +#define MAX_RENDER_QUEUE_SIZE 512 + +struct RenderQueue +{ + struct Item + { + Node* node; + int upperClip, lowerClip; + }; + + RenderQueue() + { + Reset(); + } + + void Reset() + { + head = tail = 0; + } + int Size() + { + return tail - head; + } + RenderQueue::Item* Dequeue() + { + if (head < tail) + { + RenderQueue::Item* result = &items[head++]; + if (head == tail) + { + tail = head = 0; + } + return result; + } + return nullptr; + } + + int head, tail; + Item items[MAX_RENDER_QUEUE_SIZE]; +}; + +class PageRenderer +{ +public: + PageRenderer(App& inApp); + + void Init(); + void Reset(); + void Update(); + + void RefreshAll(); + void DrawAll(DrawContext& context, Node* node); + + void GenerateDrawContext(DrawContext& context, Node* node); + + void AddToQueue(Node* node, int upperClip, int lowerClip); + + void OnPageScroll(int scrollDelta); + + void MarkNodeLayoutComplete(Node* node); + void MarkPageLayoutComplete(); + void MarkNodeDirty(Node* node); + + void InvertNode(Node* node); + + int GetVisiblePageHeight() { return visiblePageHeight; } + bool IsRendering() { return renderQueue.Size() > 0; } + + void MarkScreenRegionDirty(int left, int top, int right, int bottom); + + void SetPaused(bool paused) { isPaused = paused; } + +private: + bool IsInRenderQueue(Node* node); + + void InitContext(DrawContext& context); + void ClampContextToRect(DrawContext& context, Rect& rect); + void FindOverlappingNodesInScreenRegion(int top, int bottom); + + bool DoesOverlapWithContext(Node* node, DrawContext& context); + bool IsRenderableNode(Node* node); + + int GetDrawOffsetY(); + + App& app; + + RenderQueue renderQueue; + + Node* lastCompleteNode; + + int visiblePageHeight; + bool isPaused; +}; + +#endif + diff --git a/src/Renderer.cpp b/src/Renderer.cpp deleted file mode 100644 index 18c9add..0000000 --- a/src/Renderer.cpp +++ /dev/null @@ -1,652 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#include -#include -#include -#include "Renderer.h" -#include "Widget.h" -#include "Platform.h" -#include "App.h" -#include "Image.h" - -Renderer::Renderer(App& inApp) - : app(inApp) -{ - pageTopWidgetIndex = 0; - statusMessage[0] = '\0'; - scrollPosition = 0; -} - -void Renderer::Init() -{ - Platform::input->HideMouse(); - Platform::video->ClearWindow(); - RedrawScrollBar(); - app.ui.DrawInterfaceWidgets(); - SetTitle("MicroWeb"); - SetStatus(" "); - Platform::input->ShowMouse(); -} - -void Renderer::Reset() -{ - pageTopWidgetIndex = 0; - statusMessage[0] = '\0'; - scrollPosition = 0; - upperRenderLine = Platform::video->windowY; - lowerRenderLine = Platform::video->windowY; - Platform::input->HideMouse(); - Platform::video->ClearWindow(); - Platform::input->ShowMouse(); -} - -void Renderer::InvertWidget(Widget* widget) -{ - int baseY; - BeginWidgetDraw(widget, baseY); - - int x = widget->x; - int y = widget->y + baseY; - int width = widget->width; - int height = widget->height; - - if (widget->type == Widget::Button) - { - x++; - y++; - width -= 2; - height -= 2; - } - - if (widget->type == Widget::TextField) - { - if (widget->textField && widget->textField->buffer) - { - x += 2; - y += 2; - width = Platform::video->GetFont(1)->CalculateWidth(widget->textField->buffer); - if (width > widget->width - 4) - { - width = widget->width - 4; - } - height = Platform::video->GetFont(1)->glyphHeight; - } - } - - Platform::video->InvertRect(x, y, width, height); - EndWidgetDraw(); -} - -int Renderer::GetMaxScrollPosition() -{ - int maxScroll = app.page.GetPageHeight() - Platform::video->windowHeight; - if (maxScroll < 0) - { - maxScroll = 0; - } - return maxScroll; -} - -void Renderer::Update() -{ - int baseY = Platform::video->windowY - scrollPosition; - int lowerWindowY = Platform::video->windowY + Platform::video->windowHeight; - bool renderedAnything = false; - - if (lowerRenderLine < lowerWindowY) - { - int line = -1; - - Platform::video->SetScissorRegion(lowerRenderLine, lowerWindowY); - - for (int n = pageTopWidgetIndex; n < app.page.numFinishedWidgets; n++) - { - Widget* widget = &app.page.widgets[n]; - int widgetLine = widget->y + widget->height + baseY; - - if (widgetLine > lowerRenderLine) - { - if (line == -1 || (widget->y + baseY) <= line) - { - Platform::input->HideMouse(); - RenderWidgetInternal(widget, baseY); - line = widgetLine; - renderedAnything = true; - } - else - { - break; - } - } - } - - Platform::video->ClearScissorRegion(); - - if (line != -1) - { - lowerRenderLine = line; - if (lowerRenderLine > lowerWindowY) - { - lowerRenderLine = lowerWindowY; - } - } - } - - if (renderedAnything) - { - Platform::input->ShowMouse(); - return; - } - - if (upperRenderLine > Platform::video->windowY) - { - int bestLineStartIndex = -1; - int currentLineIndex = -1; - int currentLineHeight = -1; - int currentLineTop = -1; - - for (int n = pageTopWidgetIndex; n < app.page.numFinishedWidgets; n++) - { - Widget* widget = &app.page.widgets[n]; - int widgetTop = widget->y + baseY; - int widgetLine = widgetTop + widget->height; - - if (currentLineIndex == -1 || widgetLine != currentLineHeight) - { - // First widget in the line - possibly a new line - - if (currentLineTop != -1) - { - if (currentLineTop < upperRenderLine) - { - bestLineStartIndex = currentLineIndex; - } - else - { - // Last line was completely below the cutoff line so stop - break; - } - } - - currentLineIndex = n; - currentLineHeight = widgetLine; - currentLineTop = widgetTop; - } - - if (widgetTop < currentLineTop) - { - currentLineTop = widgetTop; - } - } - - if (bestLineStartIndex != -1) - { - int currentLineHeight = -1; - - Platform::video->SetScissorRegion(Platform::video->windowY, upperRenderLine); - - for (int n = bestLineStartIndex; n < app.page.numFinishedWidgets; n++) - { - Widget* widget = &app.page.widgets[n]; - int widgetTop = widget->y + baseY; - int widgetLine = widgetTop + widget->height; - - if (currentLineHeight == -1) - { - currentLineHeight = widgetLine; - } - if (widgetLine != currentLineHeight) - { - break; - } - - Platform::input->HideMouse(); - RenderWidgetInternal(widget, baseY); - if (widgetTop < upperRenderLine) - { - upperRenderLine = widgetTop; - } - } - - Platform::video->ClearScissorRegion(); - - if (upperRenderLine < Platform::video->windowY) - { - upperRenderLine = Platform::video->windowY; - } - } - else - { - // Everything must have been rendered, reset - upperRenderLine = Platform::video->windowY; - } - } - - Platform::input->ShowMouse(); -} - -void Renderer::ScrollTo(int targetPosition) -{ - Scroll(targetPosition - scrollPosition); -} - -void Renderer::Scroll(int delta) -{ - if (scrollPosition + delta < 0) - { - delta = -scrollPosition; - } - int maxScroll = GetMaxScrollPosition(); - if (scrollPosition + delta > maxScroll) - { - delta = maxScroll - scrollPosition; - } - - delta &= ~1; - - if (delta == 0) - { - return; - } - - Platform::input->HideMouse(); - - if (delta < Platform::video->windowHeight && delta > -Platform::video->windowHeight) - { - Platform::video->ScrollWindow(delta); - } - else - { - Platform::video->ClearWindow(); - } - - scrollPosition += delta; - - app.ui.UpdatePageScrollBar(); - - if (delta < 0) - { - for (int n = pageTopWidgetIndex - 1; n >= 0; n--) - { - Widget* widget = &app.page.widgets[n]; - if (widget->y + widget->height < scrollPosition) - { - break; - } - pageTopWidgetIndex = n; - } - } - else - { - for (int n = pageTopWidgetIndex; n < app.page.numFinishedWidgets; n++) - { - Widget* widget = &app.page.widgets[n]; - if (widget->y + widget->height < scrollPosition && n == pageTopWidgetIndex) - { - pageTopWidgetIndex++; - } - else break; - } - } - - lowerRenderLine -= delta; - upperRenderLine -= delta; - int windowBottom = Platform::video->windowY + Platform::video->windowHeight; - - if (lowerRenderLine < Platform::video->windowY) - { - lowerRenderLine = Platform::video->windowY; - } - if (lowerRenderLine > windowBottom) - { - lowerRenderLine = windowBottom; - } - if (upperRenderLine < Platform::video->windowY) - { - upperRenderLine = Platform::video->windowY; - } - if (upperRenderLine > windowBottom) - { - upperRenderLine = windowBottom; - } - - Platform::input->ShowMouse(); -} - -void Renderer::RedrawScrollBar() -{ - Platform::input->HideMouse(); - Platform::video->DrawScrollBar(app.ui.scrollBar.scrollBar->position, app.ui.scrollBar.scrollBar->size); - Platform::input->ShowMouse(); -} - -void Renderer::RenderWidgetInternal(Widget* widget, int baseY) -{ - switch (widget->type) - { - case Widget::Text: - if (widget->text->text) - { - Platform::video->DrawString(widget->text->text, widget->x, widget->y + baseY, widget->style.fontSize, widget->style.fontStyle); - if (app.ui.GetActiveWidget() && app.ui.GetActiveWidget()->GetLinkURL() && app.ui.GetActiveWidget()->GetLinkURL() == widget->GetLinkURL()) - { - Platform::video->InvertRect(widget->x, widget->y + baseY, widget->width, widget->height); - } - } - break; - case Widget::BulletPoint: - Platform::video->DrawImage(Platform::video->bulletImage, widget->x, widget->y + baseY); - break; - case Widget::HorizontalRule: - Platform::video->HLine(widget->x, widget->y + baseY, widget->width); - break; - case Widget::Button: - if (widget->button) - { - bool isSelected = app.ui.GetActiveWidget() == widget; - DrawButtonRect(widget->x, widget->y + baseY, widget->width, widget->height, isSelected); - Platform::video->DrawString(widget->button->text, widget->x + 8, widget->y + baseY + 2, widget->style.fontSize, widget->style.fontStyle); - } - break; - case Widget::Image: - if (widget->image && widget->width > 0) - { - int altTextOffset = 0; - - if (Image* imageIcon = Platform::video->imageIcon) - { - if (widget->width == imageIcon->width && widget->height == imageIcon->height) - { - Platform::video->DrawImage(Platform::video->imageIcon, widget->x, widget->y + baseY); - break; - } - else if (widget->height >= Platform::video->imageIcon->height + 4) - { - Platform::video->DrawImage(Platform::video->imageIcon, widget->x + 2, widget->y + baseY + 2); - altTextOffset += Platform::video->imageIcon->width + 2; - } - } - - DrawRect(widget->x, widget->y + baseY, widget->width, widget->height); - if (widget->image->altText) - { - DrawTruncatedString(widget->image->altText, widget->x + 2 + altTextOffset, widget->y + baseY + 2, widget->width - 4 - altTextOffset, widget->style.fontSize, widget->style.fontStyle); - } - } - break; - case Widget::TextField: - if (widget->textField) - { - DrawButtonRect(widget->x, widget->y + baseY, widget->width, widget->height); - if (widget->textField->buffer) - { - int maxWidth = widget->width - 4; - - DrawTruncatedString(widget->textField->buffer, widget->x + 3, widget->y + baseY + 2, maxWidth); - if (app.ui.GetActiveWidget() == widget) - { - DrawTextFieldCursorInternal(widget, app.ui.GetTextFieldCursorPosition(), false, baseY); - } - } - } - break; - } -} - -void Renderer::DrawTruncatedString(char* message, int x, int y, int maxTextWidth, int size, FontStyle::Type style) -{ - int textWidth = 0; - char* replaceChar = NULL; - char oldChar = 0; - - for (char* p = message; *p; p++) - { - textWidth += Platform::video->GetGlyphWidth(*p); - if (textWidth > maxTextWidth) - { - oldChar = *p; - replaceChar = p; - *p = '\0'; - break; - } - } - Platform::video->DrawString(message, x, y); - - if (replaceChar) - { - *replaceChar = oldChar; - } -} - -void Renderer::RedrawModifiedTextField(Widget* widget, int position) -{ - int baseY = 0; - - int x = widget->x + 3; - int width = widget->width - 4; - int maxX = widget->x + width - 1; - char* str = widget->textField->buffer; - char* lastChar = NULL; - char oldChar = 0; - - for (int n = 0; n < position && *str; n++, str++) - { - int glyphWidth = Platform::video->GetGlyphWidth(widget->textField->buffer[n]); - width -= glyphWidth; - x += glyphWidth; - if (width <= 0) - { - break; - } - if (x >= maxX) - { - lastChar = str; - oldChar = *lastChar; - *lastChar = '\0'; - } - } - - if (width > 0) - { - BeginWidgetDraw(widget, baseY); - int y = widget->y + 2 + baseY; - - Platform::video->ClearRect(x, y, width, Platform::video->GetFont(1)->glyphHeight); - Platform::video->DrawString(str, x, y); - - Platform::video->ClearScissorRegion(); - } - - if (lastChar) - { - *lastChar = oldChar; - } -} - -Widget* Renderer::PickPageWidget(int x, int y) -{ - if (y < upperRenderLine || y > lowerRenderLine || x > Platform::video->windowWidth) - { - return NULL; - } - return app.page.GetWidget(x, y - Platform::video->windowY + scrollPosition); -} - -bool Renderer::IsOverWidget(Widget* widget, int x, int y) -{ - if (widget->isInterfaceWidget) - { - return x >= widget->x && y >= widget->y && x < widget->x + widget->width && y < widget->y + widget->height; - } - - int adjustY = scrollPosition - Platform::video->windowY; - - return (x >= widget->x && y >= widget->y + adjustY && x < widget->x + widget->width && y < widget->y + widget->height + adjustY); -} - -void Renderer::SetTitle(const char* title) -{ - Platform::input->HideMouse(); - - Platform::video->ClearRect(app.ui.titleBar.x, app.ui.titleBar.y, app.ui.titleBar.width, app.ui.titleBar.height); - if (title) - { - int textWidth = Platform::video->GetFont(1)->CalculateWidth(title); - Platform::video->DrawString(title, app.ui.titleBar.x + Platform::video->screenWidth / 2 - textWidth / 2, app.ui.titleBar.y, 1); - } - - Platform::input->ShowMouse(); -} - -void Renderer::SetStatus(const char* status) -{ - if (app.ui.statusBar.height == 0) - { - // For video modes that hide the status bar - return; - } - - if(!status && statusMessage[0] != '\0' || strncmp(status, statusMessage, MAX_STATUS_LENGTH)) - { - Platform::input->HideMouse(); - - Platform::video->FillRect(app.ui.statusBar.x, app.ui.statusBar.y, app.ui.statusBar.width, app.ui.statusBar.height); - - if (status) - { - strncpy(statusMessage, status, MAX_STATUS_LENGTH); - } - else - { - statusMessage[0] = '\0'; - } - - Platform::video->DrawString(statusMessage, app.ui.statusBar.x, app.ui.statusBar.y, 1); - - Platform::input->ShowMouse(); - } -} - -void Renderer::DrawButtonRect(int x, int y, int width, int height, bool isSelected) -{ - Platform::video->HLine(x + 1, y, width - 2); - Platform::video->HLine(x + 1, y + height - 1, width - 2); - Platform::video->VLine(x, y + 1, height - 2); - Platform::video->VLine(x + width - 1, y + 1, height - 2); - - if (isSelected) - { - Platform::video->HLine(x + 1, y + 1, width - 2); - Platform::video->HLine(x + 1, y + height - 2, width - 2); - Platform::video->VLine(x + 1, y + 1, height - 2); - Platform::video->VLine(x + width - 2, y + 1, height - 2); - } -} - -void Renderer::DrawRect(int x, int y, int width, int height) -{ - Platform::video->HLine(x, y, width); - Platform::video->HLine(x, y + height - 1, width); - Platform::video->VLine(x, y + 1, height - 2); - Platform::video->VLine(x + width - 1, y + 1, height - 2); -} - -void Renderer::RenderWidget(Widget* widget) -{ - int baseY; - BeginWidgetDraw(widget, baseY); - RenderWidgetInternal(widget, baseY); - EndWidgetDraw(); -} - -void Renderer::RedrawWidget(Widget* widget) -{ - int baseY; - BeginWidgetDraw(widget, baseY); - - Platform::video->ClearRect(widget->x, widget->y + baseY, widget->width, widget->height); - RenderWidgetInternal(widget, baseY); - - EndWidgetDraw(); -} - -void Renderer::DrawTextFieldCursorInternal(Widget* widget, int position, bool clear, int baseY) -{ - int x = widget->x + 2; - int height = Platform::video->GetFont(1)->glyphHeight; - - for (int n = 0; n < position; n++) - { - if (!widget->textField->buffer[n]) - break; - x += Platform::video->GetGlyphWidth(widget->textField->buffer[n]); - } - - if (x >= widget->x + widget->width - 1) - { - return; - } - - int y = widget->y + 2 + baseY; - - if (clear) - { - Platform::video->ClearRect(x, y, 1, height); - } - else - { - Platform::video->VLine(x, y, height); - } -} - -void Renderer::DrawTextFieldCursor(Widget* widget, int position, bool clear) -{ - if (!widget || !widget->textField || !widget->textField->buffer) - return; - - int baseY; - BeginWidgetDraw(widget, baseY); - - DrawTextFieldCursorInternal(widget, position, clear, baseY); - - EndWidgetDraw(); -} - -void Renderer::BeginWidgetDraw(Widget* widget, int& baseY) -{ - Platform::input->HideMouse(); - - if (!widget->isInterfaceWidget) - { - baseY = Platform::video->windowY - scrollPosition; - Platform::video->SetScissorRegion(upperRenderLine, lowerRenderLine); - } - else baseY = 0; -} - -void Renderer::EndWidgetDraw() -{ - Platform::video->ClearScissorRegion(); - Platform::input->ShowMouse(); -} - -void Renderer::ScrollTo(Widget* widget) -{ - if (widget->y < app.renderer.GetScrollPosition() || widget->y + widget->height > app.renderer.GetScrollPosition() + Platform::video->windowHeight) - { - ScrollTo(widget->y - Platform::video->windowHeight / 2); - } -} - diff --git a/src/Renderer.h b/src/Renderer.h deleted file mode 100644 index e71ab5d..0000000 --- a/src/Renderer.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#pragma once - -#include "Font.h" -class App; -struct Widget; - -#define MAX_STATUS_LENGTH 80 - -class Renderer -{ -public: - Renderer(App& inApp); - - void Init(); - void Reset(); - void Update(); - void Scroll(int delta); - void ScrollTo(int position); - void ScrollTo(Widget* widget); - - void RedrawScrollBar(); - int GetMaxScrollPosition(); - int GetScrollPosition() { return scrollPosition; } - - Widget* PickPageWidget(int x, int y); - bool IsOverWidget(Widget* widget, int x, int y); - - void SetStatus(const char* status); - void SetTitle(const char* status); - - void RenderWidget(Widget* widget); - void RedrawWidget(Widget* widget); - void InvertWidget(Widget* widget); - - void RedrawModifiedTextField(Widget* widget, int position); - void DrawTextFieldCursor(Widget* widget, int position, bool clear = false); - - int GetPageTopWidgetIndex() { return pageTopWidgetIndex; } -private: - void DrawTruncatedString(char* message, int x, int y, int maxTextWidth, int size = 1, FontStyle::Type style = FontStyle::Regular); - void DrawButtonRect(int x, int y, int width, int height, bool isSelected = false); - void DrawRect(int x, int y, int width, int height); - - void RenderWidgetInternal(Widget* widget, int baseY); - void DrawTextFieldCursorInternal(Widget* widget, int position, bool clear, int baseY); - - void BeginWidgetDraw(Widget* widget, int& baseY); - void EndWidgetDraw(); - - App& app; - int scrollPosition; - int pageTopWidgetIndex; - char statusMessage[MAX_STATUS_LENGTH]; - int upperRenderLine, lowerRenderLine; -}; - diff --git a/src/Stack.h b/src/Stack.h new file mode 100644 index 0000000..40f991c --- /dev/null +++ b/src/Stack.h @@ -0,0 +1,74 @@ +#ifndef _STACK_H_ +#define _STACK_H_ + +#include "Memory/Memory.h" + +template +class Stack +{ +public: + Stack(Allocator& inAllocator) : allocator(inAllocator) + { + Reset(); + } + + void Reset() + { + top = &base; + top->next = top->prev = nullptr; + } + + T& Top() + { + return top->obj; + } + + void Push() + { + if (top->next) + { + top->next->obj = top->obj; + top = top->next; + } + else + { + Entry* newEntry = allocator.Alloc(); + if (newEntry) + { + top->next = newEntry; + newEntry->prev = top; + newEntry->next = nullptr; + newEntry->obj = top->obj; + top = newEntry; + } + else + { + // TODO: handle allocation error more gracefully + //Platform::FatalError("Could not push to stack: out of memory"); + } + } + } + + void Pop() + { + if (top->prev) + { + top = top->prev; + } + } + + struct Entry + { + T obj; + Entry* next; + Entry* prev; + }; + + Entry* top; + +private: + Entry base; + Allocator& allocator; +}; + +#endif diff --git a/src/Style.cpp b/src/Style.cpp new file mode 100644 index 0000000..d948f5e --- /dev/null +++ b/src/Style.cpp @@ -0,0 +1,58 @@ +#include +#include "Style.h" +#include "Memory/Memory.h" + +static StylePool pool; + +StylePool& StylePool::Get() +{ + return pool; +} + +ElementStyleHandle StylePool::AddStyle(const ElementStyle& style) +{ + // Check if an identical style already exists + for (ElementStyleHandle n = 0; n < numItems; n++) + { + if (!memcmp(&style, &GetStyle(n), sizeof(ElementStyle))) + { + return n; + } + } + + ElementStyleHandle newHandle = numItems; + if (newHandle >= MAX_STYLES) + return 0; + + int chunkIndex = newHandle >> STYLE_POOL_CHUNK_SHIFT; + int itemIndex = newHandle & STYLE_POOL_INDEX_MASK; + + if (chunkIndex > 0 && itemIndex == 0) + { + chunks[chunkIndex] = MemoryManager::pageAllocator.Alloc(); + if (!chunks[chunkIndex]) + { + return 0; + } + } + + chunks[chunkIndex]->items[itemIndex] = style; + numItems++; + + return newHandle; +} + +const ElementStyle& StylePool::GetStyle(ElementStyleHandle handle) +{ + if (handle < numItems) + { + int chunkIndex = handle >> STYLE_POOL_CHUNK_SHIFT; + return chunks[chunkIndex]->items[handle & STYLE_POOL_INDEX_MASK]; + } + return chunks[0]->items[0]; +} + +void StylePool::Init() +{ + chunks[0] = new StylePool::PoolChunk(); +} diff --git a/src/Style.h b/src/Style.h new file mode 100644 index 0000000..8900e0d --- /dev/null +++ b/src/Style.h @@ -0,0 +1,153 @@ +#ifndef _STYLE_H_ +#define _STYLE_H_ + +#include +#include "Font.h" + +#pragma pack(push, 1) + +struct ElementAlignment +{ + enum Type + { + Left = 0, + Center = 1, + Right = 2 + }; +}; + +struct StyleOverrideMask +{ + union + { + struct + { + FontStyle::Type fontStyle : 4; + bool fontSize : 1; + bool fontSizeDelta : 1; + bool alignment : 1; + bool fontColour : 1; + }; + uint8_t mask; + }; + + void Clear() + { + mask = 0; + } +}; + +struct ElementStyle +{ + FontStyle::Type fontStyle : 8; + int fontSize : 8; + ElementAlignment::Type alignment : 8; + uint8_t fontColour; +}; + +struct ElementStyleOverride +{ + StyleOverrideMask overrideMask; // Mask of which style settings are overriden + ElementStyle styleSettings; // The style settings + + ElementStyleOverride() + { + overrideMask.Clear(); + } + + inline void SetFontStyle(FontStyle::Type fontStyle) + { + overrideMask.fontStyle = fontStyle; + styleSettings.fontStyle = fontStyle; + } + + inline void SetFontSize(int fontSize) + { + overrideMask.fontSize = true; + styleSettings.fontSize = fontSize; + } + + inline void SetFontSizeDelta(int delta) + { + overrideMask.fontSizeDelta = true; + styleSettings.fontSize = delta; + } + + inline void SetAlignment(ElementAlignment::Type alignment) + { + overrideMask.alignment = true; + styleSettings.alignment = alignment; + } + + inline void SetFontColour(uint8_t colour) + { + overrideMask.fontColour = true; + styleSettings.fontColour = colour; + } + + inline void Apply(ElementStyle& style) + { + if (overrideMask.fontStyle) + { + style.fontStyle = (FontStyle::Type)((style.fontStyle & (~overrideMask.fontStyle)) | styleSettings.fontStyle); + } + if (overrideMask.alignment) + { + style.alignment = styleSettings.alignment; + } + if (overrideMask.fontSize) + { + style.fontSize = styleSettings.fontSize; + } + if (overrideMask.fontSizeDelta) + { + style.fontSize += styleSettings.fontSize; + } + if (overrideMask.fontColour) + { + style.fontColour = styleSettings.fontColour; + } + } +}; + +#define MAX_STYLE_POOL_CHUNKS 6 + +#define STYLE_POOL_CHUNK_SHIFT 6 +#define STYLE_POOL_CHUNK_SIZE (1 << STYLE_POOL_CHUNK_SHIFT) +#define STYLE_POOL_INDEX_MASK (STYLE_POOL_CHUNK_SIZE - 1) + +#define MAX_STYLES (MAX_STYLE_POOL_CHUNKS * STYLE_POOL_CHUNK_SIZE) + +typedef uint16_t ElementStyleHandle; + +class StylePool +{ +public: + StylePool() : numItems(0) + { + } + + ElementStyleHandle AddStyle(const ElementStyle& style); + const ElementStyle& GetStyle(ElementStyleHandle handle); + + void Init(); + void MarkInterfaceStylesComplete() { numInterfaceStyles = numItems; } + + void Reset() { numItems = numInterfaceStyles; } + + static StylePool& Get(); + +private: + struct PoolChunk + { + ElementStyle items[STYLE_POOL_CHUNK_SIZE]; + }; + + PoolChunk* chunks[MAX_STYLE_POOL_CHUNKS]; + int numItems; + int numInterfaceStyles; +}; + +#pragma pack(pop) + +#endif diff --git a/src/Tags.cpp b/src/Tags.cpp index 8bf3aa2..355d104 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -19,17 +19,35 @@ #include "Parser.h" #include "Page.h" #include "Platform.h" -#include "Image.h" +#include "App.h" +#include "Image/Image.h" +#include "DataPack.h" +#include "Memory/Memory.h" + +#include "Nodes/Section.h" +#include "Nodes/ImgNode.h" +#include "Nodes/Break.h" +#include "Nodes/StyNode.h" +#include "Nodes/LinkNode.h" +#include "Nodes/Block.h" +#include "Nodes/Button.h" +#include "Nodes/Field.h" +#include "Nodes/Form.h" +#include "Nodes/Table.h" +#include "Nodes/Select.h" +#include "Nodes/ListItem.h" +#include "Nodes/Text.h" +#include "Nodes/CheckBox.h" static const HTMLTagHandler* tagHandlers[] = { new HTMLTagHandler("generic"), - new SectionTagHandler("html", HTMLParseSection::Document), - new SectionTagHandler("head", HTMLParseSection::Head), - new SectionTagHandler("body", HTMLParseSection::Body), - new SectionTagHandler("script", HTMLParseSection::Script), - new SectionTagHandler("style", HTMLParseSection::Style), - new SectionTagHandler("title", HTMLParseSection::Title), + new SectionTagHandler("html", SectionElement::HTML), + new SectionTagHandler("head", SectionElement::Head), + new SectionTagHandler("body", SectionElement::Body), + new SectionTagHandler("script", SectionElement::Script), + new SectionTagHandler("style", SectionElement::Style), + new SectionTagHandler("title", SectionElement::Title), new HTagHandler("h1", 1), new HTagHandler("h2", 2), new HTagHandler("h3", 3), @@ -42,10 +60,8 @@ static const HTMLTagHandler* tagHandlers[] = new BlockTagHandler("div", false), new BlockTagHandler("dt", false), new BlockTagHandler("dd", false, 16), - new BlockTagHandler("tr", false), // Table rows shouldn't really be a block but we don't have table support yet - new BlockTagHandler("ul", true, 16), new BrTagHandler(), - new CenterTagHandler(), + new AlignmentTagHandler("center", ElementAlignment::Center), new FontTagHandler(), new StyleTagHandler("b", FontStyle::Bold), new StyleTagHandler("strong", FontStyle::Bold), @@ -62,12 +78,19 @@ static const HTMLTagHandler* tagHandlers[] = new LiTagHandler(), new HrTagHandler(), new SizeTagHandler("small", 0), - new InputTagHandler(), + new InputTagHandler("input"), + new InputTagHandler("textarea"), new ButtonTagHandler(), new FormTagHandler(), new ImgTagHandler(), new MetaTagHandler(), new PreformattedTagHandler("pre"), + new TableTagHandler(), + new TableRowTagHandler(), + new TableCellTagHandler("td", false), + new TableCellTagHandler("th", true), + new SelectTagHandler(), + new OptionTagHandler(), NULL }; @@ -91,221 +114,220 @@ const HTMLTagHandler* DetermineTag(const char* str) return &genericTag; } -void HTMLTagHandler::ApplyStyleAttributes(WidgetStyle& style, char* attributeStr) const -{ - AttributeParser attributes(attributeStr); - - while (attributes.Parse()) - { - if (!stricmp(attributes.Key(), "align")) - { - if (!stricmp(attributes.Value(), "center")) - { - style.center = true; - } - else if (!stricmp(attributes.Value(), "left")) - { - style.center = false; - } - } - } -} - void HrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.page.AddHorizontalRule(); + int padding = Assets.GetFont(1, FontStyle::Bold)->glyphHeight; + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding, true)); } void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.page.BreakTextLine(); + int fontSize = parser.CurrentContext().node->GetStyle().fontSize; + int padding = Assets.GetFont(fontSize, FontStyle::Regular)->glyphHeight; + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding, false, true)); } void HTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - currentStyle.fontSize = size >= 3 ? 1 : 2; - currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Bold); - ApplyStyleAttributes(currentStyle, attributeStr); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); - parser.page.PushStyle(currentStyle); + int fontSize = size >= 3 ? 1 : 2; + int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding)); + parser.PushContext(StyleNode::ConstructFontStyle(MemoryManager::pageAllocator, FontStyle::Bold, fontSize), this); } void HTagHandler::Close(class HTMLParser& parser) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); - parser.page.PopStyle(); + int fontSize = size >= 3 ? 1 : 2; + int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; + parser.PopContext(this); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding)); } void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - currentStyle.fontSize = size; - parser.page.PushStyle(currentStyle); + parser.PushContext(StyleNode::ConstructFontSize(MemoryManager::pageAllocator, size), this); } void SizeTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); + parser.PopContext(this); } void LiTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - int bulletPointWidth = Platform::video->GetFont(currentStyle.fontSize, currentStyle.fontStyle)->CalculateWidth(" * ", currentStyle.fontStyle); - - parser.page.BreakLine(); - parser.page.AddBulletPoint(); - parser.page.AdjustLeftMargin(bulletPointWidth); + if (parser.CurrentContext().tag == this) + { + // Have to do this because sometimes people dont close their
  • tag and just + // treat them as bullet point markers + parser.PopContext(this); + } + parser.PushContext(ListItemNode::Construct(MemoryManager::pageAllocator), this); } void LiTagHandler::Close(class HTMLParser& parser) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - int bulletPointWidth = Platform::video->GetFont(currentStyle.fontSize, currentStyle.fontStyle)->CalculateWidth(" * ", currentStyle.fontStyle); - - parser.page.AdjustLeftMargin(-bulletPointWidth); - parser.page.BreakLine(); + parser.PopContext(this); } void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const { AttributeParser attributes(attributeStr); + char* url = NULL; + while(attributes.Parse()) { if (!stricmp(attributes.Key(), "href")) { - parser.page.SetWidgetURL(attributes.Value()); + url = MemoryManager::pageAllocator.AllocString(attributes.Value()); } } - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Underline); - parser.page.PushStyle(currentStyle); - + parser.PushContext(LinkNode::Construct(MemoryManager::pageAllocator, url), this); } + void ATagHandler::Close(class HTMLParser& parser) const { - parser.page.ClearWidgetURL(); - parser.page.PopStyle(); + parser.PopContext(this); } void BlockTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.AdjustLeftMargin(leftMarginPadding); - parser.page.BreakLine(useVerticalPadding ? Platform::video->GetLineHeight(currentStyle.fontSize) >> 1 : 0); - ApplyStyleAttributes(currentStyle, attributeStr); - parser.page.PushStyle(currentStyle); + // TODO-refactor + int lineHeight = Assets.GetFont(1, FontStyle::Regular)->glyphHeight; + parser.PushContext(BlockNode::Construct(MemoryManager::pageAllocator, leftMarginPadding, useVerticalPadding ? lineHeight >> 1 : 0), this); } void BlockTagHandler::Close(class HTMLParser& parser) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.AdjustLeftMargin(-leftMarginPadding); - parser.page.BreakLine(useVerticalPadding ? Platform::video->GetLineHeight(currentStyle.fontSize) >> 1 : 0); - parser.page.PopStyle(); + parser.PopContext(this); } void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushSection(section); + Node* node = SectionElement::Construct(MemoryManager::pageAllocator, sectionType); + parser.PushContext(node, this); + + if (sectionType == SectionElement::Body) + { + AttributeParser attributes(attributeStr); + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "link")) + { + parser.page.colourScheme.linkColour = HTMLParser::ParseColourCode(attributes.Value()); + } + if (!stricmp(attributes.Key(), "text")) + { + parser.page.colourScheme.textColour = HTMLParser::ParseColourCode(attributes.Value()); + } + if (!stricmp(attributes.Key(), "bgcolor")) + { + parser.page.colourScheme.pageColour = HTMLParser::ParseColourCode(attributes.Value()); + App::Get().pageRenderer.RefreshAll(); + } + } + } + + if (node) + { + ElementStyle style = node->GetStyle(); + style.fontColour = parser.page.colourScheme.textColour; + node->SetStyle(style); + } } void SectionTagHandler::Close(class HTMLParser& parser) const { - parser.PopSection(section); + parser.PopContext(this); } void StyleTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | style); - parser.page.PushStyle(currentStyle); + Node* styleNode = StyleNode::ConstructFontStyle(MemoryManager::pageAllocator, style); + if (styleNode) + { + parser.PushContext(styleNode, this); + } } + void StyleTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); + parser.PopContext(this); } -void CenterTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +void AlignmentTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - - parser.page.BreakLine(); - - currentStyle.center = true; - parser.page.PushStyle(currentStyle); + parser.PushContext(StyleNode::ConstructAlignment(MemoryManager::pageAllocator, alignmentType), this); } -void CenterTagHandler::Close(class HTMLParser& parser) const +void AlignmentTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); - parser.page.BreakLine(); + parser.PopContext(this); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - - AttributeParser attributes(attributeStr); - while (attributes.Parse()) + Node* styleNode = StyleNode::Construct(MemoryManager::pageAllocator); + if (styleNode) { - if (!stricmp(attributes.Key(), "size")) - { - int size = atoi(attributes.Value()); + StyleNode::Data* data = static_cast(styleNode->data); - if (size < 0) + AttributeParser attributes(attributeStr); + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "size")) { - // Relative sizing - if (currentStyle.fontSize + size < 0) + int fontSize = atoi(attributes.Value()); + + if (attributes.Value()[0] == '+' || attributes.Value()[0] == '-') { - currentStyle.fontSize = 0; + data->styleOverride.SetFontSizeDelta(fontSize); } else { - currentStyle.fontSize += size; + switch (fontSize) + { + case 1: + case 2: + data->styleOverride.SetFontSize(0); + break; + case 0: + // Probably invalid + case 3: + case 4: + data->styleOverride.SetFontSize(1); + break; + default: + // Anything bigger + data->styleOverride.SetFontSize(2); + break; + } } } - else + else if (!stricmp(attributes.Key(), "color")) { - switch (size) + if (Platform::video->paletteLUT) { - case 1: - case 2: - currentStyle.fontSize = 0; - break; - case 0: - // Probably invalid - case 3: - case 4: - currentStyle.fontSize = 1; - break; - default: - // Anything bigger - currentStyle.fontSize = 2; - break; + data->styleOverride.SetFontColour(HTMLParser::ParseColourCode(attributes.Value())); } } } - } - parser.page.PushStyle(currentStyle); + parser.PushContext(styleNode, this); + } } void FontTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); + parser.PopContext(this); } void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + parser.PushContext(ListNode::Construct(MemoryManager::pageAllocator), this); } void ListTagHandler::Close(class HTMLParser& parser) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + parser.PopContext(this); } struct HTMLInputTag @@ -314,42 +336,43 @@ struct HTMLInputTag { Unknown, Submit, - Text + Text, + CheckBox, + Radio, + Password }; }; void ButtonTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - char* title = NULL; - AttributeParser attributes(attributeStr); while (attributes.Parse()) { - if (!stricmp(attributes.Key(), "title")) - { - title = parser.page.allocator.AllocString(attributes.Value()); - } } - if (title) - { - parser.page.AddButton(title); - } + parser.PushContext(ButtonNode::Construct(MemoryManager::pageAllocator, NULL, FormNode::OnSubmitButtonPressed), this); +} + +void ButtonTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); } void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { char* value = NULL; char* name = NULL; + bool checked = false; HTMLInputTag::Type type = HTMLInputTag::Text; int bufferLength = 80; + ExplicitDimension width; AttributeParser attributes(attributeStr); while (attributes.Parse()) { if (!stricmp(attributes.Key(), "type")) { - if (!stricmp(attributes.Value(), "submit")) + if (!stricmp(attributes.Value(), "submit") || !stricmp(attributes.Value(), "button")) { type = HTMLInputTag::Submit; } @@ -357,6 +380,18 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { type = HTMLInputTag::Text; } + else if (!stricmp(attributes.Value(), "password")) + { + type = HTMLInputTag::Password; + } + else if (!stricmp(attributes.Value(), "checkbox")) + { + type = HTMLInputTag::CheckBox; + } + else if (!stricmp(attributes.Value(), "radio")) + { + type = HTMLInputTag::Radio; + } else { type = HTMLInputTag::Unknown; @@ -364,11 +399,19 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } if (!stricmp(attributes.Key(), "value")) { - value = parser.page.allocator.AllocString(attributes.Value()); + value = MemoryManager::pageAllocator.AllocString(attributes.Value()); } if (!stricmp(attributes.Key(), "name")) { - name = parser.page.allocator.AllocString(attributes.Value()); + name = MemoryManager::pageAllocator.AllocString(attributes.Value()); + } + if (!stricmp(attributes.Key(), "width")) + { + width = ExplicitDimension::Parse(attributes.Value()); + } + if (!stricmp(attributes.Key(), "checked")) + { + checked = true; } } @@ -377,104 +420,108 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const case HTMLInputTag::Submit: if (value) { - parser.page.AddButton(value); + parser.EmitNode(ButtonNode::Construct(MemoryManager::pageAllocator, value, FormNode::OnSubmitButtonPressed)); } break; case HTMLInputTag::Text: - parser.page.AddTextField(value, bufferLength, name); + case HTMLInputTag::Password: + { + Node* fieldNode = TextFieldNode::Construct(MemoryManager::pageAllocator, value, FormNode::OnSubmitButtonPressed); + if (fieldNode && fieldNode->data) + { + TextFieldNode::Data* fieldData = static_cast(fieldNode->data); + fieldData->name = name; + fieldData->explicitWidth = width; + fieldData->isPassword = type == HTMLInputTag::Password; + parser.EmitNode(fieldNode); + } + } break; + case HTMLInputTag::CheckBox: + case HTMLInputTag::Radio: + { + Node* fieldNode = CheckBoxNode::Construct(MemoryManager::pageAllocator, name, value, type == HTMLInputTag::Radio, checked); + if (fieldNode) + { + parser.EmitNode(fieldNode); + } + } + break; } } void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetFormData* formData = parser.page.allocator.Alloc(); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + + Node* formNode = FormNode::Construct(MemoryManager::pageAllocator); + parser.PushContext(formNode, this); + + FormNode::Data* formData = static_cast(formNode->data); + if (formData) { - formData->action = NULL; - formData->method = WidgetFormData::Get; - AttributeParser attributes(attributeStr); while (attributes.Parse()) { if (!stricmp(attributes.Key(), "action")) { - formData->action = parser.page.allocator.AllocString(attributes.Value()); + formData->action = MemoryManager::pageAllocator.AllocString(attributes.Value()); } if (!stricmp(attributes.Key(), "method")) { if (!stricmp(attributes.Value(), "post")) { - formData->method = WidgetFormData::Post; + formData->method = FormNode::Data::Post; } } } - - parser.page.SetFormData(formData); } } void FormTagHandler::Close(HTMLParser& parser) const { - parser.page.SetFormData(NULL); + parser.PopContext(this); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - AttributeParser attributes(attributeStr); - int width = -1; - int height = -1; - char* altText = NULL; - - while (attributes.Parse()) - { - if (!stricmp(attributes.Key(), "alt")) - { - altText = (char*) attributes.Value(); - } - if (!stricmp(attributes.Key(), "width")) - { - width = atoi(attributes.Value()); - } - if (!stricmp(attributes.Key(), "height")) - { - height = atoi(attributes.Value()); - } - } - - if (width == -1) + Node* imageNode = ImageNode::Construct(MemoryManager::pageAllocator); + if (imageNode) { - if (height != -1) + ImageNode::Data* data = static_cast(imageNode->data); + if (data) { - width = height; - } - } - if (height == -1) - { - if (width != -1) - { - height = width; - } - } + AttributeParser attributes(attributeStr); - if (width == -1 && height == -1) - { - Image* imageIcon = Platform::video->imageIcon; - if (imageIcon) - { - parser.page.AddImage(NULL, imageIcon->width, imageIcon->height); - } + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "alt")) + { + data->altText = MemoryManager::pageAllocator.AllocString(attributes.Value()); + if (data->altText) + { + HTMLParser::ReplaceAmpersandEscapeSequences(data->altText); + } + } + else if (!stricmp(attributes.Key(), "src")) + { + data->source = MemoryManager::pageAllocator.AllocString(attributes.Value()); + } + else if (!stricmp(attributes.Key(), "width")) + { + data->explicitWidth = ExplicitDimension::Parse(attributes.Value()); + } + else if (!stricmp(attributes.Key(), "height")) + { + data->explicitHeight = ExplicitDimension::Parse(attributes.Value()); + } + } - if (altText) - { - parser.page.AppendText(altText); + parser.EmitNode(imageNode); } } - else - { - Platform::video->ScaleImageDimensions(width, height); - parser.page.AddImage(altText, width, height); - } } void MetaTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -518,20 +565,162 @@ void MetaTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void PreformattedTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize, currentStyle.fontStyle) >> 1); - - currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Monospace); - parser.page.PushStyle(currentStyle); + // TODO-refactor parser.PushPreFormatted(); - parser.page.BreakTextLine(); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + + int lineHeight = Assets.GetFont(1, FontStyle::Regular)->glyphHeight; + int padding = lineHeight >> 1; + parser.PushContext(BlockNode::Construct(MemoryManager::pageAllocator, padding, padding), this); + parser.PushContext(StyleNode::ConstructFontStyle(MemoryManager::pageAllocator, FontStyle::Monospace), this); } void PreformattedTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); + // TODO-refactor parser.PopPreFormatted(); + parser.PopContext(this); + parser.PopContext(this); +// parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); +} + +void TableTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + Node* tableNode = TableNode::Construct(MemoryManager::pageAllocator); + if (tableNode) + { + TableNode::Data* tableNodeData = static_cast(tableNode->data); + + AttributeParser attributes(attributeStr); + + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "border")) + { + tableNodeData->border = attributes.ValueAsInt(); + } + else if (!stricmp(attributes.Key(), "cellpadding")) + { + tableNodeData->cellPadding = attributes.ValueAsInt(); + } + else if (!stricmp(attributes.Key(), "cellSpacing")) + { + tableNodeData->cellSpacing = attributes.ValueAsInt(); + } + else if (!stricmp(attributes.Key(), "bgcolor")) + { + tableNodeData->bgColour = HTMLParser::ParseColourCode(attributes.Value()); + } + else if (!stricmp(attributes.Key(), "width")) + { + tableNodeData->explicitWidth = ExplicitDimension::Parse(attributes.Value()); + } + } + + parser.PushContext(tableNode, this); + } +} + +void TableTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} + +void TableRowTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + parser.PushContext(TableRowNode::Construct(MemoryManager::pageAllocator), this); +} + +void TableRowTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} + +void TableCellTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + Node* cellNode = TableCellNode::Construct(MemoryManager::pageAllocator, isHeader); + if (cellNode) + { + TableCellNode::Data* cellData = static_cast(cellNode->data); - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize, currentStyle.fontStyle) >> 1); + AttributeParser attributes(attributeStr); + + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "bgcolor")) + { + cellData->bgColour = HTMLParser::ParseColourCode(attributes.Value()); + } + else if (!stricmp(attributes.Key(), "colspan")) + { + cellData->columnSpan = attributes.ValueAsInt(); + if (cellData->columnSpan <= 0) + { + cellData->columnSpan = 1; + } + } + else if (!stricmp(attributes.Key(), "width")) + { + cellData->explicitWidth = ExplicitDimension::Parse(attributes.Value()); + } + } + parser.PushContext(cellNode, this); + } +} + +void TableCellTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} + +void SelectTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + const char* name = nullptr; + AttributeParser attributes(attributeStr); + + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "name")) + { + name = attributes.Value(); + } + } + + parser.PushContext(SelectNode::Construct(MemoryManager::pageAllocator, name), this); +} + +void SelectTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} + +void OptionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + Node* optionNode = OptionNode::Construct(MemoryManager::pageAllocator); + + if (optionNode) + { + parser.PushContext(optionNode, this); + + AttributeParser attributes(attributeStr); + + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "selected")) + { + SelectNode::Data* selectData = optionNode->FindParentDataOfType(Node::Select); + OptionNode::Data* optionData = static_cast(optionNode->data); + if (selectData && optionData) + { + selectData->selected = optionData; + } + } + } + } +} + +void OptionTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); } + diff --git a/src/Tags.h b/src/Tags.h index 244f9af..4574e5f 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -12,10 +12,13 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _TAGS_H_ +#define _TAGS_H_ #include "Parser.h" #include "Font.h" +#include "Nodes/Section.h" +#include "Style.h" class HTMLParser; @@ -27,26 +30,24 @@ class HTMLTagHandler virtual void Close(class HTMLParser& parser) const {} const char* name; - -protected: - void ApplyStyleAttributes(struct WidgetStyle& style, char* attributeStr) const; }; class SectionTagHandler : public HTMLTagHandler { public: - SectionTagHandler(const char* inName, HTMLParseSection::Type inSection) : HTMLTagHandler(inName), section(inSection) {} + SectionTagHandler(const char* inName, SectionElement::Type inSectionType) : HTMLTagHandler(inName), sectionType(inSectionType) {} virtual void Open(class HTMLParser& parser, char* attributeStr) const; virtual void Close(class HTMLParser& parser) const; - const HTMLParseSection::Type section; + const SectionElement::Type sectionType; }; -class CenterTagHandler : public HTMLTagHandler +class AlignmentTagHandler : public HTMLTagHandler { public: - CenterTagHandler() : HTMLTagHandler("center") {} + AlignmentTagHandler(const char* inName, ElementAlignment::Type inAlignmentType) : HTMLTagHandler(inName), alignmentType(inAlignmentType) {} virtual void Open(class HTMLParser& parser, char* attributeStr) const; virtual void Close(class HTMLParser& parser) const; + const ElementAlignment::Type alignmentType; }; class PreformattedTagHandler : public HTMLTagHandler @@ -144,7 +145,7 @@ class BlockTagHandler : public HTMLTagHandler class InputTagHandler : public HTMLTagHandler { public: - InputTagHandler() : HTMLTagHandler("input") {} + InputTagHandler(const char* tagName) : HTMLTagHandler(tagName) {} virtual void Open(class HTMLParser& parser, char* attributeStr) const; }; @@ -175,6 +176,51 @@ class ButtonTagHandler : public HTMLTagHandler public: ButtonTagHandler() : HTMLTagHandler("button") {} virtual void Open(class HTMLParser& parser, char* attributeStr) const; + virtual void Close(class HTMLParser& parser) const; +}; + +class TableTagHandler : public HTMLTagHandler +{ +public: + TableTagHandler() : HTMLTagHandler("table") {} + virtual void Open(class HTMLParser& parser, char* attributeStr) const; + virtual void Close(class HTMLParser& parser) const; +}; + +class TableRowTagHandler : public HTMLTagHandler +{ +public: + TableRowTagHandler() : HTMLTagHandler("tr") {} + virtual void Open(class HTMLParser& parser, char* attributeStr) const; + virtual void Close(class HTMLParser& parser) const; +}; + +class TableCellTagHandler : public HTMLTagHandler +{ +public: + TableCellTagHandler(const char* name, bool inIsHeader) : HTMLTagHandler(name), isHeader(inIsHeader) {} + virtual void Open(class HTMLParser& parser, char* attributeStr) const; + virtual void Close(class HTMLParser& parser) const; +private: + bool isHeader; +}; + +class SelectTagHandler : public HTMLTagHandler +{ +public: + SelectTagHandler() : HTMLTagHandler("select") {} + virtual void Open(class HTMLParser& parser, char* attributeStr) const; + virtual void Close(class HTMLParser& parser) const; +}; + +class OptionTagHandler : public HTMLTagHandler +{ +public: + OptionTagHandler() : HTMLTagHandler("option") {} + virtual void Open(class HTMLParser& parser, char* attributeStr) const; + virtual void Close(class HTMLParser& parser) const; }; const HTMLTagHandler* DetermineTag(const char* str); + +#endif diff --git a/src/URL.h b/src/URL.h index 0ecf41a..9ab41ab 100644 --- a/src/URL.h +++ b/src/URL.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _URL_H_ +#define _URL_H_ #include #include @@ -44,16 +45,53 @@ struct URL return *this; } - static void ProcessEscapeCodes(URL& url) + void CleanUp() { - char* match; + // Replace back slashes with forward ones + for (char* ptr = url; *ptr; ptr++) + { + if (*ptr == '\\') + *ptr = '/'; + } - while (match = strstr(url.url, "/./")) + // Collapse /./ and /../ + char* directory; + while (directory = strstr(url, "/./")) { - memmove(match, match + 2, strlen(match) - 1); + strcpy(directory, directory + 2); + } + directory = strstr(url, "/../"); + char* protocolEnd = strstr(url, "://"); + if (protocolEnd) + protocolEnd += 3; + + while (directory && directory > url) + { + char* prevSlash = directory - 1; + while (prevSlash > url && *prevSlash != '/' && prevSlash > protocolEnd) + { + prevSlash--; + } + if (*prevSlash == '/' && prevSlash > url) + { + strcpy(prevSlash, directory + 3); + directory = strstr(prevSlash, "/../"); + } + else + { + // At most top level, collapse remaining /../ instances + while (directory) + { + strcpy(directory, directory + 3); + directory = strstr(url, "/../"); + } + break; + } } - while (match = strstr(url.url, "&")) + // Fix & escape sequences + char* match; + while (match = strstr(url, "&")) { memmove(match + 1, match + 5, strlen(match + 1) - 3); } @@ -76,6 +114,14 @@ struct URL } } + // Starting with // should be treated as an absolte URL but we will prepend with http:// + if (strstr(relativeURL, "//") == relativeURL) + { + strcpy(result.url, "http:"); + strcpy(result.url + 5, relativeURL); + return result; + } + // Check if this is a hash link if (relativeURL[0] == '#') { @@ -133,9 +179,24 @@ struct URL // Skip leading '/' on relative URL - while (*relativeURL == '/') + if (*relativeURL == '/') { - relativeURL++; + while (*relativeURL == '/') + { + relativeURL++; + } + + // Move lastSlashPos to the first slash position + const char* domainPos = strstr(baseURL, "://"); + if (domainPos) + { + domainPos += 3; + const char* firstSlashPos = strchr(domainPos, '/'); + if (firstSlashPos) + { + lastSlashPos = firstSlashPos; + } + } } if (lastSlashPos) @@ -159,10 +220,12 @@ struct URL strcpy(result.url + 7, relativeURL); } - ProcessEscapeCodes(result); + result.CleanUp(); return result; } char url[MAX_URL_LENGTH]; }; + +#endif diff --git a/src/Unicode.inc b/src/Unicode.inc index d4263f5..68029be 100644 --- a/src/Unicode.inc +++ b/src/Unicode.inc @@ -39,107 +39,107 @@ TextEncodingPage UTF8_Latin1Supplement = "", // U+009E Private Message PM "", // U+009F Application Program Command APC // Latin-1 Punctuation and Symbols - " ", // U+00A0 Non-breaking space NBSP - "", // U+00A1 ¡ Inverted exclamation mark - "c", // U+00A2 ¢ Cent sign - "$", // U+00A3 £ Pound sign - "", // U+00A4 ¤ Currency sign - "Y", // U+00A5 ¥ Yen sign - "|", // U+00A6 ¦ Broken bar - "", // U+00A7 § Section sign - "\"", // U+00A8 ¨ Diaeresis - "(C)", // U+00A9 © Copyright sign - "", // U+00AA ª Feminine Ordinal Indicator - "<<", // U+00AB « Left-pointing double angle quotation mark - "", // U+00AC ¬ Not sign - "", // U+00AD Soft hyphen SHY - "(R)", // U+00AE ® Registered sign - "'", // U+00AF ¯ Macron - "", // U+00B0 ° Degree symbol - "+-", // U+00B1 ± Plus-minus sign - "", // U+00B2 ² Superscript two - "", // U+00B3 ³ Superscript three - "", // U+00B4 ´ Acute accent - "", // U+00B5 µ Micro sign - "", // U+00B6 ¶ Pilcrow sign - "", // U+00B7 · Middle dot - ".", // U+00B8 ¸ Cedilla - "", // U+00B9 ¹ Superscript one - "", // U+00BA º Masculine ordinal indicator - ">>", // U+00BB » Right-pointing double-angle quotation mark - "1/4", // U+00BC ¼ Vulgar fraction one quarter - "1/2", // U+00BD ½ Vulgar fraction one half - "3/4", // U+00BE ¾ Vulgar fraction three quarters - "", // U+00BF ¿ Inverted question mark + "\xA0", // U+00A0 Non-breaking space NBSP + "\xA1", // U+00A1 ¡ Inverted exclamation mark + "\xA2", // U+00A2 ¢ Cent sign + "\xA3", // U+00A3 £ Pound sign + "\xA4", // U+00A4 ¤ Currency sign + "\xA5", // U+00A5 ¥ Yen sign + "\xA6", // U+00A6 ¦ Broken bar + "\xA7", // U+00A7 § Section sign + "\xA8", // U+00A8 ¨ Diaeresis + "\xA9", // U+00A9 © Copyright sign + "\xAA", // U+00AA ª Feminine Ordinal Indicator + "\xAB", // U+00AB « Left-pointing double angle quotation mark + "\xAC", // U+00AC ¬ Not sign + "\xAD", // U+00AD Soft hyphen SHY + "\xAE", // U+00AE ® Registered sign + "\xAF", // U+00AF ¯ Macron + "\xB0", // U+00B0 ° Degree symbol + "\xB1", // U+00B1 ± Plus-minus sign + "\xB2", // U+00B2 ² Superscript two + "\xB3", // U+00B3 ³ Superscript three + "\xB4", // U+00B4 ´ Acute accent + "\xB5", // U+00B5 µ Micro sign + "\xB6", // U+00B6 ¶ Pilcrow sign + "\xB7", // U+00B7 · Middle dot + "\xB8", // U+00B8 ¸ Cedilla + "\xB9", // U+00B9 ¹ Superscript one + "\xBA", // U+00BA º Masculine ordinal indicator + "\xBB", // U+00BB » Right-pointing double-angle quotation mark + "\xBC", // U+00BC ¼ Vulgar fraction one quarter + "\xBD", // U+00BD ½ Vulgar fraction one half + "\xBE", // U+00BE ¾ Vulgar fraction three quarters + "\xBF", // U+00BF ¿ Inverted question mark // Letters - "A", // U+00C0 À Latin Capital Letter A with grave - "A", // U+00C1 Á Latin Capital letter A with acute - "A", // U+00C2 Â Latin Capital letter A with circumflex - "A", // U+00C3 Ã Latin Capital letter A with tilde - "A", // U+00C4 Ä Latin Capital letter A with diaeresis - "A", // U+00C5 Å Latin Capital letter A with ring above - "AE", // U+00C6 Æ Latin Capital letter AE - "C", // U+00C7 Ç Latin Capital letter C with cedilla - "E", // U+00C8 È Latin Capital letter E with grave - "E", // U+00C9 É Latin Capital letter E with acute - "E", // U+00CA Ê Latin Capital letter E with circumflex - "E", // U+00CB Ë Latin Capital letter E with diaeresis - "I", // U+00CC Ì Latin Capital letter I with grave - "I", // U+00CD Í Latin Capital letter I with acute - "I", // U+00CE Î Latin Capital letter I with circumflex - "I", // U+00CF Ï Latin Capital letter I with diaeresis - "D", // U+00D0 Ð Latin Capital letter Eth - "N", // U+00D1 Ñ Latin Capital letter N with tilde - "O", // U+00D2 Ò Latin Capital letter O with grave - "O", // U+00D3 Ó Latin Capital letter O with acute - "O", // U+00D4 Ô Latin Capital letter O with circumflex - "O", // U+00D5 Õ Latin Capital letter O with tilde - "O", // U+00D6 Ö Latin Capital letter O with diaeresis + "\xC0", // U+00C0 À Latin Capital Letter A with grave + "\xC1", // U+00C1 Á Latin Capital letter A with acute + "\xC2", // U+00C2 Â Latin Capital letter A with circumflex + "\xC3", // U+00C3 Ã Latin Capital letter A with tilde + "\xC4", // U+00C4 Ä Latin Capital letter A with diaeresis + "\xC5", // U+00C5 Å Latin Capital letter A with ring above + "\xC6", // U+00C6 Æ Latin Capital letter AE + "\xC7", // U+00C7 Ç Latin Capital letter C with cedilla + "\xC8", // U+00C8 È Latin Capital letter E with grave + "\xC9", // U+00C9 É Latin Capital letter E with acute + "\xCA", // U+00CA Ê Latin Capital letter E with circumflex + "\xCB", // U+00CB Ë Latin Capital letter E with diaeresis + "\xCC", // U+00CC Ì Latin Capital letter I with grave + "\xCD", // U+00CD Í Latin Capital letter I with acute + "\xCE", // U+00CE Î Latin Capital letter I with circumflex + "\xCF", // U+00CF Ï Latin Capital letter I with diaeresis + "\xD0", // U+00D0 Ð Latin Capital letter Eth + "\xD1", // U+00D1 Ñ Latin Capital letter N with tilde + "\xD2", // U+00D2 Ò Latin Capital letter O with grave + "\xD3", // U+00D3 Ó Latin Capital letter O with acute + "\xD4", // U+00D4 Ô Latin Capital letter O with circumflex + "\xD5", // U+00D5 Õ Latin Capital letter O with tilde + "\xD6", // U+00D6 Ö Latin Capital letter O with diaeresis // Mathematical operator - "x", // U+00D7 × Multiplication sign + "\xD7", // U+00D7 × Multiplication sign // Letters - "O", // U+00D8 Ø Latin Capital letter O with stroke - "U", // U+00D9 Ù Latin Capital letter U with grave - "U", // U+00DA Ú Latin Capital letter U with acute - "U", // U+00DB Û Latin Capital Letter U with circumflex - "U", // U+00DC Ü Latin Capital Letter U with diaeresis - "Y", // U+00DD Ý Latin Capital Letter Y with acute - "", // U+00DE Þ Latin Capital Letter Thorn - "", // U+00DF ß Latin Small Letter sharp S - "a", // U+00E0 à Latin Small Letter A with grave - "a", // U+00E1 á Latin Small Letter A with acute - "a", // U+00E2 â Latin Small Letter A with circumflex - "a", // U+00E3 ã Latin Small Letter A with tilde - "a", // U+00E4 ä Latin Small Letter A with diaeresis - "a", // U+00E5 å Latin Small Letter A with ring above - "ae", // U+00E6 æ Latin Small Letter AE - "c", // U+00E7 ç Latin Small Letter C with cedilla - "e", // U+00E8 è Latin Small Letter E with grave - "e", // U+00E9 é Latin Small Letter E with acute - "e", // U+00EA ê Latin Small Letter E with circumflex - "e", // U+00EB ë Latin Small Letter E with diaeresis - "i", // U+00EC ì Latin Small Letter I with grave - "i", // U+00ED í Latin Small Letter I with acute - "i", // U+00EE î Latin Small Letter I with circumflex - "i", // U+00EF ï Latin Small Letter I with diaeresis - "o", // U+00F0 ð Latin Small Letter Eth - "n", // U+00F1 ñ Latin Small Letter N with tilde - "o", // U+00F2 ò Latin Small Letter O with grave - "o", // U+00F3 ó Latin Small Letter O with acute - "o", // U+00F4 ô Latin Small Letter O with circumflex - "o", // U+00F5 õ Latin Small Letter O with tilde - "o", // U+00F6 ö Latin Small Letter O with diaeresis + "\xD8", // U+00D8 Ø Latin Capital letter O with stroke + "\xD9", // U+00D9 Ù Latin Capital letter U with grave + "\xDA", // U+00DA Ú Latin Capital letter U with acute + "\xDB", // U+00DB Û Latin Capital Letter U with circumflex + "\xDC", // U+00DC Ü Latin Capital Letter U with diaeresis + "\xDD", // U+00DD Ý Latin Capital Letter Y with acute + "\xDE", // U+00DE Þ Latin Capital Letter Thorn + "\xDF", // U+00DF ß Latin Small Letter sharp S + "\xE0", // U+00E0 à Latin Small Letter A with grave + "\xE1", // U+00E1 á Latin Small Letter A with acute + "\xE2", // U+00E2 â Latin Small Letter A with circumflex + "\xE3", // U+00E3 ã Latin Small Letter A with tilde + "\xE4", // U+00E4 ä Latin Small Letter A with diaeresis + "\xE5", // U+00E5 å Latin Small Letter A with ring above + "\xE6", // U+00E6 æ Latin Small Letter AE + "\xE7", // U+00E7 ç Latin Small Letter C with cedilla + "\xE8", // U+00E8 è Latin Small Letter E with grave + "\xE9", // U+00E9 é Latin Small Letter E with acute + "\xEA", // U+00EA ê Latin Small Letter E with circumflex + "\xEB", // U+00EB ë Latin Small Letter E with diaeresis + "\xEC", // U+00EC ì Latin Small Letter I with grave + "\xED", // U+00ED í Latin Small Letter I with acute + "\xEE", // U+00EE î Latin Small Letter I with circumflex + "\xEF", // U+00EF ï Latin Small Letter I with diaeresis + "\xF0", // U+00F0 ð Latin Small Letter Eth + "\xF1", // U+00F1 ñ Latin Small Letter N with tilde + "\xF2", // U+00F2 ò Latin Small Letter O with grave + "\xF3", // U+00F3 ó Latin Small Letter O with acute + "\xF4", // U+00F4 ô Latin Small Letter O with circumflex + "\xF5", // U+00F5 õ Latin Small Letter O with tilde + "\xF6", // U+00F6 ö Latin Small Letter O with diaeresis // Mathematical operator - "'/.", // U+00F7 ÷ Division sign + "\xF7", // U+00F7 ÷ Division sign // Letters - "o", // U+00F8 ø Latin Small Letter O with stroke - "u", // U+00F9 ù Latin Small Letter U with grave - "u", // U+00FA ú Latin Small Letter U with acute - "u", // U+00FB û Latin Small Letter U with circumflex - "u", // U+00FC ü Latin Small Letter U with diaeresis - "y", // U+00FD ý Latin Small Letter Y with acute - "", // U+00FE þ Latin Small Letter Thorn - "y", // U+00FF ÿ Latin Small Letter Y with diaeresis + "\xF8", // U+00F8 ø Latin Small Letter O with stroke + "\xF9", // U+00F9 ù Latin Small Letter U with grave + "\xFA", // U+00FA ú Latin Small Letter U with acute + "\xFB", // U+00FB û Latin Small Letter U with circumflex + "\xFC", // U+00FC ü Latin Small Letter U with diaeresis + "\xFD", // U+00FD ý Latin Small Letter Y with acute + "\xFE", // U+00FE þ Latin Small Letter Thorn + "\xFF", // U+00FF ÿ Latin Small Letter Y with diaeresis } }; @@ -279,7 +279,7 @@ TextEncodingPage UTF8_LatinExtendedA = } }; -// Windows-1250 / ISO-8869-2 +// Windows-1250 / ISO-8859-2 TextEncodingPage ISO_8859_2_Encoding = { { @@ -304,7 +304,7 @@ TextEncodingPage ISO_8859_2_Encoding = "'", // ’ RIGHT SINGLE QUOTATION MARK (U+2019) "\"", // “ LEFT DOUBLE QUOTATION MARK (U+201C) "\"", // ” RIGHT DOUBLE QUOTATION MARK (U+201D) - "*", // • BULLET (U+2022) + "\x95", // • BULLET (U+2022) "-", // – EN DASH (U+2013) "-", // — EM DASH (U+2014) "", // UNUSED @@ -319,96 +319,96 @@ TextEncodingPage ISO_8859_2_Encoding = "^", // ˇ CARON (U+02C7) "^", // ˘ BREVE (U+02D8) "L", // Ł LATIN CAPITAL LETTER L WITH STROKE (U+0141) - "$", // ¤ CURRENCY SIGN (U+00A4) + "\xA4", // ¤ CURRENCY SIGN (U+00A4) "A", // Ą LATIN CAPITAL LETTER A WITH OGONEK (U+0104) - "|", // ¦ BROKEN BAR (U+00A6) - "S", // § SECTION SIGN (U+00A7) - "\"", // ¨ DIAERESIS (U+00A8) - "(C)", // © COPYRIGHT SIGN (U+00A9) + "\xA6", // ¦ BROKEN BAR (U+00A6) + "\xA7", // § SECTION SIGN (U+00A7) + "\xA8", // ¨ DIAERESIS (U+00A8) + "\xA9", // © COPYRIGHT SIGN (U+00A9) "S", // Ş LATIN CAPITAL LETTER S WITH CEDILLA (U+015E) - "<<", // « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00AB) - "¬", // ¬ NOT SIGN (U+00AC) - "", // SOFT HYPHEN (U+00AD) - "(R)", // ® REGISTERED SIGN (U+00AE) + "\xAB", // « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00AB) + "\xAC", // ¬ NOT SIGN (U+00AC) + "\xAD", // SOFT HYPHEN (U+00AD) + "\xAE", // ® REGISTERED SIGN (U+00AE) "Z", // Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE (U+017B) - "", // ° DEGREE SIGN (U+00B0) - "+-", // ± PLUS-MINUS SIGN (U+00B1) + "\xB0", // ° DEGREE SIGN (U+00B0) + "\xB1", // ± PLUS-MINUS SIGN (U+00B1) ",", // ˛ OGONEK (U+02DB) - "L", // ł LATIN SMALL LETTER L WITH STROKE (U+0142) - "`", // ´ ACUTE ACCENT (U+00B4) - "u", // µ MICRO SIGN (U+00B5) - "", // ¶ PILCROW SIGN (U+00B6) - ".", // · MIDDLE DOT (U+00B7) - ".", // ¸ CEDILLA (U+00B8) + "l", // ł LATIN SMALL LETTER L WITH STROKE (U+0142) + "\xB4", // ´ ACUTE ACCENT (U+00B4) + "\xB5", // µ MICRO SIGN (U+00B5) + "\xB6", // ¶ PILCROW SIGN (U+00B6) + "\xB7", // · MIDDLE DOT (U+00B7) + "\xB8", // ¸ CEDILLA (U+00B8) "a", // ą LATIN SMALL LETTER A WITH OGONEK (U+0105) "s", // ş LATIN SMALL LETTER S WITH CEDILLA (U+015F) - ">>", // » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00BB) + "\xBB", // » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (U+00BB) "L", // Ľ LATIN CAPITAL LETTER L WITH CARON (U+013D) "\"", // ˝ DOUBLE ACUTE ACCENT (U+02DD) "l", // ľ LATIN SMALL LETTER L WITH CARON (U+013E) "z", // ż LATIN SMALL LETTER Z WITH DOT ABOVE (U+017C) "R", // Ŕ LATIN CAPITAL LETTER R WITH ACUTE (U+0154) - "A", // Á LATIN CAPITAL LETTER A WITH ACUTE (U+00C1) - "A", // Â LATIN CAPITAL LETTER A WITH CIRCUMFLEX (U+00C2) + "\xC1", // Á LATIN CAPITAL LETTER A WITH ACUTE (U+00C1) + "\xC2", // Â LATIN CAPITAL LETTER A WITH CIRCUMFLEX (U+00C2) "A", // Ă LATIN CAPITAL LETTER A WITH BREVE (U+0102) - "A", // Ä LATIN CAPITAL LETTER A WITH DIAERESIS (U+00C4) + "\xC4", // Ä LATIN CAPITAL LETTER A WITH DIAERESIS (U+00C4) "L", // Ĺ LATIN CAPITAL LETTER L WITH ACUTE (U+0139) "C", // Ć LATIN CAPITAL LETTER C WITH ACUTE (U+0106) - "C", // Ç LATIN CAPITAL LETTER C WITH CEDILLA (U+00C7) + "\xC7", // Ç LATIN CAPITAL LETTER C WITH CEDILLA (U+00C7) "C", // Č LATIN CAPITAL LETTER C WITH CARON (U+010C) - "E", // É LATIN CAPITAL LETTER E WITH ACUTE (U+00C9) + "\xC9", // É LATIN CAPITAL LETTER E WITH ACUTE (U+00C9) "E", // Ę LATIN CAPITAL LETTER E WITH OGONEK (U+0118) - "E", // Ë LATIN CAPITAL LETTER E WITH DIAERESIS (U+00CB) + "\xCB", // Ë LATIN CAPITAL LETTER E WITH DIAERESIS (U+00CB) "E", // Ě LATIN CAPITAL LETTER E WITH CARON (U+011A) - "I", // Í LATIN CAPITAL LETTER I WITH ACUTE (U+00CD) - "I", // Î LATIN CAPITAL LETTER I WITH CIRCUMFLEX (U+00CE) + "\xCD", // Í LATIN CAPITAL LETTER I WITH ACUTE (U+00CD) + "\xCE", // Î LATIN CAPITAL LETTER I WITH CIRCUMFLEX (U+00CE) "D", // Ď LATIN CAPITAL LETTER D WITH CARON (U+010E) "D", // Đ LATIN CAPITAL LETTER D WITH STROKE (U+0110) "N", // Ń LATIN CAPITAL LETTER N WITH ACUTE (U+0143) "N", // Ň LATIN CAPITAL LETTER N WITH CARON (U+0147) - "O", // Ó LATIN CAPITAL LETTER O WITH ACUTE (U+00D3) - "O", // Ô LATIN CAPITAL LETTER O WITH CIRCUMFLEX (U+00D4) + "\xD3", // Ó LATIN CAPITAL LETTER O WITH ACUTE (U+00D3) + "\xD4", // Ô LATIN CAPITAL LETTER O WITH CIRCUMFLEX (U+00D4) "O", // Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE (U+0150) - "O", // Ö LATIN CAPITAL LETTER O WITH DIAERESIS (U+00D6) - "x", // × MULTIPLICATION SIGN (U+00D7) + "\xD6", // Ö LATIN CAPITAL LETTER O WITH DIAERESIS (U+00D6) + "\xD7", // × MULTIPLICATION SIGN (U+00D7) "R", // Ř LATIN CAPITAL LETTER R WITH CARON (U+0158) "U", // Ů LATIN CAPITAL LETTER U WITH RING ABOVE (U+016E) - "U", // Ú LATIN CAPITAL LETTER U WITH ACUTE (U+00DA) + "\xDA", // Ú LATIN CAPITAL LETTER U WITH ACUTE (U+00DA) "U", // Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE (U+0170) - "U", // Ü LATIN CAPITAL LETTER U WITH DIAERESIS (U+00DC) - "Y", // Ý LATIN CAPITAL LETTER Y WITH ACUTE (U+00DD) + "\xDC", // Ü LATIN CAPITAL LETTER U WITH DIAERESIS (U+00DC) + "\xDD", // Ý LATIN CAPITAL LETTER Y WITH ACUTE (U+00DD) "T", // Ţ LATIN CAPITAL LETTER T WITH CEDILLA (U+0162) - "B", // ß LATIN SMALL LETTER SHARP S (U+00DF) + "\xDF", // ß LATIN SMALL LETTER SHARP S (U+00DF) "r", // ŕ LATIN SMALL LETTER R WITH ACUTE (U+0155) - "a", // á LATIN SMALL LETTER A WITH ACUTE (U+00E1) - "a", // â LATIN SMALL LETTER A WITH CIRCUMFLEX (U+00E2) + "\xE1", // á LATIN SMALL LETTER A WITH ACUTE (U+00E1) + "\xE2", // â LATIN SMALL LETTER A WITH CIRCUMFLEX (U+00E2) "a", // ă LATIN SMALL LETTER A WITH BREVE (U+0103) - "a", // ä LATIN SMALL LETTER A WITH DIAERESIS (U+00E4) + "\xE4", // ä LATIN SMALL LETTER A WITH DIAERESIS (U+00E4) "l", // ĺ LATIN SMALL LETTER L WITH ACUTE (U+013A) "c", // ć LATIN SMALL LETTER C WITH ACUTE (U+0107) - "c", // ç LATIN SMALL LETTER C WITH CEDILLA (U+00E7) + "\xE7", // ç LATIN SMALL LETTER C WITH CEDILLA (U+00E7) "c", // č LATIN SMALL LETTER C WITH CARON (U+010D) - "e", // é LATIN SMALL LETTER E WITH ACUTE (U+00E9) + "\xE9", // é LATIN SMALL LETTER E WITH ACUTE (U+00E9) "e", // ę LATIN SMALL LETTER E WITH OGONEK (U+0119) - "e", // ë LATIN SMALL LETTER E WITH DIAERESIS (U+00EB) + "\xEB", // ë LATIN SMALL LETTER E WITH DIAERESIS (U+00EB) "e", // ě LATIN SMALL LETTER E WITH CARON (U+011B) - "i", // í LATIN SMALL LETTER I WITH ACUTE (U+00ED) - "i", // î LATIN SMALL LETTER I WITH CIRCUMFLEX (U+00EE) + "\xED", // í LATIN SMALL LETTER I WITH ACUTE (U+00ED) + "\xEE", // î LATIN SMALL LETTER I WITH CIRCUMFLEX (U+00EE) "d", // ď LATIN SMALL LETTER D WITH CARON (U+010F) "d", // đ LATIN SMALL LETTER D WITH STROKE (U+0111) "n", // ń LATIN SMALL LETTER N WITH ACUTE (U+0144) "n", // ň LATIN SMALL LETTER N WITH CARON (U+0148) - "o", // ó LATIN SMALL LETTER O WITH ACUTE (U+00F3) - "o", // ô LATIN SMALL LETTER O WITH CIRCUMFLEX (U+00F4) + "\xF3", // ó LATIN SMALL LETTER O WITH ACUTE (U+00F3) + "\xF4", // ô LATIN SMALL LETTER O WITH CIRCUMFLEX (U+00F4) "o", // ő LATIN SMALL LETTER O WITH DOUBLE ACUTE (U+0151) - "o", // ö LATIN SMALL LETTER O WITH DIAERESIS (U+00F6) - "/", // ÷ DIVISION SIGN (U+00F7) + "\xF6", // ö LATIN SMALL LETTER O WITH DIAERESIS (U+00F6) + "\xF7", // ÷ DIVISION SIGN (U+00F7) "r", // ř LATIN SMALL LETTER R WITH CARON (U+0159) "u", // ů LATIN SMALL LETTER U WITH RING ABOVE (U+016F) - "u", // ú LATIN SMALL LETTER U WITH ACUTE (U+00FA) + "\xFA", // ú LATIN SMALL LETTER U WITH ACUTE (U+00FA) "u", // ű LATIN SMALL LETTER U WITH DOUBLE ACUTE (U+0171) "u", // ü LATIN SMALL LETTER U WITH DIAERESIS (U+00FC) - "y", // ý LATIN SMALL LETTER Y WITH ACUTE (U+00FD) + "\xFD", // ý LATIN SMALL LETTER Y WITH ACUTE (U+00FD) "t", // ţ LATIN SMALL LETTER T WITH CEDILLA (U+0163) "'" // ˙ DOT ABOVE (U+02D9) } @@ -439,7 +439,7 @@ TextEncodingPage ISO_8859_1_Encoding = "'", // ’ 146 ’ right single quotation mark "\"", // “ 147 “ left double quotation mark "\"", // ” 148 ” right double quotation mark - "*", // • 149 • bullet + "\x95", // • 149 • bullet "-", // – 150 – en dash "-", // — 151 — em dash "~", // ˜ 152 ˜ small tilde @@ -450,101 +450,101 @@ TextEncodingPage ISO_8859_1_Encoding = "", // 157 NOT USED "z", // ž 158 ž Latin small letter z with caron "Y", // Ÿ 159 Ÿ Latin capital letter Y with diaeresis - "", // 160   no-break space - "!", // ¡ 161 ¡ inverted exclamation mark - "c", // ¢ 162 ¢ cent sign - "£", // £ 163 £ pound sign - "$", // ¤ 164 ¤ currency sign - "Y", // ¥ 165 ¥ yen sign - "|", // ¦ 166 ¦ broken bar - "S", // § 167 § section sign - "\"", // ¨ 168 ¨ diaeresis - "(C)", // © 169 © copyright sign - "a", // ª 170 ª feminine ordinal indicator - "<<", // « 171 « left-pointing double angle quotation mark - "¬", // ¬ 172 ¬ not sign - "", // �­ 173 ­ soft hyphen - "(R)", // ® 174 ® registered sign - "'", // ¯ 175 ¯ macron - "o", // ° 176 ° degree sign - "+-", // ± 177 ± plus-minus sign - "2", // ² 178 ² superscript two - "3", // ³ 179 ³ superscript three - "'", // ´ 180 ´ acute accent - "u", // µ 181 µ micro sign - "", // ¶ 182 ¶ pilcrow sign - ".", // · 183 · middle dot - ".", // ¸ 184 ¸ cedilla - "1", // ¹ 185 ¹ superscript one - "", // º 186 º masculine ordinal indicator - ">>", // » 187 » right-pointing double angle quotation mark - "1/4", // ¼ 188 ¼ vulgar fraction one quarter - "1/2", // ½ 189 ½ vulgar fraction one half - "3/4", // ¾ 190 ¾ vulgar fraction three quarters - "?", // ¿ 191 ¿ inverted question mark - "A", // À 192 À Latin capital letter A with grave - "A", // Á 193 Á Latin capital letter A with acute - "A", // Â 194 Â Latin capital letter A with circumflex - "A", // Ã 195 Ã Latin capital letter A with tilde - "A", // Ä 196 Ä Latin capital letter A with diaeresis - "A", // Å 197 Å Latin capital letter A with ring above - "AE", // Æ 198 Æ Latin capital letter AE - "C", // Ç 199 Ç Latin capital letter C with cedilla - "E", // È 200 È Latin capital letter E with grave - "E", // É 201 É Latin capital letter E with acute - "E", // Ê 202 Ê Latin capital letter E with circumflex - "E", // Ë 203 Ë Latin capital letter E with diaeresis - "I", // Ì 204 Ì Latin capital letter I with grave - "I", // Í 205 Í Latin capital letter I with acute - "I", // Î 206 Î Latin capital letter I with circumflex - "I", // Ï 207 Ï Latin capital letter I with diaeresis - "D", // Ð 208 Ð Latin capital letter Eth - "N", // Ñ 209 Ñ Latin capital letter N with tilde - "O", // Ò 210 Ò Latin capital letter O with grave - "O", // Ó 211 Ó Latin capital letter O with acute - "O", // Ô 212 Ô Latin capital letter O with circumflex - "O", // Õ 213 Õ Latin capital letter O with tilde - "O", // Ö 214 Ö Latin capital letter O with diaeresis - "x", // × 215 × multiplication sign - "0", // Ø 216 Ø Latin capital letter O with stroke - "U", // Ù 217 Ù Latin capital letter U with grave - "U", // Ú 218 Ú Latin capital letter U with acute - "U", // Û 219 Û Latin capital letter U with circumflex - "U", // Ü 220 Ü Latin capital letter U with diaeresis - "Y", // Ý 221 Ý Latin capital letter Y with acute - "P", // Þ 222 Þ Latin capital letter Thorn - "B", // ß 223 ß Latin small letter sharp s - "a", // à 224 à Latin small letter a with grave - "a", // á 225 á Latin small letter a with acute - "a", // â 226 â Latin small letter a with circumflex - "a", // ã 227 ã Latin small letter a with tilde - "a", // ä 228 ä Latin small letter a with diaeresis - "a", // å 229 å Latin small letter a with ring above - "ae", // æ 230 æ Latin small letter ae - "c", // ç 231 ç Latin small letter c with cedilla - "e", // è 232 è Latin small letter e with grave - "e", // é 233 é Latin small letter e with acute - "e", // ê 234 ê Latin small letter e with circumflex - "e", // ë 235 ë Latin small letter e with diaeresis - "i", // ì 236 ì Latin small letter i with grave - "i", // í 237 í Latin small letter i with acute - "i", // î 238 î Latin small letter i with circumflex - "i", // ï 239 ï Latin small letter i with diaeresis - "o", // ð 240 ð Latin small letter eth - "n", // ñ 241 ñ Latin small letter n with tilde - "o", // ò 242 ò Latin small letter o with grave - "o", // ó 243 ó Latin small letter o with acute - "o", // ô 244 ô Latin small letter o with circumflex - "o", // õ 245 õ Latin small letter o with tilde - "o", // ö 246 ö Latin small letter o with diaeresis - "/", // ÷ 247 ÷ division sign - "o", // ø 248 ø Latin small letter o with stroke - "u", // ù 249 ù Latin small letter u with grave - "u", // ú 250 ú Latin small letter u with acute - "u", // û 251 û Latin small letter u with circumflex - "u", // ü 252 ü Latin small letter u with diaeresis - "y", // ý 253 ý Latin small letter y with acute - "b", // þ 254 þ Latin small letter thorn - "y" // ÿ 255 ÿ Latin small letter y with diaeresis + "\xA0", // 160   no-break space + "\xA1", // ¡ 161 ¡ inverted exclamation mark + "\xA2", // ¢ 162 ¢ cent sign + "\xA3", // £ 163 £ pound sign + "\xA4", // ¤ 164 ¤ currency sign + "\xA5", // ¥ 165 ¥ yen sign + "\xA6", // ¦ 166 ¦ broken bar + "\xA7", // § 167 § section sign + "\xA8", // ¨ 168 ¨ diaeresis + "\xA9", // © 169 © copyright sign + "\xAA", // ª 170 ª feminine ordinal indicator + "\xAB", // « 171 « left-pointing double angle quotation mark + "\xAC", // ¬ 172 ¬ not sign + "\xAD", // �­ 173 ­ soft hyphen + "\xAE", // ® 174 ® registered sign + "\xAF", // ¯ 175 ¯ macron + "\xB0", // ° 176 ° degree sign + "\xB1", // ± 177 ± plus-minus sign + "\xB2", // ² 178 ² superscript two + "\xB3", // ³ 179 ³ superscript three + "\xB4", // ´ 180 ´ acute accent + "\xB5", // µ 181 µ micro sign + "\xB6", // ¶ 182 ¶ pilcrow sign + "\xB7", // · 183 · middle dot + "\xB8", // ¸ 184 ¸ cedilla + "\xB9", // ¹ 185 ¹ superscript one + "\xBA", // º 186 º masculine ordinal indicator + "\xBB", // » 187 » right-pointing double angle quotation mark + "\xBC", // ¼ 188 ¼ vulgar fraction one quarter + "\xBD", // ½ 189 ½ vulgar fraction one half + "\xBE", // ¾ 190 ¾ vulgar fraction three quarters + "\xBF", // ¿ 191 ¿ inverted question mark + "\xC0", // À 192 À Latin capital letter A with grave + "\xC1", // Á 193 Á Latin capital letter A with acute + "\xC2", // Â 194 Â Latin capital letter A with circumflex + "\xC3", // Ã 195 Ã Latin capital letter A with tilde + "\xC4", // Ä 196 Ä Latin capital letter A with diaeresis + "\xC5", // Å 197 Å Latin capital letter A with ring above + "\xC6", // Æ 198 Æ Latin capital letter AE + "\xC7", // Ç 199 Ç Latin capital letter C with cedilla + "\xC8", // È 200 È Latin capital letter E with grave + "\xC9", // É 201 É Latin capital letter E with acute + "\xCA", // Ê 202 Ê Latin capital letter E with circumflex + "\xCB", // Ë 203 Ë Latin capital letter E with diaeresis + "\xCC", // Ì 204 Ì Latin capital letter I with grave + "\xCD", // Í 205 Í Latin capital letter I with acute + "\xCE", // Î 206 Î Latin capital letter I with circumflex + "\xCF", // Ï 207 Ï Latin capital letter I with diaeresis + "\xD0", // Ð 208 Ð Latin capital letter Eth + "\xD1", // Ñ 209 Ñ Latin capital letter N with tilde + "\xD2", // Ò 210 Ò Latin capital letter O with grave + "\xD3", // Ó 211 Ó Latin capital letter O with acute + "\xD4", // Ô 212 Ô Latin capital letter O with circumflex + "\xD5", // Õ 213 Õ Latin capital letter O with tilde + "\xD6", // Ö 214 Ö Latin capital letter O with diaeresis + "\xD7", // × 215 × multiplication sign + "\xD8", // Ø 216 Ø Latin capital letter O with stroke + "\xD9", // Ù 217 Ù Latin capital letter U with grave + "\xDA", // Ú 218 Ú Latin capital letter U with acute + "\xDB", // Û 219 Û Latin capital letter U with circumflex + "\xDC", // Ü 220 Ü Latin capital letter U with diaeresis + "\xDD", // Ý 221 Ý Latin capital letter Y with acute + "\xDE", // Þ 222 Þ Latin capital letter Thorn + "\xDF", // ß 223 ß Latin small letter sharp s + "\xE0", // à 224 à Latin small letter a with grave + "\xE1", // á 225 á Latin small letter a with acute + "\xE2", // â 226 â Latin small letter a with circumflex + "\xE3", // ã 227 ã Latin small letter a with tilde + "\xE4", // ä 228 ä Latin small letter a with diaeresis + "\xE5", // å 229 å Latin small letter a with ring above + "\xE6", // æ 230 æ Latin small letter ae + "\xE7", // ç 231 ç Latin small letter c with cedilla + "\xE8", // è 232 è Latin small letter e with grave + "\xE9", // é 233 é Latin small letter e with acute + "\xEA", // ê 234 ê Latin small letter e with circumflex + "\xEB", // ë 235 ë Latin small letter e with diaeresis + "\xEC", // ì 236 ì Latin small letter i with grave + "\xED", // í 237 í Latin small letter i with acute + "\xEE", // î 238 î Latin small letter i with circumflex + "\xEF", // ï 239 ï Latin small letter i with diaeresis + "\xF0", // ð 240 ð Latin small letter eth + "\xF1", // ñ 241 ñ Latin small letter n with tilde + "\xF2", // ò 242 ò Latin small letter o with grave + "\xF3", // ó 243 ó Latin small letter o with acute + "\xF4", // ô 244 ô Latin small letter o with circumflex + "\xF5", // õ 245 õ Latin small letter o with tilde + "\xF6", // ö 246 ö Latin small letter o with diaeresis + "\xF7", // ÷ 247 ÷ division sign + "\xF8", // ø 248 ø Latin small letter o with stroke + "\xF9", // ù 249 ù Latin small letter u with grave + "\xFA", // ú 250 ú Latin small letter u with acute + "\xFB", // û 251 û Latin small letter u with circumflex + "\xFC", // ü 252 ü Latin small letter u with diaeresis + "\xFD", // ý 253 ý Latin small letter y with acute + "\xFE", // þ 254 þ Latin small letter thorn + "\xFF", // ÿ 255 ÿ Latin small letter y with diaeresis } }; diff --git a/src/VidModes.cpp b/src/VidModes.cpp new file mode 100644 index 0000000..16a744d --- /dev/null +++ b/src/VidModes.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "VidModes.h" + +VideoModeInfo VideoModeList[] = +{ + // name mode width height surfaceFormat aspect % zoom % data pack vram1 vram2 vram3 vram4 + { "640x200 monochrome (CGA)", 6, 640, 200, DrawSurface::Format_1BPP, 240, 100, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, DrawSurface::Format_1BPP, 100, 100, DataPack::Default, 0xb800, 0xba00 }, + { "320x200 4 colours (CGA)", 5, 320, 200, DrawSurface::Format_2BPP, 120, 70, DataPack::Lowres, 0xb800, 0xba00 }, + { "320x200 16 colours (Composite CGA)", 4, 320, 200, DrawSurface::Format_2BPP, 120, 70, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 16 colours (EGA)", 0xe, 640, 200, DrawSurface::Format_4BPP_EGA, 240, 100, DataPack::CGA, 0xa000, }, + { "640x350 monochrome (EGA)", 0xf, 640, 350, DrawSurface::Format_1BPP, 137, 100, DataPack::EGA, 0xa000, }, + { "640x350 16 colours (EGA)", 0x10, 640, 350, DrawSurface::Format_4BPP_EGA, 137, 100, DataPack::EGA, 0xa000, }, + { "640x480 monochrome (VGA)", 0x11, 640, 480, DrawSurface::Format_1BPP, 100, 100, DataPack::Default, 0xa000, }, + { "640x480 16 colours (VGA)", 0x12, 640, 480, DrawSurface::Format_4BPP_EGA, 100, 100, DataPack::Default, 0xa000, }, + { "320x200 256 colours (VGA)", 0x13, 320, 200, DrawSurface::Format_8BPP, 120, 70, DataPack::Lowres, 0xa000, }, + { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, DrawSurface::Format_1BPP, 155, 100, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, + { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, DrawSurface::Format_1BPP, 100, 100, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, DrawSurface::Format_1BPP, 100, 100, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, DrawSurface::Format_1BPP, 100, 50, DataPack::Lowres, 0xb000, }, + { "640x200 16 colours (Amstrad PC1512)", 6, 640, 200, DrawSurface::Format_4BPP_PC1512, 240, 100, DataPack::CGA, 0xb800, 0xba00 }, + { nullptr } +}; + +VideoModeInfo* ShowVideoModePicker(int defaultSelection) +{ + int n = 0; + + printf("Pick a video mode:\n"); + while (VideoModeList[n].name) + { + printf("(%c) %s\n", 'a' + n, VideoModeList[n].name); + n++; + } + + printf("? %c\b", 'a' + defaultSelection); + + int numModes = n; + char selection = getchar(); + + if (selection == 13 || selection == 10) + { + return &VideoModeList[defaultSelection]; + } + + selection = tolower(selection); + int selectionIndex = selection - 'a'; + + if (selectionIndex >= 0 && selectionIndex < numModes) + { + return &VideoModeList[selectionIndex]; + } + + return nullptr; +} diff --git a/src/VidModes.h b/src/VidModes.h new file mode 100644 index 0000000..f46e010 --- /dev/null +++ b/src/VidModes.h @@ -0,0 +1,31 @@ +#ifndef _VIDMODES_H_ +#define _VIDMODES_H_ + +#include +#include "Datapack.h" +#include "Draw/Surface.h" + +#define HERCULES_MODE 0 +#define CGA_COMPOSITE_MODE 4 + +struct VideoModeInfo +{ + const char* name; + int biosVideoMode; + int screenWidth; + int screenHeight; + DrawSurface::Format surfaceFormat; + int aspectRatio; + int zoom; + DataPack::Preset dataPackIndex : 8; + uint16_t vramPage1; + uint16_t vramPage2; + uint16_t vramPage3; + uint16_t vramPage4; +}; + +VideoModeInfo* ShowVideoModePicker(int defaultSelection); + +extern VideoModeInfo VideoModeList[]; + +#endif diff --git a/src/Widget.h b/src/Widget.h deleted file mode 100644 index a647356..0000000 --- a/src/Widget.h +++ /dev/null @@ -1,116 +0,0 @@ -// -// Copyright (C) 2021 James Howard -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// - -#pragma once -#include -#include "Font.h" - -struct WidgetStyle -{ - WidgetStyle() {} - WidgetStyle(FontStyle::Type inFontStyle, uint8_t inFontSize = 1, bool inCenter = false) : - fontStyle(inFontStyle), fontSize(inFontSize), center(inCenter) {} - - FontStyle::Type fontStyle : 4; - uint8_t fontSize : 2; - bool center : 1; -}; - -struct TextWidgetData -{ - char* text; - char* linkURL; -}; - -struct WidgetFormData -{ - enum MethodType - { - Get, - Post - }; - char* action; - MethodType method; -}; - -struct ButtonWidgetData -{ - char* text; - WidgetFormData* form; -}; - -struct TextFieldWidgetData -{ - char* buffer; - char* name; - int bufferLength; - WidgetFormData* form; -}; - -struct ScrollBarData -{ - int position; - int size; -}; - -struct ImageWidgetData -{ - char* altText; - char* linkURL; -}; - -struct Widget -{ - enum Type - { - Text, - HorizontalRule, - Button, - TextField, - ScrollBar, - Image, - BulletPoint - }; - - Widget() : type(Text), isInterfaceWidget(false), x(0), y(0), width(0), height(0) {} - - Type type : 7; - bool isInterfaceWidget : 1; - uint16_t x, y; - uint16_t width, height; - WidgetStyle style; - - char* GetLinkURL() - { - if (type == Text && text) - { - return text->linkURL; - } - if (type == Image && image) - { - return image->linkURL; - } - return NULL; - } - - union - { - TextWidgetData* text; - ButtonWidgetData* button; - TextFieldWidgetData* textField; - ScrollBarData* scrollBar; - ImageWidgetData* image; - }; -}; - diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 1697524..d6169f0 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -13,50 +13,46 @@ // #include +#include #include "../Platform.h" #include "WinVid.h" #include "WinInput.h" +#include "WinNet.h" +#include "../Draw/Surface.h" +#include "../VidModes.h" +#include "../Memory/Memory.h" +#include "../App.h" WindowsVideoDriver winVid; -NetworkDriver nullNetworkDriver; +WindowsNetworkDriver winNetworkDriver; WindowsInputDriver winInputDriver; VideoDriver* Platform::video = &winVid; -NetworkDriver* Platform::network = &nullNetworkDriver; +NetworkDriver* Platform::network = &winNetworkDriver; InputDriver* Platform::input = &winInputDriver; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); HWND hWnd; -void Platform::Init(int argc, char* argv[]) +bool Platform::Init(int argc, char* argv[]) { - WNDCLASSW wc = { 0 }; - HINSTANCE hInstance = GetModuleHandle(NULL); - - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpszClassName = L"Pixels"; - wc.hInstance = hInstance; - wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); - wc.lpfnWndProc = WndProc; - wc.hCursor = LoadCursor(0, IDC_ARROW); - - RECT wr = { 0, 0, winVid.screenWidth, winVid.screenHeight * winVid.verticalScale }; - AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); - - RegisterClassW(&wc); - hWnd = CreateWindowW(wc.lpszClassName, L"MicroWeb", - WS_OVERLAPPEDWINDOW | WS_VISIBLE, - 100, 100, wr.right - wr.left, wr.bottom - wr.top, NULL, NULL, hInstance, NULL); + VideoModeInfo* videoMode = ShowVideoModePicker(8); + if (!videoMode) + { + return false; + } network->Init(); - video->Init(); - video->ClearScreen(); + video->Init(videoMode); input->Init(); input->ShowMouse(); + + return true; } void Platform::Shutdown() { + MemoryManager::pageBlockAllocator.Shutdown(); input->Shutdown(); video->Shutdown(); network->Shutdown(); @@ -78,6 +74,12 @@ void Platform::Update() Shutdown(); exit(0); } + + App& app = App::Get(); + if (!app.pageRenderer.IsRendering() && !app.pageLoadTask.IsBusy() && !app.pageContentLoadTask.IsBusy() && app.page.layout.IsFinished()) + { + Sleep(10); + } } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, @@ -118,11 +120,11 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, break; case WM_MOUSEWHEEL: - if (wParam < 0) + if ((int)wParam > 0) { winInputDriver.QueueKeyPress(VK_UP); } - else if (wParam > 0) + else { winInputDriver.QueueKeyPress(VK_DOWN); } @@ -131,3 +133,36 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, return DefWindowProcW(hwnd, msg, wParam, lParam); } + +void Platform::FatalError(const char* message, ...) +{ + va_list args; + + if (video) + { + video->Shutdown(); + } + + va_start(args, message); + + size_t size = vsnprintf(NULL, 0, message, args); + + // Allocate memory for the message + char* buffer = new char[size + 1]; + + // Format the message + vsnprintf(buffer, size + 1, message, args); + + va_end(args); + + int wsize = MultiByteToWideChar(CP_ACP, 0, buffer, -1, NULL, 0); + wchar_t* wbuffer = new wchar_t[wsize + 1]; + MultiByteToWideChar(CP_ACP, 0, buffer, -1, wbuffer, wsize); + + MessageBox(NULL, wbuffer, L"Fatal error", MB_OK); + + delete[] wbuffer; + delete[] buffer; + + exit(1); +} diff --git a/src/Windows/WinInput.cpp b/src/Windows/WinInput.cpp index c4637ad..c35c93d 100644 --- a/src/Windows/WinInput.cpp +++ b/src/Windows/WinInput.cpp @@ -70,6 +70,26 @@ void WindowsInputDriver::SetMousePosition(int x, int y) } } +bool WindowsInputDriver::GetMouseButtonPress(int& x, int& y) +{ + static int oldButtons = 0; + int buttons; + GetMouseStatus(buttons, x, y); + bool pressed = (buttons & 1) && !(oldButtons & 1); + oldButtons = buttons; + return pressed; +} + +bool WindowsInputDriver::GetMouseButtonRelease(int& x, int& y) +{ + static int oldButtons = 0; + int buttons; + GetMouseStatus(buttons, x, y); + bool released = !(buttons & 1) && (oldButtons & 1); + oldButtons = buttons; + return released; +} + void WindowsInputDriver::GetMouseStatus(int& buttons, int& x, int& y) { x = y = 0; @@ -79,8 +99,18 @@ void WindowsInputDriver::GetMouseStatus(int& buttons, int& x, int& y) { if (ScreenToClient(hWnd, &p)) { - x = p.x; - y = p.y / static_cast(Platform::video)->verticalScale; + // Calculate the destination rectangle to stretch the bitmap to fit the window + RECT windowRect; + GetClientRect(hWnd, &windowRect); + + int width = windowRect.right - windowRect.left; + int height = windowRect.bottom - windowRect.top; + + if (width > 0 && height > 0) + { + x = p.x * Platform::video->screenWidth / width; + y = p.y * Platform::video->screenHeight / height; + } } } @@ -132,16 +162,20 @@ InputButtonCode WindowsInputDriver::TranslateCode(WPARAM code) { switch (code) { - case VK_LBUTTON: - return KEYCODE_MOUSE_LEFT; - case VK_RBUTTON: - return KEYCODE_MOUSE_RIGHT; + //case VK_LBUTTON: + // return KEYCODE_MOUSE_LEFT; + //case VK_RBUTTON: + // return KEYCODE_MOUSE_RIGHT; case VK_ESCAPE: return KEYCODE_ESCAPE; case VK_UP: return KEYCODE_ARROW_UP; case VK_DOWN: return KEYCODE_ARROW_DOWN; + case VK_LEFT: + return KEYCODE_ARROW_LEFT; + case VK_RIGHT: + return KEYCODE_ARROW_RIGHT; case VK_HOME: return KEYCODE_HOME; case VK_END: @@ -152,6 +186,35 @@ InputButtonCode WindowsInputDriver::TranslateCode(WPARAM code) return KEYCODE_PAGE_DOWN; case VK_RETURN: return KEYCODE_ENTER; + case VK_DELETE: + return KEYCODE_DELETE; + case VK_BACK: + return KEYCODE_BACKSPACE; + case VK_TAB: + if (GetKeyState(VK_SHIFT) & 0x8000) + return KEYCODE_SHIFT_TAB; + else + return KEYCODE_TAB; + case VK_F1: + return KEYCODE_F1; + case VK_F2: + return KEYCODE_F2; + case VK_F3: + return KEYCODE_F3; + case VK_F4: + return KEYCODE_F4; + case VK_F5: + return KEYCODE_F5; + case VK_F6: + return KEYCODE_F6; + case VK_F7: + return KEYCODE_F7; + case VK_F8: + return KEYCODE_F8; + case VK_F9: + return KEYCODE_F9; + case VK_F10: + return KEYCODE_F10; default: return 0; } diff --git a/src/Windows/WinInput.h b/src/Windows/WinInput.h index 23596c4..f0722bf 100644 --- a/src/Windows/WinInput.h +++ b/src/Windows/WinInput.h @@ -28,6 +28,8 @@ class WindowsInputDriver : public InputDriver virtual void SetMouseCursor(MouseCursor::Type type); virtual void GetMouseStatus(int& buttons, int& x, int& y); virtual void SetMousePosition(int x, int y); + virtual bool GetMouseButtonPress(int& x, int& y); + virtual bool GetMouseButtonRelease(int& x, int& y); void RefreshCursor(); virtual InputButtonCode GetKeyPress(); diff --git a/src/Windows/WinNet.cpp b/src/Windows/WinNet.cpp new file mode 100644 index 0000000..b26aa9c --- /dev/null +++ b/src/Windows/WinNet.cpp @@ -0,0 +1,237 @@ +#include +#include +#include "WinNet.h" +#include "../HTTP.h" + +#pragma comment(lib, "ws2_32.lib") + +WindowsNetworkDriver::WindowsNetworkDriver() :isConnected(false) +{ + +} + +void WindowsNetworkDriver::Init() +{ + WSADATA wsData; + if (WSAStartup(MAKEWORD(2, 2), &wsData) == 0) { + isConnected = true; + } + + for (int n = 0; n < MAX_CONCURRENT_REQUESTS; n++) + { + requests[n] = NULL; + } +} + +void WindowsNetworkDriver::Shutdown() +{ + WSACleanup(); +} + +int WindowsNetworkDriver::ResolveAddress(const char* name, NetworkAddress address, bool sendRequest) +{ + // Set up the address hints for the host lookup + struct addrinfo hints; + ZeroMemory(&hints, sizeof(hints)); + hints.ai_family = AF_INET; // Allow IPv4 + hints.ai_socktype = SOCK_STREAM; // Stream socket (TCP) + hints.ai_flags = AI_PASSIVE; // For wildcard IP address + + // Asynchronous hostname lookup with non-blocking sockets + struct addrinfo* result = nullptr; + int getAddrResult = getaddrinfo(name, NULL, &hints, &result); + if (getAddrResult != 0) { + // Error doing lookup + return -1; + } + + // Loop through the address results to get IPv4 addresses + for (struct addrinfo* addr = result; addr != NULL; addr = addr->ai_next) { + if (addr->ai_family == AF_INET) { // IPv4 + + struct sockaddr_in* ipv4 = reinterpret_cast(addr->ai_addr); + + address[0] = (ipv4->sin_addr.s_addr >> 0) & 0xFF; + address[1] = (ipv4->sin_addr.s_addr >> 8) & 0xFF; + address[2] = (ipv4->sin_addr.s_addr >> 16) & 0xFF; + address[3] = (ipv4->sin_addr.s_addr >> 24) & 0xFF; + + return 0; + } + } + + // Free the memory allocated for the address info + freeaddrinfo(result); + return 1; +} + +NetworkTCPSocket* WindowsNetworkDriver::CreateSocket() +{ + return new WindowsTCPSocket(); +} + +void WindowsNetworkDriver::DestroySocket(NetworkTCPSocket* socket) +{ + if (socket) + { + socket->Close(); + } + delete socket; +} + +HTTPRequest* WindowsNetworkDriver::CreateRequest(char* url) +{ + for (int n = 0; n < MAX_CONCURRENT_REQUESTS; n++) + { + if (requests[n] == NULL) + { + requests[n] = new HTTPRequest(); + requests[n]->Open(url); + return requests[n]; + } + } + + return NULL; +} + +void WindowsNetworkDriver::DestroyRequest(HTTPRequest* request) +{ + for (int n = 0; n < MAX_CONCURRENT_REQUESTS; n++) + { + if (requests[n] == request) + { + delete request; + requests[n] = NULL; + } + } +} + +void WindowsNetworkDriver::Update() +{ + for (int n = 0; n < MAX_CONCURRENT_REQUESTS; n++) + { + if (requests[n]) + { + requests[n]->Update(); + } + } +} + +WindowsTCPSocket::WindowsTCPSocket() +{ + // Create a socket + sock = socket(AF_INET, SOCK_STREAM, 0); + + // Set the socket to non-blocking mode + u_long mode = 1; + if (ioctlsocket(sock, FIONBIO, &mode) != 0) { + Close(); + } +} + +int WindowsTCPSocket::Send(uint8_t* data, int length) +{ + if (sock != INVALID_SOCKET) + { + // Wait for the socket to be writable (connected) with a timeout + fd_set writeSet; + FD_ZERO(&writeSet); + FD_SET(sock, &writeSet); + + timeval timeout; + timeout.tv_sec = 0; // 0 seconds timeout + timeout.tv_usec = 0; + + int result = select(0, nullptr, &writeSet, nullptr, &timeout); + if (result < 0) { + // Failed to connect to the server + Close(); + return -1; + } + + if (result > 0) + { + // Send data to the server + result = send(sock, (const char*)data, length, 0); + if (result == SOCKET_ERROR) { + // Failed to send data to the server + Close(); + return -1; + } + } + + return result; + } + else + { + return -1; + } +} + +int WindowsTCPSocket::Receive(uint8_t* buffer, int length) +{ + // Wait for the socket to be readable (response received) with a timeout + fd_set readSet; + FD_ZERO(&readSet); + FD_SET(sock, &readSet); + + timeval timeout; + timeout.tv_sec = 0; // 0 seconds timeout + timeout.tv_usec = 0; + + int result = select(0, &readSet, nullptr, nullptr, &timeout); + if (result < 0) { + // Failed to receive data + Close(); + return -1; + } + else if (result > 0) + { + // Receive data from the server + result = recv(sock, (char*)buffer, length, 0); + if (result == SOCKET_ERROR) { + // Failed to receive data + Close(); + return -1; + } + } + + return result; +} + +int WindowsTCPSocket::Connect(NetworkAddress address, int port) +{ + // Connect to the server + struct sockaddr_in serverAddr; + ZeroMemory(&serverAddr, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(port); // Change this to the server's port number + serverAddr.sin_addr.S_un.S_addr = *(reinterpret_cast(address)); + + int result = connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)); + if (result == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) { + return -1; + } + + return 0; +} + +bool WindowsTCPSocket::IsConnectComplete() +{ + return sock != INVALID_SOCKET; +} + +bool WindowsTCPSocket::IsClosed() +{ + return sock == INVALID_SOCKET; +} + +void WindowsTCPSocket::Close() +{ + if (sock != INVALID_SOCKET) + { + closesocket(sock); + sock = INVALID_SOCKET; + } +} + diff --git a/src/Windows/WinNet.h b/src/Windows/WinNet.h new file mode 100644 index 0000000..055a7b5 --- /dev/null +++ b/src/Windows/WinNet.h @@ -0,0 +1,51 @@ +#ifndef WINNET_H_ +#define WINNET_H_ + +#include +#include "../Platform.h" + +#define MAX_CONCURRENT_REQUESTS 2 + +class WindowsNetworkDriver : public NetworkDriver +{ +public: + WindowsNetworkDriver(); + + virtual void Init() override; + virtual void Shutdown() override; + virtual void Update() override; + + virtual bool IsConnected() override { return isConnected; } + + // Returns zero on success, negative number is error + virtual int ResolveAddress(const char* name, NetworkAddress address, bool sendRequest) override; + + virtual NetworkTCPSocket* CreateSocket() override; + virtual void DestroySocket(NetworkTCPSocket* socket) override; + + virtual HTTPRequest* CreateRequest(char* url) override; + virtual void DestroyRequest(HTTPRequest* request) override; + +private: + HTTPRequest* requests[MAX_CONCURRENT_REQUESTS]; + + bool isConnected; +}; + +class WindowsTCPSocket : public NetworkTCPSocket +{ +public: + WindowsTCPSocket(); + virtual int Send(uint8_t* data, int length) override; + virtual int Receive(uint8_t* buffer, int length) override; + virtual int Connect(NetworkAddress address, int port) override; + virtual bool IsConnectComplete() override; + virtual bool IsClosed() override; + virtual void Close() override; + +private: + SOCKET sock; +}; + +#endif + diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index fa5deb2..403899e 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -15,324 +15,322 @@ #include #include #include "WinVid.h" -#include "../Image.h" -#include "../DOS/CGAData.inc" +#include "../Image/Image.h" #include "../Interface.h" +#include "../DataPack.h" +#include "../Draw/Surf1bpp.h" +#include "../Draw/Surf8bpp.h" +#include "../VidModes.h" -#define WINDOW_TOP 24 -#define WINDOW_HEIGHT 168 -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - +//#define SCREEN_WIDTH 800 +//#define SCREEN_HEIGHT 600 #define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT 200 - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT 12 - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 32 - -#define ADDRESS_BAR_X 60 -#define ADDRESS_BAR_Y 10 -#define ADDRESS_BAR_WIDTH 576 -#define ADDRESS_BAR_HEIGHT 12 - -#define TITLE_BAR_HEIGHT 8 -#define STATUS_BAR_HEIGHT 8 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) - -#define SCROLL_BAR_WIDTH 16 +#define SCREEN_HEIGHT 480 +#define USE_COLOUR 0 extern HWND hWnd; +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); WindowsVideoDriver::WindowsVideoDriver() { - screenWidth = 640; - screenHeight = 200; - windowWidth = screenWidth - 16; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - - foregroundColour = RGB(0, 0, 0); - backgroundColour = RGB(255, 255, 255); - verticalScale = 2; - - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = screenWidth; - scissorY2 = screenHeight; - - imageIcon = &CGA_ImageIcon; - bulletImage = &CGA_Bullet; - isTextMode = false; } -void WindowsVideoDriver::Init() +const RGBQUAD monoPalette[] = { - HDC hDC = GetDC(hWnd); - HDC hDCMem = CreateCompatibleDC(hDC); - - BITMAPINFO bi; - ZeroMemory(&bi, sizeof(BITMAPINFO)); - bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bi.bmiHeader.biWidth = screenWidth; - bi.bmiHeader.biHeight = screenHeight * verticalScale; - bi.bmiHeader.biPlanes = 1; - bi.bmiHeader.biBitCount = 32; + { 0, 0, 0, 0 }, + { 0xff, 0xff, 0xff, 0 } +}; - screenBitmap = CreateDIBSection(hDCMem, &bi, DIB_RGB_COLORS, (VOID**)&lpBitmapBits, NULL, 0); - - for (int n = 0; n < screenWidth * screenHeight * verticalScale; n++) - { - lpBitmapBits[n] = backgroundColour; - } -} - -void WindowsVideoDriver::Shutdown() +const RGBQUAD cgaPalette[] = { -} + { 0x00, 0x00, 0x00 }, // Entry 0 - Black + { 0xFF, 0xFF, 0x55 }, // Entry 11 - Light Cyan + { 0x55, 0x55, 0xFF }, // Entry 12 - Light Red + { 0xFF, 0xFF, 0xFF }, // Entry 15 - White +}; -void WindowsVideoDriver::ClearScreen() +const RGBQUAD cgaCompositePalette[] = { - FillRect(0, 0, screenWidth, TITLE_BAR_HEIGHT, foregroundColour); - FillRect(0, TITLE_BAR_HEIGHT, screenWidth, screenHeight - STATUS_BAR_HEIGHT - TITLE_BAR_HEIGHT, backgroundColour); - FillRect(0, screenHeight - STATUS_BAR_HEIGHT, screenWidth, STATUS_BAR_HEIGHT, foregroundColour); -} - -void WindowsVideoDriver::FillRect(int x, int y, int width, int height) + { 0x00, 0x00, 0x00 }, + { 0x31, 0x6e, 0x00 }, + { 0xff, 0x09, 0x31 }, + { 0xff, 0x8a, 0x00 }, + { 0x31, 0x00, 0xa7 }, + { 0x76, 0x76, 0x76 }, + { 0xff, 0x11, 0xec }, + { 0xff, 0x92, 0xbb }, + { 0x00, 0x5a, 0x31 }, + { 0x00, 0xdb, 0x00 }, + { 0x76, 0x76, 0x76 }, + { 0xbb, 0xf7, 0x45 }, + { 0x00, 0x63, 0xec }, + { 0x00, 0xe4, 0xbb }, + { 0xbb, 0x7f, 0xff }, + { 0xff, 0xff, 0xff }, +}; + + +const RGBQUAD egaPalette[] = { - FillRect(x, y, width, height, foregroundColour); -} - -void WindowsVideoDriver::FillRect(int x, int y, int width, int height, uint32_t colour) + { 0x00, 0x00, 0x00 }, // Entry 0 - Black + { 0xAA, 0x00, 0x00 }, // Entry 1 - Blue + { 0x00, 0xAA, 0x00 }, // Entry 2 - Green + { 0xAA, 0xAA, 0x00 }, // Entry 3 - Cyan + { 0x00, 0x00, 0xAA }, // Entry 4 - Red + { 0xAA, 0x00, 0xAA }, // Entry 5 - Magenta + { 0x00, 0x55, 0xAA }, // Entry 6 - Brown + { 0xAA, 0xAA, 0xAA }, // Entry 7 - Light Gray + { 0x55, 0x55, 0x55 }, // Entry 8 - Dark Gray + { 0xFF, 0x55, 0x55 }, // Entry 9 - Light Blue + { 0x55, 0xFF, 0x55 }, // Entry 10 - Light Green + { 0xFF, 0xFF, 0x55 }, // Entry 11 - Light Cyan + { 0x55, 0x55, 0xFF }, // Entry 12 - Light Red + { 0xFF, 0x55, 0xFF }, // Entry 13 - Light Magenta + { 0x55, 0xFF, 0xFF }, // Entry 14 - Yellow + { 0xFF, 0xFF, 0xFF }, // Entry 15 - White +}; + +void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) { - for (int j = 0; j < height; j++) + videoMode = inVideoMode; + + screenWidth = videoMode->screenWidth; + screenHeight = videoMode->screenHeight; + verticalScale = videoMode->aspectRatio / 100.0f; // ((screenWidth * 3.0f) / 4.0f) / screenHeight; + //verticalScale = 1.0f; + Assets.LoadPreset((DataPack::Preset) videoMode->dataPackIndex); + + // Create window + WNDCLASSW wc = { 0 }; + HINSTANCE hInstance = GetModuleHandle(NULL); + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpszClassName = L"Pixels"; + wc.hInstance = hInstance; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.lpfnWndProc = WndProc; + wc.hCursor = LoadCursor(0, IDC_ARROW); + + RECT wr = { 0, 0, screenWidth, (int)(screenHeight * verticalScale) }; + AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); + + RegisterClassW(&wc); + hWnd = CreateWindowW(wc.lpszClassName, L"MicroWeb", + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, wr.right - wr.left, wr.bottom - wr.top, NULL, NULL, hInstance, NULL); + + // Create bitmap for window contents + HDC hDC = GetDC(hWnd); + HDC hDCMem = CreateCompatibleDC(hDC); + + bool useColour = videoMode->surfaceFormat != DrawSurface::Format_1BPP; + int paletteSize = useColour ? 256 : 2; + + bitmapInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * paletteSize); + ZeroMemory(bitmapInfo, sizeof(BITMAPINFO) + sizeof(RGBQUAD) * paletteSize); + bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo->bmiHeader.biWidth = screenWidth; + bitmapInfo->bmiHeader.biHeight = screenHeight; + bitmapInfo->bmiHeader.biPlanes = 1; +// bitmapInfo->bmiHeader.biBitCount = 32; + bitmapInfo->bmiHeader.biBitCount = useColour ? 8 : 1; + bitmapInfo->bmiHeader.biCompression = BI_RGB; + bitmapInfo->bmiHeader.biClrUsed = 0; + + if (useColour) { - for (int i = 0; i < width; i++) + memcpy(bitmapInfo->bmiColors, egaPalette, sizeof(RGBQUAD) * 16); + + if (videoMode->surfaceFormat == DrawSurface::Format_8BPP) { - SetPixel(x + i, y + j, colour); + int index = 16; + for (int r = 0; r < 6; r++) + { + for (int g = 0; g < 6; g++) + { + for (int b = 0; b < 6; b++) + { + bitmapInfo->bmiColors[index].rgbRed = (r * 255) / 5; + bitmapInfo->bmiColors[index].rgbGreen = (g * 255) / 5; + bitmapInfo->bmiColors[index].rgbBlue = (b * 255) / 5; + index++; + } + } + } } - } -} - -void WindowsVideoDriver::SetPixel(int x, int y, uint32_t colour) -{ - if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) - { - int line = ((screenHeight - y - 1) * verticalScale); - for (int j = 0; j < verticalScale; j++) + else if (videoMode->surfaceFormat == DrawSurface::Format_2BPP) { - lpBitmapBits[(line + j) * screenWidth + x] = colour; + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) + { + for (int n = 0; n < 256; n += 16) + { + memcpy(bitmapInfo->bmiColors + n, cgaCompositePalette, sizeof(RGBQUAD) * 16); + } + } + else + { + for (int n = 0; n < 256; n += 4) + { + memcpy(bitmapInfo->bmiColors + n, cgaPalette, sizeof(RGBQUAD) * 4); + } + } } } -} - -void WindowsVideoDriver::InvertPixel(int x, int y, uint32_t colour) -{ - if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) + else { - int line = ((screenHeight - y - 1) * verticalScale); - for (int j = 0; j < verticalScale; j++) - { - lpBitmapBits[(line + j) * screenWidth + x] ^= colour; - } + memcpy(bitmapInfo->bmiColors, monoPalette, sizeof(RGBQUAD) * 2); } -} - -void WindowsVideoDriver::ClearWindow() -{ - FillRect(0, WINDOW_TOP, SCREEN_WIDTH - SCROLL_BAR_WIDTH, WINDOW_HEIGHT, backgroundColour); -} - -void WindowsVideoDriver::ClearRect(int x, int y, int width, int height) -{ - FillRect(x, y, width, height, backgroundColour); -} - -void WindowsVideoDriver::ScrollWindow(int delta) -{ -} -void WindowsVideoDriver::SetScissorRegion(int y1, int y2) -{ - scissorY1 = y1; - scissorY2 = y2; -} + screenBitmap = CreateDIBSection(hDCMem, bitmapInfo, DIB_RGB_COLORS, (VOID**)&lpBitmapBits, NULL, 0); -void WindowsVideoDriver::ClearScissorRegion() -{ - scissorY1 = 0; - scissorY2 = screenHeight; -} - -void WindowsVideoDriver::DrawString(const char* text, int x, int y, int size, FontStyle::Type style) -{ -// printf("%s\n", text); - Font* font = GetFont(size, style); - - int startX = x; - uint8_t glyphHeight = font->glyphHeight; - if (x >= scissorX2) - { - return; - } - if (y >= scissorY2) + if (useColour) { - return; - } - if (y + glyphHeight > scissorY2) - { - glyphHeight = (uint8_t)(scissorY2 - y); - } - if (y + glyphHeight < scissorY1) - { - return; - } + DrawSurface_8BPP* surface = new DrawSurface_8BPP(screenWidth, screenHeight); + uint8_t* buffer = (uint8_t*)(lpBitmapBits); - uint8_t firstLine = 0; - if (y < scissorY1) - { - firstLine += scissorY1 - y; - y += firstLine; - } + int pitch = screenWidth; - while (*text) - { - char c = *text++; - if (c < 32 || c >= 128) + for (int y = 0; y < screenHeight; y++) { - continue; + int bufferY = (screenHeight - 1 - y); + surface->lines[y] = &buffer[bufferY * pitch]; } + drawSurface = surface; - char index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; - - if (glyphWidth == 0) + for (int n = 0; n < screenWidth * screenHeight; n++) { - continue; + buffer[n] = 0xf; } - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - glyphData += (firstLine * font->glyphWidthBytes); - - for (uint8_t j = firstLine; j < glyphHeight; j++) + if (videoMode->surfaceFormat == DrawSurface::Format_8BPP) { - uint8_t writeOffset = (uint8_t)(x) & 0x7; + colourScheme = colourScheme666; + //paletteLUT = cgaPaletteLUT; - if ((style & FontStyle::Italic) && j < (glyphHeight >> 1)) + paletteLUT = new uint8_t[256]; + for (int n = 0; n < 256; n++) { - writeOffset++; - } + int r = (n & 0xe0); + int g = (n & 0x1c) << 3; + int b = (n & 3) << 6; - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } + int rgbBlue = (b * 255) / 0xc0; + int rgbGreen = (g * 255) / 0xe0; + int rgbRed = (r * 255) / 0xe0; - for (int k = 0; k < 8; k++) - { - if (glyphPixels & (0x80 >> k)) - { - InvertPixel(x + k + i * 8, y + j, 0xffffff); - } - } + paletteLUT[n] = RGB666(rgbRed, rgbGreen, rgbBlue); + } + } + else if (videoMode->surfaceFormat == DrawSurface::Format_2BPP) + { + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) + { + colourScheme = compositeCgaColourScheme; + paletteLUT = compositeCgaPaletteLUT; + } + else + { + colourScheme = cgaColourScheme; + paletteLUT = cgaPaletteLUT; } } + else + { + colourScheme = egaColourScheme; + paletteLUT = egaPaletteLUT; + } + } + else + { + DrawSurface_1BPP* surface = new DrawSurface_1BPP(screenWidth, screenHeight); + uint8_t* buffer = (uint8_t*)(lpBitmapBits); + + int pitch = (screenWidth + 7) / 8; // How many bytes needed for 1bpp + if (pitch & 3) // Round to nearest 32-bit + { + pitch += 4 - (pitch & 3); + } - x += glyphWidth; - if (style & FontStyle::Bold) + for (int y = 0; y < screenHeight; y++) { - x++; + int bufferY = (screenHeight - 1 - y); + surface->lines[y] = &buffer[bufferY * pitch]; } + drawSurface = surface; - if (x >= scissorX2) + for (int n = 0; n < screenWidth * screenHeight / 8; n++) { - break; + buffer[n] = 0xff; } - } - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < scissorY2) - { - HLine(startX, y - firstLine + font->glyphHeight - 1, x - startX); - } -} + colourScheme = monochromeColourScheme; -void WindowsVideoDriver::HLine(int x, int y, int count) -{ - for (int n = 0; n < count; n++) - { - SetPixel(x + n, y, foregroundColour); + paletteLUT = nullptr; } } -void WindowsVideoDriver::VLine(int x, int y, int count) +void WindowsVideoDriver::Shutdown() { - for (int n = 0; n < count; n++) - { - SetPixel(x, y + n, foregroundColour); - } } -void WindowsVideoDriver::DrawScrollBar(int position, int size) +void WindowsVideoDriver::ClearScreen() { + //FillRect(0, 0, screenWidth, TITLE_BAR_HEIGHT, foregroundColour); + //FillRect(0, TITLE_BAR_HEIGHT, screenWidth, screenHeight - STATUS_BAR_HEIGHT - TITLE_BAR_HEIGHT, backgroundColour); + //FillRect(0, screenHeight - STATUS_BAR_HEIGHT, screenWidth, STATUS_BAR_HEIGHT, foregroundColour); } -MouseCursorData* WindowsVideoDriver::GetCursorGraphic(MouseCursor::Type type) +void WindowsVideoDriver::SetPixel(int x, int y, uint32_t colour) { - return NULL; -} + if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) + { + int outY = screenHeight - y - 1; + uint8_t mask = 0x80 >> ((uint8_t)(x & 7)); + uint8_t* ptr = (uint8_t*)(lpBitmapBits); + int index = (outY * (screenWidth / 8) + (x / 8)); -Font* WindowsVideoDriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) + if (colour) + { + ptr[index] |= mask; + } + else + { + ptr[index] &= ~mask; + } + } + /* + if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) { - switch (fontSize) + int line = ((screenHeight - y - 1) * verticalScale); + for (int j = 0; j < verticalScale; j++) { - case 0: - return &CGA_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &CGA_LargeFont_Monospace; - default: - return &CGA_RegularFont_Monospace; + lpBitmapBits[(line + j) * screenWidth + x] = colour; } } + */ +} - switch (fontSize) +void WindowsVideoDriver::InvertPixel(int x, int y, uint32_t colour) +{ + if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) { - case 0: - return &CGA_SmallFont; - case 2: - return &CGA_LargeFont; - default: - return &CGA_RegularFont; + int outY = screenHeight - y - 1; + uint8_t mask = 0x80 >> ((uint8_t)(x & 7)); + uint8_t* ptr = (uint8_t*)(lpBitmapBits); + int index = (outY * (screenWidth / 8) + (x / 8)); + + ptr[index] ^= mask; } -} -int WindowsVideoDriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) -{ - Font* font = GetFont(fontSize, style); - if (c >= 32 && c < 128) + /*if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) { - int width = font->glyphWidth[c - 32]; - if (style & FontStyle::Bold) + int line = ((screenHeight - y - 1) * verticalScale); + for (int j = 0; j < verticalScale; j++) { - width++; + lpBitmapBits[(line + j) * screenWidth + x] ^= colour; } - return width; - } - return 0; -} -int WindowsVideoDriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return GetFont(fontSize, style)->glyphHeight + 1; + }*/ } void WindowsVideoDriver::Paint(HWND hwnd) @@ -344,95 +342,18 @@ void WindowsVideoDriver::Paint(HWND hwnd) BITMAP bitmap; GetObject(screenBitmap, sizeof(BITMAP), &bitmap); - BitBlt(hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, - hdcMem, 0, 0, SRCCOPY); + + // Calculate the destination rectangle to stretch the bitmap to fit the window + RECT destRect; + GetClientRect(hwnd, &destRect); + + // Stretch the bitmap to fit the window size + StretchBlt(hdc, 0, 0, destRect.right - destRect.left, destRect.bottom - destRect.top, + hdcMem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY); SelectObject(hdcMem, oldBitmap); DeleteDC(hdcMem); EndPaint(hwnd, &ps); - -/* PAINTSTRUCT ps; - RECT r; - - GetClientRect(hwnd, &r); - - if (r.bottom == 0) { - - return; - } - - HDC hdc = BeginPaint(hwnd, &ps); - - for (int y = 0; y < screenHeight; y++) - { - for (int x = 0; x < screenWidth; x++) - { - uint8_t value = screenPixels[y * screenWidth + x]; - SetPixel(hdc, x, y * 2, value ? RGB(255, 255, 255) : RGB(0, 0, 0)); - SetPixel(hdc, x, y * 2 + 1, value ? RGB(255, 255, 255) : RGB(0, 0, 0)); - } - } - - EndPaint(hwnd, &ps);*/ - - -} - -void WindowsVideoDriver::ArrangeAppInterfaceWidgets(AppInterface& app) -{ - app.addressBar.x = ADDRESS_BAR_X; - app.addressBar.y = ADDRESS_BAR_Y; - app.addressBar.width = ADDRESS_BAR_WIDTH; - app.addressBar.height = ADDRESS_BAR_HEIGHT; - - app.scrollBar.x = SCREEN_WIDTH - SCROLL_BAR_WIDTH; - app.scrollBar.y = WINDOW_TOP; - app.scrollBar.width = SCROLL_BAR_WIDTH; - app.scrollBar.height = WINDOW_HEIGHT; - - app.backButton.x = BACK_BUTTON_X; - app.backButton.y = ADDRESS_BAR_Y; - app.backButton.width = NAVIGATION_BUTTON_WIDTH; - app.backButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.forwardButton.x = FORWARD_BUTTON_X; - app.forwardButton.y = ADDRESS_BAR_Y; - app.forwardButton.width = NAVIGATION_BUTTON_WIDTH; - app.forwardButton.height = NAVIGATION_BUTTON_HEIGHT; - - app.statusBar.x = 0; - app.statusBar.y = SCREEN_HEIGHT - STATUS_BAR_HEIGHT; - app.statusBar.width = SCREEN_WIDTH; - app.statusBar.height = STATUS_BAR_HEIGHT; - - app.titleBar.x = 0; - app.titleBar.y = 0; - app.titleBar.width = SCREEN_WIDTH; - app.titleBar.height = TITLE_BAR_HEIGHT; } -void WindowsVideoDriver::InvertRect(int x, int y, int width, int height) -{ - if (y + height < scissorY1) - return; - if (y >= scissorY2) - return; - if (y < scissorY1) - { - height -= (scissorY1 - y); - y = scissorY1; - } - if (y + height >= scissorY2) - { - height = scissorY2 - y - 1; - } - - for (int j = 0; j < height; j++) - { - for (int i = 0; i < width; i++) - { - InvertPixel(x + i, y + j, 0xffffffff); - } - } -} diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index ef82b54..d30d832 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -17,50 +17,28 @@ #include "../Platform.h" #include +class DrawSurface; + class WindowsVideoDriver : public VideoDriver { public: WindowsVideoDriver(); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); - virtual void ArrangeAppInterfaceWidgets(class AppInterface& app); virtual void ClearScreen(); - virtual void ClearWindow(); - virtual void ClearRect(int x, int y, int width, int height); - virtual void FillRect(int x, int y, int width, int height); - virtual void InvertRect(int x, int y, int width, int height); - virtual void ScrollWindow(int delta); - virtual void SetScissorRegion(int y1, int y2); - virtual void ClearScissorRegion(); - - virtual void DrawString(const char* text, int x, int y, int size = 1, FontStyle::Type style = FontStyle::Regular); - virtual void DrawScrollBar(int position, int size); - virtual void DrawImage(struct Image* image, int x, int y) {} - - virtual MouseCursorData* GetCursorGraphic(MouseCursor::Type type); - - virtual Font* GetFont(int fontSize, FontStyle::Type style = FontStyle::Regular); - virtual int GetGlyphWidth(char c, int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - virtual int GetLineHeight(int fontSize = 1, FontStyle::Type style = FontStyle::Regular); - - virtual void HLine(int x, int y, int count); - virtual void VLine(int x, int y, int count); - void Paint(HWND hwnd); - int verticalScale; + float verticalScale; private: void SetPixel(int x, int y, uint32_t colour); void InvertPixel(int x, int y, uint32_t colour); - void FillRect(int x, int y, int width, int height, uint32_t colour); + BITMAPINFO* bitmapInfo; uint32_t* lpBitmapBits; HBITMAP screenBitmap; - uint32_t foregroundColour, backgroundColour; - int scissorX1, scissorY1, scissorX2, scissorY2; }; diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 72d0787..942c914 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -14,16 +14,129 @@ #include #include +#include + +#include "../src/DataPack.h" using namespace std; -void EncodeImage(const char* imageFilename, ofstream& outputFile, const char* varName); -void EncodeFont(const char* imageFilename, ofstream& outputFile, const char* varName); -void EncodeCursor(const char* imageFilename, ofstream& outputFile, const char* varName, int hotSpotX, int hotSpotY); -void GenerateDummyFont(ofstream& outputFile, const char* varName); +//void EncodeImage(const char* imageFilename, ofstream& outputFile, const char* varName); +//void EncodeFont(const char* imageFilename, ofstream& outputFile, const char* varName); +//void EncodeCursor(const char* imageFilename, ofstream& outputFile, const char* varName, int hotSpotX, int hotSpotY); +//void GenerateDummyFont(ofstream& outputFile, const char* varName); + +void EncodeFont(const char* basePath, const char* name, vector& output, bool generateBold = true); +void EncodeCursor(const char* basePath, const char* name, vector& output); +void EncodeImage(const char* basePath, const char* name, vector& output); + +void GeneratePaletteLUTs(const char* filename); + + +void AddEntryHeader(const char* name, vector& entries, vector& data) +{ + DataPackEntry entry; + memset(entry.name, 0, 8); + strncpy_s(entry.name, name, 8); + entry.offset = (uint32_t)(data.size()); + entries.push_back(entry); +} + +void GenerateAssetPack(const char* name) +{ + char basePath[256]; + vector data; + vector entries; + + snprintf(basePath, 256, "assets/%s/", name); + + AddEntryHeader("FHELV1", entries, data); + EncodeFont(basePath, "Helv1.png", data, false); + AddEntryHeader("FHELV2", entries, data); + EncodeFont(basePath, "Helv2.png", data, false); + AddEntryHeader("FHELV3", entries, data); + EncodeFont(basePath, "Helv3.png", data, false); + + //AddEntryHeader("FHELV1B", entries, data); + //EncodeFont(basePath, "Helv1.png", data, true); + //AddEntryHeader("FHELV2B", entries, data); + //EncodeFont(basePath, "Helv2.png", data, true); + //AddEntryHeader("FHELV3B", entries, data); + //EncodeFont(basePath, "Helv3.png", data, true); + + AddEntryHeader("FCOUR1", entries, data); + EncodeFont(basePath, "Cour1.png", data, false); + AddEntryHeader("FCOUR2", entries, data); + EncodeFont(basePath, "Cour2.png", data, false); + AddEntryHeader("FCOUR3", entries, data); + EncodeFont(basePath, "Cour3.png", data, false); + + //AddEntryHeader("FCOUR1B", entries, data); + //EncodeFont(basePath, "Cour1.png", data, true); + //AddEntryHeader("FCOUR2B", entries, data); + //EncodeFont(basePath, "Cour2.png", data, true); + //AddEntryHeader("FCOUR3B", entries, data); + //EncodeFont(basePath, "Cour3.png", data, true); + + AddEntryHeader("CMOUSE", entries, data); + EncodeCursor(basePath, "mouse.png", data); + AddEntryHeader("CLINK", entries, data); + EncodeCursor(basePath, "mouse-link.png", data); + AddEntryHeader("CTEXT", entries, data); + EncodeCursor(basePath, "mouse-select.png", data); + + AddEntryHeader("IIMG", entries, data); + EncodeImage(basePath, "image-icon.png", data); + AddEntryHeader("IBROKEN", entries, data); + EncodeImage(basePath, "broken-image-icon.png", data); + AddEntryHeader("ICHECK1", entries, data); + EncodeImage(basePath, "checkbox.png", data); + AddEntryHeader("ICHECK2", entries, data); + EncodeImage(basePath, "checkbox-ticked.png", data); + AddEntryHeader("IRADIO1", entries, data); + EncodeImage(basePath, "radio.png", data); + AddEntryHeader("IRADIO2", entries, data); + EncodeImage(basePath, "radio-selected.png", data); + AddEntryHeader("IDOWN", entries, data); + EncodeImage(basePath, "down-icon.png", data); + + AddEntryHeader("END", entries, data); + + char outputPath[256]; + snprintf(outputPath, 256, "%s.dat", name); + FILE* fs; + + fopen_s(&fs, outputPath, "wb"); + if (fs) + { + uint16_t numEntries = (uint16_t)(entries.size()); + for (int n = 0; n < entries.size(); n++) + { + entries[n].offset += sizeof(uint16_t) + sizeof(DataPackEntry) * numEntries; + } + + fwrite(&numEntries, sizeof(uint16_t), 1, fs); + fwrite(entries.data(), sizeof(DataPackEntry), numEntries, fs); + + fwrite(data.data(), 1, data.size(), fs); + + fclose(fs); + } +} + +void GenerateAssetPacks() +{ + GenerateAssetPack("CGA"); + GenerateAssetPack("EGA"); + GenerateAssetPack("LowRes"); + GenerateAssetPack("Default"); +} int main(int argc, char* argv) { + GenerateAssetPacks(); + + GeneratePaletteLUTs("src/Palettes.inc"); +#if 0 // CGA resources { ofstream outputFile("src/DOS/CGAData.inc"); @@ -32,13 +145,21 @@ int main(int argc, char* argv) outputFile << "// This file is auto generated" << endl << endl; outputFile << "// Fonts:" << endl; - EncodeFont("assets/CGA/font.png", outputFile, "CGA_RegularFont"); - EncodeFont("assets/CGA/font-small.png", outputFile, "CGA_SmallFont"); - EncodeFont("assets/CGA/font-large.png", outputFile, "CGA_LargeFont"); - - EncodeFont("assets/CGA/font-mono.png", outputFile, "CGA_RegularFont_Monospace"); - EncodeFont("assets/CGA/font-mono-small.png", outputFile, "CGA_SmallFont_Monospace"); - EncodeFont("assets/CGA/font-mono-large.png", outputFile, "CGA_LargeFont_Monospace"); + EncodeFont("assets/CGA/Helv2.png", outputFile, "CGA_RegularFont"); + EncodeFont("assets/CGA/Helv1.png", outputFile, "CGA_SmallFont"); + EncodeFont("assets/CGA/Helv3.png", outputFile, "CGA_LargeFont"); + + EncodeFont("assets/CGA/Cour2.png", outputFile, "CGA_RegularFont_Monospace"); + EncodeFont("assets/CGA/Cour1.png", outputFile, "CGA_SmallFont_Monospace"); + EncodeFont("assets/CGA/Cour3.png", outputFile, "CGA_LargeFont_Monospace"); + + //EncodeFont("assets/LowRes/Helv2.png", outputFile, "CGA_RegularFont"); + //EncodeFont("assets/LowRes/Helv1.png", outputFile, "CGA_SmallFont"); + //EncodeFont("assets/LowRes/Helv3.png", outputFile, "CGA_LargeFont"); + // + //EncodeFont("assets/LowRes/Cour2.png", outputFile, "CGA_RegularFont_Monospace"); + //EncodeFont("assets/LowRes/Cour1.png", outputFile, "CGA_SmallFont_Monospace"); + //EncodeFont("assets/LowRes/Cour3.png", outputFile, "CGA_LargeFont_Monospace"); outputFile << endl; outputFile << "// Mouse cursors:" << endl; @@ -64,13 +185,13 @@ int main(int argc, char* argv) outputFile << "// This file is auto generated" << endl << endl; outputFile << "// Fonts:" << endl; - EncodeFont("assets/Default/font.png", outputFile, "Default_RegularFont"); - EncodeFont("assets/Default/font-small.png", outputFile, "Default_SmallFont"); - EncodeFont("assets/Default/font-large.png", outputFile, "Default_LargeFont"); + EncodeFont("assets/Default/Helv2.png", outputFile, "Default_RegularFont"); + EncodeFont("assets/Default/Helv1.png", outputFile, "Default_SmallFont"); + EncodeFont("assets/Default/Helv3.png", outputFile, "Default_LargeFont"); - EncodeFont("assets/Default/font-mono.png", outputFile, "Default_RegularFont_Monospace"); - EncodeFont("assets/Default/font-mono-small.png", outputFile, "Default_SmallFont_Monospace"); - EncodeFont("assets/Default/font-mono-large.png", outputFile, "Default_LargeFont_Monospace"); + EncodeFont("assets/Default/Cour2.png", outputFile, "Default_RegularFont_Monospace"); + EncodeFont("assets/Default/Cour1.png", outputFile, "Default_SmallFont_Monospace"); + EncodeFont("assets/Default/Cour3.png", outputFile, "Default_LargeFont_Monospace"); outputFile << endl; outputFile << "// Mouse cursors:" << endl; @@ -100,6 +221,6 @@ int main(int argc, char* argv) outputFile << endl; outputFile.close(); } - +#endif return 0; } diff --git a/tools/FontGen.cpp b/tools/FontGen.cpp index 614f68b..f25d6e3 100644 --- a/tools/FontGen.cpp +++ b/tools/FontGen.cpp @@ -22,7 +22,17 @@ using namespace std; -void EncodeFont(const char* imageFilename, ofstream& outputFile, const char* varName) +/* +struct FontMetaData +{ + uint8_t glyphWidth[256 - 32]; + uint8_t glyphWidthBytes; + uint8_t glyphHeight; + uint8_t glyphDataStride; +}; +*/ + +void EncodeFontOld(const char* imageFilename, ofstream& outputFile, const char* varName) { vector data; unsigned width, height; @@ -45,7 +55,7 @@ void EncodeFont(const char* imageFilename, ofstream& outputFile, const char* var for(unsigned int x = 0; x < width; x++) { vector columnBuffer; - + for(unsigned int y = 0; y < height; y++) { int index = (y * width + x) * 4; @@ -178,6 +188,156 @@ void EncodeFont(const char* imageFilename, ofstream& outputFile, const char* var outputFile << "};" << endl << endl; } +void EncodeFont(const char* imageFilename, ofstream& outputFile, const char* varName) +{ + vector data; + unsigned width, height; + unsigned error = lodepng::decode(data, width, height, imageFilename); + + if (error) + { + cerr << "Error loading " << imageFilename << endl; + return; + } + + vector output; + vector glyphWidths; + vector glyphBuffer; + vector offsets; + + unsigned glyphHeight = height - 1; + int charIndex = 0; + + for (unsigned int x = 1; x < width; x++) + { + vector columnBuffer; + + if (glyphWidths.size() >= (128 - 32)) + { + break; + } + + int checkIndex = x * 4; + if (data[checkIndex] > 0) + { + // This is a break for the next glyph + uint16_t offset = (uint16_t)(output.size()); + offsets.push_back(offset); + glyphWidths.push_back((uint8_t)(glyphBuffer.size() / glyphHeight)); + output.insert(output.end(), glyphBuffer.begin(), glyphBuffer.end()); + glyphBuffer.clear(); + + continue; + } + + for (unsigned int y = 0; y < glyphHeight; y++) + { + int index = ((y + 1) * width + x) * 4; + unsigned col = data[index] | (data[index + 1] << 8) | (data[index + 2] << 16); + + if (col > 0) + { + columnBuffer.push_back(1); + } + else + { + columnBuffer.push_back(0); + } + } + glyphBuffer.insert(glyphBuffer.end(), columnBuffer.begin(), columnBuffer.end()); + } + + + cout << "Font line height: " << glyphHeight << endl; + cout << "Num glyphs: " << offsets.size() << endl; + + //for(int n = 0; n < offsets.size(); n++) + //{ + // char c = (char) n + 32; + // cout << c << " : offset " << offsets[n] << " width: " << (int) glyphWidths[n] << endl; + //} + + + int requiredBytes = 0; + + for (int n = 0; n < glyphWidths.size(); n++) + { + int width = glyphWidths[n]; + int needed = width / 8; + if (width % 8) + { + needed++; + } + if (needed > requiredBytes) + { + requiredBytes = needed; + } + } + + outputFile << "static unsigned char " << varName << "_Data[] = {" << endl; + + for (int n = 0; n < offsets.size(); n++) + { + char c = (char)(n + 32); + outputFile << "\t// '" << c << "'" << endl; + outputFile << "\t"; + + int glyphWidth = glyphWidths[n]; + for (unsigned int y = 0; y < glyphHeight; y++) + { + int x = 0; + + for (int b = 0; b < requiredBytes; b++) + { + int mask = 0; + + for (int z = 0; z < 8; z++) + { + if (x < glyphWidth) + { + int index = offsets[n] + (x * glyphHeight + y); + if (output[index]) + { + mask |= (0x80 >> z); + } + } + + x++; + } + + outputFile << "0x" << setfill('0') << setw(2) << hex << mask << ", "; + } + } + outputFile << endl; + } + + outputFile << dec; + + outputFile << "};" << endl; + outputFile << endl; + + outputFile << "Font " << varName << " = {" << endl; + outputFile << "\t// Glyph widths" << endl; + outputFile << "\t{ "; + + for (int n = 0; n < glyphWidths.size(); n++) + { + int width = glyphWidths[n]; + outputFile << width; + if (n != glyphWidths.size() - 1) + { + outputFile << ","; + } + } + outputFile << "}, " << endl; + outputFile << "\t" << requiredBytes << ", \t// Byte width" << endl; + outputFile << "\t" << glyphHeight << ", \t// Glyph height" << endl; + int stride = requiredBytes * glyphHeight; + outputFile << "\t" << stride << ", \t// Glyph stride" << endl; + outputFile << "\t" << varName << "_Data" << endl; + outputFile << "};" << endl << endl; +} + void GenerateDummyFont(ofstream& outputFile, const char* varName) { @@ -201,3 +361,198 @@ void GenerateDummyFont(ofstream& outputFile, const char* varName) outputFile << "\t" << "NULL" << endl; outputFile << "};" << endl << endl; } + +void EncodeFont(const char* basePath, const char* imageFilename, vector& outputStream, bool generateBold) +{ + char imageFilePath[256]; + snprintf(imageFilePath, 256, "%s%s", basePath, imageFilename); + vector data; + unsigned width, height; + unsigned error = lodepng::decode(data, width, height, imageFilePath); + + if (error) + { + cerr << "Error loading " << imageFilePath << endl; + return; + } + + vector output; + vector glyphWidths; + vector glyphBuffer; + vector offsets; + + unsigned glyphHeight = height - 1; + int charIndex = 0; + + for (unsigned int x = 1; x < width; x++) + { + vector columnBuffer; + + if (glyphWidths.size() >= (256 - 32)) + { + break; + } + + int checkIndex = x * 4; + if (data[checkIndex] > 0) + { + if (generateBold) + { + for (unsigned int y = 0; y < glyphHeight; y++) + { + int prevIndex = ((y + 1) * width + x - 1) * 4; + unsigned col = data[prevIndex] | (data[prevIndex + 1] << 8) | (data[prevIndex + 2] << 16); + + if (col > 0) + { + columnBuffer.push_back(1); + } + else + { + columnBuffer.push_back(0); + } + } + glyphBuffer.insert(glyphBuffer.end(), columnBuffer.begin(), columnBuffer.end()); + } + + // This is a break for the next glyph + uint16_t offset = (uint16_t)(output.size()); + offsets.push_back(offset); + glyphWidths.push_back((uint8_t)(glyphBuffer.size() / glyphHeight)); + output.insert(output.end(), glyphBuffer.begin(), glyphBuffer.end()); + glyphBuffer.clear(); + + continue; + } + + for (unsigned int y = 0; y < glyphHeight; y++) + { + int index = ((y + 1) * width + x) * 4; + unsigned col = data[index] | (data[index + 1] << 8) | (data[index + 2] << 16); + + if (generateBold && !col && x > 0) + { + int prevIndex = ((y + 1) * width + x - 1) * 4; + col = data[prevIndex] | (data[prevIndex + 1] << 8) | (data[prevIndex + 2] << 16); + } + + if (col > 0) + { + columnBuffer.push_back(1); + } + else + { + columnBuffer.push_back(0); + } + } + glyphBuffer.insert(glyphBuffer.end(), columnBuffer.begin(), columnBuffer.end()); + } + + + cout << "Font line height: " << glyphHeight << endl; + cout << "Num glyphs: " << offsets.size() << endl; + + //for(int n = 0; n < offsets.size(); n++) + //{ + // char c = (char) n + 32; + // cout << c << " : offset " << offsets[n] << " width: " << (int) glyphWidths[n] << endl; + //} + + + /* + int requiredBytes = 0; + + for (int n = 0; n < glyphWidths.size(); n++) + { + int width = glyphWidths[n]; + int needed = width / 8; + if (width % 8) + { + needed++; + } + if (needed > requiredBytes) + { + requiredBytes = needed; + } + } + */ + + + // Output the glyph data + vector outputOffsets; + vector outputData; + int count = 0; + + for (int n = 0; n < offsets.size(); n++) + { + outputOffsets.push_back(count); + + int glyphWidth = glyphWidths[n]; + for (unsigned int y = 0; y < glyphHeight; y++) + { + int x = 0; + + int glyphWidthBytes = (glyphWidth + 7) / 8; + + for (int b = 0; b < glyphWidthBytes; b++) + { + int mask = 0; + + for (int z = 0; z < 8; z++) + { + if (x < glyphWidth) + { + int index = offsets[n] + (x * glyphHeight + y); + if (output[index]) + { + mask |= (0x80 >> z); + } + } + + x++; + } + + outputData.push_back((uint8_t)mask); + count++; + } + } + } + + + // Output the metadata + // struct Glyph + // { + // uint8_t width; + // uint16_t offset; + // }; + // + // Glyph glyphs[NUM_GLYPH_ENTRIES]; + // uint8_t glyphHeight; + + const int numNeededGlyphs = 256 - 32; + + for (int n = 0; n < numNeededGlyphs; n++) + { + if (n < glyphWidths.size()) + { + outputStream.push_back(glyphWidths[n]); + uint16_t offset = outputOffsets[n]; + outputStream.push_back(offset & 0xff); + outputStream.push_back(offset >> 8); + } + else + { + outputStream.push_back(0); + outputStream.push_back(0); + outputStream.push_back(0); + } + } + + outputStream.push_back(glyphHeight); + + for (int n = 0; n < outputData.size(); n++) + { + outputStream.push_back(outputData[n]); + } + +} diff --git a/tools/ImageGen.cpp b/tools/ImageGen.cpp index ecc142e..7a1f986 100644 --- a/tools/ImageGen.cpp +++ b/tools/ImageGen.cpp @@ -90,3 +90,58 @@ void EncodeImage(const char* imageFilename, ofstream& outputFile, const char* va outputFile << "};" << endl << endl; } +void WriteOutput(vector& outputData, uint16_t x) +{ + outputData.push_back((uint8_t)(x & 0xff)); + outputData.push_back((uint8_t)(x >> 8)); +} + +void EncodeImage(const char* basePath, const char* imageFilename, vector& outputData) +{ + char imageFilePath[256]; + snprintf(imageFilePath, 256, "%s%s", basePath, imageFilename); + + vector data; + unsigned width, height; + unsigned error = lodepng::decode(data, width, height, imageFilePath); + + if (error) + { + cerr << "Error loading " << imageFilePath << endl; + return; + } + + uint8_t bpp = 1; + + WriteOutput(outputData, (uint16_t) width); + WriteOutput(outputData, (uint16_t)height); + WriteOutput(outputData, (uint16_t)(width + 7) / 8); // pitch + WriteOutput(outputData, bpp); + + for (unsigned y = 0; y < height; y++) + { + uint8_t buffer = 0; + int bufferMask = 0x80; + + for (unsigned x = 0; x < width; x++) + { + uint8_t r = data[(y * width + x) * 4]; + if (r > 128) + { + buffer |= bufferMask; + } + + bufferMask >>= 1; + if (bufferMask == 0) + { + outputData.push_back(buffer); + bufferMask = 0x80; + buffer = 0; + } + } + if (bufferMask != 0x80) + { + outputData.push_back(buffer); + } + } +} diff --git a/tools/MouseGen.cpp b/tools/MouseGen.cpp index ef95461..bf65c4f 100644 --- a/tools/MouseGen.cpp +++ b/tools/MouseGen.cpp @@ -183,3 +183,93 @@ void EncodeCursor(vector& outputData, const char* imageFilename, outputData.push_back((uint8_t)(hotSpotY & 0xff)); outputData.push_back((uint8_t)(hotSpotX >> 8)); } + +void EncodeCursor(const char* basePath, const char* imageFilename, vector& outputData) +{ + char imageFilePath[256]; + snprintf(imageFilePath, 256, "%s%s", basePath, imageFilename); + + vector data; + unsigned width, height; + unsigned error = lodepng::decode(data, width, height, imageFilePath); + int hotSpotX = 0; + int hotSpotY = 0; + + if (error) + { + cerr << "Error loading " << imageFilePath << endl; + return; + } + + if (width != 16 || height != 16) + { + cerr << "Cursor must be 16x16" << endl; + return; + } + + vector maskData; + vector colourData; + + for (int y = 0; y < 16; y++) + { + for (int x = 0; x < 16; x++) + { + int index = (y * width + x) * 4; + unsigned col = data[index]; + + if (data[index] > 0 && data[index + 1] == 0) + { + hotSpotX = x; + hotSpotY = y; + } + + + if (data[index + 3] == 0) + { + maskData.push_back(1); + colourData.push_back(0); + } + else + { + maskData.push_back(0); + colourData.push_back(col < 127 ? 0 : 1); + } + + } + } + + uint16_t output = 0; + for (int n = 0; n < 256; n++) + { + if (maskData[n] != 0) + { + output |= (0x8000 >> (n % 16)); + } + if ((n % 16) == 15) + { + outputData.push_back((uint8_t)(output & 0xff)); + outputData.push_back((uint8_t)(output >> 8)); + output = 0; + } + } + + output = 0; + for (int n = 0; n < 256; n++) + { + if (colourData[n] != 0) + { + output |= (0x8000 >> (n % 16)); + } + if ((n % 16) == 15) + { + outputData.push_back((uint8_t)(output & 0xff)); + outputData.push_back((uint8_t)(output >> 8)); + output = 0; + } + } + + outputData.push_back((uint8_t)(hotSpotX & 0xff)); + outputData.push_back((uint8_t)(hotSpotX >> 8)); + outputData.push_back((uint8_t)(hotSpotY & 0xff)); + outputData.push_back((uint8_t)(hotSpotX >> 8)); +} diff --git a/tools/PaletteGen.cpp b/tools/PaletteGen.cpp new file mode 100644 index 0000000..0f9db0d --- /dev/null +++ b/tools/PaletteGen.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include + +const RGBQUAD egaPalette[] = +{ + { 0x00, 0x00, 0x00 }, // Entry 0 - Black + { 0xAA, 0x00, 0x00 }, // Entry 1 - Blue + { 0x00, 0xAA, 0x00 }, // Entry 2 - Green + { 0xAA, 0xAA, 0x00 }, // Entry 3 - Cyan + { 0x00, 0x00, 0xAA }, // Entry 4 - Red + { 0xAA, 0x00, 0xAA }, // Entry 5 - Magenta + { 0x00, 0x55, 0xAA }, // Entry 6 - Brown + { 0xAA, 0xAA, 0xAA }, // Entry 7 - Light Gray + { 0x55, 0x55, 0x55 }, // Entry 8 - Dark Gray + { 0xFF, 0x55, 0x55 }, // Entry 9 - Light Blue + { 0x55, 0xFF, 0x55 }, // Entry 10 - Light Green + { 0xFF, 0xFF, 0x55 }, // Entry 11 - Light Cyan + { 0x55, 0x55, 0xFF }, // Entry 12 - Light Red + { 0xFF, 0x55, 0xFF }, // Entry 13 - Light Magenta + { 0x55, 0xFF, 0xFF }, // Entry 14 - Yellow + { 0xFF, 0xFF, 0xFF }, // Entry 15 - White +}; + +const RGBQUAD cgaPalette[] = +{ + { 0x00, 0x00, 0x00 }, // Entry 0 - Black + { 0xFF, 0xFF, 0x55 }, // Entry 11 - Light Cyan + { 0x55, 0x55, 0xFF }, // Entry 12 - Light Red + { 0xFF, 0xFF, 0xFF }, // Entry 15 - White +}; + +const RGBQUAD cgaCompositePalette[] = +{ + { 0x00, 0x00, 0x00 }, + { 0x31, 0x6e, 0x00 }, + { 0xff, 0x09, 0x31 }, + { 0xff, 0x8a, 0x00 }, + { 0x31, 0x00, 0xa7 }, + { 0x76, 0x76, 0x76 }, + { 0xff, 0x11, 0xec }, + { 0xff, 0x92, 0xbb }, + { 0x00, 0x5a, 0x31 }, + { 0x00, 0xdb, 0x00 }, + { 0x76, 0x76, 0x76 }, + { 0xbb, 0xf7, 0x45 }, + { 0x00, 0x63, 0xec }, + { 0x00, 0xe4, 0xbb }, + { 0xbb, 0x7f, 0xff }, + { 0xff, 0xff, 0xff }, +}; + +// Function to convert sRGB to linear RGB +double sRGBtoLinear(double value) { + if (value <= 0.04045) + return value / 12.92; + else + return pow((value + 0.055) / 1.055, 2.4); +} + +// Function to convert linear RGB to XYZ +double linearRGBtoXYZ(double value) { + if (value > 0.04045) + return pow((value + 0.055) / 1.055, 2.4); + else + return value / 12.92; +} + +// Function to convert RGB to XYZ +void RGBtoXYZ(double R, double G, double B, double& X, double& Y, double& Z) { + R = sRGBtoLinear(R / 255.0); + G = sRGBtoLinear(G / 255.0); + B = sRGBtoLinear(B / 255.0); + + X = 0.4124564 * R + 0.3575761 * G + 0.1804375 * B; + Y = 0.2126729 * R + 0.7151522 * G + 0.0721750 * B; + Z = 0.0193339 * R + 0.1191920 * G + 0.9503041 * B; +} + +// Function to convert XYZ to CIELAB +void XYZtoLAB(double X, double Y, double Z, double& L, double& a, double& b) { + X /= 0.95047; + Y /= 1.0; + Z /= 1.08883; + + if (X > 0.008856) + X = pow(X, 1.0 / 3.0); + else + X = 7.787 * X + 16.0 / 116.0; + if (Y > 0.008856) + Y = pow(Y, 1.0 / 3.0); + else + Y = 7.787 * Y + 16.0 / 116.0; + if (Z > 0.008856) + Z = pow(Z, 1.0 / 3.0); + else + Z = 7.787 * Z + 16.0 / 116.0; + + L = 116.0 * Y - 16.0; + a = 500.0 * (X - Y); + b = 200.0 * (Y - Z); +} + +// Function to compute distance between two CIELAB colors +double labDistance(double L1, double a1, double b1, double L2, double a2, double b2) { + double deltaL = L1 - L2; + double deltaA = a1 - a2; + double deltaB = b1 - b2; + return sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB); +} + +// Function to compute distance between two RGB colors using CIELAB +double rgbDistance(int R1, int G1, int B1, int R2, int G2, int B2) { + double X1, Y1, Z1, X2, Y2, Z2; + RGBtoXYZ(R1, G1, B1, X1, Y1, Z1); + RGBtoXYZ(R2, G2, B2, X2, Y2, Z2); + + double L1, a1, b1, L2, a2, b2; + XYZtoLAB(X1, Y1, Z1, L1, a1, b1); + XYZtoLAB(X2, Y2, Z2, L2, a2, b2); + + return labDistance(L1, a1, b1, L2, a2, b2); +} + +int GetClosestPaletteIndex(const RGBQUAD colour, const RGBQUAD* palette, int paletteSize) +{ + int closest = -1; + double closestDistance = 0; + bool useLABDistance = paletteSize < 16; + + for (int n = 0; n < paletteSize; n++) + { + double distance = 0; + + if (useLABDistance) + { + // LAB colour distance + distance = rgbDistance(colour.rgbRed, colour.rgbGreen, colour.rgbBlue, palette[n].rgbRed, palette[n].rgbGreen, palette[n].rgbBlue); + } + else + { + // RGB Euclidean distance + distance += (palette[n].rgbRed - colour.rgbRed) * (palette[n].rgbRed - colour.rgbRed); + distance += (palette[n].rgbGreen - colour.rgbGreen) * (palette[n].rgbGreen - colour.rgbGreen); + distance += (palette[n].rgbBlue - colour.rgbBlue) * (palette[n].rgbBlue - colour.rgbBlue); + } + + if (closest == -1 || distance < closestDistance) + { + closest = n; + closestDistance = distance; + } + } + + return closest; +} + +void GeneratePaletteLUT(FILE* fs, const char* name, const RGBQUAD* palette, int paletteSize, bool fillByte) +{ + RGBQUAD colour; + int count = 0; + + fprintf(fs, "uint8_t %s[] = {\n\t", name); + + for (int n = 0; n < 256; n++) + { + int r = (n & 0xe0); + int g = (n & 0x1c) << 3; + int b = (n & 3) << 6; + + colour.rgbBlue = (b * 255) / 0xc0; + colour.rgbGreen = (g * 255) / 0xe0; + colour.rgbRed = (r * 255) / 0xe0; + + int index = GetClosestPaletteIndex(colour, palette, paletteSize); + + if (fillByte) + { + if (paletteSize == 4) + { + index |= (index << 2); + } + //index |= (index << 4); + } + + fprintf(fs, "%d", index); + if (count != 255) + { + fprintf(fs, ", "); + if ((count % 16) == 15) + { + fprintf(fs, "\n\t"); + } + } + + count++; + } + + fprintf(fs, "\n};\n"); +} + +void GeneratePaletteLUTs(const char* filename) +{ + FILE* fs; + + if (!fopen_s(&fs, filename, "w")) + { + GeneratePaletteLUT(fs, "egaPaletteLUT", egaPalette, 16, false); + GeneratePaletteLUT(fs, "cgaPaletteLUT", cgaPalette, 4, true); + GeneratePaletteLUT(fs, "compositeCgaPaletteLUT", cgaCompositePalette, 16, true); + + fclose(fs); + } +}