From be6e69ed085df0fd7a2c7b55039a935c24bb3500 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Jul 2022 09:32:07 +0100 Subject: [PATCH 01/98] First pass refactoring to use node hierarchy to store page data --- src/App.cpp | 4 +-- src/Defines.h | 7 +++++ src/LinAlloc.h | 25 ++++++++++++++--- src/Node.cpp | 40 ++++++++++++++++++++++++++++ src/Node.h | 62 +++++++++++++++++++++++++++++++++++++++++++ src/Nodes/Section.cpp | 13 +++++++++ src/Nodes/Section.h | 29 ++++++++++++++++++++ src/Nodes/Text.cpp | 24 +++++++++++++++++ src/Nodes/Text.h | 17 ++++++++++++ src/Page.cpp | 3 +++ src/Page.h | 5 ++++ src/Parser.cpp | 47 ++++++++++++++++++++++++++++++++ src/Parser.h | 18 +++++++++++++ src/Tags.cpp | 23 ++++++++++------ src/Tags.h | 5 ++-- 15 files changed, 307 insertions(+), 15 deletions(-) create mode 100644 src/Defines.h create mode 100644 src/Node.cpp create mode 100644 src/Node.h create mode 100644 src/Nodes/Section.cpp create mode 100644 src/Nodes/Section.h create mode 100644 src/Nodes/Text.cpp create mode 100644 src/Nodes/Text.h diff --git a/src/App.cpp b/src/App.cpp index d93f99b..fbf4a1b 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -168,7 +168,7 @@ void LoadTask::Stop() if (fs) { fclose(fs); - fs = NULL; + fs = nullptr; } break; case LoadTask::RemoteFile: @@ -176,7 +176,7 @@ void LoadTask::Stop() { request->Stop(); Platform::network->DestroyRequest(request); - request = NULL; + request = nullptr; } break; } diff --git a/src/Defines.h b/src/Defines.h new file mode 100644 index 0000000..332048e --- /dev/null +++ b/src/Defines.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef __DOS__ +// Define modern C++ keywords that aren't supported by OpenWatcom +#define override +#define nullptr NULL +#endif diff --git a/src/LinAlloc.h b/src/LinAlloc.h index aad96c5..826e490 100644 --- a/src/LinAlloc.h +++ b/src/LinAlloc.h @@ -125,8 +125,26 @@ class LinearAllocator return new (Alloc(sizeof(T))) T(); } - uint32_t TotalAllocated() { return (uint32_t)numAllocatedChunks * sizeof(Chunk); } - uint32_t TotalUsed() { return totalBytesUsed; } + template + T* Alloc(A a) + { + return new (Alloc(sizeof(T))) T(a); + } + + template + T* Alloc(A a, B b) + { + return new (Alloc(sizeof(T))) T(a, b); + } + + template + T* Alloc(A a, B b, C c) + { + return new (Alloc(sizeof(T))) T(a, b, c); + } + + size_t TotalAllocated() { return numAllocatedChunks * sizeof(Chunk); } + size_t TotalUsed() { return totalBytesUsed; } AllocationError GetError() { return errorFlag; } private: @@ -136,6 +154,7 @@ class LinearAllocator size_t allocOffset; size_t numAllocatedChunks; - uint32_t totalBytesUsed; // Bytes actually used for data + size_t totalBytesUsed; // Bytes actually used for data AllocationError errorFlag; }; + diff --git a/src/Node.cpp b/src/Node.cpp new file mode 100644 index 0000000..338f915 --- /dev/null +++ b/src/Node.cpp @@ -0,0 +1,40 @@ +#include "Page.h" +#include "Node.h" +#include "Nodes/Text.h" +#include "Nodes/Section.h" + +NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = +{ + new SectionElement(), + new TextElement() +}; + +Node::Node(Type inType, void* inData) + : type(inType) + , style(0) + , parent(nullptr) + , next(nullptr) + , firstChild(nullptr) + , data(inData) +{ + anchor.Clear(); + rect.Clear(); +} + +void Node::AddChild(Node* child) +{ + child->parent = this; + + if(!firstChild) + { + firstChild = child; + } + else + { + Node* lastChild; + for(lastChild = firstChild; lastChild->next; lastChild = lastChild->next) {} + lastChild->next = child; + } +} + + diff --git a/src/Node.h b/src/Node.h new file mode 100644 index 0000000..35aba2b --- /dev/null +++ b/src/Node.h @@ -0,0 +1,62 @@ +#pragma once +#include + +class Page; +class Node; +typedef class LinearAllocator Allocator; + +typedef uint16_t ElementStyle; + +class NodeHandler +{ +public: + virtual void Draw(Page& page, Node* element) = 0; +}; + +struct Coord +{ + int16_t x, y; + + void Clear() { x = y = 0; } +}; + +struct Rect +{ + int16_t x, y, width, height; + + void Clear() { x = y = width = height = 0; } +}; + +class Node +{ +public: + enum Type + { + Section, + Text, + NumNodeTypes + }; + + NodeHandler& Handler() const + { + return *nodeHandlers[type]; + } + + Node(Type inType, void* inData); + void AddChild(Node* child); + + Type type; + + ElementStyle style; + Coord anchor; + Rect rect; + + Node* parent; + Node* next; + Node* firstChild; + + void* data; + +protected: + static NodeHandler* nodeHandlers[Node::NumNodeTypes]; +}; diff --git a/src/Nodes/Section.cpp b/src/Nodes/Section.cpp new file mode 100644 index 0000000..3746fa8 --- /dev/null +++ b/src/Nodes/Section.cpp @@ -0,0 +1,13 @@ +#include "Section.h" +#include "../LinAlloc.h" + +Node* SectionElement::Construct(Allocator& allocator, SectionElement::Type sectionType) +{ + SectionElement::Data* data = allocator.Alloc(sectionType); + + if (data) + { + return allocator.Alloc(Node::Type::Section, data); + } + return nullptr; +} diff --git a/src/Nodes/Section.h b/src/Nodes/Section.h new file mode 100644 index 0000000..1a9328f --- /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 + }; + + class Data + { + public: + Data(SectionElement::Type inType) : type(inType) { } + SectionElement::Type type; + }; + + static Node* Construct(Allocator& allocator, SectionElement::Type sectionType); + virtual void Draw(Page& page, Node* element) override {} +}; + diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp new file mode 100644 index 0000000..e32f850 --- /dev/null +++ b/src/Nodes/Text.cpp @@ -0,0 +1,24 @@ +#include "../Page.h" +#include "Text.h" +#include "../LinAlloc.h" + +void TextElement::Draw(Page& page, Node* element) +{ + TextElement::Data* data = static_cast(element->data); +} + + +Node* TextElement::Construct(Allocator& allocator, const char* text) +{ + const char* textString = allocator.AllocString(text); + if (textString) + { + TextElement::Data* data = allocator.Alloc(textString); + if (data) + { + return allocator.Alloc(Node::Type::Text, data); + } + } + + return nullptr; +} diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h new file mode 100644 index 0000000..1893b0a --- /dev/null +++ b/src/Nodes/Text.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Node.h" + +class TextElement : public NodeHandler +{ +public: + class Data + { + public: + Data(const char* inText) : text(inText) {} + const char* text; + }; + + static Node* Construct(Allocator& allocator, const char* text); + virtual void Draw(Page& page, Node* element) override; +}; diff --git a/src/Page.cpp b/src/Page.cpp index 09d68de..6f50f1b 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -19,6 +19,7 @@ #include "Page.h" #include "App.h" #include "Image.h" +#include "Nodes/Section.h" #define TOP_MARGIN_PADDING 1 @@ -47,6 +48,8 @@ void Page::Reset() styleStackSize = 1; styleStack[0] = WidgetStyle(FontStyle::Regular, 1, false); allocator.Reset(); + + rootNode = SectionElement::Construct(allocator, SectionElement::Document); } WidgetStyle& Page::GetStyleStackTop() diff --git a/src/Page.h b/src/Page.h index 77b2462..ca01f02 100644 --- a/src/Page.h +++ b/src/Page.h @@ -28,6 +28,7 @@ #define WIDGET_INDEX_TO_INDEX_IN_CHUNK(index) ((index) & 0x3f) class App; +class Node; class WidgetContainer { @@ -77,6 +78,8 @@ class Page void SetWidgetURL(const char* url); void ClearWidgetURL(); + Node* GetRootNode() { return rootNode; } + Widget* GetWidget(int x, int y); WidgetStyle& GetStyleStackTop(); @@ -114,6 +117,8 @@ class Page bool needLeadingWhiteSpace; + Node* rootNode; + WidgetContainer widgets; WidgetStyle styleStack[MAX_PAGE_STYLE_STACK_SIZE]; diff --git a/src/Parser.cpp b/src/Parser.cpp index 751be3c..185f255 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -21,10 +21,12 @@ #include "Renderer.h" #include "Page.h" #include "Unicode.inc" +#include "Nodes/Text.h" HTMLParser::HTMLParser(Page& inPage) : page(inPage) , sectionStackSize(0) +, contextStackSize(0) , parseState(ParseText) , textBufferSize(0) , parsingUnicode(false) @@ -40,8 +42,41 @@ void HTMLParser::Reset() sectionStackSize = 0; preformatted = 0; SetTextEncoding(TextEncoding::UTF8); + + contextStackSize = -1; + PushContext(page.GetRootNode(), nullptr); } +void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) +{ + if (contextStackSize < MAX_PARSE_CONTEXT_STACK_SIZE - 1) + { + contextStackSize++; + contextStack[contextStackSize].node = node; + contextStack[contextStackSize].tag = tag; + } + else + { + // TODO: ERROR + } +} + +void HTMLParser::PopContext(const HTMLTagHandler* tag) +{ + for (int n = contextStackSize; n >= 0; n--) + { + // Search for matching tag + if (contextStack[n].tag == tag) + { + contextStackSize = n - 1; + return; + } + } + + // TODO: ERROR +} + + void HTMLParser::PushSection(HTMLParseSection::Type section) { if (sectionStackSize < MAX_PARSE_SECTION_STACK_SIZE - 1) @@ -106,6 +141,16 @@ void HTMLParser::AppendTextBuffer(char c) } } +void HTMLParser::AddTextElement(const char* text) +{ + Node* textElement = TextElement::Construct(page.allocator, text); + if (textElement) + { + textElement->style = CurrentContext().node->style; + CurrentContext().node->AddChild(textElement); + } +} + void HTMLParser::FlushTextBuffer() { textBuffer[textBufferSize] = '\0'; @@ -114,6 +159,8 @@ void HTMLParser::FlushTextBuffer() { case ParseText: { + AddTextElement(textBuffer); + if(CurrentSection() == HTMLParseSection::Body && textBufferSize > 0) { page.AppendText(textBuffer); diff --git a/src/Parser.h b/src/Parser.h index afa459c..d2e270e 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -16,6 +16,8 @@ #include class Page; +class Node; +class HTMLTagHandler; struct TextEncoding { @@ -40,6 +42,12 @@ struct HTMLParseSection }; }; +struct HTMLParseContext +{ + Node* node; + const HTMLTagHandler* tag; +}; + class AttributeParser { public: @@ -58,6 +66,7 @@ class AttributeParser }; #define MAX_PARSE_SECTION_STACK_SIZE 32 +#define MAX_PARSE_CONTEXT_STACK_SIZE 32 class HTMLParser { @@ -73,6 +82,10 @@ class HTMLParser 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[contextStackSize]; } + void SetTextEncoding(TextEncoding::Type newType); void PushPreFormatted(); @@ -81,6 +94,8 @@ class HTMLParser private: void ParseChar(char c); + void AddTextElement(const char* text); + //HTMLNode* CreateNode(HTMLNode::NodeType nodeType, HTMLNode* parentNode); void AppendTextBuffer(char c); void FlushTextBuffer(); @@ -102,6 +117,9 @@ class HTMLParser HTMLParseSection::Type sectionStack[MAX_PARSE_SECTION_STACK_SIZE]; unsigned int sectionStackSize; + HTMLParseContext contextStack[MAX_PARSE_CONTEXT_STACK_SIZE]; + unsigned int contextStackSize; + bool parsingUnicode; int unicodeByteCount; uint32_t unicodePoint; diff --git a/src/Tags.cpp b/src/Tags.cpp index 8bf3aa2..d55f05a 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -21,15 +21,17 @@ #include "Platform.h" #include "Image.h" +#include "Nodes/Section.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), @@ -208,11 +210,16 @@ void BlockTagHandler::Close(class HTMLParser& parser) const void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushSection(section); + Node* sectionElement = SectionElement::Construct(parser.page.allocator, sectionType); + if (sectionElement) + { + parser.CurrentContext().node->AddChild(sectionElement); + parser.PushContext(sectionElement, this); + } } void SectionTagHandler::Close(class HTMLParser& parser) const { - parser.PopSection(section); + parser.PopContext(this); } void StyleTagHandler::Open(class HTMLParser& parser, char* attributeStr) const diff --git a/src/Tags.h b/src/Tags.h index 244f9af..83841be 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -16,6 +16,7 @@ #include "Parser.h" #include "Font.h" +#include "Nodes/Section.h" class HTMLParser; @@ -35,10 +36,10 @@ class HTMLTagHandler 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 From b3c0cfe6fb96f031b13ee836520a22129c6b6795 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Jul 2022 09:33:36 +0100 Subject: [PATCH 02/98] Updated project files to include new files --- Makefile | 81 +++++++++++++++++++++++++++++++++ project/DOS/Makefile | 2 +- project/DOS/hp95lx.mak | 2 +- project/Windows/Windows.vcxproj | 7 +++ 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 Makefile 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/project/DOS/Makefile b/project/DOS/Makefile index d92ff9a..bf98b2f 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -4,7 +4,7 @@ OBJDIR=obj objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.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) +CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h LD = wlink # begin mTCP stuff 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/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 6040a5a..0e51193 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -140,6 +140,9 @@ + + + @@ -154,6 +157,10 @@ + + + + From 63f47596ae57487d96121fc9a5b323fe00f16a51 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sun, 7 Aug 2022 18:14:51 +0100 Subject: [PATCH 03/98] refactor wip --- examples/68knews.htm | 212 +++++++++++++++++++++++++++++++ examples/simple.htm | 90 ++++++++++++++ examples/sq.htm | 214 ++++++++++++++++++++++++++++++++ project/DOS/Makefile | 17 ++- project/DOS/RESULT2.HTM | 58 +++++++++ project/Windows/Windows.vcxproj | 9 +- src/Defines.h | 14 ++- src/Font.h | 1 + src/Layout.cpp | 87 +++++++++++++ src/Layout.h | 57 +++++++++ src/Node.cpp | 11 +- src/Node.h | 18 ++- src/Nodes/Break.cpp | 24 ++++ src/Nodes/Break.h | 19 +++ src/Nodes/ImgNode.cpp | 37 ++++++ src/Nodes/ImgNode.h | 18 +++ src/Nodes/Section.cpp | 2 +- src/Nodes/Section.h | 1 - src/Nodes/StyNode.cpp | 12 ++ src/Nodes/StyNode.h | 19 +++ src/Nodes/Text.cpp | 139 ++++++++++++++++++++- src/Nodes/Text.h | 12 +- src/Page.cpp | 16 ++- src/Page.h | 4 + src/Parser.cpp | 34 ++++- src/Parser.h | 9 +- src/Tags.cpp | 24 +++- src/Widget.h | 2 +- 28 files changed, 1134 insertions(+), 26 deletions(-) create mode 100644 examples/68knews.htm create mode 100644 examples/simple.htm create mode 100644 examples/sq.htm create mode 100644 project/DOS/RESULT2.HTM create mode 100644 src/Layout.cpp create mode 100644 src/Layout.h create mode 100644 src/Nodes/Break.cpp create mode 100644 src/Nodes/Break.h create mode 100644 src/Nodes/ImgNode.cpp create mode 100644 src/Nodes/ImgNode.h create mode 100644 src/Nodes/StyNode.cpp create mode 100644 src/Nodes/StyNode.h 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": +

+
  • Space Quest X: Latex Babes of Estros (a reference to Infocom's Leather Goddesses of Phobos): In this timeframe, Roger or his son, Roger Jr. had had an undetermined affair with Zondra of the Latex Babes, which he ended abruptly. This timeframe contains the planet Estros and the Galaxy Galleria space station mall.
  • +
  • Space Quest XII: Vohaul's Revenge II: In this timeframe, Vohaul's consciousness has been uploaded in the Xenon Super Computer and infected it like a virus. He took over the planet and is sending his minions back in time to kill Roger Wilco. An underground resistance is formed against him, including his nemesis' son, Roger Wilco Jr.
+

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]

+
  • The Space Quest Trilogy: Roger Wilco - The Other World Series (1992) - a collection containing Space Quest VGA, Space Quest II and Space Quest III on floppy disks.
  • +
  • The Space Quest Saga (1993) - This collection contained games I (VGA remake), II, III and IV all on floppy disks.
  • +
  • The Space Quest 15th Anniversary Collector's Edition (1994) - Released for Sierra's 15th anniversary, this contained games I-V and Roger Wilco's Spaced Out Game Pack, plus a video featuring the Two Guys from Andromeda and a complete history of the game series. It also contained a few foreign language editions of some of the games. There is also a secret bonus program giving the strange history of the World Famous Talking Bear.
  • +
  • Roger Wilco Unclogged (1995) - All the above, plus a humorous "Inside Space Quest" video, but without the Two Guys video
  • +
  • Space Quest Collection Series: Starring Roger Wilco (1997) - All six games, plus a preview of episode VII.
  • +
  • Space Quest Collection: A Long Time Ago in A Janitor Closet Far Far Away (2006) - Released by Vivendi Universal Games and contains all six games (only the VGA remake of SQ1).
  • +
  • Space Quest 1+2+3 & 4+5+6 collections (2010) - Two collections on GoG.com, minus the VGA remake.
+

Collection bonus material[edit]

+
  • Funseeker's Guide to Eastern Madera County
  • +
  • History of Space Quest
  • +
  • Inside Space Quest
+

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

+
  • Space Quest 0: Replicated - a prequel to Space Quest I.[9]
  • +
  • Space Quest: The Lost Chapter - set between the second and third games.
  • +
  • Space Quest IV.5: Roger Wilco And The Voyage Home - set between the fourth and fifth game.[10]
  • +
  • Space Quest: Vohaul Strikes Back - an original hi-res installment set after Space Quest 6.
  • +
  • Space Quest 2 Remake: Vohaul's Revenge - a remake of Space Quest 2 in the style of Space Quest IV, developed by Infamous Adventures.[11]
  • +
  • Space Quest: Incinerations - another original hi-res installment, with a more action-oriented approach in the traditional adventure genre and with a modern sensibility.[12] According to Rock, Paper, Shotgun, Incinerations "completely re-imagines the whole Space Quest series as a sci-fi action thriller, focusing hard on character and drama while still managing to be just as tongue-in-cheek and funny as anything else that bears its name."[13]
  • +
  • Space Quest Minus 1: Decisions of the Elders - a hybrid combination of old-style AGI graphics with icon-driven interface.
+

Games influenced by Space Quest: +

+
  • Cosmos Quest - Adventure game influenced by Space Quest.[14]
+

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/project/DOS/Makefile b/project/DOS/Makefile index bf98b2f..b20dc5a 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ 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 TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj +objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -58,6 +58,21 @@ Font.obj: $(SRC_PATH)\Font.cpp Interface.obj: $(SRC_PATH)\Interface.cpp $(CC) -fo=$@ $(CFLAGS) $< +Layout.obj: $(SRC_PATH)\Layout.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Node.obj: $(SRC_PATH)\Node.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +ImgNode.obj: $(SRC_PATH)\Nodes\ImgNode.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Section.obj: $(SRC_PATH)\Nodes\Section.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Text.obj: $(SRC_PATH)\Nodes\Text.cpp + $(CC) -fo=$@ $(CFLAGS) $< + CGA.obj: $(SRC_PATH)\DOS\CGA.cpp $(CC) -fo=$@ $(CFLAGS) $< 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/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 0e51193..b83586b 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -140,8 +140,12 @@ + + + + @@ -158,8 +162,12 @@ + + + + @@ -168,7 +176,6 @@ - diff --git a/src/Defines.h b/src/Defines.h index 332048e..5ab1c1f 100644 --- a/src/Defines.h +++ b/src/Defines.h @@ -3,5 +3,17 @@ #ifdef __DOS__ // Define modern C++ keywords that aren't supported by OpenWatcom #define override -#define nullptr NULL + +#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/Font.h b/src/Font.h index d406f43..885639a 100644 --- a/src/Font.h +++ b/src/Font.h @@ -37,5 +37,6 @@ struct Font uint8_t* glyphData; int CalculateWidth(const char* text, FontStyle::Type style = FontStyle::Regular); + int GetGlyphWidth(char c) { return glyphWidth[c - 32]; } }; diff --git a/src/Layout.cpp b/src/Layout.cpp new file mode 100644 index 0000000..7c07203 --- /dev/null +++ b/src/Layout.cpp @@ -0,0 +1,87 @@ +#include "Layout.h" +#include "Page.h" + +Layout::Layout(Page& inPage) + : page(inPage) +{ + Reset(); +} + +void Layout::Reset() +{ + cursor.Clear(); + paramStackSize = 0; + currentLineHeight = 0; + + LayoutParams& params = GetParams(); + params.marginLeft = 0; + params.marginRight = 600; +} + +void Layout::PushLayout() +{ + if (paramStackSize < MAX_LAYOUT_PARAMS_STACK_SIZE - 1) + { + paramStack[paramStackSize + 1] = paramStack[paramStackSize]; + paramStackSize++; + } + else + { + // TODO error + } +} + +void Layout::PopLayout() +{ + if (paramStackSize > 0) + { + paramStackSize--; + } + else + { + // TODO error + } +} + +void Layout::BreakNewLine() +{ + // Recenter items if required + //int shift = AvailableWidth() / 2; + //TranslateNodes(lineStartNode, shift, 0); + + cursor.x = GetParams().marginLeft; + cursor.y += currentLineHeight; + currentLineHeight = 0; + lineStartNode = nullptr; +} + +void Layout::ProgressCursor(Node* nodeContext, int width, int lineHeight) +{ + if (!lineStartNode) + { + lineStartNode = nodeContext; + } + + if (lineHeight > currentLineHeight) + { + // Line height has increased so move everything down accordingly + int deltaY = lineHeight - currentLineHeight; + TranslateNodes(lineStartNode, 0, deltaY); + currentLineHeight = lineHeight; + } + + cursor.x += width; +} + +void Layout::TranslateNodes(Node* node, int deltaX, int deltaY) +{ + while (node) + { + node->anchor.x += deltaX; + node->anchor.y += deltaY; + + TranslateNodes(node->firstChild, deltaX, deltaY); + + node = node->next; + } +} diff --git a/src/Layout.h b/src/Layout.h new file mode 100644 index 0000000..1b2097f --- /dev/null +++ b/src/Layout.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Node.h" + +#define MAX_LAYOUT_PARAMS_STACK_SIZE 32 + +class Page; +class Node; + +struct LayoutParams +{ + int marginLeft, marginRight; +}; + +class Layout +{ +public: + + Layout(Page& page); + + void Reset(); + + Page& page; + + void BreakNewLine(); + + void PushLayout(); + void PopLayout(); + + void ProgressCursor(Node* nodeContext, int width, int lineHeight); + + Coord GetCursor(int lineHeight = 0) + { + Coord result = cursor; + result.y += currentLineHeight - lineHeight; + return result; + } + LayoutParams& GetParams() { return paramStack[paramStackSize]; } + int AvailableWidth() { return GetParams().marginRight - cursor.x; } + int MaxAvailableWidth() { return GetParams().marginRight - GetParams().marginLeft; } + + Node* lineStartNode; + + Coord cursor; + int currentLineHeight; + + LayoutParams paramStack[MAX_LAYOUT_PARAMS_STACK_SIZE]; + int paramStackSize; + + void TranslateNodes(Node* node, int deltaX, int deltaY); +}; + +/* + + + +*/ diff --git a/src/Node.cpp b/src/Node.cpp index 338f915..d4906f9 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -2,11 +2,18 @@ #include "Node.h" #include "Nodes/Text.h" #include "Nodes/Section.h" +#include "Nodes/ImgNode.h" +#include "Nodes/Break.h" +#include "Nodes/StyNode.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { new SectionElement(), - new TextElement() + new TextElement(), + new SubTextElement(), + new ImageNode(), + new BreakNode(), + new StyleNode() }; Node::Node(Type inType, void* inData) @@ -18,7 +25,7 @@ Node::Node(Type inType, void* inData) , data(inData) { anchor.Clear(); - rect.Clear(); + size.Clear(); } void Node::AddChild(Node* child) diff --git a/src/Node.h b/src/Node.h index 35aba2b..2daf8f4 100644 --- a/src/Node.h +++ b/src/Node.h @@ -1,8 +1,12 @@ #pragma once +#ifndef _NODE_H_ +#define _NODE_H_ + #include class Page; class Node; +class Layout; typedef class LinearAllocator Allocator; typedef uint16_t ElementStyle; @@ -10,7 +14,8 @@ typedef uint16_t ElementStyle; class NodeHandler { public: - virtual void Draw(Page& page, Node* element) = 0; + virtual void Draw(Page& page, Node* element) {} + virtual void GenerateLayout(Layout& layout, Node* node) {} }; struct Coord @@ -34,6 +39,10 @@ class Node { Section, Text, + SubText, + Image, + Break, + Style, NumNodeTypes }; @@ -48,8 +57,9 @@ class Node Type type; ElementStyle style; - Coord anchor; - Rect rect; + + Coord anchor; // Top left page position + Coord size; // Rectangle size that encapsulates node and its children Node* parent; Node* next; @@ -60,3 +70,5 @@ class Node protected: static NodeHandler* nodeHandlers[Node::NumNodeTypes]; }; + +#endif diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp new file mode 100644 index 0000000..c3e9bb7 --- /dev/null +++ b/src/Nodes/Break.cpp @@ -0,0 +1,24 @@ +#include +#include "../Layout.h" +#include "../LinAlloc.h" +#include "Break.h" + +Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayBreakLine) +{ + BreakNode::Data* data = allocator.Alloc(breakPadding, displayBreakLine); + if (data) + { + return allocator.Alloc(Node::Break, data); + } + return nullptr; +} + +void BreakNode::Draw(Page& page, Node* element) +{ + printf("\n"); +} + +void BreakNode::GenerateLayout(Layout& layout, Node* node) +{ + layout.BreakNewLine(); +} diff --git a/src/Nodes/Break.h b/src/Nodes/Break.h new file mode 100644 index 0000000..f8ab8c7 --- /dev/null +++ b/src/Nodes/Break.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Node.h" + +class BreakNode : public NodeHandler +{ +public: + class Data + { + public: + Data(int inBreakPadding, bool inDisplayBreakLine) : breakPadding(inBreakPadding), displayBreakLine(inDisplayBreakLine) {} + int breakPadding; + bool displayBreakLine; + }; + + static Node* Construct(Allocator& allocator, int breakPadding = 0, bool displayBreakLine = false); + virtual void Draw(Page& page, Node* element) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; +}; diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp new file mode 100644 index 0000000..e361201 --- /dev/null +++ b/src/Nodes/ImgNode.cpp @@ -0,0 +1,37 @@ +#include "../Platform.h" +#include "../Page.h" +#include "ImgNode.h" +#include "../LinAlloc.h" +#include "../Layout.h" + +void ImageNode::Draw(Page& page, Node* node) +{ + ImageNode::Data* data = static_cast(node->data); + printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); + Platform::video->FillRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); +} + + +Node* ImageNode::Construct(Allocator& allocator, void* imageData) +{ + ImageNode::Data* data = allocator.Alloc(imageData); + if (data) + { + return allocator.Alloc(Node::Image, data); + } + + return nullptr; +} + +void ImageNode::GenerateLayout(Layout& layout, Node* node) +{ + int imageWidth = node->size.x; + int imageHeight = node->size.y; + if (layout.AvailableWidth() < imageWidth) + { + layout.BreakNewLine(); + } + + node->anchor = layout.GetCursor(imageHeight); + layout.ProgressCursor(node, imageWidth, imageHeight); +} diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h new file mode 100644 index 0000000..bd75a31 --- /dev/null +++ b/src/Nodes/ImgNode.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Node.h" + +class ImageNode: public NodeHandler +{ +public: + class Data + { + public: + Data(void* inImageData) : imageData(inImageData) {} + void* imageData; + }; + + static Node* Construct(Allocator& allocator, void* imageData); + virtual void Draw(Page& page, Node* element) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; +}; diff --git a/src/Nodes/Section.cpp b/src/Nodes/Section.cpp index 3746fa8..570e932 100644 --- a/src/Nodes/Section.cpp +++ b/src/Nodes/Section.cpp @@ -7,7 +7,7 @@ Node* SectionElement::Construct(Allocator& allocator, SectionElement::Type secti if (data) { - return allocator.Alloc(Node::Type::Section, data); + return allocator.Alloc(Node::Section, data); } return nullptr; } diff --git a/src/Nodes/Section.h b/src/Nodes/Section.h index 1a9328f..49c3f3d 100644 --- a/src/Nodes/Section.h +++ b/src/Nodes/Section.h @@ -24,6 +24,5 @@ class SectionElement : public NodeHandler }; static Node* Construct(Allocator& allocator, SectionElement::Type sectionType); - virtual void Draw(Page& page, Node* element) override {} }; diff --git a/src/Nodes/StyNode.cpp b/src/Nodes/StyNode.cpp new file mode 100644 index 0000000..b6116df --- /dev/null +++ b/src/Nodes/StyNode.cpp @@ -0,0 +1,12 @@ +#include "StyNode.h" +#include "../LinAlloc.h" + +Node* StyleNode::Construct(Allocator& allocator) +{ + StyleNode::Data* data = allocator.Alloc(); + if (data) + { + 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..ac137aa --- /dev/null +++ b/src/Nodes/StyNode.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../Node.h" +#include "../Font.h" + +class StyleNode : public NodeHandler +{ +public: + class Data + { + public: + Data() : fontSize(1) { } + int fontSize; + FontStyle::Type fontStyle; + }; + + static Node* Construct(Allocator& allocator); +}; + diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index e32f850..5f17c3e 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -1,12 +1,20 @@ +#include "../Platform.h" #include "../Page.h" #include "Text.h" #include "../LinAlloc.h" +#include "../Layout.h" -void TextElement::Draw(Page& page, Node* element) +void TextElement::Draw(Page& page, Node* node) { - TextElement::Data* data = static_cast(element->data); -} + TextElement::Data* data = static_cast(node->data); + if (!node->firstChild && data->text) + { + Platform::video->DrawString(data->text, node->anchor.x, node->anchor.y + 50); + //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); + printf("%s [%d, %d]", data->text, node->anchor.x, node->anchor.y); + } +} Node* TextElement::Construct(Allocator& allocator, const char* text) { @@ -16,9 +24,132 @@ Node* TextElement::Construct(Allocator& allocator, const char* text) TextElement::Data* data = allocator.Alloc(textString); if (data) { - return allocator.Alloc(Node::Type::Text, data); + return allocator.Alloc(Node::Text, data); + } + } + + return nullptr; +} + +void TextElement::GenerateLayout(Layout& layout, Node* node) +{ + TextElement::Data* data = static_cast(node->data); + Font* font = Platform::video->GetFont(1); // TODO: Get font from active style + int lineHeight = font->glyphHeight; + + // Clear out SubTextElement children if we are regenerating the layout + for (Node* child = node->firstChild; child; child = child->next) + { + TextElement::Data* childData = static_cast(child->data); + if (childData->text && child != node->firstChild) + { + // Remove break and replace with white space + childData->text[-1] = ' '; + } + childData->text = nullptr; + } + + char* text = data->text; + int charIndex = 0; + int startIndex = 0; + int lastBreakPoint = 0; + int lastBreakPointWidth = 0; + int width = 0; + Node* subTextNode = node->firstChild; + + for(charIndex = 0; data->text[charIndex]; charIndex++) + { + char c = data->text[charIndex]; + if (c < 32 || c > 128) + continue; + + if (c == ' ') + { + lastBreakPoint = charIndex; + lastBreakPointWidth = width; + } + + width += font->GetGlyphWidth(c); + if (width > layout.AvailableWidth()) + { + // Needs a line break + if (startIndex == lastBreakPoint) + { + // Nothing could fit on the line before the break + layout.BreakNewLine(); + } + else + { + if (!subTextNode) + { + subTextNode = SubTextElement::Construct(layout.page.allocator, &text[startIndex]); + node->AddChild(subTextNode); + } + else + { + TextElement::Data* childData = static_cast(subTextNode->data); + childData->text = &text[startIndex]; + } + + subTextNode->anchor = layout.GetCursor(lineHeight); + subTextNode->size.x = lastBreakPointWidth; + subTextNode->size.y = lineHeight; + + text[lastBreakPoint] = '\0'; + startIndex = lastBreakPoint + 1; + width -= lastBreakPointWidth; + + layout.ProgressCursor(subTextNode, lastBreakPointWidth, lineHeight); + layout.BreakNewLine(); + + subTextNode = subTextNode->next; + } + } + } + + if (charIndex > startIndex) + { + if (startIndex == 0 && !subTextNode) + { + // No sub text nodes needed + node->anchor = layout.GetCursor(lineHeight); + node->size.x = width; + node->size.y = lineHeight; + layout.ProgressCursor(node, width, lineHeight); + } + else + { + if (!subTextNode) + { + subTextNode = SubTextElement::Construct(layout.page.allocator, &text[startIndex]); + node->AddChild(subTextNode); + } + else + { + TextElement::Data* childData = static_cast(subTextNode->data); + childData->text = &text[startIndex]; + } + subTextNode->anchor = layout.GetCursor(lineHeight); + subTextNode->size.x = width; + subTextNode->size.y = lineHeight; + + layout.ProgressCursor(subTextNode, width, lineHeight); } } +} + +Node* SubTextElement::Construct(Allocator& allocator, const char* text) +{ + TextElement::Data* data = allocator.Alloc(text); + if (data) + { + return allocator.Alloc(Node::SubText, data); + } return nullptr; } + +void SubTextElement::GenerateLayout(Layout& layout, Node* node) +{ + TextElement::Data* data = static_cast(node->data); +} diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h index 1893b0a..5102532 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -8,10 +8,18 @@ class TextElement : public NodeHandler class Data { public: - Data(const char* inText) : text(inText) {} - const char* text; + Data(const char* inText) : text(const_cast(inText)) {} + char* text; }; static Node* Construct(Allocator& allocator, const char* text); virtual void Draw(Page& page, Node* element) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; +}; + +class SubTextElement : public TextElement +{ +public: + static Node* Construct(Allocator& allocator, const char* text); + virtual void GenerateLayout(Layout& layout, Node* node) override; }; diff --git a/src/Page.cpp b/src/Page.cpp index 6f50f1b..ad49e4d 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -23,7 +23,7 @@ #define TOP_MARGIN_PADDING 1 -Page::Page(App& inApp) : app(inApp), widgets(allocator) +Page::Page(App& inApp) : app(inApp), widgets(allocator), layout(*this) { Reset(); } @@ -50,6 +50,8 @@ void Page::Reset() allocator.Reset(); rootNode = SectionElement::Construct(allocator, SectionElement::Document); + + layout.Reset(); } WidgetStyle& Page::GetStyleStackTop() @@ -569,6 +571,18 @@ void Page::SetFormData(WidgetFormData* inFormData) formData = inFormData; } +void Page::DebugDraw(Node* node) +{ + while (node) + { + node->Handler().Draw(*this, node); + + DebugDraw(node->firstChild); + + node = node->next; + } +} + WidgetContainer::WidgetContainer(LinearAllocator& inAllocator) : allocator(inAllocator), numAllocated(0) { } diff --git a/src/Page.h b/src/Page.h index ca01f02..f4fe067 100644 --- a/src/Page.h +++ b/src/Page.h @@ -17,6 +17,7 @@ #include "Widget.h" #include "LinAlloc.h" #include "URL.h" +#include "Layout.h" #define MAX_PAGE_WIDGETS 2000 #define MAX_PAGE_STYLE_STACK_SIZE 32 @@ -87,9 +88,12 @@ class Page void PopStyle(); LinearAllocator allocator; + Layout layout; int GetPageHeight() { return pageHeight; } + void DebugDraw(Node* node); + URL pageURL; private: diff --git a/src/Parser.cpp b/src/Parser.cpp index 185f255..cb738d6 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -22,6 +22,7 @@ #include "Page.h" #include "Unicode.inc" #include "Nodes/Text.h" +#include "Nodes/ImgNode.h" HTMLParser::HTMLParser(Page& inPage) : page(inPage) @@ -69,6 +70,12 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (contextStack[n].tag == tag) { contextStackSize = n - 1; + + if (contextStackSize == 0) + { + page.DebugDraw(page.GetRootNode()); + } + return; } } @@ -141,16 +148,34 @@ void HTMLParser::AppendTextBuffer(char c) } } -void HTMLParser::AddTextElement(const char* text) +void HTMLParser::EmitText(const char* text) { Node* textElement = TextElement::Construct(page.allocator, text); if (textElement) { textElement->style = CurrentContext().node->style; - CurrentContext().node->AddChild(textElement); + EmitNode(textElement); + } +} + +void HTMLParser::EmitNode(Node* node) +{ + CurrentContext().node->AddChild(node); + node->Handler().GenerateLayout(page.layout, node); +} + +void HTMLParser::EmitImage(Image* image, int imageWidth, int imageHeight) +{ + Node* node = ImageNode::Construct(page.allocator, image); + if (node) + { + node->size.x = imageWidth; + node->size.y = imageHeight; + EmitNode(node); } } + void HTMLParser::FlushTextBuffer() { textBuffer[textBufferSize] = '\0'; @@ -159,7 +184,10 @@ void HTMLParser::FlushTextBuffer() { case ParseText: { - AddTextElement(textBuffer); + if (textBufferSize > 0) + { + EmitText(textBuffer); + } if(CurrentSection() == HTMLParseSection::Body && textBufferSize > 0) { diff --git a/src/Parser.h b/src/Parser.h index d2e270e..ca97ff3 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -17,6 +17,7 @@ #include class Page; class Node; +struct Image; class HTMLTagHandler; struct TextEncoding @@ -86,6 +87,10 @@ class HTMLParser void PopContext(const HTMLTagHandler* tag); HTMLParseContext& CurrentContext() { return contextStack[contextStackSize]; } + void EmitNode(Node* node); + void EmitText(const char* text); + void EmitImage(Image* image, int imageWidth, int imageHeight); + void SetTextEncoding(TextEncoding::Type newType); void PushPreFormatted(); @@ -94,8 +99,6 @@ class HTMLParser private: void ParseChar(char c); - void AddTextElement(const char* text); - //HTMLNode* CreateNode(HTMLNode::NodeType nodeType, HTMLNode* parentNode); void AppendTextBuffer(char c); void FlushTextBuffer(); @@ -118,7 +121,7 @@ class HTMLParser unsigned int sectionStackSize; HTMLParseContext contextStack[MAX_PARSE_CONTEXT_STACK_SIZE]; - unsigned int contextStackSize; + int contextStackSize; bool parsingUnicode; int unicodeByteCount; diff --git a/src/Tags.cpp b/src/Tags.cpp index d55f05a..156d5ae 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -22,6 +22,8 @@ #include "Image.h" #include "Nodes/Section.h" +#include "Nodes/ImgNode.h" +#include "Nodes/Break.h" static const HTMLTagHandler* tagHandlers[] = { @@ -116,11 +118,13 @@ void HTMLTagHandler::ApplyStyleAttributes(WidgetStyle& style, char* attributeStr void HrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { parser.page.AddHorizontalRule(); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { parser.page.BreakTextLine(); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void HTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -131,6 +135,8 @@ void HTagHandler::Open(class HTMLParser& parser, char* attributeStr) const ApplyStyleAttributes(currentStyle, attributeStr); parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); parser.page.PushStyle(currentStyle); + + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void HTagHandler::Close(class HTMLParser& parser) const @@ -138,6 +144,8 @@ void HTagHandler::Close(class HTMLParser& parser) const WidgetStyle currentStyle = parser.page.GetStyleStackTop(); parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); parser.page.PopStyle(); + + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -157,6 +165,8 @@ 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.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.page.BreakLine(); parser.page.AddBulletPoint(); parser.page.AdjustLeftMargin(bulletPointWidth); @@ -199,6 +209,8 @@ void BlockTagHandler::Open(class HTMLParser& parser, char* attributeStr) const parser.page.BreakLine(useVerticalPadding ? Platform::video->GetLineHeight(currentStyle.fontSize) >> 1 : 0); ApplyStyleAttributes(currentStyle, attributeStr); parser.page.PushStyle(currentStyle); + + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void BlockTagHandler::Close(class HTMLParser& parser) const { @@ -206,6 +218,8 @@ void BlockTagHandler::Close(class HTMLParser& parser) const parser.page.AdjustLeftMargin(-leftMarginPadding); parser.page.BreakLine(useVerticalPadding ? Platform::video->GetLineHeight(currentStyle.fontSize) >> 1 : 0); parser.page.PopStyle(); + + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -307,12 +321,14 @@ void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { WidgetStyle currentStyle = parser.page.GetStyleStackTop(); parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void ListTagHandler::Close(class HTMLParser& parser) const { WidgetStyle currentStyle = parser.page.GetStyleStackTop(); parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } struct HTMLInputTag @@ -469,7 +485,8 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const Image* imageIcon = Platform::video->imageIcon; if (imageIcon) { - parser.page.AddImage(NULL, imageIcon->width, imageIcon->height); + //parser.page.AddImage(NULL, imageIcon->width, imageIcon->height); + parser.EmitImage(NULL, imageIcon->width, imageIcon->height); } if (altText) @@ -480,7 +497,8 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const else { Platform::video->ScaleImageDimensions(width, height); - parser.page.AddImage(altText, width, height); + //parser.page.AddImage(altText, width, height); + parser.EmitImage(NULL, width, height); } } @@ -532,6 +550,7 @@ void PreformattedTagHandler::Open(class HTMLParser& parser, char* attributeStr) parser.page.PushStyle(currentStyle); parser.PushPreFormatted(); parser.page.BreakTextLine(); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void PreformattedTagHandler::Close(class HTMLParser& parser) const @@ -541,4 +560,5 @@ void PreformattedTagHandler::Close(class HTMLParser& parser) const WidgetStyle currentStyle = parser.page.GetStyleStackTop(); parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize, currentStyle.fontStyle) >> 1); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } diff --git a/src/Widget.h b/src/Widget.h index a647356..bd3e114 100644 --- a/src/Widget.h +++ b/src/Widget.h @@ -101,7 +101,7 @@ struct Widget { return image->linkURL; } - return NULL; + return nullptr; } union From b0ff8b1aee75c052670185e80abcc9b64c067730 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 11 Nov 2022 14:28:47 +0000 Subject: [PATCH 04/98] Layout refactor WIP --- project/Windows/Windows.vcxproj | 3 + src/DOS/CGA.cpp | 49 ++++++++-- src/DOS/HP95LX.cpp | 29 +++--- src/Layout.cpp | 7 +- src/Node.cpp | 6 +- src/Node.h | 5 +- src/Nodes/LinkNode.cpp | 17 ++++ src/Nodes/LinkNode.h | 18 ++++ src/Nodes/StyNode.cpp | 43 +++++++++ src/Nodes/StyNode.h | 11 ++- src/Nodes/Text.cpp | 9 +- src/Page.cpp | 59 ++++++++++++ src/Page.h | 1 + src/Parser.cpp | 20 +++- src/Parser.h | 2 + src/Style.h | 96 +++++++++++++++++++ src/Tags.cpp | 164 ++++++++++++++++++-------------- src/Tags.h | 6 +- 18 files changed, 432 insertions(+), 113 deletions(-) create mode 100644 src/Nodes/LinkNode.cpp create mode 100644 src/Nodes/LinkNode.h create mode 100644 src/Style.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index b83586b..99618ac 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -144,6 +144,7 @@ + @@ -166,6 +167,7 @@ + @@ -176,6 +178,7 @@ + diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index ae7409e..12bdda1 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -22,30 +22,33 @@ #include "CGA.h" #include "CGAData.inc" #include "../Interface.h" +#include "DefData.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_Y 10 +#define ADDRESS_BAR_Y (TITLE_BAR_HEIGHT + 1) #define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - 64) -#define ADDRESS_BAR_HEIGHT 12 -#define TITLE_BAR_HEIGHT 8 -#define STATUS_BAR_HEIGHT 8 +#define ADDRESS_BAR_HEIGHT 15 // 12 +#define TITLE_BAR_HEIGHT 12 //8 +#define STATUS_BAR_HEIGHT 12 //8 #define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) -#define WINDOW_TOP 24 +#define WINDOW_TOP (TITLE_BAR_HEIGHT + ADDRESS_BAR_HEIGHT + 3) +//#define WINDOW_TOP 24 #define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) #define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) +#define NAVIGATION_BUTTON_WIDTH 24 +#define NAVIGATION_BUTTON_HEIGHT ADDRESS_BAR_HEIGHT + #define SCROLL_BAR_WIDTH 16 #define WINDOW_VRAM_TOP_EVEN (BYTES_PER_LINE * (WINDOW_TOP / 2)) @@ -319,7 +322,7 @@ void CGADriver::DrawString(const char* text, int x, int y, int size, FontStyle:: Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) { - if (style & FontStyle::Monospace) +/* if (style & FontStyle::Monospace) { switch (fontSize) { @@ -344,7 +347,35 @@ Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) return &CGA_LargeFont; default: return &CGA_RegularFont; + }*/ + + 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 CGADriver::HLine(int x, int y, int count) diff --git a/src/DOS/HP95LX.cpp b/src/DOS/HP95LX.cpp index 05b166f..ec52161 100644 --- a/src/DOS/HP95LX.cpp +++ b/src/DOS/HP95LX.cpp @@ -20,7 +20,8 @@ #include #include "../Image.h" #include "HP95LX.h" -#include "DefData.h" +#include "CGAData.inc" +//#include "DefData.h" #include "../Interface.h" #define USE_EGA_PREVIS 0 @@ -31,11 +32,11 @@ #define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) #endif -#define SCREEN_WIDTH 240 -#define SCREEN_HEIGHT 128 +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 200 -#define ADDRESS_BAR_HEIGHT 13 -#define TITLE_BAR_HEIGHT 8 +#define ADDRESS_BAR_HEIGHT 10 +#define TITLE_BAR_HEIGHT 6 #define STATUS_BAR_HEIGHT 0 #define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) @@ -81,8 +82,8 @@ HP95LXVideoDriver::HP95LXVideoDriver() scissorY2 = SCREEN_HEIGHT; invertScreen = true; clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; + imageIcon = &CGA_ImageIcon; + bulletImage = &CGA_Bullet; isTextMode = false; } @@ -310,22 +311,22 @@ Font* HP95LXVideoDriver::GetFont(int fontSize, FontStyle::Type style) switch (fontSize) { default: - return &Default_SmallFont_Monospace; + return &CGA_SmallFont_Monospace; case 2: case 3: case 4: - return &Default_RegularFont_Monospace; + return &CGA_RegularFont_Monospace; } } switch (fontSize) { default: - return &Default_SmallFont; + return &CGA_SmallFont; case 2: case 3: case 4: - return &Default_RegularFont; + return &CGA_RegularFont; } } @@ -578,11 +579,11 @@ MouseCursorData* HP95LXVideoDriver::GetCursorGraphic(MouseCursor::Type type) { default: case MouseCursor::Pointer: - return &Default_MouseCursor; + return &CGA_MouseCursor; case MouseCursor::Hand: - return &Default_MouseCursorHand; + return &CGA_MouseCursorHand; case MouseCursor::TextSelect: - return &Default_MouseCursorTextSelect; + return &CGA_MouseCursorTextSelect; } } diff --git a/src/Layout.cpp b/src/Layout.cpp index 7c07203..40603bb 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -46,8 +46,11 @@ void Layout::PopLayout() void Layout::BreakNewLine() { // Recenter items if required - //int shift = AvailableWidth() / 2; - //TranslateNodes(lineStartNode, shift, 0); + if (lineStartNode && lineStartNode->style.alignment == ElementAlignment::Center) + { + int shift = AvailableWidth() / 2; + TranslateNodes(lineStartNode, shift, 0); + } cursor.x = GetParams().marginLeft; cursor.y += currentLineHeight; diff --git a/src/Node.cpp b/src/Node.cpp index d4906f9..e1b1764 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -5,6 +5,7 @@ #include "Nodes/ImgNode.h" #include "Nodes/Break.h" #include "Nodes/StyNode.h" +#include "Nodes/LinkNode.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -13,12 +14,12 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new SubTextElement(), new ImageNode(), new BreakNode(), - new StyleNode() + new StyleNode(), + new LinkNode() }; Node::Node(Type inType, void* inData) : type(inType) - , style(0) , parent(nullptr) , next(nullptr) , firstChild(nullptr) @@ -31,6 +32,7 @@ Node::Node(Type inType, void* inData) void Node::AddChild(Node* child) { child->parent = this; + child->style = style; if(!firstChild) { diff --git a/src/Node.h b/src/Node.h index 2daf8f4..6f94d9e 100644 --- a/src/Node.h +++ b/src/Node.h @@ -3,19 +3,19 @@ #define _NODE_H_ #include +#include "Style.h" class Page; class Node; class Layout; typedef class LinearAllocator Allocator; -typedef uint16_t ElementStyle; - class NodeHandler { public: virtual void Draw(Page& page, Node* element) {} virtual void GenerateLayout(Layout& layout, Node* node) {} + virtual void ApplyStyle(Node* node) {} }; struct Coord @@ -43,6 +43,7 @@ class Node Image, Break, Style, + Link, NumNodeTypes }; diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp new file mode 100644 index 0000000..3e4f2ad --- /dev/null +++ b/src/Nodes/LinkNode.cpp @@ -0,0 +1,17 @@ +#include "LinkNode.h" +#include "../LinAlloc.h" + +void LinkNode::ApplyStyle(Node* node) +{ + node->style.fontStyle = (FontStyle::Type)(node->style.fontStyle | FontStyle::Underline); +} + +Node* LinkNode::Construct(Allocator& allocator) +{ + LinkNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Link, data); + } + return nullptr; +} diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h new file mode 100644 index 0000000..3b914b9 --- /dev/null +++ b/src/Nodes/LinkNode.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../Node.h" + +class LinkNode : public NodeHandler +{ +public: + class Data + { + public: + char* url; + }; + + virtual void ApplyStyle(Node* node) override; + + static Node* Construct(Allocator& allocator); + +}; \ No newline at end of file diff --git a/src/Nodes/StyNode.cpp b/src/Nodes/StyNode.cpp index b6116df..b28887d 100644 --- a/src/Nodes/StyNode.cpp +++ b/src/Nodes/StyNode.cpp @@ -1,6 +1,12 @@ #include "StyNode.h" #include "../LinAlloc.h" +void StyleNode::ApplyStyle(Node* node) +{ + StyleNode::Data* data = static_cast(node->data); + data->styleOverride.Apply(node->style); +} + Node* StyleNode::Construct(Allocator& allocator) { StyleNode::Data* data = allocator.Alloc(); @@ -10,3 +16,40 @@ Node* StyleNode::Construct(Allocator& allocator) } 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 index ac137aa..ed7470c 100644 --- a/src/Nodes/StyNode.h +++ b/src/Nodes/StyNode.h @@ -1,7 +1,9 @@ #pragma once +#include #include "../Node.h" #include "../Font.h" +#include "../Style.h" class StyleNode : public NodeHandler { @@ -9,11 +11,14 @@ class StyleNode : public NodeHandler class Data { public: - Data() : fontSize(1) { } - int fontSize; - FontStyle::Type fontStyle; + ElementStyleOverride styleOverride; }; + virtual void ApplyStyle(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/Text.cpp b/src/Nodes/Text.cpp index 5f17c3e..d67a1f2 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -10,9 +10,9 @@ void TextElement::Draw(Page& page, Node* node) if (!node->firstChild && data->text) { - Platform::video->DrawString(data->text, node->anchor.x, node->anchor.y + 50); + Platform::video->DrawString(data->text, node->anchor.x, node->anchor.y + 50, node->style.fontSize, node->style.fontStyle); //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); - printf("%s [%d, %d]", data->text, node->anchor.x, node->anchor.y); + printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); } } @@ -34,7 +34,7 @@ Node* TextElement::Construct(Allocator& allocator, const char* text) void TextElement::GenerateLayout(Layout& layout, Node* node) { TextElement::Data* data = static_cast(node->data); - Font* font = Platform::video->GetFont(1); // TODO: Get font from active style + Font* font = Platform::video->GetFont(node->style.fontSize, node->style.fontStyle); // TODO: Get font from active style int lineHeight = font->glyphHeight; // Clear out SubTextElement children if we are regenerating the layout @@ -69,7 +69,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) lastBreakPointWidth = width; } - width += font->GetGlyphWidth(c); + width += Platform::video->GetGlyphWidth(c, node->style.fontSize, node->style.fontStyle); + if (width > layout.AvailableWidth()) { // Needs a line break diff --git a/src/Page.cpp b/src/Page.cpp index ad49e4d..5b705f3 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -20,6 +20,7 @@ #include "App.h" #include "Image.h" #include "Nodes/Section.h" +#include "Nodes/Text.h" #define TOP_MARGIN_PADDING 1 @@ -50,6 +51,9 @@ void Page::Reset() allocator.Reset(); rootNode = SectionElement::Construct(allocator, SectionElement::Document); + rootNode->style.alignment = ElementAlignment::Left; + rootNode->style.fontSize = 1; + rootNode->style.fontStyle = FontStyle::Regular; layout.Reset(); } @@ -620,3 +624,58 @@ Widget& WidgetContainer::operator[](int index) { return chunks[WIDGET_INDEX_TO_CHUNK_INDEX(index)]->widgets[WIDGET_INDEX_TO_INDEX_IN_CHUNK(index)]; } + +void Page::DebugDumpNodeGraph(Node* node, int depth) +{ + static const char* nodeTypeNames[] = + { + "Section", + "Text", + "SubText", + "Image", + "Break", + "Style", + "Link" + }; + + static const char* sectionTypeNames[] = + { + "Document", + "HTML", + "Head", + "Body", + "Script", + "Style", + "Title" + }; + + for (int i = 0; i < depth; i++) + { + printf(" "); + } + + switch (node->type) + { + case Node::Text: + case Node::SubText: + { + TextElement::Data* data = static_cast(node->data); + printf("<%s> %s\n", nodeTypeNames[node->type], data->text); + } + break; + case Node::Section: + { + SectionElement::Data* data = static_cast(node->data); + printf("<%s> %s\n", nodeTypeNames[node->type], sectionTypeNames[data->type]); + } + break; + default: + printf("<%s>\n", nodeTypeNames[node->type]); + break; + } + + for (Node* child = node->firstChild; child; child = child->next) + { + DebugDumpNodeGraph(child, depth + 1); + } +} diff --git a/src/Page.h b/src/Page.h index f4fe067..cad56b4 100644 --- a/src/Page.h +++ b/src/Page.h @@ -93,6 +93,7 @@ class Page int GetPageHeight() { return pageHeight; } void DebugDraw(Node* node); + void DebugDumpNodeGraph(Node* node, int depth = 0); URL pageURL; diff --git a/src/Parser.cpp b/src/Parser.cpp index cb738d6..eb926d3 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -50,8 +50,19 @@ void HTMLParser::Reset() void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) { + if (!node) + { + return; + } if (contextStackSize < MAX_PARSE_CONTEXT_STACK_SIZE - 1) { + if (contextStackSize >= 0) + { + Node* parentNode = CurrentContext().node; + parentNode->AddChild(node); + node->Handler().ApplyStyle(node); + } + contextStackSize++; contextStack[contextStackSize].node = node; contextStack[contextStackSize].tag = tag; @@ -73,6 +84,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (contextStackSize == 0) { + page.DebugDumpNodeGraph(page.GetRootNode()); page.DebugDraw(page.GetRootNode()); } @@ -160,7 +172,13 @@ void HTMLParser::EmitText(const char* text) void HTMLParser::EmitNode(Node* node) { - CurrentContext().node->AddChild(node); + if (!node) + { + return; + } + Node* parentNode = CurrentContext().node; + parentNode->AddChild(node); + node->Handler().ApplyStyle(node); node->Handler().GenerateLayout(page.layout, node); } diff --git a/src/Parser.h b/src/Parser.h index ca97ff3..d685918 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -104,6 +104,8 @@ class HTMLParser void FlushTextBuffer(); bool IsWhiteSpace(char c); + void DebugDumpNodeGraph(Node* node, int depth = 0); + enum ParseState { ParseText, diff --git a/src/Style.h b/src/Style.h new file mode 100644 index 0000000..0ca697d --- /dev/null +++ b/src/Style.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include "Font.h" + +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; + }; + uint8_t mask; + }; + + void Clear() + { + mask = 0; + } +}; + +struct ElementStyle +{ + FontStyle::Type fontStyle : 4; + int fontSize : 4; + ElementAlignment::Type alignment : 2; +}; + +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 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; + } + } +}; diff --git a/src/Tags.cpp b/src/Tags.cpp index 156d5ae..d23402d 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -24,6 +24,8 @@ #include "Nodes/Section.h" #include "Nodes/ImgNode.h" #include "Nodes/Break.h" +#include "Nodes/StyNode.h" +#include "Nodes/LinkNode.h" static const HTMLTagHandler* tagHandlers[] = { @@ -49,7 +51,7 @@ static const HTMLTagHandler* tagHandlers[] = 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), @@ -117,67 +119,69 @@ void HTMLTagHandler::ApplyStyleAttributes(WidgetStyle& style, char* attributeStr void HrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.page.AddHorizontalRule(); + //parser.page.AddHorizontalRule(); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.page.BreakTextLine(); + //parser.page.BreakTextLine(); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } 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); + //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); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Bold, size >= 3 ? 1 : 2), 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(); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + //parser.page.PopStyle(); + parser.PopContext(this); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - currentStyle.fontSize = size; - parser.page.PushStyle(currentStyle); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //currentStyle.fontSize = size; + //parser.page.PushStyle(currentStyle); } void SizeTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); + //parser.page.PopStyle(); } 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); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //int bulletPointWidth = Platform::video->GetFont(currentStyle.fontSize, currentStyle.fontStyle)->CalculateWidth(" * ", currentStyle.fontStyle); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); - parser.page.BreakLine(); - parser.page.AddBulletPoint(); - parser.page.AdjustLeftMargin(bulletPointWidth); + //parser.page.BreakLine(); + //parser.page.AddBulletPoint(); + //parser.page.AdjustLeftMargin(bulletPointWidth); } 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(); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //int bulletPointWidth = Platform::video->GetFont(currentStyle.fontSize, currentStyle.fontStyle)->CalculateWidth(" * ", currentStyle.fontStyle); + // + //parser.page.AdjustLeftMargin(-bulletPointWidth); + //parser.page.BreakLine(); } void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -187,37 +191,40 @@ void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const { if (!stricmp(attributes.Key(), "href")) { - parser.page.SetWidgetURL(attributes.Value()); + //parser.page.SetWidgetURL(attributes.Value()); } } - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Underline); - parser.page.PushStyle(currentStyle); + parser.PushContext(LinkNode::Construct(parser.page.allocator), this); + + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Underline); + //parser.page.PushStyle(currentStyle); } void ATagHandler::Close(class HTMLParser& parser) const { - parser.page.ClearWidgetURL(); - parser.page.PopStyle(); + parser.PopContext(this); + //parser.page.ClearWidgetURL(); + //parser.page.PopStyle(); } 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); + //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); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } 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(); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //parser.page.AdjustLeftMargin(-leftMarginPadding); + //parser.page.BreakLine(useVerticalPadding ? Platform::video->GetLineHeight(currentStyle.fontSize) >> 1 : 0); + //parser.page.PopStyle(); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } @@ -227,7 +234,6 @@ void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const Node* sectionElement = SectionElement::Construct(parser.page.allocator, sectionType); if (sectionElement) { - parser.CurrentContext().node->AddChild(sectionElement); parser.PushContext(sectionElement, this); } } @@ -238,33 +244,42 @@ void SectionTagHandler::Close(class HTMLParser& parser) const 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); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | style); + //parser.page.PushStyle(currentStyle); + Node* styleNode = StyleNode::ConstructFontStyle(parser.page.allocator, 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); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + // + //parser.page.BreakLine(); + // + //currentStyle.center = true; + //parser.page.PushStyle(currentStyle); + parser.PushContext(StyleNode::ConstructAlignment(parser.page.allocator, 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.page.PopStyle(); + //parser.page.BreakLine(); } void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { + /* WidgetStyle currentStyle = parser.page.GetStyleStackTop(); AttributeParser attributes(attributeStr); @@ -310,24 +325,25 @@ void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } parser.page.PushStyle(currentStyle); + */ } void FontTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); + //parser.page.PopStyle(); } void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void ListTagHandler::Close(class HTMLParser& parser) const { - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } @@ -356,7 +372,7 @@ void ButtonTagHandler::Open(class HTMLParser& parser, char* attributeStr) const if (title) { - parser.page.AddButton(title); + //parser.page.AddButton(title); } } @@ -543,22 +559,22 @@ 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); - parser.PushPreFormatted(); - parser.page.BreakTextLine(); + //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); + //parser.PushPreFormatted(); + //parser.page.BreakTextLine(); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void PreformattedTagHandler::Close(class HTMLParser& parser) const { - parser.page.PopStyle(); - parser.PopPreFormatted(); - - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize, currentStyle.fontStyle) >> 1); + //parser.page.PopStyle(); + //parser.PopPreFormatted(); + // + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); + //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize, currentStyle.fontStyle) >> 1); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } diff --git a/src/Tags.h b/src/Tags.h index 83841be..e75b2bf 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -17,6 +17,7 @@ #include "Parser.h" #include "Font.h" #include "Nodes/Section.h" +#include "Style.h" class HTMLParser; @@ -42,12 +43,13 @@ class SectionTagHandler : public HTMLTagHandler 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 From 188303d5b11a51d4fa5b7653cb56e7e0b6dfe360 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 14 Nov 2022 20:44:06 +0000 Subject: [PATCH 05/98] Added new block tag and layout options --- project/Windows/Windows.vcxproj | 2 + src/Layout.cpp | 73 +++++++++++++++++++++++++++-- src/Layout.h | 6 ++- src/Node.cpp | 4 +- src/Node.h | 3 ++ src/Nodes/Block.cpp | 33 ++++++++++++++ src/Nodes/Block.h | 19 ++++++++ src/Nodes/Section.cpp | 3 ++ src/Nodes/StyNode.cpp | 10 ++++ src/Nodes/StyNode.h | 1 + src/Page.cpp | 3 +- src/Parser.cpp | 81 ++++++++++++++------------------- src/Parser.h | 25 ++-------- src/Tags.cpp | 11 ++--- 14 files changed, 193 insertions(+), 81 deletions(-) create mode 100644 src/Nodes/Block.cpp create mode 100644 src/Nodes/Block.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 99618ac..c187753 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -142,6 +142,7 @@ + @@ -165,6 +166,7 @@ + diff --git a/src/Layout.cpp b/src/Layout.cpp index 40603bb..eaad36d 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -49,7 +49,7 @@ void Layout::BreakNewLine() if (lineStartNode && lineStartNode->style.alignment == ElementAlignment::Center) { int shift = AvailableWidth() / 2; - TranslateNodes(lineStartNode, shift, 0); + TranslateNodes(lineStartNode, shift, 0, true); } cursor.x = GetParams().marginLeft; @@ -69,14 +69,14 @@ void Layout::ProgressCursor(Node* nodeContext, int width, int lineHeight) { // Line height has increased so move everything down accordingly int deltaY = lineHeight - currentLineHeight; - TranslateNodes(lineStartNode, 0, deltaY); + TranslateNodes(lineStartNode, 0, deltaY, true); currentLineHeight = lineHeight; } cursor.x += width; } -void Layout::TranslateNodes(Node* node, int deltaX, int deltaY) +void Layout::TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblings) { while (node) { @@ -85,6 +85,71 @@ void Layout::TranslateNodes(Node* node, int deltaX, int deltaY) TranslateNodes(node->firstChild, deltaX, deltaY); - node = node->next; + if (visitSiblings) + { + if (node->next) + { + node = node->next; + } + else + { + while(1) + { + if (!node->parent) + { + return; + } + if (!node->parent->next) + { + node = node->parent; + } + else + { + node = node->parent->next; + break; + } + } + } + } + else + { + node = node->next; + } } } + +void Layout::OnNodeEmitted(Node* 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::PadVertical(int down) +{ + cursor.x = GetParams().marginLeft; + cursor.y += down; + +} diff --git a/src/Layout.h b/src/Layout.h index 1b2097f..ce0ad2b 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -26,7 +26,11 @@ class Layout void PushLayout(); void PopLayout(); + + void PadHorizontal(int left, int right); + void PadVertical(int down); + void OnNodeEmitted(Node* node); void ProgressCursor(Node* nodeContext, int width, int lineHeight); Coord GetCursor(int lineHeight = 0) @@ -47,7 +51,7 @@ class Layout LayoutParams paramStack[MAX_LAYOUT_PARAMS_STACK_SIZE]; int paramStackSize; - void TranslateNodes(Node* node, int deltaX, int deltaY); + void TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblings = false); }; /* diff --git a/src/Node.cpp b/src/Node.cpp index e1b1764..4db89c1 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -6,6 +6,7 @@ #include "Nodes/Break.h" #include "Nodes/StyNode.h" #include "Nodes/LinkNode.h" +#include "Nodes/Block.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -15,7 +16,8 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new ImageNode(), new BreakNode(), new StyleNode(), - new LinkNode() + new LinkNode(), + new BlockNode() }; Node::Node(Type inType, void* inData) diff --git a/src/Node.h b/src/Node.h index 6f94d9e..5a200c3 100644 --- a/src/Node.h +++ b/src/Node.h @@ -15,6 +15,8 @@ class NodeHandler public: virtual void Draw(Page& page, 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) {} }; @@ -44,6 +46,7 @@ class Node Break, Style, Link, + Block, NumNodeTypes }; diff --git a/src/Nodes/Block.cpp b/src/Nodes/Block.cpp new file mode 100644 index 0000000..d4a9b0b --- /dev/null +++ b/src/Nodes/Block.cpp @@ -0,0 +1,33 @@ +#include +#include "../Layout.h" +#include "../LinAlloc.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(); + layout.PadVertical(data->verticalPadding); + layout.PushLayout(); + layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); +} + +void BlockNode::EndLayoutContext(Layout& layout, Node* node) +{ + BlockNode::Data* data = static_cast(node->data); + + layout.PopLayout(); + layout.BreakNewLine(); + layout.PadVertical(data->verticalPadding); +} 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/Section.cpp b/src/Nodes/Section.cpp index 570e932..b4b337c 100644 --- a/src/Nodes/Section.cpp +++ b/src/Nodes/Section.cpp @@ -1,5 +1,7 @@ + #include "Section.h" #include "../LinAlloc.h" +#include "../Layout.h" Node* SectionElement::Construct(Allocator& allocator, SectionElement::Type sectionType) { @@ -11,3 +13,4 @@ Node* SectionElement::Construct(Allocator& allocator, SectionElement::Type secti } return nullptr; } + diff --git a/src/Nodes/StyNode.cpp b/src/Nodes/StyNode.cpp index b28887d..f04fad7 100644 --- a/src/Nodes/StyNode.cpp +++ b/src/Nodes/StyNode.cpp @@ -1,5 +1,6 @@ #include "StyNode.h" #include "../LinAlloc.h" +#include "../Layout.h" void StyleNode::ApplyStyle(Node* node) { @@ -7,6 +8,15 @@ void StyleNode::ApplyStyle(Node* node) data->styleOverride.Apply(node->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(); diff --git a/src/Nodes/StyNode.h b/src/Nodes/StyNode.h index ed7470c..ce54667 100644 --- a/src/Nodes/StyNode.h +++ b/src/Nodes/StyNode.h @@ -15,6 +15,7 @@ class StyleNode : public NodeHandler }; 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); diff --git a/src/Page.cpp b/src/Page.cpp index 5b705f3..6f40ae3 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -635,7 +635,8 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) "Image", "Break", "Style", - "Link" + "Link", + "Block" }; static const char* sectionTypeNames[] = diff --git a/src/Parser.cpp b/src/Parser.cpp index eb926d3..9683aa6 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -26,21 +26,20 @@ HTMLParser::HTMLParser(Page& inPage) : page(inPage) -, sectionStackSize(0) +, currentParseSection(SectionElement::Document) , contextStackSize(0) , parseState(ParseText) , textBufferSize(0) , parsingUnicode(false) , preformatted(0) { - sectionStack[0] = HTMLParseSection::Document; } void HTMLParser::Reset() { parseState = ParseText; textBufferSize = 0; - sectionStackSize = 0; + currentParseSection = SectionElement::Document; preformatted = 0; SetTextEncoding(TextEncoding::UTF8); @@ -66,6 +65,14 @@ void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) contextStackSize++; contextStack[contextStackSize].node = node; contextStack[contextStackSize].tag = tag; + + if (node->type == Node::Section) + { + SectionElement::Data* data = static_cast(node->data); + currentParseSection = data->type; + } + + node->Handler().BeginLayoutContext(page.layout, node); } else { @@ -77,8 +84,12 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) { for (int n = contextStackSize; n >= 0; n--) { + HTMLParseContext& parseContext = contextStack[n]; + + parseContext.node->Handler().EndLayoutContext(page.layout, parseContext.node); + // Search for matching tag - if (contextStack[n].tag == tag) + if (parseContext.tag == tag) { contextStackSize = n - 1; @@ -88,6 +99,17 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) page.DebugDraw(page.GetRootNode()); } + // We may have exited a section so update + for (int i = contextStackSize; i >= 0; i--) + { + if (contextStack[i].node->type == Node::Section) + { + SectionElement::Data* data = static_cast(contextStack[i].node->data); + currentParseSection = data->type; + break; + } + } + return; } } @@ -95,39 +117,6 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) // TODO: ERROR } - -void HTMLParser::PushSection(HTMLParseSection::Type section) -{ - if (sectionStackSize < MAX_PARSE_SECTION_STACK_SIZE - 1) - { - sectionStackSize++; - sectionStack[sectionStackSize] = section; - } - else - { - // TODO: ERROR - } -} - -void HTMLParser::PopSection(HTMLParseSection::Type section) -{ - if (sectionStackSize > 0) - { - if (CurrentSection() != section) - { - // TODO ERROR - } - - sectionStackSize--; - } - else - { - // TODO: ERROR - } - - page.FinishSection(); -} - #define NUM_AMPERSAND_ESCAPE_SEQUENCES 14 const char* ampersandEscapeSequences[14 * 2] = { @@ -176,10 +165,13 @@ void HTMLParser::EmitNode(Node* node) { return; } + Node* parentNode = CurrentContext().node; parentNode->AddChild(node); + node->Handler().ApplyStyle(node); - node->Handler().GenerateLayout(page.layout, node); + + page.layout.OnNodeEmitted(node); } void HTMLParser::EmitImage(Image* image, int imageWidth, int imageHeight) @@ -202,16 +194,11 @@ void HTMLParser::FlushTextBuffer() { case ParseText: { - if (textBufferSize > 0) + if(CurrentSection() == SectionElement::Body && textBufferSize > 0) { EmitText(textBuffer); } - - if(CurrentSection() == HTMLParseSection::Body && textBufferSize > 0) - { - page.AppendText(textBuffer); - } - if (CurrentSection() == HTMLParseSection::Title && textBufferSize > 0) + if (CurrentSection() == SectionElement::Title && textBufferSize > 0) { page.SetTitle(textBuffer); } @@ -249,7 +236,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")) { @@ -529,7 +516,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'; diff --git a/src/Parser.h b/src/Parser.h index d685918..1cba032 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -15,6 +15,8 @@ #pragma once #include +#include "Nodes/Section.h" + class Page; class Node; struct Image; @@ -30,19 +32,6 @@ struct TextEncoding }; }; -struct HTMLParseSection -{ - enum Type - { - Document, - Head, - Body, - Script, - Style, - Title - }; -}; - struct HTMLParseContext { Node* node; @@ -66,7 +55,6 @@ class AttributeParser char* value; }; -#define MAX_PARSE_SECTION_STACK_SIZE 32 #define MAX_PARSE_CONTEXT_STACK_SIZE 32 class HTMLParser @@ -79,9 +67,7 @@ class HTMLParser Page& page; - void PushSection(HTMLParseSection::Type section); - void PopSection(HTMLParseSection::Type section); - HTMLParseSection::Type CurrentSection() { return sectionStack[sectionStackSize]; } + SectionElement::Type CurrentSection() { return currentParseSection; } void PushContext(Node* node, const HTMLTagHandler* tag); void PopContext(const HTMLTagHandler* tag); @@ -118,9 +104,8 @@ class HTMLParser ParseState parseState; char textBuffer[2560]; size_t textBufferSize; - - HTMLParseSection::Type sectionStack[MAX_PARSE_SECTION_STACK_SIZE]; - unsigned int sectionStackSize; + + SectionElement::Type currentParseSection; HTMLParseContext contextStack[MAX_PARSE_CONTEXT_STACK_SIZE]; int contextStackSize; diff --git a/src/Tags.cpp b/src/Tags.cpp index d23402d..3f08414 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -26,6 +26,7 @@ #include "Nodes/Break.h" #include "Nodes/StyNode.h" #include "Nodes/LinkNode.h" +#include "Nodes/Block.h" static const HTMLTagHandler* tagHandlers[] = { @@ -217,7 +218,7 @@ void BlockTagHandler::Open(class HTMLParser& parser, char* attributeStr) const //ApplyStyleAttributes(currentStyle, attributeStr); //parser.page.PushStyle(currentStyle); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.PushContext(BlockNode::Construct(parser.page.allocator, leftMarginPadding, useVerticalPadding ? Platform::video->GetLineHeight(1) >> 1 : 0), this); } void BlockTagHandler::Close(class HTMLParser& parser) const { @@ -226,16 +227,12 @@ void BlockTagHandler::Close(class HTMLParser& parser) const //parser.page.BreakLine(useVerticalPadding ? Platform::video->GetLineHeight(currentStyle.fontSize) >> 1 : 0); //parser.page.PopStyle(); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.PopContext(this); } void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - Node* sectionElement = SectionElement::Construct(parser.page.allocator, sectionType); - if (sectionElement) - { - parser.PushContext(sectionElement, this); - } + parser.PushContext(SectionElement::Construct(parser.page.allocator, sectionType), this); } void SectionTagHandler::Close(class HTMLParser& parser) const { From 524c27031a0e21997f94892d10f1762230c0024e Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 18 Nov 2022 12:36:28 +0000 Subject: [PATCH 06/98] Fixed DOS build --- assets/Default/mouse.png | Bin 637 -> 5582 bytes project/DOS/DOS.vcxproj | 14 ++++++++++++++ project/DOS/Makefile | 14 +++++++++++++- project/DOS/rebuild.bat | 2 ++ src/App.h | 5 ++++- src/Cursor.h | 5 ++++- src/DOS/CGA.h | 7 +++++-- src/DOS/DOSInput.h | 5 ++++- src/DOS/DOSNet.h | 5 ++++- src/DOS/DefData.inc | 2 +- src/DOS/EGA.h | 5 ++++- src/DOS/HP95LX.h | 4 +++- src/DOS/Hercules.h | 7 +++++-- src/DOS/TextMode.h | 5 ++++- src/Font.h | 4 +++- src/Image.h | 5 ++++- src/Interface.h | 5 ++++- src/KeyCodes.h | 7 +++++-- src/LinAlloc.h | 4 +++- src/Nodes/Break.cpp | 2 +- src/Nodes/Text.cpp | 2 +- src/Page.h | 5 ++++- src/Parser.cpp | 2 +- src/Parser.h | 5 ++++- src/Platform.h | 5 ++++- src/Renderer.h | 4 +++- src/Style.h | 5 ++++- src/Tags.h | 5 ++++- src/URL.h | 5 ++++- src/Widget.h | 4 +++- 30 files changed, 119 insertions(+), 30 deletions(-) diff --git a/assets/Default/mouse.png b/assets/Default/mouse.png index b5bf9b4258ef3bf2342a7d7ebf286e1778e84473..2ae2cf1c3ade778dd5028ee0558ddf5bd2d29402 100644 GIT binary patch literal 5582 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 QS^xk507*qoM6N<$f|MNNF8}}l diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index b57b375..2fe1d09 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -107,6 +107,13 @@ + + + + + + + @@ -127,6 +134,13 @@ + + + + + + + diff --git a/project/DOS/Makefile b/project/DOS/Makefile index b20dc5a..ab97c6c 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ 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 TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj +objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -67,6 +67,18 @@ Node.obj: $(SRC_PATH)\Node.cpp 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) $< + Section.obj: $(SRC_PATH)\Nodes\Section.cpp $(CC) -fo=$@ $(CFLAGS) $< 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/src/App.h b/src/App.h index dfdc7ca..c241f65 100644 --- a/src/App.h +++ b/src/App.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _APP_H_ +#define _APP_H_ #include #include "Parser.h" @@ -89,3 +90,5 @@ class App int pageHistorySize; int pageHistoryPos; }; + +#endif diff --git a/src/Cursor.h b/src/Cursor.h index 19887a1..039039d 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 @@ -30,3 +31,5 @@ struct MouseCursorData uint16_t data[32]; int hotSpotX, hotSpotY; }; + +#endif diff --git a/src/DOS/CGA.h b/src/DOS/CGA.h index b6a04e9..cc820d8 100644 --- a/src/DOS/CGA.h +++ b/src/DOS/CGA.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _CGA_H_ +#define _CGA_H_ #include "../Platform.h" @@ -69,4 +70,6 @@ class CGADriver : public VideoDriver uint16_t clearMask; int startingScreenMode; int scissorX1, scissorY1, scissorX2, scissorY2; -}; \ No newline at end of file +}; + +#endif diff --git a/src/DOS/DOSInput.h b/src/DOS/DOSInput.h index 228e4f2..782f083 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 @@ -35,3 +36,5 @@ class DOSInputDriver : public InputDriver bool mouseVisible; bool hasMouse; }; + +#endif diff --git a/src/DOS/DOSNet.h b/src/DOS/DOSNet.h index cd9f9e6..303348c 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" @@ -116,3 +117,5 @@ class DOSNetworkDriver : public NetworkDriver DOSHTTPRequest* requests[MAX_CONCURRENT_HTTP_REQUESTS]; bool isConnected; }; + +#endif diff --git a/src/DOS/DefData.inc b/src/DOS/DefData.inc index 85579f2..c231128 100644 --- a/src/DOS/DefData.inc +++ b/src/DOS/DefData.inc @@ -1219,7 +1219,7 @@ Font Default_LargeFont_Monospace = { 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, + 0xc000,0xa000,0x9000,0x8800,0x8400,0x8200,0x8100,0x8080,0x8040,0x83c0,0x9200,0xb900,0xc900,0x0480,0x0480,0x0300, }, // Hot spot 0, 0 diff --git a/src/DOS/EGA.h b/src/DOS/EGA.h index d03768f..6902488 100644 --- a/src/DOS/EGA.h +++ b/src/DOS/EGA.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _EGA_H_ +#define _EGA_H_ #include "../Platform.h" @@ -85,3 +86,5 @@ class VGADriver : public EGADriver } virtual void ScaleImageDimensions(int& width, int& height) {} }; + +#endif diff --git a/src/DOS/HP95LX.h b/src/DOS/HP95LX.h index 98828b5..ab01358 100644 --- a/src/DOS/HP95LX.h +++ b/src/DOS/HP95LX.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _HP95LX_H_ +#define _HP95LX_H_ #include "../Platform.h" @@ -72,3 +73,4 @@ class HP95LXVideoDriver : public VideoDriver }; +#endif diff --git a/src/DOS/Hercules.h b/src/DOS/Hercules.h index 9b8cee9..6cbd8d6 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" @@ -68,4 +69,6 @@ class HerculesDriver : public VideoDriver bool invertScreen; uint16_t clearMask; int scissorX1, scissorY1, scissorX2, scissorY2; -}; \ No newline at end of file +}; + +#endif diff --git a/src/DOS/TextMode.h b/src/DOS/TextMode.h index e5c6dbe..6a3fd68 100644 --- a/src/DOS/TextMode.h +++ b/src/DOS/TextMode.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _TEXTMODE_H_ +#define _TEXTMODE_H_ #include "../Platform.h" @@ -86,3 +87,5 @@ class CGATextModeDriver : public TextModeDriver private: static uint8_t cgaAttributeMap[16]; }; + +#endif diff --git a/src/Font.h b/src/Font.h index 885639a..f8ff645 100644 --- a/src/Font.h +++ b/src/Font.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _FONT_H_ +#define _FONT_H_ #include @@ -40,3 +41,4 @@ struct Font int GetGlyphWidth(char c) { return glyphWidth[c - 32]; } }; +#endif diff --git a/src/Image.h b/src/Image.h index 4a64ccd..ca8333c 100644 --- a/src/Image.h +++ b/src/Image.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef _IMAGE_H_ +#define _IMAGE_H_ #include @@ -9,3 +10,5 @@ struct Image uint16_t height; uint8_t* data; }; + +#endif diff --git a/src/Interface.h b/src/Interface.h index dfbb8cc..0e21f5f 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _INTERFACE_H_ +#define _INTERFACE_H_ #include "Widget.h" #include "Platform.h" #include "URL.h" @@ -77,3 +78,5 @@ class AppInterface TextFieldWidgetData addressBarData; ScrollBarData scrollBarData; }; + +#endif diff --git a/src/KeyCodes.h b/src/KeyCodes.h index 8ad5d81..e435df3 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 @@ -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/LinAlloc.h b/src/LinAlloc.h index 826e490..3df7520 100644 --- a/src/LinAlloc.h +++ b/src/LinAlloc.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _LINALLOC_H_ +#define _LINALLOC_H_ #include #include @@ -158,3 +159,4 @@ class LinearAllocator AllocationError errorFlag; }; +#endif diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index c3e9bb7..b146044 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -15,7 +15,7 @@ Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayB void BreakNode::Draw(Page& page, Node* element) { - printf("\n"); + //printf("\n"); } void BreakNode::GenerateLayout(Layout& layout, Node* node) diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index d67a1f2..25f30d8 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -12,7 +12,7 @@ void TextElement::Draw(Page& page, Node* node) { Platform::video->DrawString(data->text, node->anchor.x, node->anchor.y + 50, node->style.fontSize, node->style.fontStyle); //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); - printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); + //printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); } } diff --git a/src/Page.h b/src/Page.h index cad56b4..7f3d86c 100644 --- a/src/Page.h +++ b/src/Page.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _PAGE_H_ +#define _PAGE_H_ #include "Widget.h" #include "LinAlloc.h" @@ -135,3 +136,5 @@ class Page char* widgetURL; }; + +#endif diff --git a/src/Parser.cpp b/src/Parser.cpp index 9683aa6..1235e8a 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -95,7 +95,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (contextStackSize == 0) { - page.DebugDumpNodeGraph(page.GetRootNode()); + //page.DebugDumpNodeGraph(page.GetRootNode()); page.DebugDraw(page.GetRootNode()); } diff --git a/src/Parser.h b/src/Parser.h index 1cba032..5a4a3a3 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _PARSER_H_ +#define _PARSER_H_ #include #include "Nodes/Section.h" @@ -118,3 +119,5 @@ class HTMLParser unsigned int preformatted; }; + +#endif diff --git a/src/Platform.h b/src/Platform.h index 8474fde..fe1763c 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _PLATFORM_H_ +#define _PLATFORM_H_ #include #include "Font.h" @@ -129,3 +130,5 @@ class Platform static NetworkDriver* network; static InputDriver* input; }; + +#endif diff --git a/src/Renderer.h b/src/Renderer.h index e71ab5d..919864e 100644 --- a/src/Renderer.h +++ b/src/Renderer.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _RENDERER_H_ +#define _RENDERER_H_ #include "Font.h" class App; @@ -68,3 +69,4 @@ class Renderer int upperRenderLine, lowerRenderLine; }; +#endif diff --git a/src/Style.h b/src/Style.h index 0ca697d..acc093d 100644 --- a/src/Style.h +++ b/src/Style.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef _STYLE_H_ +#define _STYLE_H_ #include #include "Font.h" @@ -94,3 +95,5 @@ struct ElementStyleOverride } } }; + +#endif diff --git a/src/Tags.h b/src/Tags.h index e75b2bf..e7e0fef 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _TAGS_H_ +#define _TAGS_H_ #include "Parser.h" #include "Font.h" @@ -181,3 +182,5 @@ class ButtonTagHandler : public HTMLTagHandler }; const HTMLTagHandler* DetermineTag(const char* str); + +#endif diff --git a/src/URL.h b/src/URL.h index 0ecf41a..d90d468 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 @@ -166,3 +167,5 @@ struct URL char url[MAX_URL_LENGTH]; }; + +#endif diff --git a/src/Widget.h b/src/Widget.h index bd3e114..90e1f8e 100644 --- a/src/Widget.h +++ b/src/Widget.h @@ -12,7 +12,8 @@ // GNU General Public License for more details. // -#pragma once +#ifndef _WIDGET_H_ +#define _WIDGET_H_ #include #include "Font.h" @@ -114,3 +115,4 @@ struct Widget }; }; +#endif From c00c5248bfc9ef87847667fa8ddbf7e8fcb6d532 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 18 Nov 2022 16:34:30 +0000 Subject: [PATCH 07/98] Asset data packs WIP --- assets/CGA.dat | Bin 0 -> 10224 bytes assets/CGA/Cour1.png | Bin 0 -> 1487 bytes assets/CGA/Cour2.png | Bin 0 -> 1816 bytes assets/CGA/Cour3.png | Bin 0 -> 2379 bytes assets/CGA/Helv1.png | Bin 0 -> 1409 bytes assets/CGA/Helv2.png | Bin 0 -> 1784 bytes assets/CGA/Helv3.png | Bin 0 -> 2151 bytes assets/Default.dat | Bin 0 -> 19152 bytes assets/Default/Cour1.png | Bin 0 -> 2294 bytes assets/Default/Cour2.png | Bin 0 -> 2925 bytes assets/Default/Cour3.png | Bin 0 -> 3843 bytes assets/Default/Helv1.png | Bin 0 -> 2071 bytes assets/Default/Helv2.png | Bin 0 -> 2597 bytes assets/Default/Helv3.png | Bin 0 -> 3285 bytes assets/EGA.dat | Bin 0 -> 14832 bytes assets/EGA/Cour1.png | Bin 0 -> 1988 bytes assets/EGA/Cour2.png | Bin 0 -> 2495 bytes assets/EGA/Cour3.png | Bin 0 -> 3181 bytes assets/EGA/Helv1.png | Bin 0 -> 1780 bytes assets/EGA/Helv2.png | Bin 0 -> 2262 bytes assets/EGA/Helv3.png | Bin 0 -> 2702 bytes assets/EGA/bullet.png | Bin 0 -> 567 bytes assets/EGA/font-large.png | Bin 0 -> 8105 bytes assets/EGA/font-mono-large.png | Bin 0 -> 2651 bytes assets/EGA/font-mono-small.png | Bin 0 -> 1978 bytes assets/EGA/font-mono.png | Bin 0 -> 2343 bytes assets/EGA/font-small.png | Bin 0 -> 1726 bytes assets/EGA/font.png | Bin 0 -> 7533 bytes assets/EGA/image-icon.png | Bin 0 -> 8484 bytes assets/EGA/mouse-link.png | Bin 0 -> 664 bytes assets/EGA/mouse-select.png | Bin 0 -> 622 bytes assets/EGA/mouse.png | Bin 0 -> 5582 bytes assets/Fonts/COURA.FON | Bin 0 -> 14144 bytes assets/Fonts/COURB.FON | Bin 0 -> 19088 bytes assets/Fonts/COURC.FON | Bin 0 -> 13040 bytes assets/Fonts/COURD.FON | Bin 0 -> 21328 bytes assets/Fonts/COURE.FON | Bin 0 -> 23808 bytes assets/Fonts/HELVA.FON | Bin 0 -> 36768 bytes assets/Fonts/HELVB.FON | Bin 0 -> 50880 bytes assets/Fonts/HELVC.FON | Bin 0 -> 38960 bytes assets/Fonts/HELVD.FON | Bin 0 -> 58144 bytes assets/Fonts/HELVE.FON | Bin 0 -> 64784 bytes assets/Fonts/fonts.txt | 13 ++ assets/LowRes.dat | Bin 0 -> 10128 bytes assets/LowRes/Cour1.png | Bin 0 -> 1465 bytes assets/LowRes/Cour2.png | Bin 0 -> 1848 bytes assets/LowRes/Cour3.png | Bin 0 -> 2627 bytes assets/LowRes/Helv1.png | Bin 0 -> 1479 bytes assets/LowRes/Helv2.png | Bin 0 -> 1827 bytes assets/LowRes/Helv3.png | Bin 0 -> 2301 bytes assets/LowRes/mouse-link.png | Bin 0 -> 5551 bytes assets/LowRes/mouse-select.png | Bin 0 -> 5576 bytes assets/LowRes/mouse.png | Bin 0 -> 641 bytes project/DOS/DOS.vcxproj | 1 + project/Windows/Windows.vcxproj | 1 + src/Cursor.h | 2 +- src/DOS/CGA.cpp | 10 +- src/DataPack.cpp | 56 ++++++ src/DataPack.h | 73 ++++++-- src/Font.h | 13 +- src/Windows/WinVid.cpp | 28 +-- tools/AssetGen.cpp | 94 ++++++++-- tools/FontGen.cpp | 313 +++++++++++++++++++++++++++++++- tools/MouseGen.cpp | 85 +++++++++ 64 files changed, 632 insertions(+), 57 deletions(-) create mode 100644 assets/CGA.dat create mode 100644 assets/CGA/Cour1.png create mode 100644 assets/CGA/Cour2.png create mode 100644 assets/CGA/Cour3.png create mode 100644 assets/CGA/Helv1.png create mode 100644 assets/CGA/Helv2.png create mode 100644 assets/CGA/Helv3.png create mode 100644 assets/Default.dat create mode 100644 assets/Default/Cour1.png create mode 100644 assets/Default/Cour2.png create mode 100644 assets/Default/Cour3.png create mode 100644 assets/Default/Helv1.png create mode 100644 assets/Default/Helv2.png create mode 100644 assets/Default/Helv3.png create mode 100644 assets/EGA.dat create mode 100644 assets/EGA/Cour1.png create mode 100644 assets/EGA/Cour2.png create mode 100644 assets/EGA/Cour3.png create mode 100644 assets/EGA/Helv1.png create mode 100644 assets/EGA/Helv2.png create mode 100644 assets/EGA/Helv3.png create mode 100644 assets/EGA/bullet.png create mode 100644 assets/EGA/font-large.png create mode 100644 assets/EGA/font-mono-large.png create mode 100644 assets/EGA/font-mono-small.png create mode 100644 assets/EGA/font-mono.png create mode 100644 assets/EGA/font-small.png create mode 100644 assets/EGA/font.png create mode 100644 assets/EGA/image-icon.png create mode 100644 assets/EGA/mouse-link.png create mode 100644 assets/EGA/mouse-select.png create mode 100644 assets/EGA/mouse.png create mode 100644 assets/Fonts/COURA.FON create mode 100644 assets/Fonts/COURB.FON create mode 100644 assets/Fonts/COURC.FON create mode 100644 assets/Fonts/COURD.FON create mode 100644 assets/Fonts/COURE.FON create mode 100644 assets/Fonts/HELVA.FON create mode 100644 assets/Fonts/HELVB.FON create mode 100644 assets/Fonts/HELVC.FON create mode 100644 assets/Fonts/HELVD.FON create mode 100644 assets/Fonts/HELVE.FON create mode 100644 assets/Fonts/fonts.txt create mode 100644 assets/LowRes.dat create mode 100644 assets/LowRes/Cour1.png create mode 100644 assets/LowRes/Cour2.png create mode 100644 assets/LowRes/Cour3.png create mode 100644 assets/LowRes/Helv1.png create mode 100644 assets/LowRes/Helv2.png create mode 100644 assets/LowRes/Helv3.png create mode 100644 assets/LowRes/mouse-link.png create mode 100644 assets/LowRes/mouse-select.png create mode 100644 assets/LowRes/mouse.png create mode 100644 src/DataPack.cpp diff --git a/assets/CGA.dat b/assets/CGA.dat new file mode 100644 index 0000000000000000000000000000000000000000..1fbf3b176216685e09b1740b3180f4d3d79e03ea GIT binary patch literal 10224 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 UG`iDz8jaVg^)MAUIyo>MY$8wS$>W6 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 literal 0 HcmV?d00001 diff --git a/assets/CGA/Helv1.png b/assets/CGA/Helv1.png new file mode 100644 index 0000000000000000000000000000000000000000..4a249773dc0b92f2ed36db715bd83b6ccd4787d8 GIT binary patch literal 1409 zcmV-{1%CR8P)yE21420?XKlJW7TB)iE8GmCtDOB)dMNMFx#PMb9%1+VkzQ^m$$#SKf@Pa~w|)7C z%3+O;BBL~Efw#<8eKx=5UK`mp{O!t4Ho{xR6LhJbN?`TH1yuq_(AwmGEi zN9_4E@~N<;7)H@e(pWUpY>eiVw-38d=&6RwB~l2Uwyqqk{D;+)qB**mmlBjI_{4_kiOy%>|T-CN=Aa|ZV}p@MoUnf>cr z52u$v!X}2}LtK$in+Yd@=mFxr`@lo+v|haDmK zAn|kdYs#7qA>`pmhN~c)rrb(_EFDfk8ZL2JwY2ElK#ioUX!k3C>My?AT0 zvez?iMnw3c1X*_BgLGc!gLGmaEkHj_847V< zj&qZR7tU)DaSS0$;r(dqqIDt^1|8Z7g)3|-uL6MUIV>znn<8jhpsy(wPOiGX%P--? zVoh%y#LVGz8p$pOp**JDL=(0U)@)l1_Y>x}AkYD+4iNyCqdK`_X;@ z5^10J$nvsyB4H&_XWdCTTMIEE%vScu!dcS9QSNh@r{@x>gU*Y-LcdLe*=s5z5?}-@ zyw{;!T5IR_S=B2eNGe7L`D;@8iTpsK!o{kL&{-XVQ}A*Ig3afk9}dikwV5u0n0jJM z0-m$*0t+dJwk=m3a5gZ4ODU0@4OfBkATzz^`357l({IzrRYr_n;aL_}6}8>z4PRCv$92m<{t03);)5l-29B1y)$ ze%+^#wNnN5p8_KY@;++WP#GaCGyy@aoF_D3?onjieF@F89v`>R2m)Zx8UN+f@tjkEY_-MNYQeDEFdFoSsOJ8#3NkTJGV{4t;I-e z=5lQeOm2kQK0L_7EkUY4%;X>4V8l0p5v1+- zbvLhB2Uycd;?kEBw4o{D P00000NkvXXu0mjfF|Vl7 literal 0 HcmV?d00001 diff --git a/assets/CGA/Helv2.png b/assets/CGA/Helv2.png new file mode 100644 index 0000000000000000000000000000000000000000..3af093109729bd63fbe89312fa30f1fe63858fd3 GIT binary patch literal 1784 zcmV@F2i000KRNklOs13A(tb!7r|_ebf{if3_D z(8Bm%`9*?i-z#Bz;FI!G;fcGws!7gRPb<}mgbTFZbNalt{gZF6?XE&WEtE7ZXHI_i zEoLR`%qkbQsv=Oi*RIK}!s9>}3P}8_e`UCJ>sOlwlUjAIzhFA*-p}`!qw*1H>gUPp z?aHN>;OYtRsT&oqdBG)EeZC0YC4bGfU2CCSTl68qHg?)+r=51%=|q`*|HWxg{NkH- z!l%CDPCMmY-HP?nMr!K+sgJi_)1Dv?2aio>0ykA^B-< zm?9ci5{Tki=2_#?=Xq}YFq-QohCW@_!dOQEt(ogEGx8U zxBtR_gC*$J|7!C$#;)vd0ZQRiLizmlSqxz&g8YugftGMOlui+tPy|F3GAya}=g_7I zeMwm;+ABMPlp-;NF~m`Q+UJ@iP=uf}?i84>l5L;O#9M;D1!b&h%S#i#0d|ho26jRn zPO+G8_18ntBBRiz22JJFyH&scy-eT`4_Abg{Cckn=ip@?1jI45)@=M+V7Nd4T23(g zK)K3>7YKgJFIr0v`MxiWB6K)J)eX6bg74nZIK+4j)I{q-`&08_fNl)R z^MOME(1XuN5s_!T;aE5C3zaxyP`6LbzWSU}Ey17}+bHogQ04{pVsm{4rz(miN2myVAtnx&Rmuhyo)`$Wz#+;GUe=yjheI52 zRo;3LGvOBq+-&Mbk`jFF+qA+p}14lnqj>0$0E!-G;~i-yI<_%@wIN>h`y zD$dR?s?|OzO;GE4zv==UFD=U4V z3%lWjI0BZ)6QD?J;k+D!SbGu$bsnPdrq7o9j_|56#d+k>b3n}{rO4)1d_O7cn`ev0 z+&IJ+yZB=`M9n7cD;@C86)k#kwS3lRb9TL-Up{>V>TOL9v1HfI^&Fz$D2sW(bhok` zxaS-8zUAzjLqj^jlm&(OM&Q~WtBpehu`hD+=2dE}gbC7)*cf$tnT7r4B^)RS1=xdc zI#gP^k3K~(^Zk|`9AbG;AsZRZ?q&WZafpb<9LDAy*2Rc#MR#WC<`6fBi0Ba+CV@eA z<>w)?=P=ZIb1zQN98!2*s!8`}Hk*nQ`Y5@?kPotS6kS>U@vPKb(sNk+OLP z)-&NeM2CLGm3Jz)xVH!YrV8$8b-)C8fTmyzvBW{i{_)|RUn9H0okPBTHOaPZ&f;m> zUD}eB&Na;o)%x4{+#VY-fye-Ij1H(-*o8+=xVrwk^9bG7pjwF>8yI<`L!lP=F+@I1uU zpbd?ly6QYcN`T9SWWdp#XV=d-_i6ZJxn``oJkUFMy3nmKJJ95%nT|7sAzIg)6P>=yDpd^_y{+Nm3Lr?F)} ai~azG&q7gN@e~- literal 0 HcmV?d00001 diff --git a/assets/CGA/Helv3.png b/assets/CGA/Helv3.png new file mode 100644 index 0000000000000000000000000000000000000000..923c61ef710e96bf816b00d1749a5e53ab5bc96d GIT binary patch literal 2151 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 QKmY&$07*qoM6N<$f@a)`5&!@I literal 0 HcmV?d00001 diff --git a/assets/Default/Cour2.png b/assets/Default/Cour2.png new file mode 100644 index 0000000000000000000000000000000000000000..e51597fc7728d9814c16bb1a10b8f268b85f2533 GIT binary patch literal 2925 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 XFb(@F!^lFS00000NkvXXu0mjfm3PO| literal 0 HcmV?d00001 diff --git a/assets/Default/Cour3.png b/assets/Default/Cour3.png new file mode 100644 index 0000000000000000000000000000000000000000..9820ccc1f93595bd4007756309802050166aed04 GIT binary patch literal 3843 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 literal 0 HcmV?d00001 diff --git a/assets/Default/Helv1.png b/assets/Default/Helv1.png new file mode 100644 index 0000000000000000000000000000000000000000..396b446139d82bf7d049926e1230bde2197c76f6 GIT binary patch literal 2071 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 literal 0 HcmV?d00001 diff --git a/assets/EGA/Cour1.png b/assets/EGA/Cour1.png new file mode 100644 index 0000000000000000000000000000000000000000..70931fad2cc7fe0fef4f3c9ac20222c1bf8d0fb5 GIT binary patch literal 1988 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 JPDHLkV1oN#)e8Us literal 0 HcmV?d00001 diff --git a/assets/EGA/Cour3.png b/assets/EGA/Cour3.png new file mode 100644 index 0000000000000000000000000000000000000000..147e2a46d9aee7976916681787c02a29d24f6c2f GIT binary patch literal 3181 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 O!DM1&0joA}js7QuhY=e9 literal 0 HcmV?d00001 diff --git a/assets/EGA/Helv1.png b/assets/EGA/Helv1.png new file mode 100644 index 0000000000000000000000000000000000000000..e75a4555859480d260eb030901bd199d11ee7c7a GIT binary patch literal 1780 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 literal 0 HcmV?d00001 diff --git a/assets/EGA/Helv3.png b/assets/EGA/Helv3.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8daa87fd0fcb11fa7f8c1e95562a4e1d6c92b5 GIT binary patch literal 2702 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 FV1m3)=Vt%_ literal 0 HcmV?d00001 diff --git a/assets/EGA/font-large.png b/assets/EGA/font-large.png new file mode 100644 index 0000000000000000000000000000000000000000..76ab94dd046e9e8fa4e465f86873673fa6f0f35a GIT binary patch literal 8105 zcmeHLXIN9&){d1Tpg15>lo(Vb7*a?mp-T;l^sY!qP9TJkkc5s1sHlKQM~a|`iXeiB z;7~+F!~#lJ1}RdcsR%|85WFYosPo@$-~Bh?NuG1oe%HI!e)n2?pTjAO{l>zA zQi2c&M3`V=UMf8cq4`s88X{g+as)StUs6)CtdM%6@|o)<@T zzqhXH_EmkE6`bW7bV<|Fa+U1q%`$xAPVEXFhn^9BQP|Y$6@W3f2=Nk8k}_{L@0a$^ zaZTL0R|a#oIqGiXXEc^f=2@a4{VEv>0SLE0!_l~x1w;Dda``42;aYy00UNPv3`I2e+gK+_v4LrY9 z&w|hK(yk#={V&*d+tNHwTkGeSt4+1U?Fu?>3iXdE(b770!)WZ*>h)VOkDDz|2+PvB0&|TR-%e_qd&c3Zgi?!#1xn6$ejCqF z$t``-d9a+`N1qLA-;;C9NO;|uO^Z#x)s35XYNss(ca9QIBXhQ2xS>izWmF$%cHGZp zu6=NO!{+ij$m*sOH{aL@%D!HkzmwRfXS+*ouh!6Rd`x(4$tm1uQ;!7u$YW>tQc=L5 z`RmpT@4mPyZ5h`-yCBCna_={MMr}yGsO?!Li<{>^=aiw1eA*k&9&|6bd1_KLOm5RG zx=?8kD=)EXtzT-?ac9c?t2E!D@N(S?q{PQa6}c2+O$)Y?_A)L+p={>!U(7x6(k@cg@Acb6J!AuoPCR8q6Cb>q zyE*Q-*|o|LyQxzbV|6jKrq%-!xyL2dhYXc%_J3-m1+AHhC7L)vp)_i$gF{@f*P9?4 z`>nyO`g?EUl+AWex8sA2-vb%pkZnyXc2^X5HT7sp^ijhh6M~!)wez@ zHwe5p!>Oa(&6^Q0dgH0qNs9&w2&_wDw3CE^n^zHTZ(gofpFbPdAJ zMm~Kex$Ugj?X5B+teyM#Mr;Hn&r)A(>9KDSkD)UnaCj@ueMe8)Y}yvvH|(TMoe;xp zU)6TY`WWflldG#cJKOon_%m-!LmjWK&m#o`2Ul*p;UUzxI#BU`HBcHA5iVp|7^kZD zI4scfE`P%dIq$s{X5mq1s^{W0FGr$9BP9p3Cu8qwzZPyf-`P5;F?8k5DM zw<{>cM$l?&v+MJZM&GBPTfv=Qe<+${us1X#&{nkCBAVt`IW|=upMLl=Mi#laKZn?%mDP~AbiTN0G za2`vrwiaZTtI;~7Xg!HFr~}QZ$_K9CVpea8HxS$?)^4FEu98zak?Ua67WlG3XK3Df z$m*f(3b_VNmGgRji~UF5SC-qMRmOH4AJmw`G(luAvIl!`_D)^(FDG^$zc~En*b1GqoX( z;~lNSOeTL!%C6w1kshschZo8(chtyoNv!Sxc%e81vObdvo>dNJ`|u?lc(0&_I5RIfx zhUsVvY6jpz0y@AULIdbt-fVn;7HpXp4_@=kNEmb(!l7xw9Ly}B`V1BT#VTQyPzb{S zsvicXEeO?Qktuj91EX&g;GGuCgTrCskw|}ken zc!LG!0|Jm36iN+&!XPl}$e;beQ8Tme-rnqQDuQ|<1Bgr{S_y@u)BoVX<{0|@i1()+ zY#Z>A0ci!W8NMtMVCV;UbL4&w%JlMO{~Xho4e(6Md3%v5NHD48$UplS6U;2W`|xCR zr_!0r9z68VNHXa=j_J$tTE>t`NWcrAgNd*~X7nHM94h6<0{vk=ypexP1Pu3`{}1Tj z^;*tlSy#LPgXGH#N-)rZ@$$u!86+whzkKPYf}@aC)W`_38xf7bx}nq&ZYY#8LJdP! zC!v(xRIw!TPgDeNHizg<0(ev)xe^uRA**6lC}biLfx!Z*2&}4#GQy3bib1GRh!hGL zhsNPhZa-0&v#4Mv5WRlRibq8TsZ>-jfGQ5m21g=;RA@IMf=EJ>5pHM-$<2+3#i&!FCvG+V9~+$qI#1V{_Gz^HdH!b%^~t+L#tp^)X-RzsYo%cfJUj|a6~nPGM0=&VAY5y1d#%`A(Y9WokDTLxM6_h1^&*?W>7f(L>8du4%Ps; z&R}ybuQPPlxB64~Bi`Qw;1w4NgGQiK5g3#W293w6;&IBbud5B!MDljn-)F1I+k?!^ z@IULJ$=jpw1YRLp`!bncRDktIoqn&I{{{Dx|F;_bPwqd%zIy93n1NsydvMJCz5g=& zp8&ry?4yzZZ#LtvQvVt9RhFL)A~5H#Ht>K0&r9U@1M^#z@EYmA`1w|S|HTL(^*=%W zmcIYU^^aVCOM$-y{-?VBk?U_M@VCJKRM-ETT!KGdxdCtRxz8Vbq0U$lz8!oSU+cEl z*Z}f{_jkVfN+M_xV466xA&_;y@V+Y`>6x2BBOixgX2>_pCnko(-Hv=x0GcE@h7KHk z2A%g71^M>04)v#UJfOTsd;4$^2?&J0nqZ)36Y$&HgN_N(XnuNdNOa?(d5VmjkG9o0 zOaAUvrKO1c1IaOnZ7ba3bt|;8pN-Vsee0(YJE(T&%jXXtM;fxf=wv^u={sk3=z&AC}_Orzo*-L-N@v2%@GIl4VM@CGO4oemECV2#14NL?CWEG z@g5smVTvrQ{@9%&F~U%wtIb>VmX*eG_2(W>r93rEln_yUu8OfMcqWNG|2fHHyPn-l z)uQvnrneGl=F!fr5*rs=aOWQxh@G*Y*`>X1{Rw3ad9yQRA3Q!n%e|ZZQr;OqFc~X+ z6tn%1Wst$xMz0T43;S0=?(HbOt;pT7bx0EKLVTEl8 zPVl`5t-pi#0Y$0Xp<@*_k+=W$GfHWxvRd7*Q&QptqV=nThG)kdF4aGn%JB`Tinbd+ zF_=2S6-Yk)#z8bq+VH3%98Pm7)Q26;8<2YCWu#(hp;el2p&-32c~!S*Fgj!DBKgJ9 zGZXBxJ0HeF+Ly|YwB1RKF!R>T(*32jqfXR#_)f>1yi$oc~vi4oV<{n!=0vHvOFXB2m(U~O4?WAn8zK^GXeC~mmrliI?O~6PtCgFZ ztD#<%wGoMYdwWT<>JoQoez3$QH}IAwx7A|MaN(S7*2P7wX#H{I1jGKWTuxl>L4(5H zP8K9LNgBTUQok7-Xn~<5e#TRCqc~o1D0pzoS&lrQM;GE2RKLU#_)V zjm2(AdW}3yF!i}2t-bM_Q;3yQ3xP2qcUoW2&r*NatxF;Ck0q)Cs>a@N>smrnjz+#G zq_02c{1Go39zA{IdE^#(<-De=tqO|X%^p=LokAVX>7QNJz^ENA>Lntgnku0j*UI!Z zr7qKUhQ`>yEwtS!tK&^2zs(E?=kO^5>syYlJ>}=vYczW5v8?CbyiBU!6VkI?TXSXV zt_v=}S znYN#>>_ytuJgq6)en$j}cq+^%RWUZl||=w;uUVORJn zzhXvE@0|z}(4OIERi9wlDYzR_`EON@(r_(dIMcRD6;bS{XE*chr6e38nKmgktSV!) z+Ak~7fAZ{YXZDzXd}u)o`Et$p4VFTkm2l=r-PC5Yu3H@Ac7gQa#^AOMmP=1x=A|7b z(Nzx53981>{DkAL3T`3yTnk>R7VOL8lA>dKcW*@OV6+FF4+y?|dp3Qfk}h;-PkOcc z>FRsYA{qNb562yBP?XuzW2`YC^9mk&T`s`R{{G2T4;MRW&BpxXo$fU`eJ*UHc|#A~ zIci~p2mP9HR;iPVjlTyD^^$(!VQ{UuUYwyagIb%MsjJwZHrD@)pQ-#YI$k&po)$G- zc}1V1mDcu)J!9_H(0xVY^p~^mZG*bzl987BSyJ3~tWk&%=c2;EWaCQ{DREs`C^@BE zPE~bx_9LhAggZC58>Tf2MwTwxmMETh{<5{b-^6u-Fc;tZFv*j7%g1g_Pde)9YyAhh zfwUU6JGLz?ZH(c}i;FWkdbbkMIh}hecY7XlOB(77vc@bl9&?Y~dekv6UB2VPEQ_4q znUm6st=QR-PP?@4W5557{b#&RpS2&dJsw~3s@5@WU_ULWNTwLJ6lPa68K|^#ki=Zv zyBJ6=8}E(XO?RIwI=NnaxG*&<>qhg(f?JM&zIr}ea~_zt*3IjORrX|E(Sr9LDm{6- zV!&wJ0p@#7)EKk3CT|Zn_nsXeJxyAo0GKuP^)xwGZKP2^A?3cDNjcRx|8N{F?4IPe z^{|bvs8eLkXj7Ig_u>00`I;RgI{3>mt1@qy%Xy@Kw1y8|xzZ$C7#-9NC0tiT0$lG- z%BSRbMWu{u5s{Jy$G0$$8krZ|+q3oOAG;Jwjj0Z>i^Qz8D6kq+CbjxT2it!5T<_kO z%)I?0iXG>iQw3F?(?^!%q_QJJmTT!NgDoIgECq#9AvXZKDprz9z75tZXZIIW- zg_SQQO?@?HrF(1R80vk+#Jb4mB|2TfSd|wm^mg>v1)p-ObF%OmEZK3h_u9(!pVwAs XpC8^>oBSMn+JO)Z_ZwW_;~McVl_yDk literal 0 HcmV?d00001 diff --git a/assets/EGA/font-mono-large.png b/assets/EGA/font-mono-large.png new file mode 100644 index 0000000000000000000000000000000000000000..e75f79238ff3c625e24590aeee3c5390c1984506 GIT binary patch literal 2651 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 JPDHLkV1m6{CBFaw literal 0 HcmV?d00001 diff --git a/assets/EGA/font-mono-small.png b/assets/EGA/font-mono-small.png new file mode 100644 index 0000000000000000000000000000000000000000..7140d73aec5e7f23862dbb84db6addc8a846acab GIT binary patch literal 1978 zcmV;r2SxaaP)EX>4Tx04R}tkv&MmP!xqvQ>9WW4i*t{$WX<>f~bh2RIvyaN?V~-2a}inL6e3g z#l=x@EjakISaoo5*44pP5Cnff+}xZLU8KbOl0u6ZA6(wYdG8$VyAKc=6{eb96M(8& zMk*c?v$<6<@QMHi5TqKDn5ieyiy3&XuY36Tei!9g-uM1oy-MC>fKMczW4d7xZxGLH zS~}-_;xH>o3h_DdxIqmPKXP4m`HgeYVS#6cjZA8uI7}=SJ6P^uRx(uLY2t{YYLqWz zT~;`6aaJoe*6NeLFqGF;(p;w+MjT5>APEsNYAB-u3sKrNQcR@iJm%paa{Nhh$>b`7 zkz)Z>sE`~#_#gb9ty!3wbdy3cp!>zPKSqGSF3_yo_V=-EH%|cnGjOG~{nZ9A`$>Ae ztwoN2zHQ**x~<83z~v4w_@qmQsZeTyy8vI>+e)kfB*E-v9@P zz-W=O*FE0d(>b?)@3iLk1Bn=Nuow$0;Q#;t32;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rg2Nn|_3Fqlw(EtDd8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z1*1tsK~#9!?Oa=~+%O1@rLLjb|MHwoxL>I(1c#@cj1>R0qdWKk1`ZET8)J+EjO)PT zuk!bg{-gisKl+dUqyOmF(zn)*F~N{7Jb)r@Sa2S!J004=`4PC*L)FL4^0F=aQeH0kKcGN z?($*?jfT5Btu;TuUpekg(#>)~0adgpDDr?LEijQB?i+&yjenAnvht0i@8Gir7OVym z-2nvls`mdBxYrys+P_58rtcu2E3MmrzRm$AM?Zg-^p1ku0RXpW{rU670CP$1L*S>h z|30mUg9(svqC*e8eL7UEblDml3r{`Zy&)Is)e`Ld~z*^Hm z^x0!WqIRchMCXgzPS1K4n?D#mKMnI&^YPb46^45|xR2llHBDo`yAaUQ)!MXxc*y>JO%ld`y&Mxrz3Nmq1WU$K)`fulF6($lKx2de*lMD5uCp% z>xgtms$f~{muB;=^qvZ?Bs-1Xd-iMwm#ZLkp>rWp!H!noP;$XTugQ<{i+;{l;4~ga zD|k=nGTr~S3_dcD(nYaGFccilLfhfQo8W9EV4Nw@WC6+!aBBgoJ>V2QM%LS6WVwHq zER3a1#?zV>k+ro=t}?)qoLnV9tB}7}k5_=CRr;3&iCOtc^pfsW@<9XW7W=2sT7uhE z6}YDdSN-Zr)xcW8iB#Wcg3wdFjATMR2`-Br@!Wk5J8=~#Y5yAkA{98g{{4Lpe_1dG zlApCrE?a?<{D-%KhgyuYHczVHN_TM!z?ZW3$UsO;;($IU+?^SqAw-hLakxtSRR-a$ z;9ldy%s1N zSa}c)RIEq*=Gz; zEzV*vFa4jDN{J#+OZGhRnUzl3@+*p6061}#6FDOFqNHQgQOQ5qdgJ@}wqvxkt zd-{fkll`2*k(_K7`Y+M?+MPie6wCVbJeC$atL|_hnbLCEcMt!y_7YBiBK(u+sgvtt zm@|0j99H|0NzNeoBRNXqEiO&v!&%onceb0PU=It zf`;{pU%;s<@kV7fk`Edy?#{|RtftrIu->1QzktyL6|b&1%WM{IwcNbxbH78J*@I@k z5bYJ@zsC-Yz=Qi4prG}yBHgL9A6DN%{YQjiiT^HlQUCw| M07*qoM6N<$f-Qc{m;e9( literal 0 HcmV?d00001 diff --git a/assets/EGA/font-mono.png b/assets/EGA/font-mono.png new file mode 100644 index 0000000000000000000000000000000000000000..0b692c5bd39e5ddbf435953ba966b26c67cc2c06 GIT binary patch literal 2343 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 UJqsmfhyVZp07*qoM6N<$f(=kfiU0rr literal 0 HcmV?d00001 diff --git a/assets/EGA/font.png b/assets/EGA/font.png new file mode 100644 index 0000000000000000000000000000000000000000..2286aa810500184485f8434d9cf1baab6563d5c0 GIT binary patch literal 7533 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 B7O4OL literal 0 HcmV?d00001 diff --git a/assets/EGA/image-icon.png b/assets/EGA/image-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c700da4d885849a6866bbcc40b7a5aea56a00e73 GIT binary patch literal 8484 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 literal 0 HcmV?d00001 diff --git a/assets/EGA/mouse-link.png b/assets/EGA/mouse-link.png new file mode 100644 index 0000000000000000000000000000000000000000..e474807658e2397155198e44c193b0c077186195 GIT binary patch literal 664 zcmV;J0%!e+P)EX>4Tx04R}tkv&MmKpe$i(`rRp9PA+C5U@H~5EXHhDi*;)X)CnqU~=h)(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7NufoI2gm(*ckglc4iFj@rka6qK-DZG zorsIM{E8TSMF3%hFpO@AnR+s_kcH>?x`&UicQKyjeeTZ@RthEqd?N8I(+!Jwop^fF z(mC%FM_5@>h|h_~47wokBiCh@-#C{X7ItphU+wkkia66NI`^*8a7aYg&3_GDJIgiANTMNI{p;7WO8kQ zkz*cJsE`~#_#gc4)+|m=xJjWn(Dh>5A0t3;7iiXP`}^3onsb!yXyAV+Q;bwkfpAcZh(VB zV6;TpYaZ|J>Fn*_Gp+u906WQYks#`dEdT%j24YJ`L;wH)0002_L%V+f000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2jv6}3^ouD1}6Cc000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0001QNkliUaF;JDr^PRC~xQir)b1+>av yNc2{O|MuXyp*p_TRZRavRy}H=qy&rW8|Iy!wkjBaP<7}40000EX>4Tx04R}tkv&MmKpe$i(`rRp9PA+C5U@H~5EXHhDi*;)X)CnqU~=h)(4-+r zad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7NufoI2gm(*ckglc4iFj@rka6qK-DZG zorsIM{E8TSMF3%hFpO@AnR+s_kcH>?x`&UicQKyjeeTZ@RthEqd?N8I(+!Jwop^fF z(mC%FM_5@>h|h_~47wokBiCh@-#C{X7ItphU+wkkia66NI`^*8a7aYg&3_GDJIgiANTMNI{p;7WO8kQ zkz*cJsE`~#_#gc4)+|m=xJjWn(Dh>5A0t3;7iiXP`}^3onsb!yXyAV+Q;bwkfpAcZh(VB zV6;TpYaZ|J>Fn*_Gp+u906WQYks#`dEdT%j24YJ`L;wH)0002_L%V+f000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2jv6}3@kX6o^;p%000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0000*NklSqgW(FLdObav#K@zt6Y?|05HqTBkT=D~cz?)Y-EfcqOu34&N#8t{sPyhe`07*qo IM6N<$f|cI?X8-^I literal 0 HcmV?d00001 diff --git a/assets/EGA/mouse.png b/assets/EGA/mouse.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae2cf1c3ade778dd5028ee0558ddf5bd2d29402 GIT binary patch literal 5582 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 K39bJ}?|%TB%$5iM literal 0 HcmV?d00001 diff --git a/assets/Fonts/COURB.FON b/assets/Fonts/COURB.FON new file mode 100644 index 0000000000000000000000000000000000000000..b0ef8d1b4bb3b3010616ced38d24b5d9f7d85eec GIT binary patch literal 19088 zcmeI34QL(5oxn%WPx4CL$g5{1QCvmZ_oAeVYg}2*IkKWWJ-x_|W&Dw-aj!KYD0b|` zu9K*e8@oPNT1*MXgyJ)8FPKt{2_cwLiU}c@QeNGVD<&<$gi?H^=$sIO+w1vEDLoV7 z`~Bx*_kBs>=)pB5WB=aH{{QnmJG1kj9c|wJPim1;Duf$1(n>W1zsIWz^7C6JIN z^lWeKezp0R+1DF3{WAN>uJ@;GeJSUpuWq$f6?Q; ze9$+f(rR(kprdV89qJ)}RQtV1<`kuNsb8wTPCpWVf5SgF^mX2({Xb8=kH1y>m0G-h z>y}4G$Hp5U*t%`JC}GtK5sk0=Vz_Gc>J@9(thl#791I_L_Ulu7c1?u?D^~TbSk)i) zesS0S@Q&4Y6aMM_(+8&aJ@eJ47b%}PEZ?kD@6Nl4G5L+DJx@NhKP>LqKfQly7f<-u zGdov?tJV(u!^&`E-@Z_*r+MzDcTGLF>xq>*d!;T9MrlJ|!?%k^Yak#{S3uWYZ*I~m3gJUcb1 zD}L`n758=CzJ4c=JoWa~64mE(k-Q($eSH_%ROB5u|4x;9>#wf=$F%*w>#X0f^}jAp zny0QW@>DwQsaEKK9ISvfP=Jl_2s{Q)!4w>Z=ixY2Up-v@DX^8 zp2|Qc+y#9w2&1qCz6?8HA3O&U9E0z`58xFz3qOH3;cd7C@4+A8FOX{Tln=K+2+QFf z$ioI0heu&IOu`}f7JM6CgwyatcnwN$0p5Y%!3S^+K7^*lo(fqqYstthlhdjZblg~Jcd3lOjAob6nr)t;A6!V*ukO6B#h{gI@2 zM|nY5z}J}**NM*?kOE53&YY;N&rfM>+Hdd4rBuyQ08p`xtENQ=o07{`H{`r9xDtX8}QoO^SJ}~X0q9QHaml_ zD_bZOvR(KN=d#&s?l8XDY&aBVXPqxRXv00I!yU-w@;RLf4Q1=wMLUj;4iIiS*E7(Q zo5puonz>C<*|}!tXBa#P4Ub0Z@ox|ULwQ0HM~pEcz(spLlr`Gyf`$=NZ< zP1=PL*cQjvF5_RzmM_iy_9F#dz9w|V;;;^#ADJyknf+jh%%ts=8ZH*|DZCTm zihk1?j0xL*X*DDzpaCbsyJ*0jgzu2*>ZeJm{mYjx|Ef-9pN%W|j_7=KYte+FCJ!JVH$8oJW`g78Bn`(xw~j)oV1{+;eniKKL!I0Xa&!{F%Cn|& zGHu8cd?0tgo0XBGLz^gFxrch&TSghm@iWfE@bJT%^wPqTkqC`V_PY>ohSXko3*oJI`(<(PL_-3&OlxxxIFW; z^Lg#gms8HC=iP6}yt{Ubn;iqs&5r5V=e0Z8+AYqPE1N}==k)g6y0M3Ik5Y3tPo`s^ z*Y0Gwcor3{n!^VbaPnUJ#B*-_`AWjYV- z(|NdXZtj%aw5)8tQVn?;Fkg_8+If;xtdCUO+?jJev~Hf%?}dCgA*0O6XiV*#HK%V< zt+!R%RR3FB@?-3|pJ|(_iEPAad#0I7v&y8K)p97pc9?==a2n3RMYsZ~#%8q)`e7sN zfJ1Ns&cJ!N1lORYsafS<5XNCI9D$Q?7B0YLxDJ`c3D5m0~l+{SRa*f662~uP9n2VA&YN(tiM`kR@b$1R@xPoq7IRmXtt&% zUKcBOTvv4}X=4U-SIfJyI-#1Zk+(K9)TWbaYRTk=Q;Vz984kmIz3{C;xN=pU@(i1N z#h9yblwYb)Q+|YZ+}@BfE2fGV(l&M4N6YH8vsjR|-76^$^RLz=MrVc2w3{xL(|X^&gf(%mdlU4n}jsoHp7yH`p%TewfFWrMx5zdq0)A&F=Q*`v)P`Z9%I(l zk!u6ALN4h)IFht;WVP>(jLfoV(B-r;eK9LKYp~g{a#@nX6^nZHssmcxfm~1TJ-O`H z?5$iP`8gPh)5faY#_nf3w~f`y_g_aIe|T)&1OFufqd2sz;0SF@>@umd7HjM4!uzW$ zg3De(HeaWl_dhT)&@}S+W0j%#-glHiZOm@v`c}fwZsPLdkL1U?cSm9)zDhnewtP)a zn=6qE%vG;eje%b#mGt9fY(+ZS_76v9D*|)Vs*CA)vLjROl6`?#8i~*D5~QmKn4ifO zL~mW>Jn{8L?30SKs+`ZssyUg5T`2YoR%f%T_qqGzdi%~oVP|!5R8wl%?qYGbv8j^4 z__`jStrNbrt!$H`BK&>pi|Yqd=E+SIX8%HOup~l!W|dz1{HIOJneCyn7!%>=WB|x^ zRE3juJx)I-6$c@&`SPpL6B}oJyPqf*@s*o!6`>}1?%=2G4Vh-dw zJL}cgq!Ub3C8K7GzS;b$P_ShqTRF0qQ{klXjjIr2%BRzdpn3_D?Ddd!f;h>?%o}FxBZ5DFY_XbY!DgicB{}z%%wXkJBE!#H}N@TeQSi`Y#M)Pe3M-- z$+9joF*;LSx!db59Gf#e*n@T4Y=hpssMXX|R+{0l@khtUtMjdV>6WMDXY3|;xlsJ^ z_GP8Q&!oMsN|C-pUN0ebgXVKAq*zLYg;*bnZ}^+v{O0|Qjg5TekNZR`>Mzh z_tgoX)?cWS^|OSP?Zo=)e6u@SyZ$$OwY74yb6YDn`?$68&YbM(*2ek*noyPq|v|c9^T>dcXO< z?T0?8taWR*{JgFmVfUhI>Zg<{m z?S)+Z@Ls!DHysMc*P^3G-8!5h5ZMA7`=avs7yG5I9YOi)$|-+cIXmQa<)p7Ifa-NM z>DQHa^wgL0J)&-Sb&GdT*UnnIpnsG8dQh{j=Ab}bJA(4ml~doka{8;ToOY-$pYQos z4P8%;jZF^~Y%kdh?N-;mpqzE(=1WE$dqjWNsFjmWefwe}9J+(8ry>2>sH{x#1w`zc zT6@F=D*GamdYD&i<@vV%<1=PI(@Tmm`zF`lLwvr6_}Dc*=ec~&art};@j18Ub6(5m zoR-gb5TA2dKIgG~&SCj{1MxX`<#XQ3=bV+#_Ya?QRX*pbe9lq%eEaY@H|29)%IBPv z&vy@>b5TC$p?uCk`F!*6Irrpq-pS{jlh5}KpL0z<=b3!YG5LJ!@Hw~Sb6&~koRZIX z4qyEO-iLIv&o>UAb4NbsjeO1-`F!8-IalO!p2+7MkLy=x!4PbRgYZ3g6)wV6Xl?b?3fKrw z!SnDEoQFC12s)See6R4;m*F}14xELz;g8@4KHn)gIS9MqTW}gm@H_YrmbLk60KNqK z;RO5`eg%Jk7FKAzupXX(BXA0S3crQx(6N;Mheu!vj>C`OEw}<+JN*x%umd9a0sI6m z!C&B(o9TZThe`N0{17g{2hem2{SQT$fEoBcoP*!MpP~I$`X3&GXW#`m1HXXxA>Bd$ zLjfLx!*CK_hhM{=Aafi24_ja#9D`TjO?VGd%jkc|!=rErUWC`+9k>R;XX$^~1bg8q zybN!^Wl){;KMcWkI0)Z^SK%UDh1T2Yf7l35!SnDEoQFC12s$~xFldLPn-S_RotgC zOBQpEGj>uc{>P4-ho!j^H!LHEaxD$K5!R!?u!U* zjB_EHmPu=+Qr%@S@RrQ&5L4l=-0duFMNb5?5|oydh}_QUJGHnY zKoE4;C+F=xY4@P7WV8}joQ)ezgxrJ^QKBtNc`gj;eO)ZkyL@L%V52pDewXxxg^@zo zXM6wDM5wNYamtgFiH~`%3UW()ciU|qXZJjpY@+sRY7)A6V~#S3vw@~L)3l;L>yol@ zq6wpxZDnz`zD5|=Xriz8&RkNAzKK)Z#HB*p!*u2Lyr~UN>ZP63w~*8~s^5~ftxCi! znRxOW$2DU#j%&i`c5aJz&rRH@&}ZUzhar6%_I<(J>hchzWNv4RMctOR7q05lvGH>$ z&5m;;)ZP{%6L*ql!SfpRa6vLj{o+>U>{zk5?#|v`o1p5WQNvTG3I!AQU_iu1FW{`4 zxT`wNsBqQXlCq@hz7mjfBnth*_zFWAMQ&GaPAlU+TCC@N`O(jTjpJe$NI4n4NVQ`) z$y2YE=(mFW*!D1L{Yz=f?(jyHx5?dA3jQ_Y9`${>35(^1_{9O)VBl)k0qWR?s% z?v1q*bjn2JZW+O7?f6wn_ps|<{p^Hpl$8oP-9l-?#pTHwc3NaSS#!#!%+#wttJ+>D zx%UMgsYhsCnS%ivR_1-&oW4z^n6bpg^);bN`qI##x@|wy`Or?vOWQJC zZNVxN$CfIZ)3+ws`BEmxzSVLpaypJn4XA^=y;oYYewhgp*VhE6t;%~r$L+Y)okXtE z#&NZUq+DCs2D#6dHP)~zMk#bYeeI@$;_<8dm&8jZ2&*ssyxaG$Z4EslbO4$Q7eCD< z>4c>BWt=h4Oj3FTN0Gac+g#@6#O1#xn&??VTNgPGAM3NRA5T078sQS> z%&ojJwEI*`3)r@BedwCXK39MD+ISUe9)_ylg%=bBax$FsX35sJ6}OSB z79yjzC9)SACB7_5`t=OY#rMWmnt|; zxlE~U#G$lDTB+PE_F~Idi>oV4_hK_7l``+ik`BwCIWG3%H>AvUaSxzh>@8B|X;W47 z&NZZ*Z=S2PE4v?V6tmx>uiLnTw}}IhPnk{y3LvXsQX4l4Vd?jU+%6 zhcd=-+({87imLjT-u3K$j*b=)!nk0PV>dHsvjq_ZA({{~=xV5MwU%Xlb|lv7{T$t2 z1EU@3b%XQ8rLMB5Pu41u0^^0Bw5%8BwQ%W!1+=Ngg|w|pE3~$nk+q%aweplBDGM!W zwW&thdEQbHu)C?HWocJ@YU*kbFqDG8KD)8q-Jmy561zK+`a0WrR%fZ^$}DT@yMUJZ zE}&&zDA!V>@|4MU0n3_v7toUL0$TDd*OG6^RLotaRAPJE`m(^~#@IOYQMqMJA6;eP z?JR4`%=FAN99uEvaVhfxTFShDmVC>#FU_x&UMkZ{FEPUw3a4~2q?gLH(o2b!0bHRaMwwRP z-_K5O*=bRxva8DNoAGKecDEV7iQOF$QR=&pWxbHrJeBiV<>tAIwNj%po1J_Yu&h(* zg|z(H&;oJ!qn-t{w>8~HedB!w3RRlmc0$^M?a8I8+_E-(RB*cirmf<*gr`PvTr+~_ z)0(!LPisbStd)GrQZ_9aYfVeW`BIEjinR(qiJMEj^dK9F1MzFLG?$c_8uc-p%id)r yYU}K7YP5is8ZDru%;j3jT$S>?+h>Fh%e)`-3KT3<#Ly<$j)A7+37CMUSGxwy+epsyx~MfBLgGg zI9vyW>om=v579(MKBlaa(Jm>G8P7&WESV3HkxObuL`FTNX2w#>Ze&D0hJC*0ocFyi z?jQc)KkK%~`P}}!wpMGxQ zE7K0vEqZ0zC%fH0@ZR*m;`#M;kEw*}`^LuG)#mT+mCvyg`063`82^1)ulM+(uS+D= zgw1uj+RZAZN=-#AJDX)sQEHWXMdezXH;dd}_f6z$w0v!%r7qXnqGhEfEVyNHx!Ap= z{-#@ga>-3nR$VViOXlC^sfn>D-L%igwXgExK7 zbrip6!}?9@2L|uHr|$~Y%pP{3Ua4%~byTU`KeXnq)f>F7H5=A%7+S>)?;h-H^JafB z|AjVh-oSvTv)424*RLAdxN2paHm{VU0@bM|(la_dT&L<)qsrCu<$cgghe$Yw86B)^ z>Kbe>cRsG|Ms2$>X1hT(sa%6<(6;w#lS?skne7&A@4D*a+HTUeoAjB0dfM%%t5we7 zOWOU0Hn{^cI)v@6^}kBI-1KpM_=vuLQ=5JfGZNmDnO4tNy-FtQ)g(wk8fHNU_^=Sl zupCyy5RAZ9co=rVQ?L&X!V!2GPQn{-7T$$R;MCWvW@v>Okb}8UgvHPUD_{UN!e-b8 zkHRk43;W@DI0~=9DR>*s!9_?k)T<`=6nHQb=0E`gSORxIKU82Cw!n6H9CpLAZ~zX& zF*pII;S8LI_n~1zy>ej+WS|Z5&;^TNDfGfx*Z`w24v)Z-um`>dhu}qc6<&w8;NRf_ z&|#_(T3{+X|058FD_(ymLz60+;aw6joDM-UC z=l~xULK&9BY8ZkM*a{EBPIwCT!9h3zFT+WA1J1&`a0#47#vfW?2IOEa6k##+zzP_E zjj$QE!K1JX_QHO69*)9ma0=drb8rz7S2F(aDezz>%z**~umtXaeyG4OY=Q0YIP8XJ z;Q$P|1{j5Lcm$q=J@B9*HvZwlLV3}j|VwZqcT2z@Zjc(P78u+AT7nH z{OL5`_q8<=2$EI}{E8olMlJciE!U&VQ3xek3l4jp)JQor$mW)MOF9?5XcjHwukYQv zcYGWvSiQQZN9X2HVQProinXEBvY~%sBNs5L#y@S%6D>OALMbQG`1m+RN+wN|TZu|Y zReHsahtWcn&CCtD%fW^Wo9mM1mBuL@3m290y;?lyJ#4zKw1?V1hdc&M7tRG$ zU%X0rUe$P@6uDW4L{nG%b5#Yi(nCgadnGrJ4kWEtds;zK>NVQ7S4+}ck}Gyi|LNYM z2PI8=;D{t1IcC9 zxdt|k<8a9C} zujJX{X6L*}td~uQLR#vTX-&`*L`!@yv1G^+AL*t z6IdxhkP-)dzZ3*+N+d7H_GqVF<>q8x^IWN0Wj?!JDA5x(En5nuK(8k$SIcERv1OM^ zJ!qz%WuNTz+;Devy-;Gc+Z{8A_D)|$fKb71D0!t&DydM?tM`oTyV+8>I~t#5T^gT7 zGG2*fd=|<0ERr}FuD4v!6RZw$zUR_Nf@ldPjyaoVg)ldVt0hgN(S_v&yqVDCnw2hf4 z9#rTylPhUq2wycip4dL=6qo_+P=JN76js0>48vA<1a`qQZ~%_L zt8fb5f%9+)8Yj|zNJAUUg$1w#mcv@u2oJ(`*a>@JKfD0P;3T{S=iohPXr%ox6=p&Q zbira+2K_Juqp%Hjz;4(Fhu|n2htqHtE9Y!zE~JqWzGDHkby~(U4jOqRo8?brH(&Jg$)Y~({igyp^GD^-<3#s!9# zLN2(DxtzJ>az1N*NYjQ##B>yLoB$>=5~Yb?d`oH_`eC8+MIG1{QB#oyxVlQdm`rlb z4&{6jpQVhG%4UNRbCVe1s*+45tCEiYRW#``BSm9Cr;`?@i_n=&>N3U`<4-7SpXJ39 zK7Hh4v9|v9evz6*=vF+W&2|E)3-`T(MgzT^kGj93bF7K;Pra@o`^7FU+hY4nIf z+gI92YH0m7eHIa&PTZvXUdr~rj59&|V2Yb|Av5L796-kIfUZlbbc>O7#FQDKGwEsF z_PoB^@PSre&ow8h?MfRZknTp4O>%5_Umwn4jPHE1)ApO04yOM}r$>#SO;W;^k#y#e z=}-Nh31_V-BQwSKdsa#XF=F3U+Gi`f?i*63$SH05Kc~CL%weTg(+}mc)K#jcN9+=` zUDMM==7E&4c6IA#$t|iPsigZk$INr{1oToSUDTL)A~PlIf04^&dl6Pe8365nSVnHc zOl%nd-4+D5SNvv;8~5c?uXndeL{F(qxg1C>9r7l8zsWq>_1-D%s_kGzzL(pp2f=*c zy^NOLhgqU}&Pyzn(X&R6wb>2LbkH+~1ueC8JzKfc_j-zR9KEyJr@J;#-SXUvgy*(Y z=^y!hA|E~6T6pI1$7SC+*AI0#Y*T+mx3E)b@wG9FHrD2c@q}Rim>evpJc7Ee?uEb|^3XW#*?o-R&-k z-5!edV2M{%wC6i9+0E9{=L9vGO))G_|3|X)dbOpqRO%GTi~#*F5B}O2A^OAfLiC5S z=nQ4bSHxwR!%WqhoUO?$sVH3ynBJxB^5Trn2yK@(O1iddWo_5W(k?_@M*Cpp%lcm> z^D+Pj(SuGYuFnf}Mlw1h89kA#%g6}Gc@dtce!w76MyI2+7Gf{b#O%o*_dWU%|HfzD zqt{_Zf_{%Wd^u`j(&69M9QC8n0rQ~@eJ}(Mz{BvHun+zOUWR{wv+zIQ)H~{jVFr8w zif|jOfO}yx{3`5%--GAjFX0q?6E4Dp28Xw%qdo(3;3ilCcR~fmU_1NK?Xh#U2rS(!a5j*Ux6p#ci|9x173%Jg$q#6Rk;PGLp#ia68tO-!q3A) z@aymld>vkbzk_$+zaTl$QP)5kW`hs6z;d_;M&OrWC;Sc^guj53@GtN#T+!(8PIS~X z$ia=U7=8u@;OAf)JOO**kKic$ExZl?35hEmH5okk92DSYxC8EnVfZC@9DW-Pz@Nhj z_-8l|b^JS!3!jEI_!2CFpN6$?Ka9hdVGsNvya;~-Z^3_nx{7~qhNLA9yI>>y0z3-81^eMo;WhXtI0yd=O1@FU^lN~k1E=RK{&8KXJa$dS7+DwRLH#d)EGMP((y;?d`(_U*Lk?y3A3Er(s zGEVbn<|j;z)*Q#un)&@Vn5aAqjHEyHqY_h&c{|9r1g;U1?c7~x>4=q9R35ur0r#X- z=q4q{;-y7SrA$g)A|X#x*Z!*Cnl?e-5-`NSu}LVH!sI3>oQ|T2L=z3Pg_BJ!Et%qI z(!S#hp4XYK<46wz(os#WU-qbvN`mikb$BOY)wz9V-gQY7N#o zQX(>hSvt%_Rbk$|Te`b%@lBIbPD@KmA)Vf2a!036n^u@MZThH*7W(8SH*3u$kf?P?nh-#mrBdA|SS^ZLGIRBfVx*a;04OgD%9e074*)z#l=3a?LRvbk&~ zz1~DC`hhtuG}PBIlMt!8E8R|XMtUgpd7gy0&(j&1%7=2Bu8{=YURrf;lv`&?hy^U?U)H5X0>r7m-UpHGOPc>9k zn3r#e>bj;u&C&l?&@U~Racx-G4q7ZK0~|&fVTQtvuTtR%+ZW1M7fUc4-v*gRJc0-f zRM&$2AgVKT5r;R4n%%u1G@-v+ZLo>|zoU**+Y{-K%eH*t-v^yzhuMDVom-}gi92${ zdTD+=)bUA^C$k>i$#yNwGbc*8JupEx#ZO72CRB!^R3_cr#BnXUG8tpRevUry#M58> z>eEj=Fsc(;6H_^#RJ7)lYR&|i_*G!6PVZHbl3LNx2fqIF)BIUgaV3;aWirv3rz`7& zJv_})8CFrW;qcx|MZf=2n7qiI7G(LR+du1h61#DDczAB3glIuuHg|Ll7Ng!(ofQ4j zYLl2pqNp|QYNmN&bJ#`xVZUqPS?ZlxaXcl%|4CCjof)8blpH6Pi|MiJvNIPC2i@N`ZbWA{4dhBN7CWrDKg6Th zjYm1b;!*0x4{PeiqtuN@sT+^tiJY+a+dBg-iXYaZxvzFui{@BVG{>UUjYq{$ELWO* z+1jrw^v$2&ee11#1z%py4!g4ieRk9Bcg3O@iszy^9>uG0XSdU%w?LD)QrH{CRL;{G z&@VnfE^1Lxt~$CW-WYU8_h5T=%LEp?vBMgt<5962 z%T>~OwUb@y#tv)oITn?=v8Xs1kE%IN3%bH1l@`SgYwJ~*YrP7i)~hfoy`eSPlifHO z<$h>v?xMkxpVp z&AD+h$?7B*GDTRz5`yf7TTg<7C5S9xktHm$ghgtDh=eFdSVY1ieL=h>w|GNXybwZi zzwdjms;j%lljDS24`1e2_1^cs_wRdu>bR9W_sJM>UW;mI{nh0g%w!6CQo8KV!@9p08!mej`{C%2QnADCNjk$g6H%KtGXZo3^p59#@e`fct-P79` z;p=y7U0EHvd*qudtM@+pY*nLoG46M5n|^-VlPk$fT;4orX3PRCqwYs3Q{bO3m+JL~ zDQlZ2DMp>EnCYkNb$$QavI|;vAyKwxJmbqn%6>%4UPCc$WiO`e#Z;iZ-K}YS-*eMb zpWkp#%l7n{K=!n|$Uk2$QueD_c8Oxz%8so6y{)F_@GYHn_=0W!Ypuf%C`Q~B8EFM> z1=E`;n5D1=Hoy}w4F};Uybh<~eYgnMpmamQtbnz!5w^hda0rgUNq8I1!za*_Etp&3 z4p;{hFbR9%MK}&`z!~@uE0qbA_CSfnU2*=?KI0GNTCCGZTA6CLRJO(>M26zId;UFA^*Won04;SGYl!~+;*1|^E0?)%C zI0h%-Z8#5~Ku;g-hdW>$Ou!`Ug%{yCya8w6L%0Olg|r`5!ZZKdgcWU=!?s1Mm`@fH&bBd<<8h_h#A;YhVLB0n>00j>7A38s3MCa1Ba}Xg{ol zjj#otheL1-PQu%89zKDdTWCMr0qbA_CU41`y>2?{oJ?Hu;#f_Su2f2ftUt4$UYcG0 zAc25?PFAT_Og$rkK>zUg>sMtKlxkLFf-xeD*Oy;Ce(ySbI3yvrj3yI7%k^ZWG8wm| zTX|nTgUd&YsTB?n&JGT{bb-5PGW9smNTyMl_Qn}gcPTn<9TnoyneVHPMeP#h!J4?i zwse_Msb4G0K=mYTOC|{hYpLE;N7`(mw6xxnljzvhtLti=dcA!JJJQx`QbxO3sGt1B zd5H%>C6(ow+wN#=M|)M!ZYwFFqdUl@-eo{hLba^~+MDehSC#5tJowTVU#nKDwf*XM*_qku^3mm*R@3Rl z%4XucBtfM*Hg=z@iZP+;?W)!4wc0Mx6BWHOs9gWHWR)J=_^8tmO=ET~U4HxOr8{ka zYXJND#6|6}4GA|-JT#{5rA%Ci-qxXS{K>MKqz%Bjw)O^r3TLa;dbKL;Fis-;6z^7?XVTSY|3tiuLwjK?B_tW@$m6k4*K)#@#`@Vtp^ zhX=h9z1eQRoz;Z}TSgcsp1rrn>$80%$+YmIO#OVmD1yk1Rb^!9Zo-NJ#*0)rlM&-b zE>l<-dAsh{V@YW8nK+R2tV`9SwFz+m4KZ^x{?w?Vj|Hv$L`l?4nw0n6t2ULO6{0sA z>jf&$>0T60d(Q`PN!(+1JsxF3_8fBK3tw8nMrGcY zP#_m|v-f%atoj160={fM@8|Pbl|>gFX9vI+JN%Py1ISHLQQoy0(a~WUN`k#RU&-eyk}8NVn`OJ9YfdcN zS>0Kf)n`h`+7gPY=HFRJapCQY3O6iGuH&XA+U>4Bw(*fiHtKrHf`cP^pl5LD*IP?T zlwd(=d1*luaQAq_kOl19$Hu}jm3_a^SL0BROt$x-@gH2cVReTdCkl4YZB6dW zcklIjGTkz-)Gg~@(JiaZvv;J2=GjlqE?RWk=>7j8?V!^3;`U8yt3cYq_Mm(3*fWpJ zD|PGD=Gk}V-DBD|s0lw~9j}c)^hoUqZ7HLadp^^>pUmXDWnQUUR+D{a$6mB2Gv9u) z!FH`))%~vRTuNq;-&U0_If-jx@2CdTgD^4OON%cOU3}~l2QIWnfm?r z*qV%mj017HpR^l^KtODOxw{RBfsQ6ThnUWI`rnUAp#@^m&(z zx^&4Xw@XGly7!ZMUZ__aY8P8&(st`*hS`-U;jb zSdaIyo+20?0(;>IoPg7C9xg#4>zSpn7A9aj z9Dt*663)N{xB{gd<-<5^f@wGe$Ke#5gNx9Ur+ioi8(<6Ug(GkRPQ!V)1ce2Z4{Kop zw!;B93Mb(VT!1T3Do{R*!zP%9LvS2U!8y1HJvUN5tbz@&1@^)bI02{OJY0eT=j5fZ z7A9aj9Dt*663)N{xB{h{C?CdQ6HLP)I1Z=a99)E+Udo46umQHfUN{0L;53|vOHe3M zKCFca*bWEaD4c{dZ~?ABsgLqu95%r;9D?I;3eLer=vhelunIQ77T60%-~^n8^Kc0Y z?2(qjT9|3|xRKP`Z`!VH`HWG#rBCa0<@BMd%Yh zpB>hNZx}YC{b9D=XpE-ErXH4zs?Dki8;wx(XU^_PXTAPm*||5ufNRrla;*&c<@Ut1qb5k+Q6ki56NNO%qvdkB-c}&? zCAY6(EEP{KexcA3Q&X?(HxStN+L&U$kL$PZoPrP+XhgPmP#RFNT6)!pif>YBudGF* zF0v2|a?12oU*u6nsdVn9{(4G2fJuMRFF`1N*{=9bUj*oLHKthh>r_bV6A+H%bzkbw zf}t{rv999#{B@UG)J#&g{$vuh%i3l>`-p5lJEuM0)9SmH+V)a<;#Cf^&{as3N#a7W z82gxX)?`H#KccRVa3L~S8jV2Ydu)C7h`*_J z4x8ey%jn5w>*5dP;%*q3qK33ztbQO8@);Vg-|f$czbV5i2z4{NzpT#IUn0doxaQQJ z=O!E0CR5F(G)!%^b7XYQ@KBVGt&r#^?bB`tZGg5nB$H5-aglybCa^?EOw-!4?(!Eg zJ)rfB))}@Q|TXRbj92jUa|6EbVN?cI*-(zBE-!$QaP3ZwWpO^d*-y7*=!D-P!w??G<;SvUEM?? zjv>c&ozvHsw<30qVHl=ijN^@SFRmRhD16%(D8T)kC$ByX8K1sWY{>WXv&VZ!c*)1*kH>F*!WxEELk+UL| za`mQ{ia(gP<5}gQ?HE*L0^8?{>e+tb-8I#;{epg+yUq+Z{fm zEJp2ZhUC?>DVSm80;kUNaZi!`c17e(mW-yG_EM;w^%B!^ zId5>dpC{oED{-@dGV5DFw98r*L%r&y=s&Rj!SxTgZCzEIjjg0AL-C8}APB;!LWaXg zfArd#tch{XtyLOoHX4ywHlQsRT5r*Rc>Tup>pRQ6-cmGk2|wc&^TYzo6Fc^9SRyJS zL@uD8_A25L7dWHgMc&GY+9MK|%G$eJgO*o(y8f?ggyIjyHN;ohzkk1c*_GMH9#frr zu+6t=mn7=a9n&@2>oz};g1g(pf^Aknq49t)Sie91ZW<7w&Yv1uwK{+6Z4wv@w5H7v zzsSFNqkaTXdDCwMV%eFq<#+dHZoC< z$_y0Q-xm85`V!&f1It=+sbwVhO-mjkj^st2jPdy*XvrB<#U)FI>s$X#l;yZAyIJu^ z`(fF)w9Xgh?EL45R6-uOb3}uDiv0=wK<1Hs{`%3mV-^c1^vf~`#c79yqAm&ytClO%q!M!a zMOzgHVz-2T)L!I%;+#Ui#QKs?biR;$P<3vqjy3D{{H5&*Z3Od<Q-_7^8VX)tc7m(+jWa|HE|<)Rlhr9Z{cEjjg%<=XSnc?8W!pZxU%U>7XO zfjA)lSRN1-7Xtama>-v@C>K8=SAmn?-8*cTM05iFuqQVrKG{jS&flkh!M~BW-$T|@j5z%sQsh?Tw>?FEn_e`J!ZiE{UWc=A5%M<_`7L_U+z(qI zfMf70I1iWMR$e=-h6$L07vX1c27U+GT+u9tacIDPcm+HN4nKs~;4QcS=B6UQ0WX?S*aS2168r+r!5sAV7R{Zo0iK3~@G6{! zCR~GoVv+9^Me{fCJp2=!gnxlg!0RjWouX(Sgh}`T9ET7-gsZT4VbP4hW3U^Jz|Y}5 z_&pR#MRPkm08hdJ_zAoTzlAGMzM1}qM`0R%1h2zcxCr@0^grAWTOfdA@GCeEm*Li1 z=zo}iDR>cn24~=RkiC`uhjD1Yes~2=!3XduEczP#50AhOI1DG?9rzeByi!;SWAFs* zgQM_Ecpv@%C3d7drp!io77oEr;cfT`dKT0Funr!Fz3?);0l$Gu(6@yChlgN0{19G) zx8MTMab^%kVH3>2OYjRg2XoN-b^0GRz|(LLUWL=pgljM`m^W+PbhMLl-fPM_BcB^^ zfpx4{>fHNr8(_`x{m3i#RGQZz<=LUFR(QC4)o<{nL_?`mu3YX-o4$dYQ{){4?~LS4 zI8`n;163HEoE+5;2sSO>5yC)ZHkk9Wxqja-=WHsU=izV$@m;u4Z%bR{9j zd$~PGD4lK-CDV4=s-?)s6LIk;1z&@HXf@I|Sz|D;#931Wda@u$TD`iOg`_26+0{&9 z`A^FdIs0ftIn(|0ekB??&1M64x#7}Q%H<^kt+Ys8=28pZdU*YVtvWZ`V=Gdr6owQ{ z?4*Ok`nfW$%8HdV2`V-y){qKEBZn5vysRJMTn3VBlrti+al+0vHtxapSbp-KjfRgY zH%MuMhSb$MjF{|Un#`YdHeKRB86cge*XW|Qmtkv_=cE&YxK^}?PZ@lIupQAXd;1Q1 z-eC?BP4tVrXY?v%w>w}sv)8#?_T0p;V;p+IeqAGh+RF3fT#3tZe#Awy?4yBe4SrX%O8p2sBeysPc8>uiwNMzdl4T<0tInm6!~RT!mAsk2)OBzU`&q%04HAZXek z<3g96sP`q(Fg|Q-^vQ|iy(D*OBu>1YpPdjfTl}bx2s)=}8?kF5mZdI=<}T?H!It-W z%PYQXz%#JT1#KY$xiTjX->uKO5MC~ioK>w67EzYT@}N6C(vT5`$4E2EBR9bC9*_#F z(-}MvkDg>KS$!}=Rx`hil!>h;(vtI46Q@n5i}F#))ruT7)9#$D!f!R_56V-aF%-09 zfP$h9wM%;xWQ{B8sIxs7$TBC+^&r-WtH*)3!J8ZF$k+_fbk8e#-aw=?!*~qchbEgX zc}F?hj9WRsaQ6^SziUeRk+X+!??qYN^VxMqC22{59ldam_7T(2PT`K&jWks~yprzb-fu_dXLk`5Bg= z&LvmUp5fuaLHpYXksLdFc4nrv_Z#JLmTmR#D37v6GN?JH-A_F_a;CXVrg*f^F@b$Q z9#?dq_M=u~Dx?dIbfa#;blA8H@gk2L!CqN3bm!-`Z^X?>yk^xqSFMv-o0Mv=|0VzI#gduzx()b_Mq!)QcV*?+UyN8ziHV~2;e)&G?>Z0VnMwF^qT z_m&ky7oA9-)q7}oP%r|K zM$4mFIZL`#g$wTf>>>J+#okJxlJcatvO^16sev#%SHpEq@%+gi0H&hQLv zbGh=e6}V+?)88zAv^tYn{gHcY@PEAEHHUqIlC0^^`5#2>vDvV3^AhJ` zR{1j9wWRBej^lFW+g7aLFHx)2A3eERYjr)=QjhGZ+8kDnA16eF>_gk)*%9z$h>K>g zfh}Dh@jK|8HKHcbw9Pnm&Py53br8a^(Ux~|S-@05oXmOX?Q(g5U4ad2(CzC?u~Mnn z4U6V-_2tOD66F#d23=UXzr&%?s+$asP;^qc)}SIN11B0(1a(BVY@s7EvIWsNdN=tK zQ+n5LXoM-xaC}-4$0C34*qX`xZZHtrzl3vu8|`c+el?jLZS1#c`H1Pg`wyR$Rvz4P zZg<^CTLY4n=eV$YZad);MJL23Ty~R*^Do=aWNeyG2#MIR(H1Ko5)!ezB1ooX7PZCd z6pPDEb3{p`Wnd)JQf*lk3#Yrd9tRD_MsS^tf_Otl47g)bS$Q0dL-U&Q0HVUbxy`o z=VYub3yHLpn~bGJZLxk^EG2)=la8E+hSvah@qf$vZ z@A-a{7X;FSI)ih`6K)_c0^<{|_EPKoOUa2?<`|ZaW0_;gSQ#COSVJ!*((?QL_EueRp z@>Fk2D>FFp=0wIuB4=yKIPK@mU*>nmo^XaYfv&x@XrwiNWlhpxbnZ^I#nPfgth6YR zR$7#Zm0n21N-rd1?b;wySkK+$%>0f|^k~}mNb|mYw1ea$SXxol#MLiy_Oxvb=|d7Tsv{3l`J=Qi62pGO17aWcAeC& z6FYKRr(4&$V;BggOiiE^b_!$6YcRvhbV_G|vPoeIhBCzi1-3&`9UdJQsB2(KmxB3y z=iGB&`*3FR2NZnb-@WIY?>p~v&$;KjtL!R$z*=HimIF6#q%Er}`xU%aQ7$)gsNZtm z+CA{)*I93S_o3s?vH7D<%|G$9Gk@^e_a2%*c-+}_=)nBZ<42!-&UyQxXWn)6#PRjF z_l=kO$3EWs+59KF9{FtkTf5!2YOi}>_x7b-AF)!_>rY<$nB_J1N`aN2Xnn}~2>*Rl zJ@3(nzAKfsdb}=`Y?n1`?aj<6zwODIVp#{RuUmzYk|+MhyHcsb$UBt(olEBNKd$_i z)$`^(yWcjmb9U*rJ^N<2i>|d!qGq?h)!9%etlP4Qf6fEWwxjQzKXh>3*}QH;Vcmv} z&YHI#e9l?9=|1$Ie(v~*<42x(&(lk+5^LBOZ?mj52ks-qvG>m(dg|TJMW*w%qw~ks zI~%rae$#qq>c|mC$&WMfj~|?W-@$jSSLIu_HOGIq(KE`O?IPxOt6+0;Zp>MWD)#df zqnuwe>m8Imk$tUY_o=dPSL-s_Q+BVFwFZEa75vv#p-AZ4E&l z*29Ca1NOmwI0`4>2jIuxXW-Z1ciC-!IN+tJoq7~ zz%RhZ;U)MJ_yT+x{tZ&ywzUjK;6B&{+h8wz7aWEY@V)TE@Kf+h@JaZ6xD0;-{|MiJ z?jGB^0~~lgJOB^F+u#X!20j2Uz(?Rb{3`r5{2_cEUV&@ypU`L9)?F|T8(;#y9ljF| z!aOX%hu|mR=ixV?2A_pG`~&$6MH^W_cq(Q6IMb2 z9)h>P+u>dCES!QLgdc~WgNyLH@W=31@b~a9U}abrAP4ut8(|7|!#m*J@Lo6#ABK;? zFTy9_)9^X?Yq$zuhxF~XH2|aVI@k=`VHUm{j==lj``}059Q-m=;WO}O@Fn^RxN&bQ1wsMMHN zflJ$VR-A3KO376maiXwkWMO2J(WDB@*+@#tGs~v6V6^%a2Onq)T)@@1B&vLf#qQI_~6E*ZBeGd6A6fN%8QMPIH1LOn}Oog+#dt zrKW~zW@fVlj=SUIYc`L&$0cx5jc9g2WqrWiv}x^HKZB>-jT_glUhU3{+K4+PPIscJ zHC?P~5J>JpO-aW6tS6?5#fh%2DOH1oow_=z52T{vf&lYUPZyD_UAtCK+Y>I)be3ul z5>c{MnuTdbF`LyLGNPNnRb)jOoRoewvPM+9xr|u$kSh&oayMiu$78pqCZ?w!lDIj? zk>SzfLs*)m+tZyArRnKOKk3BOf-lKrvsuAfqM&k)LpmDMhIPRp?QH_uWn1 zPWl;4Ol|J=)96|cW_-z$)N{2AsW{#D+E2iYkCKdx==KC^c0nngnqfsbCEjjnKtV3u zW_6MP9iSt{FKIUHe6vcaE+fWC$oOS^|E~qYg^{+!0^d3ZCHBUPoA^VKK%UHwX zjyxJ_j!wx!H8JIH{i~=1AbsaZEHpn>^~#!WBVT4r!!y?h&X#FvLH|$wU?;{(&jZ+RZ_Hv({`|1 zSiH8z9IbQRt+Js_mdoV_`uh6J!{l!*5p9)?Jm_K8C zeSYU2)dIBvv1g?-S$iSerqa=XJrt3+(cLU-N4BYSG++-!B(A&`E2rj;?2YfwtG#i{ z?#MQkWwOa4sJ1BvDh${|k-(PSk$@ZD+AM3zWzTWd#9ZNS-@fzFN8QJz6C-<8IvTKt zA`(}YO6BNyr55g4b80K`*JOFySkDsujC3OVPq^hUk-~sI6bWqE9SQI(1Zpy0gMehX zsdsY6PV+XV_pCGvJLQDi6y<~g$_WEx9R^6VS$bY|Jj+ugFcb-lM|ZPL_8U{O8>uwZ zI^3pI4U9z9zywqcOn@}uZlo7ppVSwDi0BU+` zll{l+^=gDr3nh}h@64g!vQmT2Ydvq@#NP0dddB-GMw~it_j2pCa_L^{0VqKkDo}+w zG$6MWIh3Fb6{tcT8j$No4kai<1*%Yo2IP8>LkY@IfhyFY0XZ8vl%Nb1s6rhYkn2Sb zB`8A$s!)dpd=7P0CFfn87fePIy4|R zh#X2#h6+@n4h_iNfgDOuh6+@n4h_f+A%_x_p#oK?Lj!WlkwXc}P=PAcp#ix&kwXc} zP=PAcp#izOkV6T|P=PAcp#iyJ3 zVaEnJh_0Lw8=W{$UCoUxk2C(7nQhy)>i9tF>q-5ilB8dqp;aT(=~Plk*w_XF>p+uE z%v{hUrG?EFgce%;nc)_(e$-@pk&$G~X~#qa_oz97NG}j5�WP#Er(|O(<>7ooyz| zXueAq(58+e@9*mBYL|biyRUE1olN;hI#X2t$Xiewr@Fp<|65Abc6^l)NY43fawmF{|JpjoH`O?;HT>~u`~33K zyL)zac306c1MGSUH8E!I**)LV~_=$hQtP z%NO*ExNk6OFV_~FX1OZ=;i;+E{PRM+S9l#;0Mouegbu-%rVbp~w{PdpcJ=$r)Dw?Bx^w&XZC}%A{cMAb z{PTft?h2v7SOLTO;vB|CS=a3|)B!7Mf`TL$% z$X*VOX8Z#=X~iu}@c?r^om$Z0#p4^l{H>nUa@`Xar_nPHbOlqx`AalSAQ?~Wp zNJu;hbBwu}Q|B>lL~K@_!w`5sQ|SI9%`!h`#Q~mwGk!@ejE}h|vFSY-+7yy1BL^@@ zwoBhXkg!oEQ(M+OYQlCKOwf#Sn;&EGp$Y12?g@M zSDC1lr8FTVs51}eSF?uog{ODo{wX2xg0MQ1wyQr{^k4Y=1gS#L5jB46+Aaaof6{@e z*#;vq+}Lf^Ylwd#w4BEh>woqOrr_+>1m9UitdLr>PF(FN4n%2vWp7&RpJI^}6Nv>91hOJT3QcmdNj2m-Ro4 z`Er?mzCvNgK;Nzh+ow-k^78`Dm#xM~eG%z!zGrYcp8xqA(NWIFkB)NAWF6&v4eBVT zJH|3Y%kOB~YV`vUFnD9;Rbmb18YOfTP%)WXGOLf1cb zrTmU^`O?%WJ^kBJPGMqy`38@d{&h~QbjxD=KHbT_K>b7eKq9RKseeZ~_3tRB{El+k z*HNyj6!{9;qKc#PVN|}1Zfzl?eI4x&w6CL__H~p~|BiAj3+nJ8V;t^(v5IoO#QetM zsDB?5lSx3Iq%XI`0^6!{F*4pqV z0kP=Ro%g=h-v`{}ZvtLY@4x*NBToH3Aj2(Vx%}AUEiexicp0w3;L;4g3&>de;S5}a zI;6TY)>@c_GMtA?a2;|z8EYC2!;4UbE6`_Wtc_5D({KSULj&Ai%7=NVz{_wI2Ky)< z_QM&t2z5x^M)@!cWjGI);5y`Z51od?@FG;<3iRDh`A~w>Z~-nu1KfVfhk2;L%WxG2 zmr*|Khcj>y>X6D(KFmTH&ch|R4!Hr!hr{q9RN)Hr@qWG$N^lx3z-4HFdk5viJXGLi zxC(3y3|C=rl=5LeoPmo_htwG5!z`5HJY0h7kaH*>4#SI3g)7jPr+g^EX}AEFp#iQ- z`7jR^cp0w3;7ZDe{cr{@LLE}$ln=8|hVyUg{EIuOAs^O*PBGF&<4N2* zl^UqFGDWn>u&Ll6lfaYsZGy$0O?ZJ&dwTRAYWRcf3uR=L3$?rXK;gTwL>3aPnK<&p zs|pVFJTmi$E*5KCm0Koi>PIKp@Vf_kyjuFozM}m8fg?7VnV+awGDpSLB2#sY7L2Lc z6Xxeh7Lr}0)m?ls$lfKNRAf?XS5r4p^mGJQ@}n7gcA3Z;3ZaIMmHx9;Cx-b+naOs9 zDogyM2I-~9=1f!i0rzjF@aB79{CsJAe5GSX&MP|3A`1jp`bg{br&3FoGIV3fqnPIv zYmvAGMwW>nZb7G%ZpOG(bBJhde-3=FHIg)~ul+ri=pqEZuyrSS*^j=lOvYSuMo~s{Zmq;;;GjrzO{no|G$! zxM=-M#XO_ORF-8!jHyK1i&df>yXr5+_p?7tAYFpt=-mb1El&5j}=4(34mxchL&VWoT3WZ@Pkc zP(x1;S5sGgiI!{DtU142n(T>Iq`leAI+?n9D2QA1<5Em^wh-k^UJ>=6+%BHTv2ZH1 z{aK=vJCGe1ypz9?D?~C{^du0jf2K?+xKp{3>d5`_vBTAWZ7C7B8XYe#TE?5y4??mX z+be0K{U>2@t>%HB-|$_u$(U(d=|%ISo0N-;N6^;~X4Li;?r&(;9IYR{k{@#=vi>2I z`A6B|JmujY^B?skk;FCS_a}iLFJmGu`ta6d67Z5W=j+Jrm|6i@0Aq3F6!;9skLs}kQV=9q=$w+I)e(kI?(z+vVK9Wd#o!DyOi5NkO*- z&csF3f^iF!@Z8c{cB^0h7t8~7lrfXB!~IpA@=1Sd-m@7+oaxg4Z~bE0eY1Ot-O8-u zopi-ur`{N{yk-T8{^?R}B05sp;gkhXOe?A|flQTml)MqF2Ou#W0SI@tD1I zL8=(6R}$x0Y76v?rr(C3F(y8Fr%-!na6rAfbD&q^TCpg1DH}ZT$2Mn)z=fNJ4y)^! zQ+#-bxQD|(#i%*ivwLaV-m`&WU>R>s%dD_RRDSecE!rbtwrQIhhC$_{V$qIKaq$dw z0>3r!)RXa`3N1$CXvZE}v1jLwSUa+3&z{X@gLd4ACh`5zSCK&6!|RPcJOxqexr~GY zFGD9*ghcP?j;Z-t{C|pi>|g@;kuo6si@F|riPcmv9hXoOLt->pM?WJePQAw(MW%|l z@b$B2baY~3bd=x4o(i9!NNig#7Uu3Y!jv2YlKmpOUOa(3Yt8dd zCVMTes6)&?J22lH1rP1eWsh-EIfYuj7(^;Nurqhgx#om%N{`pr{8Sj{kkz7C@59Nn zd1g4h{ehe2CtatG@$vU@owc+F$HOg$GO_E8v1lFBLjQ_ww`k(Jn)N)R9MO}*d2@cg zW<;<54s{rel^gU~L{BUq$^1N1a6~V~Q6i~ls>hTL+d<@5H-C|L#~{CI41W@T&Dj64 z*`S}J_DSCE)!8RzpQIwvf3M2!NdG1Eq#28jL-p|a=L+e+q@K*sq@FCgNj=G?Nss!y zD!U`~OX^AeYT&z?+@*d=J*l4(ih`d+>X+1$`qe~3yPEN+c4#Bzf{F9y;`UvcFG;&& z(GxqDcE;`~~>6fIQ^h;8Ys^FBpPV9Or zq%X1_pkH)!kgMc3H9bAEqfppmBG1mvoz05#?bOmQybJg)JfDYgDqHzp(4^;)X;M%6 zC5$uqd677spR_lrC+!X6w4S`%6UQ-L87PBgnmE5=mQ+KzWB%8Lv50*a0bLU4Dm~d) z?OwUqTXagL#ma|fsH&#StFY@y*Cg$Kp3bpW#GS)0PEDDn{5 z*%o@9%0n(S-^c$uqlKO?jOg8#&UFO`hES~98=a>zUy^pmq9^^Q-J+jX&u3EdI0erU zGBxWX^WKa{RFCYM^vrlf_0Ve4)AL!14a?Q_Q*q)|&Wu}B&qLT$KY4pmUqj^oG>P3loBOW@7zlaiPDz7Vq)&q;H`?~+n(GCwJ|mA#YZg|7KGa{mn^R4wlS literal 0 HcmV?d00001 diff --git a/assets/Fonts/HELVA.FON b/assets/Fonts/HELVA.FON new file mode 100644 index 0000000000000000000000000000000000000000..8f03d36cc629b07f96532bb82ccf97bebbcc5cee GIT binary patch literal 36768 zcmeI54}4tJb?1*X(v1F$W=4|#jO{@h9EUi>>y9nQ3`R)8IK;3(fH-N&CiKYynLh$g zYy!e4(gRY5c&S63mbx^dsW(gV@h43wn`Vi_hd$ehA=H7CO$nuxP9Y_!o281Ar3!Ja z{hoW@d-G-_Y;V}@HoLjj@7;UPJ@@~=bI-ePe94t}NK8cXV0yYiq%QN1IAuT|Co{1B z()?n#zz^Ob8?L=>YreE)^QJ9>H|Doo^Wl$Nx8<6x`Ae=F+Om1;=HZ+3zk1zGS8u*` z>+-giC0C}pzuf$-(;un3_*AM>1zAE*y=NqF3#GTnHt62#K=6=a^uDxm*15_uFh4@+`4b#t~c%%>*m!Jaxvrn zN&T4tR|UeUPzT^GOqJW;b!Nmsnsx988_T)J(`b(^leIluP0o44M)S+)R>H0PHW?@7^k!){HEu91aV^oVUU^}>nuw7s`?gy|x2lh6`@fJYnarP#}{SH zU<}*|?gl%-Z-e{6XTgKui{N3>d;~m(`|IFI@NMuccmccwegIwtKLM`;NyH=$l8Jih z0NF&Nbb%${Oi+Nf8oL*)O*F}R>`TE0a3%2^Xv4Tiz?MW@MiUJ(4nBr|2e>zpko!pE z)42CQ{{r@xz?X@)4|w1Z@eafH40sN_2)+l7fXPIQyarEwQ(T%r3d{iuo9ZRs)F5Yo zWng7flN3Q8SO+cwmx1?#4>rYQBlfl6CU7&@23*qF-jtAguy=vo-~sSCuopZ8zS7hp zk2W>R6S$uO-vQ5qGWowkzCQ#P z+`!J(G(r8;47ehWIxqv0$-pvDSigRKK{BOGCL<-%9OYSPDv{%5qZt_;DB&&+jIJxn zSOdXQT*p72y$wZ8B=~dVStpCkFu^~;m383BN=Ew5`_Qis^ktW<9bYSBYxxId;JiNE zYjKYaj1Ji6fb>-=*y7x?=Idh1DKtnWgLW z3C|dn80@djZ`{R(kyS#ItQ!@YW}qkyz3bM|3&j!K`2mNL zNG}aU<<_mEv_hKp=?<(ft%ssU_raV9jBw-3f%JevO9~f{%h(uoN zD7DCi7p`6`vc8~&REi^`+JniAlgT7W7vp4-nT)gQ16p!N&A-x9#~P!v}$>d8Sr9y@$gI^8vr8LmDZB0uHo&lv`GKIX8&x>eW z(!Y28z05@Ct#3`#<()H5uTPW;b3c-(%SeI#Eo7H47n0`LYo4pjb2vgt&w1O}yTvpA z%zVie7)@i6HP51Xj+m!QGtdq?-z9&UKJ}58zSD0zy-(H*UVh2>l4X+gN!&bhk>`+c z+urFv|3ic0lDSYh8_Y9jX3c8zY}&vikpZR_t;&47Sn`?Pb*rSINuQae`dqbMpCb(1 zRgzuOd+{ofbK+N@!wC1pWehV@3i*uAktXxZELHc2=yR4@h}L2i=@&_vDM609@-&%e zW~sVYeL$Zhl&I*BYU$muVH8fCBTeSX497jf^e`0Wh?%(qP;|Z+V`N+FMD+P&^3BRy zClTp8>NKw%WTVE&TkOBFnA9WRv>;=3Ak!>vNFryo${OUVi;$_VFmhG1Y$SXOv^(+d zz}|%nbU*wmL+u3*gMHutcoL}W^gMV89D)BRcpY~=vQP`MQ3qJq*d~h`n~{~8Wi7Y@ zY$SXO*oItlC)kDTvj^V2gguOXps`h+G%`<%9D(*a?v{A7ECgr96UZ~91+Iv<$wp+J zEx5N~??C3+1?~rX;M)rx2K&GP!k@%`2K#yJm*6>q`zUUePwJ5|T95-euootpWijrR z$OCJD$|DycOI(5ca3%Ie!mbBf;M<111MC9#6TXM|d$AuT&OYRj1K>&U40s;A1df2C z;B`>Xp56jFz(TMXoC#KfHDE2c2)W`4a3$`I;CiqHYy)?K9bgx@AM62p!NXu5I6%B7 z!86Do&x4o15pWdR>)7?p3FL@Y=|DDEjBIcw{*}$-4Q(y%E5MatV{@Zi54Pam2H&07 zJCHYaVc!pZ5ApXxe;EHhZ~#0Bo@s8D=fM&1I%sL3AHkU|j6dv)TAJiaun}AjwzM?L zHgG4{0d}z#-VgSIec(y(JU9YgZ=vm4Tco3v{sJq(T5v@x>4EE8=_9ZW+zEDoUEqGO zr?pk~V($Y7z?0w^@O*2dyabMbqu_OD^-0<&*(4ocAy^E~1S^xY6ZTrdF9KJ9E5Sx^ zJ=g-a5q_sX9hs53AgY`+GNSWSq-UasKOC<4V>$gm@4~UH6mL8y-j)^dTq!?PW`!Hc zt{&`zPf3oZqfRyjP0&HTP*CGYPuE13CTCN_N+u_gHhWj8#yEMk*miv5h-9nj!f35Dz6p0VyuYqa z>*2>3*Y2Qww0c@!YG+cy*V9FRSf7?}=&WA&V18)Q5gA&Y%dH+F9$eaQuBM2Y(EcL* zQ7yhRO1wfr(;wAr==@>J=0A@Y>=44+8}A!t96IAeLqpmRy~B>SQ>-7q)~9!P+`*qM zj4~eaL+_7`iw=|FVR6U^6WE?O!y~jD=@|q6?KfhuwMH(MjfWIP`s6DnU&o|N!hQvy z_d3PoDDy$HE#lYglpjmmLrAs9%rn0Op!RAdO*m6w+>Y}c)1Xku`Q>q>l+7ly2*`5I zO)%5X#{Ao{2OLu@aq`yw*D9aQjeavN#eC@-8R?^)bE^wux?T+TF7cbUH#-iU?ltIj zSRvvjNR#L;Y#!NM=$6lVFnF%?CP#V^jn(uF+jVR_ZoD)(HvF4!jPM`#!x(l9IyIWx zIK#NWpe9bXpq(elf`+NladEpNM#xqvTdNQldRoUn-r~pzhf+fy?qQfHU53ZwMXa$@ zVSGFlA6Gw1R<=Oa*|=G#Y(k8%c(&T#HR$_YSO`CNv-PW2{XKS}Q=jp7^>pdN7y2c= zsApnmi1mhl`e)ZYJE2{MeNq>=3G%geZphk0X5pJ)mDTN`AX&Rl=vl(}*Ne5KpKdGY zx?D^Qsy@~aRrAZC-W8u{G%rLkTOIG_+D}2AJ5$MNF*6;cL6}s>e-}U2%^QeD_ z1)!gE#QD_$OsrDy(s{K7?K{Vwu{PC<#cO7r66XT99M=T&Wk*;00k_BsL z8HBoytsb%Vu)nV9;56$S$BX`9&KKf2W%01hI+rCKv%bYyJanBS9b*&U*bdrQ4DD=f z4$sCffxS9A(#LpNJ!03jLej2l@vOCTL)Jd$CTkA`>s~OgrGN7cx3ip5PhH=%zG|~x z8{1d`y_cbH*4;73(ADo7|5Q?cdHb6^kDNr)|GqlS@nW)3W6mSUy|D(=zX?e-v>3hb zvWAola3)dx?zNmbRHr*e*miI~v@hX*1p5H?Va_Pef+OHK=-_;C23P|w1slOFU&&Z;=mjhA738@-eVG-hiI7NxlNz1K)|K z&}Sy)7|5U}T#`siG0`d)<5s=o&A7*L?@FZPLF|3tDew|!h1UQIWMup%^odQ<1(r4; ztAYz~Ux|G+7=`B^?E8Sq?~j2e2!96qg{BlbK;&nTLWW)pRwHv?gnYdL`zrj`B72WD zCuKYK{or%pOUUGpAj==XK8*iac+21j_z5@;nvk(OKn|P%3SbRb2QCF402{#wxCOXi z2Xgmsg9mVb0Xz&I1&6@5!SmpI;8idM>XD<9U>?Y~a=rnpkkk8&tllic*xR7(L}uR& z_Ozzui^$y`P`Ug&#Cs9i4+uL3;>gq)WbcJw3GU^f2-f1i82kO$jxU4X%v+{$unRm$ z*uG?od>uRoeh8$kMdpB|ZE5LkYn97zUkygVJ>au#4f1917Ntpxl4gaN>RLTK5sz00xF$FT#1L$wrmLXaj!K;s zGokHcWQq!nwO#)z1Y8jMV{u=I2|~2lY|~j+o`6KIluHB*fo)Xd#^d)EqE8OaAl%x$ z!`fHcTj|2k@bD1lESXHcGCcf((3(gjgA>|(YINVmqL%un1h!}0p*5~{AW`W;9~{e| z4Vu<~%dkb`DzE8So1WwQlxor&vgx^wl%28zjnrWEUFFb)Luq!8wH zKp}Z3lP$B7Y#|-aXPbr`eA(jYbGuD{*dv?LOoVb7SF~zx}0y11AMoX)D}f{tj%jPY1_HVh--=tDxSo&iN?N}Izl`KQK61e?cDghIaL zY-9K?Ps=ilY^8h}gfTHbO8IQuNx$vwNz&2`i+L~$YvyI;#9860oIay8)2fExpG7Hx z)~IM`S^(|os5yAho=rs#9ol@GJ)*E8mOaB1oKUs@aZf7IKcx!C)UqB^MQdmbrveC& zmF@Ny^@*`!qb>u#IIaP|&$M!Rtpw)(e(7yF^yyG8j5_Q3HeI{5Z$O=aVbpVys@_vr z?fYs++QH%JEGt0}O>L8^O}fv~lZYNbhXanvl3mUoP9_g$%N$cH$7N+b959cC^L5HZ zN2@lxX;{~6;*K01A?|dLE_Qf$#%<5f3?|1jBTij*Q1MzR5=YM+w|lHFuWxI?*pRU2 z)SK*;!X!(I35p!3@gE*O%(GA`6jsFIW+(BNKtnq#S?3d7)-jLbC5O+g;WGzs)jSoF zGxx37EVN|LOg?i8H+!C|g*1DfIf>WogXT0|LqiwQ(=#-2@E{S7@&494gZ)GA7kL(X zGS|IKM}!OX8JmTc?3u~en&02!DX|Bi$DX?&piu#z)^%1u(|)pi3xYP#+WNHU{7tXm#9!P@J2-x5 zAxTV3KQyxql2~0lppmdKuA~+Pvuu}hd+oYNyW7&xjNP#%JMFap^8x5?T2uN#+0vS=QQQ3p_%g4(6rS^ zuF~R~YN+=Jvxtr%_TNo6Y#on;X$xtgbPR=t63n1cf*CY&n?Z|b!(G>xZk#Y(9K&H~ zK&^Vu8xZR^&waD{m6Kd(yxHhit|uEcM!&*4EA%UI^eY|cRl3ljtZqnie=ce6%;`Nj zy(4!Yx|2P;1Kx*DMc?DTh=s@N*w{Z6f zy$beKyuZ8!_fG5w@jr$RMgco?}Gk7EBCm-0q_)f9=rmMg5#hi$z3h5 z7%T^Czy;t6a22>7+yd@QHp)(LKkf&?!{9OSBzP9Q1YQNNgLqq9IzSgV6Rd9I{v3LW zOVM3i3ATV8=q&a?+t=1Chro+%DS54}NmA${7NVb6nd1FrDkbm7e^ZLPYS4CJ?*(6h zb^tsNj-uaaK_@XUP5INjhsS+EIxSa$TfokAi#(W4%46VJ@G6LBlF|iMXWHe`Oq*=U zw8{>!2kZmSpwl=4UIX>*+`DUULsvn+x2NSIun}wnyTD$;4`4q9&nw_KnAg!J%R7>C zK}V}x)zKoifSuq$@ECX&yb9u-?b6lB`{7R71#AI3z@ARp1sv*R9&~aKw3G1%y?#!s zECegTMPMV?Hi!Fr*bmHUk*|QKz$@VR9NJ`Vt1O>Od(7p2oj)Dzq~z(S5y$vPMn*z< zJl>z2Sg^qO9cRj$G%GCn!oF{groY)|t`M2s%C%7{>K4*uo3siSM3EIMDk>M!um@6; zZBQctXisR7kBD$2cph7~N#|@82IT@2+1% z#KWI z3ynZT8uv_)A;aBOHZFa5zN>$*j-}EU@Pon-mdFmG(#aat&{X`t_4K^z%MgA%wc_ao z1zxuJ6xZ{(IiRK@dQwI74*VHL*~^z#3a`n4&P(jHV~DS*F-f6qAC>;Wo8U#%gRz!NAWyE(?_%omH*=6q?y;z zHt`%I44XLpDhV55Sj#RNWa!LAHO6fz#%asOD^WGH6Mf+sH+@@Jz(TDCn%|=$`wtG; zS7gxR+wVC%8no?-MX5zA9Ay{7Oy+7FO(kyAal%v(!RlfubA4R?5fmw}D?`I(P`)(JiYuwzJ)Ry+?Hm>C|aZPTFHGNm<^RR5VHm+lP z%+P(elNvKtKefoYbr1;yKUq2ggVAEUA z)tyOAwvaD-#%1oW==GHuM$<9(G6-=ap%dIMvZDuGnCi$>NhTD`8r zG7lN(=i=0W`jz5_{NCV?y8=1Kb3O442M@9`z^UrEo~~H9(D*EGAUElnziGr=V}r4x ztwYgfE*FGzC%EF`Cl^|F09&u;g=8*6`R=q{n;Eh1U_y-^#f-34IZJ%%P%(&g6@kMu zD=dMN*^)QqGnKOZv+h(dEB&B|rHAH^(nx`Ejbq~I(JdVv9RV?F2McCEYYJA2fKb`# zRVm3;4zBX}#$3Y*Jb?}WiXIM|Y6{L1yO)mHwWTn*qGz?Qu5`Rdl10b19JN}obryc) zsjM^D6(nniOpe3VZ1PepLU%Qa%Qi>V(hDN|8sO78{D$c$IiMr<&q|kgb4XYAfFD<) zZ9#kgpxryCUH7=_Mu&=F4&;qG6$C?>jk;KQ!_EtjL`}aVNzO`7Et;LaD3Eh%(kT~o zs;FUs^~AW;9VnI5eb|>z{C$&rj|K9WilzEhizV|m@Q|m3`QAD z`IYWQWHS^XA)NqfZe_MMzrMBnnob~tkxoAHpiIcipU ziBT{sol!6=y=~s?bVkQ){HgZTtaL3?lz;rLY4^A(X!XO$Yz?}PV_a=Z3?q70*-Yy~ zv-BG#*l843-CCE~=vwC4=)s8AxWR~4_q7k%-cxrlqSY!tR{td5asOVOX7?kqQDgM2=;7$J zX7r}nhIVwVZ8Fp_NA76IaBq!!SA0+W*XFz8r2G_p?0ml6eJ9_ztiygU_C~&s`7rn- z_#^P=;2%H&z3E%Q`Ct%C@SVzMz!$lT_BHTV;Gdz-O=P79eW{bka0jiO?_b;HoA_Tt z*E*-E1N~{c{5ty4d(fHwKKj$IqD%d5(;WF9&=)s%$a|X8+#BoQ&RDxVj{7IgDRiUl z+yhI?hg#;z?dV3o(2_wHn&wUy-^ZfsY)AK*Z%xXtpbxzS`v&Y!a6jwwxW9q@GX6Sr zlikTQdd?JgvaD|Nwq%>!jr+mm9Qjk|F9G4+RhPMY#a%e`l-Hrd{5bl_`*44`t<&7M zn#O$^I>~cVP4a8#B(KH3m3vm(u|Eg?3pfD&0+hi&f#aZ!`&2pb4zLDX2tEKdfm^_D zfZqoHiDUA^U_b6}gTDo@f`0+YOp`1EXM(fAdhn}Y7<>eLEEAVcfj#Ip{{%dpNy~rF zq@)#H<=fF?7Qy@5JLG15RN)W7pRpMJO?yIq2CW6X)f=Q4+g=++_-!Od=Y#NJUO>r{tCqBbx03zz;A+m;QRBqvpBy)-ZH;kE(0G0d%<_- zXQUyUmc`jN830#-F>pV41Uw6VlAS9Z3s~e9w8@9S?cn#oe_arlzXaa{KL+&+Q_>E8 z1zZAd0G|M#2j5uOBrjth1(`)DSq?4+*Dq?7yBDS9LF}(DYLb`y>1ZdVeVT2x!0u|j zJ(y^4t-G$jej?dqHL-H|?%mV*} zW0rxP%=S}D4O4N0`c5$pt(Rt}-OYytN|<7J8raZ8PREL$4WS6mWS)vt!t$A?CZnd` z(k63pMsGalkBtbUI&1Ph21!#@8xw>wAk=6`B$?StH4#hy*zndqOo{Yi1N0u^aedVC z8Nc!w6Akq|#+Mq;H?Hw9`Bt3OtK8O`D|xhSA?r7+O($#(%VVp8yV$#Wb z^`4+*3o`Pox6li3B_l2Uq2bkCvsYne<1n40UB+y-$?~ji=Ug%u>DyV8T9o>k5vVi` zo0O~0+N6%9;;E_B8?`gTdkg`n=ue$8;%j*<84Q(a#*Hy1;b2TykAEsdNvNk~jJ5_Q zh--=Ali93mZroo*&3erAcP#@$yRcwEo3C=`OSHp>w{NeH`SlB4i3QAbZG!hrj*W}M z_P3u@f#I$Tr}f5BV0bi-YrQ%pct;j2o5zyHvWEDhy$ju@Z&ab76V#R(52x86%qrs3 zXO|5*czC72Rb!jAphDArHPg33n(68n7D|8q63Uk}PT5bN@po2{5B1IpW9bn+TMoT? zdbj#5LsCUXu*O1&hEk#DirH-Oxk%iJ>bR3(T$IAc%v_2vn~Z`L+a{GQmSOM<8B8lE zx`_l{Z1v3W+AEkEu;Q$HhY-YiJRSTjbOv7$+v%SED`Bn@hGP27t(L%obR<&4rVAst!?R{Fw}E+ z`_vG6!#QOsPN6+`lcj|6AO1xo~25PVrbmcRw4R%$AP1{vM$eEky8Cv9@>9U@;Z**Fl{z>>R;-_OGbe zgmkA4T-HwX4n1}Fsi9tf6P@gOc|qNRmm`~KxOK6!*LCxv)vpiH31Z!drk5QW%0~Qz zMT09b4bN1tHu*8ed>bp#yXny5hlV4iTJd))dS)+GcO{y-+-h&}(4k@##hj%gl`t}; z?unSOLpkTTb?UQkC>36Q!5fQLf^fN2KJ~Edo68RaHpB>TCF|}V8yhoUFOm zeOApRpB%4?94Vl=N)NtsRyT(GLo2$a-+FkO{&GVvj-hOJDC)1|YLengir;4{t33W6 zJN482h;e=QEX+^ujwK__?|N>~-?r&0M`;dxL*MvBcPKELa>8y*EG98T%*Yl4*`D}# zc~Et1aoG^q^!;NCuMnc49;rq`>91(0>s5tvhcOzOuMXu@6b*Hps<^QR&NG$6lsn)? z^VK>b$0s>ZQX)0e45JYPPg1YH&*CY`pRUKNc z4yCQ4aXqIhZXDG$i;4jmQQv4Gk$niXhR|HKcl2;o=;3NF{F>@C@CT`$2k)BwJjmtO z=o#nUvXScF;EcmCjWit{q)m5%GftEliS7iYs?YzKZ#Y(utTE>d};$^e0G} zrVF;vUfdWHcW7C0llA^I(Y9iLMP;U@Q6{4XOG8#%DqoK@&=uyk9es2xu3C^L6_Nut9 z)Ev`nHF?&4bW#8J%irw#zmxo&@>-+xFO!WL^Zt+DWr#IPp6@FzXjs5+5_j-h6^rCY z4RhsO^IH_H@@c-2IKb~t{GhQ@QhW#TZoZSa5&KivUyXOj--2wSLoNq9z(H^fyp#8L zH-XQCGQa(>fbSXln>*zi@BsKO@9Z)ydAYDm0eQwNoC({f|I@ zBjH0yeuI_wZuceI<>}-iX=7gZ??^mzryZF&6TUb@1{EB+o}1|oSrLd(>b|4 z-6@X)$#nAmtCjovE&R?vt31YUHhwR|JFNDMoZsFq*R^-b9_+uyp2PK@OFEimdq<~y z5%=GBB)EH@kQLm)Ux)qC&P8%>XNSB1=FaJqOTovvSMSZqaG$=FXSj z!hRA=f_d{=rFS0hOtC*QFC)K?y&rrZw9Rjo9`I{m0{mz2=io^Yz0*6ve*kwZYL?G~Z!PMO zAA@B%`UgC~migUWMvj2YX<1o)T8nHxEhWEmTBm#s_fMcL?8?Y{yISS8u1@)bE`E~& z9EY}~J0ll?Qa8T`g8gVWza;{i^1K7fFOm)5v*3sMb}61dS3V5(f|o&V@mzTy*txh{ z9$!3PI^Qx!E(X5=9s~dU7JjjNNt2up-Vd%@GGE5PpDtM-uYxsiT_D@RGob5?R=NI+ z1#<5h^W_CF_igj#QtDbFv@1?aU6j z08D^Kz>m&M$eHg*$hqJ$F!YWNxdr?&_yIV5S%+K+J_QahqaXfVhr9z^4L$>&1?lB! zS--qfZo>Zj@+SF~KOOC)JmE9v8(s|O-k9-hHPykKvsmJ4jqVIwgl4s~2F=p6j_pkBJT}>zO2kst zHQ+0CuRQ9fWg4|Q-C2AjpGw*GPm!RWQ`~Ac^%k-@LXCkt>u<%?e4LF=OC(NS z_S2zr?5FUR9-8V(*!LTWEa7~AtVQxxRLqy`C8m%g7BCWAw(V{AI7+XCYe8Vpbj9+qNFX*_L)`z78 zPd9L2cOxV54vj3sIi%$?8OGAp!1QrU~7F0KivdZc@c#hgz0N>Puc6N!{5YfVu#%P_Kuqmd#JyBPq9^v_~52S6Cj zI&9^aqmghnQF5U6#IMaKno05J`3Cilf0yD^0zyb7vwpUA=^nq|358 z8E|#$fH#j^w_iAoQD>sn1x@C9Wd>ZC!9CQUAlqzTr4)B@kx=S!qRI}#ay)XwxxJJGBp+C_(naX;guem>PoTp zrBhwuC=B99?AeA1Wv&cK%d1l%5=)(LT!^}vh1hv~^o2ljt4Rm%Y=uy+>W=W(SIe}I z8b1wonBMM~=%ODq^fZq0wDxK^k;U{o)4wv2C;S8=)^xdIe6kk__MJiJ=PEfyn1TX_ zomQL-(kM|#70-f;3pSQ6U^{odFcnm&q)Uz)Ns-F1e-_y`Qu^P#SQAgD7o1%vsjL{` zy<`8umxpd$-4_fdO{*rc5)vm``vePhkYimU_2^`IDmvyWiMpPjBZD$sU2>#}MAsve zw#|I5N<^Q46mMPd7K1jQCd*!Wl8~UisI=v)tY7I=l`)G?z+-I>%R`;Rl@IQYpk1yG z#)sks`?ahU89;~JIeq%lhdN{&ph|S2o5h+bWj+~4S~KJ{Zl!C(dRDqN9CowRvFzs@vCMExI4VlV`H`P~}3Y2)- zbVji|5^)x%Wva-Eu-Kv0{m-*-M@R?(9M{A3UPD;23wi*m;wlt#ZTw(J#T3Uz4jsDd zuIN(8VZW$bMTLn&PKuYh;TE7FxzV!AFbwt%B?X0Gy9jzHD1@uw)=aPwq;sRuMei7C z5g5!eRZnm<7-jpzm`ZszZtzsEZL7)?l8uqD@kt@}of~HK|mn3q1kvFMB9xoc|wMBB<+$Z4S##!8zu{ zoJj4M?pVCR=SwynJL@fuch|>*#of}~s1T+bba7gj{0Ya8hW^d}I3S`YCz3INErmsT z)ibrI;0IvU{Vav-61mM@$taAby3kyDqHdxRjfk8${%q2o>3q8GonF<}5$rk@TP4RL zzvoygcC)SevZJax^n{f9;b<<D|MWyIj{PD#LuETdc(rc zz2@)|7RHn`Qd}yd?;~m|>MFl^VDqa_l@Fyf0OPv)0HM*}hVp-II;6p67()V%;Cx=8 z8Et;W>$~)@g=XtkeflIT_}unlz?<%$nCLfxbeQNdy(Dc-z-8=;?yHJCtA|5!n4ZtT z?fEj!c%QlbGi$60kzuL-Pg10jwY{_<_^UPy%UsPUWLTEUAy*~G@D-bep&r)HG6FW^ zNwjvph0p8)T3~yDBU1X4rqx|gWpjTR6^Cm^RFGPwx4FmR8)|sRU0jrO0wyt@SYvZL z|DjaCrM9+4SsTO{!t}$QT;$as1JY!dsCuv9^IOYgTdmvX3zAdQZR^s9#2hNcyG|{l zW?3C^YUiosXqu=x;w!zU64CT#b;O;gl%sj$P#tmVo_nTFN%3D>M=Cvmp7yR-%zVz| zcLF~F!Y_d6!=3g?X1kj*o^a=TV+AM+>2xw(*A0L5y8xF0M75pDA)`;(70Tunw6f4ukAPoDG@NFWLaD*Z__So~D&48vnLXnlq%{o05O@`wovaS9Q8zaNSBzmntk zPc>q#{=T%=VLWFmi5EPlSVXM zR9jStKkZI&QtSV*3Ac}FS4ZF-!SGe0TP{sX<>i@@B6{CI{0ZI|c#~OGch6=LGZp`C z`4klY+O@TB4CvaEzcJv3&5Ve2?I}b|%#276okB#Gk6_9NO~6K7ODeuPgY@bQQZb## zMjBd^^SC2${NxIDq~Kb0H~)#$$=_rMJyeq;z0d!YrLPduRsafWrSehW#;%(YQ77NY zBho{)IqKv)c|_`18&T_c@`!Y8ZA6`XCyjWDTN7=e$$$73w$zIGX?oGnUDZwKN_7*` z_I8+5-WbsKr}D<2Zbn4f{uCn8_O&_E_NNe$CajG}6P`juj-R#f6*wbvKr$~kT-#on Z^w0Nx2(?UY@*J^ilCN}%b=6*X|DPd#VjKVf literal 0 HcmV?d00001 diff --git a/assets/Fonts/HELVB.FON b/assets/Fonts/HELVB.FON new file mode 100644 index 0000000000000000000000000000000000000000..81750c022cd2735fe78ecb8a6bcdcc55eb16c213 GIT binary patch literal 50880 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 X@3e6q8l{DQNZKGRjb5w!D;oa?=!Pv8 literal 0 HcmV?d00001 diff --git a/assets/Fonts/HELVC.FON b/assets/Fonts/HELVC.FON new file mode 100644 index 0000000000000000000000000000000000000000..4bfb251b89d21d3c72271e10191de54b7dcfb665 GIT binary patch literal 38960 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 literal 0 HcmV?d00001 diff --git a/assets/Fonts/HELVD.FON b/assets/Fonts/HELVD.FON new file mode 100644 index 0000000000000000000000000000000000000000..0b3e7d94c2b2454f06ab4ec46a7861dd6f541fa7 GIT binary patch literal 58144 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 anLEK^uf9V%#g(9skWQZ#oLBjKVgD}xZBp<6 literal 0 HcmV?d00001 diff --git a/assets/Fonts/HELVE.FON b/assets/Fonts/HELVE.FON new file mode 100644 index 0000000000000000000000000000000000000000..a65a450ebab3c2500660dd49f032be0142ab462d GIT binary patch literal 64784 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 Z0F+O28=3h7-s$m7DYOGn{Eb;n{SRA#)wTct literal 0 HcmV?d00001 diff --git a/assets/LowRes/Cour1.png b/assets/LowRes/Cour1.png new file mode 100644 index 0000000000000000000000000000000000000000..0c04da22be70ffb168f8ad4b4829cba0fca86ecf GIT binary patch literal 1465 zcmV;q1xEUbP)F}`XqFfvYV`jk4)A%X{3=x z8fm1FMjC0Zs2LK8L?V$$Boc`vF30ftsC@qW{QIVP{`+^>@yGP6P3^IpVPD61D7%Gk zPr9iO+j`8nlti*aWM>kIL?V$$Boavtj9>lx_QS?|G$JM110q`vvAY0%Umu>{W1AR+ zC|%Ofo_*IrOlWH#w)I%_eLbQEee5!09Ijft>c<@;s_Lywp9QfG`>fvs{=baHy^v$* zk>DE5>eWnJXmggtF~?^Id+g}zk9U7N(WP>y{sQ67xAu?Sc(BbzkFEOIAnB3O9ranE zVUFC<7%X7^VPz5}L-IQ#-BbL$UPLDP{GR@C-3W*=R*vG$wNB%Nsu_QEYs2%G1V0D_ zI^LbOXZs>t{VM(VwVF`GnA9KQjW9i9QR$uZQ}u-2bjvaN|ky(+M&ca#IL_;a$i& z9SB=zqv-5dh~c^Dvt(=%bqYw10?dOI<`#}EiDuZ0V+K&4VrN>K>o^Due}$~q?J+!< zFZvz~@20Hr`3ua$yAaxSL75~x&U;Q{zmgQosy#%ZRwj=cjr}fy;gj_RbY%Xa7#^&_ zJf(%ZJ%7)9T2-}?j0QXB(|!)9ATtK8H;A$qNkX)i{J=3hvImA6woWHzaMX=p{Tr9t zJi5PCe1q7c>+3=6WHS*6;IvJ5h2g8_1JElG7@p2#R!#i*ky!2%L+=ZDd^cV<>}Tw( z(a3W^M4M^$7SGoSjrX8d<>3+HkLE6sEyM=4;LAv~Of}QWvSf@D9mp=atHSUcDQTo$ z&duxETv8!+N@DmaFVl&lJKxt}*VpW(W8Av*eaWY)249C^xfBP)$Jr|`)}59zJ44u% zs?i=d&s>7xL6ivgSg3-UhEJJ1ypbgcA*GT$yt{BCGsovKyc_K$tJ#wnY&UtI%n@bm z2gw&zVff)~e$k2HJ2{|2iyG8+2GR(__hWc^r_r~xS`B9&TH~8St%I&H= ze1^ddVff@(J1~6taxWsi)=*)-&$pH@LQD1!XbICQi71B0O=ZXCSe@$;kl5}M^YEP< zkTAS3{Jk;!plazu?$OpGmGj_MPse^ZYxm9CUAT~Y5k%(&1NR%OhrcBT1G%kp%?5uv zc@F5V7=CoE1nI``$nM|t4BCU?!QHmG{nCv=PE<9Iidtwe%}#LeM6EcuFFlg|;O=f` zaWnhU6K$@tgwXz_c0~sF!Pu%iJeX;5#~`=6q02G5Ru|$^2D=`f^Txyb$^)^R7sATZ z9$RQ!7`_?958#_JL`>p>^VQHp?w*f)49}dryIrS{-6;&*$^$XnCCdwFpjwul(P>qkG|XcI>#mukjZYeLCoZuj8{`3bH>Z zQWshh|1~^U?I}A(?nUf}?hQF5iG;(t7r*kp;jeFYsy7q~%YN=>Kgj3n-;VhQeBShN T<0`H;00000NkvXXu0mjfexK-* literal 0 HcmV?d00001 diff --git a/assets/LowRes/Cour2.png b/assets/LowRes/Cour2.png new file mode 100644 index 0000000000000000000000000000000000000000..945b891d289d463d9e51128ab47a36c291aa500a GIT binary patch literal 1848 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 literal 0 HcmV?d00001 diff --git a/assets/LowRes/Helv1.png b/assets/LowRes/Helv1.png new file mode 100644 index 0000000000000000000000000000000000000000..9384f43774e13feae22dc67474d93e34c6d2e701 GIT binary patch literal 1479 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 RCCmT-002ovPDHLkV1kGFdlUcw literal 0 HcmV?d00001 diff --git a/assets/LowRes/Helv3.png b/assets/LowRes/Helv3.png new file mode 100644 index 0000000000000000000000000000000000000000..432b4218570feef5c04061076e60b1e33ca19368 GIT binary patch literal 2301 zcmV|AU@l@IiLXC@{(OCX#lJH8ef{@CZjTC= zd>qQ(;d+PsiFpR8lkxn%F;On1t`jQvq^cs7j=$$L9lRA)GVIF;&2~k_(=xR*3v9O| ze6`*Rke?=eAf%#ht|xhpiuvu$LSqHk^Pu0Eg4d{@ozmWXsAg4c8udY$?c}FVWcA)k z2^71^rDa$=s%Zs>9O}|Ai^I2|*!L=F8xA$9#H-6>v;NTDkrq?rYI$1XnoEf$N#VzF2(7RzxWn`uxcsEFy)9>jw@{q}dxbnq2Z?6jX0dD* z*F}rPVzF2(7K_F5dTB%hANPD@4!Ze z^Qt?Yjqa&c)D|Z=*?92d@-L(*%FK0C6)0Oc;fXuDs{ej~)#%zdZ!aG`<>Tb*<@lNT z)1T_#Mn!SW5m?%{pC&1^+w%E-XnOKAW)th7D*L#Odf_CSRHa2q;I;r;iltI+$(PNS z??y!gwxD{jD)y2}+_ZDL+?Jv^;gxV-?z}2my1t9cmQ^1?oq_E%@)?4xJS^2!@)4gZ zMqHE3Uxq4#>FiPK)^abK+X*T=pFHr;^+U)CQZbPVcQSfaR*l?$C~sD*4Ng~jU}io@ zV;8W<)QoF|8iPEPH7i51#`YNaJ&Pn)exHDyB0(#;X1+6;M%9TX@kOaT9W5wMGpX$o~DJ?eNt}cw0OP* zpq6W=lFRAvwxG?wY=_^a0&cD~H!ai^tl zXufkr)F0&d7U(SEJ5P=Gl{qcg{}v;?)lD9#)iyz&VBb71TeiSMb+X05>QcTlBHxI) zCR-DVHAiR~^6yHxM3(Qc`)c93^6et$ChfeRhH8h068EN)R+($FiBk@%FcHeA2{EzB2et|XT^b8bo9uzv0lSIBr)d%2sXKFmO3aAjb@839 z(YJnE&|CE_dadD{%$9D}kp2bZiyoH{z}D{o&u2F}I3GjrJLWqt;yc$tB)8K6 z4ZSrGGi`~9h6BD;@Vkr~zLkq(mKZUM>~|38w!uHEI4}$LI~vfz7jk9$ zohaC0ZQwf}!$y(sAS0s2ccyy(e!dfhXjA!AmNKBj;SRfyYrbQ?a{}K%`qD&kqO=Q{ z?+7=XLL%0FXSL!)xic(Xdtg3z=`aL*2s4@Qwf>x2$QsYsKD*qilKa_R_>RCtD17;|?RP2~EzWoF$|lzz+wYj~yqE8^1lB)P>-Y{^hPYEXe(jVV2C&2FBZssVuk-m%MU6Dy!Q<~d?kd%*6<0P( zG2dCscZxV)<2x;oyZ72I*k4@V(5iw)kxz)flP|0V>_ha({#lg$PJMch8>2?S@wHNh z(P27NX^F*Yc1sXtitj{g{GE!o@J_xH%i`~_JMwn9Vwvxl@4TAtIo6SdfvVB)As z2N0g6*?tEJAd+l+fS7fh!*_y-IWjUc87W@K_7GsW7X0#O_s?RN{)8AYF*8=2ezhs~ zqQ(Zq!31}crrH{~fcd>`vi7Cavc_h<1Af`1d?x^pfKl6N+&^ozsu6%4GQNKn7%(En z{-=xh4h5jicg%NA<~spc^;dSbmIEO#+9#|1iIh3Ba8Ja{u+q=4-&BZ;GzE(<N9~_A)y%cT1nB1zu7$Be z*WI&dhWg00?~-$Js_B##OG}+!&38_2-PSj>Y0B#0iCFeCC>OvACB0jL-3- z_)c`VQd=w*i>v`tp`*V4dzvZSQE(?-#dp|gQdjg@i^XEu(`#A$6)di$mSaB$XaM^U X?g|zR2T4uv00000NkvXXu0mjfa7>RW literal 0 HcmV?d00001 diff --git a/assets/LowRes/mouse-link.png b/assets/LowRes/mouse-link.png new file mode 100644 index 0000000000000000000000000000000000000000..386f06b6db8b6178a472ed22059319c80366ce59 GIT binary patch literal 5551 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 literal 0 HcmV?d00001 diff --git a/assets/LowRes/mouse.png b/assets/LowRes/mouse.png new file mode 100644 index 0000000000000000000000000000000000000000..562900d8c08e0dc8abcb8fa482e82986b8b993d6 GIT binary patch literal 641 zcmV-{0)G98P)EX>4Tx04R}tkv&MmKp2MKrbE=M<*vm7b)?(q|hS9JC1vJ?|WbFz5|4MnW<*SIG}2l zk&4H}Y;HviyrKsonqwG}n5iey3mJHhuY36Tei!9g-gSSDUL|ibz$X&VGTpF<*NLY$ zEuHf|ahR1Ph4`F!)SwFzKXP4m`HgeYVS#6cjZA8eI7}=S+gNU6Rx(uLDdLEtYLqWz zT~;`6aaPM!*1RWwVKA>Pr@2mR5OFLbfh0u8sA2;jFNZGRuzcH;!_KLb}<%U`JjGoPf_ zT3X}?2yFuw*DX!n11@)f{wG~BBuDbo5()+2{fxdT2lU+n-K%cjn&&uu05a68r5oVj z5Ev;^_L|4LyW0EqZ%wm*KfoGt#;2#`tpET324YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&Y86FUF_sDWt!000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}00013Nkl zwy}@^U;_~VkQ_r4!4#sLOdxt1U5LKS4&()}ji{|aPA()F6R^Al-%1_b*}-XbE1Z$b b&;F$@vK2h~8uII!00000NkvXXu0mjfXLtmx literal 0 HcmV?d00001 diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 2fe1d09..e8e6d6a 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -96,6 +96,7 @@ + diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index c187753..9cfe9b3 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -140,6 +140,7 @@ + diff --git a/src/Cursor.h b/src/Cursor.h index 039039d..6e066b6 100644 --- a/src/Cursor.h +++ b/src/Cursor.h @@ -29,7 +29,7 @@ struct MouseCursor struct MouseCursorData { uint16_t data[32]; - int hotSpotX, hotSpotY; + int16_t hotSpotX, hotSpotY; }; #endif diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index 12bdda1..e58619d 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -322,7 +322,7 @@ void CGADriver::DrawString(const char* text, int x, int y, int size, FontStyle:: Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) { -/* if (style & FontStyle::Monospace) + if (style & FontStyle::Monospace) { switch (fontSize) { @@ -336,7 +336,7 @@ Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) return &CGA_RegularFont_Monospace; } } - + switch (fontSize) { case 0: @@ -347,8 +347,8 @@ Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) return &CGA_LargeFont; default: return &CGA_RegularFont; - }*/ - + } + /* if (style & FontStyle::Monospace) { switch (fontSize) @@ -375,7 +375,7 @@ Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) default: return &Default_RegularFont; } - + */ } void CGADriver::HLine(int x, int y, int count) diff --git a/src/DataPack.cpp b/src/DataPack.cpp new file mode 100644 index 0000000..bd000e0 --- /dev/null +++ b/src/DataPack.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "DataPack.h" + +DataPack Assets; + +#pragma warning(disable:4996) + +bool DataPack::Load(const char* path) +{ + FILE* fs = fopen(path, "rb"); + + if (!fs) + { + return false; + } + + fseek(fs, 0, SEEK_END); + long dataPackSize = ftell(fs); + fseek(fs, 0, SEEK_SET); + + if (dataPackSize <= sizeof(DataPackHeader)) + { + return false; + } + + DataPackHeader header; + + fread(&header, sizeof(DataPackHeader), 1, fs); + + long rawDataSize = dataPackSize - sizeof(DataPackHeader); + uint8_t* rawData = new uint8_t[rawDataSize]; + if (!rawData) + { + return false; + } + + fread(rawData, 1, rawDataSize, fs); + + fclose(fs); + + pointerCursor = (MouseCursorData*)(&rawData[header.pointerCursorOffset]); + linkCursor = (MouseCursorData*)(&rawData[header.linkCursorOffset]); + textSelectCursor = (MouseCursorData*)(&rawData[header.textSelectCursorOffset]); + + for (int n = 0; n < NUM_FONT_SIZES; n++) + { + memcpy(&fonts[n], &rawData[header.fontOffsets[n]], sizeof(FontMetaData)); + fonts[n].glyphData = &rawData[header.fontOffsets[n] + sizeof(FontMetaData)]; + + memcpy(&monoFonts[n], &rawData[header.monoFontOffsets[n]], sizeof(FontMetaData)); + monoFonts[n].glyphData = &rawData[header.monoFontOffsets[n] + sizeof(FontMetaData)]; + } + + return true; +} diff --git a/src/DataPack.h b/src/DataPack.h index a86358a..f3eab58 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -1,24 +1,63 @@ -#pragma once +#ifndef _DATAPACK_H_ +#define _DATAPACK_H_ +#include +#include "Cursor.h" #include "Image.h" #include "Font.h" -#include "Cursor.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 DataPackHeader +{ + uint16_t fontOffsets[NUM_FONT_SIZES]; + uint16_t monoFontOffsets[NUM_FONT_SIZES]; + uint16_t pointerCursorOffset; + uint16_t linkCursorOffset; + uint16_t textSelectCursorOffset; +}; struct DataPack { - MouseCursorData cursor; - MouseCursorData hand; - MouseCursorData select; - Image imageIcon; - Font small; - Font medium; - Font large; - - void Fixup() - { - imageIcon.data += (uint8_t*)(this); - small.glyphData += (uint8_t*)(this); - medium.glyphData += (uint8_t*)(this); - large.glyphData += (uint8_t*)(this); - } + MouseCursorData* pointerCursor; + MouseCursorData* linkCursor; + MouseCursorData* textSelectCursor; + + Font fonts[NUM_FONT_SIZES]; + Font monoFonts[NUM_FONT_SIZES]; + + bool Load(const char* path); }; + +extern DataPack Assets; + +#endif diff --git a/src/Font.h b/src/Font.h index f8ff645..73e8abb 100644 --- a/src/Font.h +++ b/src/Font.h @@ -17,6 +17,9 @@ #include +#define FIRST_FONT_GLYPH 32 +#define LAST_FONT_GLYPH 255 + struct FontStyle { enum Type @@ -29,16 +32,20 @@ struct FontStyle }; }; -struct Font +struct FontMetaData { - uint8_t glyphWidth[96]; + uint8_t glyphWidth[LAST_FONT_GLYPH + 1 - FIRST_FONT_GLYPH]; uint8_t glyphWidthBytes; uint8_t glyphHeight; uint8_t glyphDataStride; +}; + +struct Font : public FontMetaData +{ uint8_t* glyphData; int CalculateWidth(const char* text, FontStyle::Type style = FontStyle::Regular); - int GetGlyphWidth(char c) { return glyphWidth[c - 32]; } + int GetGlyphWidth(char c) { return glyphWidth[c - FIRST_FONT_GLYPH]; } }; #endif diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index fa5deb2..6ff7778 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -16,8 +16,9 @@ #include #include "WinVid.h" #include "../Image.h" -#include "../DOS/CGAData.inc" +//#include "../DOS/CGAData.inc" #include "../Interface.h" +#include "../DataPack.h" #define WINDOW_TOP 24 #define WINDOW_HEIGHT 168 @@ -48,7 +49,7 @@ extern HWND hWnd; WindowsVideoDriver::WindowsVideoDriver() { screenWidth = 640; - screenHeight = 200; + screenHeight = 480; windowWidth = screenWidth - 16; windowHeight = WINDOW_HEIGHT; windowX = 0; @@ -56,15 +57,20 @@ WindowsVideoDriver::WindowsVideoDriver() foregroundColour = RGB(0, 0, 0); backgroundColour = RGB(255, 255, 255); - verticalScale = 2; + verticalScale = 1; scissorX1 = 0; scissorY1 = 0; scissorX2 = screenWidth; scissorY2 = screenHeight; - imageIcon = &CGA_ImageIcon; - bulletImage = &CGA_Bullet; + + + //imageIcon = &CGA_ImageIcon; + //bulletImage = &CGA_Bullet; + + Assets.Load("Default.dat"); + isTextMode = false; } @@ -295,24 +301,24 @@ Font* WindowsVideoDriver::GetFont(int fontSize, FontStyle::Type style) switch (fontSize) { case 0: - return &CGA_SmallFont_Monospace; + return &Assets.monoFonts[0]; case 2: case 3: case 4: - return &CGA_LargeFont_Monospace; + return &Assets.monoFonts[2]; default: - return &CGA_RegularFont_Monospace; + return &Assets.monoFonts[1]; } } switch (fontSize) { case 0: - return &CGA_SmallFont; + return &Assets.fonts[0]; case 2: - return &CGA_LargeFont; + return &Assets.fonts[2]; default: - return &CGA_RegularFont; + return &Assets.fonts[1]; } } diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 72d0787..aa5dbea 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -14,16 +14,66 @@ #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, uint16_t& headerOffsetPosition); +void EncodeCursor(const char* basePath, const char* name, vector& output, uint16_t& headerOffsetPosition); + +void GenerateAssetPack(const char* name) +{ + char basePath[256]; + DataPackHeader header; + vector data; + + snprintf(basePath, 256, "assets/%s/", name); + + EncodeFont(basePath, "Helv1.png", data, header.fontOffsets[0]); + EncodeFont(basePath, "Helv2.png", data, header.fontOffsets[1]); + EncodeFont(basePath, "Helv3.png", data, header.fontOffsets[2]); + + EncodeFont(basePath, "Cour1.png", data, header.monoFontOffsets[0]); + EncodeFont(basePath, "Cour2.png", data, header.monoFontOffsets[1]); + EncodeFont(basePath, "Cour3.png", data, header.monoFontOffsets[2]); + + EncodeCursor(basePath, "mouse.png", data, header.pointerCursorOffset); + EncodeCursor(basePath, "mouse-link.png", data, header.linkCursorOffset); + EncodeCursor(basePath, "mouse-select.png", data, header.textSelectCursorOffset); + + char outputPath[256]; + snprintf(outputPath, 256, "assets/%s.dat", name); + FILE* fs; + + fopen_s(&fs, outputPath, "wb"); + if (fs) + { + fwrite(&header, sizeof(DataPackHeader), 1, 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(); +#if 0 // CGA resources { ofstream outputFile("src/DOS/CGAData.inc"); @@ -32,13 +82,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 +122,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 +158,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..027c0f5 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,152 @@ void GenerateDummyFont(ofstream& outputFile, const char* varName) outputFile << "\t" << "NULL" << endl; outputFile << "};" << endl << endl; } + +void EncodeFont(const char* basePath, const char* imageFilename, vector& outputStream, uint16_t& headerOffsetPosition) +{ + headerOffsetPosition = (uint16_t) outputStream.size(); + + 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() >= (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; + } + } + + // Output the metadata + // uint8_t glyphWidth[256 - 32]; + // uint8_t glyphWidthBytes; + // uint8_t glyphHeight; + // uint8_t glyphDataStride; + + const int numNeededGlyphs = 256 - 32; + + for (int n = 0; n < numNeededGlyphs; n++) + { + if (n < glyphWidths.size()) + { + outputStream.push_back(glyphWidths[n]); + } + else + { + outputStream.push_back(0); + } + } + + outputStream.push_back(requiredBytes); + outputStream.push_back(glyphHeight); + + int stride = requiredBytes * glyphHeight; + outputStream.push_back((uint8_t) stride); + + // Output the glyph data + for (int n = 0; n < offsets.size(); n++) + { + 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++; + } + + outputStream.push_back((uint8_t)mask); + } + } + } + +} \ No newline at end of file diff --git a/tools/MouseGen.cpp b/tools/MouseGen.cpp index ef95461..3178efe 100644 --- a/tools/MouseGen.cpp +++ b/tools/MouseGen.cpp @@ -183,3 +183,88 @@ 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, uint16_t& headerOffsetPosition) +{ + headerOffsetPosition = (uint16_t)(outputData.size()); + 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; + } + + 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] | (data[index + 1] << 8) | (data[index + 2] << 16); + + if (data[index + 3] == 0) + { + maskData.push_back(1); + colourData.push_back(0); + } + else + { + maskData.push_back(0); + colourData.push_back(col == 0 ? 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; + } + } + + int hotSpotX = 0; + int hotSpotY = 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)); +} From 4c4a7b6da3c5440416d04dd093f7ef5321032107 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sun, 20 Nov 2022 21:03:28 +0000 Subject: [PATCH 08/98] Added device indepedent 1bpp draw surface routines --- project/Windows/Windows.vcxproj | 3 + src/Draw/Surf1bpp.cpp | 349 ++++++++++++++++++++++++++++++++ src/Draw/Surf1bpp.h | 21 ++ src/Draw/Surface.h | 32 +++ src/Windows/WinVid.cpp | 136 ++++++++++--- src/Windows/WinVid.h | 5 + 6 files changed, 516 insertions(+), 30 deletions(-) create mode 100644 src/Draw/Surf1bpp.cpp create mode 100644 src/Draw/Surf1bpp.h create mode 100644 src/Draw/Surface.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 9cfe9b3..abd48e2 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -141,6 +141,7 @@ + @@ -165,6 +166,8 @@ + + diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp new file mode 100644 index 0000000..b483744 --- /dev/null +++ b/src/Draw/Surf1bpp.cpp @@ -0,0 +1,349 @@ +#include "Surf1bpp.h" +#include "../Font.h" + +DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; +} + +void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) +{ + 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 - count - 1; + } + if (count < 0) + { + return; + } + + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + + uint8_t data = *VRAMptr; + + if (colour) + { + 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++ = 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) +{ + 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 - 1 - y; + } + if (count <= 0) + { + return; + } + + uint8_t mask = (0x80 >> (x & 7)); + int index = x >> 3; + + 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) +{ + 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 - 1 - x; + } + if (y + height >= context.clipBottom) + { + height = context.clipBottom - 1 - y; + } + if (width <= 0 || height <= 0) + { + return; + } + + 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; + workX++; + mask = (mask >> 1) | 0x80; + if ((workX & 7) == 0) + { + *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) +{ + 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; + } + + 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); + + 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++; + } + + 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)); + } + + 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++; + } + + 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)); + } + + outY++; + VRAMptr = lines[outY] + (x >> 3); + } + } + + x += glyphWidth; + if (style & FontStyle::Bold) + { + x++; + } + + if (x >= context.clipRight) + { + break; + } + } + + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + { + HLine(context, startX, y - firstLine + font->glyphHeight - 1, x - startX, colour); + } + +} + +void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + +} diff --git a/src/Draw/Surf1bpp.h b/src/Draw/Surf1bpp.h new file mode 100644 index 0000000..9adf0f2 --- /dev/null +++ b/src/Draw/Surf1bpp.h @@ -0,0 +1,21 @@ +#ifndef _SURF1BPP_H_ +#define _SURF1BPP_H_ + +#include +#include "Surface.h" + +class DrawSurface_1BPP : public DrawSurface +{ +public: + DrawSurface_1BPP(int inWidth, int inHeight); + + 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); + + uint8_t** lines; +}; + +#endif diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h new file mode 100644 index 0000000..bbd2ff9 --- /dev/null +++ b/src/Draw/Surface.h @@ -0,0 +1,32 @@ +#ifndef _SURFACE_H_ +#define _SURFACE_H_ + +#include "../Font.h" +struct Font; +class Image; + +struct DrawContext +{ + DrawContext() {} + DrawContext(int inClipLeft, int inClipTop, int inClipRight, int inClipBottom) + : clipLeft(inClipLeft), clipTop(inClipTop), clipRight(inClipRight), clipBottom(inClipBottom) + { + + } + int clipLeft, clipTop, clipRight, clipBottom; +}; + +class DrawSurface +{ +public: + DrawSurface(int inWidth, int inHeight) : width(inWidth), height(inHeight) {} + 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 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; + + int width, height; +}; + +#endif diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 6ff7778..fdbf7a4 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -19,13 +19,14 @@ //#include "../DOS/CGAData.inc" #include "../Interface.h" #include "../DataPack.h" +#include "../Draw/Surf1bpp.h" #define WINDOW_TOP 24 #define WINDOW_HEIGHT 168 #define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) #define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT 200 +#define SCREEN_HEIGHT 480 #define NAVIGATION_BUTTON_WIDTH 24 #define NAVIGATION_BUTTON_HEIGHT 12 @@ -48,8 +49,8 @@ extern HWND hWnd; WindowsVideoDriver::WindowsVideoDriver() { - screenWidth = 640; - screenHeight = 480; + screenWidth = SCREEN_WIDTH; + screenHeight = SCREEN_HEIGHT; windowWidth = screenWidth - 16; windowHeight = WINDOW_HEIGHT; windowX = 0; @@ -79,19 +80,51 @@ void WindowsVideoDriver::Init() 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; + bitmapInfo = (BITMAPINFO*) malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 2); + ZeroMemory(bitmapInfo, sizeof(BITMAPINFO)); + bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo->bmiHeader.biWidth = screenWidth; + bitmapInfo->bmiHeader.biHeight = screenHeight * verticalScale; + bitmapInfo->bmiHeader.biPlanes = 1; +// bitmapInfo->bmiHeader.biBitCount = 32; + bitmapInfo->bmiHeader.biBitCount = 1; + bitmapInfo->bmiHeader.biCompression = BI_RGB; + bitmapInfo->bmiHeader.biClrUsed = 0; + + bitmapInfo->bmiColors[0].rgbRed = 0; + bitmapInfo->bmiColors[0].rgbGreen = 0; + bitmapInfo->bmiColors[0].rgbBlue = 0; + bitmapInfo->bmiColors[1].rgbRed = 0xff; + bitmapInfo->bmiColors[1].rgbGreen = 0xff; + bitmapInfo->bmiColors[1].rgbBlue = 0xff; + + screenBitmap = CreateDIBSection(hDCMem, bitmapInfo, DIB_RGB_COLORS, (VOID**)&lpBitmapBits, NULL, 0); - screenBitmap = CreateDIBSection(hDCMem, &bi, DIB_RGB_COLORS, (VOID**)&lpBitmapBits, NULL, 0); + { + DrawSurface_1BPP* surface = new DrawSurface_1BPP(screenWidth, screenHeight); + uint8_t* buffer = (uint8_t*)(lpBitmapBits); + for (int y = 0; y < screenHeight; y++) + { + int bufferY = (screenHeight - 1 - y); + surface->lines[y] = &buffer[bufferY * (screenWidth / 8)]; + } + drawSurface = surface; + } - for (int n = 0; n < screenWidth * screenHeight * verticalScale; n++) + if (bitmapInfo->bmiHeader.biBitCount == 1) { - lpBitmapBits[n] = backgroundColour; + uint8_t* ptr = (uint8_t*)(lpBitmapBits); + for (int n = 0; n < screenWidth * screenHeight / 8; n++) + { + ptr[n] = 0xff; + } + } + else + { + for (int n = 0; n < screenWidth * screenHeight * verticalScale; n++) + { + lpBitmapBits[n] = backgroundColour; + } } } @@ -108,22 +141,45 @@ void WindowsVideoDriver::ClearScreen() void WindowsVideoDriver::FillRect(int x, int y, int width, int height) { - FillRect(x, y, width, height, foregroundColour); + DrawContext context(0, 0, screenWidth, screenHeight); + drawSurface->FillRect(context, x, y, width, height, 0); + + // FillRect(x, y, width, height, foregroundColour); } void WindowsVideoDriver::FillRect(int x, int y, int width, int height, uint32_t colour) { - for (int j = 0; j < height; j++) - { - for (int i = 0; i < width; i++) - { - SetPixel(x + i, y + j, colour); - } - } + DrawContext context(0, 0, screenWidth, screenHeight); + drawSurface->FillRect(context, x, y, width, height, colour ? 1 : 0); + +// for (int j = 0; j < height; j++) +// { +// for (int i = 0; i < width; i++) +// { +// SetPixel(x + i, y + j, colour); +// } +// } } void WindowsVideoDriver::SetPixel(int x, int y, uint32_t colour) { + 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)); + + if (colour) + { + ptr[index] |= mask; + } + else + { + ptr[index] &= ~mask; + } + } + /* if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) { int line = ((screenHeight - y - 1) * verticalScale); @@ -132,18 +188,29 @@ void WindowsVideoDriver::SetPixel(int x, int y, uint32_t colour) lpBitmapBits[(line + j) * screenWidth + x] = colour; } } + */ } void WindowsVideoDriver::InvertPixel(int x, int y, uint32_t colour) { 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)); + + ptr[index] ^= mask; + } + + /*if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) { int line = ((screenHeight - y - 1) * verticalScale); for (int j = 0; j < verticalScale; j++) { lpBitmapBits[(line + j) * screenWidth + x] ^= colour; } - } + }*/ } void WindowsVideoDriver::ClearWindow() @@ -177,6 +244,11 @@ void WindowsVideoDriver::DrawString(const char* text, int x, int y, int size, Fo // printf("%s\n", text); Font* font = GetFont(size, style); + DrawContext context(0, 0, screenWidth, screenHeight); + drawSurface->DrawString(context, font, text, x, y, 0, style); + return; + + int startX = x; uint8_t glyphHeight = font->glyphHeight; if (x >= scissorX2) @@ -271,18 +343,22 @@ void WindowsVideoDriver::DrawString(const char* text, int x, int y, int size, Fo void WindowsVideoDriver::HLine(int x, int y, int count) { - for (int n = 0; n < count; n++) - { - SetPixel(x + n, y, foregroundColour); - } + DrawContext context(0, 0, screenWidth, screenHeight); + drawSurface->HLine(context, x, y, count, 0); + //for (int n = 0; n < count; n++) + //{ + // SetPixel(x + n, y, foregroundColour); + //} } void WindowsVideoDriver::VLine(int x, int y, int count) { - for (int n = 0; n < count; n++) - { - SetPixel(x, y + n, foregroundColour); - } + DrawContext context(0, 0, screenWidth, screenHeight); + drawSurface->VLine(context, x, y, count, 0); + //for (int n = 0; n < count; n++) + //{ + // SetPixel(x, y + n, foregroundColour); + //} } void WindowsVideoDriver::DrawScrollBar(int position, int size) diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index ef82b54..120a4ae 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -17,6 +17,8 @@ #include "../Platform.h" #include +class DrawSurface; + class WindowsVideoDriver : public VideoDriver { public: @@ -58,9 +60,12 @@ class WindowsVideoDriver : public VideoDriver 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; + DrawSurface* drawSurface; + uint32_t foregroundColour, backgroundColour; int scissorX1, scissorY1, scissorX2, scissorY2; }; From 5cd3a7afd0ffdd2f0cc9f8848c4d8ed1d75683ff Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 24 Nov 2022 15:54:43 +0000 Subject: [PATCH 09/98] Interface and renderer refactor WIP --- assets/LowRes/image-icon.png | Bin 0 -> 557 bytes project/DOS/Makefile | 17 +- project/Windows/Windows.vcxproj | 6 + src/App.cpp | 5 +- src/App.h | 2 + src/DOS/CGA.cpp | 29 +- src/DOS/DOSInput.cpp | 3 +- src/DOS/DefData.inc | 1190 ++++++++++++++++--------------- src/DOS/EGA.cpp | 29 +- src/DOS/Hercules.cpp | 28 +- src/DOS/Platform.cpp | 2 +- src/DOS/TextMode.cpp | 5 +- src/DataPack.cpp | 45 ++ src/DataPack.h | 2 + src/Draw/Surf1bpp.cpp | 18 +- src/Draw/Surface.h | 9 +- src/Interface.cpp | 196 ++++- src/Interface.h | 30 + src/Layout.cpp | 4 +- src/Node.cpp | 64 +- src/Node.h | 13 +- src/Nodes/Block.cpp | 2 + src/Nodes/Break.cpp | 5 - src/Nodes/Break.h | 6 +- src/Nodes/Button.cpp | 85 +++ src/Nodes/Button.h | 24 + src/Nodes/Field.cpp | 71 ++ src/Nodes/Field.h | 25 + src/Nodes/ImgNode.cpp | 12 +- src/Nodes/ImgNode.h | 2 +- src/Nodes/LinkNode.cpp | 1 + src/Nodes/LinkNode.h | 2 - src/Nodes/Section.h | 3 +- src/Nodes/Text.cpp | 8 +- src/Nodes/Text.h | 2 +- src/Page.cpp | 19 +- src/Page.h | 3 +- src/Parser.cpp | 13 +- src/Platform.h | 3 + src/Render.cpp | 47 ++ src/Render.h | 30 + src/Renderer.cpp | 6 +- src/Tags.cpp | 13 +- src/Windows/WinVid.cpp | 35 +- src/Windows/WinVid.h | 2 - 45 files changed, 1429 insertions(+), 687 deletions(-) create mode 100644 assets/LowRes/image-icon.png create mode 100644 src/Nodes/Button.cpp create mode 100644 src/Nodes/Button.h create mode 100644 src/Nodes/Field.cpp create mode 100644 src/Nodes/Field.h create mode 100644 src/Render.cpp create mode 100644 src/Render.h diff --git a/assets/LowRes/image-icon.png b/assets/LowRes/image-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9126e12b814394b6eb78a5a2c6faed3ff0772a82 GIT binary patch literal 557 zcmV+|0@D47P)EX>4Tx04R}tkv&MmKp2MKrbf~bh2RIvyaN?V~-2a`+xph-iL z;^HW{799LptU9+0Yt2!cN#j!sUBE>hxmNufoIcO3Wd-uJ%TeFq5jGE>ct2|(2> zBN>Z|ne3_M{K$3L zaarNK#aS&^S@WL!g~6P*lHxk8VZ^Y6I1&&cqlyyBun?hLBgI6L&Z8dwA;+H}mrSk_ z7&#VDfeOj-ga5(r-kSNTNjE7N1v+1B`(p$M>;jFNZGRuzcH;!_KLb}<%U`Jjv!A5b zT3YxB=-UP^u3MVC2VCv|gHO6-NRH&ECFJwK`x$*x78tk%y4T#kHP3PS0Hmo`%QwKm zAuw8?>~)WKceVHJ- + + @@ -156,6 +158,7 @@ + @@ -172,6 +175,8 @@ + + @@ -183,6 +188,7 @@ + diff --git a/src/App.cpp b/src/App.cpp index fbf4a1b..d3c92c2 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -17,7 +17,7 @@ #include "Platform.h" App::App() - : page(*this), renderer(*this), parser(page), ui(*this) + : page(*this), renderer(*this), pageRenderer(*this), parser(page), ui(*this) { requestedNewPage = false; pageHistorySize = 0; @@ -43,6 +43,7 @@ void App::Run(int argc, char* argv[]) running = true; renderer.Init(); + ui.Init(); if (argc > 1) { @@ -59,7 +60,7 @@ void App::Run(int argc, char* argv[]) while (running) { Platform::Update(); - renderer.Update(); + //renderer.Update(); if (loadTask.HasContent()) { diff --git a/src/App.h b/src/App.h index c241f65..be3f808 100644 --- a/src/App.h +++ b/src/App.h @@ -21,6 +21,7 @@ #include "Renderer.h" #include "URL.h" #include "Interface.h" +#include "Render.h" #define MAX_PAGE_URL_HISTORY 5 @@ -73,6 +74,7 @@ class App Page page; Renderer renderer; + PageRenderer pageRenderer; HTMLParser parser; AppInterface ui; diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index e58619d..b81548a 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -20,11 +20,14 @@ #include #include "../Image.h" #include "CGA.h" -#include "CGAData.inc" +//#include "CGAData.inc" #include "../Interface.h" #include "DefData.h" +#include "../DataPack.h" +#include "../Draw/Surf1bpp.h" #define CGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) +#define CGA_PAGE2_VRAM_ADDRESS (uint8_t*) MK_FP(0xBA00, 0) #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 200 @@ -71,8 +74,10 @@ CGADriver::CGADriver() scissorY2 = SCREEN_HEIGHT; invertScreen = false; clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &CGA_ImageIcon; - bulletImage = &CGA_Bullet; + //imageIcon = &CGA_ImageIcon; + //bulletImage = &CGA_Bullet; + imageIcon = NULL; + bulletImage = NULL; isTextMode = false; } @@ -80,6 +85,16 @@ void CGADriver::Init() { startingScreenMode = GetScreenMode(); SetScreenMode(6); + + Assets.Load("CGA.DAT"); + + DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); + for (int y = 0; y < screenHeight; y += 2) + { + drawSurface1BPP->lines[y] = (CGA_BASE_VRAM_ADDRESS) + (40 * y); + drawSurface1BPP->lines[y + 1] = (CGA_PAGE2_VRAM_ADDRESS) + (40 * y); + } + drawSurface = drawSurface1BPP; } void CGADriver::Shutdown() @@ -322,6 +337,8 @@ void CGADriver::DrawString(const char* text, int x, int y, int size, FontStyle:: Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) { + return Assets.GetFont(fontSize, style); + /* if (style & FontStyle::Monospace) { switch (fontSize) @@ -347,7 +364,7 @@ Font* CGADriver::GetFont(int fontSize, FontStyle::Type style) return &CGA_LargeFont; default: return &CGA_RegularFont; - } + }*/ /* if (style & FontStyle::Monospace) { @@ -650,6 +667,8 @@ void CGADriver::VLine(int x, int y, int count) MouseCursorData* CGADriver::GetCursorGraphic(MouseCursor::Type type) { + return NULL; + /* switch (type) { default: @@ -659,7 +678,7 @@ MouseCursorData* CGADriver::GetCursorGraphic(MouseCursor::Type type) return &CGA_MouseCursorHand; case MouseCursor::TextSelect: return &CGA_MouseCursorTextSelect; - } + }*/ } int CGADriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index c4e4c81..37d333f 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() { @@ -92,7 +93,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); diff --git a/src/DOS/DefData.inc b/src/DOS/DefData.inc index c231128..abc0b51 100644 --- a/src/DOS/DefData.inc +++ b/src/DOS/DefData.inc @@ -4,1213 +4,1225 @@ // 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x7e, 0x00, 0x28, 0x00, 0x28, 0x00, 0xfc, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0x54, 0x00, 0x54, 0x00, 0x50, 0x00, 0x38, 0x00, 0x14, 0x00, 0x14, 0x00, 0x54, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x40, 0x48, 0x80, 0x31, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0xc0, 0x11, 0x20, 0x21, 0x20, 0x00, 0xc0, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x18, 0x00, 0x28, 0x00, 0x44, 0x00, 0x45, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 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, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, 0xa8, 0x00, 0x70, 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, 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, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 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, 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, 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, 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, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x14, 0x00, 0x14, 0x00, 0x24, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x34, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x18, 0x60, 0x20, 0x10, 0x20, 0x10, 0x43, 0xc8, 0x44, 0x48, 0x44, 0x48, 0x44, 0x88, 0x23, 0x70, 0x20, 0x00, 0x18, 0x30, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, // 'A' - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x14, 0x00, 0x14, 0x00, 0x22, 0x00, 0x22, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x80, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x41, 0x00, 0x7e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x00, 0x40, 0x00, 0x43, 0x80, 0x40, 0x80, 0x40, 0x80, 0x21, 0x80, 0x1e, 0x80, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x7f, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x60, 0x00, 0x50, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x41, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x60, 0xc0, 0x60, 0xc0, 0x51, 0x40, 0x51, 0x40, 0x4a, 0x40, 0x4a, 0x40, 0x44, 0x40, 0x44, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x60, 0x80, 0x50, 0x80, 0x50, 0x80, 0x48, 0x80, 0x44, 0x80, 0x42, 0x80, 0x42, 0x80, 0x41, 0x80, 0x40, 0x80, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x40, 0x80, 0x40, 0x80, 0x41, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x42, 0x80, 0x21, 0x00, 0x1e, 0x80, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x40, 0x80, 0x40, 0x80, 0x41, 0x00, 0x7f, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x41, 0x00, 0x40, 0x00, 0x38, 0x00, 0x06, 0x00, 0x01, 0x00, 0x41, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x08, 0x82, 0x08, 0x82, 0x08, 0x45, 0x10, 0x45, 0x10, 0x28, 0xa0, 0x28, 0xa0, 0x28, 0xa0, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x81, 0x00, 0x42, 0x00, 0x24, 0x00, 0x18, 0x00, 0x18, 0x00, 0x24, 0x00, 0x42, 0x00, 0x81, 0x00, 0x81, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x41, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x70, 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, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 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, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, 0x10, 0x00, 0x28, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, + 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, // '`' - 0x80, 0x00, 0x40, 0x00, 0x00, 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, 0x20, 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, 0x78, 0x00, 0x84, 0x00, 0x7c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x3e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3a, 0x00, 0x46, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0xe0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x46, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3c, 0x00, // 'h' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xb0, 0x00, 0xc8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x70, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x80, 0x66, 0x40, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x8c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x8c, 0x00, 0x74, 0x00, 0x04, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x46, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x38, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0xe0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x34, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x80, 0x88, 0x80, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x48, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x48, 0x00, 0x84, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x78, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x18, 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, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 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, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x18, 0x00, 0x20, 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, + 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // '' + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 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}, + { 3,3,5,7,7,12,9,3,4,4,5,7,3,4,3,4,7,7,7,7,7,7,7,7,7,7,3,3,7,7,7,7,14,9,9,9,10,9,8,10,10,3,7,8,7,11,10,10,9,10,10,9,9,10,9,13,8,9,8,4,4,4,7,7,4,8,8,7,8,8,3,8,7,3,3,7,3,11,7,8,8,8,4,7,3,7,7,9,6,7,6,5,3,5,9,4}, 2, // Byte width - 11, // Glyph height - 22, // Glyph stride + 16, // Glyph height + 32, // 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, 0x00, 0x00, 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, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // '"' - 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, 0x7c, 0x00, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x7c, 0x00, 0x28, 0x00, 0x28, 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, 0x10, 0x00, 0x38, 0x00, 0x54, 0x00, 0x50, 0x00, 0x30, 0x00, 0x18, 0x00, 0x14, 0x00, 0x54, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00, 0x00, // '%' - 0x00, 0x00, 0x62, 0x00, 0x94, 0x00, 0x68, 0x00, 0x10, 0x00, 0x2c, 0x00, 0x52, 0x00, 0x8c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x49, 0x00, 0x32, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x26, 0x00, 0x49, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // '&' - 0x00, 0x00, 0x40, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0x40, 0x00, 0xa8, 0x00, 0x90, 0x00, 0x68, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x50, 0x00, 0x20, 0x00, 0x20, 0x00, 0x54, 0x00, 0x48, 0x00, 0x48, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, // ''' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 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, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, // ')' - 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, // '*' - 0x00, 0x00, 0xa0, 0x00, 0x40, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x20, 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, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 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, 0x60, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // '/' - 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // '0' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // '1' - 0x00, 0x00, 0x20, 0x00, 0xe0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // '2' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, // '3' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // '4' - 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x50, 0x00, 0x90, 0x00, 0xf8, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x18, 0x00, 0x28, 0x00, 0x28, 0x00, 0x48, 0x00, 0x7c, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, // '5' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // '6' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // '7' - 0x00, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, // '8' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 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, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 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, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // '@' - 0x00, 0x00, 0x3f, 0x00, 0x40, 0x80, 0x8e, 0x40, 0x92, 0x40, 0x92, 0x40, 0x8d, 0x80, 0x40, 0x00, 0x3f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x47, 0x20, 0x49, 0x20, 0x49, 0x20, 0x46, 0xe0, 0x20, 0x00, 0x30, 0x00, 0x0f, 0x80, 0x00, 0x00, // 'A' - 0x00, 0x00, 0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0x44, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, // 'B' - 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, // 'C' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'D' - 0x00, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x44, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, // 'E' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'F' - 0x00, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 'G' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x80, 0x00, 0x9c, 0x00, 0x84, 0x00, 0x84, 0x00, 0x7c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x4e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, // 'H' - 0x00, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xfc, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // 'I' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 'J' - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, // 'K' - 0x00, 0x00, 0x88, 0x00, 0x90, 0x00, 0xa0, 0x00, 0xe0, 0x00, 0x90, 0x00, 0x88, 0x00, 0x84, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x60, 0x00, 0x60, 0x00, 0x50, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // 'L' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'M' - 0x00, 0x00, 0x82, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xaa, 0x00, 0xaa, 0x00, 0x92, 0x00, 0x92, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x63, 0x00, 0x55, 0x00, 0x55, 0x00, 0x49, 0x00, 0x49, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, // 'N' - 0x00, 0x00, 0x84, 0x00, 0xc4, 0x00, 0xa4, 0x00, 0xa4, 0x00, 0x94, 0x00, 0x8c, 0x00, 0x84, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x62, 0x00, 0x62, 0x00, 0x52, 0x00, 0x52, 0x00, 0x4a, 0x00, 0x46, 0x00, 0x46, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // 'O' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'P' - 0x00, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 'Q' - 0x00, 0x00, 0x78, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x94, 0x00, 0x78, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x4a, 0x00, 0x46, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x00, 0x00, // 'R' - 0x00, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0xf8, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // 'S' - 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x80, 0x00, 0x70, 0x00, 0x08, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x38, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // 'T' - 0x00, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 'U' - 0x00, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'V' - 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 'W' - 0x00, 0x00, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x44, 0x40, 0x2a, 0x80, 0x2a, 0x80, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x2a, 0x80, 0x2a, 0x80, 0x11, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, // 'X' - 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, // 'Y' - 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 'Z' - 0x00, 0x00, 0xf8, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, // '[' - 0x00, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, // '\' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 0x00, // '^' - 0x20, 0x00, 0x50, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x28, 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, 0xfc, 0x00, + 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, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 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, 0x70, 0x00, 0x08, 0x00, 0x78, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'b' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // 'd' - 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0xf8, 0x00, 0x80, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // 'f' - 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x78, 0x00, // 'h' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, // 'i' - 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 'j' - 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, // 'k' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x90, 0x00, 0xa0, 0x00, 0xe0, 0x00, 0x90, 0x00, 0x90, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x48, 0x00, 0x50, 0x00, 0x60, 0x00, 0x50, 0x00, 0x48, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, // 'l' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf0, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x40, 0x00, 0x40, 0x00, // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x78, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x04, 0x00, // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x80, 0x00, 0x60, 0x00, 0x10, 0x00, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x20, 0x00, 0x10, 0x00, 0x48, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, // 't' - 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 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, 0x78, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x50, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x00, 0x92, 0x00, 0xaa, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x49, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x90, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x20, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x10, 0x00, 0x60, 0x00, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, // '{' - 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, // '|' - 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, // '}' - 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, // '~' - 0x00, 0x00, 0x64, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // '' + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 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}, + { 3,3,4,6,6,8,6,2,3,3,4,6,3,3,3,3,6,6,6,6,6,6,6,6,6,6,3,3,6,6,6,6,11,7,7,7,8,7,6,8,8,3,5,7,6,9,8,8,7,8,8,7,7,8,7,11,7,7,7,3,3,3,6,6,3,6,6,6,6,6,3,6,6,2,2,6,2,8,6,6,6,6,3,5,3,6,6,8,5,5,5,4,2,4,6,3}, 2, // Byte width - 9, // Glyph height - 18, // Glyph stride + 13, // Glyph height + 26, // 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x7f, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0xfe, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1c, 0x00, 0x2a, 0x00, 0x49, 0x00, 0x48, 0x00, 0x28, 0x00, 0x1c, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x49, 0x00, 0x2a, 0x00, 0x1c, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x44, 0x10, 0x44, 0x20, 0x44, 0x40, 0x38, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x70, 0x08, 0x88, 0x10, 0x88, 0x20, 0x88, 0x40, 0x70, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x18, 0x00, 0x18, 0x00, 0x24, 0x00, 0x42, 0x40, 0x41, 0x40, 0x40, 0x80, 0x21, 0x40, 0x1e, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 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, 0x10, 0x00, 0x54, 0x00, 0x38, 0x00, 0x28, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x02, 0x00, 0x01, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x12, 0x00, 0x12, 0x00, 0x22, 0x00, 0x42, 0x00, 0x7f, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x01, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 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, 0x03, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x40, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x01, 0x00, 0x06, 0x00, 0x18, 0x00, 0x60, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x0c, 0x08, 0x10, 0x04, 0x21, 0xc2, 0x22, 0x22, 0x44, 0x22, 0x44, 0x22, 0x44, 0x64, 0x43, 0xb8, 0x20, 0x00, 0x18, 0x18, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x11, 0x00, 0x11, 0x00, 0x20, 0x80, 0x20, 0x80, 0x7f, 0xc0, 0x40, 0x40, 0x80, 0x20, 0x80, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x20, 0x20, 0x40, 0x30, 0xc0, 0x0f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0xc0, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0xc0, 0x3f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0x80, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0xc0, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0x80, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x00, 0x40, 0x00, 0x43, 0xe0, 0x40, 0x20, 0x40, 0x20, 0x20, 0x60, 0x30, 0xe0, 0x0f, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x3f, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x21, 0x00, 0x22, 0x00, 0x24, 0x00, 0x28, 0x00, 0x30, 0x00, 0x28, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x20, 0x80, 0x20, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x30, 0x60, 0x30, 0x60, 0x28, 0xa0, 0x28, 0xa0, 0x25, 0x20, 0x25, 0x20, 0x22, 0x20, 0x22, 0x20, 0x20, 0x20, 0x20, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x30, 0x80, 0x30, 0x80, 0x28, 0x80, 0x28, 0x80, 0x24, 0x80, 0x24, 0x80, 0x22, 0x80, 0x22, 0x80, 0x21, 0x80, 0x21, 0x80, 0x20, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x20, 0x40, 0x30, 0xc0, 0x0f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x42, 0x20, 0x21, 0x40, 0x30, 0xc0, 0x0f, 0x40, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x20, 0x80, 0x40, 0x40, 0x40, 0x00, 0x20, 0x00, 0x1f, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x20, 0x80, 0x1f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0x80, 0x0f, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x80, 0x20, 0x40, 0x40, 0x40, 0x40, 0x20, 0x80, 0x20, 0x80, 0x11, 0x00, 0x11, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x04, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x81, 0x02, 0x41, 0x04, 0x41, 0x04, 0x42, 0x84, 0x22, 0x88, 0x22, 0x88, 0x14, 0x50, 0x14, 0x50, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x40, 0x40, 0x20, 0x80, 0x11, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x11, 0x00, 0x20, 0x80, 0x40, 0x40, 0x80, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x40, 0x40, 0x20, 0x80, 0x11, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xff, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 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, 0x10, 0x00, 0x28, 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, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x01, 0x00, 0x01, 0x00, 0x3f, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x3e, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x62, 0x00, 0x5c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x20, 0x00, 0x20, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x3c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x70, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x41, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xc0, 0x65, 0x20, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x42, 0x00, 0x3c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x84, 0x20, 0x44, 0x40, 0x44, 0x40, 0x2a, 0x80, 0x2a, 0x80, 0x11, 0x00, 0x11, 0x00, 0x11, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x44, 0x00, 0x82, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7e, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 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, + 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // '' + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 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 + { 4,4,6,9,9,14,11,3,5,5,6,9,4,5,4,4,9,9,9,9,9,9,9,9,9,9,4,4,9,9,9,9,16,11,11,11,12,11,10,13,12,5,8,10,9,13,11,12,10,12,12,11,9,12,11,15,11,11,10,4,4,4,7,9,5,9,9,8,9,9,5,9,9,3,3,8,3,13,9,9,9,9,5,8,5,9,7,11,7,7,8,5,5,5,9,5}, + 2, // Byte width + 20, // Glyph height + 40, // 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3f, 0x80, 0x12, 0x00, 0x12, 0x00, 0x7f, 0x00, 0x24, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x08, 0x00, 0x08, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x80, 0x31, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x23, 0x00, 0x44, 0x80, 0x03, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x80, 0x43, 0x00, 0x43, 0x00, 0x3c, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 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, // '(' - 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 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, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 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, 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, 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, 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, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 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, 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, 0x00, 0x00, 0x0c, 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, 0x0c, 0x00, 0x0c, 0x00, 0x00, 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, + 0x00, 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, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x3f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x12, 0x00, 0x22, 0x00, 0x3f, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x21, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x00, 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, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 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, 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, // '>' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x4e, 0x80, 0x52, 0x80, 0x52, 0x80, 0x52, 0x80, 0x4d, 0x00, 0x40, 0x00, 0x20, 0x00, 0x1f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x14, 0x00, 0x14, 0x00, 0x22, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x7e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x22, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x22, 0x00, 0x7c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x21, 0x00, 0x20, 0x00, 0x24, 0x00, 0x3c, 0x00, 0x24, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x21, 0x00, 0x20, 0x00, 0x24, 0x00, 0x3c, 0x00, 0x24, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x40, 0x00, 0x47, 0x80, 0x41, 0x00, 0x41, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x24, 0x00, 0x24, 0x00, 0x28, 0x00, 0x38, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x41, 0x00, 0x63, 0x00, 0x63, 0x00, 0x55, 0x00, 0x55, 0x00, 0x49, 0x00, 0x49, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0x41, 0x00, 0x61, 0x00, 0x51, 0x00, 0x51, 0x00, 0x49, 0x00, 0x45, 0x00, 0x45, 0x00, 0x43, 0x00, 0xf1, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x33, 0x00, 0x00, 0x00, // 'R' - 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x43, 0x00, 0x41, 0x00, 0x40, 0x00, 0x38, 0x00, 0x06, 0x00, 0x01, 0x00, 0x41, 0x00, 0x61, 0x00, 0x5e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x88, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x80, 0x80, 0x88, 0x80, 0x88, 0x80, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0e, 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, 0x00, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1c, 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, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, // '`' - 0x10, 0x00, 0x08, 0x00, 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, 0x08, 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, 0x3c, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x2c, 0x00, 0x33, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x33, 0x00, 0x6c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x63, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1b, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x63, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x42, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x27, 0x00, 0x24, 0x00, 0x28, 0x00, 0x38, 0x00, 0x24, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x6d, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0xed, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x32, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x63, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x33, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x33, 0x00, 0x2c, 0x00, 0x20, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1a, 0x00, 0x02, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x18, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x40, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x26, 0x00, 0x1b, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x80, 0x49, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x42, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 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, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 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, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, // '~' - 0x00, 0x00, 0x31, 0x00, 0x49, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // '' + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 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}, + { 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,9}, 2, // Byte width - 11, // Glyph height - 22, // Glyph stride + 16, // Glyph height + 32, // Glyph stride Default_RegularFont_Monospace_Data }; static unsigned char Default_SmallFont_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, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, // '"' - 0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // '#' - 0x00, 0x0a, 0x0a, 0x3f, 0x14, 0x7e, 0x28, 0x28, 0x00, + 0x00, 0x00, 0x0a, 0x0a, 0x3f, 0x14, 0x14, 0x14, 0x7e, 0x28, 0x28, 0x00, 0x00, // '$' - 0x00, 0x08, 0x1e, 0x20, 0x1c, 0x02, 0x3c, 0x08, 0x00, + 0x00, 0x00, 0x08, 0x1c, 0x22, 0x20, 0x1c, 0x02, 0x22, 0x1c, 0x08, 0x00, 0x00, // '%' - 0x00, 0x21, 0x52, 0x24, 0x08, 0x12, 0x25, 0x42, 0x00, + 0x00, 0x00, 0x20, 0x51, 0x22, 0x04, 0x08, 0x10, 0x22, 0x45, 0x02, 0x00, 0x00, // '&' - 0x00, 0x18, 0x20, 0x10, 0x29, 0x46, 0x44, 0x3b, 0x00, + 0x00, 0x00, 0x18, 0x20, 0x20, 0x10, 0x30, 0x49, 0x4a, 0x44, 0x3b, 0x00, 0x00, // ''' - 0x00, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // '(' - 0x00, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10, 0x08, 0x04, + 0x00, 0x00, 0x04, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, // ')' - 0x00, 0x10, 0x08, 0x04, 0x04, 0x04, 0x04, 0x08, 0x10, + 0x00, 0x00, 0x10, 0x08, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08, 0x10, // '*' - 0x00, 0x00, 0x36, 0x1c, 0x7f, 0x1c, 0x36, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x36, 0x1c, 0x7f, 0x1c, 0x36, 0x00, 0x00, 0x00, 0x00, // '+' - 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x7f, 0x08, 0x08, 0x08, 0x00, 0x00, // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, // '-' - 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, // '/' - 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, // '0' - 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, // '1' - 0x00, 0x08, 0x38, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, + 0x00, 0x00, 0x08, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // '2' - 0x00, 0x1c, 0x22, 0x02, 0x04, 0x08, 0x10, 0x3e, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, 0x00, 0x00, // '3' - 0x00, 0x1c, 0x22, 0x02, 0x0c, 0x02, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x0c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00, // '4' - 0x00, 0x04, 0x0c, 0x14, 0x24, 0x3e, 0x04, 0x0e, 0x00, + 0x00, 0x00, 0x04, 0x0c, 0x0c, 0x14, 0x14, 0x24, 0x3e, 0x04, 0x0e, 0x00, 0x00, // '5' - 0x00, 0x3e, 0x20, 0x20, 0x3c, 0x02, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x3e, 0x20, 0x20, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00, // '6' - 0x00, 0x0c, 0x10, 0x20, 0x3c, 0x22, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x0c, 0x10, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, // '7' - 0x00, 0x3e, 0x22, 0x04, 0x08, 0x08, 0x10, 0x10, 0x00, + 0x00, 0x00, 0x3e, 0x22, 0x02, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x00, 0x00, // '8' - 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, // '9' - 0x00, 0x1c, 0x22, 0x22, 0x1e, 0x02, 0x04, 0x18, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x04, 0x18, 0x00, 0x00, // ':' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, // ';' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x08, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, // '<' - 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, // '=' - 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, // '>' - 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, // '?' - 0x00, 0x1c, 0x22, 0x02, 0x04, 0x08, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, // '@' - 0x00, 0x3e, 0x41, 0x4d, 0x55, 0x5e, 0x40, 0x3c, 0x00, + 0x00, 0x00, 0x1e, 0x21, 0x4d, 0x55, 0x55, 0x55, 0x4e, 0x20, 0x1c, 0x00, 0x00, // 'A' - 0x00, 0x38, 0x08, 0x14, 0x14, 0x3e, 0x22, 0x77, 0x00, + 0x00, 0x00, 0x18, 0x08, 0x08, 0x14, 0x14, 0x22, 0x3e, 0x22, 0x77, 0x00, 0x00, // 'B' - 0x00, 0x7e, 0x21, 0x21, 0x3e, 0x21, 0x21, 0x7e, 0x00, + 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x21, 0x21, 0x21, 0x7e, 0x00, 0x00, // 'C' - 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x21, 0x1e, 0x00, + 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x40, 0x40, 0x21, 0x1e, 0x00, 0x00, // 'D' - 0x00, 0x7c, 0x22, 0x21, 0x21, 0x21, 0x22, 0x7c, 0x00, + 0x00, 0x00, 0x7c, 0x22, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x7c, 0x00, 0x00, // 'E' - 0x00, 0x7f, 0x21, 0x20, 0x3c, 0x20, 0x21, 0x7f, 0x00, + 0x00, 0x00, 0x7f, 0x21, 0x20, 0x24, 0x3c, 0x24, 0x20, 0x21, 0x7f, 0x00, 0x00, // 'F' - 0x00, 0x7f, 0x21, 0x20, 0x3c, 0x20, 0x20, 0x78, 0x00, + 0x00, 0x00, 0x7f, 0x21, 0x20, 0x24, 0x3c, 0x24, 0x20, 0x20, 0x78, 0x00, 0x00, // 'G' - 0x00, 0x1e, 0x21, 0x40, 0x40, 0x47, 0x21, 0x1e, 0x00, + 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x47, 0x41, 0x21, 0x1e, 0x00, 0x00, // 'H' - 0x00, 0x77, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x77, 0x00, + 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, // 'I' - 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, + 0x00, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // 'J' - 0x00, 0x1e, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, + 0x00, 0x00, 0x1e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, 0x00, // 'K' - 0x00, 0x73, 0x22, 0x24, 0x38, 0x24, 0x22, 0x73, 0x00, + 0x00, 0x00, 0x73, 0x22, 0x24, 0x24, 0x28, 0x38, 0x24, 0x22, 0x73, 0x00, 0x00, // 'L' - 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x11, 0x7f, 0x00, + 0x00, 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x7f, 0x00, 0x00, // 'M' - 0x00, 0x63, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x77, 0x00, + 0x00, 0x00, 0x63, 0x22, 0x36, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x77, 0x00, 0x00, // 'N' - 0x00, 0x67, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x72, 0x00, + 0x00, 0x00, 0x67, 0x22, 0x32, 0x32, 0x2a, 0x26, 0x26, 0x22, 0x72, 0x00, 0x00, // 'O' - 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x00, 0x00, // 'P' - 0x00, 0x7e, 0x21, 0x21, 0x3e, 0x20, 0x20, 0x78, 0x00, + 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x20, 0x20, 0x20, 0x78, 0x00, 0x00, // 'Q' - 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x1b, + 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x1b, 0x00, // 'R' - 0x00, 0x7e, 0x21, 0x21, 0x3e, 0x24, 0x22, 0x73, 0x00, + 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x24, 0x24, 0x22, 0x73, 0x00, 0x00, // 'S' - 0x00, 0x3e, 0x41, 0x40, 0x3e, 0x01, 0x41, 0x3e, 0x00, + 0x00, 0x00, 0x3e, 0x41, 0x40, 0x40, 0x3e, 0x01, 0x01, 0x41, 0x3e, 0x00, 0x00, // 'T' - 0x00, 0x7f, 0x49, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, + 0x00, 0x00, 0x7f, 0x49, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, // 'U' - 0x00, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, + 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, // 'V' - 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x00, + 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x14, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, // 'W' - 0x00, 0x77, 0x22, 0x22, 0x2a, 0x2a, 0x14, 0x14, 0x00, + 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x2a, 0x14, 0x14, 0x00, 0x00, // 'X' - 0x00, 0x77, 0x22, 0x14, 0x08, 0x14, 0x22, 0x77, 0x00, + 0x00, 0x00, 0x77, 0x22, 0x14, 0x14, 0x08, 0x14, 0x14, 0x22, 0x77, 0x00, 0x00, // 'Y' - 0x00, 0x77, 0x22, 0x14, 0x08, 0x08, 0x08, 0x1c, 0x00, + 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, // 'Z' - 0x00, 0x7f, 0x42, 0x04, 0x08, 0x10, 0x21, 0x7f, 0x00, + 0x00, 0x00, 0x7f, 0x42, 0x04, 0x04, 0x08, 0x10, 0x10, 0x21, 0x7f, 0x00, 0x00, // '[' - 0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1c, + 0x00, 0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1c, // '\' - 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, // ']' - 0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1c, + 0x00, 0x00, 0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1c, // '^' - 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, // '`' - 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 'a' - 0x00, 0x00, 0x00, 0x3c, 0x02, 0x3e, 0x42, 0x3d, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x02, 0x3e, 0x42, 0x42, 0x3d, 0x00, 0x00, // 'b' - 0x00, 0x60, 0x20, 0x3e, 0x21, 0x21, 0x21, 0x7e, 0x00, + 0x00, 0x00, 0x60, 0x20, 0x20, 0x3e, 0x21, 0x21, 0x21, 0x21, 0x7e, 0x00, 0x00, // 'c' - 0x00, 0x00, 0x00, 0x3e, 0x41, 0x40, 0x41, 0x3e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x40, 0x40, 0x41, 0x3e, 0x00, 0x00, // 'd' - 0x00, 0x06, 0x02, 0x3e, 0x42, 0x42, 0x42, 0x3f, 0x00, + 0x00, 0x00, 0x06, 0x02, 0x02, 0x3e, 0x42, 0x42, 0x42, 0x42, 0x3f, 0x00, 0x00, // 'e' - 0x00, 0x00, 0x00, 0x3e, 0x41, 0x7f, 0x40, 0x3e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x7f, 0x40, 0x41, 0x3e, 0x00, 0x00, // 'f' - 0x00, 0x0c, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x3c, 0x00, + 0x00, 0x00, 0x0c, 0x10, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x10, 0x3c, 0x00, 0x00, // 'g' - 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x3e, 0x02, 0x3c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x42, 0x3e, 0x02, 0x02, 0x3c, // 'h' - 0x00, 0x60, 0x20, 0x2c, 0x32, 0x22, 0x22, 0x77, 0x00, + 0x00, 0x00, 0x60, 0x20, 0x20, 0x2c, 0x32, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, // 'i' - 0x00, 0x08, 0x00, 0x38, 0x08, 0x08, 0x08, 0x3e, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // 'j' - 0x00, 0x04, 0x00, 0x3c, 0x04, 0x04, 0x04, 0x04, 0x38, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x3c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x38, // 'k' - 0x00, 0x60, 0x20, 0x26, 0x24, 0x38, 0x24, 0x63, 0x00, + 0x00, 0x00, 0x60, 0x20, 0x20, 0x26, 0x24, 0x28, 0x38, 0x24, 0x63, 0x00, 0x00, // 'l' - 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, + 0x00, 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, // 'm' - 0x00, 0x00, 0x00, 0x74, 0x2a, 0x2a, 0x2a, 0x6b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x2a, 0x2a, 0x2a, 0x2a, 0x6b, 0x00, 0x00, // 'n' - 0x00, 0x00, 0x00, 0x6c, 0x32, 0x22, 0x22, 0x77, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x32, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, // 'o' - 0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x41, 0x3e, 0x00, 0x00, // 'p' - 0x00, 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x21, 0x3e, 0x20, 0x70, // 'q' - 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x42, 0x3e, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x42, 0x42, 0x3e, 0x02, 0x07, // 'r' - 0x00, 0x00, 0x00, 0x76, 0x19, 0x10, 0x10, 0x7c, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x19, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, // 's' - 0x00, 0x00, 0x00, 0x3f, 0x40, 0x3e, 0x01, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x38, 0x06, 0x41, 0x3e, 0x00, 0x00, // 't' - 0x00, 0x00, 0x10, 0x3c, 0x10, 0x10, 0x12, 0x0c, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x12, 0x0c, 0x00, 0x00, // 'u' - 0x00, 0x00, 0x00, 0x66, 0x22, 0x22, 0x26, 0x1b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x22, 0x22, 0x22, 0x26, 0x1b, 0x00, 0x00, // 'v' - 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00, 0x00, // 'w' - 0x00, 0x00, 0x00, 0x77, 0x22, 0x2a, 0x14, 0x14, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x2a, 0x2a, 0x14, 0x14, 0x00, 0x00, // 'x' - 0x00, 0x00, 0x00, 0x36, 0x14, 0x08, 0x14, 0x36, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x1c, 0x1c, 0x22, 0x77, 0x00, 0x00, // 'y' - 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x08, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x30, // 'z' - 0x00, 0x00, 0x00, 0x7e, 0x44, 0x18, 0x22, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x44, 0x08, 0x10, 0x22, 0x7e, 0x00, 0x00, // '{' - 0x00, 0x06, 0x08, 0x08, 0x30, 0x08, 0x08, 0x06, 0x00, + 0x00, 0x00, 0x06, 0x08, 0x08, 0x08, 0x08, 0x30, 0x08, 0x08, 0x08, 0x08, 0x06, // '|' - 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // '}' - 0x00, 0x30, 0x08, 0x08, 0x06, 0x08, 0x08, 0x30, 0x00, + 0x00, 0x00, 0x30, 0x08, 0x08, 0x08, 0x08, 0x06, 0x08, 0x08, 0x08, 0x08, 0x30, // '~' - 0x00, 0x31, 0x49, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x31, 0x49, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // '' + 0x00, 0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 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}, + { 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,8}, 1, // Byte width - 9, // Glyph height - 9, // Glyph stride + 13, // Glyph height + 13, // 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x1f, 0xe0, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x3f, 0xc0, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x00, 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, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x40, 0x10, 0xc0, 0x10, 0x40, 0x10, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x80, 0x00, 0x40, 0x10, 0x40, 0x18, 0x40, 0x17, 0x80, 0x02, 0x00, 0x02, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x20, 0x44, 0x40, 0x44, 0x80, 0x39, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0xe0, 0x09, 0x10, 0x11, 0x10, 0x21, 0x10, 0x00, 0xe0, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x10, 0x80, 0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x14, 0x00, 0x12, 0x00, 0x21, 0x20, 0x20, 0xa0, 0x20, 0x40, 0x20, 0xa0, 0x1f, 0x10, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x02, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 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, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 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, 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, // '+' - 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, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3f, 0xe0, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, 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, 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, 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, + 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, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x1a, 0x00, 0x02, 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, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x40, 0x10, 0x40, 0x1f, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x10, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x10, 0x80, 0x0f, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x02, 0x80, 0x04, 0x80, 0x04, 0x80, 0x08, 0x80, 0x10, 0x80, 0x1f, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x03, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x1f, 0x00, 0x10, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x10, 0x80, 0x0f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x17, 0x00, 0x18, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x10, 0x40, 0x10, 0x40, 0x00, 0x80, 0x00, 0x80, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 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, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0xc0, 0x07, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x03, 0x00, 0x1c, 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, 0x02, 0x00, 0x02, 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, 0x02, 0x00, 0x02, 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, 0x02, 0x00, 0x02, 0x00, 0x04, 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, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 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, 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, 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, // '?' - 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, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x10, 0x40, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x20, 0x20, 0x23, 0xe0, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x23, 0xe0, 0x20, 0x00, 0x10, 0x00, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x80, 0x08, 0x80, 0x10, 0x40, 0x1f, 0xc0, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x40, 0x1f, 0xc0, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x7f, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd0, 0x18, 0x30, 0x10, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x10, 0x18, 0x30, 0x07, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x60, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x10, 0x60, 0x7f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x00, 0x10, 0x80, 0x1f, 0x80, 0x10, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x10, 0x20, 0x7f, 0xe0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x08, 0x10, 0x08, 0x10, 0x08, 0x00, 0x08, 0x40, 0x0f, 0xc0, 0x08, 0x40, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x30, 0x60, 0x20, 0x20, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x41, 0xf0, 0x40, 0x20, 0x40, 0x20, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x02, 0x00, 0x02, 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, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x20, 0x80, 0x20, 0x80, 0x1f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0xe0, 0x20, 0x80, 0x21, 0x00, 0x22, 0x00, 0x24, 0x00, 0x28, 0x00, 0x34, 0x00, 0x22, 0x00, 0x21, 0x00, 0x20, 0x80, 0x20, 0x40, 0x70, 0x30, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x10, 0x20, 0x7f, 0xe0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x30, 0x60, 0x30, 0x60, 0x28, 0xa0, 0x28, 0xa0, 0x25, 0x20, 0x25, 0x20, 0x22, 0x20, 0x22, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xf0, 0x30, 0x20, 0x30, 0x20, 0x28, 0x20, 0x24, 0x20, 0x22, 0x20, 0x22, 0x20, 0x21, 0x20, 0x20, 0xa0, 0x20, 0x60, 0x20, 0x60, 0x78, 0x20, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x40, 0x1f, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x08, 0x00, 0x1c, 0x40, 0x23, 0x80, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x40, 0x1f, 0x80, 0x11, 0x00, 0x10, 0x80, 0x10, 0x40, 0x10, 0x20, 0x7c, 0x10, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x10, 0x60, 0x20, 0x20, 0x20, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x01, 0x80, 0x00, 0x40, 0x00, 0x20, 0x20, 0x20, 0x30, 0x40, 0x2f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf0, 0x42, 0x10, 0x42, 0x10, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x40, 0x10, 0x40, 0x10, 0x42, 0x10, 0x42, 0x10, 0x25, 0x20, 0x25, 0x20, 0x28, 0xa0, 0x28, 0xa0, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xf0, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x80, 0x10, 0x40, 0x20, 0x20, 0x78, 0xf0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x80, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x20, 0x20, 0x20, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 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, 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, 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, + 0x00, 0x00, 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, 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, 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, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x1f, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x1f, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x07, 0xc0, 0x18, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x1f, 0xa0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x18, 0x40, 0x37, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xa0, 0x08, 0x60, 0x10, 0x20, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x08, 0x20, 0x07, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x0f, 0x40, 0x10, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0xc0, 0x0f, 0x60, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x20, 0x00, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x60, 0x10, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0xc0, 0x0f, 0x40, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0xe0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1f, 0xc0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x11, 0xe0, 0x10, 0x80, 0x11, 0x00, 0x12, 0x00, 0x16, 0x00, 0x19, 0x00, 0x10, 0x80, 0x10, 0x40, 0x30, 0x30, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 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, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xc0, 0x33, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x73, 0x30, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0xe0, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x80, 0x18, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x18, 0x40, 0x17, 0x80, 0x10, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x60, 0x10, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0xc0, 0x0f, 0x40, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xc0, 0x0c, 0x20, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3f, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x10, 0x60, 0x20, 0x20, 0x10, 0x00, 0x0f, 0x80, 0x00, 0x40, 0x20, 0x20, 0x30, 0x40, 0x2f, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1f, 0xc0, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x40, 0x04, 0x40, 0x03, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0xc0, 0x0f, 0x60, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x22, 0x20, 0x22, 0x20, 0x15, 0x40, 0x15, 0x40, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x00, 0x00, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 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, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1e, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x02, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x80, 0x02, 0x00, 0x02, 0x00, 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, + 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // '' + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 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}, + { 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,12}, 2, // Byte width - 14, // Glyph height - 28, // Glyph stride + 20, // Glyph height + 40, // Glyph stride Default_LargeFont_Monospace_Data }; diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp index 80c46a4..6fb05d3 100644 --- a/src/DOS/EGA.cpp +++ b/src/DOS/EGA.cpp @@ -20,8 +20,9 @@ #include #include "../Image.h" #include "EGA.h" -#include "DefData.h" +#include "../DataPack.h" #include "../Interface.h" +#include "../Draw/Surf1bpp.h" #define EGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) @@ -72,8 +73,13 @@ void EGADriver::SetupVars(int inScreenMode, int inScreenHeight) scissorY2 = SCREEN_HEIGHT; invertScreen = false; clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; + + //imageIcon = &Default_ImageIcon; + //bulletImage = &Default_Bullet; + + imageIcon = NULL; + bulletImage = NULL; + isTextMode = false; } @@ -81,6 +87,15 @@ void EGADriver::Init() { startingScreenMode = GetScreenMode(); SetScreenMode(screenModeToUse); + + Assets.Load("DEFAULT.DAT"); + + DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); + for (int y = 0; y < screenHeight; y++) + { + drawSurface1BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(80 * y); + } + drawSurface = drawSurface1BPP; } void EGADriver::Shutdown() @@ -292,6 +307,8 @@ void EGADriver::DrawString(const char* text, int x, int y, int size, FontStyle:: Font* EGADriver::GetFont(int fontSize, FontStyle::Type style) { + return Assets.GetFont(fontSize, style); + /* if (style & FontStyle::Monospace) { switch (fontSize) @@ -318,6 +335,7 @@ Font* EGADriver::GetFont(int fontSize, FontStyle::Type style) default: return &Default_RegularFont; } + */ } void EGADriver::HLine(int x, int y, int count) @@ -544,7 +562,8 @@ void EGADriver::VLine(int x, int y, int count) MouseCursorData* EGADriver::GetCursorGraphic(MouseCursor::Type type) { - switch (type) + return Assets.GetMouseCursorData(type); +/* switch (type) { default: case MouseCursor::Pointer: @@ -553,7 +572,7 @@ MouseCursorData* EGADriver::GetCursorGraphic(MouseCursor::Type type) return &Default_MouseCursorHand; case MouseCursor::TextSelect: return &Default_MouseCursorTextSelect; - } + }*/ } int EGADriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) diff --git a/src/DOS/Hercules.cpp b/src/DOS/Hercules.cpp index f136d3a..6d1cdef 100644 --- a/src/DOS/Hercules.cpp +++ b/src/DOS/Hercules.cpp @@ -20,8 +20,10 @@ #include #include "../Image.h" #include "Hercules.h" -#include "DefData.h" +//#include "DefData.h" +#include "../DataPack.h" #include "../Interface.h" +#include "../Draw/Surf1bpp.h" #define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) @@ -78,8 +80,9 @@ HerculesDriver::HerculesDriver() scissorY2 = SCREEN_HEIGHT; invertScreen = false; clearMask = invertScreen ? 0 : 0xffff; - imageIcon = &Default_ImageIcon; - bulletImage = &Default_Bullet; + //imageIcon = &Default_ImageIcon; + //bulletImage = &Default_Bullet; + imageIcon = bulletImage = NULL; isTextMode = false; } @@ -93,6 +96,19 @@ static uint8_t textModeCRTC[] = { 0x61, 0x50, 0x52, 0x0f, 0x19, 0x06, 0x19, 0x19 void HerculesDriver::Init() { 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; } void HerculesDriver::Shutdown() @@ -341,6 +357,8 @@ void HerculesDriver::DrawString(const char* text, int x, int y, int size, FontSt Font* HerculesDriver::GetFont(int fontSize, FontStyle::Type style) { + return Assets.GetFont(fontSize, style); + /* if (style & FontStyle::Monospace) { switch (fontSize) @@ -367,6 +385,7 @@ Font* HerculesDriver::GetFont(int fontSize, FontStyle::Type style) default: return &Default_RegularFont; } + */ } void HerculesDriver::HLine(int x, int y, int count) @@ -625,6 +644,8 @@ void HerculesDriver::VLine(int x, int y, int count) MouseCursorData* HerculesDriver::GetCursorGraphic(MouseCursor::Type type) { + return Assets.GetMouseCursorData(type); + /* switch (type) { default: @@ -635,6 +656,7 @@ MouseCursorData* HerculesDriver::GetCursorGraphic(MouseCursor::Type type) case MouseCursor::TextSelect: return &Default_MouseCursorTextSelect; } + */ } int HerculesDriver::GetGlyphWidth(char c, int fontSize, FontStyle::Type style) diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 19521db..6d07169 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -28,7 +28,7 @@ #include "../Image.h" #include "../Cursor.h" #include "../Font.h" -#include "DefData.inc" +//#include "DefData.inc" static DOSInputDriver DOSinput; static DOSNetworkDriver DOSNet; diff --git a/src/DOS/TextMode.cpp b/src/DOS/TextMode.cpp index 7fe6d2f..f680b2c 100644 --- a/src/DOS/TextMode.cpp +++ b/src/DOS/TextMode.cpp @@ -20,7 +20,7 @@ #include #include "../Image.h" #include "TextMode.h" -#include "TextData.inc" +//#include "TextData.inc" #include "../Interface.h" #define NAVIGATION_BUTTON_WIDTH 3 @@ -181,7 +181,8 @@ void TextModeDriver::DrawString(const char* text, int x, int y, int size, FontSt Font* TextModeDriver::GetFont(int fontSize, FontStyle::Type style) { - return &TextMode_Font; + return NULL; + //return &TextMode_Font; } void TextModeDriver::HLine(int x, int y, int count) diff --git a/src/DataPack.cpp b/src/DataPack.cpp index bd000e0..ecc0d3d 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -54,3 +54,48 @@ bool DataPack::Load(const char* path) return true; } + +Font* DataPack::GetFont(int fontSize, FontStyle::Type fontStyle) +{ + if (fontStyle & FontStyle::Monospace) + { + switch (fontSize) + { + case 0: + return &monoFonts[0]; + case 2: + case 3: + case 4: + return &monoFonts[2]; + default: + return &monoFonts[1]; + } + } + + switch (fontSize) + { + case 0: + return &fonts[0]; + case 2: + case 3: + case 4: + return &fonts[2]; + default: + return &fonts[1]; + } + +} + +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; +} diff --git a/src/DataPack.h b/src/DataPack.h index f3eab58..3c4da10 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -56,6 +56,8 @@ struct DataPack Font monoFonts[NUM_FONT_SIZES]; bool Load(const char* path); + Font* GetFont(int fontSize, FontStyle::Type fontStyle); + MouseCursorData* GetMouseCursorData(MouseCursor::Type type); }; extern DataPack Assets; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index b483744..e9c0941 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -9,6 +9,9 @@ DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) 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; @@ -20,7 +23,7 @@ void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint } if (x + count >= context.clipRight) { - count = context.clipRight - count - 1; + count = context.clipRight - x; } if (count < 0) { @@ -84,6 +87,9 @@ void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint 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; @@ -130,6 +136,9 @@ void DrawSurface_1BPP::VLine(DrawContext& context, int x, int y, int count, uint 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); @@ -217,6 +226,9 @@ void DrawSurface_1BPP::FillRect(DrawContext& context, int x, int y, int width, i 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; @@ -338,12 +350,14 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) { - HLine(context, startX, y - firstLine + font->glyphHeight - 1, x - startX, colour); + 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) { + x += context.drawOffsetX; + y += context.drawOffsetY; } diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index bbd2ff9..6ff9f7b 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -3,17 +3,20 @@ #include "../Font.h" struct Font; -class Image; +struct Image; +class DrawSurface; struct DrawContext { DrawContext() {} - DrawContext(int inClipLeft, int inClipTop, int inClipRight, int inClipBottom) - : clipLeft(inClipLeft), clipTop(inClipTop), clipRight(inClipRight), clipBottom(inClipBottom) + 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) { } + DrawSurface* surface; int clipLeft, clipTop, clipRight, clipBottom; + int drawOffsetX, drawOffsetY; }; class DrawSurface diff --git a/src/Interface.cpp b/src/Interface.cpp index d7239bb..96f4b4a 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -16,6 +16,12 @@ #include "Interface.h" #include "App.h" #include "KeyCodes.h" +#include "Nodes/Section.h" +#include "Nodes/Button.h" +#include "Nodes/Field.h" +#include "Nodes/Text.h" +#include "Draw/Surface.h" +#include "DataPack.h" #define MIN_SCROLL_WIDGET_SIZE 8 @@ -29,6 +35,17 @@ AppInterface::AppInterface(App& inApp) : app(inApp) oldButtons = 0; textFieldCursorPosition = 0; clickingButton = false; + + hoverNode = nullptr; + focusedNode = nullptr; +} + +void AppInterface::Init() +{ + GenerateInterfaceNodes(); + + DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); + DrawInterfaceNodes(context); } void AppInterface::Reset() @@ -42,10 +59,20 @@ void AppInterface::Reset() { hoverWidget = NULL; } + + if (focusedNode && !IsInterfaceNode(focusedNode)) + { + focusedNode = nullptr; + } + if (!hoverNode && !IsInterfaceNode(hoverNode)) + { + hoverNode = nullptr; + } } void AppInterface::DrawInterfaceWidgets() { + /* for (int n = 0; n < NUM_APP_INTERFACE_WIDGETS; n++) { app.renderer.RenderWidget(appInterfaceWidgets[n]); @@ -53,6 +80,7 @@ void AppInterface::DrawInterfaceWidgets() // Page top line divider Platform::video->HLine(0, Platform::video->windowY - 1, Platform::video->screenWidth); + */ } void AppInterface::Update() @@ -66,15 +94,15 @@ void AppInterface::Update() 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)) @@ -111,20 +139,26 @@ void AppInterface::Update() 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) + if (hoverNode) { - Platform::input->SetMouseCursor(MouseCursor::TextSelect); + switch (hoverNode->type) + { + case Node::Link: + Platform::input->SetMouseCursor(MouseCursor::Hand); + // TODO show link URL + break; + case Node::TextField: + Platform::input->SetMouseCursor(MouseCursor::TextSelect); + break; + default: + Platform::input->SetMouseCursor(MouseCursor::Pointer); + break; + } } else { - app.renderer.SetStatus(""); Platform::input->SetMouseCursor(MouseCursor::Pointer); } } @@ -265,6 +299,46 @@ Widget* AppInterface::PickWidget(int x, int y) return app.renderer.PickPageWidget(x, y); } +bool AppInterface::IsOverNode(Node* node, int x, int y) +{ + if (!node) + { + return false; + } + if (IsInterfaceNode(node)) + { + return node->IsPointInsideNode(x, y); + } + else + { + x -= windowRect.x; + y -= windowRect.y - app.pageRenderer.GetScrollPositionY(); + return node->IsPointInsideNode(x, y); + } +} + +Node* AppInterface::PickNode(int x, int y) +{ + Node* interfaceNode = rootInterfaceNode->Handler().Pick(rootInterfaceNode, x, y); + + if (interfaceNode) + { + 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 - app.pageRenderer.GetScrollPositionY(); + + Node* pageRootNode = app.page.GetRootNode(); + return pageRootNode->Handler().Pick(pageRootNode, pageX, pageY); + } + + return nullptr; +} + + void AppInterface::DeactivateWidget() { if (activeWidget) @@ -370,6 +444,14 @@ void AppInterface::HandleClick(int mouseX, int mouseY) DeactivateWidget(); } + { + Node* clickedNode = PickNode(mouseX, mouseY); + if (clickedNode) + { + printf("Clicked a node\n"); + } + } + if (hoverWidget) { if (hoverWidget->GetLinkURL()) @@ -708,7 +790,7 @@ void AppInterface::SubmitForm(WidgetFormData* form) void AppInterface::UpdateAddressBar(const URL& url) { addressBarURL = url; - app.renderer.RedrawWidget(&addressBar); + //app.renderer.RedrawWidget(&addressBar); } void AppInterface::UpdatePageScrollBar() @@ -827,3 +909,87 @@ void AppInterface::CycleWidgets(int direction) } } } + +void AppInterface::GenerateInterfaceNodes() +{ + rootInterfaceNode = SectionElement::Construct(allocator, SectionElement::Interface); + rootInterfaceNode->style.alignment = ElementAlignment::Left; + rootInterfaceNode->style.fontSize = 1; + rootInterfaceNode->style.fontStyle = FontStyle::Regular; + + Font* interfaceFont = Assets.GetFont(1, FontStyle::Regular); + + { + titleBuffer[0] = '\0'; + TextElement::Data* titleNodeData = allocator.Alloc(titleBuffer); + titleNode = allocator.Alloc(Node::Text, titleNodeData); + titleNode->anchor.Clear(); + titleNode->size.x = Platform::video->screenWidth; + titleNode->size.y = interfaceFont->glyphHeight; + titleNode->style = rootInterfaceNode->style; + rootInterfaceNode->AddChild(titleNode); + } + + backButtonNode = ButtonNode::Construct(allocator, " < "); + backButtonNode->style = rootInterfaceNode->style; + backButtonNode->size = ButtonNode::CalculateSize(backButtonNode); + backButtonNode->anchor.x = 1; + backButtonNode->anchor.y = titleNode->size.y; + rootInterfaceNode->AddChild(backButtonNode); + + forwardButtonNode = ButtonNode::Construct(allocator, " > "); + forwardButtonNode->style = rootInterfaceNode->style; + 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, "http://www.example.com"); + addressBarNode->style = rootInterfaceNode->style; + 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); + + rootInterfaceNode->EncapsulateChildren(); + + windowRect.x = 0; + windowRect.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; + windowRect.width = Platform::video->screenWidth - 16; + windowRect.height = Platform::video->screenHeight - windowRect.y; +} + +void AppInterface::DrawInterfaceNodes(DrawContext& context) +{ + Platform::input->HideMouse(); + app.pageRenderer.DrawAll(context, rootInterfaceNode); + + uint8_t dividerColour = 0; + context.surface->HLine(context, 0, windowRect.y - 1, Platform::video->screenWidth, dividerColour); + Platform::input->ShowMouse(); +} + +void AppInterface::SetTitle(const char* title) +{ + strncpy(titleBuffer, title, MAX_TITLE_LENGTH); + Font* font = Assets.GetFont(titleNode->style.fontSize, titleNode->style.fontStyle); + int titleWidth = font->CalculateWidth(titleBuffer, titleNode->style.fontStyle); + titleNode->anchor.x = Platform::video->screenWidth / 2 - titleWidth / 2; + if (titleNode->anchor.x < 0) + { + titleNode->anchor.x = 0; + } + + DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); + Platform::input->HideMouse(); + uint8_t fillColour = 1; + context.surface->FillRect(context, 0, 0, titleNode->size.x, titleNode->size.y, fillColour); + titleNode->Handler().Draw(context, titleNode); + Platform::input->ShowMouse(); +} + +bool AppInterface::IsInterfaceNode(Node* node) +{ + return node && node->parent == rootInterfaceNode; +} diff --git a/src/Interface.h b/src/Interface.h index 0e21f5f..6c3d4b3 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -17,19 +17,26 @@ #include "Widget.h" #include "Platform.h" #include "URL.h" +#include "LinAlloc.h" +#include "Node.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 DrawInterfaceNodes(DrawContext& context); void DrawInterfaceWidgets(); void UpdateAddressBar(const URL& url); @@ -37,6 +44,8 @@ class AppInterface Widget* GetActiveWidget() { return activeWidget; } int GetTextFieldCursorPosition() { return textFieldCursorPosition; } + void SetTitle(const char* title); + Widget scrollBar; Widget addressBar; Widget backButton; @@ -45,9 +54,15 @@ class AppInterface Widget titleBar; Widget statusBar; + Rect windowRect; + private: + void GenerateInterfaceNodes(); + void GenerateWidgets(); Widget* PickWidget(int x, int y); + Node* PickNode(int x, int y); + void HandleClick(int mouseX, int mouseY); void HandleRelease(); bool HandleActiveWidget(InputButtonCode keyPress); @@ -59,9 +74,14 @@ class AppInterface void ActivateWidget(Widget* widget); void DeactivateWidget(); + bool IsInterfaceNode(Node* node); + bool IsOverNode(Node* node, int x, int y); + Widget* appInterfaceWidgets[NUM_APP_INTERFACE_WIDGETS]; App& app; + Node* focusedNode; + Node* hoverNode; Widget* activeWidget; Widget* hoverWidget; int oldButtons; @@ -77,6 +97,16 @@ class AppInterface ButtonWidgetData forwardButtonData; TextFieldWidgetData addressBarData; ScrollBarData scrollBarData; + + LinearAllocator allocator; + + Node* rootInterfaceNode; + Node* titleNode; + Node* backButtonNode; + Node* forwardButtonNode; + Node* addressBarNode; + + char titleBuffer[MAX_TITLE_LENGTH]; }; #endif diff --git a/src/Layout.cpp b/src/Layout.cpp index eaad36d..4a1b421 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -1,10 +1,10 @@ #include "Layout.h" #include "Page.h" +#include "Platform.h" Layout::Layout(Page& inPage) : page(inPage) { - Reset(); } void Layout::Reset() @@ -15,7 +15,7 @@ void Layout::Reset() LayoutParams& params = GetParams(); params.marginLeft = 0; - params.marginRight = 600; + params.marginRight = page.GetPageWidth(); } void Layout::PushLayout() diff --git a/src/Node.cpp b/src/Node.cpp index 4db89c1..1c6e35a 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -7,6 +7,8 @@ #include "Nodes/StyNode.h" #include "Nodes/LinkNode.h" #include "Nodes/Block.h" +#include "Nodes/Button.h" +#include "Nodes/Field.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -17,7 +19,9 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new BreakNode(), new StyleNode(), new LinkNode(), - new BlockNode() + new BlockNode(), + new ButtonNode(), + new TextFieldNode() }; Node::Node(Type inType, void* inData) @@ -48,4 +52,62 @@ void Node::AddChild(Node* child) } } +void Node::EncapsulateChildren() +{ + if (firstChild) + { + anchor = firstChild->anchor; + size = firstChild->size; + + for (Node* node = firstChild->next; node; node = node->next) + { + if (node->anchor.x < anchor.x) + { + anchor.x = node->anchor.x; + } + if (node->anchor.y < anchor.y) + { + anchor.y = node->anchor.y; + } + if (node->anchor.x + node->size.x > anchor.x + size.x) + { + size.x = node->anchor.x + node->size.x - anchor.x; + } + if (node->anchor.y + node->size.y > anchor.y + size.y) + { + size.y = node->anchor.y + node->size.y - anchor.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; +} + +Node* NodeHandler::Pick(Node* node, int x, int y) +{ + if (!node || !node->IsPointInsideNode(x, y)) + { + return nullptr; + } + + for (Node* it = node->firstChild; it; it = it->next) + { + if (it->IsPointInsideNode(x, y)) + { + return it->Handler().Pick(it, x, y); + } + } + + return nullptr; +} + +void NodeHandler::EndLayoutContext(Layout& layout, Node* node) +{ + if (node->size.x == 0 && node->size.y == 0) + { + node->EncapsulateChildren(); + } +} diff --git a/src/Node.h b/src/Node.h index 5a200c3..92c46a4 100644 --- a/src/Node.h +++ b/src/Node.h @@ -5,19 +5,24 @@ #include #include "Style.h" +class App; class Page; class Node; class Layout; +struct DrawContext; typedef class LinearAllocator Allocator; class NodeHandler { public: - virtual void Draw(Page& page, Node* element) {} + 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 EndLayoutContext(Layout& layout, Node* node); virtual void ApplyStyle(Node* node) {} + virtual Node* Pick(Node* node, int x, int y); + virtual void OnMouseClick(App& app) {} + virtual void OnMouseRelease(App& app) {} }; struct Coord @@ -47,6 +52,8 @@ class Node Style, Link, Block, + Button, + TextField, NumNodeTypes }; @@ -57,6 +64,8 @@ class Node Node(Type inType, void* inData); void AddChild(Node* child); + void EncapsulateChildren(); // Sets anchor and size based on children + bool IsPointInsideNode(int x, int y); Type type; diff --git a/src/Nodes/Block.cpp b/src/Nodes/Block.cpp index d4a9b0b..16f3044 100644 --- a/src/Nodes/Block.cpp +++ b/src/Nodes/Block.cpp @@ -25,6 +25,8 @@ void BlockNode::BeginLayoutContext(Layout& layout, Node* node) void BlockNode::EndLayoutContext(Layout& layout, Node* node) { + NodeHandler::EndLayoutContext(layout, node); + BlockNode::Data* data = static_cast(node->data); layout.PopLayout(); diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index b146044..befe006 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -13,11 +13,6 @@ Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayB return nullptr; } -void BreakNode::Draw(Page& page, Node* element) -{ - //printf("\n"); -} - void BreakNode::GenerateLayout(Layout& layout, Node* node) { layout.BreakNewLine(); diff --git a/src/Nodes/Break.h b/src/Nodes/Break.h index f8ab8c7..5e8f2cb 100644 --- a/src/Nodes/Break.h +++ b/src/Nodes/Break.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef _BREAK_H_ +#define _BREAK_H_ #include "../Node.h" @@ -14,6 +15,7 @@ class BreakNode : public NodeHandler }; static Node* Construct(Allocator& allocator, int breakPadding = 0, bool displayBreakLine = false); - virtual void Draw(Page& page, 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..b098634 --- /dev/null +++ b/src/Nodes/Button.cpp @@ -0,0 +1,85 @@ +#include "Button.h" +#include "../DataPack.h" +#include "../Draw/Surface.h" +#include "../LinAlloc.h" +#include "../Layout.h" + +void ButtonNode::Draw(DrawContext& context, Node* node) +{ + ButtonNode::Data* data = static_cast(node->data); + + if (data->buttonText) + { + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + uint8_t textColour = 0; + uint8_t buttonOutlineColour = 0; + 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); + context.surface->DrawString(context, font, data->buttonText, node->anchor.x + 8, node->anchor.y + 2, textColour, node->style.fontStyle); + } +} + +Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText) +{ + const char* buttonText = NULL; + if (inButtonText) + { + buttonText = allocator.AllocString(inButtonText); + if (!buttonText) + { + return nullptr; + } + } + + ButtonNode::Data* data = allocator.Alloc(buttonText); + if (data) + { + return allocator.Alloc(Node::Button, data); + } + + return nullptr; +} + +Coord ButtonNode::CalculateSize(Node* node) +{ + ButtonNode::Data* data = static_cast(node->data); + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + int labelHeight = font->glyphHeight; + int labelWidth = 0; + + if (data->buttonText) + { + labelWidth = font->CalculateWidth(data->buttonText); + } + + 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); +} + +Node* ButtonNode::Pick(Node* node, int x, int y) +{ + if (node->IsPointInsideNode(x, y)) + { + return node; + } + return nullptr; +} diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h new file mode 100644 index 0000000..c5238cf --- /dev/null +++ b/src/Nodes/Button.h @@ -0,0 +1,24 @@ +#ifndef _BUTTON_H_ +#define _BUTTON_H_ + +#include "../Node.h" + +class ButtonNode : public NodeHandler +{ +public: + class Data + { + public: + Data(const char* inButtonText) : buttonText(inButtonText) {} + const char* buttonText; + }; + + static Node* Construct(Allocator& allocator, const char* buttonText); + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* node) override; + virtual Node* Pick(Node* node, int x, int y) override; + + static Coord CalculateSize(Node* node); +}; + +#endif diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp new file mode 100644 index 0000000..6b05e1c --- /dev/null +++ b/src/Nodes/Field.cpp @@ -0,0 +1,71 @@ +#include "Field.h" +#include "../DataPack.h" +#include "../Draw/Surface.h" +#include "../LinAlloc.h" +#include "../Layout.h" +#include "../Platform.h" + +void TextFieldNode::Draw(DrawContext& context, Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + uint8_t textColour = 0; + uint8_t buttonOutlineColour = 0; + 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); + context.surface->DrawString(context, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); +} + +Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue) +{ + char* buffer = (char*) allocator.Alloc(DEFAULT_TEXT_FIELD_BUFFER_SIZE); + if (!buffer) + { + return nullptr; + } + + TextFieldNode::Data* data = allocator.Alloc(buffer, DEFAULT_TEXT_FIELD_BUFFER_SIZE); + if (data) + { + if (inValue) + { + strncpy(buffer, inValue, DEFAULT_TEXT_FIELD_BUFFER_SIZE); + } + else + { + buffer[0] = '\0'; + } + return allocator.Alloc(Node::TextField, data); + } + + return nullptr; +} + +void TextFieldNode::GenerateLayout(Layout& layout, Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + node->size.x = Platform::video->screenWidth / 3; + node->size.y = font->glyphHeight + 4; + + 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* TextFieldNode::Pick(Node* node, int x, int y) +{ + if (node->IsPointInsideNode(x, y)) + { + return node; + } + return nullptr; +} diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h new file mode 100644 index 0000000..148c3ee --- /dev/null +++ b/src/Nodes/Field.h @@ -0,0 +1,25 @@ +#ifndef _FIELD_H_ +#define _FIELD_H_ + +#include "../Node.h" + +#define DEFAULT_TEXT_FIELD_BUFFER_SIZE 80 + +class TextFieldNode : public NodeHandler +{ +public: + class Data + { + public: + Data(char* inBuffer, int inBufferSize) : buffer(inBuffer), bufferSize(inBufferSize) {} + char* buffer; + int bufferSize; + }; + + static Node* Construct(Allocator& allocator, const char* buttonText); + virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual void Draw(DrawContext& context, Node* node) override; + virtual Node* Pick(Node* node, int x, int y) override; +}; + +#endif diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index e361201..7aefc7a 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -3,12 +3,18 @@ #include "ImgNode.h" #include "../LinAlloc.h" #include "../Layout.h" +#include "../Draw/Surface.h" -void ImageNode::Draw(Page& page, Node* node) +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); - Platform::video->FillRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); + //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); + uint8_t outlineColour = 0; + + 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); } diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index bd75a31..b63c7ef 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -13,6 +13,6 @@ class ImageNode: public NodeHandler }; static Node* Construct(Allocator& allocator, void* imageData); - virtual void Draw(Page& page, Node* element) override; + virtual void Draw(DrawContext& context, Node* element) override; virtual void GenerateLayout(Layout& layout, Node* node) override; }; diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 3e4f2ad..a22c180 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -15,3 +15,4 @@ Node* LinkNode::Construct(Allocator& allocator) } return nullptr; } + diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h index 3b914b9..f7e612c 100644 --- a/src/Nodes/LinkNode.h +++ b/src/Nodes/LinkNode.h @@ -12,7 +12,5 @@ class LinkNode : public NodeHandler }; virtual void ApplyStyle(Node* node) override; - static Node* Construct(Allocator& allocator); - }; \ No newline at end of file diff --git a/src/Nodes/Section.h b/src/Nodes/Section.h index 49c3f3d..5913cb0 100644 --- a/src/Nodes/Section.h +++ b/src/Nodes/Section.h @@ -13,7 +13,8 @@ class SectionElement : public NodeHandler Body, Script, Style, - Title + Title, + Interface }; class Data diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 25f30d8..f378bcc 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -3,14 +3,18 @@ #include "Text.h" #include "../LinAlloc.h" #include "../Layout.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" -void TextElement::Draw(Page& page, Node* node) +void TextElement::Draw(DrawContext& context, Node* node) { TextElement::Data* data = static_cast(node->data); if (!node->firstChild && data->text) { - Platform::video->DrawString(data->text, node->anchor.x, node->anchor.y + 50, node->style.fontSize, node->style.fontStyle); + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + uint8_t textColour = 0; + context.surface->DrawString(context, font, data->text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); //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 index 5102532..77e3fe2 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -13,7 +13,7 @@ class TextElement : public NodeHandler }; static Node* Construct(Allocator& allocator, const char* text); - virtual void Draw(Page& page, Node* element) override; + virtual void Draw(DrawContext& context, Node* element) override; virtual void GenerateLayout(Layout& layout, Node* node) override; }; diff --git a/src/Page.cpp b/src/Page.cpp index 6f40ae3..66c6678 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -540,7 +540,7 @@ void Page::BreakTextLine() void Page::SetTitle(const char* inTitle) { - app.renderer.SetTitle(inTitle); + app.ui.SetTitle(inTitle); } Widget* Page::GetWidget(int x, int y) @@ -575,13 +575,13 @@ void Page::SetFormData(WidgetFormData* inFormData) formData = inFormData; } -void Page::DebugDraw(Node* node) +void Page::DebugDraw(DrawContext& context, Node* node) { while (node) { - node->Handler().Draw(*this, node); + node->Handler().Draw(context, node); - DebugDraw(node->firstChild); + DebugDraw(context, node->firstChild); node = node->next; } @@ -661,17 +661,17 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) case Node::SubText: { TextElement::Data* data = static_cast(node->data); - printf("<%s> %s\n", nodeTypeNames[node->type], data->text); + 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); } break; case Node::Section: { SectionElement::Data* data = static_cast(node->data); - printf("<%s> %s\n", nodeTypeNames[node->type], sectionTypeNames[data->type]); + 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]); } break; default: - printf("<%s>\n", nodeTypeNames[node->type]); + printf("<%s> [%d,%d:%d,%d]\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); break; } @@ -680,3 +680,8 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) DebugDumpNodeGraph(child, depth + 1); } } + +int Page::GetPageWidth() +{ + return app.ui.windowRect.width; +} diff --git a/src/Page.h b/src/Page.h index 7f3d86c..69d3d9a 100644 --- a/src/Page.h +++ b/src/Page.h @@ -91,9 +91,10 @@ class Page LinearAllocator allocator; Layout layout; + int GetPageWidth(); int GetPageHeight() { return pageHeight; } - void DebugDraw(Node* node); + void DebugDraw(DrawContext& context, Node* node); void DebugDumpNodeGraph(Node* node, int depth = 0); URL pageURL; diff --git a/src/Parser.cpp b/src/Parser.cpp index 1235e8a..7bb5e27 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -24,6 +24,10 @@ #include "Nodes/Text.h" #include "Nodes/ImgNode.h" +// debug +#include "Platform.h" +#include "Draw/Surface.h" + HTMLParser::HTMLParser(Page& inPage) : page(inPage) , currentParseSection(SectionElement::Document) @@ -95,8 +99,13 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (contextStackSize == 0) { - //page.DebugDumpNodeGraph(page.GetRootNode()); - page.DebugDraw(page.GetRootNode()); + page.GetRootNode()->EncapsulateChildren(); + DrawContext context(Platform::video->drawSurface, 0, 20, Platform::video->screenWidth, Platform::video->screenHeight - 20); + context.drawOffsetY = 50; +#ifdef _WIN32 + page.DebugDumpNodeGraph(page.GetRootNode()); +#endif + page.DebugDraw(context, page.GetRootNode()); } // We may have exited a section so update diff --git a/src/Platform.h b/src/Platform.h index fe1763c..314f3fa 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -21,6 +21,7 @@ #include "Widget.h" struct Image; +class DrawSurface; class VideoDriver { @@ -67,6 +68,8 @@ class VideoDriver Image* bulletImage; bool isTextMode; + + DrawSurface* drawSurface; }; class HTTPRequest diff --git a/src/Render.cpp b/src/Render.cpp new file mode 100644 index 0000000..4b5808a --- /dev/null +++ b/src/Render.cpp @@ -0,0 +1,47 @@ +#include "Render.h" +#include "Node.h" +#include "Draw/Surface.h" + +PageRenderer::PageRenderer(App& inApp) + : app(inApp), scrollPositionY(0) +{ + +} + +void PageRenderer::Init() +{ + +} + +void PageRenderer::Reset() +{ + scrollPositionY = 0; +} + +void PageRenderer::Update() +{ + +} + +void PageRenderer::ScrollRelative(int delta) +{ + +} + +void PageRenderer::ScrollAbsolute(int position) +{ + +} + +void PageRenderer::DrawAll(DrawContext& context, Node* node) +{ + while (node) + { + node->Handler().Draw(context, node); + + DrawAll(context, node->firstChild); + + node = node->next; + } +} + diff --git a/src/Render.h b/src/Render.h new file mode 100644 index 0000000..08ce4e5 --- /dev/null +++ b/src/Render.h @@ -0,0 +1,30 @@ +#ifndef _RENDER_H_ +#define _RENDER_H_ + +class App; +class Node; +struct DrawContext; + +class PageRenderer +{ +public: + PageRenderer(App& inApp); + + void Init(); + void Reset(); + void Update(); + + void ScrollRelative(int delta); + void ScrollAbsolute(int position); + + void DrawAll(DrawContext& context, Node* node); + + int GetScrollPositionY() { return scrollPositionY; } + +private: + App& app; + int scrollPositionY; +}; + +#endif + diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 18c9add..3ab340c 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -34,9 +34,9 @@ void Renderer::Init() Platform::input->HideMouse(); Platform::video->ClearWindow(); RedrawScrollBar(); - app.ui.DrawInterfaceWidgets(); - SetTitle("MicroWeb"); - SetStatus(" "); + //app.ui.DrawInterfaceWidgets(); + //SetTitle("MicroWeb"); + //SetStatus(" "); Platform::input->ShowMouse(); } diff --git a/src/Tags.cpp b/src/Tags.cpp index 3f08414..36b39d7 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -27,6 +27,8 @@ #include "Nodes/StyNode.h" #include "Nodes/LinkNode.h" #include "Nodes/Block.h" +#include "Nodes/Button.h" +#include "Nodes/Field.h" static const HTMLTagHandler* tagHandlers[] = { @@ -367,10 +369,7 @@ void ButtonTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } } - if (title) - { - //parser.page.AddButton(title); - } + parser.EmitNode(ButtonNode::Construct(parser.page.allocator, title)); } void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -385,7 +384,7 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { if (!stricmp(attributes.Key(), "type")) { - if (!stricmp(attributes.Value(), "submit")) + if (!stricmp(attributes.Value(), "submit") || !stricmp(attributes.Value(), "button")) { type = HTMLInputTag::Submit; } @@ -413,11 +412,11 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const case HTMLInputTag::Submit: if (value) { - parser.page.AddButton(value); + parser.EmitNode(ButtonNode::Construct(parser.page.allocator, value)); } break; case HTMLInputTag::Text: - parser.page.AddTextField(value, bufferLength, name); + parser.EmitNode(TextFieldNode::Construct(parser.page.allocator, value)); break; } } diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index fdbf7a4..b8896eb 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -25,8 +25,8 @@ #define WINDOW_HEIGHT 168 #define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) -#define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT 480 +#define SCREEN_WIDTH 800 +#define SCREEN_HEIGHT 600 #define NAVIGATION_BUTTON_WIDTH 24 #define NAVIGATION_BUTTON_HEIGHT 12 @@ -103,10 +103,17 @@ void WindowsVideoDriver::Init() { 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); + } + for (int y = 0; y < screenHeight; y++) { int bufferY = (screenHeight - 1 - y); - surface->lines[y] = &buffer[bufferY * (screenWidth / 8)]; + surface->lines[y] = &buffer[bufferY * pitch]; } drawSurface = surface; } @@ -134,22 +141,22 @@ void WindowsVideoDriver::Shutdown() 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); + //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) { - DrawContext context(0, 0, screenWidth, screenHeight); - drawSurface->FillRect(context, x, y, width, height, 0); + DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); + //drawSurface->FillRect(context, x, y, width, height, 0); // FillRect(x, y, width, height, foregroundColour); } void WindowsVideoDriver::FillRect(int x, int y, int width, int height, uint32_t colour) { - DrawContext context(0, 0, screenWidth, screenHeight); + DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); drawSurface->FillRect(context, x, y, width, height, colour ? 1 : 0); // for (int j = 0; j < height; j++) @@ -215,12 +222,12 @@ void WindowsVideoDriver::InvertPixel(int x, int y, uint32_t colour) void WindowsVideoDriver::ClearWindow() { - FillRect(0, WINDOW_TOP, SCREEN_WIDTH - SCROLL_BAR_WIDTH, WINDOW_HEIGHT, backgroundColour); + //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); + //FillRect(x, y, width, height, backgroundColour); } void WindowsVideoDriver::ScrollWindow(int delta) @@ -244,7 +251,7 @@ void WindowsVideoDriver::DrawString(const char* text, int x, int y, int size, Fo // printf("%s\n", text); Font* font = GetFont(size, style); - DrawContext context(0, 0, screenWidth, screenHeight); + DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); drawSurface->DrawString(context, font, text, x, y, 0, style); return; @@ -343,7 +350,7 @@ void WindowsVideoDriver::DrawString(const char* text, int x, int y, int size, Fo void WindowsVideoDriver::HLine(int x, int y, int count) { - DrawContext context(0, 0, screenWidth, screenHeight); + DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); drawSurface->HLine(context, x, y, count, 0); //for (int n = 0; n < count; n++) //{ @@ -353,7 +360,7 @@ void WindowsVideoDriver::HLine(int x, int y, int count) void WindowsVideoDriver::VLine(int x, int y, int count) { - DrawContext context(0, 0, screenWidth, screenHeight); + DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); drawSurface->VLine(context, x, y, count, 0); //for (int n = 0; n < count; n++) //{ diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index 120a4ae..d41608c 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -64,8 +64,6 @@ class WindowsVideoDriver : public VideoDriver uint32_t* lpBitmapBits; HBITMAP screenBitmap; - DrawSurface* drawSurface; - uint32_t foregroundColour, backgroundColour; int scissorX1, scissorY1, scissorX2, scissorY2; }; From d811a3a807560a4c068b606630f7231cac44cc20 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sun, 9 Jul 2023 21:20:41 +0100 Subject: [PATCH 10/98] Layout refactor WIP --- src/Tags.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tags.cpp b/src/Tags.cpp index 36b39d7..3b93c7e 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -157,6 +157,8 @@ void HTagHandler::Close(class HTMLParser& parser) const void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { + parser.PushContext(StyleNode::ConstructFontSize(parser.page.allocator, size), this); + //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); //currentStyle.fontSize = size; //parser.page.PushStyle(currentStyle); @@ -164,6 +166,8 @@ void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void SizeTagHandler::Close(class HTMLParser& parser) const { + parser.PopContext(this); + //parser.page.PopStyle(); } From a9abe3e9db73b79222a1efbce709ed1d348bd681 Mon Sep 17 00:00:00 2001 From: James Howard Date: Mon, 10 Jul 2023 12:35:00 +0100 Subject: [PATCH 11/98] Event handling WIP --- project/Windows/Windows.vcxproj | 4 +-- src/Draw/Surf1bpp.cpp | 63 +++++++++++++++++++++++++++++++++ src/Draw/Surf1bpp.h | 2 ++ src/Draw/Surface.h | 1 + src/Event.h | 26 ++++++++++++++ src/Interface.cpp | 52 ++++++++++++++++++++++----- src/Interface.h | 7 +++- src/Node.cpp | 5 +++ src/Node.h | 5 +-- src/Nodes/Button.cpp | 30 +++++++++++++--- src/Nodes/Button.h | 3 +- src/Nodes/Field.cpp | 8 ----- src/Nodes/Field.h | 2 +- src/Nodes/LinkNode.h | 2 ++ src/Page.h | 2 ++ src/Parser.cpp | 6 ++-- src/Render.cpp | 41 +++++++++++++++++++++ src/Render.h | 2 ++ 18 files changed, 231 insertions(+), 30 deletions(-) create mode 100644 src/Event.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 9f1c879..5331742 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.15063.0 @@ -42,8 +42,8 @@ Application true - v142 Unicode + v141 Application diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index e9c0941..e3d8922 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -359,5 +359,68 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int { x += context.drawOffsetX; y += context.drawOffsetY; +} + +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 - 1 - x; + } + if (y + height >= context.clipBottom) + { + height = context.clipBottom - 1 - y; + } + if (width <= 0 || height <= 0) + { + return; + } + while (height) + { + uint8_t* VRAMptr = lines[y]; + VRAMptr += (x >> 3); + int count = width; + int workX = x; + uint8_t data = *VRAMptr; + uint8_t mask = (0x80 >> (x & 7)); + + while (count--) + { + data ^= mask; + workX++; + //mask = (mask >> 1) | 0x80; + mask >>= 1; + if ((workX & 7) == 0) + { + *VRAMptr++ = data; + while (count > 8) + { + *VRAMptr++ ^= 0xff; + count -= 8; + } + mask = 0x80; + data = *VRAMptr; + } + } + + *VRAMptr = data; + + height--; + y++; + } } + diff --git a/src/Draw/Surf1bpp.h b/src/Draw/Surf1bpp.h index 9adf0f2..0da60df 100644 --- a/src/Draw/Surf1bpp.h +++ b/src/Draw/Surf1bpp.h @@ -14,6 +14,8 @@ class DrawSurface_1BPP : public DrawSurface 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); + uint8_t** lines; }; diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index 6ff9f7b..1353557 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -26,6 +26,7 @@ class DrawSurface 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; diff --git a/src/Event.h b/src/Event.h new file mode 100644 index 0000000..ad9cfbb --- /dev/null +++ b/src/Event.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include "Platform.h" + +class App; + +struct Event +{ + enum Type + { + MouseClick, + MouseRelease, + KeyPress + }; + + Event(App& inApp, Type inType) : app(inApp), type(inType), key(0) {} + Event(App& inApp, Type inType, InputButtonCode inKey) : app(inApp), type(inType), key(inKey) {} + + App& app; + Type type; + InputButtonCode key; +}; + +#endif diff --git a/src/Interface.cpp b/src/Interface.cpp index 96f4b4a..5b49f01 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -22,6 +22,7 @@ #include "Nodes/Text.h" #include "Draw/Surface.h" #include "DataPack.h" +#include "Event.h" #define MIN_SCROLL_WIDGET_SIZE 8 @@ -161,6 +162,23 @@ void AppInterface::Update() { Platform::input->SetMouseCursor(MouseCursor::Pointer); } + + { + // For debugging picking + 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); + } + } + } @@ -169,6 +187,11 @@ void AppInterface::Update() while (keyPress = Platform::input->GetKeyPress()) { + if (focusedNode && focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::KeyPress, keyPress))) + { + continue; + } + if (activeWidget) { if (HandleActiveWidget(keyPress)) @@ -241,7 +264,8 @@ void AppInterface::Update() if (scrollDelta) { - app.renderer.Scroll(scrollDelta); + app.pageRenderer.ScrollRelative(scrollDelta); + //app.renderer.Scroll(scrollDelta); } } @@ -329,7 +353,7 @@ Node* AppInterface::PickNode(int x, int y) 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 - app.pageRenderer.GetScrollPositionY(); + int pageY = y - windowRect.y + app.pageRenderer.GetScrollPositionY(); Node* pageRootNode = app.page.GetRootNode(); return pageRootNode->Handler().Pick(pageRootNode, pageX, pageY); @@ -444,12 +468,10 @@ void AppInterface::HandleClick(int mouseX, int mouseY) DeactivateWidget(); } + if (hoverNode) { - Node* clickedNode = PickNode(mouseX, mouseY); - if (clickedNode) - { - printf("Clicked a node\n"); - } + FocusNode(hoverNode); + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseClick)); } if (hoverWidget) @@ -539,6 +561,11 @@ void AppInterface::HandleButtonClicked(Widget* widget) void AppInterface::HandleRelease() { + if (focusedNode) + { + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseRelease)); + } + if (activeWidget && activeWidget->type == Widget::Button) { if (clickingButton) @@ -991,5 +1018,14 @@ void AppInterface::SetTitle(const char* title) bool AppInterface::IsInterfaceNode(Node* node) { - return node && node->parent == rootInterfaceNode; + return node == rootInterfaceNode || (node && node->parent == rootInterfaceNode); } + +void AppInterface::FocusNode(Node* node) +{ + if (node != focusedNode) + { + focusedNode = node; + } +} + diff --git a/src/Interface.h b/src/Interface.h index 6c3d4b3..903f06f 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -46,6 +46,12 @@ class AppInterface void SetTitle(const char* title); + void FocusNode(Node* node); + Node* GetFocusedNode() { return focusedNode; } + Node* GetHoverNode() { return hoverNode; } + Node* GetRootInterfaceNode() { return rootInterfaceNode; } + bool IsInterfaceNode(Node* node); + Widget scrollBar; Widget addressBar; Widget backButton; @@ -74,7 +80,6 @@ class AppInterface void ActivateWidget(Widget* widget); void DeactivateWidget(); - bool IsInterfaceNode(Node* node); bool IsOverNode(Node* node, int x, int y); Widget* appInterfaceWidgets[NUM_APP_INTERFACE_WIDGETS]; diff --git a/src/Node.cpp b/src/Node.cpp index 1c6e35a..3af99ac 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -93,6 +93,11 @@ Node* NodeHandler::Pick(Node* node, int x, int y) return nullptr; } + if (CanPick(node)) + { + return node; + } + for (Node* it = node->firstChild; it; it = it->next) { if (it->IsPointInsideNode(x, y)) diff --git a/src/Node.h b/src/Node.h index 92c46a4..ed349f7 100644 --- a/src/Node.h +++ b/src/Node.h @@ -4,6 +4,7 @@ #include #include "Style.h" +#include "Event.h" class App; class Page; @@ -21,8 +22,8 @@ class NodeHandler virtual void EndLayoutContext(Layout& layout, Node* node); virtual void ApplyStyle(Node* node) {} virtual Node* Pick(Node* node, int x, int y); - virtual void OnMouseClick(App& app) {} - virtual void OnMouseRelease(App& app) {} + virtual bool CanPick(Node* node) { return false; } + virtual bool HandleEvent(Node* node, const Event& event) { return false; } }; struct Coord diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index b098634..155609d 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -1,8 +1,11 @@ + #include "Button.h" #include "../DataPack.h" #include "../Draw/Surface.h" #include "../LinAlloc.h" #include "../Layout.h" +#include "../Event.h" +#include "../App.h" void ButtonNode::Draw(DrawContext& context, Node* node) { @@ -15,6 +18,7 @@ void ButtonNode::Draw(DrawContext& context, Node* node) uint8_t buttonOutlineColour = 0; 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->style.fontStyle); @@ -56,7 +60,7 @@ Coord ButtonNode::CalculateSize(Node* node) Coord result; result.x = labelWidth + 16; - result.y = labelHeight + 4; + result.y = labelHeight + 5; return result; } @@ -75,11 +79,27 @@ void ButtonNode::GenerateLayout(Layout& layout, Node* node) layout.ProgressCursor(node, node->size.x, node->size.y); } -Node* ButtonNode::Pick(Node* node, int x, int y) +bool ButtonNode::HandleEvent(Node* node, const Event& event) { - if (node->IsPointInsideNode(x, y)) + switch (event.type) { - return node; + case Event::MouseClick: + { + DrawContext context; + event.app.pageRenderer.GenerateDrawContext(context, node); + context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); + return true; + } + case Event::MouseRelease: + { + DrawContext context; + event.app.pageRenderer.GenerateDrawContext(context, node); + context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); + return true; + } + default: + break; } - return nullptr; + + return false; } diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h index c5238cf..73d656f 100644 --- a/src/Nodes/Button.h +++ b/src/Nodes/Button.h @@ -16,7 +16,8 @@ class ButtonNode : public NodeHandler static Node* Construct(Allocator& allocator, const char* buttonText); virtual void GenerateLayout(Layout& layout, Node* node) override; virtual void Draw(DrawContext& context, Node* node) override; - virtual Node* Pick(Node* node, int x, int y) override; + virtual bool CanPick(Node* node) { return true; } + virtual bool HandleEvent(Node* node, const Event& event); static Coord CalculateSize(Node* node); }; diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 6b05e1c..b2dcf49 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -61,11 +61,3 @@ void TextFieldNode::GenerateLayout(Layout& layout, Node* node) layout.ProgressCursor(node, node->size.x, node->size.y); } -Node* TextFieldNode::Pick(Node* node, int x, int y) -{ - if (node->IsPointInsideNode(x, y)) - { - return node; - } - return nullptr; -} diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 148c3ee..59582a6 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -19,7 +19,7 @@ class TextFieldNode : public NodeHandler static Node* Construct(Allocator& allocator, const char* buttonText); virtual void GenerateLayout(Layout& layout, Node* node) override; virtual void Draw(DrawContext& context, Node* node) override; - virtual Node* Pick(Node* node, int x, int y) override; + virtual bool CanPick(Node* node) { return true; } }; #endif diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h index f7e612c..17423ae 100644 --- a/src/Nodes/LinkNode.h +++ b/src/Nodes/LinkNode.h @@ -12,5 +12,7 @@ class LinkNode : public NodeHandler }; virtual void ApplyStyle(Node* node) override; + virtual bool CanPick(Node* node) override { return true; } + static Node* Construct(Allocator& allocator); }; \ No newline at end of file diff --git a/src/Page.h b/src/Page.h index 69d3d9a..80f4b7a 100644 --- a/src/Page.h +++ b/src/Page.h @@ -99,6 +99,8 @@ class Page URL pageURL; + App& GetApp() { return app; } + private: friend class Renderer; friend class AppInterface; diff --git a/src/Parser.cpp b/src/Parser.cpp index 7bb5e27..ed5f5ea 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -27,6 +27,7 @@ // debug #include "Platform.h" #include "Draw/Surface.h" +#include "App.h" HTMLParser::HTMLParser(Page& inPage) : page(inPage) @@ -100,8 +101,9 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (contextStackSize == 0) { page.GetRootNode()->EncapsulateChildren(); - DrawContext context(Platform::video->drawSurface, 0, 20, Platform::video->screenWidth, Platform::video->screenHeight - 20); - context.drawOffsetY = 50; + DrawContext context; + page.GetApp().pageRenderer.GenerateDrawContext(context, page.GetRootNode()); + #ifdef _WIN32 page.DebugDumpNodeGraph(page.GetRootNode()); #endif diff --git a/src/Render.cpp b/src/Render.cpp index 4b5808a..72811ac 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -1,5 +1,7 @@ #include "Render.h" #include "Node.h" +#include "App.h" +#include "Interface.h" #include "Draw/Surface.h" PageRenderer::PageRenderer(App& inApp) @@ -25,7 +27,22 @@ void PageRenderer::Update() void PageRenderer::ScrollRelative(int delta) { + scrollPositionY += delta; + if (scrollPositionY < 0) + scrollPositionY = 0; + + int maxScrollY = app.page.GetRootNode()->size.y - app.ui.windowRect.height; + if (maxScrollY > 0 && scrollPositionY > maxScrollY) + { + scrollPositionY = maxScrollY; + } + DrawContext context; + GenerateDrawContext(context, NULL); + context.drawOffsetY = 0; + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 1); + GenerateDrawContext(context, NULL); + DrawAll(context, app.page.GetRootNode()); } void PageRenderer::ScrollAbsolute(int position) @@ -45,3 +62,27 @@ void PageRenderer::DrawAll(DrawContext& context, Node* node) } } +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 - scrollPositionY; + } +} diff --git a/src/Render.h b/src/Render.h index 08ce4e5..80d7494 100644 --- a/src/Render.h +++ b/src/Render.h @@ -20,6 +20,8 @@ class PageRenderer void DrawAll(DrawContext& context, Node* node); int GetScrollPositionY() { return scrollPositionY; } + + void GenerateDrawContext(DrawContext& context, Node* node); private: App& app; From 75a1e3f798927ddc6a143e97b9500b18b9895bc1 Mon Sep 17 00:00:00 2001 From: James Howard Date: Mon, 10 Jul 2023 14:51:33 +0100 Subject: [PATCH 12/98] Added form support for new node based system --- project/DOS/Makefile | 5 ++- project/Windows/Windows.vcxproj | 1 + src/Interface.cpp | 13 +++++- src/Interface.h | 6 ++- src/Node.cpp | 17 ++++++- src/Node.h | 2 + src/Nodes/Button.cpp | 11 ++++- src/Nodes/Button.h | 7 ++- src/Nodes/Field.h | 3 +- src/Nodes/Form.cpp | 79 +++++++++++++++++++++++++++++++++ src/Nodes/Form.h | 36 +++++++++++++++ src/Nodes/LinkNode.cpp | 25 ++++++++++- src/Nodes/LinkNode.h | 4 +- src/Page.cpp | 12 ++++- src/Tags.cpp | 36 +++++++++------ 15 files changed, 230 insertions(+), 27 deletions(-) create mode 100644 src/Nodes/Form.cpp create mode 100644 src/Nodes/Form.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index bb1865a..11bda97 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ 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 TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj +objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -88,6 +88,9 @@ Section.obj: $(SRC_PATH)\Nodes\Section.cpp Text.obj: $(SRC_PATH)\Nodes\Text.cpp $(CC) -fo=$@ $(CFLAGS) $< +Form.obj: $(SRC_PATH)\Nodes\Form.cpp + $(CC) -fo=$@ $(CFLAGS) $< + Field.obj: $(SRC_PATH)\Nodes\Field.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 5331742..1d69584 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -148,6 +148,7 @@ + diff --git a/src/Interface.cpp b/src/Interface.cpp index 5b49f01..fd4e92d 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -957,14 +957,14 @@ void AppInterface::GenerateInterfaceNodes() rootInterfaceNode->AddChild(titleNode); } - backButtonNode = ButtonNode::Construct(allocator, " < "); + backButtonNode = ButtonNode::Construct(allocator, " < ", OnBackButtonPressed); backButtonNode->style = rootInterfaceNode->style; backButtonNode->size = ButtonNode::CalculateSize(backButtonNode); backButtonNode->anchor.x = 1; backButtonNode->anchor.y = titleNode->size.y; rootInterfaceNode->AddChild(backButtonNode); - forwardButtonNode = ButtonNode::Construct(allocator, " > "); + forwardButtonNode = ButtonNode::Construct(allocator, " > ", OnForwardButtonPressed); forwardButtonNode->style = rootInterfaceNode->style; forwardButtonNode->size = ButtonNode::CalculateSize(forwardButtonNode); forwardButtonNode->anchor.x = backButtonNode->anchor.x + backButtonNode->size.x + 2; @@ -1029,3 +1029,12 @@ void AppInterface::FocusNode(Node* node) } } +void AppInterface::OnBackButtonPressed(App& app, Node* node) +{ + app.PreviousPage(); +} + +void AppInterface::OnForwardButtonPressed(App& app, Node* node) +{ + app.NextPage(); +} diff --git a/src/Interface.h b/src/Interface.h index 903f06f..348fdf8 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -52,6 +52,8 @@ class AppInterface Node* GetRootInterfaceNode() { return rootInterfaceNode; } bool IsInterfaceNode(Node* node); + URL addressBarURL; + Widget scrollBar; Widget addressBar; Widget backButton; @@ -82,6 +84,9 @@ class AppInterface bool IsOverNode(Node* node, int x, int y); + static void OnBackButtonPressed(App& app, Node* node); + static void OnForwardButtonPressed(App& app, Node* node); + Widget* appInterfaceWidgets[NUM_APP_INTERFACE_WIDGETS]; App& app; @@ -91,7 +96,6 @@ class AppInterface Widget* hoverWidget; int oldButtons; int oldMouseX, oldMouseY; - URL addressBarURL; int scrollBarRelativeClickPositionX; int scrollBarRelativeClickPositionY; int oldPageHeight; diff --git a/src/Node.cpp b/src/Node.cpp index 3af99ac..7c3c3a2 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -9,6 +9,7 @@ #include "Nodes/Block.h" #include "Nodes/Button.h" #include "Nodes/Field.h" +#include "Nodes/Form.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -21,7 +22,8 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new LinkNode(), new BlockNode(), new ButtonNode(), - new TextFieldNode() + new TextFieldNode(), + new FormNode() }; Node::Node(Type inType, void* inData) @@ -116,3 +118,16 @@ void NodeHandler::EndLayoutContext(Layout& layout, Node* node) node->EncapsulateChildren(); } } + +Node* Node::FindParentOfType(Node::Type searchType) +{ + for(Node* node = parent; node; node = node->parent) + { + if (node->type == searchType) + { + return node; + } + } + return NULL; +} + diff --git a/src/Node.h b/src/Node.h index ed349f7..4ad3f3c 100644 --- a/src/Node.h +++ b/src/Node.h @@ -55,6 +55,7 @@ class Node Block, Button, TextField, + Form, NumNodeTypes }; @@ -67,6 +68,7 @@ class Node void AddChild(Node* child); void EncapsulateChildren(); // Sets anchor and size based on children bool IsPointInsideNode(int x, int y); + Node* FindParentOfType(Node::Type searchType); Type type; diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index 155609d..3031cee 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -25,7 +25,7 @@ void ButtonNode::Draw(DrawContext& context, Node* node) } } -Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText) +Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, OnButtonNodeClickedCallback callback) { const char* buttonText = NULL; if (inButtonText) @@ -37,7 +37,7 @@ Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText) } } - ButtonNode::Data* data = allocator.Alloc(buttonText); + ButtonNode::Data* data = allocator.Alloc(buttonText, callback); if (data) { return allocator.Alloc(Node::Button, data); @@ -95,6 +95,13 @@ bool ButtonNode::HandleEvent(Node* node, const Event& event) DrawContext context; event.app.pageRenderer.GenerateDrawContext(context, node); context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); + + ButtonNode::Data* data = static_cast(node->data); + if (data->callback) + { + data->callback(event.app, node); + } + return true; } default: diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h index 73d656f..4d41a8a 100644 --- a/src/Nodes/Button.h +++ b/src/Nodes/Button.h @@ -3,17 +3,20 @@ #include "../Node.h" +typedef void(*OnButtonNodeClickedCallback)(App& app, Node* node); + class ButtonNode : public NodeHandler { public: class Data { public: - Data(const char* inButtonText) : buttonText(inButtonText) {} + Data(const char* inButtonText, OnButtonNodeClickedCallback inCallback) : buttonText(inButtonText), callback(inCallback) {} const char* buttonText; + OnButtonNodeClickedCallback callback; }; - static Node* Construct(Allocator& allocator, const char* buttonText); + static Node* Construct(Allocator& allocator, const char* buttonText, OnButtonNodeClickedCallback callback); virtual void GenerateLayout(Layout& layout, Node* node) override; virtual void Draw(DrawContext& context, Node* node) override; virtual bool CanPick(Node* node) { return true; } diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 59582a6..60f2cee 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -11,9 +11,10 @@ class TextFieldNode : public NodeHandler class Data { public: - Data(char* inBuffer, int inBufferSize) : buffer(inBuffer), bufferSize(inBufferSize) {} + Data(char* inBuffer, int inBufferSize) : buffer(inBuffer), bufferSize(inBufferSize), name(NULL) {} char* buffer; int bufferSize; + char* name; }; static Node* Construct(Allocator& allocator, const char* buttonText); diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp new file mode 100644 index 0000000..589af02 --- /dev/null +++ b/src/Nodes/Form.cpp @@ -0,0 +1,79 @@ +#include "Form.h" +#include "../LinAlloc.h" +#include "../App.h" +#include "../Interface.h" +#include "Field.h" + +Node* FormNode::Construct(Allocator& allocator) +{ + FormNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Form, data); + } + + return nullptr; +} + +void FormNode::BuildAddressParameterList(Node* node, char* address, int& numParams) +{ + if (node->type == Node::TextField) + { + TextFieldNode::Data* fieldData = static_cast(node->data); + + if (fieldData->name && fieldData->buffer) + { + if (numParams == 0) + { + strcat(address, "?"); + } + else + { + strcat(address, "&"); + } + strcat(address, fieldData->name); + strcat(address, "="); + strcat(address, fieldData->buffer); + numParams++; + } + } + + for (node = node->firstChild; node; node = node->next) + { + BuildAddressParameterList(node, address, numParams); + } +} + +void FormNode::SubmitForm(App& app, Node* node) +{ + FormNode::Data* data = static_cast(node->data); + + if (data->method == FormNode::Data::Get) + { + char* address = app.ui.addressBarURL.url; + strcpy(address, data->action); + int numParams = 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(App& app, Node* node) +{ + Node* formNode = node->FindParentOfType(Node::Form); + if (formNode) + { + SubmitForm(app, formNode); + } +} diff --git a/src/Nodes/Form.h b/src/Nodes/Form.h new file mode 100644 index 0000000..4532a00 --- /dev/null +++ b/src/Nodes/Form.h @@ -0,0 +1,36 @@ +#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(App& app, Node* node); + + static void OnSubmitButtonPressed(App& app, Node* node); + +private: + static void BuildAddressParameterList(Node* node, char* address, int& numParams); +}; + +#endif diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index a22c180..14b8bcb 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -1,14 +1,16 @@ #include "LinkNode.h" #include "../LinAlloc.h" +#include "../Event.h" +#include "../App.h" void LinkNode::ApplyStyle(Node* node) { node->style.fontStyle = (FontStyle::Type)(node->style.fontStyle | FontStyle::Underline); } -Node* LinkNode::Construct(Allocator& allocator) +Node* LinkNode::Construct(Allocator& allocator, char* url) { - LinkNode::Data* data = allocator.Alloc(); + LinkNode::Data* data = allocator.Alloc(url); if (data) { return allocator.Alloc(Node::Link, data); @@ -16,3 +18,22 @@ Node* LinkNode::Construct(Allocator& allocator) return nullptr; } +bool LinkNode::HandleEvent(Node* node, const Event& event) +{ + switch (event.type) + { + case Event::MouseRelease: + { + LinkNode::Data* data = static_cast(node->data); + if (data->url) + { + event.app.OpenURL(data->url); + } + return true; + } + default: + break; + } + + return false; +} diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h index 17423ae..f5962cf 100644 --- a/src/Nodes/LinkNode.h +++ b/src/Nodes/LinkNode.h @@ -8,11 +8,13 @@ class LinkNode : public NodeHandler 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); + static Node* Construct(Allocator& allocator, char* url); }; \ No newline at end of file diff --git a/src/Page.cpp b/src/Page.cpp index 66c6678..20215a6 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -21,6 +21,7 @@ #include "Image.h" #include "Nodes/Section.h" #include "Nodes/Text.h" +#include "Nodes/Form.h" #define TOP_MARGIN_PADDING 1 @@ -636,7 +637,10 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) "Break", "Style", "Link", - "Block" + "Block", + "Button", + "TextField", + "Form" }; static const char* sectionTypeNames[] = @@ -670,6 +674,12 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) 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]); } break; + case Node::Form: + { + FormNode::Data* data = static_cast(node->data); + printf("<%s> action: %s\n", nodeTypeNames[node->type], data->action ? data->action : "NONE"); + } + 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; diff --git a/src/Tags.cpp b/src/Tags.cpp index 3b93c7e..fdc1315 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -29,6 +29,7 @@ #include "Nodes/Block.h" #include "Nodes/Button.h" #include "Nodes/Field.h" +#include "Nodes/Form.h" static const HTMLTagHandler* tagHandlers[] = { @@ -194,15 +195,17 @@ void LiTagHandler::Close(class HTMLParser& parser) const 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 = parser.page.allocator.AllocString(attributes.Value()); } } - parser.PushContext(LinkNode::Construct(parser.page.allocator), this); + parser.PushContext(LinkNode::Construct(parser.page.allocator, url), this); //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); //currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Underline); @@ -373,7 +376,7 @@ void ButtonTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } } - parser.EmitNode(ButtonNode::Construct(parser.page.allocator, title)); + parser.EmitNode(ButtonNode::Construct(parser.page.allocator, title, NULL)); } void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -416,23 +419,32 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const case HTMLInputTag::Submit: if (value) { - parser.EmitNode(ButtonNode::Construct(parser.page.allocator, value)); + parser.EmitNode(ButtonNode::Construct(parser.page.allocator, value, FormNode::OnSubmitButtonPressed)); } break; case HTMLInputTag::Text: - parser.EmitNode(TextFieldNode::Construct(parser.page.allocator, value)); + { + Node* fieldNode = TextFieldNode::Construct(parser.page.allocator, value); + if (fieldNode && fieldNode->data) + { + TextFieldNode::Data* fieldData = static_cast(fieldNode->data); + fieldData->name = name; + parser.EmitNode(fieldNode); + } + } break; } } void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - WidgetFormData* formData = parser.page.allocator.Alloc(); + Node* formNode = FormNode::Construct(parser.page.allocator); + 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()) { @@ -444,18 +456,16 @@ void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { 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); } void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const From c60b591f907891b3736decd8345b6e2a973f135f Mon Sep 17 00:00:00 2001 From: James Howard Date: Mon, 10 Jul 2023 22:31:55 +0100 Subject: [PATCH 13/98] Added status bar node - refactor WIP --- project/DOS/Makefile | 5 +- project/Windows/Windows.vcxproj | 3 + src/App.cpp | 9 +- src/App.h | 6 + src/Draw/Surf1bpp.cpp | 16 +-- src/Interface.cpp | 45 ++++-- src/Interface.h | 8 +- src/Node.cpp | 16 ++- src/Node.h | 5 + src/Nodes/Button.cpp | 6 +- src/Nodes/Button.h | 8 +- src/Nodes/Field.cpp | 242 +++++++++++++++++++++++++++++++- src/Nodes/Field.h | 16 ++- src/Nodes/Form.cpp | 7 +- src/Nodes/Form.h | 4 +- src/Nodes/Status.cpp | 30 ++++ src/Nodes/Status.h | 22 +++ src/Tags.cpp | 2 +- src/Windows/WinInput.cpp | 8 ++ 19 files changed, 417 insertions(+), 41 deletions(-) create mode 100644 src/Nodes/Status.cpp create mode 100644 src/Nodes/Status.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 11bda97..8d7f459 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ 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 TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj +objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -91,6 +91,9 @@ Text.obj: $(SRC_PATH)\Nodes\Text.cpp Form.obj: $(SRC_PATH)\Nodes\Form.cpp $(CC) -fo=$@ $(CFLAGS) $< +Status.obj: $(SRC_PATH)\Nodes\Status.cpp + $(CC) -fo=$@ $(CFLAGS) $< + Field.obj: $(SRC_PATH)\Nodes\Field.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 1d69584..37518d0 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -152,6 +152,7 @@ + @@ -178,9 +179,11 @@ + + diff --git a/src/App.cpp b/src/App.cpp index d3c92c2..392c7c4 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -16,9 +16,12 @@ #include "App.h" #include "Platform.h" +App* App::app; + App::App() : page(*this), renderer(*this), pageRenderer(*this), parser(page), ui(*this) { + app = this; requestedNewPage = false; pageHistorySize = 0; pageHistoryPos = -1; @@ -26,14 +29,14 @@ App::App() App::~App() { - + app = NULL; } void App::ResetPage() { page.Reset(); - renderer.Reset(); + //renderer.Reset(); parser.Reset(); ui.Reset(); } @@ -105,7 +108,7 @@ void App::Run(int argc, char* argv[]) } } if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) - renderer.SetStatus(loadTask.request->GetStatusString()); + ui.SetStatusMessage(loadTask.request->GetStatusString()); ui.Update(); } diff --git a/src/App.h b/src/App.h index be3f808..7513f3f 100644 --- a/src/App.h +++ b/src/App.h @@ -72,6 +72,8 @@ class App void ShowErrorPage(const char* message); + static App& Get() { return *app; } + Page page; Renderer renderer; PageRenderer pageRenderer; @@ -91,6 +93,10 @@ class App URL pageHistory[MAX_PAGE_URL_HISTORY]; int pageHistorySize; int pageHistoryPos; + + static App* app; + }; + #endif diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index e3d8922..17d80d8 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -149,13 +149,13 @@ void DrawSurface_1BPP::FillRect(DrawContext& context, int x, int y, int width, i height -= (context.clipTop - y); y = context.clipTop; } - if (x + width >= context.clipRight) + if (x + width > context.clipRight) { - width = context.clipRight - 1 - x; + width = context.clipRight - x; } - if (y + height >= context.clipBottom) + if (y + height > context.clipBottom) { - height = context.clipBottom - 1 - y; + height = context.clipBottom - y; } if (width <= 0 || height <= 0) { @@ -376,13 +376,13 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, height -= (context.clipTop - y); y = context.clipTop; } - if (x + width >= context.clipRight) + if (x + width > context.clipRight) { - width = context.clipRight - 1 - x; + width = context.clipRight - x; } - if (y + height >= context.clipBottom) + if (y + height > context.clipBottom) { - height = context.clipBottom - 1 - y; + height = context.clipBottom - y; } if (width <= 0 || height <= 0) { diff --git a/src/Interface.cpp b/src/Interface.cpp index fd4e92d..1eaee28 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -20,6 +20,7 @@ #include "Nodes/Button.h" #include "Nodes/Field.h" #include "Nodes/Text.h" +#include "Nodes/Status.h" #include "Draw/Surface.h" #include "DataPack.h" #include "Event.h" @@ -165,6 +166,7 @@ void AppInterface::Update() { // For debugging picking + Platform::input->HideMouse(); if (hoverNode) { DrawContext context; @@ -177,6 +179,7 @@ void AppInterface::Update() app.pageRenderer.GenerateDrawContext(context, oldHoverNode); context.surface->InvertRect(context, oldHoverNode->anchor.x, oldHoverNode->anchor.y, oldHoverNode->size.x, oldHoverNode->size.y); } + Platform::input->ShowMouse(); } } @@ -252,7 +255,7 @@ void AppInterface::Update() { 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); + app.ui.SetStatusMessage(tempMessage); } break; @@ -438,7 +441,7 @@ void AppInterface::ActivateWidget(Widget* widget) if (widget->GetLinkURL()) { InvertWidgetsWithLinkURL(widget->GetLinkURL()); - app.renderer.SetStatus(URL::GenerateFromRelative(app.page.pageURL.url, widget->GetLinkURL()).url); + SetStatusMessage(URL::GenerateFromRelative(app.page.pageURL.url, widget->GetLinkURL()).url); } break; } @@ -817,6 +820,7 @@ void AppInterface::SubmitForm(WidgetFormData* form) void AppInterface::UpdateAddressBar(const URL& url) { addressBarURL = url; + addressBarNode->Redraw(); //app.renderer.RedrawWidget(&addressBar); } @@ -971,7 +975,7 @@ void AppInterface::GenerateInterfaceNodes() forwardButtonNode->anchor.y = titleNode->size.y; rootInterfaceNode->AddChild(forwardButtonNode); - addressBarNode = TextFieldNode::Construct(allocator, "http://www.example.com"); + addressBarNode = TextFieldNode::Construct(allocator, addressBarURL.url, MAX_URL_LENGTH - 1, OnAddressBarSubmit); addressBarNode->style = rootInterfaceNode->style; addressBarNode->anchor.x = forwardButtonNode->anchor.x + forwardButtonNode->size.x + 2; addressBarNode->anchor.y = titleNode->size.y; @@ -979,12 +983,20 @@ void AppInterface::GenerateInterfaceNodes() addressBarNode->size.y = backButtonNode->size.y; rootInterfaceNode->AddChild(addressBarNode); + statusBarNode = StatusBarNode::Construct(allocator); + statusBarNode->style = rootInterfaceNode->style; + statusBarNode->size.x = Platform::video->screenWidth; + statusBarNode->size.y = interfaceFont->glyphHeight + 2; + statusBarNode->anchor.x = 0; + statusBarNode->anchor.y = Platform::video->screenHeight - statusBarNode->size.y; + rootInterfaceNode->AddChild(statusBarNode); + rootInterfaceNode->EncapsulateChildren(); windowRect.x = 0; windowRect.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; windowRect.width = Platform::video->screenWidth - 16; - windowRect.height = Platform::video->screenHeight - windowRect.y; + windowRect.height = Platform::video->screenHeight - windowRect.y - statusBarNode->size.y; } void AppInterface::DrawInterfaceNodes(DrawContext& context) @@ -1029,12 +1041,29 @@ void AppInterface::FocusNode(Node* node) } } -void AppInterface::OnBackButtonPressed(App& app, Node* node) +void AppInterface::OnBackButtonPressed(Node* node) +{ + App::Get().PreviousPage(); +} + +void AppInterface::OnForwardButtonPressed(Node* node) { - app.PreviousPage(); + App::Get().NextPage(); } -void AppInterface::OnForwardButtonPressed(App& app, Node* node) +void AppInterface::OnAddressBarSubmit(Node* node) { - app.NextPage(); + App& app = App::Get(); + TextFieldNode::Data* data = static_cast(node->data); + app.OpenURL(data->buffer); +} + +void AppInterface::SetStatusMessage(const char* message) +{ + StatusBarNode::Data* data = static_cast(statusBarNode->data); + if (strcmp(data->message, message)) + { + strcpy(data->message, message); + statusBarNode->Redraw(); + } } diff --git a/src/Interface.h b/src/Interface.h index 348fdf8..cdfa8f3 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -39,6 +39,7 @@ class AppInterface void DrawInterfaceNodes(DrawContext& context); void DrawInterfaceWidgets(); void UpdateAddressBar(const URL& url); + void SetStatusMessage(const char* message); void UpdatePageScrollBar(); Widget* GetActiveWidget() { return activeWidget; } @@ -84,8 +85,10 @@ class AppInterface bool IsOverNode(Node* node, int x, int y); - static void OnBackButtonPressed(App& app, Node* node); - static void OnForwardButtonPressed(App& app, Node* node); + static void OnBackButtonPressed(Node* node); + static void OnForwardButtonPressed(Node* node); + static void OnAddressBarSubmit(Node* node); + Widget* appInterfaceWidgets[NUM_APP_INTERFACE_WIDGETS]; App& app; @@ -114,6 +117,7 @@ class AppInterface Node* backButtonNode; Node* forwardButtonNode; Node* addressBarNode; + Node* statusBarNode; char titleBuffer[MAX_TITLE_LENGTH]; }; diff --git a/src/Node.cpp b/src/Node.cpp index 7c3c3a2..c93217f 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -1,4 +1,6 @@ #include "Page.h" +#include "App.h" +#include "Draw/Surface.h" #include "Node.h" #include "Nodes/Text.h" #include "Nodes/Section.h" @@ -10,6 +12,7 @@ #include "Nodes/Button.h" #include "Nodes/Field.h" #include "Nodes/Form.h" +#include "Nodes/Status.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -23,7 +26,8 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new BlockNode(), new ButtonNode(), new TextFieldNode(), - new FormNode() + new FormNode(), + new StatusBarNode() }; Node::Node(Type inType, void* inData) @@ -131,3 +135,13 @@ Node* Node::FindParentOfType(Node::Type searchType) return NULL; } +void Node::Redraw() +{ + DrawContext context; + + Platform::input->HideMouse(); + App::Get().pageRenderer.GenerateDrawContext(context, this); + Handler().Draw(context, this); + + Platform::input->ShowMouse(); +} diff --git a/src/Node.h b/src/Node.h index 4ad3f3c..e539fdd 100644 --- a/src/Node.h +++ b/src/Node.h @@ -56,6 +56,7 @@ class Node Button, TextField, Form, + StatusBar, NumNodeTypes }; @@ -69,6 +70,7 @@ class Node void EncapsulateChildren(); // Sets anchor and size based on children bool IsPointInsideNode(int x, int y); Node* FindParentOfType(Node::Type searchType); + void Redraw(); Type type; @@ -87,4 +89,7 @@ class Node static NodeHandler* nodeHandlers[Node::NumNodeTypes]; }; +typedef void(*NodeCallbackFunction)(Node* node); + + #endif diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index 3031cee..1c01daf 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -25,7 +25,7 @@ void ButtonNode::Draw(DrawContext& context, Node* node) } } -Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, OnButtonNodeClickedCallback callback) +Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, NodeCallbackFunction callback) { const char* buttonText = NULL; if (inButtonText) @@ -97,9 +97,9 @@ bool ButtonNode::HandleEvent(Node* node, const Event& event) context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); ButtonNode::Data* data = static_cast(node->data); - if (data->callback) + if (data->onClick) { - data->callback(event.app, node); + data->onClick(node); } return true; diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h index 4d41a8a..3ff3d8d 100644 --- a/src/Nodes/Button.h +++ b/src/Nodes/Button.h @@ -3,20 +3,18 @@ #include "../Node.h" -typedef void(*OnButtonNodeClickedCallback)(App& app, Node* node); - class ButtonNode : public NodeHandler { public: class Data { public: - Data(const char* inButtonText, OnButtonNodeClickedCallback inCallback) : buttonText(inButtonText), callback(inCallback) {} + Data(const char* inButtonText, NodeCallbackFunction inOnClick) : buttonText(inButtonText), onClick(inOnClick) {} const char* buttonText; - OnButtonNodeClickedCallback callback; + NodeCallbackFunction onClick; }; - static Node* Construct(Allocator& allocator, const char* buttonText, OnButtonNodeClickedCallback callback); + 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; } diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index b2dcf49..4007b3f 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -4,6 +4,8 @@ #include "../LinAlloc.h" #include "../Layout.h" #include "../Platform.h" +#include "../KeyCodes.h" +#include "../App.h" void TextFieldNode::Draw(DrawContext& context, Node* node) { @@ -12,14 +14,21 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); uint8_t textColour = 0; uint8_t buttonOutlineColour = 0; + uint8_t clearColour = 1; + 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); context.surface->DrawString(context, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); + + if (node == App::Get().ui.GetFocusedNode()) + { + DrawCursor(context, node, false); + } } -Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue) +Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue, NodeCallbackFunction onSubmit) { char* buffer = (char*) allocator.Alloc(DEFAULT_TEXT_FIELD_BUFFER_SIZE); if (!buffer) @@ -27,7 +36,7 @@ Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue) return nullptr; } - TextFieldNode::Data* data = allocator.Alloc(buffer, DEFAULT_TEXT_FIELD_BUFFER_SIZE); + TextFieldNode::Data* data = allocator.Alloc(buffer, DEFAULT_TEXT_FIELD_BUFFER_SIZE, onSubmit); if (data) { if (inValue) @@ -44,6 +53,17 @@ Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue) 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); @@ -61,3 +81,221 @@ void TextFieldNode::GenerateLayout(Layout& layout, Node* node) layout.ProgressCursor(node, node->size.x, node->size.y); } +bool TextFieldNode::HandleEvent(Node* node, const Event& event) +{ + TextFieldNode::Data* data = static_cast(node->data); + + switch (event.type) + { + case Event::MouseRelease: + cursorPosition = -1; + break; + case Event::KeyPress: + if (event.key >= 32 && event.key < 128) + { + if (cursorPosition == -1) + { + data->buffer[0] = '\0'; + cursorPosition = 0; + node->Redraw(); + } + + 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); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition - 1, true); + //app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition - 1); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + else if (event.key == KEYCODE_BACKSPACE) + { + if (cursorPosition == -1) + { + data->buffer[0] = '\0'; + node->Redraw(); + MoveCursorPosition(node, 0); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + 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); + //app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + else if (event.key == KEYCODE_DELETE) + { + int len = strlen(data->buffer); + if (cursorPosition == -1) + { + data->buffer[0] = '\0'; + node->Redraw(); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + else if (cursorPosition < len) + { + for (int n = cursorPosition; n < len; n++) + { + data->buffer[n] = data->buffer[n + 1]; + } + RedrawModified(node, cursorPosition); + //app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + else if (event.key == KEYCODE_ENTER) + { + if (data->onSubmit) + { + data->onSubmit(node); + } + /*if (activeWidget == &addressBar) + { + app.OpenURL(addressBarURL.url); + } + else if (activeWidget->textField->form) + { + SubmitForm(activeWidget->textField->form); + } + DeactivateWidget();*/ + return true; + } + else if (event.key == KEYCODE_ARROW_LEFT) + { + if (cursorPosition == -1) + { + //app.renderer.RedrawWidget(activeWidget); + MoveCursorPosition(node, strlen(data->buffer)); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + else if (cursorPosition > 0) + { + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); + MoveCursorPosition(node, cursorPosition - 1); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + else if (event.key == KEYCODE_ARROW_RIGHT) + { + if (cursorPosition == -1) + { + //app.renderer.RedrawWidget(activeWidget); + MoveCursorPosition(node, strlen(data->buffer)); + + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + else if (cursorPosition < strlen(data->buffer)) + { + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); + MoveCursorPosition(node, cursorPosition + 1); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + else if (event.key == KEYCODE_END) + { + if (cursorPosition == -1) + { + //app.renderer.RedrawWidget(activeWidget); + MoveCursorPosition(node, strlen(data->buffer)); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + else + { + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); + MoveCursorPosition(node, strlen(data->buffer)); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + else if (event.key == KEYCODE_HOME) + { + if (cursorPosition == -1) + { + //app.renderer.RedrawWidget(activeWidget); + MoveCursorPosition(node, strlen(data->buffer)); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + else + { + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); + MoveCursorPosition(node, 0); + //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); + } + return true; + } + break; + default: + break; + } + + return false; +} + +void TextFieldNode::DrawCursor(DrawContext& context, Node* node, bool clear) +{ + TextFieldNode::Data* data = static_cast(node->data); + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + + int x = node->anchor.x + 2; + int height = font->glyphHeight; + + for (int n = 0; n < cursorPosition; n++) + { + if (!data->buffer[n]) + break; + x += font->GetGlyphWidth(data->buffer[n]); + } + + if (x >= node->anchor.x + node->size.x - 1) + { + return; + } + + int y = node->anchor.y + 2; + + context.surface->VLine(context, x, y, height, clear ? 1 : 0); +} + +void TextFieldNode::MoveCursorPosition(Node* node, int newPosition) +{ + Platform::input->HideMouse(); + + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + DrawCursor(context, node, true); + cursorPosition = newPosition; + DrawCursor(context, node, false); + + Platform::input->ShowMouse(); +} + +void TextFieldNode::RedrawModified(Node* node, int position) +{ + DrawContext context; + + Platform::input->HideMouse(); + App::Get().pageRenderer.GenerateDrawContext(context, node); + Draw(context, node); + + Platform::input->ShowMouse(); +} diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 60f2cee..164b614 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -11,16 +11,28 @@ class TextFieldNode : public NodeHandler class Data { public: - Data(char* inBuffer, int inBufferSize) : buffer(inBuffer), bufferSize(inBufferSize), name(NULL) {} + Data(char* inBuffer, int inBufferSize, NodeCallbackFunction inOnSubmit) : buffer(inBuffer), bufferSize(inBufferSize), name(NULL), onSubmit(inOnSubmit) {} char* buffer; int bufferSize; char* name; + NodeCallbackFunction onSubmit; }; - static Node* Construct(Allocator& allocator, const char* buttonText); + 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) { return true; } + virtual bool HandleEvent(Node* node, const Event& event) override; + +private: + int cursorPosition; + + void DrawCursor(DrawContext& context, Node* node, bool clear); + void MoveCursorPosition(Node* node, int newPosition); + void RedrawModified(Node* node, int position); + + }; #endif diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp index 589af02..59299c5 100644 --- a/src/Nodes/Form.cpp +++ b/src/Nodes/Form.cpp @@ -44,9 +44,10 @@ void FormNode::BuildAddressParameterList(Node* node, char* address, int& numPara } } -void FormNode::SubmitForm(App& app, Node* node) +void FormNode::SubmitForm(Node* node) { FormNode::Data* data = static_cast(node->data); + App& app = App::Get(); if (data->method == FormNode::Data::Get) { @@ -69,11 +70,11 @@ void FormNode::SubmitForm(App& app, Node* node) } } -void FormNode::OnSubmitButtonPressed(App& app, Node* node) +void FormNode::OnSubmitButtonPressed(Node* node) { Node* formNode = node->FindParentOfType(Node::Form); if (formNode) { - SubmitForm(app, formNode); + SubmitForm(formNode); } } diff --git a/src/Nodes/Form.h b/src/Nodes/Form.h index 4532a00..cf43667 100644 --- a/src/Nodes/Form.h +++ b/src/Nodes/Form.h @@ -25,9 +25,9 @@ class FormNode : public NodeHandler static Node* Construct(Allocator& allocator); - static void SubmitForm(App& app, Node* node); + static void SubmitForm(Node* node); - static void OnSubmitButtonPressed(App& app, Node* node); + static void OnSubmitButtonPressed(Node* node); private: static void BuildAddressParameterList(Node* node, char* address, int& numParams); diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp new file mode 100644 index 0000000..d1860f8 --- /dev/null +++ b/src/Nodes/Status.cpp @@ -0,0 +1,30 @@ +#include "Status.h" +#include "../LinAlloc.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 = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + uint8_t textColour = 1; + uint8_t clearColour = 0; + context.surface->FillRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y, clearColour); + context.surface->DrawString(context, font, data->message, node->anchor.x + 1, node->anchor.y + 1, textColour, node->style.fontStyle); +} + + diff --git a/src/Nodes/Status.h b/src/Nodes/Status.h new file mode 100644 index 0000000..6610026 --- /dev/null +++ b/src/Nodes/Status.h @@ -0,0 +1,22 @@ +#ifndef _STATUS_H_ +#define _STATUS_H_ + +#include "../Node.h" + +#define MAX_STATUS_BAR_MESSAGE_LENGTH 80 + +class StatusBarNode : public NodeHandler +{ +public: + class Data + { + public: + Data() { message[0] = '\0'; } + char message[MAX_STATUS_BAR_MESSAGE_LENGTH]; + }; + + static Node* Construct(Allocator& allocator); + virtual void Draw(DrawContext& context, Node* node) override; +}; + +#endif diff --git a/src/Tags.cpp b/src/Tags.cpp index fdc1315..1a22192 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -424,7 +424,7 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const break; case HTMLInputTag::Text: { - Node* fieldNode = TextFieldNode::Construct(parser.page.allocator, value); + Node* fieldNode = TextFieldNode::Construct(parser.page.allocator, value, FormNode::OnSubmitButtonPressed); if (fieldNode && fieldNode->data) { TextFieldNode::Data* fieldData = static_cast(fieldNode->data); diff --git a/src/Windows/WinInput.cpp b/src/Windows/WinInput.cpp index c4637ad..ab6bba0 100644 --- a/src/Windows/WinInput.cpp +++ b/src/Windows/WinInput.cpp @@ -142,6 +142,10 @@ InputButtonCode WindowsInputDriver::TranslateCode(WPARAM code) 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 +156,10 @@ 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; default: return 0; } From fd1c5ca144e382466f749c876e9dcdc0c06597d5 Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 19 Jul 2023 08:20:26 +0100 Subject: [PATCH 14/98] First pass scroll bar node --- project/DOS/DOS.vcxproj | 2 ++ project/DOS/Makefile | 5 ++- project/Windows/Windows.vcxproj | 2 ++ src/Draw/Surf1bpp.cpp | 54 +++++++++++++++++++++++++++++++ src/Draw/Surf1bpp.h | 2 +- src/Draw/Surface.h | 1 + src/Interface.cpp | 57 +++++++++++++++++++++++++++++---- src/Interface.h | 8 +++++ src/Node.cpp | 4 ++- src/Node.h | 1 + src/Nodes/Button.cpp | 2 +- src/Nodes/Scroll.cpp | 47 +++++++++++++++++++++++++++ src/Nodes/Scroll.h | 21 ++++++++++++ src/Nodes/Status.cpp | 7 ++-- src/Render.cpp | 30 ++--------------- src/Render.h | 6 ---- src/Windows/WinVid.cpp | 8 +++-- 17 files changed, 208 insertions(+), 49 deletions(-) create mode 100644 src/Nodes/Scroll.cpp create mode 100644 src/Nodes/Scroll.h diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index e8e6d6a..e94abcc 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -112,6 +112,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 8d7f459..9df21c9 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ 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 TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj +objects = MicroWeb.obj App.obj Parser.obj Renderer.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj Scroll.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -94,6 +94,9 @@ Form.obj: $(SRC_PATH)\Nodes\Form.cpp 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) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 37518d0..7c3f35d 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -151,6 +151,7 @@ + @@ -182,6 +183,7 @@ + diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 17d80d8..f03bbfa 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -424,3 +424,57 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, } } +void DrawSurface_1BPP::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; + + 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; + } +} diff --git a/src/Draw/Surf1bpp.h b/src/Draw/Surf1bpp.h index 0da60df..e9021f1 100644 --- a/src/Draw/Surf1bpp.h +++ b/src/Draw/Surf1bpp.h @@ -15,7 +15,7 @@ class DrawSurface_1BPP : public DrawSurface 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); uint8_t** lines; }; diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index 1353557..e1b51af 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -29,6 +29,7 @@ class DrawSurface 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; int width, height; }; diff --git a/src/Interface.cpp b/src/Interface.cpp index 1eaee28..b1f8ff3 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -21,6 +21,7 @@ #include "Nodes/Field.h" #include "Nodes/Text.h" #include "Nodes/Status.h" +#include "Nodes/Scroll.h" #include "Draw/Surface.h" #include "DataPack.h" #include "Event.h" @@ -52,6 +53,7 @@ void AppInterface::Init() void AppInterface::Reset() { + scrollPositionY = 0; oldPageHeight = 0; if (activeWidget && !activeWidget->isInterfaceWidget) { @@ -267,7 +269,7 @@ void AppInterface::Update() if (scrollDelta) { - app.pageRenderer.ScrollRelative(scrollDelta); + ScrollRelative(scrollDelta); //app.renderer.Scroll(scrollDelta); } } @@ -339,7 +341,7 @@ bool AppInterface::IsOverNode(Node* node, int x, int y) else { x -= windowRect.x; - y -= windowRect.y - app.pageRenderer.GetScrollPositionY(); + y -= windowRect.y - scrollPositionY; return node->IsPointInsideNode(x, y); } } @@ -356,7 +358,7 @@ Node* AppInterface::PickNode(int x, int y) 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 + app.pageRenderer.GetScrollPositionY(); + int pageY = y - windowRect.y + scrollPositionY; Node* pageRootNode = app.page.GetRootNode(); return pageRootNode->Handler().Pick(pageRootNode, pageX, pageY); @@ -453,7 +455,7 @@ void AppInterface::InvertWidgetsWithLinkURL(const char* url) for (int n = app.renderer.GetPageTopWidgetIndex(); n < app.page.numFinishedWidgets; n++) { Widget* pageWidget = &app.page.widgets[n]; - if (pageWidget->y > app.renderer.GetScrollPosition() + Platform::video->windowHeight) + if (pageWidget->y > scrollPositionY + Platform::video->windowHeight) { break; } @@ -826,7 +828,7 @@ void AppInterface::UpdateAddressBar(const URL& url) void AppInterface::UpdatePageScrollBar() { - int pageHeight = app.page.GetPageHeight(); + /*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) @@ -844,6 +846,14 @@ void AppInterface::UpdatePageScrollBar() } app.renderer.RedrawScrollBar(); + */ + ScrollBarNode::Data* data = static_cast(scrollBarNode->data); + int maxScrollHeight = app.page.GetRootNode()->size.y - app.ui.windowRect.height; + if (maxScrollHeight < 0) + maxScrollHeight = 0; + data->scrollPosition = scrollPositionY; + data->maxScroll = maxScrollHeight; + scrollBarNode->Redraw(); } void AppInterface::CycleWidgets(int direction) @@ -991,11 +1001,19 @@ void AppInterface::GenerateInterfaceNodes() statusBarNode->anchor.y = Platform::video->screenHeight - statusBarNode->size.y; rootInterfaceNode->AddChild(statusBarNode); + scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight); + scrollBarNode->style = rootInterfaceNode->style; + scrollBarNode->anchor.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; + 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); + rootInterfaceNode->EncapsulateChildren(); windowRect.x = 0; windowRect.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; - windowRect.width = Platform::video->screenWidth - 16; + windowRect.width = Platform::video->screenWidth - scrollBarNode->size.x; windowRect.height = Platform::video->screenHeight - windowRect.y - statusBarNode->size.y; } @@ -1067,3 +1085,30 @@ void AppInterface::SetStatusMessage(const char* message) statusBarNode->Redraw(); } } + +void AppInterface::ScrollRelative(int delta) +{ + scrollPositionY += delta; + if (scrollPositionY < 0) + scrollPositionY = 0; + + int maxScrollY = app.page.GetRootNode()->size.y - app.ui.windowRect.height; + if (maxScrollY > 0 && scrollPositionY > maxScrollY) + { + scrollPositionY = maxScrollY; + } + + UpdatePageScrollBar(); + + DrawContext context; + app.pageRenderer.GenerateDrawContext(context, NULL); + context.drawOffsetY = 0; + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 1); + app.pageRenderer.GenerateDrawContext(context, NULL); + app.pageRenderer.DrawAll(context, app.page.GetRootNode()); +} + +void AppInterface::ScrollAbsolute(int position) +{ + +} diff --git a/src/Interface.h b/src/Interface.h index cdfa8f3..3ef2f3e 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -53,6 +53,11 @@ class AppInterface Node* GetRootInterfaceNode() { return rootInterfaceNode; } bool IsInterfaceNode(Node* node); + int GetScrollPositionY() { return scrollPositionY; } + void ScrollRelative(int delta); + void ScrollAbsolute(int position); + + URL addressBarURL; Widget scrollBar; @@ -118,6 +123,9 @@ class AppInterface Node* forwardButtonNode; Node* addressBarNode; Node* statusBarNode; + Node* scrollBarNode; + + int scrollPositionY; char titleBuffer[MAX_TITLE_LENGTH]; }; diff --git a/src/Node.cpp b/src/Node.cpp index c93217f..34eb67a 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -13,6 +13,7 @@ #include "Nodes/Field.h" #include "Nodes/Form.h" #include "Nodes/Status.h" +#include "Nodes/Scroll.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -27,7 +28,8 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new ButtonNode(), new TextFieldNode(), new FormNode(), - new StatusBarNode() + new StatusBarNode(), + new ScrollBarNode() }; Node::Node(Type inType, void* inData) diff --git a/src/Node.h b/src/Node.h index e539fdd..06b2038 100644 --- a/src/Node.h +++ b/src/Node.h @@ -57,6 +57,7 @@ class Node TextField, Form, StatusBar, + ScrollBar, NumNodeTypes }; diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index 1c01daf..04f8412 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -60,7 +60,7 @@ Coord ButtonNode::CalculateSize(Node* node) Coord result; result.x = labelWidth + 16; - result.y = labelHeight + 5; + result.y = labelHeight + 4; return result; } diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp new file mode 100644 index 0000000..325c9e0 --- /dev/null +++ b/src/Nodes/Scroll.cpp @@ -0,0 +1,47 @@ +#include "Scroll.h" +#include "../LinAlloc.h" +#include "../DataPack.h" +#include "../App.h" +#include "../Interface.h" +#include "../Draw/Surface.h" + +Node* ScrollBarNode::Construct(Allocator& allocator, int scrollPosition, int maxScroll) +{ + ScrollBarNode::Data* data = allocator.Alloc(scrollPosition, maxScroll); + if (data) + { + return allocator.Alloc(Node::ScrollBar, data); + } + + return nullptr; +} + +void ScrollBarNode::Draw(DrawContext& context, Node* node) +{ + ScrollBarNode::Data* data = static_cast(node->data); + + const int minWidgetSize = 15; + const int maxWidgetSize = node->size.y; + if (data->maxScroll == 0) + { + context.surface->VerticalScrollBar(context, node->anchor.x, node->anchor.y, node->size.y, 0, maxWidgetSize); + } + else + { + int widgetSize = (int)(((int32_t)node->size.y * node->size.y) / data->maxScroll); + if (widgetSize < minWidgetSize) + { + widgetSize = minWidgetSize; + } + int maxWidgetPosition = node->size.y - widgetSize; + int widgetPosition = (int32_t)maxWidgetPosition * data->scrollPosition / data->maxScroll; + + context.surface->VerticalScrollBar(context, node->anchor.x, node->anchor.y, node->size.y, widgetPosition, widgetSize); + } + + +// context.surface->FillRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y, 0); + +} + + diff --git a/src/Nodes/Scroll.h b/src/Nodes/Scroll.h new file mode 100644 index 0000000..32c7355 --- /dev/null +++ b/src/Nodes/Scroll.h @@ -0,0 +1,21 @@ +#ifndef _SCROLL_H_ +#define _SCROLL_H_ + +#include "../Node.h" + +class ScrollBarNode : public NodeHandler +{ +public: + class Data + { + public: + Data(int inScrollPosition, int inMaxScroll) : scrollPosition(inScrollPosition), maxScroll(inMaxScroll) { } + int scrollPosition; + int maxScroll; + }; + + static Node* Construct(Allocator& allocator, int scrollPosition = 0, int maxScroll = 0); + virtual void Draw(DrawContext& context, Node* node) override; +}; + +#endif diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp index d1860f8..8a7f014 100644 --- a/src/Nodes/Status.cpp +++ b/src/Nodes/Status.cpp @@ -21,9 +21,10 @@ void StatusBarNode::Draw(DrawContext& context, Node* node) StatusBarNode::Data* data = static_cast(node->data); Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = 1; - uint8_t clearColour = 0; - context.surface->FillRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y, clearColour); + uint8_t textColour = 0; + uint8_t clearColour = 1; + 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); context.surface->DrawString(context, font, data->message, node->anchor.x + 1, node->anchor.y + 1, textColour, node->style.fontStyle); } diff --git a/src/Render.cpp b/src/Render.cpp index 72811ac..15f5e32 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -5,7 +5,7 @@ #include "Draw/Surface.h" PageRenderer::PageRenderer(App& inApp) - : app(inApp), scrollPositionY(0) + : app(inApp) { } @@ -17,7 +17,6 @@ void PageRenderer::Init() void PageRenderer::Reset() { - scrollPositionY = 0; } void PageRenderer::Update() @@ -25,31 +24,6 @@ void PageRenderer::Update() } -void PageRenderer::ScrollRelative(int delta) -{ - scrollPositionY += delta; - if (scrollPositionY < 0) - scrollPositionY = 0; - - int maxScrollY = app.page.GetRootNode()->size.y - app.ui.windowRect.height; - if (maxScrollY > 0 && scrollPositionY > maxScrollY) - { - scrollPositionY = maxScrollY; - } - - DrawContext context; - GenerateDrawContext(context, NULL); - context.drawOffsetY = 0; - context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 1); - GenerateDrawContext(context, NULL); - DrawAll(context, app.page.GetRootNode()); -} - -void PageRenderer::ScrollAbsolute(int position) -{ - -} - void PageRenderer::DrawAll(DrawContext& context, Node* node) { while (node) @@ -83,6 +57,6 @@ void PageRenderer::GenerateDrawContext(DrawContext& context, Node* node) context.clipTop = windowRect.y; context.clipBottom = windowRect.y + windowRect.height; context.drawOffsetX = windowRect.x; - context.drawOffsetY = windowRect.y - scrollPositionY; + context.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); } } diff --git a/src/Render.h b/src/Render.h index 80d7494..4bdfe97 100644 --- a/src/Render.h +++ b/src/Render.h @@ -14,18 +14,12 @@ class PageRenderer void Reset(); void Update(); - void ScrollRelative(int delta); - void ScrollAbsolute(int position); - void DrawAll(DrawContext& context, Node* node); - - int GetScrollPositionY() { return scrollPositionY; } void GenerateDrawContext(DrawContext& context, Node* node); private: App& app; - int scrollPositionY; }; #endif diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index b8896eb..a7b4434 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -25,8 +25,10 @@ #define WINDOW_HEIGHT 168 #define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 600 +//#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 @@ -71,6 +73,8 @@ WindowsVideoDriver::WindowsVideoDriver() //bulletImage = &CGA_Bullet; Assets.Load("Default.dat"); +// Assets.Load("Lowres.dat"); +// Assets.Load("CGA.dat"); isTextMode = false; } From bce397420ab587b46f5d08d7e2418e45e9c3f610 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 20 Jul 2023 12:21:50 +0100 Subject: [PATCH 15/98] Added image support for data pack system --- project/Windows/Windows.vcxproj | 4 +- src/DataPack.cpp | 2 + src/DataPack.h | 2 + src/Draw/Surf1bpp.cpp | 183 +++++++++++++++++++------------- src/Image.h | 3 +- src/Render.cpp | 5 + tools/AssetGen.cpp | 3 + tools/ImageGen.cpp | 53 +++++++++ 8 files changed, 181 insertions(+), 74 deletions(-) diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 7c3f35d..a4b8c56 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -23,7 +23,7 @@ Win32Proj {05554dc2-eb0d-4107-856c-c5be0836f410} Windows - 10.0.15063.0 + 10.0.18362.0 @@ -43,7 +43,7 @@ Application true Unicode - v141 + v142 Application diff --git a/src/DataPack.cpp b/src/DataPack.cpp index ecc0d3d..a0f27d7 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -43,6 +43,8 @@ bool DataPack::Load(const char* path) linkCursor = (MouseCursorData*)(&rawData[header.linkCursorOffset]); textSelectCursor = (MouseCursorData*)(&rawData[header.textSelectCursorOffset]); + imageIcon = (Image*)(&rawData[header.imageIconOffset]); + for (int n = 0; n < NUM_FONT_SIZES; n++) { memcpy(&fonts[n], &rawData[header.fontOffsets[n]], sizeof(FontMetaData)); diff --git a/src/DataPack.h b/src/DataPack.h index 3c4da10..6051c47 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -44,6 +44,7 @@ struct DataPackHeader uint16_t pointerCursorOffset; uint16_t linkCursorOffset; uint16_t textSelectCursorOffset; + uint16_t imageIconOffset; }; struct DataPack @@ -51,6 +52,7 @@ struct DataPack MouseCursorData* pointerCursor; MouseCursorData* linkCursor; MouseCursorData* textSelectCursor; + Image* imageIcon; Font fonts[NUM_FONT_SIZES]; Font monoFonts[NUM_FONT_SIZES]; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index f03bbfa..e966dec 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -1,5 +1,6 @@ #include "Surf1bpp.h" #include "../Font.h" +#include "../Image.h" DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) @@ -232,28 +233,16 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* int startX = x; uint8_t glyphHeight = font->glyphHeight; - if (x >= context.clipRight) + if (x >= context.clipRight || y >= context.clipBottom || y + glyphHeight <= context.clipTop) { - return; - } - if (y >= context.clipBottom) - { - return; - } - if (y + glyphHeight > context.clipBottom) - { - glyphHeight = (uint8_t)(context.clipBottom - y); - } - if (y + glyphHeight < context.clipTop) - { - return; + return; // No need to draw anything if fully outside the clipping region. } - uint8_t firstLine = 0; + // Adjust glyphHeight to fit within the clipping region. if (y < context.clipTop) { - firstLine += context.clipTop - y; - y += firstLine; + glyphHeight -= (context.clipTop - y); + y = context.clipTop; } while (*text) @@ -261,7 +250,7 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* char c = *text++; if (c < 32 || c >= 128) { - continue; + continue; // Skip non-printable characters. } char index = c - 32; @@ -269,98 +258,150 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* if (glyphWidth == 0) { - continue; + continue; // Skip zero-width characters. } uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - glyphData += (firstLine * font->glyphWidthBytes); - int outY = y; - uint8_t* VRAMptr = lines[y] + (x >> 3); + uint8_t* VRAMptr = lines[outY] + (x >> 3); + uint8_t writeOffset = (uint8_t)(x) & 0x7; - if (!colour) + if (style & FontStyle::Italic && outY - y < (font->glyphHeight >> 1)) { - for (uint8_t j = firstLine; j < glyphHeight; j++) + writeOffset++; + } + + for (uint8_t j = 0; j < glyphHeight; j++) + { + for (uint8_t i = 0; i < font->glyphWidthBytes; i++) { - uint8_t writeOffset = (uint8_t)(x) & 0x7; + uint8_t glyphPixels = *glyphData++; - if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) + if (style & FontStyle::Bold) { - writeOffset++; + glyphPixels |= (glyphPixels >> 1); } - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + if (!colour) { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - 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)) + else { - 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)); } - - outY++; - VRAMptr = lines[outY] + (x >> 3); } - } - x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; + outY++; + VRAMptr = lines[outY] + (x >> 3); } + x += glyphWidth + (style & FontStyle::Bold ? 1 : 0); + if (x >= context.clipRight) { - break; + break; // No need to continue drawing if reached the clipping region's boundary. } } - if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) + if (style & FontStyle::Underline) { - HLine(context, startX, y - firstLine + font->glyphHeight - 1 - context.drawOffsetY, x - startX - context.drawOffsetX, colour); + int underlineY = y - context.drawOffsetY + glyphHeight - 1; + if (underlineY < context.clipBottom) + { + HLine(context, startX, underlineY, x - startX - context.drawOffsetX, colour); + } } - } void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { 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); + } + } } + void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, int height) { x += context.drawOffsetX; diff --git a/src/Image.h b/src/Image.h index ca8333c..9af1d24 100644 --- a/src/Image.h +++ b/src/Image.h @@ -8,7 +8,8 @@ struct Image { uint16_t width; uint16_t height; - uint8_t* data; + uint16_t pitch; + uint8_t data[1]; }; #endif diff --git a/src/Render.cpp b/src/Render.cpp index 15f5e32..b628c7b 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -3,6 +3,7 @@ #include "App.h" #include "Interface.h" #include "Draw/Surface.h" +#include "DataPack.h" PageRenderer::PageRenderer(App& inApp) : app(inApp) @@ -34,6 +35,10 @@ void PageRenderer::DrawAll(DrawContext& context, Node* node) node = node->next; } + + 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) diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index aa5dbea..84e889b 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -27,6 +27,7 @@ using namespace std; void EncodeFont(const char* basePath, const char* name, vector& output, uint16_t& headerOffsetPosition); void EncodeCursor(const char* basePath, const char* name, vector& output, uint16_t& headerOffsetPosition); +void EncodeImage(const char* basePath, const char* name, vector& output, uint16_t& headerOffsetPosition); void GenerateAssetPack(const char* name) { @@ -48,6 +49,8 @@ void GenerateAssetPack(const char* name) EncodeCursor(basePath, "mouse-link.png", data, header.linkCursorOffset); EncodeCursor(basePath, "mouse-select.png", data, header.textSelectCursorOffset); + EncodeImage(basePath, "image-icon.png", data, header.imageIconOffset); + char outputPath[256]; snprintf(outputPath, 256, "assets/%s.dat", name); FILE* fs; diff --git a/tools/ImageGen.cpp b/tools/ImageGen.cpp index ecc142e..1409cbe 100644 --- a/tools/ImageGen.cpp +++ b/tools/ImageGen.cpp @@ -90,3 +90,56 @@ 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, uint16_t& headerOffsetPosition) +{ + headerOffsetPosition = (uint16_t)(outputData.size()); + 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; + } + + WriteOutput(outputData, (uint16_t) width); + WriteOutput(outputData, (uint16_t)height); + WriteOutput(outputData, (uint16_t)(width + 7) / 8); // pitch + + 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); + } + } +} From ada14b9b15a9de7e9895fd5b5c03322374d7ee74 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 20 Jul 2023 13:22:38 +0100 Subject: [PATCH 16/98] Stripped out old widget layout code and old video driver code --- project/DOS/DOS.vcxproj | 4 - project/DOS/Makefile | 8 +- project/Windows/Windows.vcxproj | 3 - src/App.cpp | 59 +- src/App.h | 2 - src/DOS/CGA.cpp | 852 +------------------- src/DOS/CGA.h | 36 - src/DOS/CGAData.inc | 1269 ------------------------------ src/DOS/DOSInput.cpp | 6 - src/DOS/DefData.h | 16 - src/DOS/DefData.inc | 1279 ------------------------------- src/DOS/EGA.cpp | 735 +----------------- src/DOS/EGA.h | 41 +- src/DOS/HP95LX.cpp | 884 +-------------------- src/DOS/HP95LX.h | 38 - src/DOS/Hercules.cpp | 826 +------------------- src/DOS/Hercules.h | 37 - src/DOS/Platform.cpp | 21 +- src/DOS/TextData.inc | 14 - src/DOS/TextMode.cpp | 372 --------- src/DOS/TextMode.h | 91 --- src/Draw/Surf1bpp.cpp | 114 ++- src/Draw/Surf1bpp.h | 1 + src/Draw/Surface.h | 1 + src/Font.cpp | 14 + src/Font.h | 2 +- src/Interface.cpp | 679 +--------------- src/Interface.h | 30 - src/Nodes/Text.cpp | 4 +- src/Page.cpp | 562 +------------- src/Page.h | 72 -- src/Parser.cpp | 12 +- src/Platform.h | 36 - src/Renderer.cpp | 652 ---------------- src/Renderer.h | 72 -- src/Tags.cpp | 165 +--- src/Tags.h | 3 - src/Widget.h | 118 --- src/Windows/Platform.cpp | 3 +- src/Windows/WinVid.cpp | 324 +------- src/Windows/WinVid.h | 23 - 41 files changed, 202 insertions(+), 9278 deletions(-) delete mode 100644 src/DOS/CGAData.inc delete mode 100644 src/DOS/DefData.h delete mode 100644 src/DOS/DefData.inc delete mode 100644 src/DOS/TextData.inc delete mode 100644 src/DOS/TextMode.cpp delete mode 100644 src/DOS/TextMode.h delete mode 100644 src/Renderer.cpp delete mode 100644 src/Renderer.h delete mode 100644 src/Widget.h diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index e94abcc..7dbab1d 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -104,7 +104,6 @@ - @@ -118,7 +117,6 @@ - @@ -147,10 +145,8 @@ - - diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 9df21c9..36aea4e 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ 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 TextMode.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj Scroll.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj Scroll.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -40,9 +40,6 @@ App.obj: $(SRC_PATH)\App.cpp Parser.obj: $(SRC_PATH)\Parser.cpp $(CC) -fo=$@ $(CFLAGS) $< -Renderer.obj: $(SRC_PATH)\Renderer.cpp - $(CC) -fo=$@ $(CFLAGS) $< - Render.obj: $(SRC_PATH)\Render.cpp $(CC) -fo=$@ $(CFLAGS) $< @@ -115,9 +112,6 @@ Hercules.obj: $(SRC_PATH)\DOS\Hercules.cpp 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) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index a4b8c56..438dd5d 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -162,7 +162,6 @@ - @@ -195,11 +194,9 @@ - - diff --git a/src/App.cpp b/src/App.cpp index 392c7c4..680e07e 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -19,7 +19,7 @@ App* App::app; App::App() - : page(*this), renderer(*this), pageRenderer(*this), parser(page), ui(*this) + : page(*this), pageRenderer(*this), parser(page), ui(*this) { app = this; requestedNewPage = false; @@ -36,7 +36,6 @@ App::~App() void App::ResetPage() { page.Reset(); - //renderer.Reset(); parser.Reset(); ui.Reset(); } @@ -45,7 +44,6 @@ void App::Run(int argc, char* argv[]) { running = true; - renderer.Init(); ui.Init(); if (argc > 1) @@ -63,7 +61,6 @@ void App::Run(int argc, char* argv[]) while (running) { Platform::Update(); - //renderer.Update(); if (loadTask.HasContent()) { @@ -263,15 +260,15 @@ void App::ShowErrorPage(const char* message) loadTask.Stop(); 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.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); @@ -285,24 +282,24 @@ 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.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(); diff --git a/src/App.h b/src/App.h index 7513f3f..d5de602 100644 --- a/src/App.h +++ b/src/App.h @@ -18,7 +18,6 @@ #include #include "Parser.h" #include "Page.h" -#include "Renderer.h" #include "URL.h" #include "Interface.h" #include "Render.h" @@ -75,7 +74,6 @@ class App static App& Get() { return *app; } Page page; - Renderer renderer; PageRenderer pageRenderer; HTMLParser parser; AppInterface ui; diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index b81548a..0f29c7e 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -20,65 +20,19 @@ #include #include "../Image.h" #include "CGA.h" -//#include "CGAData.inc" #include "../Interface.h" -#include "DefData.h" #include "../DataPack.h" #include "../Draw/Surf1bpp.h" #define CGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) #define CGA_PAGE2_VRAM_ADDRESS (uint8_t*) MK_FP(0xBA00, 0) -#define SCREEN_WIDTH 640 -#define SCREEN_HEIGHT 200 - -#define BACK_BUTTON_X 4 -#define FORWARD_BUTTON_X 32 - -#define ADDRESS_BAR_X 60 -//#define ADDRESS_BAR_Y 10 -#define ADDRESS_BAR_Y (TITLE_BAR_HEIGHT + 1) -#define ADDRESS_BAR_WIDTH (SCREEN_WIDTH - 64) -#define ADDRESS_BAR_HEIGHT 15 // 12 -#define TITLE_BAR_HEIGHT 12 //8 -#define STATUS_BAR_HEIGHT 12 //8 -#define STATUS_BAR_Y (SCREEN_HEIGHT - STATUS_BAR_HEIGHT) - -#define WINDOW_TOP (TITLE_BAR_HEIGHT + ADDRESS_BAR_HEIGHT + 3) -//#define WINDOW_TOP 24 -#define WINDOW_HEIGHT (SCREEN_HEIGHT - WINDOW_TOP - STATUS_BAR_HEIGHT) -#define WINDOW_BOTTOM (WINDOW_TOP + WINDOW_HEIGHT) - -#define NAVIGATION_BUTTON_WIDTH 24 -#define NAVIGATION_BUTTON_HEIGHT ADDRESS_BAR_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; - imageIcon = NULL; - bulletImage = NULL; - isTextMode = false; + screenWidth = 640; + screenHeight = 200; } void CGADriver::Init() @@ -127,808 +81,6 @@ static void FastMemSet(void far* mem, uint8_t value, unsigned int count); modify [di cx] \ parm[es di][al][cx]; -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) -{ - return Assets.GetFont(fontSize, 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; - }*/ - /* - 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 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) -{ - return NULL; - /* - 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 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 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 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] - -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 diff --git a/src/DOS/CGA.h b/src/DOS/CGA.h index cc820d8..aa3435f 100644 --- a/src/DOS/CGA.h +++ b/src/DOS/CGA.h @@ -26,50 +26,14 @@ class CGADriver : public VideoDriver 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; }; #endif 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 37d333f..06172b8 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -135,12 +135,6 @@ void DOSInputDriver::GetMouseStatus(int& buttons, int& x, int& y) x = outreg.x.cx; y = outreg.x.dx; buttons = outreg.x.bx; - - if (Platform::video->isTextMode) - { - x >>= 3; - y >>= 3; - } } InputButtonCode DOSInputDriver::GetKeyPress() 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 abc0b51..0000000 --- a/src/DOS/DefData.inc +++ /dev/null @@ -1,1279 +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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x7e, 0x00, 0x28, 0x00, 0x28, 0x00, 0xfc, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0x54, 0x00, 0x54, 0x00, 0x50, 0x00, 0x38, 0x00, 0x14, 0x00, 0x14, 0x00, 0x54, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x40, 0x48, 0x80, 0x31, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0xc0, 0x11, 0x20, 0x21, 0x20, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x18, 0x00, 0x28, 0x00, 0x44, 0x00, 0x45, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 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, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, - // ')' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // '*' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0xa8, 0x00, 0x70, 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, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 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, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x14, 0x00, 0x14, 0x00, 0x24, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x34, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 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, 0x7c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x18, 0x60, 0x20, 0x10, 0x20, 0x10, 0x43, 0xc8, 0x44, 0x48, 0x44, 0x48, 0x44, 0x88, 0x23, 0x70, 0x20, 0x00, 0x18, 0x30, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x14, 0x00, 0x14, 0x00, 0x22, 0x00, 0x22, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x80, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x41, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x00, 0x40, 0x00, 0x43, 0x80, 0x40, 0x80, 0x40, 0x80, 0x21, 0x80, 0x1e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x7f, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x60, 0x00, 0x50, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x60, 0xc0, 0x60, 0xc0, 0x51, 0x40, 0x51, 0x40, 0x4a, 0x40, 0x4a, 0x40, 0x44, 0x40, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x60, 0x80, 0x50, 0x80, 0x50, 0x80, 0x48, 0x80, 0x44, 0x80, 0x42, 0x80, 0x42, 0x80, 0x41, 0x80, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x40, 0x80, 0x40, 0x80, 0x41, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x42, 0x80, 0x21, 0x00, 0x1e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x41, 0x00, 0x40, 0x80, 0x40, 0x80, 0x41, 0x00, 0x7f, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x41, 0x00, 0x40, 0x00, 0x38, 0x00, 0x06, 0x00, 0x01, 0x00, 0x41, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x08, 0x82, 0x08, 0x82, 0x08, 0x45, 0x10, 0x45, 0x10, 0x28, 0xa0, 0x28, 0xa0, 0x28, 0xa0, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x81, 0x00, 0x42, 0x00, 0x24, 0x00, 0x18, 0x00, 0x18, 0x00, 0x24, 0x00, 0x42, 0x00, 0x81, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x41, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x70, 0x00, - // '\' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 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, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xe0, 0x00, - // '^' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x28, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, - // '`' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 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, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x3e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3a, 0x00, 0x46, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7e, 0x00, 0x40, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0xe0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x46, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3c, 0x00, - // 'h' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // 'k' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x70, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x80, 0x66, 0x40, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x46, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x38, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0xe0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x80, 0x88, 0x80, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x48, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x48, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0xc0, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x18, 0x00, - // '|' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, - // '}' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x18, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, - // '~' - 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_RegularFont = { - // Glyph widths - { 3,3,5,7,7,12,9,3,4,4,5,7,3,4,3,4,7,7,7,7,7,7,7,7,7,7,3,3,7,7,7,7,14,9,9,9,10,9,8,10,10,3,7,8,7,11,10,10,9,10,10,9,9,10,9,13,8,9,8,4,4,4,7,7,4,8,8,7,8,8,3,8,7,3,3,7,3,11,7,8,8,8,4,7,3,7,7,9,6,7,6,5,3,5,9,4}, - 2, // Byte width - 16, // Glyph height - 32, // 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x50, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, 0x7c, 0x00, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x7c, 0x00, 0x28, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0x54, 0x00, 0x50, 0x00, 0x30, 0x00, 0x18, 0x00, 0x14, 0x00, 0x54, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x49, 0x00, 0x32, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x26, 0x00, 0x49, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x50, 0x00, 0x20, 0x00, 0x20, 0x00, 0x54, 0x00, 0x48, 0x00, 0x48, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, - // ')' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, - // '*' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x20, 0x00, 0x50, 0x00, 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, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x18, 0x00, 0x28, 0x00, 0x28, 0x00, 0x48, 0x00, 0x7c, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x47, 0x20, 0x49, 0x20, 0x49, 0x20, 0x46, 0xe0, 0x20, 0x00, 0x30, 0x00, 0x0f, 0x80, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x28, 0x00, 0x28, 0x00, 0x44, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x44, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x4e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x46, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x60, 0x00, 0x60, 0x00, 0x50, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x63, 0x00, 0x55, 0x00, 0x55, 0x00, 0x49, 0x00, 0x49, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x62, 0x00, 0x62, 0x00, 0x52, 0x00, 0x52, 0x00, 0x4a, 0x00, 0x46, 0x00, 0x46, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x4a, 0x00, 0x46, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x7c, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x38, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x80, 0x20, 0x44, 0x40, 0x44, 0x40, 0x44, 0x40, 0x2a, 0x80, 0x2a, 0x80, 0x11, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, - // '\' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 0x00, - // '^' - 0x00, 0x00, 0x10, 0x00, 0x28, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, - // '`' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 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, 0x00, 0x38, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x40, 0x00, 0x40, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x7c, 0x00, 0x40, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x78, 0x00, - // 'h' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, - // 'k' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x48, 0x00, 0x50, 0x00, 0x60, 0x00, 0x50, 0x00, 0x48, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x64, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x78, 0x00, 0x40, 0x00, 0x40, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x04, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x20, 0x00, 0x10, 0x00, 0x48, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x49, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, 0x00, 0x20, 0x00, 0x20, 0x00, 0xc0, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, - // '|' - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, - // '}' - 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '~' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, -}; - -Font Default_SmallFont = { - // Glyph widths - { 3,3,4,6,6,8,6,2,3,3,4,6,3,3,3,3,6,6,6,6,6,6,6,6,6,6,3,3,6,6,6,6,11,7,7,7,8,7,6,8,8,3,5,7,6,9,8,8,7,8,8,7,7,8,7,11,7,7,7,3,3,3,6,6,3,6,6,6,6,6,3,6,6,2,2,6,2,8,6,6,6,6,3,5,3,6,6,8,5,5,5,4,2,4,6,3}, - 2, // Byte width - 13, // Glyph height - 26, // 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x12, 0x00, 0x12, 0x00, 0x7f, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0xfe, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1c, 0x00, 0x2a, 0x00, 0x49, 0x00, 0x48, 0x00, 0x28, 0x00, 0x1c, 0x00, 0x0a, 0x00, 0x09, 0x00, 0x09, 0x00, 0x49, 0x00, 0x2a, 0x00, 0x1c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x44, 0x10, 0x44, 0x20, 0x44, 0x40, 0x38, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x70, 0x08, 0x88, 0x10, 0x88, 0x20, 0x88, 0x40, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x18, 0x00, 0x18, 0x00, 0x24, 0x00, 0x42, 0x40, 0x41, 0x40, 0x40, 0x80, 0x21, 0x40, 0x1e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, - // ')' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '*' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x54, 0x00, 0x38, 0x00, 0x28, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 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, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x02, 0x00, 0x01, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x12, 0x00, 0x12, 0x00, 0x22, 0x00, 0x42, 0x00, 0x7f, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x01, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x0c, 0x00, 0x30, 0x00, 0x40, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x18, 0x00, 0x06, 0x00, 0x01, 0x00, 0x06, 0x00, 0x18, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x0c, 0x08, 0x10, 0x04, 0x21, 0xc2, 0x22, 0x22, 0x44, 0x22, 0x44, 0x22, 0x44, 0x64, 0x43, 0xb8, 0x20, 0x00, 0x18, 0x18, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x11, 0x00, 0x11, 0x00, 0x20, 0x80, 0x20, 0x80, 0x7f, 0xc0, 0x40, 0x40, 0x80, 0x20, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x20, 0x20, 0x40, 0x30, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0xc0, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0xc0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0x80, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0x80, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x00, 0x40, 0x00, 0x43, 0xe0, 0x40, 0x20, 0x40, 0x20, 0x20, 0x60, 0x30, 0xe0, 0x0f, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x3f, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x21, 0x00, 0x22, 0x00, 0x24, 0x00, 0x28, 0x00, 0x30, 0x00, 0x28, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x20, 0x80, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x30, 0x60, 0x30, 0x60, 0x28, 0xa0, 0x28, 0xa0, 0x25, 0x20, 0x25, 0x20, 0x22, 0x20, 0x22, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x30, 0x80, 0x30, 0x80, 0x28, 0x80, 0x28, 0x80, 0x24, 0x80, 0x24, 0x80, 0x22, 0x80, 0x22, 0x80, 0x21, 0x80, 0x21, 0x80, 0x20, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x20, 0x40, 0x30, 0xc0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xc0, 0x20, 0x40, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x42, 0x20, 0x21, 0x40, 0x30, 0xc0, 0x0f, 0x40, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x80, 0x3f, 0x00, 0x20, 0x80, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x20, 0x80, 0x40, 0x40, 0x40, 0x00, 0x20, 0x00, 0x1f, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x40, 0x40, 0x20, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x80, 0x20, 0x40, 0x40, 0x40, 0x40, 0x20, 0x80, 0x20, 0x80, 0x11, 0x00, 0x11, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x02, 0x81, 0x02, 0x41, 0x04, 0x41, 0x04, 0x42, 0x84, 0x22, 0x88, 0x22, 0x88, 0x14, 0x50, 0x14, 0x50, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x40, 0x40, 0x20, 0x80, 0x11, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x11, 0x00, 0x20, 0x80, 0x40, 0x40, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x40, 0x40, 0x20, 0x80, 0x11, 0x00, 0x0a, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, 0x00, 0x00, - // '\' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 0x00, 0x00, 0x00, - // '^' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x28, 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, 0x00, 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, 0x00, 0x00, 0x00, 0x00, - // '`' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x01, 0x00, 0x01, 0x00, 0x3f, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x3e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x20, 0x00, 0x20, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x3c, 0x00, - // 'h' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // 'k' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x44, 0x00, 0x48, 0x00, 0x50, 0x00, 0x70, 0x00, 0x48, 0x00, 0x44, 0x00, 0x42, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xc0, 0x65, 0x20, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x42, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x62, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x62, 0x00, 0x5c, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x60, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x40, 0x00, 0x40, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xf8, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0x84, 0x20, 0x44, 0x40, 0x44, 0x40, 0x2a, 0x80, 0x2a, 0x80, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00, 0x28, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00, 0x00, - // '|' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '}' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00, 0x00, - // '~' - 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -Font Default_LargeFont = { - // Glyph widths - { 4,4,6,9,9,14,11,3,5,5,6,9,4,5,4,4,9,9,9,9,9,9,9,9,9,9,4,4,9,9,9,9,16,11,11,11,12,11,10,13,12,5,8,10,9,13,11,12,10,12,12,11,9,12,11,15,11,11,10,4,4,4,7,9,5,9,9,8,9,9,5,9,9,3,3,8,3,13,9,9,9,9,5,8,5,9,7,11,7,7,8,5,5,5,9,5}, - 2, // Byte width - 20, // Glyph height - 40, // 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, 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, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x3f, 0x80, 0x12, 0x00, 0x12, 0x00, 0x7f, 0x00, 0x24, 0x00, 0x24, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x48, 0x80, 0x31, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x23, 0x00, 0x44, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x80, 0x43, 0x00, 0x43, 0x00, 0x3c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 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, - // '(' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, - // ')' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, - // '*' - 0x00, 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, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, - // '-' - 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '/' - 0x00, 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, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x06, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x12, 0x00, 0x22, 0x00, 0x3f, 0x00, 0x02, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x10, 0x00, 0x20, 0x00, 0x20, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x21, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x23, 0x00, 0x1d, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x1c, 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, 0x0c, 0x00, 0x0c, 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, 0x0e, 0x00, 0x0c, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x08, 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, 0x3f, 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, 0x20, 0x00, 0x10, 0x00, 0x08, 0x00, 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x21, 0x00, 0x40, 0x80, 0x4e, 0x80, 0x52, 0x80, 0x52, 0x80, 0x52, 0x80, 0x4d, 0x00, 0x40, 0x00, 0x20, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x14, 0x00, 0x14, 0x00, 0x22, 0x00, 0x22, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x22, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x22, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x21, 0x00, 0x20, 0x00, 0x24, 0x00, 0x3c, 0x00, 0x24, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x21, 0x00, 0x20, 0x00, 0x24, 0x00, 0x3c, 0x00, 0x24, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x23, 0x00, 0x41, 0x00, 0x41, 0x00, 0x40, 0x00, 0x47, 0x80, 0x41, 0x00, 0x41, 0x00, 0x21, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x24, 0x00, 0x24, 0x00, 0x28, 0x00, 0x38, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x41, 0x00, 0x63, 0x00, 0x63, 0x00, 0x55, 0x00, 0x55, 0x00, 0x49, 0x00, 0x49, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0x41, 0x00, 0x61, 0x00, 0x51, 0x00, 0x51, 0x00, 0x49, 0x00, 0x45, 0x00, 0x45, 0x00, 0x43, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x33, 0x00, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x3e, 0x00, 0x24, 0x00, 0x22, 0x00, 0x21, 0x00, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x43, 0x00, 0x41, 0x00, 0x40, 0x00, 0x38, 0x00, 0x06, 0x00, 0x01, 0x00, 0x41, 0x00, 0x61, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x88, 0x80, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x80, 0x80, 0x80, 0x88, 0x80, 0x88, 0x80, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x41, 0x00, 0xe3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x41, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0e, 0x00, - // '\' - 0x00, 0x00, 0x00, 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, 0x00, 0x00, - // ']' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1c, 0x00, - // '^' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, - // '`' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 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, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x2c, 0x00, 0x33, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x33, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x63, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1a, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x63, 0x00, 0x41, 0x00, 0x7f, 0x00, 0x40, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x11, 0x00, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3c, 0x00, - // 'h' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x2c, 0x00, 0x32, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x44, 0x00, 0x38, 0x00, - // 'k' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x27, 0x00, 0x24, 0x00, 0x28, 0x00, 0x38, 0x00, 0x24, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdb, 0x00, 0x6d, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0x49, 0x00, 0xed, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x32, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x63, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0x63, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x33, 0x00, 0x21, 0x00, 0x21, 0x00, 0x21, 0x00, 0x33, 0x00, 0x2c, 0x00, 0x20, 0x00, 0x20, 0x00, 0x78, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x66, 0x00, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x66, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x00, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x18, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x41, 0x00, 0x40, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x41, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x11, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x26, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xeb, 0x80, 0x49, 0x00, 0x55, 0x00, 0x55, 0x00, 0x55, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x22, 0x00, 0x14, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x80, 0x41, 0x00, 0x22, 0x00, 0x22, 0x00, 0x14, 0x00, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x60, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x42, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x21, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x30, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x06, 0x00, - // '|' - 0x00, 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, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, - // '}' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x18, 0x00, - // '~' - 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x1c, 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,9}, - 2, // Byte width - 16, // Glyph height - 32, // Glyph stride - Default_RegularFont_Monospace_Data -}; - -static unsigned char Default_SmallFont_Monospace_Data[] = { - // ' ' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '!' - 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x0a, 0x0a, 0x3f, 0x14, 0x14, 0x14, 0x7e, 0x28, 0x28, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x08, 0x1c, 0x22, 0x20, 0x1c, 0x02, 0x22, 0x1c, 0x08, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x20, 0x51, 0x22, 0x04, 0x08, 0x10, 0x22, 0x45, 0x02, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x18, 0x20, 0x20, 0x10, 0x30, 0x49, 0x4a, 0x44, 0x3b, 0x00, 0x00, - // ''' - 0x00, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '(' - 0x00, 0x00, 0x04, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, - // ')' - 0x00, 0x00, 0x10, 0x08, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08, 0x10, - // '*' - 0x00, 0x00, 0x00, 0x00, 0x36, 0x1c, 0x7f, 0x1c, 0x36, 0x00, 0x00, 0x00, 0x00, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x7f, 0x08, 0x08, 0x08, 0x00, 0x00, - // ',' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, - // '-' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, - // '.' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, - // '/' - 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x08, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3e, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x0c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x04, 0x0c, 0x0c, 0x14, 0x14, 0x24, 0x3e, 0x04, 0x0e, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x3e, 0x20, 0x20, 0x20, 0x3c, 0x02, 0x02, 0x22, 0x1c, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x0c, 0x10, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x3e, 0x22, 0x02, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x04, 0x18, 0x00, 0x00, - // ':' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, - // ';' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, - // '<' - 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, - // '=' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, - // '>' - 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, - // '?' - 0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x04, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x1e, 0x21, 0x4d, 0x55, 0x55, 0x55, 0x4e, 0x20, 0x1c, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x18, 0x08, 0x08, 0x14, 0x14, 0x22, 0x3e, 0x22, 0x77, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x21, 0x21, 0x21, 0x7e, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x40, 0x40, 0x21, 0x1e, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x7c, 0x22, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x7c, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x7f, 0x21, 0x20, 0x24, 0x3c, 0x24, 0x20, 0x21, 0x7f, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x7f, 0x21, 0x20, 0x24, 0x3c, 0x24, 0x20, 0x20, 0x78, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x47, 0x41, 0x21, 0x1e, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x1e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x73, 0x22, 0x24, 0x24, 0x28, 0x38, 0x24, 0x22, 0x73, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x7f, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x63, 0x22, 0x36, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x77, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x67, 0x22, 0x32, 0x32, 0x2a, 0x26, 0x26, 0x22, 0x72, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x20, 0x20, 0x20, 0x78, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x1b, 0x00, - // 'R' - 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x3e, 0x24, 0x24, 0x22, 0x73, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x3e, 0x41, 0x40, 0x40, 0x3e, 0x01, 0x01, 0x41, 0x3e, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x7f, 0x49, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x14, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x2a, 0x2a, 0x2a, 0x14, 0x14, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x77, 0x22, 0x14, 0x14, 0x08, 0x14, 0x14, 0x22, 0x77, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x08, 0x1c, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x7f, 0x42, 0x04, 0x04, 0x08, 0x10, 0x10, 0x21, 0x7f, 0x00, 0x00, - // '[' - 0x00, 0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1c, - // '\' - 0x00, 0x00, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, - // ']' - 0x00, 0x00, 0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1c, - // '^' - 0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '_' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - // '`' - 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'a' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x02, 0x3e, 0x42, 0x42, 0x3d, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x60, 0x20, 0x20, 0x3e, 0x21, 0x21, 0x21, 0x21, 0x7e, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x40, 0x40, 0x41, 0x3e, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x06, 0x02, 0x02, 0x3e, 0x42, 0x42, 0x42, 0x42, 0x3f, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x7f, 0x40, 0x41, 0x3e, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x0c, 0x10, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x10, 0x3c, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x42, 0x3e, 0x02, 0x02, 0x3c, - // 'h' - 0x00, 0x00, 0x60, 0x20, 0x20, 0x2c, 0x32, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x08, 0x00, 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x04, 0x00, 0x00, 0x3c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x38, - // 'k' - 0x00, 0x00, 0x60, 0x20, 0x20, 0x26, 0x24, 0x28, 0x38, 0x24, 0x63, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x2a, 0x2a, 0x2a, 0x2a, 0x6b, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x32, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x41, 0x3e, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x21, 0x21, 0x21, 0x21, 0x3e, 0x20, 0x70, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x42, 0x42, 0x42, 0x42, 0x3e, 0x02, 0x07, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x19, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x38, 0x06, 0x41, 0x3e, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x10, 0x10, 0x3c, 0x10, 0x10, 0x10, 0x12, 0x0c, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x22, 0x22, 0x22, 0x26, 0x1b, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x2a, 0x2a, 0x14, 0x14, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x1c, 0x1c, 0x22, 0x77, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x30, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x44, 0x08, 0x10, 0x22, 0x7e, 0x00, 0x00, - // '{' - 0x00, 0x00, 0x06, 0x08, 0x08, 0x08, 0x08, 0x30, 0x08, 0x08, 0x08, 0x08, 0x06, - // '|' - 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, - // '}' - 0x00, 0x00, 0x30, 0x08, 0x08, 0x08, 0x08, 0x06, 0x08, 0x08, 0x08, 0x08, 0x30, - // '~' - 0x00, 0x00, 0x00, 0x31, 0x49, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 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,8}, - 1, // Byte width - 13, // Glyph height - 13, // 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, 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, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '"' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 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, 0x00, 0x00, - // '#' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x1f, 0xe0, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x3f, 0xc0, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '$' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x40, 0x10, 0xc0, 0x10, 0x40, 0x10, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x80, 0x00, 0x40, 0x10, 0x40, 0x18, 0x40, 0x17, 0x80, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - // '%' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x44, 0x20, 0x44, 0x40, 0x44, 0x80, 0x39, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0xe0, 0x09, 0x10, 0x11, 0x10, 0x21, 0x10, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '&' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x10, 0x80, 0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x14, 0x00, 0x12, 0x00, 0x21, 0x20, 0x20, 0xa0, 0x20, 0x40, 0x20, 0xa0, 0x1f, 0x10, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, - // ')' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, - // '*' - 0x00, 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, - // '+' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3f, 0xe0, 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, 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, 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '0' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '1' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x1a, 0x00, 0x02, 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, 0x00, 0x00, - // '2' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x40, 0x10, 0x40, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '3' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x10, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x10, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '4' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x80, 0x02, 0x80, 0x04, 0x80, 0x04, 0x80, 0x08, 0x80, 0x10, 0x80, 0x1f, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '5' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x1f, 0x00, 0x10, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x10, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '6' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x10, 0x00, 0x17, 0x00, 0x18, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '7' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x10, 0x40, 0x10, 0x40, 0x00, 0x80, 0x00, 0x80, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '8' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '9' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x08, 0x80, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x08, 0xc0, 0x07, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x03, 0x00, 0x1c, 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, 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, 0x02, 0x00, 0x02, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 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, 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, - // '?' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x10, 0x40, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '@' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x20, 0x20, 0x23, 0xe0, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x24, 0x20, 0x23, 0xe0, 0x20, 0x00, 0x10, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, - // 'A' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x80, 0x08, 0x80, 0x10, 0x40, 0x1f, 0xc0, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'B' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x40, 0x1f, 0xc0, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'C' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd0, 0x18, 0x30, 0x10, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x10, 0x10, 0x18, 0x30, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'D' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x60, 0x10, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x10, 0x60, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'E' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x10, 0x20, 0x10, 0x20, 0x10, 0x00, 0x10, 0x80, 0x1f, 0x80, 0x10, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x10, 0x20, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'F' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x08, 0x10, 0x08, 0x10, 0x08, 0x00, 0x08, 0x40, 0x0f, 0xc0, 0x08, 0x40, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'G' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x30, 0x60, 0x20, 0x20, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x41, 0xf0, 0x40, 0x20, 0x40, 0x20, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'H' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'I' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x02, 0x00, 0x02, 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, 0x00, 0x00, - // 'J' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x20, 0x80, 0x20, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'K' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0xe0, 0x20, 0x80, 0x21, 0x00, 0x22, 0x00, 0x24, 0x00, 0x28, 0x00, 0x34, 0x00, 0x22, 0x00, 0x21, 0x00, 0x20, 0x80, 0x20, 0x40, 0x70, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'L' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x20, 0x10, 0x20, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'M' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x30, 0x60, 0x30, 0x60, 0x28, 0xa0, 0x28, 0xa0, 0x25, 0x20, 0x25, 0x20, 0x22, 0x20, 0x22, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'N' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xf0, 0x30, 0x20, 0x30, 0x20, 0x28, 0x20, 0x24, 0x20, 0x22, 0x20, 0x22, 0x20, 0x21, 0x20, 0x20, 0xa0, 0x20, 0x60, 0x20, 0x60, 0x78, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'O' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'P' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x40, 0x1f, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x30, 0x60, 0x20, 0x20, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x20, 0x20, 0x30, 0x60, 0x0f, 0x80, 0x08, 0x00, 0x1c, 0x40, 0x23, 0x80, 0x00, 0x00, - // 'R' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x10, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x40, 0x1f, 0x80, 0x11, 0x00, 0x10, 0x80, 0x10, 0x40, 0x10, 0x20, 0x7c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'S' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x10, 0x60, 0x20, 0x20, 0x20, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x01, 0x80, 0x00, 0x40, 0x00, 0x20, 0x20, 0x20, 0x30, 0x40, 0x2f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'T' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf0, 0x42, 0x10, 0x42, 0x10, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'U' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'V' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'W' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x30, 0x40, 0x10, 0x40, 0x10, 0x42, 0x10, 0x42, 0x10, 0x25, 0x20, 0x25, 0x20, 0x28, 0xa0, 0x28, 0xa0, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'X' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xf0, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x05, 0x00, 0x08, 0x80, 0x10, 0x40, 0x20, 0x20, 0x78, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x08, 0x80, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'Z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x20, 0x20, 0x20, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x07, 0x80, 0x00, 0x00, - // '\' - 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, - // ']' - 0x00, 0x00, 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, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0f, 0x00, 0x00, 0x00, - // '^' - 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, - // '`' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x00, 0x40, 0x07, 0xc0, 0x18, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x1f, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'b' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x18, 0x40, 0x37, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'c' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xa0, 0x08, 0x60, 0x10, 0x20, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x08, 0x20, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'd' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x0f, 0x40, 0x10, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0xc0, 0x0f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'e' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x20, 0x20, 0x3f, 0xe0, 0x20, 0x00, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'f' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0xc0, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'g' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x60, 0x10, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0xc0, 0x0f, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x80, 0x1f, 0x00, - // 'h' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x17, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'i' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'j' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x1c, 0x00, - // 'k' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x11, 0xe0, 0x10, 0x80, 0x11, 0x00, 0x12, 0x00, 0x16, 0x00, 0x19, 0x00, 0x10, 0x80, 0x10, 0x40, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'l' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 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, 0x00, 0x00, - // 'm' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xc0, 0x33, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x22, 0x20, 0x73, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'n' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x80, 0x18, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x38, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'o' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x10, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x40, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'p' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x80, 0x18, 0x40, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x10, 0x20, 0x18, 0x40, 0x17, 0x80, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x3c, 0x00, - // 'q' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x60, 0x10, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x10, 0xc0, 0x0f, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x01, 0xe0, - // 'r' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xc0, 0x0c, 0x20, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 's' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa0, 0x10, 0x60, 0x20, 0x20, 0x10, 0x00, 0x0f, 0x80, 0x00, 0x40, 0x20, 0x20, 0x30, 0x40, 0x2f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 't' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1f, 0xc0, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x40, 0x04, 0x40, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'u' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0x40, 0x10, 0xc0, 0x0f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'v' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'w' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x22, 0x20, 0x22, 0x20, 0x15, 0x40, 0x15, 0x40, 0x08, 0x80, 0x08, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 'x' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, - // 'y' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x20, 0x20, 0x10, 0x40, 0x10, 0x40, 0x08, 0x80, 0x08, 0x80, 0x05, 0x00, 0x05, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04, 0x00, 0x1e, 0x00, - // 'z' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, - // '|' - 0x00, 0x00, 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, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, - // '}' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x01, 0x80, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // '' - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80, 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,12}, - 2, // Byte width - 20, // Glyph height - 40, // 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, - 0xc000,0xa000,0x9000,0x8800,0x8400,0x8200,0x8100,0x8080,0x8040,0x83c0,0x9200,0xb900,0xc900,0x0480,0x0480,0x0300, - }, - // 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 index 6fb05d3..456371d 100644 --- a/src/DOS/EGA.cpp +++ b/src/DOS/EGA.cpp @@ -25,62 +25,19 @@ #include "../Draw/Surf1bpp.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); + SetupVars("EGA.DAT", 0x10, 350); } -void EGADriver::SetupVars(int inScreenMode, int inScreenHeight) +void EGADriver::SetupVars(const char* inAssetPackToUse, int inScreenMode, int inScreenHeight) { + assetPackToUse = inAssetPackToUse; screenModeToUse = inScreenMode; - screenWidth = SCREEN_WIDTH; + screenWidth = 640; 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; - - imageIcon = NULL; - bulletImage = NULL; - - isTextMode = false; } void EGADriver::Init() @@ -88,12 +45,12 @@ void EGADriver::Init() startingScreenMode = GetScreenMode(); SetScreenMode(screenModeToUse); - Assets.Load("DEFAULT.DAT"); + Assets.Load(assetPackToUse); DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); for (int y = 0; y < screenHeight; y++) { - drawSurface1BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(80 * y); + drawSurface1BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); } drawSurface = drawSurface1BPP; } @@ -128,686 +85,6 @@ static void FastMemSet(void far* mem, uint8_t value, unsigned int count); 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) -{ - return Assets.GetFont(fontSize, 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) -{ - return Assets.GetMouseCursorData(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 diff --git a/src/DOS/EGA.h b/src/DOS/EGA.h index 6902488..86e0992 100644 --- a/src/DOS/EGA.h +++ b/src/DOS/EGA.h @@ -26,55 +26,20 @@ class EGADriver : public VideoDriver 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); + void SetupVars(const char* inAssetPackToUse, int inScreenMode, int inScreenHeight); int screenModeToUse; + const char* assetPackToUse; }; class VGADriver : public EGADriver @@ -82,7 +47,7 @@ class VGADriver : public EGADriver public: VGADriver() { - SetupVars(0x11, 480); + SetupVars("DEFAULT.DAT", 0x11, 480); } virtual void ScaleImageDimensions(int& width, int& height) {} }; diff --git a/src/DOS/HP95LX.cpp b/src/DOS/HP95LX.cpp index ec52161..0c6a1ad 100644 --- a/src/DOS/HP95LX.cpp +++ b/src/DOS/HP95LX.cpp @@ -20,71 +20,27 @@ #include #include "../Image.h" #include "HP95LX.h" -#include "CGAData.inc" -//#include "DefData.h" #include "../Interface.h" +#include "../DataPack.h" +#include "../Draw/Surf1bpp.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 320 -#define SCREEN_HEIGHT 200 - -#define ADDRESS_BAR_HEIGHT 10 -#define TITLE_BAR_HEIGHT 6 -#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 BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) #define BYTES_PER_LINE 30 #endif -#define WINDOW_VRAM_TOP (BYTES_PER_LINE * (WINDOW_TOP)) -#define WINDOW_VRAM_BOTTOM (BYTES_PER_LINE * (WINDOW_BOTTOM)) - - +#define SCREEN_WIDTH 240 +#define SCREEN_HEIGHT 128 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 = &CGA_ImageIcon; - bulletImage = &CGA_Bullet; - isTextMode = false; + screenWidth = 240; + screenHeight = 128; } void HP95LXVideoDriver::Init() @@ -95,6 +51,15 @@ void HP95LXVideoDriver::Init() #else SetScreenMode(0x20); #endif + + Assets.Load("LOWRES.DAT"); + + DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); + for (int y = 0; y < screenHeight; y++) + { + drawSurface1BPP->lines[y] = (BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); + } + drawSurface = drawSurface1BPP; } void HP95LXVideoDriver::Shutdown() @@ -121,823 +86,6 @@ void HP95LXVideoDriver::SetScreenMode(int 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 &CGA_SmallFont_Monospace; - case 2: - case 3: - case 4: - return &CGA_RegularFont_Monospace; - } - } - - switch (fontSize) - { - default: - return &CGA_SmallFont; - case 2: - case 3: - case 4: - return &CGA_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 &CGA_MouseCursor; - case MouseCursor::Hand: - return &CGA_MouseCursorHand; - case MouseCursor::TextSelect: - return &CGA_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 index ab01358..82570aa 100644 --- a/src/DOS/HP95LX.h +++ b/src/DOS/HP95LX.h @@ -26,51 +26,13 @@ class HP95LXVideoDriver : public VideoDriver 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; - }; #endif diff --git a/src/DOS/Hercules.cpp b/src/DOS/Hercules.cpp index 6d1cdef..e3c57b8 100644 --- a/src/DOS/Hercules.cpp +++ b/src/DOS/Hercules.cpp @@ -20,70 +20,18 @@ #include #include "../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; - imageIcon = bulletImage = NULL; - isTextMode = false; + screenWidth = 720; + screenHeight = 348; } // graphics mode CRTC register values @@ -149,776 +97,6 @@ void HerculesDriver::SetTextMode() 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) -{ - return Assets.GetFont(fontSize, 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) -{ - return Assets.GetMouseCursorData(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 diff --git a/src/DOS/Hercules.h b/src/DOS/Hercules.h index 6cbd8d6..e9e36b5 100644 --- a/src/DOS/Hercules.h +++ b/src/DOS/Hercules.h @@ -26,49 +26,12 @@ class HerculesDriver : public VideoDriver 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: 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; }; #endif diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 6d07169..7ea08f6 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -20,7 +20,6 @@ #include "CGA.h" #include "EGA.h" #include "Hercules.h" -#include "TextMode.h" #endif #include "DOSInput.h" #include "DOSNet.h" @@ -28,7 +27,7 @@ #include "../Image.h" #include "../Cursor.h" #include "../Font.h" -//#include "DefData.inc" +#include "../Draw/Surface.h" static DOSInputDriver DOSinput; static DOSNetworkDriver DOSNet; @@ -154,10 +153,7 @@ static void AutoDetectVideoDriver() if (isMono) { - Platform::video = new MDATextModeDriver(); - printf("Detected MDA\n"); - return; - //fprintf(stderr, "MDA cards not supported!\n"); + fprintf(stderr, "MDA cards not supported!\n"); } else { @@ -191,14 +187,6 @@ void Platform::Init(int argc, char* argv[]) { 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")) { @@ -213,12 +201,13 @@ void Platform::Init(int argc, char* argv[]) network->Init(); video->Init(); - video->ClearScreen(); + video->drawSurface->Clear(); input->Init(); if (inverse) { - video->InvertScreen(); + // TODO-refactor + //video->InvertScreen(); } } 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 f680b2c..0000000 --- a/src/DOS/TextMode.cpp +++ /dev/null @@ -1,372 +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 NULL; - //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 6a3fd68..0000000 --- a/src/DOS/TextMode.h +++ /dev/null @@ -1,91 +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. -// - -#ifndef _TEXTMODE_H_ -#define _TEXTMODE_H_ - -#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]; -}; - -#endif diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index e966dec..29f4144 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -1,3 +1,4 @@ +#include #include "Surf1bpp.h" #include "../Font.h" #include "../Image.h" @@ -233,16 +234,28 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* int startX = x; uint8_t glyphHeight = font->glyphHeight; - if (x >= context.clipRight || y >= context.clipBottom || y + glyphHeight <= context.clipTop) + if (x >= context.clipRight) { - return; // No need to draw anything if fully outside the clipping region. + return; + } + if (y >= context.clipBottom) + { + return; + } + if (y + glyphHeight > context.clipBottom) + { + glyphHeight = (uint8_t)(context.clipBottom - y); + } + if (y + glyphHeight < context.clipTop) + { + return; } - // Adjust glyphHeight to fit within the clipping region. + uint8_t firstLine = 0; if (y < context.clipTop) { - glyphHeight -= (context.clipTop - y); - y = context.clipTop; + firstLine += context.clipTop - y; + y += firstLine; } while (*text) @@ -250,7 +263,7 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* char c = *text++; if (c < 32 || c >= 128) { - continue; // Skip non-printable characters. + continue; } char index = c - 32; @@ -258,63 +271,90 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* if (glyphWidth == 0) { - continue; // Skip zero-width characters. + continue; } uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); - int outY = y; - uint8_t* VRAMptr = lines[outY] + (x >> 3); - uint8_t writeOffset = (uint8_t)(x) & 0x7; + glyphData += (firstLine * font->glyphWidthBytes); - if (style & FontStyle::Italic && outY - y < (font->glyphHeight >> 1)) - { - writeOffset++; - } + int outY = y; + uint8_t* VRAMptr = lines[y] + (x >> 3); - for (uint8_t j = 0; j < glyphHeight; j++) + if (!colour) { - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + for (uint8_t j = firstLine; j < glyphHeight; j++) { - uint8_t glyphPixels = *glyphData++; + uint8_t writeOffset = (uint8_t)(x) & 0x7; - if (style & FontStyle::Bold) + if ((style & FontStyle::Italic) && j < (font->glyphHeight >> 1)) { - glyphPixels |= (glyphPixels >> 1); + writeOffset++; } - if (!colour) + 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)); } - else + + 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++; + } + + 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)); } - } - outY++; - VRAMptr = lines[outY] + (x >> 3); + outY++; + VRAMptr = lines[outY] + (x >> 3); + } } - x += glyphWidth + (style & FontStyle::Bold ? 1 : 0); + x += glyphWidth; + if (style & FontStyle::Bold) + { + x++; + } if (x >= context.clipRight) { - break; // No need to continue drawing if reached the clipping region's boundary. + break; } } - if (style & FontStyle::Underline) + if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) { - int underlineY = y - context.drawOffsetY + glyphHeight - 1; - if (underlineY < context.clipBottom) - { - HLine(context, startX, underlineY, x - startX - context.drawOffsetX, colour); - } + 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) @@ -467,6 +507,7 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) { + return; x += context.drawOffsetX; y += context.drawOffsetY; int startY = y; @@ -519,3 +560,12 @@ void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int *(uint16_t*)(&lines[y++][x]) = inner; } } + +void DrawSurface_1BPP::Clear() +{ + int widthBytes = width >> 3; + for (int y = 0; y < height; y++) + { + memset(lines[y], 0xff, widthBytes); + } +} diff --git a/src/Draw/Surf1bpp.h b/src/Draw/Surf1bpp.h index e9021f1..d2799ee 100644 --- a/src/Draw/Surf1bpp.h +++ b/src/Draw/Surf1bpp.h @@ -9,6 +9,7 @@ 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); diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index e1b51af..d291452 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -23,6 +23,7 @@ class DrawSurface { public: 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; diff --git a/src/Font.cpp b/src/Font.cpp index 5224de0..d10d12a 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -37,3 +37,17 @@ int Font::CalculateWidth(const char* text, FontStyle::Type style) return result; } + +int Font::GetGlyphWidth(char c, FontStyle::Type style) +{ + if (c < FIRST_FONT_GLYPH || c >= 128) + { + return 0; + } + int result = glyphWidth[c - FIRST_FONT_GLYPH]; + if (style & FontStyle::Bold) + { + result++; + } + return result; +} diff --git a/src/Font.h b/src/Font.h index 73e8abb..9cbf48d 100644 --- a/src/Font.h +++ b/src/Font.h @@ -45,7 +45,7 @@ struct Font : public FontMetaData uint8_t* glyphData; int CalculateWidth(const char* text, FontStyle::Type style = FontStyle::Regular); - int GetGlyphWidth(char c) { return glyphWidth[c - FIRST_FONT_GLYPH]; } + int GetGlyphWidth(char c, FontStyle::Type style = FontStyle::Regular); }; #endif diff --git a/src/Interface.cpp b/src/Interface.cpp index b1f8ff3..7ed023b 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -26,13 +26,8 @@ #include "DataPack.h" #include "Event.h" -#define MIN_SCROLL_WIDGET_SIZE 8 - AppInterface::AppInterface(App& inApp) : app(inApp) { - GenerateWidgets(); - - hoverWidget = NULL; oldMouseX = -1; oldMouseY = -1; oldButtons = 0; @@ -55,14 +50,6 @@ void AppInterface::Reset() { scrollPositionY = 0; oldPageHeight = 0; - if (activeWidget && !activeWidget->isInterfaceWidget) - { - activeWidget = NULL; - } - if (hoverWidget && !hoverWidget->isInterfaceWidget) - { - hoverWidget = NULL; - } if (focusedNode && !IsInterfaceNode(focusedNode)) { @@ -74,19 +61,6 @@ void AppInterface::Reset() } } -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); - */ -} - void AppInterface::Update() { if (app.page.GetPageHeight() != oldPageHeight) @@ -118,27 +92,6 @@ void AppInterface::Update() HandleRelease(); } - if (activeWidget == &scrollBar) - { - 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); - } - } - oldMouseX = mouseX; oldMouseY = mouseY; oldButtons = buttons; @@ -197,14 +150,6 @@ void AppInterface::Update() continue; } - if (activeWidget) - { - if (HandleActiveWidget(keyPress)) - { - continue; - } - } - switch (keyPress) { // case KEYCODE_MOUSE_LEFT: @@ -220,37 +165,38 @@ 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()); + //ScrollAbsolute(0); break; case KEYCODE_BACKSPACE: app.PreviousPage(); break; case KEYCODE_F2: { - Platform::input->HideMouse(); - Platform::video->InvertScreen(); - Platform::input->ShowMouse(); + // TODO-refactor + //Platform::input->HideMouse(); + //Platform::video->InvertScreen(); + //Platform::input->ShowMouse(); } break; case KEYCODE_CTRL_L: case KEYCODE_F6: - ActivateWidget(&addressBar); + //ActivateWidget(&addressBar); break; case KEYCODE_TAB: - CycleWidgets(1); + //CycleWidgets(1); break; case KEYCODE_SHIFT_TAB: - CycleWidgets(-1); + //CycleWidgets(-1); break; case 'm': @@ -274,60 +220,6 @@ void AppInterface::Update() } } -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++) - { - Widget* widget = appInterfaceWidgets[n]; - if (x >= widget->x && y >= widget->y && x < widget->x + widget->width && y < widget->y + widget->height) - { - return widget; - } - } - - return app.renderer.PickPageWidget(x, y); -} - bool AppInterface::IsOverNode(Node* node, int x, int y) { if (!node) @@ -367,201 +259,13 @@ Node* AppInterface::PickNode(int x, int y) return nullptr; } - -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 (textFieldCursorPosition == -1) - { - app.renderer.InvertWidget(activeWidget); - } - else - { - app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); - } - } - else if (activeWidget->GetLinkURL()) - { - InvertWidgetsWithLinkURL(activeWidget->GetLinkURL()); - } - activeWidget = NULL; - } -} - -void AppInterface::ActivateWidget(Widget* widget) -{ - if (activeWidget && activeWidget != widget) - { - DeactivateWidget(); - } - - activeWidget = widget; - - if (activeWidget) - { - switch (activeWidget->type) - { - 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()); - SetStatusMessage(URL::GenerateFromRelative(app.page.pageURL.url, widget->GetLinkURL()).url); - } - break; - } - } -} - -void AppInterface::InvertWidgetsWithLinkURL(const char* url) -{ - for (int n = app.renderer.GetPageTopWidgetIndex(); n < app.page.numFinishedWidgets; n++) - { - Widget* pageWidget = &app.page.widgets[n]; - if (pageWidget->y > scrollPositionY + Platform::video->windowHeight) - { - break; - } - if (pageWidget->GetLinkURL() == url) - { - app.renderer.InvertWidget(pageWidget); - } - } -} - void AppInterface::HandleClick(int mouseX, int mouseY) { - if (activeWidget && activeWidget != hoverWidget) - { - DeactivateWidget(); - } - if (hoverNode) { FocusNode(hoverNode); focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseClick)); } - - if (hoverWidget) - { - 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); - } - - if(shouldPick) - { - if (hoverWidget == activeWidget && textFieldCursorPosition == -1) - { - app.renderer.InvertWidget(activeWidget); - } - activeWidget = hoverWidget; - - 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; - - 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; - } - } - } -} - -void AppInterface::HandleButtonClicked(Widget* widget) -{ - 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(); - } - } } void AppInterface::HandleRelease() @@ -571,282 +275,16 @@ void AppInterface::HandleRelease() focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseRelease)); } - if (activeWidget && activeWidget->type == Widget::Button) - { - if (clickingButton) - { - if (hoverWidget == activeWidget) - { - HandleButtonClicked(activeWidget); - } - DeactivateWidget(); - - activeWidget = NULL; - clickingButton = false; - } - } - else if (activeWidget == &scrollBar) - { - 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; - } -} - -bool AppInterface::HandleActiveWidget(InputButtonCode keyPress) -{ - 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; - } - - 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; - } - - return false; -} - -void AppInterface::SubmitForm(WidgetFormData* form) -{ - if (form->method == WidgetFormData::Get) - { - char* address = addressBarURL.url; - strcpy(address, form->action); - int numParams = 0; - - for (int n = 0; n < app.page.numFinishedWidgets; n++) - { - Widget& widget = app.page.widgets[n]; - - 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; - } - } - - // Replace any spaces with + - for (char* p = address; *p; p++) - { - if (*p == ' ') - { - *p = '+'; - } - } - - app.OpenURL(URL::GenerateFromRelative(app.page.pageURL.url, address).url); - } } void AppInterface::UpdateAddressBar(const URL& url) { addressBarURL = url; addressBarNode->Redraw(); - //app.renderer.RedrawWidget(&addressBar); } void AppInterface::UpdatePageScrollBar() { - /*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 maxScroll = app.renderer.GetMaxScrollPosition(); - int widgetPosition = maxScroll == 0 ? 0 : (int32_t)maxWidgetPosition * app.renderer.GetScrollPosition() / maxScroll; - - scrollBar.scrollBar->size = scrollWidgetSize; - - if (activeWidget != &scrollBar) - { - scrollBar.scrollBar->position = widgetPosition; - } - - app.renderer.RedrawScrollBar(); - */ ScrollBarNode::Data* data = static_cast(scrollBarNode->data); int maxScrollHeight = app.page.GetRootNode()->size.y - app.ui.windowRect.height; if (maxScrollHeight < 0) @@ -856,101 +294,6 @@ void AppInterface::UpdatePageScrollBar() scrollBarNode->Redraw(); } -void AppInterface::CycleWidgets(int direction) -{ - if (app.page.numFinishedWidgets == 0) - { - return; - } - - int pageScrollBottom = app.renderer.GetScrollPosition() + Platform::video->windowHeight; - - // If there is an active widget, find index and check if it is on screen - int currentPos = -1; - if (activeWidget && !activeWidget->isInterfaceWidget) - { - 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; - } - } - } - - // If there is no active widget or not on screen, find the first widget on screen - if (currentPos == -1) - { - if (direction > 0) - { - for (currentPos = app.renderer.GetPageTopWidgetIndex(); currentPos < app.page.numFinishedWidgets; currentPos++) - { - Widget* widget = &app.page.widgets[currentPos]; - if (widget->y >= app.renderer.GetScrollPosition()) - { - break; - } - } - - 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) - { - break; - } - } - - if (currentPos < 0) - { - currentPos = app.page.numFinishedWidgets - 1; - } - } - } - - int startPos = currentPos; - - 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; - } - } - - currentPos += direction; - if (currentPos >= app.page.numFinishedWidgets) - { - currentPos = 0; - } - if (currentPos < 0) - { - currentPos = app.page.numFinishedWidgets - 1; - } - if (currentPos == startPos) - { - break; - } - } -} - void AppInterface::GenerateInterfaceNodes() { rootInterfaceNode = SectionElement::Construct(allocator, SectionElement::Interface); diff --git a/src/Interface.h b/src/Interface.h index 3ef2f3e..2e4759a 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -14,13 +14,11 @@ #ifndef _INTERFACE_H_ #define _INTERFACE_H_ -#include "Widget.h" #include "Platform.h" #include "URL.h" #include "LinAlloc.h" #include "Node.h" -#define NUM_APP_INTERFACE_WIDGETS 4 #define MAX_TITLE_LENGTH 80 class App; @@ -37,12 +35,10 @@ class AppInterface void Update(); void DrawInterfaceNodes(DrawContext& context); - void DrawInterfaceWidgets(); void UpdateAddressBar(const URL& url); void SetStatusMessage(const char* message); void UpdatePageScrollBar(); - Widget* GetActiveWidget() { return activeWidget; } int GetTextFieldCursorPosition() { return textFieldCursorPosition; } void SetTitle(const char* title); @@ -60,33 +56,15 @@ class AppInterface URL addressBarURL; - Widget scrollBar; - Widget addressBar; - Widget backButton; - Widget forwardButton; - - Widget titleBar; - Widget statusBar; - Rect windowRect; private: void GenerateInterfaceNodes(); - void GenerateWidgets(); - Widget* PickWidget(int x, int y); 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 ActivateWidget(Widget* widget); - void DeactivateWidget(); bool IsOverNode(Node* node, int x, int y); @@ -95,13 +73,10 @@ class AppInterface static void OnAddressBarSubmit(Node* node); - Widget* appInterfaceWidgets[NUM_APP_INTERFACE_WIDGETS]; App& app; Node* focusedNode; Node* hoverNode; - Widget* activeWidget; - Widget* hoverWidget; int oldButtons; int oldMouseX, oldMouseY; int scrollBarRelativeClickPositionX; @@ -110,11 +85,6 @@ class AppInterface int textFieldCursorPosition; bool clickingButton; - ButtonWidgetData backButtonData; - ButtonWidgetData forwardButtonData; - TextFieldWidgetData addressBarData; - ScrollBarData scrollBarData; - LinearAllocator allocator; Node* rootInterfaceNode; diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index f378bcc..8384b35 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -38,7 +38,7 @@ Node* TextElement::Construct(Allocator& allocator, const char* text) void TextElement::GenerateLayout(Layout& layout, Node* node) { TextElement::Data* data = static_cast(node->data); - Font* font = Platform::video->GetFont(node->style.fontSize, node->style.fontStyle); // TODO: Get font from active style + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); // TODO: Get font from active style int lineHeight = font->glyphHeight; // Clear out SubTextElement children if we are regenerating the layout @@ -73,7 +73,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) lastBreakPointWidth = width; } - width += Platform::video->GetGlyphWidth(c, node->style.fontSize, node->style.fontStyle); + width += font->GetGlyphWidth(c, node->style.fontStyle); if (width > layout.AvailableWidth()) { diff --git a/src/Page.cpp b/src/Page.cpp index 20215a6..d6473f0 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -25,30 +25,21 @@ #define TOP_MARGIN_PADDING 1 -Page::Page(App& inApp) : app(inApp), widgets(allocator), layout(*this) +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; - styleStackSize = 1; - styleStack[0] = WidgetStyle(FontStyle::Regular, 1, false); allocator.Reset(); rootNode = SectionElement::Construct(allocator, SectionElement::Document); @@ -59,523 +50,11 @@ void Page::Reset() layout.Reset(); } -WidgetStyle& Page::GetStyleStackTop() -{ - return styleStack[styleStackSize - 1]; -} - -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; - } -} - -void Page::PushStyle(const WidgetStyle& style) -{ - if (styleStackSize < MAX_PAGE_STYLE_STACK_SIZE) - { - styleStack[styleStackSize] = style; - styleStackSize++; - FinishCurrentWidget(); - } -} - -void Page::PopStyle() -{ - if (styleStackSize > 1) - { - styleStackSize--; - } - FinishCurrentWidget(); -} - -void Page::AddHorizontalRule() -{ - BreakLine(1); - Widget* widget = CreateWidget(Widget::HorizontalRule); - if (widget) - { - widget->width = Platform::video->windowWidth - 8; - widget->x = 4; - widget->height = 1; - } - BreakLine(1); -} - -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; - - widget->width = Platform::video->screenWidth / 3; - widget->height = Platform::video->GetFont(1)->glyphHeight + 4; - //widget->width = Platform::video->GetFont(1)->CalculateWidth(text) + 4; - } - } -} - -Widget* Page::CreateTextWidget() -{ - Widget* widget = CreateWidget(Widget::Text); - if (widget) - { - widget->text = allocator.Alloc(); - if (widget->text) - { - widget->text->linkURL = widgetURL; - widget->height = Platform::video->GetLineHeight(widget->style.fontSize); - widget->text->text = NULL; - } - } - return widget; -} - -Widget* Page::CreateWidget(Widget::Type type) -{ - if (allocator.GetError() == LinearAllocator::Error_OutOfMemory) - { - return NULL; - } - - 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() -{ - if (currentWidgetIndex != -1) - { - 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; - } -} - -void Page::FinishCurrentLine(bool includeCurrentWidget) -{ - if (includeCurrentWidget) - { - FinishCurrentWidget(); - } - - if (currentLineStartWidgetIndex != -1) - { - 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++) - { - if (widgets[n].height > lineHeight) - { - lineHeight = widgets[n].height; - } - if (widgets[n].x + widgets[n].width > lineWidth) - { - lineWidth = widgets[n].x + widgets[n].width; - } - } - - int centerAdjust = (Platform::video->windowWidth - lineWidth) >> 1; - - for (int n = currentLineStartWidgetIndex; n < lineEndWidget; n++) - { - widgets[n].y += lineHeight - widgets[n].height; - if (widgets[n].style.center) - { - widgets[n].x += centerAdjust; - } - } - - cursorY += lineHeight; - pageHeight = cursorY; - - numFinishedWidgets = lineEndWidget; - - if (includeCurrentWidget) - { - currentLineStartWidgetIndex = -1; - } - else - { - currentLineStartWidgetIndex = currentWidgetIndex; - } - } - - 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) - { - // Move to new line - FinishCurrentLine(false); - widget->x = cursorX; - widget->y = cursorY; - } - } -} - -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) - { - needLeadingWhiteSpace = false; - if (current->width > 0 && textBufferSize < MAX_TEXT_BUFFER_SIZE) - { - textBuffer[textBufferSize++] = ' '; - current->width += Platform::video->GetGlyphWidth(' ', current->style.fontSize, current->style.fontStyle); - } - } - - for (int index = 0; text[index] != '\0'; index++) - { - if (text[index] < 32 || text[index] > 128) - continue; - - int glyphWidth = font->glyphWidth[text[index] - 32]; - if (current->style.fontStyle & FontStyle::Bold) - { - glyphWidth++; - } - - if (current->x + current->width + addedWidth + glyphWidth < Platform::video->windowWidth) - { - addedWidth += glyphWidth; - - if (text[index] == ' ') - { - // 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; - } - } - else - { - if (current->width == 0) - { - 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; - } - } - else - { - // Case where we need to make a new widget on the next line - FinishCurrentLine(); - current = CreateTextWidget(); - } - - if (!current) - { - // Couldn't make a new widget so probably out of memory - return; - } - - // 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; - } - } -} - -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.ui.SetTitle(inTitle); } -Widget* Page::GetWidget(int x, int y) -{ - for (int n = 0; n < widgets.Count(); n++) - { - Widget* widget = &widgets[n]; - - if (x >= widget->x && y >= widget->y && x < widget->x + widget->width && y < widget->y + widget->height) - return widget; - } - 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; -} - void Page::DebugDraw(DrawContext& context, Node* node) { while (node) @@ -588,43 +67,6 @@ void Page::DebugDraw(DrawContext& context, Node* node) } } -WidgetContainer::WidgetContainer(LinearAllocator& inAllocator) : allocator(inAllocator), numAllocated(0) -{ -} - -bool WidgetContainer::Allocate() -{ - 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) - { - // Need to allocate a new chunk - chunks[chunkIndex] = allocator.Alloc(); - if (chunks[chunkIndex] == NULL) - { - return false; - } - } - - 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)]; -} void Page::DebugDumpNodeGraph(Node* node, int depth) { diff --git a/src/Page.h b/src/Page.h index 80f4b7a..7d4fe10 100644 --- a/src/Page.h +++ b/src/Page.h @@ -15,47 +15,16 @@ #ifndef _PAGE_H_ #define _PAGE_H_ -#include "Widget.h" #include "LinAlloc.h" #include "URL.h" #include "Layout.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 Node; -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 Page { public: @@ -63,31 +32,10 @@ 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); - - WidgetStyle& GetStyleStackTop(); - void PushStyle(const WidgetStyle& style); - void PopStyle(); - LinearAllocator allocator; Layout layout; @@ -102,42 +50,22 @@ class Page App& GetApp() { return app; } private: - friend class Renderer; friend class AppInterface; - Widget* CreateWidget(Widget::Type type); - Widget* CreateTextWidget(); - void FinishCurrentWidget(); - void FinishCurrentLine(bool includeCurrentWidget = true); - App& app; char* title; - int currentLineStartWidgetIndex; - int currentWidgetIndex; - int numFinishedWidgets; - int pageWidth, pageHeight; int cursorX, cursorY; int pendingVerticalPadding; int leftMarginPadding; - bool needLeadingWhiteSpace; - Node* rootNode; - WidgetContainer widgets; - - WidgetStyle styleStack[MAX_PAGE_STYLE_STACK_SIZE]; - uint8_t styleStackSize; - WidgetFormData* formData; - char textBuffer[MAX_TEXT_BUFFER_SIZE]; int textBufferSize; - - char* widgetURL; }; #endif diff --git a/src/Parser.cpp b/src/Parser.cpp index ed5f5ea..cfaf15e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -18,7 +18,6 @@ #include #include "Parser.h" #include "Tags.h" -#include "Renderer.h" #include "Page.h" #include "Unicode.inc" #include "Nodes/Text.h" @@ -270,7 +269,7 @@ void HTMLParser::FlushTextBuffer() { if(textBufferSize == 0) { - page.AppendText("&"); + EmitText("&"); } else { @@ -295,7 +294,7 @@ void HTMLParser::FlushTextBuffer() if(matching && escapeSequence[textBufferSize] == '\0') { - page.AppendText(ampersandEscapeSequences[n * 2 + 1]); + EmitText(ampersandEscapeSequences[n * 2 + 1]); break; } } @@ -465,7 +464,8 @@ void HTMLParser::ParseChar(char c) c = ' '; if (textBufferSize == 0) { - page.FlagLeadingWhiteSpace(); + // TODO-refactor + //page.FlagLeadingWhiteSpace(); break; } else @@ -482,7 +482,9 @@ void HTMLParser::ParseChar(char c) if (c == '\n') { FlushTextBuffer(); - page.BreakTextLine(); + + // TODO-refactor + //page.BreakTextLine(); break; } else if (c == '\r') diff --git a/src/Platform.h b/src/Platform.h index 314f3fa..920cdb7 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -18,7 +18,6 @@ #include #include "Font.h" #include "Cursor.h" -#include "Widget.h" struct Image; class DrawSurface; @@ -29,46 +28,11 @@ class VideoDriver virtual void Init() = 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; - int screenWidth; int screenHeight; - int windowWidth; - int windowHeight; - int windowX, windowY; - - Image* imageIcon; - Image* bulletImage; - - bool isTextMode; - DrawSurface* drawSurface; }; diff --git a/src/Renderer.cpp b/src/Renderer.cpp deleted file mode 100644 index 3ab340c..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 919864e..0000000 --- a/src/Renderer.h +++ /dev/null @@ -1,72 +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. -// - -#ifndef _RENDERER_H_ -#define _RENDERER_H_ - -#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; -}; - -#endif diff --git a/src/Tags.cpp b/src/Tags.cpp index 1a22192..313afd5 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -20,6 +20,7 @@ #include "Page.h" #include "Platform.h" #include "Image.h" +#include "DataPack.h" #include "Nodes/Section.h" #include "Nodes/ImgNode.h" @@ -101,57 +102,25 @@ 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(); + // TODO-refactor parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - //parser.page.BreakTextLine(); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } 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); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Bold, size >= 3 ? 1 : 2), 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(); - parser.PopContext(this); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } @@ -159,37 +128,20 @@ void HTagHandler::Close(class HTMLParser& parser) const void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { parser.PushContext(StyleNode::ConstructFontSize(parser.page.allocator, size), this); - - //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - //currentStyle.fontSize = size; - //parser.page.PushStyle(currentStyle); } void SizeTagHandler::Close(class HTMLParser& parser) const { parser.PopContext(this); - - //parser.page.PopStyle(); } 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); - + // TODO-refactor : add bullet point parser.EmitNode(BreakNode::Construct(parser.page.allocator)); - - //parser.page.BreakLine(); - //parser.page.AddBulletPoint(); - //parser.page.AdjustLeftMargin(bulletPointWidth); } 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(); } void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -206,36 +158,21 @@ void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const } parser.PushContext(LinkNode::Construct(parser.page.allocator, url), this); - - //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - //currentStyle.fontStyle = (FontStyle::Type)(currentStyle.fontStyle | FontStyle::Underline); - //parser.page.PushStyle(currentStyle); - } + void ATagHandler::Close(class HTMLParser& parser) const { parser.PopContext(this); - //parser.page.ClearWidgetURL(); - //parser.page.PopStyle(); } 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); - - parser.PushContext(BlockNode::Construct(parser.page.allocator, leftMarginPadding, useVerticalPadding ? Platform::video->GetLineHeight(1) >> 1 : 0), this); + // TODO-refactor + int lineHeight = Assets.GetFont(1, FontStyle::Regular)->glyphHeight; + parser.PushContext(BlockNode::Construct(parser.page.allocator, 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); } @@ -250,9 +187,6 @@ void SectionTagHandler::Close(class HTMLParser& parser) const 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(parser.page.allocator, style); if (styleNode) { @@ -267,89 +201,32 @@ void StyleTagHandler::Close(class HTMLParser& parser) 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(parser.page.allocator, alignmentType), this); } void AlignmentTagHandler::Close(class HTMLParser& parser) const { parser.PopContext(this); - //parser.page.PopStyle(); - //parser.page.BreakLine(); } void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - /* - WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - - AttributeParser attributes(attributeStr); - while (attributes.Parse()) - { - if (!stricmp(attributes.Key(), "size")) - { - int size = atoi(attributes.Value()); - - if (size < 0) - { - // Relative sizing - if (currentStyle.fontSize + size < 0) - { - currentStyle.fontSize = 0; - } - else - { - currentStyle.fontSize += size; - } - } - else - { - switch (size) - { - 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; - } - } - } - } - - parser.page.PushStyle(currentStyle); - */ + // TODO-refactor } void FontTagHandler::Close(class HTMLParser& parser) const { - //parser.page.PopStyle(); } void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + // TODO-refactor parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void ListTagHandler::Close(class HTMLParser& parser) const { - //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize) >> 1); + // TODO-refactor parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } @@ -508,22 +385,20 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const if (width == -1 && height == -1) { - Image* imageIcon = Platform::video->imageIcon; + Image* imageIcon = Assets.imageIcon; if (imageIcon) { - //parser.page.AddImage(NULL, imageIcon->width, imageIcon->height); - parser.EmitImage(NULL, imageIcon->width, imageIcon->height); + parser.EmitImage(imageIcon, imageIcon->width, imageIcon->height); } if (altText) { - parser.page.AppendText(altText); + parser.EmitText(altText); } } else { Platform::video->ScaleImageDimensions(width, height); - //parser.page.AddImage(altText, width, height); parser.EmitImage(NULL, width, height); } } @@ -569,22 +444,12 @@ 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); - //parser.PushPreFormatted(); - //parser.page.BreakTextLine(); + // TODO-refactor parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void PreformattedTagHandler::Close(class HTMLParser& parser) const { - //parser.page.PopStyle(); - //parser.PopPreFormatted(); - // - //WidgetStyle currentStyle = parser.page.GetStyleStackTop(); - //parser.page.BreakLine(Platform::video->GetLineHeight(currentStyle.fontSize, currentStyle.fontStyle) >> 1); + // TODO-refactor parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } diff --git a/src/Tags.h b/src/Tags.h index e7e0fef..f20a444 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -30,9 +30,6 @@ 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 diff --git a/src/Widget.h b/src/Widget.h deleted file mode 100644 index 90e1f8e..0000000 --- a/src/Widget.h +++ /dev/null @@ -1,118 +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. -// - -#ifndef _WIDGET_H_ -#define _WIDGET_H_ -#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 nullptr; - } - - union - { - TextWidgetData* text; - ButtonWidgetData* button; - TextFieldWidgetData* textField; - ScrollBarData* scrollBar; - ImageWidgetData* image; - }; -}; - -#endif diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 1697524..3572860 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -16,6 +16,7 @@ #include "../Platform.h" #include "WinVid.h" #include "WinInput.h" +#include "../Draw/Surface.h" WindowsVideoDriver winVid; NetworkDriver nullNetworkDriver; @@ -50,7 +51,7 @@ void Platform::Init(int argc, char* argv[]) network->Init(); video->Init(); - video->ClearScreen(); + video->drawSurface->Clear(); input->Init(); input->ShowMouse(); } diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index a7b4434..09be833 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -16,36 +16,14 @@ #include #include "WinVid.h" #include "../Image.h" -//#include "../DOS/CGAData.inc" #include "../Interface.h" #include "../DataPack.h" #include "../Draw/Surf1bpp.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 extern HWND hWnd; @@ -53,30 +31,13 @@ WindowsVideoDriver::WindowsVideoDriver() { screenWidth = SCREEN_WIDTH; screenHeight = SCREEN_HEIGHT; - windowWidth = screenWidth - 16; - windowHeight = WINDOW_HEIGHT; - windowX = 0; - windowY = WINDOW_TOP; - foregroundColour = RGB(0, 0, 0); backgroundColour = RGB(255, 255, 255); verticalScale = 1; - scissorX1 = 0; - scissorY1 = 0; - scissorX2 = screenWidth; - scissorY2 = screenHeight; - - - - //imageIcon = &CGA_ImageIcon; - //bulletImage = &CGA_Bullet; - Assets.Load("Default.dat"); // Assets.Load("Lowres.dat"); // Assets.Load("CGA.dat"); - - isTextMode = false; } void WindowsVideoDriver::Init() @@ -150,28 +111,6 @@ void WindowsVideoDriver::ClearScreen() //FillRect(0, screenHeight - STATUS_BAR_HEIGHT, screenWidth, STATUS_BAR_HEIGHT, foregroundColour); } -void WindowsVideoDriver::FillRect(int x, int y, int width, int height) -{ - DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); - //drawSurface->FillRect(context, x, y, width, height, 0); - - // FillRect(x, y, width, height, foregroundColour); -} - -void WindowsVideoDriver::FillRect(int x, int y, int width, int height, uint32_t colour) -{ - DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); - drawSurface->FillRect(context, x, y, width, height, colour ? 1 : 0); - -// for (int j = 0; j < height; j++) -// { -// for (int i = 0; i < width; i++) -// { -// SetPixel(x + i, y + j, colour); -// } -// } -} - void WindowsVideoDriver::SetPixel(int x, int y, uint32_t colour) { if (x >= 0 && y >= 0 && x < screenWidth && y < screenHeight) @@ -224,210 +163,6 @@ void WindowsVideoDriver::InvertPixel(int x, int y, uint32_t colour) }*/ } -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; -} - -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); - - DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); - drawSurface->DrawString(context, font, text, x, y, 0, style); - return; - - - 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; - } - - 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); - - for (uint8_t j = firstLine; j < glyphHeight; j++) - { - uint8_t writeOffset = (uint8_t)(x) & 0x7; - - if ((style & FontStyle::Italic) && j < (glyphHeight >> 1)) - { - writeOffset++; - } - - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) - { - uint8_t glyphPixels = *glyphData++; - - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - - for (int k = 0; k < 8; k++) - { - if (glyphPixels & (0x80 >> k)) - { - InvertPixel(x + k + i * 8, y + j, 0xffffff); - } - } - } - } - - 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); - } -} - -void WindowsVideoDriver::HLine(int x, int y, int count) -{ - DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); - drawSurface->HLine(context, x, y, count, 0); - //for (int n = 0; n < count; n++) - //{ - // SetPixel(x + n, y, foregroundColour); - //} -} - -void WindowsVideoDriver::VLine(int x, int y, int count) -{ - DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); - drawSurface->VLine(context, x, y, count, 0); - //for (int n = 0; n < count; n++) - //{ - // SetPixel(x, y + n, foregroundColour); - //} -} - -void WindowsVideoDriver::DrawScrollBar(int position, int size) -{ -} - -MouseCursorData* WindowsVideoDriver::GetCursorGraphic(MouseCursor::Type type) -{ - return NULL; -} - -Font* WindowsVideoDriver::GetFont(int fontSize, FontStyle::Type style) -{ - if (style & FontStyle::Monospace) - { - switch (fontSize) - { - case 0: - return &Assets.monoFonts[0]; - case 2: - case 3: - case 4: - return &Assets.monoFonts[2]; - default: - return &Assets.monoFonts[1]; - } - } - - switch (fontSize) - { - case 0: - return &Assets.fonts[0]; - case 2: - return &Assets.fonts[2]; - default: - return &Assets.fonts[1]; - } - -} -int WindowsVideoDriver::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 WindowsVideoDriver::GetLineHeight(int fontSize, FontStyle::Type style) -{ - return GetFont(fontSize, style)->glyphHeight + 1; -} - void WindowsVideoDriver::Paint(HWND hwnd) { PAINTSTRUCT ps; @@ -472,60 +207,3 @@ void WindowsVideoDriver::Paint(HWND hwnd) } -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 d41608c..76787d8 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -27,30 +27,8 @@ class WindowsVideoDriver : public VideoDriver virtual void Init(); 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; @@ -65,5 +43,4 @@ class WindowsVideoDriver : public VideoDriver HBITMAP screenBitmap; uint32_t foregroundColour, backgroundColour; - int scissorX1, scissorY1, scissorX2, scissorY2; }; From e2415c651b659f9767ce274285af692211dd711f Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 20 Jul 2023 15:46:27 +0100 Subject: [PATCH 17/98] Refactored HTTP request system to be platform agnostic and added Windows network driver --- project/DOS/Makefile | 5 +- project/Windows/Windows.vcxproj | 8 +- src/App.cpp | 1 + src/DOS/DOSNet.cpp | 447 ++++---------------------------- src/DOS/DOSNet.h | 85 ++---- src/HTTP.cpp | 438 +++++++++++++++++++++++++++++++ src/HTTP.h | 91 +++++++ src/Interface.cpp | 2 - src/Interface.h | 5 - src/Nodes/Field.cpp | 26 -- src/Platform.h | 32 +-- src/Render.cpp | 6 +- src/Windows/Platform.cpp | 5 +- src/Windows/WinNet.cpp | 237 +++++++++++++++++ src/Windows/WinNet.h | 51 ++++ 15 files changed, 919 insertions(+), 520 deletions(-) create mode 100644 src/HTTP.cpp create mode 100644 src/HTTP.h create mode 100644 src/Windows/WinNet.cpp create mode 100644 src/Windows/WinNet.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 36aea4e..848ef9b 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj Scroll.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -52,6 +52,9 @@ Page.obj: $(SRC_PATH)\Page.cpp Platform.obj: $(SRC_PATH)\DOS\Platform.cpp $(CC) -fo=$@ $(CFLAGS) $< +HTTP.obj: $(SRC_PATH)\HTTP.cpp + $(CC) -fo=$@ $(CFLAGS) $< + Font.obj: $(SRC_PATH)\Font.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 438dd5d..02e3b42 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -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 @@ -142,6 +142,7 @@ + @@ -165,6 +166,7 @@ + @@ -173,6 +175,7 @@ + @@ -198,6 +201,7 @@ + diff --git a/src/App.cpp b/src/App.cpp index 680e07e..359d6b8 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -15,6 +15,7 @@ #include #include "App.h" #include "Platform.h" +#include "HTTP.h" App* App::app; diff --git a/src/DOS/DOSNet.cpp b/src/DOS/DOSNet.cpp index 2c0974a..a7359ad 100644 --- a/src/DOS/DOSNet.cpp +++ b/src/DOS/DOSNet.cpp @@ -13,6 +13,7 @@ // #include "DOSNet.h" +#include "../HTTP.h" #include #include @@ -66,7 +67,7 @@ void DOSNetworkDriver::Init() for (int n = 0; n < MAX_CONCURRENT_HTTP_REQUESTS; n++) { - requests[n] = new DOSHTTPRequest(); + requests[n] = new HTTPRequest(); } isConnected = true; @@ -122,441 +123,91 @@ void DOSNetworkDriver::DestroyRequest(HTTPRequest* request) } } -DOSHTTPRequest::DOSHTTPRequest() : status(HTTPRequest::Stopped), sock(NULL) +// Returns zero on success, negative number is error +int DOSNetworkDriver::ResolveAddress(const char* name, NetworkAddress address, bool sendRequest) { + return Dns::resolve(name, address, sendRequest ? 1 : 0); } -void DOSHTTPRequest::Reset() +NetworkTCPSocket* DOSNetworkDriver::CreateSocket() { - 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'; - } -} - -bool DOSHTTPRequest::SendPendingWrites() -{ - 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: - { - WriteLine("GET %s HTTP/1.0", path); - WriteLine("User-Agent: MicroWeb " __DATE__); - 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 303348c..1fc98c5 100644 --- a/src/DOS/DOSNet.h +++ b/src/DOS/DOSNet.h @@ -26,83 +26,30 @@ #define TCP_RECV_BUFFER_SIZE (16384) #define MAX_CONCURRENT_HTTP_REQUESTS 3 #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(); @@ -110,11 +57,19 @@ 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; }; diff --git a/src/HTTP.cpp b/src/HTTP.cpp new file mode 100644 index 0000000..88d0927 --- /dev/null +++ b/src/HTTP.cpp @@ -0,0 +1,438 @@ +#include +#include +#include +#include "HTTP.h" + +HTTPRequest::HTTPRequest() : status(HTTPRequest::Stopped), sock(NULL) +{ +} + +void HTTPRequest::Reset() +{ + lineBufferSize = 0; + lineBufferSendPos = -1; +} + +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::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"); + 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) { + MarkError(InvalidPort); + return; + } + + // Truncate hostname early + *portStart = 0; + } + + 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:// + MarkError(InvalidProtocol); + } +} + +size_t HTTPRequest::ReadData(char* buffer, size_t count) +{ + if (status == HTTPRequest::Downloading && sock) + { + int16_t rc = sock->Receive((unsigned char*)buffer, count); + if (rc < 0) + { + MarkError(ContentReceiveError); + } + else + { + return (size_t)(rc); + } + } + 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 (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; + } + break; + case ConnectingSocket: + { + if (sock->IsConnectComplete()) + { + internalStatus = SendHeaders; + break; + } + else if (sock->IsClosed()) + { + MarkError(SocketConnectionError); + break; + } + } + break; + case SendHeaders: + { + WriteLine("GET %s HTTP/1.0", path); + WriteLine("User-Agent: MicroWeb " __DATE__); + 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)) { + 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; + } + } + 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; + } +} + +bool HTTPRequest::ReadLine() +{ + 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; + } + + 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"; + } + 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..264acd7 --- /dev/null +++ b/src/HTTP.h @@ -0,0 +1,91 @@ +#ifndef HTTP_H_ +#define HTTP_H_ + +#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 + +class HTTPRequest +{ +public: + enum Status + { + Stopped, + Connecting, + Downloading, + Finished, + Error, + UnsupportedHTTPS + }; + + HTTPRequest(); + + void Open(char* url); + + 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; } + +private: + enum InternalStatus + { + // Errors + InvalidPort, + InvalidProtocol, + SocketCreationError, + SocketConnectionError, + HeaderSendError, + ContentReceiveError, + UnsupportedHTTPError, + MalformedHTTPVersionLineError, + WriteLineError, + HostNameResolveError, + + // Connection states + QueuedDNSRequest, + WaitingDNSResolve, + OpeningSocket, + ConnectingSocket, + SendHeaders, + ReceiveHeaderResponse, + ReceiveHeaderContent, + ReceiveContent + }; + + void MarkError(InternalStatus statusError); + bool ReadLine(); + void WriteLine(const char* fmt, ...); + bool SendPendingWrites(); + + void Reset(); + + 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 lineBuffer[LINE_BUFFER_SIZE]; + int lineBufferSize; + int lineBufferSendPos; +}; + + +#endif + diff --git a/src/Interface.cpp b/src/Interface.cpp index 7ed023b..8f9a801 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -31,8 +31,6 @@ AppInterface::AppInterface(App& inApp) : app(inApp) oldMouseX = -1; oldMouseY = -1; oldButtons = 0; - textFieldCursorPosition = 0; - clickingButton = false; hoverNode = nullptr; focusedNode = nullptr; diff --git a/src/Interface.h b/src/Interface.h index 2e4759a..ff1bbc1 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -39,7 +39,6 @@ class AppInterface void SetStatusMessage(const char* message); void UpdatePageScrollBar(); - int GetTextFieldCursorPosition() { return textFieldCursorPosition; } void SetTitle(const char* title); @@ -79,11 +78,7 @@ class AppInterface Node* hoverNode; int oldButtons; int oldMouseX, oldMouseY; - int scrollBarRelativeClickPositionX; - int scrollBarRelativeClickPositionY; int oldPageHeight; - int textFieldCursorPosition; - bool clickingButton; LinearAllocator allocator; diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 4007b3f..1ada574 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -110,9 +110,6 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) data->buffer[cursorPosition] = (char)(event.key); MoveCursorPosition(node, cursorPosition + 1); RedrawModified(node, cursorPosition - 1); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition - 1, true); - //app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition - 1); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } @@ -123,7 +120,6 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) data->buffer[0] = '\0'; node->Redraw(); MoveCursorPosition(node, 0); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } else if (cursorPosition > 0) { @@ -134,8 +130,6 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) } MoveCursorPosition(node, cursorPosition - 1); RedrawModified(node, cursorPosition); - //app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } @@ -146,7 +140,6 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) { data->buffer[0] = '\0'; node->Redraw(); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } else if (cursorPosition < len) { @@ -155,8 +148,6 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) data->buffer[n] = data->buffer[n + 1]; } RedrawModified(node, cursorPosition); - //app.renderer.RedrawModifiedTextField(activeWidget, textFieldCursorPosition); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } @@ -181,15 +172,11 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) { if (cursorPosition == -1) { - //app.renderer.RedrawWidget(activeWidget); MoveCursorPosition(node, strlen(data->buffer)); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } else if (cursorPosition > 0) { - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); MoveCursorPosition(node, cursorPosition - 1); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } @@ -197,16 +184,11 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) { if (cursorPosition == -1) { - //app.renderer.RedrawWidget(activeWidget); MoveCursorPosition(node, strlen(data->buffer)); - - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } else if (cursorPosition < strlen(data->buffer)) { - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); MoveCursorPosition(node, cursorPosition + 1); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } @@ -214,15 +196,11 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) { if (cursorPosition == -1) { - //app.renderer.RedrawWidget(activeWidget); MoveCursorPosition(node, strlen(data->buffer)); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } else { - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); MoveCursorPosition(node, strlen(data->buffer)); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } @@ -230,15 +208,11 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) { if (cursorPosition == -1) { - //app.renderer.RedrawWidget(activeWidget); MoveCursorPosition(node, strlen(data->buffer)); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } else { - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, true); MoveCursorPosition(node, 0); - //app.renderer.DrawTextFieldCursor(activeWidget, textFieldCursorPosition, false); } return true; } diff --git a/src/Platform.h b/src/Platform.h index 920cdb7..7b1f6db 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -36,24 +36,18 @@ class VideoDriver DrawSurface* drawSurface; }; -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 @@ -65,6 +59,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) {} }; diff --git a/src/Render.cpp b/src/Render.cpp index b628c7b..9b719be 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -36,9 +36,9 @@ void PageRenderer::DrawAll(DrawContext& context, Node* node) node = node->next; } - context.surface->BlitImage(context, Assets.imageIcon, 0, 50); - context.surface->BlitImage(context, Assets.imageIcon, 1, 100); - context.surface->BlitImage(context, Assets.imageIcon, 2, 150); + //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) diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 3572860..f760ef9 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -16,14 +16,15 @@ #include "../Platform.h" #include "WinVid.h" #include "WinInput.h" +#include "WinNet.h" #include "../Draw/Surface.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); 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..fb55789 --- /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 5 + +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 + From 11c7ab61870363a22f7270802c65acf0c008f610 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 20 Jul 2023 16:01:45 +0100 Subject: [PATCH 18/98] Resizable window in Windows - stretches output and corrects mouse position queries --- src/Windows/WinInput.cpp | 11 +++++++++-- src/Windows/WinVid.cpp | 40 ++++++++++------------------------------ 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/Windows/WinInput.cpp b/src/Windows/WinInput.cpp index ab6bba0..e5aeb6f 100644 --- a/src/Windows/WinInput.cpp +++ b/src/Windows/WinInput.cpp @@ -79,8 +79,15 @@ 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; + + x = p.x * Platform::video->screenWidth / width; + y = p.y * Platform::video->screenHeight / height; } } diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 09be833..37423be 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -49,7 +49,7 @@ void WindowsVideoDriver::Init() ZeroMemory(bitmapInfo, sizeof(BITMAPINFO)); bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapInfo->bmiHeader.biWidth = screenWidth; - bitmapInfo->bmiHeader.biHeight = screenHeight * verticalScale; + bitmapInfo->bmiHeader.biHeight = screenHeight; bitmapInfo->bmiHeader.biPlanes = 1; // bitmapInfo->bmiHeader.biBitCount = 32; bitmapInfo->bmiHeader.biBitCount = 1; @@ -93,7 +93,7 @@ void WindowsVideoDriver::Init() } else { - for (int n = 0; n < screenWidth * screenHeight * verticalScale; n++) + for (int n = 0; n < screenWidth * screenHeight; n++) { lpBitmapBits[n] = backgroundColour; } @@ -172,38 +172,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);*/ - - } From 33d1d84bf69536eb98edfa04dd9e89ee4f525be7 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 21 Jul 2023 11:27:43 +0100 Subject: [PATCH 19/98] Added support for extended character sets --- CGA.dat | Bin 0 -> 21768 bytes Default.dat | Bin 0 -> 42612 bytes EGA.dat | Bin 0 -> 32532 bytes LowRes.dat | Bin 0 -> 24225 bytes assets/CGA.dat | Bin 10224 -> 0 bytes assets/Default.dat | Bin 19152 -> 0 bytes assets/EGA.dat | Bin 14832 -> 0 bytes assets/Fonts/fonts.txt | 2 + assets/LowRes.dat | Bin 10128 -> 0 bytes examples/win1250.htm | 141 ++++++++++++ src/Draw/Surf1bpp.cpp | 8 +- src/Font.cpp | 10 +- src/Nodes/Text.cpp | 4 +- src/Parser.cpp | 2 + src/Tags.cpp | 4 + src/Unicode.inc | 480 ++++++++++++++++++++--------------------- tools/AssetGen.cpp | 2 +- tools/FontGen.cpp | 2 +- 18 files changed, 402 insertions(+), 253 deletions(-) create mode 100644 CGA.dat create mode 100644 Default.dat create mode 100644 EGA.dat create mode 100644 LowRes.dat delete mode 100644 assets/CGA.dat delete mode 100644 assets/Default.dat delete mode 100644 assets/EGA.dat delete mode 100644 assets/LowRes.dat create mode 100644 examples/win1250.htm diff --git a/CGA.dat b/CGA.dat new file mode 100644 index 0000000000000000000000000000000000000000..9795d2d29607898284dbc6438ca3d8e816e1f803 GIT binary patch literal 21768 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 N(a2qyoSYi-e*xB)J&6DS literal 0 HcmV?d00001 diff --git a/EGA.dat b/EGA.dat new file mode 100644 index 0000000000000000000000000000000000000000..1dfde1a0884e59522072eea09796e3e6d9fec29f GIT binary patch literal 32532 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 Ao&W#< literal 0 HcmV?d00001 diff --git a/LowRes.dat b/LowRes.dat new file mode 100644 index 0000000000000000000000000000000000000000..f4e9b3a98620601ca8c3ac143cb253adc8dbe1df GIT binary patch literal 24225 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 R4PVfH{qpl)`~2Pw^FMwwE_wg} literal 0 HcmV?d00001 diff --git a/assets/CGA.dat b/assets/CGA.dat deleted file mode 100644 index 1fbf3b176216685e09b1740b3180f4d3d79e03ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10224 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 diff --git a/assets/Fonts/fonts.txt b/assets/Fonts/fonts.txt index 000714f..dc30ba5 100644 --- a/assets/Fonts/fonts.txt +++ b/assets/Fonts/fonts.txt @@ -1,3 +1,5 @@ +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) diff --git a/assets/LowRes.dat b/assets/LowRes.dat deleted file mode 100644 index 4c813b8ae4276af6cf0d551af6a79043dfc767fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10128 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 Z0F+O28=3h7-s$m7DYOGn{Eb;n{SRA#)wTct 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/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 29f4144..a714a1e 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -260,13 +260,14 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* while (*text) { - char c = *text++; - if (c < 32 || c >= 128) + unsigned char c = (unsigned char) *text++; + + if (c < 32) { continue; } - char index = c - 32; + int index = c - 32; uint8_t glyphWidth = font->glyphWidth[index]; if (glyphWidth == 0) @@ -507,7 +508,6 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) { - return; x += context.drawOffsetX; y += context.drawOffsetY; int startY = y; diff --git a/src/Font.cpp b/src/Font.cpp index d10d12a..0950e87 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -21,11 +21,12 @@ int Font::CalculateWidth(const char* text, FontStyle::Type style) 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]; @@ -40,11 +41,12 @@ int Font::CalculateWidth(const char* text, FontStyle::Type style) int Font::GetGlyphWidth(char c, FontStyle::Type style) { - if (c < FIRST_FONT_GLYPH || c >= 128) + int index = (unsigned char)(c)-FIRST_FONT_GLYPH; + if (index < 0) { return 0; } - int result = glyphWidth[c - FIRST_FONT_GLYPH]; + int result = glyphWidth[index]; if (style & FontStyle::Bold) { result++; diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 8384b35..80fc61f 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -64,10 +64,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) for(charIndex = 0; data->text[charIndex]; charIndex++) { char c = data->text[charIndex]; - if (c < 32 || c > 128) - continue; - if (c == ' ') + if (c == ' ' || c == '\t') { lastBreakPoint = charIndex; lastBreakPointWidth = width; diff --git a/src/Parser.cpp b/src/Parser.cpp index 00dae14..146a466 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -22,6 +22,7 @@ #include "Unicode.inc" #include "Nodes/Text.h" #include "Nodes/ImgNode.h" +#include "Nodes/Break.h" // debug #include "Platform.h" @@ -483,6 +484,7 @@ void HTMLParser::ParseChar(char c) { FlushTextBuffer(); + EmitNode(BreakNode::Construct(page.allocator)); // TODO-refactor //page.BreakTextLine(); break; diff --git a/src/Tags.cpp b/src/Tags.cpp index 313afd5..3defba3 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -445,11 +445,15 @@ void MetaTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void PreformattedTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { // TODO-refactor + parser.PushPreFormatted(); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Monospace), this); } void PreformattedTagHandler::Close(class HTMLParser& parser) const { // TODO-refactor + parser.PopPreFormatted(); + parser.PopContext(this); parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } diff --git a/src/Unicode.inc b/src/Unicode.inc index d4263f5..0773ff3 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 = { { @@ -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) + "\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) } @@ -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/tools/AssetGen.cpp b/tools/AssetGen.cpp index 84e889b..9bd7c8b 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -52,7 +52,7 @@ void GenerateAssetPack(const char* name) EncodeImage(basePath, "image-icon.png", data, header.imageIconOffset); char outputPath[256]; - snprintf(outputPath, 256, "assets/%s.dat", name); + snprintf(outputPath, 256, "%s.dat", name); FILE* fs; fopen_s(&fs, outputPath, "wb"); diff --git a/tools/FontGen.cpp b/tools/FontGen.cpp index 027c0f5..d7c816e 100644 --- a/tools/FontGen.cpp +++ b/tools/FontGen.cpp @@ -390,7 +390,7 @@ void EncodeFont(const char* basePath, const char* imageFilename, vector { vector columnBuffer; - if (glyphWidths.size() >= (128 - 32)) + if (glyphWidths.size() >= (256 - 32)) { break; } From 4734964f29d8f20b1517534cbe54b9a791a14a0c Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 21 Jul 2023 15:36:04 +0100 Subject: [PATCH 20/98] Prebake bold fonts for improved rendering. Refactored asset pack system to allow for bigger sized data files --- CGA.dat | Bin 21768 -> 47060 bytes Default.dat | Bin 42612 -> 92544 bytes EGA.dat | Bin 32532 -> 70592 bytes LowRes.dat | Bin 24225 -> 51757 bytes src/DataPack.cpp | 138 ++++++++++++++++++++++++--------------- src/DataPack.h | 25 ++++--- src/Draw/Surf1bpp.cpp | 14 ---- src/Font.cpp | 9 --- src/Font.h | 8 +-- src/Interface.cpp | 4 +- src/Nodes/LinkNode.cpp | 2 +- src/Nodes/Scroll.cpp | 8 +++ src/Windows/Platform.cpp | 2 +- src/Windows/WinVid.cpp | 4 +- src/Windows/WinVid.h | 2 +- tools/AssetGen.cpp | 79 +++++++++++++++++----- tools/FontGen.cpp | 29 +++++++- tools/ImageGen.cpp | 3 +- tools/MouseGen.cpp | 3 +- 19 files changed, 210 insertions(+), 120 deletions(-) diff --git a/CGA.dat b/CGA.dat index 9795d2d29607898284dbc6438ca3d8e816e1f803..49cf7d783df5d46926bce73a04f40f75e49a16fe 100644 GIT binary patch literal 47060 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 delta 44 zcmV+{0Mq}}?*fRb0TciLV+%$asVvMguR#)2R#ci)-c+&9sVtM>V&AjBV~Qq|^0wlO CixhbP diff --git a/Default.dat b/Default.dat index 49cc9e18f1509e7f781604fd3386a7e5de350552..37f5f41d32f3b1aaedf4d28994aff1c41b29529f 100644 GIT binary patch literal 92544 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 Z^>Olwd1bPPu$z26N%-%!(RgCa{{@emv@-wz delta 45 zcmV+|0Mh@!rv#M#0TciLV-iLvX+sQ6?_v^uR(_g(-hQ#pX+x92l;5+TmC8An(h>pg Dpl=jv diff --git a/LowRes.dat b/LowRes.dat index f4e9b3a98620601ca8c3ac143cb253adc8dbe1df..1c8fdf71087ad591a916dc7ddc556299cb95392b 100644 GIT binary patch literal 51757 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 C?G!oy delta 44 zcmV+{0Mq}ilmnr?0TciLqX~u|sX9zVuTQdF_FXz&eqOQ8sXCJ(cHguAb}Ay1Qpwwm CzZ19s diff --git a/src/DataPack.cpp b/src/DataPack.cpp index a0f27d7..2cc79e2 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -1,6 +1,8 @@ #include #include +#include #include "DataPack.h" +#include DataPack Assets; @@ -15,77 +17,76 @@ bool DataPack::Load(const char* path) return false; } - fseek(fs, 0, SEEK_END); - long dataPackSize = ftell(fs); - fseek(fs, 0, SEEK_SET); - - if (dataPackSize <= sizeof(DataPackHeader)) - { - return false; - } - DataPackHeader header; - fread(&header, sizeof(DataPackHeader), 1, fs); - - long rawDataSize = dataPackSize - sizeof(DataPackHeader); - uint8_t* rawData = new uint8_t[rawDataSize]; - if (!rawData) - { - return false; - } - - fread(rawData, 1, rawDataSize, fs); - + fread(&header.numEntries, sizeof(uint16_t), 1, fs); + header.entries = new DataPackEntry[header.numEntries]; + 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 = (Image*)LoadAsset(fs, header, "IIMG"); + + fonts[0] = (Font*)LoadAsset(fs, header, "FHELV1"); + fonts[1] = (Font*)LoadAsset(fs, header, "FHELV2"); + fonts[2] = (Font*)LoadAsset(fs, header, "FHELV3"); + boldFonts[0] = (Font*)LoadAsset(fs, header, "FHELV1B"); + boldFonts[1] = (Font*)LoadAsset(fs, header, "FHELV2B"); + boldFonts[2] = (Font*)LoadAsset(fs, header, "FHELV3B"); + monoFonts[0] = (Font*)LoadAsset(fs, header, "FCOUR1"); + monoFonts[1] = (Font*)LoadAsset(fs, header, "FCOUR2"); + monoFonts[2] = (Font*)LoadAsset(fs, header, "FCOUR3"); + boldMonoFonts[0] = (Font*)LoadAsset(fs, header, "FCOUR1B"); + boldMonoFonts[1] = (Font*)LoadAsset(fs, header, "FCOUR2B"); + boldMonoFonts[2] = (Font*)LoadAsset(fs, header, "FCOUR3B"); + + delete[] header.entries; fclose(fs); - pointerCursor = (MouseCursorData*)(&rawData[header.pointerCursorOffset]); - linkCursor = (MouseCursorData*)(&rawData[header.linkCursorOffset]); - textSelectCursor = (MouseCursorData*)(&rawData[header.textSelectCursorOffset]); - - imageIcon = (Image*)(&rawData[header.imageIconOffset]); + return true; +} - for (int n = 0; n < NUM_FONT_SIZES; n++) +int DataPack::FontSizeToIndex(int fontSize) +{ + switch (fontSize) { - memcpy(&fonts[n], &rawData[header.fontOffsets[n]], sizeof(FontMetaData)); - fonts[n].glyphData = &rawData[header.fontOffsets[n] + sizeof(FontMetaData)]; - - memcpy(&monoFonts[n], &rawData[header.monoFontOffsets[n]], sizeof(FontMetaData)); - monoFonts[n].glyphData = &rawData[header.monoFontOffsets[n] + sizeof(FontMetaData)]; + case 0: + return 0; + case 2: + case 3: + case 4: + return 2; + default: + return 1; } - - return true; } Font* DataPack::GetFont(int fontSize, FontStyle::Type fontStyle) { if (fontStyle & FontStyle::Monospace) { - switch (fontSize) + if (fontStyle & FontStyle::Bold) { - case 0: - return &monoFonts[0]; - case 2: - case 3: - case 4: - return &monoFonts[2]; - default: - return &monoFonts[1]; + return boldMonoFonts[FontSizeToIndex(fontSize)]; + } + else + { + return monoFonts[FontSizeToIndex(fontSize)]; } } - - switch (fontSize) + else { - case 0: - return &fonts[0]; - case 2: - case 3: - case 4: - return &fonts[2]; - default: - return &fonts[1]; + if (fontStyle & FontStyle::Bold) + { + return boldFonts[FontSizeToIndex(fontSize)]; + } + else + { + return fonts[FontSizeToIndex(fontSize)]; + } } - } MouseCursorData* DataPack::GetMouseCursorData(MouseCursor::Type type) @@ -101,3 +102,34 @@ MouseCursorData* DataPack::GetMouseCursorData(MouseCursor::Type type) } return NULL; } + +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); + } + + fread(buffer, length, 1, fs); + + return buffer; +} diff --git a/src/DataPack.h b/src/DataPack.h index 6051c47..60af419 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -1,6 +1,7 @@ #ifndef _DATAPACK_H_ #define _DATAPACK_H_ +#include #include #include "Cursor.h" #include "Image.h" @@ -37,14 +38,16 @@ uint8 glyphData[variable] #define NUM_FONT_SIZES 3 +struct DataPackEntry +{ + char name[8]; + uint32_t offset; +}; + struct DataPackHeader { - uint16_t fontOffsets[NUM_FONT_SIZES]; - uint16_t monoFontOffsets[NUM_FONT_SIZES]; - uint16_t pointerCursorOffset; - uint16_t linkCursorOffset; - uint16_t textSelectCursorOffset; - uint16_t imageIconOffset; + uint16_t numEntries; + DataPackEntry* entries; }; struct DataPack @@ -54,12 +57,18 @@ struct DataPack MouseCursorData* textSelectCursor; Image* imageIcon; - Font fonts[NUM_FONT_SIZES]; - Font monoFonts[NUM_FONT_SIZES]; + Font* fonts[NUM_FONT_SIZES]; + Font* boldFonts[NUM_FONT_SIZES]; + Font* monoFonts[NUM_FONT_SIZES]; + Font* boldMonoFonts[NUM_FONT_SIZES]; 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); + int FontSizeToIndex(int fontSize); }; extern DataPack Assets; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index a714a1e..8b7e69a 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -297,11 +297,6 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* { uint8_t glyphPixels = *glyphData++; - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - VRAMptr[i] &= ~(glyphPixels >> writeOffset); VRAMptr[i + 1] &= ~(glyphPixels << (8 - writeOffset)); } @@ -325,11 +320,6 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* { uint8_t glyphPixels = *glyphData++; - if (style & FontStyle::Bold) - { - glyphPixels |= (glyphPixels >> 1); - } - VRAMptr[i] |= (glyphPixels >> writeOffset); VRAMptr[i + 1] |= (glyphPixels << (8 - writeOffset)); } @@ -340,10 +330,6 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* } x += glyphWidth; - if (style & FontStyle::Bold) - { - x++; - } if (x >= context.clipRight) { diff --git a/src/Font.cpp b/src/Font.cpp index 0950e87..97514b1 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -29,11 +29,6 @@ int Font::CalculateWidth(const char* text, FontStyle::Type style) } result += glyphWidth[index]; - - if (style & FontStyle::Bold) - { - result++; - } } return result; @@ -47,9 +42,5 @@ int Font::GetGlyphWidth(char c, FontStyle::Type style) return 0; } int result = glyphWidth[index]; - if (style & FontStyle::Bold) - { - result++; - } return result; } diff --git a/src/Font.h b/src/Font.h index 9cbf48d..266c9a3 100644 --- a/src/Font.h +++ b/src/Font.h @@ -32,17 +32,13 @@ struct FontStyle }; }; -struct FontMetaData +struct Font { uint8_t glyphWidth[LAST_FONT_GLYPH + 1 - FIRST_FONT_GLYPH]; uint8_t glyphWidthBytes; uint8_t glyphHeight; uint8_t glyphDataStride; -}; - -struct Font : public FontMetaData -{ - 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); diff --git a/src/Interface.cpp b/src/Interface.cpp index 8f9a801..1bc152b 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -300,6 +300,7 @@ void AppInterface::GenerateInterfaceNodes() rootInterfaceNode->style.fontStyle = FontStyle::Regular; Font* interfaceFont = Assets.GetFont(1, FontStyle::Regular); + Font* smallInterfaceFont = Assets.GetFont(0, FontStyle::Regular); { titleBuffer[0] = '\0'; @@ -337,10 +338,11 @@ void AppInterface::GenerateInterfaceNodes() statusBarNode = StatusBarNode::Construct(allocator); statusBarNode->style = rootInterfaceNode->style; statusBarNode->size.x = Platform::video->screenWidth; - statusBarNode->size.y = interfaceFont->glyphHeight + 2; + statusBarNode->size.y = smallInterfaceFont->glyphHeight + 2; statusBarNode->anchor.x = 0; statusBarNode->anchor.y = Platform::video->screenHeight - statusBarNode->size.y; rootInterfaceNode->AddChild(statusBarNode); + statusBarNode->style.fontSize = 0; scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight); scrollBarNode->style = rootInterfaceNode->style; diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 14b8bcb..62a568a 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -27,7 +27,7 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) LinkNode::Data* data = static_cast(node->data); if (data->url) { - event.app.OpenURL(data->url); + App::Get().OpenURL(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url); } return true; } diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp index 325c9e0..ec71b2e 100644 --- a/src/Nodes/Scroll.cpp +++ b/src/Nodes/Scroll.cpp @@ -33,8 +33,16 @@ void ScrollBarNode::Draw(DrawContext& context, Node* node) { widgetSize = minWidgetSize; } + if (widgetSize > maxWidgetSize) + { + widgetSize = maxWidgetSize; + } int maxWidgetPosition = node->size.y - widgetSize; int widgetPosition = (int32_t)maxWidgetPosition * data->scrollPosition / data->maxScroll; + if (widgetPosition < 0) + widgetPosition = 0; + if (widgetPosition > maxWidgetPosition) + widgetPosition = maxWidgetPosition; context.surface->VerticalScrollBar(context, node->anchor.x, node->anchor.y, node->size.y, widgetPosition, widgetSize); } diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index f760ef9..092420a 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -42,7 +42,7 @@ void Platform::Init(int argc, char* argv[]) wc.lpfnWndProc = WndProc; wc.hCursor = LoadCursor(0, IDC_ARROW); - RECT wr = { 0, 0, winVid.screenWidth, winVid.screenHeight * winVid.verticalScale }; + RECT wr = { 0, 0, winVid.screenWidth, (int)(winVid.screenHeight * winVid.verticalScale) }; AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); RegisterClassW(&wc); diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 37423be..a2e5142 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -33,9 +33,11 @@ WindowsVideoDriver::WindowsVideoDriver() screenHeight = SCREEN_HEIGHT; foregroundColour = RGB(0, 0, 0); backgroundColour = RGB(255, 255, 255); - verticalScale = 1; + verticalScale = ((screenWidth * 3.0f) / 4.0f) / screenHeight; + //verticalScale = 1.0f; Assets.Load("Default.dat"); +// Assets.Load("EGA.dat"); // Assets.Load("Lowres.dat"); // Assets.Load("CGA.dat"); } diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index 76787d8..82beaa7 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -31,7 +31,7 @@ class WindowsVideoDriver : public VideoDriver void Paint(HWND hwnd); - int verticalScale; + float verticalScale; private: void SetPixel(int x, int y, uint32_t colour); diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 9bd7c8b..429566f 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -25,31 +25,66 @@ using namespace std; //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, uint16_t& headerOffsetPosition); -void EncodeCursor(const char* basePath, const char* name, vector& output, uint16_t& headerOffsetPosition); -void EncodeImage(const char* basePath, const char* name, vector& output, uint16_t& headerOffsetPosition); +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 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]; - DataPackHeader header; vector data; + vector entries; snprintf(basePath, 256, "assets/%s/", name); - EncodeFont(basePath, "Helv1.png", data, header.fontOffsets[0]); - EncodeFont(basePath, "Helv2.png", data, header.fontOffsets[1]); - EncodeFont(basePath, "Helv3.png", data, header.fontOffsets[2]); - - EncodeFont(basePath, "Cour1.png", data, header.monoFontOffsets[0]); - EncodeFont(basePath, "Cour2.png", data, header.monoFontOffsets[1]); - EncodeFont(basePath, "Cour3.png", data, header.monoFontOffsets[2]); - - EncodeCursor(basePath, "mouse.png", data, header.pointerCursorOffset); - EncodeCursor(basePath, "mouse-link.png", data, header.linkCursorOffset); - EncodeCursor(basePath, "mouse-select.png", data, header.textSelectCursorOffset); - - EncodeImage(basePath, "image-icon.png", data, header.imageIconOffset); + 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("END", entries, data); char outputPath[256]; snprintf(outputPath, 256, "%s.dat", name); @@ -58,7 +93,15 @@ void GenerateAssetPack(const char* name) fopen_s(&fs, outputPath, "wb"); if (fs) { - fwrite(&header, sizeof(DataPackHeader), 1, 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); diff --git a/tools/FontGen.cpp b/tools/FontGen.cpp index d7c816e..f5625a2 100644 --- a/tools/FontGen.cpp +++ b/tools/FontGen.cpp @@ -362,10 +362,8 @@ void GenerateDummyFont(ofstream& outputFile, const char* varName) outputFile << "};" << endl << endl; } -void EncodeFont(const char* basePath, const char* imageFilename, vector& outputStream, uint16_t& headerOffsetPosition) +void EncodeFont(const char* basePath, const char* imageFilename, vector& outputStream, bool generateBold) { - headerOffsetPosition = (uint16_t) outputStream.size(); - char imageFilePath[256]; snprintf(imageFilePath, 256, "%s%s", basePath, imageFilename); vector data; @@ -398,6 +396,25 @@ void EncodeFont(const char* basePath, const char* imageFilename, vector 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); @@ -413,6 +430,12 @@ void EncodeFont(const char* basePath, const char* imageFilename, vector 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); diff --git a/tools/ImageGen.cpp b/tools/ImageGen.cpp index 1409cbe..0e8eb30 100644 --- a/tools/ImageGen.cpp +++ b/tools/ImageGen.cpp @@ -96,9 +96,8 @@ void WriteOutput(vector& outputData, uint16_t x) outputData.push_back((uint8_t)(x >> 8)); } -void EncodeImage(const char* basePath, const char* imageFilename, vector& outputData, uint16_t& headerOffsetPosition) +void EncodeImage(const char* basePath, const char* imageFilename, vector& outputData) { - headerOffsetPosition = (uint16_t)(outputData.size()); char imageFilePath[256]; snprintf(imageFilePath, 256, "%s%s", basePath, imageFilename); diff --git a/tools/MouseGen.cpp b/tools/MouseGen.cpp index 3178efe..3b06422 100644 --- a/tools/MouseGen.cpp +++ b/tools/MouseGen.cpp @@ -184,9 +184,8 @@ void EncodeCursor(vector& outputData, const char* imageFilename, outputData.push_back((uint8_t)(hotSpotX >> 8)); } -void EncodeCursor(const char* basePath, const char* imageFilename, vector& outputData, uint16_t& headerOffsetPosition) +void EncodeCursor(const char* basePath, const char* imageFilename, vector& outputData) { - headerOffsetPosition = (uint16_t)(outputData.size()); char imageFilePath[256]; snprintf(imageFilePath, 256, "%s%s", basePath, imageFilename); From 185bbd6ef3864767a5228f994f0da5751237ef3e Mon Sep 17 00:00:00 2001 From: James Howard Date: Mon, 24 Jul 2023 22:15:52 +0100 Subject: [PATCH 21/98] Improved node picking logic --- src/App.cpp | 2 +- src/Interface.cpp | 53 ++++++++++++---------------------------- src/Interface.h | 4 +-- src/Node.h | 1 + src/Nodes/Button.h | 1 + src/Nodes/Field.h | 3 ++- src/Nodes/LinkNode.h | 2 +- src/Nodes/Text.cpp | 5 ++++ src/Nodes/Text.h | 3 +++ src/Windows/WinInput.cpp | 7 ++++-- 10 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 359d6b8..f6c5278 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -105,7 +105,7 @@ void App::Run(int argc, char* argv[]) } } } - if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) + if (loadTask.type == LoadTask::RemoteFile && loadTask.request)// && loadTask.request->GetStatus() == HTTPRequest::Connecting) ui.SetStatusMessage(loadTask.request->GetStatusString()); ui.Update(); diff --git a/src/Interface.cpp b/src/Interface.cpp index 1bc152b..f3ef468 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -72,14 +72,7 @@ void AppInterface::Update() Node* oldHoverNode = hoverNode; - if (hoverNode && !IsOverNode(hoverNode, mouseX, mouseY)) - { - hoverNode = PickNode(mouseX, mouseY); - } - else if (!hoverNode && (mouseX != oldMouseX || mouseY != oldMouseY)) - { - hoverNode = PickNode(mouseX, mouseY); - } + hoverNode = PickFocusableNode(mouseX, mouseY); if ((buttons & 1) && !(oldButtons & 1)) { @@ -218,43 +211,29 @@ void AppInterface::Update() } } -bool AppInterface::IsOverNode(Node* node, int x, int y) +Node* AppInterface::PickFocusableNode(int x, int y) { - if (!node) - { - return false; - } - if (IsInterfaceNode(node)) - { - return node->IsPointInsideNode(x, y); - } - else - { - x -= windowRect.x; - y -= windowRect.y - scrollPositionY; - return node->IsPointInsideNode(x, y); - } -} - -Node* AppInterface::PickNode(int x, int y) -{ - Node* interfaceNode = rootInterfaceNode->Handler().Pick(rootInterfaceNode, x, y); + Node* result = nullptr; + result = rootInterfaceNode->Handler().Pick(rootInterfaceNode, x, y); - if (interfaceNode) + if (!result) { - 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(); + result = pageRootNode->Handler().Pick(pageRootNode, pageX, pageY); + } } - if (x >= windowRect.x && y >= windowRect.y && x < windowRect.x + windowRect.width && y < windowRect.y + windowRect.height) + while (result && !result->Handler().CanFocus(result)) { - int pageX = x - windowRect.x; - int pageY = y - windowRect.y + scrollPositionY; - - Node* pageRootNode = app.page.GetRootNode(); - return pageRootNode->Handler().Pick(pageRootNode, pageX, pageY); + result = result->parent; } - return nullptr; + return result; } void AppInterface::HandleClick(int mouseX, int mouseY) diff --git a/src/Interface.h b/src/Interface.h index ff1bbc1..90c6d5c 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -60,13 +60,11 @@ class AppInterface private: void GenerateInterfaceNodes(); - Node* PickNode(int x, int y); + Node* PickFocusableNode(int x, int y); void HandleClick(int mouseX, int mouseY); void HandleRelease(); - bool IsOverNode(Node* node, int x, int y); - static void OnBackButtonPressed(Node* node); static void OnForwardButtonPressed(Node* node); static void OnAddressBarSubmit(Node* node); diff --git a/src/Node.h b/src/Node.h index 06b2038..65207c9 100644 --- a/src/Node.h +++ b/src/Node.h @@ -23,6 +23,7 @@ class NodeHandler virtual void ApplyStyle(Node* node) {} virtual Node* Pick(Node* node, int x, int y); virtual bool CanPick(Node* node) { return false; } + virtual bool CanFocus(Node* node) { return false; } virtual bool HandleEvent(Node* node, const Event& event) { return false; } }; diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h index 3ff3d8d..6dfe5c2 100644 --- a/src/Nodes/Button.h +++ b/src/Nodes/Button.h @@ -17,6 +17,7 @@ class ButtonNode : public NodeHandler 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 CanFocus(Node* node) { return true; } virtual bool CanPick(Node* node) { return true; } virtual bool HandleEvent(Node* node, const Event& event); diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 164b614..2fad122 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -22,7 +22,8 @@ class TextFieldNode : public NodeHandler 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) { return true; } + virtual bool CanPick(Node* node) override { return true; } + virtual bool CanFocus(Node* node) override { return true; } virtual bool HandleEvent(Node* node, const Event& event) override; private: diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h index f5962cf..a2a88d4 100644 --- a/src/Nodes/LinkNode.h +++ b/src/Nodes/LinkNode.h @@ -13,7 +13,7 @@ class LinkNode : public NodeHandler }; virtual void ApplyStyle(Node* node) override; - virtual bool CanPick(Node* node) override { return true; } + virtual bool CanFocus(Node* node) override { return true; } virtual bool HandleEvent(Node* node, const Event& event) override; static Node* Construct(Allocator& allocator, char* url); diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 80fc61f..3a80fcd 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -139,6 +139,11 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) layout.ProgressCursor(subTextNode, width, lineHeight); } } + + if (node->firstChild) + { + node->EncapsulateChildren(); + } } Node* SubTextElement::Construct(Allocator& allocator, const char* text) diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h index 77e3fe2..4c04ceb 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -15,6 +15,8 @@ class TextElement : public NodeHandler static Node* Construct(Allocator& allocator, const char* text); virtual void Draw(DrawContext& context, Node* element) override; virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual bool CanPick(Node* node) override { return node->firstChild == NULL; } + }; class SubTextElement : public TextElement @@ -22,4 +24,5 @@ class SubTextElement : public TextElement public: static Node* Construct(Allocator& allocator, const char* text); virtual void GenerateLayout(Layout& layout, Node* node) override; + virtual bool CanPick(Node* node) override { return true; } }; diff --git a/src/Windows/WinInput.cpp b/src/Windows/WinInput.cpp index e5aeb6f..6a8a449 100644 --- a/src/Windows/WinInput.cpp +++ b/src/Windows/WinInput.cpp @@ -86,8 +86,11 @@ void WindowsInputDriver::GetMouseStatus(int& buttons, int& x, int& y) int width = windowRect.right - windowRect.left; int height = windowRect.bottom - windowRect.top; - x = p.x * Platform::video->screenWidth / width; - y = p.y * Platform::video->screenHeight / height; + if (width > 0 && height > 0) + { + x = p.x * Platform::video->screenWidth / width; + y = p.y * Platform::video->screenHeight / height; + } } } From 686ef774e0356c20df558e713a037588bf19af96 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 31 Jan 2024 19:46:20 +0000 Subject: [PATCH 22/98] Added 4-bit planar colour screen routines for EGA/VGA --- project/DOS/DOS.vcxproj | 2 + project/DOS/Makefile | 5 +- src/DOS/EGA.cpp | 9 + src/DOS/EGA.h | 3 +- src/DOS/Surf4bpp.cpp | 641 ++++++++++++++++++++++++++++++++++++++++ src/Draw/Surf4bpp.h | 24 ++ src/Interface.cpp | 4 +- src/Nodes/Field.cpp | 2 +- src/Nodes/Status.cpp | 2 +- 9 files changed, 686 insertions(+), 6 deletions(-) create mode 100644 src/DOS/Surf4bpp.cpp create mode 100644 src/Draw/Surf4bpp.h diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 3fcea34..4c3f1c7 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -105,6 +105,8 @@ + + diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 62d46b7..cf320fd 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -127,6 +127,9 @@ DOSNet.obj: $(SRC_PATH)\DOS\DOSNet.cpp Surf1bpp.obj: $(SRC_PATH)\Draw\Surf1bpp.cpp $(CC) -fo=$@ $(CFLAGS) $< +Surf4bpp.obj: $(SRC_PATH)\DOS\Surf4bpp.cpp + $(CC) -fo=$@ $(CFLAGS) $< + clean: .symbolic del *.obj del $(bin) \ No newline at end of file diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp index 456371d..9acaad2 100644 --- a/src/DOS/EGA.cpp +++ b/src/DOS/EGA.cpp @@ -23,6 +23,7 @@ #include "../DataPack.h" #include "../Interface.h" #include "../Draw/Surf1bpp.h" +#include "../Draw/Surf4bpp.h" #define EGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) #define BYTES_PER_LINE 80 @@ -47,12 +48,20 @@ void EGADriver::Init() Assets.Load(assetPackToUse); + /* DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); for (int y = 0; y < screenHeight; y++) { drawSurface1BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); } drawSurface = drawSurface1BPP; + */ + DrawSurface_4BPP* drawSurface4BPP = new DrawSurface_4BPP(screenWidth, screenHeight); + for (int y = 0; y < screenHeight; y++) + { + drawSurface4BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); + } + drawSurface = drawSurface4BPP; } void EGADriver::Shutdown() diff --git a/src/DOS/EGA.h b/src/DOS/EGA.h index 86e0992..05c886d 100644 --- a/src/DOS/EGA.h +++ b/src/DOS/EGA.h @@ -47,7 +47,8 @@ class VGADriver : public EGADriver public: VGADriver() { - SetupVars("DEFAULT.DAT", 0x11, 480); +// SetupVars("DEFAULT.DAT", 0x11, 480); + SetupVars("DEFAULT.DAT", 0x12, 480); } virtual void ScaleImageDimensions(int& width, int& height) {} }; diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp new file mode 100644 index 0000000..ea3f8bb --- /dev/null +++ b/src/DOS/Surf4bpp.cpp @@ -0,0 +1,641 @@ +#include +#include +#include +#include "../Draw/Surf4bpp.h" +#include "../Font.h" +#include "../Image.h" + +#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 GC_XOR 0x18 + +static uint8_t pixelBitmasks[8] = +{ + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static uint8_t pixelStartBitmasks[8] = +{ + 0xff, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01 +}; + +static uint8_t pixelEndBitmasks[8] = +{ + 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff +}; + +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]; +} + +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]; + uint8_t* VRAMend = VRAMptr; + VRAMptr += (x >> 3); + VRAMend += ((x + count) >> 3); + + if (VRAMptr == VRAMend) + { + // Starts and ends within the same byte + uint8_t mask = 0; + while (count--) + { + mask |= pixelBitmasks[x & 7]; + x++; + } + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr |= 0xff; + } + else + { + uint8_t mask; + + // Start byte + mask = pixelStartBitmasks[x & 7]; + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + + // Middle bytes + count -= 8 - (x & 7); + 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 - 1 - 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]; + uint8_t* VRAMend = VRAMptr; + VRAMptr += (x >> 3); + VRAMend += ((x + count) >> 3); + + if (VRAMptr == VRAMend) + { + // Starts and ends within the same byte + uint8_t mask = 0; + while (count--) + { + mask |= pixelBitmasks[x & 7]; + x++; + } + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr |= 0xff; + } + else + { + uint8_t mask; + + // Start byte + mask = pixelStartBitmasks[x & 7]; + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + + // Middle bytes + count -= 8 - (x & 7); + 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); + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int 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); + + 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++; + } + + for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + 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 (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::BlitImage(DrawContext& context, Image* image, int x, int y) +{ + 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); + } + } +} + + +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]; + uint8_t* VRAMend = VRAMptr; + VRAMptr += (x >> 3); + VRAMend += ((x + count) >> 3); + + if (VRAMptr == VRAMend) + { + // Starts and ends within the same byte + uint8_t mask = 0; + while (count--) + { + mask |= pixelBitmasks[x & 7]; + x++; + } + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr |= 0xff; + } + else + { + uint8_t mask; + + // Start byte + mask = pixelStartBitmasks[x & 7]; + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + + // Middle bytes + count -= 8 - (x & 7); + 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); + } +} diff --git a/src/Draw/Surf4bpp.h b/src/Draw/Surf4bpp.h new file mode 100644 index 0000000..8128b7e --- /dev/null +++ b/src/Draw/Surf4bpp.h @@ -0,0 +1,24 @@ +#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); + + uint8_t** lines; +}; + +#endif diff --git a/src/Interface.cpp b/src/Interface.cpp index 1bc152b..164c164 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -383,7 +383,7 @@ void AppInterface::SetTitle(const char* title) DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); Platform::input->HideMouse(); - uint8_t fillColour = 1; + uint8_t fillColour = 0xf; context.surface->FillRect(context, 0, 0, titleNode->size.x, titleNode->size.y, fillColour); titleNode->Handler().Draw(context, titleNode); Platform::input->ShowMouse(); @@ -446,7 +446,7 @@ void AppInterface::ScrollRelative(int delta) DrawContext context; app.pageRenderer.GenerateDrawContext(context, NULL); context.drawOffsetY = 0; - context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 1); + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 0xf); app.pageRenderer.GenerateDrawContext(context, NULL); app.pageRenderer.DrawAll(context, app.page.GetRootNode()); } diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 1ada574..a22c4ac 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -14,7 +14,7 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); uint8_t textColour = 0; uint8_t buttonOutlineColour = 0; - uint8_t clearColour = 1; + uint8_t clearColour = 0xf; 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); diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp index 8a7f014..57da6ea 100644 --- a/src/Nodes/Status.cpp +++ b/src/Nodes/Status.cpp @@ -22,7 +22,7 @@ void StatusBarNode::Draw(DrawContext& context, Node* node) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); uint8_t textColour = 0; - uint8_t clearColour = 1; + uint8_t clearColour = 0xf; 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); context.surface->DrawString(context, font, data->message, node->anchor.x + 1, node->anchor.y + 1, textColour, node->style.fontStyle); From e4ffec6fe58dfac2460c2f2a565f798f7b4f49ba Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 31 Jan 2024 21:28:59 +0000 Subject: [PATCH 23/98] Update the dimensions of the node hierarchy to correctly pick nodes --- src/Interface.cpp | 4 ++-- src/Layout.cpp | 5 +++++ src/Node.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- src/Node.h | 2 ++ src/Nodes/Text.cpp | 2 ++ src/Page.cpp | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/Interface.cpp b/src/Interface.cpp index 164c164..4a073f2 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -226,13 +226,13 @@ bool AppInterface::IsOverNode(Node* node, int x, int y) } if (IsInterfaceNode(node)) { - return node->IsPointInsideNode(x, y); + return node->IsPointInsideChildren(x, y); } else { x -= windowRect.x; y -= windowRect.y - scrollPositionY; - return node->IsPointInsideNode(x, y); + return node->IsPointInsideChildren(x, y); } } diff --git a/src/Layout.cpp b/src/Layout.cpp index 4a1b421..4c52e14 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -52,6 +52,11 @@ void Layout::BreakNewLine() TranslateNodes(lineStartNode, shift, 0, true); } + if (lineStartNode && lineStartNode->parent) + { + lineStartNode->parent->OnChildLayoutChanged(); + } + cursor.x = GetParams().marginLeft; cursor.y += currentLineHeight; currentLineHeight = 0; diff --git a/src/Node.cpp b/src/Node.cpp index 34eb67a..b14cb0a 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -60,6 +60,15 @@ void Node::AddChild(Node* child) } } +void Node::OnChildLayoutChanged() +{ + EncapsulateChildren(); + if (parent) + { + parent->OnChildLayoutChanged(); + } +} + void Node::EncapsulateChildren() { if (firstChild) @@ -94,6 +103,29 @@ 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 (!IsPointInsideNode(x, y)) + { + return false; + } + + if (firstChild == nullptr) + { + return true; + } + + 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->IsPointInsideNode(x, y)) @@ -103,7 +135,14 @@ Node* NodeHandler::Pick(Node* node, int x, int y) if (CanPick(node)) { - return node; + // 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) diff --git a/src/Node.h b/src/Node.h index 06b2038..e1dbaa2 100644 --- a/src/Node.h +++ b/src/Node.h @@ -70,6 +70,8 @@ class Node void AddChild(Node* child); void EncapsulateChildren(); // Sets anchor and size based on children bool IsPointInsideNode(int x, int y); + bool IsPointInsideChildren(int x, int y); + void OnChildLayoutChanged(); Node* FindParentOfType(Node::Type searchType); void Redraw(); diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 80fc61f..3ce9ca6 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -139,6 +139,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) layout.ProgressCursor(subTextNode, width, lineHeight); } } + + node->EncapsulateChildren(); } Node* SubTextElement::Construct(Allocator& allocator, const char* text) diff --git a/src/Page.cpp b/src/Page.cpp index d6473f0..7b09f4f 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -22,6 +22,7 @@ #include "Nodes/Section.h" #include "Nodes/Text.h" #include "Nodes/Form.h" +#include "Nodes/StyNode.h" #define TOP_MARGIN_PADDING 1 @@ -122,6 +123,38 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) printf("<%s> action: %s\n", nodeTypeNames[node->type], data->action ? data->action : "NONE"); } break; + case Node::Style: + { + 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) + { + switch (data->styleOverride.styleSettings.alignment) + { + case ElementAlignment::Center: + printf("align center "); + break; + } + } + if (data->styleOverride.overrideMask.fontStyle) + { + if (data->styleOverride.styleSettings.fontStyle & FontStyle::Bold) + { + printf("bold "); + } + if (data->styleOverride.styleSettings.fontStyle & FontStyle::Italic) + { + printf("italic "); + } + if (data->styleOverride.styleSettings.fontStyle & FontStyle::Underline) + { + printf("underline "); + } + } + + 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; From 7a3e0a313adb7aaadeba3dad5cc799371e418aba Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sat, 10 Feb 2024 19:59:36 +0000 Subject: [PATCH 24/98] Improvedments to layout generation and added support for parsing and showing coloured text in colour video modes --- project/AssetGen/AssetGen.vcxproj | 1 + project/Windows/Windows.vcxproj | 2 + src/DOS/EGA.cpp | 7 + src/Draw/Surf1bpp.cpp | 13 +- src/Draw/Surf8bpp.cpp | 418 ++++++++++++++++++++++++++++++ src/Draw/Surf8bpp.h | 24 ++ src/Interface.cpp | 4 +- src/Layout.cpp | 11 + src/Node.cpp | 42 +++ src/Nodes/Break.cpp | 39 ++- src/Nodes/Break.h | 6 +- src/Nodes/Button.cpp | 6 +- src/Nodes/LinkNode.cpp | 1 + src/Nodes/Text.cpp | 7 +- src/Page.cpp | 2 + src/Palettes.inc | 18 ++ src/Parser.cpp | 69 +++++ src/Parser.h | 2 + src/Platform.h | 3 + src/Style.h | 12 + src/Tags.cpp | 65 ++++- src/Theme.h | 12 + src/Windows/Platform.cpp | 4 +- src/Windows/WinVid.cpp | 100 +++++-- tools/AssetGen.cpp | 5 + tools/PaletteGen.cpp | 91 +++++++ 26 files changed, 920 insertions(+), 44 deletions(-) create mode 100644 src/Draw/Surf8bpp.cpp create mode 100644 src/Draw/Surf8bpp.h create mode 100644 src/Palettes.inc create mode 100644 src/Theme.h create mode 100644 tools/PaletteGen.cpp 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/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 02e3b42..47fc869 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -142,6 +142,7 @@ + @@ -174,6 +175,7 @@ + diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp index 9acaad2..1288bff 100644 --- a/src/DOS/EGA.cpp +++ b/src/DOS/EGA.cpp @@ -24,6 +24,7 @@ #include "../Interface.h" #include "../Draw/Surf1bpp.h" #include "../Draw/Surf4bpp.h" +#include "../Palettes.inc" #define EGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) #define BYTES_PER_LINE 80 @@ -39,6 +40,7 @@ void EGADriver::SetupVars(const char* inAssetPackToUse, int inScreenMode, int in screenModeToUse = inScreenMode; screenWidth = 640; screenHeight = inScreenHeight; + paletteLUT = cgaPaletteLUT; } void EGADriver::Init() @@ -62,6 +64,11 @@ void EGADriver::Init() drawSurface4BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); } drawSurface = drawSurface4BPP; + + colourScheme.pageColour = 0xf; + colourScheme.linkColour = 1; + colourScheme.textColour = 0; + colourScheme.buttonColour = 7; } void EGADriver::Shutdown() diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 8b7e69a..6f4777b 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -275,6 +275,11 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* continue; } + if (x + glyphWidth > context.clipRight) + { + break; + } + uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); glyphData += (firstLine * font->glyphWidthBytes); @@ -331,10 +336,10 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* x += glyphWidth; - if (x >= context.clipRight) - { - break; - } + //if (x >= context.clipRight) + //{ + // break; + //} } if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp new file mode 100644 index 0000000..c925aee --- /dev/null +++ b/src/Draw/Surf8bpp.cpp @@ -0,0 +1,418 @@ +#include +#include "Surf8bpp.h" +#include "../Font.h" +#include "../Image.h" + +DrawSurface_8BPP::DrawSurface_8BPP(int inWidth, int inHeight) + : DrawSurface(inWidth, inHeight) +{ + lines = new uint8_t * [height]; +} + +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 - 1 - 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; + } + + while (*text) + { + unsigned char c = (unsigned char) *text++; + + if (c < 32) + { + continue; + } + + int index = c - 32; + uint8_t glyphWidth = font->glyphWidth[index]; + + if (glyphWidth == 0) + { + continue; + } + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); + + glyphData += (firstLine * font->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++; + } + + for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + { + uint8_t glyphPixels = *glyphData++; + + 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 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++; + } +} + +void DrawSurface_8BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) +{ +#if 0 + 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; + + 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; + } +#endif +} + +void DrawSurface_8BPP::Clear() +{ + int widthBytes = width; + for (int y = 0; y < height; y++) + { + memset(lines[y], 0xf, widthBytes); + } +} diff --git a/src/Draw/Surf8bpp.h b/src/Draw/Surf8bpp.h new file mode 100644 index 0000000..b2e2ed0 --- /dev/null +++ b/src/Draw/Surf8bpp.h @@ -0,0 +1,24 @@ +#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); + + uint8_t** lines; +}; + +#endif diff --git a/src/Interface.cpp b/src/Interface.cpp index 4a073f2..5be7862 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -53,7 +53,7 @@ void AppInterface::Reset() { focusedNode = nullptr; } - if (!hoverNode && !IsInterfaceNode(hoverNode)) + if (hoverNode && !IsInterfaceNode(hoverNode)) { hoverNode = nullptr; } @@ -117,6 +117,7 @@ void AppInterface::Update() Platform::input->SetMouseCursor(MouseCursor::Pointer); } + if(0) { // For debugging picking Platform::input->HideMouse(); @@ -298,6 +299,7 @@ void AppInterface::GenerateInterfaceNodes() rootInterfaceNode->style.alignment = ElementAlignment::Left; rootInterfaceNode->style.fontSize = 1; rootInterfaceNode->style.fontStyle = FontStyle::Regular; + rootInterfaceNode->style.fontColour = Platform::video->colourScheme.textColour; Font* interfaceFont = Assets.GetFont(1, FontStyle::Regular); Font* smallInterfaceFont = Assets.GetFont(0, FontStyle::Regular); diff --git a/src/Layout.cpp b/src/Layout.cpp index 4c52e14..ce581a6 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -65,6 +65,12 @@ void Layout::BreakNewLine() 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; @@ -131,6 +137,11 @@ void Layout::OnNodeEmitted(Node* node) } node->Handler().GenerateLayout(*this, node); + + if (node->parent) + { + node->parent->OnChildLayoutChanged(); + } } void Layout::PadHorizontal(int left, int right) diff --git a/src/Node.cpp b/src/Node.cpp index b14cb0a..28acb54 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -71,6 +71,47 @@ void Node::OnChildLayoutChanged() void Node::EncapsulateChildren() { + Coord newAnchor, newSize; + newAnchor.Clear(); + newSize.Clear(); + + for (Node* node = firstChild; node; node = node->next) + { + if (node->size.x == 0 || node->size.y == 0) + continue; + + if (newSize.x == 0 || newSize.y == 0) + { + newAnchor = node->anchor; + newSize = node->size; + continue; + } + + if (node->anchor.x < newAnchor.x) + { + newAnchor.x = node->anchor.x; + } + if (node->anchor.y < newAnchor.y) + { + newAnchor.y = node->anchor.y; + } + if (node->anchor.x + node->size.x > newAnchor.x + newSize.x) + { + newSize.x = node->anchor.x + node->size.x - newAnchor.x; + } + if (node->anchor.y + node->size.y > newAnchor.y + newSize.y) + { + newSize.y = node->anchor.y + node->size.y - newAnchor.y; + } + } + + if (newSize.x != 0 && newSize.y != 0) + { + anchor = newAnchor; + size = newSize; + } + + /* if (firstChild) { anchor = firstChild->anchor; @@ -96,6 +137,7 @@ void Node::EncapsulateChildren() } } } + */ } bool Node::IsPointInsideNode(int x, int y) diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index befe006..67b47ab 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -1,11 +1,13 @@ #include #include "../Layout.h" #include "../LinAlloc.h" +#include "../Draw/Surface.h" + #include "Break.h" -Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayBreakLine) +Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayBreakLine, bool onlyPadEmptyLines) { - BreakNode::Data* data = allocator.Alloc(breakPadding, displayBreakLine); + BreakNode::Data* data = allocator.Alloc(breakPadding, displayBreakLine, onlyPadEmptyLines); if (data) { return allocator.Alloc(Node::Break, data); @@ -13,7 +15,40 @@ Node* BreakNode::Construct(Allocator& allocator, int breakPadding, bool displayB return nullptr; } +void BreakNode::Draw(DrawContext& context, Node* node) +{ + BreakNode::Data* data = static_cast(node->data); + + if (data->displayBreakLine) + { + uint8_t outlineColour = 0; + context.surface->HLine(context, node->anchor.x, node->anchor.y, 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); + } + + node->anchor = layout.GetCursor(); + node->anchor.x += 8; + node->anchor.y -= breakPadding / 2; + node->size.x = layout.AvailableWidth() - 16; + node->size.y = data->displayBreakLine ? 1 : 0; } diff --git a/src/Nodes/Break.h b/src/Nodes/Break.h index 5e8f2cb..cbaf9aa 100644 --- a/src/Nodes/Break.h +++ b/src/Nodes/Break.h @@ -9,12 +9,14 @@ class BreakNode : public NodeHandler class Data { public: - Data(int inBreakPadding, bool inDisplayBreakLine) : breakPadding(inBreakPadding), displayBreakLine(inDisplayBreakLine) {} + Data(int inBreakPadding, bool inDisplayBreakLine, bool inOnlyPadEmptyLines) : breakPadding(inBreakPadding), displayBreakLine(inDisplayBreakLine), onlyPadEmptyLines(inOnlyPadEmptyLines) {} int breakPadding; bool displayBreakLine; + bool onlyPadEmptyLines; }; - static Node* Construct(Allocator& allocator, int breakPadding = 0, bool displayBreakLine = false); + 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; }; diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index 04f8412..67cad10 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -14,8 +14,10 @@ void ButtonNode::Draw(DrawContext& context, Node* node) if (data->buttonText) { Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = 0; - uint8_t buttonOutlineColour = 0; + 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); diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 62a568a..04c61e0 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -6,6 +6,7 @@ void LinkNode::ApplyStyle(Node* node) { node->style.fontStyle = (FontStyle::Type)(node->style.fontStyle | FontStyle::Underline); + node->style.fontColour = Platform::video->colourScheme.linkColour; } Node* LinkNode::Construct(Allocator& allocator, char* url) diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 3ce9ca6..e3e18fd 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -13,7 +13,7 @@ void TextElement::Draw(DrawContext& context, Node* node) if (!node->firstChild && data->text) { Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = 0; + uint8_t textColour = node->style.fontColour; context.surface->DrawString(context, font, data->text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); //printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); @@ -140,7 +140,10 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) } } - node->EncapsulateChildren(); + if (node->firstChild) + { + node->EncapsulateChildren(); + } } Node* SubTextElement::Construct(Allocator& allocator, const char* text) diff --git a/src/Page.cpp b/src/Page.cpp index 7b09f4f..008d17c 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -23,6 +23,7 @@ #include "Nodes/Text.h" #include "Nodes/Form.h" #include "Nodes/StyNode.h" +#include "Draw/Surface.h" #define TOP_MARGIN_PADDING 1 @@ -47,6 +48,7 @@ void Page::Reset() rootNode->style.alignment = ElementAlignment::Left; rootNode->style.fontSize = 1; rootNode->style.fontStyle = FontStyle::Regular; + rootNode->style.fontColour = Platform::video->colourScheme.textColour; layout.Reset(); } diff --git a/src/Palettes.inc b/src/Palettes.inc new file mode 100644 index 0000000..4cb801a --- /dev/null +++ b/src/Palettes.inc @@ -0,0 +1,18 @@ +uint8_t cgaPaletteLUT[] = { + 0, 0, 1, 1, 0, 0, 1, 1, 0, 8, 1, 1, 2, 8, 3, 3, + 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, + 0, 0, 1, 1, 0, 8, 1, 1, 0, 8, 8, 1, 2, 8, 8, 9, + 2, 8, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 10, 10, 3, + 0, 8, 1, 1, 0, 8, 8, 1, 8, 8, 8, 9, 8, 8, 8, 9, + 2, 8, 8, 9, 2, 8, 3, 3, 2, 10, 10, 3, 2, 10, 10, 11, + 4, 8, 5, 5, 4, 8, 8, 9, 6, 8, 8, 9, 6, 8, 8, 9, + 6, 8, 8, 9, 2, 8, 7, 7, 2, 10, 10, 7, 10, 10, 10, 11, + 4, 4, 5, 5, 4, 8, 5, 5, 6, 8, 8, 9, 6, 8, 8, 9, + 6, 8, 7, 7, 6, 8, 7, 7, 10, 10, 7, 7, 10, 10, 10, 7, + 4, 4, 5, 5, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 7, 7, + 6, 6, 7, 7, 6, 6, 7, 7, 6, 10, 7, 7, 10, 10, 7, 7, + 4, 4, 5, 5, 4, 4, 5, 5, 6, 12, 12, 5, 6, 12, 12, 7, + 6, 12, 7, 7, 6, 12, 7, 7, 6, 14, 7, 7, 14, 14, 7, 7, + 4, 4, 5, 5, 4, 12, 12, 5, 6, 12, 12, 13, 6, 12, 12, 13, + 6, 12, 12, 7, 6, 12, 7, 7, 14, 14, 7, 7, 14, 14, 14, 15 +}; diff --git a/src/Parser.cpp b/src/Parser.cpp index 146a466..4cd208d 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -107,6 +107,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) #ifdef _WIN32 page.DebugDumpNodeGraph(page.GetRootNode()); #endif + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 0xf); page.DebugDraw(context, page.GetRootNode()); } @@ -728,3 +729,71 @@ bool AttributeParser::IsWhiteSpace(char c) return c == ' ' || c == '\n' || c == '\t'; } +#define RGB332(red, green, blue) (((red) & 0xe0) | (((green) & 0xe0) >> 3) | (((blue) & 0xc0) >> 6)) + +struct NamedColour +{ + const char* name; + uint8_t colour; +}; + +NamedColour namedColours[] = +{ + { "black", RGB332(0x00, 0x00, 0x00) }, + { "white", RGB332(0xff, 0xff, 0xff) }, + { "gray", RGB332(0x80, 0x80, 0x80) }, + { "silver", RGB332(0xc0, 0xc0, 0xc0) }, + { "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 (!Platform::video->paletteLUT) + { + return 0; + } + + 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; + } + + return Platform::video->paletteLUT[RGB332(red, green, blue)]; + } + else + { + for (int n = 0; n < NUM_NAMED_COLOURS; n++) + { + if (!stricmp(namedColours[n].name, colourCode)) + { + return Platform::video->paletteLUT[namedColours[n].colour]; + } + } + } + return 0; +} diff --git a/src/Parser.h b/src/Parser.h index 5a4a3a3..74f5ed4 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -83,6 +83,8 @@ class HTMLParser void PushPreFormatted(); void PopPreFormatted(); + static uint8_t ParseColourCode(const char* colourCode); + private: void ParseChar(char c); diff --git a/src/Platform.h b/src/Platform.h index 7b1f6db..e0d533b 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -17,6 +17,7 @@ #include #include "Font.h" +#include "Theme.h" #include "Cursor.h" struct Image; @@ -34,6 +35,8 @@ class VideoDriver int screenHeight; DrawSurface* drawSurface; + ColourScheme colourScheme; + uint8_t* paletteLUT; }; typedef uint8_t NetworkAddress[4]; // An IPv4 address is 4 bytes diff --git a/src/Style.h b/src/Style.h index acc093d..4dd6912 100644 --- a/src/Style.h +++ b/src/Style.h @@ -24,6 +24,7 @@ struct StyleOverrideMask bool fontSize : 1; bool fontSizeDelta : 1; bool alignment : 1; + bool fontColour : 1; }; uint8_t mask; }; @@ -39,6 +40,7 @@ struct ElementStyle FontStyle::Type fontStyle : 4; int fontSize : 4; ElementAlignment::Type alignment : 2; + uint8_t fontColour; }; struct ElementStyleOverride @@ -75,6 +77,12 @@ struct ElementStyleOverride styleSettings.alignment = alignment; } + inline void SetFontColour(uint8_t colour) + { + overrideMask.fontColour = true; + styleSettings.fontColour = colour; + } + inline void Apply(ElementStyle& style) { if (overrideMask.fontStyle) @@ -93,6 +101,10 @@ struct ElementStyleOverride { style.fontSize += styleSettings.fontSize; } + if (overrideMask.fontColour) + { + style.fontColour = styleSettings.fontColour; + } } }; diff --git a/src/Tags.cpp b/src/Tags.cpp index 3defba3..ddef118 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -104,25 +104,31 @@ const HTMLTagHandler* DetermineTag(const char* str) void HrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - // TODO-refactor - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + int padding = Assets.GetFont(1, FontStyle::Bold)->glyphHeight; + parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding, true)); } void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + int fontSize = parser.CurrentContext().node->style.fontSize; + int padding = Assets.GetFont(fontSize, FontStyle::Regular)->glyphHeight; + parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding, false, true)); } void HTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); - parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Bold, size >= 3 ? 1 : 2), this); + int fontSize = size >= 3 ? 1 : 2; + int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; + parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding)); + parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Bold, fontSize), this); } void HTagHandler::Close(class HTMLParser& parser) const { + int fontSize = size >= 3 ? 1 : 2; + int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; + parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding)); parser.PopContext(this); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -207,15 +213,57 @@ void AlignmentTagHandler::Open(class HTMLParser& parser, char* attributeStr) con void AlignmentTagHandler::Close(class HTMLParser& parser) const { parser.PopContext(this); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - // TODO-refactor + Node* styleNode = StyleNode::Construct(parser.page.allocator); + if (styleNode) + { + StyleNode::Data* data = static_cast(styleNode->data); + + AttributeParser attributes(attributeStr); + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "size")) + { + int fontSize = atoi(attributes.Value()); + + 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 if (!stricmp(attributes.Key(), "color")) + { + if (Platform::video->paletteLUT) + { + data->styleOverride.SetFontColour(HTMLParser::ParseColourCode(attributes.Value())); + } + } + } + + parser.PushContext(styleNode, this); + } } void FontTagHandler::Close(class HTMLParser& parser) const { + parser.PopContext(this); } void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -315,6 +363,8 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + Node* formNode = FormNode::Construct(parser.page.allocator); parser.PushContext(formNode, this); @@ -343,6 +393,7 @@ void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void FormTagHandler::Close(HTMLParser& parser) const { parser.PopContext(this); + parser.EmitNode(BreakNode::Construct(parser.page.allocator)); } void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const diff --git a/src/Theme.h b/src/Theme.h new file mode 100644 index 0000000..b6587b9 --- /dev/null +++ b/src/Theme.h @@ -0,0 +1,12 @@ +#ifndef _THEME_H_ +#define _THEME_H_ + +struct ColourScheme +{ + uint8_t pageColour; + uint8_t textColour; + uint8_t linkColour; + uint8_t buttonColour; +}; + +#endif diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 092420a..c6de429 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -120,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); } diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index a2e5142..e614ca9 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -19,6 +19,8 @@ #include "../Interface.h" #include "../DataPack.h" #include "../Draw/Surf1bpp.h" +#include "../Draw/Surf8bpp.h" +#include "../Palettes.inc" //#define SCREEN_WIDTH 800 //#define SCREEN_HEIGHT 600 @@ -38,35 +40,93 @@ WindowsVideoDriver::WindowsVideoDriver() Assets.Load("Default.dat"); // Assets.Load("EGA.dat"); -// Assets.Load("Lowres.dat"); + //Assets.Load("Lowres.dat"); // Assets.Load("CGA.dat"); } +const RGBQUAD monoPalette[] = +{ + { 0, 0, 0, 0 }, + { 0xff, 0xff, 0xff, 0 } +}; + +const RGBQUAD cgaPalette[] = +{ + { 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() { HDC hDC = GetDC(hWnd); HDC hDCMem = CreateCompatibleDC(hDC); - bitmapInfo = (BITMAPINFO*) malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 2); - ZeroMemory(bitmapInfo, sizeof(BITMAPINFO)); + bool useColour = true; + 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 = 1; + bitmapInfo->bmiHeader.biBitCount = useColour ? 8 : 1; bitmapInfo->bmiHeader.biCompression = BI_RGB; bitmapInfo->bmiHeader.biClrUsed = 0; - bitmapInfo->bmiColors[0].rgbRed = 0; - bitmapInfo->bmiColors[0].rgbGreen = 0; - bitmapInfo->bmiColors[0].rgbBlue = 0; - bitmapInfo->bmiColors[1].rgbRed = 0xff; - bitmapInfo->bmiColors[1].rgbGreen = 0xff; - bitmapInfo->bmiColors[1].rgbBlue = 0xff; + if (useColour) + { + memcpy(bitmapInfo->bmiColors, cgaPalette, sizeof(RGBQUAD) * 16); + } + else + { + memcpy(bitmapInfo->bmiColors, monoPalette, sizeof(RGBQUAD) * 2); + } screenBitmap = CreateDIBSection(hDCMem, bitmapInfo, DIB_RGB_COLORS, (VOID**)&lpBitmapBits, NULL, 0); + if (useColour) + { + DrawSurface_8BPP* surface = new DrawSurface_8BPP(screenWidth, screenHeight); + uint8_t* buffer = (uint8_t*)(lpBitmapBits); + + int pitch = screenWidth; + + for (int y = 0; y < screenHeight; y++) + { + int bufferY = (screenHeight - 1 - y); + surface->lines[y] = &buffer[bufferY * pitch]; + } + drawSurface = surface; + + for (int n = 0; n < screenWidth * screenHeight; n++) + { + buffer[n] = 0xf; + } + + colourScheme.pageColour = 0xf; + colourScheme.linkColour = 1; + colourScheme.textColour = 0; + colourScheme.buttonColour = 7; + + paletteLUT = cgaPaletteLUT; + } + else { DrawSurface_1BPP* surface = new DrawSurface_1BPP(screenWidth, screenHeight); uint8_t* buffer = (uint8_t*)(lpBitmapBits); @@ -83,22 +143,18 @@ void WindowsVideoDriver::Init() surface->lines[y] = &buffer[bufferY * pitch]; } drawSurface = surface; - } - if (bitmapInfo->bmiHeader.biBitCount == 1) - { - uint8_t* ptr = (uint8_t*)(lpBitmapBits); for (int n = 0; n < screenWidth * screenHeight / 8; n++) { - ptr[n] = 0xff; - } - } - else - { - for (int n = 0; n < screenWidth * screenHeight; n++) - { - lpBitmapBits[n] = backgroundColour; + buffer[n] = 0xff; } + + colourScheme.pageColour = 1; + colourScheme.linkColour = 0; + colourScheme.textColour = 0; + colourScheme.buttonColour = 1; + + paletteLUT = nullptr; } } diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 429566f..42742f6 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -29,6 +29,9 @@ void EncodeFont(const char* basePath, const char* name, vector& output, 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; @@ -119,6 +122,8 @@ void GenerateAssetPacks() int main(int argc, char* argv) { GenerateAssetPacks(); + + GeneratePaletteLUTs("src/Palettes.inc"); #if 0 // CGA resources { diff --git a/tools/PaletteGen.cpp b/tools/PaletteGen.cpp new file mode 100644 index 0000000..5a52367 --- /dev/null +++ b/tools/PaletteGen.cpp @@ -0,0 +1,91 @@ +#include +#include +#include + +const RGBQUAD cgaPalette[] = +{ + { 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 +}; + +int GetClosestPaletteIndex(const RGBQUAD colour, const RGBQUAD* palette, int paletteSize) +{ + int closest = -1; + int closestDistance = 0; + + for (int n = 0; n < paletteSize; n++) + { + int distance = 0; + 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) +{ + 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); + 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, "cgaPaletteLUT", cgaPalette, 16); + + fclose(fs); + } +} From bbc91718a8a88fd89f0c5c8a8ed9db80bfe6d5b3 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 12 Feb 2024 20:37:00 +0000 Subject: [PATCH 25/98] GIF image support WIP --- CGA.dat | Bin 47060 -> 47062 bytes Default.dat | Bin 92544 -> 92546 bytes EGA.dat | Bin 70592 -> 70594 bytes LowRes.dat | Bin 51757 -> 51759 bytes project/Windows/Windows.vcxproj | 5 + src/App.cpp | 50 +++- src/App.h | 1 + src/Colour.h | 22 ++ src/DataPack.cpp | 15 +- src/DataPack.h | 3 +- src/Draw/Surf1bpp.cpp | 2 +- src/Draw/Surf8bpp.cpp | 68 ++++- src/HTTP.cpp | 22 +- src/HTTP.h | 2 + src/Image.h | 15 -- src/Image/Decoder.h | 29 +++ src/Image/Decoders.h | 30 +++ src/Image/Gif.cpp | 435 ++++++++++++++++++++++++++++++++ src/Image/Gif.h | 134 ++++++++++ src/Image/Image.h | 23 ++ src/Layout.cpp | 42 +++ src/Layout.h | 2 + src/LinAlloc.h | 2 +- src/Node.h | 3 + src/Nodes/ImgNode.cpp | 49 +++- src/Nodes/ImgNode.h | 17 +- src/Page.cpp | 33 ++- src/Page.h | 2 + src/Palettes.inc | 32 +-- src/Parser.cpp | 12 +- src/Platform.h | 2 +- src/Tags.cpp | 43 +++- src/Theme.h | 12 - src/Windows/WinNet.h | 2 +- src/Windows/WinVid.cpp | 2 +- tools/ImageGen.cpp | 3 + 36 files changed, 1030 insertions(+), 84 deletions(-) create mode 100644 src/Colour.h delete mode 100644 src/Image.h create mode 100644 src/Image/Decoder.h create mode 100644 src/Image/Decoders.h create mode 100644 src/Image/Gif.cpp create mode 100644 src/Image/Gif.h create mode 100644 src/Image/Image.h delete mode 100644 src/Theme.h diff --git a/CGA.dat b/CGA.dat index 49cf7d783df5d46926bce73a04f40f75e49a16fe..43a9e661abd166f421195a441fa13262507b942b 100644 GIT binary patch delta 33 pcmccep6S|qrU|DQuWda2bDIz&1A|2Uiu8uMf)oP>f%*>&3;-Hp4r2fS delta 31 ncmccip6SYarU|DQuWUU1bDIEzME#2NhPr|j0|$Zn4-5g|NdYbX3kQ^fegF$^jeS$xO|ARUptVu{oNKkM9!ukgc3;_Hq B6C?lt delta 46 zcmX@KoaMlBmIX?MdYbX3kPL%CeL#JJKZE~+Iv}h`NJ&UgZ~(&k2Mi1V>Gu;K diff --git a/LowRes.dat b/LowRes.dat index 1c8fdf71087ad591a916dc7ddc556299cb95392b..60d836ac52d06644a4d622af8278c2baf8b7839c 100644 GIT binary patch delta 25 hcmZ2Gg?arH<_V`5^*5f5I?2VrSmonamgH2&004)u32OiV delta 23 fcmZ2Kg?a51<_V`5bvK@lI>}Mx<5!mCRL1}SdIJe& diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 47fc869..7fc1d1e 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -144,6 +144,7 @@ + @@ -172,12 +173,16 @@ + + + + diff --git a/src/App.cpp b/src/App.cpp index 359d6b8..4e0a358 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -71,6 +71,7 @@ void App::Run(int argc, char* argv[]) requestedNewPage = false; page.pageURL = loadTask.GetURL(); ui.UpdateAddressBar(page.pageURL); + loadTaskTargetNode = page.GetRootNode(); } static char buffer[256]; @@ -78,7 +79,18 @@ void App::Run(int argc, char* argv[]) size_t bytesRead = loadTask.GetContent(buffer, 256); if (bytesRead) { - parser.Parse(buffer, bytesRead); + if (loadTaskTargetNode == page.GetRootNode()) + { + parser.Parse(buffer, bytesRead); + } + else if(loadTaskTargetNode) + { + bool stillProcessing = loadTaskTargetNode->Handler().ParseContent(loadTaskTargetNode, buffer, bytesRead); + if (!stillProcessing) + { + loadTask.Stop(); + } + } } } else @@ -104,6 +116,18 @@ void App::Run(int argc, char* argv[]) } } } + else + { + bool isStillConnecting = loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting; + + if(!isStillConnecting) + { + if (loadTaskTargetNode) + { + loadTaskTargetNode = page.ProcessNextLoadTask(loadTaskTargetNode, loadTask); + } + } + } } if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) ui.SetStatusMessage(loadTask.request->GetStatusString()); @@ -210,16 +234,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; @@ -230,6 +267,7 @@ void App::RequestNewPage(const char* url) StopLoad(); loadTask.Load(url); requestedNewPage = true; + loadTaskTargetNode = nullptr; //ui.UpdateAddressBar(loadTask.url); } diff --git a/src/App.h b/src/App.h index d5de602..2ef6aeb 100644 --- a/src/App.h +++ b/src/App.h @@ -86,6 +86,7 @@ class App bool requestedNewPage; LoadTask loadTask; + Node* loadTaskTargetNode; bool running; URL pageHistory[MAX_PAGE_URL_HISTORY]; diff --git a/src/Colour.h b/src/Colour.h new file mode 100644 index 0000000..8c4da71 --- /dev/null +++ b/src/Colour.h @@ -0,0 +1,22 @@ +#ifndef _COLOUR_H_ +#define _COLOUR_H_ + +#include + +#define RGB332(red, green, blue) (((red) & 0xe0) | (((green) & 0xe0) >> 3) | (((blue) & 0xc0) >> 6)) + +struct ColourScheme +{ + uint8_t pageColour; + uint8_t textColour; + uint8_t linkColour; + uint8_t buttonColour; +}; + +struct NamedColour +{ + const char* name; + uint8_t colour; +}; + +#endif diff --git a/src/DataPack.cpp b/src/DataPack.cpp index 2cc79e2..2c0a960 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -27,7 +27,7 @@ bool DataPack::Load(const char* path) linkCursor = (MouseCursorData*)LoadAsset(fs, header, "CLINK"); textSelectCursor = (MouseCursorData*)LoadAsset(fs, header, "CTEXT"); - imageIcon = (Image*)LoadAsset(fs, header, "IIMG"); + imageIcon = LoadImageAsset(fs, header, "IIMG"); fonts[0] = (Font*)LoadAsset(fs, header, "FHELV1"); fonts[1] = (Font*)LoadAsset(fs, header, "FHELV2"); @@ -103,6 +103,19 @@ MouseCursorData* DataPack::GetMouseCursorData(MouseCursor::Type type) return NULL; } +Image* DataPack::LoadImageAsset(FILE* fs, DataPackHeader& header, const char* entryName) +{ + void* asset = LoadAsset(fs, header, entryName); + if (asset) + { + Image* image = new Image(); + memcpy(image, asset, sizeof(ImageMetadata)); + image->data = ((uint8_t*) asset) + sizeof(ImageMetadata); + return image; + } + return nullptr; +} + void* DataPack::LoadAsset(FILE* fs, DataPackHeader& header, const char* entryName, void* buffer) { DataPackEntry* entry = NULL; diff --git a/src/DataPack.h b/src/DataPack.h index 60af419..2bb3792 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -4,7 +4,7 @@ #include #include #include "Cursor.h" -#include "Image.h" +#include "Image/Image.h" #include "Font.h" /* @@ -68,6 +68,7 @@ struct DataPack 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); }; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 6f4777b..ef8b401 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -1,7 +1,7 @@ #include #include "Surf1bpp.h" #include "../Font.h" -#include "../Image.h" +#include "../Image/Image.h" DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index c925aee..a9ca787 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -1,7 +1,7 @@ #include #include "Surf8bpp.h" #include "../Font.h" -#include "../Image.h" +#include "../Image/Image.h" DrawSurface_8BPP::DrawSurface_8BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) @@ -222,6 +222,72 @@ void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { + if (!image->data) + return; + + 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) + { + if (image->bpp == 1) + { + srcData += ((context.clipLeft - x) >> 3); + } + else + { + srcData += (context.clipLeft - x); + } + 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. + } + + if (image->bpp == 8) + { + // 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; + + for (int i = 0; i < destWidth; i++) + { + *destRow++ = *srcRow++; + } + } + } + #if 0 x += context.drawOffsetX; y += context.drawOffsetY; diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 88d0927..45806ab 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -152,7 +152,17 @@ size_t HTTPRequest::ReadData(char* buffer, size_t count) } else { - return (size_t)(rc); + size_t bytesRead = (size_t)(rc); + if (contentRemaining > 0) + { + contentRemaining -= bytesRead; + if (contentRemaining <= 0) + { + Stop(); + } + } + + return bytesRead; } } return 0; @@ -250,7 +260,7 @@ void HTTPRequest::Update() break; case SendHeaders: { - WriteLine("GET %s HTTP/1.0", path); + WriteLine("GET %s HTTP/1.1", path); WriteLine("User-Agent: MicroWeb " __DATE__); WriteLine("Host: %s", hostname); WriteLine("Connection: close"); @@ -285,6 +295,8 @@ void HTTPRequest::Update() //printf("Response code: %d", responseCode); //getchar(); internalStatus = ReceiveHeaderContent; + + contentRemaining = -1; } } break; @@ -311,8 +323,12 @@ void HTTPRequest::Update() break; } } + if (!strncmp(lineBuffer, "Content-Length:", 15)) + { + contentRemaining = atoi(lineBuffer + 15); + } - //printf("Header: %s -- ", lineBuffer); + printf("Header: %s -- \n", lineBuffer); //getchar(); } } diff --git a/src/HTTP.h b/src/HTTP.h index 264acd7..4c49bf5 100644 --- a/src/HTTP.h +++ b/src/HTTP.h @@ -84,6 +84,8 @@ class HTTPRequest char lineBuffer[LINE_BUFFER_SIZE]; int lineBufferSize; int lineBufferSendPos; + + long contentRemaining; }; diff --git a/src/Image.h b/src/Image.h deleted file mode 100644 index 9af1d24..0000000 --- a/src/Image.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _IMAGE_H_ -#define _IMAGE_H_ - -#include - -// Images are 1bpp -struct Image -{ - uint16_t width; - uint16_t height; - uint16_t pitch; - uint8_t data[1]; -}; - -#endif diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h new file mode 100644 index 0000000..ad7295b --- /dev/null +++ b/src/Image/Decoder.h @@ -0,0 +1,29 @@ +#ifndef _DECODER_H_ +#define _DECODER_H_ + +#include +struct Image; + +class ImageDecoder +{ +public: + enum State + { + Stopped, + Decoding, + Success, + Error + }; + + ImageDecoder() : outputImage(NULL) {} + virtual void Begin(Image* image) = 0; + virtual void Process(uint8_t* data, size_t dataLength) = 0; + virtual State GetState() = 0; + +protected: + Image* outputImage; +}; + + + +#endif diff --git a/src/Image/Decoders.h b/src/Image/Decoders.h new file mode 100644 index 0000000..f73fc78 --- /dev/null +++ b/src/Image/Decoders.h @@ -0,0 +1,30 @@ +#ifndef _DECODERS_H_ +#define _DECODERS_H_ + +#include "Gif.h" +#include + +class LinearAllocator; + +struct ImageDecoderUnion +{ + inline ImageDecoder* Get() { return (ImageDecoder*)(buffer); } + + template + void Create(LinearAllocator& allocator) + { + new (buffer) T(allocator); + } + +private: + union + { + char gif[sizeof(GifDecoder)]; + //char png[sizeof(PngDecoder)]; + //char jpeg[sizeof(JpegDecoder)]; + char buffer[1]; + }; + +} ImageDecoders; + +#endif diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp new file mode 100644 index 0000000..b77e212 --- /dev/null +++ b/src/Image/Gif.cpp @@ -0,0 +1,435 @@ +#include "Gif.h" +#include "Image.h" +#include "../Platform.h" +#include "../Colour.h" +#include + +#define BLOCK_TYPE_EXTENSION_INTRODUCER 0x21 +#define BLOCK_TYPE_IMAGE_DESCRIPTOR 0x2C +#define BLOCK_TYPE_TRAILER 0x3B + +GifDecoder::GifDecoder(LinearAllocator& inAllocator) +: allocator(inAllocator), state(ImageDecoder::Stopped) +{ +} + +void GifDecoder::Begin(Image* image) +{ + state = ImageDecoder::Decoding; + outputImage = image; + outputImage->bpp = 8; + + structFillPosition = 0; + internalState = ParseHeader; +} + +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)) + { + // Not a GIF89a + state = ImageDecoder::Error; + return; + } + + outputImage->width = header.width; + outputImage->height = header.height; + outputImage->pitch = header.width; + //outputImage->data = (uint8_t*) allocator.Alloc((header.width * header.height) >> 3); + //outputImage->data = (uint8_t*) allocator.Alloc((header.width * header.height) * 3); + outputImage->data = (uint8_t*)allocator.Alloc(outputImage->pitch * header.height); + if(!outputImage->data) + { + // Allocation error + printf("Could not allocate!\n"); + state = ImageDecoder::Error; + return; + } + + backgroundColour = header.backgroundColour; + + printf("Image is %d x %d\n", outputImage->width, outputImage->height); + + if(header.fields & (1 << 7)) + { + paletteSize = (1 << ((header.fields & 0x7) + 1)); + printf("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)) + { + //printf("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) + { + for (int n = 0; n < paletteSize; n++) + { + paletteLUT[n] = Platform::video->paletteLUT[RGB332(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2])]; + } + + paletteLUT[header.backgroundColour] = Platform::video->colourScheme.pageColour; + + 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: + printf("Invalid block type: %x\n", (int)(blockType)); + state = ImageDecoder::Error; + return; + } + } + break; + + case ParseImageDescriptor: + { + if(FillStruct(&data, dataLength, &imageDescriptor, sizeof(ImageDescriptor))) + { + printf("Image: %d, %d %d, %d\n", imageDescriptor.x, imageDescriptor.y, imageDescriptor.width, imageDescriptor.height); + + drawX = drawY = 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)) + { + // TODO: use local colour table for something + + paletteIndex++; + if (paletteIndex == localColourTableLength) + { + internalState = ParseLZWCodeSize; + } + } + } + break; + + case ParseLZWCodeSize: + { + lzwCodeSize = NextByte(&data, dataLength); + printf("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); + printf("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); + //printf("-Data: %x\n", dataByte); + + for(int n = 0; n < 8; n++) + { + if(dataByte & (1 << n)) + { + code |= (1 << codeBit); + } + codeBit++; + + if (codeBit == codeLength) + { + //printf("code: %x [len=%d]\n", code, codeLength); + + // Code complete + if (code == clearCode) + { + printf("CLEAR\n"); + codeLength = resetCodeLength; + ClearDictionary(); + prev = -1; + codeBit = 0; + code = 0; + continue; + } + else if (code == stopCode) + { + printf("STOP\n"); + if (imageSubBlockSize) + { + printf("Malformed GIF\n"); + state = ImageDecoder::Error; + } + continue; + } + + if (prev > -1 && codeLength <= 12) + { + if (code > dictionaryIndex) + { + printf("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) + { + dictionary[dictionaryIndex].len = dictionary[prev].len + 1; + } + else + { + dictionary[dictionaryIndex].len = 1; + } + + dictionaryIndex++; + + if(dictionaryIndex == (1 << (codeLength)) && codeLength < 12) + { + codeLength++; + //printf("Code length: %d\n", codeLength); + } + } + + prev = code; + + { + uint8_t stack[1024]; + int stackSize = 0; + + while(code != -1) + { + if (stackSize >= 1024) + { + fprintf(stderr, "Stack overflow!"); + exit(-1); + } + + stack[stackSize++] = dictionary[code].byte; + //printf(" value: %x\n", dictionary[code].byte); + + code = dictionary[code].prev; + } + + while(stackSize) + { + OutputPixel(stack[stackSize - 1]); + stackSize--; + } + } + + codeBit = 0; + code = 0; + } + } + } + else + { + //printf("--\n"); + internalState = ParseImageSubBlockSize; + } + } + break; + + case ParseExtension: + { + if(FillStruct(&data, dataLength, &extensionHeader, sizeof(ExtensionHeader))) + { + printf("Extension: %x Size: %d\n", (int) extensionHeader.code, (int) extensionHeader.size); + internalState = ParseExtensionContents; + } + } + break; + + case ParseExtensionContents: + { + if(extensionHeader.size == 0) + { + internalState = ParseExtensionSubBlockSize; + } + else + { + NextByte(&data, dataLength); + extensionHeader.size--; + } + } + break; + + case ParseExtensionSubBlockSize: + { + extensionSubBlockSize = NextByte(&data, dataLength); + if(extensionSubBlockSize > 0) + { + internalState = ParseExtensionSubBlock; + } + else + { + internalState = ParseDataBlock; + } + } + break; + + case ParseExtensionSubBlock: + { + if(extensionSubBlockSize > 0) + { + NextByte(&data, dataLength); + extensionSubBlockSize--; + } + else + { + internalState = ParseExtensionSubBlockSize; + } + } + break; + } + } +} + +bool GifDecoder::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; + } +} + +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; +} + +void GifDecoder::OutputPixel(uint8_t pixelValue) +{ + //printf(" value: %x\n", pixelValue); + /*if(pixelValue == header.backgroundColour) + { + outputImage->data[drawX++] = 0xff; + outputImage->data[drawX++] = 0x00; + outputImage->data[drawX++] = 0xff; + outputImage->data[drawX++] = 0xff; + } + else*/ + { + outputImage->data[drawX++] = paletteLUT[pixelValue]; + //outputImage->data[drawX++] = palette[pixelValue * 3]; + //outputImage->data[drawX++] = palette[pixelValue * 3 + 1]; + //outputImage->data[drawX++] = palette[pixelValue * 3 + 2]; + //outputImage->data[drawX++] = pixelValue == header.backgroundColour ? 0 : 255; + } +} diff --git a/src/Image/Gif.h b/src/Image/Gif.h new file mode 100644 index 0000000..8a37bae --- /dev/null +++ b/src/Image/Gif.h @@ -0,0 +1,134 @@ +#ifndef _GIF_H_ +#define _GIF_H_ + +#include +#include "Decoder.h" +#include "../LinAlloc.h" + +#define GIF_MAX_LZW_CODE_LENGTH 12 +#define GIF_MAX_DICTIONARY_ENTRIES (1 << (GIF_MAX_LZW_CODE_LENGTH + 1)) + +class GifDecoder : public ImageDecoder +{ +public: + GifDecoder(LinearAllocator& inAllocator); + + virtual void Begin(Image* image); + virtual void Process(uint8_t* data, size_t dataLength); + virtual ImageDecoder::State GetState() { return state; } + +private: + 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; + } + void ClearDictionary(); + void OutputPixel(uint8_t pixelValue); + + enum InternalState + { + ParseHeader, + ParsePalette, + ParseImageDescriptor, + ParseLocalColourTable, + ParseLZWCodeSize, + ParseDataBlock, + ParseImageSubBlockSize, + ParseImageSubBlock, + ParseExtension, + ParseExtensionContents, + ParseExtensionSubBlockSize, + ParseExtensionSubBlock, + }; + + #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; + int32_t prev; + int len; + }; + #pragma pack(pop) + + LinearAllocator& allocator; + ImageDecoder::State state; + 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; + uint8_t lzwCodeSize; + + size_t structFillPosition; + + 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; + }; + struct + { + // ParseExtension temporary vars + ExtensionHeader extensionHeader; + uint8_t extensionSubBlockSize; + }; + //}; +}; + +#endif diff --git a/src/Image/Image.h b/src/Image/Image.h new file mode 100644 index 0000000..9e63b27 --- /dev/null +++ b/src/Image/Image.h @@ -0,0 +1,23 @@ +#ifndef _IMAGE_H_ +#define _IMAGE_H_ + +#include + +struct ImageMetadata +{ + uint16_t width; + uint16_t height; + uint16_t pitch; + uint8_t bpp; +}; + +struct Image : ImageMetadata +{ + Image() : data(nullptr) + { + width = height = pitch = bpp = 0; + } + uint8_t* data; +}; + +#endif diff --git a/src/Layout.cpp b/src/Layout.cpp index ce581a6..421865f 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -1,6 +1,8 @@ #include "Layout.h" #include "Page.h" #include "Platform.h" +#include "App.h" +#include "Render.h" Layout::Layout(Page& inPage) : page(inPage) @@ -12,6 +14,7 @@ void Layout::Reset() cursor.Clear(); paramStackSize = 0; currentLineHeight = 0; + lineStartNode = nullptr; LayoutParams& params = GetParams(); params.marginLeft = 0; @@ -169,3 +172,42 @@ void Layout::PadVertical(int down) cursor.y += down; } + +void Layout::RecalculateLayout() +{ + Reset(); + + Node* node = page.GetRootNode(); + bool checkChildren = true; + + while (node) + { + if (checkChildren && node->firstChild) + { + node = node->firstChild; + } + else if (node->next) + { + node = node->next; + checkChildren = true; + } + else + { + node = node->parent; + checkChildren = false; + continue; + } + + if (node) + { + node->Handler().GenerateLayout(*this, node); + } + } + + // FIXME + page.GetApp().ui.ScrollRelative(0); + +#ifdef _WIN32 + page.DebugDumpNodeGraph(page.GetRootNode()); +#endif +} diff --git a/src/Layout.h b/src/Layout.h index ce0ad2b..eb8cdb0 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -33,6 +33,8 @@ class Layout void OnNodeEmitted(Node* node); void ProgressCursor(Node* nodeContext, int width, int lineHeight); + void RecalculateLayout(); + Coord GetCursor(int lineHeight = 0) { Coord result = cursor; diff --git a/src/LinAlloc.h b/src/LinAlloc.h index 3df7520..b8fa8b2 100644 --- a/src/LinAlloc.h +++ b/src/LinAlloc.h @@ -22,7 +22,7 @@ #pragma warning(disable:4996) // 8K chunk size including next chunk pointer -#define CHUNK_DATA_SIZE (8 * 1024 - sizeof(struct Chunk*)) +#define CHUNK_DATA_SIZE (16 * 1024 - sizeof(struct Chunk*)) class LinearAllocator { diff --git a/src/Node.h b/src/Node.h index e1dbaa2..2009e91 100644 --- a/src/Node.h +++ b/src/Node.h @@ -24,6 +24,9 @@ class NodeHandler 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; } }; struct Coord diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 7aefc7a..dc22f99 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -4,6 +4,8 @@ #include "../LinAlloc.h" #include "../Layout.h" #include "../Draw/Surface.h" +#include "../App.h" +#include "../Image/Decoders.h" void ImageNode::Draw(DrawContext& context, Node* node) { @@ -11,16 +13,23 @@ void ImageNode::Draw(DrawContext& context, Node* node) //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); uint8_t outlineColour = 0; - 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); + if (data->image.data) + { + context.surface->BlitImage(context, &data->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); + } } -Node* ImageNode::Construct(Allocator& allocator, void* imageData) +Node* ImageNode::Construct(Allocator& allocator) { - ImageNode::Data* data = allocator.Alloc(imageData); + ImageNode::Data* data = allocator.Alloc(); if (data) { return allocator.Alloc(Node::Image, data); @@ -41,3 +50,31 @@ void ImageNode::GenerateLayout(Layout& layout, Node* node) node->anchor = layout.GetCursor(imageHeight); layout.ProgressCursor(node, imageWidth, imageHeight); } + +void ImageNode::LoadContent(Node* node, LoadTask& loadTask) +{ + ImageNode::Data* data = static_cast(node->data); + if (data && data->source) + { + loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); + ImageDecoders.Create(App::Get().page.allocator); + ImageDecoders.Get()->Begin(&data->image); + } +} + +bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) +{ + ImageDecoder* decoder = ImageDecoders.Get(); + ImageNode::Data* data = static_cast(node->data); + + decoder->Process((uint8_t*) buffer, count); + if (decoder->GetState() == ImageDecoder::Success) + { + node->size.x = data->image.width; + node->size.y = data->image.height; + App::Get().page.layout.RecalculateLayout(); + printf("Success!\n"); + } + + return decoder->GetState() == ImageDecoder::Decoding; +} diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index b63c7ef..b068aff 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -1,6 +1,8 @@ -#pragma once +#ifndef _IMGNODE_H_ +#define _IMGNODE_H_ #include "../Node.h" +#include "../Image/Image.h" class ImageNode: public NodeHandler { @@ -8,11 +10,18 @@ class ImageNode: public NodeHandler class Data { public: - Data(void* inImageData) : imageData(inImageData) {} - void* imageData; + Data() : source(nullptr) {} + Image image; + const char* source; }; - static Node* Construct(Allocator& allocator, void* imageData); + static Node* Construct(Allocator& allocator); virtual void Draw(DrawContext& context, Node* element) 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; + }; + +#endif diff --git a/src/Page.cpp b/src/Page.cpp index 008d17c..5b131d2 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -18,7 +18,7 @@ #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" @@ -172,3 +172,34 @@ int Page::GetPageWidth() { return app.ui.windowRect.width; } + +Node* Page::ProcessNextLoadTask(Node* lastNode, LoadTask& loadTask) +{ + Node* node = lastNode; + bool checkChildren = true; + + while (node) + { + if (checkChildren && node->firstChild) + { + node = node->firstChild; + } + else if (node->next) + { + node = node->next; + checkChildren = true; + } + else + { + node = node->parent; + checkChildren = false; + } + + if (node && node->type == Node::Image) + { + node->Handler().LoadContent(node, loadTask); + return node; + } + } + return nullptr; +} diff --git a/src/Page.h b/src/Page.h index 7d4fe10..023c8e0 100644 --- a/src/Page.h +++ b/src/Page.h @@ -49,6 +49,8 @@ class Page App& GetApp() { return app; } + Node* ProcessNextLoadTask(Node* lastNode, struct LoadTask& loadTask); + private: friend class AppInterface; diff --git a/src/Palettes.inc b/src/Palettes.inc index 4cb801a..a4772ef 100644 --- a/src/Palettes.inc +++ b/src/Palettes.inc @@ -1,18 +1,18 @@ uint8_t cgaPaletteLUT[] = { - 0, 0, 1, 1, 0, 0, 1, 1, 0, 8, 1, 1, 2, 8, 3, 3, - 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, - 0, 0, 1, 1, 0, 8, 1, 1, 0, 8, 8, 1, 2, 8, 8, 9, - 2, 8, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 10, 10, 3, - 0, 8, 1, 1, 0, 8, 8, 1, 8, 8, 8, 9, 8, 8, 8, 9, - 2, 8, 8, 9, 2, 8, 3, 3, 2, 10, 10, 3, 2, 10, 10, 11, - 4, 8, 5, 5, 4, 8, 8, 9, 6, 8, 8, 9, 6, 8, 8, 9, - 6, 8, 8, 9, 2, 8, 7, 7, 2, 10, 10, 7, 10, 10, 10, 11, - 4, 4, 5, 5, 4, 8, 5, 5, 6, 8, 8, 9, 6, 8, 8, 9, - 6, 8, 7, 7, 6, 8, 7, 7, 10, 10, 7, 7, 10, 10, 10, 7, - 4, 4, 5, 5, 4, 4, 5, 5, 6, 6, 5, 5, 6, 6, 7, 7, - 6, 6, 7, 7, 6, 6, 7, 7, 6, 10, 7, 7, 10, 10, 7, 7, - 4, 4, 5, 5, 4, 4, 5, 5, 6, 12, 12, 5, 6, 12, 12, 7, - 6, 12, 7, 7, 6, 12, 7, 7, 6, 14, 7, 7, 14, 14, 7, 7, - 4, 4, 5, 5, 4, 12, 12, 5, 6, 12, 12, 13, 6, 12, 12, 13, - 6, 12, 12, 7, 6, 12, 7, 7, 14, 14, 7, 7, 14, 14, 14, 15 + 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 }; diff --git a/src/Parser.cpp b/src/Parser.cpp index 4cd208d..91eee3a 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -109,6 +109,8 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) #endif context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 0xf); page.DebugDraw(context, page.GetRootNode()); + + page.GetApp().StopLoad(); } // We may have exited a section so update @@ -188,7 +190,7 @@ void HTMLParser::EmitNode(Node* node) void HTMLParser::EmitImage(Image* image, int imageWidth, int imageHeight) { - Node* node = ImageNode::Construct(page.allocator, image); + Node* node = ImageNode::Construct(page.allocator); if (node) { node->size.x = imageWidth; @@ -729,14 +731,6 @@ bool AttributeParser::IsWhiteSpace(char c) return c == ' ' || c == '\n' || c == '\t'; } -#define RGB332(red, green, blue) (((red) & 0xe0) | (((green) & 0xe0) >> 3) | (((blue) & 0xc0) >> 6)) - -struct NamedColour -{ - const char* name; - uint8_t colour; -}; - NamedColour namedColours[] = { { "black", RGB332(0x00, 0x00, 0x00) }, diff --git a/src/Platform.h b/src/Platform.h index e0d533b..eb28ce8 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -17,7 +17,7 @@ #include #include "Font.h" -#include "Theme.h" +#include "Colour.h" #include "Cursor.h" struct Image; diff --git a/src/Tags.cpp b/src/Tags.cpp index ddef118..049131f 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -19,7 +19,7 @@ #include "Parser.h" #include "Page.h" #include "Platform.h" -#include "Image.h" +#include "Image/Image.h" #include "DataPack.h" #include "Nodes/Section.h" @@ -401,7 +401,8 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const AttributeParser attributes(attributeStr); int width = -1; int height = -1; - char* altText = NULL; + char* altText = nullptr; + char* src = nullptr; while (attributes.Parse()) { @@ -409,11 +410,15 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { altText = (char*) attributes.Value(); } - if (!stricmp(attributes.Key(), "width")) + else if (!stricmp(attributes.Key(), "src")) + { + src = (char*)attributes.Value(); + } + else if (!stricmp(attributes.Key(), "width")) { width = atoi(attributes.Value()); } - if (!stricmp(attributes.Key(), "height")) + else if (!stricmp(attributes.Key(), "height")) { height = atoi(attributes.Value()); } @@ -434,6 +439,35 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } } + Node* imageNode = ImageNode::Construct(parser.page.allocator); + if (imageNode) + { + ImageNode::Data* data = static_cast(imageNode->data); + if (data) + { + if (src) + { + data->source = parser.page.allocator.AllocString(src); + } + + if (width != -1 && height != -1) + { + Platform::video->ScaleImageDimensions(width, height); + imageNode->size.x = width; + imageNode->size.y = height; + } + else + { + imageNode->size.x = 16; + imageNode->size.y = 16; + + } + + parser.EmitNode(imageNode); + } + } + + /* if (width == -1 && height == -1) { Image* imageIcon = Assets.imageIcon; @@ -452,6 +486,7 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const Platform::video->ScaleImageDimensions(width, height); parser.EmitImage(NULL, width, height); } + */ } void MetaTagHandler::Open(class HTMLParser& parser, char* attributeStr) const diff --git a/src/Theme.h b/src/Theme.h deleted file mode 100644 index b6587b9..0000000 --- a/src/Theme.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef _THEME_H_ -#define _THEME_H_ - -struct ColourScheme -{ - uint8_t pageColour; - uint8_t textColour; - uint8_t linkColour; - uint8_t buttonColour; -}; - -#endif diff --git a/src/Windows/WinNet.h b/src/Windows/WinNet.h index fb55789..379bc54 100644 --- a/src/Windows/WinNet.h +++ b/src/Windows/WinNet.h @@ -4,7 +4,7 @@ #include #include "../Platform.h" -#define MAX_CONCURRENT_REQUESTS 5 +#define MAX_CONCURRENT_REQUESTS 1 class WindowsNetworkDriver : public NetworkDriver { diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index e614ca9..5ddbeec 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -15,7 +15,7 @@ #include #include #include "WinVid.h" -#include "../Image.h" +#include "../Image/Image.h" #include "../Interface.h" #include "../DataPack.h" #include "../Draw/Surf1bpp.h" diff --git a/tools/ImageGen.cpp b/tools/ImageGen.cpp index 0e8eb30..7a1f986 100644 --- a/tools/ImageGen.cpp +++ b/tools/ImageGen.cpp @@ -111,9 +111,12 @@ void EncodeImage(const char* basePath, const char* imageFilename, vector Date: Tue, 13 Feb 2024 20:19:58 +0000 Subject: [PATCH 26/98] Improved GIF image loading --- project/Windows/Windows.vcxproj | 1 + src/App.cpp | 2 + src/DOS/CGA.cpp | 2 +- src/DOS/EGA.cpp | 2 +- src/DOS/Hercules.cpp | 2 +- src/DOS/Olivetti.cpp | 2 +- src/DOS/Platform.cpp | 2 +- src/DOS/Surf4bpp.cpp | 103 +++++++++++++++++++++-------- src/HTTP.cpp | 2 +- src/Image/Decoder.cpp | 22 ++++++ src/Image/Decoder.h | 12 ++++ src/Image/Decoders.h | 30 --------- src/Image/Gif.cpp | 114 +++++++++++++++++++++++--------- src/Image/Gif.h | 7 ++ src/Layout.cpp | 8 +++ src/LinAlloc.h | 4 +- src/Nodes/ImgNode.cpp | 19 +++--- 17 files changed, 228 insertions(+), 106 deletions(-) create mode 100644 src/Image/Decoder.cpp delete mode 100644 src/Image/Decoders.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 7fc1d1e..b7a9d50 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -144,6 +144,7 @@ + diff --git a/src/App.cpp b/src/App.cpp index 4e0a358..bd9df08 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -138,6 +138,8 @@ void App::Run(int argc, char* argv[]) void LoadTask::Load(const char* targetURL) { + Stop(); + url = targetURL; // Check for protocol substring diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index 0f29c7e..861b5c8 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "../Image.h" +#include "../Image/Image.h" #include "CGA.h" #include "../Interface.h" #include "../DataPack.h" diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp index 1288bff..39bc0b5 100644 --- a/src/DOS/EGA.cpp +++ b/src/DOS/EGA.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "../Image.h" +#include "../Image/Image.h" #include "EGA.h" #include "../DataPack.h" #include "../Interface.h" diff --git a/src/DOS/Hercules.cpp b/src/DOS/Hercules.cpp index e3c57b8..72972fc 100644 --- a/src/DOS/Hercules.cpp +++ b/src/DOS/Hercules.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "../Image.h" +#include "../Image/Image.h" #include "Hercules.h" #include "../DataPack.h" #include "../Interface.h" diff --git a/src/DOS/Olivetti.cpp b/src/DOS/Olivetti.cpp index 8dcbbbc..293399e 100644 --- a/src/DOS/Olivetti.cpp +++ b/src/DOS/Olivetti.cpp @@ -18,7 +18,7 @@ #include #include #include -#include "../Image.h" +#include "../Image/Image.h" #include "Olivetti.h" #include "../Interface.h" #include "../DataPack.h" diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index b54169f..77b9fd0 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -25,7 +25,7 @@ #include "DOSInput.h" #include "DOSNet.h" -#include "../Image.h" +#include "../Image/Image.h" #include "../Cursor.h" #include "../Font.h" #include "../Draw/Surface.h" diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index ea3f8bb..eb3994a 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -3,7 +3,7 @@ #include #include "../Draw/Surf4bpp.h" #include "../Font.h" -#include "../Image.h" +#include "../Image/Image.h" #define GC_INDEX 0x3ce #define GC_DATA 0x3cf @@ -375,6 +375,8 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { + if (!image->data) + return; x += context.drawOffsetX; y += context.drawOffsetY; @@ -389,7 +391,14 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int if (x < context.clipLeft) { - srcData += ((context.clipLeft - x) >> 3); + if (image->bpp == 1) + { + srcData += ((context.clipLeft - x) >> 3); + } + else + { + srcData += (context.clipLeft - x); + } destWidth -= (context.clipLeft - x); x = context.clipLeft; } @@ -416,43 +425,81 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int 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++) + if (image->bpp == 8) { - 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 + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); - // Handle the first destination byte separately with proper masking - if (xBits == 0) + // Blit the image data line by line + for (int j = 0; j < destHeight; j++) { - // If x is on a byte boundary, copy the whole byte from the source - for (int i = 0; i < destByteWidth; i++) + uint8_t* srcRow = srcData + (j * srcPitch); + uint8_t* destRow = lines[y + j] + (x >> 3); + uint8_t destMask = 0x80 >> (x & 7); + + for (int i = 0; i < destWidth; i++) { - destRow[i] = srcRow[i]; + uint8_t colour = *srcRow++; + + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, destMask); + + outp(GC_INDEX, GC_SET_RESET); + outp(GC_DATA, colour); + + *destRow |= 0xff; + + destMask >>= 1; + if (!destMask) + { + destMask = 0x80; + destRow++; + } } } - else + } + else + { + 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++) { - // Copy the first destination byte with proper masking - destRow[0] = (destRow[0] & (0xFF << xBits)) | (srcRow[0] >> (8 - xBits)); + 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 - // Copy the remaining bytes - for (int i = 1; i < destByteWidth; i++) + // Handle the first destination byte separately with proper masking + if (xBits == 0) { - destRow[i] = (srcRow[i - 1] << xBits) | (srcRow[i] >> (8 - xBits)); + // 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)); - // 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); + // 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); + } } } } diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 45806ab..2996552 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -328,7 +328,7 @@ void HTTPRequest::Update() contentRemaining = atoi(lineBuffer + 15); } - printf("Header: %s -- \n", lineBuffer); + //printf("Header: %s -- \n", lineBuffer); //getchar(); } } diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp new file mode 100644 index 0000000..1883652 --- /dev/null +++ b/src/Image/Decoder.cpp @@ -0,0 +1,22 @@ +#include +#include "Gif.h" +#include "Decoder.h" + +union +{ + char gif[sizeof(GifDecoder)]; + //char png[sizeof(PngDecoder)]; + //char jpeg[sizeof(JpegDecoder)]; + char buffer[1]; +} ImageDecoderUnion; + + +ImageDecoder* ImageDecoder::Get() +{ + return (ImageDecoder*)(ImageDecoderUnion.buffer); +} + +ImageDecoder* ImageDecoder::Create(DecoderType type, LinearAllocator& allocator) +{ + return new (ImageDecoderUnion.buffer) GifDecoder(allocator); +} diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index ad7295b..b6dcad6 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -2,7 +2,9 @@ #define _DECODER_H_ #include +#include struct Image; +class LinearAllocator; class ImageDecoder { @@ -15,11 +17,21 @@ class ImageDecoder Error }; + enum DecoderType + { + Gif, + Png, + Jpeg + }; + ImageDecoder() : outputImage(NULL) {} virtual void Begin(Image* image) = 0; virtual void Process(uint8_t* data, size_t dataLength) = 0; virtual State GetState() = 0; + static ImageDecoder* Get(); + static ImageDecoder* Create(DecoderType type, LinearAllocator& allocator); + protected: Image* outputImage; }; diff --git a/src/Image/Decoders.h b/src/Image/Decoders.h deleted file mode 100644 index f73fc78..0000000 --- a/src/Image/Decoders.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef _DECODERS_H_ -#define _DECODERS_H_ - -#include "Gif.h" -#include - -class LinearAllocator; - -struct ImageDecoderUnion -{ - inline ImageDecoder* Get() { return (ImageDecoder*)(buffer); } - - template - void Create(LinearAllocator& allocator) - { - new (buffer) T(allocator); - } - -private: - union - { - char gif[sizeof(GifDecoder)]; - //char png[sizeof(PngDecoder)]; - //char jpeg[sizeof(JpegDecoder)]; - char buffer[1]; - }; - -} ImageDecoders; - -#endif diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index b77e212..595753f 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -4,6 +4,12 @@ #include "../Colour.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 @@ -55,19 +61,19 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if(!outputImage->data) { // Allocation error - printf("Could not allocate!\n"); + DEBUG_MESSAGE("Could not allocate!\n"); state = ImageDecoder::Error; return; } backgroundColour = header.backgroundColour; - printf("Image is %d x %d\n", outputImage->width, outputImage->height); + DEBUG_MESSAGE("Image is %d x %d\n", outputImage->width, outputImage->height); if(header.fields & (1 << 7)) { paletteSize = (1 << ((header.fields & 0x7) + 1)); - printf("Image has a palette of %d colours\n", (int) paletteSize); + DEBUG_MESSAGE("Image has a palette of %d colours\n", (int) paletteSize); internalState = ParsePalette; paletteIndex = 0; @@ -84,7 +90,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if(FillStruct(&data, dataLength, rgb, 3)) { - //printf("RGB index %d: %x %x %x\n", paletteIndex, (int)(rgb[0]), (int)(rgb[1]), (int)(rgb[2])); + //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; @@ -128,7 +134,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) break; default: - printf("Invalid block type: %x\n", (int)(blockType)); + DEBUG_MESSAGE("Invalid block type: %x\n", (int)(blockType)); state = ImageDecoder::Error; return; } @@ -139,9 +145,10 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if(FillStruct(&data, dataLength, &imageDescriptor, sizeof(ImageDescriptor))) { - printf("Image: %d, %d %d, %d\n", imageDescriptor.x, imageDescriptor.y, imageDescriptor.width, imageDescriptor.height); + DEBUG_MESSAGE("Image: %d, %d %d, %d\n", imageDescriptor.x, imageDescriptor.y, imageDescriptor.width, imageDescriptor.height); drawX = drawY = 0; + writePosition = 0; if (imageDescriptor.fields & 0x80) { @@ -175,7 +182,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) case ParseLZWCodeSize: { lzwCodeSize = NextByte(&data, dataLength); - printf("LZW code size: %d\n", lzwCodeSize); + DEBUG_MESSAGE("LZW code size: %d\n", lzwCodeSize); // Init LZW vars code = 0; @@ -193,7 +200,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) case ParseImageSubBlockSize: { imageSubBlockSize = NextByte(&data, dataLength); - printf("Sub block size: %d bytes\n", imageSubBlockSize); + DEBUG_MESSAGE("Sub block size: %d bytes\n", imageSubBlockSize); if(imageSubBlockSize) { internalState = ParseImageSubBlock; @@ -215,7 +222,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { imageSubBlockSize--; uint8_t dataByte = NextByte(&data, dataLength); - //printf("-Data: %x\n", dataByte); + //DEBUG_MESSAGE("-Data: %x\n", dataByte); for(int n = 0; n < 8; n++) { @@ -227,12 +234,12 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if (codeBit == codeLength) { - //printf("code: %x [len=%d]\n", code, codeLength); + //DEBUG_MESSAGE("code: %x [len=%d]\n", code, codeLength); // Code complete if (code == clearCode) { - printf("CLEAR\n"); + DEBUG_MESSAGE("CLEAR\n"); codeLength = resetCodeLength; ClearDictionary(); prev = -1; @@ -242,10 +249,10 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) } else if (code == stopCode) { - printf("STOP\n"); + DEBUG_MESSAGE("STOP\n"); if (imageSubBlockSize) { - printf("Malformed GIF\n"); + DEBUG_MESSAGE("Malformed GIF\n"); state = ImageDecoder::Error; } continue; @@ -255,7 +262,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if (code > dictionaryIndex) { - printf("Error: code = %x, but dictionaryIndex = %x\n", code, dictionaryIndex); + DEBUG_MESSAGE("Error: code = %x, but dictionaryIndex = %x\n", code, dictionaryIndex); state = ImageDecoder::Error; return; } @@ -282,7 +289,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if(dictionaryIndex == (1 << (codeLength)) && codeLength < 12) { codeLength++; - //printf("Code length: %d\n", codeLength); + //DEBUG_MESSAGE("Code length: %d\n", codeLength); } } @@ -296,12 +303,12 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if (stackSize >= 1024) { - fprintf(stderr, "Stack overflow!"); + DEBUG_MESSAGE("Stack overflow!"); exit(-1); } stack[stackSize++] = dictionary[code].byte; - //printf(" value: %x\n", dictionary[code].byte); + //DEBUG_MESSAGE(" value: %x\n", dictionary[code].byte); code = dictionary[code].prev; } @@ -320,7 +327,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) } else { - //printf("--\n"); + //DEBUG_MESSAGE("--\n"); internalState = ParseImageSubBlockSize; } } @@ -330,7 +337,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if(FillStruct(&data, dataLength, &extensionHeader, sizeof(ExtensionHeader))) { - printf("Extension: %x Size: %d\n", (int) extensionHeader.code, (int) extensionHeader.size); + DEBUG_MESSAGE("Extension: %x Size: %d\n", (int) extensionHeader.code, (int) extensionHeader.size); internalState = ParseExtensionContents; } } @@ -338,15 +345,10 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) case ParseExtensionContents: { - if(extensionHeader.size == 0) + if(SkipBytes(&data, dataLength, extensionHeader.size)) { internalState = ParseExtensionSubBlockSize; } - else - { - NextByte(&data, dataLength); - extensionHeader.size--; - } } break; @@ -366,12 +368,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) case ParseExtensionSubBlock: { - if(extensionSubBlockSize > 0) - { - NextByte(&data, dataLength); - extensionSubBlockSize--; - } - else + if(SkipBytes(&data, dataLength, extensionSubBlockSize)) { internalState = ParseExtensionSubBlockSize; } @@ -381,6 +378,26 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) } } +bool GifDecoder::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; + } +} + + bool GifDecoder::FillStruct(uint8_t** data, size_t& dataLength, void* dest, size_t size) { size_t bytesLeft = size - structFillPosition; @@ -426,10 +443,43 @@ void GifDecoder::OutputPixel(uint8_t pixelValue) } else*/ { - outputImage->data[drawX++] = paletteLUT[pixelValue]; + outputImage->data[writePosition++] = paletteLUT[pixelValue]; + + if (imageDescriptor.fields & GIF_INTERLACE_BIT) + { + drawX++; + if (drawX == imageDescriptor.width) + { + drawX = 0; + drawY++; + int outputLine = CalculateLineIndex(drawY); + writePosition = outputLine * header.width; + } + } //outputImage->data[drawX++] = palette[pixelValue * 3]; //outputImage->data[drawX++] = palette[pixelValue * 3 + 1]; //outputImage->data[drawX++] = palette[pixelValue * 3 + 2]; //outputImage->data[drawX++] = pixelValue == header.backgroundColour ? 0 : 255; } } + +// 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; +} \ No newline at end of file diff --git a/src/Image/Gif.h b/src/Image/Gif.h index 8a37bae..5266c3a 100644 --- a/src/Image/Gif.h +++ b/src/Image/Gif.h @@ -8,6 +8,8 @@ #define GIF_MAX_LZW_CODE_LENGTH 12 #define GIF_MAX_DICTIONARY_ENTRIES (1 << (GIF_MAX_LZW_CODE_LENGTH + 1)) +#define GIF_INTERLACE_BIT 0x40 + class GifDecoder : public ImageDecoder { public: @@ -26,8 +28,12 @@ class GifDecoder : public ImageDecoder dataLength--; return result; } + bool SkipBytes(uint8_t** data, size_t& dataLength, size_t size); + void ClearDictionary(); void OutputPixel(uint8_t pixelValue); + int CalculateLineIndex(int y); + enum InternalState { @@ -121,6 +127,7 @@ class GifDecoder : public ImageDecoder int codeBit; int drawX, drawY; + int writePosition; }; struct { diff --git a/src/Layout.cpp b/src/Layout.cpp index 421865f..6a896d2 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -194,6 +194,10 @@ void Layout::RecalculateLayout() else { node = node->parent; + if (node) + { + node->EncapsulateChildren(); + } checkChildren = false; continue; } @@ -201,6 +205,10 @@ void Layout::RecalculateLayout() if (node) { node->Handler().GenerateLayout(*this, node); + /*if (node->parent) + { + node->parent->OnChildLayoutChanged(); + }*/ } } diff --git a/src/LinAlloc.h b/src/LinAlloc.h index b8fa8b2..bb04c1c 100644 --- a/src/LinAlloc.h +++ b/src/LinAlloc.h @@ -21,8 +21,8 @@ #include #pragma warning(disable:4996) -// 8K chunk size including next chunk pointer -#define CHUNK_DATA_SIZE (16 * 1024 - sizeof(struct Chunk*)) +// 32K chunk size including next chunk pointer +#define CHUNK_DATA_SIZE (32 * 1024 - sizeof(struct Chunk*)) class LinearAllocator { diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index dc22f99..5972804 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -5,7 +5,7 @@ #include "../Layout.h" #include "../Draw/Surface.h" #include "../App.h" -#include "../Image/Decoders.h" +#include "../Image/Decoder.h" void ImageNode::Draw(DrawContext& context, Node* node) { @@ -57,23 +57,26 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) if (data && data->source) { loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); - ImageDecoders.Create(App::Get().page.allocator); - ImageDecoders.Get()->Begin(&data->image); + ImageDecoder::Create(ImageDecoder::Gif, App::Get().page.allocator); + ImageDecoder::Get()->Begin(&data->image); } } bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) { - ImageDecoder* decoder = ImageDecoders.Get(); + ImageDecoder* decoder = ImageDecoder::Get(); ImageNode::Data* data = static_cast(node->data); decoder->Process((uint8_t*) buffer, count); if (decoder->GetState() == ImageDecoder::Success) { - node->size.x = data->image.width; - node->size.y = data->image.height; - App::Get().page.layout.RecalculateLayout(); - printf("Success!\n"); + if (node->size.x != data->image.width || node->size.y != data->image.height) + { + node->size.x = data->image.width; + node->size.y = data->image.height; + App::Get().page.layout.RecalculateLayout(); + } + //printf("Success!\n"); } return decoder->GetState() == ImageDecoder::Decoding; From 1bd42bb1bd12922b393d72a7f05ef9cc56feb36b Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 16 Feb 2024 20:06:10 +0000 Subject: [PATCH 27/98] New memory manager as basis for supporting EMS / disk swap. Improvements to GIF loading and added support for resizing images --- project/DOS/Makefile | 8 +- project/Windows/Windows.vcxproj | 6 +- src/DataPack.cpp | 2 +- src/Draw/Surf1bpp.cpp | 3 + src/Draw/Surf8bpp.cpp | 16 +-- src/Image/Decoder.cpp | 4 +- src/Image/Decoder.h | 2 +- src/Image/Gif.cpp | 171 ++++++++++++++++++++++++++++---- src/Image/Gif.h | 17 +++- src/Image/Image.h | 4 +- src/Interface.cpp | 4 +- src/Interface.h | 3 - src/{ => Memory}/LinAlloc.h | 4 +- src/Memory/MemBlock.cpp | 100 +++++++++++++++++++ src/Memory/MemBlock.h | 63 ++++++++++++ src/Memory/Memory.cpp | 5 + src/Memory/Memory.h | 17 ++++ src/Nodes/Block.cpp | 2 +- src/Nodes/Break.cpp | 2 +- src/Nodes/Button.cpp | 2 +- src/Nodes/Field.cpp | 2 +- src/Nodes/Form.cpp | 2 +- src/Nodes/ImgNode.cpp | 6 +- src/Nodes/LinkNode.cpp | 2 +- src/Nodes/Scroll.cpp | 2 +- src/Nodes/Section.cpp | 2 +- src/Nodes/Status.cpp | 2 +- src/Nodes/StyNode.cpp | 2 +- src/Nodes/Text.cpp | 6 +- src/Page.cpp | 5 +- src/Page.h | 2 - src/Parser.cpp | 9 +- src/Tags.cpp | 70 +++++++------ src/Windows/WinVid.cpp | 12 +++ src/Windows/WinVid.h | 2 + 35 files changed, 457 insertions(+), 104 deletions(-) rename src/{ => Memory}/LinAlloc.h (96%) create mode 100644 src/Memory/MemBlock.cpp create mode 100644 src/Memory/MemBlock.h create mode 100644 src/Memory/Memory.cpp create mode 100644 src/Memory/Memory.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index cf320fd..bf8ead4 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Image.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -106,6 +106,12 @@ Button.obj: $(SRC_PATH)\Nodes\Button.cpp DataPack.obj: $(SRC_PATH)\DataPack.cpp $(CC) -fo=$@ $(CFLAGS) $< +Decoder.obj: $(SRC_PATH)\Image\Decoder.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Image.obj: $(SRC_PATH)\Image\Gif.cpp + $(CC) -fo=$@ $(CFLAGS) $< + CGA.obj: $(SRC_PATH)\DOS\CGA.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index b7a9d50..6c41066 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -147,6 +147,8 @@ + + @@ -185,6 +187,9 @@ + + + @@ -200,7 +205,6 @@ - diff --git a/src/DataPack.cpp b/src/DataPack.cpp index 2c0a960..7aa49af 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -110,7 +110,7 @@ Image* DataPack::LoadImageAsset(FILE* fs, DataPackHeader& header, const char* en { Image* image = new Image(); memcpy(image, asset, sizeof(ImageMetadata)); - image->data = ((uint8_t*) asset) + sizeof(ImageMetadata); + //image->data = ((uint8_t*) asset) + sizeof(ImageMetadata); return image; } return nullptr; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index ef8b401..259259c 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -351,12 +351,14 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { +#if 0 x += context.drawOffsetX; y += context.drawOffsetY; int srcWidth = image->width; int srcHeight = image->height; int srcPitch = image->pitch; + int startX = 0; uint8_t* srcData = image->data; // Calculate the destination width and height to copy, considering clipping region @@ -431,6 +433,7 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int destRow[destByteWidth - 1] = (destRow[destByteWidth - 1] & ~mask) | (srcRow[destByteWidth] & mask); } } +#endif } diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index a9ca787..c1b798a 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -2,6 +2,7 @@ #include "Surf8bpp.h" #include "../Font.h" #include "../Image/Image.h" +#include "../Memory/MemBlock.h" DrawSurface_8BPP::DrawSurface_8BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) @@ -222,7 +223,7 @@ void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { - if (!image->data) + if (!image->lines) return; x += context.drawOffsetX; @@ -231,7 +232,8 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int int srcWidth = image->width; int srcHeight = image->height; int srcPitch = image->pitch; - uint8_t* srcData = image->data; + int srcX = 0; + int srcY = 0; // Calculate the destination width and height to copy, considering clipping region int destWidth = srcWidth; @@ -241,11 +243,11 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int { if (image->bpp == 1) { - srcData += ((context.clipLeft - x) >> 3); + srcX += ((context.clipLeft - x) >> 3); } else { - srcData += (context.clipLeft - x); + srcX += (context.clipLeft - x); } destWidth -= (context.clipLeft - x); x = context.clipLeft; @@ -258,7 +260,7 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int if (y < context.clipTop) { - srcData += (context.clipTop - y) * srcPitch; + srcY += (context.clipTop - y); destHeight -= (context.clipTop - y); y = context.clipTop; } @@ -278,12 +280,12 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* srcRow = srcData + (j * srcPitch); + uint8_t* src = image->lines[srcY + j].Get() + srcX; uint8_t* destRow = lines[y + j] + x; for (int i = 0; i < destWidth; i++) { - *destRow++ = *srcRow++; + *destRow++ = *src++; } } } diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 1883652..1c37e6c 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -16,7 +16,7 @@ ImageDecoder* ImageDecoder::Get() return (ImageDecoder*)(ImageDecoderUnion.buffer); } -ImageDecoder* ImageDecoder::Create(DecoderType type, LinearAllocator& allocator) +ImageDecoder* ImageDecoder::Create(DecoderType type) { - return new (ImageDecoderUnion.buffer) GifDecoder(allocator); + return new (ImageDecoderUnion.buffer) GifDecoder(); } diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index b6dcad6..68f63cc 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -30,7 +30,7 @@ class ImageDecoder virtual State GetState() = 0; static ImageDecoder* Get(); - static ImageDecoder* Create(DecoderType type, LinearAllocator& allocator); + static ImageDecoder* Create(DecoderType type); protected: Image* outputImage; diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 595753f..04ed362 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -2,6 +2,9 @@ #include "Image.h" #include "../Platform.h" #include "../Colour.h" +#include "../Memory/Memory.h" +#include "../Page.h" +#include "../App.h" #include #ifdef _WIN32 @@ -14,8 +17,8 @@ #define BLOCK_TYPE_IMAGE_DESCRIPTOR 0x2C #define BLOCK_TYPE_TRAILER 0x3B -GifDecoder::GifDecoder(LinearAllocator& inAllocator) -: allocator(inAllocator), state(ImageDecoder::Stopped) +GifDecoder::GifDecoder() +: state(ImageDecoder::Stopped) { } @@ -27,6 +30,7 @@ void GifDecoder::Begin(Image* image) structFillPosition = 0; internalState = ParseHeader; + lineBufferSkipCount = 0; } void GifDecoder::Process(uint8_t* data, size_t dataLength) @@ -45,26 +49,54 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if(FillStruct(&data, dataLength, &header, sizeof(Header))) { // Header structure is complete - if(memcmp(header.versionTag, "GIF89a", 6)) + 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++; + } - outputImage->width = header.width; - outputImage->height = header.height; - outputImage->pitch = header.width; + if (outputImage->width == 0 && outputImage->height == 0) + { + int width = header.width; + int height = header.height; + Platform::video->ScaleImageDimensions(width, height); + + outputImage->width = width; + outputImage->height = height; + outputImage->pitch = width; + } //outputImage->data = (uint8_t*) allocator.Alloc((header.width * header.height) >> 3); //outputImage->data = (uint8_t*) allocator.Alloc((header.width * header.height) * 3); - outputImage->data = (uint8_t*)allocator.Alloc(outputImage->pitch * header.height); - if(!outputImage->data) + + outputImage->lines = (MemBlockHandle*)MemoryManager::pageAllocator.Alloc(sizeof(MemBlockHandle) * outputImage->height); + //outputImage->data = (uint8_t*)allocator.Alloc(outputImage->pitch * header.height); + if(!outputImage->lines) { // Allocation error DEBUG_MESSAGE("Could not allocate!\n"); state = ImageDecoder::Error; return; } + + for (int j = 0; j < outputImage->height; j++) + { + outputImage->lines[j] = MemoryManager::pageBlockAllocator.Allocate(outputImage->width); + if (!outputImage->lines[j].IsAllocated()) + { + // Allocation error + DEBUG_MESSAGE("Could not allocate!\n"); + state = ImageDecoder::Error; + return; + } + } backgroundColour = header.backgroundColour; @@ -106,7 +138,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) paletteLUT[n] = Platform::video->paletteLUT[RGB332(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2])]; } - paletteLUT[header.backgroundColour] = Platform::video->colourScheme.pageColour; + //paletteLUT[header.backgroundColour] = Platform::video->colourScheme.pageColour; internalState = ParseDataBlock; } @@ -148,7 +180,11 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) DEBUG_MESSAGE("Image: %d, %d %d, %d\n", imageDescriptor.x, imageDescriptor.y, imageDescriptor.width, imageDescriptor.height); drawX = drawY = 0; - writePosition = 0; + outputLine = 0; + + linesProcessed = 0; + lineBufferSize = 0; + lineBufferFlushCount = 0; if (imageDescriptor.fields & 0x80) { @@ -253,7 +289,10 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if (imageSubBlockSize) { DEBUG_MESSAGE("Malformed GIF\n"); - state = ImageDecoder::Error; + // FIXME + //state = ImageDecoder::Success; + //return; + //state = ImageDecoder::Error; } continue; } @@ -313,10 +352,27 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) code = dictionary[code].prev; } - while(stackSize) + while (stackSize) { - OutputPixel(stack[stackSize - 1]); + if (lineBufferSkipCount == lineBufferDivider - 1) + { + lineBuffer[lineBufferSize++] = stack[stackSize - 1]; + lineBufferSkipCount = 0; + } + else + { + lineBufferSkipCount++; + } + + lineBufferFlushCount++; stackSize--; + + if (lineBufferFlushCount == imageDescriptor.width) + { + ProcessLineBuffer(); + lineBufferSize = 0; + lineBufferFlushCount = 0; + } } } @@ -443,18 +499,28 @@ void GifDecoder::OutputPixel(uint8_t pixelValue) } else*/ { - outputImage->data[writePosition++] = paletteLUT[pixelValue]; + outputImage->lines[outputLine].Get()[drawX] = paletteLUT[pixelValue]; + outputImage->lines[outputLine].Commit(); + drawX++; - if (imageDescriptor.fields & GIF_INTERLACE_BIT) + if (drawX == imageDescriptor.width) { - drawX++; - if (drawX == imageDescriptor.width) + drawX = 0; + drawY++; + if (imageDescriptor.fields & GIF_INTERLACE_BIT) + { + outputLine = CalculateLineIndex(drawY); + } + else { - drawX = 0; - drawY++; - int outputLine = CalculateLineIndex(drawY); - writePosition = outputLine * header.width; + outputLine = drawY; } + + if (outputLine >= imageDescriptor.height) + { + outputLine = imageDescriptor.height - 1; + } + } //outputImage->data[drawX++] = palette[pixelValue * 3]; //outputImage->data[drawX++] = palette[pixelValue * 3 + 1]; @@ -482,4 +548,65 @@ int GifDecoder::CalculateLineIndex(int y) y -= p; /* pass 4 */ return y * 2 + 1; -} \ No newline at end of file +} + +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 * outputImage->height / header.height; + int last = (outputY + 1) * outputImage->height / header.height; + + for (int y = first; y < last; y++) + { + EmitLine(y); + } + } + + linesProcessed++; +} + +void GifDecoder::EmitLine(int y) +{ + uint8_t* output = outputImage->lines[y].Get(); + + 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 y = 0; + + for (int i = 0; i < outputImage->width; i++) + { + output[i] = paletteLUT[lineBuffer[y]]; + while (D > 0) + { + y++; + D -= 2 * dx; + } + D += 2 * dy; + } + } + + outputImage->lines[y].Commit(); +} + diff --git a/src/Image/Gif.h b/src/Image/Gif.h index 5266c3a..bdbb2d9 100644 --- a/src/Image/Gif.h +++ b/src/Image/Gif.h @@ -3,17 +3,17 @@ #include #include "Decoder.h" -#include "../LinAlloc.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(LinearAllocator& inAllocator); + GifDecoder(); virtual void Begin(Image* image); virtual void Process(uint8_t* data, size_t dataLength); @@ -32,7 +32,10 @@ class GifDecoder : public ImageDecoder void ClearDictionary(); void OutputPixel(uint8_t pixelValue); + int CalculateLineIndex(int y); + void ProcessLineBuffer(); + void EmitLine(int y); enum InternalState @@ -83,7 +86,6 @@ class GifDecoder : public ImageDecoder }; #pragma pack(pop) - LinearAllocator& allocator; ImageDecoder::State state; InternalState internalState; @@ -127,7 +129,14 @@ class GifDecoder : public ImageDecoder int codeBit; int drawX, drawY; - int writePosition; + int outputLine; + + uint8_t lineBuffer[GIF_LINE_BUFFER_MAX_SIZE]; + int lineBufferSize; + int linesProcessed; + int lineBufferDivider; + int lineBufferSkipCount; + int lineBufferFlushCount; }; struct { diff --git a/src/Image/Image.h b/src/Image/Image.h index 9e63b27..d701cf8 100644 --- a/src/Image/Image.h +++ b/src/Image/Image.h @@ -13,11 +13,11 @@ struct ImageMetadata struct Image : ImageMetadata { - Image() : data(nullptr) + Image() : lines(nullptr) { width = height = pitch = bpp = 0; } - uint8_t* data; + struct MemBlockHandle* lines; }; #endif diff --git a/src/Interface.cpp b/src/Interface.cpp index 5be7862..87fe976 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -25,6 +25,7 @@ #include "Draw/Surface.h" #include "DataPack.h" #include "Event.h" +#include "Memory/Memory.h" AppInterface::AppInterface(App& inApp) : app(inApp) { @@ -201,7 +202,7 @@ void AppInterface::Update() 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)); + snprintf(tempMessage, 50, "Allocated: %dK Used: %dK\n", (int)(MemoryManager::pageAllocator.TotalAllocated() / 1024), (int)(MemoryManager::pageAllocator.TotalUsed() / 1024)); app.ui.SetStatusMessage(tempMessage); } break; @@ -295,6 +296,7 @@ void AppInterface::UpdatePageScrollBar() void AppInterface::GenerateInterfaceNodes() { + LinearAllocator& allocator = MemoryManager::interfaceAllocator; rootInterfaceNode = SectionElement::Construct(allocator, SectionElement::Interface); rootInterfaceNode->style.alignment = ElementAlignment::Left; rootInterfaceNode->style.fontSize = 1; diff --git a/src/Interface.h b/src/Interface.h index ff1bbc1..899aec3 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -16,7 +16,6 @@ #define _INTERFACE_H_ #include "Platform.h" #include "URL.h" -#include "LinAlloc.h" #include "Node.h" #define MAX_TITLE_LENGTH 80 @@ -80,8 +79,6 @@ class AppInterface int oldMouseX, oldMouseY; int oldPageHeight; - LinearAllocator allocator; - Node* rootInterfaceNode; Node* titleNode; Node* backButtonNode; diff --git a/src/LinAlloc.h b/src/Memory/LinAlloc.h similarity index 96% rename from src/LinAlloc.h rename to src/Memory/LinAlloc.h index bb04c1c..d17bf92 100644 --- a/src/LinAlloc.h +++ b/src/Memory/LinAlloc.h @@ -21,8 +21,8 @@ #include #pragma warning(disable:4996) -// 32K chunk size including next chunk pointer -#define CHUNK_DATA_SIZE (32 * 1024 - sizeof(struct Chunk*)) +// 16K chunk size including next chunk pointer +#define CHUNK_DATA_SIZE (16 * 1024 - sizeof(struct Chunk*)) class LinearAllocator { diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp new file mode 100644 index 0000000..1f4ac49 --- /dev/null +++ b/src/Memory/MemBlock.cpp @@ -0,0 +1,100 @@ +#include "MemBlock.h" +#include "LinAlloc.h" +#include "Memory.h" + +void* MemBlockHandle::Get() +{ + switch (type) + { + case MemBlockHandle::Conventional: + return conventionalPointer; + case MemBlockHandle::DiskSwap: + { + return MemoryManager::pageBlockAllocator.AccessSwap(*this); + } + default: + return nullptr; + } +} + +void MemBlockHandle::Commit() +{ + switch (type) + { + case MemBlockHandle::DiskSwap: + MemoryManager::pageBlockAllocator.CommitSwap(*this); + break; + + } +} + +MemBlockAllocator::MemBlockAllocator() +{ + //swapFile = fopen("Microweb.swp", "w+"); + swapFile = NULL; + if (swapFile) + { + swapBuffer = malloc(MAX_SWAP_ALLOCATION); + lastSwapRead = -1; + swapFileLength = 0; + } +} + + +MemBlockHandle MemBlockAllocator::Allocate(size_t size) +{ + MemBlockHandle result; + + if (swapFile && size <= MAX_SWAP_ALLOCATION) + { + result.swapFilePosition = swapFileLength; + result.allocatedSize = size; + fseek(swapFile, swapFileLength, SEEK_SET); + + char empty[32]; + size_t toWrite = size; + while (toWrite > 0) + { + if (toWrite >= 32) + { + fwrite(empty, 1, 32, swapFile); + toWrite -= 32; + } + else + { + fwrite(empty, 1, toWrite, swapFile); + break; + } + } + swapFileLength += size; + result.type = MemBlockHandle::DiskSwap; + } + else + { + result.conventionalPointer = MemoryManager::pageAllocator.Alloc(size); + if (result.conventionalPointer) + { + result.type = MemBlockHandle::Conventional; + } + } + + return result; +} + +void* MemBlockAllocator::AccessSwap(MemBlockHandle& handle) +{ + if (lastSwapRead != handle.swapFilePosition) + { + fseek(swapFile, handle.swapFilePosition, SEEK_SET); + fread(swapBuffer, 1, handle.allocatedSize, swapFile); + lastSwapRead = handle.swapFilePosition; + } + + return swapBuffer; +} + +void MemBlockAllocator::CommitSwap(MemBlockHandle& handle) +{ + fseek(swapFile, handle.swapFilePosition, SEEK_SET); + fwrite(swapBuffer, 1, handle.allocatedSize, swapFile); +} diff --git a/src/Memory/MemBlock.h b/src/Memory/MemBlock.h new file mode 100644 index 0000000..52203f4 --- /dev/null +++ b/src/Memory/MemBlock.h @@ -0,0 +1,63 @@ +#ifndef _MEMBLOCK_H_ +#define _MEMBLOCK_H_ + +#include +#include + +#define MAX_SWAP_ALLOCATION (1024) +#define MAX_SWAP_SIZE (1024 * 1024) + +// Abstract way of allocating a chunk of memory from conventional memory, EMS, disk swap + +struct MemBlockHandle +{ + enum Type + { + Unallocated, + Conventional, + EMS, + DiskSwap + }; + + Type type; + + MemBlockHandle() : type(Unallocated), allocatedSize(0) {} + void* Get(); + + template + inline T* Get() { return (T*)Get(); } + void Commit(); + + bool IsAllocated() { return type != Unallocated; } + + union + { + void* conventionalPointer; + long swapFilePosition; + // TODO: EMS, disk swap + }; + size_t allocatedSize; +}; + +class LinearAllocator; + +class MemBlockAllocator +{ +public: + MemBlockAllocator(); + + MemBlockHandle Allocate(size_t size); + +private: + friend struct MemBlockHandle; + void* AccessSwap(MemBlockHandle& handle); + void CommitSwap(MemBlockHandle& handle); + + FILE* swapFile; + long swapFileLength; + void* swapBuffer; + long lastSwapRead; +}; + + +#endif diff --git a/src/Memory/Memory.cpp b/src/Memory/Memory.cpp new file mode 100644 index 0000000..338cc7a --- /dev/null +++ b/src/Memory/Memory.cpp @@ -0,0 +1,5 @@ +#include "Memory.h" + +LinearAllocator MemoryManager::pageAllocator; +LinearAllocator MemoryManager::interfaceAllocator; +MemBlockAllocator MemoryManager::pageBlockAllocator; diff --git a/src/Memory/Memory.h b/src/Memory/Memory.h new file mode 100644 index 0000000..38f2d98 --- /dev/null +++ b/src/Memory/Memory.h @@ -0,0 +1,17 @@ +#ifndef _MEMORY_H_ +#define _MEMORY_H_ + +#include "LinAlloc.h" +#include "MemBlock.h" + +class MemoryManager +{ +public: + static LinearAllocator pageAllocator; + static LinearAllocator interfaceAllocator; + + static MemBlockAllocator pageBlockAllocator; +}; + + +#endif diff --git a/src/Nodes/Block.cpp b/src/Nodes/Block.cpp index 16f3044..4de3459 100644 --- a/src/Nodes/Block.cpp +++ b/src/Nodes/Block.cpp @@ -1,6 +1,6 @@ #include #include "../Layout.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "Block.h" Node* BlockNode::Construct(Allocator& allocator, int horizontalPadding, int verticalPadding) diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index 67b47ab..531e9bf 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -1,6 +1,6 @@ #include #include "../Layout.h" -#include "../LinAlloc.h" +#include "../Memory/LinAlloc.h" #include "../Draw/Surface.h" #include "Break.h" diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index 67cad10..fa8f5fb 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -2,7 +2,7 @@ #include "Button.h" #include "../DataPack.h" #include "../Draw/Surface.h" -#include "../LinAlloc.h" +#include "../Memory/LinAlloc.h" #include "../Layout.h" #include "../Event.h" #include "../App.h" diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index a22c4ac..ac19954 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -1,7 +1,7 @@ #include "Field.h" #include "../DataPack.h" #include "../Draw/Surface.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../Layout.h" #include "../Platform.h" #include "../KeyCodes.h" diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp index 59299c5..9707ca5 100644 --- a/src/Nodes/Form.cpp +++ b/src/Nodes/Form.cpp @@ -1,5 +1,5 @@ #include "Form.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../App.h" #include "../Interface.h" #include "Field.h" diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 5972804..3aec4f0 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -1,7 +1,7 @@ #include "../Platform.h" #include "../Page.h" #include "ImgNode.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../Layout.h" #include "../Draw/Surface.h" #include "../App.h" @@ -13,7 +13,7 @@ void ImageNode::Draw(DrawContext& context, Node* node) //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); uint8_t outlineColour = 0; - if (data->image.data) + if (data->image.lines) { context.surface->BlitImage(context, &data->image, node->anchor.x, node->anchor.y); } @@ -57,7 +57,7 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) if (data && data->source) { loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); - ImageDecoder::Create(ImageDecoder::Gif, App::Get().page.allocator); + ImageDecoder::Create(ImageDecoder::Gif); ImageDecoder::Get()->Begin(&data->image); } } diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 04c61e0..82883f7 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -1,5 +1,5 @@ #include "LinkNode.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../Event.h" #include "../App.h" diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp index ec71b2e..6b56037 100644 --- a/src/Nodes/Scroll.cpp +++ b/src/Nodes/Scroll.cpp @@ -1,5 +1,5 @@ #include "Scroll.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../DataPack.h" #include "../App.h" #include "../Interface.h" diff --git a/src/Nodes/Section.cpp b/src/Nodes/Section.cpp index b4b337c..cc16838 100644 --- a/src/Nodes/Section.cpp +++ b/src/Nodes/Section.cpp @@ -1,6 +1,6 @@ #include "Section.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../Layout.h" Node* SectionElement::Construct(Allocator& allocator, SectionElement::Type sectionType) diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp index 57da6ea..c8ed170 100644 --- a/src/Nodes/Status.cpp +++ b/src/Nodes/Status.cpp @@ -1,5 +1,5 @@ #include "Status.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../DataPack.h" #include "../App.h" #include "../Interface.h" diff --git a/src/Nodes/StyNode.cpp b/src/Nodes/StyNode.cpp index f04fad7..e961c9a 100644 --- a/src/Nodes/StyNode.cpp +++ b/src/Nodes/StyNode.cpp @@ -1,5 +1,5 @@ #include "StyNode.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../Layout.h" void StyleNode::ApplyStyle(Node* node) diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index e3e18fd..be5617a 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -1,7 +1,7 @@ #include "../Platform.h" #include "../Page.h" #include "Text.h" -#include "../LinAlloc.h" +#include "../Memory/Memory.h" #include "../Layout.h" #include "../Draw/Surface.h" #include "../DataPack.h" @@ -85,7 +85,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) { if (!subTextNode) { - subTextNode = SubTextElement::Construct(layout.page.allocator, &text[startIndex]); + subTextNode = SubTextElement::Construct(MemoryManager::pageAllocator, &text[startIndex]); node->AddChild(subTextNode); } else @@ -124,7 +124,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) { if (!subTextNode) { - subTextNode = SubTextElement::Construct(layout.page.allocator, &text[startIndex]); + subTextNode = SubTextElement::Construct(MemoryManager::pageAllocator, &text[startIndex]); node->AddChild(subTextNode); } else diff --git a/src/Page.cpp b/src/Page.cpp index 5b131d2..bd05477 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -24,6 +24,7 @@ #include "Nodes/Form.h" #include "Nodes/StyNode.h" #include "Draw/Surface.h" +#include "Memory/Memory.h" #define TOP_MARGIN_PADDING 1 @@ -42,9 +43,9 @@ void Page::Reset() cursorX = leftMarginPadding; cursorY = TOP_MARGIN_PADDING; - allocator.Reset(); + MemoryManager::pageAllocator.Reset(); - rootNode = SectionElement::Construct(allocator, SectionElement::Document); + rootNode = SectionElement::Construct(MemoryManager::pageAllocator, SectionElement::Document); rootNode->style.alignment = ElementAlignment::Left; rootNode->style.fontSize = 1; rootNode->style.fontStyle = FontStyle::Regular; diff --git a/src/Page.h b/src/Page.h index 023c8e0..814f383 100644 --- a/src/Page.h +++ b/src/Page.h @@ -15,7 +15,6 @@ #ifndef _PAGE_H_ #define _PAGE_H_ -#include "LinAlloc.h" #include "URL.h" #include "Layout.h" @@ -36,7 +35,6 @@ class Page Node* GetRootNode() { return rootNode; } - LinearAllocator allocator; Layout layout; int GetPageWidth(); diff --git a/src/Parser.cpp b/src/Parser.cpp index 91eee3a..e622d66 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -23,6 +23,7 @@ #include "Nodes/Text.h" #include "Nodes/ImgNode.h" #include "Nodes/Break.h" +#include "Memory/Memory.h" // debug #include "Platform.h" @@ -165,7 +166,7 @@ void HTMLParser::AppendTextBuffer(char c) void HTMLParser::EmitText(const char* text) { - Node* textElement = TextElement::Construct(page.allocator, text); + Node* textElement = TextElement::Construct(MemoryManager::pageAllocator, text); if (textElement) { textElement->style = CurrentContext().node->style; @@ -190,7 +191,7 @@ void HTMLParser::EmitNode(Node* node) void HTMLParser::EmitImage(Image* image, int imageWidth, int imageHeight) { - Node* node = ImageNode::Construct(page.allocator); + Node* node = ImageNode::Construct(MemoryManager::pageAllocator); if (node) { node->size.x = imageWidth; @@ -318,7 +319,7 @@ bool HTMLParser::IsWhiteSpace(char c) 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--; @@ -487,7 +488,7 @@ void HTMLParser::ParseChar(char c) { FlushTextBuffer(); - EmitNode(BreakNode::Construct(page.allocator)); + EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); // TODO-refactor //page.BreakTextLine(); break; diff --git a/src/Tags.cpp b/src/Tags.cpp index 049131f..4ddf974 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -21,6 +21,7 @@ #include "Platform.h" #include "Image/Image.h" #include "DataPack.h" +#include "Memory/Memory.h" #include "Nodes/Section.h" #include "Nodes/ImgNode.h" @@ -105,35 +106,35 @@ const HTMLTagHandler* DetermineTag(const char* str) void HrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { int padding = Assets.GetFont(1, FontStyle::Bold)->glyphHeight; - parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding, true)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding, true)); } void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { int fontSize = parser.CurrentContext().node->style.fontSize; int padding = Assets.GetFont(fontSize, FontStyle::Regular)->glyphHeight; - parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding, false, true)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding, false, true)); } void HTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { int fontSize = size >= 3 ? 1 : 2; int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; - parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding)); - parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Bold, fontSize), this); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding)); + parser.PushContext(StyleNode::ConstructFontStyle(MemoryManager::pageAllocator, FontStyle::Bold, fontSize), this); } void HTagHandler::Close(class HTMLParser& parser) const { int fontSize = size >= 3 ? 1 : 2; int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; - parser.EmitNode(BreakNode::Construct(parser.page.allocator, padding)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding)); parser.PopContext(this); } void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(StyleNode::ConstructFontSize(parser.page.allocator, size), this); + parser.PushContext(StyleNode::ConstructFontSize(MemoryManager::pageAllocator, size), this); } void SizeTagHandler::Close(class HTMLParser& parser) const @@ -144,7 +145,7 @@ void SizeTagHandler::Close(class HTMLParser& parser) const void LiTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { // TODO-refactor : add bullet point - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void LiTagHandler::Close(class HTMLParser& parser) const { @@ -159,11 +160,11 @@ void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const { if (!stricmp(attributes.Key(), "href")) { - url = parser.page.allocator.AllocString(attributes.Value()); + url = MemoryManager::pageAllocator.AllocString(attributes.Value()); } } - parser.PushContext(LinkNode::Construct(parser.page.allocator, url), this); + parser.PushContext(LinkNode::Construct(MemoryManager::pageAllocator, url), this); } void ATagHandler::Close(class HTMLParser& parser) const @@ -175,7 +176,7 @@ void BlockTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { // TODO-refactor int lineHeight = Assets.GetFont(1, FontStyle::Regular)->glyphHeight; - parser.PushContext(BlockNode::Construct(parser.page.allocator, leftMarginPadding, useVerticalPadding ? lineHeight >> 1 : 0), this); + parser.PushContext(BlockNode::Construct(MemoryManager::pageAllocator, leftMarginPadding, useVerticalPadding ? lineHeight >> 1 : 0), this); } void BlockTagHandler::Close(class HTMLParser& parser) const { @@ -184,7 +185,7 @@ void BlockTagHandler::Close(class HTMLParser& parser) const void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(SectionElement::Construct(parser.page.allocator, sectionType), this); + parser.PushContext(SectionElement::Construct(MemoryManager::pageAllocator, sectionType), this); } void SectionTagHandler::Close(class HTMLParser& parser) const { @@ -193,7 +194,7 @@ void SectionTagHandler::Close(class HTMLParser& parser) const void StyleTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - Node* styleNode = StyleNode::ConstructFontStyle(parser.page.allocator, style); + Node* styleNode = StyleNode::ConstructFontStyle(MemoryManager::pageAllocator, style); if (styleNode) { parser.PushContext(styleNode, this); @@ -207,18 +208,18 @@ void StyleTagHandler::Close(class HTMLParser& parser) const void AlignmentTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(StyleNode::ConstructAlignment(parser.page.allocator, alignmentType), this); + parser.PushContext(StyleNode::ConstructAlignment(MemoryManager::pageAllocator, alignmentType), this); } void AlignmentTagHandler::Close(class HTMLParser& parser) const { parser.PopContext(this); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - Node* styleNode = StyleNode::Construct(parser.page.allocator); + Node* styleNode = StyleNode::Construct(MemoryManager::pageAllocator); if (styleNode) { StyleNode::Data* data = static_cast(styleNode->data); @@ -269,13 +270,13 @@ void FontTagHandler::Close(class HTMLParser& parser) const void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { // TODO-refactor - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void ListTagHandler::Close(class HTMLParser& parser) const { // TODO-refactor - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } struct HTMLInputTag @@ -297,11 +298,11 @@ void ButtonTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { if (!stricmp(attributes.Key(), "title")) { - title = parser.page.allocator.AllocString(attributes.Value()); + title = MemoryManager::pageAllocator.AllocString(attributes.Value()); } } - parser.EmitNode(ButtonNode::Construct(parser.page.allocator, title, NULL)); + parser.EmitNode(ButtonNode::Construct(MemoryManager::pageAllocator, title, NULL)); } void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -331,11 +332,11 @@ 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()); } } @@ -344,12 +345,12 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const case HTMLInputTag::Submit: if (value) { - parser.EmitNode(ButtonNode::Construct(parser.page.allocator, value, FormNode::OnSubmitButtonPressed)); + parser.EmitNode(ButtonNode::Construct(MemoryManager::pageAllocator, value, FormNode::OnSubmitButtonPressed)); } break; case HTMLInputTag::Text: { - Node* fieldNode = TextFieldNode::Construct(parser.page.allocator, value, FormNode::OnSubmitButtonPressed); + Node* fieldNode = TextFieldNode::Construct(MemoryManager::pageAllocator, value, FormNode::OnSubmitButtonPressed); if (fieldNode && fieldNode->data) { TextFieldNode::Data* fieldData = static_cast(fieldNode->data); @@ -363,9 +364,9 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); - Node* formNode = FormNode::Construct(parser.page.allocator); + Node* formNode = FormNode::Construct(MemoryManager::pageAllocator); parser.PushContext(formNode, this); FormNode::Data* formData = static_cast(formNode->data); @@ -377,7 +378,7 @@ void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { 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")) { @@ -393,7 +394,7 @@ void FormTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void FormTagHandler::Close(HTMLParser& parser) const { parser.PopContext(this); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -439,7 +440,7 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } } - Node* imageNode = ImageNode::Construct(parser.page.allocator); + Node* imageNode = ImageNode::Construct(MemoryManager::pageAllocator); if (imageNode) { ImageNode::Data* data = static_cast(imageNode->data); @@ -447,7 +448,7 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { if (src) { - data->source = parser.page.allocator.AllocString(src); + data->source = MemoryManager::pageAllocator.AllocString(src); } if (width != -1 && height != -1) @@ -455,12 +456,15 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const Platform::video->ScaleImageDimensions(width, height); imageNode->size.x = width; imageNode->size.y = height; + data->image.width = width; + data->image.height = height; } else { imageNode->size.x = 16; imageNode->size.y = 16; - + data->image.width = 0; + data->image.height = 0; } parser.EmitNode(imageNode); @@ -532,8 +536,8 @@ void PreformattedTagHandler::Open(class HTMLParser& parser, char* attributeStr) { // TODO-refactor parser.PushPreFormatted(); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); - parser.PushContext(StyleNode::ConstructFontStyle(parser.page.allocator, FontStyle::Monospace), this); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + parser.PushContext(StyleNode::ConstructFontStyle(MemoryManager::pageAllocator, FontStyle::Monospace), this); } void PreformattedTagHandler::Close(class HTMLParser& parser) const @@ -541,5 +545,5 @@ void PreformattedTagHandler::Close(class HTMLParser& parser) const // TODO-refactor parser.PopPreFormatted(); parser.PopContext(this); - parser.EmitNode(BreakNode::Construct(parser.page.allocator)); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 5ddbeec..b9a2666 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -245,3 +245,15 @@ void WindowsVideoDriver::Paint(HWND hwnd) EndPaint(hwnd, &ps); } +void WindowsVideoDriver::ScaleImageDimensions(int& width, int& height) +{ + height = (height / verticalScale); + + int maxWidth = screenWidth - 16; + + if (width > maxWidth) + { + height = (height * maxWidth) / width; + width = maxWidth; + } +} diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index 82beaa7..b6c6b53 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -28,6 +28,8 @@ class WindowsVideoDriver : public VideoDriver virtual void Shutdown(); virtual void ClearScreen(); + virtual void ScaleImageDimensions(int& width, int& height) override; + void Paint(HWND hwnd); From fe8d04d8f38fa0db358cf58efa6df44e556cc0f9 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 23 Feb 2024 11:13:56 +0000 Subject: [PATCH 28/98] Fixed up DOS 4bpp mode --- project/DOS/Makefile | 10 ++++++++-- src/DOS/Surf4bpp.cpp | 26 ++++++++++++++------------ src/Image/Gif.cpp | 7 +++++-- src/Layout.cpp | 7 ++++++- src/Memory/MemBlock.cpp | 2 +- src/Memory/MemBlock.h | 4 ++-- src/Node.cpp | 25 +++++++++++++++++++++++++ src/Node.h | 2 ++ src/Nodes/ImgNode.cpp | 32 +++++++++++++++++++++++++++++--- src/Page.cpp | 22 +++------------------- 10 files changed, 95 insertions(+), 42 deletions(-) diff --git a/project/DOS/Makefile b/project/DOS/Makefile index bf8ead4..d12c9a1 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Image.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -109,7 +109,13 @@ DataPack.obj: $(SRC_PATH)\DataPack.cpp Decoder.obj: $(SRC_PATH)\Image\Decoder.cpp $(CC) -fo=$@ $(CFLAGS) $< -Image.obj: $(SRC_PATH)\Image\Gif.cpp +Gif.obj: $(SRC_PATH)\Image\Gif.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +MemBlock.obj: $(SRC_PATH)\Memory\MemBlock.cpp + $(CC) -fo=$@ $(CFLAGS) $< + +Memory.obj: $(SRC_PATH)\Memory\Memory.cpp $(CC) -fo=$@ $(CFLAGS) $< CGA.obj: $(SRC_PATH)\DOS\CGA.cpp diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index eb3994a..30132c0 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -4,6 +4,7 @@ #include "../Draw/Surf4bpp.h" #include "../Font.h" #include "../Image/Image.h" +#include "../Memory/MemBlock.h" #define GC_INDEX 0x3ce #define GC_DATA 0x3cf @@ -375,7 +376,7 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { - if (!image->data) + if (!image->lines) return; x += context.drawOffsetX; y += context.drawOffsetY; @@ -383,7 +384,8 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int int srcWidth = image->width; int srcHeight = image->height; int srcPitch = image->pitch; - uint8_t* srcData = image->data; + int srcX = 0; + int srcY = 0; // Calculate the destination width and height to copy, considering clipping region int destWidth = srcWidth; @@ -393,11 +395,11 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int { if (image->bpp == 1) { - srcData += ((context.clipLeft - x) >> 3); + srcX += ((context.clipLeft - x) >> 3); } else { - srcData += (context.clipLeft - x); + srcX += (context.clipLeft - x); } destWidth -= (context.clipLeft - x); x = context.clipLeft; @@ -410,7 +412,7 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int if (y < context.clipTop) { - srcData += (context.clipTop - y) * srcPitch; + srcY += (context.clipTop - y); destHeight -= (context.clipTop - y); y = context.clipTop; } @@ -434,13 +436,13 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* srcRow = srcData + (j * srcPitch); + uint8_t* src = image->lines[srcY + j].Get() + srcX; uint8_t* destRow = lines[y + j] + (x >> 3); uint8_t destMask = 0x80 >> (x & 7); for (int i = 0; i < destWidth; i++) { - uint8_t colour = *srcRow++; + uint8_t colour = *src++; // Set bitmask outp(GC_INDEX, GC_BITMASK); @@ -468,7 +470,7 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* srcRow = srcData + (j * srcPitch); + uint8_t* src = image->lines[srcY + j].Get() + srcX; uint8_t* destRow = lines[y + j] + (x >> 3); int xBits = 7 - (x & 0x7); // Number of bits in the first destination byte to skip @@ -478,18 +480,18 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int // 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]; + destRow[i] = src[i]; } } else { // Copy the first destination byte with proper masking - destRow[0] = (destRow[0] & (0xFF << xBits)) | (srcRow[0] >> (8 - xBits)); + destRow[0] = (destRow[0] & (0xFF << xBits)) | (src[0] >> (8 - xBits)); // Copy the remaining bytes for (int i = 1; i < destByteWidth; i++) { - destRow[i] = (srcRow[i - 1] << xBits) | (srcRow[i] >> (8 - xBits)); + destRow[i] = (src[i - 1] << xBits) | (src[i] >> (8 - xBits)); } } @@ -498,7 +500,7 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int if (lastBit > 0) { int mask = 0xFF << lastBit; - destRow[destByteWidth - 1] = (destRow[destByteWidth - 1] & ~mask) | (srcRow[destByteWidth] & mask); + destRow[destByteWidth - 1] = (destRow[destByteWidth - 1] & ~mask) | (src[destByteWidth] & mask); } } } diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 04ed362..da784df 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -138,7 +138,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) paletteLUT[n] = Platform::video->paletteLUT[RGB332(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2])]; } - //paletteLUT[header.backgroundColour] = Platform::video->colourScheme.pageColour; + paletteLUT[header.backgroundColour] = Platform::video->colourScheme.pageColour; internalState = ParseDataBlock; } @@ -204,7 +204,10 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if (FillStruct(&data, dataLength, &rgb, 3)) { - // TODO: use local colour table for something + if (paletteIndex != header.backgroundColour) + { + paletteLUT[paletteIndex] = Platform::video->paletteLUT[RGB332(rgb[0], rgb[1], rgb[2])]; + } paletteIndex++; if (paletteIndex == localColourTableLength) diff --git a/src/Layout.cpp b/src/Layout.cpp index 6a896d2..ae3b1ae 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -176,7 +176,12 @@ void Layout::PadVertical(int down) void Layout::RecalculateLayout() { Reset(); - + + //for (Node* node = page.GetRootNode(); node; node = node->GetNextInTree()) + //{ + // node->Handler().GenerateLayout(*this, node); + //} + Node* node = page.GetRootNode(); bool checkChildren = true; diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index 1f4ac49..2a9dc2a 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -2,7 +2,7 @@ #include "LinAlloc.h" #include "Memory.h" -void* MemBlockHandle::Get() +void* MemBlockHandle::GetPtr() { switch (type) { diff --git a/src/Memory/MemBlock.h b/src/Memory/MemBlock.h index 52203f4..527d334 100644 --- a/src/Memory/MemBlock.h +++ b/src/Memory/MemBlock.h @@ -22,10 +22,10 @@ struct MemBlockHandle Type type; MemBlockHandle() : type(Unallocated), allocatedSize(0) {} - void* Get(); + void* GetPtr(); template - inline T* Get() { return (T*)Get(); } + inline T* Get() { return (T*)GetPtr(); } void Commit(); bool IsAllocated() { return type != Unallocated; } diff --git a/src/Node.cpp b/src/Node.cpp index 28acb54..3ec31be 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -228,3 +228,28 @@ void Node::Redraw() Platform::input->ShowMouse(); } + +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; +} diff --git a/src/Node.h b/src/Node.h index 2009e91..42382dc 100644 --- a/src/Node.h +++ b/src/Node.h @@ -78,6 +78,8 @@ class Node Node* FindParentOfType(Node::Type searchType); void Redraw(); + Node* GetNextInTree(); + Type type; ElementStyle style; diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 3aec4f0..d8404df 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -54,7 +54,7 @@ void ImageNode::GenerateLayout(Layout& layout, Node* node) void ImageNode::LoadContent(Node* node, LoadTask& loadTask) { ImageNode::Data* data = static_cast(node->data); - if (data && data->source) + if (data && data->source && !data->image.lines) { loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); ImageDecoder::Create(ImageDecoder::Gif); @@ -65,12 +65,38 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) { ImageDecoder* decoder = ImageDecoder::Get(); - ImageNode::Data* data = static_cast(node->data); decoder->Process((uint8_t*) buffer, count); if (decoder->GetState() == ImageDecoder::Success) { - if (node->size.x != data->image.width || node->size.y != data->image.height) + ImageNode::Data* data = static_cast(node->data); + + // Loop through image nodes in case this image is used multiple times + bool needsLayoutRefresh = false; + 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)) + { + if (n != node) + { + otherData->image = data->image; + } + + if (n->size.x != data->image.width || n->size.y != data->image.height) + { + n->size.x = data->image.width; + n->size.y = data->image.height; + needsLayoutRefresh = true; + } + } + } + } + + if (needsLayoutRefresh) { node->size.x = data->image.width; node->size.y = data->image.height; diff --git a/src/Page.cpp b/src/Page.cpp index bd05477..2684549 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -176,31 +176,15 @@ int Page::GetPageWidth() Node* Page::ProcessNextLoadTask(Node* lastNode, LoadTask& loadTask) { - Node* node = lastNode; - bool checkChildren = true; - - while (node) + Node* node; + for (node = lastNode->GetNextInTree(); node; node = node->GetNextInTree()) { - if (checkChildren && node->firstChild) - { - node = node->firstChild; - } - else if (node->next) - { - node = node->next; - checkChildren = true; - } - else - { - node = node->parent; - checkChildren = false; - } - if (node && node->type == Node::Image) { node->Handler().LoadContent(node, loadTask); return node; } } + return nullptr; } From cf7caf101fbb6d76844f947f7f1d037b7440f961 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sat, 24 Feb 2024 20:03:36 +0000 Subject: [PATCH 29/98] Refactored video driver system and added video mode picker on startup --- project/DOS/DOS.vcxproj | 3 + project/DOS/Makefile | 13 +- project/Windows/Windows.vcxproj | 3 + src/Colour.cpp | 26 +++ src/Colour.h | 8 + src/DOS/BIOSVid.cpp | 150 +++++++++++++++++ src/DOS/BIOSVid.h | 40 +++++ src/DOS/CGA.cpp | 8 +- src/DOS/CGA.h | 2 +- src/DOS/EGA.cpp | 4 +- src/DOS/EGA.h | 2 +- src/DOS/Hercules.cpp | 3 +- src/DOS/Hercules.h | 2 +- src/DOS/Olivetti.cpp | 2 +- src/DOS/Olivetti.h | 2 +- src/DOS/Platform.cpp | 103 ++++-------- src/DOS/Surf4bpp.cpp | 17 +- src/DataPack.cpp | 15 ++ src/DataPack.h | 10 ++ src/Draw/Surf1bpp.cpp | 68 ++++---- src/Draw/Surf1bpp.h | 2 - src/Draw/Surf4bpp.h | 2 - src/Draw/Surf8bpp.cpp | 50 +++++- src/Draw/Surf8bpp.h | 2 - src/Draw/Surface.h | 2 + src/Image/Decoder.cpp | 27 +++ src/Image/Decoder.h | 4 + src/Image/Gif.cpp | 285 ++++++++++++++++++++++++++++---- src/Image/Gif.h | 10 ++ src/Microweb.cpp | 5 +- src/Platform.h | 5 +- src/VidModes.cpp | 53 ++++++ src/VidModes.h | 26 +++ src/Windows/Platform.cpp | 29 ++-- src/Windows/WinVid.cpp | 55 +++--- src/Windows/WinVid.h | 4 +- 36 files changed, 830 insertions(+), 212 deletions(-) create mode 100644 src/Colour.cpp create mode 100644 src/DOS/BIOSVid.cpp create mode 100644 src/DOS/BIOSVid.h create mode 100644 src/VidModes.cpp create mode 100644 src/VidModes.h diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 4c3f1c7..2440e2d 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -96,7 +96,9 @@ + + @@ -124,6 +126,7 @@ + diff --git a/project/DOS/Makefile b/project/DOS/Makefile index d12c9a1..2c8299c 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj CGA.obj EGA.obj Hercules.obj Olivetti.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -118,16 +118,16 @@ MemBlock.obj: $(SRC_PATH)\Memory\MemBlock.cpp Memory.obj: $(SRC_PATH)\Memory\Memory.cpp $(CC) -fo=$@ $(CFLAGS) $< -CGA.obj: $(SRC_PATH)\DOS\CGA.cpp +VidModes.obj: $(SRC_PATH)\VidModes.cpp $(CC) -fo=$@ $(CFLAGS) $< -Hercules.obj: $(SRC_PATH)\DOS\Hercules.cpp +Colour.obj: $(SRC_PATH)\Colour.cpp $(CC) -fo=$@ $(CFLAGS) $< -Olivetti.obj: $(SRC_PATH)\DOS\Olivetti.cpp +BIOSVid.obj: $(SRC_PATH)\DOS\BIOSVid.cpp $(CC) -fo=$@ $(CFLAGS) $< -EGA.obj: $(SRC_PATH)\DOS\EGA.cpp +Hercules.obj: $(SRC_PATH)\DOS\Hercules.cpp $(CC) -fo=$@ $(CFLAGS) $< DOSInput.obj: $(SRC_PATH)\DOS\DOSInput.cpp @@ -142,6 +142,9 @@ Surf1bpp.obj: $(SRC_PATH)\Draw\Surf1bpp.cpp Surf4bpp.obj: $(SRC_PATH)\DOS\Surf4bpp.cpp $(CC) -fo=$@ $(CFLAGS) $< +Surf8bpp.obj: $(SRC_PATH)\Draw\Surf8bpp.cpp + $(CC) -fo=$@ $(CFLAGS) $< + clean: .symbolic del *.obj del $(bin) \ No newline at end of file diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 6c41066..5e42fe5 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -140,6 +140,7 @@ + @@ -169,6 +170,7 @@ + @@ -212,6 +214,7 @@ + diff --git a/src/Colour.cpp b/src/Colour.cpp new file mode 100644 index 0000000..22df5b2 --- /dev/null +++ b/src/Colour.cpp @@ -0,0 +1,26 @@ +#include "Colour.h" +#include "Palettes.inc" + +ColourScheme monochromeColourScheme = +{ + // Page + 1, + // Text + 0, + // Link + 0, + // Button + 1 +}; + +ColourScheme egaColourScheme = +{ + // Page + 15, + // Text + 0, + // Link + 1, + // Button + 7 +}; diff --git a/src/Colour.h b/src/Colour.h index 8c4da71..34ec589 100644 --- a/src/Colour.h +++ b/src/Colour.h @@ -4,6 +4,9 @@ #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 TRANSPARENT_COLOUR_VALUE 0xff struct ColourScheme { @@ -19,4 +22,9 @@ struct NamedColour uint8_t colour; }; +extern ColourScheme monochromeColourScheme; +extern ColourScheme egaColourScheme; + +extern uint8_t cgaPaletteLUT[]; + #endif diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp new file mode 100644 index 0000000..4639254 --- /dev/null +++ b/src/DOS/BIOSVid.cpp @@ -0,0 +1,150 @@ +// +// 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/Surf4bpp.h" +#include "../Draw/Surf8bpp.h" +#include "../VidModes.h" + +BIOSVideoDriver::BIOSVideoDriver() +{ +} + +void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) +{ + videoModeInfo = inVideoModeInfo; + startingScreenMode = GetScreenMode(); + + screenWidth = videoModeInfo->screenWidth; + screenHeight = videoModeInfo->screenHeight; + + SetScreenMode(videoModeInfo->biosVideoMode); + + Assets.LoadPreset(videoModeInfo->dataPackIndex); + + int screenPitch = 0; + + if (videoModeInfo->bpp == 1) + { + drawSurface = new DrawSurface_1BPP(screenWidth, screenHeight); + screenPitch = screenWidth / 8; + colourScheme = monochromeColourScheme; + paletteLUT = nullptr; + } + else if (videoModeInfo->bpp == 4) + { + drawSurface = new DrawSurface_4BPP(screenWidth, screenHeight); + screenPitch = screenWidth / 8; + colourScheme = egaColourScheme; + paletteLUT = cgaPaletteLUT; + } + else if (videoModeInfo->bpp == 8) + { + drawSurface = new DrawSurface_8BPP(screenWidth, screenHeight); + screenPitch = screenWidth; + colourScheme = egaColourScheme; + paletteLUT = cgaPaletteLUT; + } + + if (videoModeInfo->vramPage3) + { + // 4 page interlaced memory layout + int offset = 0; + for (int y = 0; y < screenHeight; y += 4) + { + drawSurface->lines[y] = (uint8_t*) MK_FP(videoModeInfo->vramPage1, offset); + drawSurface->lines[y + 1] = (uint8_t*) MK_FP(videoModeInfo->vramPage2, offset); + drawSurface->lines[y + 2] = (uint8_t*) MK_FP(videoModeInfo->vramPage3, offset); + drawSurface->lines[y + 3] = (uint8_t*) MK_FP(videoModeInfo->vramPage4, offset); + offset += screenPitch; + } + } + else if (videoModeInfo->vramPage2) + { + // 2 page interlaced memory layout + int offset = 0; + for (int y = 0; y < screenHeight; y += 2) + { + drawSurface->lines[y] = (uint8_t*)MK_FP(videoModeInfo->vramPage1, offset); + drawSurface->lines[y + 1] = (uint8_t*)MK_FP(videoModeInfo->vramPage2, offset); + offset += screenPitch; + } + } + else + { + // No interlacing + int offset = 0; + for (int y = 0; y < screenHeight; y++) + { + drawSurface->lines[y] = (uint8_t*)MK_FP(videoModeInfo->vramPage1, offset); + offset += screenPitch; + } + } + + //DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); + //for (int y = 0; y < screenHeight; y += 2) + //{ + // drawSurface1BPP->lines[y] = (CGA_BASE_VRAM_ADDRESS) + (40 * y); + // drawSurface1BPP->lines[y + 1] = (CGA_PAGE2_VRAM_ADDRESS) + (40 * y); + //} + //drawSurface = drawSurface1BPP; + // + //colourScheme.pageColour = 1; + //colourScheme.linkColour = 0; + //colourScheme.textColour = 0; + //colourScheme.buttonColour = 1; + +} + +void BIOSVideoDriver::Shutdown() +{ + SetScreenMode(startingScreenMode); +} + +int BIOSVideoDriver::GetScreenMode() +{ + union REGS inreg, outreg; + inreg.h.ah = 0xf; + + int86(0x10, &inreg, &outreg); + + return (int)outreg.h.al; +} + +void BIOSVideoDriver::SetScreenMode(int screenMode) +{ + union REGS inreg, outreg; + inreg.h.ah = 0; + inreg.h.al = (unsigned char)screenMode; + + int86(0x10, &inreg, &outreg); +} + + +void BIOSVideoDriver::ScaleImageDimensions(int& width, int& height) +{ + height /= videoModeInfo->aspectRatio; + // Scale to 4:3 + //height = (height * 5) / 12; +} diff --git a/src/DOS/BIOSVid.h b/src/DOS/BIOSVid.h new file mode 100644 index 0000000..ce077ad --- /dev/null +++ b/src/DOS/BIOSVid.h @@ -0,0 +1,40 @@ +// +// 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(); + + virtual void ScaleImageDimensions(int& width, int& height); + +private: + int GetScreenMode(); + void SetScreenMode(int screenMode); + + int startingScreenMode; + VideoModeInfo* videoModeInfo; +}; + +#endif diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp index 861b5c8..bf0f458 100644 --- a/src/DOS/CGA.cpp +++ b/src/DOS/CGA.cpp @@ -35,7 +35,7 @@ CGADriver::CGADriver() screenHeight = 200; } -void CGADriver::Init() +void CGADriver::Init(VideoModeInfo* videoMode) { startingScreenMode = GetScreenMode(); SetScreenMode(6); @@ -49,6 +49,12 @@ void CGADriver::Init() drawSurface1BPP->lines[y + 1] = (CGA_PAGE2_VRAM_ADDRESS) + (40 * y); } drawSurface = drawSurface1BPP; + + colourScheme.pageColour = 1; + colourScheme.linkColour = 0; + colourScheme.textColour = 0; + colourScheme.buttonColour = 1; + } void CGADriver::Shutdown() diff --git a/src/DOS/CGA.h b/src/DOS/CGA.h index aa3435f..0c0a976 100644 --- a/src/DOS/CGA.h +++ b/src/DOS/CGA.h @@ -24,7 +24,7 @@ class CGADriver : public VideoDriver public: CGADriver(); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); virtual void ScaleImageDimensions(int& width, int& height); diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp index 39bc0b5..9948c61 100644 --- a/src/DOS/EGA.cpp +++ b/src/DOS/EGA.cpp @@ -24,7 +24,7 @@ #include "../Interface.h" #include "../Draw/Surf1bpp.h" #include "../Draw/Surf4bpp.h" -#include "../Palettes.inc" +#include "../Colour.h" #define EGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) #define BYTES_PER_LINE 80 @@ -43,7 +43,7 @@ void EGADriver::SetupVars(const char* inAssetPackToUse, int inScreenMode, int in paletteLUT = cgaPaletteLUT; } -void EGADriver::Init() +void EGADriver::Init(VideoModeInfo* videoMode) { startingScreenMode = GetScreenMode(); SetScreenMode(screenModeToUse); diff --git a/src/DOS/EGA.h b/src/DOS/EGA.h index 05c886d..9530243 100644 --- a/src/DOS/EGA.h +++ b/src/DOS/EGA.h @@ -24,7 +24,7 @@ class EGADriver : public VideoDriver public: EGADriver(); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); virtual void ScaleImageDimensions(int& width, int& height); diff --git a/src/DOS/Hercules.cpp b/src/DOS/Hercules.cpp index 72972fc..9dc4066 100644 --- a/src/DOS/Hercules.cpp +++ b/src/DOS/Hercules.cpp @@ -41,7 +41,7 @@ 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* videoMode) { SetGraphicsMode(); @@ -57,6 +57,7 @@ void HerculesDriver::Init() drawSurface1BPP->lines[y + 3] = (BASE_VRAM_ADDRESS) + offset + 0x6000; } drawSurface = drawSurface1BPP; + colourScheme = monochromeColourScheme; } void HerculesDriver::Shutdown() diff --git a/src/DOS/Hercules.h b/src/DOS/Hercules.h index e9e36b5..41549cb 100644 --- a/src/DOS/Hercules.h +++ b/src/DOS/Hercules.h @@ -24,7 +24,7 @@ class HerculesDriver : public VideoDriver public: HerculesDriver(); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); virtual void ScaleImageDimensions(int& width, int& height); diff --git a/src/DOS/Olivetti.cpp b/src/DOS/Olivetti.cpp index 293399e..9d48de6 100644 --- a/src/DOS/Olivetti.cpp +++ b/src/DOS/Olivetti.cpp @@ -35,7 +35,7 @@ OlivettiDriver::OlivettiDriver(int inScreenModeToUse) screenHeight = 400; } -void OlivettiDriver::Init() +void OlivettiDriver::Init(VideoModeInfo* videoMode) { startingScreenMode = GetScreenMode(); SetScreenMode(screenModeToUse); diff --git a/src/DOS/Olivetti.h b/src/DOS/Olivetti.h index 3dc8681..5551b60 100644 --- a/src/DOS/Olivetti.h +++ b/src/DOS/Olivetti.h @@ -24,7 +24,7 @@ class OlivettiDriver : public VideoDriver public: OlivettiDriver(int screenModeToUse); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); virtual void ScaleImageDimensions(int& width, int& height); diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 77b9fd0..cf66449 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -14,14 +14,9 @@ #include #include "../Platform.h" -#ifdef HP95LX -#include "HP95LX.h" -#else -#include "CGA.h" -#include "EGA.h" #include "Hercules.h" -#include "Olivetti.h" -#endif +#include "../VidModes.h" +#include "BIOSVid.h" #include "DOSInput.h" #include "DOSNet.h" @@ -93,32 +88,30 @@ bool DetectHercules(); modify [ax cx dx] \ value [al] -static void AutoDetectVideoDriver() +static int AutoDetectVideoMode() { union REGS inreg, outreg; -#ifdef HP95LX + const int HP95LX = 11; + const int Hercules = 8; + const int CGA = 0; + const int EGA = 4; + const int VGA = 6; + + // 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; + return HP95LX; } - 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 +121,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,70 +136,44 @@ static void AutoDetectVideoDriver() // Attempt to detect Hercules if (isMono && DetectHercules()) { - Platform::video = new HerculesDriver(); - printf("Detected Hercules\n"); - return; + return Hercules; } - if (isMono) - { - fprintf(stderr, "MDA cards not supported!\n"); - } - else - { - fprintf(stderr, "Could not detect video card!\n"); - } - exit(1); -#endif + return CGA; } -void Platform::Init(int argc, char* argv[]) +bool Platform::Init(int argc, char* argv[]) { bool inverse = false; video = NULL; for (int n = 1; n < argc; n++) { -#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(); - } -#endif if (!stricmp(argv[n], "-i")) { inverse = true; } } - if (!video) + int suggestedMode = AutoDetectVideoMode(); + VideoModeInfo* videoMode = ShowVideoModePicker(suggestedMode); + if (!videoMode) { - AutoDetectVideoDriver(); + return false; } - network->Init(); - video->Init(); + if (videoMode->biosVideoMode == HERCULES_MODE) + { + video = new HerculesDriver(); + } + else + { + video = new BIOSVideoDriver(); + } + + video->Init(videoMode); video->drawSurface->Clear(); + network->Init(); input->Init(); if (inverse) @@ -218,6 +181,8 @@ void Platform::Init(int argc, char* argv[]) // TODO-refactor //video->InvertScreen(); } + + return true; } void Platform::Shutdown() diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index 30132c0..efe5699 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -5,6 +5,7 @@ #include "../Font.h" #include "../Image/Image.h" #include "../Memory/MemBlock.h" +#include "../Colour.h" #define GC_INDEX 0x3ce #define GC_DATA 0x3cf @@ -46,6 +47,7 @@ DrawSurface_4BPP::DrawSurface_4BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; + bpp = 4; } void DrawSurface_4BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) @@ -444,14 +446,17 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int { uint8_t colour = *src++; - // Set bitmask - outp(GC_INDEX, GC_BITMASK); - outp(GC_DATA, destMask); + if (colour != TRANSPARENT_COLOUR_VALUE) + { + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, destMask); - outp(GC_INDEX, GC_SET_RESET); - outp(GC_DATA, colour); + outp(GC_INDEX, GC_SET_RESET); + outp(GC_DATA, colour); - *destRow |= 0xff; + *destRow |= 0xff; + } destMask >>= 1; if (!destMask) diff --git a/src/DataPack.cpp b/src/DataPack.cpp index 7aa49af..f8bcaa4 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -8,6 +8,20 @@ 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"); @@ -146,3 +160,4 @@ void* DataPack::LoadAsset(FILE* fs, DataPackHeader& header, const char* entryNam return buffer; } + diff --git a/src/DataPack.h b/src/DataPack.h index 2bb3792..2a6f10d 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -52,6 +52,14 @@ struct DataPackHeader struct DataPack { + enum Preset + { + CGA, + EGA, + Default, + Lowres + }; + MouseCursorData* pointerCursor; MouseCursorData* linkCursor; MouseCursorData* textSelectCursor; @@ -62,6 +70,7 @@ struct DataPack Font* monoFonts[NUM_FONT_SIZES]; Font* boldMonoFonts[NUM_FONT_SIZES]; + bool LoadPreset(Preset preset); bool Load(const char* path); Font* GetFont(int fontSize, FontStyle::Type fontStyle); MouseCursorData* GetMouseCursorData(MouseCursor::Type type); @@ -70,6 +79,7 @@ struct DataPack 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; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 259259c..0943ca7 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -2,11 +2,13 @@ #include "Surf1bpp.h" #include "../Font.h" #include "../Image/Image.h" +#include "../Memory/MemBlock.h" DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; + bpp = 1; } void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) @@ -351,15 +353,17 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { -#if 0 + if (!image->lines || image->bpp != 1) + return; + x += context.drawOffsetX; y += context.drawOffsetY; int srcWidth = image->width; int srcHeight = image->height; - int srcPitch = image->pitch; int startX = 0; - uint8_t* srcData = image->data; + int srcX = 0; + int srcY = 0; // Calculate the destination width and height to copy, considering clipping region int destWidth = srcWidth; @@ -367,7 +371,7 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int if (x < context.clipLeft) { - srcData += ((context.clipLeft - x) >> 3); + srcX += (context.clipLeft - x); destWidth -= (context.clipLeft - x); x = context.clipLeft; } @@ -379,7 +383,7 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int if (y < context.clipTop) { - srcData += (context.clipTop - y) * srcPitch; + srcY += (context.clipTop - y); destHeight -= (context.clipTop - y); y = context.clipTop; } @@ -395,45 +399,45 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int } 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 +// int destByteWidth = (destWidth + 7) >> 3; // Calculate the width of the destination image in bytes + int destByteWidth = (destWidth) >> 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 + uint8_t* src = image->lines[j + srcY].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; - // Handle the first destination byte separately with proper masking - if (xBits == 0) + for (int i = 0; i < destWidth; i++) { - // If x is on a byte boundary, copy the whole byte from the source - for (int i = 0; i < destByteWidth; i++) + if (srcBuffer & srcMask) { - destRow[i] = srcRow[i]; + destBuffer |= destMask; } - } - 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++) + else { - destRow[i] = (srcRow[i - 1] << xBits) | (srcRow[i] >> (8 - xBits)); + destBuffer &= ~destMask; + } + srcMask >>= 1; + if (!srcMask) + { + srcMask = 0x80; + srcBuffer = *src++; + } + destMask >>= 1; + if (!destMask) + { + *dest++ = destBuffer; + destBuffer = *dest; + destMask = 0x80; } } - - // 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); - } + *dest = destBuffer; } -#endif } diff --git a/src/Draw/Surf1bpp.h b/src/Draw/Surf1bpp.h index d2799ee..a9830d3 100644 --- a/src/Draw/Surf1bpp.h +++ b/src/Draw/Surf1bpp.h @@ -17,8 +17,6 @@ class DrawSurface_1BPP : public DrawSurface 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); - - uint8_t** lines; }; #endif diff --git a/src/Draw/Surf4bpp.h b/src/Draw/Surf4bpp.h index 8128b7e..2e24bad 100644 --- a/src/Draw/Surf4bpp.h +++ b/src/Draw/Surf4bpp.h @@ -17,8 +17,6 @@ class DrawSurface_4BPP : public DrawSurface 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); - - uint8_t** lines; }; #endif diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index c1b798a..46c933c 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -3,11 +3,13 @@ #include "../Font.h" #include "../Image/Image.h" #include "../Memory/MemBlock.h" +#include "../Colour.h" DrawSurface_8BPP::DrawSurface_8BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; + bpp = 8; } void DrawSurface_8BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) @@ -241,14 +243,7 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int if (x < context.clipLeft) { - if (image->bpp == 1) - { - srcX += ((context.clipLeft - x) >> 3); - } - else - { - srcX += (context.clipLeft - x); - } + srcX += (context.clipLeft - x); destWidth -= (context.clipLeft - x); x = context.clipLeft; } @@ -285,7 +280,44 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int for (int i = 0; i < destWidth; i++) { - *destRow++ = *src++; + 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++) + { + uint8_t* src = image->lines[srcY + j].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++; + } } } } diff --git a/src/Draw/Surf8bpp.h b/src/Draw/Surf8bpp.h index b2e2ed0..d0faacd 100644 --- a/src/Draw/Surf8bpp.h +++ b/src/Draw/Surf8bpp.h @@ -17,8 +17,6 @@ class DrawSurface_8BPP : public DrawSurface 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); - - uint8_t** lines; }; #endif diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index d291452..b74abf2 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -32,7 +32,9 @@ class DrawSurface 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; + uint8_t** lines; int width, height; + uint8_t bpp; }; #endif diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 1c37e6c..2707645 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -10,6 +10,33 @@ union char buffer[1]; } ImageDecoderUnion; +const int8_t ImageDecoder::colourDitherMatrix[16] = +{ + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 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 +}; ImageDecoder* ImageDecoder::Get() { diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index 68f63cc..a0378f1 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -34,6 +34,10 @@ class ImageDecoder protected: Image* outputImage; + + static const uint8_t greyDitherMatrix[256]; + static const int8_t colourDitherMatrix[16]; + }; diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index da784df..e81424a 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -5,6 +5,7 @@ #include "../Memory/Memory.h" #include "../Page.h" #include "../App.h" +#include "../Draw/Surface.h" #include #ifdef _WIN32 @@ -16,6 +17,7 @@ #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() : state(ImageDecoder::Stopped) @@ -26,11 +28,12 @@ void GifDecoder::Begin(Image* image) { state = ImageDecoder::Decoding; outputImage = image; - outputImage->bpp = 8; + outputImage->bpp = Platform::video->drawSurface->bpp == 1 ? 1 : 8; structFillPosition = 0; internalState = ParseHeader; lineBufferSkipCount = 0; + transparentColourIndex = -1; } void GifDecoder::Process(uint8_t* data, size_t dataLength) @@ -71,13 +74,18 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) outputImage->width = width; outputImage->height = height; - outputImage->pitch = width; } - //outputImage->data = (uint8_t*) allocator.Alloc((header.width * header.height) >> 3); - //outputImage->data = (uint8_t*) allocator.Alloc((header.width * header.height) * 3); - + + if (outputImage->bpp == 1) + { + outputImage->pitch = (outputImage->width + 7) / 8; + } + else // 8bpp + { + outputImage->pitch = outputImage->width; + } + outputImage->lines = (MemBlockHandle*)MemoryManager::pageAllocator.Alloc(sizeof(MemBlockHandle) * outputImage->height); - //outputImage->data = (uint8_t*)allocator.Alloc(outputImage->pitch * header.height); if(!outputImage->lines) { // Allocation error @@ -88,7 +96,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) for (int j = 0; j < outputImage->height; j++) { - outputImage->lines[j] = MemoryManager::pageBlockAllocator.Allocate(outputImage->width); + outputImage->lines[j] = MemoryManager::pageBlockAllocator.Allocate(outputImage->pitch); if (!outputImage->lines[j].IsAllocated()) { // Allocation error @@ -96,6 +104,11 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) state = ImageDecoder::Error; return; } + void* pixels = outputImage->lines[j].GetPtr(); + if (pixels) + { + memset(pixels, TRANSPARENT_COLOUR_VALUE, outputImage->pitch); + } } backgroundColour = header.backgroundColour; @@ -124,7 +137,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { //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); + //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]; @@ -133,12 +146,25 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if(paletteIndex == paletteSize) { - for (int n = 0; n < paletteSize; n++) + if (outputImage->bpp == 8) { - paletteLUT[n] = Platform::video->paletteLUT[RGB332(palette[n * 3], palette[n * 3 + 1], palette[n * 3 + 2])]; + 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]); + } } - paletteLUT[header.backgroundColour] = Platform::video->colourScheme.pageColour; + if (transparentColourIndex >= 0) + { + paletteLUT[transparentColourIndex] = TRANSPARENT_COLOUR_VALUE; + } internalState = ParseDataBlock; } @@ -204,14 +230,34 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if (FillStruct(&data, dataLength, &rgb, 3)) { - if (paletteIndex != header.backgroundColour) - { - paletteLUT[paletteIndex] = Platform::video->paletteLUT[RGB332(rgb[0], rgb[1], rgb[2])]; - } + 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; } } @@ -397,7 +443,15 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if(FillStruct(&data, dataLength, &extensionHeader, sizeof(ExtensionHeader))) { DEBUG_MESSAGE("Extension: %x Size: %d\n", (int) extensionHeader.code, (int) extensionHeader.size); - internalState = ParseExtensionContents; + + if (extensionHeader.code == BLOCK_TYPE_GRAPHIC_CONTROL_EXTENSION) + { + internalState = ParseGraphicControlExtension; + } + else + { + internalState = ParseExtensionContents; + } } } break; @@ -410,6 +464,20 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) } } 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: { @@ -584,29 +652,188 @@ void GifDecoder::EmitLine(int y) { uint8_t* output = outputImage->lines[y].Get(); - if (outputImage->width == lineBufferSize) + if (outputImage->bpp == 8) { - for (int i = 0; i < lineBufferSize; i++) + bool useColourDithering = false; + + if (useColourDithering) { - output[i] = paletteLUT[lineBuffer[i]]; + const int8_t* ditherPattern = colourDitherMatrix + 4 * (y & 3); + int ditherIndex = 0; + + if (outputImage->width == lineBufferSize) + { + for (int i = 0; i < lineBufferSize; i++) + { + int 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; + int green = palette[index + 1] + offset; + if (green > 255) + green = 255; + int blue = palette[index + 2] + offset; + if (blue > 255) + blue = 255; + + 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[i] == transparentColourIndex) + { + output[i] = TRANSPARENT_COLOUR_VALUE; + } + else + { + int index = lineBuffer[x] * 3; + int red = palette[index] + offset; + if (red > 255) + red = 255; + int green = palette[index + 1] + offset; + if (green > 255) + green = 255; + int blue = palette[index + 2] + offset; + if (blue > 255) + blue = 255; + + 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 { - int dy = lineBufferSize; - int dx = outputImage->width; - int D = 2 * dy - dx; - int y = 0; + const uint8_t* ditherPattern = greyDitherMatrix + 16 * (y & 15); + uint8_t buffer = 0; + uint8_t mask = 0x80; + int ditherIndex = 0; - for (int i = 0; i < outputImage->width; i++) + if (outputImage->width == lineBufferSize) { - output[i] = paletteLUT[lineBuffer[y]]; - while (D > 0) + 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) { - y++; - D -= 2 * dx; + *output = buffer; } - D += 2 * dy; } } diff --git a/src/Image/Gif.h b/src/Image/Gif.h index bdbb2d9..f307a8b 100644 --- a/src/Image/Gif.h +++ b/src/Image/Gif.h @@ -52,6 +52,7 @@ class GifDecoder : public ImageDecoder ParseExtensionContents, ParseExtensionSubBlockSize, ParseExtensionSubBlock, + ParseGraphicControlExtension }; #pragma pack(push, 1) @@ -84,6 +85,13 @@ class GifDecoder : public ImageDecoder int32_t prev; int len; }; + + struct GraphicControlExtension + { + uint8_t packedFields; + uint16_t delayTime; + uint8_t transparentColourIndex; + }; #pragma pack(pop) ImageDecoder::State state; @@ -93,6 +101,7 @@ class GifDecoder : public ImageDecoder uint8_t paletteLUT[256]; // GIF palette colour to video mode palette colour int paletteSize; uint8_t backgroundColour; + int transparentColourIndex; uint8_t lzwCodeSize; size_t structFillPosition; @@ -143,6 +152,7 @@ class GifDecoder : public ImageDecoder // ParseExtension temporary vars ExtensionHeader extensionHeader; uint8_t extensionSubBlockSize; + GraphicControlExtension graphicControlExtension; }; //}; }; diff --git a/src/Microweb.cpp b/src/Microweb.cpp index f34202f..6d596a5 100644 --- a/src/Microweb.cpp +++ b/src/Microweb.cpp @@ -20,7 +20,10 @@ int main(int argc, char* argv[]) { - Platform::Init(argc, argv); + if (!Platform::Init(argc, argv)) + { + return 0; + } App* app = new App(); diff --git a/src/Platform.h b/src/Platform.h index eb28ce8..4035108 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -22,11 +22,12 @@ 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 ScaleImageDimensions(int& width, int& height) {} @@ -92,7 +93,7 @@ class InputDriver class Platform { public: - static void Init(int argc, char* argv[]); + static bool Init(int argc, char* argv[]); static void Shutdown(); static void Update(); diff --git a/src/VidModes.cpp b/src/VidModes.cpp new file mode 100644 index 0000000..87b56b0 --- /dev/null +++ b/src/VidModes.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "VidModes.h" + +VideoModeInfo VideoModeList[] = +{ + // name mode width height bpp aspect data pack vram1 vram2 vram3 vram4 + { "640x200 monochrome (CGA)", 6, 640, 200, 1, 2.4f, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 1.0f, DataPack::Default, 0xb800, 0xba00 }, + { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 2.4f, DataPack::CGA, 0xa000, }, + { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 1.37f, DataPack::EGA, 0xa000, }, + { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 1.37f, DataPack::EGA, 0xa000, }, + { "640x480 monochrome (VGA)", 0x11, 640, 480, 1, 1.0f, DataPack::Default, 0xa000, }, + { "640x480 16 colours (VGA)", 0x12, 640, 480, 4, 1.0f, DataPack::Default, 0xa000, }, + { "320x200 256 colours (VGA)", 0x13, 320, 200, 8, 1.2f, DataPack::Lowres, 0xa000, }, + { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, 1, 1.55f, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, + { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, 1, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, 1, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, 1, 1.0f, DataPack::Lowres, 0xb000, }, + { 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..83f7508 --- /dev/null +++ b/src/VidModes.h @@ -0,0 +1,26 @@ +#ifndef _VIDMODES_H_ +#define _VIDMODES_H_ + +#include +#include "Datapack.h" + +#define HERCULES_MODE 0 + +struct VideoModeInfo +{ + const char* name; + int biosVideoMode; + int screenWidth; + int screenHeight; + uint8_t bpp; + float aspectRatio; + DataPack::Preset dataPackIndex : 8; + uint16_t vramPage1; + uint16_t vramPage2; + uint16_t vramPage3; + uint16_t vramPage4; +}; + +VideoModeInfo* ShowVideoModePicker(int defaultSelection); + +#endif diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index c6de429..3f7d9c6 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -18,6 +18,7 @@ #include "WinInput.h" #include "WinNet.h" #include "../Draw/Surface.h" +#include "../VidModes.h" WindowsVideoDriver winVid; WindowsNetworkDriver winNetworkDriver; @@ -30,31 +31,21 @@ 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, (int)(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(6); + if (!videoMode) + { + return false; + } network->Init(); - video->Init(); + video->Init(videoMode); video->drawSurface->Clear(); input->Init(); input->ShowMouse(); + + return true; } void Platform::Shutdown() diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index b9a2666..f397e0c 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -20,28 +20,19 @@ #include "../DataPack.h" #include "../Draw/Surf1bpp.h" #include "../Draw/Surf8bpp.h" -#include "../Palettes.inc" +#include "../VidModes.h" //#define SCREEN_WIDTH 800 //#define SCREEN_HEIGHT 600 #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 +#define USE_COLOUR 0 extern HWND hWnd; +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); WindowsVideoDriver::WindowsVideoDriver() { - screenWidth = SCREEN_WIDTH; - screenHeight = SCREEN_HEIGHT; - foregroundColour = RGB(0, 0, 0); - backgroundColour = RGB(255, 255, 255); - verticalScale = ((screenWidth * 3.0f) / 4.0f) / screenHeight; - //verticalScale = 1.0f; - - Assets.Load("Default.dat"); -// Assets.Load("EGA.dat"); - //Assets.Load("Lowres.dat"); -// Assets.Load("CGA.dat"); } const RGBQUAD monoPalette[] = @@ -70,12 +61,38 @@ const RGBQUAD cgaPalette[] = { 0xFF, 0xFF, 0xFF }, // Entry 15 - White }; -void WindowsVideoDriver::Init() +void WindowsVideoDriver::Init(VideoModeInfo* videoMode) { + screenWidth = videoMode->screenWidth; + screenHeight = videoMode->screenHeight; + verticalScale = videoMode->aspectRatio; // ((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 = true; + bool useColour = videoMode->bpp != 1; int paletteSize = useColour ? 256 : 2; bitmapInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * paletteSize); @@ -119,10 +136,7 @@ void WindowsVideoDriver::Init() buffer[n] = 0xf; } - colourScheme.pageColour = 0xf; - colourScheme.linkColour = 1; - colourScheme.textColour = 0; - colourScheme.buttonColour = 7; + colourScheme = egaColourScheme; paletteLUT = cgaPaletteLUT; } @@ -149,10 +163,7 @@ void WindowsVideoDriver::Init() buffer[n] = 0xff; } - colourScheme.pageColour = 1; - colourScheme.linkColour = 0; - colourScheme.textColour = 0; - colourScheme.buttonColour = 1; + colourScheme = monochromeColourScheme; paletteLUT = nullptr; } diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index b6c6b53..e40decb 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -24,7 +24,7 @@ class WindowsVideoDriver : public VideoDriver public: WindowsVideoDriver(); - virtual void Init(); + virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); virtual void ClearScreen(); @@ -38,11 +38,9 @@ class WindowsVideoDriver : public VideoDriver 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; }; From 14ea6ec60cbcc10ed7507ffe63584366f90d44ab Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 27 Feb 2024 20:58:05 +0000 Subject: [PATCH 30/98] First pass table parsing --- project/Windows/Windows.vcxproj | 2 + src/Colour.cpp | 12 ++++ src/Colour.h | 2 + src/DOS/BIOSVid.cpp | 40 +++++++++++- src/DOS/Platform.cpp | 2 +- src/Draw/Surf8bpp.cpp | 3 +- src/Image/Gif.cpp | 2 +- src/Interface.cpp | 4 +- src/Layout.cpp | 52 +++++++++++++--- src/Layout.h | 17 ++++- src/Node.cpp | 6 +- src/Node.h | 3 + src/Nodes/Field.cpp | 6 +- src/Nodes/Status.cpp | 4 +- src/Nodes/Table.cpp | 107 ++++++++++++++++++++++++++++++++ src/Nodes/Table.h | 48 ++++++++++++++ src/Page.cpp | 7 ++- src/Tags.cpp | 37 ++++++++++- src/Tags.h | 24 +++++++ src/Windows/WinVid.cpp | 59 +++++++++++++++++- 20 files changed, 409 insertions(+), 28 deletions(-) create mode 100644 src/Nodes/Table.cpp create mode 100644 src/Nodes/Table.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 5e42fe5..f737ee0 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -162,6 +162,7 @@ + @@ -204,6 +205,7 @@ + diff --git a/src/Colour.cpp b/src/Colour.cpp index 22df5b2..5d5928c 100644 --- a/src/Colour.cpp +++ b/src/Colour.cpp @@ -24,3 +24,15 @@ ColourScheme egaColourScheme = // 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 index 34ec589..7482eed 100644 --- a/src/Colour.h +++ b/src/Colour.h @@ -5,6 +5,7 @@ #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 @@ -24,6 +25,7 @@ struct NamedColour extern ColourScheme monochromeColourScheme; extern ColourScheme egaColourScheme; +extern ColourScheme colourScheme666; extern uint8_t cgaPaletteLUT[]; diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp index 4639254..d1d3236 100644 --- a/src/DOS/BIOSVid.cpp +++ b/src/DOS/BIOSVid.cpp @@ -63,8 +63,37 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) { drawSurface = new DrawSurface_8BPP(screenWidth, screenHeight); screenPitch = screenWidth; - colourScheme = egaColourScheme; - paletteLUT = cgaPaletteLUT; + colourScheme = colourScheme666; + paletteLUT = new uint8_t[256]; + 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); + } + } + } } if (videoModeInfo->vramPage3) @@ -147,4 +176,11 @@ void BIOSVideoDriver::ScaleImageDimensions(int& width, int& height) height /= videoModeInfo->aspectRatio; // Scale to 4:3 //height = (height * 5) / 12; + int maxWidth = screenWidth - 16; + + if (width > maxWidth) + { + height = ((long)height * maxWidth) / width; + width = maxWidth; + } } diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index cf66449..222069e 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -171,9 +171,9 @@ bool Platform::Init(int argc, char* argv[]) video = new BIOSVideoDriver(); } + network->Init(); video->Init(videoMode); video->drawSurface->Clear(); - network->Init(); input->Init(); if (inverse) diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index 46c933c..6cd1454 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -4,6 +4,7 @@ #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) @@ -513,6 +514,6 @@ void DrawSurface_8BPP::Clear() int widthBytes = width; for (int y = 0; y < height; y++) { - memset(lines[y], 0xf, widthBytes); + memset(lines[y], Platform::video->colourScheme.pageColour, widthBytes); } } diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index e81424a..cc93f2d 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -700,7 +700,7 @@ void GifDecoder::EmitLine(int y) int offset = ditherPattern[ditherIndex]; ditherIndex = (ditherIndex + 1) & 3; - if (lineBuffer[i] == transparentColourIndex) + if (lineBuffer[x] == transparentColourIndex) { output[i] = TRANSPARENT_COLOUR_VALUE; } diff --git a/src/Interface.cpp b/src/Interface.cpp index 87fe976..a9a8e3c 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -387,7 +387,7 @@ void AppInterface::SetTitle(const char* title) DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); Platform::input->HideMouse(); - uint8_t fillColour = 0xf; + uint8_t fillColour = Platform::video->colourScheme.pageColour; context.surface->FillRect(context, 0, 0, titleNode->size.x, titleNode->size.y, fillColour); titleNode->Handler().Draw(context, titleNode); Platform::input->ShowMouse(); @@ -450,7 +450,7 @@ void AppInterface::ScrollRelative(int delta) DrawContext context; app.pageRenderer.GenerateDrawContext(context, NULL); context.drawOffsetY = 0; - context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 0xf); + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); app.pageRenderer.GenerateDrawContext(context, NULL); app.pageRenderer.DrawAll(context, app.page.GetRootNode()); } diff --git a/src/Layout.cpp b/src/Layout.cpp index ae3b1ae..ad85084 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -11,16 +11,42 @@ Layout::Layout(Page& inPage) void Layout::Reset() { - cursor.Clear(); paramStackSize = 0; + cursorStackSize = 0; currentLineHeight = 0; lineStartNode = nullptr; + Cursor().Clear(); LayoutParams& params = GetParams(); params.marginLeft = 0; params.marginRight = page.GetPageWidth(); } +void Layout::PushCursor() +{ + if (cursorStackSize < MAX_CURSOR_STACK_SIZE - 1) + { + cursorStack[cursorStackSize + 1] = cursorStack[cursorStackSize]; + cursorStackSize++; + } + else + { + // TODO error + } +} + +void Layout::PopCursor() +{ + if (cursorStackSize > 0) + { + cursorStackSize--; + } + else + { + // TODO: error + } +} + void Layout::PushLayout() { if (paramStackSize < MAX_LAYOUT_PARAMS_STACK_SIZE - 1) @@ -60,8 +86,8 @@ void Layout::BreakNewLine() lineStartNode->parent->OnChildLayoutChanged(); } - cursor.x = GetParams().marginLeft; - cursor.y += currentLineHeight; + Cursor().x = GetParams().marginLeft; + Cursor().y += currentLineHeight; currentLineHeight = 0; lineStartNode = nullptr; } @@ -87,7 +113,7 @@ void Layout::ProgressCursor(Node* nodeContext, int width, int lineHeight) currentLineHeight = lineHeight; } - cursor.x += width; + Cursor().x += width; } void Layout::TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblings) @@ -160,16 +186,26 @@ void Layout::PadHorizontal(int left, int right) params.marginLeft += left; params.marginRight -= right; } - if (cursor.x < params.marginLeft) + 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) { - cursor.x = params.marginLeft; + params.marginRight = marginRight; } } void Layout::PadVertical(int down) { - cursor.x = GetParams().marginLeft; - cursor.y += down; + Cursor().x = GetParams().marginLeft; + Cursor().y += down; } diff --git a/src/Layout.h b/src/Layout.h index eb8cdb0..7dd443a 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -3,6 +3,7 @@ #include "Node.h" #define MAX_LAYOUT_PARAMS_STACK_SIZE 32 +#define MAX_CURSOR_STACK_SIZE 16 class Page; class Node; @@ -29,6 +30,7 @@ class Layout void PadHorizontal(int left, int right); void PadVertical(int down); + void RestrictHorizontal(int maxWidth); void OnNodeEmitted(Node* node); void ProgressCursor(Node* nodeContext, int width, int lineHeight); @@ -37,17 +39,26 @@ class Layout Coord GetCursor(int lineHeight = 0) { - Coord result = cursor; + Coord result = Cursor(); result.y += currentLineHeight - lineHeight; return result; } LayoutParams& GetParams() { return paramStack[paramStackSize]; } - int AvailableWidth() { return GetParams().marginRight - cursor.x; } + int AvailableWidth() { return GetParams().marginRight - Cursor().x; } int MaxAvailableWidth() { return GetParams().marginRight - GetParams().marginLeft; } Node* lineStartNode; - Coord cursor; + Coord& Cursor() + { + return cursorStack[cursorStackSize]; + } + void PushCursor(); + void PopCursor(); + + Coord cursorStack[MAX_CURSOR_STACK_SIZE]; + int cursorStackSize; + int currentLineHeight; LayoutParams paramStack[MAX_LAYOUT_PARAMS_STACK_SIZE]; diff --git a/src/Node.cpp b/src/Node.cpp index 3ec31be..368c380 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -14,6 +14,7 @@ #include "Nodes/Form.h" #include "Nodes/Status.h" #include "Nodes/Scroll.h" +#include "Nodes/Table.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -29,7 +30,10 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new TextFieldNode(), new FormNode(), new StatusBarNode(), - new ScrollBarNode() + new ScrollBarNode(), + new TableNode(), + new TableRowNode(), + new TableCellNode() }; Node::Node(Type inType, void* inData) diff --git a/src/Node.h b/src/Node.h index 42382dc..bb1875f 100644 --- a/src/Node.h +++ b/src/Node.h @@ -61,6 +61,9 @@ class Node Form, StatusBar, ScrollBar, + Table, + TableRow, + TableCell, NumNodeTypes }; diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index ac19954..ad1f4aa 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -12,9 +12,9 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) TextFieldNode::Data* data = static_cast(node->data); Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = 0; - uint8_t buttonOutlineColour = 0; - uint8_t clearColour = 0xf; + 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); diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp index c8ed170..1288b04 100644 --- a/src/Nodes/Status.cpp +++ b/src/Nodes/Status.cpp @@ -21,8 +21,8 @@ void StatusBarNode::Draw(DrawContext& context, Node* node) StatusBarNode::Data* data = static_cast(node->data); Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = 0; - uint8_t clearColour = 0xf; + 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); context.surface->DrawString(context, font, data->message, node->anchor.x + 1, node->anchor.y + 1, textColour, node->style.fontStyle); diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp new file mode 100644 index 0000000..d1fa7e9 --- /dev/null +++ b/src/Nodes/Table.cpp @@ -0,0 +1,107 @@ +#include +#include "../Layout.h" +#include "../Memory/Memory.h" +#include "Table.h" + +Node* TableNode::Construct(Allocator& allocator) +{ + TableNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Table, data); + } + return nullptr; +} + +void TableNode::BeginLayoutContext(Layout& layout, Node* node) +{ + TableNode::Data* data = static_cast(node->data); + + layout.BreakNewLine(); +// layout.PadVertical(data->verticalPadding); + layout.PushLayout(); + //layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); +} + +void TableNode::EndLayoutContext(Layout& layout, Node* node) +{ + NodeHandler::EndLayoutContext(layout, node); + + TableNode::Data* data = static_cast(node->data); + + layout.PopLayout(); + layout.BreakNewLine(); + //layout.PadVertical(data->verticalPadding); +} + +// Table row node + +Node* TableRowNode::Construct(Allocator& allocator) +{ + TableRowNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::TableRow, data); + } + return nullptr; +} + +void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) +{ + TableRowNode::Data* data = static_cast(node->data); + + layout.BreakNewLine(); + // layout.PadVertical(data->verticalPadding); + layout.PushLayout(); + //layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); +} + +void TableRowNode::EndLayoutContext(Layout& layout, Node* node) +{ + NodeHandler::EndLayoutContext(layout, node); + + TableRowNode::Data* data = static_cast(node->data); + + layout.PopLayout(); + layout.BreakNewLine(); + layout.PadVertical(node->size.y); + //layout.PadVertical(data->verticalPadding); +} + +// Table cell node + +Node* TableCellNode::Construct(Allocator& allocator) +{ + TableCellNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::TableCell, data); + } + return nullptr; +} + +void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) +{ + TableCellNode::Data* data = static_cast(node->data); + + //layout.BreakNewLine(); + // layout.PadVertical(data->verticalPadding); + layout.PushLayout(); + layout.RestrictHorizontal(200); + layout.PushCursor(); + //layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); +} + +void TableCellNode::EndLayoutContext(Layout& layout, Node* node) +{ + NodeHandler::EndLayoutContext(layout, node); + + TableCellNode::Data* data = static_cast(node->data); + + layout.PopCursor(); + layout.PopLayout(); + //layout.ProgressCursor(node, 100, 0); + layout.PadHorizontal(200, 0); + //layout.BreakNewLine(); + //layout.PadVertical(data->verticalPadding); +} diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h new file mode 100644 index 0000000..24e1270 --- /dev/null +++ b/src/Nodes/Table.h @@ -0,0 +1,48 @@ +#ifndef _TABLE_H_ +#define _TABLE_H + +#include "../Node.h" + +class TableNode : public NodeHandler +{ +public: + class Data + { + public: + Data() {} + }; + + static Node* Construct(Allocator& allocator); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; +}; + +class TableRowNode : public NodeHandler +{ +public: + class Data + { + public: + Data() {} + }; + + static Node* Construct(Allocator& allocator); + virtual void BeginLayoutContext(Layout& layout, Node* node) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; +}; + +class TableCellNode : public NodeHandler +{ +public: + class Data + { + public: + Data() {} + }; + + 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/Page.cpp b/src/Page.cpp index 2684549..9500530 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -86,7 +86,12 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) "Block", "Button", "TextField", - "Form" + "Form", + "StatusBar", + "ScrollBar", + "Table", + "TableRow", + "TableCell" }; static const char* sectionTypeNames[] = diff --git a/src/Tags.cpp b/src/Tags.cpp index 4ddf974..559391e 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -32,6 +32,7 @@ #include "Nodes/Button.h" #include "Nodes/Field.h" #include "Nodes/Form.h" +#include "Nodes/Table.h" static const HTMLTagHandler* tagHandlers[] = { @@ -54,7 +55,7 @@ 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("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 AlignmentTagHandler("center", ElementAlignment::Center), @@ -80,6 +81,10 @@ static const HTMLTagHandler* tagHandlers[] = new ImgTagHandler(), new MetaTagHandler(), new PreformattedTagHandler("pre"), + new TableTagHandler(), + new TableRowTagHandler(), + new TableCellTagHandler("td"), + new TableCellTagHandler("th"), NULL }; @@ -547,3 +552,33 @@ void PreformattedTagHandler::Close(class HTMLParser& parser) const parser.PopContext(this); parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } + +void TableTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + parser.PushContext(TableNode::Construct(MemoryManager::pageAllocator), 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 +{ + parser.PushContext(TableCellNode::Construct(MemoryManager::pageAllocator), this); +} + +void TableCellTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} diff --git a/src/Tags.h b/src/Tags.h index f20a444..8affaf9 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -178,6 +178,30 @@ class ButtonTagHandler : public HTMLTagHandler virtual void Open(class HTMLParser& parser, char* attributeStr) 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) : HTMLTagHandler(name) {} + 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/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index f397e0c..07243b3 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -109,6 +109,24 @@ void WindowsVideoDriver::Init(VideoModeInfo* videoMode) if (useColour) { memcpy(bitmapInfo->bmiColors, cgaPalette, sizeof(RGBQUAD) * 16); + + if (videoMode->bpp == 8) + { + 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++; + } + } + } + } } else { @@ -136,9 +154,31 @@ void WindowsVideoDriver::Init(VideoModeInfo* videoMode) buffer[n] = 0xf; } - colourScheme = egaColourScheme; - paletteLUT = cgaPaletteLUT; + if (videoMode->bpp == 8) + { + colourScheme = colourScheme666; + //paletteLUT = cgaPaletteLUT; + + paletteLUT = new uint8_t[256]; + for (int n = 0; n < 256; n++) + { + int r = (n & 0xe0); + int g = (n & 0x1c) << 3; + int b = (n & 3) << 6; + + int rgbBlue = (b * 255) / 0xc0; + int rgbGreen = (g * 255) / 0xe0; + int rgbRed = (r * 255) / 0xe0; + + paletteLUT[n] = RGB666(rgbRed, rgbGreen, rgbBlue); + } + } + else + { + colourScheme = egaColourScheme; + paletteLUT = cgaPaletteLUT; + } } else { @@ -258,6 +298,12 @@ void WindowsVideoDriver::Paint(HWND hwnd) void WindowsVideoDriver::ScaleImageDimensions(int& width, int& height) { + if (screenWidth <= 320) + { + width /= 2; + height /= 2; + } + height = (height / verticalScale); int maxWidth = screenWidth - 16; @@ -267,4 +313,13 @@ void WindowsVideoDriver::ScaleImageDimensions(int& width, int& height) height = (height * maxWidth) / width; width = maxWidth; } + + if (width == 0) + { + width = 1; + } + if (height == 0) + { + height = 1; + } } From 32c0cde95a216db1481364109124582df5465b71 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 1 Mar 2024 14:47:51 +0000 Subject: [PATCH 31/98] Table support WIP --- src/Layout.cpp | 58 ++++++++- src/Layout.h | 1 + src/Node.h | 11 ++ src/Nodes/Table.cpp | 293 ++++++++++++++++++++++++++++++++++++++++---- src/Nodes/Table.h | 40 +++++- 5 files changed, 370 insertions(+), 33 deletions(-) diff --git a/src/Layout.cpp b/src/Layout.cpp index ad85084..51be370 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -75,11 +75,11 @@ void Layout::PopLayout() 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 && lineStartNode->style.alignment == ElementAlignment::Center) + //{ + // int shift = AvailableWidth() / 2; + // TranslateNodes(lineStartNode, shift, 0, true); + //} if (lineStartNode && lineStartNode->parent) { @@ -209,6 +209,50 @@ void Layout::PadVertical(int down) } +void Layout::RecalculateLayoutForNode(Node* targetNode) +{ + 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) + { + 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); + } + } + +} + void Layout::RecalculateLayout() { Reset(); @@ -237,7 +281,8 @@ void Layout::RecalculateLayout() node = node->parent; if (node) { - node->EncapsulateChildren(); + node->Handler().EndLayoutContext(*this, node); +// node->EncapsulateChildren(); } checkChildren = false; continue; @@ -245,6 +290,7 @@ void Layout::RecalculateLayout() if (node) { + node->Handler().BeginLayoutContext(*this, node); node->Handler().GenerateLayout(*this, node); /*if (node->parent) { diff --git a/src/Layout.h b/src/Layout.h index 7dd443a..215c3d0 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -36,6 +36,7 @@ class Layout void ProgressCursor(Node* nodeContext, int width, int lineHeight); void RecalculateLayout(); + void RecalculateLayoutForNode(Node* node); Coord GetCursor(int lineHeight = 0) { diff --git a/src/Node.h b/src/Node.h index bb1875f..303dbdd 100644 --- a/src/Node.h +++ b/src/Node.h @@ -79,6 +79,17 @@ class Node bool IsPointInsideChildren(int x, int y); void OnChildLayoutChanged(); Node* FindParentOfType(Node::Type searchType); + template + T* FindParentDataOfType(Node::Type searchType) + { + Node* parent = FindParentOfType(searchType); + if (parent) + { + return static_cast(parent->data); + } + return nullptr; + } + void Redraw(); Node* GetNextInTree(); diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index d1fa7e9..9157967 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -1,26 +1,60 @@ #include #include "../Layout.h" #include "../Memory/Memory.h" +#include "../Draw/Surface.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); + + uint8_t borderColour = Platform::video->colourScheme.textColour; + int x = node->anchor.x - data->cellSpacing - data->cellPadding; + int y = node->anchor.y - data->cellSpacing - data->cellPadding; + int w = node->size.x + (data->cellSpacing + data->cellPadding) * 2; + int h = node->size.y + (data->cellSpacing + data->cellPadding) * 2; + 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); +} + void TableNode::BeginLayoutContext(Layout& layout, Node* node) { TableNode::Data* data = static_cast(node->data); layout.BreakNewLine(); -// layout.PadVertical(data->verticalPadding); + layout.PushCursor(); layout.PushLayout(); - //layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); + + layout.PadHorizontal(data->cellSpacing, data->cellSpacing); + + if (!data->IsGeneratingLayout()) + { + if (data->numRows > 0) + { + layout.PadVertical(data->cellSpacing + data->cellPadding); + } + } } void TableNode::EndLayoutContext(Layout& layout, Node* node) @@ -30,8 +64,123 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) TableNode::Data* data = static_cast(node->data); layout.PopLayout(); + layout.PopCursor(); + + if (data->IsGeneratingLayout()) + { + // 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.Alloc(sizeof(TableCellNode::Data*) * data->numRows * data->numColumns); + } + + if (!data->columns) + { + data->columns = (TableNode::Data::ColumnInfo*)MemoryManager::pageAllocator.Alloc(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; + } + + for (int n = 0; n < data->numColumns; n++) + { + data->columns[n].preferredWidth = 16; + } + + // 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[j * data->numColumns + i] = cell; + } + } + + cell->columnIndex = columnIndex; + cell->rowIndex = rowIndex; + columnIndex += cell->columnSpan; + } + rowIndex++; + } + + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + { + for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) + { + int preferredWidth = (2 * data->cellPadding + cell->node->size.x) / cell->columnSpan; + + for (int i = 0; i < cell->columnSpan; i++) + { + if (data->columns[cell->columnIndex + i].preferredWidth < preferredWidth) + { + data->columns[cell->columnIndex + i].preferredWidth = preferredWidth; + } + } + } + } + + int totalPreferredWidth = data->cellSpacing * (data->numColumns - 1); + for (int i = 0; i < data->numColumns; i++) + { + totalPreferredWidth += data->columns[i].preferredWidth; + } + + int maxAvailableWidth = layout.MaxAvailableWidth(); + if (totalPreferredWidth > maxAvailableWidth) + { + // TODO resize widths to fit + for (int i = 0; i < data->numColumns; i++) + { + data->columns[i].preferredWidth = ((long)maxAvailableWidth * data->columns[i].preferredWidth) / totalPreferredWidth; + } + } + + layout.RecalculateLayoutForNode(node); + } + layout.BreakNewLine(); - //layout.PadVertical(data->verticalPadding); } // Table row node @@ -50,10 +199,38 @@ void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) { TableRowNode::Data* data = static_cast(node->data); - layout.BreakNewLine(); - // layout.PadVertical(data->verticalPadding); + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + if (tableData) + { + if (tableData->IsGeneratingLayout()) + { + 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 + { + layout.PadVertical(tableData->cellPadding); + } + } + + layout.PushCursor(); + //layout.BreakNewLine(); layout.PushLayout(); - //layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); } void TableRowNode::EndLayoutContext(Layout& layout, Node* node) @@ -61,11 +238,15 @@ void TableRowNode::EndLayoutContext(Layout& layout, Node* node) NodeHandler::EndLayoutContext(layout, node); TableRowNode::Data* data = static_cast(node->data); + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); - layout.PopLayout(); - layout.BreakNewLine(); - layout.PadVertical(node->size.y); - //layout.PadVertical(data->verticalPadding); + if (tableData) + { + layout.PopLayout(); + layout.BreakNewLine(); + layout.PopCursor(); + layout.PadVertical(node->size.y + tableData->cellSpacing + tableData->cellPadding); + } } // Table cell node @@ -75,7 +256,9 @@ Node* TableCellNode::Construct(Allocator& allocator) TableCellNode::Data* data = allocator.Alloc(); if (data) { - return allocator.Alloc(Node::TableCell, data); + Node* node = allocator.Alloc(Node::TableCell, data); + data->node = node; + return node; } return nullptr; } @@ -84,24 +267,90 @@ void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) { TableCellNode::Data* data = static_cast(node->data); - //layout.BreakNewLine(); - // layout.PadVertical(data->verticalPadding); + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + TableRowNode::Data* rowData = node->FindParentDataOfType(Node::TableRow); + layout.PushLayout(); - layout.RestrictHorizontal(200); + + if (tableData && rowData) + { + if (tableData->IsGeneratingLayout()) + { + 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 + { + layout.RestrictHorizontal(tableData->columns[data->columnIndex].preferredWidth); + layout.PadHorizontal(tableData->cellPadding, tableData->cellPadding); + } + } + layout.PushCursor(); - //layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); } void TableCellNode::EndLayoutContext(Layout& layout, Node* node) { - NodeHandler::EndLayoutContext(layout, node); + //NodeHandler::EndLayoutContext(layout, node); + node->EncapsulateChildren(); + + layout.PopCursor(); + layout.PopLayout(); TableCellNode::Data* data = static_cast(node->data); - layout.PopCursor(); - layout.PopLayout(); - //layout.ProgressCursor(node, 100, 0); - layout.PadHorizontal(200, 0); - //layout.BreakNewLine(); - //layout.PadVertical(data->verticalPadding); + TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); + TableRowNode::Data* rowData = node->FindParentDataOfType(Node::TableRow); + if (tableData && rowData) + { + if (tableData->IsGeneratingLayout()) + { + } + else + { + layout.PadHorizontal(tableData->columns[data->columnIndex].preferredWidth + tableData->cellSpacing, 0); + } + } + +// layout.PadHorizontal(200, 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 - tableData->cellPadding; + int y = node->anchor.y - tableData->cellPadding; + int w = tableData->columns[data->columnIndex].preferredWidth; + int h = rowNode->size.y + 2 * tableData->cellPadding; + + 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); + } + } diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index 24e1270..61a1e40 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -3,18 +3,26 @@ #include "../Node.h" -class TableNode : public NodeHandler + +class TableCellNode : public NodeHandler { public: class Data { public: - Data() {} + Data() : columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), nextCell(nullptr) {} + Node* node; + int columnIndex; + int rowIndex; + int columnSpan; + int rowSpan; + TableCellNode::Data* nextCell; }; 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; }; class TableRowNode : public NodeHandler @@ -23,7 +31,11 @@ class TableRowNode : public NodeHandler class Data { public: - Data() {} + Data() : rowIndex(0), numCells(0), nextRow(nullptr), firstCell(nullptr) {} + int rowIndex; + int numCells; + TableRowNode::Data* nextRow; + TableCellNode::Data* firstCell; }; static Node* Construct(Allocator& allocator); @@ -31,18 +43,36 @@ class TableRowNode : public NodeHandler virtual void EndLayoutContext(Layout& layout, Node* node) override; }; -class TableCellNode : public NodeHandler +class TableNode : public NodeHandler { public: class Data { public: - Data() {} + struct ColumnInfo + { + int preferredWidth; + int calculatedWidth; + }; + + Data() : numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), columns(nullptr), firstRow(nullptr), cells(nullptr) {} + + bool IsGeneratingLayout() { return columns == nullptr; } + + int numColumns; + int numRows; + int cellSpacing; + int cellPadding; + ColumnInfo* columns; + TableRowNode::Data* firstRow; + TableCellNode::Data** cells; }; 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 From b2ae6d1b9aa65fbf5e473310e1b810719e04bc6b Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 5 Mar 2024 17:31:18 +0000 Subject: [PATCH 32/98] Tables WIP --- project/DOS/Makefile | 5 +- src/Layout.cpp | 47 ++++++++-- src/Layout.h | 3 +- src/Node.cpp | 2 +- src/Nodes/Block.cpp | 7 +- src/Nodes/Break.cpp | 10 ++- src/Nodes/Table.cpp | 200 ++++++++++++++++++++++++------------------- src/Nodes/Table.h | 12 ++- src/Nodes/Text.cpp | 36 +++++++- src/Nodes/Text.h | 3 +- 10 files changed, 220 insertions(+), 105 deletions(-) diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 2c8299c..1276cab 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -100,6 +100,9 @@ Scroll.obj: $(SRC_PATH)\Nodes\Scroll.cpp Field.obj: $(SRC_PATH)\Nodes\Field.cpp $(CC) -fo=$@ $(CFLAGS) $< +Table.obj: $(SRC_PATH)\Nodes\Table.cpp + $(CC) -fo=$@ $(CFLAGS) $< + Button.obj: $(SRC_PATH)\Nodes\Button.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/src/Layout.cpp b/src/Layout.cpp index 51be370..7c9d484 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -15,6 +15,7 @@ void Layout::Reset() cursorStackSize = 0; currentLineHeight = 0; lineStartNode = nullptr; + lastNodeContext = nullptr; Cursor().Clear(); LayoutParams& params = GetParams(); @@ -80,12 +81,18 @@ void Layout::BreakNewLine() // int shift = AvailableWidth() / 2; // TranslateNodes(lineStartNode, shift, 0, true); //} - - if (lineStartNode && lineStartNode->parent) + + if (lineStartNode && lineStartNode->style.alignment == ElementAlignment::Center) { - lineStartNode->parent->OnChildLayoutChanged(); + int shift = AvailableWidth() / 2; + // TranslateNodes(lineStartNode, lastNodeContext, shift, 0); } + //if (lineStartNode && lineStartNode->parent) + //{ + // lineStartNode->parent->OnChildLayoutChanged(); + //} + Cursor().x = GetParams().marginLeft; Cursor().y += currentLineHeight; currentLineHeight = 0; @@ -105,17 +112,40 @@ void Layout::ProgressCursor(Node* nodeContext, int width, int lineHeight) 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, 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->parent) + //{ + // node->parent->OnChildLayoutChanged(); + //} + + if (node == end) + { + break; + } + } +} + +/* void Layout::TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblings) { while (node) @@ -157,6 +187,7 @@ void Layout::TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblin } } } +*/ void Layout::OnNodeEmitted(Node* node) { @@ -167,10 +198,10 @@ void Layout::OnNodeEmitted(Node* node) node->Handler().GenerateLayout(*this, node); - if (node->parent) - { - node->parent->OnChildLayoutChanged(); - } + //if (node->parent) + //{ + // node->parent->OnChildLayoutChanged(); + //} } void Layout::PadHorizontal(int left, int right) diff --git a/src/Layout.h b/src/Layout.h index 215c3d0..1bfd6c5 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -49,6 +49,7 @@ class Layout int MaxAvailableWidth() { return GetParams().marginRight - GetParams().marginLeft; } Node* lineStartNode; + Node* lastNodeContext; Coord& Cursor() { @@ -65,7 +66,7 @@ class Layout LayoutParams paramStack[MAX_LAYOUT_PARAMS_STACK_SIZE]; int paramStackSize; - void TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblings = false); + void TranslateNodes(Node* start, Node* end, int deltaX, int deltaY); }; /* diff --git a/src/Node.cpp b/src/Node.cpp index 368c380..65980e2 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -204,7 +204,7 @@ Node* NodeHandler::Pick(Node* node, int x, int y) void NodeHandler::EndLayoutContext(Layout& layout, Node* node) { - if (node->size.x == 0 && node->size.y == 0) + //if (node->size.x == 0 && node->size.y == 0) { node->EncapsulateChildren(); } diff --git a/src/Nodes/Block.cpp b/src/Nodes/Block.cpp index 4de3459..e1e0d18 100644 --- a/src/Nodes/Block.cpp +++ b/src/Nodes/Block.cpp @@ -18,6 +18,9 @@ void BlockNode::BeginLayoutContext(Layout& layout, Node* node) BlockNode::Data* data = static_cast(node->data); layout.BreakNewLine(); + node->anchor = layout.GetCursor(); + node->size.x = layout.MaxAvailableWidth(); + layout.PadVertical(data->verticalPadding); layout.PushLayout(); layout.PadHorizontal(data->horizontalPadding, data->horizontalPadding); @@ -25,11 +28,13 @@ void BlockNode::BeginLayoutContext(Layout& layout, Node* node) void BlockNode::EndLayoutContext(Layout& layout, Node* node) { - NodeHandler::EndLayoutContext(layout, node); + + //NodeHandler::EndLayoutContext(layout, node); BlockNode::Data* data = static_cast(node->data); layout.PopLayout(); layout.BreakNewLine(); layout.PadVertical(data->verticalPadding); + node->size.y = layout.GetCursor().y - node->anchor.y; } diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index 531e9bf..2355265 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -22,7 +22,7 @@ void BreakNode::Draw(DrawContext& context, Node* node) if (data->displayBreakLine) { uint8_t outlineColour = 0; - 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 / 2, node->size.x, outlineColour); } } @@ -48,7 +48,11 @@ void BreakNode::GenerateLayout(Layout& layout, Node* node) node->anchor = layout.GetCursor(); node->anchor.x += 8; - node->anchor.y -= breakPadding / 2; + node->anchor.y -= breakPadding; node->size.x = layout.AvailableWidth() - 16; - node->size.y = data->displayBreakLine ? 1 : 0; + node->size.y = breakPadding; + if (!breakPadding && data->displayBreakLine) + { + node->size.y = 1; + } } diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 9157967..a3772c4 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -42,6 +42,11 @@ void TableNode::BeginLayoutContext(Layout& layout, Node* node) { TableNode::Data* data = static_cast(node->data); + if (data->state == Data::FinishedLayout) + { + data->state = Data::GeneratingLayout; + } + layout.BreakNewLine(); layout.PushCursor(); layout.PushLayout(); @@ -59,7 +64,8 @@ void TableNode::BeginLayoutContext(Layout& layout, Node* node) void TableNode::EndLayoutContext(Layout& layout, Node* node) { - NodeHandler::EndLayoutContext(layout, node); + //NodeHandler::EndLayoutContext(layout, node); + node->EncapsulateChildren(); TableNode::Data* data = static_cast(node->data); @@ -68,81 +74,84 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) if (data->IsGeneratingLayout()) { - // Calculate number of rows and columns - data->numRows = data->numColumns = 0; - - for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + if (!data->HasGeneratedCellGrid()) { - int columnCount = 0; - for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) - { - columnCount += cell->columnSpan; - } - data->numRows++; + // Calculate number of rows and columns + data->numRows = data->numColumns = 0; - if (columnCount > data->numColumns) + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) { - data->numColumns = columnCount; - } - } + 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.Alloc(sizeof(TableCellNode::Data*) * data->numRows * data->numColumns); - } - if (!data->columns) - { - data->columns = (TableNode::Data::ColumnInfo*)MemoryManager::pageAllocator.Alloc(sizeof(TableNode::Data::ColumnInfo) * data->numColumns); - } - - if (!data->cells || !data->columns) - { - // TODO: allocation error - return; - } + if (!data->cells) + { + data->cells = (TableCellNode::Data**)MemoryManager::pageAllocator.Alloc(sizeof(TableCellNode::Data*) * data->numRows * data->numColumns); + } - for (int n = 0; n < data->numRows * data->numColumns; n++) - { - data->cells[n] = nullptr; - } + if (!data->columns) + { + data->columns = (TableNode::Data::ColumnInfo*)MemoryManager::pageAllocator.Alloc(sizeof(TableNode::Data::ColumnInfo) * data->numColumns); + } - for (int n = 0; n < data->numColumns; n++) - { - data->columns[n].preferredWidth = 16; - } + if (!data->cells || !data->columns) + { + // TODO: allocation error + return; + } - // 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 (int n = 0; n < data->numRows * data->numColumns; n++) + { + data->cells[n] = nullptr; + } - for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) + // Fill a grid array with pointers to cells + int rowIndex = 0; + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) { - while (data->cells[rowIndex * data->numColumns + columnIndex] && columnIndex < data->numColumns) - { - columnIndex++; - } - if (columnIndex == data->numColumns) - { - break; - } + int columnIndex = 0; - for (int j = 0; j < cell->rowSpan; j++) + for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) { - for (int i = 0; i < cell->columnSpan; i++) + while (data->cells[rowIndex * data->numColumns + columnIndex] && columnIndex < data->numColumns) { - data->cells[j * data->numColumns + i] = cell; + columnIndex++; + } + if (columnIndex == data->numColumns) + { + break; } - } - cell->columnIndex = columnIndex; - cell->rowIndex = rowIndex; - columnIndex += cell->columnSpan; + for (int j = 0; j < cell->rowSpan; j++) + { + for (int i = 0; i < cell->columnSpan; i++) + { + data->cells[j * data->numColumns + i] = cell; + } + } + + cell->columnIndex = columnIndex; + cell->rowIndex = rowIndex; + columnIndex += cell->columnSpan; + } + rowIndex++; } - rowIndex++; + } + + for (int n = 0; n < data->numColumns; n++) + { + data->columns[n].preferredWidth = 16; } for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) @@ -167,7 +176,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) totalPreferredWidth += data->columns[i].preferredWidth; } - int maxAvailableWidth = layout.MaxAvailableWidth(); + int maxAvailableWidth = layout.MaxAvailableWidth() - data->cellSpacing * 2; if (totalPreferredWidth > maxAvailableWidth) { // TODO resize widths to fit @@ -177,7 +186,10 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } + data->state = Data::FinalisingLayout; layout.RecalculateLayoutForNode(node); + layout.PadVertical(node->size.y + (data->cellPadding + data->cellSpacing) * 2); + data->state = Data::FinishedLayout; } layout.BreakNewLine(); @@ -204,23 +216,26 @@ void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) { if (tableData->IsGeneratingLayout()) { - if (!tableData->firstRow) + if (!tableData->HasGeneratedCellGrid()) { - tableData->firstRow = data; - } - else - { - for (TableRowNode::Data* row = tableData->firstRow; row; row = row->nextRow) + if (!tableData->firstRow) + { + tableData->firstRow = data; + } + else { - if (row->nextRow == nullptr) + for (TableRowNode::Data* row = tableData->firstRow; row; row = row->nextRow) { - row->nextRow = data; - break; + if (row->nextRow == nullptr) + { + row->nextRow = data; + break; + } } } + data->rowIndex = tableData->numRows; + tableData->numRows++; } - data->rowIndex = tableData->numRows; - tableData->numRows++; } else { @@ -235,7 +250,8 @@ void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) void TableRowNode::EndLayoutContext(Layout& layout, Node* node) { - NodeHandler::EndLayoutContext(layout, node); + //NodeHandler::EndLayoutContext(layout, node); + node->EncapsulateChildren(); TableRowNode::Data* data = static_cast(node->data); TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); @@ -276,25 +292,28 @@ void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) { if (tableData->IsGeneratingLayout()) { - if (!rowData->firstCell) - { - rowData->firstCell = data; - } - else + if (!tableData->HasGeneratedCellGrid()) { - for (TableCellNode::Data* cell = rowData->firstCell; cell; cell = cell->nextCell) + if (!rowData->firstCell) + { + rowData->firstCell = data; + } + else { - if (!cell->nextCell) + for (TableCellNode::Data* cell = rowData->firstCell; cell; cell = cell->nextCell) { - cell->nextCell = data; - break; + if (!cell->nextCell) + { + cell->nextCell = data; + break; + } } } - } - data->rowIndex = rowData->rowIndex; - data->columnIndex = rowData->numCells; - rowData->numCells++; + data->rowIndex = rowData->rowIndex; + data->columnIndex = rowData->numCells; + rowData->numCells++; + } } else { @@ -309,8 +328,17 @@ void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) void TableCellNode::EndLayoutContext(Layout& layout, Node* node) { //NodeHandler::EndLayoutContext(layout, node); - node->EncapsulateChildren(); + if (node->firstChild) + { + node->EncapsulateChildren(); + } + else + { + // Cell was empty + node->anchor = layout.GetCursor(); + } + layout.BreakNewLine(); layout.PopCursor(); layout.PopLayout(); diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index 61a1e40..6724866 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -54,11 +54,19 @@ class TableNode : public NodeHandler int preferredWidth; int calculatedWidth; }; + enum State + { + GeneratingLayout, + FinalisingLayout, + FinishedLayout + }; - Data() : numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), columns(nullptr), firstRow(nullptr), cells(nullptr) {} + Data() : state(GeneratingLayout), numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), columns(nullptr), firstRow(nullptr), cells(nullptr) {} - bool IsGeneratingLayout() { return columns == nullptr; } + bool IsGeneratingLayout() { return state == GeneratingLayout; } + bool HasGeneratedCellGrid() { return cells != nullptr; } + State state; int numColumns; int numRows; int cellSpacing; diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index be5617a..01a942b 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -38,9 +38,41 @@ Node* TextElement::Construct(Allocator& allocator, const char* text) void TextElement::GenerateLayout(Layout& layout, Node* node) { TextElement::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); // TODO: Get font from active style + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); int lineHeight = font->glyphHeight; + /* 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()) + { + Coord cursor = layout.GetCursor(lineHeight); + Coord previousAnchor = node->firstChild ? node->firstChild->anchor : node->anchor; + Coord offset; + offset.x = cursor.x - previousAnchor.x; + offset.y = cursor.y - previousAnchor.y; + + if (offset.x || offset.y) + { + node->anchor.x += offset.x; + node->anchor.y += offset.y; + + for (Node* n = node->firstChild; n; n = n->next) + { + n->anchor.x += offset.x; + n->anchor.y += offset.y; + } + } + + return; + } + } + + data->lastAvailableWidth = layout.AvailableWidth(); + */ + // Clear out SubTextElement children if we are regenerating the layout for (Node* child = node->firstChild; child; child = child->next) { @@ -51,6 +83,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) childData->text[-1] = ' '; } childData->text = nullptr; + child->anchor = layout.GetCursor(); + child->size.Clear(); } char* text = data->text; diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h index 77e3fe2..7209ee6 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -8,8 +8,9 @@ class TextElement : public NodeHandler class Data { public: - Data(const char* inText) : text(const_cast(inText)) {} + Data(const char* inText) : text(const_cast(inText)), lastAvailableWidth(-1) {} char* text; + int lastAvailableWidth; }; static Node* Construct(Allocator& allocator, const char* text); From b87fec55cea2ed2b31c1b2503349a01510bd757b Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 5 Mar 2024 21:45:49 +0000 Subject: [PATCH 33/98] Refactored text layout to add line breaks in the middle of words and to adhere to non breaking spaces correctly --- src/Nodes/Text.cpp | 175 +++++++++++++++++++++++++-------------------- src/Nodes/Text.h | 11 ++- src/Page.cpp | 16 ++++- src/Parser.cpp | 2 +- 4 files changed, 119 insertions(+), 85 deletions(-) diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 01a942b..4eba199 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -6,20 +6,6 @@ #include "../Draw/Surface.h" #include "../DataPack.h" -void TextElement::Draw(DrawContext& context, Node* node) -{ - TextElement::Data* data = static_cast(node->data); - - if (!node->firstChild && data->text) - { - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = node->style.fontColour; - context.surface->DrawString(context, font, data->text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); - //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); - //printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); - } -} - Node* TextElement::Construct(Allocator& allocator, const char* text) { const char* textString = allocator.AllocString(text); @@ -41,7 +27,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); int lineHeight = font->glyphHeight; - /* TODO: optimisation +#if 0 + // TODO: optimisation if (data->lastAvailableWidth != -1) { // We must be regenerating this text element, see if we can just shuffle position rather than @@ -49,7 +36,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) if (data->lastAvailableWidth == layout.AvailableWidth()) { Coord cursor = layout.GetCursor(lineHeight); - Coord previousAnchor = node->firstChild ? node->firstChild->anchor : node->anchor; + Coord previousAnchor = node->firstChild->anchor; Coord offset; offset.x = cursor.x - previousAnchor.x; offset.y = cursor.y - previousAnchor.y; @@ -71,18 +58,15 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) } data->lastAvailableWidth = layout.AvailableWidth(); - */ +#endif + // Clear out SubTextElement children if we are regenerating the layout for (Node* child = node->firstChild; child; child = child->next) { - TextElement::Data* childData = static_cast(child->data); - if (childData->text && child != node->firstChild) - { - // Remove break and replace with white space - childData->text[-1] = ' '; - } - childData->text = nullptr; + SubTextElement::Data* childData = static_cast(child->data); + childData->startIndex = 0; + childData->length = 0; child->anchor = layout.GetCursor(); child->size.Clear(); } @@ -95,9 +79,10 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) int width = 0; Node* subTextNode = node->firstChild; - for(charIndex = 0; data->text[charIndex]; charIndex++) + for(charIndex = 0; ; charIndex++) { char c = data->text[charIndex]; + bool isEnd = data->text[charIndex + 1] == 0; if (c == ' ' || c == '\t') { @@ -105,84 +90,95 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) lastBreakPointWidth = width; } - width += font->GetGlyphWidth(c, node->style.fontStyle); + if (c == '\x1f') + { + // Non breaking space + data->text[charIndex] = ' '; + } + + int glyphWidth = font->GetGlyphWidth(c, node->style.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 (width > layout.AvailableWidth()) + if (cannotFit || isEnd) { // Needs a line break - if (startIndex == lastBreakPoint) + + int emitStartPosition = startIndex; + int emitLength; + int emitWidth; + int nextIndex; + + if (isEnd && !cannotFit) { - // Nothing could fit on the line before the break - layout.BreakNewLine(); + // End of the line so just emit everything + emitLength = charIndex + 1 - emitStartPosition; + emitWidth = width; + nextIndex = -1; + } + else if (lastBreakPoint) + { + // There was a space or tab that we can break at + emitLength = lastBreakPoint - emitStartPosition; + emitWidth = lastBreakPointWidth; + nextIndex = lastBreakPoint + 1; } else { - if (!subTextNode) - { - subTextNode = SubTextElement::Construct(MemoryManager::pageAllocator, &text[startIndex]); - node->AddChild(subTextNode); - } - else - { - TextElement::Data* childData = static_cast(subTextNode->data); - childData->text = &text[startIndex]; - } - - subTextNode->anchor = layout.GetCursor(lineHeight); - subTextNode->size.x = lastBreakPointWidth; - subTextNode->size.y = lineHeight; - - text[lastBreakPoint] = '\0'; - startIndex = lastBreakPoint + 1; - width -= lastBreakPointWidth; - - layout.ProgressCursor(subTextNode, lastBreakPointWidth, lineHeight); - layout.BreakNewLine(); - - subTextNode = subTextNode->next; + // Need to break in the middle of a word + emitLength = charIndex - emitStartPosition; + emitWidth = width - glyphWidth; + nextIndex = charIndex; } - } - } - - if (charIndex > startIndex) - { - if (startIndex == 0 && !subTextNode) - { - // No sub text nodes needed - node->anchor = layout.GetCursor(lineHeight); - node->size.x = width; - node->size.y = lineHeight; - layout.ProgressCursor(node, width, lineHeight); - } - else - { + if (!subTextNode) { - subTextNode = SubTextElement::Construct(MemoryManager::pageAllocator, &text[startIndex]); + subTextNode = SubTextElement::Construct(MemoryManager::pageAllocator, emitStartPosition, emitLength); node->AddChild(subTextNode); } else { - TextElement::Data* childData = static_cast(subTextNode->data); - childData->text = &text[startIndex]; + SubTextElement::Data* subTextData = static_cast(subTextNode->data); + subTextData->startIndex = emitStartPosition; + subTextData->length = emitLength; } + subTextNode->anchor = layout.GetCursor(lineHeight); - subTextNode->size.x = width; + subTextNode->size.x = emitWidth; subTextNode->size.y = lineHeight; - layout.ProgressCursor(subTextNode, width, lineHeight); + startIndex = nextIndex; + width -= emitWidth; + + layout.ProgressCursor(subTextNode, emitWidth, lineHeight); + subTextNode = subTextNode->next; + + if (isEnd) + { + break; + } + + lastBreakPoint = 0; + lastBreakPointWidth = 0; + + layout.BreakNewLine(); } } - if (node->firstChild) - { - node->EncapsulateChildren(); - } + node->EncapsulateChildren(); } -Node* SubTextElement::Construct(Allocator& allocator, const char* text) +Node* SubTextElement::Construct(Allocator& allocator, int startIndex, int length) { - TextElement::Data* data = allocator.Alloc(text); + SubTextElement::Data* data = allocator.Alloc(startIndex, length); if (data) { return allocator.Alloc(Node::SubText, data); @@ -195,3 +191,24 @@ 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) + { + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + uint8_t textColour = node->style.fontColour; + char* text = textData->text + subTextData->startIndex; + char temp = text[subTextData->length]; + text[subTextData->length] = 0; + + context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); + + text[subTextData->length] = temp; + //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); + //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 index 7209ee6..e245c59 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -14,13 +14,20 @@ class TextElement : public NodeHandler }; static Node* Construct(Allocator& allocator, const char* text); - virtual void Draw(DrawContext& context, Node* element) override; virtual void GenerateLayout(Layout& layout, Node* node) override; }; class SubTextElement : public TextElement { public: - static Node* Construct(Allocator& allocator, const char* text); + 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 9500530..e837b56 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -112,11 +112,21 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) switch (node->type) { - case Node::Text: - case Node::SubText: + case Node::Text: { TextElement::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->text); + printf("<%s> [%d,%d:%d,%d]\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); + } + break; + case Node::SubText: + { + TextElement::Data* data = static_cast(node->parent->data); + SubTextElement::Data* subData = static_cast(node->data); + char* text = data->text + 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; } break; case Node::Section: diff --git a/src/Parser.cpp b/src/Parser.cpp index e622d66..e88bc8f 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -139,7 +139,7 @@ const char* ampersandEscapeSequences[14 * 2] = "amp", "&", "lt", "<", "gt", ">", - "nbsp", " ", + "nbsp", "\x1f", "pound", "£", "brvbar", "¦", "uml", "\"", From 9feffda506c23861671926a210b80cc07a1522fc Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 6 Mar 2024 09:38:15 +0000 Subject: [PATCH 34/98] Table layout WIP --- src/Layout.cpp | 35 +++++++++++++++++++++++++++++++++-- src/Nodes/Table.cpp | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Layout.cpp b/src/Layout.cpp index 7c9d484..ba24d0a 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -33,6 +33,7 @@ void Layout::PushCursor() else { // TODO error + exit(1); } } @@ -45,6 +46,7 @@ void Layout::PopCursor() else { // TODO: error + exit(1); } } @@ -58,6 +60,7 @@ void Layout::PushLayout() else { // TODO error + exit(1); } } @@ -70,6 +73,7 @@ void Layout::PopLayout() else { // TODO error + exit(1); } } @@ -242,6 +246,17 @@ void Layout::PadVertical(int 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; @@ -256,6 +271,12 @@ void Layout::RecalculateLayoutForNode(Node* targetNode) } else if (node->next) { + if (checkChildren) + { + // ??? + node->Handler().EndLayoutContext(*this, node); + } + node = node->next; checkChildren = true; } @@ -281,13 +302,17 @@ void Layout::RecalculateLayoutForNode(Node* targetNode) node->Handler().GenerateLayout(*this, node); } } - +#endif } void Layout::RecalculateLayout() { Reset(); - + + Node* node = page.GetRootNode(); + RecalculateLayoutForNode(node); + +#if 0 //for (Node* node = page.GetRootNode(); node; node = node->GetNextInTree()) //{ // node->Handler().GenerateLayout(*this, node); @@ -304,6 +329,11 @@ void Layout::RecalculateLayout() } else if (node->next) { + if (checkChildren) + { + node->Handler().EndLayoutContext(*this, node); + } + node = node->next; checkChildren = true; } @@ -329,6 +359,7 @@ void Layout::RecalculateLayout() }*/ } } +#endif // FIXME page.GetApp().ui.ScrollRelative(0); diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index a3772c4..4c86204 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -137,7 +137,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) { for (int i = 0; i < cell->columnSpan; i++) { - data->cells[j * data->numColumns + i] = cell; + data->cells[(rowIndex + j) * data->numColumns + columnIndex + i] = cell; } } From 43929200fbef635ef686a38180389c2547f7dee1 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 6 Mar 2024 10:10:29 +0000 Subject: [PATCH 35/98] Refactored various stacks to use dynamic allocation instead of fixed array sizes --- project/Windows/Windows.vcxproj | 1 + src/Layout.cpp | 62 ++-------------------------- src/Layout.h | 22 +++++----- src/Parser.cpp | 58 ++++++++++---------------- src/Parser.h | 12 +++--- src/Stack.h | 72 +++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 114 deletions(-) create mode 100644 src/Stack.h diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index f737ee0..36f1610 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -213,6 +213,7 @@ + diff --git a/src/Layout.cpp b/src/Layout.cpp index ba24d0a..cf0a460 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -5,14 +5,14 @@ #include "Render.h" Layout::Layout(Page& inPage) - : page(inPage) + : page(inPage), cursorStack(MemoryManager::pageAllocator), paramStack(MemoryManager::pageAllocator) { } void Layout::Reset() { - paramStackSize = 0; - cursorStackSize = 0; + paramStack.Reset(); + cursorStack.Reset(); currentLineHeight = 0; lineStartNode = nullptr; lastNodeContext = nullptr; @@ -23,60 +23,6 @@ void Layout::Reset() params.marginRight = page.GetPageWidth(); } -void Layout::PushCursor() -{ - if (cursorStackSize < MAX_CURSOR_STACK_SIZE - 1) - { - cursorStack[cursorStackSize + 1] = cursorStack[cursorStackSize]; - cursorStackSize++; - } - else - { - // TODO error - exit(1); - } -} - -void Layout::PopCursor() -{ - if (cursorStackSize > 0) - { - cursorStackSize--; - } - else - { - // TODO: error - exit(1); - } -} - -void Layout::PushLayout() -{ - if (paramStackSize < MAX_LAYOUT_PARAMS_STACK_SIZE - 1) - { - paramStack[paramStackSize + 1] = paramStack[paramStackSize]; - paramStackSize++; - } - else - { - // TODO error - exit(1); - } -} - -void Layout::PopLayout() -{ - if (paramStackSize > 0) - { - paramStackSize--; - } - else - { - // TODO error - exit(1); - } -} - void Layout::BreakNewLine() { // Recenter items if required @@ -365,6 +311,6 @@ void Layout::RecalculateLayout() page.GetApp().ui.ScrollRelative(0); #ifdef _WIN32 - page.DebugDumpNodeGraph(page.GetRootNode()); + //page.DebugDumpNodeGraph(page.GetRootNode()); #endif } diff --git a/src/Layout.h b/src/Layout.h index 1bfd6c5..b0ce4e4 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -1,9 +1,7 @@ #pragma once #include "Node.h" - -#define MAX_LAYOUT_PARAMS_STACK_SIZE 32 -#define MAX_CURSOR_STACK_SIZE 16 +#include "Stack.h" class Page; class Node; @@ -25,8 +23,8 @@ class Layout void BreakNewLine(); - void PushLayout(); - void PopLayout(); + void PushLayout() { paramStack.Push(); } + void PopLayout() { paramStack.Pop(); } void PadHorizontal(int left, int right); void PadVertical(int down); @@ -44,7 +42,7 @@ class Layout result.y += currentLineHeight - lineHeight; return result; } - LayoutParams& GetParams() { return paramStack[paramStackSize]; } + LayoutParams& GetParams() { return paramStack.Top(); } int AvailableWidth() { return GetParams().marginRight - Cursor().x; } int MaxAvailableWidth() { return GetParams().marginRight - GetParams().marginLeft; } @@ -53,18 +51,16 @@ class Layout Coord& Cursor() { - return cursorStack[cursorStackSize]; + return cursorStack.Top(); } - void PushCursor(); - void PopCursor(); + void PushCursor() { cursorStack.Push(); } + void PopCursor() { cursorStack.Pop(); } - Coord cursorStack[MAX_CURSOR_STACK_SIZE]; - int cursorStackSize; + Stack cursorStack; int currentLineHeight; - LayoutParams paramStack[MAX_LAYOUT_PARAMS_STACK_SIZE]; - int paramStackSize; + Stack paramStack; void TranslateNodes(Node* start, Node* end, int deltaX, int deltaY); }; diff --git a/src/Parser.cpp b/src/Parser.cpp index e88bc8f..6487a42 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -32,7 +32,7 @@ HTMLParser::HTMLParser(Page& inPage) : page(inPage) -, currentParseSection(SectionElement::Document) +, contextStack(MemoryManager::pageAllocator) , contextStackSize(0) , parseState(ParseText) , textBufferSize(0) @@ -45,10 +45,10 @@ void HTMLParser::Reset() { parseState = ParseText; textBufferSize = 0; - currentParseSection = SectionElement::Document; preformatted = 0; SetTextEncoding(TextEncoding::UTF8); + contextStack.Reset(); contextStackSize = -1; PushContext(page.GetRootNode(), nullptr); } @@ -59,46 +59,42 @@ void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) { return; } - if (contextStackSize < MAX_PARSE_CONTEXT_STACK_SIZE - 1) + + if (contextStackSize >= 0) { - if (contextStackSize >= 0) - { - Node* parentNode = CurrentContext().node; - parentNode->AddChild(node); - node->Handler().ApplyStyle(node); - } + Node* parentNode = CurrentContext().node; + parentNode->AddChild(node); + node->Handler().ApplyStyle(node); + } - contextStackSize++; - contextStack[contextStackSize].node = node; - contextStack[contextStackSize].tag = tag; + contextStack.Push(); + contextStackSize++; - if (node->type == Node::Section) - { - SectionElement::Data* data = static_cast(node->data); - currentParseSection = data->type; - } + contextStack.Top().node = node; + contextStack.Top().tag = tag; - node->Handler().BeginLayoutContext(page.layout, node); - } - else + if (node->type == Node::Section) { - // TODO: ERROR + SectionElement::Data* data = static_cast(node->data); + contextStack.Top().parseSection = data->type; } + + node->Handler().BeginLayoutContext(page.layout, node); } void HTMLParser::PopContext(const HTMLTagHandler* tag) { - for (int n = contextStackSize; n >= 0; n--) + while(contextStackSize >= 0) { - HTMLParseContext& parseContext = contextStack[n]; + HTMLParseContext& parseContext = contextStack.Top(); + contextStack.Pop(); + contextStackSize--; parseContext.node->Handler().EndLayoutContext(page.layout, parseContext.node); // Search for matching tag if (parseContext.tag == tag) { - contextStackSize = n - 1; - if (contextStackSize == 0) { page.GetRootNode()->EncapsulateChildren(); @@ -114,22 +110,12 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) page.GetApp().StopLoad(); } - // We may have exited a section so update - for (int i = contextStackSize; i >= 0; i--) - { - if (contextStack[i].node->type == Node::Section) - { - SectionElement::Data* data = static_cast(contextStack[i].node->data); - currentParseSection = data->type; - break; - } - } - return; } } // TODO: ERROR + exit(1); } #define NUM_AMPERSAND_ESCAPE_SEQUENCES 14 diff --git a/src/Parser.h b/src/Parser.h index 74f5ed4..3772a38 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -17,6 +17,7 @@ #include #include "Nodes/Section.h" +#include "Stack.h" class Page; class Node; @@ -37,6 +38,7 @@ struct HTMLParseContext { Node* node; const HTMLTagHandler* tag; + SectionElement::Type parseSection; }; class AttributeParser @@ -56,8 +58,6 @@ class AttributeParser char* value; }; -#define MAX_PARSE_CONTEXT_STACK_SIZE 32 - class HTMLParser { public: @@ -68,11 +68,11 @@ class HTMLParser Page& page; - SectionElement::Type CurrentSection() { return currentParseSection; } void PushContext(Node* node, const HTMLTagHandler* tag); void PopContext(const HTMLTagHandler* tag); - HTMLParseContext& CurrentContext() { return contextStack[contextStackSize]; } + HTMLParseContext& CurrentContext() { return contextStack.Top(); } + SectionElement::Type CurrentSection() { return CurrentContext().parseSection; } void EmitNode(Node* node); void EmitText(const char* text); @@ -108,9 +108,7 @@ class HTMLParser char textBuffer[2560]; size_t textBufferSize; - SectionElement::Type currentParseSection; - - HTMLParseContext contextStack[MAX_PARSE_CONTEXT_STACK_SIZE]; + Stack contextStack; int contextStackSize; bool parsingUnicode; diff --git a/src/Stack.h b/src/Stack.h new file mode 100644 index 0000000..a07cdd6 --- /dev/null +++ b/src/Stack.h @@ -0,0 +1,72 @@ +#ifndef _STACK_H_ +#define _STACK_H_ + +#include "Memory/Memory.h" + +template +class Stack +{ +public: + Stack(LinearAllocator& 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 + } + } + } + + void Pop() + { + if (top->prev) + { + top = top->prev; + } + } + +private: + struct Entry + { + T obj; + Entry* next; + Entry* prev; + }; + + LinearAllocator& allocator; + Entry base; + Entry* top; +}; + +#endif From d6fc15e451a1afb7cf65e775e4905e187e7024f8 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 6 Mar 2024 10:33:00 +0000 Subject: [PATCH 36/98] Table layout WIP --- src/Layout.cpp | 2 +- src/Nodes/ImgNode.cpp | 2 +- src/Nodes/Table.cpp | 47 ++++++++++++++++++++++++++++++++++++++----- src/Nodes/Table.h | 7 +++++-- src/Tags.cpp | 6 +++--- src/Tags.h | 4 +++- 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/Layout.cpp b/src/Layout.cpp index cf0a460..e37d8ee 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -35,7 +35,7 @@ void Layout::BreakNewLine() if (lineStartNode && lineStartNode->style.alignment == ElementAlignment::Center) { int shift = AvailableWidth() / 2; - // TranslateNodes(lineStartNode, lastNodeContext, shift, 0); + TranslateNodes(lineStartNode, lastNodeContext, shift, 0); } //if (lineStartNode && lineStartNode->parent) diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index d8404df..41a7c5a 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -11,7 +11,7 @@ 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 = 0; + uint8_t outlineColour = 7; if (data->image.lines) { diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 4c86204..d0e9fed 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -30,7 +30,7 @@ void TableNode::Draw(DrawContext& context, Node* node) uint8_t borderColour = Platform::video->colourScheme.textColour; int x = node->anchor.x - data->cellSpacing - data->cellPadding; int y = node->anchor.y - data->cellSpacing - data->cellPadding; - int w = node->size.x + (data->cellSpacing + data->cellPadding) * 2; + int w = data->totalWidth; // node->size.x + (data->cellSpacing + data->cellPadding) * 2; int h = node->size.y + (data->cellSpacing + data->cellPadding) * 2; context.surface->HLine(context, x, y, w, borderColour); context.surface->HLine(context, x, y + h - 1, w, borderColour); @@ -170,13 +170,14 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } - int totalPreferredWidth = data->cellSpacing * (data->numColumns - 1); + int totalPreferredWidth = data->cellSpacing * (data->numColumns + 1); for (int i = 0; i < data->numColumns; i++) { totalPreferredWidth += data->columns[i].preferredWidth; } - int maxAvailableWidth = layout.MaxAvailableWidth() - data->cellSpacing * 2; + int totalCellSpacing = (data->numColumns + 1) * data->cellSpacing; + int maxAvailableWidth = layout.MaxAvailableWidth() - totalCellSpacing; if (totalPreferredWidth > maxAvailableWidth) { // TODO resize widths to fit @@ -186,9 +187,30 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } + int totalWidth = totalCellSpacing; + for (int i = 0; i < data->numColumns; i++) + { + totalWidth += data->columns[i].preferredWidth; + } + data->totalWidth = totalWidth; + + layout.PushLayout(); + if (node->style.alignment == ElementAlignment::Center) + { + int available = layout.AvailableWidth(); + if (totalWidth < available) + { + layout.PadHorizontal((available - totalWidth) / 2, 0); + } + } + data->state = Data::FinalisingLayout; layout.RecalculateLayoutForNode(node); + + layout.PopLayout(); + layout.PadVertical(node->size.y + (data->cellPadding + data->cellSpacing) * 2); + data->state = Data::FinishedLayout; } @@ -267,9 +289,9 @@ void TableRowNode::EndLayoutContext(Layout& layout, Node* node) // Table cell node -Node* TableCellNode::Construct(Allocator& allocator) +Node* TableCellNode::Construct(Allocator& allocator, bool isHeader) { - TableCellNode::Data* data = allocator.Alloc(); + TableCellNode::Data* data = allocator.Alloc(isHeader); if (data) { Node* node = allocator.Alloc(Node::TableCell, data); @@ -279,6 +301,21 @@ Node* TableCellNode::Construct(Allocator& allocator) return nullptr; } +void TableCellNode::ApplyStyle(Node* node) +{ + TableCellNode::Data* data = static_cast(node->data); + if (data->isHeader) + { + node->style.alignment = ElementAlignment::Center; + node->style.fontStyle = FontStyle::Bold; + } + else + { + node->style.alignment = ElementAlignment::Left; + } +} + + void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) { TableCellNode::Data* data = static_cast(node->data); diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index 6724866..eff77ef 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -10,8 +10,9 @@ class TableCellNode : public NodeHandler class Data { public: - Data() : columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), nextCell(nullptr) {} + Data(bool inIsHeader) : isHeader(inIsHeader), columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), nextCell(nullptr) {} Node* node; + bool isHeader; int columnIndex; int rowIndex; int columnSpan; @@ -19,7 +20,8 @@ class TableCellNode : public NodeHandler TableCellNode::Data* nextCell; }; - static Node* Construct(Allocator& allocator); + 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; @@ -71,6 +73,7 @@ class TableNode : public NodeHandler int numRows; int cellSpacing; int cellPadding; + int totalWidth; ColumnInfo* columns; TableRowNode::Data* firstRow; TableCellNode::Data** cells; diff --git a/src/Tags.cpp b/src/Tags.cpp index 559391e..0bf4851 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -83,8 +83,8 @@ static const HTMLTagHandler* tagHandlers[] = new PreformattedTagHandler("pre"), new TableTagHandler(), new TableRowTagHandler(), - new TableCellTagHandler("td"), - new TableCellTagHandler("th"), + new TableCellTagHandler("td", false), + new TableCellTagHandler("th", true), NULL }; @@ -575,7 +575,7 @@ void TableRowTagHandler::Close(class HTMLParser& parser) const void TableCellTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(TableCellNode::Construct(MemoryManager::pageAllocator), this); + parser.PushContext(TableCellNode::Construct(MemoryManager::pageAllocator, isHeader), this); } void TableCellTagHandler::Close(class HTMLParser& parser) const diff --git a/src/Tags.h b/src/Tags.h index 8affaf9..028449d 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -197,9 +197,11 @@ class TableRowTagHandler : public HTMLTagHandler class TableCellTagHandler : public HTMLTagHandler { public: - TableCellTagHandler(const char* name) : HTMLTagHandler(name) {} + 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; }; const HTMLTagHandler* DetermineTag(const char* str); From b168d26215f1426fa5d9fd4fb68c98e6f8890dbb Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 7 Mar 2024 15:47:20 +0000 Subject: [PATCH 37/98] Added EMS support --- project/DOS/DOS.vcxproj | 2 + project/DOS/Makefile | 5 +- src/DOS/EMS.cpp | 113 +++++++++++++++++++++++++++++++++++++++ src/DOS/EMS.h | 35 ++++++++++++ src/DOS/Platform.cpp | 3 ++ src/HTTP.cpp | 7 ++- src/Image/Decoder.cpp | 14 +++-- src/Image/Gif.cpp | 18 ++++++- src/Interface.cpp | 2 +- src/Memory/MemBlock.cpp | 77 +++++++++++++++++++++++++- src/Memory/MemBlock.h | 17 +++++- src/Nodes/Table.cpp | 5 ++ src/Nodes/Table.h | 1 + src/Nodes/Text.cpp | 25 +++++---- src/Nodes/Text.h | 5 +- src/Page.cpp | 3 +- src/Parser.cpp | 23 +++++++- src/Stack.h | 7 +-- src/Windows/Platform.cpp | 3 ++ 19 files changed, 336 insertions(+), 29 deletions(-) create mode 100644 src/DOS/EMS.cpp create mode 100644 src/DOS/EMS.h diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 2440e2d..5995bc8 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -103,6 +103,7 @@ + @@ -129,6 +130,7 @@ + diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 1276cab..5fb9fda 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj EMS.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -121,6 +121,9 @@ MemBlock.obj: $(SRC_PATH)\Memory\MemBlock.cpp 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) $< diff --git a/src/DOS/EMS.cpp b/src/DOS/EMS.cpp new file mode 100644 index 0000000..40e4f4c --- /dev/null +++ b/src/DOS/EMS.cpp @@ -0,0 +1,113 @@ +#include "EMS.h" +#include +#include +#include + +#define EMS_INTERRUPT_NUMBER 0x67 + +void EMSManager::Init() +{ + union REGS inregs, outregs; + + // Check for EMS driver + inregs.h.ah = 0x40; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + if (outregs.h.ah) { + // No EMS present + 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; + mappedPage = 0xffff; + + 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) + { + if (mappedPage != handle.emsPage) + { + union REGS inregs, outregs; + inregs.h.ah = 0x44; + inregs.h.al = 0; + inregs.x.bx = handle.emsPage; + inregs.x.dx = allocationHandle; + int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); + mappedPage = handle.emsPage; + } + + return MK_FP(pageAddressSegment, handle.emsPageOffset); + } + + return nullptr; +} diff --git a/src/DOS/EMS.h b/src/DOS/EMS.h new file mode 100644 index 0000000..2ca9a93 --- /dev/null +++ b/src/DOS/EMS.h @@ -0,0 +1,35 @@ +#ifndef _EMS_H_ +#define _EMS_H_ + +#include +#include "../Memory/MemBlock.h" + +#define EMS_PAGE_SIZE (16 * 1024l) + +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(); + +private: + bool isAvailable; + int numAllocatedPages; + uint16_t pageAddressSegment; + uint16_t allocationHandle; + + uint16_t allocationPageIndex; + uint16_t allocationPageUsed; + uint16_t mappedPage; +}; + +#endif diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 222069e..d16b246 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -24,6 +24,7 @@ #include "../Cursor.h" #include "../Font.h" #include "../Draw/Surface.h" +#include "../Memory/Memory.h" static DOSInputDriver DOSinput; static DOSNetworkDriver DOSNet; @@ -175,6 +176,7 @@ bool Platform::Init(int argc, char* argv[]) video->Init(videoMode); video->drawSurface->Clear(); input->Init(); + MemoryManager::pageBlockAllocator.Init(); if (inverse) { @@ -187,6 +189,7 @@ bool Platform::Init(int argc, char* argv[]) void Platform::Shutdown() { + MemoryManager::pageBlockAllocator.Shutdown(); input->Shutdown(); video->Shutdown(); network->Shutdown(); diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 2996552..8a4cff2 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -350,6 +350,8 @@ void HTTPRequest::Update() bool HTTPRequest::ReadLine() { + bool allowBufferTruncation = true; + while (1) { int rc = sock->Receive((unsigned char*)lineBuffer + lineBufferSize, 1); @@ -395,7 +397,10 @@ bool HTTPRequest::ReadLine() return true; } - lineBufferSize++; + if (!allowBufferTruncation || lineBufferSize < LINE_BUFFER_SIZE - 1) + { + lineBufferSize++; + } } } diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 2707645..10053e9 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -10,12 +10,18 @@ union char buffer[1]; } ImageDecoderUnion; +#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 +// 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] = diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index cc93f2d..4054364 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -101,6 +101,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { // Allocation error DEBUG_MESSAGE("Could not allocate!\n"); + outputImage->lines = nullptr; state = ImageDecoder::Error; return; } @@ -108,6 +109,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) if (pixels) { memset(pixels, TRANSPARENT_COLOUR_VALUE, outputImage->pitch); + outputImage->lines[j].Commit(); } } @@ -654,7 +656,7 @@ void GifDecoder::EmitLine(int y) if (outputImage->bpp == 8) { - bool useColourDithering = false; + bool useColourDithering = true; if (useColourDithering) { @@ -665,7 +667,7 @@ void GifDecoder::EmitLine(int y) { for (int i = 0; i < lineBufferSize; i++) { - int offset = ditherPattern[ditherIndex]; + int8_t offset = ditherPattern[ditherIndex]; ditherIndex = (ditherIndex + 1) & 3; if (lineBuffer[i] == transparentColourIndex) @@ -678,12 +680,18 @@ void GifDecoder::EmitLine(int y) 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)]; } @@ -710,12 +718,18 @@ void GifDecoder::EmitLine(int y) 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)]; } diff --git a/src/Interface.cpp b/src/Interface.cpp index a9a8e3c..e39d82b 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -308,7 +308,7 @@ void AppInterface::GenerateInterfaceNodes() { titleBuffer[0] = '\0'; - TextElement::Data* titleNodeData = allocator.Alloc(titleBuffer); + TextElement::Data* titleNodeData = allocator.Alloc(MemBlockHandle (titleBuffer)); titleNode = allocator.Alloc(Node::Text, titleNodeData); titleNode->anchor.Clear(); titleNode->size.x = Platform::video->screenWidth; diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index 2a9dc2a..ef98c1e 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -1,7 +1,14 @@ +#include #include "MemBlock.h" #include "LinAlloc.h" #include "Memory.h" +#ifdef __DOS__ +#include "../DOS/EMS.h" + +static EMSManager ems; +#endif + void* MemBlockHandle::GetPtr() { switch (type) @@ -12,7 +19,14 @@ void* MemBlockHandle::GetPtr() { return MemoryManager::pageBlockAllocator.AccessSwap(*this); } +#ifdef __DOS__ + case MemBlockHandle::EMS: + { + return ems.MapBlock(*this); + } +#endif default: + exit(1); return nullptr; } } @@ -29,23 +43,72 @@ void MemBlockHandle::Commit() } MemBlockAllocator::MemBlockAllocator() + : swapFile(nullptr) + , swapFileLength(0) + , swapBuffer(nullptr) + , lastSwapRead(-1) + , maxSwapSize(0) { +} + +void MemBlockAllocator::Init() +{ + // Disable swap for now //swapFile = fopen("Microweb.swp", "w+"); - swapFile = NULL; + if (swapFile) { swapBuffer = malloc(MAX_SWAP_ALLOCATION); lastSwapRead = -1; swapFileLength = 0; + maxSwapSize = MAX_SWAP_SIZE; + } + +#ifdef __DOS__ + 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(size_t size) { MemBlockHandle result; - if (swapFile && size <= MAX_SWAP_ALLOCATION) +#ifdef __DOS__ + if (ems.IsAvailable()) + { + result = ems.Allocate(size); + if (result.IsAllocated()) + { + return result; + } + } +#endif + + if (swapFile && size <= MAX_SWAP_ALLOCATION && swapFileLength + size < maxSwapSize) { result.swapFilePosition = swapFileLength; result.allocatedSize = size; @@ -98,3 +161,13 @@ void MemBlockAllocator::CommitSwap(MemBlockHandle& handle) fseek(swapFile, handle.swapFilePosition, SEEK_SET); fwrite(swapBuffer, 1, handle.allocatedSize, swapFile); } + +void MemBlockAllocator::Reset() +{ + swapFileLength = 0; + lastSwapRead = -1; + +#ifdef __DOS__ + ems.Reset(); +#endif +} diff --git a/src/Memory/MemBlock.h b/src/Memory/MemBlock.h index 527d334..c938896 100644 --- a/src/Memory/MemBlock.h +++ b/src/Memory/MemBlock.h @@ -5,7 +5,7 @@ #include #define MAX_SWAP_ALLOCATION (1024) -#define MAX_SWAP_SIZE (1024 * 1024) +#define MAX_SWAP_SIZE (1024l * 1024l) // Abstract way of allocating a chunk of memory from conventional memory, EMS, disk swap @@ -22,6 +22,7 @@ struct MemBlockHandle Type type; MemBlockHandle() : type(Unallocated), allocatedSize(0) {} + MemBlockHandle(void* buffer) : type(Conventional), conventionalPointer(buffer) {} void* GetPtr(); template @@ -34,7 +35,12 @@ struct MemBlockHandle { void* conventionalPointer; long swapFilePosition; - // TODO: EMS, disk swap + + struct + { + uint16_t emsPage; + uint16_t emsPageOffset; + }; }; size_t allocatedSize; }; @@ -46,7 +52,13 @@ class MemBlockAllocator public: MemBlockAllocator(); + void Init(); + void Shutdown(); + MemBlockHandle Allocate(size_t size); + MemBlockHandle AllocString(const char* inString); + + void Reset(); private: friend struct MemBlockHandle; @@ -57,6 +69,7 @@ class MemBlockAllocator long swapFileLength; void* swapBuffer; long lastSwapRead; + long maxSwapSize; }; diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index d0e9fed..c4cea59 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -287,6 +287,11 @@ void TableRowNode::EndLayoutContext(Layout& layout, Node* node) } } +void TableRowNode::ApplyStyle(Node* node) +{ + node->style.alignment = ElementAlignment::Left; +} + // Table cell node Node* TableCellNode::Construct(Allocator& allocator, bool isHeader) diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index eff77ef..712f799 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -43,6 +43,7 @@ class TableRowNode : public NodeHandler 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 diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 4eba199..c7927c6 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -8,10 +8,10 @@ Node* TextElement::Construct(Allocator& allocator, const char* text) { - const char* textString = allocator.AllocString(text); - if (textString) + MemBlockHandle textHandle = MemoryManager::pageBlockAllocator.AllocString(text); + if (textHandle.IsAllocated()) { - TextElement::Data* data = allocator.Alloc(textString); + TextElement::Data* data = allocator.Alloc(textHandle); if (data) { return allocator.Alloc(Node::Text, data); @@ -71,18 +71,19 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) child->size.Clear(); } - char* text = data->text; + 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 = data->text[charIndex]; - bool isEnd = data->text[charIndex + 1] == 0; + char c = text[charIndex]; + bool isEnd = text[charIndex + 1] == 0; if (c == ' ' || c == '\t') { @@ -93,7 +94,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) if (c == '\x1f') { // Non breaking space - data->text[charIndex] = ' '; + text[charIndex] = ' '; + hasModified = true; } int glyphWidth = font->GetGlyphWidth(c, node->style.fontStyle); @@ -173,6 +175,11 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) } } + if (hasModified) + { + data->text.Commit(); + } + node->EncapsulateChildren(); } @@ -197,11 +204,11 @@ 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) + if (textData && subTextData && textData->text.IsAllocated()) { Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); uint8_t textColour = node->style.fontColour; - char* text = textData->text + subTextData->startIndex; + char* text = textData->text.Get() + subTextData->startIndex; char temp = text[subTextData->length]; text[subTextData->length] = 0; diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h index e245c59..c1b6574 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -1,6 +1,7 @@ #pragma once #include "../Node.h" +#include "../Memory/MemBlock.h" class TextElement : public NodeHandler { @@ -8,8 +9,8 @@ class TextElement : public NodeHandler class Data { public: - Data(const char* inText) : text(const_cast(inText)), lastAvailableWidth(-1) {} - char* text; + Data(MemBlockHandle& inText) : text(inText), lastAvailableWidth(-1) {} + MemBlockHandle text; int lastAvailableWidth; }; diff --git a/src/Page.cpp b/src/Page.cpp index e837b56..64ccfb0 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -44,6 +44,7 @@ void Page::Reset() cursorY = TOP_MARGIN_PADDING; MemoryManager::pageAllocator.Reset(); + MemoryManager::pageBlockAllocator.Reset(); rootNode = SectionElement::Construct(MemoryManager::pageAllocator, SectionElement::Document); rootNode->style.alignment = ElementAlignment::Left; @@ -122,7 +123,7 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) { TextElement::Data* data = static_cast(node->parent->data); SubTextElement::Data* subData = static_cast(node->data); - char* text = data->text + subData->startIndex; + 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); diff --git a/src/Parser.cpp b/src/Parser.cpp index 6487a42..9984700 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -84,6 +84,27 @@ void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) void HTMLParser::PopContext(const HTMLTagHandler* tag) { + if (contextStackSize >= 0) + { + 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) + { + if (entry->obj.tag == tag) + { + hasEntry = true; + break; + } + } + + if (!hasEntry) + { + return; + } + } + + // Keep popping contexts until we get to the matching tag while(contextStackSize >= 0) { HTMLParseContext& parseContext = contextStack.Top(); @@ -92,9 +113,9 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) parseContext.node->Handler().EndLayoutContext(page.layout, parseContext.node); - // Search for matching tag if (parseContext.tag == tag) { + // If the stack is emptied then we have finished parsing the document if (contextStackSize == 0) { page.GetRootNode()->EncapsulateChildren(); diff --git a/src/Stack.h b/src/Stack.h index a07cdd6..dc66926 100644 --- a/src/Stack.h +++ b/src/Stack.h @@ -56,7 +56,6 @@ class Stack } } -private: struct Entry { T obj; @@ -64,9 +63,11 @@ class Stack Entry* prev; }; - LinearAllocator& allocator; - Entry base; Entry* top; + +private: + Entry base; + LinearAllocator& allocator; }; #endif diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 3f7d9c6..b42b9c3 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -19,6 +19,7 @@ #include "WinNet.h" #include "../Draw/Surface.h" #include "../VidModes.h" +#include "../Memory/Memory.h" WindowsVideoDriver winVid; WindowsNetworkDriver winNetworkDriver; @@ -44,12 +45,14 @@ bool Platform::Init(int argc, char* argv[]) video->drawSurface->Clear(); input->Init(); input->ShowMouse(); + MemoryManager::pageBlockAllocator.Init(); return true; } void Platform::Shutdown() { + MemoryManager::pageBlockAllocator.Shutdown(); input->Shutdown(); video->Shutdown(); network->Shutdown(); From bf14e9a9c01fda1ea40fdf50bf84dd9acdfe9340 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 7 Mar 2024 21:39:45 +0000 Subject: [PATCH 38/98] Added composite CGA video modes --- project/DOS/Makefile | 5 +- project/Windows/Windows.vcxproj | 2 + src/Colour.cpp | 24 ++ src/Colour.h | 4 + src/DOS/BIOSVid.cpp | 48 +++- src/DOS/DOSInput.cpp | 5 + src/DOS/Platform.cpp | 8 +- src/DataPack.cpp | 32 ++- src/DataPack.h | 4 +- src/Draw/Surf1bpp.cpp | 13 +- src/Draw/Surf2bpp.cpp | 481 ++++++++++++++++++++++++++++++++ src/Draw/Surf2bpp.h | 22 ++ src/Palettes.inc | 38 ++- src/Parser.cpp | 2 +- src/VidModes.cpp | 2 + src/VidModes.h | 1 + src/Windows/Platform.cpp | 2 +- src/Windows/WinVid.cpp | 63 ++++- tools/PaletteGen.cpp | 46 ++- 19 files changed, 766 insertions(+), 36 deletions(-) create mode 100644 src/Draw/Surf2bpp.cpp create mode 100644 src/Draw/Surf2bpp.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 5fb9fda..2cd5c22 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj EMS.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj EMS.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -145,6 +145,9 @@ DOSNet.obj: $(SRC_PATH)\DOS\DOSNet.cpp 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) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 36f1610..66747b5 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -143,6 +143,7 @@ + @@ -183,6 +184,7 @@ + diff --git a/src/Colour.cpp b/src/Colour.cpp index 5d5928c..afe84df 100644 --- a/src/Colour.cpp +++ b/src/Colour.cpp @@ -13,6 +13,30 @@ ColourScheme monochromeColourScheme = 1 }; +ColourScheme cgaColourScheme = +{ + // Page + 0xff, + // Text + 0, + // Link + 0x55, + // Button + 0xff +}; + +ColourScheme compositeCgaColourScheme = +{ + // Page + 0xff, + // Text + 0, + // Link + 0x22, + // Button + 0x55 +}; + ColourScheme egaColourScheme = { // Page diff --git a/src/Colour.h b/src/Colour.h index 7482eed..d1d41a8 100644 --- a/src/Colour.h +++ b/src/Colour.h @@ -25,8 +25,12 @@ struct NamedColour 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/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp index d1d3236..56a193a 100644 --- a/src/DOS/BIOSVid.cpp +++ b/src/DOS/BIOSVid.cpp @@ -23,6 +23,7 @@ #include "../Interface.h" #include "../DataPack.h" #include "../Draw/Surf1bpp.h" +#include "../Draw/Surf2bpp.h" #include "../Draw/Surf4bpp.h" #include "../Draw/Surf8bpp.h" #include "../VidModes.h" @@ -41,7 +42,16 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) SetScreenMode(videoModeInfo->biosVideoMode); - Assets.LoadPreset(videoModeInfo->dataPackIndex); + if (videoModeInfo->biosVideoMode == CGA_COMPOSITE_MODE) + { + SetScreenMode(6); + outp(0x3D8, 0x1a); + Assets.LoadPreset(videoModeInfo->dataPackIndex); + } + else + { + Assets.LoadPreset(videoModeInfo->dataPackIndex); + } int screenPitch = 0; @@ -52,12 +62,28 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) colourScheme = monochromeColourScheme; paletteLUT = nullptr; } + else if (videoModeInfo->bpp == 2) + { + drawSurface = new DrawSurface_2BPP(screenWidth, screenHeight); + screenPitch = screenWidth / 4; + + if (videoModeInfo->biosVideoMode == CGA_COMPOSITE_MODE) + { + paletteLUT = compositeCgaPaletteLUT; + colourScheme = compositeCgaColourScheme; + } + else + { + paletteLUT = cgaPaletteLUT; + colourScheme = cgaColourScheme; + } + } else if (videoModeInfo->bpp == 4) { drawSurface = new DrawSurface_4BPP(screenWidth, screenHeight); screenPitch = screenWidth / 8; colourScheme = egaColourScheme; - paletteLUT = cgaPaletteLUT; + paletteLUT = egaPaletteLUT; } else if (videoModeInfo->bpp == 8) { @@ -173,9 +199,14 @@ void BIOSVideoDriver::SetScreenMode(int screenMode) void BIOSVideoDriver::ScaleImageDimensions(int& width, int& height) { + if (screenWidth <= 320) + { + width = (3l * width) / 4; + height = (3l * height) / 4; + } + height /= videoModeInfo->aspectRatio; - // Scale to 4:3 - //height = (height * 5) / 12; + int maxWidth = screenWidth - 16; if (width > maxWidth) @@ -183,4 +214,13 @@ void BIOSVideoDriver::ScaleImageDimensions(int& width, int& height) height = ((long)height * maxWidth) / width; width = maxWidth; } + + if (width == 0) + { + width = 1; + } + if (height == 0) + { + height = 1; + } } diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index 06172b8..d472de2 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -135,6 +135,11 @@ void DOSInputDriver::GetMouseStatus(int& buttons, int& x, int& y) x = outreg.x.cx; y = outreg.x.dx; buttons = outreg.x.bx; + + if (Platform::video->screenWidth == 320) + { + x /= 2; + } } InputButtonCode DOSInputDriver::GetKeyPress() diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index d16b246..10507b6 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -92,11 +92,11 @@ bool DetectHercules(); static int AutoDetectVideoMode() { union REGS inreg, outreg; - const int HP95LX = 11; - const int Hercules = 8; + const int HP95LX = 13; + const int Hercules = 10; const int CGA = 0; - const int EGA = 4; - const int VGA = 6; + const int EGA = 6; + const int VGA = 8; // Look for HP 95LX inreg.x.ax = 0x4dd4; diff --git a/src/DataPack.cpp b/src/DataPack.cpp index f8bcaa4..b31cfb7 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -16,13 +16,13 @@ const char* DataPack::datapackFilenames[] = "LOWRES.DAT" }; -bool DataPack::LoadPreset(DataPack::Preset preset) +bool DataPack::LoadPreset(DataPack::Preset preset, bool forceBold) { - return Load(datapackFilenames[preset]); + return Load(datapackFilenames[preset], forceBold); } -bool DataPack::Load(const char* path) +bool DataPack::Load(const char* path, bool forceBold) { FILE* fs = fopen(path, "rb"); @@ -43,19 +43,33 @@ bool DataPack::Load(const char* path) imageIcon = LoadImageAsset(fs, header, "IIMG"); - fonts[0] = (Font*)LoadAsset(fs, header, "FHELV1"); - fonts[1] = (Font*)LoadAsset(fs, header, "FHELV2"); - fonts[2] = (Font*)LoadAsset(fs, header, "FHELV3"); boldFonts[0] = (Font*)LoadAsset(fs, header, "FHELV1B"); boldFonts[1] = (Font*)LoadAsset(fs, header, "FHELV2B"); boldFonts[2] = (Font*)LoadAsset(fs, header, "FHELV3B"); - monoFonts[0] = (Font*)LoadAsset(fs, header, "FCOUR1"); - monoFonts[1] = (Font*)LoadAsset(fs, header, "FCOUR2"); - monoFonts[2] = (Font*)LoadAsset(fs, header, "FCOUR3"); boldMonoFonts[0] = (Font*)LoadAsset(fs, header, "FCOUR1B"); boldMonoFonts[1] = (Font*)LoadAsset(fs, header, "FCOUR2B"); boldMonoFonts[2] = (Font*)LoadAsset(fs, header, "FCOUR3B"); + if (forceBold) + { + fonts[0] = boldFonts[0]; + fonts[1] = boldFonts[1]; + fonts[2] = boldFonts[2]; + monoFonts[0] = boldMonoFonts[0]; + monoFonts[1] = boldMonoFonts[1]; + monoFonts[2] = boldMonoFonts[2]; + } + else + { + 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); diff --git a/src/DataPack.h b/src/DataPack.h index 2a6f10d..ce8754e 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -70,8 +70,8 @@ struct DataPack Font* monoFonts[NUM_FONT_SIZES]; Font* boldMonoFonts[NUM_FONT_SIZES]; - bool LoadPreset(Preset preset); - bool Load(const char* path); + bool LoadPreset(Preset preset, bool forceBold = false); + bool Load(const char* path, bool forceBold = false); Font* GetFont(int fontSize, FontStyle::Type fontStyle); MouseCursorData* GetMouseCursorData(MouseCursor::Type type); diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 0943ca7..ecef147 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -47,8 +47,8 @@ void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint { data |= mask; x++; - mask = (mask >> 1) | 0x80; - if ((x & 7) == 0) + mask >>= 1; + if (!mask) { *VRAMptr++ = data; while (count > 8) @@ -181,9 +181,8 @@ void DrawSurface_1BPP::FillRect(DrawContext& context, int x, int y, int width, i while (count--) { data |= mask; - workX++; - mask = (mask >> 1) | 0x80; - if ((workX & 7) == 0) + mask >>= 1; + if (!mask) { *VRAMptr++ = data; while (count > 8) @@ -474,17 +473,15 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, uint8_t* VRAMptr = lines[y]; VRAMptr += (x >> 3); int count = width; - int workX = x; uint8_t data = *VRAMptr; uint8_t mask = (0x80 >> (x & 7)); while (count--) { data ^= mask; - workX++; //mask = (mask >> 1) | 0x80; mask >>= 1; - if ((workX & 7) == 0) + if (!mask) { *VRAMptr++ = data; while (count > 8) diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp new file mode 100644 index 0000000..299dec1 --- /dev/null +++ b/src/Draw/Surf2bpp.cpp @@ -0,0 +1,481 @@ +#include +#include "Surf2BPP.h" +#include "../Font.h" +#include "../Image/Image.h" +#include "../Memory/MemBlock.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]; + bpp = 2; +} + +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; + } + + 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 - 1 - y; + } + if (count <= 0) + { + return; + } + + 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; + } + + 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; + } + + 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->glyphWidth[index]; + + if (glyphWidth == 0) + { + continue; + } + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); + + glyphData += (firstLine * font->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 < font->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) + 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++) + { + uint8_t* src = image->lines[j + srcY].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; +// srcBuffer |= (srcBuffer << 4); + destBuffer = (destBuffer & (~destMask)) | ((srcBuffer) & destMask); + 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 = (0xc0 >> (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 0 + 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; + + 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; + } +#endif +} + +void DrawSurface_2BPP::Clear() +{ + int widthBytes = width >> 2; + for (int y = 0; y < height; y++) + { + memset(lines[y], 0xff, widthBytes); + } +} diff --git a/src/Draw/Surf2bpp.h b/src/Draw/Surf2bpp.h new file mode 100644 index 0000000..ed782d6 --- /dev/null +++ b/src/Draw/Surf2bpp.h @@ -0,0 +1,22 @@ +#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); +}; + +#endif diff --git a/src/Palettes.inc b/src/Palettes.inc index a4772ef..0836ac3 100644 --- a/src/Palettes.inc +++ b/src/Palettes.inc @@ -1,4 +1,4 @@ -uint8_t cgaPaletteLUT[] = { +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, @@ -16,3 +16,39 @@ uint8_t cgaPaletteLUT[] = { 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, 85, 0, 0, 0, 85, 0, 0, 85, 85, + 0, 0, 85, 85, 0, 0, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, + 0, 0, 0, 0, 0, 0, 0, 85, 0, 0, 0, 85, 0, 0, 85, 85, + 0, 0, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, + 0, 0, 0, 85, 0, 0, 0, 85, 0, 0, 0, 85, 0, 0, 85, 85, + 0, 0, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, 85, 85, 85, 85, + 0, 0, 170, 170, 0, 0, 170, 85, 0, 170, 170, 85, 0, 170, 85, 85, + 170, 170, 85, 85, 170, 170, 85, 85, 170, 85, 85, 85, 170, 85, 85, 85, + 0, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 85, 170, 170, 170, 85, + 170, 170, 85, 85, 170, 170, 85, 85, 170, 170, 85, 85, 170, 85, 85, 85, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 255, + 170, 170, 170, 255, 170, 170, 255, 255, 170, 170, 255, 255, 170, 170, 255, 255, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 255, + 170, 170, 170, 255, 170, 170, 255, 255, 170, 170, 255, 255, 170, 170, 255, 255, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 255, + 170, 170, 170, 255, 170, 170, 255, 255, 170, 170, 255, 255, 170, 170, 255, 255 +}; +uint8_t compositeCgaPaletteLUT[] = { + 0, 0, 34, 34, 0, 17, 34, 34, 136, 17, 51, 51, 17, 17, 51, 51, + 17, 17, 51, 51, 153, 17, 51, 51, 153, 153, 187, 51, 153, 153, 187, 187, + 0, 0, 34, 34, 0, 17, 34, 34, 136, 17, 34, 34, 136, 17, 51, 51, + 136, 17, 51, 51, 153, 17, 187, 51, 153, 153, 187, 187, 153, 153, 187, 187, + 0, 68, 34, 34, 136, 85, 34, 34, 136, 85, 85, 34, 136, 85, 85, 51, + 136, 85, 85, 51, 153, 85, 187, 51, 153, 187, 187, 187, 153, 187, 187, 187, + 68, 68, 34, 34, 136, 68, 85, 34, 136, 85, 85, 34, 136, 85, 85, 119, + 136, 85, 85, 119, 221, 85, 187, 119, 221, 85, 187, 187, 221, 187, 187, 187, + 68, 68, 68, 102, 68, 68, 85, 102, 68, 85, 85, 119, 204, 85, 85, 119, + 221, 85, 85, 119, 221, 85, 85, 119, 221, 221, 187, 119, 221, 221, 187, 187, + 68, 68, 102, 102, 68, 68, 102, 102, 204, 68, 238, 119, 204, 85, 238, 119, + 204, 85, 238, 119, 221, 85, 119, 119, 221, 221, 119, 119, 221, 221, 255, 255, + 68, 68, 102, 102, 204, 68, 102, 102, 204, 204, 238, 102, 204, 204, 238, 119, + 204, 204, 238, 119, 221, 221, 238, 119, 221, 221, 238, 255, 221, 221, 255, 255, + 68, 68, 102, 102, 204, 68, 102, 102, 204, 204, 238, 102, 204, 204, 238, 238, + 204, 204, 238, 119, 221, 238, 238, 255, 221, 221, 238, 255, 221, 221, 255, 255 +}; diff --git a/src/Parser.cpp b/src/Parser.cpp index 9984700..4d36ebe 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -125,7 +125,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) #ifdef _WIN32 page.DebugDumpNodeGraph(page.GetRootNode()); #endif - context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, 0xf); + context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); page.DebugDraw(context, page.GetRootNode()); page.GetApp().StopLoad(); diff --git a/src/VidModes.cpp b/src/VidModes.cpp index 87b56b0..41078b9 100644 --- a/src/VidModes.cpp +++ b/src/VidModes.cpp @@ -7,6 +7,8 @@ VideoModeInfo VideoModeList[] = // name mode width height bpp aspect data pack vram1 vram2 vram3 vram4 { "640x200 monochrome (CGA)", 6, 640, 200, 1, 2.4f, DataPack::CGA, 0xb800, 0xba00 }, { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 1.0f, DataPack::Default, 0xb800, 0xba00 }, + { "320x200 4 colours (CGA)", 5, 320, 200, 2, 1.2f, DataPack::Lowres, 0xb800, 0xba00 }, + { "320x200 16 colours (Composite CGA)", 4, 320, 200, 2, 1.2f, DataPack::CGA, 0xb800, 0xba00 }, { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 2.4f, DataPack::CGA, 0xa000, }, { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 1.37f, DataPack::EGA, 0xa000, }, { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 1.37f, DataPack::EGA, 0xa000, }, diff --git a/src/VidModes.h b/src/VidModes.h index 83f7508..ade79db 100644 --- a/src/VidModes.h +++ b/src/VidModes.h @@ -5,6 +5,7 @@ #include "Datapack.h" #define HERCULES_MODE 0 +#define CGA_COMPOSITE_MODE 4 struct VideoModeInfo { diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index b42b9c3..b44e4fb 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -34,7 +34,7 @@ HWND hWnd; bool Platform::Init(int argc, char* argv[]) { - VideoModeInfo* videoMode = ShowVideoModePicker(6); + VideoModeInfo* videoMode = ShowVideoModePicker(8); if (!videoMode) { return false; diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 07243b3..5235c76 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -42,6 +42,35 @@ const RGBQUAD monoPalette[] = }; 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 }, +}; + + +const RGBQUAD egaPalette[] = { { 0x00, 0x00, 0x00 }, // Entry 0 - Black { 0xAA, 0x00, 0x00 }, // Entry 1 - Blue @@ -108,7 +137,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* videoMode) if (useColour) { - memcpy(bitmapInfo->bmiColors, cgaPalette, sizeof(RGBQUAD) * 16); + memcpy(bitmapInfo->bmiColors, egaPalette, sizeof(RGBQUAD) * 16); if (videoMode->bpp == 8) { @@ -127,6 +156,23 @@ void WindowsVideoDriver::Init(VideoModeInfo* videoMode) } } } + else if (videoMode->bpp == 2) + { + 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); + } + } + } } else { @@ -174,10 +220,23 @@ void WindowsVideoDriver::Init(VideoModeInfo* videoMode) paletteLUT[n] = RGB666(rgbRed, rgbGreen, rgbBlue); } } + else if (videoMode->bpp == 2) + { + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) + { + colourScheme = compositeCgaColourScheme; + paletteLUT = compositeCgaPaletteLUT; + } + else + { + colourScheme = cgaColourScheme; + paletteLUT = cgaPaletteLUT; + } + } else { colourScheme = egaColourScheme; - paletteLUT = cgaPaletteLUT; + paletteLUT = egaPaletteLUT; } } else diff --git a/tools/PaletteGen.cpp b/tools/PaletteGen.cpp index 5a52367..aa52425 100644 --- a/tools/PaletteGen.cpp +++ b/tools/PaletteGen.cpp @@ -2,7 +2,7 @@ #include #include -const RGBQUAD cgaPalette[] = +const RGBQUAD egaPalette[] = { { 0x00, 0x00, 0x00 }, // Entry 0 - Black { 0xAA, 0x00, 0x00 }, // Entry 1 - Blue @@ -22,6 +22,34 @@ const RGBQUAD cgaPalette[] = { 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 }, +}; + int GetClosestPaletteIndex(const RGBQUAD colour, const RGBQUAD* palette, int paletteSize) { int closest = -1; @@ -44,7 +72,7 @@ int GetClosestPaletteIndex(const RGBQUAD colour, const RGBQUAD* palette, int pal return closest; } -void GeneratePaletteLUT(FILE* fs, const char* name, const RGBQUAD* palette, int paletteSize) +void GeneratePaletteLUT(FILE* fs, const char* name, const RGBQUAD* palette, int paletteSize, bool fillByte) { RGBQUAD colour; int count = 0; @@ -62,6 +90,16 @@ void GeneratePaletteLUT(FILE* fs, const char* name, const RGBQUAD* palette, int 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) { @@ -84,7 +122,9 @@ void GeneratePaletteLUTs(const char* filename) if (!fopen_s(&fs, filename, "w")) { - GeneratePaletteLUT(fs, "cgaPaletteLUT", cgaPalette, 16); + GeneratePaletteLUT(fs, "egaPaletteLUT", egaPalette, 16, false); + GeneratePaletteLUT(fs, "cgaPaletteLUT", cgaPalette, 4, true); + GeneratePaletteLUT(fs, "compositeCgaPaletteLUT", cgaCompositePalette, 16, true); fclose(fs); } From 5fa23bca02709e0e9890a00edf6ec9c5cbbb4841 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 8 Mar 2024 13:44:18 +0000 Subject: [PATCH 39/98] Added parsing of select / option tags. Added HTTP chunking support. Improved ampersand escape parsing. --- project/DOS/Makefile | 5 +- project/Windows/Windows.vcxproj | 2 + src/DOS/Surf4bpp.cpp | 2 +- src/HTTP.cpp | 53 ++++++++++-- src/HTTP.h | 6 +- src/Node.cpp | 5 +- src/Node.h | 2 + src/Nodes/Button.cpp | 3 +- src/Nodes/Select.cpp | 114 +++++++++++++++++++++++++ src/Nodes/Select.h | 39 +++++++++ src/Nodes/Text.cpp | 16 ++++ src/Nodes/Text.h | 1 + src/Page.cpp | 11 ++- src/Parser.cpp | 145 ++++++++++++++++++++++++-------- src/Parser.h | 5 +- src/Tags.cpp | 24 ++++++ src/Tags.h | 16 ++++ 17 files changed, 399 insertions(+), 50 deletions(-) create mode 100644 src/Nodes/Select.cpp create mode 100644 src/Nodes/Select.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 2cd5c22..e3d2c85 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj EMS.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.obj Interface.obj DOSInput.obj DOSNet.obj Page.obj Layout.obj Node.obj Text.obj Table.obj Section.obj ImgNode.obj Block.obj StyNode.obj LinkNode.obj Break.obj Render.obj Button.obj Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj EMS.obj memory_model = -ml CC = wpp CFLAGS = -zq -0 -ot -bt=DOS -w2 $(memory_model) -fi=$(SRC_PATH)\Defines.h @@ -106,6 +106,9 @@ Table.obj: $(SRC_PATH)\Nodes\Table.cpp Button.obj: $(SRC_PATH)\Nodes\Button.cpp $(CC) -fo=$@ $(CFLAGS) $< +Select.obj: $(SRC_PATH)\Nodes\Select.cpp + $(CC) -fo=$@ $(CFLAGS) $< + DataPack.obj: $(SRC_PATH)\DataPack.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 66747b5..5667d1f 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -161,6 +161,7 @@ + @@ -205,6 +206,7 @@ + diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index efe5699..0a0f64a 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -29,7 +29,7 @@ static uint8_t pixelStartBitmasks[8] = static uint8_t pixelEndBitmasks[8] = { - 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff + 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; inline void SetPenColour(uint8_t colour) diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 8a4cff2..fe816d6 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -143,8 +143,13 @@ void HTTPRequest::Open(char* inURL) size_t HTTPRequest::ReadData(char* buffer, size_t count) { - if (status == HTTPRequest::Downloading && sock) + 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) { @@ -160,6 +165,17 @@ size_t HTTPRequest::ReadData(char* buffer, size_t count) { Stop(); } + else + { + if (usingChunkedTransfer) + { + chunkSizeRemaining -= bytesRead; + if (!chunkSizeRemaining) + { + internalStatus = ParseChunkHeader; + } + } + } } return bytesRead; @@ -263,6 +279,7 @@ void HTTPRequest::Update() 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; @@ -297,6 +314,7 @@ void HTTPRequest::Update() internalStatus = ReceiveHeaderContent; contentRemaining = -1; + usingChunkedTransfer = false; } } break; @@ -307,12 +325,18 @@ void HTTPRequest::Update() if (lineBuffer[0] == '\0') { // Header has finished - status = Downloading; - internalStatus = ReceiveContent; + if (usingChunkedTransfer) + { + internalStatus = ParseChunkHeader; + } + else + { + status = Downloading; + internalStatus = ReceiveContent; + } break; } - - if (!strncmp(lineBuffer, "Location: ", 10)) + else if (!strncmp(lineBuffer, "Location: ", 10)) { if (responseCode == RESPONSE_MOVED_PERMANENTLY || responseCode == RESPONSE_MOVED_TEMPORARILY || responseCode == RESPONSE_TEMPORARY_REDIRECTION || responseCode == RESPONSE_PERMANENT_REDIRECT) { @@ -323,10 +347,14 @@ void HTTPRequest::Update() break; } } - if (!strncmp(lineBuffer, "Content-Length:", 15)) + else if (!strncmp(lineBuffer, "Content-Length:", 15)) { contentRemaining = atoi(lineBuffer + 15); } + else if (!stricmp(lineBuffer, "Transfer-Encoding: chunked")) + { + usingChunkedTransfer = true; + } //printf("Header: %s -- \n", lineBuffer); //getchar(); @@ -346,6 +374,19 @@ void HTTPRequest::Update() } break; } + + if (internalStatus == ParseChunkHeader) + { + if (ReadLine()) + { + chunkSizeRemaining = strtol(lineBuffer, NULL, 16); + if (chunkSizeRemaining) + { + status = Downloading; + internalStatus = ReceiveContent; + } + } + } } bool HTTPRequest::ReadLine() diff --git a/src/HTTP.h b/src/HTTP.h index 4c49bf5..dab0558 100644 --- a/src/HTTP.h +++ b/src/HTTP.h @@ -60,7 +60,8 @@ class HTTPRequest SendHeaders, ReceiveHeaderResponse, ReceiveHeaderContent, - ReceiveContent + ReceiveContent, + ParseChunkHeader }; void MarkError(InternalStatus statusError); @@ -86,6 +87,9 @@ class HTTPRequest int lineBufferSendPos; long contentRemaining; + + long chunkSizeRemaining; + bool usingChunkedTransfer; }; diff --git a/src/Node.cpp b/src/Node.cpp index 65980e2..20fd559 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -15,6 +15,7 @@ #include "Nodes/Status.h" #include "Nodes/Scroll.h" #include "Nodes/Table.h" +#include "Nodes/Select.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -33,7 +34,9 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new ScrollBarNode(), new TableNode(), new TableRowNode(), - new TableCellNode() + new TableCellNode(), + new SelectNode(), + new OptionNode() }; Node::Node(Type inType, void* inData) diff --git a/src/Node.h b/src/Node.h index 303dbdd..f71b04a 100644 --- a/src/Node.h +++ b/src/Node.h @@ -64,6 +64,8 @@ class Node Table, TableRow, TableCell, + Select, + Option, NumNodeTypes }; diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index fa8f5fb..fa3d517 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -29,7 +29,7 @@ void ButtonNode::Draw(DrawContext& context, Node* node) Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, NodeCallbackFunction callback) { - const char* buttonText = NULL; + char* buttonText = NULL; if (inButtonText) { buttonText = allocator.AllocString(inButtonText); @@ -37,6 +37,7 @@ Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, Node { return nullptr; } + HTMLParser::ReplaceAmpersandEscapeSequences(buttonText); } ButtonNode::Data* data = allocator.Alloc(buttonText, callback); diff --git a/src/Nodes/Select.cpp b/src/Nodes/Select.cpp new file mode 100644 index 0000000..0e0cc81 --- /dev/null +++ b/src/Nodes/Select.cpp @@ -0,0 +1,114 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" + +#include "Select.h" + +Node* SelectNode::Construct(Allocator& allocator) +{ + SelectNode::Data* data = allocator.Alloc(); + if (data) + { + return allocator.Alloc(Node::Select, data); + } + return nullptr; +} + +void SelectNode::Draw(DrawContext& context, Node* node) +{ + SelectNode::Data* data = static_cast(node->data); + + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + 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->style.fontStyle); + } +} + +void SelectNode::EndLayoutContext(Layout& layout, Node* node) +{ + SelectNode::Data* data = static_cast(node->data); + + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + 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 += 6; + + 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::EndLayoutContext(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; + select->selected = data; + } + else + { + for (OptionNode::Data* option = select->firstOption; option; option = option->next) + { + if (!option->next) + { + option->next = data; + break; + } + } + } + + data->addedToSelectNode = true; + } + + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + if (data->text) + { + node->size.x = font->CalculateWidth(data->text, node->style.fontStyle); + } + } +} diff --git a/src/Nodes/Select.h b/src/Nodes/Select.h new file mode 100644 index 0000000..551819d --- /dev/null +++ b/src/Nodes/Select.h @@ -0,0 +1,39 @@ +#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 EndLayoutContext(Layout& layout, Node* node) override; +}; + +class SelectNode : public NodeHandler +{ +public: + class Data + { + public: + Data() : firstOption(nullptr), selected(nullptr) {} + OptionNode::Data* firstOption; + OptionNode::Data* selected; + }; + + static Node* Construct(Allocator& allocator); + virtual void Draw(DrawContext& context, Node* element) override; + virtual void EndLayoutContext(Layout& layout, Node* node) override; +}; + +#endif diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index c7927c6..c28c414 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -21,6 +21,22 @@ Node* TextElement::Construct(Allocator& allocator, const char* text) return nullptr; } +void TextElement::Draw(DrawContext& context, Node* node) +{ + TextElement::Data* data = static_cast(node->data); + + if (!node->firstChild && data->text.IsAllocated()) + { + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + uint8_t textColour = node->style.fontColour; + char* text = data->text.Get(); + + context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); + } +} + + void TextElement::GenerateLayout(Layout& layout, Node* node) { TextElement::Data* data = static_cast(node->data); diff --git a/src/Nodes/Text.h b/src/Nodes/Text.h index c1b6574..b1796a0 100644 --- a/src/Nodes/Text.h +++ b/src/Nodes/Text.h @@ -16,6 +16,7 @@ class TextElement : public NodeHandler 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 diff --git a/src/Page.cpp b/src/Page.cpp index 64ccfb0..41377f1 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -23,6 +23,7 @@ #include "Nodes/Text.h" #include "Nodes/Form.h" #include "Nodes/StyNode.h" +#include "Nodes/Select.h" #include "Draw/Surface.h" #include "Memory/Memory.h" @@ -92,7 +93,9 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) "ScrollBar", "Table", "TableRow", - "TableCell" + "TableCell", + "Select", + "Option" }; static const char* sectionTypeNames[] = @@ -130,6 +133,12 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) text[subData->length] = temp; } break; + case Node::Option: + { + OptionNode::Data* data = static_cast(node->data); + printf("<%s> [%s]\n", nodeTypeNames[node->type], data->text); + } + break; case Node::Section: { SectionElement::Data* data = static_cast(node->data); diff --git a/src/Parser.cpp b/src/Parser.cpp index 4d36ebe..8d931bb 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -23,6 +23,7 @@ #include "Nodes/Text.h" #include "Nodes/ImgNode.h" #include "Nodes/Break.h" +#include "Nodes/Select.h" #include "Memory/Memory.h" // debug @@ -139,8 +140,8 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) exit(1); } -#define NUM_AMPERSAND_ESCAPE_SEQUENCES 14 -const char* ampersandEscapeSequences[14 * 2] = +#define NUM_AMPERSAND_ESCAPE_SEQUENCES (sizeof(ampersandEscapeSequences) / (2 * sizeof(const char*))) +const char* ampersandEscapeSequences[] = { "quot", "\"", "amp", "&", @@ -216,11 +217,16 @@ void HTMLParser::FlushTextBuffer() { case ParseText: { - if(CurrentSection() == SectionElement::Body && textBufferSize > 0) + if (CurrentContext().node && CurrentContext().node->type == Node::Option) + { + OptionNode::Data* option = static_cast(CurrentContext().node->data); + option->text = MemoryManager::pageAllocator.AllocString(textBuffer); + } + else if(CurrentSection() == SectionElement::Body && textBufferSize > 0) { EmitText(textBuffer); } - if (CurrentSection() == SectionElement::Title && textBufferSize > 0) + else if (CurrentSection() == SectionElement::Title && textBufferSize > 0) { page.SetTitle(textBuffer); } @@ -279,44 +285,112 @@ void HTMLParser::FlushTextBuffer() break; case ParseAmpersandEscape: { - if(textBufferSize == 0) + // Flush everything before the escape sequence started + if (escapeSequenceStartIndex) + { + textBufferSize = escapeSequenceStartIndex; + parseState = ParseText; + FlushTextBuffer(); + parseState = ParseAmpersandEscape; + textBuffer[escapeSequenceStartIndex] = '&'; + strcpy(textBuffer, textBuffer + escapeSequenceStartIndex); + } + 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])) { - EmitText("&"); + escapeSequenceLength++; } - else + bool correctlyTerminated = buffer[escapeSequenceLength] == ';'; + char* nextBufferPosition = correctlyTerminated ? buffer + escapeSequenceLength + 1 : buffer + escapeSequenceLength; + + if(escapeSequenceLength > 0) { - for(int n = 0; n < NUM_AMPERSAND_ESCAPE_SEQUENCES; n++) + 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 { - if(escapeSequence[i] == '\0') + 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++) + { + 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') - { - EmitText(ampersandEscapeSequences[n * 2 + 1]); - break; - } } } } - break; + buffer++; } - - textBufferSize = 0; - textBuffer[0] = '\0'; } bool HTMLParser::IsWhiteSpace(char c) @@ -464,8 +538,9 @@ void HTMLParser::ParseChar(char c) } else if(c == '&') { - FlushTextBuffer(); parseState = ParseAmpersandEscape; + escapeSequenceStartIndex = textBufferSize; + AppendTextBuffer(c); } else { @@ -553,18 +628,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; diff --git a/src/Parser.h b/src/Parser.h index 3772a38..49623b3 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -85,13 +85,15 @@ class HTMLParser static uint8_t ParseColourCode(const char* colourCode); + static void ReplaceAmpersandEscapeSequences(char* buffer, bool replaceNonBreakingSpace = true); + 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); @@ -107,6 +109,7 @@ class HTMLParser ParseState parseState; char textBuffer[2560]; size_t textBufferSize; + int escapeSequenceStartIndex; Stack contextStack; int contextStackSize; diff --git a/src/Tags.cpp b/src/Tags.cpp index 0bf4851..ca6479c 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -33,6 +33,7 @@ #include "Nodes/Field.h" #include "Nodes/Form.h" #include "Nodes/Table.h" +#include "Nodes/Select.h" static const HTMLTagHandler* tagHandlers[] = { @@ -85,6 +86,8 @@ static const HTMLTagHandler* tagHandlers[] = new TableRowTagHandler(), new TableCellTagHandler("td", false), new TableCellTagHandler("th", true), + new SelectTagHandler(), + new OptionTagHandler(), NULL }; @@ -582,3 +585,24 @@ void TableCellTagHandler::Close(class HTMLParser& parser) const { parser.PopContext(this); } + +void SelectTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + parser.PushContext(SelectNode::Construct(MemoryManager::pageAllocator), this); +} + +void SelectTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} + +void OptionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const +{ + parser.PushContext(OptionNode::Construct(MemoryManager::pageAllocator), this); +} + +void OptionTagHandler::Close(class HTMLParser& parser) const +{ + parser.PopContext(this); +} + diff --git a/src/Tags.h b/src/Tags.h index 028449d..c447d84 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -204,6 +204,22 @@ class TableCellTagHandler : public HTMLTagHandler 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 From b8b92215bf1fbc84f4f93c605227c9db9f0a35cb Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 8 Mar 2024 14:15:01 +0000 Subject: [PATCH 40/98] Fixed HTTP chunking --- src/HTTP.cpp | 22 +++++++++++++--------- src/Interface.cpp | 7 +++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/HTTP.cpp b/src/HTTP.cpp index fe816d6..f6d2010 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -81,6 +81,7 @@ void HTTPRequest::Open(char* inURL) // level directory. char* proxy = getenv("HTTP_PROXY"); + //const char* proxy = "192.168.56.1:8888"; if (proxy == NULL) { char* pathStart = strchr(hostnameStart, '/'); @@ -164,17 +165,16 @@ size_t HTTPRequest::ReadData(char* buffer, size_t count) if (contentRemaining <= 0) { Stop(); + return bytesRead; } - else + } + + if (usingChunkedTransfer) + { + chunkSizeRemaining -= bytesRead; + if (!chunkSizeRemaining) { - if (usingChunkedTransfer) - { - chunkSizeRemaining -= bytesRead; - if (!chunkSizeRemaining) - { - internalStatus = ParseChunkHeader; - } - } + internalStatus = ParseChunkHeader; } } @@ -380,6 +380,7 @@ void HTTPRequest::Update() if (ReadLine()) { chunkSizeRemaining = strtol(lineBuffer, NULL, 16); + if (chunkSizeRemaining) { status = Downloading; @@ -393,6 +394,9 @@ bool HTTPRequest::ReadLine() { bool allowBufferTruncation = true; + if (!sock) + return false; + while (1) { int rc = sock->Receive((unsigned char*)lineBuffer + lineBufferSize, 1); diff --git a/src/Interface.cpp b/src/Interface.cpp index e39d82b..76e9ae1 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -207,6 +207,13 @@ void AppInterface::Update() } break; + case 'n': + { +#ifdef _WIN32 + app.page.DebugDumpNodeGraph(app.page.GetRootNode()); +#endif + } + default: // printf("%x\n", keyPress); break; From 8ddae9185f22a8b79a370905a7a93e85c3621a9e Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sat, 9 Mar 2024 19:30:11 +0000 Subject: [PATCH 41/98] Added progressive renderer --- src/App.cpp | 4 + src/DOS/DOSInput.cpp | 13 +- src/DOS/DOSInput.h | 1 + src/DOS/Surf4bpp.cpp | 53 +++++- src/Draw/Surf1bpp.cpp | 24 ++- src/Draw/Surf1bpp.h | 1 + src/Draw/Surf2bpp.cpp | 21 +++ src/Draw/Surf2bpp.h | 1 + src/Draw/Surf4bpp.h | 1 + src/Draw/Surf8bpp.cpp | 22 ++- src/Draw/Surf8bpp.h | 1 + src/Draw/Surface.h | 1 + src/Image/Image.h | 2 + src/Interface.cpp | 24 ++- src/Layout.cpp | 10 +- src/Layout.h | 1 + src/Node.cpp | 1 + src/Node.h | 2 + src/Nodes/Field.cpp | 4 +- src/Nodes/ImgNode.cpp | 6 +- src/Nodes/Table.cpp | 2 + src/Page.cpp | 12 -- src/Page.h | 1 - src/Parser.cpp | 7 +- src/Render.cpp | 373 +++++++++++++++++++++++++++++++++++++++++- src/Render.h | 35 ++++ 26 files changed, 580 insertions(+), 43 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index bd9df08..a01bb85 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -39,6 +39,8 @@ void App::ResetPage() page.Reset(); parser.Reset(); ui.Reset(); + pageRenderer.Reset(); + pageRenderer.RefreshAll(); } void App::Run(int argc, char* argv[]) @@ -46,6 +48,7 @@ void App::Run(int argc, char* argv[]) running = true; ui.Init(); + pageRenderer.Init(); if (argc > 1) { @@ -132,6 +135,7 @@ void App::Run(int argc, char* argv[]) if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) ui.SetStatusMessage(loadTask.request->GetStatusString()); + pageRenderer.Update(); ui.Update(); } } diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index d472de2..6edea5e 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -32,7 +32,7 @@ void DOSInputDriver::Init() currentCursor = MouseCursor::Hand; SetMouseCursor(MouseCursor::Pointer); lastMouseButtons = 0; - mouseVisible = false; + mouseHideCount = 1; ShowMouse(); } @@ -46,12 +46,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) @@ -69,8 +71,11 @@ 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); diff --git a/src/DOS/DOSInput.h b/src/DOS/DOSInput.h index 782f083..f0911e1 100644 --- a/src/DOS/DOSInput.h +++ b/src/DOS/DOSInput.h @@ -35,6 +35,7 @@ class DOSInputDriver : public InputDriver int lastMouseButtons; bool mouseVisible; bool hasMouse; + int mouseHideCount; }; #endif diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index 0a0f64a..fdb80a1 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -15,6 +15,9 @@ #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] = @@ -148,9 +151,9 @@ void DrawSurface_4BPP::VLine(DrawContext& context, int x, int y, int count, uint { return; } - if (y + count >= context.clipBottom) + if (y + count > context.clipBottom) { - count = context.clipBottom - 1 - y; + count = context.clipBottom - y; } if (count <= 0) { @@ -693,3 +696,49 @@ void DrawSurface_4BPP::Clear() 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/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index ecef147..eac6adf 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -107,9 +107,9 @@ void DrawSurface_1BPP::VLine(DrawContext& context, int x, int y, int count, uint { return; } - if (y + count >= context.clipBottom) + if (y + count > context.clipBottom) { - count = context.clipBottom - 1 - y; + count = context.clipBottom - y; } if (count <= 0) { @@ -564,3 +564,23 @@ void DrawSurface_1BPP::Clear() memset(lines[y], 0xff, 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 index a9830d3..ab60f1b 100644 --- a/src/Draw/Surf1bpp.h +++ b/src/Draw/Surf1bpp.h @@ -17,6 +17,7 @@ class DrawSurface_1BPP : public DrawSurface 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 index 299dec1..8814fc7 100644 --- a/src/Draw/Surf2bpp.cpp +++ b/src/Draw/Surf2bpp.cpp @@ -479,3 +479,24 @@ void DrawSurface_2BPP::Clear() 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 index ed782d6..18fd439 100644 --- a/src/Draw/Surf2bpp.h +++ b/src/Draw/Surf2bpp.h @@ -17,6 +17,7 @@ class DrawSurface_2BPP : public DrawSurface 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 index 2e24bad..bc9bf9b 100644 --- a/src/Draw/Surf4bpp.h +++ b/src/Draw/Surf4bpp.h @@ -17,6 +17,7 @@ class DrawSurface_4BPP : public DrawSurface 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 index 6cd1454..7329a40 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -63,9 +63,9 @@ void DrawSurface_8BPP::VLine(DrawContext& context, int x, int y, int count, uint { return; } - if (y + count >= context.clipBottom) + if (y + count > context.clipBottom) { - count = context.clipBottom - 1 - y; + count = context.clipBottom - y; } if (count <= 0) { @@ -517,3 +517,21 @@ void DrawSurface_8BPP::Clear() 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 index d0faacd..8aba796 100644 --- a/src/Draw/Surf8bpp.h +++ b/src/Draw/Surf8bpp.h @@ -17,6 +17,7 @@ class DrawSurface_8BPP : public DrawSurface 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 index b74abf2..44312f2 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -31,6 +31,7 @@ class DrawSurface 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; diff --git a/src/Image/Image.h b/src/Image/Image.h index d701cf8..afdb086 100644 --- a/src/Image/Image.h +++ b/src/Image/Image.h @@ -16,8 +16,10 @@ struct Image : ImageMetadata Image() : lines(nullptr) { width = height = pitch = bpp = 0; + isLoaded = false; } struct MemBlockHandle* lines; + bool isLoaded; }; #endif diff --git a/src/Interface.cpp b/src/Interface.cpp index 76e9ae1..cf82f39 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -373,9 +373,9 @@ void AppInterface::GenerateInterfaceNodes() void AppInterface::DrawInterfaceNodes(DrawContext& context) { - Platform::input->HideMouse(); app.pageRenderer.DrawAll(context, rootInterfaceNode); + Platform::input->HideMouse(); uint8_t dividerColour = 0; context.surface->HLine(context, 0, windowRect.y - 1, Platform::video->screenWidth, dividerColour); Platform::input->ShowMouse(); @@ -442,6 +442,8 @@ void AppInterface::SetStatusMessage(const char* message) void AppInterface::ScrollRelative(int delta) { + int oldScrollPositionY = scrollPositionY; + scrollPositionY += delta; if (scrollPositionY < 0) scrollPositionY = 0; @@ -452,14 +454,22 @@ void AppInterface::ScrollRelative(int delta) scrollPositionY = maxScrollY; } + delta = scrollPositionY - oldScrollPositionY; + UpdatePageScrollBar(); - DrawContext context; - app.pageRenderer.GenerateDrawContext(context, NULL); - context.drawOffsetY = 0; - context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); - app.pageRenderer.GenerateDrawContext(context, NULL); - app.pageRenderer.DrawAll(context, app.page.GetRootNode()); + //Platform::input->HideMouse(); + + app.pageRenderer.OnPageScroll(delta); + + //DrawContext context; + //app.pageRenderer.GenerateDrawContext(context, NULL); + //context.drawOffsetY = 0; + //context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + //app.pageRenderer.GenerateDrawContext(context, NULL); + ////app.pageRenderer.DrawAll(context, app.page.GetRootNode()); + + Platform::input->ShowMouse(); } void AppInterface::ScrollAbsolute(int position) diff --git a/src/Layout.cpp b/src/Layout.cpp index e37d8ee..4d18e13 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -17,6 +17,7 @@ void Layout::Reset() lineStartNode = nullptr; lastNodeContext = nullptr; Cursor().Clear(); + tableDepth = 0; LayoutParams& params = GetParams(); params.marginLeft = 0; @@ -38,6 +39,11 @@ void Layout::BreakNewLine() TranslateNodes(lineStartNode, lastNodeContext, shift, 0); } + if (lastNodeContext && !tableDepth) + { + page.GetApp().pageRenderer.MarkNodeLayoutComplete(lastNodeContext); + } + //if (lineStartNode && lineStartNode->parent) //{ // lineStartNode->parent->OnChildLayoutChanged(); @@ -307,8 +313,8 @@ void Layout::RecalculateLayout() } #endif - // FIXME - page.GetApp().ui.ScrollRelative(0); + page.GetApp().pageRenderer.MarkPageLayoutComplete(); + page.GetApp().pageRenderer.RefreshAll(); #ifdef _WIN32 //page.DebugDumpNodeGraph(page.GetRootNode()); diff --git a/src/Layout.h b/src/Layout.h index b0ce4e4..5ba655d 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -59,6 +59,7 @@ class Layout Stack cursorStack; int currentLineHeight; + int tableDepth; Stack paramStack; diff --git a/src/Node.cpp b/src/Node.cpp index 20fd559..d701fed 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -44,6 +44,7 @@ Node::Node(Type inType, void* inData) , parent(nullptr) , next(nullptr) , firstChild(nullptr) + , nextNodeToRender(nullptr) , data(inData) { anchor.Clear(); diff --git a/src/Node.h b/src/Node.h index f71b04a..e876140 100644 --- a/src/Node.h +++ b/src/Node.h @@ -107,6 +107,8 @@ class Node Node* next; Node* firstChild; + Node* nextNodeToRender; + void* data; protected: diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index ad1f4aa..08c490a 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -230,7 +230,7 @@ void TextFieldNode::DrawCursor(DrawContext& context, Node* node, bool clear) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - int x = node->anchor.x + 2; + int x = node->anchor.x + 3; int height = font->glyphHeight; for (int n = 0; n < cursorPosition; n++) @@ -247,7 +247,7 @@ void TextFieldNode::DrawCursor(DrawContext& context, Node* node, bool clear) int y = node->anchor.y + 2; - context.surface->VLine(context, x, y, height, clear ? 1 : 0); + context.surface->VLine(context, x, y, height, clear ? Platform::video->colourScheme.pageColour : Platform::video->colourScheme.textColour); } void TextFieldNode::MoveCursorPosition(Node* node, int newPosition) diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 41a7c5a..6e74d88 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -13,7 +13,7 @@ void ImageNode::Draw(DrawContext& context, Node* node) //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); uint8_t outlineColour = 7; - if (data->image.lines) + if (data->image.isLoaded && data->image.lines) { context.surface->BlitImage(context, &data->image, node->anchor.x, node->anchor.y); } @@ -70,6 +70,9 @@ bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) if (decoder->GetState() == ImageDecoder::Success) { ImageNode::Data* data = static_cast(node->data); + data->image.isLoaded = true; + + App::Get().pageRenderer.MarkNodeDirty(node); // Loop through image nodes in case this image is used multiple times bool needsLayoutRefresh = false; @@ -84,6 +87,7 @@ bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) if (n != node) { otherData->image = data->image; + App::Get().pageRenderer.MarkNodeDirty(n); } if (n->size.x != data->image.width || n->size.y != data->image.height) diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index c4cea59..59f8251 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -48,6 +48,7 @@ void TableNode::BeginLayoutContext(Layout& layout, Node* node) } layout.BreakNewLine(); + layout.tableDepth++; layout.PushCursor(); layout.PushLayout(); @@ -214,6 +215,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) data->state = Data::FinishedLayout; } + layout.tableDepth--; layout.BreakNewLine(); } diff --git a/src/Page.cpp b/src/Page.cpp index 41377f1..b853350 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -61,18 +61,6 @@ void Page::SetTitle(const char* inTitle) app.ui.SetTitle(inTitle); } -void Page::DebugDraw(DrawContext& context, Node* node) -{ - while (node) - { - node->Handler().Draw(context, node); - - DebugDraw(context, node->firstChild); - - node = node->next; - } -} - void Page::DebugDumpNodeGraph(Node* node, int depth) { diff --git a/src/Page.h b/src/Page.h index 814f383..ce91935 100644 --- a/src/Page.h +++ b/src/Page.h @@ -40,7 +40,6 @@ class Page int GetPageWidth(); int GetPageHeight() { return pageHeight; } - void DebugDraw(DrawContext& context, Node* node); void DebugDumpNodeGraph(Node* node, int depth = 0); URL pageURL; diff --git a/src/Parser.cpp b/src/Parser.cpp index 8d931bb..223fd97 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -25,6 +25,7 @@ #include "Nodes/Break.h" #include "Nodes/Select.h" #include "Memory/Memory.h" +#include "App.h" // debug #include "Platform.h" @@ -120,14 +121,12 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (contextStackSize == 0) { page.GetRootNode()->EncapsulateChildren(); - DrawContext context; - page.GetApp().pageRenderer.GenerateDrawContext(context, page.GetRootNode()); #ifdef _WIN32 page.DebugDumpNodeGraph(page.GetRootNode()); #endif - context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); - page.DebugDraw(context, page.GetRootNode()); + //page.GetApp().pageRenderer.RefreshAll(); + page.GetApp().pageRenderer.MarkPageLayoutComplete(); page.GetApp().StopLoad(); } diff --git a/src/Render.cpp b/src/Render.cpp index 9b719be..f7a41db 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -8,33 +8,288 @@ 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; + 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.clipBottom = windowRect.y + windowRect.height; + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + + Platform::input->ShowMouse(); + + upperContext.clipBottom = upperContext.clipTop; + lowerContext.clipTop = upperContext.clipBottom; + + // Clear the render queue + for (Node* node = renderQueueHead; node;) + { + Node* next = node->nextNodeToRender; + node->nextNodeToRender = nullptr; + node = next; + } + renderQueueHead = renderQueueTail = nullptr; + + FindOverlappingNodesInScreenRegion(windowRect.y, windowRect.y + windowRect.height); +} + +void PageRenderer::OnPageScroll(int scrollDelta) +{ + if (scrollDelta == 0) + { + return; + } + + Rect& windowRect = app.ui.windowRect; + upperContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); + lowerContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); + + int minWinY = windowRect.y; + int maxWinY = windowRect.y + windowRect.height; + + if (scrollDelta > 0) + { + lowerContext.clipTop -= scrollDelta; + if (lowerContext.clipTop < minWinY) + lowerContext.clipTop = minWinY; + + upperContext.clipBottom -= scrollDelta; + if (upperContext.clipBottom < minWinY) + upperContext.clipBottom = minWinY; + + if (lowerContext.clipTop < upperContext.clipBottom) + { + lowerContext.clipTop = upperContext.clipBottom; + } + + int top = maxWinY - scrollDelta; + if (top < minWinY) + top = minWinY; + FindOverlappingNodesInScreenRegion(top, maxWinY); + } + else if (scrollDelta < 0) + { + upperContext.clipBottom -= scrollDelta; + if (upperContext.clipBottom > maxWinY) + upperContext.clipBottom = maxWinY; + + lowerContext.clipTop -= scrollDelta; + if (lowerContext.clipTop > maxWinY) + lowerContext.clipTop = maxWinY; + + if (upperContext.clipBottom > lowerContext.clipTop) + { + upperContext.clipBottom = lowerContext.clipTop; + } + + 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; + clearContext.clipBottom = maxWinY; + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + } + else + { + Platform::video->drawSurface->ScrollScreen(minWinY - scrollDelta, maxWinY, windowRect.width, scrollDelta); + clearContext.clipTop = minWinY; + clearContext.clipBottom = minWinY - scrollDelta; + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + } + + Platform::input->ShowMouse(); +} + +bool PageRenderer::IsRenderableNode(Node::Type type) +{ + switch (type) + { + case Node::Style: + case Node::Section: + case Node::Link: + case Node::Form: + return false; + default: + return true; + } +} + +void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) +{ + int drawOffsetY = upperContext.drawOffsetY; + + if (!lastCompleteNode) + return; + for(Node* node = app.page.GetRootNode(); node; node = node->GetNextInTree()) + { + if(IsRenderableNode(node->type)) + { + bool outsideOfRegion = (node->anchor.y + drawOffsetY > bottom) || (node->anchor.y + node->size.y + drawOffsetY < top); + + if (!outsideOfRegion) + { + AddToQueue(node); + } + } + + if (node == lastCompleteNode) + break; + } } void PageRenderer::Reset() { + renderQueueHead = nullptr; + renderQueueTail = nullptr; + renderQueueSize = 0; + lastCompleteNode = nullptr; + + InitContext(upperContext); + InitContext(lowerContext); + + Rect& windowRect = app.ui.windowRect; + lowerContext.clipBottom = lowerContext.clipTop = windowRect.y + windowRect.height; +} + +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() { +// if (rand() % 256) +// return; + + bool renderedSomething = false; + + while(renderQueueHead && !renderedSomething) + { + Node* toRender = renderQueueHead; + + Platform::input->HideMouse(); + if (upperContext.clipTop != upperContext.clipBottom && DoesOverlapWithContext(toRender, upperContext)) + { + toRender->Handler().Draw(upperContext, toRender); + renderedSomething = true; + } + if (lowerContext.clipTop != lowerContext.clipBottom && DoesOverlapWithContext(toRender, lowerContext)) + { + toRender->Handler().Draw(lowerContext, toRender); + renderedSomething = true; + } + Platform::input->ShowMouse(); + + renderQueueHead = toRender->nextNodeToRender; + toRender->nextNodeToRender = nullptr; + + if (!renderQueueHead) + { + renderQueueTail = nullptr; + } + renderQueueSize--; + } + if (!renderQueueHead) + { + lowerContext.clipTop = lowerContext.clipBottom; + upperContext.clipBottom = upperContext.clipTop; + } } +void PageRenderer::AddToQueue(Node* node) +{ + if (node->nextNodeToRender || node == renderQueueTail) + { + // Already in queue + return; + } + + if (!renderQueueHead) + { + renderQueueTail = renderQueueHead = node; + } + else + { + renderQueueTail->nextNodeToRender = node; + renderQueueTail = node; + } + + renderQueueSize++; +} + +bool PageRenderer::IsInRenderQueue(Node* node) +{ + return node && (node->nextNodeToRender || node == renderQueueTail); +} + void PageRenderer::DrawAll(DrawContext& context, Node* node) { + Platform::input->HideMouse(); while (node) { node->Handler().Draw(context, node); - DrawAll(context, node->firstChild); - - node = node->next; + node = node->GetNextInTree(); } + Platform::input->ShowMouse(); //context.surface->BlitImage(context, Assets.imageIcon, 0, 50); //context.surface->BlitImage(context, Assets.imageIcon, 1, 100); @@ -65,3 +320,113 @@ void PageRenderer::GenerateDrawContext(DrawContext& context, Node* node) context.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); } } + +void PageRenderer::MarkNodeLayoutComplete(Node* node) +{ + Rect& windowRect = app.ui.windowRect; + int drawOffsetY = upperContext.drawOffsetY; + Node* startNode = lastCompleteNode ? lastCompleteNode->GetNextInTree() : app.page.GetRootNode(); + lastCompleteNode = node; + + int minWinY = windowRect.y; + int maxWinY = windowRect.y + windowRect.height; + + for (Node* node = startNode; node; node = node->GetNextInTree()) + { + if (IsRenderableNode(node->type)) + { + int nodeTop = node->anchor.y + drawOffsetY; + int nodeBottom = nodeTop + node->size.y; + bool outsideOfWindow = (nodeTop > maxWinY) || (nodeBottom < minWinY); + + if (!outsideOfWindow) + { + if (upperContext.clipBottom < nodeBottom) + { + upperContext.clipBottom = nodeBottom; + if (upperContext.clipBottom > maxWinY) + { + upperContext.clipBottom = maxWinY; + } + } + if (lowerContext.clipTop < upperContext.clipBottom) + { + lowerContext.clipTop = upperContext.clipBottom; + } + AddToQueue(node); + } + } + + if (node == lastCompleteNode) + break; + } + +} + +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 = upperContext.drawOffsetY; + 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 (upperContext.clipBottom < nodeBottom) + { + upperContext.clipBottom = nodeBottom; + if (upperContext.clipBottom > maxWinY) + { + upperContext.clipBottom = maxWinY; + } + } + if (lowerContext.clipTop < upperContext.clipBottom) + { + lowerContext.clipTop = upperContext.clipBottom; + } + AddToQueue(dirtyNode); + + Platform::input->HideMouse(); + + Rect& windowRect = app.ui.windowRect; + + DrawContext clearContext; + InitContext(clearContext); + clearContext.clipBottom = windowRect.y + windowRect.height; + clearContext.surface->FillRect(clearContext, dirtyNode->anchor.x, dirtyNode->anchor.y, dirtyNode->size.x, dirtyNode->size.y, Platform::video->colourScheme.pageColour); + + Platform::input->ShowMouse(); + } +} + +void PageRenderer::MarkPageLayoutComplete() +{ + Node* node = lastCompleteNode; + while (node) + { + Node* next = node->GetNextInTree(); + if (next) + { + node = next; + } + else + { + MarkNodeLayoutComplete(node); + break; + } + } +} \ No newline at end of file diff --git a/src/Render.h b/src/Render.h index 4bdfe97..4e5a11b 100644 --- a/src/Render.h +++ b/src/Render.h @@ -1,9 +1,13 @@ #ifndef _RENDER_H_ #define _RENDER_H_ +#include "Draw/Surface.h" +#include "Node.h" + class App; class Node; struct DrawContext; +struct Rect; class PageRenderer { @@ -14,12 +18,43 @@ class PageRenderer void Reset(); void Update(); + void RefreshAll(); void DrawAll(DrawContext& context, Node* node); void GenerateDrawContext(DrawContext& context, Node* node); + void AddToQueue(Node* node); + + void OnPageScroll(int scrollDelta); + + void MarkNodeLayoutComplete(Node* node); + void MarkPageLayoutComplete(); + void MarkNodeDirty(Node* node); + 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::Type type); + + void AddToQueueIfVisible(Node* node); + App& app; + + Node* renderQueueHead; + Node* renderQueueTail; + + int renderQueueSize; + + Node* lastCompleteNode; + + // Draw contexts for upper and lower parts of the screen to facilitate scrolling + DrawContext upperContext; + DrawContext lowerContext; }; #endif From 565fd8166132794c0755461dcc51080483ceae0b Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 12 Mar 2024 11:04:08 +0000 Subject: [PATCH 42/98] Memory optimisations --- src/DOS/DOSNet.h | 2 +- src/DOS/EMS.h | 3 ++ src/DOS/Platform.cpp | 22 +++++++++- src/Image/Gif.cpp | 2 +- src/Interface.cpp | 9 ++-- src/Memory/Alloc.h | 92 +++++++++++++++++++++++++++++++++++++++++ src/Memory/LinAlloc.h | 61 ++++----------------------- src/Memory/MemBlock.cpp | 66 ++++++++++++++++++----------- src/Memory/MemBlock.h | 14 ++++--- src/Memory/Memory.cpp | 38 ++++++++++++++++- src/Memory/Memory.h | 5 ++- src/Node.h | 10 +++-- src/Nodes/Break.h | 8 ++-- src/Nodes/Field.cpp | 2 +- src/Nodes/Status.h | 2 +- src/Nodes/Table.cpp | 4 +- src/Page.cpp | 66 +++++++++++++++++++---------- src/Page.h | 4 +- src/Parser.cpp | 2 +- src/Stack.h | 4 +- src/Style.h | 4 ++ 21 files changed, 292 insertions(+), 128 deletions(-) create mode 100644 src/Memory/Alloc.h diff --git a/src/DOS/DOSNet.h b/src/DOS/DOSNet.h index 1fc98c5..1aba884 100644 --- a/src/DOS/DOSNet.h +++ b/src/DOS/DOSNet.h @@ -24,7 +24,7 @@ #define MAX_CONCURRENT_HTTP_REQUESTS 1 #else #define TCP_RECV_BUFFER_SIZE (16384) -#define MAX_CONCURRENT_HTTP_REQUESTS 3 +#define MAX_CONCURRENT_HTTP_REQUESTS 1 #endif struct TcpSocket; diff --git a/src/DOS/EMS.h b/src/DOS/EMS.h index 2ca9a93..ebaf9d3 100644 --- a/src/DOS/EMS.h +++ b/src/DOS/EMS.h @@ -21,6 +21,9 @@ class EMSManager void Shutdown(); + long TotalAllocated() { return numAllocatedPages * EMS_PAGE_SIZE; } + long TotalUsed() { return allocationPageIndex * EMS_PAGE_SIZE + allocationPageUsed; } + private: bool isAvailable; int numAllocatedPages; diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 10507b6..4b89248 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -95,6 +95,7 @@ static int AutoDetectVideoMode() const int HP95LX = 13; const int Hercules = 10; const int CGA = 0; + const int CGAPalmtop = 1; const int EGA = 6; const int VGA = 8; @@ -103,7 +104,14 @@ static int AutoDetectVideoMode() int86(0x15, &inreg, &outreg); if (outreg.x.bx == 0x4850) { - return HP95LX; + if (outreg.x.cx == 0x0101) + { + return HP95LX; + } + else if (outreg.x.cx == 0x0102) + { + return CGAPalmtop; + } } // First try detect presence of VGA card @@ -143,6 +151,10 @@ static int AutoDetectVideoMode() return CGA; } +#include "../Node.h" +#include +#include + bool Platform::Init(int argc, char* argv[]) { bool inverse = false; @@ -156,6 +168,14 @@ bool Platform::Init(int argc, char* argv[]) } } + // + printf("Node: %d\n", sizeof(Node)); + printf("BlockHandle: %d\n", sizeof(MemBlockHandle)); + printf("void*: %d\n", sizeof(void*)); + printf("Style: %d\n", sizeof(ElementStyle)); + getchar(); + // + int suggestedMode = AutoDetectVideoMode(); VideoModeInfo* videoMode = ShowVideoModePicker(suggestedMode); if (!videoMode) diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 4054364..c0b7888 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -85,7 +85,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) outputImage->pitch = outputImage->width; } - outputImage->lines = (MemBlockHandle*)MemoryManager::pageAllocator.Alloc(sizeof(MemBlockHandle) * outputImage->height); + outputImage->lines = (MemBlockHandle*)MemoryManager::pageAllocator.Allocate(sizeof(MemBlockHandle) * outputImage->height); if(!outputImage->lines) { // Allocation error diff --git a/src/Interface.cpp b/src/Interface.cpp index cf82f39..f4d89eb 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -54,6 +54,7 @@ void AppInterface::Reset() { focusedNode = nullptr; } + focusedNode = nullptr; if (hoverNode && !IsInterfaceNode(hoverNode)) { hoverNode = nullptr; @@ -201,8 +202,8 @@ void AppInterface::Update() case 'm': { - char tempMessage[50]; - snprintf(tempMessage, 50, "Allocated: %dK Used: %dK\n", (int)(MemoryManager::pageAllocator.TotalAllocated() / 1024), (int)(MemoryManager::pageAllocator.TotalUsed() / 1024)); + char tempMessage[100]; + MemoryManager::GenerateMemoryReport(tempMessage); app.ui.SetStatusMessage(tempMessage); } break; @@ -210,7 +211,7 @@ void AppInterface::Update() case 'n': { #ifdef _WIN32 - app.page.DebugDumpNodeGraph(app.page.GetRootNode()); + app.page.DebugDumpNodeGraph(); #endif } @@ -303,7 +304,7 @@ void AppInterface::UpdatePageScrollBar() void AppInterface::GenerateInterfaceNodes() { - LinearAllocator& allocator = MemoryManager::interfaceAllocator; + Allocator& allocator = MemoryManager::interfaceAllocator; rootInterfaceNode = SectionElement::Construct(allocator, SectionElement::Interface); rootInterfaceNode->style.alignment = ElementAlignment::Left; rootInterfaceNode->style.fontSize = 1; diff --git a/src/Memory/Alloc.h b/src/Memory/Alloc.h new file mode 100644 index 0000000..6384069 --- /dev/null +++ b/src/Memory/Alloc.h @@ -0,0 +1,92 @@ +#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; + } +}; + +class MallocWrapper : public Allocator +{ +public: + virtual void* Allocate(size_t numBytes) + { + return malloc(numBytes); + } + +}; + +#endif diff --git a/src/Memory/LinAlloc.h b/src/Memory/LinAlloc.h index d17bf92..94ca022 100644 --- a/src/Memory/LinAlloc.h +++ b/src/Memory/LinAlloc.h @@ -19,12 +19,13 @@ #include #include #include +#include "Alloc.h" #pragma warning(disable:4996) // 16K chunk size including next chunk pointer #define CHUNK_DATA_SIZE (16 * 1024 - sizeof(struct Chunk*)) -class LinearAllocator +class LinearAllocator : public Allocator { struct Chunk { @@ -64,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) { @@ -102,6 +82,7 @@ class LinearAllocator if (!currentChunk->next) { currentChunk->next = new Chunk(); + if (!currentChunk->next) { errorFlag = Error_OutOfMemory; @@ -115,37 +96,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(); - } - - template - T* Alloc(A a) - { - return new (Alloc(sizeof(T))) T(a); - } - - template - T* Alloc(A a, B b) - { - return new (Alloc(sizeof(T))) T(a, b); - } - - template - T* Alloc(A a, B b, C c) - { - return new (Alloc(sizeof(T))) T(a, b, c); - } - - size_t TotalAllocated() { return numAllocatedChunks * sizeof(Chunk); } - size_t TotalUsed() { return totalBytesUsed; } + long TotalAllocated() { return numAllocatedChunks * sizeof(Chunk); } + long TotalUsed() { return totalBytesUsed; } AllocationError GetError() { return errorFlag; } private: @@ -154,8 +111,8 @@ class LinearAllocator Chunk* currentChunk; size_t allocOffset; - size_t numAllocatedChunks; - size_t totalBytesUsed; // Bytes actually used for data + long numAllocatedChunks; + long totalBytesUsed; // Bytes actually used for data AllocationError errorFlag; }; diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index ef98c1e..93fefb0 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -6,7 +6,7 @@ #ifdef __DOS__ #include "../DOS/EMS.h" -static EMSManager ems; +EMSManager ems; #endif void* MemBlockHandle::GetPtr() @@ -54,7 +54,7 @@ MemBlockAllocator::MemBlockAllocator() void MemBlockAllocator::Init() { // Disable swap for now - //swapFile = fopen("Microweb.swp", "w+"); + swapFile = fopen("Microweb.swp", "wb+"); if (swapFile) { @@ -93,7 +93,7 @@ MemBlockHandle MemBlockAllocator::AllocString(const char* inString) return result; } -MemBlockHandle MemBlockAllocator::Allocate(size_t size) +MemBlockHandle MemBlockAllocator::Allocate(uint16_t size) { MemBlockHandle result; @@ -103,41 +103,53 @@ MemBlockHandle MemBlockAllocator::Allocate(size_t size) result = ems.Allocate(size); if (result.IsAllocated()) { + totalAllocated += size; return result; } } #endif - if (swapFile && size <= MAX_SWAP_ALLOCATION && swapFileLength + size < maxSwapSize) + if (swapFile) { - result.swapFilePosition = swapFileLength; - result.allocatedSize = size; - fseek(swapFile, swapFileLength, SEEK_SET); + uint16_t sizeNeededForSwap = size + sizeof(uint16_t); - char empty[32]; - size_t toWrite = size; - while (toWrite > 0) + if (sizeNeededForSwap <= MAX_SWAP_ALLOCATION && swapFileLength + sizeNeededForSwap + sizeof(uint16_t) < maxSwapSize) { - if (toWrite >= 32) - { - fwrite(empty, 1, 32, swapFile); - toWrite -= 32; - } - else + result.swapFilePosition = swapFileLength; + fseek(swapFile, swapFileLength, SEEK_SET); + + fwrite(&size, sizeof(uint16_t), 1, swapFile); + + char empty[32]; + size_t toWrite = size; + while (toWrite > 0) { - fwrite(empty, 1, toWrite, swapFile); - break; + if (toWrite >= 32) + { + fwrite(empty, 1, 32, swapFile); + toWrite -= 32; + } + else + { + fwrite(empty, 1, toWrite, swapFile); + break; + } } + + swapFileLength += sizeNeededForSwap; + result.type = MemBlockHandle::DiskSwap; + totalAllocated += sizeNeededForSwap; + return result; } - swapFileLength += size; - result.type = MemBlockHandle::DiskSwap; } - else + + //if(0) { - result.conventionalPointer = MemoryManager::pageAllocator.Alloc(size); + result.conventionalPointer = MemoryManager::pageAllocator.Allocate(size); if (result.conventionalPointer) { result.type = MemBlockHandle::Conventional; + totalAllocated += size; } } @@ -149,7 +161,9 @@ void* MemBlockAllocator::AccessSwap(MemBlockHandle& handle) if (lastSwapRead != handle.swapFilePosition) { fseek(swapFile, handle.swapFilePosition, SEEK_SET); - fread(swapBuffer, 1, handle.allocatedSize, swapFile); + uint16_t allocatedSize = 0; + fread(&allocatedSize, sizeof(uint16_t), 1, swapFile); + fread(swapBuffer, 1, allocatedSize, swapFile); lastSwapRead = handle.swapFilePosition; } @@ -159,13 +173,17 @@ void* MemBlockAllocator::AccessSwap(MemBlockHandle& handle) void MemBlockAllocator::CommitSwap(MemBlockHandle& handle) { fseek(swapFile, handle.swapFilePosition, SEEK_SET); - fwrite(swapBuffer, 1, handle.allocatedSize, swapFile); + 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(); diff --git a/src/Memory/MemBlock.h b/src/Memory/MemBlock.h index c938896..61e8c51 100644 --- a/src/Memory/MemBlock.h +++ b/src/Memory/MemBlock.h @@ -9,7 +9,8 @@ // Abstract way of allocating a chunk of memory from conventional memory, EMS, disk swap -struct MemBlockHandle +#pragma pack(push, 1) +struct MemBlockHandle { enum Type { @@ -19,9 +20,9 @@ struct MemBlockHandle DiskSwap }; - Type type; + Type type : 8; - MemBlockHandle() : type(Unallocated), allocatedSize(0) {} + MemBlockHandle() : type(Unallocated) {} MemBlockHandle(void* buffer) : type(Conventional), conventionalPointer(buffer) {} void* GetPtr(); @@ -42,8 +43,8 @@ struct MemBlockHandle uint16_t emsPageOffset; }; }; - size_t allocatedSize; }; +#pragma pack(pop) class LinearAllocator; @@ -55,9 +56,11 @@ class MemBlockAllocator void Init(); void Shutdown(); - MemBlockHandle Allocate(size_t size); + MemBlockHandle Allocate(uint16_t size); MemBlockHandle AllocString(const char* inString); + long TotalAllocated() { return totalAllocated; } + void Reset(); private: @@ -70,6 +73,7 @@ class MemBlockAllocator void* swapBuffer; long lastSwapRead; long maxSwapSize; + long totalAllocated; }; diff --git a/src/Memory/Memory.cpp b/src/Memory/Memory.cpp index 338cc7a..b5e49b8 100644 --- a/src/Memory/Memory.cpp +++ b/src/Memory/Memory.cpp @@ -1,5 +1,41 @@ +#include +#include #include "Memory.h" +#ifdef _DOS +#include +#include "../DOS/EMS.h" +extern EMSManager ems; +#endif LinearAllocator MemoryManager::pageAllocator; -LinearAllocator MemoryManager::interfaceAllocator; +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 index 38f2d98..f0e6fd7 100644 --- a/src/Memory/Memory.h +++ b/src/Memory/Memory.h @@ -1,6 +1,7 @@ #ifndef _MEMORY_H_ #define _MEMORY_H_ +#include "Alloc.h" #include "LinAlloc.h" #include "MemBlock.h" @@ -8,9 +9,11 @@ class MemoryManager { public: static LinearAllocator pageAllocator; - static LinearAllocator interfaceAllocator; + static MallocWrapper interfaceAllocator; static MemBlockAllocator pageBlockAllocator; + + static void GenerateMemoryReport(char* outString); }; diff --git a/src/Node.h b/src/Node.h index e876140..5d99efd 100644 --- a/src/Node.h +++ b/src/Node.h @@ -5,13 +5,14 @@ #include #include "Style.h" #include "Event.h" +#include "Defines.h" +#include "Memory/Alloc.h" class App; class Page; class Node; class Layout; struct DrawContext; -typedef class LinearAllocator Allocator; class NodeHandler { @@ -43,6 +44,7 @@ struct Rect void Clear() { x = y = width = height = 0; } }; +#pragma pack(push, 1) class Node { public: @@ -96,10 +98,9 @@ class Node Node* GetNextInTree(); - Type type; - ElementStyle style; - + Type type : 8; + Coord anchor; // Top left page position Coord size; // Rectangle size that encapsulates node and its children @@ -114,6 +115,7 @@ class Node protected: static NodeHandler* nodeHandlers[Node::NumNodeTypes]; }; +#pragma pack(pop) typedef void(*NodeCallbackFunction)(Node* node); diff --git a/src/Nodes/Break.h b/src/Nodes/Break.h index cbaf9aa..afa8e60 100644 --- a/src/Nodes/Break.h +++ b/src/Nodes/Break.h @@ -9,10 +9,10 @@ class BreakNode : public NodeHandler class Data { public: - Data(int inBreakPadding, bool inDisplayBreakLine, bool inOnlyPadEmptyLines) : breakPadding(inBreakPadding), displayBreakLine(inDisplayBreakLine), onlyPadEmptyLines(inOnlyPadEmptyLines) {} - int breakPadding; - bool displayBreakLine; - bool onlyPadEmptyLines; + 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); diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 08c490a..bf66b58 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -30,7 +30,7 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue, NodeCallbackFunction onSubmit) { - char* buffer = (char*) allocator.Alloc(DEFAULT_TEXT_FIELD_BUFFER_SIZE); + char* buffer = (char*) allocator.Allocate(DEFAULT_TEXT_FIELD_BUFFER_SIZE); if (!buffer) { return nullptr; diff --git a/src/Nodes/Status.h b/src/Nodes/Status.h index 6610026..8973bea 100644 --- a/src/Nodes/Status.h +++ b/src/Nodes/Status.h @@ -3,7 +3,7 @@ #include "../Node.h" -#define MAX_STATUS_BAR_MESSAGE_LENGTH 80 +#define MAX_STATUS_BAR_MESSAGE_LENGTH 100 class StatusBarNode : public NodeHandler { diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 59f8251..8e41e7d 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -98,12 +98,12 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) if (!data->cells) { - data->cells = (TableCellNode::Data**)MemoryManager::pageAllocator.Alloc(sizeof(TableCellNode::Data*) * data->numRows * data->numColumns); + data->cells = (TableCellNode::Data**)MemoryManager::pageAllocator.Allocate(sizeof(TableCellNode::Data*) * data->numRows * data->numColumns); } if (!data->columns) { - data->columns = (TableNode::Data::ColumnInfo*)MemoryManager::pageAllocator.Alloc(sizeof(TableNode::Data::ColumnInfo) * data->numColumns); + data->columns = (TableNode::Data::ColumnInfo*)MemoryManager::pageAllocator.Allocate(sizeof(TableNode::Data::ColumnInfo) * data->numColumns); } if (!data->cells || !data->columns) diff --git a/src/Page.cpp b/src/Page.cpp index b853350..9af1b5e 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -61,31 +61,53 @@ void Page::SetTitle(const char* inTitle) app.ui.SetTitle(inTitle); } - -void Page::DebugDumpNodeGraph(Node* node, int depth) +static const char* nodeTypeNames[] = { - static const char* nodeTypeNames[] = + "Section", + "Text", + "SubText", + "Image", + "Break", + "Style", + "Link", + "Block", + "Button", + "TextField", + "Form", + "StatusBar", + "ScrollBar", + "Table", + "TableRow", + "TableCell", + "Select", + "Option" +}; + +void Page::DebugDumpNodeGraph() +{ + DebugDumpNodeGraph(GetRootNode()); + + int nodeTypeCounts[Node::NumNodeTypes]; + + memset(nodeTypeCounts, 0, sizeof(nodeTypeCounts)); + int totalCount = 0; + + for (Node* node = GetRootNode(); node; node = node->GetNextInTree()) { - "Section", - "Text", - "SubText", - "Image", - "Break", - "Style", - "Link", - "Block", - "Button", - "TextField", - "Form", - "StatusBar", - "ScrollBar", - "Table", - "TableRow", - "TableCell", - "Select", - "Option" - }; + nodeTypeCounts[node->type]++; + totalCount++; + } + + for (int n = 0; n < Node::NumNodeTypes; n++) + { + printf("%s :\t%d\n", nodeTypeNames[n], nodeTypeCounts[n]); + } + printf("Total: %d nodes\n", totalCount); +} + +void Page::DebugDumpNodeGraph(Node* node, int depth) +{ static const char* sectionTypeNames[] = { "Document", diff --git a/src/Page.h b/src/Page.h index ce91935..91a968e 100644 --- a/src/Page.h +++ b/src/Page.h @@ -40,7 +40,7 @@ class Page int GetPageWidth(); int GetPageHeight() { return pageHeight; } - void DebugDumpNodeGraph(Node* node, int depth = 0); + void DebugDumpNodeGraph(); URL pageURL; @@ -51,6 +51,8 @@ class Page private: friend class AppInterface; + void DebugDumpNodeGraph(Node* node, int depth = 0); + App& app; char* title; diff --git a/src/Parser.cpp b/src/Parser.cpp index 223fd97..32f5392 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -123,7 +123,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) page.GetRootNode()->EncapsulateChildren(); #ifdef _WIN32 - page.DebugDumpNodeGraph(page.GetRootNode()); + page.DebugDumpNodeGraph(); #endif //page.GetApp().pageRenderer.RefreshAll(); page.GetApp().pageRenderer.MarkPageLayoutComplete(); diff --git a/src/Stack.h b/src/Stack.h index dc66926..3079a1c 100644 --- a/src/Stack.h +++ b/src/Stack.h @@ -7,7 +7,7 @@ template class Stack { public: - Stack(LinearAllocator& inAllocator) : allocator(inAllocator) + Stack(Allocator& inAllocator) : allocator(inAllocator) { Reset(); } @@ -67,7 +67,7 @@ class Stack private: Entry base; - LinearAllocator& allocator; + Allocator& allocator; }; #endif diff --git a/src/Style.h b/src/Style.h index 4dd6912..9c10591 100644 --- a/src/Style.h +++ b/src/Style.h @@ -4,6 +4,8 @@ #include #include "Font.h" +#pragma pack(push, 1) + struct ElementAlignment { enum Type @@ -108,4 +110,6 @@ struct ElementStyleOverride } }; +#pragma pack(pop) + #endif From d7ed5fcbc192f583bd204d46dc5994aa4026b281 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 12 Mar 2024 19:43:59 +0000 Subject: [PATCH 43/98] Fix bug with parsing http response header for content length --- src/HTTP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HTTP.cpp b/src/HTTP.cpp index f6d2010..3b902f7 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -349,7 +349,7 @@ void HTTPRequest::Update() } else if (!strncmp(lineBuffer, "Content-Length:", 15)) { - contentRemaining = atoi(lineBuffer + 15); + contentRemaining = strtol(lineBuffer + 15, NULL, 10); } else if (!stricmp(lineBuffer, "Transfer-Encoding: chunked")) { From da786b149b32af67b15100df36d7b1d0cb5a0ee1 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 12 Mar 2024 19:44:26 +0000 Subject: [PATCH 44/98] Added debug option for dumping content from http request --- src/App.cpp | 13 +++++++++++++ src/App.h | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/App.cpp b/src/App.cpp index a01bb85..654b097 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -82,6 +82,11 @@ void App::Run(int argc, char* argv[]) size_t bytesRead = loadTask.GetContent(buffer, 256); if (bytesRead) { + if (loadTask.debugDumpFile) + { + fwrite(buffer, 1, bytesRead, loadTask.debugDumpFile); + } + if (loadTaskTargetNode == page.GetRootNode()) { parser.Parse(buffer, bytesRead); @@ -189,11 +194,19 @@ void LoadTask::Load(const char* targetURL) if (type == LoadTask::RemoteFile) { request = Platform::network->CreateRequest(url.url); + + //debugDumpFile = fopen("dump.htm", "wb"); } } void LoadTask::Stop() { + if (debugDumpFile) + { + fclose(debugDumpFile); + debugDumpFile = NULL; + } + switch (type) { case LoadTask::LocalFile: diff --git a/src/App.h b/src/App.h index 2ef6aeb..bfc20bc 100644 --- a/src/App.h +++ b/src/App.h @@ -28,7 +28,7 @@ class HTTPRequest; struct LoadTask { - LoadTask() : type(LocalFile), fs(NULL) {} + LoadTask() : type(LocalFile), fs(NULL), debugDumpFile(NULL) {} void Load(const char* url); void Stop(); @@ -50,6 +50,8 @@ struct LoadTask FILE* fs; HTTPRequest* request; }; + + FILE* debugDumpFile; }; struct Widget; From f12e04d208876415c6aa29ba498c41af1468a829 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 12 Mar 2024 20:42:37 +0000 Subject: [PATCH 45/98] Memory optimisation - avoid generating text sub nodes when possible --- src/Nodes/Text.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index c28c414..40210b3 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -141,6 +141,17 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) 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) { @@ -196,7 +207,10 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) data->text.Commit(); } - node->EncapsulateChildren(); + if (node->firstChild) + { + node->EncapsulateChildren(); + } } Node* SubTextElement::Construct(Allocator& allocator, int startIndex, int length) From 97611f7d8d5a63f0e3b6bd327b8bd170b01bc723 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 13 Mar 2024 12:41:30 +0000 Subject: [PATCH 46/98] Improved UI interactions with field nodes --- src/DOS/DOSInput.cpp | 19 --- src/DOS/DOSInput.h | 1 - src/DOS/Platform.cpp | 8 -- src/DOS/Surf4bpp.cpp | 149 ++++++++------------ src/Event.h | 11 +- src/Interface.cpp | 35 ++++- src/Interface.h | 6 +- src/KeyCodes.h | 4 +- src/Nodes/Button.cpp | 36 +++-- src/Nodes/Button.h | 1 + src/Nodes/Field.cpp | 297 +++++++++++++++++++++++++++++++++------ src/Nodes/Field.h | 14 +- src/Nodes/LinkNode.cpp | 2 +- src/Parser.cpp | 10 ++ src/Windows/WinInput.cpp | 8 +- 15 files changed, 402 insertions(+), 199 deletions(-) diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index 6edea5e..87ae90f 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -31,7 +31,6 @@ void DOSInputDriver::Init() currentCursor = MouseCursor::Hand; SetMouseCursor(MouseCursor::Pointer); - lastMouseButtons = 0; mouseHideCount = 1; ShowMouse(); @@ -159,23 +158,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 f0911e1..7f7afa0 100644 --- a/src/DOS/DOSInput.h +++ b/src/DOS/DOSInput.h @@ -32,7 +32,6 @@ class DOSInputDriver : public InputDriver private: MouseCursor::Type currentCursor; - int lastMouseButtons; bool mouseVisible; bool hasMouse; int mouseHideCount; diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 4b89248..4aa0b7f 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -168,14 +168,6 @@ bool Platform::Init(int argc, char* argv[]) } } - // - printf("Node: %d\n", sizeof(Node)); - printf("BlockHandle: %d\n", sizeof(MemBlockHandle)); - printf("void*: %d\n", sizeof(void*)); - printf("Style: %d\n", sizeof(ElementStyle)); - getchar(); - // - int suggestedMode = AutoDetectVideoMode(); VideoModeInfo* videoMode = ShowVideoModePicker(suggestedMode); if (!videoMode) diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index fdb80a1..231f422 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -25,11 +25,6 @@ static uint8_t pixelBitmasks[8] = 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; -static uint8_t pixelStartBitmasks[8] = -{ - 0xff, 0x7f, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x01 -}; - static uint8_t pixelEndBitmasks[8] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe @@ -83,54 +78,42 @@ void DrawSurface_4BPP::HLine(DrawContext& context, int x, int y, int count, uint SetPenColour(colour); uint8_t* VRAMptr = lines[y]; - uint8_t* VRAMend = VRAMptr; VRAMptr += (x >> 3); - VRAMend += ((x + count) >> 3); - if (VRAMptr == VRAMend) + // Start pixels + uint8_t mask = 0; + while (count--) { - // Starts and ends within the same byte - uint8_t mask = 0; - while (count--) - { - mask |= pixelBitmasks[x & 7]; - x++; - } + mask |= pixelBitmasks[x & 7]; + x++; + if (!(x & 7)) + break; + } - // Set bitmask - outp(GC_INDEX, GC_BITMASK); - outp(GC_DATA, mask); + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); - *VRAMptr |= 0xff; - } - else + *VRAMptr++ |= 0xff; + + if (!count) + return; + + // Middle bytes + outp(GC_DATA, 0xff); + while (count >= 8) { - uint8_t mask; + count -= 8; + *VRAMptr++ |= 0xff; + } - // Start byte - mask = pixelStartBitmasks[x & 7]; - outp(GC_INDEX, GC_BITMASK); + // End byte + if (count > 0) + { + mask = pixelEndBitmasks[count]; outp(GC_DATA, mask); *VRAMptr++ |= 0xff; - - // Middle bytes - count -= 8 - (x & 7); - 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) @@ -218,38 +201,28 @@ void DrawSurface_4BPP::FillRect(DrawContext& context, int x, int y, int width, i { int count = width; uint8_t* VRAMptr = lines[y]; - uint8_t* VRAMend = VRAMptr; VRAMptr += (x >> 3); - VRAMend += ((x + count) >> 3); - if (VRAMptr == VRAMend) + // Start pixels + uint8_t mask = 0; + int workX = x; + while (count--) { - // Starts and ends within the same byte - uint8_t mask = 0; - while (count--) - { - mask |= pixelBitmasks[x & 7]; - x++; - } - - // Set bitmask - outp(GC_INDEX, GC_BITMASK); - outp(GC_DATA, mask); - - *VRAMptr |= 0xff; + mask |= pixelBitmasks[workX & 7]; + workX++; + if (!(workX & 7)) + break; } - else - { - uint8_t mask; - // Start byte - mask = pixelStartBitmasks[x & 7]; - outp(GC_INDEX, GC_BITMASK); - outp(GC_DATA, mask); - *VRAMptr++ |= 0xff; + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + + *VRAMptr++ |= 0xff; + if (count) + { // Middle bytes - count -= 8 - (x & 7); outp(GC_DATA, 0xff); while (count >= 8) { @@ -556,38 +529,28 @@ void DrawSurface_4BPP::InvertRect(DrawContext& context, int x, int y, int width, { int count = width; uint8_t* VRAMptr = lines[y]; - uint8_t* VRAMend = VRAMptr; VRAMptr += (x >> 3); - VRAMend += ((x + count) >> 3); - if (VRAMptr == VRAMend) + // Start pixels + uint8_t mask = 0; + int workX = x; + while (count--) { - // Starts and ends within the same byte - uint8_t mask = 0; - while (count--) - { - mask |= pixelBitmasks[x & 7]; - x++; - } - - // Set bitmask - outp(GC_INDEX, GC_BITMASK); - outp(GC_DATA, mask); - - *VRAMptr |= 0xff; + mask |= pixelBitmasks[workX & 7]; + workX++; + if (!(workX & 7)) + break; } - else - { - uint8_t mask; - // Start byte - mask = pixelStartBitmasks[x & 7]; - outp(GC_INDEX, GC_BITMASK); - outp(GC_DATA, mask); - *VRAMptr++ |= 0xff; + // Set bitmask + outp(GC_INDEX, GC_BITMASK); + outp(GC_DATA, mask); + *VRAMptr++ |= 0xff; + + if (count) + { // Middle bytes - count -= 8 - (x & 7); outp(GC_DATA, 0xff); while (count >= 8) { diff --git a/src/Event.h b/src/Event.h index ad9cfbb..31210b1 100644 --- a/src/Event.h +++ b/src/Event.h @@ -12,15 +12,20 @@ struct Event { MouseClick, MouseRelease, - KeyPress + MouseDrag, + KeyPress, + Focus, + Unfocus }; - Event(App& inApp, Type inType) : app(inApp), type(inType), key(0) {} - Event(App& inApp, Type inType, InputButtonCode inKey) : app(inApp), type(inType), key(inKey) {} + 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/Interface.cpp b/src/Interface.cpp index f4d89eb..5686a92 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -89,7 +89,11 @@ void AppInterface::Update() } else if (!(buttons & 1) && (oldButtons & 1)) { - HandleRelease(); + HandleRelease(mouseX, mouseY); + } + else if ((buttons & 1) && (oldButtons & 1) && (mouseX != oldMouseX || mouseY != oldMouseY)) + { + HandleDrag(mouseX, mouseY); } oldMouseX = mouseX; @@ -271,16 +275,27 @@ void AppInterface::HandleClick(int mouseX, int mouseY) { if (hoverNode) { - FocusNode(hoverNode); - focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseClick)); + hoverNode->Handler().HandleEvent(hoverNode, Event(app, Event::MouseClick, mouseX, mouseY)); + } + else if (focusedNode) + { + FocusNode(nullptr); + } +} + +void AppInterface::HandleDrag(int mouseX, int mouseY) +{ + if (focusedNode) + { + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseDrag, mouseX, mouseY)); } } -void AppInterface::HandleRelease() +void AppInterface::HandleRelease(int mouseX, int mouseY) { if (focusedNode) { - focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseRelease)); + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::MouseRelease, mouseX, mouseY)); } } @@ -410,7 +425,17 @@ void AppInterface::FocusNode(Node* node) { if (node != focusedNode) { + if (focusedNode) + { + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::Unfocus)); + } + focusedNode = node; + + if (focusedNode) + { + focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::Focus)); + } } } diff --git a/src/Interface.h b/src/Interface.h index 899aec3..59c8635 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -46,6 +46,7 @@ class AppInterface Node* GetHoverNode() { return hoverNode; } Node* GetRootInterfaceNode() { return rootInterfaceNode; } bool IsInterfaceNode(Node* node); + bool IsOverNode(Node* node, int x, int y); int GetScrollPositionY() { return scrollPositionY; } void ScrollRelative(int delta); @@ -62,9 +63,8 @@ class AppInterface Node* PickNode(int x, int y); void HandleClick(int mouseX, int mouseY); - void HandleRelease(); - - bool IsOverNode(Node* node, int x, int y); + void HandleRelease(int mouseX, int mouseY); + void HandleDrag(int mouseX, int mouseY); static void OnBackButtonPressed(Node* node); static void OnForwardButtonPressed(Node* node); diff --git a/src/KeyCodes.h b/src/KeyCodes.h index e435df3..6c04e19 100644 --- a/src/KeyCodes.h +++ b/src/KeyCodes.h @@ -27,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 diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index fa3d517..77d867c 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -84,32 +84,42 @@ void ButtonNode::GenerateLayout(Layout& layout, Node* node) bool ButtonNode::HandleEvent(Node* node, const Event& event) { + AppInterface& ui = App::Get().ui; + switch (event.type) { case Event::MouseClick: { - DrawContext context; - event.app.pageRenderer.GenerateDrawContext(context, node); - context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); - return true; + InvertButton(node); + ui.FocusNode(node); } + return true; case Event::MouseRelease: { - DrawContext context; - event.app.pageRenderer.GenerateDrawContext(context, node); - context.surface->InvertRect(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, node->size.y - 3); - - ButtonNode::Data* data = static_cast(node->data); - if (data->onClick) + InvertButton(node); + if (ui.IsOverNode(node, event.x, event.y)) { - data->onClick(node); + ButtonNode::Data* data = static_cast(node->data); + if (data->onClick) + { + data->onClick(node); + } } - - return true; } + 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(); +} diff --git a/src/Nodes/Button.h b/src/Nodes/Button.h index 3ff3d8d..468ff55 100644 --- a/src/Nodes/Button.h +++ b/src/Nodes/Button.h @@ -21,6 +21,7 @@ class ButtonNode : public NodeHandler virtual bool HandleEvent(Node* node, const Event& event); static Coord CalculateSize(Node* node); + void InvertButton(Node* node); }; #endif diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index bf66b58..4af1122 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -20,11 +20,26 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) 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); - context.surface->DrawString(context, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); + + DrawContext subContext = context; + subContext.clipRight = node->anchor.x + node->size.x - 2; if (node == App::Get().ui.GetFocusedNode()) { - DrawCursor(context, node, false); + subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); + + if (selectionLength > 0) + { + DrawSelection(subContext, node); + } + else + { + DrawCursor(context, node); + } + } + else + { + subContext.surface->DrawString(subContext, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); } } @@ -84,20 +99,101 @@ void TextFieldNode::GenerateLayout(Layout& layout, Node* node) 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::MouseRelease: - cursorPosition = -1; + case Event::Focus: + shiftPosition = 0; + ShiftIntoView(node); + + if (App::Get().ui.IsInterfaceNode(node)) + { + // 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); + } + break; + 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); + } + } + break; + 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 (cursorPosition == -1) + if (selectionLength > 0) { - data->buffer[0] = '\0'; - cursorPosition = 0; - node->Redraw(); + DeleteSelectionContents(node); } if (cursorPosition < data->bufferSize) @@ -111,15 +207,14 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) MoveCursorPosition(node, cursorPosition + 1); RedrawModified(node, cursorPosition - 1); } + ShiftIntoView(node); return true; } else if (event.key == KEYCODE_BACKSPACE) { - if (cursorPosition == -1) + if (selectionLength > 0) { - data->buffer[0] = '\0'; - node->Redraw(); - MoveCursorPosition(node, 0); + DeleteSelectionContents(node); } else if (cursorPosition > 0) { @@ -131,15 +226,15 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) MoveCursorPosition(node, cursorPosition - 1); RedrawModified(node, cursorPosition); } + ShiftIntoView(node); return true; } else if (event.key == KEYCODE_DELETE) { int len = strlen(data->buffer); - if (cursorPosition == -1) + if (selectionLength > 0) { - data->buffer[0] = '\0'; - node->Redraw(); + DeleteSelectionContents(node); } else if (cursorPosition < len) { @@ -149,6 +244,7 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) } RedrawModified(node, cursorPosition); } + ShiftIntoView(node); return true; } else if (event.key == KEYCODE_ENTER) @@ -157,63 +253,58 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) { data->onSubmit(node); } - /*if (activeWidget == &addressBar) - { - app.OpenURL(addressBarURL.url); - } - else if (activeWidget->textField->form) - { - SubmitForm(activeWidget->textField->form); - } - DeactivateWidget();*/ return true; } else if (event.key == KEYCODE_ARROW_LEFT) { - if (cursorPosition == -1) + if (selectionLength > 0) { - MoveCursorPosition(node, strlen(data->buffer)); + ClearSelection(node); } else if (cursorPosition > 0) { MoveCursorPosition(node, cursorPosition - 1); } + ShiftIntoView(node); return true; } else if (event.key == KEYCODE_ARROW_RIGHT) { - if (cursorPosition == -1) + if (selectionLength > 0) { - MoveCursorPosition(node, strlen(data->buffer)); + ClearSelection(node); } else if (cursorPosition < strlen(data->buffer)) { MoveCursorPosition(node, cursorPosition + 1); } + ShiftIntoView(node); return true; } else if (event.key == KEYCODE_END) { - if (cursorPosition == -1) + if (selectionLength > 0) { - MoveCursorPosition(node, strlen(data->buffer)); + ClearSelection(node); } else { MoveCursorPosition(node, strlen(data->buffer)); } + ShiftIntoView(node); return true; } else if (event.key == KEYCODE_HOME) { - if (cursorPosition == -1) + if (selectionLength > 0) { - MoveCursorPosition(node, strlen(data->buffer)); + ClearSelection(node); } else { MoveCursorPosition(node, 0); } + ShiftIntoView(node); return true; } break; @@ -224,22 +315,39 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) return false; } -void TextFieldNode::DrawCursor(DrawContext& context, Node* node, bool clear) +int TextFieldNode::GetBufferPixelWidth(Node* node, int start, int end) { TextFieldNode::Data* data = static_cast(node->data); Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - - - int x = node->anchor.x + 3; - int height = font->glyphHeight; + int x = 0; - for (int n = 0; n < cursorPosition; n++) + for (int n = 0; n < end; n++) { if (!data->buffer[n]) break; - x += font->GetGlyphWidth(data->buffer[n]); + + if (n >= start) + { + 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 = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + int x = node->anchor.x + 3; + int height = font->glyphHeight; + + x += GetBufferPixelWidth(node, shiftPosition, cursorPosition); + if (x >= node->anchor.x + node->size.x - 1) { return; @@ -247,7 +355,7 @@ void TextFieldNode::DrawCursor(DrawContext& context, Node* node, bool clear) int y = node->anchor.y + 2; - context.surface->VLine(context, x, y, height, clear ? Platform::video->colourScheme.pageColour : Platform::video->colourScheme.textColour); + context.surface->InvertRect(context, x, y, 1, height); } void TextFieldNode::MoveCursorPosition(Node* node, int newPosition) @@ -256,20 +364,121 @@ void TextFieldNode::MoveCursorPosition(Node* node, int newPosition) DrawContext context; App::Get().pageRenderer.GenerateDrawContext(context, node); - DrawCursor(context, node, true); + DrawCursor(context, node); cursorPosition = newPosition; - DrawCursor(context, node, false); + DrawCursor(context, node); + + Platform::input->ShowMouse(); +} + +void TextFieldNode::DrawSelection(DrawContext& context, Node* node) +{ + TextFieldNode::Data* data = static_cast(node->data); + + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + int selectionX1 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition); + int selectionX2 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition + selectionLength); + Platform::input->HideMouse(); + context.surface->InvertRect(context, selectionX1 + node->anchor.x + 3, 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 = Assets.GetFont(node->style.fontSize, node->style.fontStyle); 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) + 3; + int clearWidth = node->size.x - 1 - drawPosition; + drawPosition += node->anchor.x; - Platform::input->HideMouse(); App::Get().pageRenderer.GenerateDrawContext(context, node); - Draw(context, node); + Platform::input->HideMouse(); + context.surface->FillRect(context, drawPosition, node->anchor.y + 1, clearWidth, node->size.y - 2, clearColour); + context.surface->DrawString(context, font, data->buffer + position, drawPosition, node->anchor.y + 2, textColour, node->style.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 + 3; + int result = shiftPosition; + + TextFieldNode::Data* data = static_cast(node->data); + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + while(x > 0) + { + if (!data->buffer[result]) + { + break; + } + + x -= font->GetGlyphWidth(data->buffer[result]); + result++; + } + + return result; +} diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 164b614..741c96e 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -26,13 +26,21 @@ class TextFieldNode : public NodeHandler virtual bool HandleEvent(Node* node, const Event& event) override; private: + int shiftPosition; int cursorPosition; + int selectionStartPosition; + int selectionLength; + int pickedPosition; - void DrawCursor(DrawContext& context, Node* node, bool clear); + 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); }; #endif diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 82883f7..8ff8633 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -23,7 +23,7 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) { switch (event.type) { - case Event::MouseRelease: + case Event::MouseClick: { LinkNode::Data* data = static_cast(node->data); if (data->url) diff --git a/src/Parser.cpp b/src/Parser.cpp index 32f5392..5fa770a 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -505,6 +505,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) diff --git a/src/Windows/WinInput.cpp b/src/Windows/WinInput.cpp index 6a8a449..a248e36 100644 --- a/src/Windows/WinInput.cpp +++ b/src/Windows/WinInput.cpp @@ -142,10 +142,10 @@ 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: From e9a1a7e09f36a46b8e603cf0ba32bca859a7a604 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 13 Mar 2024 14:53:30 +0000 Subject: [PATCH 47/98] First pass keyboard navigation --- src/App.cpp | 12 ++++++- src/Draw/Surf2bpp.cpp | 2 +- src/Draw/Surf8bpp.cpp | 63 ++++++++++++++++++++------------ src/Image/Gif.cpp | 4 +++ src/Interface.cpp | 73 +++++++++++++++++++++++++------------ src/Interface.h | 4 ++- src/Layout.cpp | 2 +- src/Node.cpp | 52 +++++++++++++++++++++++++++ src/Node.h | 2 ++ src/Nodes/Button.cpp | 48 +++++++++++++++++++++++-- src/Nodes/Button.h | 5 +++ src/Nodes/Field.cpp | 2 +- src/Nodes/LinkNode.cpp | 77 ++++++++++++++++++++++++++++++++++++++++ src/Nodes/LinkNode.h | 3 ++ src/Nodes/Text.cpp | 15 +++++++- src/Page.cpp | 5 --- src/Page.h | 3 -- src/Render.cpp | 22 ++++++++++++ src/Render.h | 4 +++ src/Windows/WinInput.cpp | 25 +++++++++++++ 20 files changed, 361 insertions(+), 62 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 654b097..c0d6437 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -50,18 +50,28 @@ void App::Run(int argc, char* argv[]) ui.Init(); pageRenderer.Init(); + char* targetURL = nullptr; if (argc > 1) { for (int n = 1; n < argc; n++) { if (*argv[n] != '-') { - OpenURL(argv[n]); + targetURL = argv[n]; break; } } } + if (targetURL) + { + OpenURL(targetURL); + } + else + { + ui.FocusNode(ui.addressBarNode); + } + while (running) { Platform::Update(); diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp index 8814fc7..21e0fd3 100644 --- a/src/Draw/Surf2bpp.cpp +++ b/src/Draw/Surf2bpp.cpp @@ -388,7 +388,7 @@ void DrawSurface_2BPP::InvertRect(DrawContext& context, int x, int y, int width, VRAMptr += (x >> 2); int count = width; uint8_t data = *VRAMptr; - uint8_t mask = (0xc0 >> (x & 3)); + uint8_t mask = bitmaskTable[x & 3]; while (count--) { diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index 7329a40..3a915b1 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -452,61 +452,78 @@ void DrawSurface_8BPP::InvertRect(DrawContext& context, int x, int y, int width, } } +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) { -#if 0 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; while (position--) { - *(uint16_t*)(&lines[y++][x]) = inner; + memcpy(&lines[y++][x], inner, 16); } - 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; + memcpy(&lines[y++][x], inner, 16); + memcpy(&lines[y++][x], widgetEdge, 16); while (topPaddingSize--) { - *(uint16_t*)(&lines[y++][x]) = widgetInner; + memcpy(&lines[y++][x], widgetInner, 16); } - *(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; + 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--) { - *(uint16_t*)(&lines[y++][x]) = widgetInner; + memcpy(&lines[y++][x], widgetInner, 16); } - *(uint16_t*)(&lines[y++][x]) = widgetEdge; - *(uint16_t*)(&lines[y++][x]) = inner; + memcpy(&lines[y++][x], widgetEdge, 16); + memcpy(&lines[y++][x], inner, 16); while (bottomSpacing--) { - *(uint16_t*)(&lines[y++][x]) = inner; + memcpy(&lines[y++][x], inner, 16); } -#endif } void DrawSurface_8BPP::Clear() diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index c0b7888..49de7f4 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -657,6 +657,10 @@ void GifDecoder::EmitLine(int y) if (outputImage->bpp == 8) { bool useColourDithering = true; + //if (Platform::video->paletteLUT == compositeCgaPaletteLUT) + //{ + // useColourDithering = false; + //} if (useColourDithering) { diff --git a/src/Interface.cpp b/src/Interface.cpp index 5686a92..aced89e 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -63,12 +63,6 @@ void AppInterface::Reset() void AppInterface::Update() { - if (app.page.GetPageHeight() != oldPageHeight) - { - oldPageHeight = app.page.GetPageHeight(); - UpdatePageScrollBar(); - } - int buttons, mouseX, mouseY; Platform::input->GetMouseStatus(buttons, mouseX, mouseY); @@ -179,7 +173,13 @@ void AppInterface::Update() ScrollAbsolute(0); break; case KEYCODE_END: - //ScrollAbsolute(0); + { + int end = app.pageRenderer.GetVisiblePageHeight() - windowRect.height; + if (end > 0) + { + ScrollAbsolute(end); + } + } break; case KEYCODE_BACKSPACE: app.PreviousPage(); @@ -194,14 +194,14 @@ void AppInterface::Update() break; case KEYCODE_CTRL_L: case KEYCODE_F6: - //ActivateWidget(&addressBar); + FocusNode(addressBarNode); break; case KEYCODE_TAB: - //CycleWidgets(1); + CycleNodes(1); break; case KEYCODE_SHIFT_TAB: - //CycleWidgets(-1); + CycleNodes(-1); break; case 'm': @@ -309,7 +309,7 @@ void AppInterface::UpdateAddressBar(const URL& url) void AppInterface::UpdatePageScrollBar() { ScrollBarNode::Data* data = static_cast(scrollBarNode->data); - int maxScrollHeight = app.page.GetRootNode()->size.y - app.ui.windowRect.height; + int maxScrollHeight = app.pageRenderer.GetVisiblePageHeight() - app.ui.windowRect.height; if (maxScrollHeight < 0) maxScrollHeight = 0; data->scrollPosition = scrollPositionY; @@ -474,7 +474,7 @@ void AppInterface::ScrollRelative(int delta) if (scrollPositionY < 0) scrollPositionY = 0; - int maxScrollY = app.page.GetRootNode()->size.y - app.ui.windowRect.height; + int maxScrollY = app.pageRenderer.GetVisiblePageHeight() - app.ui.windowRect.height; if (maxScrollY > 0 && scrollPositionY > maxScrollY) { scrollPositionY = maxScrollY; @@ -484,21 +484,48 @@ void AppInterface::ScrollRelative(int delta) UpdatePageScrollBar(); - //Platform::input->HideMouse(); - app.pageRenderer.OnPageScroll(delta); - - //DrawContext context; - //app.pageRenderer.GenerateDrawContext(context, NULL); - //context.drawOffsetY = 0; - //context.surface->FillRect(context, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); - //app.pageRenderer.GenerateDrawContext(context, NULL); - ////app.pageRenderer.DrawAll(context, app.page.GetRootNode()); - - Platform::input->ShowMouse(); } void AppInterface::ScrollAbsolute(int position) { + int delta = position - scrollPositionY; + if (delta) + { + ScrollRelative(delta); + } +} + +void AppInterface::CycleNodes(int direction) +{ + Node* node = focusedNode; + + if (!node) + { + node = app.page.GetRootNode(); + } + if (node) + { + if (!IsInterfaceNode(node)) + { + while (node) + { + if (direction > 0) + { + node = node->GetNextInTree(); + } + else + { + node = node->GetPreviousInTree(); + } + + if (node && node->Handler().CanPick(node)) + { + FocusNode(node); + return; + } + } + } + } } diff --git a/src/Interface.h b/src/Interface.h index 59c8635..8104397 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -53,6 +53,7 @@ class AppInterface void ScrollAbsolute(int position); + Node* addressBarNode; URL addressBarURL; Rect windowRect; @@ -66,6 +67,8 @@ class AppInterface void HandleRelease(int mouseX, int mouseY); void HandleDrag(int mouseX, int mouseY); + void CycleNodes(int direction); + static void OnBackButtonPressed(Node* node); static void OnForwardButtonPressed(Node* node); static void OnAddressBarSubmit(Node* node); @@ -83,7 +86,6 @@ class AppInterface Node* titleNode; Node* backButtonNode; Node* forwardButtonNode; - Node* addressBarNode; Node* statusBarNode; Node* scrollBarNode; diff --git a/src/Layout.cpp b/src/Layout.cpp index 4d18e13..9cf718d 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -21,7 +21,7 @@ void Layout::Reset() LayoutParams& params = GetParams(); params.marginLeft = 0; - params.marginRight = page.GetPageWidth(); + params.marginRight = page.GetApp().ui.windowRect.width; } void Layout::BreakNewLine() diff --git a/src/Node.cpp b/src/Node.cpp index d701fed..c97ef86 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -237,6 +237,45 @@ void Node::Redraw() 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; @@ -261,3 +300,16 @@ Node* Node::GetNextInTree() return nullptr; } + +bool Node::IsChildOf(Node* potentialParent) +{ + for(Node* node = parent; node; node = node->parent) + { + if (node == potentialParent) + { + return true; + } + } + + return false; +} diff --git a/src/Node.h b/src/Node.h index 5d99efd..f1c1696 100644 --- a/src/Node.h +++ b/src/Node.h @@ -93,9 +93,11 @@ class Node } return nullptr; } + bool IsChildOf(Node* node); void Redraw(); + Node* GetPreviousInTree(); Node* GetNextInTree(); ElementStyle style; diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index 77d867c..f795af0 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -6,6 +6,7 @@ #include "../Layout.h" #include "../Event.h" #include "../App.h" +#include "../KeyCodes.h" void ButtonNode::Draw(DrawContext& context, Node* node) { @@ -24,6 +25,14 @@ void ButtonNode::Draw(DrawContext& context, Node* node) 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->style.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); + } } } @@ -85,21 +94,23 @@ void ButtonNode::GenerateLayout(Layout& layout, Node* node) 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: { - InvertButton(node); + 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)) { - ButtonNode::Data* data = static_cast(node->data); if (data->onClick) { data->onClick(node); @@ -107,6 +118,26 @@ bool ButtonNode::HandleEvent(Node* node, const Event& event) } } 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; } @@ -123,3 +154,16 @@ void ButtonNode::InvertButton(Node* node) 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 index 468ff55..97f0330 100644 --- a/src/Nodes/Button.h +++ b/src/Nodes/Button.h @@ -14,6 +14,8 @@ class ButtonNode : public NodeHandler 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; @@ -22,6 +24,9 @@ class ButtonNode : public NodeHandler static Coord CalculateSize(Node* node); void InvertButton(Node* node); + void HighlightButton(Node* node, uint8_t colour); + + bool focusingFromMouseClick; }; #endif diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 4af1122..d9a8f09 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -108,7 +108,7 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) shiftPosition = 0; ShiftIntoView(node); - if (App::Get().ui.IsInterfaceNode(node)) + if (App::Get().ui.IsInterfaceNode(node) && strlen(data->buffer) > 0) { // This is the address bar cursorPosition = strlen(data->buffer); diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 8ff8633..1613bb2 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -23,6 +23,16 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) { switch (event.type) { + case Event::Focus: + { + HighlightChildren(node); + } + return true; + case Event::Unfocus: + { + HighlightChildren(node); + } + return true; case Event::MouseClick: { LinkNode::Data* data = static_cast(node->data); @@ -38,3 +48,70 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) return false; } + +void LinkNode::HighlightChildren(Node* node) +{ + DrawContext context; + App::Get().pageRenderer.GenerateDrawContext(context, node); + + Node* child = node->firstChild; + if (!child) + return; + + bool isDescendingTree = true; + + Platform::input->HideMouse(); + + 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) + { + context.surface->InvertRect(context, child->anchor.x, child->anchor.y, child->size.x, child->size.y); + } + + 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; + } + } + } + + Platform::input->ShowMouse(); + +} diff --git a/src/Nodes/LinkNode.h b/src/Nodes/LinkNode.h index f5962cf..a1edce7 100644 --- a/src/Nodes/LinkNode.h +++ b/src/Nodes/LinkNode.h @@ -17,4 +17,7 @@ class LinkNode : public NodeHandler 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/Text.cpp b/src/Nodes/Text.cpp index 40210b3..4a6cfac 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -5,6 +5,8 @@ #include "../Layout.h" #include "../Draw/Surface.h" #include "../DataPack.h" +#include "../Interface.h" +#include "../App.h" Node* TextElement::Construct(Allocator& allocator, const char* text) { @@ -33,6 +35,12 @@ void TextElement::Draw(DrawContext& context, Node* node) char* text = data->text.Get(); context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.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); + } } } @@ -244,8 +252,13 @@ void SubTextElement::Draw(DrawContext& context, Node* node) context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.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; - //Platform::video->InvertRect(node->anchor.x, node->anchor.y + 50, node->size.x, node->size.y); //printf("%s [%d, %d](%d %d)", data->text, node->anchor.x, node->anchor.y, node->style.fontStyle, node->style.fontSize); } } diff --git a/src/Page.cpp b/src/Page.cpp index 9af1b5e..d56921c 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -204,11 +204,6 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) } } -int Page::GetPageWidth() -{ - return app.ui.windowRect.width; -} - Node* Page::ProcessNextLoadTask(Node* lastNode, LoadTask& loadTask) { Node* node; diff --git a/src/Page.h b/src/Page.h index 91a968e..959b5fe 100644 --- a/src/Page.h +++ b/src/Page.h @@ -37,9 +37,6 @@ class Page Layout layout; - int GetPageWidth(); - int GetPageHeight() { return pageHeight; } - void DebugDumpNodeGraph(); URL pageURL; diff --git a/src/Render.cpp b/src/Render.cpp index f7a41db..fb6fbd3 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -134,6 +134,10 @@ void PageRenderer::OnPageScroll(int scrollDelta) { 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, Platform::video->colourScheme.pageColour); } @@ -142,6 +146,10 @@ void PageRenderer::OnPageScroll(int scrollDelta) 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, Platform::video->colourScheme.pageColour); } @@ -198,6 +206,8 @@ void PageRenderer::Reset() Rect& windowRect = app.ui.windowRect; lowerContext.clipBottom = lowerContext.clipTop = windowRect.y + windowRect.height; + + visiblePageHeight = 0; } bool PageRenderer::DoesOverlapWithContext(Node* node, DrawContext& context) @@ -331,6 +341,8 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) int minWinY = windowRect.y; int maxWinY = windowRect.y + windowRect.height; + bool expandedPage = false; + for (Node* node = startNode; node; node = node->GetNextInTree()) { if (IsRenderableNode(node->type)) @@ -339,6 +351,12 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) int nodeBottom = nodeTop + node->size.y; bool outsideOfWindow = (nodeTop > maxWinY) || (nodeBottom < minWinY); + if (nodeBottom > visiblePageHeight) + { + visiblePageHeight = nodeBottom; + expandedPage = true; + } + if (!outsideOfWindow) { if (upperContext.clipBottom < nodeBottom) @@ -361,6 +379,10 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) break; } + if (expandedPage) + { + App::Get().ui.UpdatePageScrollBar(); + } } void PageRenderer::MarkNodeDirty(Node* dirtyNode) diff --git a/src/Render.h b/src/Render.h index 4e5a11b..56f4d3f 100644 --- a/src/Render.h +++ b/src/Render.h @@ -31,6 +31,8 @@ class PageRenderer void MarkPageLayoutComplete(); void MarkNodeDirty(Node* node); + int GetVisiblePageHeight() { return visiblePageHeight; } + private: bool IsInRenderQueue(Node* node); @@ -55,6 +57,8 @@ class PageRenderer // Draw contexts for upper and lower parts of the screen to facilitate scrolling DrawContext upperContext; DrawContext lowerContext; + + int visiblePageHeight; }; #endif diff --git a/src/Windows/WinInput.cpp b/src/Windows/WinInput.cpp index a248e36..2710d18 100644 --- a/src/Windows/WinInput.cpp +++ b/src/Windows/WinInput.cpp @@ -170,6 +170,31 @@ InputButtonCode WindowsInputDriver::TranslateCode(WPARAM code) 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; } From 4489e0e8818c3ac9016ed9b743f9adf7e9ebbf4e Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 14 Mar 2024 21:40:44 +0000 Subject: [PATCH 48/98] Working scroll bar --- src/Interface.cpp | 9 +++- src/Interface.h | 2 +- src/Memory/MemBlock.cpp | 2 +- src/Nodes/Field.h | 12 ++++- src/Nodes/Scroll.cpp | 100 +++++++++++++++++++++++++++++++--------- src/Nodes/Scroll.h | 12 ++++- 6 files changed, 110 insertions(+), 27 deletions(-) diff --git a/src/Interface.cpp b/src/Interface.cpp index aced89e..d1e74a2 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -371,7 +371,7 @@ void AppInterface::GenerateInterfaceNodes() rootInterfaceNode->AddChild(statusBarNode); statusBarNode->style.fontSize = 0; - scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight); + scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight, OnScrollBarMoved); scrollBarNode->style = rootInterfaceNode->style; scrollBarNode->anchor.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; scrollBarNode->size.x = 16; @@ -529,3 +529,10 @@ void AppInterface::CycleNodes(int direction) } } } + +void AppInterface::OnScrollBarMoved(Node* node) +{ + ScrollBarNode::Data* data = static_cast(node->data); + AppInterface& ui = App::Get().ui; + ui.ScrollRelative(data->scrollPosition - ui.scrollPositionY); +} diff --git a/src/Interface.h b/src/Interface.h index 8104397..ace4a4e 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -72,7 +72,7 @@ class AppInterface static void OnBackButtonPressed(Node* node); static void OnForwardButtonPressed(Node* node); static void OnAddressBarSubmit(Node* node); - + static void OnScrollBarMoved(Node* node); App& app; diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index 93fefb0..08726b5 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -54,7 +54,7 @@ MemBlockAllocator::MemBlockAllocator() void MemBlockAllocator::Init() { // Disable swap for now - swapFile = fopen("Microweb.swp", "wb+"); + //swapFile = fopen("Microweb.swp", "wb+"); if (swapFile) { diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 741c96e..425149c 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -8,6 +8,16 @@ class TextFieldNode : public NodeHandler { public: + TextFieldNode() : + shiftPosition(0), + cursorPosition(0), + selectionStartPosition(0), + selectionLength(0), + pickedPosition(0) + { + + } + class Data { public: @@ -22,7 +32,7 @@ class TextFieldNode : public NodeHandler 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) { return true; } + virtual bool CanPick(Node* node) override { return true; } virtual bool HandleEvent(Node* node, const Event& event) override; private: diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp index 6b56037..c8b3509 100644 --- a/src/Nodes/Scroll.cpp +++ b/src/Nodes/Scroll.cpp @@ -5,9 +5,9 @@ #include "../Interface.h" #include "../Draw/Surface.h" -Node* ScrollBarNode::Construct(Allocator& allocator, int scrollPosition, int maxScroll) +Node* ScrollBarNode::Construct(Allocator& allocator, int scrollPosition, int maxScroll, NodeCallbackFunction onScroll) { - ScrollBarNode::Data* data = allocator.Alloc(scrollPosition, maxScroll); + ScrollBarNode::Data* data = allocator.Alloc(scrollPosition, maxScroll, onScroll); if (data) { return allocator.Alloc(Node::ScrollBar, data); @@ -16,40 +16,98 @@ Node* ScrollBarNode::Construct(Allocator& allocator, int scrollPosition, int max return nullptr; } -void ScrollBarNode::Draw(DrawContext& context, Node* node) +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) { - context.surface->VerticalScrollBar(context, node->anchor.x, node->anchor.y, node->size.y, 0, maxWidgetSize); + outPosition = 0; + outSize = maxWidgetSize; } else { - int widgetSize = (int)(((int32_t)node->size.y * node->size.y) / data->maxScroll); - if (widgetSize < minWidgetSize) + outSize = (int)(((int32_t)node->size.y * node->size.y) / (data->maxScroll + node->size.y)); + if (outSize < minWidgetSize) { - widgetSize = minWidgetSize; + outSize = minWidgetSize; } - if (widgetSize > maxWidgetSize) + if (outSize > maxWidgetSize) { - widgetSize = maxWidgetSize; + outSize = maxWidgetSize; } - int maxWidgetPosition = node->size.y - widgetSize; - int widgetPosition = (int32_t)maxWidgetPosition * data->scrollPosition / data->maxScroll; - if (widgetPosition < 0) - widgetPosition = 0; - if (widgetPosition > maxWidgetPosition) - widgetPosition = maxWidgetPosition; - - context.surface->VerticalScrollBar(context, node->anchor.x, node->anchor.y, node->size.y, widgetPosition, widgetSize); + int maxWidgetPosition = node->size.y - outSize; + outPosition = (int32_t)maxWidgetPosition * scrollPosition / data->maxScroll; + if (outPosition < 0) + outPosition = 0; + if (outPosition > maxWidgetPosition) + outPosition = maxWidgetPosition; } +} - -// context.surface->FillRect(context, node->anchor.x, node->anchor.y, node->size.x, node->size.y, 0); - +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::MouseClick: + { + 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 index 32c7355..a931841 100644 --- a/src/Nodes/Scroll.h +++ b/src/Nodes/Scroll.h @@ -9,13 +9,21 @@ class ScrollBarNode : public NodeHandler class Data { public: - Data(int inScrollPosition, int inMaxScroll) : scrollPosition(inScrollPosition), maxScroll(inMaxScroll) { } + 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); + 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 From 97e5e6a888b02ae99e1f10fb5dc4fbd3675fc229 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 14 Mar 2024 23:07:23 +0000 Subject: [PATCH 49/98] Added inverted mode and option to hide status + title bar --- src/App.cpp | 20 +++++++++++++++++ src/Colour.cpp | 12 ++++++++++ src/Colour.h | 1 + src/DOS/Platform.cpp | 21 +++++++++++------- src/Draw/Surf1bpp.cpp | 13 ++++++----- src/Image/Gif.cpp | 9 ++++---- src/Interface.cpp | 51 ++++++++++++++++++++++++++++++++++++------- src/Interface.h | 3 ++- src/Nodes/ImgNode.cpp | 2 +- src/Nodes/Text.cpp | 10 +++++++++ src/Platform.h | 2 ++ src/Render.cpp | 4 ++++ 12 files changed, 121 insertions(+), 27 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index c0d6437..3f227d4 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -393,3 +393,23 @@ void App::NextPage() RequestNewPage(pageHistory[pageHistoryPos].url); } } + +void VideoDriver::InvertVideoOutput() +{ + if (drawSurface->bpp == 1) + { + if (colourScheme.pageColour == 0) + { + colourScheme = monochromeColourScheme; + } + else + { + colourScheme = monochromeInverseColourScheme; + } + + 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/Colour.cpp b/src/Colour.cpp index afe84df..092a44c 100644 --- a/src/Colour.cpp +++ b/src/Colour.cpp @@ -13,6 +13,18 @@ ColourScheme monochromeColourScheme = 1 }; +ColourScheme monochromeInverseColourScheme = +{ + // Page + 0, + // Text + 1, + // Link + 1, + // Button + 0 +}; + ColourScheme cgaColourScheme = { // Page diff --git a/src/Colour.h b/src/Colour.h index d1d41a8..153929b 100644 --- a/src/Colour.h +++ b/src/Colour.h @@ -24,6 +24,7 @@ struct NamedColour }; extern ColourScheme monochromeColourScheme; +extern ColourScheme monochromeInverseColourScheme; extern ColourScheme egaColourScheme; extern ColourScheme cgaColourScheme; extern ColourScheme compositeCgaColourScheme; diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index 4aa0b7f..faccf1e 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -89,15 +89,16 @@ bool DetectHercules(); modify [ax cx dx] \ value [al] +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; - const int HP95LX = 13; - const int Hercules = 10; - const int CGA = 0; - const int CGAPalmtop = 1; - const int EGA = 6; - const int VGA = 8; // Look for HP 95LX inreg.x.ax = 0x4dd4; @@ -184,6 +185,11 @@ bool Platform::Init(int argc, char* argv[]) video = new BIOSVideoDriver(); } + if (videoMode->biosVideoMode == HP95LX || videoMode->biosVideoMode == CGAPalmtop) + { + inverse = true; + } + network->Init(); video->Init(videoMode); video->drawSurface->Clear(); @@ -192,8 +198,7 @@ bool Platform::Init(int argc, char* argv[]) if (inverse) { - // TODO-refactor - //video->InvertScreen(); + video->InvertVideoOutput(); } return true; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index eac6adf..34b05f2 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -3,6 +3,7 @@ #include "../Font.h" #include "../Image/Image.h" #include "../Memory/MemBlock.h" +#include "../Platform.h" DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) @@ -503,6 +504,13 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) { + uint16_t inverseMask = Platform::video->colourScheme.pageColour == 0 ? 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; @@ -513,8 +521,6 @@ void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int 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; while (position--) @@ -522,12 +528,9 @@ void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int *(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--) { diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 49de7f4..2ac31ba 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -783,6 +783,7 @@ void GifDecoder::EmitLine(int y) uint8_t buffer = 0; uint8_t mask = 0x80; int ditherIndex = 0; + uint8_t inverseMask = (Platform::video->colourScheme.pageColour == 0) ? 0xff : 0; if (outputImage->width == lineBufferSize) { @@ -801,7 +802,7 @@ void GifDecoder::EmitLine(int y) mask >>= 1; if (!mask) { - *output++ = buffer; + *output++ = buffer ^ inverseMask; buffer = 0; mask = 0x80; } @@ -809,7 +810,7 @@ void GifDecoder::EmitLine(int y) if (mask != 0x80) { - *output = buffer; + *output = buffer ^ inverseMask; } } else @@ -835,7 +836,7 @@ void GifDecoder::EmitLine(int y) mask >>= 1; if (!mask) { - *output++ = buffer; + *output++ = buffer ^ inverseMask; buffer = 0; mask = 0x80; } @@ -850,7 +851,7 @@ void GifDecoder::EmitLine(int y) if (mask != 0x80) { - *output = buffer; + *output = buffer ^ inverseMask; } } } diff --git a/src/Interface.cpp b/src/Interface.cpp index d1e74a2..2a5ff79 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -41,6 +41,8 @@ void AppInterface::Init() { GenerateInterfaceNodes(); + SetTitle("MicroWeb"); + DrawContext context(Platform::video->drawSurface, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight); DrawInterfaceNodes(context); } @@ -186,16 +188,17 @@ void AppInterface::Update() break; case KEYCODE_F2: { - // TODO-refactor - //Platform::input->HideMouse(); - //Platform::video->InvertScreen(); - //Platform::input->ShowMouse(); + Platform::video->InvertVideoOutput(); + titleNode->style.fontColour = Platform::video->colourScheme.textColour; } break; case KEYCODE_CTRL_L: case KEYCODE_F6: FocusNode(addressBarNode); break; + case KEYCODE_F3: + ToggleStatusAndTitleBar(); + break; case KEYCODE_TAB: CycleNodes(1); @@ -373,7 +376,7 @@ void AppInterface::GenerateInterfaceNodes() scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight, OnScrollBarMoved); scrollBarNode->style = rootInterfaceNode->style; - scrollBarNode->anchor.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; + 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; @@ -382,7 +385,7 @@ void AppInterface::GenerateInterfaceNodes() rootInterfaceNode->EncapsulateChildren(); windowRect.x = 0; - windowRect.y = backButtonNode->anchor.y + backButtonNode->size.y + 3; + 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; } @@ -392,7 +395,7 @@ void AppInterface::DrawInterfaceNodes(DrawContext& context) app.pageRenderer.DrawAll(context, rootInterfaceNode); Platform::input->HideMouse(); - uint8_t dividerColour = 0; + uint8_t dividerColour = Platform::video->colourScheme.textColour; context.surface->HLine(context, 0, windowRect.y - 1, Platform::video->screenWidth, dividerColour); Platform::input->ShowMouse(); } @@ -411,7 +414,7 @@ void AppInterface::SetTitle(const char* title) 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, 0, titleNode->size.x, titleNode->size.y, fillColour); + context.surface->FillRect(context, titleNode->anchor.x, titleNode->anchor.y, titleNode->size.x, titleNode->size.y, fillColour); titleNode->Handler().Draw(context, titleNode); Platform::input->ShowMouse(); } @@ -536,3 +539,35 @@ void AppInterface::OnScrollBarMoved(Node* node) AppInterface& ui = App::Get().ui; ui.ScrollRelative(data->scrollPosition - ui.scrollPositionY); } + +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 ace4a4e..129b63c 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -52,7 +52,6 @@ class AppInterface void ScrollRelative(int delta); void ScrollAbsolute(int position); - Node* addressBarNode; URL addressBarURL; @@ -69,6 +68,8 @@ class AppInterface void CycleNodes(int direction); + void ToggleStatusAndTitleBar(); + static void OnBackButtonPressed(Node* node); static void OnForwardButtonPressed(Node* node); static void OnAddressBarSubmit(Node* node); diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 6e74d88..3c22470 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -11,7 +11,7 @@ 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 = 7; + uint8_t outlineColour = Platform::video->colourScheme.textColour; if (data->image.isLoaded && data->image.lines) { diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 4a6cfac..a34233a 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -34,6 +34,11 @@ void TextElement::Draw(DrawContext& context, Node* node) uint8_t textColour = node->style.fontColour; char* text = data->text.Get(); + if (context.surface->bpp == 1) + { + textColour = Platform::video->colourScheme.textColour; + } + context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); Node* focusedNode = App::Get().ui.GetFocusedNode(); @@ -250,6 +255,11 @@ void SubTextElement::Draw(DrawContext& context, Node* node) char temp = text[subTextData->length]; text[subTextData->length] = 0; + if (context.surface->bpp == 1) + { + textColour = Platform::video->colourScheme.textColour; + } + context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); Node* focusedNode = App::Get().ui.GetFocusedNode(); diff --git a/src/Platform.h b/src/Platform.h index 4035108..029ce58 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -32,6 +32,8 @@ class VideoDriver virtual void ScaleImageDimensions(int& width, int& height) {} + void InvertVideoOutput(); + int screenWidth; int screenHeight; diff --git a/src/Render.cpp b/src/Render.cpp index fb6fbd3..d3a02eb 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -55,8 +55,12 @@ void PageRenderer::RefreshAll() Platform::input->ShowMouse(); + upperContext.clipTop = windowRect.y; upperContext.clipBottom = upperContext.clipTop; + lowerContext.clipBottom = windowRect.y + windowRect.height; lowerContext.clipTop = upperContext.clipBottom; + upperContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); + lowerContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); // Clear the render queue for (Node* node = renderQueueHead; node;) From 72ddbc5985b1941f60d7242ead0af59647add31c Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sun, 17 Mar 2024 22:15:18 +0000 Subject: [PATCH 50/98] Improved table layout generation. Removed requirement that nodes need to encapsulate their children - if size is zero then skip bounds checks --- src/Interface.cpp | 7 +- src/Layout.cpp | 98 ---------------------------- src/Node.cpp | 113 ++++++++++++++------------------- src/Node.h | 4 +- src/Nodes/Table.cpp | 151 ++++++++++++++++++++++++++++---------------- src/Nodes/Table.h | 8 ++- src/Nodes/Text.cpp | 35 +++++----- src/Page.cpp | 2 +- src/Parser.cpp | 2 - 9 files changed, 171 insertions(+), 249 deletions(-) diff --git a/src/Interface.cpp b/src/Interface.cpp index 2a5ff79..58badcf 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -382,8 +382,6 @@ void AppInterface::GenerateInterfaceNodes() scrollBarNode->anchor.x = Platform::video->screenWidth - scrollBarNode->size.x; rootInterfaceNode->AddChild(scrollBarNode); - rootInterfaceNode->EncapsulateChildren(); - windowRect.x = 0; windowRect.y = backButtonNode->anchor.y + backButtonNode->size.y + 2; windowRect.width = Platform::video->screenWidth - scrollBarNode->size.x; @@ -478,7 +476,10 @@ void AppInterface::ScrollRelative(int delta) scrollPositionY = 0; int maxScrollY = app.pageRenderer.GetVisiblePageHeight() - app.ui.windowRect.height; - if (maxScrollY > 0 && scrollPositionY > maxScrollY) + if (maxScrollY < 0) + maxScrollY = 0; + + if (scrollPositionY > maxScrollY) { scrollPositionY = maxScrollY; } diff --git a/src/Layout.cpp b/src/Layout.cpp index 9cf718d..3546c64 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -89,11 +89,6 @@ void Layout::TranslateNodes(Node* start, Node* end, int deltaX, int deltaY) node->anchor.x += deltaX; node->anchor.y += deltaY; - //if (node->parent) - //{ - // node->parent->OnChildLayoutChanged(); - //} - if (node == end) { break; @@ -101,50 +96,6 @@ void Layout::TranslateNodes(Node* start, Node* end, int deltaX, int deltaY) } } -/* -void Layout::TranslateNodes(Node* node, int deltaX, int deltaY, bool visitSiblings) -{ - while (node) - { - node->anchor.x += deltaX; - node->anchor.y += deltaY; - - TranslateNodes(node->firstChild, deltaX, deltaY); - - if (visitSiblings) - { - if (node->next) - { - node = node->next; - } - else - { - while(1) - { - if (!node->parent) - { - return; - } - if (!node->parent->next) - { - node = node->parent; - } - else - { - node = node->parent->next; - break; - } - } - } - } - else - { - node = node->next; - } - } -} -*/ - void Layout::OnNodeEmitted(Node* node) { if (!lineStartNode) @@ -264,55 +215,6 @@ void Layout::RecalculateLayout() Node* node = page.GetRootNode(); RecalculateLayoutForNode(node); -#if 0 - //for (Node* node = page.GetRootNode(); node; node = node->GetNextInTree()) - //{ - // node->Handler().GenerateLayout(*this, node); - //} - - Node* node = page.GetRootNode(); - bool checkChildren = true; - - 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); -// node->EncapsulateChildren(); - } - checkChildren = false; - continue; - } - - if (node) - { - node->Handler().BeginLayoutContext(*this, node); - node->Handler().GenerateLayout(*this, node); - /*if (node->parent) - { - node->parent->OnChildLayoutChanged(); - }*/ - } - } -#endif - page.GetApp().pageRenderer.MarkPageLayoutComplete(); page.GetApp().pageRenderer.RefreshAll(); diff --git a/src/Node.cpp b/src/Node.cpp index c97ef86..19ba36e 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -68,86 +68,70 @@ void Node::AddChild(Node* child) } } -void Node::OnChildLayoutChanged() +void Node::CalculateEncapsulatingRect(Rect& rect) { - EncapsulateChildren(); - if (parent) + rect.Clear(); + Node* node = this; + bool checkChildren = true; + + if (!firstChild) { - parent->OnChildLayoutChanged(); + return; } -} -void Node::EncapsulateChildren() -{ - Coord newAnchor, newSize; - newAnchor.Clear(); - newSize.Clear(); - - for (Node* node = firstChild; node; node = node->next) + 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 (newSize.x == 0 || newSize.y == 0) + if (rect.width == 0 && rect.height == 0) { - newAnchor = node->anchor; - newSize = node->size; + 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 < newAnchor.x) + if (node->anchor.x < rect.x) { - newAnchor.x = node->anchor.x; + rect.x = node->anchor.x; } - if (node->anchor.y < newAnchor.y) + if (node->anchor.y < rect.y) { - newAnchor.y = node->anchor.y; + rect.y = node->anchor.y; } - if (node->anchor.x + node->size.x > newAnchor.x + newSize.x) + if (node->anchor.x + node->size.x > rect.x + rect.width) { - newSize.x = node->anchor.x + node->size.x - newAnchor.x; + rect.width = node->anchor.x + node->size.x - rect.x; } - if (node->anchor.y + node->size.y > newAnchor.y + newSize.y) + if (node->anchor.y + node->size.y > rect.y + rect.height) { - newSize.y = node->anchor.y + node->size.y - newAnchor.y; + rect.height = node->anchor.y + node->size.y - rect.y; } } - - if (newSize.x != 0 && newSize.y != 0) - { - anchor = newAnchor; - size = newSize; - } - - /* - if (firstChild) - { - anchor = firstChild->anchor; - size = firstChild->size; - - for (Node* node = firstChild->next; node; node = node->next) - { - if (node->anchor.x < anchor.x) - { - anchor.x = node->anchor.x; - } - if (node->anchor.y < anchor.y) - { - anchor.y = node->anchor.y; - } - if (node->anchor.x + node->size.x > anchor.x + size.x) - { - size.x = node->anchor.x + node->size.x - anchor.x; - } - if (node->anchor.y + node->size.y > anchor.y + size.y) - { - size.y = node->anchor.y + node->size.y - anchor.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; @@ -155,7 +139,7 @@ bool Node::IsPointInsideNode(int x, int y) bool Node::IsPointInsideChildren(int x, int y) { - if (!IsPointInsideNode(x, y)) + if (!size.IsZero() && !IsPointInsideNode(x, y)) { return false; } @@ -178,7 +162,7 @@ bool Node::IsPointInsideChildren(int x, int y) Node* NodeHandler::Pick(Node* node, int x, int y) { - if (!node || !node->IsPointInsideNode(x, y)) + if (!node || (!node->size.IsZero() && !node->IsPointInsideNode(x, y))) { return nullptr; } @@ -192,14 +176,15 @@ Node* NodeHandler::Pick(Node* node, int x, int y) return node; } - return nullptr;; + return nullptr; } for (Node* it = node->firstChild; it; it = it->next) { - if (it->IsPointInsideNode(x, y)) + Node* result = it->Handler().Pick(it, x, y); + if (result) { - return it->Handler().Pick(it, x, y); + return result; } } @@ -208,10 +193,6 @@ Node* NodeHandler::Pick(Node* node, int x, int y) void NodeHandler::EndLayoutContext(Layout& layout, Node* node) { - //if (node->size.x == 0 && node->size.y == 0) - { - node->EncapsulateChildren(); - } } Node* Node::FindParentOfType(Node::Type searchType) diff --git a/src/Node.h b/src/Node.h index f1c1696..19865e7 100644 --- a/src/Node.h +++ b/src/Node.h @@ -35,6 +35,7 @@ struct Coord int16_t x, y; void Clear() { x = y = 0; } + bool IsZero() const { return x == 0 && y == 0; } }; struct Rect @@ -78,10 +79,9 @@ class Node Node(Type inType, void* inData); void AddChild(Node* child); - void EncapsulateChildren(); // Sets anchor and size based on children + void CalculateEncapsulatingRect(Rect& rect); bool IsPointInsideNode(int x, int y); bool IsPointInsideChildren(int x, int y); - void OnChildLayoutChanged(); Node* FindParentOfType(Node::Type searchType); template T* FindParentDataOfType(Node::Type searchType) diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 8e41e7d..4fc974d 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -28,10 +28,14 @@ void TableNode::Draw(DrawContext& context, Node* node) TableNode::Data* data = static_cast(node->data); uint8_t borderColour = Platform::video->colourScheme.textColour; - int x = node->anchor.x - data->cellSpacing - data->cellPadding; - int y = node->anchor.y - data->cellSpacing - data->cellPadding; - int w = data->totalWidth; // node->size.x + (data->cellSpacing + data->cellPadding) * 2; - int h = node->size.y + (data->cellSpacing + data->cellPadding) * 2; + //int x = node->anchor.x - data->cellSpacing - data->cellPadding; + //int y = node->anchor.y - data->cellSpacing - data->cellPadding; + //int w = data->totalWidth; // node->size.x + (data->cellSpacing + data->cellPadding) * 2; + //int h = node->size.y + (data->cellSpacing + data->cellPadding) * 2; + int x = node->anchor.x; + int y = node->anchor.y; + int w = node->size.x; + int h = node->size.y; 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); @@ -42,32 +46,36 @@ void TableNode::BeginLayoutContext(Layout& layout, Node* node) { TableNode::Data* data = static_cast(node->data); - if (data->state == Data::FinishedLayout) - { - data->state = Data::GeneratingLayout; - } - layout.BreakNewLine(); layout.tableDepth++; layout.PushCursor(); layout.PushLayout(); - layout.PadHorizontal(data->cellSpacing, data->cellSpacing); - + 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 + data->cellPadding); + layout.PadVertical(data->cellSpacing); } } } void TableNode::EndLayoutContext(Layout& layout, Node* node) { - //NodeHandler::EndLayoutContext(layout, node); - node->EncapsulateChildren(); - TableNode::Data* data = static_cast(node->data); layout.PopLayout(); @@ -171,20 +179,19 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } - int totalPreferredWidth = data->cellSpacing * (data->numColumns + 1); + int totalCellSpacing = (data->numColumns + 1) * data->cellSpacing; + int totalPreferredWidth = 0; for (int i = 0; i < data->numColumns; i++) { totalPreferredWidth += data->columns[i].preferredWidth; } - int totalCellSpacing = (data->numColumns + 1) * data->cellSpacing; - int maxAvailableWidth = layout.MaxAvailableWidth() - totalCellSpacing; - if (totalPreferredWidth > maxAvailableWidth) + int maxAvailableWidthForCells = layout.MaxAvailableWidth() - totalCellSpacing; + if (totalPreferredWidth > maxAvailableWidthForCells) { - // TODO resize widths to fit for (int i = 0; i < data->numColumns; i++) { - data->columns[i].preferredWidth = ((long)maxAvailableWidth * data->columns[i].preferredWidth) / totalPreferredWidth; + data->columns[i].preferredWidth = ((long)maxAvailableWidthForCells * data->columns[i].preferredWidth) / totalPreferredWidth; } } @@ -194,7 +201,9 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) totalWidth += data->columns[i].preferredWidth; } data->totalWidth = totalWidth; + node->size.x = totalWidth; + layout.PushCursor(); layout.PushLayout(); if (node->style.alignment == ElementAlignment::Center) { @@ -202,6 +211,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) if (totalWidth < available) { layout.PadHorizontal((available - totalWidth) / 2, 0); + node->anchor = layout.Cursor(); } } @@ -209,13 +219,23 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) layout.RecalculateLayoutForNode(node); layout.PopLayout(); - - layout.PadVertical(node->size.y + (data->cellPadding + data->cellSpacing) * 2); + 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(); } @@ -226,7 +246,8 @@ Node* TableRowNode::Construct(Allocator& allocator) TableRowNode::Data* data = allocator.Alloc(); if (data) { - return allocator.Alloc(Node::TableRow, data); + data->node = allocator.Alloc(Node::TableRow, data); + return data->node; } return nullptr; } @@ -263,7 +284,8 @@ void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) } else { - layout.PadVertical(tableData->cellPadding); + node->anchor = layout.Cursor(); + node->size.x = tableData->totalWidth; } } @@ -274,9 +296,6 @@ void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) void TableRowNode::EndLayoutContext(Layout& layout, Node* node) { - //NodeHandler::EndLayoutContext(layout, node); - node->EncapsulateChildren(); - TableRowNode::Data* data = static_cast(node->data); TableNode::Data* tableData = node->FindParentDataOfType(Node::Table); @@ -285,7 +304,24 @@ void TableRowNode::EndLayoutContext(Layout& layout, Node* node) layout.PopLayout(); layout.BreakNewLine(); layout.PopCursor(); - layout.PadVertical(node->size.y + tableData->cellSpacing + tableData->cellPadding); + + 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); + } } } @@ -361,47 +397,48 @@ void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) } else { - layout.RestrictHorizontal(tableData->columns[data->columnIndex].preferredWidth); + node->anchor = layout.Cursor(); + node->size.x = tableData->columns[data->columnIndex].preferredWidth; + layout.RestrictHorizontal(node->size.x); layout.PadHorizontal(tableData->cellPadding, tableData->cellPadding); } } layout.PushCursor(); -} -void TableCellNode::EndLayoutContext(Layout& layout, Node* node) -{ - //NodeHandler::EndLayoutContext(layout, node); - if (node->firstChild) - { - node->EncapsulateChildren(); - } - else + if (tableData) { - // Cell was empty - node->anchor = layout.GetCursor(); + layout.PadVertical(tableData->cellPadding); } +} - layout.BreakNewLine(); - layout.PopCursor(); - layout.PopLayout(); - +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 && rowData) + + if (tableData) { + Rect rect; + node->CalculateEncapsulatingRect(rect); + node->size.y = rect.y + rect.height + tableData->cellPadding - node->anchor.y; + if (tableData->IsGeneratingLayout()) { - } - else - { - layout.PadHorizontal(tableData->columns[data->columnIndex].preferredWidth + tableData->cellSpacing, 0); + node->size.x = rect.width; } } -// layout.PadHorizontal(200, 0); + 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) @@ -414,10 +451,14 @@ void TableCellNode::Draw(DrawContext& context, Node* node) if (tableData && !tableData->IsGeneratingLayout() && rowNode) { - int x = node->anchor.x - tableData->cellPadding; - int y = node->anchor.y - tableData->cellPadding; - int w = tableData->columns[data->columnIndex].preferredWidth; - int h = rowNode->size.y + 2 * tableData->cellPadding; + //int x = node->anchor.x - tableData->cellPadding; + //int y = node->anchor.y - tableData->cellPadding; + //int w = tableData->columns[data->columnIndex].preferredWidth; + //int h = rowNode->size.y + 2 * tableData->cellPadding; + int x = node->anchor.x; + int y = node->anchor.y; + int w = node->size.x; + int h = node->size.y; context.surface->HLine(context, x, y, w, borderColour); context.surface->HLine(context, x, y + h - 1, w, borderColour); diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index 712f799..014f8b6 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -10,7 +10,7 @@ class TableCellNode : public NodeHandler class Data { public: - Data(bool inIsHeader) : isHeader(inIsHeader), columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), nextCell(nullptr) {} + Data(bool inIsHeader) : node(nullptr), isHeader(inIsHeader), columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), nextCell(nullptr) {} Node* node; bool isHeader; int columnIndex; @@ -33,7 +33,8 @@ class TableRowNode : public NodeHandler class Data { public: - Data() : rowIndex(0), numCells(0), nextRow(nullptr), firstCell(nullptr) {} + Data() : node(nullptr), rowIndex(0), numCells(0), nextRow(nullptr), firstCell(nullptr) {} + Node* node; int rowIndex; int numCells; TableRowNode::Data* nextRow; @@ -64,7 +65,7 @@ class TableNode : public NodeHandler FinishedLayout }; - Data() : state(GeneratingLayout), numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), columns(nullptr), firstRow(nullptr), cells(nullptr) {} + Data() : state(GeneratingLayout), numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), columns(nullptr), firstRow(nullptr), cells(nullptr), lastAvailableWidth(-1) {} bool IsGeneratingLayout() { return state == GeneratingLayout; } bool HasGeneratedCellGrid() { return cells != nullptr; } @@ -78,6 +79,7 @@ class TableNode : public NodeHandler ColumnInfo* columns; TableRowNode::Data* firstRow; TableCellNode::Data** cells; + int lastAvailableWidth; }; static Node* Construct(Allocator& allocator); diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index a34233a..5ad4e40 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -56,7 +56,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); int lineHeight = font->glyphHeight; -#if 0 +#if 1 // TODO: optimisation if (data->lastAvailableWidth != -1) { @@ -64,23 +64,23 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) // completely regenerating the whole layout if (data->lastAvailableWidth == layout.AvailableWidth()) { - Coord cursor = layout.GetCursor(lineHeight); - Coord previousAnchor = node->firstChild->anchor; - Coord offset; - offset.x = cursor.x - previousAnchor.x; - offset.y = cursor.y - previousAnchor.y; - - if (offset.x || offset.y) + if (node->firstChild) { - node->anchor.x += offset.x; - node->anchor.y += offset.y; - - for (Node* n = node->firstChild; n; n = n->next) + for (Node* child = node->firstChild; child; child = child->next) { - n->anchor.x += offset.x; - n->anchor.y += offset.y; + 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; } @@ -100,6 +100,8 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) child->size.Clear(); } + node->size.Clear(); + char* text = data->text.Get(); int charIndex = 0; int startIndex = 0; @@ -219,11 +221,6 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) { data->text.Commit(); } - - if (node->firstChild) - { - node->EncapsulateChildren(); - } } Node* SubTextElement::Construct(Allocator& allocator, int startIndex, int length) diff --git a/src/Page.cpp b/src/Page.cpp index d56921c..d3b283a 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -158,7 +158,7 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) case Node::Form: { FormNode::Data* data = static_cast(node->data); - printf("<%s> action: %s\n", nodeTypeNames[node->type], data->action ? data->action : "NONE"); + 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"); } break; case Node::Style: diff --git a/src/Parser.cpp b/src/Parser.cpp index 5fa770a..dc5d738 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -120,8 +120,6 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) // If the stack is emptied then we have finished parsing the document if (contextStackSize == 0) { - page.GetRootNode()->EncapsulateChildren(); - #ifdef _WIN32 page.DebugDumpNodeGraph(); #endif From e69636c7ee0eb1682eaefedccb8d2ab60e1bcdc0 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Sun, 17 Mar 2024 23:17:53 +0000 Subject: [PATCH 51/98] Additional table attribute support --- src/Node.cpp | 2 +- src/Nodes/Break.cpp | 19 ++++++++------- src/Nodes/Table.cpp | 58 +++++++++++++++++++++++++++++++-------------- src/Nodes/Table.h | 9 ++++--- src/Parser.h | 1 + src/Tags.cpp | 55 ++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src/Node.cpp b/src/Node.cpp index 19ba36e..3e9f70d 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -146,7 +146,7 @@ bool Node::IsPointInsideChildren(int x, int y) if (firstChild == nullptr) { - return true; + return IsPointInsideNode(x, y); } for (Node* it = firstChild; it; it = it->next) diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index 2355265..75582a5 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -21,7 +21,7 @@ void BreakNode::Draw(DrawContext& context, Node* node) if (data->displayBreakLine) { - uint8_t outlineColour = 0; + uint8_t outlineColour = Platform::video->colourScheme.textColour; context.surface->HLine(context, node->anchor.x, node->anchor.y + node->size.y / 2, node->size.x, outlineColour); } } @@ -46,13 +46,16 @@ void BreakNode::GenerateLayout(Layout& layout, Node* node) layout.PadVertical(breakPadding); } - node->anchor = layout.GetCursor(); - node->anchor.x += 8; - node->anchor.y -= breakPadding; - node->size.x = layout.AvailableWidth() - 16; - node->size.y = breakPadding; - if (!breakPadding && data->displayBreakLine) + if (data->displayBreakLine) { - node->size.y = 1; + 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/Table.cpp b/src/Nodes/Table.cpp index 4fc974d..63526a9 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -27,19 +27,28 @@ void TableNode::Draw(DrawContext& context, Node* node) { TableNode::Data* data = static_cast(node->data); - uint8_t borderColour = Platform::video->colourScheme.textColour; - //int x = node->anchor.x - data->cellSpacing - data->cellPadding; - //int y = node->anchor.y - data->cellSpacing - data->cellPadding; - //int w = data->totalWidth; // node->size.x + (data->cellSpacing + data->cellPadding) * 2; - //int h = node->size.y + (data->cellSpacing + data->cellPadding) * 2; int x = node->anchor.x; int y = node->anchor.y; int w = node->size.x; int h = node->size.y; - 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 (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 (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + { + context.surface->FillRect(context, x + 1, y + 1, w - 2, h - 2, data->bgColour); + } + } + else if (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + { + context.surface->FillRect(context, x, y, w, h, data->bgColour); + } } void TableNode::BeginLayoutContext(Layout& layout, Node* node) @@ -56,7 +65,7 @@ void TableNode::BeginLayoutContext(Layout& layout, Node* node) if (data->state == Data::FinishedLayout) { - if (availableWidth != data->lastAvailableWidth) + //if (availableWidth != data->lastAvailableWidth) { data->state = Data::GeneratingLayout; } @@ -398,7 +407,13 @@ void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) else { node->anchor = layout.Cursor(); + node->size.x = tableData->columns[data->columnIndex].preferredWidth; + for (int n = 1; n < data->columnSpan && n < tableData->numColumns; n++) + { + node->size.x += tableData->columns[data->columnIndex + n].preferredWidth + tableData->cellSpacing; + } + layout.RestrictHorizontal(node->size.x); layout.PadHorizontal(tableData->cellPadding, tableData->cellPadding); } @@ -451,19 +466,26 @@ void TableCellNode::Draw(DrawContext& context, Node* node) if (tableData && !tableData->IsGeneratingLayout() && rowNode) { - //int x = node->anchor.x - tableData->cellPadding; - //int y = node->anchor.y - tableData->cellPadding; - //int w = tableData->columns[data->columnIndex].preferredWidth; - //int h = rowNode->size.y + 2 * tableData->cellPadding; int x = node->anchor.x; int y = node->anchor.y; int w = node->size.x; int h = node->size.y; - 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 (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 (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + { + context.surface->FillRect(context, x + 1, y + 1, w - 2, h - 2, data->bgColour); + } + } + else if (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + { + context.surface->FillRect(context, x, y, w, h, data->bgColour); + } } } diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index 014f8b6..1911982 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -2,7 +2,7 @@ #define _TABLE_H #include "../Node.h" - +#include "../Colour.h" class TableCellNode : public NodeHandler { @@ -10,13 +10,14 @@ class TableCellNode : public NodeHandler class Data { public: - Data(bool inIsHeader) : node(nullptr), isHeader(inIsHeader), columnIndex(0), rowIndex(0), columnSpan(1), rowSpan(1), nextCell(nullptr) {} + 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; }; @@ -65,7 +66,7 @@ class TableNode : public NodeHandler FinishedLayout }; - Data() : state(GeneratingLayout), numColumns(0), numRows(0), cellSpacing(2), cellPadding(2), columns(nullptr), firstRow(nullptr), cells(nullptr), lastAvailableWidth(-1) {} + 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; } @@ -75,10 +76,12 @@ class TableNode : public NodeHandler int numRows; int cellSpacing; int cellPadding; + uint8_t border; int totalWidth; ColumnInfo* columns; TableRowNode::Data* firstRow; TableCellNode::Data** cells; + uint8_t bgColour; int lastAvailableWidth; }; diff --git a/src/Parser.h b/src/Parser.h index 49623b3..4e2b365 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -49,6 +49,7 @@ class AttributeParser const char* Key() { return key; } const char* Value() { return value; } + int ValueAsInt() { return atoi(value); } private: bool IsWhiteSpace(char c); diff --git a/src/Tags.cpp b/src/Tags.cpp index ca6479c..07e19c9 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -558,7 +558,35 @@ void PreformattedTagHandler::Close(class HTMLParser& parser) const void TableTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(TableNode::Construct(MemoryManager::pageAllocator), this); + 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(); + } + if (!stricmp(attributes.Key(), "cellpadding")) + { + tableNodeData->cellPadding = attributes.ValueAsInt(); + } + if (!stricmp(attributes.Key(), "cellSpacing")) + { + tableNodeData->cellSpacing = attributes.ValueAsInt(); + } + if (!stricmp(attributes.Key(), "bgcolor")) + { + tableNodeData->bgColour = HTMLParser::ParseColourCode(attributes.Value()); + } + } + + parser.PushContext(tableNode, this); + } } void TableTagHandler::Close(class HTMLParser& parser) const @@ -578,7 +606,30 @@ void TableRowTagHandler::Close(class HTMLParser& parser) const void TableCellTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(TableCellNode::Construct(MemoryManager::pageAllocator, isHeader), this); + Node* cellNode = TableCellNode::Construct(MemoryManager::pageAllocator, isHeader); + if (cellNode) + { + TableCellNode::Data* cellData = static_cast(cellNode->data); + + AttributeParser attributes(attributeStr); + + while (attributes.Parse()) + { + if (!stricmp(attributes.Key(), "bgcolor")) + { + cellData->bgColour = HTMLParser::ParseColourCode(attributes.Value()); + } + if (!stricmp(attributes.Key(), "colspan")) + { + cellData->columnSpan = attributes.ValueAsInt(); + if (cellData->columnSpan <= 0) + { + cellData->columnSpan = 1; + } + } + } + parser.PushContext(cellNode, this); + } } void TableCellTagHandler::Close(class HTMLParser& parser) const From f8ed9382753bec5191b209ab898d947fdf3028c1 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 18 Mar 2024 20:48:18 +0000 Subject: [PATCH 52/98] More efficient EMS usage. Several misc fixes --- src/DOS/EMS.cpp | 31 +++++++++++--- src/DOS/EMS.h | 6 ++- src/DOS/Surf4bpp.cpp | 10 +++-- src/Draw/Surf1bpp.cpp | 6 ++- src/Draw/Surf2bpp.cpp | 90 ++++++++++++++++++++++++++++------------- src/Draw/Surf8bpp.cpp | 10 +++-- src/Image/Gif.cpp | 76 +++++++++++----------------------- src/Image/Gif.h | 1 - src/Image/Image.h | 6 ++- src/Interface.cpp | 12 ++++-- src/Layout.cpp | 14 +++++-- src/Memory/MemBlock.cpp | 18 +++++---- src/Memory/MemBlock.h | 2 +- src/Nodes/ImgNode.cpp | 16 +++++++- src/Nodes/Scroll.cpp | 17 ++++++-- src/Nodes/Table.cpp | 35 ++++++++++++---- src/Nodes/Text.cpp | 6 +-- src/Page.cpp | 2 +- src/Palettes.inc | 64 ++++++++++++++--------------- src/Parser.cpp | 34 +++++++++++++++- src/Parser.h | 7 +++- src/Tags.cpp | 40 ++++++++++-------- src/Tags.h | 2 +- tools/PaletteGen.cpp | 2 +- 24 files changed, 324 insertions(+), 183 deletions(-) diff --git a/src/DOS/EMS.cpp b/src/DOS/EMS.cpp index 40e4f4c..a6d84a2 100644 --- a/src/DOS/EMS.cpp +++ b/src/DOS/EMS.cpp @@ -9,6 +9,8 @@ void EMSManager::Init() { union REGS inregs, outregs; + //return; + // Check for EMS driver inregs.h.ah = 0x40; int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); @@ -43,7 +45,12 @@ void EMSManager::Init() allocationPageIndex = 0; allocationPageUsed = 0; - mappedPage = 0xffff; + + for (int n = 0; n < NUM_MAPPABLE_PAGES; n++) + { + mappedPages[n] = 0xffff; + } + nextPageToMap = 0; isAvailable = true; } @@ -95,18 +102,32 @@ void* EMSManager::MapBlock(MemBlockHandle& handle) { if (isAvailable && handle.type == MemBlockHandle::EMS) { - if (mappedPage != handle.emsPage) + // 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 = 0; + inregs.h.al = mappedPageIndex; inregs.x.bx = handle.emsPage; inregs.x.dx = allocationHandle; int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); - mappedPage = handle.emsPage; + mappedPages[mappedPageIndex] = handle.emsPage; } - return MK_FP(pageAddressSegment, handle.emsPageOffset); + 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 index ebaf9d3..244e6b0 100644 --- a/src/DOS/EMS.h +++ b/src/DOS/EMS.h @@ -5,6 +5,8 @@ #include "../Memory/MemBlock.h" #define EMS_PAGE_SIZE (16 * 1024l) +#define NUM_MAPPABLE_PAGES 4 +#define EMS_PAGE_SEGMENT_SPACING (1024) class EMSManager { @@ -32,7 +34,9 @@ class EMSManager uint16_t allocationPageIndex; uint16_t allocationPageUsed; - uint16_t mappedPage; + + uint16_t mappedPages[NUM_MAPPABLE_PAGES]; + uint8_t nextPageToMap; }; #endif diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index 231f422..4240c92 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -354,7 +354,7 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { - if (!image->lines) + if (!image->lines.IsAllocated()) return; x += context.drawOffsetX; y += context.drawOffsetY; @@ -414,7 +414,9 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* src = image->lines[srcY + j].Get() + srcX; + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[srcY + j]; + uint8_t* src = imageLine.Get() + srcX; uint8_t* destRow = lines[y + j] + (x >> 3); uint8_t destMask = 0x80 >> (x & 7); @@ -451,7 +453,9 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* src = image->lines[srcY + j].Get() + srcX; + MemBlockHandle* imageLines = image->lines.Get(); + MemBlockHandle imageLine = imageLines[srcY + j]; + uint8_t* src = imageLine.Get() + srcX; uint8_t* destRow = lines[y + j] + (x >> 3); int xBits = 7 - (x & 0x7); // Number of bits in the first destination byte to skip diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 34b05f2..57b94f3 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -353,7 +353,7 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { - if (!image->lines || image->bpp != 1) + if (!image->lines.IsAllocated() || image->bpp != 1) return; x += context.drawOffsetX; @@ -405,7 +405,9 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* src = image->lines[j + srcY].Get() + (srcX >> 3); + 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); diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp index 21e0fd3..58c27ea 100644 --- a/src/Draw/Surf2bpp.cpp +++ b/src/Draw/Surf2bpp.cpp @@ -3,6 +3,7 @@ #include "../Font.h" #include "../Image/Image.h" #include "../Memory/MemBlock.h" +#include "../Colour.h" static uint8_t bitmaskTable[] = { @@ -39,6 +40,8 @@ void DrawSurface_2BPP::HLine(DrawContext& context, int x, int y, int count, uint return; } + colour |= (colour << 4); + uint8_t* VRAMptr = lines[y]; VRAMptr += (x >> 2); @@ -93,6 +96,8 @@ void DrawSurface_2BPP::VLine(DrawContext& context, int x, int y, int count, uint return; } + colour |= (colour << 4); + uint8_t mask = bitmaskTable[x & 3]; uint8_t andMask = ~mask; uint8_t orMask = mask & colour; @@ -133,6 +138,8 @@ void DrawSurface_2BPP::FillRect(DrawContext& context, int x, int y, int width, i return; } + colour |= (colour << 4); + while (height) { uint8_t* VRAMptr = lines[y]; @@ -191,6 +198,8 @@ void DrawSurface_2BPP::DrawString(DrawContext& context, Font* font, const char* return; } + colour |= (colour << 4); + uint8_t firstLine = 0; if (y < context.clipTop) { @@ -280,7 +289,7 @@ void DrawSurface_2BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_2BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { - if (!image->lines) + if (!image->lines.IsAllocated()) return; x += context.drawOffsetX; @@ -329,7 +338,9 @@ void DrawSurface_2BPP::BlitImage(DrawContext& context, Image* image, int x, int { for (int j = 0; j < destHeight; j++) { - uint8_t* src = image->lines[j + srcY].Get() + srcX; + 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; @@ -337,8 +348,11 @@ void DrawSurface_2BPP::BlitImage(DrawContext& context, Image* image, int x, int for (int i = 0; i < destWidth; i++) { uint8_t srcBuffer = *src; -// srcBuffer |= (srcBuffer << 4); - destBuffer = (destBuffer & (~destMask)) | ((srcBuffer) & destMask); + if (srcBuffer != TRANSPARENT_COLOUR_VALUE) + { + srcBuffer |= (srcBuffer << 4); + destBuffer = (destBuffer & (~destMask)) | ((srcBuffer)&destMask); + } src++; destMask >>= 2; if (!destMask) @@ -416,57 +430,77 @@ void DrawSurface_2BPP::InvertRect(DrawContext& context, int x, int y, int width, void DrawSurface_2BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) { -#if 0 +#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 >>= 3; + x >>= 2; 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; while (position--) { - *(uint16_t*)(&lines[y++][x]) = inner; + ((uint16_t*)(&lines[y][x]))[0] = inner1; + ((uint16_t*)(&lines[y++][x]))[1] = inner2; } - const uint16_t widgetEdge = 0x0660; - *(uint16_t*)(&lines[y++][x]) = inner; - *(uint16_t*)(&lines[y++][x]) = widgetEdge; + ((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; - 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; + ((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]) = widgetInner; + ((uint16_t*)(&lines[y][x]))[0] = widgetInner1; + ((uint16_t*)(&lines[y++][x]))[1] = widgetInner2; } - *(uint16_t*)(&lines[y++][x]) = widgetEdge; - *(uint16_t*)(&lines[y++][x]) = inner; + ((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]) = inner; + ((uint16_t*)(&lines[y][x]))[0] = inner1; + ((uint16_t*)(&lines[y++][x]))[1] = inner2; } #endif } diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index 3a915b1..0705420 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -226,7 +226,7 @@ void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int y) { - if (!image->lines) + if (!image->lines.IsAllocated()) return; x += context.drawOffsetX; @@ -276,7 +276,9 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* src = image->lines[srcY + j].Get() + srcX; + 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++) @@ -296,7 +298,9 @@ void DrawSurface_8BPP::BlitImage(DrawContext& context, Image* image, int x, int // Blit the image data line by line for (int j = 0; j < destHeight; j++) { - uint8_t* src = image->lines[srcY + j].Get() + (srcX >> 3); + 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; diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 2ac31ba..227a149 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -85,8 +85,8 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) outputImage->pitch = outputImage->width; } - outputImage->lines = (MemBlockHandle*)MemoryManager::pageAllocator.Allocate(sizeof(MemBlockHandle) * outputImage->height); - if(!outputImage->lines) + outputImage->lines = MemoryManager::pageBlockAllocator.Allocate(sizeof(MemBlockHandle) * outputImage->height); + if(!outputImage->lines.IsAllocated()) { // Allocation error DEBUG_MESSAGE("Could not allocate!\n"); @@ -94,22 +94,32 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) return; } + MemBlockHandle* lines = outputImage->lines.Get(); + for (int j = 0; j < outputImage->height; j++) { - outputImage->lines[j] = MemoryManager::pageBlockAllocator.Allocate(outputImage->pitch); - if (!outputImage->lines[j].IsAllocated()) + lines[j] = MemoryManager::pageBlockAllocator.Allocate(outputImage->pitch); + + if (!lines[j].IsAllocated()) { // Allocation error DEBUG_MESSAGE("Could not allocate!\n"); - outputImage->lines = nullptr; + outputImage->lines.type = MemBlockHandle::Unallocated; state = ImageDecoder::Error; return; } - void* pixels = outputImage->lines[j].GetPtr(); + } + + 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); - outputImage->lines[j].Commit(); + line.Commit(); } } @@ -560,48 +570,6 @@ void GifDecoder::ClearDictionary() dictionaryIndex += 2; } -void GifDecoder::OutputPixel(uint8_t pixelValue) -{ - //printf(" value: %x\n", pixelValue); - /*if(pixelValue == header.backgroundColour) - { - outputImage->data[drawX++] = 0xff; - outputImage->data[drawX++] = 0x00; - outputImage->data[drawX++] = 0xff; - outputImage->data[drawX++] = 0xff; - } - else*/ - { - outputImage->lines[outputLine].Get()[drawX] = paletteLUT[pixelValue]; - outputImage->lines[outputLine].Commit(); - drawX++; - - if (drawX == imageDescriptor.width) - { - drawX = 0; - drawY++; - if (imageDescriptor.fields & GIF_INTERLACE_BIT) - { - outputLine = CalculateLineIndex(drawY); - } - else - { - outputLine = drawY; - } - - if (outputLine >= imageDescriptor.height) - { - outputLine = imageDescriptor.height - 1; - } - - } - //outputImage->data[drawX++] = palette[pixelValue * 3]; - //outputImage->data[drawX++] = palette[pixelValue * 3 + 1]; - //outputImage->data[drawX++] = palette[pixelValue * 3 + 2]; - //outputImage->data[drawX++] = pixelValue == header.backgroundColour ? 0 : 255; - } -} - // Compute output index of y-th input line, in frame of height h. int GifDecoder::CalculateLineIndex(int y) { @@ -638,8 +606,8 @@ void GifDecoder::ProcessLineBuffer() } else { - int first = outputY * outputImage->height / header.height; - int last = (outputY + 1) * outputImage->height / header.height; + int first = outputY * (long)outputImage->height / header.height; + int last = (outputY + 1) * (long)outputImage->height / header.height; for (int y = first; y < last; y++) { @@ -652,7 +620,9 @@ void GifDecoder::ProcessLineBuffer() void GifDecoder::EmitLine(int y) { - uint8_t* output = outputImage->lines[y].Get(); + MemBlockHandle* lines = outputImage->lines.Get(); + MemBlockHandle lineOutput = lines[y]; + uint8_t* output = lineOutput.Get(); if (outputImage->bpp == 8) { @@ -856,6 +826,6 @@ void GifDecoder::EmitLine(int y) } } - outputImage->lines[y].Commit(); + lineOutput.Commit(); } diff --git a/src/Image/Gif.h b/src/Image/Gif.h index f307a8b..402a02c 100644 --- a/src/Image/Gif.h +++ b/src/Image/Gif.h @@ -31,7 +31,6 @@ class GifDecoder : public ImageDecoder bool SkipBytes(uint8_t** data, size_t& dataLength, size_t size); void ClearDictionary(); - void OutputPixel(uint8_t pixelValue); int CalculateLineIndex(int y); void ProcessLineBuffer(); diff --git a/src/Image/Image.h b/src/Image/Image.h index afdb086..1657e67 100644 --- a/src/Image/Image.h +++ b/src/Image/Image.h @@ -3,6 +3,8 @@ #include +#include "../Memory/MemBlock.h" + struct ImageMetadata { uint16_t width; @@ -13,12 +15,12 @@ struct ImageMetadata struct Image : ImageMetadata { - Image() : lines(nullptr) + Image() { width = height = pitch = bpp = 0; isLoaded = false; } - struct MemBlockHandle* lines; + MemBlockHandle lines; bool isLoaded; }; diff --git a/src/Interface.cpp b/src/Interface.cpp index 58badcf..210e752 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -247,9 +247,13 @@ bool AppInterface::IsOverNode(Node* node, int x, int y) } else { - x -= windowRect.x; - y -= windowRect.y - scrollPositionY; - return node->IsPointInsideChildren(x, y); + if (x >= windowRect.x && y >= windowRect.y && x < windowRect.x + windowRect.width && y < windowRect.y + windowRect.height) + { + x -= windowRect.x; + y -= windowRect.y - scrollPositionY; + return node->IsPointInsideChildren(x, y); + } + return false; } } @@ -412,7 +416,7 @@ void AppInterface::SetTitle(const char* title) 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, titleNode->anchor.x, titleNode->anchor.y, titleNode->size.x, titleNode->size.y, fillColour); + context.surface->FillRect(context, 0, titleNode->anchor.y, Platform::video->screenWidth, titleNode->size.y, fillColour); titleNode->Handler().Draw(context, titleNode); Platform::input->ShowMouse(); } diff --git a/src/Layout.cpp b/src/Layout.cpp index 3546c64..db770e7 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -33,10 +33,18 @@ void Layout::BreakNewLine() // TranslateNodes(lineStartNode, shift, 0, true); //} - if (lineStartNode && lineStartNode->style.alignment == ElementAlignment::Center) + if (lineStartNode) { - int shift = AvailableWidth() / 2; - TranslateNodes(lineStartNode, lastNodeContext, shift, 0); + if (lineStartNode->style.alignment == ElementAlignment::Center) + { + int shift = AvailableWidth() / 2; + TranslateNodes(lineStartNode, lastNodeContext, shift, 0); + } + else if (lineStartNode->style.alignment == ElementAlignment::Right) + { + int shift = AvailableWidth(); + TranslateNodes(lineStartNode, lastNodeContext, shift, 0); + } } if (lastNodeContext && !tableDepth) diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index 08726b5..0d79f05 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -26,6 +26,7 @@ void* MemBlockHandle::GetPtr() } #endif default: + printf("Invalid pointer type: %d\n", type); exit(1); return nullptr; } @@ -87,7 +88,7 @@ MemBlockHandle MemBlockAllocator::AllocString(const char* inString) MemBlockHandle result = Allocate(strlen(inString) + 1); if (result.IsAllocated()) { - strcpy(result.Get(), inString); + strcpy(result.Get(), inString); result.Commit(); } return result; @@ -158,7 +159,7 @@ MemBlockHandle MemBlockAllocator::Allocate(uint16_t size) void* MemBlockAllocator::AccessSwap(MemBlockHandle& handle) { - if (lastSwapRead != handle.swapFilePosition) + if (swapFile && lastSwapRead != handle.swapFilePosition) { fseek(swapFile, handle.swapFilePosition, SEEK_SET); uint16_t allocatedSize = 0; @@ -172,11 +173,14 @@ void* MemBlockAllocator::AccessSwap(MemBlockHandle& handle) void MemBlockAllocator::CommitSwap(MemBlockHandle& handle) { - 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); + 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() diff --git a/src/Memory/MemBlock.h b/src/Memory/MemBlock.h index 61e8c51..df333d7 100644 --- a/src/Memory/MemBlock.h +++ b/src/Memory/MemBlock.h @@ -27,7 +27,7 @@ struct MemBlockHandle void* GetPtr(); template - inline T* Get() { return (T*)GetPtr(); } + inline T Get() { return (T)GetPtr(); } void Commit(); bool IsAllocated() { return type != Unallocated; } diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 3c22470..899ba85 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -13,7 +13,7 @@ void ImageNode::Draw(DrawContext& context, Node* node) //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); uint8_t outlineColour = Platform::video->colourScheme.textColour; - if (data->image.isLoaded && data->image.lines) + if (data->image.isLoaded && data->image.lines.IsAllocated()) { context.surface->BlitImage(context, &data->image, node->anchor.x, node->anchor.y); } @@ -40,8 +40,20 @@ Node* ImageNode::Construct(Allocator& allocator) void ImageNode::GenerateLayout(Layout& layout, Node* node) { + ImageNode::Data* data = static_cast(node->data); int imageWidth = node->size.x; int imageHeight = node->size.y; + + if (!data->image.isLoaded && imageWidth > layout.MaxAvailableWidth()) + { + node->size.x = layout.MaxAvailableWidth(); + node->size.y = ((long)node->size.y * node->size.x) / imageWidth; + imageWidth = node->size.x; + imageHeight = node->size.y; + data->image.width = imageWidth; + data->image.height = imageHeight; + } + if (layout.AvailableWidth() < imageWidth) { layout.BreakNewLine(); @@ -54,7 +66,7 @@ void ImageNode::GenerateLayout(Layout& layout, Node* node) void ImageNode::LoadContent(Node* node, LoadTask& loadTask) { ImageNode::Data* data = static_cast(node->data); - if (data && data->source && !data->image.lines) + if (data && data->source && !data->image.lines.IsAllocated()) { loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); ImageDecoder::Create(ImageDecoder::Gif); diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp index c8b3509..355e9a0 100644 --- a/src/Nodes/Scroll.cpp +++ b/src/Nodes/Scroll.cpp @@ -72,9 +72,20 @@ bool ScrollBarNode::HandleEvent(Node* node, const Event& event) { case Event::MouseClick: { - ui.FocusNode(node); - startDragOffset = (event.y - node->anchor.y) - widgetPosition; - draggingScrollPosition = data->scrollPosition; + 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: diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 63526a9..145e77e 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -31,6 +31,7 @@ void TableNode::Draw(DrawContext& context, Node* node) int y = node->anchor.y; int w = node->size.x; int h = node->size.y; + bool needsFill = data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1; if (data->border) { @@ -40,12 +41,12 @@ void TableNode::Draw(DrawContext& context, Node* node) context.surface->VLine(context, x, y + 1, h - 2, borderColour); context.surface->VLine(context, x + w - 1, y + 1, h - 2, borderColour); - if (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + 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->bpp > 1) + else if (needsFill) { context.surface->FillRect(context, x, y, w, h, data->bgColour); } @@ -214,12 +215,23 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) layout.PushCursor(); layout.PushLayout(); - if (node->style.alignment == ElementAlignment::Center) + + int available = layout.AvailableWidth(); + if (totalWidth < available) { - int available = layout.AvailableWidth(); - if (totalWidth < available) + int alignmentPadding = 0; + + if (node->style.alignment == ElementAlignment::Center) { - layout.PadHorizontal((available - totalWidth) / 2, 0); + alignmentPadding = (available - totalWidth) / 2; + } + else if (node->style.alignment == ElementAlignment::Right) + { + alignmentPadding = (available - totalWidth); + } + if (alignmentPadding) + { + layout.PadHorizontal(alignmentPadding, 0); node->anchor = layout.Cursor(); } } @@ -471,20 +483,27 @@ void TableCellNode::Draw(DrawContext& context, Node* node) int w = node->size.x; int h = node->size.y; + bool needsFill = context.surface->bpp > 1 + && 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 (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + 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->bpp > 1) { - context.surface->FillRect(context, x, y, w, h, data->bgColour); + if (needsFill) + { + context.surface->FillRect(context, x, y, w, h, data->bgColour); + } } } diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 5ad4e40..7e170f4 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -32,7 +32,7 @@ void TextElement::Draw(DrawContext& context, Node* node) Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); uint8_t textColour = node->style.fontColour; - char* text = data->text.Get(); + char* text = data->text.Get(); if (context.surface->bpp == 1) { @@ -102,7 +102,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) node->size.Clear(); - char* text = data->text.Get(); + char* text = data->text.Get(); int charIndex = 0; int startIndex = 0; int lastBreakPoint = 0; @@ -248,7 +248,7 @@ void SubTextElement::Draw(DrawContext& context, Node* node) { Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); uint8_t textColour = node->style.fontColour; - char* text = textData->text.Get() + subTextData->startIndex; + char* text = textData->text.Get() + subTextData->startIndex; char temp = text[subTextData->length]; text[subTextData->length] = 0; diff --git a/src/Page.cpp b/src/Page.cpp index d3b283a..718047b 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -136,7 +136,7 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) { TextElement::Data* data = static_cast(node->parent->data); SubTextElement::Data* subData = static_cast(node->data); - char* text = data->text.Get() + subData->startIndex; + 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); diff --git a/src/Palettes.inc b/src/Palettes.inc index 0836ac3..40a4afe 100644 --- a/src/Palettes.inc +++ b/src/Palettes.inc @@ -17,38 +17,38 @@ uint8_t egaPaletteLUT[] = { 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, 85, 0, 0, 0, 85, 0, 0, 85, 85, - 0, 0, 85, 85, 0, 0, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, - 0, 0, 0, 0, 0, 0, 0, 85, 0, 0, 0, 85, 0, 0, 85, 85, - 0, 0, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, - 0, 0, 0, 85, 0, 0, 0, 85, 0, 0, 0, 85, 0, 0, 85, 85, - 0, 0, 85, 85, 0, 85, 85, 85, 0, 85, 85, 85, 85, 85, 85, 85, - 0, 0, 170, 170, 0, 0, 170, 85, 0, 170, 170, 85, 0, 170, 85, 85, - 170, 170, 85, 85, 170, 170, 85, 85, 170, 85, 85, 85, 170, 85, 85, 85, - 0, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 85, 170, 170, 170, 85, - 170, 170, 85, 85, 170, 170, 85, 85, 170, 170, 85, 85, 170, 85, 85, 85, - 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 255, - 170, 170, 170, 255, 170, 170, 255, 255, 170, 170, 255, 255, 170, 170, 255, 255, - 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 255, - 170, 170, 170, 255, 170, 170, 255, 255, 170, 170, 255, 255, 170, 170, 255, 255, - 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 255, - 170, 170, 170, 255, 170, 170, 255, 255, 170, 170, 255, 255, 170, 170, 255, 255 + 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 5, 5, + 0, 0, 5, 5, 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, + 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 5, 5, + 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, + 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 5, 5, + 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, + 0, 0, 10, 10, 0, 0, 10, 5, 0, 10, 10, 5, 0, 10, 5, 5, + 10, 10, 5, 5, 10, 10, 5, 5, 10, 5, 5, 5, 10, 5, 5, 5, + 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 5, 10, 10, 10, 5, + 10, 10, 5, 5, 10, 10, 5, 5, 10, 10, 5, 5, 10, 5, 5, 5, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, + 10, 10, 10, 15, 10, 10, 15, 15, 10, 10, 15, 15, 10, 10, 15, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, + 10, 10, 10, 15, 10, 10, 15, 15, 10, 10, 15, 15, 10, 10, 15, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, + 10, 10, 10, 15, 10, 10, 15, 15, 10, 10, 15, 15, 10, 10, 15, 15 }; uint8_t compositeCgaPaletteLUT[] = { - 0, 0, 34, 34, 0, 17, 34, 34, 136, 17, 51, 51, 17, 17, 51, 51, - 17, 17, 51, 51, 153, 17, 51, 51, 153, 153, 187, 51, 153, 153, 187, 187, - 0, 0, 34, 34, 0, 17, 34, 34, 136, 17, 34, 34, 136, 17, 51, 51, - 136, 17, 51, 51, 153, 17, 187, 51, 153, 153, 187, 187, 153, 153, 187, 187, - 0, 68, 34, 34, 136, 85, 34, 34, 136, 85, 85, 34, 136, 85, 85, 51, - 136, 85, 85, 51, 153, 85, 187, 51, 153, 187, 187, 187, 153, 187, 187, 187, - 68, 68, 34, 34, 136, 68, 85, 34, 136, 85, 85, 34, 136, 85, 85, 119, - 136, 85, 85, 119, 221, 85, 187, 119, 221, 85, 187, 187, 221, 187, 187, 187, - 68, 68, 68, 102, 68, 68, 85, 102, 68, 85, 85, 119, 204, 85, 85, 119, - 221, 85, 85, 119, 221, 85, 85, 119, 221, 221, 187, 119, 221, 221, 187, 187, - 68, 68, 102, 102, 68, 68, 102, 102, 204, 68, 238, 119, 204, 85, 238, 119, - 204, 85, 238, 119, 221, 85, 119, 119, 221, 221, 119, 119, 221, 221, 255, 255, - 68, 68, 102, 102, 204, 68, 102, 102, 204, 204, 238, 102, 204, 204, 238, 119, - 204, 204, 238, 119, 221, 221, 238, 119, 221, 221, 238, 255, 221, 221, 255, 255, - 68, 68, 102, 102, 204, 68, 102, 102, 204, 204, 238, 102, 204, 204, 238, 238, - 204, 204, 238, 119, 221, 238, 238, 255, 221, 221, 238, 255, 221, 221, 255, 255 + 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 dc5d738..54915aa 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -275,7 +275,31 @@ 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")) + { + if (!stricmp(attributes.Value(), "center")) + { + CurrentContext().node->style.alignment = ElementAlignment::Center; + } + else if (!stricmp(attributes.Value(), "left")) + { + CurrentContext().node->style.alignment = ElementAlignment::Left; + } + else if (!stricmp(attributes.Value(), "right")) + { + CurrentContext().node->style.alignment = ElementAlignment::Right; + } + } + } + } } } } @@ -668,6 +692,12 @@ void HTMLParser::ParseChar(char c) } +AttributeParser::AttributeParser(const char* inAttributeString) :key(NULL), value(NULL) +{ + strncpy(attributeStringBuffer, inAttributeString, MAX_ATTRIBUTE_STRING_LENGTH); + attributeString = attributeStringBuffer; +} + bool AttributeParser::Parse() { key = value = NULL; @@ -822,7 +852,9 @@ NamedColour namedColours[] = { "black", RGB332(0x00, 0x00, 0x00) }, { "white", RGB332(0xff, 0xff, 0xff) }, { "gray", RGB332(0x80, 0x80, 0x80) }, - { "silver", RGB332(0xc0, 0xc0, 0xc0) }, + { "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) }, diff --git a/src/Parser.h b/src/Parser.h index 4e2b365..496c1d8 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -41,10 +41,12 @@ struct HTMLParseContext SectionElement::Type parseSection; }; +#define MAX_ATTRIBUTE_STRING_LENGTH 256 + class AttributeParser { public: - AttributeParser(char* inAttributeString) : attributeString(inAttributeString), key(NULL), value(NULL) {} + AttributeParser(const char* inAttributeString); bool Parse(); const char* Key() { return key; } @@ -54,9 +56,10 @@ class AttributeParser private: bool IsWhiteSpace(char c); - char* attributeString; + char attributeStringBuffer[MAX_ATTRIBUTE_STRING_LENGTH]; char* key; char* value; + char* attributeString; }; class HTMLParser diff --git a/src/Tags.cpp b/src/Tags.cpp index 07e19c9..5bddc35 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -76,7 +76,8 @@ 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(), @@ -239,22 +240,29 @@ void FontTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { int fontSize = atoi(attributes.Value()); - switch (fontSize) + if (attributes.Value()[0] == '+' || attributes.Value()[0] == '-') { - 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; + data->styleOverride.SetFontSizeDelta(fontSize); + } + else + { + 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 if (!stricmp(attributes.Key(), "color")) diff --git a/src/Tags.h b/src/Tags.h index c447d84..8955177 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -145,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; }; diff --git a/tools/PaletteGen.cpp b/tools/PaletteGen.cpp index aa52425..20d9577 100644 --- a/tools/PaletteGen.cpp +++ b/tools/PaletteGen.cpp @@ -97,7 +97,7 @@ void GeneratePaletteLUT(FILE* fs, const char* name, const RGBQUAD* palette, int { index |= (index << 2); } - index |= (index << 4); + //index |= (index << 4); } fprintf(fs, "%d", index); From 71c13177474e63f3d9e554e158d73cbc332d4d25 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 21 Mar 2024 16:46:27 +0000 Subject: [PATCH 53/98] Image headers are now loaded in parallel to parsing to correctly generate layout. Improved error reporting. Reduced memory usage by changing font format. Added hotspots for mouse cursors. Improved EMS detection routine --- CGA.dat | Bin 47062 -> 20772 bytes Default.dat | Bin 92546 -> 37933 bytes EGA.dat | Bin 70594 -> 29666 bytes LowRes.dat | Bin 51759 -> 21236 bytes assets/CGA/mouse-link.png | Bin 5579 -> 5817 bytes assets/CGA/mouse-select.png | Bin 5584 -> 5810 bytes assets/CGA/mouse.png | Bin 5798 -> 6070 bytes assets/Default/mouse-link.png | Bin 664 -> 5607 bytes assets/Default/mouse-select.png | Bin 622 -> 5605 bytes assets/Default/mouse.png | Bin 5582 -> 5890 bytes assets/EGA/mouse-link.png | Bin 664 -> 5607 bytes assets/EGA/mouse-select.png | Bin 622 -> 5605 bytes assets/EGA/mouse.png | Bin 5582 -> 5889 bytes assets/LowRes/mouse-link.png | Bin 5551 -> 5797 bytes assets/LowRes/mouse-select.png | Bin 5576 -> 5774 bytes assets/LowRes/mouse.png | Bin 641 -> 5585 bytes project/DOS/TCP.CFG | 3 +- src/App.cpp | 114 ++++++++++++++++++++------------ src/App.h | 15 ++++- src/DOS/BIOSVid.cpp | 16 ++++- src/DOS/DOSInput.cpp | 29 ++++++++ src/DOS/DOSInput.h | 3 + src/DOS/DOSNet.cpp | 22 ++++-- src/DOS/DOSNet.h | 4 +- src/DOS/EMS.cpp | 22 ++++-- src/DOS/Platform.cpp | 55 ++++++++++++--- src/DOS/Surf4bpp.cpp | 39 ++++++++--- src/DataPack.cpp | 73 ++++++++------------ src/DataPack.h | 6 +- src/Draw/Surf1bpp.cpp | 47 +++++++++++-- src/Draw/Surf2bpp.cpp | 13 ++-- src/Draw/Surf8bpp.cpp | 29 ++++++-- src/Font.cpp | 16 ++++- src/Font.h | 13 +++- src/Image/Decoder.cpp | 28 +++++++- src/Image/Decoder.h | 3 +- src/Image/Gif.cpp | 19 +++++- src/Image/Gif.h | 7 +- src/Image/Image.h | 2 - src/Interface.cpp | 19 ++++-- src/Layout.cpp | 111 +++++++++++++++++++++++++++---- src/Layout.h | 9 +++ src/Memory/LinAlloc.h | 6 ++ src/Memory/MemBlock.cpp | 5 +- src/Microweb.cpp | 3 +- src/Nodes/Button.cpp | 2 +- src/Nodes/ImgNode.cpp | 96 ++++++++++++++++----------- src/Nodes/ImgNode.h | 15 ++++- src/Parser.cpp | 20 ++++-- src/Parser.h | 7 +- src/Platform.h | 6 ++ src/Render.cpp | 6 ++ src/Stack.h | 3 +- src/Windows/Platform.cpp | 34 ++++++++++ src/Windows/WinInput.cpp | 20 ++++++ src/Windows/WinInput.h | 2 + src/Windows/WinNet.h | 2 +- tools/AssetGen.cpp | 24 +++---- tools/FontGen.cpp | 79 ++++++++++++++-------- tools/MouseGen.cpp | 16 +++-- 60 files changed, 785 insertions(+), 278 deletions(-) diff --git a/CGA.dat b/CGA.dat index 43a9e661abd166f421195a441fa13262507b942b..e661ceed76411b57c714d525c564fb2aa041af26 100644 GIT binary patch delta 8349 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 literal 47062 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 diff --git a/Default.dat b/Default.dat index b91f08b388cdeca36d74ed61f39ecb6d0ff0b851..8be5ac90a7e5ece54336f313025066d67330d1fa 100644 GIT binary patch delta 11319 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 literal 92546 zcmeIbU5s4Gl_nPT!^$kO$V&EbR!McK>Q*%+aoU2M6lHd`y1FvSu9nzi_1{=+NPs-J z9LnSjOo=7;LR~FUl~-C}r(e9=Gw?&d?4tc(z;Euy-3J3#D?n=l4Qn3^L-qikT2UBow)87~8o*O?mZf3DZ6#Hn^|Yg7q376<^TAS`S5=< zM$(@%AAY-Er7xQgzw#ec=}XtlQ(se@YvzC5SDb6+zxywce(vSh{s4TkpHyk^x%smy z4L)D|H;Qx3{9bQP@IQCWy#MJcefgUCZ};ZRb1%O9+JE%C`S8#G80jy+@X~*1KK$^1 zX8J47fAuR!fBaA8%nL8P_$4Gi{6nOle`$}&4}XaC!ouRxQg>;d_XUKdI!O9dr_FK6=+~yFrPDvri+|UA-$v|CbQ*axPQf5NBn#Z5y|RD zFOdaO3tnd7Bk+misG@wumQ_78ZaqkL4}U6g0|6N>%4_FNoVU zP0#FGnJAY;1(9a4(kSJ+rvPYOAbktp!?T7i+NE#X?K<$gh#Kx$jUH5#J`K51Ucwk1 zw|lm9wrH=?xpE_o;Oaew>@;K(jFBRu#4u#_W7rBTpE()su0D!PtvJnSu3`(N=2B|a zD6PO*!m1gR+2~8z3y5EwWU%kmLa11G_5;@Qp7|l72Gy6lD`dx9gS;BAnH>dK9bW@f z;vSwgNc*rAgwE9|hK!U1e5IV1EWaFr*Xueuo3+}!bjv#q7P9s8DBHVUYHLRfOcVR!kAmA%X4Fskmcx4aU>0`eNRx1b^X9% zjlgSKpr&`MmYm*1HtjurUl2~(O8$t%-m9FbbH~lm&_;Zk%xWh0^b<-datXJ^sz-`! zUp3#bG83j@WD^3HYdeH+2Emg)Bt6Jx*ARirRUvZ!j_o__KQDv;laftPs5gmjBo~OU zYnJ9hsDniZ2yDyCMsk3$fMfD?q+B51ysB~2DpayBd#eDUB_!@Y0%6@bifkc)gGaplFMs+ElN9&{m$Ivh;UYN@# z4UZru(%G+P5YeN(*4a3vedO`b`^k(#@s>*fM>Q(eoQ`3&#cYxA;(QSe3cD*m?2S^&;5 z621bNjr|Ki)x$ZAu&?0nTms0oek$aROiJ*dn6KFp*_Ysh2BynkEi?BRrMS+~ote{h zX|-RNNrY{TKr-CpkU_)Kg3b%q&EL0eLEIgb&A0=-XbgFb!pG!OR*l%aXEw%Y`_RGk z_K~(>8)?YQ4QTs<`91SH=9A`2W=;VpK_H*Ss&WgK)2Si3%{gcOq(MUz&TP~-HKkTB z>&)gZM|OUx{SWg<)uiFvmxi#vq#^CxDDg%iJKC2qV_!FKnSWd`mw{`4=}o+HpT+$z z*Ce`62zOJgPZ~lA(hy3Wh7>5%c;$;E$g;XkLq@h=3#Pl0=3V@`98D3zz?BRS18=F50a?WVX>_IF04-LagggEXWMJ4-`YP8xCnJkt&Fx~)+t3JJtzgS1h_m?ERKdHq#iv}XrA0T3$f0e0ejV2Uh6}j zhd`7@>(hj2eVP#VA6Cy`14k(KBo`IJxg-r?ej0KSHM?m0gtVO78Huh_ zyD6uGn8xWKhBzI>5N%V*_xGnxJ28!|jyz4i-TIP5zMr{KWvE_N0=o#my@(usu= z%fN{OeD8h$8NveiK#s+3XR!k)GAIv<<{!dod!>sWWfB<0` zx}Eu6X7w`jqFq4m#_J9w=JJ8DEy&{K&`!!9Byh!fPxt+{UFi7kd-BBaHo9J1GS(S;x|mF3xLn8E>KDIuM0tic%I%+Ko$X?Vq900@Uh9M#~j{;OP!b0es3(((~ z5QA{K$NKiVh^LVMqFJ%+egP0J_Zj`*mN~*FrYC>4hZ`(I{=$=o4o(j~f33gXQ~aqP z4qB8gh6}He$I?%0wx2ihv@h@#XlH;WIuEc`lq9-yu;pJWD5Y=-F{ED7KGZ-sWOs^w zp7pZcbN~I`b;P%x*w0LV&?y}cYL0!Fj;=8r<^_U-8gqy|M!hrThjp`QMg?2Q#$wZ~ z`(R54cpYQMz1S76tu7~`Uu)SdXMF& zJ+cJaC+(D+{P4pryq%0b{LpY_((x}t zO~dxvueGY$mHU3hq`+uUR5&uj$cU|FOuTu=%|Kb@fZ!r&Pf;J@{$Y(f0{_{8+o@gr zF{b9%uVcF;&_6O?Pp~7s)LXZFiZpK+zhHRe#^@gyDvoaFXrVEEX8BGk?jw75beanJ zv&5YWBouD;;8^d6k$n&>z0QIYuc(lOM#g~sY6J-Bm8-k^a7llyxHXbPySV=gyj^~Q z``clyxnD}yejk+hu1Ou(5a&l|86MhjGyNka^6nvjuBS>5N>>Qi7wn(z2uBd;`q5a{Da^7J z{m83%m==VPMmf3iFX15Si-9cRL({3B2MCYgO%3=8YC?az^hb-H7Tide#`-Z9 z)2$Zgj*t=xm@C~F#3c#HsDk(FJksQ6^{UPUri}(v?UB^IOq90;> z<)7?f9Nd@6*pf6yaL75cQgDse#Z$OHeA}@~rZ{u{6Z2mo{*%Vqg9P*x3{dX@(z>Qg zF`9PFMf|p-F30Q0CA}2o`{(Arwtw;3$gT1`f+#ut5KNJ&b@g9_yU-jjWwBM$(iyA^ z2=`yyQl4@r?W~}4zudoDwX#s{;LuFs=k5BRdw^6Jr7~Oc*rs^}%aEeoX05p5{~~-^ z`@aG!7aIr}T{Hi}u5GTHZgIAFtoU@16+VW8pEBnP9;MojWD&YSR0A?rEmMNjDAtw0UVNd<)QN^yDvluC#oX+^imn|LD_`3$V~V ztls`KFh#fjx8s8KKP;Mg_*ta>P5*)V7o`5}aH)SgT_TQ0Pb5G=EspbgldX z51}J#pCW+ppXGmG`)V7q?Ogi{7tRliU$5>=`9WHdSn2(}9MH7G={1r4d77TbwA0|E zZikaQcS`N$TXOU45z-?Mg=+zKbSdWm9vIr24iAqpoAjbh zx5A128^XWc{~$Kg?!OWKDSCWwXL|>`CN_?uli7ONn1ZTE<@#aCYejDFuk9 zAjgSQ-~=enNh#0D%E~f|N1zm^PLXcq6mpSouRL~&3xWcgA z-RtdTd!y}Q&R}}o@4nT0D|>6SA7U8{Zh9!n)V0yo5hj+=dKTk}@r>f^=vf?FSu{wE zpch8hrCg3IHT5cZu9TkPA@A`W*Rlv4LmQ0850tn)+;0c9K(a=DSVO0wBEsa`iCfhI zi;FEeu2_q(xES89YAJQqYm7*K2uls(NsQkNCGRnKunuyB!Ud1SxLmCVd8i&!{2cVEdBS|c zN|?eE{#jH^{H)+OKWnk81#Bt?fDLe8P~zlWwivq6NTMJ!M?dDF*%Iz~5=rZ;b|Af@ z*+&+RR=T~#&401Awa564=l&TS(EZLNX3B%ClZgCQP*!BcxWvGLM2t5ibc9&cDVvV* zolqr1uZqcM`+M;7=MIq>yNzmr4&CaHVxmjy$GASi2Ih=G}OGp^&Xce z&%;9Dde0CB`5@;(=qIyJW?P613fFs93=l6OUO?PJ+(gU@h3mc6U#*nZ_J!0o(qihU zeDogkyOwRzUPd4YLTGiBEsnAX3vcN{TcZ)lH;gH0f}>GZARUF&Av8y#IMmgYy4Y*4 zXxcMWm7yq;%DbFXwW#s(g^k<&?QH|(FR zS1|+6*;vePTtXLxjv6w16gY~H@p7uHCFUM-0p&6(uC}mS8whfamS~HP-$y;v_5=#* zGD4ZLBtCAO<|_-Fmxj1A?iMkI)YG5cu41P9dW=>*%P1{0ocWvcFH4)leDy?CB1`4` zl@2=Ub2d)%lS9Y*4PB@CHOr+m+!Sd@)t55HVPrD|+rJ(cPiTr7U7qz78a+NwmFt)0 z(lw(X@zwG_&93$Q7=Y`CuzgeY6;y2xVx-$c@IrpH7nQUw(k9aF!8QrQSFtKk4ss9S z`lQYeehYct#2b8KWj4n4($;6Bo_cM!(|FU_#;?eJgguS>KC=5pverQEKQ!DXUqoN# z-htmxPKUT$){goqB6N9mhpT590mBz}h^#vw(a)&TlRWvgk(|^uEHdWcQ;cfVSx5Rc zA`TqV(P2m`jkO=6wQzfc_eg)Df!JVv)p}t+Jx(|--mhA|NBsW(v4dZ=o{wWw4rgSO zoH4@aFqqorT|V$;=}auTWo6f*JP>lb55>Kg#|rdmHi3vVW9~ z^1N*Bw+7f-ZNuNW4SokRcq&-FQA#PN2k|LQ#}R0o2T*G9Pa*%GW&bq$b6oifc3g|* zZmGRYz62BgB1Jo{9Eo>fCF%=n+6(!K*KfM^h{pr2>1E66XPQJIOb=r8{{5<-IIN-h zZWX^^_rCNgNpv`2DhnTv`NW>(Ue%zT=e4a*V~aWtCJ#&F$27EIqQLHRQzBP;|+S(v)-s%*k3Q3!cXYb>n9e$F-vzLZ2LtZ z(GxJuuP2^>*?8o_{x^$Xzqn95aSS|D_2yap&H(SMIWx0d(T6de4^Ln$TPD>9n`RSz z>8int{f+mbYJZcC^k@(IC+RDv-<8^%2h+4a(fJ)!f3xtGAXBfH%?~>dV9mxWAa@45 ztR<2MgILCFJiZL}7Vu~BGZyPOc(!=Yzk5HcLg3$3dzs~T)b^;`4V_M6=y%lin1!e9 zF$-^P^r_Jkg>nAkmU6Y-X7LlP=#{-yj^TN>kqWCBehIF;nqAGVWqa9m!~yK| zQj4W(!QJ;ys$OQ{sa|H`X?vEe=0` zuV(S;Tf5DTyhSAEs~AZbW#*tgg|EnP77;xAMXw4^%F(OpZx){FZx){FWfq=}f>Hzj zs*=e)L;VUQeGEy5c=AfkY`pZ#HRKqFKZadhghsU{X+NRbOXw#gZWz>_eQrAA`)B4WB(^+`7e^RV6w1bqtSk|KL{P=4DcoW9{Y5?BZ@qWcX(ku8$faNZJ zOaQ<8+rbMpc$bD5%(mD0e_O9+dXBkEN^n#yKnE6A)IW`0?8l*KGVB?uy z`tRW2;}C!Dt~7lMIl#7c4ezxQr(c1#%{Pl@uwdjjxt~JX{qWJimcwtB9hy6cyUMJR ztA6wn15aj^eqiA__{1@ilG^`+R&9>Ve)O7_>!=csay^7MOAvelP7^CBM zXiI1L{9Zky2GR(w-ebsaLpH(46|tdd$31KXt$+Hr0jL$H8D&l@;V%h5Lh0%hLvk$!d~aIpyO!f^Y%(-x8RNPvg*dr1r4VOMq`pzyC|*Y#BYqR{Mxit}&FhHYL%eHlBRX!Zj~|`h4y%0CMXt=O z4wQ!ccFjTsb2JK9BDN+a-f7sVj1Rshlty{z-N{Cq&|bjt&{0PTOr#F#Jdyn52gp;>caGRub}cG_h5vWZ!DE93dQM{r)37S$~cqm(67@q`Gr8v5m-k z7?>ka+V{i*-m7PYdsVK^ZL|~Z4cdqOmeDlHd`zXuD}LObD(^b0N;Hpk6yp(u-yV$P z-v#Z^ZqlJ064%qswT~bl%?{EnAh|hi+I!`$*eiDaWgbT#9j!Q`u!nO$LD-?9TPz)0 z-JC$M=5hrQ?lN4-#&VNq(r~0^DR;~j?50*Mcjhx3$*pl+$0&HWc(b^R@o+tW?qejB z9o<2Y?;h4s;sO3bI)QVop9;=06-o)l#rN%q>~s215fXRNWddNcIOeO)4r`=|)qa`K zgh0Y^UC7#doWONn;O>a+z_5!n<1X}~(E{pAUZ>g<1G{=Mka-_}jB#MwOm81)8@7># zf%!Jt`HFeAc)WP3xLwQ@F=S}Y1LKzI)R29fvu?iMpdl@1HtJg}TdZDIn$2B~?EJVQ zlr@Fh|1ghKO$g_{G=%*n4Qb~_i8snMTh86$9(v0A=4T~y8Fc?6J&OA*?ti%^(S1U= zn__*^5K54SQ0g?KwDu!qyDQ}qD|C61A|$ugoPw~w$N4!)Y~ad`JCFa@UUjYfjQJLx zQ@bhdebc)s>L3kSgPo-zEGG@Q0_oC_N^sn0W7MxdUJkKSyNk$OAmx`{PpNf(rT1OS zh*E2g-UULbHAn9Pp=_F?cgL-LpT$-`mb%cXocf!x@OYM$KLGOV+-)3(jj`q1Yg5T()jG$C4_ zCPZ4E<;2`da5uJeO2DXrBNTg*iwY4RpUZb6?SVx}LBs;tCv=`xZldedZp!H(rg1um zAx;M|MB7yIscQ{RJ28!5(0&f_$9oud(Jwi3eaG8xh}2q&zt0k>QxK6l1rga&5NRKs zPq%G5O{{p*j@m~!R%yG+E-j|Pw{W}N()m$ zbj;(sxA-9A41R3beqC0+g}ca~9qul!@XN3#x*$A>-}uG<^*hNg&W54!EnJp@pBY|; z94D5$_ztgr3m3m`e3E|%XY7?OdX&%5wO9QT?(!M?EnMruw<(+?kS(sq{-I$}oVyyY zJ5aN30b%vrxX9vV4Aa`d_iS~s;n3L3G?xS1iR-)XiG|@Uzf(S4SU1z;{Ck()3wF+! zTCdp1>G+npUEIWX&>VM4E78yM(=-`bb|_|UbJVa)OYe7I!7eK%1M>l7|G;ONNOOsy zi^&8y$jVI_JnO@KP0YCG0h}dE9krGv_?I;fwH8J<2oR}{+6fPLt%;EkdglW4Hzvd* zobItTdtJg)$bSj%53${^0K(-yqaWNdNBG3_A~l(_1AlfKlQ^w zi?YRV;gx$V{j_HLdAX;3fuDhPwy{L#0oEBMiS8V1`IibxDO^Gfsh6}5H4qNjopNn| z&>!?~{+nBO5Z}Lie_+}ewv0>VM(HDH$>UmrJcQTf)^+g7txbs4 zigm>HantBQ1-H~(FuOwsGICrs73wcX*q`iA-bB2c-_7Z`_XWty{qg|uPI;@miFgC` z@ZnYc>)N&HpL$mb+Uaky{Q~}Mcj^%I_KtM!(+>d$26}zR%prB782VuIprnO`(MQns z(R(aE?U7t*pR`kQ^21M77;nFG`<S_JLN)F(5U6vNXT_QufGJk?3z8Tsg9F=Ge3bSm5Oh)T)<#zl@^@%~W8?DkcRxTM-U~aAA%_|wXXh)a2Lvdvcy(ROUGkV!u=Pwl&9QhjW5K~ zxnJ(OPZ9cE$R%;tuK&3QNQFF=*^fa8R`nAJLv_v>!SqW_}YbHHI^ZYxZq&%`$A0lZBu6Bq` zXfELDdWi9Z`{!TckMSn7DB%;uQ%JP?7es&U{sr!f`CCuc|5G;i?`O>8Q{hs6JN=~s ze|5^SAwy(A!+m6qE;QYWy98LLfX+>hC_xEx@(+;QCM2=h2^gO1W2A9LbHvZ(! zol+ZIYcf2#*&p_A-bW4lLw1WOkY0KM=Sa4!?!R39Z^Coa^e?#jr{S*tX}GIj8eZD= z64~_X3!fSVe0XXU@Zq8ird=!*JlszoO`H$jUn9E zUy9z@PnzD>Z;D=op$r_?cyERU$frmDuOpp%DB;nioCA2^s(x;T68k6Uq4QTTFC^aKV+KmGvO>DkE67E@z4FM_hMxTwtM&-gRe%Si-Gy`1{I4Wnao6?{ z{(!axoG;g(Hk`J6Opx-yngoVkf3UEh&J!!Qb-Z3-@TN8~p$I(U2N6#f{2&t`W-Btzx;NQf7tL=<{;?ov@ldC=t z4bEm*;xWsuWd?_uRs_a%y38mSrQjptEzvc^kFW4Hnd@@vQcxDs@|j+)Pg~&^W<#qy zowk;z&2HQBYeFR2aKu7<%w~F_;GzGvZ7*DrM$YjAbS|Mz9Fun64$iQ#xq_CqMqka0 z;OiMwXyv+bg%ST}}y| zl5Gwi+d@>k_emO;MQNgH)iNIx@v zrPX63_OK0=?wg$>ex7=dpUVS&1nQv&Nl3MAKUkRIbH#oxmj<6p_5*~r5VS8$+WVk& zX4`Y=wW)L^=+OH-Y`HfF=F-HLJF#3+^vMn@O;Z^dBxng61sf4VL~hyR(UesmMkYYa zX$cR?VA}GhusYpB>S+n3G(P=u+PG2L(Tp-%2_dA6cryAe*&(;B+;YME_#c=X|B#=X zKQ))%=V$$ZpB4Q31N&*Eg)!I?Q5?#Wf@HiE9YV0S08o=wIMmlR+VRA;NLei#fDiTd z4ecto&4q?&3;PxaXfr>+j6!Ld%Qg2npxpWcF_Hn?YMki_VEO?JG0xobv;G#+O^xMN z;I`@%%C`Cxd1uH_@spVoFB_{EKCYly9v)m~%&)Iq{(5Dsz$R?kpo}~%_ zf%gDH2}rbkDvZVr_!5wq`6&feuQmQn+bQnYBeVnS+&~{eCd6(FHSLr{8*QC`Z(`nf z4?g3>r^abZd;~q$tbDtYronZ(UDdS7a$mRoDEFG6#`hC5?P|_imrqis@7WDP?iEp| zYn-;ku5C@hM|#12(7s5wg}T?Y^`qmAX0>kp#4-E+PmI9_c8Jf8{h&now8>oOmjm-t z>pi=Ll`vLHI*DU$of-T>Q5=dE8IV6Sw8<>W%x&A1EnLR7QI>T6MbV%F3G&;5;M6Tu zmlEaR5tvbE;v{n-DtXLPLu;EbK8L5R^YgZ!^)TrJ9iXNp+W1W&sX>ouMFX`$rX(Ke z>XEM6%;VS1HZZc~Z8FWu|C`XZ>BrA+E8oXyOFyC&4YcXU&wr$=f4917)Bi)|=ZcL) z+ecA?JJ!`)dmD4R;BroOI&az>tmnZh8P*L+@WCL^w<|GRis89%2>1b125$+wY&?FWr ziz>rHq~b}OwoDiU%nYB87CtxzS*@tQwEBUrkoXImhW4f?3dZv{3}O;cG7=v-i0R@A zUDauD!(&^Ja)I2p-SD9rS7<>9@;eVBxs;W+$#m@en|9ybyrRBfE}w|mu;7lb{$S7$ z&vSH*sFIK9=D*qihR_T?`ypTCBL~;|6T+uGZQG_n>(mYOP)!`Mu6wTZ;t1+L4)BgS z&U$hlL&zc268!V~rhjDc8wWRzrk!afXeZ|Md!}z0=B4@ZkzZ7h-tL<_c&RPN`{H~N z=oz^Bk6muf2=*)i34CgE%|iz%@&R44sJP-IR)kRwA5wV~kaCz08Q4&D#131*}`>?bq8nx?} zn&iZU&ty9nV&qpi7MUKmX5 zcx6whKd{&8#fF?9Ao#)WN9g9;_H)cLYu~N{oAeH>bI&IIa(%mcSX$q%9+q}JH&5j} zg-3W;T9DK>`4MPYWEgJPW#qc;p}1l)LeBJnS3A(b(T+DT``}Cj?&|?(3VgP6g6@VK z{5Ik&*8V}&P$@t7Z8U`tpeC)~Mh{C18Ng7&!_xXPmw$uvZt1E+!!cQAK)K=(nF6gu zY}}+NfhnJ-LMFnYLBx!{j@3w`6c_`lgAe*F((=KGoeY?a(AMC9@UXNX6q!E)?bDvG z{$a~SahEAC!$Z-Ew4g=eVQDRey9^J^jrYymKjdflp;a1ad1Ui zq>_GzMp^T?`0ZQMf@Ewt)U+OjOZx5mu(W>rJ}fOHsN3jaX-jV+&Q(Rt1d3<|bg|r+ z(gKiZ`&f8<0rHXNA>H;-l=%KPp~bEQt-NCwhrYVdxYOI$??;EJcQAxQEUSHPvnwH) zUT&LR9f!8fu8u?N*SD#t=`y3zNt--QxxNA+zXWtZ33X2O+D@x5$9unC03ZTl@{a%$+_h`lzo}W$`cj5YC-DR|Cw+lS4co zUcAVx?%GziHovuI(Xv;!7Q4Nz=jRFOXIXFX!td`rW6`?aKOV!+XZ9HIvzYh?hxT7# z@wYg8M7**#Uc15s17sNEPafndSWJ)oY@`KkZXOb3BMV#Gl3?f8?zYrum!P$+d5c#+ ztjwbhgeGP4}}C#<)&*1;W?ZG#m{ zs~``S$WYE6HDJT8_XZ7eDIN1gQ2wK0g!RoAUwiGfmoh6szuP@`Zfkez4MGk#H#c|j zXGfOx%@M=qIwAMAkiEINb&n6@&FsR?g>2KRu6oE02(dI@x+*kppgz0!v#fV3L)f)w z{CeKv-rhDJUf;TK;o|m%E%Y>$V_UR$*0R32b>+&FPgV?WZ+-4_Pk!oCTmL!H*0!!P zh!U;TP_dPuhCB{53%O9S-n}{+?as|zwGxb95P7T)C@4ey0J{`(A>_#?pA@rwV++|( zmaPvUMCw|a@inw!uO|vwlPcg!*ociYKS!Yc{OY)-Y1DbM}w==(c?b@@< z%d?ElBkhA{R!Qo!G*6^!*M=48?$vPxS?Tq5DA7&@83Jo}ejbnD_LpL|!WbCqm6panBxtr}sb(3| zzmTzOSR1{{fofO(gXbGt<8zoXZ2MYjVS@2(>y_&l9y_^bhnk=RuWh~f>Z>ox5Mug^ zYZv(Y!*w{jwYSB8$jz{8r!`6AYDYTU*S5a)+Sgta1{egq&^_l32oT54WtOaaZc&6J zb{{`1-B+?jkA!)s-PYC)C$!;rzx&-U;G5CfnRLceN<5PdJG!ku^ZOSrT#)arS6Hac zmi?!e`R3MzH-G1oFWUi*58wUW&1<%2=V!Oh{fR=@3Yhm;yI$9uau)rE?l}+g^h@5u z7RAwN3hHnVsL%QSlb?O^;Dwj1XfkPSt}m|ix??v@E&31La~@>NpUrw*Z<<>4AG+r} z$d>UQWNv(O)6R`gzvN9*Js2AYK|4*Mq1;3FocHkbOWp(i>#IX!jjnEC&e^ht`H8LP zpMT-iSGT@SC3jyHtvOK*0g09A7&RWF4j&j^Ig>u{j%5e{1?H)k0%^qnxEruuW z;hgty!Jo~pbU#1O*~rpBYBx<;2^3-_&<87lK7hu}W>y@9*v^BlsJ(6Y+>2kkaabwQ z%~@dW9>Cf?fSY>&noMn+e{`<5Ed(@$RR4SRH}-byAEM`K){m|gsDEP~tD-zLugivU zSP*Y)F4*W}!65N3o%t_*cg-B)Roq#ghJm?WTr%_cchUTZ!B3dIVnIRn&VRqme}>L~ zDZN)bXPz@Vg^!@J`+0tvoXOjd%$cu{_-~ZuAAq}m(9WEm$AmwYGsd5GJ`ZG*!h2km z5ktf0Gw}M>^Gg6422X7d{OPVJ6!H(}j4J;O}DE#QyT%C^lUe6 zp^T1SFDU$s#hL?l$bTNEt|t*6Lpu(^Wu6C|UKrZe%_{3?(w{*~WQdTy8a0(_Af#6~{~@;2v!1U#DV^3`$^)ZKv{jnz>oh0-vRf_Br?>9(NV5v3 z?bAXXABFI)`WGnsmFrsy9~CbZe_-Q_W&~aOExZlR?Xe2#YuDc$0o;lLGG+r~;`~@j6-$0Ex>RrSOR2OVrzg_qY4s5MhGl_Bi zeq3C|H?nS;*AQPojOF{6fZxDBfPWRR=MkSXf911B@7)58i}63a*Y?{)>xu-S{!;WS z&WniF*4Eoi*6&$JbQNvCjeb6d)D828fqV~&w+dgooDck}Z^m4Z@D~mV+ zmGEl=vm@NK<^nxq&O&=V^g3Q8<>)l^c*Xyg z`8djBNjnut<^Nn|8(7RMk-dv{I0|YE+aFFGgt(RVVDA!y|EIkXAms0JmJ&k#!sRc_ zH*N3d2=IRv6>INy$F#{WNTNUXp;&(czvH#vXKagxqk!t~8uUmF&hvFCFT7#;h3fyF zm#;}#<8g(@^<5Z9cUZMu53T8XhQqs6x6Y@M_ThRd`)M=$AH@r{|H}x_gmaW8B}ag; zeFQh;&y`djFd4(5WQ7`!-|&9baZsnaeAmnd2aQt#?|c1!!Atim`b)3~&x2Owx*Pw& zMjRcKmfMVv;7$Wg?S$jcZ#tYmhZ@${>4vH1dNzdrrt9bLdi}!IYCdr^O^-~9%hkvk z?7lJ^FConw4O~BN0pe!paDT}4IDG@_SU;qKjJ&@n&Y1^=$PrqjlTE1O=MC_M55ep~ zinH)UEMcVfXQ;dV*}y@L1$#g~hC_rURU9PcW!bG<#VgNcCIT{`?@<#;fY3__aa>1l3v!o>nUQ49*@Bq z=5bAz!C7-w&>e={cMDqDs3IB`-!~GUH79JuE6R!BcHjwMl*Qe&&aYjhQg){#V&80k z+GV=^wdKVMlv3`Q$zfGn=9A{%Grwzo&%9iOHlXlNo6i(IU|utw!skH$*je~bUNp}n z>Dd$6AI1B_{j;`{to+;e_tG-$aITi%00!r@f8c5J(p31CtwuXN`N9v~%HNXXD`G3f zIn?~q*q2;DFW)QvuRUQaH(ZG{B!2ZD61VVT4;llJLiWs zxK`s0(=85)e_Xs}-YQc2x6uFKkMC;5ZTnNOtU$81_=l}Om?&mQ-uUijG(?1~X*Y02N^_TK5`1(u1ef_22ItoyKDY&oS6r9zU z6^5)^(yI;H2giU{asRs2m|moQ?fwU;Uprjt*A5r?+u_2WqXc^~XIi$i^FK)c_x*37 z|3>(y=TKyf|Xqm?-wJJc~_;zM`VI)}1!$ zmAw&-4aZ-F8TCf}(Z`Co>}|7`?Tz+|r>#_rljYvo-e%8Cl#||9O|PI^jmGzw+03$c zvr+c$MtJfCWUXhjhRR9qJl^9XKaR|u^H90aXHFc2+96^)Fj_4-Sgv;qmzz$JmT@@I zR2nbNB{mm$?O`}+hczMOmk9Lvk4)hofQ}ky9n=%88^kW^ABUyNxvDjl`$ie#2|twU zN(S#H9_>J|aA+>6QLhMHx0ZJ?hd5s>B0^IBui?TJ3#Em5mkn!GJsc2Ld?7)K-&`qf z&z||1fn(U#J5}F}k}#GW@5BlPP{4wCq4OTZ>lrRoJ58la|bT2oWiL zc$KbgfV}{{qDbIWS!#KDPhoPAB7D@gnpmq=w`v9R79o^YXo!~~JTbYDX?$Wrr%d<_ z!85k5+(vW6qD7HvKU`6Q<(TDWh`_*ALN+d?Gg%FFO-jrXRB+D>mY4m~T(Af?F~kAAcVAO#{3U ze!n0J?)XFxVW_93Uh=^pALJL`z4-1YvrlGtM-#P3)gwO|WP|J?Y=tqEv6bnc<2*{Hhx{RHhQ9P*df2Oum{@Xy~g~u`fe{r1!3oS{iHx6 z<6$uR_dQO+b9{8IcdZw<+cY}>qbl?;lR3#GCH6wK$jr|kMecFP2S7o|*LrLZwhL)6 zB1Ylh2w%?tFX6)8$^LwB6roji>M*57P5G=9k7U*o=N(=f&6=Z8r-)~K_Z+&Rpjv3z zjak$+GNQ0*;+27WcC*sF9e*OyhkShC>82V-kIjLjr5Sa~J_iKEjdw%R0V>A$v7!&F z)7T$9RfKr@)Ooj?1FNFyW}Q+Y)xCta5LdAVloEOEm8*G#S3T-zj#f0Zi@1H~%BtO~ zyV~_?*}^Z6%C*v%dOQm1I}!`!b$b%>Qwx__L`$7F@%YC!XV}2K8RzTyTFX5<=@nG}}Y*EI-d) zK30fL>?lHYUro2iXl@cBK2xq_I=AuOoZkdrMnOB*W7J5Ef6}WrO4qx&Z&@h9^7Ls? z`dVqXQ;vef;dCan$PSvi_PptPP*0p_UrMeT+u+>ApfrP63fx{OuJ(6$7Rj) zX_!~7*09WF&1Gpi^%&@ zF<-|s6NlxeSm%ci;%nAdDd(}v7u`+Qi}o{O9pC=M_Gp8t(Ch7I&gy*S=x82*z`lH3-dGm!S{8Vea^PmX(3ABu>z3j*Nk{&VCuSc(I zhT!?H>?(LRp6<+N@jEm6oq~0mg$KXQvG7s-zgs(+1Imr zTfH|LdT#Si&Y{=9{|$}OcL0Cq#P0I3`)u($R{EQTr}~?Prz7r6yh=VDE5!Qs>e+TB z-z+?pZx)`)Hw(`;2K86_cMmIPG4$#Achz2I`5m=A+WdrCo_MynpPBBT)cVfCQ~74$ zsa|H`)p8=?JbUWb^P!SneH-=2M{i%XzQR4st?~kbD=xdmC5i6hZJq6cy&T#PWBQEov**JQ-s1a;J zwPRORqjtZZYG)RnYG)RnvzqDtMAhOfek_-x(JVZz{j(GJw0rBlCywk_YkJR46cXN> z{z)DG1kcwW?W)c1=<9D9p0B@Yc)nhy;#H%d%nmyGrTmVvYn+9*WR@|~>ul4`k6&zN zn8oiD6~`XsRMx;nvW&byoNj$Odn)L=9$Lb&Q>rhl@U4QJu0US{EGeP`iu6lCBi$aNa& z^kj&hSM^U0?C59D7jirPaSY7*dF(INa@+a+#{PUkRk8Wg9}i6+e&So-F4|vItx(pX zZdG_lwTkby4GV#f@#dj(&#!>uGQLf)ZWeG~2WJff^Ja0`@Vhd7KyGu}RfRR+<9FZ9 zHhxs_F1|hIj8^1QJpWEUrdEY(L@3U}IaDS^4? zuoblaYFGaRWbWe76|eco({GI#>f1t)8^hhqHz; zXc&jpeXPacSnpDiqx~gbs*e%zLvi3BRJ>mHUH};5d4N{i#UK7TH6&4XA!OivcgK}; zySM@R4JCn;Vr`U|(QzBv`Z$Yo+S`D3HQqu$b-n(3;C2O(Bf%AL`(7>Ep|uL_rRP1V zUkA;wilGE=0P+mt&^(0*Znf0Z@eR~OqHm*4*R)h$2ThudN;*eF*?p;pD1~h^M1P$_ zX-)^@;+uuj4clFVYE<+Y$cFkb%6Lg?4N4<=AY20>d{)bH?ISb-6U%cnH*Nb!PxLKh zJ!l1K4J|8&wzN0+2X=h#Ut3%6q>&L`%bb0GP!NYDObZ-!hIq61rZ@WU+m;-{)+LT3 z2HHw~`*grZIfdMWm^5Rwtxv=8t&e`CR=OLPQ;-uRv%Cc=vhBi3rEOjNNIl({?QBQJ zrrkiR(uaiGQ*{q|y-*Qkba58vxCzD~q>|_)P)JiYflvn$V{Y0bIA#rkUkMpjIuI>% z3gN`-*nFZ8%^kW-n_@XTrW5Fzmf&>#IOd`-yiw`%DQs;+h;R2z2zB81AHg6EbN%tn zL9~ahVCCV$7$X~d5NPVm4ss-J*D1QHH8;MK(8pA`mh&`5k=r$QKS623sL(K%PZ}OU zI9A2+*F`(wT1BPey&dxFT%W(IviX|#(;Tn0f66_Ca`YJB&)>`NJI!2$=}6|rQO+we zBXIQdInl+-aftg5mgD-bE8Ulbv>V`cfM1KbVjjmz{g$_~tAC?BECNrI_iphf;$4*I zN}3~QJ*OPIagoV{<`c|Ad+eL?nn+IC#K zrFS{hW*Wj)Nkg8+9zPACO{XDZ9nx!PUF>7d4u-`iiZ$dh-lGqpQ0=e>xt$jGKivPa zKH;88*UGF<8bS%u5K5hfu$=U6ishssEGG?NIcW%MdkBqimHd7DxqTyb{YH`6O;I*# zQdfdEfF%}5+0qc^ry(pS4WZO&2&HC_lC@09L%Ttq+r@3HlQ@#;K3H1L^--pr)EsU6 zC=hZ^L!{+=iY7PKSVkb%U%6V>T}il3W=$Ct>Q<1HoV{B2Cc{E&N`0pxlspZgzS9ua zDFq>~wsI^9WIkOCi#zB)ZZ=bh?1nkFvHvhgeNwwAsZRJ&uE*{Rxqy+(tq zp3)HNEd`NZA5B5nv!r$6ossDJFtO8fI*4hU4q}MYK@5>{sDl{dv=c+93$`jpAYLWS zlmzVBFdBZp_%>!R4I*`FpCwYKAR=`NBC@3*!cBUf_9EH;#QS>JhlE>9gWm}MjSlnp z`q#07TPR8@Zt%ykOk`{erwhhmNt|&Ziwz%_-79u{u_O)K=1%!^p-$7w#jSFoP#EtV z{swkgykl;aH(`9cr4Lv6{X9QS&NLf8GH0PeqUmxW!8Hd#!%UT85`$louPA<8T80+^C?e5#3F1wUT zxXcX?A&3*heK$PeuKe32rH{-1Ynw|4txR-r{o>`XzZ@X{)sohwkuKX^X4ioe3r9UY zrnpqRS=K-#?pf;<8|PKG1&Z^pA>ZqWJO=tEVl3bP4*0k2f)(yP#GA!03#H{fTnM`* zx8i8~iTjlj+*6Rfr|8d^X9`8~>3N_L*N@8qj_-8J&LQP53&`U^`F`n-y|I3^eYAC6 zFWjP-^a7V9uul2R*YK~)5+%G}$}yVYDU5g0a|uovq^_ZT3tTaXHT201?M8aA^gUXV zkFaVvhc)DrI|PjmLrR~T?H7)|`lMP=sY9w+PY>!+WN9`F-)$n`j4yobeQUoslt9m7 ze5N?`!Bu(#7M$A=AZ|Z7JlQaBpyx2Y5sW^{YTdjJJ@XJHreB3)!FTV|J=P2xh42S= z^i`IlShf7HU5R1)$!O`8?{WLf0ZZIU?x0fmKZTW#jsPKlpR-s=XkSv72dF!HKcj0q zS5LQ9@SPM6F8X61iuETrps(AHPN;YkQ2p8Vv7=r1cHp>JE9F3@?;Rhz@@0+3Iov-4 z6FBL{_>*|Pa6QA(>tg>h4rezCm#-<;OVcjB2jw@RK}NQ5Gccc&909`i5!{eJd-{IB zWL(@tKV^PAe!F8F9{IR&K)-e1a^jQ}K)0gCg+f#6*fXa6SV!nNCdSbhJ%;hw50BtY z4g5Cgr-QX^Xc2zxvHoe@uLxdKdm*<+@TLZQ4L6~`TK)NDrP8uv2*;)nk7Lp*#v#rc z1Kd~bc%?WTQMp6cW2=6&e&BkXzJU#loJ!;0W#s+qavdv0ks_?WlT4`M=MBtj@F7%3 zOs9(u=Ke#kfXBUbGfAnwg`Lp>;`hpLmGSO@>$3&O&N^~lO3mE{PR_@%1CS%N)4)oF zvE5y|qm(AjuxRE{zC*inlWQnnYNZhKIAgWn@ELe$qEY9)2XxuBlgYq zCp_K$+VWxrN-6h&*@snOT?6?y3OHoT&;}fQx9kHGhiawIf&MYBneMjGbA8GlD!UWz zFSVUy<=?))mzHUVbG0NcZ;nyaK{Q+>e^$P4=;_g1 zcMts!{`js|+~!|UD{h0U6}Q3FO55O4K1V@~;T*zo+do*0{SQ)pJAHio7}IZ{Txxg2 zdRUtN0cpQ>|AMbS_7m4XMEkz}QgB~?DY%XT)L#nj>o*0b6tcn)yGXA#XdiO$|9WvK zf>&3mU%UT7>emjJ`nAJF{&u+V=O`g-4E6`#{~-O}_rHby8{wa#Km8jXKEe+Ts~cbM zv;n5$uu^J_A>s2<&bV=;*u%;Bf-%Hm39FO-q@UB+I)y1s-p|+ajWRA9N1TIO`K|KV zf{>-7)9;*r=h8c-@df(TqF>UTM&o8}EeWO5Mn*S;G*M$9r7l$C0`I za+M2x=EPB`9Zzfr^3{^d^_9})rc^ps}84-5{=%ogzl;U>Md~y8FhYfoJ)lTvsx9H}Pl(f`vnKN#*@AblqCsVpxiGOX@3K*flXgS-LV^^(xrw+v`(}fVVO#H1eK#hAu`F7Qv>0tH4yr%$ zP)(-zdC*B`LhYayMYv#cyu%g^pBz>^0*I78yh_(Lz+Qk}Q6_MzEVVqnr!cul5k6{L zO{`U`TeX4(i;$`Z@dgN&kFE?fUOu8zCfqEqqC_6RQ;*d1#)MP{2XV<`mODTM2CfnY z<4KIyxxcpE?{k5wvEN_Y?lkbgZ@=bKaqI7o%b4Hz$d+ngp{0y)d>%LY@$mjiAJMGj ztCN+yURV_|ztyAdef;*W?IJhs=NKdhgYErLC!{$zpdJ{8NA_kfO}%VPsJ}R(H$|ay zM9cK!pd6GNMTBPFJQ`rj&jaM0Y5@^lretseFFQAOIhnJdxEc-YQ|9w)&Y-@ntW`6c4a*xCQHc(LV%|6?M?P8Uf$9VVd51=`Yc;q6Z++c9;evGH(Dkib9wkTsf zYsIzIzVcP>n-u$%qPG#VFqv-K-=vj7T5y@qwqCY8*W_2ac8| z?@ZP>ARul;w)G-=z_^s=GyBa_GrN(KuEis}|83Jt z!!TMhXjcsWxU6|T3G=GeTD7y~tlz|Rez=W7XKJh)Cq6GtlY`Eyb_J>c?=I{6g9FZ| z;{A}nYJYTGonDg_wiLr8Ql*C}*oNN;_FZ37W{o6mkb8x3` zIiV@UW6jyd8nLUX=XBB{sSckcY0@$Ng{ zL@KU_zr#?v^j*9uY_l{x&Xu-OUqVwy((sR+?0!G&?UUbLSP>-hF3wnrOG$=UB8 zaIFjIc(Am;a&-Q^OYkQ(F+IGa&O0mp{?Vo4#{DMVPX88mluPC9VmO7LYK?au6rmk* zfBzvqMBR~b4bHy2fxgGRis!#7X#=zIbY~v=37h;@^T$ipX%-&*HpjwaxyO-@Wio8s z@ANx;e61*deDZj1U(f1a?vERKZu3uC?=AX{>TjmsvC`ixJk{SUJRNao;-S^47ac2T z2QGTHUCB2KPvx71r}E9hvyFiZ{B`tzAdJPk3#YK}-&K2=<#*KfX!8?ldE(iE{mt-C zYJF$nseH5WR4=peYB`Z`o;~(X_Dmlt>D8aZ&$^%g{Kh-<72d+!3jgf>-BK}CFS0%m zKZIWW@j&%53s3bj3s37i3y-4qh>i8#&eErd?+Wd~b{-)vi`kRL5>t!llH45tNPn%-&#y?vT zUFXcg>zLE|#c#F1muAge!S-c6Bn17EY&ct$ywCDG#_iF@FK&-E zytqBu@KnB8^@!W8jUVgkPFKY`sqMC4Pjikgy>}VsQacGhrs`#;f3lhlXW^+{X5ndl zXW_{jOGZJi(@57TItA-Y&#U?;x9#Z1@o$Z@W);`=V}G%h%TYDW@4@iF#)H)dod*jK z<{sd@jlWjI)PuqJ{`mfb4{!qcu6f(MWqxFSXns%>qoVu==FiPPH~&L{|2Yh?5FY&U z!4c!?(ZbQ(+x8y=7JOG=VbVG3-0yHrRk@A-)zbg8^ug^9-uPhn!N&cK`>RJCys}}# z&wlx*N2~XTA2#Fa{XaeW*)Io6*O%X08qGE1dw>kAt7ipYG_n79cQAfsoWE>do*W|H P%fFH5_P;ZZC&v7LcT~sB diff --git a/EGA.dat b/EGA.dat index 3a77e7eb390fc5099afe3b54ab82190049cb7bf0..e434868979705686d2169639b289316df7a134a3 100644 GIT binary patch delta 9934 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 ayps3$KS>Gi8(--3Hc1fn8{h2ouKr*4-zX#i literal 70594 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 a$JfWnE9RBS9>Q+&^(5i{+eYJwG5;3{owPOp diff --git a/LowRes.dat b/LowRes.dat index 60d836ac52d06644a4d622af8278c2baf8b7839c..67701f9c804083e1b8a3965be77a7401ee4ae51f 100644 GIT binary patch literal 21236 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�b1R=rnJ+5#$5rXc} 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 C`V>6? diff --git a/assets/CGA/mouse-link.png b/assets/CGA/mouse-link.png index ce3df9dc43903dd10f3c629a9c1165460bdfe3b1..2a0ea0be2095e1e95a68753e94cd4f50215fe4aa 100644 GIT binary patch delta 1651 zcmV-(28{X3E4eL@B!7*1R9JLUVRs;Ka&Km7Y-J#Hd2nSQWq4_3004N}?N?it<0uUM zXBD#qBm{!x@H}UBFw36{CQc=t?oK6DJwIcPdC0PK(Uovw`agdU^A|2A79(C`NFi8U zk|c4)B--ODt0ya~-wS)0+{n%OfFW`i-LXu(`(IFE@0uY%n*kacnc~40d~`2cw~D9 z^J8qTG5X2%%zw6anyfT1*TmSfyZkt3cj=*d%iSTr8VculL+eFO{!Z>rH++LAUOn?i zMAe@z>^-Yvv^Yjz%0{vT>^baBUAD4jP%JZA>$QNUxIN~&T0)C9+ltg_##U5R!6FfZ z<3U1=G7ai$tW*I}xU*tpuI!d9f{Vn}P0nRDLxa^N*MD3D{S~?5Am$SbEx&2Gs}zy< zjiAW{V>I*X(#*%jJM9NUW6YW%vhj=+aPiD&z?jU<9$FxT#*Qf%H7mq)2KwVQiFIJm zuP{e!FxzsND4wplVpw+9$a~EgR1Zhlzx)aXfg%{r4#!>CwEuo9`a@+zQh~fGiN4sU%{N2)ZH=nBX18_Yx84J z7k^``3e5_tbtpe5^q4v3`5FE6XkLBsHA6${oN(zuMyoP%J-`ppGAu%Y!+&YuB!6>! z+K}sGJP>}~(A$W9hvd&8dKSs|Bl;bZpFKCPZsp5jbpPBu3;p5qM+y1t)*kAeh+d`Y zX++Pm_AQ8>LiUT%yo=;NM)aAY{T@W`)PLI7Ao>J?KY!HzZwS9*@Dm6Bd(FSQ8St!~ zcD#fD00D(*LqkwWLqi~Na&Km7Y-Iodc$|HaJ4nMo9LB$6404M~cNqu^R_@Ud8RaB}%`+xD> zeIIw<0Ya_BRI_ayP&La)L?dE4vnqDH!jFD*VFG;;Gxb<6%3VlnmDAW8s!UVmle)ioPX6)g*EQUUl_=0D@m@?97YsNh#?LUGAbyd1PftW zHBw9@Xg}iNA9DP0a>?W>f{|kZWvGxGKlmT~?$*prO}a^e2+;mw+aDu9$1YH>+V=Oc zZP!l#|1)rS+rMWT{rv!expJ+wo9j;i01$h#R|Wn8 zBx5o;W;kPGWi2scIA$$0IW=J|VKruCEig1PFkv)gGcaOdW|Mgcjt@66FgZ0dH8nOe zIXF2pIkOfCivp9v3S1;NHDotqIW%Q0H!w6eEi^f0GA%e}W;rc0G&M0cGBY-0Vl!ov zLJLO_G%YYQEiy1vF*Z6iGCDIhvzZHC2^OmYeewVR00v@9M??Vs0RI60puMM)llv4N z3g`nA5Hut;7d-)!BNZrra7jc#R5;7Ul2Ho4APhs3vin~-`%t$IN^SjFKoB&|GYG`e zTw7UW763`846!)B6rK!d=qlWaa9AdgwEFP-r)2B4?gs-9$9{N8`opbAexD$zT_Rua xf!Pso;hz$qOELq)*zJ&IjtH{Plac?Z8u z6K71+ZqG7$vVQb)VK0*#x!E@uB8Sm!%fwT@LXJ7jb(&7-qJMuew0p{!g<~VeqxWn@V!~Y6dOwEf7L&#uSX25n|c{ef@M|9T*HF z%!&hMyKE+wKu2!5&U3H+fadL1d?a-mK#1Tw01P=WU?aIek*qKa5#p%eCj&V<&kLDC z6arVEWPk5m1UaYz_{KXzLze?@V%(k}BA_M()FhWdmiGbaBOX_6@;SKRLkKZQ(2zoo zF7^tGQDVP_vpDe*B#MYilB`a>28}AJnlvX)&fuC33H6vXrIa(AW?0U6J3}Mqg%`K@ zB`k4~MVGYXg7hiAgc6GsEve+n)tQPyuCYqhnty6uX+YrcgR zn>1~y<(;+3>Md($<{q;~lQkYhIfG7i)*u$Sn4o^0gw7clV^3gQIs-^(?wnaA@3}L# zplS@hJE)4lKp8qYl{#Z!(67Xy(azn$++E&`*SC1%Gjpa>_X*}qr|!huSKjWhR{O`G z41dN(6`B!L>rj4BST(ce{uzCFH1EFno}spU0o(Le%X|jUV?$5iI7agsJogN}gyR^^ zXYd?`-o)|gJOT{!0LNpLr(N^xwZ@mqK1L$}Dap(nEG!ST(9ZV_u<$bagGbeK0QLf^k3|BxQ$J^%mUhWu~nc|#3< zSd#e-(V*yThMb1V0004mX+uL$Nkc;*aB^>EX>4Tx0C=2zkv&MmKpe$iQ)@*k4t5Z6 z$WWauh>AE$D;B{*X)CnqU~=gfG-*guTpR`0f`cE6RRyJ6ykH@34<<3{K$3L#TiG{=!f}Tgh;p<|q_@99*t>dpXfw@o88yzij1PpEi7uOw4-UBXofZ-=yGCd?m^3xQG zMd1C6z9|n3-2(kT3B0I5-5xOO(Ct@$SCv-u^w)?(YY0;&P@2pZrz; z01uqAPX+!0BsMZJVK*^iF)cG< zOQ}E%^^<}TS$eo6ziJE(hd6Fda6JYh9~ZBCbwPJePaM(;JUq-u8@3>uu6=~ xP>JjmaLBJJz^;;!(rJ`4@+{{n{Gp$417FE90CXf+ga7~l07*qoM6N<$0fI__)J*^Y diff --git a/assets/CGA/mouse-select.png b/assets/CGA/mouse-select.png index 9e8a2b0d02addb1a372a5527f54d8366fc8b8301..f9e564781b74e55ebbd03c5352dc14217375e399 100644 GIT binary patch delta 1666 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 RV&MP)002ovPDHLkV1k77)>i-k diff --git a/assets/Default/mouse-link.png b/assets/Default/mouse-link.png index e474807658e2397155198e44c193b0c077186195..660a2f68df4f019313936a1cfe039a37f4bef077 100644 GIT binary patch literal 5607 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 G=KdS=!aIZj delta 562 zcmV-20?qy9EA9l4BYy#fX+uL$Nkc;*aB^>EX>4Tx04R}tkv&MmKpe$i(`rRp9PA+C z5U@H~5EXHhDi*;)X)CnqU~=h)(4-+rad8w}3l4rPRvlcNb#-tR1i=pwH#a9m7b)?7 zNufoI2gm(*ckglc4iFj@rka6qK-DZGorsIM{E8TSMF3%hFn^40iJ5vbvyg@7__~LW zuXiz?<$dnY5mpK&1AHR!EYl5(c%689)6zNb6GvECQi#uq#|*k4@gvt|m)|&-92R(H z#K@-Sh$F;ese|PXW@SSqo+6Gasz&)j&Si!37H73mW37Ag7lsPja)#?PhmgP`l1M>> zj2bpjfrS{Y8hV%r}h zKyVjm)@}Ry*tVM|fd3h|(%SxN1DN?Fz24TMM?l{;aBw3Zu9j|qgF|4nMA>T|?@aFL?Csw(t^R%hJIQj9AnJ-O z00006VoOIv00000008+zyMF)x010qNS#tmYE+YT{E+YYWr9XB6000McNliruSqgW(FLd zObav#K@zt6Y?|05HqTBkT=D~cz?)Y-EfcqOu34&N#8t{sPyhe`07*qoM6N<$f{`-x Ao&W#< diff --git a/assets/Default/mouse.png b/assets/Default/mouse.png index 2ae2cf1c3ade778dd5028ee0558ddf5bd2d29402..82decbdb6ff0a151165fd05247d4b02654b5463b 100644 GIT binary patch delta 1735 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 Ao&W#< diff --git a/assets/EGA/mouse.png b/assets/EGA/mouse.png index 2ae2cf1c3ade778dd5028ee0558ddf5bd2d29402..c99cdac41db242a64ffdb631b011565ca6091ffe 100644 GIT binary patch delta 1734 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 U_yK~zh5!Hn07*qoM6N<$g6n3|UjP6A diff --git a/assets/LowRes/mouse-select.png b/assets/LowRes/mouse-select.png index 7fb1bc7efde2691ea7343c59c00724649df39003..fb5bbff05e8d6a217db9bd78a00a207df3d2bad1 100644 GIT binary patch delta 1550 zcmV+p2J!jGD~>IYB!7{5R9JLUVRs;Ka&Km7Y-J#Hd2nSQWq4_3004N}?N?ip+$s$H zXBAljl6Z*a@Gw<(2Y2~7VA}3>W-{HG&R^QNO&P~`@SwjA!&R#TB|FvTPi(&P9g^%C_2oJZHys7R<UNYV!LW>M&DR1(#Lq*Hb0iG4*;bTJGq$3m3O0$* zd$yCKMwvQobFEYX(ecI+D|2O;It3Srs|?QF7=}7WNq^ErQ4JNfB2e>zg=X9|!&Oq$ zeIr;h!5NLZx(#!``Of&oQSYN?h^!vD0xljE4H$#DF~bUkP+vI(qh_X=To&wp(9Ep%e1wR?c(R5tM zR3h)W0e>YskpObg1&EDzB28QNysL40f{4+R5!58fAj>(A@`+9jYAAd0&U+tx^y-t( zLBi;;5MmhDaE=mP^fAO3HKv$V)T!5?QB{-X#7PXUX`j%KNmEKWBWQ-_jJGo^axPq? zxZ+DFv1mypSCmh6)z?sC)tYK<+{83AatkdsZGWldjW(3h<~F~DEpED{Eq9b!m+rdn zp~tR0^?b0lSbb#e%G~c+qs1CeqQqc|2WwErcymGHI`M-uFvgC+cpMC%pm}g+m7L?j zTtU|ue0R_lo`KR2avBZBz@XcReW!!FlerIhGfw}AH@-4w26gXX&J5}<%>ByS2duU6 zF@LC&vDt-YCe;es4+fiNR#^0Mii(fwE?(CMEAy$@i>vwA|La=w^g*6R^xF)4tu^1r zavtp~!>6#B_i*;LqnB8FOC_0S(Quw()eQ~Q{gAnMbnb#=^i7b=TM`7(ys6bbck~LH zzn`O5(fpkpee*iX=1qk?9DNtdt~GDEjv+oe+8=yE>}cBeTu1*L{d06*X1(E`Ve=C? z$pdkr)pf3u-vb$cJ4nMo9LB$6404M~cN zqu^R_@Ud8RaB}%`|;g6%3VlnmDAW8s!UV zmle)ioYhi=HSWn@7|3cXNv_izMifhkAr28TDk!1^3t?I{QcNUhKjPsZa{O^}$>b`6 zkz)a6sE{0gKlmT~?$*prO}a^e2+;mw+aDu9$1YH>+V=OcZP!l#|1)r$S>rVgx5PNB}bp`zbBsgO-FfceYG%YzWG&n6Z zVP-ZhI5jviEn#LjIbmWjHDNS3Vw08#jt@66FgZ0dH#a#rGB-FkHM27bivpA13S1;K zIWsV3G&wRYV_`F9Ei^D?GA%hdVqz^eH#9h5W-?`CH85k7UkgVNG%YYQEiy1vF*Z6i zHaammv$hLf2^QW>D5n4b00v@9M??Vs00000GbeoWlPVP+3g`nA5I7SdAJ+ntQWYox zL6dtGDl4EEph$^HgTo8da{m7TOGxA;qW}N^07*qoM6N<$g4s35 Al>h($ delta 1513 zcmVnIHU z*D87mNDPAIFqm`R4tn{0!NeIVRpli8%rS!^q>HYEL)*W9I{kx3#2Rv5V@M$wJTAE; z8Ix%Cr_7#>oBdjt%j8Z@#}5LL#c20sk|p0D$DG$XEhl`@zke7yJY~%M^@p67yxhyn zGAQxU&_ZB&_iWnQunbvQh}^!e3sFc>?~c~$^6ZuO*!)os>_P$MJa@hr(IjlonjxuLZEF)TTTR>C19&?>7p+%ETMd~y%6%|#mNG#yE zv*X5@J8yHWTmjL^CW=v{ik-3uE|OF*&0rdhxQp6kn}3Z+gNj_S5X*sur{6T)RgTE} z#?TaoF`l`!8r?2FXWkwt$_{l&n)=6Qi z%-%}_N`G5#9!?peET2vYhisA9azCL)lyJy!XMYk3RVv zY#0?5f`)MoXSM3oYf#mwNprN(MIS@d7-Nb#NwS1%+9%XwiYcX>;WWc?#_kM_oQo7& zT=69oEwQAME7GUB>T9T4V@)+TX_guaxrL@Jwtv*}#v4*;bDQ77rd!<7mOE0d+wQvW zp=*yl^?b0lSiNWMk-48)6og*TDc1S_Wq(vU4(+ zE2tX6cL!DB1t|R>r}1D647!cQcRskgGWV1>a`7$RfdYA_uFpNPe|#c=U+{c{2q%_ z#^`$=ui>KK!*~uSo$`4e(Hlq}MtX_xAAY6NdHgG)cT(|L9-ZEF8qtTy=;ckP5xt3w z-agK0L@y$vj|2U85Pf*54Jo~LS^RKAzCz;pC$#i=DxSTMo*;ULjLvg{@BnbD259DyCwP$^wddxcr_Z#lfDBPe?3UUKp2MK zrbE= zM<*vm7b)?(q|hS9JC1vJ?|WbFz5|4MnW<*SIG}2lk&4H}Y;HviyrKsonqwG}n5iey z3mJHhuY36Tei!9g-gSSDe_kbTGQcMi&obSxh}VgyH!Yp>K5>|pB!&2#c+{W^5WjyOy#7TZ{EV^%U$;wj>YqH2^cWL;J`Z*f-3Ro1*Ge_=4MEvLCo zYY=fPB7r1C$f#liWmt&Pu90FQMdvXO|A6C9l1nDn1{gW!QGp7{fANF=!SCLhg~pURz9|Rv-2&aKZr_^cIDG&z)T^Z%;NTD#DN^>D$Gf}Q`}S{5vwuIp z8gj<3bS`QD01upLvs4BB0wgzOGh{e7VK6N?GBYwQG-75pEjTeYVl6o^V=^^mI5;+B zW@M9f2#yanHaImjGBh+eGcz|bI5V>n35x==d<<3z76jodG5`Po24YJ`L;(K){{a7> zy{D6t=oKCc<_ixL05BemNO+SF7AOHZlQ|YDBa9kAjcBB1LLx6Ij{*h&B63)=o!mYJ P00000NkvXXu0mjfM0vX4 diff --git a/assets/LowRes/mouse.png b/assets/LowRes/mouse.png index 562900d8c08e0dc8abcb8fa482e82986b8b993d6..e9264166d6b70e0003472331d693fd0f514b53f9 100644 GIT binary patch literal 5585 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 1) { for (int n = 1; n < argc; n++) @@ -60,9 +60,21 @@ void App::Run(int argc, char* argv[]) targetURL = argv[n]; break; } + else if (!stricmp(argv[n], "-noimages")) + { + config.loadImages = false; + } } } + if (config.loadImages) + { + ImageDecoder::Allocate(); + } + + ui.Init(); + pageRenderer.Init(); + if (targetURL) { OpenURL(targetURL); @@ -76,80 +88,80 @@ void App::Run(int argc, char* argv[]) { Platform::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(); } - static char buffer[256]; - - size_t bytesRead = loadTask.GetContent(buffer, 256); + size_t bytesRead = pageLoadTask.GetContent(loadBuffer, APP_LOAD_BUFFER_SIZE); if (bytesRead) { - if (loadTask.debugDumpFile) + if (pageLoadTask.debugDumpFile) { - fwrite(buffer, 1, bytesRead, loadTask.debugDumpFile); + fwrite(loadBuffer, 1, bytesRead, pageLoadTask.debugDumpFile); } - if (loadTaskTargetNode == page.GetRootNode()) - { - parser.Parse(buffer, bytesRead); - } - else if(loadTaskTargetNode) - { - bool stillProcessing = loadTaskTargetNode->Handler().ParseContent(loadTaskTargetNode, buffer, bytesRead); - if (!stillProcessing) - { - loadTask.Stop(); - } - } + 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"); 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 + else if (!parser.IsFinished()) { - bool isStillConnecting = loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting; - - if(!isStillConnecting) + 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) { - if (loadTaskTargetNode) - { - loadTaskTargetNode = page.ProcessNextLoadTask(loadTaskTargetNode, loadTask); - } + pageContentLoadTask.Stop(); } } } - if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) - ui.SetStatusMessage(loadTask.request->GetStatusString()); + else if(!pageContentLoadTask.IsBusy()) + { + if (loadTaskTargetNode) + { + loadTaskTargetNode = page.ProcessNextLoadTask(loadTaskTargetNode, pageContentLoadTask); + } + } + //if (loadTask.type == LoadTask::RemoteFile && loadTask.request && loadTask.request->GetStatus() == HTTPRequest::Connecting) + // ui.SetStatusMessage(loadTask.request->GetStatusString()); + page.layout.Update(); pageRenderer.Update(); ui.Update(); } @@ -246,6 +258,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) @@ -294,7 +312,7 @@ size_t LoadTask::GetContent(char* buffer, size_t count) void App::RequestNewPage(const char* url) { StopLoad(); - loadTask.Load(url); + pageLoadTask.Load(url); requestedNewPage = true; loadTaskTargetNode = nullptr; //ui.UpdateAddressBar(loadTask.url); @@ -314,18 +332,19 @@ void App::OpenURL(const char* url) pageHistoryPos--; } - pageHistory[pageHistoryPos] = loadTask.url; + pageHistory[pageHistoryPos] = pageLoadTask.url; pageHistorySize = pageHistoryPos + 1; } void App::StopLoad() { - loadTask.Stop(); + pageLoadTask.Stop(); + pageContentLoadTask.Stop(); } void App::ShowErrorPage(const char* message) { - loadTask.Stop(); + StopLoad(); ResetPage(); // page.BreakLine(2); @@ -370,10 +389,10 @@ void App::ShowNoHTTPSPage() //page.FinishSection(); page.SetTitle("HTTPS unsupported"); - page.pageURL = loadTask.GetURL(); + page.pageURL = pageLoadTask.GetURL(); ui.UpdateAddressBar(page.pageURL); - loadTask.Stop(); + StopLoad(); } void App::PreviousPage() @@ -394,6 +413,12 @@ void App::NextPage() } } +void App::LoadImageNodeContent(Node* node) +{ + loadTaskTargetNode = node; + node->Handler().LoadContent(node, pageContentLoadTask); +} + void VideoDriver::InvertVideoOutput() { if (drawSurface->bpp == 1) @@ -413,3 +438,4 @@ void VideoDriver::InvertVideoOutput() Platform::input->ShowMouse(); } } + diff --git a/src/App.h b/src/App.h index bfc20bc..6e6137c 100644 --- a/src/App.h +++ b/src/App.h @@ -23,6 +23,7 @@ #include "Render.h" #define MAX_PAGE_URL_HISTORY 5 +#define APP_LOAD_BUFFER_SIZE 256 class HTTPRequest; @@ -33,6 +34,7 @@ struct LoadTask void Load(const char* url); void Stop(); bool HasContent(); + bool IsBusy(); size_t GetContent(char* buffer, size_t count); const char* GetURL(); @@ -56,6 +58,11 @@ struct LoadTask struct Widget; +struct AppConfig +{ + bool loadImages : 1; +}; + class App { public: @@ -79,6 +86,12 @@ class App PageRenderer pageRenderer; HTMLParser parser; AppInterface ui; + AppConfig config; + + LoadTask pageLoadTask; + LoadTask pageContentLoadTask; + + void LoadImageNodeContent(Node* node); private: void ResetPage(); @@ -87,7 +100,6 @@ class App void ShowNoHTTPSPage(); bool requestedNewPage; - LoadTask loadTask; Node* loadTaskTargetNode; bool running; @@ -97,6 +109,7 @@ class App static App* app; + char loadBuffer[APP_LOAD_BUFFER_SIZE]; }; diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp index 56a193a..719ddc2 100644 --- a/src/DOS/BIOSVid.cpp +++ b/src/DOS/BIOSVid.cpp @@ -30,6 +30,7 @@ BIOSVideoDriver::BIOSVideoDriver() { + startingScreenMode = -1; } void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) @@ -91,6 +92,11 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) 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); @@ -122,6 +128,11 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) } } + if (!drawSurface || !drawSurface->lines) + { + Platform::FatalError("Could not allocate memory for draw surface"); + } + if (videoModeInfo->vramPage3) { // 4 page interlaced memory layout @@ -174,7 +185,10 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) void BIOSVideoDriver::Shutdown() { - SetScreenMode(startingScreenMode); + if (startingScreenMode != -1) + { + SetScreenMode(startingScreenMode); + } } int BIOSVideoDriver::GetScreenMode() diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index 87ae90f..db5f85b 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -66,6 +66,35 @@ 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; + 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; + return (outreg.x.bx > 0); +} + + void DOSInputDriver::HideMouse() { if (!hasMouse) diff --git a/src/DOS/DOSInput.h b/src/DOS/DOSInput.h index 7f7afa0..b4a9bf1 100644 --- a/src/DOS/DOSInput.h +++ b/src/DOS/DOSInput.h @@ -28,6 +28,9 @@ 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: diff --git a/src/DOS/DOSNet.cpp b/src/DOS/DOSNet.cpp index a7359ad..e9dcbf6 100644 --- a/src/DOS/DOSNet.cpp +++ b/src/DOS/DOSNet.cpp @@ -53,21 +53,29 @@ 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 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"); + } } isConnected = true; @@ -133,13 +141,13 @@ NetworkTCPSocket* DOSNetworkDriver::CreateSocket() { for (int n = 0; n < MAX_CONCURRENT_HTTP_REQUESTS; n++) { - if (sockets[n].GetSock() == NULL) + if (sockets[n]->GetSock() == NULL) { TcpSocket* sock = TcpSocketMgr::getSocket(); if (sock) { - sockets[n].SetSock(sock); - return &sockets[n]; + sockets[n]->SetSock(sock); + return sockets[n]; } return NULL; } diff --git a/src/DOS/DOSNet.h b/src/DOS/DOSNet.h index 1aba884..210784e 100644 --- a/src/DOS/DOSNet.h +++ b/src/DOS/DOSNet.h @@ -24,7 +24,7 @@ #define MAX_CONCURRENT_HTTP_REQUESTS 1 #else #define TCP_RECV_BUFFER_SIZE (16384) -#define MAX_CONCURRENT_HTTP_REQUESTS 1 +#define MAX_CONCURRENT_HTTP_REQUESTS 2 #endif struct TcpSocket; @@ -68,7 +68,7 @@ class DOSNetworkDriver : public NetworkDriver private: HTTPRequest* requests[MAX_CONCURRENT_HTTP_REQUESTS]; - DOSTCPSocket sockets[MAX_CONCURRENT_HTTP_REQUESTS]; + DOSTCPSocket* sockets[MAX_CONCURRENT_HTTP_REQUESTS]; bool isConnected; }; diff --git a/src/DOS/EMS.cpp b/src/DOS/EMS.cpp index a6d84a2..3c205d2 100644 --- a/src/DOS/EMS.cpp +++ b/src/DOS/EMS.cpp @@ -2,23 +2,35 @@ #include #include #include +#include #define EMS_INTERRUPT_NUMBER 0x67 void EMSManager::Init() { union REGS inregs, outregs; - - //return; + struct SREGS sregs; // Check for EMS driver - inregs.h.ah = 0x40; - int86(EMS_INTERRUPT_NUMBER, &inregs, &outregs); - if (outregs.h.ah) { + 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); diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index faccf1e..e608fb2 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -13,6 +13,7 @@ // #include +#include #include "../Platform.h" #include "Hercules.h" #include "../VidModes.h" @@ -26,12 +27,9 @@ #include "../Draw/Surface.h" #include "../Memory/Memory.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 @@ -159,7 +157,6 @@ static int AutoDetectVideoMode() bool Platform::Init(int argc, char* argv[]) { bool inverse = false; - video = NULL; for (int n = 1; n < argc; n++) { @@ -169,6 +166,15 @@ bool Platform::Init(int argc, char* argv[]) } } + network = new DOSNetworkDriver(); + if (network) + { + network->Init(); + } + else FatalError("Could not create network driver"); + + MemoryManager::pageBlockAllocator.Init(); + int suggestedMode = AutoDetectVideoMode(); VideoModeInfo* videoMode = ShowVideoModePicker(suggestedMode); if (!videoMode) @@ -185,22 +191,31 @@ bool Platform::Init(int argc, char* argv[]) video = new BIOSVideoDriver(); } + if (!video) + { + FatalError("Could not create video driver"); + } + if (videoMode->biosVideoMode == HP95LX || videoMode->biosVideoMode == CGAPalmtop) { inverse = true; } - network->Init(); video->Init(videoMode); video->drawSurface->Clear(); - input->Init(); - MemoryManager::pageBlockAllocator.Init(); if (inverse) { video->InvertVideoOutput(); } + input = new DOSInputDriver(); + if (input) + { + input->Init(); + } + else FatalError("Could not create input driver"); + return true; } @@ -219,3 +234,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/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index 4240c92..c1273c7 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -285,6 +285,8 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* // Set bit mask outp(GC_INDEX, GC_BITMASK); + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + while (*text) { unsigned char c = (unsigned char) *text++; @@ -295,16 +297,24 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* } int index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; if (glyphWidth == 0) { continue; } - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); + glyphWidth += bold; + + if (x + glyphWidth > context.clipRight) + { + break; + } + + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; - glyphData += (firstLine * font->glyphWidthBytes); + glyphData += (firstLine * glyphWidthBytes); int outY = y; uint8_t* VRAMptr = lines[y] + (x >> 3); @@ -319,10 +329,26 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* writeOffset++; } - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + 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)); @@ -338,11 +364,6 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* } x += glyphWidth; - - if (x >= context.clipRight) - { - break; - } } if ((style & FontStyle::Underline) && y - firstLine + font->glyphHeight - 1 < context.clipBottom) diff --git a/src/DataPack.cpp b/src/DataPack.cpp index b31cfb7..e39d508 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -2,6 +2,7 @@ #include #include #include "DataPack.h" +#include "Platform.h" #include DataPack Assets; @@ -16,18 +17,19 @@ const char* DataPack::datapackFilenames[] = "LOWRES.DAT" }; -bool DataPack::LoadPreset(DataPack::Preset preset, bool forceBold) +bool DataPack::LoadPreset(DataPack::Preset preset) { - return Load(datapackFilenames[preset], forceBold); + return Load(datapackFilenames[preset]); } -bool DataPack::Load(const char* path, bool forceBold) +bool DataPack::Load(const char* path) { FILE* fs = fopen(path, "rb"); if (!fs) { + Platform::FatalError("Could not load %s", path); return false; } @@ -35,6 +37,13 @@ bool DataPack::Load(const char* path, bool forceBold) 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"); @@ -43,32 +52,12 @@ bool DataPack::Load(const char* path, bool forceBold) imageIcon = LoadImageAsset(fs, header, "IIMG"); - boldFonts[0] = (Font*)LoadAsset(fs, header, "FHELV1B"); - boldFonts[1] = (Font*)LoadAsset(fs, header, "FHELV2B"); - boldFonts[2] = (Font*)LoadAsset(fs, header, "FHELV3B"); - boldMonoFonts[0] = (Font*)LoadAsset(fs, header, "FCOUR1B"); - boldMonoFonts[1] = (Font*)LoadAsset(fs, header, "FCOUR2B"); - boldMonoFonts[2] = (Font*)LoadAsset(fs, header, "FCOUR3B"); - - if (forceBold) - { - fonts[0] = boldFonts[0]; - fonts[1] = boldFonts[1]; - fonts[2] = boldFonts[2]; - monoFonts[0] = boldMonoFonts[0]; - monoFonts[1] = boldMonoFonts[1]; - monoFonts[2] = boldMonoFonts[2]; - } - else - { - 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"); - } - + 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); @@ -95,25 +84,11 @@ Font* DataPack::GetFont(int fontSize, FontStyle::Type fontStyle) { if (fontStyle & FontStyle::Monospace) { - if (fontStyle & FontStyle::Bold) - { - return boldMonoFonts[FontSizeToIndex(fontSize)]; - } - else - { - return monoFonts[FontSizeToIndex(fontSize)]; - } + return monoFonts[FontSizeToIndex(fontSize)]; } else { - if (fontStyle & FontStyle::Bold) - { - return boldFonts[FontSizeToIndex(fontSize)]; - } - else - { - return fonts[FontSizeToIndex(fontSize)]; - } + return fonts[FontSizeToIndex(fontSize)]; } } @@ -137,6 +112,10 @@ Image* DataPack::LoadImageAsset(FILE* fs, DataPackHeader& header, const char* en 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)); //image->data = ((uint8_t*) asset) + sizeof(ImageMetadata); return image; @@ -169,6 +148,10 @@ void* DataPack::LoadAsset(FILE* fs, DataPackHeader& header, const char* entryNam { buffer = malloc(length); } + if (!buffer) + { + Platform::FatalError("Could not allocate memory for data pack asset %s", entryName); + } fread(buffer, length, 1, fs); diff --git a/src/DataPack.h b/src/DataPack.h index ce8754e..afb32b7 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -66,12 +66,10 @@ struct DataPack Image* imageIcon; Font* fonts[NUM_FONT_SIZES]; - Font* boldFonts[NUM_FONT_SIZES]; Font* monoFonts[NUM_FONT_SIZES]; - Font* boldMonoFonts[NUM_FONT_SIZES]; - bool LoadPreset(Preset preset, bool forceBold = false); - bool Load(const char* path, bool forceBold = false); + bool LoadPreset(Preset preset); + bool Load(const char* path); Font* GetFont(int fontSize, FontStyle::Type fontStyle); MouseCursorData* GetMouseCursorData(MouseCursor::Type type); diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 57b94f3..fcf1b8c 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -260,6 +260,8 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* y += firstLine; } + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + while (*text) { unsigned char c = (unsigned char) *text++; @@ -270,21 +272,24 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* } int index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; + 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->glyphDataStride * index); + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; - glyphData += (firstLine * font->glyphWidthBytes); + glyphData += (firstLine * glyphWidthBytes); int outY = y; uint8_t* VRAMptr = lines[y] + (x >> 3); @@ -300,10 +305,26 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* writeOffset++; } - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + 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)); } @@ -323,10 +344,26 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* writeOffset++; } - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + 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)); } diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp index 58c27ea..96b2be5 100644 --- a/src/Draw/Surf2bpp.cpp +++ b/src/Draw/Surf2bpp.cpp @@ -87,9 +87,9 @@ void DrawSurface_2BPP::VLine(DrawContext& context, int x, int y, int count, uint { return; } - if (y + count >= context.clipBottom) + if (y + count > context.clipBottom) { - count = context.clipBottom - 1 - y; + count = context.clipBottom - y; } if (count <= 0) { @@ -217,7 +217,8 @@ void DrawSurface_2BPP::DrawString(DrawContext& context, Font* font, const char* } int index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; + uint8_t glyphWidth = font->glyphs[index].width; + uint8_t glyphWidthBytes = (glyphWidth + 7) >> 3; if (glyphWidth == 0) { @@ -229,9 +230,9 @@ void DrawSurface_2BPP::DrawString(DrawContext& context, Font* font, const char* break; } - uint8_t* glyphData = font->glyphData + (font->glyphDataStride * index); + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; - glyphData += (firstLine * font->glyphWidthBytes); + glyphData += (firstLine * glyphWidthBytes); int outY = y; @@ -247,7 +248,7 @@ void DrawSurface_2BPP::DrawString(DrawContext& context, Font* font, const char* writeOffset++; } - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + for (uint8_t i = 0; i < glyphWidthBytes; i++) { uint8_t glyphPixels = *glyphData++; diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index 0705420..7691761 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -155,6 +155,8 @@ void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* y += firstLine; } + uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + while (*text) { unsigned char c = (unsigned char) *text++; @@ -165,21 +167,24 @@ void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* } int index = c - 32; - uint8_t glyphWidth = font->glyphWidth[index]; + 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->glyphDataStride * index); + uint8_t* glyphData = font->glyphData + font->glyphs[index].offset; - glyphData += (firstLine * font->glyphWidthBytes); + glyphData += (firstLine * glyphWidthBytes); int outY = y; uint8_t* VRAMptr = lines[y] + x; @@ -191,10 +196,26 @@ void DrawSurface_8BPP::DrawString(DrawContext& context, Font* font, const char* VRAMptr++; } - for (uint8_t i = 0; i < font->glyphWidthBytes; i++) + 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)) diff --git a/src/Font.cpp b/src/Font.cpp index 97514b1..48e3463 100644 --- a/src/Font.cpp +++ b/src/Font.cpp @@ -18,6 +18,8 @@ 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++; @@ -28,7 +30,11 @@ int Font::CalculateWidth(const char* text, FontStyle::Type style) continue; } - result += glyphWidth[index]; + uint8_t width = glyphs[index].width; + if (width) + { + result += width + bold; + } } return result; @@ -41,6 +47,12 @@ int Font::GetGlyphWidth(char c, FontStyle::Type style) { return 0; } - int result = glyphWidth[index]; + 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 266c9a3..8c360ca 100644 --- a/src/Font.h +++ b/src/Font.h @@ -19,6 +19,7 @@ #define FIRST_FONT_GLYPH 32 #define LAST_FONT_GLYPH 255 +#define NUM_GLYPH_ENTRIES (LAST_FONT_GLYPH + 1 - FIRST_FONT_GLYPH) struct FontStyle { @@ -34,10 +35,16 @@ struct FontStyle struct Font { - uint8_t glyphWidth[LAST_FONT_GLYPH + 1 - FIRST_FONT_GLYPH]; - 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[1]; int CalculateWidth(const char* text, FontStyle::Type style = FontStyle::Regular); diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 10053e9..4a3cc78 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -1,8 +1,12 @@ #include #include "Gif.h" #include "Decoder.h" +#include "../Platform.h" -union +#include +#include + +typedef union { char gif[sizeof(GifDecoder)]; //char png[sizeof(PngDecoder)]; @@ -10,6 +14,8 @@ union char buffer[1]; } ImageDecoderUnion; +static ImageDecoderUnion* imageDecoderUnion = nullptr; + #define COLDITH(x) ((x * 4) - 32) const int8_t ImageDecoder::colourDitherMatrix[16] = @@ -44,12 +50,28 @@ const uint8_t ImageDecoder::greyDitherMatrix[256] = 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); + return (ImageDecoder*)(imageDecoderUnion->buffer); } ImageDecoder* ImageDecoder::Create(DecoderType type) { - return new (ImageDecoderUnion.buffer) GifDecoder(); + return new (imageDecoderUnion->buffer) GifDecoder(); } diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index a0378f1..8f22bef 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -25,10 +25,11 @@ class ImageDecoder }; ImageDecoder() : outputImage(NULL) {} - virtual void Begin(Image* image) = 0; + virtual void Begin(Image* image, bool dimensionsOnly) = 0; virtual void Process(uint8_t* data, size_t dataLength) = 0; virtual State GetState() = 0; + static void Allocate(); static ImageDecoder* Get(); static ImageDecoder* Create(DecoderType type); diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 227a149..df672b0 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -24,8 +24,9 @@ GifDecoder::GifDecoder() { } -void GifDecoder::Begin(Image* image) +void GifDecoder::Begin(Image* image, bool dimensionsOnly) { + onlyDownloadDimensions = dimensionsOnly; state = ImageDecoder::Decoding; outputImage = image; outputImage->bpp = Platform::video->drawSurface->bpp == 1 ? 1 : 8; @@ -74,6 +75,12 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) outputImage->width = width; outputImage->height = height; + + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; + } } if (outputImage->bpp == 1) @@ -377,6 +384,13 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) 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 @@ -403,8 +417,7 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) { if (stackSize >= 1024) { - DEBUG_MESSAGE("Stack overflow!"); - exit(-1); + Platform::FatalError("Stack overflow in GIF decoding"); } stack[stackSize++] = dictionary[code].byte; diff --git a/src/Image/Gif.h b/src/Image/Gif.h index 402a02c..0e9830e 100644 --- a/src/Image/Gif.h +++ b/src/Image/Gif.h @@ -15,7 +15,7 @@ class GifDecoder : public ImageDecoder public: GifDecoder(); - virtual void Begin(Image* image); + virtual void Begin(Image* image, bool onlyDimensions); virtual void Process(uint8_t* data, size_t dataLength); virtual ImageDecoder::State GetState() { return state; } @@ -81,8 +81,8 @@ class GifDecoder : public ImageDecoder struct DictionaryEntry { uint8_t byte; - int32_t prev; - int len; + int16_t prev; + uint16_t len; }; struct GraphicControlExtension @@ -95,6 +95,7 @@ class GifDecoder : public ImageDecoder ImageDecoder::State state; InternalState internalState; + bool onlyDownloadDimensions; uint8_t palette[256*3]; // RGB values uint8_t paletteLUT[256]; // GIF palette colour to video mode palette colour diff --git a/src/Image/Image.h b/src/Image/Image.h index 1657e67..df52974 100644 --- a/src/Image/Image.h +++ b/src/Image/Image.h @@ -18,10 +18,8 @@ struct Image : ImageMetadata Image() { width = height = pitch = bpp = 0; - isLoaded = false; } MemBlockHandle lines; - bool isLoaded; }; #endif diff --git a/src/Interface.cpp b/src/Interface.cpp index 210e752..015ce4d 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -79,17 +79,22 @@ void AppInterface::Update() 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(mouseX, mouseY); + HandleDrag(mouseX, mouseY); } - else if ((buttons & 1) && (oldButtons & 1) && (mouseX != oldMouseX || mouseY != oldMouseY)) + + if (Platform::input->GetMouseButtonRelease(clickX, clickY)) { - HandleDrag(mouseX, mouseY); + HandleRelease(clickX, clickY); } oldMouseX = mouseX; @@ -196,6 +201,10 @@ void AppInterface::Update() case KEYCODE_F6: FocusNode(addressBarNode); break; + case KEYCODE_F5: + app.page.layout.RecalculateLayout(); + //FocusNode(addressBarNode); + break; case KEYCODE_F3: ToggleStatusAndTitleBar(); break; diff --git a/src/Layout.cpp b/src/Layout.cpp index db770e7..a5f531e 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -3,6 +3,7 @@ #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) @@ -16,14 +17,96 @@ void Layout::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) + { + if (currentNodeToProcess->type == Node::Image && App::Get().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().BeginLayoutContext(*this, currentNodeToProcess); + 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 (currentNodeToProcess->next) + { + currentNodeToProcess = currentNodeToProcess->next; + break; + } + } + } + } + } + +// if (lastNodeContext && !tableDepth && !lineStartNode) +// { +// page.GetApp().pageRenderer.MarkNodeLayoutComplete(lastNodeContext); +// } + } + + if (!isFinished && App::Get().parser.IsFinished() && !currentNodeToProcess) + { + // Layout has finished so now we can load image content + //RecalculateLayout(); + page.GetApp().pageRenderer.MarkPageLayoutComplete(); + App::Get().LoadImageNodeContent(page.GetRootNode()); + isFinished = true; + } + + //if (!currentNodeToProcess) + //{ + // page.GetApp().pageRenderer.MarkNodeLayoutComplete(page.GetRootNode()); + // page.GetApp().pageRenderer.MarkPageLayoutComplete(); + //} +} + void Layout::BreakNewLine() { // Recenter items if required @@ -49,14 +132,9 @@ void Layout::BreakNewLine() if (lastNodeContext && !tableDepth) { - page.GetApp().pageRenderer.MarkNodeLayoutComplete(lastNodeContext); + //page.GetApp().pageRenderer.MarkNodeLayoutComplete(lastNodeContext); } - //if (lineStartNode && lineStartNode->parent) - //{ - // lineStartNode->parent->OnChildLayoutChanged(); - //} - Cursor().x = GetParams().marginLeft; Cursor().y += currentLineHeight; currentLineHeight = 0; @@ -106,17 +184,19 @@ void Layout::TranslateNodes(Node* start, Node* end, int deltaX, int deltaY) void Layout::OnNodeEmitted(Node* node) { - if (!lineStartNode) + if (!currentNodeToProcess) { - lineStartNode = node; + currentNodeToProcess = page.GetRootNode(); } - node->Handler().GenerateLayout(*this, node); + lastNodeToProcess = node; - //if (node->parent) - //{ - // node->parent->OnChildLayoutChanged(); - //} +// if (!lineStartNode) +// { +// lineStartNode = node; +// } +// +// node->Handler().GenerateLayout(*this, node); } void Layout::PadHorizontal(int left, int right) @@ -230,3 +310,8 @@ void Layout::RecalculateLayout() //page.DebugDumpNodeGraph(page.GetRootNode()); #endif } + +void Layout::MarkParsingComplete() +{ + lastNodeToProcess = nullptr; +} diff --git a/src/Layout.h b/src/Layout.h index 5ba655d..7294344 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -18,6 +18,7 @@ class Layout Layout(Page& page); void Reset(); + void Update(); Page& page; @@ -31,6 +32,7 @@ class Layout void RestrictHorizontal(int maxWidth); void OnNodeEmitted(Node* node); + void MarkParsingComplete(); void ProgressCursor(Node* nodeContext, int width, int lineHeight); void RecalculateLayout(); @@ -46,9 +48,14 @@ class Layout 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(); @@ -64,6 +71,8 @@ class Layout Stack paramStack; void TranslateNodes(Node* start, Node* end, int deltaX, int deltaY); + + bool isFinished; }; /* diff --git a/src/Memory/LinAlloc.h b/src/Memory/LinAlloc.h index 94ca022..b42eed2 100644 --- a/src/Memory/LinAlloc.h +++ b/src/Memory/LinAlloc.h @@ -73,6 +73,12 @@ class LinearAllocator : public Allocator return NULL; } + if (!currentChunk) + { + errorFlag = Error_OutOfMemory; + return nullptr; + } + uint8_t* result = ¤tChunk->data[allocOffset]; if (allocOffset + numBytes > CHUNK_DATA_SIZE) diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index 0d79f05..7776c8d 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -2,6 +2,7 @@ #include "MemBlock.h" #include "LinAlloc.h" #include "Memory.h" +#include "../Platform.h" #ifdef __DOS__ #include "../DOS/EMS.h" @@ -26,8 +27,8 @@ void* MemBlockHandle::GetPtr() } #endif default: - printf("Invalid pointer type: %d\n", type); - exit(1); + + Platform::FatalError("Invalid pointer type: %d\n", type); return nullptr; } } diff --git a/src/Microweb.cpp b/src/Microweb.cpp index 6d596a5..17ac2bf 100644 --- a/src/Microweb.cpp +++ b/src/Microweb.cpp @@ -29,8 +29,7 @@ int main(int argc, char* argv[]) if (!app) { - Platform::Shutdown(); - fprintf(stderr, "Not enough memory\n"); + Platform::FatalError("Error allocating memory for application"); return 0; } diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index f795af0..cd6e3ad 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -67,7 +67,7 @@ Coord ButtonNode::CalculateSize(Node* node) if (data->buttonText) { - labelWidth = font->CalculateWidth(data->buttonText); + labelWidth = font->CalculateWidth(data->buttonText, node->style.fontStyle); } Coord result; diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 899ba85..e132697 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -13,7 +13,7 @@ void ImageNode::Draw(DrawContext& context, Node* node) //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); uint8_t outlineColour = Platform::video->colourScheme.textColour; - if (data->image.isLoaded && data->image.lines.IsAllocated()) + if (data->state == ImageNode::FinishedDownloadingContent) { context.surface->BlitImage(context, &data->image, node->anchor.x, node->anchor.y); } @@ -41,84 +41,106 @@ Node* ImageNode::Construct(Allocator& allocator) void ImageNode::GenerateLayout(Layout& layout, Node* node) { ImageNode::Data* data = static_cast(node->data); - int imageWidth = node->size.x; - int imageHeight = node->size.y; - if (!data->image.isLoaded && imageWidth > layout.MaxAvailableWidth()) + if (!data->AreDimensionsLocked() && data->image.width > layout.MaxAvailableWidth()) { - node->size.x = layout.MaxAvailableWidth(); - node->size.y = ((long)node->size.y * node->size.x) / imageWidth; - imageWidth = node->size.x; - imageHeight = node->size.y; - data->image.width = imageWidth; - data->image.height = imageHeight; + int imageWidth = data->image.width; + data->image.width = layout.MaxAvailableWidth(); + data->image.height = ((long)data->image.height * data->image.width) / imageWidth; } - if (layout.AvailableWidth() < 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(imageHeight); - layout.ProgressCursor(node, imageWidth, imageHeight); + 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::Get().config.loadImages) + { + return; + } + ImageNode::Data* data = static_cast(node->data); - if (data && data->source && !data->image.lines.IsAllocated()) + if (data && data->state != ImageNode::ErrorDownloading && data->state != ImageNode::FinishedDownloadingContent) { - loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); - ImageDecoder::Create(ImageDecoder::Gif); - ImageDecoder::Get()->Begin(&data->image); + if (data->HasDimensions() && !App::Get().page.layout.IsFinished()) + { + return; + } + + if (!data->source) + { + data->state = ImageNode::ErrorDownloading; + } + else + { + bool loadDimensionsOnly = !data->HasDimensions(); + if (!loadDimensionsOnly && App::Get().pageLoadTask.HasContent()) + return; + loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); + ImageDecoder::Create(ImageDecoder::Gif); + ImageDecoder::Get()->Begin(&data->image, loadDimensionsOnly); + data->state = loadDimensionsOnly ? ImageNode::DownloadingDimensions : ImageNode::DownloadingContent; + } } } bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) { + ImageNode::Data* data = static_cast(node->data); ImageDecoder* decoder = ImageDecoder::Get(); decoder->Process((uint8_t*) buffer, count); if (decoder->GetState() == ImageDecoder::Success) { - ImageNode::Data* data = static_cast(node->data); - data->image.isLoaded = true; - - App::Get().pageRenderer.MarkNodeDirty(node); + 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 - bool needsLayoutRefresh = false; 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)) + if (otherData->source && !strcmp(data->source, otherData->source) && n != node) { - if (n != node) + if (!otherData->HasDimensions() || (otherData->image.width == data->image.width && otherData->image.height == data->image.height)) { otherData->image = data->image; - App::Get().pageRenderer.MarkNodeDirty(n); - } + otherData->state = data->state; - if (n->size.x != data->image.width || n->size.y != data->image.height) - { - n->size.x = data->image.width; - n->size.y = data->image.height; - needsLayoutRefresh = true; + if (data->state == ImageNode::FinishedDownloadingContent) + { + App::Get().pageRenderer.MarkNodeDirty(n); + } } } } } - - if (needsLayoutRefresh) + } + else if (decoder->GetState() != ImageDecoder::Decoding) + { + data->state = ImageNode::ErrorDownloading; + if (!data->HasDimensions()) { - node->size.x = data->image.width; - node->size.y = data->image.height; - App::Get().page.layout.RecalculateLayout(); + data->image.width = data->image.height = 1; } - //printf("Success!\n"); } return decoder->GetState() == ImageDecoder::Decoding; diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index b068aff..3094064 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -7,12 +7,25 @@ class ImageNode: public NodeHandler { public: + enum State + { + WaitingToDownload, + DownloadingDimensions, + FinishedDownloadingDimensions, + DownloadingContent, + FinishedDownloadingContent, + ErrorDownloading + }; + class Data { public: - Data() : source(nullptr) {} + Data() : source(nullptr), state(WaitingToDownload) {} + bool HasDimensions() { return image.width > 0; } + bool AreDimensionsLocked() { return state == DownloadingContent || state == FinishedDownloadingContent; } Image image; const char* source; + State state; }; static Node* Construct(Allocator& allocator); diff --git a/src/Parser.cpp b/src/Parser.cpp index 54915aa..ebec75b 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -81,7 +81,7 @@ void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) contextStack.Top().parseSection = data->type; } - node->Handler().BeginLayoutContext(page.layout, node); + //node->Handler().BeginLayoutContext(page.layout, node); } void HTMLParser::PopContext(const HTMLTagHandler* tag) @@ -113,7 +113,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) contextStack.Pop(); contextStackSize--; - parseContext.node->Handler().EndLayoutContext(page.layout, parseContext.node); + //parseContext.node->Handler().EndLayoutContext(page.layout, parseContext.node); if (parseContext.tag == tag) { @@ -123,18 +123,23 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) #ifdef _WIN32 page.DebugDumpNodeGraph(); #endif + Finish(); //page.GetApp().pageRenderer.RefreshAll(); - page.GetApp().pageRenderer.MarkPageLayoutComplete(); - - page.GetApp().StopLoad(); + //page.GetApp().pageRenderer.MarkPageLayoutComplete(); } return; } } - // TODO: ERROR - exit(1); + Platform::FatalError("Error popping context in HTML parser"); +} + +void HTMLParser::Finish() +{ + parseState = ParseFinished; + page.layout.MarkParsingComplete(); + page.GetApp().StopLoad(); } #define NUM_AMPERSAND_ESCAPE_SEQUENCES (sizeof(ampersandEscapeSequences) / (2 * sizeof(const char*))) @@ -909,3 +914,4 @@ uint8_t HTMLParser::ParseColourCode(const char* colourCode) } return 0; } + diff --git a/src/Parser.h b/src/Parser.h index 496c1d8..e0c7484 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -72,7 +72,6 @@ class HTMLParser Page& page; - void PushContext(Node* node, const HTMLTagHandler* tag); void PopContext(const HTMLTagHandler* tag); HTMLParseContext& CurrentContext() { return contextStack.Top(); } @@ -91,6 +90,9 @@ class HTMLParser static void ReplaceAmpersandEscapeSequences(char* buffer, bool replaceNonBreakingSpace = true); + void Finish(); + bool IsFinished() { return parseState == ParseFinished; } + private: void ParseChar(char c); @@ -107,7 +109,8 @@ class HTMLParser ParsePossibleTag, ParseTag, ParseAmpersandEscape, - ParseComment + ParseComment, + ParseFinished }; ParseState parseState; diff --git a/src/Platform.h b/src/Platform.h index 029ce58..70db2a0 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -89,6 +89,10 @@ 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; } }; @@ -99,6 +103,8 @@ class Platform static void Shutdown(); static void Update(); + static void FatalError(const char* message, ...); + static VideoDriver* video; static NetworkDriver* network; static InputDriver* input; diff --git a/src/Render.cpp b/src/Render.cpp index d3a02eb..6ec5878 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -442,6 +442,12 @@ void PageRenderer::MarkNodeDirty(Node* dirtyNode) void PageRenderer::MarkPageLayoutComplete() { Node* node = lastCompleteNode; + + if (!lastCompleteNode) + { + node = app.page.GetRootNode(); + } + while (node) { Node* next = node->GetNextInTree(); diff --git a/src/Stack.h b/src/Stack.h index 3079a1c..baabb3b 100644 --- a/src/Stack.h +++ b/src/Stack.h @@ -43,7 +43,8 @@ class Stack } else { - // TODO: handle allocation error + // TODO: handle allocation error more gracefully + Platform::FatalError("Could not push to stack: out of memory"); } } } diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index b44e4fb..589947d 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -13,6 +13,7 @@ // #include +#include #include "../Platform.h" #include "WinVid.h" #include "WinInput.h" @@ -127,3 +128,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 2710d18..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; 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.h b/src/Windows/WinNet.h index 379bc54..055a7b5 100644 --- a/src/Windows/WinNet.h +++ b/src/Windows/WinNet.h @@ -4,7 +4,7 @@ #include #include "../Platform.h" -#define MAX_CONCURRENT_REQUESTS 1 +#define MAX_CONCURRENT_REQUESTS 2 class WindowsNetworkDriver : public NetworkDriver { diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 42742f6..042ce02 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -56,12 +56,12 @@ void GenerateAssetPack(const char* name) 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("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); @@ -70,12 +70,12 @@ void GenerateAssetPack(const char* name) 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("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); diff --git a/tools/FontGen.cpp b/tools/FontGen.cpp index f5625a2..f25d6e3 100644 --- a/tools/FontGen.cpp +++ b/tools/FontGen.cpp @@ -459,6 +459,7 @@ void EncodeFont(const char* basePath, const char* imageFilename, vector //} + /* int requiredBytes = 0; for (int n = 0; n < glyphWidths.size(); n++) @@ -474,42 +475,26 @@ void EncodeFont(const char* basePath, const char* imageFilename, vector requiredBytes = needed; } } + */ - // Output the metadata - // uint8_t glyphWidth[256 - 32]; - // uint8_t glyphWidthBytes; - // uint8_t glyphHeight; - // uint8_t glyphDataStride; - - const int numNeededGlyphs = 256 - 32; - - for (int n = 0; n < numNeededGlyphs; n++) - { - if (n < glyphWidths.size()) - { - outputStream.push_back(glyphWidths[n]); - } - else - { - outputStream.push_back(0); - } - } - - outputStream.push_back(requiredBytes); - outputStream.push_back(glyphHeight); - - int stride = requiredBytes * glyphHeight; - outputStream.push_back((uint8_t) stride); // 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; - for (int b = 0; b < requiredBytes; b++) + int glyphWidthBytes = (glyphWidth + 7) / 8; + + for (int b = 0; b < glyphWidthBytes; b++) { int mask = 0; @@ -527,9 +512,47 @@ void EncodeFont(const char* basePath, const char* imageFilename, vector x++; } - outputStream.push_back((uint8_t)mask); + outputData.push_back((uint8_t)mask); + count++; } } } -} \ No newline at end of file + + // 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/MouseGen.cpp b/tools/MouseGen.cpp index 3b06422..bf65c4f 100644 --- a/tools/MouseGen.cpp +++ b/tools/MouseGen.cpp @@ -192,6 +192,8 @@ void EncodeCursor(const char* basePath, const char* imageFilename, vector data; unsigned width, height; unsigned error = lodepng::decode(data, width, height, imageFilePath); + int hotSpotX = 0; + int hotSpotY = 0; if (error) { @@ -213,7 +215,14 @@ void EncodeCursor(const char* basePath, const char* imageFilename, vector 0 && data[index + 1] == 0) + { + hotSpotX = x; + hotSpotY = y; + } + if (data[index + 3] == 0) { @@ -223,7 +232,7 @@ void EncodeCursor(const char* basePath, const char* imageFilename, vector> 8)); outputData.push_back((uint8_t)(hotSpotY & 0xff)); From be057fc05b449af81ab71981158d4161dd681bb9 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Mar 2024 13:10:26 +0000 Subject: [PATCH 54/98] Added support for list items. Added image / broken image icons. Added error pages. --- CGA.dat | Bin 20772 -> 20835 bytes Default.dat | Bin 37933 -> 38012 bytes EGA.dat | Bin 29666 -> 29745 bytes LowRes.dat | Bin 21236 -> 21295 bytes assets/CGA/broken-image-icon.png | Bin 0 -> 8394 bytes assets/Default/broken-image-icon.png | Bin 0 -> 9712 bytes assets/EGA/broken-image-icon.png | Bin 0 -> 9710 bytes assets/LowRes/broken-image-icon.png | Bin 0 -> 8944 bytes assets/LowRes/bullet.png | Bin 0 -> 6192 bytes project/DOS/Makefile | 6 +- project/Windows/Windows.vcxproj | 2 + src/App.cpp | 55 +++++++--------- src/DataPack.cpp | 15 ++++- src/DataPack.h | 2 + src/Draw/Surf1bpp.cpp | 1 + src/Draw/Surface.h | 17 +++++ src/Interface.cpp | 1 + src/Node.cpp | 13 +++- src/Node.h | 5 ++ src/Nodes/ImgNode.cpp | 92 ++++++++++++++++++++++++--- src/Nodes/ImgNode.h | 8 ++- src/Nodes/LinkNode.cpp | 14 ++++ src/Nodes/ListItem.cpp | 81 +++++++++++++++++++++++ src/Nodes/ListItem.h | 37 +++++++++++ src/Page.cpp | 4 +- src/Parser.cpp | 29 ++++++--- src/Parser.h | 1 + src/Render.cpp | 1 + src/Tags.cpp | 19 +++--- src/URL.h | 8 +++ tools/AssetGen.cpp | 4 ++ 31 files changed, 351 insertions(+), 64 deletions(-) create mode 100644 assets/CGA/broken-image-icon.png create mode 100644 assets/Default/broken-image-icon.png create mode 100644 assets/EGA/broken-image-icon.png create mode 100644 assets/LowRes/broken-image-icon.png create mode 100644 assets/LowRes/bullet.png create mode 100644 src/Nodes/ListItem.cpp create mode 100644 src/Nodes/ListItem.h diff --git a/CGA.dat b/CGA.dat index e661ceed76411b57c714d525c564fb2aa041af26..106aaefb31fc6771c326e94a887caf1407e49a1c 100644 GIT binary patch delta 207 zcmZ3oi1G0v#(G``HxE~zFhhoa^B5Q)Y$Jw$i#ecdV}^fcgn?{l|Ii?ynyt!EHc-tp zZ73V4#@vR1!P(b8G}x8lUqk?q?c?d^&G7Hv91uIiH6jGaegI^9diuHpng3J*fo!KB ze{UIAKL#5xC)CHsHH0Aq$aeK}0jc?yys>#_AaQhCeL~3@v~D005|gKR*Bf delta 143 zcmaF7h;hjx#(HiBHxE~zFhhoaZ43+$wh_a>ZVo8hnBm_RVIbStKQsuaX09@n4OCO7 z4P^t>$lEY5IQ#mC2D>u+a|i&keLVfV8UFoi0kK0|BSL`e6F|17r>{GZ`Hv@%fx*?! Ph2b9ykfXA3_QC)F64ox@ diff --git a/Default.dat b/Default.dat index 8be5ac90a7e5ece54336f313025066d67330d1fa..97ba800d326511ec155b2a74489240b894626802 100644 GIT binary patch delta 223 zcmZ3xg6Yo+rg~lmHxE~zFhhoa^B5Q)Y$Jw$5BQ*LV}^epm4Iw#|Ii?y8X-q08>mLu zAIb)**^ux^d<;wsj0_A0^#Sz>{tW&P>VU8& jAjKdd!Qp`8g6ae|1~vw6pbAEYKYv<)o?vMC^XCr$7BxvN delta 143 zcmeyff@$pvrh0A$HxE~zFhhoaZ43+$wh_a>6MRs%F~h%`Nr^D zACwJLGbfjU!P(b8G}x8lpY3EI+sD(-o8jNTMi4v1H6jGaJ_=-ediuHpng2MaFfh3K QxiI`=0djOV&R#Vc01I<6qyPW_ diff --git a/EGA.dat b/EGA.dat index e434868979705686d2169639b289316df7a134a3..e1b4b49dd42003e1f8a9b25fab24e5a53d688b93 100644 GIT binary patch delta 205 zcmaF#oN?m|#(G``HxE~zFhhoa^B5Q)Y$Jw$hCEQVF~h%S(m=Mee`pX;jk+u+<0=NSeLVfV8UFqA1F=I~BSL`eNkF!zr>{GZ`R^Kt?G)ti zE$iyX@C(cd_3?2HVUR0fU~u(w0jc?CShBGtu6XjkVsV9l6oZ5WhXalasuS24*ciAO S7=iH5pBA8#8Cw4Q`2zsRfkNm2 delta 144 zcmdn^g7MLF#(HiBHxE~zFhhoaZ43+$wh_ZWNggQMnBm_!X&~F#KQsuahTj;<2C6Br zgR+5Y1j85@oPGU6gIyW^eJTR7eLVfV8UFn?$DJ)6>@-$o#ht$aeK} QVfe=iq(D z6v_sw$#Y_0aQ5{N4R&Strym4l`*`|!GyMBk3}T14MuY&_+ktFPPhWQ+^WSd}+bPK3 zTh`T&fhQQm3H9-D4PlT4vR(aLKx+Q!2XAaC2vX)?;ACI~!73l$>av_D16Bquu)vQW M9Y7~{{P^($0PJ8sod5s; delta 144 zcmZ3#jPc7-#(HiBHxE~zFhhoaZ43+$wh_a>=iE@XF~dI*Ng&(VKQsua=87hi4OC-l z3S|S;ggG%VIQ#mC2D>u+6AuEieLVfV8UFo?2C+k2BSL`e`9QX(r>{GZ`R_51?ds>k P@Q)S9`4Y5oPC*a=Q+F_1 diff --git a/assets/CGA/broken-image-icon.png b/assets/CGA/broken-image-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c3e3bdceb88710a51c76e01bd8be00ef476264c0 GIT binary patch literal 8394 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 PCK&*wXR2GG?GpN5?dsax literal 0 HcmV?d00001 diff --git a/assets/Default/broken-image-icon.png b/assets/Default/broken-image-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2fedd963ac76cd6a71ca74593e9b3ce3217e6b88 GIT binary patch literal 9712 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 =NY<*z@`Q~|W%cf>)(^xY$^VVaT z?bm!Gm)Lpd-!v%?mw(Kw_VFGTpsn0wZd71XUbGrL<=f2bKfG9D)a2mz!X^X0mBy_fd&LWqH@kgv&nP%!$BE^g=MNO)+(le>xC>+% z`Yo-7`*ZnKgn7wI?&$hMt<5LBv4<*zE*&V#KQ)EsOiwA?^=M6+Qb@XqVQg9-{6pz}3q$q@~AQX~^?f z@y=a`qHcd{$`mpwMEIehDn5DUSSb_DvF&)xnj05ZVD&tvuM6w>EWb!*B$>2eUKIdo zno&w0Q}px8kJzHa#BN!IOFk=^$zkBf8_4yTIJrb(j)^GTBMrX2;61eV7H_Ds_Rh(5 z-x@ErKK7JgLj+W?Y-rN$$%=e^Co>N$Go4?v}r?Fx1T73FFX1a1KYumuPrL!M<%$Eh_^24q@ zDX$#lnPFm`=FS?VG)+mcZz>VWI4k-hl$~wc7UNu)bKkr-y>s$b)r_E%zt%J?qTinn zFJ}u@6(^O-us8zma__u7c%za7Sg;Ok_rs1#wKoLEjYvOwnk1R=ie0H9kyY-l@JRBT z^~%<1>6|A%?aLpLs5r-HhKVr8B=f2~PVO;N2U#+9%+!&U!M?^^ZW5|A4AmJPh`PQ# z07)2hWi^Qu8Rxi}$zMY%9A=^)Qo_CDVtRe*gKIGQv&eh@2g9LS%GDft2TaVjDjvNy z?yh!f;orS{+wr-z+w3Bj<7n?q!|UdOaXKao6O+aY#Gw2_|HkZ=rXx>kze*+Y)eOC< zZ*CXi1M@ad4SI*JH~MKagjp2d(e3ZS&Aj$W4N|XJkZey*)YLy2u_c)@#atG{tx z&OVBHd`7X~D6QWAI37tyVzn(8JWE1&Tt3({m}!S7i{fK4o7Vvk9FrdcR&_!febGp# z(4%L0@{ir>I$zdFE(;Id1_c+hl>|hcyb7i@hmh;4-r>ZKHMKmu`nB+5-)}2|m0;JE zm}dfXVjm80M|Xv$IXV|4Fjm~Vc8`cC zib%TA*uP-I4Q-s)(I-XbUE6plij18s;%gF%IVcdEZ=Ka6Td9nxMR301_^^_4%Gi90 zh4~PT`dLDo#1r$R+ibXJ8N>I)&rcrUG+leHFy1Gx;I4IZ+vTmMt-_g#1G!RivCbRG z_W5PA2|ku5xrU-Zojb}_#ZiYJX)#kK(2tXpjaU!gp^VSEO#s$*{GMWoW*9xI|?r=3b8lc0eZP zHeW#81f#Xy{)GA!a~JS~?x^Q+6#=|0vaHK#ck&iDI4#QT8~v~3x$Ab3gD;=t^?b8t zA7n99nQR^K&GiN%&Bs`yGdrhzf6ScRj>VJbD2A9(^g~cIB0_%n0;}V8L|bVZ-Ne)M z@zaO#Y0yqkU68Hrc+1FlA7!su_252H^z9V3q&VVtpPLJYQ*p1VhGxBF4%ENwdQkVODaxnhP*N#H)vww5uU zt?@~>CxkL!mJFc(hagVngcv)t~t}1)?MiMMkl7BX(4)1?X~`gi@r6C{AWhdFCwN8 zhMsq&c%BHIIC^wZ@sR#I&`X7YeN(*e1$r0-tC&k;pyybhN*QV27QUIXe)~cA47o0W z>ns;5FMrDAuOMYH3Fv-qYlLyV%#~x+oFPlM!%-&J4QK$!wUGk)fY_~ZO#8lw@}>R; z@fFkb>EP9bj}bLRU&ao`NcS#zGWu7X=p+s^=(0^k*M18^Ot=V|KH-MsS&2fG+8IF2 zP0@5YXf;w-A<3SkMxOP*7>MgaMH+ft2s1w^{KRP6j2~M$+7aTqFe8r>3>NHdk5><^ zY|SfpWozBUyrW%n{tL(1>8r65&-)FZA*mn8@+jT_Gke{?EI2C3@-d)=TJ-_@<{VZ+r2h+HKBi8R1Js z_k7b6tBZ!+6%m;S%C{ngU86sAtf{Vv)v3xjsICv%z5|Mb7CqoC59YuRL?>?>_O1_X zawG{Gw!YR{SLTPOyqLZ?H8zP1T>P%REnl78aLG8_<@+tNU$yEDeWB?pipAAGCS z2RWXL=LulD;^yyinolSuOV%$Xon2A)1(K5wet=$0lg*heerWR`LGeu}D zUJib=AF;?{Ze8ii2!pTsl{l^y9|7kmr)-dJ=*kif+Ucl0TaP4?9})fL+N8$fo;I9;55yo)ID(Y%?vUz7XvyC+;ZhZG`W-kD~nCa%nl9S)pK zt4JtUCp}p?Rf5~Bn^Ar(V3Ba~>2S%)ED2(xQX;_}{M1z9OXR`(azt@;P#=_mopFEy zbcyi~$Hg>C#m5F#O<|EYR#C>i4grY#yU#9ZUbyukS(Ci@N(sv)6j<6*HfN zZ0rfE3RC)f0%A2)??-sg@t_57l=}O2>3_<7EGG37d3F(v3Vg(N!RI)SqZ{J@q{&FQ zoLBP-Pe0etjbf%7!yT!m6c$uaH0*=c`yw_MGTZ&t{zAOeQs5HEy{_a-(&*CdIR4`e zrw=+@&`K(JS4-bw+qClyDch}HC!)R%emS6@8S#njQklaLikUC^T|XhYV*K<=C^Pb% zqr~U8E9tM?{4U8kLf-B(+d1rKLr3;5S_1Z-I5h)RoPkhVYD&d}p2q z8c$JHZt$%M@o;4w;m-}nPh3x(uPKbcW&|5QKg@rUb;lv+?L12etX$+y&bJc1Y5|S5 zW^}9{F*WSanYe4BPE|2rU3RgvI6~&}lecf(k-dMNtfH$UxlG|`V$>{R?TypfdC0sv z{W!MqJSKCd%{ILC8?mS1VRntQuxq@8)HU-L^HYR@=-O6&gHHi3(O&ubZ1C`O3CM`0 zk@tlI+8gZ+g%=v!?=H2L2Wt3c*lGq!8Gn3!s3XU#cWi=7k>}p|Q>FA%qHB*;Tj%+T zmz9C2(!{89))5bj#+T{huSY42 z8r_|$$^r|K&(YnD*KxrrCzDy1zs0D$ez{q@D=fag)71!$Tr{h;6KSvVWJI$po}PWASDYuoxlB>A z%Mxh5f|!J&+-Z`%``nM4959RL5@h<8e^m8DGtv0crTqh?wI6Gzj}NgbEPbvR^Ljo| zb~Kna(yk=4E2zU=RON}4_@+Fj;0*FDVgbYQD3`(&SU|gA>9%v-sVl1*+x5tv*Vv~n zfSJJOBsAI(AgWnl7U~|%^3v#m&ex+xebd~nD(f9p#~LT-$JEe{muZ+B-d!6t5s@0d zml~Q~Gk7z&$U6b2i~po$ia*uaTm0UdB~Re$@WU*$Xqpx zqw4hJR`=z}yQ?58&^5A|8gyBE43JdjzJoxA79|RT!s3jN?<7xe>yh$oq=RKdgJPp4 zk4$ijze|jauNl4I0DbVlxClpbYe^9!;lFZ~*1Yp`8WMjUxm~Dy8E&Jz-Rx`jr}|&V~>YammdwSIj>@jmpv*LpN(Nvr+thJWM=NuYQ9uo zh%sC~cWEdF=afE~1mba;x>{1il4G#Xw9stjZEl>Y<=02AKwpMfZs45|n+uqj+h<_5 zl#}NYAYV5DBusC1|BGB!2G~-cd9_YwRQ;^uVq@F^ofG!NxFbP|=?_vXt`v?$tzDx` znpXzJw%vbr^TM#V3o`%i6{|dc-gjj|y6$VA7n2SrOK)dY6D7N1hkLbO*mJ%G*X52y z%TI8I!A>j+n?HBqopOFQP;}Kh-_N0;L;9SK;{;|#Bf;7$A>}{^cgIYVPD!24p}cS> zBMsFx3^JAr{Gt$4_*O_b&izo847#BG*g^NYe!iz?H?E)!ne{`>q?C3_b_?L zD`|oj7v$0wlv7T0C_6qD&rB<3a=VRDRlh)=CGbhk*X6HYW*-yUqSIbAK0K+FX#`z( zP+@PrR?8^0a^rkmA2YM#y%+th(PkOW_jD(=kfXsn1M7aCcFVRZ>*ZIL^HWJS->0{@ z1(k18u;TaRx%y0B{FaB4bKoCjuATT`2mKZ#oK(`2kwjk@%+ef146F|OHr83G_TXCp z##XQ5%4p6i-RuxE#Q=3>tA0X&!86nTP2@{HMwr&u_O(vh*wt!1(d${0LA2KI2YU~F zh$Pa`(1s8-HH|Qun!lflQP09s0%K3=)~WM1RzC`7KN0SA$i2xZReBxGpBb+G)To~+ z50zx~{i--CCj4gl=modWg%+M}?(e&6%n$7s0=2Q7370V$--$!tFHx{`ft-l17GxNG`;sTSknLiva_h!mTDneiGRx<% zdXLiz(&Mb-cMSBWDh>Gw^cZ1P2|>iCtM$=ViPo1sqYwrH-Dlk=Y4oi(JPIpj~I9#dKvoJQeY zU1YEXR~#VY28L|s2J zVCr|q4DaWJS0aGbRGE~0P*egJfPw}2xHywMP(CW)Jzf;`dUseB4BCTGoK(OT21X!F zS26%X${=N+5N#i#7aXk01X3mw>`^9Kr+!jU?^M8!6p9;4R@U3wTgDq9<4Sgrg()d1 z$wJ|>a5#jDfOzMUN{ zhdTJv!S4MYy$6LLi=o~isq3KvWZ_V#A_NMDz?EcwwWrP+82n*P^7yGDRZm$TteY%M z1}f{~@)rvainiCE{{E$fhZ*&>Sk?saaP=hP0c|gUL=pMbshhK>$FDv;J%HVzy||qT z_OjHV_B{V;qk}Op`om^dMhBvc+n&WP`d1_Y{|C;^lkB{QA>d^JXTXIThzFG!_7^;b zX#Zz|{<5Ckng0<8)!iTbe?kAQ*Iq1px}vmP@t(U*FZctGwKDh$bkf+gXBT`DTM43Wx10OX(qIRFU( z02l&-#L6o|6bbS;2o4I9Q?N%sDFI3_2vR{2 zPyK=ss8j#}qJ)qK034J6MJi$UsCM&!(lElPfZ;OGKSzw5u@rk(vJ17nh$Momx5uAT zW<(djl!D!r4W`uoPFanX9X_3V1hK&@Sa(Q-PF!whQXC zD}K*#4-61?JML%Cp|K9Kd$-E6{|@{=m`ojAy-ELfJbyy}V9_8`yj{sohGawBMF3Cv z_dNd!{0EZ>b+_}NkbN=#%cA}pPWeZ{>QZf8$-ck%HwD~(-2K>+oQZo$$`#AAm6ta>W7Jve9JOqFt0SHnaP=YAJpg0H| z4@1E15d9H=!w%`>&R_VNrm{gcKgiK)6LD92$26; z2EVt({{i=l{m&Ns-^_o7{jk<_b@QceQjQcuZ_>YY|1W?)7)}%M0LjDk--Z4wkJ z87LS7ZH7<#j?&QX=W{3trViO+v^30)w}%*8vb};b(tX$}m{aYID9-y>hYY^Tj?>bj zv8&?KJr$f|ovQ1%Z3)q)`&@nu1IfM^FIb!TI9*r%d{Aesv!sc0O3uPiM$$Zd_PD(D jhnvfuQU+I)#Fl7$+0h&ancgF)u4pjYXSDLsc31xkYrHb% literal 0 HcmV?d00001 diff --git a/assets/LowRes/broken-image-icon.png b/assets/LowRes/broken-image-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..77ae646096c93e441f8a32e8cce67433ea3e8567 GIT binary patch literal 8944 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 + @@ -204,6 +205,7 @@ + diff --git a/src/App.cpp b/src/App.cpp index abb445a..6e8b0c6 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -155,6 +155,7 @@ void App::Run(int argc, char* argv[]) { if (loadTaskTargetNode) { + loadTaskTargetNode->Handler().FinishContent(loadTaskTargetNode, pageContentLoadTask); loadTaskTargetNode = page.ProcessNextLoadTask(loadTaskTargetNode, pageContentLoadTask); } } @@ -186,6 +187,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, "://")) { @@ -347,19 +351,14 @@ void App::ShowErrorPage(const char* message) 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.SetTitle("Error"); +// page.pageURL = "about:error"; +// ui.UpdateAddressBar(page.pageURL); + + parser.Write("

Error loading page

"); + parser.Write("
"); + parser.Write(message); + parser.Finish(); } static const char* frogFindURL = "http://frogfind.com/read.php?a="; @@ -369,30 +368,22 @@ 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 = pageLoadTask.GetURL(); ui.UpdateAddressBar(page.pageURL); StopLoad(); + + page.pageURL = frogFindURL; + strncpy(page.pageURL.url + FROG_FIND_URL_LENGTH, pageLoadTask.GetURL(), MAX_URL_LENGTH - FROG_FIND_URL_LENGTH); + + parser.Write("

HTTPS unsupported

"); + parser.Write("
"); + parser.Write("Sorry this browser does not support HTTPS!
"); + parser.Write("
Visit this site via FrogFind"); + parser.Finish(); } void App::PreviousPage() diff --git a/src/DataPack.cpp b/src/DataPack.cpp index e39d508..516d5a7 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -51,6 +51,8 @@ bool DataPack::Load(const char* path) textSelectCursor = (MouseCursorData*)LoadAsset(fs, header, "CTEXT"); imageIcon = LoadImageAsset(fs, header, "IIMG"); + brokenImageIcon = LoadImageAsset(fs, header, "IBROKEN"); + bulletIcon = LoadImageAsset(fs, header, "IBULLET"); fonts[0] = (Font*)LoadAsset(fs, header, "FHELV1"); fonts[1] = (Font*)LoadAsset(fs, header, "FHELV2"); @@ -117,7 +119,18 @@ Image* DataPack::LoadImageAsset(FILE* fs, DataPackHeader& header, const char* en Platform::FatalError("Could not allocate memory for data pack image %s", entryName); } memcpy(image, asset, sizeof(ImageMetadata)); - //image->data = ((uint8_t*) 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; diff --git a/src/DataPack.h b/src/DataPack.h index afb32b7..2e6c4a5 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -64,6 +64,8 @@ struct DataPack MouseCursorData* linkCursor; MouseCursorData* textSelectCursor; Image* imageIcon; + Image* brokenImageIcon; + Image* bulletIcon; Font* fonts[NUM_FONT_SIZES]; Font* monoFonts[NUM_FONT_SIZES]; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index fcf1b8c..361f45e 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -626,3 +626,4 @@ void DrawSurface_1BPP::ScrollScreen(int top, int bottom, int width, int amount) } } } + diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index 44312f2..7bf83e3 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -14,6 +14,23 @@ struct DrawContext { } + + 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; diff --git a/src/Interface.cpp b/src/Interface.cpp index 015ce4d..6ae9ed2 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -468,6 +468,7 @@ 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) diff --git a/src/Node.cpp b/src/Node.cpp index 3e9f70d..a41c3a7 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -16,6 +16,7 @@ #include "Nodes/Scroll.h" #include "Nodes/Table.h" #include "Nodes/Select.h" +#include "Nodes/ListItem.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -36,7 +37,9 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new TableRowNode(), new TableCellNode(), new SelectNode(), - new OptionNode() + new OptionNode(), + new ListNode(), + new ListItemNode() }; Node::Node(Type inType, void* inData) @@ -68,6 +71,14 @@ void Node::AddChild(Node* child) } } +void Node::InsertSibling(Node* sibling) +{ + sibling->style = style; + sibling->parent = parent; + sibling->next = next; + next = sibling; +} + void Node::CalculateEncapsulatingRect(Rect& rect) { rect.Clear(); diff --git a/src/Node.h b/src/Node.h index 19865e7..7405898 100644 --- a/src/Node.h +++ b/src/Node.h @@ -28,6 +28,8 @@ class NodeHandler 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 @@ -69,6 +71,8 @@ class Node TableCell, Select, Option, + List, + ListItem, NumNodeTypes }; @@ -79,6 +83,7 @@ class Node 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); diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index e132697..892e122 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -6,6 +6,8 @@ #include "../Draw/Surface.h" #include "../App.h" #include "../Image/Decoder.h" +#include "../DataPack.h" +#include "Text.h" void ImageNode::Draw(DrawContext& context, Node* node) { @@ -19,13 +21,44 @@ void ImageNode::Draw(DrawContext& context, Node* node) } 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); + 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 = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + uint8_t textColour = Platform::video->colourScheme.textColour; + croppedContext.surface->DrawString(croppedContext, font, data->altText, node->anchor.x + image->width + 4, node->anchor.y + 2, textColour, node->style.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) { @@ -58,6 +91,7 @@ void ImageNode::GenerateLayout(Layout& layout, Node* node) } node->anchor = layout.GetCursor(node->size.y); + layout.ProgressCursor(node, node->size.x, node->size.y); } @@ -78,7 +112,7 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) if (!data->source) { - data->state = ImageNode::ErrorDownloading; + ImageLoadError(node); } else { @@ -93,6 +127,48 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) } } +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: + 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); @@ -136,11 +212,7 @@ bool ImageNode::ParseContent(Node* node, char* buffer, size_t count) } else if (decoder->GetState() != ImageDecoder::Decoding) { - data->state = ImageNode::ErrorDownloading; - if (!data->HasDimensions()) - { - data->image.width = data->image.height = 1; - } + ImageLoadError(node); } return decoder->GetState() == ImageDecoder::Decoding; diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index 3094064..69f7b9b 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -20,11 +20,13 @@ class ImageNode: public NodeHandler class Data { public: - Data() : source(nullptr), state(WaitingToDownload) {} + Data() : source(nullptr), altText(nullptr), state(WaitingToDownload) {} bool HasDimensions() { return image.width > 0; } - bool AreDimensionsLocked() { return state == DownloadingContent || state == FinishedDownloadingContent; } + bool AreDimensionsLocked() { return state == DownloadingContent || state == FinishedDownloadingContent || state == ErrorDownloading; } + bool IsBrokenImageWithoutDimensions(); Image image; const char* source; + char* altText; State state; }; @@ -34,7 +36,9 @@ class ImageNode: public NodeHandler 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; + void ImageLoadError(Node* node); }; #endif diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 1613bb2..4132f04 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -2,6 +2,7 @@ #include "../Memory/Memory.h" #include "../Event.h" #include "../App.h" +#include "../KeyCodes.h" void LinkNode::ApplyStyle(Node* node) { @@ -42,6 +43,19 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) } return true; } + case Event::KeyPress: + { + if (event.key == KEYCODE_ENTER) + { + LinkNode::Data* data = static_cast(node->data); + if (data->url) + { + App::Get().OpenURL(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url); + } + return true; + } + return false; + } default: break; } diff --git a/src/Nodes/ListItem.cpp b/src/Nodes/ListItem.cpp new file mode 100644 index 0000000..34c0962 --- /dev/null +++ b/src/Nodes/ListItem.cpp @@ -0,0 +1,81 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" +#include "ListItem.h" + +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 = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + layout.BreakNewLine(); + layout.PadVertical(font->glyphHeight); + layout.PushLayout(); + layout.PadHorizontal(16, 0); +} + +void ListNode::EndLayoutContext(Layout& layout, Node* node) +{ + ListNode::Data* data = static_cast(node->data); + Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + layout.PopLayout(); + layout.BreakNewLine(); + layout.PadVertical(font->glyphHeight); +} + +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); + + if (data) + { + 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 = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + + layout.BreakNewLine(); + node->anchor = layout.GetCursor(); + node->anchor.y += (font->glyphHeight - Assets.bulletIcon->height) / 2; + node->size.x = Assets.bulletIcon->width; + node->size.y = Assets.bulletIcon->height; + layout.PushLayout(); + layout.PadHorizontal(Assets.bulletIcon->width * 2, 0); +} + +void ListItemNode::EndLayoutContext(Layout& layout, Node* node) +{ + ListItemNode::Data* data = static_cast(node->data); + + layout.PopLayout(); + layout.BreakNewLine(); +} 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/Page.cpp b/src/Page.cpp index 718047b..f61a966 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -80,7 +80,9 @@ static const char* nodeTypeNames[] = "TableRow", "TableCell", "Select", - "Option" + "Option", + "List", + "ListItem" }; void Page::DebugDumpNodeGraph() diff --git a/src/Parser.cpp b/src/Parser.cpp index ebec75b..2be4eee 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -120,7 +120,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) // If the stack is emptied then we have finished parsing the document if (contextStackSize == 0) { -#ifdef _WIN32 +#ifdef WIN32 page.DebugDumpNodeGraph(); #endif Finish(); @@ -137,9 +137,10 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) void HTMLParser::Finish() { + FlushTextBuffer(); parseState = ParseFinished; page.layout.MarkParsingComplete(); - page.GetApp().StopLoad(); + page.GetApp().pageLoadTask.Stop(); } #define NUM_AMPERSAND_ESCAPE_SEQUENCES (sizeof(ampersandEscapeSequences) / (2 * sizeof(const char*))) @@ -224,13 +225,20 @@ void HTMLParser::FlushTextBuffer() OptionNode::Data* option = static_cast(CurrentContext().node->data); option->text = MemoryManager::pageAllocator.AllocString(textBuffer); } - else if(CurrentSection() == SectionElement::Body && textBufferSize > 0) + else if(textBufferSize > 0) { - EmitText(textBuffer); - } - else if (CurrentSection() == SectionElement::Title && textBufferSize > 0) - { - page.SetTitle(textBuffer); + switch (CurrentSection()) + { + case SectionElement::Title: + page.SetTitle(textBuffer); + break; + case SectionElement::Script: + case SectionElement::Style: + break; + default: + EmitText(textBuffer); + break; + } } } break; @@ -424,6 +432,11 @@ 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 && MemoryManager::pageAllocator.GetError() == LinearAllocator::Error_None) diff --git a/src/Parser.h b/src/Parser.h index e0c7484..f53aaec 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -69,6 +69,7 @@ class HTMLParser void Reset(); void Parse(char* buffer, size_t count); + void Write(const char* str); Page& page; diff --git a/src/Render.cpp b/src/Render.cpp index 6ec5878..052355f 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -433,6 +433,7 @@ void PageRenderer::MarkNodeDirty(Node* dirtyNode) 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, Platform::video->colourScheme.pageColour); Platform::input->ShowMouse(); diff --git a/src/Tags.cpp b/src/Tags.cpp index 5bddc35..667f031 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -34,6 +34,8 @@ #include "Nodes/Form.h" #include "Nodes/Table.h" #include "Nodes/Select.h" +#include "Nodes/ListItem.h" +#include "Nodes/Text.h" static const HTMLTagHandler* tagHandlers[] = { @@ -56,8 +58,6 @@ 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 AlignmentTagHandler("center", ElementAlignment::Center), new FontTagHandler(), @@ -153,11 +153,11 @@ void SizeTagHandler::Close(class HTMLParser& parser) const void LiTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - // TODO-refactor : add bullet point - parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + parser.PushContext(ListItemNode::Construct(MemoryManager::pageAllocator), this); } void LiTagHandler::Close(class HTMLParser& parser) const { + parser.PopContext(this); } void ATagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -285,14 +285,12 @@ void FontTagHandler::Close(class HTMLParser& parser) const void ListTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - // TODO-refactor - parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + parser.PushContext(ListNode::Construct(MemoryManager::pageAllocator), this); } void ListTagHandler::Close(class HTMLParser& parser) const { - // TODO-refactor - parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + parser.PopContext(this); } struct HTMLInputTag @@ -483,6 +481,11 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const data->image.height = 0; } + if (altText) + { + data->altText = MemoryManager::pageAllocator.AllocString(altText); + } + parser.EmitNode(imageNode); } } diff --git a/src/URL.h b/src/URL.h index d90d468..33f8f75 100644 --- a/src/URL.h +++ b/src/URL.h @@ -77,6 +77,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] == '#') { diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 042ce02..44c5281 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -86,6 +86,10 @@ void GenerateAssetPack(const char* name) AddEntryHeader("IIMG", entries, data); EncodeImage(basePath, "image-icon.png", data); + AddEntryHeader("IBROKEN", entries, data); + EncodeImage(basePath, "broken-image-icon.png", data); + AddEntryHeader("IBULLET", entries, data); + EncodeImage(basePath, "bullet.png", data); AddEntryHeader("END", entries, data); From 76c09c0f038d9e28a585224cf793c71186e155a1 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Mar 2024 13:43:57 +0000 Subject: [PATCH 55/98] Fixed 1bpp image rendering for EGA planar modes. Fixed HTML parsing error --- src/App.cpp | 11 ++++++++ src/DOS/Surf4bpp.cpp | 65 ++++++++++++++++++++++++------------------- src/Draw/Surf1bpp.cpp | 4 --- src/Parser.cpp | 3 ++ 4 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 6e8b0c6..788182f 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -217,6 +217,13 @@ void LoadTask::Load(const char* targetURL) } } + // Replace back slashes with forward ones + for (char* ptr = url.url; *ptr; ptr++) + { + if (*ptr == '\\') + *ptr = '/'; + } + if (type == LoadTask::RemoteFile) { request = Platform::network->CreateRequest(url.url); @@ -355,9 +362,11 @@ void App::ShowErrorPage(const char* message) // page.pageURL = "about:error"; // ui.UpdateAddressBar(page.pageURL); + parser.Write(""); parser.Write("

Error loading page

"); parser.Write("
"); parser.Write(message); + parser.Write(""); parser.Finish(); } @@ -377,12 +386,14 @@ void App::ShowNoHTTPSPage() 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(); } diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index c1273c7..f227e1c 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -468,46 +468,55 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int } else { - 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 + // Set write mode 3 + outp(GC_INDEX, GC_MODE); + outp(GC_DATA, 0x3); + + outp(GC_INDEX, GC_ROTATE); + outp(GC_DATA, 0); + + // colour 0 for black + outp(GC_INDEX, GC_SET_RESET); + outp(GC_DATA, 0); + + // Set bit mask + outp(GC_INDEX, GC_BITMASK); // 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 >> 3); - int xBits = 7 - (x & 0x7); // Number of bits in the first destination byte to skip + 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 destBuffer = 0x0; // *dest; - // Handle the first destination byte separately with proper masking - if (xBits == 0) + for (int i = 0; i < destWidth; i++) { - // If x is on a byte boundary, copy the whole byte from the source - for (int i = 0; i < destByteWidth; i++) + if (!(srcBuffer & srcMask)) { - destRow[i] = src[i]; + destBuffer |= destMask; } - } - else - { - // Copy the first destination byte with proper masking - destRow[0] = (destRow[0] & (0xFF << xBits)) | (src[0] >> (8 - xBits)); - - // Copy the remaining bytes - for (int i = 1; i < destByteWidth; i++) + srcMask >>= 1; + if (!srcMask) { - destRow[i] = (src[i - 1] << xBits) | (src[i] >> (8 - xBits)); + srcMask = 0x80; + srcBuffer = *src++; + } + destMask >>= 1; + if (!destMask) + { + outp(GC_DATA, destBuffer); + *dest++ |= 0xff; + destBuffer = 0; + destMask = 0x80; } } - - // 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) | (src[destByteWidth] & mask); - } + outp(GC_DATA, destBuffer); + *dest++ |= 0xff; } } } diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 361f45e..d00b0a0 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -435,10 +435,6 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int 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 - int destByteWidth = (destWidth) >> 3; // Calculate the width of the destination image in bytes - // Blit the image data line by line for (int j = 0; j < destHeight; j++) { diff --git a/src/Parser.cpp b/src/Parser.cpp index 2be4eee..26e3531 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -234,8 +234,11 @@ void HTMLParser::FlushTextBuffer() break; case SectionElement::Script: case SectionElement::Style: + case SectionElement::Document: break; default: + case SectionElement::Body: + case SectionElement::HTML: EmitText(textBuffer); break; } From 4aff6beb28ff99f92e65d5076f8cdedceec24646 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Mar 2024 14:44:36 +0000 Subject: [PATCH 56/98] Improved URL parsing --- src/App.cpp | 29 +++++++++++++++++++---------- src/HTTP.cpp | 20 +++++++++++++++++++- src/URL.h | 35 +++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 788182f..6c6919a 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -132,6 +132,11 @@ void App::Run(int argc, char* argv[]) requestedNewPage = false; } } + else if (pageLoadTask.type == LoadTask::LocalFile) + { + ShowErrorPage("File not found"); + requestedNewPage = false; + } } else if (!parser.IsFinished()) { @@ -210,19 +215,23 @@ 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); + } } } - // Replace back slashes with forward ones - for (char* ptr = url.url; *ptr; ptr++) - { - if (*ptr == '\\') - *ptr = '/'; - } + url.CleanUp(); if (type == LoadTask::RemoteFile) { diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 3b902f7..04c8523 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -343,7 +343,25 @@ void HTTPRequest::Update() //printf("Redirecting to %s", lineBuffer + 10); //getchar(); Stop(); - Open(lineBuffer + 10); + + 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; } } diff --git a/src/URL.h b/src/URL.h index 33f8f75..658e0c9 100644 --- a/src/URL.h +++ b/src/URL.h @@ -45,16 +45,39 @@ 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, "/./")) + { + strcpy(directory, directory + 2); + } + directory = strstr(url, "/../"); + while (directory && directory > url) { - memmove(match, match + 2, strlen(match) - 1); + char* prevSlash = directory - 1; + while (prevSlash > url && *prevSlash != '/') + { + prevSlash--; + } + if (*prevSlash == '/' && prevSlash > url) + { + strcpy(prevSlash, directory + 3); + } + directory = strstr(prevSlash + 1, "/../"); } - while (match = strstr(url.url, "&")) + // Fix & escape sequences + char* match; + while (match = strstr(url, "&")) { memmove(match + 1, match + 5, strlen(match + 1) - 3); } @@ -168,7 +191,7 @@ struct URL strcpy(result.url + 7, relativeURL); } - ProcessEscapeCodes(result); + result.CleanUp(); return result; } From 22455a171e7f6e9ac784908edbc59ad3ff45a178 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Mar 2024 20:40:29 +0000 Subject: [PATCH 57/98] Improved keyboard navigation --- src/Interface.cpp | 30 +++++++++++++++++++++++++++++- src/Node.cpp | 4 ++++ src/Nodes/LinkNode.cpp | 9 +-------- src/Render.cpp | 26 +++++++++++++++++++++++++- src/Render.h | 2 ++ 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/Interface.cpp b/src/Interface.cpp index 6ae9ed2..25b6054 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -489,7 +489,7 @@ void AppInterface::ScrollRelative(int delta) if (scrollPositionY < 0) scrollPositionY = 0; - int maxScrollY = app.pageRenderer.GetVisiblePageHeight() - app.ui.windowRect.height; + int maxScrollY = app.pageRenderer.GetVisiblePageHeight() - windowRect.height; if (maxScrollY < 0) maxScrollY = 0; @@ -523,6 +523,17 @@ void AppInterface::CycleNodes(int direction) 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 (node) { if (!IsInterfaceNode(node)) @@ -540,6 +551,23 @@ void AppInterface::CycleNodes(int direction) 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); + } FocusNode(node); return; } diff --git a/src/Node.cpp b/src/Node.cpp index a41c3a7..083db87 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -87,6 +87,10 @@ void Node::CalculateEncapsulatingRect(Rect& rect) if (!firstChild) { + rect.x = anchor.x; + rect.y = anchor.y; + rect.width = size.x; + rect.height = size.y; return; } diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 4132f04..3246947 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -65,17 +65,12 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) void LinkNode::HighlightChildren(Node* node) { - DrawContext context; - App::Get().pageRenderer.GenerateDrawContext(context, node); - Node* child = node->firstChild; if (!child) return; bool isDescendingTree = true; - Platform::input->HideMouse(); - while (child != node) { if (isDescendingTree) @@ -95,7 +90,7 @@ void LinkNode::HighlightChildren(Node* node) if (shouldHighlight) { - context.surface->InvertRect(context, child->anchor.x, child->anchor.y, child->size.x, child->size.y); + App::Get().pageRenderer.InvertNode(child); } if (child->firstChild) @@ -126,6 +121,4 @@ void LinkNode::HighlightChildren(Node* node) } } - Platform::input->ShowMouse(); - } diff --git a/src/Render.cpp b/src/Render.cpp index 052355f..bf7f0dd 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -462,4 +462,28 @@ void PageRenderer::MarkPageLayoutComplete() break; } } -} \ No newline at end of file +} + +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 index 56f4d3f..04d496a 100644 --- a/src/Render.h +++ b/src/Render.h @@ -31,6 +31,8 @@ class PageRenderer void MarkPageLayoutComplete(); void MarkNodeDirty(Node* node); + void InvertNode(Node* node); + int GetVisiblePageHeight() { return visiblePageHeight; } private: From 9dba9caf8aa565e1a7e731812f7397baf519add4 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Mar 2024 21:01:56 +0000 Subject: [PATCH 58/98] Fixeds for list item parsing and layout --- src/Nodes/ListItem.cpp | 5 +++-- src/Page.cpp | 16 +++++++++++++++- src/Tags.cpp | 6 ++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Nodes/ListItem.cpp b/src/Nodes/ListItem.cpp index 34c0962..e31dc96 100644 --- a/src/Nodes/ListItem.cpp +++ b/src/Nodes/ListItem.cpp @@ -66,8 +66,7 @@ void ListItemNode::BeginLayoutContext(Layout& layout, Node* node) layout.BreakNewLine(); node->anchor = layout.GetCursor(); node->anchor.y += (font->glyphHeight - Assets.bulletIcon->height) / 2; - node->size.x = Assets.bulletIcon->width; - node->size.y = Assets.bulletIcon->height; + node->size.x = layout.AvailableWidth(); layout.PushLayout(); layout.PadHorizontal(Assets.bulletIcon->width * 2, 0); } @@ -78,4 +77,6 @@ void ListItemNode::EndLayoutContext(Layout& layout, Node* node) layout.PopLayout(); layout.BreakNewLine(); + + node->size.y = layout.Cursor().y - node->anchor.y; } diff --git a/src/Page.cpp b/src/Page.cpp index f61a966..36a54ac 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -24,6 +24,7 @@ #include "Nodes/Form.h" #include "Nodes/StyNode.h" #include "Nodes/Select.h" +#include "Nodes/LinkNode.h" #include "Draw/Surface.h" #include "Memory/Memory.h" @@ -131,7 +132,14 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) case Node::Text: { TextElement::Data* data = static_cast(node->data); - printf("<%s> [%d,%d:%d,%d]\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); + if (node->firstChild) + { + printf("<%s> [%d,%d:%d,%d]\n", nodeTypeNames[node->type], node->anchor.x, node->anchor.y, node->size.x, node->size.y); + } + else + { + 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()); + } } break; case Node::SubText: @@ -151,6 +159,12 @@ void Page::DebugDumpNodeGraph(Node* node, int depth) printf("<%s> [%s]\n", nodeTypeNames[node->type], data->text); } break; + case Node::Link: + { + 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); + } + break; case Node::Section: { SectionElement::Data* data = static_cast(node->data); diff --git a/src/Tags.cpp b/src/Tags.cpp index 667f031..112f0e5 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -153,6 +153,12 @@ void SizeTagHandler::Close(class HTMLParser& parser) const void LiTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { + 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 From 955ee94bc42e495dcdc161112d39a33b4692dda1 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Fri, 22 Mar 2024 22:37:12 +0000 Subject: [PATCH 59/98] Added framework for PNG and JPEG decoders that currently only parse image dimensions --- project/DOS/Makefile | 8 ++- project/Windows/Windows.vcxproj | 4 ++ src/Image/Decoder.cpp | 100 ++++++++++++++++++++++++++- src/Image/Decoder.h | 60 +++++++++++++++- src/Image/Gif.cpp | 52 -------------- src/Image/Gif.h | 15 ---- src/Image/Jpeg.cpp | 119 ++++++++++++++++++++++++++++++++ src/Image/Jpeg.h | 41 +++++++++++ src/Image/Png.cpp | 88 +++++++++++++++++++++++ src/Image/Png.h | 49 +++++++++++++ src/Nodes/ImgNode.cpp | 18 +++-- 11 files changed, 475 insertions(+), 79 deletions(-) create mode 100644 src/Image/Jpeg.cpp create mode 100644 src/Image/Jpeg.h create mode 100644 src/Image/Png.cpp create mode 100644 src/Image/Png.h diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 93d8a61..677a17a 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.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 Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj MemBlock.obj Memory.obj EMS.obj +objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.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 Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.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) -fi=$(SRC_PATH)\Defines.h @@ -122,6 +122,12 @@ Decoder.obj: $(SRC_PATH)\Image\Decoder.cpp 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) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 29ed43c..07fa21b 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -148,6 +148,8 @@ + + @@ -193,6 +195,8 @@ + + diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 4a3cc78..eda5574 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -1,7 +1,14 @@ #include +#include +#include #include "Gif.h" +#include "Jpeg.h" +#include "Png.h" #include "Decoder.h" #include "../Platform.h" +#include "Image.h" +#include "../Draw/Surface.h" +#pragma warning(disable:4996) #include #include @@ -9,8 +16,8 @@ typedef union { char gif[sizeof(GifDecoder)]; - //char png[sizeof(PngDecoder)]; - //char jpeg[sizeof(JpegDecoder)]; + char png[sizeof(PngDecoder)]; + char jpeg[sizeof(JpegDecoder)]; char buffer[1]; } ImageDecoderUnion; @@ -73,5 +80,92 @@ ImageDecoder* ImageDecoder::Get() ImageDecoder* ImageDecoder::Create(DecoderType type) { - return new (imageDecoderUnion->buffer) GifDecoder(); + 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::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->bpp == 1 ? 1 : 8; } diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index 8f22bef..27d13fa 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -6,6 +6,45 @@ 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: @@ -24,17 +63,32 @@ class ImageDecoder Jpeg }; - ImageDecoder() : outputImage(NULL) {} - virtual void Begin(Image* image, bool dimensionsOnly) = 0; + ImageDecoder() : outputImage(NULL), state(Stopped), structFillPosition(0) {} + + void Begin(Image* image, bool dimensionsOnly); virtual void Process(uint8_t* data, size_t dataLength) = 0; - virtual State GetState() = 0; + State GetState() { return state; } static void Allocate(); static ImageDecoder* Get(); static ImageDecoder* Create(DecoderType type); + static ImageDecoder* CreateFromExtension(const char* path); 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; + Image* outputImage; + ImageDecoder::State state; + bool onlyDownloadDimensions; static const uint8_t greyDitherMatrix[256]; static const int8_t colourDitherMatrix[16]; diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index df672b0..d01d22a 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -20,18 +20,7 @@ #define BLOCK_TYPE_GRAPHIC_CONTROL_EXTENSION 0xF9 GifDecoder::GifDecoder() -: state(ImageDecoder::Stopped) { -} - -void GifDecoder::Begin(Image* image, bool dimensionsOnly) -{ - onlyDownloadDimensions = dimensionsOnly; - state = ImageDecoder::Decoding; - outputImage = image; - outputImage->bpp = Platform::video->drawSurface->bpp == 1 ? 1 : 8; - - structFillPosition = 0; internalState = ParseHeader; lineBufferSkipCount = 0; transparentColourIndex = -1; @@ -530,47 +519,6 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) } } -bool GifDecoder::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; - } -} - - -bool GifDecoder::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; - } -} - void GifDecoder::ClearDictionary() { for(dictionaryIndex = 0; dictionaryIndex < (1 << lzwCodeSize); dictionaryIndex++) diff --git a/src/Image/Gif.h b/src/Image/Gif.h index 0e9830e..ade0965 100644 --- a/src/Image/Gif.h +++ b/src/Image/Gif.h @@ -15,20 +15,9 @@ class GifDecoder : public ImageDecoder public: GifDecoder(); - virtual void Begin(Image* image, bool onlyDimensions); virtual void Process(uint8_t* data, size_t dataLength); - virtual ImageDecoder::State GetState() { return state; } private: - 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); void ClearDictionary(); @@ -93,9 +82,7 @@ class GifDecoder : public ImageDecoder }; #pragma pack(pop) - ImageDecoder::State state; InternalState internalState; - bool onlyDownloadDimensions; uint8_t palette[256*3]; // RGB values uint8_t paletteLUT[256]; // GIF palette colour to video mode palette colour @@ -103,8 +90,6 @@ class GifDecoder : public ImageDecoder uint8_t backgroundColour; int transparentColourIndex; uint8_t lzwCodeSize; - - size_t structFillPosition; DictionaryEntry dictionary[GIF_MAX_DICTIONARY_ENTRIES]; diff --git a/src/Image/Jpeg.cpp b/src/Image/Jpeg.cpp new file mode 100644 index 0000000..3d53b8c --- /dev/null +++ b/src/Image/Jpeg.cpp @@ -0,0 +1,119 @@ +#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) + { + int width = frameHeader.width; + int height = frameHeader.height; + Platform::video->ScaleImageDimensions(width, height); + + outputImage->width = width; + outputImage->height = height; + + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; + } + } + + state = ImageDecoder::Error; + 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..8a0f82b --- /dev/null +++ b/src/Image/Png.cpp @@ -0,0 +1,88 @@ +#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) + { + int width = imageHeader.width; + int height = imageHeader.height; + Platform::video->ScaleImageDimensions(width, height); + + outputImage->width = width; + outputImage->height = height; + + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; + } + } + + state = ImageDecoder::Error; + 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/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 892e122..15bc361 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -79,7 +79,7 @@ void ImageNode::GenerateLayout(Layout& layout, Node* node) { int imageWidth = data->image.width; data->image.width = layout.MaxAvailableWidth(); - data->image.height = ((long)data->image.height * data->image.width) / imageWidth; + data->image.height = (uint16_t)(((long)data->image.height * data->image.width) / imageWidth); } node->size.x = data->image.width; @@ -119,10 +119,18 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) bool loadDimensionsOnly = !data->HasDimensions(); if (!loadDimensionsOnly && App::Get().pageLoadTask.HasContent()) return; - loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); - ImageDecoder::Create(ImageDecoder::Gif); - ImageDecoder::Get()->Begin(&data->image, loadDimensionsOnly); - data->state = loadDimensionsOnly ? ImageNode::DownloadingDimensions : ImageNode::DownloadingContent; + + if (ImageDecoder::CreateFromExtension(data->source)) + { + loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); + ImageDecoder::Get()->Begin(&data->image, loadDimensionsOnly); + data->state = loadDimensionsOnly ? ImageNode::DownloadingDimensions : ImageNode::DownloadingContent; + } + else + { + // Unsupported image format + ImageLoadError(node); + } } } } From 8dfb92cb31ec70348763c762dd2956e712727218 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 10:28:19 +0000 Subject: [PATCH 60/98] Added checking of MIME types to determine image format from HTTP request --- src/HTTP.cpp | 7 +++++++ src/HTTP.h | 4 ++++ src/Image/Decoder.cpp | 17 +++++++++++++++++ src/Image/Decoder.h | 1 + src/Image/Jpeg.cpp | 2 +- src/Image/Png.cpp | 2 +- src/Nodes/ImgNode.cpp | 37 +++++++++++++++++++++++++------------ src/Nodes/ImgNode.h | 1 + 8 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 04c8523..0023be7 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -5,12 +5,14 @@ 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, ...) @@ -315,6 +317,7 @@ void HTTPRequest::Update() contentRemaining = -1; usingChunkedTransfer = false; + contentType[0] = '\0'; } } break; @@ -373,6 +376,10 @@ void HTTPRequest::Update() { usingChunkedTransfer = true; } + else if (!strnicmp(lineBuffer, "Content-Type:", 13)) + { + strncpy(contentType, lineBuffer + 14, MAX_CONTENT_TYPE_LENGTH); + } //printf("Header: %s -- \n", lineBuffer); //getchar(); diff --git a/src/HTTP.h b/src/HTTP.h index dab0558..e9d49d1 100644 --- a/src/HTTP.h +++ b/src/HTTP.h @@ -13,6 +13,8 @@ #define RESPONSE_TEMPORARY_REDIRECTION 307 #define RESPONSE_PERMANENT_REDIRECT 308 +#define MAX_CONTENT_TYPE_LENGTH 32 + class HTTPRequest { public: @@ -36,6 +38,7 @@ class HTTPRequest void Update(); virtual const char* GetStatusString(); virtual const char* GetURL() { return url.url; } + const char* GetContentType() { return contentType; } private: enum InternalStatus @@ -81,6 +84,7 @@ class HTTPRequest uint16_t serverPort; NetworkTCPSocket* sock; int responseCode; + char contentType[MAX_CONTENT_TYPE_LENGTH]; char lineBuffer[LINE_BUFFER_SIZE]; int lineBufferSize; diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index eda5574..5970349 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -93,6 +93,23 @@ ImageDecoder* ImageDecoder::Create(DecoderType type) } } +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); diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index 27d13fa..f03572d 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -73,6 +73,7 @@ class ImageDecoder 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); diff --git a/src/Image/Jpeg.cpp b/src/Image/Jpeg.cpp index 3d53b8c..7b42ae0 100644 --- a/src/Image/Jpeg.cpp +++ b/src/Image/Jpeg.cpp @@ -109,7 +109,7 @@ void JpegDecoder::Process(uint8_t* data, size_t dataLength) } } - state = ImageDecoder::Error; + state = ImageDecoder::Success; return; } break; diff --git a/src/Image/Png.cpp b/src/Image/Png.cpp index 8a0f82b..0551e61 100644 --- a/src/Image/Png.cpp +++ b/src/Image/Png.cpp @@ -78,7 +78,7 @@ void PngDecoder::Process(uint8_t* data, size_t dataLength) } } - state = ImageDecoder::Error; + state = ImageDecoder::Success; return; } break; diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 15bc361..e7c9ca1 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -7,6 +7,7 @@ #include "../App.h" #include "../Image/Decoder.h" #include "../DataPack.h" +#include "../HTTP.h" #include "Text.h" void ImageNode::Draw(DrawContext& context, Node* node) @@ -15,7 +16,7 @@ void ImageNode::Draw(DrawContext& context, Node* node) //printf("--IMG [%d, %d]\n", node->anchor.x, node->anchor.y); uint8_t outlineColour = Platform::video->colourScheme.textColour; - if (data->state == ImageNode::FinishedDownloadingContent) + if (data->state == ImageNode::FinishedDownloadingContent && data->image.lines.IsAllocated()) { context.surface->BlitImage(context, &data->image, node->anchor.x, node->anchor.y); } @@ -120,17 +121,8 @@ void ImageNode::LoadContent(Node* node, LoadTask& loadTask) if (!loadDimensionsOnly && App::Get().pageLoadTask.HasContent()) return; - if (ImageDecoder::CreateFromExtension(data->source)) - { - loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); - ImageDecoder::Get()->Begin(&data->image, loadDimensionsOnly); - data->state = loadDimensionsOnly ? ImageNode::DownloadingDimensions : ImageNode::DownloadingContent; - } - else - { - // Unsupported image format - ImageLoadError(node); - } + loadTask.Load(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->source).url); + data->state = ImageNode::DeterminingFormat; } } } @@ -145,6 +137,7 @@ void ImageNode::FinishContent(Node* node, struct LoadTask& loadTask) { case ImageNode::DownloadingDimensions: case ImageNode::DownloadingContent: + case ImageNode::DeterminingFormat: ImageLoadError(node); break; } @@ -180,6 +173,26 @@ void ImageNode::ImageLoadError(Node* node) 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); diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index 69f7b9b..fd30a92 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -10,6 +10,7 @@ class ImageNode: public NodeHandler enum State { WaitingToDownload, + DeterminingFormat, DownloadingDimensions, FinishedDownloadingDimensions, DownloadingContent, From 832455030a4ab9e91c10c95d0374afec4a30885f Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 12:44:44 +0000 Subject: [PATCH 61/98] Added status bar functionality. Fixed various bugs. Added HTTP respone timeout of 20 seconds. --- src/App.cpp | 29 +++++++++++++++++++++++-- src/DOS/BIOSVid.cpp | 10 +++++++-- src/DOS/BIOSVid.h | 2 +- src/DOS/DOSNet.cpp | 1 + src/HTTP.cpp | 43 +++++++++++++++++++++++++++++------- src/HTTP.h | 18 +++++++++++----- src/Interface.cpp | 49 ++++++++++++++++++++++++++++++++++++------ src/Interface.h | 5 ++++- src/Layout.cpp | 18 ++++++---------- src/Nodes/Field.cpp | 1 + src/Nodes/ImgNode.h | 2 ++ src/Nodes/LinkNode.cpp | 12 +++++++++-- src/Nodes/Status.cpp | 17 ++++++++++++++- src/Nodes/Status.h | 31 +++++++++++++++++++++++--- src/Parser.cpp | 5 +++++ src/Parser.h | 4 ++-- src/Render.cpp | 4 ++-- src/Stack.h | 2 +- 18 files changed, 204 insertions(+), 49 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 6c6919a..20bd9f0 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -39,8 +39,8 @@ void App::ResetPage() { page.Reset(); parser.Reset(); - ui.Reset(); pageRenderer.Reset(); + ui.Reset(); pageRenderer.RefreshAll(); } @@ -97,6 +97,7 @@ void App::Run(int argc, char* argv[]) page.pageURL = pageLoadTask.GetURL(); ui.UpdateAddressBar(page.pageURL); loadTaskTargetNode = page.GetRootNode(); + ui.SetStatusMessage("Parsing page content...", StatusBarNode::GeneralStatus); } size_t bytesRead = pageLoadTask.GetContent(loadBuffer, APP_LOAD_BUFFER_SIZE); @@ -118,7 +119,14 @@ void App::Run(int argc, char* argv[]) { 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 (pageLoadTask.request->GetStatus() == HTTPRequest::Error) @@ -162,6 +170,18 @@ void App::Run(int argc, char* argv[]) { 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) @@ -336,6 +356,11 @@ void App::RequestNewPage(const char* 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) diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp index 719ddc2..01eaa81 100644 --- a/src/DOS/BIOSVid.cpp +++ b/src/DOS/BIOSVid.cpp @@ -41,7 +41,11 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) screenWidth = videoModeInfo->screenWidth; screenHeight = videoModeInfo->screenHeight; - SetScreenMode(videoModeInfo->biosVideoMode); + if (!SetScreenMode(videoModeInfo->biosVideoMode)) + { + Platform::FatalError("Could not set video mode: %d", videoModeInfo->biosVideoMode); + return; + } if (videoModeInfo->biosVideoMode == CGA_COMPOSITE_MODE) { @@ -201,13 +205,15 @@ int BIOSVideoDriver::GetScreenMode() return (int)outreg.h.al; } -void BIOSVideoDriver::SetScreenMode(int screenMode) +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 index ce077ad..b324862 100644 --- a/src/DOS/BIOSVid.h +++ b/src/DOS/BIOSVid.h @@ -31,7 +31,7 @@ class BIOSVideoDriver : public VideoDriver private: int GetScreenMode(); - void SetScreenMode(int screenMode); + bool SetScreenMode(int screenMode); int startingScreenMode; VideoModeInfo* videoModeInfo; diff --git a/src/DOS/DOSNet.cpp b/src/DOS/DOSNet.cpp index e9dcbf6..512aa8f 100644 --- a/src/DOS/DOSNet.cpp +++ b/src/DOS/DOSNet.cpp @@ -78,6 +78,7 @@ void DOSNetworkDriver::Init() } } + printf("Network interface initialised\n"); isConnected = true; } diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 0023be7..6c6059b 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -69,6 +69,11 @@ bool HTTPRequest::SendPendingWrites() return false; } +void HTTPRequest::ResetTimeOutTimer() +{ + timeout = clock() + HTTP_RESPONSE_TIMEOUT; +} + void HTTPRequest::Open(char* inURL) { url = inURL; @@ -134,6 +139,8 @@ void HTTPRequest::Open(char* inURL) status = HTTPRequest::Connecting; internalStatus = QueuedDNSRequest; + + ResetTimeOutTimer(); } else if (strnicmp(url.url, "https://", 8) == 0) { status = HTTPRequest::UnsupportedHTTPS; @@ -158,8 +165,10 @@ size_t HTTPRequest::ReadData(char* buffer, size_t count) { MarkError(ContentReceiveError); } - else + else if(rc > 0) { + ResetTimeOutTimer(); + size_t bytesRead = (size_t)(rc); if (contentRemaining > 0) { @@ -205,6 +214,12 @@ void HTTPRequest::MarkError(InternalStatus statusError) void HTTPRequest::Update() { + if ((status == HTTPRequest::Connecting || status == HTTPRequest::Downloading) && clock() > timeout) + { + MarkError(TimedOut); + return; + } + if (SendPendingWrites()) { return; @@ -260,6 +275,7 @@ void HTTPRequest::Update() break; } internalStatus = ConnectingSocket; + ResetTimeOutTimer(); } break; case ConnectingSocket: @@ -267,6 +283,7 @@ void HTTPRequest::Update() if (sock->IsConnectComplete()) { internalStatus = SendHeaders; + ResetTimeOutTimer(); break; } else if (sock->IsClosed()) @@ -327,19 +344,27 @@ void HTTPRequest::Update() { if (lineBuffer[0] == '\0') { - // Header has finished - if (usingChunkedTransfer) + if (contentRemaining == 0) { - internalStatus = ParseChunkHeader; + // Received header with zero content + MarkError(ContentReceiveError); } else { - status = Downloading; - internalStatus = ReceiveContent; + // Header has finished + if (usingChunkedTransfer) + { + internalStatus = ParseChunkHeader; + } + else + { + status = Downloading; + internalStatus = ReceiveContent; + } } break; } - else if (!strncmp(lineBuffer, "Location: ", 10)) + else if (!strnicmp(lineBuffer, "Location: ", 10)) { if (responseCode == RESPONSE_MOVED_PERMANENTLY || responseCode == RESPONSE_MOVED_TEMPORARILY || responseCode == RESPONSE_TEMPORARY_REDIRECTION || responseCode == RESPONSE_PERMANENT_REDIRECT) { @@ -368,7 +393,7 @@ void HTTPRequest::Update() break; } } - else if (!strncmp(lineBuffer, "Content-Length:", 15)) + else if (!strnicmp(lineBuffer, "Content-Length:", 15)) { contentRemaining = strtol(lineBuffer + 15, NULL, 10); } @@ -501,6 +526,8 @@ const char* HTTPRequest::GetStatusString() return "Error writing headers"; case HostNameResolveError: return "Error resolving host name"; + case TimedOut: + return "Connection timed out"; } break; diff --git a/src/HTTP.h b/src/HTTP.h index e9d49d1..f96be01 100644 --- a/src/HTTP.h +++ b/src/HTTP.h @@ -1,6 +1,7 @@ #ifndef HTTP_H_ #define HTTP_H_ +#include #include "Platform.h" #include "URL.h" @@ -15,6 +16,9 @@ #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: @@ -32,12 +36,12 @@ class HTTPRequest void Open(char* url); - virtual HTTPRequest::Status GetStatus() { return status; } - virtual size_t ReadData(char* buffer, size_t count); - virtual void Stop(); + HTTPRequest::Status GetStatus() { return status; } + size_t ReadData(char* buffer, size_t count); + void Stop(); void Update(); - virtual const char* GetStatusString(); - virtual const char* GetURL() { return url.url; } + const char* GetStatusString(); + const char* GetURL() { return url.url; } const char* GetContentType() { return contentType; } private: @@ -53,6 +57,7 @@ class HTTPRequest UnsupportedHTTPError, MalformedHTTPVersionLineError, WriteLineError, + TimedOut, HostNameResolveError, // Connection states @@ -73,6 +78,7 @@ class HTTPRequest bool SendPendingWrites(); void Reset(); + void ResetTimeOutTimer(); HTTPRequest::Status status; InternalStatus internalStatus; @@ -94,6 +100,8 @@ class HTTPRequest long chunkSizeRemaining; bool usingChunkedTransfer; + + clock_t timeout; }; diff --git a/src/Interface.cpp b/src/Interface.cpp index 25b6054..ae76213 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -19,8 +19,10 @@ #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" @@ -61,6 +63,10 @@ void AppInterface::Reset() { hoverNode = nullptr; } + + ClearStatusMessage(StatusBarNode::HoverStatus); + ClearStatusMessage(StatusBarNode::GeneralStatus); + UpdatePageScrollBar(); } void AppInterface::Update() @@ -103,17 +109,36 @@ void AppInterface::Update() if (hoverNode != oldHoverNode) { + bool hasHoverStatusMessage = false; + if (hoverNode) { switch (hoverNode->type) { case Node::Link: Platform::input->SetMouseCursor(MouseCursor::Hand); - // TODO show link URL + { + 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; + } + } + break; default: Platform::input->SetMouseCursor(MouseCursor::Pointer); break; @@ -124,6 +149,11 @@ void AppInterface::Update() Platform::input->SetMouseCursor(MouseCursor::Pointer); } + if (!hasHoverStatusMessage) + { + ClearStatusMessage(StatusBarNode::HoverStatus); + } + if(0) { // For debugging picking @@ -220,7 +250,7 @@ void AppInterface::Update() { char tempMessage[100]; MemoryManager::GenerateMemoryReport(tempMessage); - app.ui.SetStatusMessage(tempMessage); + SetStatusMessage(tempMessage, StatusBarNode::GeneralStatus); } break; @@ -414,6 +444,7 @@ void AppInterface::DrawInterfaceNodes(DrawContext& context) void AppInterface::SetTitle(const char* title) { strncpy(titleBuffer, title, MAX_TITLE_LENGTH); + titleBuffer[MAX_TITLE_LENGTH - 1] = '\0'; Font* font = Assets.GetFont(titleNode->style.fontSize, titleNode->style.fontStyle); int titleWidth = font->CalculateWidth(titleBuffer, titleNode->style.fontStyle); titleNode->anchor.x = Platform::video->screenWidth / 2 - titleWidth / 2; @@ -471,14 +502,18 @@ void AppInterface::OnAddressBarSubmit(Node* node) app.ui.FocusNode(nullptr); } -void AppInterface::SetStatusMessage(const char* message) +void AppInterface::SetStatusMessage(const char* message, StatusBarNode::StatusType type) { - StatusBarNode::Data* data = static_cast(statusBarNode->data); - if (strcmp(data->message, message)) + if (message) { - strcpy(data->message, message); - statusBarNode->Redraw(); + StatusBarNode::SetStatus(statusBarNode, message, type); } + else ClearStatusMessage(type); +} + +void AppInterface::ClearStatusMessage(StatusBarNode::StatusType type) +{ + StatusBarNode::SetStatus(statusBarNode, "", type); } void AppInterface::ScrollRelative(int delta) diff --git a/src/Interface.h b/src/Interface.h index 129b63c..d1ac2b5 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -17,6 +17,7 @@ #include "Platform.h" #include "URL.h" #include "Node.h" +#include "Nodes/Status.h" #define MAX_TITLE_LENGTH 80 @@ -35,7 +36,9 @@ class AppInterface void DrawInterfaceNodes(DrawContext& context); void UpdateAddressBar(const URL& url); - void SetStatusMessage(const char* message); + + void SetStatusMessage(const char* message, StatusBarNode::StatusType type); + void ClearStatusMessage(StatusBarNode::StatusType type); void UpdatePageScrollBar(); diff --git a/src/Layout.cpp b/src/Layout.cpp index a5f531e..7927a02 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -85,26 +85,20 @@ void Layout::Update() } } -// if (lastNodeContext && !tableDepth && !lineStartNode) -// { -// page.GetApp().pageRenderer.MarkNodeLayoutComplete(lastNodeContext); -// } } 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 - //RecalculateLayout(); - page.GetApp().pageRenderer.MarkPageLayoutComplete(); App::Get().LoadImageNodeContent(page.GetRootNode()); + page.GetApp().ui.SetStatusMessage("Loading images...", StatusBarNode::GeneralStatus); isFinished = true; } - - //if (!currentNodeToProcess) - //{ - // page.GetApp().pageRenderer.MarkNodeLayoutComplete(page.GetRootNode()); - // page.GetApp().pageRenderer.MarkPageLayoutComplete(); - //} } void Layout::BreakNewLine() diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index d9a8f09..7f67951 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -57,6 +57,7 @@ Node* TextFieldNode::Construct(Allocator& allocator, const char* inValue, NodeCa if (inValue) { strncpy(buffer, inValue, DEFAULT_TEXT_FIELD_BUFFER_SIZE); + buffer[DEFAULT_TEXT_FIELD_BUFFER_SIZE - 1] = '\0'; } else { diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index fd30a92..1231b08 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -39,6 +39,8 @@ class ImageNode: public NodeHandler 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); }; diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index 3246947..b5c32ed 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -22,21 +22,30 @@ Node* LinkNode::Construct(Allocator& allocator, char* url) 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: { - LinkNode::Data* data = static_cast(node->data); if (data->url) { App::Get().OpenURL(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url); @@ -47,7 +56,6 @@ bool LinkNode::HandleEvent(Node* node, const Event& event) { if (event.key == KEYCODE_ENTER) { - LinkNode::Data* data = static_cast(node->data); if (data->url) { App::Get().OpenURL(URL::GenerateFromRelative(App::Get().page.pageURL.url, data->url).url); diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp index 1288b04..329c0e2 100644 --- a/src/Nodes/Status.cpp +++ b/src/Nodes/Status.cpp @@ -25,7 +25,22 @@ void StatusBarNode::Draw(DrawContext& context, Node* node) 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); - context.surface->DrawString(context, font, data->message, node->anchor.x + 1, node->anchor.y + 1, textColour, node->style.fontStyle); + + 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->style.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 index 8973bea..c0bfd2e 100644 --- a/src/Nodes/Status.h +++ b/src/Nodes/Status.h @@ -3,20 +3,45 @@ #include "../Node.h" -#define MAX_STATUS_BAR_MESSAGE_LENGTH 100 +#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: - Data() { message[0] = '\0'; } - char message[MAX_STATUS_BAR_MESSAGE_LENGTH]; + Data() { } + + struct Message + { + Message() + { + 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/Parser.cpp b/src/Parser.cpp index 26e3531..2e9bcda 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -713,9 +713,14 @@ 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; } diff --git a/src/Parser.h b/src/Parser.h index f53aaec..e40ceec 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -41,7 +41,7 @@ struct HTMLParseContext SectionElement::Type parseSection; }; -#define MAX_ATTRIBUTE_STRING_LENGTH 256 +#define MAX_ATTRIBUTE_STRING_LENGTH 1024 class AttributeParser { @@ -56,7 +56,7 @@ class AttributeParser private: bool IsWhiteSpace(char c); - char attributeStringBuffer[MAX_ATTRIBUTE_STRING_LENGTH]; + static char attributeStringBuffer[MAX_ATTRIBUTE_STRING_LENGTH]; char* key; char* value; char* attributeString; diff --git a/src/Render.cpp b/src/Render.cpp index bf7f0dd..803aaf8 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -355,9 +355,9 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) int nodeBottom = nodeTop + node->size.y; bool outsideOfWindow = (nodeTop > maxWinY) || (nodeBottom < minWinY); - if (nodeBottom > visiblePageHeight) + if (node->anchor.y + node->size.y > visiblePageHeight) { - visiblePageHeight = nodeBottom; + visiblePageHeight = node->anchor.y + node->size.y; expandedPage = true; } diff --git a/src/Stack.h b/src/Stack.h index baabb3b..40f991c 100644 --- a/src/Stack.h +++ b/src/Stack.h @@ -44,7 +44,7 @@ class Stack else { // TODO: handle allocation error more gracefully - Platform::FatalError("Could not push to stack: out of memory"); + //Platform::FatalError("Could not push to stack: out of memory"); } } } From bcd22c908349031ba93cd9c856f6123869fc1519 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 13:21:06 +0000 Subject: [PATCH 62/98] Fixes to form parsing --- src/Nodes/Form.cpp | 7 ++++++ src/Parser.cpp | 62 ++++++++++++++++++++++++++++++++-------------- src/Parser.h | 1 + src/Tags.cpp | 13 +++++----- src/Tags.h | 1 + 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp index 9707ca5..c38de60 100644 --- a/src/Nodes/Form.cpp +++ b/src/Nodes/Form.cpp @@ -55,6 +55,13 @@ void FormNode::SubmitForm(Node* node) 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 + diff --git a/src/Parser.cpp b/src/Parser.cpp index 2e9bcda..7a14a55 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -24,6 +24,7 @@ #include "Nodes/ImgNode.h" #include "Nodes/Break.h" #include "Nodes/Select.h" +#include "Nodes/Button.h" #include "Memory/Memory.h" #include "App.h" @@ -55,6 +56,18 @@ void HTMLParser::Reset() PushContext(page.GetRootNode(), nullptr); } +HTMLParseContext* HTMLParser::FindContextInStack(Node::Type nodeType) +{ + for (Stack::Entry* entry = contextStack.top; entry; entry = entry->prev) + { + if (entry->obj.node && entry->obj.node->type == nodeType) + { + return &entry->obj; + } + } + return nullptr; +} + void HTMLParser::PushContext(Node* node, const HTMLTagHandler* tag) { if (!node) @@ -220,27 +233,38 @@ void HTMLParser::FlushTextBuffer() { case ParseText: { - if (CurrentContext().node && CurrentContext().node->type == Node::Option) - { - OptionNode::Data* option = static_cast(CurrentContext().node->data); - option->text = MemoryManager::pageAllocator.AllocString(textBuffer); - } - else if(textBufferSize > 0) + if(textBufferSize > 0) { - switch (CurrentSection()) + HTMLParseContext* optionContext = FindContextInStack(Node::Option); + HTMLParseContext* buttonContext = FindContextInStack(Node::Button); + + if (optionContext) { - 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; + 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; + } } } } diff --git a/src/Parser.h b/src/Parser.h index e40ceec..a5a3b3a 100644 --- a/src/Parser.h +++ b/src/Parser.h @@ -77,6 +77,7 @@ class HTMLParser 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); diff --git a/src/Tags.cpp b/src/Tags.cpp index 112f0e5..7f20564 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -311,18 +311,17 @@ struct HTMLInputTag void ButtonTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - char* title = NULL; - AttributeParser attributes(attributeStr); while (attributes.Parse()) { - if (!stricmp(attributes.Key(), "title")) - { - title = MemoryManager::pageAllocator.AllocString(attributes.Value()); - } } - parser.EmitNode(ButtonNode::Construct(MemoryManager::pageAllocator, title, NULL)); + 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 diff --git a/src/Tags.h b/src/Tags.h index 8955177..4574e5f 100644 --- a/src/Tags.h +++ b/src/Tags.h @@ -176,6 +176,7 @@ 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 From 9532932a0adc524985521253d4b07087839610e8 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 14:08:14 +0000 Subject: [PATCH 63/98] Added support for hash tag jumps --- src/HTTP.cpp | 7 +++++++ src/Interface.cpp | 26 ++++++++++++++++++++++++++ src/Interface.h | 4 ++++ src/Parser.cpp | 7 +++++++ 4 files changed, 44 insertions(+) diff --git a/src/HTTP.cpp b/src/HTTP.cpp index 6c6059b..0261025 100644 --- a/src/HTTP.cpp +++ b/src/HTTP.cpp @@ -122,6 +122,13 @@ void HTTPRequest::Open(char* inURL) 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, ':'); diff --git a/src/Interface.cpp b/src/Interface.cpp index ae76213..b86eeea 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -37,6 +37,8 @@ AppInterface::AppInterface(App& inApp) : app(inApp) hoverNode = nullptr; focusedNode = nullptr; + jumpTagName = nullptr; + jumpNode = nullptr; } void AppInterface::Init() @@ -63,6 +65,8 @@ void AppInterface::Reset() { hoverNode = nullptr; } + jumpTagName = nullptr; + jumpNode = nullptr; ClearStatusMessage(StatusBarNode::HoverStatus); ClearStatusMessage(StatusBarNode::GeneralStatus); @@ -272,6 +276,26 @@ void AppInterface::Update() ScrollRelative(scrollDelta); //app.renderer.Scroll(scrollDelta); } + + if (jumpNode) + { + Node* node = App::Get().ui.jumpNode; + while (node && node->size.IsZero()) + { + node = node->GetNextInTree(); + } + + if (node) + { + int jumpPosition = node->anchor.y; + + if (app.page.layout.IsFinished() || jumpPosition + windowRect.height < app.pageRenderer.GetVisiblePageHeight()) + { + ScrollAbsolute(jumpPosition); + jumpNode = nullptr; + } + } + } } bool AppInterface::IsOverNode(Node* node, int x, int y) @@ -350,6 +374,8 @@ void AppInterface::UpdateAddressBar(const URL& url) { addressBarURL = url; addressBarNode->Redraw(); + + jumpTagName = strstr(url.url, "#"); } void AppInterface::UpdatePageScrollBar() diff --git a/src/Interface.h b/src/Interface.h index d1ac2b5..cfdd7df 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -60,6 +60,10 @@ class AppInterface Rect windowRect; + // For jumping to #name in the page + const char* jumpTagName; + Node* jumpNode; + private: void GenerateInterfaceNodes(); diff --git a/src/Parser.cpp b/src/Parser.cpp index 7a14a55..d87828e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -338,6 +338,13 @@ void HTMLParser::FlushTextBuffer() CurrentContext().node->style.alignment = ElementAlignment::Right; } } + if (!stricmp(attributes.Key(), "name")) + { + if (App::Get().ui.jumpTagName && !stricmp(attributes.Value(), App::Get().ui.jumpTagName + 1)) + { + App::Get().ui.jumpNode = CurrentContext().node; + } + } } } } From d54780c3653bb09c5fe403c6efb066851b918909 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 14:52:38 +0000 Subject: [PATCH 64/98] Fixed relative URL generation --- src/URL.h | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/URL.h b/src/URL.h index 658e0c9..0c5dac8 100644 --- a/src/URL.h +++ b/src/URL.h @@ -72,7 +72,7 @@ struct URL { strcpy(prevSlash, directory + 3); } - directory = strstr(prevSlash + 1, "/../"); + directory = strstr(prevSlash, "/../"); } // Fix & escape sequences @@ -165,9 +165,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) From 53a83012458629372a46da236dd5de6fac9cdbea Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 14:52:56 +0000 Subject: [PATCH 65/98] Shrink text field when needed --- src/Nodes/Field.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 7f67951..ba32d57 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -88,6 +88,11 @@ void TextFieldNode::GenerateLayout(Layout& layout, Node* node) 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(); From 14d2cb0efeac464b4bfdfbd4ed2e656ec0a9af06 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Mon, 25 Mar 2024 22:18:28 +0000 Subject: [PATCH 66/98] Improved image dimension calculation to include percentages --- project/DOS/DOS.vcxproj | 8 --- src/App.cpp | 11 +++- src/App.h | 1 + src/DOS/BIOSVid.cpp | 71 ++++++++------------------ src/DOS/BIOSVid.h | 3 -- src/DOS/CGA.cpp | 94 ---------------------------------- src/DOS/CGA.h | 39 --------------- src/DOS/DOSInput.cpp | 12 +++++ src/DOS/EGA.cpp | 108 ---------------------------------------- src/DOS/EGA.h | 56 --------------------- src/DOS/HP95LX.cpp | 91 --------------------------------- src/DOS/HP95LX.h | 38 -------------- src/DOS/Hercules.cpp | 9 +--- src/DOS/Hercules.h | 2 - src/DOS/Olivetti.cpp | 91 --------------------------------- src/DOS/Olivetti.h | 40 --------------- src/DOS/Source.cpp | 0 src/Image/Decoder.cpp | 40 +++++++++++++++ src/Image/Decoder.h | 2 + src/Image/Gif.cpp | 18 ++----- src/Image/Jpeg.cpp | 7 +-- src/Image/Png.cpp | 7 +-- src/Interface.cpp | 3 +- src/Interface.h | 2 + src/Layout.cpp | 37 +++++++++++++- src/Layout.h | 3 ++ src/Node.cpp | 21 ++++++++ src/Node.h | 15 ++++++ src/Nodes/Field.cpp | 9 +++- src/Nodes/Field.h | 1 + src/Nodes/ImgNode.cpp | 28 +++++++++-- src/Nodes/ImgNode.h | 6 ++- src/Platform.h | 6 ++- src/Tags.cpp | 108 +++++++++------------------------------- src/VidModes.cpp | 30 +++++------ src/VidModes.h | 1 + src/Windows/WinVid.cpp | 31 ++---------- src/Windows/WinVid.h | 2 - 38 files changed, 259 insertions(+), 792 deletions(-) delete mode 100644 src/DOS/CGA.cpp delete mode 100644 src/DOS/CGA.h delete mode 100644 src/DOS/EGA.cpp delete mode 100644 src/DOS/EGA.h delete mode 100644 src/DOS/HP95LX.cpp delete mode 100644 src/DOS/HP95LX.h delete mode 100644 src/DOS/Olivetti.cpp delete mode 100644 src/DOS/Olivetti.h delete mode 100644 src/DOS/Source.cpp diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 5995bc8..06c0270 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -99,14 +99,10 @@ - - - - @@ -129,18 +125,14 @@ - - - - diff --git a/src/App.cpp b/src/App.cpp index 20bd9f0..bd5181f 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -50,6 +50,7 @@ void App::Run(int argc, char* argv[]) char* targetURL = nullptr; config.loadImages = true; + config.dumpPage = false; if (argc > 1) { @@ -64,6 +65,10 @@ void App::Run(int argc, char* argv[]) { config.loadImages = false; } + else if (!stricmp(argv[n], "-dumppage")) + { + config.dumpPage = true; + } } } @@ -257,7 +262,10 @@ void LoadTask::Load(const char* targetURL) { request = Platform::network->CreateRequest(url.url); - //debugDumpFile = fopen("dump.htm", "wb"); + if (App::Get().config.dumpPage && this == &App::Get().pageLoadTask) + { + debugDumpFile = fopen("dump.htm", "wb"); + } } } @@ -474,4 +482,3 @@ void VideoDriver::InvertVideoOutput() Platform::input->ShowMouse(); } } - diff --git a/src/App.h b/src/App.h index 6e6137c..25cb7fa 100644 --- a/src/App.h +++ b/src/App.h @@ -61,6 +61,7 @@ struct Widget; struct AppConfig { bool loadImages : 1; + bool dumpPage : 1; }; class App diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp index 01eaa81..721e565 100644 --- a/src/DOS/BIOSVid.cpp +++ b/src/DOS/BIOSVid.cpp @@ -35,44 +35,44 @@ BIOSVideoDriver::BIOSVideoDriver() void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) { - videoModeInfo = inVideoModeInfo; + videoMode = inVideoModeInfo; startingScreenMode = GetScreenMode(); - screenWidth = videoModeInfo->screenWidth; - screenHeight = videoModeInfo->screenHeight; + screenWidth = videoMode->screenWidth; + screenHeight = videoMode->screenHeight; - if (!SetScreenMode(videoModeInfo->biosVideoMode)) + if (!SetScreenMode(videoMode->biosVideoMode)) { - Platform::FatalError("Could not set video mode: %d", videoModeInfo->biosVideoMode); + Platform::FatalError("Could not set video mode: %d", videoMode->biosVideoMode); return; } - if (videoModeInfo->biosVideoMode == CGA_COMPOSITE_MODE) + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) { SetScreenMode(6); outp(0x3D8, 0x1a); - Assets.LoadPreset(videoModeInfo->dataPackIndex); + Assets.LoadPreset(videoMode->dataPackIndex); } else { - Assets.LoadPreset(videoModeInfo->dataPackIndex); + Assets.LoadPreset(videoMode->dataPackIndex); } int screenPitch = 0; - if (videoModeInfo->bpp == 1) + if (videoMode->bpp == 1) { drawSurface = new DrawSurface_1BPP(screenWidth, screenHeight); screenPitch = screenWidth / 8; colourScheme = monochromeColourScheme; paletteLUT = nullptr; } - else if (videoModeInfo->bpp == 2) + else if (videoMode->bpp == 2) { drawSurface = new DrawSurface_2BPP(screenWidth, screenHeight); screenPitch = screenWidth / 4; - if (videoModeInfo->biosVideoMode == CGA_COMPOSITE_MODE) + if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) { paletteLUT = compositeCgaPaletteLUT; colourScheme = compositeCgaColourScheme; @@ -83,14 +83,14 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) colourScheme = cgaColourScheme; } } - else if (videoModeInfo->bpp == 4) + else if (videoMode->bpp == 4) { drawSurface = new DrawSurface_4BPP(screenWidth, screenHeight); screenPitch = screenWidth / 8; colourScheme = egaColourScheme; paletteLUT = egaPaletteLUT; } - else if (videoModeInfo->bpp == 8) + else if (videoMode->bpp == 8) { drawSurface = new DrawSurface_8BPP(screenWidth, screenHeight); screenPitch = screenWidth; @@ -137,27 +137,27 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) Platform::FatalError("Could not allocate memory for draw surface"); } - if (videoModeInfo->vramPage3) + 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(videoModeInfo->vramPage1, offset); - drawSurface->lines[y + 1] = (uint8_t*) MK_FP(videoModeInfo->vramPage2, offset); - drawSurface->lines[y + 2] = (uint8_t*) MK_FP(videoModeInfo->vramPage3, offset); - drawSurface->lines[y + 3] = (uint8_t*) MK_FP(videoModeInfo->vramPage4, offset); + 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 (videoModeInfo->vramPage2) + 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(videoModeInfo->vramPage1, offset); - drawSurface->lines[y + 1] = (uint8_t*)MK_FP(videoModeInfo->vramPage2, offset); + drawSurface->lines[y] = (uint8_t*)MK_FP(videoMode->vramPage1, offset); + drawSurface->lines[y + 1] = (uint8_t*)MK_FP(videoMode->vramPage2, offset); offset += screenPitch; } } @@ -167,7 +167,7 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) int offset = 0; for (int y = 0; y < screenHeight; y++) { - drawSurface->lines[y] = (uint8_t*)MK_FP(videoModeInfo->vramPage1, offset); + drawSurface->lines[y] = (uint8_t*)MK_FP(videoMode->vramPage1, offset); offset += screenPitch; } } @@ -217,30 +217,3 @@ bool BIOSVideoDriver::SetScreenMode(int screenMode) } -void BIOSVideoDriver::ScaleImageDimensions(int& width, int& height) -{ - if (screenWidth <= 320) - { - width = (3l * width) / 4; - height = (3l * height) / 4; - } - - height /= videoModeInfo->aspectRatio; - - int maxWidth = screenWidth - 16; - - if (width > maxWidth) - { - height = ((long)height * maxWidth) / width; - width = maxWidth; - } - - if (width == 0) - { - width = 1; - } - if (height == 0) - { - height = 1; - } -} diff --git a/src/DOS/BIOSVid.h b/src/DOS/BIOSVid.h index b324862..9c3bf26 100644 --- a/src/DOS/BIOSVid.h +++ b/src/DOS/BIOSVid.h @@ -27,14 +27,11 @@ class BIOSVideoDriver : public VideoDriver virtual void Init(VideoModeInfo* inVideoModeInfo); virtual void Shutdown(); - virtual void ScaleImageDimensions(int& width, int& height); - private: int GetScreenMode(); bool SetScreenMode(int screenMode); int startingScreenMode; - VideoModeInfo* videoModeInfo; }; #endif diff --git a/src/DOS/CGA.cpp b/src/DOS/CGA.cpp deleted file mode 100644 index bf0f458..0000000 --- a/src/DOS/CGA.cpp +++ /dev/null @@ -1,94 +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/Image.h" -#include "CGA.h" -#include "../Interface.h" -#include "../DataPack.h" -#include "../Draw/Surf1bpp.h" - -#define CGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) -#define CGA_PAGE2_VRAM_ADDRESS (uint8_t*) MK_FP(0xBA00, 0) - -#define BYTES_PER_LINE 80 - -CGADriver::CGADriver() -{ - screenWidth = 640; - screenHeight = 200; -} - -void CGADriver::Init(VideoModeInfo* videoMode) -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(6); - - Assets.Load("CGA.DAT"); - - DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); - for (int y = 0; y < screenHeight; y += 2) - { - drawSurface1BPP->lines[y] = (CGA_BASE_VRAM_ADDRESS) + (40 * y); - drawSurface1BPP->lines[y + 1] = (CGA_PAGE2_VRAM_ADDRESS) + (40 * y); - } - drawSurface = drawSurface1BPP; - - colourScheme.pageColour = 1; - colourScheme.linkColour = 0; - colourScheme.textColour = 0; - colourScheme.buttonColour = 1; - -} - -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); -} - -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 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 0c0a976..0000000 --- a/src/DOS/CGA.h +++ /dev/null @@ -1,39 +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. -// - -#ifndef _CGA_H_ -#define _CGA_H_ - -#include "../Platform.h" - -struct Image; - -class CGADriver : public VideoDriver -{ -public: - CGADriver(); - - virtual void Init(VideoModeInfo* videoMode); - virtual void Shutdown(); - - virtual void ScaleImageDimensions(int& width, int& height); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - int startingScreenMode; -}; - -#endif diff --git a/src/DOS/DOSInput.cpp b/src/DOS/DOSInput.cpp index db5f85b..aa857fb 100644 --- a/src/DOS/DOSInput.cpp +++ b/src/DOS/DOSInput.cpp @@ -77,6 +77,12 @@ bool DOSInputDriver::GetMouseButtonPress(int& x, int& y) int86(0x33, &inreg, &outreg); x = outreg.x.cx; y = outreg.x.dx; + + if (Platform::video->screenWidth == 320) + { + x /= 2; + } + return (outreg.x.bx > 0); } @@ -91,6 +97,12 @@ bool DOSInputDriver::GetMouseButtonRelease(int& x, int& y) int86(0x33, &inreg, &outreg); x = outreg.x.cx; y = outreg.x.dx; + + if (Platform::video->screenWidth == 320) + { + x /= 2; + } + return (outreg.x.bx > 0); } diff --git a/src/DOS/EGA.cpp b/src/DOS/EGA.cpp deleted file mode 100644 index 9948c61..0000000 --- a/src/DOS/EGA.cpp +++ /dev/null @@ -1,108 +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/Image.h" -#include "EGA.h" -#include "../DataPack.h" -#include "../Interface.h" -#include "../Draw/Surf1bpp.h" -#include "../Draw/Surf4bpp.h" -#include "../Colour.h" - -#define EGA_BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) -#define BYTES_PER_LINE 80 - -EGADriver::EGADriver() -{ - SetupVars("EGA.DAT", 0x10, 350); -} - -void EGADriver::SetupVars(const char* inAssetPackToUse, int inScreenMode, int inScreenHeight) -{ - assetPackToUse = inAssetPackToUse; - screenModeToUse = inScreenMode; - screenWidth = 640; - screenHeight = inScreenHeight; - paletteLUT = cgaPaletteLUT; -} - -void EGADriver::Init(VideoModeInfo* videoMode) -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(screenModeToUse); - - Assets.Load(assetPackToUse); - - /* - DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); - for (int y = 0; y < screenHeight; y++) - { - drawSurface1BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); - } - drawSurface = drawSurface1BPP; - */ - DrawSurface_4BPP* drawSurface4BPP = new DrawSurface_4BPP(screenWidth, screenHeight); - for (int y = 0; y < screenHeight; y++) - { - drawSurface4BPP->lines[y] = (EGA_BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); - } - drawSurface = drawSurface4BPP; - - colourScheme.pageColour = 0xf; - colourScheme.linkColour = 1; - colourScheme.textColour = 0; - colourScheme.buttonColour = 7; -} - -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::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 9530243..0000000 --- a/src/DOS/EGA.h +++ /dev/null @@ -1,56 +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. -// - -#ifndef _EGA_H_ -#define _EGA_H_ - -#include "../Platform.h" - -struct Image; - -class EGADriver : public VideoDriver -{ -public: - EGADriver(); - - virtual void Init(VideoModeInfo* videoMode); - virtual void Shutdown(); - - virtual void ScaleImageDimensions(int& width, int& height); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - int startingScreenMode; - -protected: - void SetupVars(const char* inAssetPackToUse, int inScreenMode, int inScreenHeight); - - int screenModeToUse; - const char* assetPackToUse; -}; - -class VGADriver : public EGADriver -{ -public: - VGADriver() - { -// SetupVars("DEFAULT.DAT", 0x11, 480); - SetupVars("DEFAULT.DAT", 0x12, 480); - } - virtual void ScaleImageDimensions(int& width, int& height) {} -}; - -#endif diff --git a/src/DOS/HP95LX.cpp b/src/DOS/HP95LX.cpp deleted file mode 100644 index 0c6a1ad..0000000 --- a/src/DOS/HP95LX.cpp +++ /dev/null @@ -1,91 +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 "../Interface.h" -#include "../DataPack.h" -#include "../Draw/Surf1bpp.h" - -#define USE_EGA_PREVIS 0 - -#if USE_EGA_PREVIS -#define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xA000, 0) -#define BYTES_PER_LINE 80 -#else -#define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB000, 0) -#define BYTES_PER_LINE 30 -#endif - -#define SCREEN_WIDTH 240 -#define SCREEN_HEIGHT 128 - -HP95LXVideoDriver::HP95LXVideoDriver() -{ - screenWidth = 240; - screenHeight = 128; -} - -void HP95LXVideoDriver::Init() -{ - startingScreenMode = GetScreenMode(); -#if USE_EGA_PREVIS - SetScreenMode(0x10); -#else - SetScreenMode(0x20); -#endif - - Assets.Load("LOWRES.DAT"); - - DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); - for (int y = 0; y < screenHeight; y++) - { - drawSurface1BPP->lines[y] = (BASE_VRAM_ADDRESS)+(BYTES_PER_LINE * y); - } - drawSurface = drawSurface1BPP; -} - -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); -} - -void HP95LXVideoDriver::ScaleImageDimensions(int& width, int& height) -{ -} diff --git a/src/DOS/HP95LX.h b/src/DOS/HP95LX.h deleted file mode 100644 index 82570aa..0000000 --- a/src/DOS/HP95LX.h +++ /dev/null @@ -1,38 +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. -// - -#ifndef _HP95LX_H_ -#define _HP95LX_H_ - -#include "../Platform.h" - -struct Image; - -class HP95LXVideoDriver : public VideoDriver -{ -public: - HP95LXVideoDriver(); - - virtual void Init(); - virtual void Shutdown(); - virtual void ScaleImageDimensions(int& width, int& height); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - int startingScreenMode; -}; - -#endif diff --git a/src/DOS/Hercules.cpp b/src/DOS/Hercules.cpp index 9dc4066..cfebc54 100644 --- a/src/DOS/Hercules.cpp +++ b/src/DOS/Hercules.cpp @@ -41,8 +41,9 @@ 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(VideoModeInfo* videoMode) +void HerculesDriver::Init(VideoModeInfo* inVideoMode) { + videoMode = inVideoMode; SetGraphicsMode(); Assets.Load("EGA.DAT"); @@ -97,9 +98,3 @@ void HerculesDriver::SetTextMode() outp(0x03B8, 0x08); FastMemSet(BASE_VRAM_ADDRESS, 0, 0x4000); } - -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 41549cb..815eeec 100644 --- a/src/DOS/Hercules.h +++ b/src/DOS/Hercules.h @@ -27,8 +27,6 @@ class HerculesDriver : public VideoDriver virtual void Init(VideoModeInfo* videoMode); virtual void Shutdown(); - virtual void ScaleImageDimensions(int& width, int& height); - private: void SetGraphicsMode(); void SetTextMode(); diff --git a/src/DOS/Olivetti.cpp b/src/DOS/Olivetti.cpp deleted file mode 100644 index 9d48de6..0000000 --- a/src/DOS/Olivetti.cpp +++ /dev/null @@ -1,91 +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/Image.h" -#include "Olivetti.h" -#include "../Interface.h" -#include "../DataPack.h" -#include "../Draw/Surf1bpp.h" - -#define BASE_VRAM_ADDRESS (uint8_t*) MK_FP(0xB800, 0) - -#define BYTES_PER_LINE 80 - -OlivettiDriver::OlivettiDriver(int inScreenModeToUse) -{ - screenModeToUse = inScreenModeToUse; - screenWidth = 640; - screenHeight = 400; -} - -void OlivettiDriver::Init(VideoModeInfo* videoMode) -{ - startingScreenMode = GetScreenMode(); - SetScreenMode(screenModeToUse); - - Assets.Load("DEFAULT.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; -} - -void OlivettiDriver::Shutdown() -{ - SetScreenMode(startingScreenMode); -} - -int OlivettiDriver::GetScreenMode() -{ - union REGS inreg, outreg; - inreg.h.ah = 0xf; - - int86(0x10, &inreg, &outreg); - - return (int)outreg.h.al; -} - -void OlivettiDriver::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 OlivettiDriver::ScaleImageDimensions(int& width, int& height) -{ - // Scale to 4:3 - height = (height * 5) / 12; -} diff --git a/src/DOS/Olivetti.h b/src/DOS/Olivetti.h deleted file mode 100644 index 5551b60..0000000 --- a/src/DOS/Olivetti.h +++ /dev/null @@ -1,40 +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. -// - -#ifndef _OLIVETTI_H_ -#define _OLIVETTI_H_ - -#include "../Platform.h" - -struct Image; - -class OlivettiDriver : public VideoDriver -{ -public: - OlivettiDriver(int screenModeToUse); - - virtual void Init(VideoModeInfo* videoMode); - virtual void Shutdown(); - - virtual void ScaleImageDimensions(int& width, int& height); - -private: - int GetScreenMode(); - void SetScreenMode(int screenMode); - - int screenModeToUse; - int startingScreenMode; -}; - -#endif diff --git a/src/DOS/Source.cpp b/src/DOS/Source.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 5970349..443ad48 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -6,6 +6,7 @@ #include "Png.h" #include "Decoder.h" #include "../Platform.h" +#include "../VidModes.h" #include "Image.h" #include "../Draw/Surface.h" #pragma warning(disable:4996) @@ -186,3 +187,42 @@ void ImageDecoder::Begin(Image* image, bool dimensionsOnly) outputImage = image; outputImage->bpp = Platform::video->drawSurface->bpp == 1 ? 1 : 8; } + +void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) +{ + sourceHeight /= Platform::video->GetVideoModeInfo()->aspectRatio; + sourceWidth *= Platform::video->GetVideoModeInfo()->zoom; + sourceHeight *= Platform::video->GetVideoModeInfo()->zoom; + + 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 (outputImage->height != 0) + { + // Specified by layout + calculatedHeight = outputImage->height; + + if (outputImage->width == 0) + { + calculatedWidth = ((long)sourceWidth * outputImage->height) / sourceHeight; + } + } + + if (calculatedWidth <= 0) + calculatedWidth = 1; + if (calculatedHeight <= 0) + calculatedHeight = 1; + + outputImage->width = calculatedWidth; + outputImage->height = calculatedHeight; +} diff --git a/src/Image/Decoder.h b/src/Image/Decoder.h index f03572d..464647a 100644 --- a/src/Image/Decoder.h +++ b/src/Image/Decoder.h @@ -87,6 +87,8 @@ class ImageDecoder 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; diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index d01d22a..5195659 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -56,20 +56,12 @@ void GifDecoder::Process(uint8_t* data, size_t dataLength) lineBufferDivider++; } - if (outputImage->width == 0 && outputImage->height == 0) - { - int width = header.width; - int height = header.height; - Platform::video->ScaleImageDimensions(width, height); - - outputImage->width = width; - outputImage->height = height; + CalculateImageDimensions(header.width, header.height); - if (onlyDownloadDimensions) - { - state = ImageDecoder::Success; - return; - } + if (onlyDownloadDimensions) + { + state = ImageDecoder::Success; + return; } if (outputImage->bpp == 1) diff --git a/src/Image/Jpeg.cpp b/src/Image/Jpeg.cpp index 7b42ae0..b1e83bc 100644 --- a/src/Image/Jpeg.cpp +++ b/src/Image/Jpeg.cpp @@ -95,12 +95,7 @@ void JpegDecoder::Process(uint8_t* data, size_t dataLength) { if (outputImage->width == 0 && outputImage->height == 0) { - int width = frameHeader.width; - int height = frameHeader.height; - Platform::video->ScaleImageDimensions(width, height); - - outputImage->width = width; - outputImage->height = height; + CalculateImageDimensions(frameHeader.width, frameHeader.height); if (onlyDownloadDimensions) { diff --git a/src/Image/Png.cpp b/src/Image/Png.cpp index 0551e61..4b39533 100644 --- a/src/Image/Png.cpp +++ b/src/Image/Png.cpp @@ -64,12 +64,7 @@ void PngDecoder::Process(uint8_t* data, size_t dataLength) { if (outputImage->width == 0 && outputImage->height == 0) { - int width = imageHeader.width; - int height = imageHeader.height; - Platform::video->ScaleImageDimensions(width, height); - - outputImage->width = width; - outputImage->height = height; + CalculateImageDimensions(imageHeader.width, imageHeader.height); if (onlyDownloadDimensions) { diff --git a/src/Interface.cpp b/src/Interface.cpp index b86eeea..10f1f9e 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -274,7 +274,6 @@ void AppInterface::Update() if (scrollDelta) { ScrollRelative(scrollDelta); - //app.renderer.Scroll(scrollDelta); } if (jumpNode) @@ -455,6 +454,8 @@ void AppInterface::GenerateInterfaceNodes() 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; } void AppInterface::DrawInterfaceNodes(DrawContext& context) diff --git a/src/Interface.h b/src/Interface.h index cfdd7df..b09881e 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -54,6 +54,7 @@ class AppInterface int GetScrollPositionY() { return scrollPositionY; } void ScrollRelative(int delta); void ScrollAbsolute(int position); + int GetPageHeightForDimensionScaling() { return pageHeightForDimensionScaling; } Node* addressBarNode; URL addressBarURL; @@ -98,6 +99,7 @@ class AppInterface Node* scrollBarNode; int scrollPositionY; + int pageHeightForDimensionScaling; char titleBuffer[MAX_TITLE_LENGTH]; }; diff --git a/src/Layout.cpp b/src/Layout.cpp index 7927a02..03ddf11 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -1,3 +1,4 @@ +#include "VidModes.h" #include "Layout.h" #include "Page.h" #include "Platform.h" @@ -31,6 +32,8 @@ void Layout::Update() { while (currentNodeToProcess && currentNodeToProcess != lastNodeToProcess) { + currentNodeToProcess->Handler().BeginLayoutContext(*this, currentNodeToProcess); + if (currentNodeToProcess->type == Node::Image && App::Get().config.loadImages) { ImageNode::Data* imageData = static_cast(currentNodeToProcess->data); @@ -45,7 +48,6 @@ void Layout::Update() } } - currentNodeToProcess->Handler().BeginLayoutContext(*this, currentNodeToProcess); currentNodeToProcess->Handler().GenerateLayout(*this, currentNodeToProcess); if (currentNodeToProcess->firstChild) @@ -309,3 +311,36 @@ void Layout::MarkParsingComplete() { lastNodeToProcess = nullptr; } + +int Layout::CalculateWidth(ExplicitDimension explicitWidth) +{ + if (explicitWidth.IsSet()) + { + if (explicitWidth.IsPercentage()) + { + return ((long)MaxAvailableWidth() * explicitWidth.Value()) / 100; + } + else + { + return explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom; + } + } + return 0; +} + +int Layout::CalculateHeight(ExplicitDimension explicitHeight) +{ + if (explicitHeight.IsSet()) + { + if (explicitHeight.IsPercentage()) + { + return ((long)App::Get().ui.GetPageHeightForDimensionScaling() * explicitHeight.Value()) / 100; + } + else + { + return (explicitHeight.Value() / Platform::video->GetVideoModeInfo()->aspectRatio) * Platform::video->GetVideoModeInfo()->zoom; + } + } + return 0; + +} diff --git a/src/Layout.h b/src/Layout.h index 7294344..46f8fc7 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -38,6 +38,9 @@ class Layout void RecalculateLayout(); void RecalculateLayoutForNode(Node* node); + int CalculateWidth(ExplicitDimension explicitWidth); + int CalculateHeight(ExplicitDimension explicitHeight); + Coord GetCursor(int lineHeight = 0) { Coord result = Cursor(); diff --git a/src/Node.cpp b/src/Node.cpp index 083db87..6ca2bbc 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -309,3 +309,24 @@ bool Node::IsChildOf(Node* potentialParent) 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; +} diff --git a/src/Node.h b/src/Node.h index 7405898..c93ee3c 100644 --- a/src/Node.h +++ b/src/Node.h @@ -47,6 +47,21 @@ struct Rect 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 { diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index ba32d57..09f5eec 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -85,7 +85,14 @@ void TextFieldNode::GenerateLayout(Layout& layout, Node* node) TextFieldNode::Data* data = static_cast(node->data); Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - node->size.x = Platform::video->screenWidth / 3; + 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) diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 425149c..62074a5 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -26,6 +26,7 @@ class TextFieldNode : public NodeHandler int bufferSize; char* name; NodeCallbackFunction onSubmit; + ExplicitDimension explicitWidth; }; static Node* Construct(Allocator& allocator, const char* text, NodeCallbackFunction onSubmit = NULL); diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index e7c9ca1..541313c 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -72,15 +72,35 @@ Node* ImageNode::Construct(Allocator& allocator) 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->explicitHeight.IsSet()) + { + data->image.height = layout.CalculateHeight(data->explicitHeight); + } + } +} + void ImageNode::GenerateLayout(Layout& layout, Node* node) { ImageNode::Data* data = static_cast(node->data); - if (!data->AreDimensionsLocked() && data->image.width > layout.MaxAvailableWidth()) + if (!data->AreDimensionsLocked()) { - int imageWidth = data->image.width; - data->image.width = layout.MaxAvailableWidth(); - data->image.height = (uint16_t)(((long)data->image.height * data->image.width) / imageWidth); + 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; diff --git a/src/Nodes/ImgNode.h b/src/Nodes/ImgNode.h index 1231b08..cd85987 100644 --- a/src/Nodes/ImgNode.h +++ b/src/Nodes/ImgNode.h @@ -22,17 +22,21 @@ class ImageNode: public NodeHandler { public: Data() : source(nullptr), altText(nullptr), state(WaitingToDownload) {} - bool HasDimensions() { return image.width > 0; } + 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; diff --git a/src/Platform.h b/src/Platform.h index 70db2a0..97a17d8 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -30,9 +30,8 @@ class VideoDriver virtual void Init(VideoModeInfo* videoMode) = 0; virtual void Shutdown() = 0; - virtual void ScaleImageDimensions(int& width, int& height) {} - void InvertVideoOutput(); + VideoModeInfo* GetVideoModeInfo() { return videoMode; } int screenWidth; int screenHeight; @@ -40,6 +39,9 @@ class VideoDriver DrawSurface* drawSurface; ColourScheme colourScheme; uint8_t* paletteLUT; + +protected: + VideoModeInfo* videoMode; }; typedef uint8_t NetworkAddress[4]; // An IPv4 address is 4 bytes diff --git a/src/Tags.cpp b/src/Tags.cpp index 7f20564..b8732e3 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -330,6 +330,7 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const char* name = NULL; HTMLInputTag::Type type = HTMLInputTag::Text; int bufferLength = 80; + ExplicitDimension width; AttributeParser attributes(attributeStr); while (attributes.Parse()) @@ -357,6 +358,10 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { name = MemoryManager::pageAllocator.AllocString(attributes.Value()); } + if (!stricmp(attributes.Key(), "width")) + { + width = ExplicitDimension::Parse(attributes.Value()); + } } switch (type) @@ -374,6 +379,7 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { TextFieldNode::Data* fieldData = static_cast(fieldNode->data); fieldData->name = name; + fieldData->explicitWidth = width; parser.EmitNode(fieldNode); } } @@ -418,103 +424,37 @@ void FormTagHandler::Close(HTMLParser& parser) const void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - AttributeParser attributes(attributeStr); - int width = -1; - int height = -1; - char* altText = nullptr; - char* src = nullptr; - - while (attributes.Parse()) - { - if (!stricmp(attributes.Key(), "alt")) - { - altText = (char*) attributes.Value(); - } - else if (!stricmp(attributes.Key(), "src")) - { - src = (char*)attributes.Value(); - } - else if (!stricmp(attributes.Key(), "width")) - { - width = atoi(attributes.Value()); - } - else if (!stricmp(attributes.Key(), "height")) - { - height = atoi(attributes.Value()); - } - } - - if (width == -1) - { - if (height != -1) - { - width = height; - } - } - if (height == -1) - { - if (width != -1) - { - height = width; - } - } - Node* imageNode = ImageNode::Construct(MemoryManager::pageAllocator); if (imageNode) { ImageNode::Data* data = static_cast(imageNode->data); if (data) { - if (src) - { - data->source = MemoryManager::pageAllocator.AllocString(src); - } - - if (width != -1 && height != -1) - { - Platform::video->ScaleImageDimensions(width, height); - imageNode->size.x = width; - imageNode->size.y = height; - data->image.width = width; - data->image.height = height; - } - else - { - imageNode->size.x = 16; - imageNode->size.y = 16; - data->image.width = 0; - data->image.height = 0; - } + AttributeParser attributes(attributeStr); - if (altText) + while (attributes.Parse()) { - data->altText = MemoryManager::pageAllocator.AllocString(altText); + if (!stricmp(attributes.Key(), "alt")) + { + data->altText = MemoryManager::pageAllocator.AllocString(attributes.Value()); + } + 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()); + } } parser.EmitNode(imageNode); } } - - /* - if (width == -1 && height == -1) - { - Image* imageIcon = Assets.imageIcon; - if (imageIcon) - { - parser.EmitImage(imageIcon, imageIcon->width, imageIcon->height); - } - - if (altText) - { - parser.EmitText(altText); - } - } - else - { - Platform::video->ScaleImageDimensions(width, height); - parser.EmitImage(NULL, width, height); - } - */ } void MetaTagHandler::Open(class HTMLParser& parser, char* attributeStr) const diff --git a/src/VidModes.cpp b/src/VidModes.cpp index 41078b9..96c90ea 100644 --- a/src/VidModes.cpp +++ b/src/VidModes.cpp @@ -4,21 +4,21 @@ VideoModeInfo VideoModeList[] = { - // name mode width height bpp aspect data pack vram1 vram2 vram3 vram4 - { "640x200 monochrome (CGA)", 6, 640, 200, 1, 2.4f, DataPack::CGA, 0xb800, 0xba00 }, - { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 1.0f, DataPack::Default, 0xb800, 0xba00 }, - { "320x200 4 colours (CGA)", 5, 320, 200, 2, 1.2f, DataPack::Lowres, 0xb800, 0xba00 }, - { "320x200 16 colours (Composite CGA)", 4, 320, 200, 2, 1.2f, DataPack::CGA, 0xb800, 0xba00 }, - { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 2.4f, DataPack::CGA, 0xa000, }, - { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 1.37f, DataPack::EGA, 0xa000, }, - { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 1.37f, DataPack::EGA, 0xa000, }, - { "640x480 monochrome (VGA)", 0x11, 640, 480, 1, 1.0f, DataPack::Default, 0xa000, }, - { "640x480 16 colours (VGA)", 0x12, 640, 480, 4, 1.0f, DataPack::Default, 0xa000, }, - { "320x200 256 colours (VGA)", 0x13, 320, 200, 8, 1.2f, DataPack::Lowres, 0xa000, }, - { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, 1, 1.55f, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, - { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, 1, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, - { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, 1, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, - { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, 1, 1.0f, DataPack::Lowres, 0xb000, }, + // name mode width height bpp aspect zoom data pack vram1 vram2 vram3 vram4 + { "640x200 monochrome (CGA)", 6, 640, 200, 1, 2.4f, 1.0f, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 1.0f, 1.0f, DataPack::Default, 0xb800, 0xba00 }, + { "320x200 4 colours (CGA)", 5, 320, 200, 2, 1.2f, 0.7f, DataPack::Lowres, 0xb800, 0xba00 }, + { "320x200 16 colours (Composite CGA)", 4, 320, 200, 2, 1.2f, 0.7f, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 2.4f, 1.0f, DataPack::CGA, 0xa000, }, + { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 1.37f, 1.0f, DataPack::EGA, 0xa000, }, + { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 1.37f, 1.0f, DataPack::EGA, 0xa000, }, + { "640x480 monochrome (VGA)", 0x11, 640, 480, 1, 1.0f, 1.0f, DataPack::Default, 0xa000, }, + { "640x480 16 colours (VGA)", 0x12, 640, 480, 4, 1.0f, 1.0f, DataPack::Default, 0xa000, }, + { "320x200 256 colours (VGA)", 0x13, 320, 200, 8, 1.2f, 0.7f, DataPack::Lowres, 0xa000, }, + { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, 1, 1.55f, 1.0f, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, + { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, 1, 1.0f, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, 1, 1.0f, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, 1, 1.0f, 0.5f, DataPack::Lowres, 0xb000, }, { nullptr } }; diff --git a/src/VidModes.h b/src/VidModes.h index ade79db..2740dbc 100644 --- a/src/VidModes.h +++ b/src/VidModes.h @@ -15,6 +15,7 @@ struct VideoModeInfo int screenHeight; uint8_t bpp; float aspectRatio; + float zoom; DataPack::Preset dataPackIndex : 8; uint16_t vramPage1; uint16_t vramPage2; diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 5235c76..00b58a2 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -90,8 +90,10 @@ const RGBQUAD egaPalette[] = { 0xFF, 0xFF, 0xFF }, // Entry 15 - White }; -void WindowsVideoDriver::Init(VideoModeInfo* videoMode) +void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) { + videoMode = inVideoMode; + screenWidth = videoMode->screenWidth; screenHeight = videoMode->screenHeight; verticalScale = videoMode->aspectRatio; // ((screenWidth * 3.0f) / 4.0f) / screenHeight; @@ -355,30 +357,3 @@ void WindowsVideoDriver::Paint(HWND hwnd) EndPaint(hwnd, &ps); } -void WindowsVideoDriver::ScaleImageDimensions(int& width, int& height) -{ - if (screenWidth <= 320) - { - width /= 2; - height /= 2; - } - - height = (height / verticalScale); - - int maxWidth = screenWidth - 16; - - if (width > maxWidth) - { - height = (height * maxWidth) / width; - width = maxWidth; - } - - if (width == 0) - { - width = 1; - } - if (height == 0) - { - height = 1; - } -} diff --git a/src/Windows/WinVid.h b/src/Windows/WinVid.h index e40decb..d30d832 100644 --- a/src/Windows/WinVid.h +++ b/src/Windows/WinVid.h @@ -28,8 +28,6 @@ class WindowsVideoDriver : public VideoDriver virtual void Shutdown(); virtual void ClearScreen(); - virtual void ScaleImageDimensions(int& width, int& height) override; - void Paint(HWND hwnd); From 238adffb9168f7da6537af8faacced349b5571db Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 26 Mar 2024 14:42:19 +0000 Subject: [PATCH 67/98] Parsing of table widths and table layout generation --- src/Nodes/Table.cpp | 244 +++++++++++++++++++++++++++++++++++++++----- src/Nodes/Table.h | 11 ++ src/Tags.cpp | 16 ++- 3 files changed, 241 insertions(+), 30 deletions(-) diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 145e77e..a0e0c8f 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -168,66 +168,258 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } + int minCellWidth = 16; + for (int n = 0; n < data->numColumns; n++) { - data->columns[n].preferredWidth = 16; + data->columns[n].Clear(); + data->columns[n].preferredWidth = minCellWidth; } - for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) + // 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 (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) + for (TableRowNode::Data* row = data->firstRow; row; row = row->nextRow) { - int preferredWidth = (2 * data->cellPadding + cell->node->size.x) / cell->columnSpan; - - for (int i = 0; i < cell->columnSpan; i++) + for (TableCellNode::Data* cell = row->firstCell; cell; cell = cell->nextCell) { - if (data->columns[cell->columnIndex + i].preferredWidth < preferredWidth) + 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()) { - data->columns[cell->columnIndex + i].preferredWidth = preferredWidth; + if (cell->explicitWidth.IsPercentage()) + { + explicitWidthPercentage = cell->explicitWidth.Value(); + } + else + { + explicitWidth = cell->explicitWidth.Value(); + } + } + + 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 = 0; + 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; - int totalPreferredWidth = 0; - for (int i = 0; i < data->numColumns; i++) - { - totalPreferredWidth += data->columns[i].preferredWidth; - } - int maxAvailableWidthForCells = layout.MaxAvailableWidth() - totalCellSpacing; - if (totalPreferredWidth > maxAvailableWidthForCells) + 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++) { - data->columns[i].preferredWidth = ((long)maxAvailableWidthForCells * data->columns[i].preferredWidth) / totalPreferredWidth; + 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; + float z = data->columns[i].explicitWidthPercentage / 100.0f; + data->columns[i].calculatedWidth = (z * totalPreferredWidth) / (1.0f - z); + totalPreferredWidth += data->columns[i].calculatedWidth; + changesMade = true; + } + } + } + + if (!changesMade) + break; + } + + if (totalPreferredWidth <= maxAvailableWidthForCells) + { + data->totalWidth = totalPreferredWidth + totalCellSpacing; + node->size.x = data->totalWidth; } } - int totalWidth = totalCellSpacing; - for (int i = 0; i < data->numColumns; i++) + if (data->explicitWidth.IsSet() || !data->totalWidth) { - totalWidth += data->columns[i].preferredWidth; + // 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; + 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) + { + 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) + { + data->columns[i].calculatedWidth = ((long)widthRemaining * data->columns[i].preferredWidth) / totalUnsetWidth; + } + totalCellsWidth += data->columns[i].calculatedWidth; + } + } + + if (totalCellsWidth < maxAvailableWidthForCells) + { + data->columns[data->numColumns - 1].calculatedWidth += maxAvailableWidthForCells - totalCellsWidth; + } } - data->totalWidth = totalWidth; - node->size.x = totalWidth; layout.PushCursor(); layout.PushLayout(); int available = layout.AvailableWidth(); - if (totalWidth < available) + if (data->totalWidth < available) { int alignmentPadding = 0; if (node->style.alignment == ElementAlignment::Center) { - alignmentPadding = (available - totalWidth) / 2; + alignmentPadding = (available - data->totalWidth) / 2; } else if (node->style.alignment == ElementAlignment::Right) { - alignmentPadding = (available - totalWidth); + alignmentPadding = (available - data->totalWidth); } if (alignmentPadding) { @@ -420,10 +612,10 @@ void TableCellNode::BeginLayoutContext(Layout& layout, Node* node) { node->anchor = layout.Cursor(); - node->size.x = tableData->columns[data->columnIndex].preferredWidth; + 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].preferredWidth + tableData->cellSpacing; + node->size.x += tableData->columns[data->columnIndex + n].calculatedWidth + tableData->cellSpacing; } layout.RestrictHorizontal(node->size.x); diff --git a/src/Nodes/Table.h b/src/Nodes/Table.h index 1911982..f7cbd1b 100644 --- a/src/Nodes/Table.h +++ b/src/Nodes/Table.h @@ -19,6 +19,7 @@ class TableCellNode : public NodeHandler int rowSpan; uint8_t bgColour; TableCellNode::Data* nextCell; + ExplicitDimension explicitWidth; }; static Node* Construct(Allocator& allocator, bool isHeader); @@ -56,8 +57,17 @@ class TableNode : public NodeHandler public: struct ColumnInfo { + void Clear() + { + preferredWidth = 0; + calculatedWidth = 0; + explicitWidthPixels = 0; + explicitWidthPercentage = 0; + } int preferredWidth; int calculatedWidth; + int explicitWidthPixels; + int explicitWidthPercentage; }; enum State { @@ -83,6 +93,7 @@ class TableNode : public NodeHandler TableCellNode::Data** cells; uint8_t bgColour; int lastAvailableWidth; + ExplicitDimension explicitWidth; }; static Node* Construct(Allocator& allocator); diff --git a/src/Tags.cpp b/src/Tags.cpp index b8732e3..ebe5bb1 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -527,18 +527,22 @@ void TableTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { tableNodeData->border = attributes.ValueAsInt(); } - if (!stricmp(attributes.Key(), "cellpadding")) + else if (!stricmp(attributes.Key(), "cellpadding")) { tableNodeData->cellPadding = attributes.ValueAsInt(); } - if (!stricmp(attributes.Key(), "cellSpacing")) + else if (!stricmp(attributes.Key(), "cellSpacing")) { tableNodeData->cellSpacing = attributes.ValueAsInt(); } - if (!stricmp(attributes.Key(), "bgcolor")) + 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); @@ -575,7 +579,7 @@ void TableCellTagHandler::Open(class HTMLParser& parser, char* attributeStr) con { cellData->bgColour = HTMLParser::ParseColourCode(attributes.Value()); } - if (!stricmp(attributes.Key(), "colspan")) + else if (!stricmp(attributes.Key(), "colspan")) { cellData->columnSpan = attributes.ValueAsInt(); if (cellData->columnSpan <= 0) @@ -583,6 +587,10 @@ void TableCellTagHandler::Open(class HTMLParser& parser, char* attributeStr) con cellData->columnSpan = 1; } } + else if (!stricmp(attributes.Key(), "width")) + { + cellData->explicitWidth = ExplicitDimension::Parse(attributes.Value()); + } } parser.PushContext(cellNode, this); } From f49cb4fec0c15f09ff7f3d7e85b018ed9909e3cf Mon Sep 17 00:00:00 2001 From: jhhoward Date: Tue, 26 Mar 2024 14:56:03 +0000 Subject: [PATCH 68/98] Fixes for divide by zero errors --- src/Image/Decoder.cpp | 14 +++++++++----- src/Nodes/ImgNode.cpp | 4 ++++ src/Nodes/Table.cpp | 5 +++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 443ad48..2a7206c 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -194,6 +194,11 @@ void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) sourceWidth *= Platform::video->GetVideoModeInfo()->zoom; sourceHeight *= Platform::video->GetVideoModeInfo()->zoom; + if (sourceHeight < 1) + sourceHeight = 1; + if (sourceWidth < 1) + sourceWidth = 1; + int calculatedWidth = sourceWidth; int calculatedHeight = sourceHeight; @@ -205,6 +210,8 @@ void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) if (outputImage->height == 0) { calculatedHeight = ((long)sourceHeight * outputImage->width) / sourceWidth; + if (calculatedHeight <= 0) + calculatedHeight = 1; } } if (outputImage->height != 0) @@ -215,14 +222,11 @@ void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) if (outputImage->width == 0) { calculatedWidth = ((long)sourceWidth * outputImage->height) / sourceHeight; + if (calculatedWidth <= 0) + calculatedWidth = 1; } } - if (calculatedWidth <= 0) - calculatedWidth = 1; - if (calculatedHeight <= 0) - calculatedHeight = 1; - outputImage->width = calculatedWidth; outputImage->height = calculatedHeight; } diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 541313c..702ad69 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -81,10 +81,14 @@ void ImageNode::BeginLayoutContext(Layout& layout, Node* node) 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; } } } diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index a0e0c8f..80cd0dd 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -2,6 +2,7 @@ #include "../Layout.h" #include "../Memory/Memory.h" #include "../Draw/Surface.h" +#include "../VidModes.h" #include "Table.h" /* @@ -202,7 +203,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } else { - explicitWidth = cell->explicitWidth.Value(); + explicitWidth = cell->explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom; } } @@ -223,7 +224,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } else { - int columnsPreferredWidth = 0; + int columnsPreferredWidth = data->cellSpacing * (cell->columnSpan - 1); int columnsExplicitWidthPercentage = 0; int columnsExplicitWidthPixels = 0; From 204d77295b914ab08f9a8a9a763de51f10f17a91 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Wed, 27 Mar 2024 20:55:00 +0000 Subject: [PATCH 69/98] Fix for table width calculation --- src/Nodes/Table.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 80cd0dd..e153757 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -177,6 +177,12 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) data->columns[n].preferredWidth = minCellWidth; } + 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 @@ -207,6 +213,15 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } + if (preferredWidth > maxConstrainedTableWidth) + { + preferredWidth = maxConstrainedTableWidth; + } + if (explicitWidth > maxConstrainedTableWidth) + { + explicitWidth = maxConstrainedTableWidth; + } + if (pass == 0) { if (data->columns[cell->columnIndex].preferredWidth < preferredWidth) From 69c61f5dddca31270726b635a1d213a279eb2197 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 28 Mar 2024 12:59:17 +0000 Subject: [PATCH 70/98] Refactored page renderer to correctly render overlapping items. Render images 8 lines at a time. In some cases reduces memory and CPU usage --- src/Node.cpp | 1 - src/Node.h | 2 - src/Render.cpp | 317 +++++++++++++++++++++++++++++-------------------- src/Render.h | 55 +++++++-- 4 files changed, 230 insertions(+), 145 deletions(-) diff --git a/src/Node.cpp b/src/Node.cpp index 6ca2bbc..8db0a54 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -47,7 +47,6 @@ Node::Node(Type inType, void* inData) , parent(nullptr) , next(nullptr) , firstChild(nullptr) - , nextNodeToRender(nullptr) , data(inData) { anchor.Clear(); diff --git a/src/Node.h b/src/Node.h index c93ee3c..c5c70ee 100644 --- a/src/Node.h +++ b/src/Node.h @@ -130,8 +130,6 @@ class Node Node* next; Node* firstChild; - Node* nextNodeToRender; - void* data; protected: diff --git a/src/Render.cpp b/src/Render.cpp index 803aaf8..763da33 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -4,6 +4,7 @@ #include "Interface.h" #include "Draw/Surface.h" #include "DataPack.h" +#include "Nodes/ImgNode.h" PageRenderer::PageRenderer(App& inApp) : app(inApp) @@ -23,7 +24,7 @@ void PageRenderer::InitContext(DrawContext& context) context.clipLeft = windowRect.x; context.clipRight = windowRect.x + windowRect.width; context.clipTop = windowRect.y; - context.clipBottom = windowRect.y; + context.clipBottom = windowRect.y + windowRect.height; context.drawOffsetX = windowRect.x; context.drawOffsetY = windowRect.y; } @@ -50,26 +51,11 @@ void PageRenderer::RefreshAll() DrawContext clearContext; InitContext(clearContext); - clearContext.clipBottom = windowRect.y + windowRect.height; clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); Platform::input->ShowMouse(); - upperContext.clipTop = windowRect.y; - upperContext.clipBottom = upperContext.clipTop; - lowerContext.clipBottom = windowRect.y + windowRect.height; - lowerContext.clipTop = upperContext.clipBottom; - upperContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); - lowerContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); - - // Clear the render queue - for (Node* node = renderQueueHead; node;) - { - Node* next = node->nextNodeToRender; - node->nextNodeToRender = nullptr; - node = next; - } - renderQueueHead = renderQueueTail = nullptr; + renderQueue.Reset(); FindOverlappingNodesInScreenRegion(windowRect.y, windowRect.y + windowRect.height); } @@ -82,27 +68,35 @@ void PageRenderer::OnPageScroll(int scrollDelta) } Rect& windowRect = app.ui.windowRect; - upperContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); - lowerContext.drawOffsetY = windowRect.y - app.ui.GetScrollPositionY(); int minWinY = windowRect.y; int maxWinY = windowRect.y + windowRect.height; - if (scrollDelta > 0) + for (int i = renderQueue.head; i < renderQueue.tail; i++) { - lowerContext.clipTop -= scrollDelta; - if (lowerContext.clipTop < minWinY) - lowerContext.clipTop = minWinY; + RenderQueue::Item* item = &renderQueue.items[i]; + item->lowerClip -= scrollDelta; + item->upperClip -= scrollDelta; - upperContext.clipBottom -= scrollDelta; - if (upperContext.clipBottom < minWinY) - upperContext.clipBottom = minWinY; + if (item->upperClip < minWinY) + item->upperClip = minWinY; + if (item->lowerClip > maxWinY) + item->lowerClip = maxWinY; - if (lowerContext.clipTop < upperContext.clipBottom) + if (item->lowerClip <= item->upperClip) { - lowerContext.clipTop = upperContext.clipBottom; + // 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; @@ -110,19 +104,6 @@ void PageRenderer::OnPageScroll(int scrollDelta) } else if (scrollDelta < 0) { - upperContext.clipBottom -= scrollDelta; - if (upperContext.clipBottom > maxWinY) - upperContext.clipBottom = maxWinY; - - lowerContext.clipTop -= scrollDelta; - if (lowerContext.clipTop > maxWinY) - lowerContext.clipTop = maxWinY; - - if (upperContext.clipBottom > lowerContext.clipTop) - { - upperContext.clipBottom = lowerContext.clipTop; - } - int bottom = minWinY - scrollDelta; if (bottom > maxWinY) bottom = maxWinY; @@ -160,9 +141,12 @@ void PageRenderer::OnPageScroll(int scrollDelta) Platform::input->ShowMouse(); } -bool PageRenderer::IsRenderableNode(Node::Type type) +bool PageRenderer::IsRenderableNode(Node* node) { - switch (type) + if (node->size.IsZero()) + return false; + + switch (node->type) { case Node::Style: case Node::Section: @@ -174,22 +158,34 @@ bool PageRenderer::IsRenderableNode(Node::Type type) } } +int PageRenderer::GetDrawOffsetY() +{ + Rect& windowRect = app.ui.windowRect; + return windowRect.y - app.ui.GetScrollPositionY(); +} + void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) { - int drawOffsetY = upperContext.drawOffsetY; + int drawOffsetY = GetDrawOffsetY(); if (!lastCompleteNode) return; for(Node* node = app.page.GetRootNode(); node; node = node->GetNextInTree()) { - if(IsRenderableNode(node->type)) + if(IsRenderableNode(node)) { - bool outsideOfRegion = (node->anchor.y + drawOffsetY > bottom) || (node->anchor.y + node->size.y + drawOffsetY < top); + int nodeTop = node->anchor.y + drawOffsetY; + int nodeBottom = nodeTop + node->size.y; + + if (nodeTop < top) + nodeTop = top; + if (nodeBottom > bottom) + nodeBottom = bottom; - if (!outsideOfRegion) + if(nodeBottom - nodeTop > 0) { - AddToQueue(node); + AddToQueue(node, nodeTop, nodeBottom); } } @@ -200,17 +196,8 @@ void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) void PageRenderer::Reset() { - renderQueueHead = nullptr; - renderQueueTail = nullptr; - renderQueueSize = 0; + renderQueue.Reset(); lastCompleteNode = nullptr; - - InitContext(upperContext); - InitContext(lowerContext); - - Rect& windowRect = app.ui.windowRect; - lowerContext.clipBottom = lowerContext.clipTop = windowRect.y + windowRect.height; - visiblePageHeight = 0; } @@ -229,69 +216,151 @@ bool PageRenderer::DoesOverlapWithContext(Node* node, DrawContext& context) void PageRenderer::Update() { -// if (rand() % 256) -// return; +#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 - bool renderedSomething = false; + int itemsToRender = 5; - while(renderQueueHead && !renderedSomething) + DrawContext itemContext; + InitContext(itemContext); + itemContext.drawOffsetY = GetDrawOffsetY(); + + while(renderQueue.Size() && itemsToRender) { - Node* toRender = renderQueueHead; + itemsToRender--; + + RenderQueue::Item* item = &renderQueue.items[renderQueue.head]; + Node* toRender = item->node; + bool finishedRendering = true; Platform::input->HideMouse(); - if (upperContext.clipTop != upperContext.clipBottom && DoesOverlapWithContext(toRender, upperContext)) - { - toRender->Handler().Draw(upperContext, toRender); - renderedSomething = true; - } - if (lowerContext.clipTop != lowerContext.clipBottom && DoesOverlapWithContext(toRender, lowerContext)) + itemContext.clipTop = item->upperClip; + itemContext.clipBottom = item->lowerClip; + + if (toRender->type == Node::Image) { - toRender->Handler().Draw(lowerContext, toRender); - renderedSomething = true; + // 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; + } + } } - Platform::input->ShowMouse(); - renderQueueHead = toRender->nextNodeToRender; - toRender->nextNodeToRender = nullptr; + toRender->Handler().Draw(itemContext, toRender); - if (!renderQueueHead) + Platform::input->ShowMouse(); + + if (finishedRendering) { - renderQueueTail = nullptr; + renderQueue.Dequeue(); + itemsToRender = 0; } - renderQueueSize--; - } - - if (!renderQueueHead) - { - lowerContext.clipTop = lowerContext.clipBottom; - upperContext.clipBottom = upperContext.clipTop; } } -void PageRenderer::AddToQueue(Node* node) +void PageRenderer::AddToQueue(Node* node, int upperClip, int lowerClip) { - if (node->nextNodeToRender || node == renderQueueTail) + if (lowerClip <= upperClip) { - // Already in queue return; } - if (!renderQueueHead) + int insertPosition = renderQueue.tail; + + // Check if in queue already + for (int i = renderQueue.head; i < renderQueue.tail; i++) { - renderQueueTail = renderQueueHead = node; + 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 { - renderQueueTail->nextNodeToRender = node; - renderQueueTail = node; + //printf("Error! Out of space in render queue!\n"); } - - renderQueueSize++; } bool PageRenderer::IsInRenderQueue(Node* node) { - return node && (node->nextNodeToRender || node == renderQueueTail); + 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) @@ -338,7 +407,7 @@ void PageRenderer::GenerateDrawContext(DrawContext& context, Node* node) void PageRenderer::MarkNodeLayoutComplete(Node* node) { Rect& windowRect = app.ui.windowRect; - int drawOffsetY = upperContext.drawOffsetY; + int drawOffsetY = GetDrawOffsetY(); Node* startNode = lastCompleteNode ? lastCompleteNode->GetNextInTree() : app.page.GetRootNode(); lastCompleteNode = node; @@ -349,11 +418,10 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) for (Node* node = startNode; node; node = node->GetNextInTree()) { - if (IsRenderableNode(node->type)) + if (IsRenderableNode(node)) { int nodeTop = node->anchor.y + drawOffsetY; int nodeBottom = nodeTop + node->size.y; - bool outsideOfWindow = (nodeTop > maxWinY) || (nodeBottom < minWinY); if (node->anchor.y + node->size.y > visiblePageHeight) { @@ -361,21 +429,13 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) expandedPage = true; } - if (!outsideOfWindow) + if (nodeTop < minWinY) + nodeTop = minWinY; + if (nodeBottom > maxWinY) + nodeBottom = maxWinY; + if (nodeBottom > nodeTop) { - if (upperContext.clipBottom < nodeBottom) - { - upperContext.clipBottom = nodeBottom; - if (upperContext.clipBottom > maxWinY) - { - upperContext.clipBottom = maxWinY; - } - } - if (lowerContext.clipTop < upperContext.clipBottom) - { - lowerContext.clipTop = upperContext.clipBottom; - } - AddToQueue(node); + AddToQueue(node, nodeTop, nodeBottom); } } @@ -402,7 +462,7 @@ void PageRenderer::MarkNodeDirty(Node* dirtyNode) } Rect& windowRect = app.ui.windowRect; - int drawOffsetY = upperContext.drawOffsetY; + int drawOffsetY = GetDrawOffsetY(); int minWinY = windowRect.y; int maxWinY = windowRect.y + windowRect.height; @@ -412,19 +472,11 @@ void PageRenderer::MarkNodeDirty(Node* dirtyNode) if (!outsideOfWindow) { - if (upperContext.clipBottom < nodeBottom) - { - upperContext.clipBottom = nodeBottom; - if (upperContext.clipBottom > maxWinY) - { - upperContext.clipBottom = maxWinY; - } - } - if (lowerContext.clipTop < upperContext.clipBottom) - { - lowerContext.clipTop = upperContext.clipBottom; - } - AddToQueue(dirtyNode); + if (nodeTop < minWinY) + nodeTop = minWinY; + if (nodeBottom > maxWinY) + nodeBottom = maxWinY; + AddToQueue(dirtyNode, nodeTop, nodeBottom); Platform::input->HideMouse(); @@ -473,17 +525,18 @@ void PageRenderer::InvertNode(Node* node) 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; - } + //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 index 04d496a..509663c 100644 --- a/src/Render.h +++ b/src/Render.h @@ -9,6 +9,47 @@ 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: @@ -23,7 +64,7 @@ class PageRenderer void GenerateDrawContext(DrawContext& context, Node* node); - void AddToQueue(Node* node); + void AddToQueue(Node* node, int upperClip, int lowerClip); void OnPageScroll(int scrollDelta); @@ -43,23 +84,17 @@ class PageRenderer void FindOverlappingNodesInScreenRegion(int top, int bottom); bool DoesOverlapWithContext(Node* node, DrawContext& context); - bool IsRenderableNode(Node::Type type); + bool IsRenderableNode(Node* node); void AddToQueueIfVisible(Node* node); + int GetDrawOffsetY(); App& app; - Node* renderQueueHead; - Node* renderQueueTail; - - int renderQueueSize; + RenderQueue renderQueue; Node* lastCompleteNode; - // Draw contexts for upper and lower parts of the screen to facilitate scrolling - DrawContext upperContext; - DrawContext lowerContext; - int visiblePageHeight; }; From b483d6f6a23ee5a55e6755d6dd99ad5c924922ac Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 28 Mar 2024 21:07:15 +0000 Subject: [PATCH 71/98] Fix for uninitialised string --- src/Nodes/Status.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Nodes/Status.h b/src/Nodes/Status.h index c0bfd2e..c735b5b 100644 --- a/src/Nodes/Status.h +++ b/src/Nodes/Status.h @@ -19,12 +19,11 @@ class StatusBarNode : public NodeHandler class Data { public: - Data() { } - 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] = '.'; From d8984533592883d9f9a742ecd42f5dc6371c9f11 Mon Sep 17 00:00:00 2001 From: jhhoward Date: Thu, 28 Mar 2024 21:07:28 +0000 Subject: [PATCH 72/98] Fix for alt text parsing --- src/Tags.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Tags.cpp b/src/Tags.cpp index ebe5bb1..542a182 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -437,6 +437,10 @@ void ImgTagHandler::Open(class HTMLParser& parser, char* attributeStr) const 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")) { From c9b9f1859d1b5e7501eb9e1b5de13a7861dcd26d Mon Sep 17 00:00:00 2001 From: James Howard Date: Thu, 28 Mar 2024 21:38:20 +0000 Subject: [PATCH 73/98] Improved 8088 compatibility by removing use of floats --- src/Image/Decoder.cpp | 14 +++++++++++--- src/Layout.cpp | 24 ++++++++++++++++++++++-- src/Nodes/Table.cpp | 4 ++-- src/VidModes.cpp | 30 +++++++++++++++--------------- src/VidModes.h | 4 ++-- src/Windows/WinVid.cpp | 2 +- 6 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 2a7206c..610fc77 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -190,9 +190,17 @@ void ImageDecoder::Begin(Image* image, bool dimensionsOnly) void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) { - sourceHeight /= Platform::video->GetVideoModeInfo()->aspectRatio; - sourceWidth *= Platform::video->GetVideoModeInfo()->zoom; - sourceHeight *= Platform::video->GetVideoModeInfo()->zoom; + 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; diff --git a/src/Layout.cpp b/src/Layout.cpp index 03ddf11..c59c23d 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -322,7 +322,15 @@ int Layout::CalculateWidth(ExplicitDimension explicitWidth) } else { - return explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom; + int result = explicitWidth.Value(); + VideoModeInfo* modeInfo = Platform::video->GetVideoModeInfo(); + + if (modeInfo->zoom != 100) + { + result = ((long)modeInfo->zoom * result) / 100; + } + return result; + //return explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom; } } return 0; @@ -338,7 +346,19 @@ int Layout::CalculateHeight(ExplicitDimension explicitHeight) } else { - return (explicitHeight.Value() / Platform::video->GetVideoModeInfo()->aspectRatio) * Platform::video->GetVideoModeInfo()->zoom; + 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 (explicitHeight.Value() / Platform::video->GetVideoModeInfo()->aspectRatio) * Platform::video->GetVideoModeInfo()->zoom; } } return 0; diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index e153757..a454f83 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -317,8 +317,8 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) if (desiredWidth != data->columns[i].calculatedWidth) { totalPreferredWidth -= data->columns[i].calculatedWidth; - float z = data->columns[i].explicitWidthPercentage / 100.0f; - data->columns[i].calculatedWidth = (z * totalPreferredWidth) / (1.0f - z); + long z = data->columns[i].explicitWidthPercentage; + data->columns[i].calculatedWidth = (z * totalPreferredWidth) / (100 - z); totalPreferredWidth += data->columns[i].calculatedWidth; changesMade = true; } diff --git a/src/VidModes.cpp b/src/VidModes.cpp index 96c90ea..f1a2a6d 100644 --- a/src/VidModes.cpp +++ b/src/VidModes.cpp @@ -4,21 +4,21 @@ VideoModeInfo VideoModeList[] = { - // name mode width height bpp aspect zoom data pack vram1 vram2 vram3 vram4 - { "640x200 monochrome (CGA)", 6, 640, 200, 1, 2.4f, 1.0f, DataPack::CGA, 0xb800, 0xba00 }, - { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 1.0f, 1.0f, DataPack::Default, 0xb800, 0xba00 }, - { "320x200 4 colours (CGA)", 5, 320, 200, 2, 1.2f, 0.7f, DataPack::Lowres, 0xb800, 0xba00 }, - { "320x200 16 colours (Composite CGA)", 4, 320, 200, 2, 1.2f, 0.7f, DataPack::CGA, 0xb800, 0xba00 }, - { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 2.4f, 1.0f, DataPack::CGA, 0xa000, }, - { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 1.37f, 1.0f, DataPack::EGA, 0xa000, }, - { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 1.37f, 1.0f, DataPack::EGA, 0xa000, }, - { "640x480 monochrome (VGA)", 0x11, 640, 480, 1, 1.0f, 1.0f, DataPack::Default, 0xa000, }, - { "640x480 16 colours (VGA)", 0x12, 640, 480, 4, 1.0f, 1.0f, DataPack::Default, 0xa000, }, - { "320x200 256 colours (VGA)", 0x13, 320, 200, 8, 1.2f, 0.7f, DataPack::Lowres, 0xa000, }, - { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, 1, 1.55f, 1.0f, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, - { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, 1, 1.0f, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, - { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, 1, 1.0f, 1.0f, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, - { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, 1, 1.0f, 0.5f, DataPack::Lowres, 0xb000, }, + // name mode width height bpp aspect % zoom % data pack vram1 vram2 vram3 vram4 + { "640x200 monochrome (CGA)", 6, 640, 200, 1, 240, 100, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 100, 100, DataPack::Default, 0xb800, 0xba00 }, + { "320x200 4 colours (CGA)", 5, 320, 200, 2, 120, 70, DataPack::Lowres, 0xb800, 0xba00 }, + { "320x200 16 colours (Composite CGA)", 4, 320, 200, 2, 120, 70, DataPack::CGA, 0xb800, 0xba00 }, + { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 240, 100, DataPack::CGA, 0xa000, }, + { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 137, 100, DataPack::EGA, 0xa000, }, + { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 137, 100, DataPack::EGA, 0xa000, }, + { "640x480 monochrome (VGA)", 0x11, 640, 480, 1, 100, 100, DataPack::Default, 0xa000, }, + { "640x480 16 colours (VGA)", 0x12, 640, 480, 4, 100, 100, DataPack::Default, 0xa000, }, + { "320x200 256 colours (VGA)", 0x13, 320, 200, 8, 120, 70, DataPack::Lowres, 0xa000, }, + { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, 1, 155, 100, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, + { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, 1, 100, 100, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, 1, 100, 100, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, + { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, 1, 100, 50, DataPack::Lowres, 0xb000, }, { nullptr } }; diff --git a/src/VidModes.h b/src/VidModes.h index 2740dbc..cbae13e 100644 --- a/src/VidModes.h +++ b/src/VidModes.h @@ -14,8 +14,8 @@ struct VideoModeInfo int screenWidth; int screenHeight; uint8_t bpp; - float aspectRatio; - float zoom; + int aspectRatio; + int zoom; DataPack::Preset dataPackIndex : 8; uint16_t vramPage1; uint16_t vramPage2; diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 00b58a2..6d6d6a9 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -96,7 +96,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) screenWidth = videoMode->screenWidth; screenHeight = videoMode->screenHeight; - verticalScale = videoMode->aspectRatio; // ((screenWidth * 3.0f) / 4.0f) / screenHeight; + verticalScale = videoMode->aspectRatio / 100.0f; // ((screenWidth * 3.0f) / 4.0f) / screenHeight; //verticalScale = 1.0f; Assets.LoadPreset((DataPack::Preset) videoMode->dataPackIndex); From db2bd7e49fc78738aa577a7a8f532804760201c9 Mon Sep 17 00:00:00 2001 From: James Howard Date: Sun, 31 Mar 2024 20:15:28 +0100 Subject: [PATCH 74/98] Added style pool system instead of storing the style information on each node --- project/DOS/Makefile | 5 ++- project/Windows/Windows.vcxproj | 1 + src/App.cpp | 3 ++ src/Image/Jpeg.cpp | 2 +- src/Image/Png.cpp | 2 +- src/Interface.cpp | 37 ++++++++++++------- src/Layout.cpp | 6 +-- src/Node.cpp | 24 +++++++++++- src/Node.h | 6 ++- src/Nodes/Button.cpp | 8 ++-- src/Nodes/Field.cpp | 20 +++++----- src/Nodes/Form.cpp | 5 ++- src/Nodes/ImgNode.cpp | 4 +- src/Nodes/LinkNode.cpp | 6 ++- src/Nodes/ListItem.cpp | 6 +-- src/Nodes/Select.cpp | 10 ++--- src/Nodes/Status.cpp | 4 +- src/Nodes/StyNode.cpp | 4 +- src/Nodes/Table.cpp | 65 +++++++++++++++++++++++++-------- src/Nodes/Text.cpp | 16 ++++---- src/Page.cpp | 11 +++--- src/Parser.cpp | 45 ++++++++++++++++++----- src/Style.cpp | 58 +++++++++++++++++++++++++++++ src/Style.h | 46 +++++++++++++++++++++-- src/Tags.cpp | 2 +- src/URL.h | 18 ++++++++- 26 files changed, 316 insertions(+), 98 deletions(-) create mode 100644 src/Style.cpp diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 677a17a..41f0dea 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=obj -objects = MicroWeb.obj App.obj Parser.obj Tags.obj Platform.obj Colour.obj Hercules.obj BIOSVid.obj VidModes.obj Font.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 Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj Jpeg.obj Png.obj MemBlock.obj Memory.obj EMS.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 Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.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) -fi=$(SRC_PATH)\Defines.h @@ -59,6 +59,9 @@ HTTP.obj: $(SRC_PATH)\HTTP.cpp Font.obj: $(SRC_PATH)\Font.cpp $(CC) -fo=$@ $(CFLAGS) $< +Style.obj: $(SRC_PATH)\Style.cpp + $(CC) -fo=$@ $(CFLAGS) $< + Interface.obj: $(SRC_PATH)\Interface.cpp $(CC) -fo=$@ $(CFLAGS) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 07fa21b..2569653 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -175,6 +175,7 @@ + diff --git a/src/App.cpp b/src/App.cpp index bd5181f..11c727d 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -37,6 +37,7 @@ App::~App() void App::ResetPage() { + StylePool::Get().Reset(); page.Reset(); parser.Reset(); pageRenderer.Reset(); @@ -77,7 +78,9 @@ void App::Run(int argc, char* argv[]) ImageDecoder::Allocate(); } + StylePool::Get().Init(); ui.Init(); + page.Reset(); pageRenderer.Init(); if (targetURL) diff --git a/src/Image/Jpeg.cpp b/src/Image/Jpeg.cpp index b1e83bc..f028aec 100644 --- a/src/Image/Jpeg.cpp +++ b/src/Image/Jpeg.cpp @@ -93,7 +93,7 @@ void JpegDecoder::Process(uint8_t* data, size_t dataLength) case ParseStartOfFrame: if (FillStruct(&data, dataLength, &frameHeader, sizeof(FrameHeader))) { - if (outputImage->width == 0 && outputImage->height == 0) + if (outputImage->width == 0 || outputImage->height == 0) { CalculateImageDimensions(frameHeader.width, frameHeader.height); diff --git a/src/Image/Png.cpp b/src/Image/Png.cpp index 4b39533..83d2937 100644 --- a/src/Image/Png.cpp +++ b/src/Image/Png.cpp @@ -62,7 +62,7 @@ void PngDecoder::Process(uint8_t* data, size_t dataLength) case ParseImageHeader: if (FillStruct(&data, dataLength, &imageHeader, sizeof(ImageHeader))) { - if (outputImage->width == 0 && outputImage->height == 0) + if (outputImage->width == 0 || outputImage->height == 0) { CalculateImageDimensions(imageHeader.width, imageHeader.height); diff --git a/src/Interface.cpp b/src/Interface.cpp index 10f1f9e..3110bdf 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -228,7 +228,9 @@ void AppInterface::Update() case KEYCODE_F2: { Platform::video->InvertVideoOutput(); - titleNode->style.fontColour = Platform::video->colourScheme.textColour; + ElementStyle titleStyle = titleNode->GetStyle(); + titleStyle.fontColour = Platform::video->colourScheme.textColour; + titleNode->SetStyle(titleStyle); } break; case KEYCODE_CTRL_L: @@ -391,11 +393,14 @@ void AppInterface::UpdatePageScrollBar() void AppInterface::GenerateInterfaceNodes() { Allocator& allocator = MemoryManager::interfaceAllocator; + ElementStyle rootInterfaceStyle; + rootInterfaceStyle.alignment = ElementAlignment::Left; + rootInterfaceStyle.fontSize = 1; + rootInterfaceStyle.fontStyle = FontStyle::Regular; + rootInterfaceStyle.fontColour = Platform::video->colourScheme.textColour; + rootInterfaceNode = SectionElement::Construct(allocator, SectionElement::Interface); - rootInterfaceNode->style.alignment = ElementAlignment::Left; - rootInterfaceNode->style.fontSize = 1; - rootInterfaceNode->style.fontStyle = FontStyle::Regular; - rootInterfaceNode->style.fontColour = Platform::video->colourScheme.textColour; + rootInterfaceNode->SetStyle(rootInterfaceStyle); Font* interfaceFont = Assets.GetFont(1, FontStyle::Regular); Font* smallInterfaceFont = Assets.GetFont(0, FontStyle::Regular); @@ -407,26 +412,26 @@ void AppInterface::GenerateInterfaceNodes() titleNode->anchor.Clear(); titleNode->size.x = Platform::video->screenWidth; titleNode->size.y = interfaceFont->glyphHeight; - titleNode->style = rootInterfaceNode->style; + titleNode->styleHandle = rootInterfaceNode->styleHandle; rootInterfaceNode->AddChild(titleNode); } backButtonNode = ButtonNode::Construct(allocator, " < ", OnBackButtonPressed); - backButtonNode->style = rootInterfaceNode->style; + 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->style = rootInterfaceNode->style; + 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->style = rootInterfaceNode->style; + 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; @@ -434,16 +439,18 @@ void AppInterface::GenerateInterfaceNodes() rootInterfaceNode->AddChild(addressBarNode); statusBarNode = StatusBarNode::Construct(allocator); - statusBarNode->style = rootInterfaceNode->style; 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); - statusBarNode->style.fontSize = 0; + + ElementStyle statusBarStyle = rootInterfaceStyle; + statusBarStyle.fontSize = 0; + statusBarNode->SetStyle(statusBarStyle); scrollBarNode = ScrollBarNode::Construct(allocator, scrollPositionY, app.page.pageHeight, OnScrollBarMoved); - scrollBarNode->style = rootInterfaceNode->style; + 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; @@ -456,6 +463,8 @@ void AppInterface::GenerateInterfaceNodes() windowRect.height = Platform::video->screenHeight - windowRect.y - statusBarNode->size.y; pageHeightForDimensionScaling = windowRect.height; + + StylePool::Get().MarkInterfaceStylesComplete(); } void AppInterface::DrawInterfaceNodes(DrawContext& context) @@ -472,8 +481,8 @@ void AppInterface::SetTitle(const char* title) { strncpy(titleBuffer, title, MAX_TITLE_LENGTH); titleBuffer[MAX_TITLE_LENGTH - 1] = '\0'; - Font* font = Assets.GetFont(titleNode->style.fontSize, titleNode->style.fontStyle); - int titleWidth = font->CalculateWidth(titleBuffer, titleNode->style.fontStyle); + 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) { diff --git a/src/Layout.cpp b/src/Layout.cpp index c59c23d..9085a58 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -114,12 +114,12 @@ void Layout::BreakNewLine() if (lineStartNode) { - if (lineStartNode->style.alignment == ElementAlignment::Center) + if (lineStartNode->GetStyle().alignment == ElementAlignment::Center) { int shift = AvailableWidth() / 2; TranslateNodes(lineStartNode, lastNodeContext, shift, 0); } - else if (lineStartNode->style.alignment == ElementAlignment::Right) + else if (lineStartNode->GetStyle().alignment == ElementAlignment::Right) { int shift = AvailableWidth(); TranslateNodes(lineStartNode, lastNodeContext, shift, 0); @@ -330,7 +330,6 @@ int Layout::CalculateWidth(ExplicitDimension explicitWidth) result = ((long)modeInfo->zoom * result) / 100; } return result; - //return explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom; } } return 0; @@ -358,7 +357,6 @@ int Layout::CalculateHeight(ExplicitDimension explicitHeight) result = ((long)modeInfo->zoom * result) / 100; } return result; - //return (explicitHeight.Value() / Platform::video->GetVideoModeInfo()->aspectRatio) * Platform::video->GetVideoModeInfo()->zoom; } } return 0; diff --git a/src/Node.cpp b/src/Node.cpp index 8db0a54..2efde28 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -1,6 +1,7 @@ #include "Page.h" #include "App.h" #include "Draw/Surface.h" +#include "DataPack.h" #include "Node.h" #include "Nodes/Text.h" #include "Nodes/Section.h" @@ -56,7 +57,7 @@ Node::Node(Type inType, void* inData) void Node::AddChild(Node* child) { child->parent = this; - child->style = style; + child->styleHandle = styleHandle; if(!firstChild) { @@ -72,7 +73,7 @@ void Node::AddChild(Node* child) void Node::InsertSibling(Node* sibling) { - sibling->style = style; + sibling->styleHandle = styleHandle; sibling->parent = parent; sibling->next = next; next = sibling; @@ -329,3 +330,22 @@ ExplicitDimension ExplicitDimension::Parse(const char* str) 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 index c5c70ee..346fff9 100644 --- a/src/Node.h +++ b/src/Node.h @@ -115,12 +115,16 @@ class Node } bool IsChildOf(Node* node); + const ElementStyle& GetStyle(); + void SetStyle(const ElementStyle& style); + Font* GetStyleFont(); + void Redraw(); Node* GetPreviousInTree(); Node* GetNextInTree(); - ElementStyle style; + ElementStyleHandle styleHandle; Type type : 8; Coord anchor; // Top left page position diff --git a/src/Nodes/Button.cpp b/src/Nodes/Button.cpp index cd6e3ad..b6788b6 100644 --- a/src/Nodes/Button.cpp +++ b/src/Nodes/Button.cpp @@ -14,7 +14,7 @@ void ButtonNode::Draw(DrawContext& context, Node* node) if (data->buttonText) { - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + 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; @@ -24,7 +24,7 @@ void ButtonNode::Draw(DrawContext& context, Node* node) 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->style.fontStyle); + 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) { @@ -61,13 +61,13 @@ Node* ButtonNode::Construct(Allocator& allocator, const char* inButtonText, Node Coord ButtonNode::CalculateSize(Node* node) { ButtonNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); int labelHeight = font->glyphHeight; int labelWidth = 0; if (data->buttonText) { - labelWidth = font->CalculateWidth(data->buttonText, node->style.fontStyle); + labelWidth = font->CalculateWidth(data->buttonText, node->GetStyle().fontStyle); } Coord result; diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index 09f5eec..c8686de 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -11,7 +11,7 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) { TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + 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; @@ -26,7 +26,7 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) if (node == App::Get().ui.GetFocusedNode()) { - subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); + subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); if (selectionLength > 0) { @@ -39,7 +39,7 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) } else { - subContext.surface->DrawString(subContext, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); + subContext.surface->DrawString(subContext, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); } } @@ -83,7 +83,7 @@ Node* TextFieldNode::Construct(Allocator& allocator, char* buffer, int bufferLen void TextFieldNode::GenerateLayout(Layout& layout, Node* node) { TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); if (data->explicitWidth.IsSet()) { @@ -331,7 +331,7 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) int TextFieldNode::GetBufferPixelWidth(Node* node, int start, int end) { TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); int x = 0; for (int n = 0; n < end; n++) @@ -354,7 +354,7 @@ void TextFieldNode::DrawCursor(DrawContext& context, Node* node) return; TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); int x = node->anchor.x + 3; int height = font->glyphHeight; @@ -388,7 +388,7 @@ void TextFieldNode::DrawSelection(DrawContext& context, Node* node) { TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); int selectionX1 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition); int selectionX2 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition + selectionLength); @@ -400,7 +400,7 @@ void TextFieldNode::DrawSelection(DrawContext& context, Node* node) void TextFieldNode::RedrawModified(Node* node, int position) { TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); DrawContext context; uint8_t textColour = Platform::video->colourScheme.textColour; uint8_t clearColour = Platform::video->colourScheme.pageColour; @@ -414,7 +414,7 @@ void TextFieldNode::RedrawModified(Node* node, int position) Platform::input->HideMouse(); context.surface->FillRect(context, drawPosition, node->anchor.y + 1, clearWidth, node->size.y - 2, clearColour); - context.surface->DrawString(context, font, data->buffer + position, drawPosition, node->anchor.y + 2, textColour, node->style.fontStyle); + context.surface->DrawString(context, font, data->buffer + position, drawPosition, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); DrawCursor(context, node); Platform::input->ShowMouse(); } @@ -480,7 +480,7 @@ int TextFieldNode::PickPosition(Node* node, int x, int y) int result = shiftPosition; TextFieldNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); while(x > 0) { diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp index c38de60..fefabd5 100644 --- a/src/Nodes/Form.cpp +++ b/src/Nodes/Form.cpp @@ -52,7 +52,10 @@ void FormNode::SubmitForm(Node* node) if (data->method == FormNode::Data::Get) { char* address = app.ui.addressBarURL.url; - strcpy(address, data->action); + if (data->action) + { + strcpy(address, data->action); + } int numParams = 0; // Remove anything after existing ? diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 702ad69..92da0bd 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -41,9 +41,9 @@ void ImageNode::Draw(DrawContext& context, Node* node) if (data->altText) { - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); uint8_t textColour = Platform::video->colourScheme.textColour; - croppedContext.surface->DrawString(croppedContext, font, data->altText, node->anchor.x + image->width + 4, node->anchor.y + 2, textColour, node->style.fontStyle); + croppedContext.surface->DrawString(croppedContext, font, data->altText, node->anchor.x + image->width + 4, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); } } } diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index b5c32ed..deff0c4 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -6,8 +6,10 @@ void LinkNode::ApplyStyle(Node* node) { - node->style.fontStyle = (FontStyle::Type)(node->style.fontStyle | FontStyle::Underline); - node->style.fontColour = Platform::video->colourScheme.linkColour; + ElementStyle style = node->GetStyle(); + style.fontStyle = (FontStyle::Type)(style.fontStyle | FontStyle::Underline); + style.fontColour = Platform::video->colourScheme.linkColour; + node->SetStyle(style); } Node* LinkNode::Construct(Allocator& allocator, char* url) diff --git a/src/Nodes/ListItem.cpp b/src/Nodes/ListItem.cpp index e31dc96..569198d 100644 --- a/src/Nodes/ListItem.cpp +++ b/src/Nodes/ListItem.cpp @@ -18,7 +18,7 @@ Node* ListNode::Construct(Allocator& allocator) void ListNode::BeginLayoutContext(Layout& layout, Node* node) { ListNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); layout.BreakNewLine(); layout.PadVertical(font->glyphHeight); @@ -29,7 +29,7 @@ void ListNode::BeginLayoutContext(Layout& layout, Node* node) void ListNode::EndLayoutContext(Layout& layout, Node* node) { ListNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); layout.PopLayout(); layout.BreakNewLine(); @@ -61,7 +61,7 @@ void ListItemNode::BeginLayoutContext(Layout& layout, Node* node) { ListItemNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); layout.BreakNewLine(); node->anchor = layout.GetCursor(); diff --git a/src/Nodes/Select.cpp b/src/Nodes/Select.cpp index 0e0cc81..e2b752c 100644 --- a/src/Nodes/Select.cpp +++ b/src/Nodes/Select.cpp @@ -20,7 +20,7 @@ void SelectNode::Draw(DrawContext& context, Node* node) { SelectNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + 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; @@ -32,7 +32,7 @@ void SelectNode::Draw(DrawContext& context, Node* node) if (data->selected && data->selected->text) { - context.surface->DrawString(context, font, data->selected->text, node->anchor.x + 3, node->anchor.y + 2, textColour, node->style.fontStyle); + context.surface->DrawString(context, font, data->selected->text, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); } } @@ -40,7 +40,7 @@ void SelectNode::EndLayoutContext(Layout& layout, Node* node) { SelectNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); node->size.y = font->glyphHeight + 4; node->size.x = node->size.y; @@ -104,11 +104,11 @@ void OptionNode::EndLayoutContext(Layout& layout, Node* node) data->addedToSelectNode = true; } - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); if (data->text) { - node->size.x = font->CalculateWidth(data->text, node->style.fontStyle); + node->size.x = font->CalculateWidth(data->text, node->GetStyle().fontStyle); } } } diff --git a/src/Nodes/Status.cpp b/src/Nodes/Status.cpp index 329c0e2..35bc342 100644 --- a/src/Nodes/Status.cpp +++ b/src/Nodes/Status.cpp @@ -20,14 +20,14 @@ void StatusBarNode::Draw(DrawContext& context, Node* node) { StatusBarNode::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + 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->style.fontStyle); + 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) diff --git a/src/Nodes/StyNode.cpp b/src/Nodes/StyNode.cpp index e961c9a..645d6f1 100644 --- a/src/Nodes/StyNode.cpp +++ b/src/Nodes/StyNode.cpp @@ -5,7 +5,9 @@ void StyleNode::ApplyStyle(Node* node) { StyleNode::Data* data = static_cast(node->data); - data->styleOverride.Apply(node->style); + ElementStyle style = node->GetStyle(); + data->styleOverride.Apply(style); + node->SetStyle(style); } void StyleNode::GenerateLayout(Layout& layout, Node* node) diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index a454f83..2fbbf5c 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -174,7 +174,33 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) for (int n = 0; n < data->numColumns; n++) { data->columns[n].Clear(); - data->columns[n].preferredWidth = minCellWidth; + //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(); @@ -209,7 +235,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } else { - explicitWidth = cell->explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom; + explicitWidth = ((long)cell->explicitWidth.Value() * Platform::video->GetVideoModeInfo()->zoom) / 100; } } @@ -378,7 +404,9 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) else { totalUnsetWidth += data->columns[i].preferredWidth; - minUnsetWidth += minCellWidth; + + if(data->columns[i].preferredWidth) + minUnsetWidth += minCellWidth; } } @@ -394,7 +422,8 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) { if (!data->columns[i].calculatedWidth) { - data->columns[i].calculatedWidth = minCellWidth; + if(data->columns[i].preferredWidth) + data->columns[i].calculatedWidth = minCellWidth; } else { @@ -407,7 +436,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) { for (int i = 0; i < data->numColumns; i++) { - if (!data->columns[i].calculatedWidth) + if (!data->columns[i].calculatedWidth && totalUnsetWidth) { data->columns[i].calculatedWidth = ((long)widthRemaining * data->columns[i].preferredWidth) / totalUnsetWidth; } @@ -415,7 +444,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) } } - if (totalCellsWidth < maxAvailableWidthForCells) + if (totalCellsWidth < maxAvailableWidthForCells && data->numColumns > 0) { data->columns[data->numColumns - 1].calculatedWidth += maxAvailableWidthForCells - totalCellsWidth; } @@ -429,11 +458,11 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) { int alignmentPadding = 0; - if (node->style.alignment == ElementAlignment::Center) + if (node->GetStyle().alignment == ElementAlignment::Center) { alignmentPadding = (available - data->totalWidth) / 2; } - else if (node->style.alignment == ElementAlignment::Right) + else if (node->GetStyle().alignment == ElementAlignment::Right) { alignmentPadding = (available - data->totalWidth); } @@ -446,7 +475,7 @@ void TableNode::EndLayoutContext(Layout& layout, Node* node) data->state = Data::FinalisingLayout; layout.RecalculateLayoutForNode(node); - + layout.PopLayout(); layout.PopCursor(); @@ -485,6 +514,8 @@ 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) { @@ -519,7 +550,6 @@ void TableRowNode::BeginLayoutContext(Layout& layout, Node* node) } layout.PushCursor(); - //layout.BreakNewLine(); layout.PushLayout(); } @@ -530,8 +560,8 @@ void TableRowNode::EndLayoutContext(Layout& layout, Node* node) if (tableData) { - layout.PopLayout(); layout.BreakNewLine(); + layout.PopLayout(); layout.PopCursor(); if (!tableData->IsGeneratingLayout()) @@ -556,7 +586,9 @@ void TableRowNode::EndLayoutContext(Layout& layout, Node* node) void TableRowNode::ApplyStyle(Node* node) { - node->style.alignment = ElementAlignment::Left; + ElementStyle style = node->GetStyle(); + style.alignment = ElementAlignment::Left; + node->SetStyle(style); } // Table cell node @@ -576,15 +608,18 @@ Node* TableCellNode::Construct(Allocator& allocator, bool isHeader) void TableCellNode::ApplyStyle(Node* node) { TableCellNode::Data* data = static_cast(node->data); + ElementStyle style = node->GetStyle(); + if (data->isHeader) { - node->style.alignment = ElementAlignment::Center; - node->style.fontStyle = FontStyle::Bold; + style.alignment = ElementAlignment::Center; + style.fontStyle = FontStyle::Bold; } else { - node->style.alignment = ElementAlignment::Left; + style.alignment = ElementAlignment::Left; } + node->SetStyle(style); } diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 7e170f4..75ee42a 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -29,9 +29,9 @@ void TextElement::Draw(DrawContext& context, Node* node) if (!node->firstChild && data->text.IsAllocated()) { - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); - uint8_t textColour = node->style.fontColour; + uint8_t textColour = node->GetStyle().fontColour; char* text = data->text.Get(); if (context.surface->bpp == 1) @@ -39,7 +39,7 @@ void TextElement::Draw(DrawContext& context, Node* node) textColour = Platform::video->colourScheme.textColour; } - context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); + 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)) @@ -53,7 +53,7 @@ void TextElement::Draw(DrawContext& context, Node* node) void TextElement::GenerateLayout(Layout& layout, Node* node) { TextElement::Data* data = static_cast(node->data); - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); + Font* font = node->GetStyleFont(); int lineHeight = font->glyphHeight; #if 1 @@ -129,7 +129,7 @@ void TextElement::GenerateLayout(Layout& layout, Node* node) hasModified = true; } - int glyphWidth = font->GetGlyphWidth(c, node->style.fontStyle); + int glyphWidth = font->GetGlyphWidth(c, node->GetStyle().fontStyle); width += glyphWidth; bool cannotFit = width > layout.AvailableWidth(); @@ -246,8 +246,8 @@ void SubTextElement::Draw(DrawContext& context, Node* node) if (textData && subTextData && textData->text.IsAllocated()) { - Font* font = Assets.GetFont(node->style.fontSize, node->style.fontStyle); - uint8_t textColour = node->style.fontColour; + 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; @@ -257,7 +257,7 @@ void SubTextElement::Draw(DrawContext& context, Node* node) textColour = Platform::video->colourScheme.textColour; } - context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->style.fontStyle); + 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)) diff --git a/src/Page.cpp b/src/Page.cpp index 36a54ac..dcaf721 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -32,7 +32,6 @@ Page::Page(App& inApp) : app(inApp), layout(*this) { - Reset(); } void Page::Reset() @@ -49,10 +48,12 @@ void Page::Reset() MemoryManager::pageBlockAllocator.Reset(); rootNode = SectionElement::Construct(MemoryManager::pageAllocator, SectionElement::Document); - rootNode->style.alignment = ElementAlignment::Left; - rootNode->style.fontSize = 1; - rootNode->style.fontStyle = FontStyle::Regular; - rootNode->style.fontColour = Platform::video->colourScheme.textColour; + ElementStyle rootStyle; + rootStyle.alignment = ElementAlignment::Left; + rootStyle.fontSize = 1; + rootStyle.fontStyle = FontStyle::Regular; + rootStyle.fontColour = Platform::video->colourScheme.textColour; + rootNode->SetStyle(rootStyle); layout.Reset(); } diff --git a/src/Parser.cpp b/src/Parser.cpp index d87828e..032eefe 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -190,12 +190,7 @@ void HTMLParser::AppendTextBuffer(char c) void HTMLParser::EmitText(const char* text) { - Node* textElement = TextElement::Construct(MemoryManager::pageAllocator, text); - if (textElement) - { - textElement->style = CurrentContext().node->style; - EmitNode(textElement); - } + EmitNode(TextElement::Construct(MemoryManager::pageAllocator, text)); } void HTMLParser::EmitNode(Node* node) @@ -205,6 +200,36 @@ void HTMLParser::EmitNode(Node* 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); @@ -325,18 +350,20 @@ void HTMLParser::FlushTextBuffer() { if (!stricmp(attributes.Key(), "align")) { + ElementStyle style = CurrentContext().node->GetStyle(); if (!stricmp(attributes.Value(), "center")) { - CurrentContext().node->style.alignment = ElementAlignment::Center; + style.alignment = ElementAlignment::Center; } else if (!stricmp(attributes.Value(), "left")) { - CurrentContext().node->style.alignment = ElementAlignment::Left; + style.alignment = ElementAlignment::Left; } else if (!stricmp(attributes.Value(), "right")) { - CurrentContext().node->style.alignment = ElementAlignment::Right; + style.alignment = ElementAlignment::Right; } + CurrentContext().node->SetStyle(style); } if (!stricmp(attributes.Key(), "name")) { 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 index 9c10591..8900e0d 100644 --- a/src/Style.h +++ b/src/Style.h @@ -13,7 +13,7 @@ struct ElementAlignment Left = 0, Center = 1, Right = 2 - }; + }; }; struct StyleOverrideMask @@ -39,9 +39,9 @@ struct StyleOverrideMask struct ElementStyle { - FontStyle::Type fontStyle : 4; - int fontSize : 4; - ElementAlignment::Type alignment : 2; + FontStyle::Type fontStyle : 8; + int fontSize : 8; + ElementAlignment::Type alignment : 8; uint8_t fontColour; }; @@ -110,6 +110,44 @@ struct ElementStyleOverride } }; +#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 542a182..44df01e 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -120,7 +120,7 @@ void HrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const void BrTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - int fontSize = parser.CurrentContext().node->style.fontSize; + int fontSize = parser.CurrentContext().node->GetStyle().fontSize; int padding = Assets.GetFont(fontSize, FontStyle::Regular)->glyphHeight; parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding, false, true)); } diff --git a/src/URL.h b/src/URL.h index 0c5dac8..9ab41ab 100644 --- a/src/URL.h +++ b/src/URL.h @@ -61,18 +61,32 @@ struct URL 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 != '/') + 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; } - directory = strstr(prevSlash, "/../"); } // Fix & escape sequences From f44bdf64e3259b809f06b5164116e7cedefba2ee Mon Sep 17 00:00:00 2001 From: James Howard Date: Sun, 31 Mar 2024 21:33:46 +0100 Subject: [PATCH 75/98] Tweaks to the way colour and screen inverting work --- src/App.cpp | 14 ++++++-------- src/App.h | 1 + src/Colour.cpp | 12 ------------ src/Colour.h | 1 - src/Draw/Surf1bpp.cpp | 21 ++++++++++++++++++--- src/Image/Gif.cpp | 9 ++++----- src/Interface.cpp | 3 --- src/Nodes/Break.cpp | 4 ++-- src/Nodes/ImgNode.cpp | 4 ++-- src/Nodes/LinkNode.cpp | 2 +- src/Nodes/ListItem.cpp | 4 ++-- src/Nodes/Text.cpp | 22 ++++++++++++++++++---- src/Page.cpp | 3 ++- src/Page.h | 3 +++ src/Parser.cpp | 23 ++++++++++++++++------- src/Render.cpp | 8 ++++---- src/Tags.cpp | 41 ++++++++++++++++++++++++++++++++++++++--- 17 files changed, 117 insertions(+), 58 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 11c727d..2dcca52 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -52,6 +52,7 @@ void App::Run(int argc, char* argv[]) config.loadImages = true; config.dumpPage = false; + config.invertScreen = false; if (argc > 1) { @@ -70,6 +71,10 @@ void App::Run(int argc, char* argv[]) { config.dumpPage = true; } + else if (!stricmp(argv[n], "-i")) + { + config.invertScreen = true; + } } } @@ -470,14 +475,7 @@ void VideoDriver::InvertVideoOutput() { if (drawSurface->bpp == 1) { - if (colourScheme.pageColour == 0) - { - colourScheme = monochromeColourScheme; - } - else - { - colourScheme = monochromeInverseColourScheme; - } + App::Get().config.invertScreen = !App::Get().config.invertScreen; DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); Platform::input->HideMouse(); diff --git a/src/App.h b/src/App.h index 25cb7fa..48eade3 100644 --- a/src/App.h +++ b/src/App.h @@ -62,6 +62,7 @@ struct AppConfig { bool loadImages : 1; bool dumpPage : 1; + bool invertScreen : 1; }; class App diff --git a/src/Colour.cpp b/src/Colour.cpp index 092a44c..afe84df 100644 --- a/src/Colour.cpp +++ b/src/Colour.cpp @@ -13,18 +13,6 @@ ColourScheme monochromeColourScheme = 1 }; -ColourScheme monochromeInverseColourScheme = -{ - // Page - 0, - // Text - 1, - // Link - 1, - // Button - 0 -}; - ColourScheme cgaColourScheme = { // Page diff --git a/src/Colour.h b/src/Colour.h index 153929b..d1d41a8 100644 --- a/src/Colour.h +++ b/src/Colour.h @@ -24,7 +24,6 @@ struct NamedColour }; extern ColourScheme monochromeColourScheme; -extern ColourScheme monochromeInverseColourScheme; extern ColourScheme egaColourScheme; extern ColourScheme cgaColourScheme; extern ColourScheme compositeCgaColourScheme; diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index d00b0a0..29b430c 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -4,6 +4,7 @@ #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) @@ -40,6 +41,9 @@ void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint uint8_t data = *VRAMptr; + if (App::Get().config.invertScreen) + colour = !colour; + if (colour) { uint8_t mask = (0x80 >> (x & 7)); @@ -120,6 +124,9 @@ void DrawSurface_1BPP::VLine(DrawContext& context, int x, int y, int count, uint uint8_t mask = (0x80 >> (x & 7)); int index = x >> 3; + if (App::Get().config.invertScreen) + colour = !colour; + if (colour) { while (count--) @@ -167,6 +174,9 @@ void DrawSurface_1BPP::FillRect(DrawContext& context, int x, int y, int width, i return; } + if (App::Get().config.invertScreen) + colour = !colour; + while (height) { uint8_t* VRAMptr = lines[y]; @@ -262,6 +272,9 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; + if (App::Get().config.invertScreen) + colour = !colour; + while (*text) { unsigned char c = (unsigned char) *text++; @@ -435,6 +448,8 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int return; // Nothing to draw if fully outside the clipping region. } + uint8_t invertMask = App::Get().config.invertScreen ? 0xff : 0; + // Blit the image data line by line for (int j = 0; j < destHeight; j++) { @@ -444,7 +459,7 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int 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 srcBuffer = (*src++) ^ invertMask; uint8_t destBuffer = *dest; for (int i = 0; i < destWidth; i++) @@ -461,7 +476,7 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int if (!srcMask) { srcMask = 0x80; - srcBuffer = *src++; + srcBuffer = (*src++) ^ invertMask; } destMask >>= 1; if (!destMask) @@ -539,7 +554,7 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) { - uint16_t inverseMask = Platform::video->colourScheme.pageColour == 0 ? 0xffff : 0; + uint16_t inverseMask = App::Get().config.invertScreen ? 0xffff : 0; const uint16_t widgetEdge = 0x0660 ^ inverseMask; const uint16_t widgetInner = 0xfa5f ^ inverseMask; const uint16_t grab = 0x0a50 ^ inverseMask; diff --git a/src/Image/Gif.cpp b/src/Image/Gif.cpp index 5195659..9fae3d2 100644 --- a/src/Image/Gif.cpp +++ b/src/Image/Gif.cpp @@ -706,7 +706,6 @@ void GifDecoder::EmitLine(int y) uint8_t buffer = 0; uint8_t mask = 0x80; int ditherIndex = 0; - uint8_t inverseMask = (Platform::video->colourScheme.pageColour == 0) ? 0xff : 0; if (outputImage->width == lineBufferSize) { @@ -725,7 +724,7 @@ void GifDecoder::EmitLine(int y) mask >>= 1; if (!mask) { - *output++ = buffer ^ inverseMask; + *output++ = buffer; buffer = 0; mask = 0x80; } @@ -733,7 +732,7 @@ void GifDecoder::EmitLine(int y) if (mask != 0x80) { - *output = buffer ^ inverseMask; + *output = buffer; } } else @@ -759,7 +758,7 @@ void GifDecoder::EmitLine(int y) mask >>= 1; if (!mask) { - *output++ = buffer ^ inverseMask; + *output++ = buffer; buffer = 0; mask = 0x80; } @@ -774,7 +773,7 @@ void GifDecoder::EmitLine(int y) if (mask != 0x80) { - *output = buffer ^ inverseMask; + *output = buffer; } } } diff --git a/src/Interface.cpp b/src/Interface.cpp index 3110bdf..0288dee 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -228,9 +228,6 @@ void AppInterface::Update() case KEYCODE_F2: { Platform::video->InvertVideoOutput(); - ElementStyle titleStyle = titleNode->GetStyle(); - titleStyle.fontColour = Platform::video->colourScheme.textColour; - titleNode->SetStyle(titleStyle); } break; case KEYCODE_CTRL_L: diff --git a/src/Nodes/Break.cpp b/src/Nodes/Break.cpp index 75582a5..b0c9fd1 100644 --- a/src/Nodes/Break.cpp +++ b/src/Nodes/Break.cpp @@ -2,7 +2,7 @@ #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) @@ -21,7 +21,7 @@ void BreakNode::Draw(DrawContext& context, Node* node) if (data->displayBreakLine) { - uint8_t outlineColour = Platform::video->colourScheme.textColour; + 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); } } diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index 92da0bd..e68fec4 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -14,7 +14,7 @@ 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 = Platform::video->colourScheme.textColour; + uint8_t outlineColour = App::Get().page.colourScheme.textColour; if (data->state == ImageNode::FinishedDownloadingContent && data->image.lines.IsAllocated()) { @@ -42,7 +42,7 @@ void ImageNode::Draw(DrawContext& context, Node* node) if (data->altText) { Font* font = node->GetStyleFont(); - uint8_t textColour = Platform::video->colourScheme.textColour; + 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); } } diff --git a/src/Nodes/LinkNode.cpp b/src/Nodes/LinkNode.cpp index deff0c4..ac12d84 100644 --- a/src/Nodes/LinkNode.cpp +++ b/src/Nodes/LinkNode.cpp @@ -8,7 +8,7 @@ void LinkNode::ApplyStyle(Node* node) { ElementStyle style = node->GetStyle(); style.fontStyle = (FontStyle::Type)(style.fontStyle | FontStyle::Underline); - style.fontColour = Platform::video->colourScheme.linkColour; + style.fontColour = App::Get().page.colourScheme.linkColour; node->SetStyle(style); } diff --git a/src/Nodes/ListItem.cpp b/src/Nodes/ListItem.cpp index 569198d..706bf16 100644 --- a/src/Nodes/ListItem.cpp +++ b/src/Nodes/ListItem.cpp @@ -21,7 +21,7 @@ void ListNode::BeginLayoutContext(Layout& layout, Node* node) Font* font = node->GetStyleFont(); layout.BreakNewLine(); - layout.PadVertical(font->glyphHeight); + layout.PadVertical(font->glyphHeight / 2); layout.PushLayout(); layout.PadHorizontal(16, 0); } @@ -33,7 +33,7 @@ void ListNode::EndLayoutContext(Layout& layout, Node* node) layout.PopLayout(); layout.BreakNewLine(); - layout.PadVertical(font->glyphHeight); + layout.PadVertical(font->glyphHeight / 2); } Node* ListItemNode::Construct(Allocator& allocator) diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index 75ee42a..bbe553e 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -34,9 +34,16 @@ void TextElement::Draw(DrawContext& context, Node* node) uint8_t textColour = node->GetStyle().fontColour; char* text = data->text.Get(); - if (context.surface->bpp == 1) + if (textColour == App::Get().page.colourScheme.pageColour) { - textColour = Platform::video->colourScheme.textColour; + if (context.surface->bpp == 1) + { + textColour = !textColour; + } + else + { + textColour ^= 0x08; + } } context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->GetStyle().fontStyle); @@ -252,9 +259,16 @@ void SubTextElement::Draw(DrawContext& context, Node* node) char temp = text[subTextData->length]; text[subTextData->length] = 0; - if (context.surface->bpp == 1) + if (textColour == App::Get().page.colourScheme.pageColour) { - textColour = Platform::video->colourScheme.textColour; + if (context.surface->bpp == 1) + { + textColour = !textColour; + } + else + { + textColour ^= 0x08; + } } context.surface->DrawString(context, font, text, node->anchor.x, node->anchor.y, textColour, node->GetStyle().fontStyle); diff --git a/src/Page.cpp b/src/Page.cpp index dcaf721..32a44ec 100644 --- a/src/Page.cpp +++ b/src/Page.cpp @@ -43,6 +43,7 @@ void Page::Reset() leftMarginPadding = 1; cursorX = leftMarginPadding; cursorY = TOP_MARGIN_PADDING; + colourScheme = Platform::video->colourScheme; MemoryManager::pageAllocator.Reset(); MemoryManager::pageBlockAllocator.Reset(); @@ -52,7 +53,7 @@ void Page::Reset() rootStyle.alignment = ElementAlignment::Left; rootStyle.fontSize = 1; rootStyle.fontStyle = FontStyle::Regular; - rootStyle.fontColour = Platform::video->colourScheme.textColour; + rootStyle.fontColour = colourScheme.textColour; rootNode->SetStyle(rootStyle); layout.Reset(); diff --git a/src/Page.h b/src/Page.h index 959b5fe..adc599c 100644 --- a/src/Page.h +++ b/src/Page.h @@ -17,6 +17,7 @@ #include "URL.h" #include "Layout.h" +#include "Colour.h" #define MAX_PAGE_STYLE_STACK_SIZE 32 #define MAX_TEXT_BUFFER_SIZE 128 @@ -45,6 +46,8 @@ class Page Node* ProcessNextLoadTask(Node* lastNode, struct LoadTask& loadTask); + ColourScheme colourScheme; + private: friend class AppInterface; diff --git a/src/Parser.cpp b/src/Parser.cpp index 032eefe..8530531 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -958,11 +958,6 @@ NamedColour namedColours[] = uint8_t HTMLParser::ParseColourCode(const char* colourCode) { - if (!Platform::video->paletteLUT) - { - return 0; - } - if (colourCode[0] == '#') { uint8_t red = 0, green = 0, blue = 0; @@ -979,7 +974,13 @@ uint8_t HTMLParser::ParseColourCode(const char* colourCode) blue += blue * 16; } - return Platform::video->paletteLUT[RGB332(red, green, blue)]; + if (Platform::video->paletteLUT) + { + return Platform::video->paletteLUT[RGB332(red, green, blue)]; + } + + int grey = red + green + blue; + return grey > (127 * 3) ? 1 : 0; } else { @@ -987,7 +988,15 @@ uint8_t HTMLParser::ParseColourCode(const char* colourCode) { if (!stricmp(namedColours[n].name, colourCode)) { - return Platform::video->paletteLUT[namedColours[n].colour]; + uint8_t rgb332 = namedColours[n].colour; + if (Platform::video->paletteLUT) + { + return Platform::video->paletteLUT[rgb332]; + } + else + { + return (rgb332 & 0xda) != 0; + } } } } diff --git a/src/Render.cpp b/src/Render.cpp index 763da33..edc12a6 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -51,7 +51,7 @@ void PageRenderer::RefreshAll() DrawContext clearContext; InitContext(clearContext); - clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, app.page.colourScheme.pageColour); Platform::input->ShowMouse(); @@ -124,7 +124,7 @@ void PageRenderer::OnPageScroll(int scrollDelta) clearContext.clipTop = minWinY; } clearContext.clipBottom = maxWinY; - clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, app.page.colourScheme.pageColour); } else { @@ -135,7 +135,7 @@ void PageRenderer::OnPageScroll(int scrollDelta) { clearContext.clipBottom = maxWinY; } - clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, Platform::video->colourScheme.pageColour); + clearContext.surface->FillRect(clearContext, 0, 0, Platform::video->screenWidth, Platform::video->screenHeight, app.page.colourScheme.pageColour); } Platform::input->ShowMouse(); @@ -486,7 +486,7 @@ void PageRenderer::MarkNodeDirty(Node* dirtyNode) 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, Platform::video->colourScheme.pageColour); + clearContext.surface->FillRect(clearContext, dirtyNode->anchor.x, dirtyNode->anchor.y, dirtyNode->size.x, dirtyNode->size.y, app.page.colourScheme.pageColour); Platform::input->ShowMouse(); } diff --git a/src/Tags.cpp b/src/Tags.cpp index 44df01e..5282726 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -19,6 +19,7 @@ #include "Parser.h" #include "Page.h" #include "Platform.h" +#include "App.h" #include "Image/Image.h" #include "DataPack.h" #include "Memory/Memory.h" @@ -137,8 +138,8 @@ void HTagHandler::Close(class HTMLParser& parser) const { int fontSize = size >= 3 ? 1 : 2; int padding = Assets.GetFont(fontSize, FontStyle::Bold)->glyphHeight / 2; - parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding)); parser.PopContext(this); + parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator, padding)); } void SizeTagHandler::Open(class HTMLParser& parser, char* attributeStr) const @@ -200,7 +201,36 @@ void BlockTagHandler::Close(class HTMLParser& parser) const void SectionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(SectionElement::Construct(MemoryManager::pageAllocator, sectionType), this); + 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 { @@ -505,6 +535,10 @@ void PreformattedTagHandler::Open(class HTMLParser& parser, char* attributeStr) // TODO-refactor parser.PushPreFormatted(); 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); } @@ -513,7 +547,8 @@ void PreformattedTagHandler::Close(class HTMLParser& parser) const // TODO-refactor parser.PopPreFormatted(); parser.PopContext(this); - parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); + parser.PopContext(this); +// parser.EmitNode(BreakNode::Construct(MemoryManager::pageAllocator)); } void TableTagHandler::Open(class HTMLParser& parser, char* attributeStr) const From ecceeda90488b042ba0327abe8ea0f4e7cc34a6b Mon Sep 17 00:00:00 2001 From: James Howard Date: Thu, 4 Apr 2024 19:30:44 +0100 Subject: [PATCH 76/98] Added more config options --- src/App.cpp | 18 +++++++++++++++--- src/App.h | 4 +++- src/DOS/Platform.cpp | 29 ++++++---------------------- src/Draw/Surf1bpp.cpp | 16 +++++++++------- src/Interface.cpp | 4 +++- src/Layout.cpp | 2 +- src/Memory/MemBlock.cpp | 41 ++++++++++++++++++++++++---------------- src/Nodes/ImgNode.cpp | 2 +- src/VidModes.h | 2 ++ src/Windows/Platform.cpp | 3 +-- 10 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 2dcca52..3029add 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -19,6 +19,7 @@ #include "Image/Decoder.h" App* App::app; +AppConfig App::config; App::App() : page(*this), pageRenderer(*this), parser(page), ui(*this) @@ -52,7 +53,8 @@ void App::Run(int argc, char* argv[]) config.loadImages = true; config.dumpPage = false; - config.invertScreen = false; + config.useSwap = false; + config.useEMS = true; if (argc > 1) { @@ -75,9 +77,19 @@ void App::Run(int argc, char* argv[]) { 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(); @@ -270,7 +282,7 @@ void LoadTask::Load(const char* targetURL) { request = Platform::network->CreateRequest(url.url); - if (App::Get().config.dumpPage && this == &App::Get().pageLoadTask) + if (App::config.dumpPage && this == &App::Get().pageLoadTask) { debugDumpFile = fopen("dump.htm", "wb"); } @@ -475,7 +487,7 @@ void VideoDriver::InvertVideoOutput() { if (drawSurface->bpp == 1) { - App::Get().config.invertScreen = !App::Get().config.invertScreen; + App::config.invertScreen = !App::config.invertScreen; DrawContext context(drawSurface, 0, 0, screenWidth, screenHeight); Platform::input->HideMouse(); diff --git a/src/App.h b/src/App.h index 48eade3..ab12649 100644 --- a/src/App.h +++ b/src/App.h @@ -63,6 +63,8 @@ struct AppConfig bool loadImages : 1; bool dumpPage : 1; bool invertScreen : 1; + bool useSwap : 1; + bool useEMS : 1; }; class App @@ -88,7 +90,7 @@ class App PageRenderer pageRenderer; HTMLParser parser; AppInterface ui; - AppConfig config; + static AppConfig config; LoadTask pageLoadTask; LoadTask pageContentLoadTask; diff --git a/src/DOS/Platform.cpp b/src/DOS/Platform.cpp index e608fb2..82d8ac2 100644 --- a/src/DOS/Platform.cpp +++ b/src/DOS/Platform.cpp @@ -26,6 +26,7 @@ #include "../Font.h" #include "../Draw/Surface.h" #include "../Memory/Memory.h" +#include "../App.h" VideoDriver* Platform::video = nullptr; InputDriver* Platform::input = nullptr; @@ -156,16 +157,6 @@ static int AutoDetectVideoMode() bool Platform::Init(int argc, char* argv[]) { - bool inverse = false; - - for (int n = 1; n < argc; n++) - { - if (!stricmp(argv[n], "-i")) - { - inverse = true; - } - } - network = new DOSNetworkDriver(); if (network) { @@ -173,8 +164,6 @@ bool Platform::Init(int argc, char* argv[]) } else FatalError("Could not create network driver"); - MemoryManager::pageBlockAllocator.Init(); - int suggestedMode = AutoDetectVideoMode(); VideoModeInfo* videoMode = ShowVideoModePicker(suggestedMode); if (!videoMode) @@ -182,6 +171,11 @@ bool Platform::Init(int argc, char* argv[]) return false; } + if (videoMode == &VideoModeList[HP95LX] || videoMode == &VideoModeList[CGAPalmtop]) + { + App::config.invertScreen = true; + } + if (videoMode->biosVideoMode == HERCULES_MODE) { video = new HerculesDriver(); @@ -196,18 +190,7 @@ bool Platform::Init(int argc, char* argv[]) FatalError("Could not create video driver"); } - if (videoMode->biosVideoMode == HP95LX || videoMode->biosVideoMode == CGAPalmtop) - { - inverse = true; - } - video->Init(videoMode); - video->drawSurface->Clear(); - - if (inverse) - { - video->InvertVideoOutput(); - } input = new DOSInputDriver(); if (input) diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 29b430c..5f09089 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -41,7 +41,7 @@ void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint uint8_t data = *VRAMptr; - if (App::Get().config.invertScreen) + if (App::config.invertScreen) colour = !colour; if (colour) @@ -124,7 +124,7 @@ void DrawSurface_1BPP::VLine(DrawContext& context, int x, int y, int count, uint uint8_t mask = (0x80 >> (x & 7)); int index = x >> 3; - if (App::Get().config.invertScreen) + if (App::config.invertScreen) colour = !colour; if (colour) @@ -174,7 +174,7 @@ void DrawSurface_1BPP::FillRect(DrawContext& context, int x, int y, int width, i return; } - if (App::Get().config.invertScreen) + if (App::config.invertScreen) colour = !colour; while (height) @@ -272,7 +272,7 @@ void DrawSurface_1BPP::DrawString(DrawContext& context, Font* font, const char* uint8_t bold = (style & FontStyle::Bold) ? 1 : 0; - if (App::Get().config.invertScreen) + if (App::config.invertScreen) colour = !colour; while (*text) @@ -448,7 +448,7 @@ void DrawSurface_1BPP::BlitImage(DrawContext& context, Image* image, int x, int return; // Nothing to draw if fully outside the clipping region. } - uint8_t invertMask = App::Get().config.invertScreen ? 0xff : 0; + uint8_t invertMask = App::config.invertScreen ? 0xff : 0; // Blit the image data line by line for (int j = 0; j < destHeight; j++) @@ -554,7 +554,7 @@ void DrawSurface_1BPP::InvertRect(DrawContext& context, int x, int y, int width, void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int height, int position, int size) { - uint16_t inverseMask = App::Get().config.invertScreen ? 0xffff : 0; + 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; @@ -612,9 +612,11 @@ void DrawSurface_1BPP::VerticalScrollBar(DrawContext& context, int x, int y, int 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], 0xff, widthBytes); + memset(lines[y], clear, widthBytes); } } diff --git a/src/Interface.cpp b/src/Interface.cpp index 0288dee..fe322f6 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -466,9 +466,11 @@ void AppInterface::GenerateInterfaceNodes() void AppInterface::DrawInterfaceNodes(DrawContext& context) { + Platform::input->HideMouse(); + Platform::video->drawSurface->Clear(); + app.pageRenderer.DrawAll(context, rootInterfaceNode); - Platform::input->HideMouse(); uint8_t dividerColour = Platform::video->colourScheme.textColour; context.surface->HLine(context, 0, windowRect.y - 1, Platform::video->screenWidth, dividerColour); Platform::input->ShowMouse(); diff --git a/src/Layout.cpp b/src/Layout.cpp index 9085a58..3b6fe0b 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -34,7 +34,7 @@ void Layout::Update() { currentNodeToProcess->Handler().BeginLayoutContext(*this, currentNodeToProcess); - if (currentNodeToProcess->type == Node::Image && App::Get().config.loadImages) + if (currentNodeToProcess->type == Node::Image && App::config.loadImages) { ImageNode::Data* imageData = static_cast(currentNodeToProcess->data); if (!imageData->HasDimensions()) diff --git a/src/Memory/MemBlock.cpp b/src/Memory/MemBlock.cpp index 7776c8d..0cc3f44 100644 --- a/src/Memory/MemBlock.cpp +++ b/src/Memory/MemBlock.cpp @@ -3,8 +3,10 @@ #include "LinAlloc.h" #include "Memory.h" #include "../Platform.h" +#include "../App.h" #ifdef __DOS__ +#include #include "../DOS/EMS.h" EMSManager ems; @@ -55,9 +57,11 @@ MemBlockAllocator::MemBlockAllocator() void MemBlockAllocator::Init() { - // Disable swap for now - //swapFile = fopen("Microweb.swp", "wb+"); - + if (App::config.useSwap) + { + swapFile = fopen("Microweb.swp", "wb+"); + } + if (swapFile) { swapBuffer = malloc(MAX_SWAP_ALLOCATION); @@ -67,7 +71,10 @@ void MemBlockAllocator::Init() } #ifdef __DOS__ - ems.Init(); + if (App::config.useEMS) + { + ems.Init(); + } #endif } @@ -98,6 +105,7 @@ MemBlockHandle MemBlockAllocator::AllocString(const char* inString) MemBlockHandle MemBlockAllocator::Allocate(uint16_t size) { MemBlockHandle result; + long conventionalMemoryAvailable = 0; #ifdef __DOS__ if (ems.IsAvailable()) @@ -109,9 +117,13 @@ MemBlockHandle MemBlockAllocator::Allocate(uint16_t size) return result; } } + + conventionalMemoryAvailable += _memmax(); #endif + + conventionalMemoryAvailable += MemoryManager::pageAllocator.TotalAllocated() - MemoryManager::pageAllocator.TotalUsed(); - if (swapFile) + if (swapFile && conventionalMemoryAvailable < 16 * 1024) // If we have less than 16K available, fall back to disk { uint16_t sizeNeededForSwap = size + sizeof(uint16_t); @@ -122,20 +134,17 @@ MemBlockHandle MemBlockAllocator::Allocate(uint16_t size) fwrite(&size, sizeof(uint16_t), 1, swapFile); - char empty[32]; - size_t toWrite = size; - while (toWrite > 0) + char empty = 0xaa; + size_t bytesLeft = size; + while (bytesLeft > 0) { - if (toWrite >= 32) - { - fwrite(empty, 1, 32, swapFile); - toWrite -= 32; - } - else + if(!fwrite(&empty, 1, 1, swapFile)) { - fwrite(empty, 1, toWrite, swapFile); - break; + // Out of disk space? + result.type = MemBlockHandle::Unallocated; + return result; } + bytesLeft--; } swapFileLength += sizeNeededForSwap; diff --git a/src/Nodes/ImgNode.cpp b/src/Nodes/ImgNode.cpp index e68fec4..5cd2fb8 100644 --- a/src/Nodes/ImgNode.cpp +++ b/src/Nodes/ImgNode.cpp @@ -122,7 +122,7 @@ void ImageNode::GenerateLayout(Layout& layout, Node* node) void ImageNode::LoadContent(Node* node, LoadTask& loadTask) { - if (!App::Get().config.loadImages) + if (!App::config.loadImages) { return; } diff --git a/src/VidModes.h b/src/VidModes.h index cbae13e..76a3fe0 100644 --- a/src/VidModes.h +++ b/src/VidModes.h @@ -25,4 +25,6 @@ struct VideoModeInfo VideoModeInfo* ShowVideoModePicker(int defaultSelection); +extern VideoModeInfo VideoModeList[]; + #endif diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 589947d..23b33c9 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -21,6 +21,7 @@ #include "../Draw/Surface.h" #include "../VidModes.h" #include "../Memory/Memory.h" +#include "../App.h" WindowsVideoDriver winVid; WindowsNetworkDriver winNetworkDriver; @@ -43,10 +44,8 @@ bool Platform::Init(int argc, char* argv[]) network->Init(); video->Init(videoMode); - video->drawSurface->Clear(); input->Init(); input->ShowMouse(); - MemoryManager::pageBlockAllocator.Init(); return true; } From fcf2560fbeccbaecefce9751c9c3762d932b9fb2 Mon Sep 17 00:00:00 2001 From: James Howard Date: Thu, 4 Apr 2024 19:31:03 +0100 Subject: [PATCH 77/98] Updated README --- README.md | 70 ++++++++++++++++++++++++++++----------------- screenshot.gif | Bin 0 -> 5779 bytes screenshot.png | Bin 6966 -> 0 bytes screenshot_vga.png | Bin 18934 -> 0 bytes 4 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 screenshot.gif delete mode 100644 screenshot.png delete mode 100644 screenshot_vga.png diff --git a/README.md b/README.md index 5909dc3..2f947d2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # MicroWeb DOS web browser -![Screenshot](screenshot.png) +![Screenshot](screenshot.gif) MicroWeb is a web browser for DOS! It is a 16-bit real mode application, designed to run on minimal hardware. @@ -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 as little as 384K +* EMS can be used if available and is recommended for loading heavier web pages ## 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/screenshot.gif b/screenshot.gif new file mode 100644 index 0000000000000000000000000000000000000000..4c5dae8d4062bebb28e9ad8cabe2bf9f62ac6f99 GIT binary patch literal 5779 zcmV;E7HsK9Nk%w1VSobQ0pkb&000000IF40RjO400IC40s;X80Rsa80|NsC0|NsC0 z|NsC0|NsC0|NsC0|NsC0A^s6Va%Ew3Wn>_CX>@2HM@dak04x9i004jj-~j*x{t(GY ztGzhu&Ab0#D2`-lo@lDBZ0o*oEYEap-*~R?eDD9jpm0bm8jr}Na>;BupU|jO0;pO6 zrK=Tz?R2}-uy{-^o6jRKN-g@L-|)D6PQS5DcaXcz@B9CNaCul?TN7J*6>e1&h*W`) zk&=^?I$DK~hnZZ3SDB1lo1l4>rKYE-sDqlUYKe`ao1wF*wYImoajUYgudTnpxx&N5 z#YMZcv#zqiz0AhY(bCf?$jh{&7m3@X)ZX9V;mDkzRE)l#tmMk!?e6dJ8r|{q_4fCQ z`1em0TJ~@KU3K2nQZa$grWqhY%x5oJg^v#fum-YTU@NqsNc_AVZ1-d8mNN zlPFWFRQYA4%aj-UjbuKhw(xMd^A_saD8PYy66%YkrmAWTF=}(_s<6iD zL923AD(kJX(wdL0xb~W=t|9s=?67m0#VfJMMha}P$^JGA>$0LcD{ZOJqB`xho>p5e zw%n$v?W^5}%jCDmifb-&%RWBKmuFJArrD==08 z4{UIAr*3=j!VxA6F2fM(Q*gu-OYCsD6=y8X#2R<(4#ysc>9 z3(GFYTnfxG*BlAWHs|aI&N}xz2hTnSjRw#{7d-~iMkgHw(n>eY1k+AOeFW4}SFP5? zR%bma)>?NB1=U`MT?5!16JYk)XS+}kaC+z_HQ5@QJvPR0xg9m!8Ou#WIn~(hG~N~K zZ2{T@On~;_gsX;uYhcC+Bi$tfPO;z>q-}WN{s>U6x8-jx-bsd?uLJtuTq{ku<(gwI z!RD%G-a2dlBo{j0I=)?c(v(w>y6%|oevs#*14p~xO-mke?w9kfyz9${UU%@akIuK! z#zTz!>aF|kyYq^(Ej(%5Q-8bB*E65|`P&EoyZ6z1Uv>2im!CW9%X6;&?BIh=?xDi5EPg#fzf;6k&HjvjIQ=UIZfnzKBKfbr5nPtRmsAU_~pc zv5k%ESre0{#xHvD3StDn+3dK-H}a8;`_tJRh4#ii7P5_ZMC2N^AV)3wu#S1eV;Cnn zMgdqMkC+VOrgApOpcV3vitJ-3ML9-B%B+(qhgQ=JC@N1uHMJL*-z|MxoQ<~`i$!}4~{ zS-nzHq%}3$RzWLHu}+J!h6R9P9lKh$wzjQsg={TN+bq;pmam${ZD)16*|!Gvx33)R zylk7=*D}_%$8BwFW&S(YdlC1xxsC2_rQ6%S`mv3H{puZKO3@k~mbJ+ZFL;v*QQ6MZ zV9(7M8>>rQ-tsn?fE8$9X-lm1YV5nn9dCY-Ysq<hjph~@$Q$xZars) zH9T1X*HEYjX0M2?`^&9{mT=9aFRmaQu?f2v#z;PDN|$R*!rHinINouNL9AE!3Rb>0 zbux!ne1!@h`OD;GX@(svE+WU&vQ$oUj^PVhnre59ZVYmB&1Gh%N|T{41~YPH{L&fs zQD5KUa*6M%{$?p7Sdv^VF_z*L%>Lkay#W-GTt!J!hT&ud%tUidZryN}zM|s4({Odx|Hp^;bE7{mSNVE0qY@<$l z%*Q4bx>v1j9oxImmmM~vi@i*F&3naTY`4JKZGd+(H{PTAcZa`??}l@G)Lh*oc9;F& zYp)mqMqanZVGV9SQ{1E$7qr8-y={H-d*2uJ_mhQI009VK04;xc%m?uDm)8Zc1b;T3 zVT=&|kC#&ATMnB53PAIr7oF%er}@oEo^Ld-86n36?VEx8adp)hSv?uL%VECtq*EZ{ z99Qfot)Se4`kclU54zU0-txAy-Q``6`v8>wrjd8N(G1tDUx{tvl)vlh#6k%IPBM6^ zbe-m0-?*^L+w-%t6zD2m`%B*L_R6yz@iiYh(-$9Hf|y#PrxW*^%`Gj!56dKlFF@*7 zPx#j7wBye^+?Dri=v=}5Nhq)N%n!ifCht4nvs`<%XHD>~SAFu4k9wGFo#$qs zeWnpEZ`QPf8* z`A1H0_eCz}f$ju%ZO4BrGIRh4NZ==aDR_SSHhLtLYQ`rr3^;=VKmst4dJZUo?h=)U?hq3a3 zW9UwP_=hoAh>-?}i1lo9kQj+bG>MdGHFS81X;+1s7(yEiJ-V? zhd7ETB#NeJZKVhsSQLZ{AzXN%Knl?YkmHK(qZzg+LIyZimZ%@b!7IbVDzc~+z{ntp zk&BB#JbUAYd4Y<7Fg>{#j9)U0L4b_Xcyq!?K+Bjdy(k;MNF%1Cjj92TU=lm#cuhPN zj$EWajiZjEVJ42Fjk04(=SW45gO1%0j*N7V2!xA%mCfC=}*8Uz$Q zj3Ygr(HV6EkP!Jj2#E*?d5aABIR#WZp;3``(~xX4PU-lO2RV@e$&M(=jvm<~&Ikww znUc4-5YD3`<`_QTQ$QPek@L8c_mhzcd6Oxbku?dEB1sAyxswhV{**eol;c8>Ht~`a zNoI^Qlt77*7ip79i9br|j#b${=hJmNWsCdAUABIhGvhk3DIW zX$hEh*_TzR4RCpwNlB5PxsZ~1nJc-Lg*lh`(UO1Qm~AqW?f9Bh2|K7s5Ro~QLMc%+ zIhmZ zi!VT(%7UG#$VuQCo;3rV!lVk}q*B_SI(nrML!|~`rRU}-U>c?#u_j6S zl*wY9W}2lxTBJl6rA^7E-r}Z6dZulvrp|(<*BPgFDx`2KCS|%aLx89BQl~Mxq^vSS zbgHL?+Lm8}sC~*Va{4ixX^(SZkYu7EFrLu@?3?uTX`IxAF8maBTAcY~D zGNK(x@~Btxr+r|Iu~Qfe5+APOs#XH4e1NJ`;taH!s#78?f;tnrS_i!P8=nxWzIvareNvJ>L5H_IY5EB>@U`y}?-uDHgq4QsSB3$#w_v@ol)NgJ}a zux#imYU^BB3$!p>v_T8Ea@)23QMEi< zG-(U5Q!BF%>$fEfxJ8?`E6cX_dawZ7wlJ8tgoL>Is<_UjwCCoy0b97WVy|Ipxw}$C z@2a`MqNVE@wxDY-_d2@#BDtp9DW#jbda=5!3oxkrx{~6$vU?V^Te}1kySN)Dx4XMd z(YwBjFrpj0s>ry+8@a=Kyr-qR%KNI!+q_MgywFRh(mTEAS-sXPtk0Xh$hp1TJE++E zy|)6q;%hPCTfWcYz397`=excJ{@T9oTYc~wzn?BsX_3DMObrN}z#CJ*44k|F`@r97xDs5v7F)r0>cAK*yvDo1;Tpjn9K9VJ!pNq< zBupM4e8O#+!YX{7EZo8mvcNE`3MRY*z*!`f5&!}40XjSaI{d>syceG8Ve~{Pz-mDr z(8Cj;#60}N6~M%05yTg;!&0mPRXoKAaK%>)0$ZHKPn^V1e5y5UDriD0Io!n*5XK_F z#A>|6Xq*K@Y$VtELYPVduK~qvoB>MA#!$=#Z%oA<@WyHU#UvoeSe(T^yu|oB#yJqG zn|s6?0K`^o$Q;1OdQ8dwZBWOpO2>5p$#&etLF~j$ECGC6#djdZs7%U%yvl=I$RQBQ zCxFSuAj6#e7F7C*$ePBYOv<9{#CzP!lKjM|Jj}mL#l?KZ|A>(@(juw)kZt0rvdWhG zGpmLH%*1@nyqwKhyvx}9H&)NLT@BGfbti?b)%>V4q0nNm%fezgfBO}rp%(ALw0nyR2s;R-x z1>Mf_oY1=*%+HX@-yF`c?9Je;$bbyeA+63O&CcRX$RQoi39ZsqEX%XZ$U=;)(+VQd z!q3`_&i)M00ImMZZamHd-O~u28IIi08`8{3-Hu00(JrLa=#bH++`~S7&;e}~CoR(^ z-O{Q2)hn&i;tbZUEY{~d(_?IEEAQa8hR*le$EzAe)(Rr}dC~eMYUCv#-*6p0uT1?Vxozj*~)}8$h zZcQbiiYhu?+KxTcK#kW}oz;5H*c;u~QVi9Ao!AeZA+!ywr+wJBovc$V&$7+Y!fn^e zfzoVE&z3FFYTe3|joG1X+#_AmpiR@$Fxp@FJJY(%j_lgUtlIq?+(P}@mR!~2NEjM| zK+4Lc&;G32=P1pVy4~D;&EJj9;a%LE4bmDt-Yf0VudU9#%-LuC#bAx!9Ze6`Ed!7% z3CB_?0t-3RO$HAh4F}$xq(Idce&HCN;Tpc-5&ks-`I8(T;un6&B3|Mqe&Q&e;vJ3) z6F$Q?W795f4=og5oG=7QbHOfBZR?XOXe zE}pys1;WW5blP_w$DupIsn!a_!5bdgnz$^gznZKb(hQq19DW&0X8tVHY%Pnh&wtI- z{>yqGf!*7=eb}M{-&-2z%W)C!wP zJ^_I#ZZ~E+d4rThTz2+V80$=ai^2_HZ5Nl@}8KQ$ts@-L3?EZ+|(|MG#l@-n}gG+*O53>+i~sm7FZGgdzLa12_u%-LfAPG&`Anhto`1QVANpx}`J}(& zk$?I`j`*stHmu+J;+p!fujH~n`-W)xwy(gipZg~O`mOK#st^39FZ`rW{Go6Bo{#*S zul$(L{FU$gk`MilFa3;9{fTe=hL8P&ul<0}{eAEKdao(;$ou5s1?ow9{^+0n>c9T% z-~R4D5#%5L@<0FdU;p-h|M;K(`oI7D-~ayq{}6!Wq}5)W_2%7wFce3!G*2{DSGILu RIF@I+wr@PwcV3SG06SUE)64(> literal 0 HcmV?d00001 diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index c9581ce37ec2d064e235faf29001f8a21cd2d171..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6966 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 diff --git a/screenshot_vga.png b/screenshot_vga.png deleted file mode 100644 index 91c812f4a9868a73d0bd1a63385de47ec497f330..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18934 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 Date: Thu, 4 Apr 2024 19:31:25 +0100 Subject: [PATCH 78/98] Added swap file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From 7abbaa1de0180d0ed3bc69ba42c0be81bb4fd8cb Mon Sep 17 00:00:00 2001 From: James Howard Date: Thu, 4 Apr 2024 21:06:39 +0100 Subject: [PATCH 79/98] Added F5 to reload and refactored page history to be more memory efficient --- src/App.cpp | 61 ++++++++++++++++++++++++++++++++++------------- src/App.h | 8 +++---- src/Interface.cpp | 3 +-- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 3029add..5a8e289 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -26,8 +26,9 @@ App::App() { app = this; requestedNewPage = false; - pageHistorySize = 0; - pageHistoryPos = -1; + + memset(pageHistoryBuffer, 0, MAX_PAGE_HISTORY_BUFFER_SIZE); + pageHistoryPtr = pageHistoryBuffer; } App::~App() @@ -140,6 +141,9 @@ void App::Run(int argc, char* argv[]) { if (requestedNewPage) { + page.pageURL = pageLoadTask.GetURL(); + ui.UpdateAddressBar(page.pageURL); + if (pageLoadTask.type == LoadTask::RemoteFile) { if (!pageLoadTask.request) @@ -379,6 +383,9 @@ size_t LoadTask::GetContent(char* buffer, size_t count) void App::RequestNewPage(const char* url) { + if (!url || !*url) + return; + StopLoad(); pageLoadTask.Load(url); requestedNewPage = true; @@ -395,18 +402,24 @@ void App::OpenURL(const char* url) { RequestNewPage(url); - pageHistoryPos++; - if (pageHistoryPos >= MAX_PAGE_URL_HISTORY) + size_t urlStringLength = strlen(url) + 1; + + if (*pageHistoryPtr) { - for (int n = 0; n < MAX_PAGE_URL_HISTORY - 1; n++) + 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) + { + do { - pageHistory[n] = pageHistory[n + 1]; - } - pageHistoryPos--; + pageHistoryPtr--; + } while (pageHistoryPtr > pageHistoryBuffer && pageHistoryPtr[-1]); } - pageHistory[pageHistoryPos] = pageLoadTask.url; - pageHistorySize = pageHistoryPos + 1; + strcpy(pageHistoryPtr, url); + memset(pageHistoryPtr + urlStringLength, 0, MAX_PAGE_HISTORY_BUFFER_SIZE - (pageHistoryPtr + urlStringLength - pageHistoryBuffer)); } void App::StopLoad() @@ -461,19 +474,35 @@ void App::ShowNoHTTPSPage() 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) { - pageHistoryPos++; - RequestNewPage(pageHistory[pageHistoryPos].url); + RequestNewPage(pageHistoryPtr); } } diff --git a/src/App.h b/src/App.h index ab12649..b1cf281 100644 --- a/src/App.h +++ b/src/App.h @@ -22,7 +22,7 @@ #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; @@ -81,6 +81,7 @@ class App void NextPage(); void StopLoad(); + void ReloadPage(); void ShowErrorPage(const char* message); @@ -107,9 +108,8 @@ class App 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; diff --git a/src/Interface.cpp b/src/Interface.cpp index fe322f6..23df01d 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -235,8 +235,7 @@ void AppInterface::Update() FocusNode(addressBarNode); break; case KEYCODE_F5: - app.page.layout.RecalculateLayout(); - //FocusNode(addressBarNode); + app.ReloadPage(); break; case KEYCODE_F3: ToggleStatusAndTitleBar(); From 5e354fe824484a6581f966d608328d43dc62159c Mon Sep 17 00:00:00 2001 From: James Howard Date: Thu, 4 Apr 2024 21:15:06 +0100 Subject: [PATCH 80/98] Fixed bugs where address bar kept being redrawn and mouse pointer type was not updated on loading a new page --- src/App.cpp | 9 ++++----- src/Interface.cpp | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 5a8e289..249ec27 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -141,9 +141,6 @@ void App::Run(int argc, char* argv[]) { if (requestedNewPage) { - page.pageURL = pageLoadTask.GetURL(); - ui.UpdateAddressBar(page.pageURL); - if (pageLoadTask.type == LoadTask::RemoteFile) { if (!pageLoadTask.request) @@ -171,6 +168,7 @@ void App::Run(int argc, char* argv[]) } else if (pageLoadTask.type == LoadTask::LocalFile) { + ui.UpdateAddressBar(pageLoadTask.GetURL()); ShowErrorPage("File not found"); requestedNewPage = false; } @@ -434,8 +432,9 @@ void App::ShowErrorPage(const char* message) ResetPage(); page.SetTitle("Error"); -// page.pageURL = "about:error"; -// ui.UpdateAddressBar(page.pageURL); + + page.pageURL = pageLoadTask.GetURL(); + ui.UpdateAddressBar(page.pageURL); parser.Write(""); parser.Write("

    Error loading page

    "); diff --git a/src/Interface.cpp b/src/Interface.cpp index 23df01d..2013e65 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -64,6 +64,7 @@ void AppInterface::Reset() if (hoverNode && !IsInterfaceNode(hoverNode)) { hoverNode = nullptr; + Platform::input->SetMouseCursor(MouseCursor::Pointer); } jumpTagName = nullptr; jumpNode = nullptr; From 4c5092db24bcc4b7954a3c37e1baaa2d6bae928b Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 5 Apr 2024 16:39:52 +0100 Subject: [PATCH 81/98] Various fixes for mousing over elements --- src/Interface.cpp | 6 ++++++ src/Node.cpp | 3 ++- src/Node.h | 3 ++- src/Nodes/Block.cpp | 2 +- src/Render.cpp | 2 ++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Interface.cpp b/src/Interface.cpp index 2013e65..1306caf 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -142,6 +142,7 @@ void AppInterface::Update() SetStatusMessage(imageData->altText, StatusBarNode::HoverStatus); hasHoverStatusMessage = true; } + Platform::input->SetMouseCursor(MouseCursor::Pointer); } break; default: @@ -462,6 +463,11 @@ void AppInterface::GenerateInterfaceNodes() pageHeightForDimensionScaling = windowRect.height; StylePool::Get().MarkInterfaceStylesComplete(); + + for (Node* node = rootInterfaceNode; node; node = node->GetNextInTree()) + { + node->isLayoutComplete = true; + } } void AppInterface::DrawInterfaceNodes(DrawContext& context) diff --git a/src/Node.cpp b/src/Node.cpp index 2efde28..8d95d04 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -45,6 +45,7 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = Node::Node(Type inType, void* inData) : type(inType) + , isLayoutComplete(false) , parent(nullptr) , next(nullptr) , firstChild(nullptr) @@ -182,7 +183,7 @@ Node* NodeHandler::Pick(Node* node, int x, int y) return nullptr; } - if (CanPick(node)) + 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 diff --git a/src/Node.h b/src/Node.h index 346fff9..56b807b 100644 --- a/src/Node.h +++ b/src/Node.h @@ -125,7 +125,8 @@ class Node Node* GetNextInTree(); ElementStyleHandle styleHandle; - Type type : 8; + Type type : 7; + bool isLayoutComplete : 1; Coord anchor; // Top left page position Coord size; // Rectangle size that encapsulates node and its children diff --git a/src/Nodes/Block.cpp b/src/Nodes/Block.cpp index e1e0d18..8028550 100644 --- a/src/Nodes/Block.cpp +++ b/src/Nodes/Block.cpp @@ -19,7 +19,6 @@ void BlockNode::BeginLayoutContext(Layout& layout, Node* node) layout.BreakNewLine(); node->anchor = layout.GetCursor(); - node->size.x = layout.MaxAvailableWidth(); layout.PadVertical(data->verticalPadding); layout.PushLayout(); @@ -33,6 +32,7 @@ void BlockNode::EndLayoutContext(Layout& layout, Node* node) BlockNode::Data* data = static_cast(node->data); + node->size.x = layout.MaxAvailableWidth(); layout.PopLayout(); layout.BreakNewLine(); layout.PadVertical(data->verticalPadding); diff --git a/src/Render.cpp b/src/Render.cpp index edc12a6..de0b7fb 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -418,6 +418,8 @@ void PageRenderer::MarkNodeLayoutComplete(Node* node) for (Node* node = startNode; node; node = node->GetNextInTree()) { + node->isLayoutComplete = true; + if (IsRenderableNode(node)) { int nodeTop = node->anchor.y + drawOffsetY; From 5ffc41392ba4c9015fd8b1368639157e0f411af1 Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 5 Apr 2024 16:40:13 +0100 Subject: [PATCH 82/98] Fix for page history when filling buffer --- src/App.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 249ec27..160137e 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -410,10 +410,17 @@ void App::OpenURL(const char* url) // If not enough space in the buffer then move to previous space while (pageHistoryPtr + urlStringLength > pageHistoryBuffer + MAX_PAGE_HISTORY_BUFFER_SIZE) { - do + int firstLength = strlen(pageHistoryBuffer); + + if (!firstLength) { - pageHistoryPtr--; - } while (pageHistoryPtr > pageHistoryBuffer && pageHistoryPtr[-1]); + // This shouldn't happen + pageHistoryPtr = pageHistoryBuffer; + break; + } + + memmove(pageHistoryBuffer, pageHistoryBuffer + firstLength + 1, MAX_PAGE_HISTORY_BUFFER_SIZE - firstLength - 1); + pageHistoryPtr -= firstLength + 1; } strcpy(pageHistoryPtr, url); From 3a7e0b6a06f88cbd679f3eaa53c2f71911d4870d Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 5 Apr 2024 17:27:39 +0100 Subject: [PATCH 83/98] Updated README --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2f947d2..c060111 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ # MicroWeb DOS web browser +MicroWeb is a web browser for DOS that runs as a 16-bit real mode application and is designed to run on minimal hardware. ![Screenshot](screenshot.gif) -MicroWeb is a web browser for DOS! It is a 16-bit real mode application, designed to run on minimal hardware. - ## Minimum requirements To run you will need: * Intel 8088 or compatible CPU * 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 but can run with as little as 384K -* EMS can be used if available and is recommended for loading heavier web pages +* 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 * HTTP only (See HTTPS limitations below) From b2ef7606222a13c3317753b48021aa7b5b28090b Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 5 Apr 2024 17:28:51 +0100 Subject: [PATCH 84/98] Updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c060111..91e5c66 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # MicroWeb DOS web browser MicroWeb is a web browser for DOS that runs as a 16-bit real mode application and is designed to run on minimal hardware. + ![Screenshot](screenshot.gif) ## Minimum requirements From 8073a2cec270b5b7a4f6712094e0c9caf5808e30 Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 5 Apr 2024 17:29:13 +0100 Subject: [PATCH 85/98] Added idle sleep for Windows build when inactive --- src/Render.h | 1 + src/Windows/Platform.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/Render.h b/src/Render.h index 509663c..02324f6 100644 --- a/src/Render.h +++ b/src/Render.h @@ -75,6 +75,7 @@ class PageRenderer void InvertNode(Node* node); int GetVisiblePageHeight() { return visiblePageHeight; } + bool IsRendering() { return renderQueue.Size() > 0; } private: bool IsInRenderQueue(Node* node); diff --git a/src/Windows/Platform.cpp b/src/Windows/Platform.cpp index 23b33c9..d6169f0 100644 --- a/src/Windows/Platform.cpp +++ b/src/Windows/Platform.cpp @@ -74,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, From 411c8e6f9ee59947f5d2f9d63e6d511bbdb04053 Mon Sep 17 00:00:00 2001 From: James Howard Date: Sat, 6 Apr 2024 19:36:16 +0100 Subject: [PATCH 86/98] Added support for Amstrad PC1512 640x200 16 colour video mode --- src/App.cpp | 2 +- src/DOS/BIOSVid.cpp | 45 ++- src/DOS/Surf1512.cpp | 824 +++++++++++++++++++++++++++++++++++++++++ src/DOS/Surf1512.h | 23 ++ src/DOS/Surf4bpp.cpp | 2 +- src/Draw/Surf1bpp.cpp | 2 +- src/Draw/Surf2bpp.cpp | 2 +- src/Draw/Surf8bpp.cpp | 2 +- src/Draw/Surface.h | 11 +- src/Image/Decoder.cpp | 2 +- src/Nodes/Table.cpp | 6 +- src/Nodes/Text.cpp | 4 +- src/VidModes.cpp | 31 +- src/VidModes.h | 3 +- src/Windows/WinVid.cpp | 10 +- 15 files changed, 912 insertions(+), 57 deletions(-) create mode 100644 src/DOS/Surf1512.cpp create mode 100644 src/DOS/Surf1512.h diff --git a/src/App.cpp b/src/App.cpp index 160137e..1162179 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -520,7 +520,7 @@ void App::LoadImageNodeContent(Node* node) void VideoDriver::InvertVideoOutput() { - if (drawSurface->bpp == 1) + if (drawSurface->format == DrawSurface::Format_1BPP) { App::config.invertScreen = !App::config.invertScreen; diff --git a/src/DOS/BIOSVid.cpp b/src/DOS/BIOSVid.cpp index 721e565..55839b0 100644 --- a/src/DOS/BIOSVid.cpp +++ b/src/DOS/BIOSVid.cpp @@ -26,6 +26,7 @@ #include "../Draw/Surf2bpp.h" #include "../Draw/Surf4bpp.h" #include "../Draw/Surf8bpp.h" +#include "Surf1512.h" #include "../VidModes.h" BIOSVideoDriver::BIOSVideoDriver() @@ -60,15 +61,15 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) int screenPitch = 0; - if (videoMode->bpp == 1) + switch(videoMode->surfaceFormat) { + case DrawSurface::Format_1BPP: drawSurface = new DrawSurface_1BPP(screenWidth, screenHeight); screenPitch = screenWidth / 8; colourScheme = monochromeColourScheme; paletteLUT = nullptr; - } - else if (videoMode->bpp == 2) - { + break; + case DrawSurface::Format_2BPP: drawSurface = new DrawSurface_2BPP(screenWidth, screenHeight); screenPitch = screenWidth / 4; @@ -82,16 +83,14 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) paletteLUT = cgaPaletteLUT; colourScheme = cgaColourScheme; } - } - else if (videoMode->bpp == 4) - { + break; + case DrawSurface::Format_4BPP_EGA: drawSurface = new DrawSurface_4BPP(screenWidth, screenHeight); screenPitch = screenWidth / 8; colourScheme = egaColourScheme; paletteLUT = egaPaletteLUT; - } - else if (videoMode->bpp == 8) - { + break; + case DrawSurface::Format_8BPP: drawSurface = new DrawSurface_8BPP(screenWidth, screenHeight); screenPitch = screenWidth; colourScheme = colourScheme666; @@ -130,6 +129,18 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) } } } + 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) @@ -171,20 +182,6 @@ void BIOSVideoDriver::Init(VideoModeInfo* inVideoModeInfo) offset += screenPitch; } } - - //DrawSurface_1BPP* drawSurface1BPP = new DrawSurface_1BPP(screenWidth, screenHeight); - //for (int y = 0; y < screenHeight; y += 2) - //{ - // drawSurface1BPP->lines[y] = (CGA_BASE_VRAM_ADDRESS) + (40 * y); - // drawSurface1BPP->lines[y + 1] = (CGA_PAGE2_VRAM_ADDRESS) + (40 * y); - //} - //drawSurface = drawSurface1BPP; - // - //colourScheme.pageColour = 1; - //colourScheme.linkColour = 0; - //colourScheme.textColour = 0; - //colourScheme.buttonColour = 1; - } void BIOSVideoDriver::Shutdown() diff --git a/src/DOS/Surf1512.cpp b/src/DOS/Surf1512.cpp new file mode 100644 index 0000000..a629f54 --- /dev/null +++ b/src/DOS/Surf1512.cpp @@ -0,0 +1,824 @@ +#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; + } + + count--; + } + *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 index f227e1c..048d220 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -45,7 +45,7 @@ DrawSurface_4BPP::DrawSurface_4BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; - bpp = 4; + format = DrawSurface::Format_4BPP_EGA; } void DrawSurface_4BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) diff --git a/src/Draw/Surf1bpp.cpp b/src/Draw/Surf1bpp.cpp index 5f09089..c540a53 100644 --- a/src/Draw/Surf1bpp.cpp +++ b/src/Draw/Surf1bpp.cpp @@ -10,7 +10,7 @@ DrawSurface_1BPP::DrawSurface_1BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; - bpp = 1; + format = DrawSurface::Format_1BPP; } void DrawSurface_1BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp index 96b2be5..0f72f62 100644 --- a/src/Draw/Surf2bpp.cpp +++ b/src/Draw/Surf2bpp.cpp @@ -14,7 +14,7 @@ DrawSurface_2BPP::DrawSurface_2BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; - bpp = 2; + format = DrawSurface::Format_2BPP; } void DrawSurface_2BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) diff --git a/src/Draw/Surf8bpp.cpp b/src/Draw/Surf8bpp.cpp index 7691761..d7c318f 100644 --- a/src/Draw/Surf8bpp.cpp +++ b/src/Draw/Surf8bpp.cpp @@ -10,7 +10,7 @@ DrawSurface_8BPP::DrawSurface_8BPP(int inWidth, int inHeight) : DrawSurface(inWidth, inHeight) { lines = new uint8_t * [height]; - bpp = 8; + format = DrawSurface::Format_8BPP; } void DrawSurface_8BPP::HLine(DrawContext& context, int x, int y, int count, uint8_t colour) diff --git a/src/Draw/Surface.h b/src/Draw/Surface.h index 7bf83e3..dbebf64 100644 --- a/src/Draw/Surface.h +++ b/src/Draw/Surface.h @@ -39,6 +39,15 @@ struct DrawContext 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; @@ -52,7 +61,7 @@ class DrawSurface uint8_t** lines; int width, height; - uint8_t bpp; + Format format; }; #endif diff --git a/src/Image/Decoder.cpp b/src/Image/Decoder.cpp index 610fc77..878424c 100644 --- a/src/Image/Decoder.cpp +++ b/src/Image/Decoder.cpp @@ -185,7 +185,7 @@ void ImageDecoder::Begin(Image* image, bool dimensionsOnly) onlyDownloadDimensions = dimensionsOnly; state = ImageDecoder::Decoding; outputImage = image; - outputImage->bpp = Platform::video->drawSurface->bpp == 1 ? 1 : 8; + outputImage->bpp = Platform::video->drawSurface->format == DrawSurface::Format_1BPP ? 1 : 8; } void ImageDecoder::CalculateImageDimensions(int sourceWidth, int sourceHeight) diff --git a/src/Nodes/Table.cpp b/src/Nodes/Table.cpp index 2fbbf5c..6cbf40c 100644 --- a/src/Nodes/Table.cpp +++ b/src/Nodes/Table.cpp @@ -32,7 +32,7 @@ void TableNode::Draw(DrawContext& context, Node* node) int y = node->anchor.y; int w = node->size.x; int h = node->size.y; - bool needsFill = data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1; + bool needsFill = data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->format != DrawSurface::Format_1BPP; if (data->border) { @@ -726,7 +726,7 @@ void TableCellNode::Draw(DrawContext& context, Node* node) int w = node->size.x; int h = node->size.y; - bool needsFill = context.surface->bpp > 1 + bool needsFill = context.surface->format != DrawSurface::Format_1BPP && data->bgColour != TRANSPARENT_COLOUR_VALUE && data->bgColour != tableData->bgColour; @@ -741,7 +741,7 @@ void TableCellNode::Draw(DrawContext& context, Node* node) context.surface->FillRect(context, x + 1, y + 1, w - 2, h - 2, data->bgColour); } } - else if (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->bpp > 1) + else if (data->bgColour != TRANSPARENT_COLOUR_VALUE && context.surface->format != DrawSurface::Format_1BPP) { if (needsFill) { diff --git a/src/Nodes/Text.cpp b/src/Nodes/Text.cpp index bbe553e..fc0ba7a 100644 --- a/src/Nodes/Text.cpp +++ b/src/Nodes/Text.cpp @@ -36,7 +36,7 @@ void TextElement::Draw(DrawContext& context, Node* node) if (textColour == App::Get().page.colourScheme.pageColour) { - if (context.surface->bpp == 1) + if (context.surface->format == DrawSurface::Format_1BPP) { textColour = !textColour; } @@ -261,7 +261,7 @@ void SubTextElement::Draw(DrawContext& context, Node* node) if (textColour == App::Get().page.colourScheme.pageColour) { - if (context.surface->bpp == 1) + if (context.surface->format == DrawSurface::Format_1BPP) { textColour = !textColour; } diff --git a/src/VidModes.cpp b/src/VidModes.cpp index f1a2a6d..16a744d 100644 --- a/src/VidModes.cpp +++ b/src/VidModes.cpp @@ -4,21 +4,22 @@ VideoModeInfo VideoModeList[] = { - // name mode width height bpp aspect % zoom % data pack vram1 vram2 vram3 vram4 - { "640x200 monochrome (CGA)", 6, 640, 200, 1, 240, 100, DataPack::CGA, 0xb800, 0xba00 }, - { "640x200 inverse monochrome (Palmtop CGA)", 6, 640, 200, 1, 100, 100, DataPack::Default, 0xb800, 0xba00 }, - { "320x200 4 colours (CGA)", 5, 320, 200, 2, 120, 70, DataPack::Lowres, 0xb800, 0xba00 }, - { "320x200 16 colours (Composite CGA)", 4, 320, 200, 2, 120, 70, DataPack::CGA, 0xb800, 0xba00 }, - { "640x200 16 colours (EGA)", 0xe, 640, 200, 4, 240, 100, DataPack::CGA, 0xa000, }, - { "640x350 monochrome (EGA)", 0xf, 640, 350, 1, 137, 100, DataPack::EGA, 0xa000, }, - { "640x350 16 colours (EGA)", 0x10, 640, 350, 4, 137, 100, DataPack::EGA, 0xa000, }, - { "640x480 monochrome (VGA)", 0x11, 640, 480, 1, 100, 100, DataPack::Default, 0xa000, }, - { "640x480 16 colours (VGA)", 0x12, 640, 480, 4, 100, 100, DataPack::Default, 0xa000, }, - { "320x200 256 colours (VGA)", 0x13, 320, 200, 8, 120, 70, DataPack::Lowres, 0xa000, }, - { "720x348 monochrome (Hercules)", HERCULES_MODE, 720, 348, 1, 155, 100, DataPack::EGA, 0xb000, 0xb200, 0xb400, 0xb600 }, - { "640x400 monochrome (Olivetti M24)", 0x40, 640, 400, 1, 100, 100, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, - { "640x400 monochrome (Toshiba T3100)", 0x74, 640, 400, 1, 100, 100, DataPack::Default, 0xb800, 0xba00, 0xbc00, 0xbe00 }, - { "240x128 monochrome (HP 95LX)", 0x20, 240, 128, 1, 100, 50, DataPack::Lowres, 0xb000, }, + // 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 } }; diff --git a/src/VidModes.h b/src/VidModes.h index 76a3fe0..f46e010 100644 --- a/src/VidModes.h +++ b/src/VidModes.h @@ -3,6 +3,7 @@ #include #include "Datapack.h" +#include "Draw/Surface.h" #define HERCULES_MODE 0 #define CGA_COMPOSITE_MODE 4 @@ -13,7 +14,7 @@ struct VideoModeInfo int biosVideoMode; int screenWidth; int screenHeight; - uint8_t bpp; + DrawSurface::Format surfaceFormat; int aspectRatio; int zoom; DataPack::Preset dataPackIndex : 8; diff --git a/src/Windows/WinVid.cpp b/src/Windows/WinVid.cpp index 6d6d6a9..403899e 100644 --- a/src/Windows/WinVid.cpp +++ b/src/Windows/WinVid.cpp @@ -123,7 +123,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) HDC hDC = GetDC(hWnd); HDC hDCMem = CreateCompatibleDC(hDC); - bool useColour = videoMode->bpp != 1; + bool useColour = videoMode->surfaceFormat != DrawSurface::Format_1BPP; int paletteSize = useColour ? 256 : 2; bitmapInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * paletteSize); @@ -141,7 +141,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) { memcpy(bitmapInfo->bmiColors, egaPalette, sizeof(RGBQUAD) * 16); - if (videoMode->bpp == 8) + if (videoMode->surfaceFormat == DrawSurface::Format_8BPP) { int index = 16; for (int r = 0; r < 6; r++) @@ -158,7 +158,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) } } } - else if (videoMode->bpp == 2) + else if (videoMode->surfaceFormat == DrawSurface::Format_2BPP) { if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) { @@ -203,7 +203,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) } - if (videoMode->bpp == 8) + if (videoMode->surfaceFormat == DrawSurface::Format_8BPP) { colourScheme = colourScheme666; //paletteLUT = cgaPaletteLUT; @@ -222,7 +222,7 @@ void WindowsVideoDriver::Init(VideoModeInfo* inVideoMode) paletteLUT[n] = RGB666(rgbRed, rgbGreen, rgbBlue); } } - else if (videoMode->bpp == 2) + else if (videoMode->surfaceFormat == DrawSurface::Format_2BPP) { if (videoMode->biosVideoMode == CGA_COMPOSITE_MODE) { From 6a778d4a0d0a421774f2355fd09d2d9d5c0e2dd6 Mon Sep 17 00:00:00 2001 From: James Howard Date: Sat, 6 Apr 2024 21:37:12 +0100 Subject: [PATCH 87/98] Crash fix --- src/DOS/Surf1512.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/DOS/Surf1512.cpp b/src/DOS/Surf1512.cpp index a629f54..9cbfe5d 100644 --- a/src/DOS/Surf1512.cpp +++ b/src/DOS/Surf1512.cpp @@ -562,7 +562,7 @@ void DrawSurface_4BPP_PC1512::BlitImage(DrawContext& context, Image* image, int int count = destWidth; - while(count) + while(count--) { uint8_t colour = *src++; @@ -643,8 +643,6 @@ void DrawSurface_4BPP_PC1512::BlitImage(DrawContext& context, Image* image, int destMask = 0x80; } - - count--; } *dest = destBuffer; } From 40e2e31a48e205db5ca569a1c22b4c8d3ba260ee Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 10 Apr 2024 13:10:03 +0100 Subject: [PATCH 88/98] Added bullet point to fonts and added support for password fields --- CGA.dat | Bin 20835 -> 20835 bytes Default.dat | Bin 38012 -> 38012 bytes EGA.dat | Bin 29745 -> 29745 bytes LowRes.dat | Bin 21295 -> 21295 bytes assets/CGA/Cour1.png | Bin 1487 -> 6374 bytes assets/CGA/Cour2.png | Bin 1816 -> 7551 bytes assets/CGA/Cour3.png | Bin 2379 -> 7292 bytes assets/CGA/Helv1.png | Bin 1409 -> 7136 bytes assets/CGA/Helv2.png | Bin 1784 -> 7478 bytes assets/CGA/Helv3.png | Bin 2151 -> 7878 bytes assets/Default/Cour1.png | Bin 2294 -> 7884 bytes assets/Default/Cour2.png | Bin 2925 -> 8357 bytes assets/Default/Cour3.png | Bin 3843 -> 9150 bytes assets/Default/Helv1.png | Bin 2071 -> 7925 bytes assets/Default/Helv2.png | Bin 2597 -> 8304 bytes assets/Default/Helv3.png | Bin 3285 -> 8899 bytes assets/EGA/Cour1.png | Bin 1988 -> 7624 bytes assets/EGA/Cour2.png | Bin 2495 -> 8112 bytes assets/EGA/Cour3.png | Bin 3181 -> 8752 bytes assets/EGA/Helv1.png | Bin 1780 -> 7276 bytes assets/EGA/Helv2.png | Bin 2262 -> 7731 bytes assets/EGA/Helv3.png | Bin 2702 -> 8538 bytes assets/LowRes/Cour1.png | Bin 1465 -> 7400 bytes assets/LowRes/Cour2.png | Bin 1848 -> 7755 bytes assets/LowRes/Cour3.png | Bin 2627 -> 8537 bytes assets/LowRes/Helv1.png | Bin 1479 -> 7299 bytes assets/LowRes/Helv2.png | Bin 1827 -> 7602 bytes assets/LowRes/Helv3.png | Bin 2301 -> 7985 bytes src/Nodes/Field.cpp | 59 +++++++++++++++++++++++++++++++++++---- src/Nodes/Field.h | 4 ++- src/Nodes/ListItem.cpp | 14 ++++++++-- src/Tags.cpp | 10 ++++++- src/Unicode.inc | 6 ++-- 33 files changed, 80 insertions(+), 13 deletions(-) diff --git a/CGA.dat b/CGA.dat index 106aaefb31fc6771c326e94a887caf1407e49a1c..68c0643e86d3a2b3b6b0cf8694ef1963dcf0518d 100644 GIT binary patch delta 118 zcmaF7i1G0v#tnZN8Cf>}V-#m$VMzFpFu9-AZgV)>YGy{Z&CFc8SXmh={`{$!%*7wP zSyVun6)37H`i~td8X!}=dA@8JH;aUg1jFQo8bFNu6%Z2z0R^)u q75)d4UmSR|pB;7!ldmX3vsWoL43j-HuCqHf$q|!aM%uI9NQDU9gB}O~ diff --git a/Default.dat b/Default.dat index 97ba800d326511ec155b2a74489240b894626802..5600dca24a18c99a5b5cc29202b2358442f8b8b7 100644 GIT binary patch delta 173 zcmeyfg6Yo+rVW1?8Cf>}V~kcDt4nKgGO}$BRAJ@k0qU#x0|XTy1NIqSnEX;#e6y!X zoG8#-1_>J*381i|XW--y9;Tbkyi~*{@6+SpwqpQ68K4S=&AAB{N|O~d*|_-`jeQgP&C^B delta 114 zcmV-&0FD3rr~>?`0j?~#Kq&{a zCmSgV0R^*BBn1zX?lIhx=`9ztNi<#+ldwpSvo=a37PFdNU;&fvEg+NNKLoShVaFh| UU4OU%lVF4;ll+GZvyFuB50*46>i_@% diff --git a/EGA.dat b/EGA.dat index e1b4b49dd42003e1f8a9b25fab24e5a53d688b93..d6b3a8bc9b4df65c9e5c0a9c8c58d859d6661016 100644 GIT binary patch delta 139 zcmdn^f^p*u#tnZN8Cf>}WBkm@%D|BDAz`urd*}W4zAF`r$(Y1H)tk_Ke9(MFchnah&63E%@={N5N!4;rh)> zM4q!UvTg2>I?BPpQ1J%{Di|g|R5G5Npp-S4LxFGdIuVY|JSt+mED1n!COa5pZJuCw umz#~jqNb+CVsgAq-R6(Be|b3=Z0dl(hGBA`cf#ZgUbY~;n-}`@u>k;4sW@f; delta 97 zcmV-n0G|J^rU9>}0kHi60RyxC0oMhSKL>1+q!baeQV7lqlQ0v6v!oQ~1px)Kj2guV zlW-!WlLsIVldcp9vkW8_4U?cSS(BeI6|;~r-3ya_JcP6HJ^c-n;7X;F2p|r#p-hej DE?_0^ diff --git a/assets/CGA/Cour1.png b/assets/CGA/Cour1.png index 3aecdef43596cfacebeab3d27e22401683da34b8..52b7905796cd6fa72e31f83bfa8c4c5d13308199 100644 GIT binary patch literal 6374 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 delta 1470 zcmV;v1ws1eG0zK-BYy>!NklfLj9Bu2-?RkODi z{>!^5yVT3q;_N+Aja~-lsYSUH(pi3u^}1Zm6CdtgIKS%di7`T19G3NR*}Ia&N;;yD zZAlRXoa%^6eagQR{;B(Z*6M+#;Uf%U^-6uj6a>fSxCg#~E#2hpBskLDHq>CCut%H< z{2$?ApzY7ct$#CXkzIfI&TYfhFA@T15w1J0I46a;GIu|NE(&ER!H)LxJHgRjD8vYe zr%esxZc+&6DICDiJP5vVv|^yfum@?cG7fMFQn_p?5lDv`1I__FBuSUA(x`!*moce| zD8TMzNV*NlKDf#|FQxH95X3%qslw=G99HlSBQ7lB5P$4|>?2U4%Pw3g*#5NkhTy#7 z@dFscq{I*NXbo5yNpw-%p!gPtd$dJyyWnp_@Z~HXru3S3!g1Dh82t!jRdaztBq-A0 zZ#gg?aE46IBO)Wb+^%+hRG6}mJ4YO;QIO5Llw~{`{DK@#^CsT+_N_Mdl!6rFg%gQkr z;&aLET-a0_hV3lXk*E}c<^*c-7weg)laQq}BGU(b(F*GTf&-L%xmra47FaVX%qs#V z3uVnR9&Nm^VwFcA1C3`oWhl79T;dQ%6r&fUMSn)y<@ztV*pM<*lqW50M@kC>(-NR| zlMS%8gL4Z*vZ`p2oZB%HFanZ67JB1}I|SMTS08lgf*8F9an45l6amIPl?oqG3w~S$ zJ5Tu$#7DO1>G^Xx2gVAQi>g!JGdTW~6o?D#UJ6lBHp||+lwYxfe5L2WDxA)SO&bpG zy??RcqG-xL3-Z%o5d<8+EMx)oynHP3gvif-shBR&BN}SOpKB|Hx^gXYOau*Wpi`() zmrwlP&D!Q|WQ3p_97%QLdK8JIIl~lmI2Xg*E=Es9qfeD;QN2FJ!n&MK1pO(JcEn0P znB{70s_3e7V0G1>h2;r|x2KiLYAF{jdViPC%`Hg92pGLkn$NO(csBhc7_lmN1A9)!6g82#f3 z1|-&Vx1s<|>_DQjm5UQ#i*sOXSy;>0BRR0g>gXvIFfTx99ep~aF82cOe;jIU`ykH; zXxGN!z7!6QkGx{Tk#`?GWeY**Du3=HvKv6Siz704O37-&h}=i7ebhnFdqS?r4yTPP z6gA5yh;vHHFcDgkzn&2h;H4wbz5Wo5jUIul(O1n7!?tscD`XfE3}Wq{wo{T!DF{>EnnR}dM^p2 YA7gO=6Bbi1OaK4?07*qoM6N<$f_jy~PXGV_ diff --git a/assets/CGA/Cour2.png b/assets/CGA/Cour2.png index 244f62fe1624b795f7326488051c4aaada710766..2676c5282ad2c2353b1f5017eda53d38b7dcb968 100644 GIT binary patch literal 7551 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 diff --git a/assets/CGA/Cour3.png b/assets/CGA/Cour3.png index 8b2dfa09c2d4773cf219f85da3ae3bd4fb045da2..8032611d6e1b670c0617fbc38155ab81e11d355c 100644 GIT binary patch literal 7292 zcmeHMc|26>|DPhVWQ|B=nnoeB7|Se~vNIxkQNoxxm@qSD#uyY8X;Rh}l;!Fc>WUW1 zl8cm*rBYE>q{xz;ET#CKp}Owx*YAG6_x1XH|7%_|XU_AS_vih2p7(iwo^#I3F}ocW zE5uieLm-e9Bui5V2t~3a1-)ClV z&FfXUz2u9$D4_Y6RdG&fZvqBZ25*#WZH~ND_IsP?td6fo(Bsc{&YE+SZ6BlA*Z`2NH>v)rLyZud@}*G!JuS`Vz7Tt|QWcr!*C- zjA@$--+Qw9RCQ^V4sL6fwH%JzcHCKXmCxR_Zp(~DMDM<=B0Vv)`i#^lP>JkHUCNhwumra4u?rVp@M^hk-<7h2FnMfjmP6r zXbcL2L4XJZJA}?5^AL3Q1_8wahbh3Ous}o87<8zBlkCL^;t*jl@Hq4G+fFDk0o_t;? zf+>R%Bw!(#5@CV?5U30ajY^o`^}^vP7#*q(LZ6DmB6KPGWCTtdR2-*E(blJusTe92 zy@-lLXLHDO3Lu~Y$&oaWheFlG;n7$<1R0M(BXo7VsR%EeJ`SOaLzA^B6g(NNPhCV| z%c6nSCi^cQm4J#0Qjzf#J*qwhi=a@^AeA?0F)s`rKxlhm(Re+5ZErpAh0zFvB^cR} zh%gLtQIDNJnd8l11%UNJqf;5d?D@R&U?*At;K(5hWYflKWAt%Y9W)Mu*2U`T{0Z6# zu-Kq)1+3a=q&9B;2$e!G2RX^0e`o<@9{|Oq`+S=Z!Gr~nISiH)gW*qv2@*jCq4U)M zHTddtf;EFe7SzK$7@!JDrV_Jf-3|7dZ{Ek52LTh0y@u$s81%=Mn zFM&)Es7Qpd$y{K;QlO3thbX>ex(@&@lwYglTR-gwhQ*R~F;qRY9s&ot4WWxhV-Ywk z89;bbwXpymqld==y7MRaJ3E`<%?T#603#o;jKK2@7Sa59hN^${nZ{r7!M=dNL}-jI z0u8?LPTF_^1_!=j3uhZ@fD$Z?-)3tdSUhZO2#fVF5G*bPlE83|K}@DU4PgCMr*FRb zKX8lUzk2lFg)fFJM4K>}Az-!oa%_X?f1CaXzy$_t8U>)U8GkqQ#gGMA7F|KWF)#Fi zS0?a6hx&e{`|1)wA^jI$U)}d#%m7mVO!ANP{VCT^x&Dy?{|NlkT|edeM+*ES@K1OB zzsV*3=d&_E2k-lW!6#vYtaTCi6fMTIbY+7-Aju0pOCV>{l)z3=4#~z$v{zV6Zp|9U z%sep=HbgQta^n3lxVMhg1(l>;o}7yanM}C+?5@(2q_fF6K@T^U>x-eaGJTbFiY>hW@e14oCaD2V|bUB02O^|DB*OD+y5qDUKE{182xT>RVhhYqg0 zQtnXK_Boa2YPzNMmh%^GXlrrxC&=KJbwz2Hb`%66*s(-4zannL;716dYFf$PcP%%= zKj`tfV=shk*+h9-#76xZjf1itrR!I8r3dFp^}RoroOeleI=ljCcdAQTNCPLvXs4^(y+9y zc_R%5jV=EC(Y-XaDe0h;TV;Ood#9t+*#RLT<-QB1a-Mf}1L1XT1}HuIu<7VWwt+cS zl{wqjg+~`oY>w;bzmr+XLM5k-bPs*dB3FCUKzGkRhBbGb2Zhig&s$%=-&`icN z>odDW2KMw{+|$MH_!K1^Ga=2b%TeabYFE#O<1=nXZx$PC$-gI@qnvNKax!5;?A6d- zC2f&x$n0P(r*K65?rMYGs+fRIIhkLMNu3<)K$Wb`c;eiZe5kRoW%b*7R;;I1|I6nm zNkLv0R~l#>4ILYoCi-EFkHg;=*2)&tVNG()>E`L4hZ~{-ea@6lol~G)Z@B;Rs$rvO za+6p=Uqx&Fg!iMtIRC95d3T|!Urv5B!I@@1=~t+K5@8h|+g-q|lXc|{OmJQdD#KB8 zHAJQ8O%c3W>rQ))QexIb_S%6yys*CBNX|=Z8d>$7abw1{8r_6LnmdhP*?x2RBcIO1 z4;VIEd{Jq+yX}&?VdEd~dWNi+Ri91m+75iN?Rw-onr>6}*)b_Jl=ff7Br)&$-JJwKtE>FcV753nlzpv-B^Rtz2!{k|N@C zWTtRu=U~Lfm!+-E8*lIv6h_9{ciOJbv*Bgcp4d@ovHHDv=TPeLW{r#{J!Pbr#!9Ew zTl+ue?kDQRc)P!9`_$9AtLrz#=fn5(y0|MVy8+z`l~H-gtfh4tv6Kdvn*EROd>N1A zOV(o1j(hCwlcN#ae$`4ZR#XA`>K?)RGIhmjN~ z_l>DXVvU5wLS20vk?gFA!aXhrRajL?1}lw^pK=rXu=7meajETb8dK3n;Pqy*`PYwz zs5lfrqYDf#I$}+X~mFTb@q-8>*5RPy}FbrBMVqV3PX) zCgq(Ha*~2M>*{0GHtRM^f3D&o9%t+)4qff_^jGt7w~!ahH@rM=SANQ|&ZX#PacLtr zrN>JlQ!?+eNMv3Bt@Z{G*Mmx=;$$Lm@AIYh`pjJ4w?RC9dQxKQUcfHZE<_cMz!hR` zZ4qAMxN6D^8?Pi=#|^E;|zJWDU>1aZiw)7z~om~FBNWnxat9<&PQpRPIiwEUy< z(}8`(`+Dt)gtv#fm9C`94Q5%?aC*CY1J{2jboOlRZWxVlQ{6H?c+M}+-t|7Z&Scv| z;B`VB(lsb4re~mypI~v(2%gl?&?kh6R*O6{J3`#hQ7*(k$rvt{*{uF-R_OZ6lNEYt zL#JhvS#;~dD|3A?lgR;jxQ#HCw!5X#U0=GgxZXJ<=L5g!DY<hsW?pL}wifFlCe{?@Xj~I;Hyjk7?OMi7WccL&NHJY`GMfJE{ zR^%~$V^Z6-Q|XmdY2A5F$#J#Op{81PM%ZAT(avZ+DjyceI69iKuCcP1bi9V#fBSrB zKbG5i)7ebzf{M`Io>---8hAfUBqMt6KK^L#$X?qPmWO4LsznzoJ!NB(7`+Gg`mSeq zL`{!lwZa2HT_pcxT2>l8q$}lskjLejBsIC(UNvXdtKtpIEUK8*6Dss4zc+0i!FnN~ zq}*$zQb*2OVM5#)e&#zWw$%km9K>FdFtC104C-3}QDr*dLq2_;ETo@MNs|n-rY=#; zUUn&?Ib9>{&eE;beVtO>V)mN7KKtHfNJyxQur~3UgzRlBNZ|;>!-tPtg{-IDr`J4n ze;S(lcB1so-jVZKomN+5*Io~p74D}^m)%azRDSSc8KqrJF?@H&#o7|PG-skHjbT38 zJ3Qvz@ca{z_jDajrlZPtS7CgZ@vWN2>b_AQlKLrCn<0stZw;sunU_=?N>!D$TGTs& zVQ&vyKmBIu$%!Yv0Rr^m1GhaK#f zZP@B0x)w1osH1o2>|9OJT9vKr4bSft@muYu-$d4~;*kV*K>H8JcyGV+P{{n{Dbw5r zg{E_4YrN$~dqX&Q^M#=&vD0hPH=QD0xs7LvOm7=*+0Rcr_2Hr5j~0^I4%2*N&%^%$ D_g|^O delta 2369 zcmV-H3BLCHILi`{BYz1JNklb2z37cqtEWXY)ws0w1EaOX`XJK%OnO7 zv2F3^^ZESz{EU@IO=?n;n$)BwHK|EWYEqM$)TAagsYy+0Qj?n0q$V|~Nlj`}lbY0| zCN-%^?O?Sbkw_#Gi9{liNF)-8L?V$$B$D>>`Fxf-2rev<vf;{uL&YeD@ za~)+hqiKuXK!bE{o3BZThn(xZFLBFYI~ApsEl4Di|AK%_AdyHU5{X2zYgI;Bk7+$D zkw_FqBoc{4qFH9#^Cu=7pA&_R<^n%qdkc{*_VM%`zW+ZK*#-{wruAI~O^S3VWw&?> znP6|UtS4ABZ+{!NLyz`exn;L}ri?|@Ilk?(+v#oNcIyMY_dRY|=}td+Ei?5!g|&K3 zf9K-?OoH(~Xc!$j54P)~H?m@PJk9h>r2Ub#F%AJ_d}%`%$U5#=23h)>6|A{F@OPmn z(2M4Wgyv5JA3JZ`@8EoWnj3?gf5POYNDQ9QslMe)NG08Tq6{;y}bS za3tH)JS#MD&G08VKP8<87d}hya8g%!UUIn)Ug!P)9C>7by6O;Pr7!Fu`Eu;ThY7#Vn%Eb&~4s zHUitzt=jh6jVEB^=^VKCP%~L>>4_DZ+TgO$XGM<&&zSz8tx>> z|1KjYILvTIqM(PYPvDw&kN7I` zqr;fYG2PFS@AdS!7!ZsVFfcMkD{v)5n3zlpl9ab*wnzEHb+Va`zRN3_Kel1 z4hE`F1&NTZ{-&_zmX9!UVf^pmV5xcx)i$fR3?xQJ@*+@?KvZkGkqebYWAl3DtQ zu5ND4Mve&Uco1=XU3+u>UP@k`+JiwVB7c4Bof`>>m!Z0=UljCd#?wX)gCQ7daAFER z1Ntb^qmU=pbAq-8KnJW-k0pVu;)!RP%0~t3nOR3Jo`L-N_arxtbp2POft@w!g=O=I zjx+&et&o#-LZPE(EL@eVhL5bF-D0UdLXF5Hk$GRJ#zmYQsR;7AIrughbwrlubAPP1 znw+zApS(2L1Oz(pPxx)3B#wx$5MwuJ-pUc-wyN^PZT~tVFNx&t(6{BFpc`r2vaE>U zkT|7aqrsCvX2dh^@JZs?w$5tE--7%b5Jx&u?XF66TWm0rn4NMmp#b#j-&uon34j$F z+@eOo29c-rI>#A5XNG4F%d{ZR8h?1Ll&@WzQ422PUJ}X%WYWUMmntohL42hh@nQFT zpS>X8e)@c#uUwPeU>O5sZga1$5!!w*&-2~mgDj7^kudVEyPc}#ZZWtt!aEE15Z}`x zDP|-@XK+Ngkkeq-2zgQg-paZ)bg(0S2o=AekRIPC7@V~%(ELjaIB@Q4ihsTXu?wnW z)k*RJSQ+tcP)`eJw^(Xtsb$Q_GOrwoKD!|d&L&^Vt*?M{L1uZbdK~MffV~B3*3T(ZyK8?c=+{{d z3^m*u!((4@boV*QIA5ympnssNO)T@IA=BCTUnL3jdsuuM;=p0Q?Bs}mUhYn&msckc z4=bpSND@Gw6n~L{gu5jxv++$=NtVg(dXstS8)?fqbqU!FURAmdbwutKw8U}BP4OLG zfiHXTYKM_g>3$k(506FTbJMCum=fydjb%^bJtye;_!g{N3#R+Nmw%3}b&`U4MFy<1 zog-pB80`*jd5{ybtf1FP>WnlM@1{siYQ<`KbF|DWM`8gisaTEU`)DbDhdkdPhOx^46no0Uz3WjVgnS(DCgA31d)l4J|B3(OA?G1HAD4^ey_E48^ zY0eQD-daBKJFK4_-+u{DH&7d|&mUhw z1uIxa67)Xs1buHNK!Sg&QSJ4`c#0-+v7}X8`pIsi300B7vvOPypr!*1J~J|8QWp>X>xfJ;X19fro`1rRqEhZ|8J z6EeQEk|sj`1m7Eri9|xg6YBX8lE*j?xH`(F5TTkoq?KWO=*{M@T}(+N5{ZJF nL_$v*I}fP&u(ERCCd&T+oQQaUag_T800000NkvXXu0mjfF!h93 diff --git a/assets/CGA/Helv1.png b/assets/CGA/Helv1.png index 4a249773dc0b92f2ed36db715bd83b6ccd4787d8..ab4abb0fcf5eec391e25c37439b10a9e5570e615 100644 GIT binary patch literal 7136 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 P00000NkvXXu0mjfF|Vl7 diff --git a/assets/CGA/Helv2.png b/assets/CGA/Helv2.png index 3af093109729bd63fbe89312fa30f1fe63858fd3..dbea7d0243e0d2907303a0d0d9a6fc588a4a9446 100644 GIT binary patch literal 7478 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 literal 1784 zcmV@F2i000KRNklOs13A(tb!7r|_ebf{if3_D z(8Bm%`9*?i-z#Bz;FI!G;fcGws!7gRPb<}mgbTFZbNalt{gZF6?XE&WEtE7ZXHI_i zEoLR`%qkbQsv=Oi*RIK}!s9>}3P}8_e`UCJ>sOlwlUjAIzhFA*-p}`!qw*1H>gUPp z?aHN>;OYtRsT&oqdBG)EeZC0YC4bGfU2CCSTl68qHg?)+r=51%=|q`*|HWxg{NkH- z!l%CDPCMmY-HP?nMr!K+sgJi_)1Dv?2aio>0ykA^B-< zm?9ci5{Tki=2_#?=Xq}YFq-QohCW@_!dOQEt(ogEGx8U zxBtR_gC*$J|7!C$#;)vd0ZQRiLizmlSqxz&g8YugftGMOlui+tPy|F3GAya}=g_7I zeMwm;+ABMPlp-;NF~m`Q+UJ@iP=uf}?i84>l5L;O#9M;D1!b&h%S#i#0d|ho26jRn zPO+G8_18ntBBRiz22JJFyH&scy-eT`4_Abg{Cckn=ip@?1jI45)@=M+V7Nd4T23(g zK)K3>7YKgJFIr0v`MxiWB6K)J)eX6bg74nZIK+4j)I{q-`&08_fNl)R z^MOME(1XuN5s_!T;aE5C3zaxyP`6LbzWSU}Ey17}+bHogQ04{pVsm{4rz(miN2myVAtnx&Rmuhyo)`$Wz#+;GUe=yjheI52 zRo;3LGvOBq+-&Mbk`jFF+qA+p}14lnqj>0$0E!-G;~i-yI<_%@wIN>h`y zD$dR?s?|OzO;GE4zv==UFD=U4V z3%lWjI0BZ)6QD?J;k+D!SbGu$bsnPdrq7o9j_|56#d+k>b3n}{rO4)1d_O7cn`ev0 z+&IJ+yZB=`M9n7cD;@C86)k#kwS3lRb9TL-Up{>V>TOL9v1HfI^&Fz$D2sW(bhok` zxaS-8zUAzjLqj^jlm&(OM&Q~WtBpehu`hD+=2dE}gbC7)*cf$tnT7r4B^)RS1=xdc zI#gP^k3K~(^Zk|`9AbG;AsZRZ?q&WZafpb<9LDAy*2Rc#MR#WC<`6fBi0Ba+CV@eA z<>w)?=P=ZIb1zQN98!2*s!8`}Hk*nQ`Y5@?kPotS6kS>U@vPKb(sNk+OLP z)-&NeM2CLGm3Jz)xVH!YrV8$8b-)C8fTmyzvBW{i{_)|RUn9H0okPBTHOaPZ&f;m> zUD}eB&Na;o)%x4{+#VY-fye-Ij1H(-*o8+=xVrwk^9bG7pjwF>8yI<`L!lP=F+@I1uU zpbd?ly6QYcN`T9SWWdp#XV=d-_i6ZJxn``oJkUFMy3nmKJJ95%nT|7sAzIg)6P>=yDpd^_y{+Nm3Lr?F)} ai~azG&q7gN@e~- diff --git a/assets/CGA/Helv3.png b/assets/CGA/Helv3.png index 923c61ef710e96bf816b00d1749a5e53ab5bc96d..daed9672c78f929b5802c7847f32f294d0058f11 100644 GIT binary patch literal 7878 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 SOaXpNfh^5zOiOopM*SCNhq2)R literal 2151 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 Q`(FmyW|#F9E1$T30m4hG+5i9m delta 2283 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 AApigX delta 2919 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 Ok5C3?`tSAbhW`s|KhJ6a diff --git a/assets/Default/Cour3.png b/assets/Default/Cour3.png index 9820ccc1f93595bd4007756309802050166aed04..200439a5ce60b234149f5a67444b78e009f60096 100644 GIT binary patch literal 9150 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 literal 3843 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 diff --git a/assets/Default/Helv1.png b/assets/Default/Helv1.png index 396b446139d82bf7d049926e1230bde2197c76f6..90e3a33de6cac5eb9a59402e4d8d9bd4607205f5 100644 GIT binary patch literal 7925 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 TT|W-?U-VdGE2A3*E|LEQXzass literal 2071 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 literal 2597 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%0Db?@eu-U z^t?u8(@h)SbyC8G*HybtY*r=oU@nTF=Vr!xt@WbN8}z_%&TlGg@;QnaRHj(kbZnzo zrycr8%i9l;ibU1E4l^O2=hRILjl<4tpV#+gWPUSXm!6akQrN6l6j(zV9O;sH)0RcN z7FemU9=Qy-qaFW^DCRVjY<_conG|>#k`Q z4;0xbiN|~Smj0>!sqT#p$D@flMX3uqC%F0U9nQ+_DjdMaiB+j$%d!s|!wPRIm_Ks1 zPa0o{N=)N^MyE$#TrSFrr9DlT!N{xdlxckxqLO#%?x;IpgkLH`=wTT^`*0&>Z7sCY zwypQVJbe&J95Js~X}-66AvbF(R6A_*NY~sYEk{YghsHZk(yo(&Vrmg&DFeqLKcU7J zxjD2o7_9ZuPW!}LX%SxW-r3tZ_I;fcOM~KNen_}5-K6%KoI=UkIAMy;trX1aXs?ll zyL9ryg6mpg;@ykf$K4K|McL*WlP|O$Rh*%;Tc92LU6(g%6t2l(?KxRqZAenG&r4WH zc?2Tjd$8>@n(w5B^L)u(WTUR&qW!Xrg|jYur+mPymew{o&V2xhnFnB~y?&sT|9q;+ zyXI{5Iu`9)6U}(ps#>ID;Up<4P9{H5{1>@fVleJ% zz!rX#MA^4a2z@oMF`7UU-#d$4bH%nC%ZLNd7Y%>!y~LhV$KP9l3cPLh&NyA5S{!q0 zN)0jb-brW`t;thY(taKmu2EcHqMi(MM45y`qBcs@E7FzPdI=hf*f8?!i-NWS)a(lp z&&)oxZ;0g?xez%|QL;nYM(*p`tOM;c^~CC`%CT+`avo;sB|jBY62T0lLb>!24xaqrsrBlansIu>f;WwZ8f)xjX zxrcVYZ=V;4@jJA>INDoeJ{d}`xVN!K`f-1Pk#qk}h85|uK%RITVdD1nW{KtFVj8*q z#FT;O5tSm-VmUtL!+SNWXMp=;`q5`u<969%L+`^>M{?Rc?5dBfP7J=m=JCt#ssT8i zg&Rg>jeiJ>7-g$pxEJh1{vX<3v~R&ZZ%ciF6X zymMoS!i(`3VQ_}Rb73~{Vz>TotFOn;ybSNi8@s_zavt=p2qZy>La1&Z!rQ%^hiC~>o2%*?Lc$DKTrn3TD%H>{E?bKz#7 z8JLSZx8!{5t#kfG{0ESchc6fJrX|x_RIc=PV?>8&0O{Yp(dZ(XNb?jZW zz%d?Y=ZADU`Y$|!@kJpC4NbNpwrPoG`LzhA2ZxW1-VE$=t`T(PI6}RGk$w0zxv1c7 zF=|YF0>uNDf(4u-Xl3~~$WveH6Vu+dMjaqGX)W`nOcnVp_>QKEFCr1Wac8sn9|PBv zAF~G^&T%TPidNTXD)Yq1?z$%r?EOAq?ZRja$Vd{Teb+hfXsD&STii3by3)$lWodV` z9M!&}-Lc&8bkDBEaqmpPP~@EtB@GtD3#Te4%1R$TUs9^YTO1hXKGFgZ{~G82!So0< zC6Y11`G&7@cyo{7=fIUm@|g1asYqM#Y>@*RZ4F5VI}g1s(2&{dXet&b1{WHnX1AKf zK83l8dnDzAKOY45acd|V<&+f6?y=iDp+V{Rl;zsk<65-5`p9iM-suj?r=#8Cibk9K zEEcHLW=eR67I--vk>qAMP&x3fNEM>K30@$^d{-}zcP-aDZ0{3a-kB|AePwY!gPX*b zJlG&SHW*mZBfvU$^hp%AnAk;hx{*uQVv0I==NoD9ln01~`$2E4`%ewKH?9d994!zO z4E2bO(DAL{$Z(Sk6QzmP-@^o(Ah7G`9< zxI49>leFml7_FWpZBzNQaYy4*-^ry}lHg1jw{eX?0s=Kw;x&(W0sJr($ZvxrPlZF$K=H$c@1l*w5eI{skg&q~o9zM`raiK|4h9lulTu=|%-~6e( zlTru&-fw-V?`yj0%S(RaGHBMblZPJnhCJ9GnlR9M&6MEB3&M3`TfrV2MS4*rQ)ogz z`{a578$O;pB(yF!xV_j5Q_m@^=RH<iS{XLwy0MYa<|$JOeDa1E61QS?af zyD4VT8W7%MTfRPoRxcURkduhKCG~pmSR%Unv^;wW;>#H83s=i90kYPYsO)KO*m`$i zZ)0$#1D}cIS|=y{&Q#u6j#Kh3F-lF%pA8zHVQnK{yU6)DAr9izZe;f=0MRRxBtc(k z9v#o$fW%kmA=a1T6FbxNlbwFBiJM=Cnq-2i^`E?_2L=>|u5FnX3^AA6c{2 zno}BcIL=t>m_l=c2o@>&pSx3o9hlo;hzTWI@Hx)G=5V_E1Y`t#M6#;cF*8a*l$@@s z@=7&z3f+-;nat%XHT_%lBOBIz;-_Ec9OGwg-W8MK$K$xW!C3I1v%zuZ z0UDH`fX+!*5b0DQe&#NTG5SrhZ;5&O)J&4Peu66n!#p^Twm=5e?7 pX(R*4!aPAULpYYM(sXANpof0Z7a5n8yY(+YQzHw*Vgsju{{yICtqcGF literal 3285 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 JPDHLkV1iUj*y#WO diff --git a/assets/EGA/Cour2.png b/assets/EGA/Cour2.png index 2dbd8b3bf481c09cb815ec4f2424d92bf8db96fc..04b7b74d88c832ea0fdbb09ea05d66f554c7ffe9 100644 GIT binary patch literal 8112 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 Avj6}9 diff --git a/assets/EGA/Cour3.png b/assets/EGA/Cour3.png index 147e2a46d9aee7976916681787c02a29d24f6c2f..6bc8ac736864b31248719e5fd059274aa74b1801 100644 GIT binary patch literal 8752 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 Kuo``r$o~R8Xd3hY diff --git a/assets/EGA/Helv1.png b/assets/EGA/Helv1.png index e75a4555859480d260eb030901bd199d11ee7c7a..21f9a1ad8dc0b09b824484f07ad8f1fe0e7f8796 100644 GIT binary patch literal 7276 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 literal 1780 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 delta 2251 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 LGu6#I<{0!}btfAZ literal 2702 zcmV;93UT#`P)N zIA{sdR;#Kl-j~7cd;QHTH7?wru1t8MPR@v`_nV)jo@9;E$M(a5D+8fF4Tz1fALc9$ zMA00;c%#m0Fk~|1Iqx+`?9O+A%{A4Ie?b*2J&%355q4=o7LKZ1dT%NJt7frSEEbE! zVzF2(7K`Nw`G@oW{TDwJ50w8a#T5vK9sgS^^jKFj)%oBLe;3Jn2^>M;q~dfcQ)|tS zt2kk%RcNtT?h=2-7K_DVu~;k?i^XF3E6(F3KeMz$?b&%+tO>uMgHH2|DR^ z^@2mZ*QjZ2nB0kaaO6wmRQ>wqS4YhaII#!6*g2EqGkIb>(%p`_>Mqc>zG@FxdFr>S zHZO=$KHt4^x{izj@<-`*9do|aie%3g0tXEUr-0c$db)yQ=N$5NSfK>AQG*kN@@>c2NmjB zT#Z;jxQia=tAQXl`*CR#b_ZK$H`IgPb|>5U^i^I+=~aZyTWJ^Sf>AZA4e_RAL8B*ip2W>J&ay9(kTE#4YWtW;OzLIrCOT>RHRU4csn zUr4LE!}(G7&`z#u9ku#A*-lLhnk?Iy^v&T)M<7I=vhVtG2Id!d9)Kuh?dGqo9m<H+6TiGkfCmVI4TDic91Sa64xl#JWZ;(A&^oVa#%APy7Y%pctU?)eS+f&&a zwUDE$d?RJw2BbP&ClafBjNMHF8|+*GNfG-#2Sr!j9KkE3sIj2(*lRmzM%-7~e!gnq zSEC}fG_oEKT_)m6Cw|^~o@l1*x1*{b+YenQP~t=%`K|UFiBVD3}xSdlOzuHtgP0O2e&B-Wt}q#+!Y<%D^p&% zdzDIHSL%m!DFU+aEv$m1?k#Ot`ik!L9urEtMMzBT&y$yAJK+-MfTSnRw5D<l72@Oo2A_pt~MslK8O=6RG6k9c_L>);w=w4Bn2#EP%3==R>&ta`TxgJ0H*9s z*|%H0>?_By!MAhu=i-&EPIEWR{aovaU7_q-7?7GWQ)vz^pzLYiQZ6*S)v_wR0sFdt zy)gIS7CPAt%apwY^(;==H^8j{p->hNBgpy-#@S1XzE;bYs$kcgHgl$aoa359Qa6^; z``rq!fTMmsF%E5Lg4Q;&-AX9?pLzmEIfU))64@gtdnDOC_vI6uNcUah!+`9_j^NQa zzA}X_h=cBy%wLo>$hz|!jGjS02P9V`ZE10*ZduAcYq1gCuBwMSyDDbUA+W13%XwwZ zoKM+Twh8VGzbT@cBz6hW)GcgB+7(nz!FL_Ow7JVn*_*Oo(96EN-QcDOaXT07ot-ix zV=Ixf8NLD~+)x20=av0)^^jg#0Ol)>v) zp`P_Adwj7a{P7o|?3*;}Z1GfVm(mHaYbeNSvyuVcSPIf92;s2KsDYXTDf_I=-HimP zTy;B{6drmJf$Dsz-e%6E(B_)tm`a+&%*GrQH{gxD?58EhX@xYeSB^_r_wZKsj#t%F znIRJ>`_wT>%s-BstJ5g^fHR^U4J!7{Ov*k>eC*=xfoI_WF;eUi7JM3bOn05cZ+Wj< zlkcYNr&0Dai_xW(LHfH%c=gZ>hOzW^nh_HyIPu_+s{dd*KVm%Uow&N3f z*>fN(b>!KeM%-s%usYiEE4y{AEqe#uP!htjOSeTt#AljnQSS_z9lbkE>iZz zNX(g(J;%S}0h_|vs6~Jh!r~S$`^s)rkN%?_K(>g`tVA#rDSJAvy^1C25z2m? z_eo<@&q=PI&0Zox?~JMQ^|L8^y3bRgoZ5O)Fyt5==d#yQ_7&8#doFuA2kBmvef$NT z$M)8_DdHS`TJUalv1cvTx&a*|%DTG}R`aRcnQ8 zKiXXOl~7{Dn5p0DTqoR3*_*Q8_xf4k%}J6?2agzQ*LhJ=t&$tnb>mqs_j=hA{`E2G zFRZ7&o9w7VLmRnQ(?*p2eAKfVqfO6~3iz6%R$=oWMcKE)wOI52IWsX%+r(1}-zidH zP*@61KV*>+urS~WKiK*fW_%l+7)QcMahd(;T<1Y2gB{-O_Lfg01J`mEkMA;Y3 zBrRp%s1#CM+s5rv!rDgEnF=tv3q*id4ydkO&4yEKt%Kj2MIfn$<<=OaqoV9Vgs4J2 zi>r0#7+492l5SCxQ1ucX4_M((H9rj1Ef$NH3(Jx7$CUm1ORf_XX|Y%= z7RyEyz+rrSdlF^8UXGEtZmE ztHdA>h@`cZg#!d4OaRZtMHhj;y#ifs;DlzVlPk}G90X-^STqI`fbv4v02Byj&>)cD z&O#TTCj*9}GnX6(L>H-<)(|e`cSCDz>ps^r8QsgTOC?vB zITJ6o%jP}X>d$KLV238+OsDDv#fGo3syzWsZPZ%t<{S43g1RgIU@9CFsarqj)g4^> z?EHzthKdRq&!Ik}Hm5F%-rjY(&aZq}lH-~gb9VF7gQE{kGGF(to_%!e^J)>;4aiIJ zb(8ljh1f5UK^I+x#k@@_TE+X@ry8?NM8zG1>7SxIq&){BSWWA`HXilBiMKf)G^#nm zG*&zzCg9HACnP~tKc90Xp(8Fbd(WxJ!CIO3o{>9WF44Rmr=>|5q$rLkZ1=f-W&GGi z-$l~SP7>YFJX4poa$ky`H`+O!KiihynxEIX^PG26etvFV?cV)z7jkK@+H^cBf&&7$ z6R(=CE!pYm+gT9e1CMsBwDY`F`K5C6pzh6&ou%#*y`d7d^dt7MQi{f%aSt3d9#*$7 zn&}xuT5zX$XP50am{U8~8J3g*Y1^;*b=~SpH1b(`q3=KTXhDjH=)o>BB>Gj=wm|K@OS!Sd-3r$BE4Q>&k&g2e1or~c?IhOw2;{`3inDKS zS1W##=f1X^*o{mx?Fs6QxZ8=_<>zs*XIn6`A$nM`k}*(n&(3#oew}NWoIJXbs+gKU zTwWxQs#x~Yjf90}x>BHl^1LcjbJ^xYPT|nvP7Behw5C4}N43REpD~bd$@^3>*3QoG z71+^Y6<=sZcK0o5*gx^iJzSA}EV+Cjx z9ZQh0?&78l3&f3h9$BQ8U@QwLSc*hpy&s?5@UD(2oo~P4Q`_iC{~Z1CXPl%Qx=va-jBo4KKg^alPR6*b!aeLdw zOGy##iCT~l*T_W&|Xgx+oR5`gdPZ?-s-t6R8efKw|M?jiv>rkN6H-{ExtwttWuhzZIF z%YK#F*q$9In|^e5aRKX|QoKJl2ke~mU$#sJn@8cdOnkh(R3uW={2OMku3fu5YEa%TJMt)9)0HEq`XOKkDrI#6FVMj|JRdIJ zcR<5vQc*s<=fmsm5QxBDhPk<&wYmA9T^e+BV%H(ORh6m2zP?;JqIPuPve(I{vD-$M z`XA9=amS3YuH=m;=d}uoVddbVB-|do$K-&sh&+9}vWGBXRHVA&j&&2!W=3~yN!(cC zi`Bi;y=mnmkkj_${FMqKP7V4FW9jJ)h4DR?L>rY%6=aqhe6k4D6MQ@tuvDdQh7&TVAZwz3s1y)fuf%K4je> z6B~EWOQcS@-e#W-j-+JolrX-xpiXQM{%Ggf`)(5h2#Hsvsi=0wUg?_U%dN~}4z7B5 zuF~vCV1lg7T5M9tWUTV;6c7FD4?PE5P35%JYL?E5+z#_uk*m>@<0&x|TJ$bS`c#;_ zcbo6@^wY8o&B$*nuRZCU-6@#b=&k<1weWGs8Q8R&`wuAIO z9?IZU_QM;?UwtkNNNg5?n%{K0B_Jgg)dT}Bg;kX26|=szm3)~HOts~dCioSegg}-$ zGr(=rm9!a4VfpHksVpx*H`tdAiUR_{nFO=R6d!;G^#bS&KRj%t{5A~ApyFX$*OQPW zwmIOj-gweD4{+S3>9W#EQSllf&{(*j|>g=W%_Zk!FbplFBUxKPa|N^IS9`O z4|65iLCskl0E*H@=_298U`C)m%vcPH<4|c>2aAp0DZmjP=FQ`=u?R#^P>^nrfi8t9u;8?4p89rfB=L(5{ZT*_2K#$#6o*;l|=es?Z^F|MKGU;U@{w_ zr;9}R`u@d&%OeK<^!G~(t`qq01>pd2SpgghKnw)@c&ZDXvY7$gg+2qg0Do#OZYGt6 z0E3$IywJwdnq>FGhMz_{!;MjP4nw6N04CrI2EqlI_5Okf?SB~j z@^a?L=MsgrU{M12EY=oy7(W0kl|^Atv2&+11GGM%4;a8{WGV`dGFoo{Cj)wDxE_s+ zq9AD~q!CJg0hP5Mmq+%a0DLNtT$cgzpuH$)6w1f|jxo>&DKJP1+-p4zfTIloJq+5& zfToWE7Esu77@)Mt%!N_$si+{87sk-gYdw_=M*>Fc;V3;rkkrV)2u?#`Xc#>&Dn*Yr zPsL9xmSAU%hw1Ag=g8+~?3iR8jm7Z=>xJP*Wd(76E;%uL0Y@I0pEf-sJ$*y85gH8D z0Esq0{RFuH94@F^KC2#5R}Ve6LZx6gf}CVfKMY?o9YC=C=yMBT)L3&4K<2SHPAnD^ z597xG<(tk`0~GgN=h)3G3YlLIb6|kVFO~1bVn(JT=7u=L-+})RlcP5)$nW3d`3e2O zLg4U%SR5Z)j;+^DfWrHGo?n4~Fgbv2ip%4KSpSQQ`d>KQyfCdmTNWo|fqzH9e||LI zOPGwgR6(J0^@}A__*ulmxa2^9I(G}eJkBpsyvcra0Bn@stK`pl#xDd*Mgs;^Jwr0w z$ct(SM**N<$$Ew;IMs`a)}wf>rvT`AmHlAnvS_>@G6x{g!7>8xGgw4(_Zh17U1w|m z8cmQlz!wn`tOq3c!8qw*um(n0eGF{=ZbNYherx>mYH|GLK_X!n<^#uXE?8^6;2ZgNB!EwKNt(SK#W5H@dZ&SHmv)#}Z&4f6Y2_rCznGi+v106#A4??PV) znNQ1t9R!Sdeh%E3z>N;^W2gJB5`H257mx4i`!9L`see29NBRAouHWhUM;Z7>;NR-{ zovweBfqw-4t*-xAy2O4yD+7Mu|GpsbNtn^M;}G~1y@YMGjSIf3mf?Q|AjeNAfRiFT zYZ6gpNNCA&b!BZOX?5`Z^@z0v!6~@&-S#HVFjTbklp|(laCDPl zy(UYkI>sU38i^A1xnRUOK;;n)*G##e82<#aPTWgn+;OXAab9v*_Xa}ink&(~Q2PnH z4njgvZtcarc6a1-8l5)Swk}=WyUf}jFHQ{g*@o@Dw1!pESrLz+zSTCX_@WwdYsDGV zaG*`x9wIaIhKmo6ePGG+gk__9a!iXo-(4QOCsevEv?&9lQGKnJP;l=d0U0P;bCXM8 zGIuG}YZQDsaLi?*C9=|S$u0k<%`3Wi*BmKm(Qfcg*c;0p)jH@TS7@tT>(8WL4)qZ(?cZb`h+3{PW)mamEG{g3p(?0lHvBSIFHQXV zQ^6Z*v(Ch8i`zaO9wfN*Mwl3vYDabuP2SFv;-tM?>=`>qG(LDFCR*^C2oIuuy&C#_|$nMH;fwjcY6;@UYn6PuZb5{xnbiQpht$2v3uPHh0aB@boxn|KsYDMD4jYx;O5S^4M@k|nAs6Wly zYG##3Lwy(8A#E1cDmwOgwtvcY`i;;`vhmum{l|S}cZz+CT0@D}O3Nj3s*|4E3Bmf$ z5v8J!FH2k^y_XwP+C`}x50B{9F#h;VVxuS3!9Va@->wkCHk7HHhM+bHxuCO_&1Rlu z8SSG@kDBEt!{P3VG-;eWX-8i#v(zkoU~zWMi>Q1RJ5-sQN=hM+m3ufS*X@ga->tl< z4xQBgWN6wz?Ni;uU5*dTh2zjt;KRvP$lSS`JOQVP*4{%)J$1kHz0WmIW33vEdvDBmd6mbW2^ zv?_R;n2B8RFTDKexxIapDg&pO@f0Ch)x*pPN1oR>@#eIN<0@PHeXKXHS_8+flg!&z zaLD}43(=#qZJUEt0#-N{C{a_T)rMc-rY~x&jQZp`gK^h|t4XTI1>X)*J)A%X{3=x8fmVm84`&^B9TZW5{V=($ME{7eE$3V z`=)vR`*+y!$MmdC?XjC-U&nYTyM=F0x~UJ_dd#?#M6yFlx z_QS?|G$JM110q`vvAY0%Umu>{W1AR+C|%Ofo_*IrOlWH#w)I%_eLbQEee5!09Ijft z>c<@;s_Lywp9QfG`>fvs{=baHy^v$*k>DE5>eWnJXmggtF~?^Id+g}zk9U7N(WP>y z{sQ67xAu?Scz>|XMvtxf*&ykW(H-?!p<#~P(HJaX{$XVjB}4K%Bi&Q{yk0~m`uv{$ zaoq@rF;UEj__dl)#F*3|5n5D(R2)=k z=#dM?#?jM3G!f-5R=vt$RIWYviJ|LEt1WhLK%K0a@qeUW)a$D_27yQ|!a=h`A#1O%+vKh~ane*0^W8 zJ~BBSH%@K1C))cG4L%EhN&gVT8Dwu=3?Fhwwag1LKjKV8h>~*&g{Z?{5yzqY1n0y1 zLz+3=6WHS*6;IvJ5h2g8_ z1Aovf5g4A%WL8c5`H@)e6GQI{d3-lsH|%HZtkKAGKt!8q_7>0A361xlR^{Ol;*aJo zkuAgqw&2T1v`jVA%CcmP6dlMex~szQ94Tp}Ue3+y+FVj0c1mLSDKFEBqC4N$VAt2| zreoZ?^nJ;vss>+&VYw6s#K+kyF4movGJiWm*p#Z#9yiZig5g1w2=-X0f|`a;nLNCa zB?uvR{&gH!k%sh(LpSy9?4f62kVt@Ee zRRANg3c7@LaWx?hC~2$r#_$*UQJ;r5_~6jW?W#O{hQSSC_~cnTFnsxPFCx9xP+`8$ zx0WwLOZE?F3DYWxD2B&PWyj`No$C>h*zOba@SPlxFuXAQy)pctYUxAn(bglC^Waub z$9_0#_s!Z}xR84hMCSzq_ZzH-zkekL1G%kp%?5uvc@F5V7=CoE1nI``$nM|t4BCU? z!QHmG{nCv=PE<9Iidtwe%}#LeM6EcuFFlg|;O=f`aWnhU6K$@tgwXz_c0~sF!Pu%i zJeX;5#~`=6q02G5Ru|$^2D=`f^Txyb$^)^R7sATZ9$RQ!7`_?958#_JM1M@;g7ekT zL++lBd<@T=yt`eekl&XSZy~ah|5AB5JEGRU^yg}j2@Wh{*^!5L?PpZ4htKEp>1cVJ z{j~^AgRlJGzN35Lb$0ByzOV5Y6n#4Ag0JJVUJ9~5CsG$$68|+kSM4b~M(#!IhwcqI zC5eQ?x);ClzTvNLcB(fN2_VaU?q@&9=j-2&`3HR7^l{@Vt~LMw002ovPDHLkV1g5P B>u>-7 diff --git a/assets/LowRes/Cour2.png b/assets/LowRes/Cour2.png index 945b891d289d463d9e51128ab47a36c291aa500a..9e610a0b8f978f98343c9c5dcf2367fdc005e6a5 100644 GIT binary patch literal 7755 zcmeHLc|6o>+aI!ptXV>uCQGU@i?Pg@k!36+A{3Qm%nT-GhMB=wIz>^oBr0o!q7a2S zDw32IgeakqES2nqh@Ri5({Y~nyytn(=kq@Q9iK7tyRZ9ueXsj>-QVlF=Qq*zJ1y6U zDv5$Xpfv<5yaNa%w2^l$!VkO~AK0e{0tuRgIJvMLh(TaKCc~5JO98Wk{U~4xhw2Fe zak`54_&giX5dYe&#S~C$IretKVK@Es+x+XevR+cs$32wI%928AxAvcDTN-J$I?`uZ zG3fNbHT_V13Ad~AZSC};Pe&%RdaNTsYeZ+BiXHsC%|l%7)8LZN;@B0E>o40lo!OJY z*e}yji=8KXln>T0w`C1~ZY9J`<92@TEErudN$<9FepuETrr~xC>wNfjK+y1!d6c^` zb+x$M(1C)1k+ENe2VE=JpJuCxs%N8y9Z(mOrn?+$C8GGxyV?cys=2lI z`5rY?*tCkD@9Vtwq6IsPHTm*HN3xs{uULf7v5v(EjuvL2-gpc)MO_GH-mI@u)W0w| z;bfQVzKT?6P?=FBTTmF;d#8o+>wy^!^fuT%Ck^J{#?n^}-*6U+ovaxS?x{LbKqsz+w|ZeSfpi zQ4y}-Q+vKud#{LSQAm1_zIWh)I@bNwl5k^4RiBzOUPO~LOj7oVyLNM1QCL)By7BR~ zP$9aMBe-x+_%B5mRQn|>IcYgryJvYmtygodUe-r4@=51u!@PM@tW z1`Auac{c6$sb+nEw_kUD`1Mp`ZEhTf{I2v(ZoJXPtiAa*nRa#fPyy;_VW^9|cgZ>WObMK=H zGp7zc;oc?m7*_>?&sB1-Jf9_Zz6v)RH#R-Zr|dFQ>h&giS8H_#rlNwD3eAqDZiNSh z99k<8_E_U8(Wju)NK6Z?p<+y_=XM95Xp*__-s6*zT(}?3zuw)v>$!ZDghHXL)&XwQ z>SG5sV$7#3u4cC0T?oh)bUiWl-t!S=?I!zF+DSEoJ9=Ac#vG%sTNN~eL0y* zZDpFPQ$D$gG#@j}*=ZPd)KMZq@@1p0W$))e`=|Mm=8+CV69SyoDS;F4Z7?J4Usdc) zl8%~3P}{BK?@NV8?~JoL-V%7HHVIw6$F>0!xJzwHe(r+EKs0smxL9@r*BoPU{3%}_ zZ2}o}%pTQlGJ^IkYR_@!pF4GPE$W1?W#Y>sW15yz4KeHXSlRCO=Ovdc1+H=8r&tck zr#y}K4cwRKna=5r4p7vl5jW%#oS z6OA>_xoD}GFK!75VpakLb!dydb={}$JbC9QD)s!AEgnpMrPIm18wJxf>_?;mvEl=A zZkD~SdAh?r!x<;WqOslCBYa1mUX)%8cz8*WoGLwSQ6hk}-*IG>GP@;qj!&Qg=&7E6 z+Dxf_Y!a`T;$loBjv9-s8n3#iS-N)Dg)$HbX zgPz6hrT(Mv%@1j(8AHCYqv26_VF8mOd!sPTCL| zh?8+R5oc9rtQggMS${5 zyIWYn>%e+fL6ZsL`<=CfR&(pG+FIo&Cr`aq?OEtKe|sEsaToFGIz?fpCZxk;N=j34 z%<+C~K^($}Xq)smP)R(P8M* zLzn9J%?`lChbqpZI;fG-4`pPmOpnJYJt??rnj9FvR#pp>5IlEc{gE@S$eT~x2V9Kh zwx~UnToSGc^^w1<@jB04{9VZP(FE!2PzCQ7w1tJ2+;q*O-_}(=>smUrDrHfvy5|MN z()gR6pa1I@1Gqk>uwuwFbu*^gcIZWWP*3x_bmrYYXv^)Pnc{#`ZNgx)>V0?kBqYLH zA(WEPs@wTxjBhW>=Vw=)wPRMq(~C1fAj#cS;5>D)wZV`WG;Jc8;X%>n(EI>xfI!&o z96ut-hr$MXP`s#g9Ax}<4FpUj;~>tuws2cNGm1CWDuhXK4B6>K3h^PK$&l?vqF4?F zAfQp$L@sSG-p$4T^H1h8=s2rv)+LC%xAIR`NQ2*4XJ)3exQ7y%fdfcpUeFeDsq z0EHu=NHlDvKQL=+`_r4w`jJH-pD+&54~Ec&!)UZWc(B;!fxpN5QxBFC@U;f!Kw&Wg zm?VmMAcfA}v@)olZvbm$%m5aJXIfsjFWD0YENVION*_ytt^H3QUK+iqG{0pJ9(pB` zO!|rQ3t;*#W5^^J#g{?@7QzCU5r4n~{y!c5^mdlXmlK7-Ge`kE76Kjz;Vl3|W{{|4 z%<`oUisDH!Kp2U$lC>Y+zQdXPL3aJZhw z@&^B8XE8k4K}06S#0w}RV4r~^THa^y<{vuK`XfHbo5B+j9EpO$ffw2dfyU_QVRYe; z@4F4g!g#Imx4Xsinuo0|W@SFGyyk)-@C4@=;OFN{r7-`Pr{8q*zu;E*|Ip}va$gDi z?rp~K3kIsyn{5|F|I6@y0{qTkLnTq@EXH3geI?|3T2`DOz?#3?fRhP0=wLrjx*sax z71Dq4?}z&SixB|oe}eq2{Qi@!f7120GVr&+|ETMqbp5Ri{4MZ5>iU06m+0@$$`m^A zy)OuO5*BuCEs!F^y&hvi#24k$DK?>ky;rJf#8JmHy^fC(XZoh@>-Z~Q9xe}pkjUPddjf-n zW{BvP_w^;2dQo~AHv+wzlg~Ui*jQ!RpR~`v_*_Gskk?$^fzWtG6Rqep(9ENA77bZ4 zQ$e&W@bVw|1|-JaxGiRnU6;Ub)Z3@#$x4(hl6q+^h_h(pi>ZAO>nH`rfvI~l%6;MjtTb#sHwN!O zokV99TaD(GMWzl*pVy4WH`R{tXR!z3!ZPKcCCx>f!jm*&R09vj_zO#+E-+n*dB zO`GKxgm5i=-Rj#@ME53P&6|=nU#oV19onqyXt*1^56`D$2EF-8Q~L6a=E`BD$Hqxb z>C)n}6H_XkA7x16bK)5{-?*--xUNyTW+tc6vP4&~`w+iK4l-r5{E1tE&Y=zWoHM9- zPEvDe$t{S+cCRCC(2>Quz&nnG9YraM5|q5-^OQ9$E*&sKN!gcayX1Sb<8cu$QK6#R zK?WA~VtDEOz52Q*O}v}~v~T4_)>5Q5y^}abu#q}4o;q+#9UoWFdr!^N-n-VsP6>&C zt$QqP!OZ4Nc3Qa2UmCp%er>++QDjbw>Z6R#4?BSfY?!T&u^(-*I}Qy;>^tbNF=&mr z%LTDI1%rk$6904eZ=*YAIHTv9I%V4=oL|$4kZz97p_-1GaVZM9S?S?Y@ATtYnd>oL zgCFq(zdHUg&909AlePXHTk8s`vfB*ro>H?e?|ULuGG6K0dB#(yKq;j2BP!`_W43`! z=b48l10NF~r=5cZ#m~;)WP~3*=no0yCcY}cL&ED_UEkiHo!a);@%G`=@#8m|3F}|J ze_xq!w^t)drfU-S_I~O{<4M#kqU&tntMbS<`AFFQfEl+ev8Q-)bEux|cC*q9LRadN z@>K(5cenT{={*OwQ3LzebnN)Lps=p@r&9p0S9>1h2f z`%%2TRgUJ3wXM19CpinrW(nOrMwXknvWa$AyJpu&rISC3$(9WGY+!xl>vSA$NZC#g z*H0C>U!E7@gg$HGo3#zMHUIUeSJ!sUjd@f_*D4Rk=w@|jKXTPuH18Oh@#9r9frx|UD)`X3TN#_HPsX3xT36KNJ;Lbgr|lvnTPI|xm6z<2YSgx9tN30Y+4@+r5Y8a*0#4# zUF)hP2zxwG8X5#2iGLSY+SMc;W%^)#Z?-t6Xz}#gp{Xjv>7s>LiEn)fErT?>Gt<77 zs#n~{cD`1RCswCRwmD5#a%~`^(ObNZJW0Puk%NZ!I~qrL#wUtJ&lBdaT}0gxX{Pj#$rZmM$$T} v(qiPTnTI;i(XEEjTde1AT`V7WT-tD=b8K**>D?LLZ`K6!o%m~}?h*e250#Po delta 1834 zcmV+_2i5q?Jh%>!BYy_}NklthpOY&z5Iny7#R~L?8NZf(&-0Ydi7%8)E_P}xk zR-T1{UVi=xbT_kXl-cn21GwrkldR_u!ry54&Dqaqtz5qR{&W1Bdh0!69Lf!m=kM3l z-)Q*Fp_E)go_~Fwb^g+jc{(Z$;jJg7gikQMpLu3rj51qLY^w?ENW=I54YH{uLu?b(Yl-40!8HkD)Q=vA1pK)zUIhg|ckiw3o&kqrBz+9CiCw5Yf?&EAoXSX}jXB)sPf%ZzZ*-dadI+2} z^qb<^FX^f3qJD1n_ZDoi24f0@FoY9s?~5_zUskubz4}LN3kJ_`CA>&M*}U=u0f-n( zlbnQMj0rs5hd{en9@nJ~mL81B3cE~>1GaeEZHr?bg6~q%ubA{Uws8JreWX8Tld;BhaCDbowmI7a$gamXC8Q%o`!)I z=YN1?O0|K9vCF;%J&jAfOfif}QKZZ`?b(_KrIg2*yb{Kv@`hRhA`l#70_CY&4q+%w zJQ1)%*H(kSOF~Jk$bkx~Q+Yd~IlH!hIB+#Cpc;Y;_I)arE0M!hgAZABWb!NsZ1tHRWe}^#3$gf?~h|Lm$wUJayhJ&)Ddg3~IapZb0+zDFydoj>h5{J1b6Tgt#2IEk4xMpRV z)&V~ssE~v)CSA)__IWSH6!zyOF(#P=v97?Fq$2tQuTHQrFad-G?;gjr(O}+=F-al` zU=h5SVd6gce7!LF>|D;HFi;89V}J6RQo02a&cX5SgGmje@Yt?O-U$dfj4>4gM#v0t z1u9qNb`|EzWh9@e$J8v4KyF&zhA%1dmsFeb+rrZM*@-c^QWW=w)QPiN%9c30DjR_K z1u>?N#h7qhz-6V(uo47gGEIVja%-A!G3-XM7z4R-;G2e5hgrwhbiZAI41eX2I?Cq! zgj~4;ZFp%3U2(0=(?rMlNsK9uF?mziz*Ymrze~I>rZNE@eqr~x__HShp&9vj^4S+8 zT8}9?9OuLY?!}l6d7}~#Q>oyC=;isFCC|i|!dH#7VN8<9m21YBG8g}`M3lg42!#)MxmnZGVnnRenV*&9|3^LKWD?#J+M3kt-H z`80ioU?vnUPdq)U@a!}v?TQkEk_!wLxTri1K$K%l0#}|kMJLh7d4F))fWCe2{6y4Z zd~NFTH07o&Iy|lcFTIq!^ne%>F(!&m)R^c%4TM4i=CUVT)K{~#O`v9F5@Yg8$vcga z=;N?&SikKdS)tW*rXFI$o0`C)T%?{V(Wh0jo{4?k2I#7i;nEfNGTSPSFP-yuoS#x! zmko)tb7M?}j*ivOEPuSif^?whYHw*FAsX`s&d*0~1%<^s4(agW!{q8-ImSdHQR^st zW+oeVQTI-ZW17KXVaSg}%Z0@GNji2Y!N}j&GhR43mAMiqI?_|;=f9l}_LSmR0m#`% zTo{(!j_2MQV+ugDRX3@VNYpxNH&1~}?62N1+ZyT|h$lI;ZX(^b07*qoM6N<$f{uBAL;wH) diff --git a/assets/LowRes/Cour3.png b/assets/LowRes/Cour3.png index ca4b5e356de1374ba0313ca956b5202510629049..6c42f11a70711f18c2621c25092ce7a3aedb0934 100644 GIT binary patch literal 8537 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 diff --git a/assets/LowRes/Helv1.png b/assets/LowRes/Helv1.png index 9384f43774e13feae22dc67474d93e34c6d2e701..a9ee588d0459f0de3edc3b87c49d8d3d54c7b2b4 100644 GIT binary patch literal 7299 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 Q^Xk;X%*M3D*em9L0715x{{R30 literal 1479 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 delta 1813 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 U!V+`h{^ek9vd6g4&@KGG04_T7z5oCK literal 2301 zcmV|AU@l@IiLXC@{(OCX#lJH8ef{@CZjTC= zd>qQ(;d+PsiFpR8lkxn%F;On1t`jQvq^cs7j=$$L9lRA)GVIF;&2~k_(=xR*3v9O| ze6`*Rke?=eAf%#ht|xhpiuvu$LSqHk^Pu0Eg4d{@ozmWXsAg4c8udY$?c}FVWcA)k z2^71^rDa$=s%Zs>9O}|Ai^I2|*!L=F8xA$9#H-6>v;NTDkrq?rYI$1XnoEf$N#VzF2(7RzxWn`uxcsEFy)9>jw@{q}dxbnq2Z?6jX0dD* z*F}rPVzF2(7K_F5dTB%hANPD@4!Ze z^Qt?Yjqa&c)D|Z=*?92d@-L(*%FK0C6)0Oc;fXuDs{ej~)#%zdZ!aG`<>Tb*<@lNT z)1T_#Mn!SW5m?%{pC&1^+w%E-XnOKAW)th7D*L#Odf_CSRHa2q;I;r;iltI+$(PNS z??y!gwxD{jD)y2}+_ZDL+?Jv^;gxV-?z}2my1t9cmQ^1?oq_E%@)?4xJS^2!@)4gZ zMqHE3Uxq4#>FiPK)^abK+X*T=pFHr;^+U)CQZbPVcQSfaR*l?$C~sD*4Ng~jU}io@ zV;8W<)QoF|8iPEPH7i51#`YNaJ&Pn)exHDyB0(#;X1+6;M%9TX@kOaT9W5wMGpX$o~DJ?eNt}cw0OP* zpq6W=lFRAvwxG?wY=_^a0&cD~H!ai^tl zXufkr)F0&d7U(SEJ5P=Gl{qcg{}v;?)lD9#)iyz&VBb71TeiSMb+X05>QcTlBHxI) zCR-DVHAiR~^6yHxM3(Qc`)c93^6et$ChfeRhH8h068EN)R+($FiBk@%FcHeA2{EzB2et|XT^b8bo9uzv0lSIBr)d%2sXKFmO3aAjb@839 z(YJnE&|CE_dadD{%$9D}kp2bZiyoH{z}D{o&u2F}I3GjrJLWqt;yc$tB)8K6 z4ZSrGGi`~9h6BD;@Vkr~zLkq(mKZUM>~|38w!uHEI4}$LI~vfz7jk9$ zohaC0ZQwf}!$y(sAS0s2ccyy(e!dfhXjA!AmNKBj;SRfyYrbQ?a{}K%`qD&kqO=Q{ z?+7=XLL%0FXSL!)xic(Xdtg3z=`aL*2s4@Qwf>x2$QsYsKD*qilKa_R_>RCtD17;|?RP2~EzWoF$|lzz+wYj~yqE8^1lB)P>-Y{^hPYEXe(jVV2C&2FBZssVuk-m%MU6Dy!Q<~d?kd%*6<0P( zG2dCscZxV)<2x;oyZ72I*k4@V(5iw)kxz)flP|0V>_ha({#lg$PJMch8>2?S@wHNh z(P27NX^F*Yc1sXtitj{g{GE!o@J_xH%i`~_JMwn9Vwvxl@4TAtIo6SdfvVB)As z2N0g6*?tEJAd+l+fS7fh!*_y-IWjUc87W@K_7GsW7X0#O_s?RN{)8AYF*8=2ezhs~ zqQ(Zq!31}crrH{~fcd>`vi7Cavc_h<1Af`1d?x^pfKl6N+&^ozsu6%4GQNKn7%(En z{-=xh4h5jicg%NA<~spc^;dSbmIEO#+9#|1iIh3Ba8Ja{u+q=4-&BZ;GzE(<N9~_A)y%cT1nB1zu7$Be z*WI&dhWg00?~-$Js_B##OG}+!&38_2-PSj>Y0B#0iCFeCC>OvACB0jL-3- z_)c`VQd=w*i>v`tp`*V4dzvZSQE(?-#dp|gQdjg@i^XEu(`#A$6)di$mSaB$XaM^U X?g|zR2T4uv00000NkvXXu0mjfa7>RW diff --git a/src/Nodes/Field.cpp b/src/Nodes/Field.cpp index c8686de..903a0f7 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -7,6 +7,9 @@ #include "../KeyCodes.h" #include "../App.h" +#define PASSWORD_CHARACTER '\x95' +#define PASSWORD_CHARACTER_STRING "\x95" + void TextFieldNode::Draw(DrawContext& context, Node* node) { TextFieldNode::Data* data = static_cast(node->data); @@ -26,7 +29,14 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) if (node == App::Get().ui.GetFocusedNode()) { - subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + if (data->isPassword) + { + DrawPasswordString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour); + } + else + { + subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } if (selectionLength > 0) { @@ -39,7 +49,25 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) } else { - subContext.surface->DrawString(subContext, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + if (data->isPassword) + { + DrawPasswordString(subContext, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour); + } + else + { + subContext.surface->DrawString(subContext, font, data->buffer, node->anchor.x + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + } + } +} + +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; } } @@ -341,7 +369,14 @@ int TextFieldNode::GetBufferPixelWidth(Node* node, int start, int end) if (n >= start) { - x += font->GetGlyphWidth(data->buffer[n]); + if (data->isPassword) + { + x += font->GetGlyphWidth(PASSWORD_CHARACTER); + } + else + { + x += font->GetGlyphWidth(data->buffer[n]); + } } } @@ -414,7 +449,14 @@ void TextFieldNode::RedrawModified(Node* node, int position) Platform::input->HideMouse(); context.surface->FillRect(context, drawPosition, node->anchor.y + 1, clearWidth, node->size.y - 2, clearColour); - context.surface->DrawString(context, font, data->buffer + position, drawPosition, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + 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(); } @@ -489,7 +531,14 @@ int TextFieldNode::PickPosition(Node* node, int x, int y) break; } - x -= font->GetGlyphWidth(data->buffer[result]); + if (data->isPassword) + { + x -= font->GetGlyphWidth(PASSWORD_CHARACTER); + } + else + { + x -= font->GetGlyphWidth(data->buffer[result]); + } result++; } diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index 62074a5..f06f255 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -21,10 +21,11 @@ class TextFieldNode : public NodeHandler class Data { public: - Data(char* inBuffer, int inBufferSize, NodeCallbackFunction inOnSubmit) : buffer(inBuffer), bufferSize(inBufferSize), name(NULL), onSubmit(inOnSubmit) {} + 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; }; @@ -52,6 +53,7 @@ class TextFieldNode : public NodeHandler 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); }; #endif diff --git a/src/Nodes/ListItem.cpp b/src/Nodes/ListItem.cpp index 706bf16..518534d 100644 --- a/src/Nodes/ListItem.cpp +++ b/src/Nodes/ListItem.cpp @@ -5,6 +5,9 @@ #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(); @@ -49,10 +52,13 @@ Node* ListItemNode::Construct(Allocator& allocator) 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->BlitImage(context, Assets.bulletIcon, node->anchor.x, node->anchor.y); + 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); } } @@ -65,10 +71,12 @@ void ListItemNode::BeginLayoutContext(Layout& layout, Node* node) layout.BreakNewLine(); node->anchor = layout.GetCursor(); - node->anchor.y += (font->glyphHeight - Assets.bulletIcon->height) / 2; + //node->anchor.y += (font->glyphHeight - Assets.bulletIcon->height) / 2; node->size.x = layout.AvailableWidth(); layout.PushLayout(); - layout.PadHorizontal(Assets.bulletIcon->width * 2, 0); + + int margin = font->GetGlyphWidth(BULLET_CHARACTER) * 2; + layout.PadHorizontal(margin, 0); } void ListItemNode::EndLayoutContext(Layout& layout, Node* node) diff --git a/src/Tags.cpp b/src/Tags.cpp index 5282726..4cca641 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -335,7 +335,9 @@ struct HTMLInputTag { Unknown, Submit, - Text + Text, + Check, + Password }; }; @@ -375,6 +377,10 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { type = HTMLInputTag::Text; } + else if (!stricmp(attributes.Value(), "password")) + { + type = HTMLInputTag::Password; + } else { type = HTMLInputTag::Unknown; @@ -403,6 +409,7 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } break; case HTMLInputTag::Text: + case HTMLInputTag::Password: { Node* fieldNode = TextFieldNode::Construct(MemoryManager::pageAllocator, value, FormNode::OnSubmitButtonPressed); if (fieldNode && fieldNode->data) @@ -410,6 +417,7 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const TextFieldNode::Data* fieldData = static_cast(fieldNode->data); fieldData->name = name; fieldData->explicitWidth = width; + fieldData->isPassword = type == HTMLInputTag::Password; parser.EmitNode(fieldNode); } } diff --git a/src/Unicode.inc b/src/Unicode.inc index 0773ff3..68029be 100644 --- a/src/Unicode.inc +++ b/src/Unicode.inc @@ -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 @@ -334,7 +334,7 @@ TextEncodingPage ISO_8859_2_Encoding = "\xB0", // ° DEGREE SIGN (U+00B0) "\xB1", // ± PLUS-MINUS SIGN (U+00B1) ",", // ˛ OGONEK (U+02DB) - "L", // ł LATIN SMALL LETTER L WITH STROKE (U+0142) + "l", // ł LATIN SMALL LETTER L WITH STROKE (U+0142) "\xB4", // ´ ACUTE ACCENT (U+00B4) "\xB5", // µ MICRO SIGN (U+00B5) "\xB6", // ¶ PILCROW SIGN (U+00B6) @@ -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 From 9708b8c4ab6eb207fe9c85da7a9a11015fbaf2a6 Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 10 Apr 2024 13:10:36 +0100 Subject: [PATCH 89/98] Added Amstrad PC1512 source file to makefile and project file --- project/DOS/DOS.vcxproj | 2 ++ project/DOS/Makefile | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/project/DOS/DOS.vcxproj b/project/DOS/DOS.vcxproj index 06c0270..ec8c7ba 100644 --- a/project/DOS/DOS.vcxproj +++ b/project/DOS/DOS.vcxproj @@ -104,6 +104,7 @@ + @@ -127,6 +128,7 @@ + diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 41f0dea..236aa1e 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=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 Select.obj Field.obj DataPack.obj Surf1bpp.obj Surf2bpp.obj Surf4bpp.obj Surf8bpp.obj Form.obj Status.obj Scroll.obj HTTP.obj Decoder.obj Gif.obj Jpeg.obj Png.obj MemBlock.obj Memory.obj EMS.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 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) -fi=$(SRC_PATH)\Defines.h @@ -170,6 +170,9 @@ Surf4bpp.obj: $(SRC_PATH)\DOS\Surf4bpp.cpp Surf8bpp.obj: $(SRC_PATH)\Draw\Surf8bpp.cpp $(CC) -fo=$@ $(CFLAGS) $< +Surf1512.obj: $(SRC_PATH)\DOS\Surf1512.cpp + $(CC) -fo=$@ $(CFLAGS) $< + clean: .symbolic del *.obj del $(bin) \ No newline at end of file From 368c47a051f03a8339aedba84d4a40566441996f Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 10 Apr 2024 13:11:02 +0100 Subject: [PATCH 90/98] Fixed bug where parsing would stop if there is no tag in the document --- src/Parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 8530531..4157411 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -131,7 +131,7 @@ void HTMLParser::PopContext(const HTMLTagHandler* tag) if (parseContext.tag == tag) { // If the stack is emptied then we have finished parsing the document - if (contextStackSize == 0) + if (contextStackSize == 0 && parseContext.parseSection == SectionElement::HTML) { #ifdef WIN32 page.DebugDumpNodeGraph(); From d2ff893fa36078a2fa9be9c4c7895296874fdd70 Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 12 Apr 2024 11:13:09 +0100 Subject: [PATCH 91/98] Checkbox and radio buttons first pass --- assets/CGA/checkbox-ticked.png | Bin 0 -> 606 bytes assets/CGA/checkbox.png | Bin 0 -> 588 bytes assets/CGA/radio-selected.png | Bin 0 -> 594 bytes assets/CGA/radio.png | Bin 0 -> 585 bytes assets/Default/checkbox-ticked.png | Bin 0 -> 616 bytes assets/Default/checkbox.png | Bin 0 -> 590 bytes assets/Default/radio-selected.png | Bin 0 -> 621 bytes assets/Default/radio.png | Bin 0 -> 610 bytes assets/EGA/checkbox-ticked.png | Bin 0 -> 617 bytes assets/EGA/checkbox.png | Bin 0 -> 590 bytes assets/EGA/radio-selected.png | Bin 0 -> 617 bytes assets/EGA/radio.png | Bin 0 -> 609 bytes assets/LowRes/checkbox-ticked.png | Bin 0 -> 559 bytes assets/LowRes/checkbox.png | Bin 0 -> 542 bytes assets/LowRes/radio-selected.png | Bin 0 -> 565 bytes assets/LowRes/radio.png | Bin 0 -> 555 bytes project/DOS/Makefile | 5 +- project/Windows/Windows.vcxproj | 2 + src/DataPack.cpp | 5 +- src/DataPack.h | 6 +- src/Layout.cpp | 5 ++ src/Memory/Alloc.h | 11 +++ src/Node.cpp | 4 +- src/Node.h | 1 + src/Nodes/CheckBox.cpp | 121 +++++++++++++++++++++++++++++ src/Nodes/CheckBox.h | 28 +++++++ src/Nodes/Form.cpp | 60 +++++++++++--- src/Nodes/Form.h | 1 + src/Nodes/Select.cpp | 12 ++- src/Nodes/Select.h | 7 +- src/Parser.cpp | 12 ++- src/Tags.cpp | 62 ++++++++++++++- tools/AssetGen.cpp | 10 ++- 33 files changed, 322 insertions(+), 30 deletions(-) create mode 100644 assets/CGA/checkbox-ticked.png create mode 100644 assets/CGA/checkbox.png create mode 100644 assets/CGA/radio-selected.png create mode 100644 assets/CGA/radio.png create mode 100644 assets/Default/checkbox-ticked.png create mode 100644 assets/Default/checkbox.png create mode 100644 assets/Default/radio-selected.png create mode 100644 assets/Default/radio.png create mode 100644 assets/EGA/checkbox-ticked.png create mode 100644 assets/EGA/checkbox.png create mode 100644 assets/EGA/radio-selected.png create mode 100644 assets/EGA/radio.png create mode 100644 assets/LowRes/checkbox-ticked.png create mode 100644 assets/LowRes/checkbox.png create mode 100644 assets/LowRes/radio-selected.png create mode 100644 assets/LowRes/radio.png create mode 100644 src/Nodes/CheckBox.cpp create mode 100644 src/Nodes/CheckBox.h diff --git a/assets/CGA/checkbox-ticked.png b/assets/CGA/checkbox-ticked.png new file mode 100644 index 0000000000000000000000000000000000000000..c8628362c93a2827b92ee58cd27f14f132dd0c82 GIT binary patch literal 606 zcmV-k0-^nhP)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDysIRWH5vu*$Y00(qQ zO+^Rj1PTlf6;Gjbs{jB18FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z07pqgK~xyiosrQB03ZkhXY~KSd_9C=AxZ6V?3}0p=u=&i%EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDysIRWH5vu*$Y00(qQ zO+^Rj1PTleIb>of2><{98FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z05wTOK~xyiWBmXB|9=KD0Sf~I0|O%?Bgq>7|Njrsj4VJ1AX~)5Kmiyy%`CY5L$qdc aV+#Pl@eLH1B6?2%0000EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDysIRWH5vu*$Y00(qQ zO+^Rj1PTlg6!rW{qW}N^8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z06R%UK~xyioscmK06+-C?9u;!xi<+NMA2+QBq$^)4$ubxt?Ui@!W4IpolupM7e_eV g(k`aBe$J+pe7Ln752H76O8@`>07*qoM6N<$f{Zu$2mk;8 literal 0 HcmV?d00001 diff --git a/assets/CGA/radio.png b/assets/CGA/radio.png new file mode 100644 index 0000000000000000000000000000000000000000..8eac466eecf0ef70181788233b9ef0ca32f2e03d GIT binary patch literal 585 zcmV-P0=E5$P)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDysIRWH5vu*$Y00(qQ zO+^Rj1PTlg0PiNA-T(jq8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z05VBLK~xyiWBmXB|9=KD01GmQk&zLvVq`m5kTnpp99bO`1DSyS8d;G2Msna_lOO~D X5_lF4JM`a#00000NkvXXu0mjfp@;KN literal 0 HcmV?d00001 diff --git a/assets/Default/checkbox-ticked.png b/assets/Default/checkbox-ticked.png new file mode 100644 index 0000000000000000000000000000000000000000..9b526caf313c960a0a5108a700695c0a1ae73695 GIT binary patch literal 616 zcmV-u0+;=XP)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy{BLR4&KXw2B00(qQ zO+^Rj1PTln7_$qIPXGV_8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z08vRqK~yNurBXo(05Ax$=>K1KhY^gnsBPW?O+keuaR~ze5orrZPLUrK{JD0BF^S>< z+KCgR|2wVuGnY-|gsK8P^-$GdS%KvuZdYO5HLMum#UCSPQm#Az0000EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy{BLR4&KXw2B00(qQ zO+^Rj1PTln21x|1EC2ui8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z05?fQK~yNuWBmXB|9=J&0Sf~I0|O%?BhecE|Njrs1mh3_FvFP`$N;07=&p$cJ>rPg cL~8N_00Y_$9tm$I6951J07*qoM6N<$f;uPjc>n+a literal 0 HcmV?d00001 diff --git a/assets/Default/radio-selected.png b/assets/Default/radio-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..9f08deb765dc6ad3ea97ef4c93399aebd6f4377b GIT binary patch literal 621 zcmV-z0+RiSP)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy{BLR4&KXw2B00(qQ zO+^Rj1PTlo3dbt_$N&HU8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z09HvvK~yNul~G9!05J#xs)6LEy0O0w96RovyZaDXHv+(&icX!7JkEX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy{BLR4&KXw2B00(qQ zO+^Rj1PTlnFAKIcKmY&$8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z082?kK~yNuy^%2r05AvxHTeH8r<2eIwRCAVEX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy)tpV2g&5i&700(qQ zO+^Rj1PTlkHQo82`2YX_8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z08&XrK~yNum65>;03ZlM=kWi3c^x7UDO|e+zBOG)56p`T?@8za z6{Pz^jn6)GbME`HUuFWX@#@g)qWS7#EcdtIbc?N8$73QPC0R^M00000NkvXXu0mjf DW7-IK literal 0 HcmV?d00001 diff --git a/assets/EGA/checkbox.png b/assets/EGA/checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..b19cec954810afa6b3fa179d50bcb4ab8787153c GIT binary patch literal 590 zcmV-U0EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy)tpV2g&5i&700(qQ zO+^Rj1PTlk9q3Fq0{{R38FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z05?fQK~yNuWBmXB|9=J&0Sf~I0|O%?BhecE|Njrs1mh3_FvFP`$N;07DAB}%9tT8g cA~hue0PIT*9DPfgg8%>k07*qoM6N<$g07JCL;wH) literal 0 HcmV?d00001 diff --git a/assets/EGA/radio-selected.png b/assets/EGA/radio-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..216da0e517feb32884b174b445a1b15a654b9914 GIT binary patch literal 617 zcmV-v0+#)WP)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy)tpV2g&5i&700(qQ zO+^Rj1PTllE!U1V?f?J)8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z08&XrK~yNum65>?03ZlL!}EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vG?BLDy)tpV2g&5i&700(qQ zO+^Rj1PTll8gDa`;Q#;t8FWQhbVF}#ZDnqB07G(RVRU6=Aa`kWXdp*PO;A^X4i^9b z07^+jK~yNuosmfj05AvxHTeH8rzfEeYH4#*#w|KY+Jiz9?taADFBDclbC}?quusJV vS4FT#{T1k*m2EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rj1PTls99NQ!od5s;JxN4CR2b8JkUl;s0N*2NREX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rj1PTlq7zU;80ssI2EJ;K`R2b7^{Qv(y10H}!`v3p`EVvXgGBPqSFfcLT1p_I~ gg6vLAz=%Is0Q7MW3{;RUbpQYW07*qoM6N<$f)#J$YybcN literal 0 HcmV?d00001 diff --git a/assets/LowRes/radio-selected.png b/assets/LowRes/radio-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..8b7cf5de33a6c968bb0328591fdb6a365e2a7f61 GIT binary patch literal 565 zcmV-50?Pe~P)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rj1PTlt6*pA`cmMzZLrFwIR2b8JkiiZBAP7Qx&j0^qJG7By=?P^BN<@r*xey>M zjf=}`)LP}1dM&d&1+v9WchPd9LoKHoZHIauM*o%xVy+|-KDXz*00000NkvXXu0mjf D0^jaG literal 0 HcmV?d00001 diff --git a/assets/LowRes/radio.png b/assets/LowRes/radio.png new file mode 100644 index 0000000000000000000000000000000000000000..887fa458e6658b72db007a2d6792017e988ac283 GIT binary patch literal 555 zcmV+`0@VG9P)EX>4Tx04R}tkv&MmKp2MKrWHjhf_4z;kfAzR@DFj6Di*;)X)CnqU~=gnG-*gu zTpR`0f`dPcRR>vuL#f_Ko}DeGxbDzF$2%>bq^ok@1i`*yYA1?r{qlr_(bA4rW+RV2Jy_M zrE}gV4zZG?5T6r|8FWG7N3P2*zi}=)Ebz>bkx9)Hhls^u2g@DIN`^{2O&n2Fjq-)8 z%L?Z$&T6H`TKD8H4Cb|!G}mc`5yuh|NJ4~+8p^1^LX>um6cZ^rk9qiq9ev5wS~QBS^xk532;bRa{vGf6951U69E94oEQKA00(qQ zO+^Rj1PTlt2D9r$rT_o{IY~r8R2b7^{Qv(y18%?qXE8D|!r5>=EO0qwW8hpSOgV^d th+#|&cmd_oaGNllhHw;Ck1^s87626TAq_TSx|aX|002ovPDHLkV1oGj=&Jw# literal 0 HcmV?d00001 diff --git a/project/DOS/Makefile b/project/DOS/Makefile index 236aa1e..6539e1f 100644 --- a/project/DOS/Makefile +++ b/project/DOS/Makefile @@ -1,7 +1,7 @@ bin = MicroWeb.exe SRC_PATH = ..\..\src OBJDIR=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 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 +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) -fi=$(SRC_PATH)\Defines.h @@ -86,6 +86,9 @@ Block.obj: $(SRC_PATH)\Nodes\Block.cpp 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) $< diff --git a/project/Windows/Windows.vcxproj b/project/Windows/Windows.vcxproj index 2569653..f349e79 100644 --- a/project/Windows/Windows.vcxproj +++ b/project/Windows/Windows.vcxproj @@ -157,6 +157,7 @@ + @@ -206,6 +207,7 @@ + diff --git a/src/DataPack.cpp b/src/DataPack.cpp index 516d5a7..0cf7a4e 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -52,7 +52,10 @@ bool DataPack::Load(const char* path) imageIcon = LoadImageAsset(fs, header, "IIMG"); brokenImageIcon = LoadImageAsset(fs, header, "IBROKEN"); - bulletIcon = LoadImageAsset(fs, header, "IBULLET"); + checkbox = LoadImageAsset(fs, header, "ICHECK1"); + checkboxTicked = LoadImageAsset(fs, header, "ICHECK2"); + radio = LoadImageAsset(fs, header, "IRADIO1"); + radioSelected = LoadImageAsset(fs, header, "IRADIO2"); fonts[0] = (Font*)LoadAsset(fs, header, "FHELV1"); fonts[1] = (Font*)LoadAsset(fs, header, "FHELV2"); diff --git a/src/DataPack.h b/src/DataPack.h index 2e6c4a5..f8f5897 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -65,7 +65,11 @@ struct DataPack MouseCursorData* textSelectCursor; Image* imageIcon; Image* brokenImageIcon; - Image* bulletIcon; + + Image* checkbox; + Image* checkboxTicked; + Image* radio; + Image* radioSelected; Font* fonts[NUM_FONT_SIZES]; Font* monoFonts[NUM_FONT_SIZES]; diff --git a/src/Layout.cpp b/src/Layout.cpp index 3b6fe0b..7d5adb4 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -77,6 +77,11 @@ void Layout::Update() { currentNodeToProcess->Handler().EndLayoutContext(*this, currentNodeToProcess); + if (!tableDepth && !lineStartNode) + { + page.GetApp().pageRenderer.MarkNodeLayoutComplete(currentNodeToProcess); + } + if (currentNodeToProcess->next) { currentNodeToProcess = currentNodeToProcess->next; diff --git a/src/Memory/Alloc.h b/src/Memory/Alloc.h index 6384069..a612834 100644 --- a/src/Memory/Alloc.h +++ b/src/Memory/Alloc.h @@ -77,6 +77,17 @@ class Allocator } 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 diff --git a/src/Node.cpp b/src/Node.cpp index 8d95d04..5443fb9 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -18,6 +18,7 @@ #include "Nodes/Table.h" #include "Nodes/Select.h" #include "Nodes/ListItem.h" +#include "Nodes/CheckBox.h" NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = { @@ -40,7 +41,8 @@ NodeHandler* Node::nodeHandlers[Node::NumNodeTypes] = new SelectNode(), new OptionNode(), new ListNode(), - new ListItemNode() + new ListItemNode(), + new CheckBoxNode() }; Node::Node(Type inType, void* inData) diff --git a/src/Node.h b/src/Node.h index 56b807b..25ee612 100644 --- a/src/Node.h +++ b/src/Node.h @@ -88,6 +88,7 @@ class Node Option, List, ListItem, + CheckBox, NumNodeTypes }; diff --git a/src/Nodes/CheckBox.cpp b/src/Nodes/CheckBox.cpp new file mode 100644 index 0000000..8cd0afa --- /dev/null +++ b/src/Nodes/CheckBox.cpp @@ -0,0 +1,121 @@ +#include +#include "../Layout.h" +#include "../Memory/LinAlloc.h" +#include "../Draw/Surface.h" +#include "../DataPack.h" +#include "../App.h" +#include "CheckBox.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 + 1, node->anchor.y + 1, node->size.x - 2, outlineColour); + context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 3, node->size.x - 2, outlineColour); + context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 2, node->size.y - 5, outlineColour); + context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 2, node->size.y - 5, 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); +} + +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) + { + for (Node* n = App::Get().page.GetRootNode(); n; n = n->GetNextInTree()) + { + if (n != node && n->type == Node::CheckBox) + { + CheckBoxNode::Data* otherData = static_cast(n->data); + if (otherData && otherData->isRadio && otherData->name && !strcmp(otherData->name, data->name)) + { + otherData->isChecked = false; + n->Redraw(); + } + } + } + } + } + } + else + { + data->isChecked = !data->isChecked; + } + node->Redraw(); + } + return true; + } + + return false; +} \ No newline at end of file diff --git a/src/Nodes/CheckBox.h b/src/Nodes/CheckBox.h new file mode 100644 index 0000000..bccaa9e --- /dev/null +++ b/src/Nodes/CheckBox.h @@ -0,0 +1,28 @@ +#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); +}; + +#endif diff --git a/src/Nodes/Form.cpp b/src/Nodes/Form.cpp index fefabd5..cb5fc9c 100644 --- a/src/Nodes/Form.cpp +++ b/src/Nodes/Form.cpp @@ -3,6 +3,8 @@ #include "../App.h" #include "../Interface.h" #include "Field.h" +#include "CheckBox.h" +#include "Select.h" Node* FormNode::Construct(Allocator& allocator) { @@ -15,27 +17,63 @@ Node* FormNode::Construct(Allocator& allocator) 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) { - if (node->type == Node::TextField) + switch(node->type) { - TextFieldNode::Data* fieldData = static_cast(node->data); + case Node::TextField: + { + TextFieldNode::Data* fieldData = static_cast(node->data); - if (fieldData->name && fieldData->buffer) + if (fieldData->name && fieldData->buffer) + { + AppendParameter(address, fieldData->name, fieldData->buffer, numParams); + } + } + break; + case Node::CheckBox: { - if (numParams == 0) + CheckBoxNode::Data* checkboxData = static_cast(node->data); + + if (checkboxData && checkboxData->isChecked && checkboxData->name && checkboxData->value) { - strcat(address, "?"); + AppendParameter(address, checkboxData->name, checkboxData->value, numParams); } - else + } + break; + case Node::Select: + { + SelectNode::Data* selectData = static_cast(node->data); + + if (selectData && selectData->selected) { - strcat(address, "&"); + AppendParameter(address, selectData->name, selectData->selected->text, numParams); } - strcat(address, fieldData->name); - strcat(address, "="); - strcat(address, fieldData->buffer); - numParams++; } + break; } for (node = node->firstChild; node; node = node->next) diff --git a/src/Nodes/Form.h b/src/Nodes/Form.h index cf43667..7f65ee9 100644 --- a/src/Nodes/Form.h +++ b/src/Nodes/Form.h @@ -31,6 +31,7 @@ class FormNode : public NodeHandler 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/Select.cpp b/src/Nodes/Select.cpp index e2b752c..a9e9b3f 100644 --- a/src/Nodes/Select.cpp +++ b/src/Nodes/Select.cpp @@ -6,9 +6,9 @@ #include "Select.h" -Node* SelectNode::Construct(Allocator& allocator) +Node* SelectNode::Construct(Allocator& allocator, const char* name) { - SelectNode::Data* data = allocator.Alloc(); + SelectNode::Data* data = allocator.Alloc(allocator.AllocString(name)); if (data) { return allocator.Alloc(Node::Select, data); @@ -76,7 +76,7 @@ Node* OptionNode::Construct(Allocator& allocator) return nullptr; } -void OptionNode::EndLayoutContext(Layout& layout, Node* node) +void OptionNode::GenerateLayout(Layout& layout, Node* node) { OptionNode::Data* data = static_cast(node->data); if (!data->addedToSelectNode) @@ -87,7 +87,6 @@ void OptionNode::EndLayoutContext(Layout& layout, Node* node) if (!select->firstOption) { select->firstOption = data; - select->selected = data; } else { @@ -101,6 +100,11 @@ void OptionNode::EndLayoutContext(Layout& layout, Node* node) } } + if(!select->selected) + { + select->selected = data; + } + data->addedToSelectNode = true; } diff --git a/src/Nodes/Select.h b/src/Nodes/Select.h index 551819d..9e55018 100644 --- a/src/Nodes/Select.h +++ b/src/Nodes/Select.h @@ -17,7 +17,7 @@ class OptionNode : public NodeHandler }; static Node* Construct(Allocator& allocator); - virtual void EndLayoutContext(Layout& layout, Node* node) override; + virtual void GenerateLayout(Layout& layout, Node* node) override; }; class SelectNode : public NodeHandler @@ -26,12 +26,13 @@ class SelectNode : public NodeHandler class Data { public: - Data() : firstOption(nullptr), selected(nullptr) {} + Data(const char* inName) : name(inName), firstOption(nullptr), selected(nullptr) {} + const char* name; OptionNode::Data* firstOption; OptionNode::Data* selected; }; - static Node* Construct(Allocator& allocator); + static Node* Construct(Allocator& allocator, const char* inName); virtual void Draw(DrawContext& context, Node* element) override; virtual void EndLayoutContext(Layout& layout, Node* node) override; }; diff --git a/src/Parser.cpp b/src/Parser.cpp index 4157411..60ff27b 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -836,7 +836,9 @@ bool AttributeParser::Parse() { if(*attributeString == '\0') { - return false; + // Key but no value + value = attributeString; + return true; } if(*attributeString == '=') { @@ -854,7 +856,9 @@ bool AttributeParser::Parse() { if(*attributeString == '\0') { - return false; + // Key but no value + value = attributeString; + return true; } if(*attributeString == '=') { @@ -862,7 +866,9 @@ bool AttributeParser::Parse() } if(!IsWhiteSpace(*attributeString)) { - return false; + // Key but no value + value = key + strlen(key); + return true; } attributeString++; } diff --git a/src/Tags.cpp b/src/Tags.cpp index 4cca641..355d104 100644 --- a/src/Tags.cpp +++ b/src/Tags.cpp @@ -37,6 +37,7 @@ #include "Nodes/Select.h" #include "Nodes/ListItem.h" #include "Nodes/Text.h" +#include "Nodes/CheckBox.h" static const HTMLTagHandler* tagHandlers[] = { @@ -336,7 +337,8 @@ struct HTMLInputTag Unknown, Submit, Text, - Check, + CheckBox, + Radio, Password }; }; @@ -360,6 +362,7 @@ 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; @@ -381,6 +384,14 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { 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; @@ -398,6 +409,10 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { width = ExplicitDimension::Parse(attributes.Value()); } + if (!stricmp(attributes.Key(), "checked")) + { + checked = true; + } } switch (type) @@ -422,6 +437,16 @@ void InputTagHandler::Open(class HTMLParser& parser, char* attributeStr) const } } 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; } } @@ -650,7 +675,18 @@ void TableCellTagHandler::Close(class HTMLParser& parser) const void SelectTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(SelectNode::Construct(MemoryManager::pageAllocator), this); + 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 @@ -660,7 +696,27 @@ void SelectTagHandler::Close(class HTMLParser& parser) const void OptionTagHandler::Open(class HTMLParser& parser, char* attributeStr) const { - parser.PushContext(OptionNode::Construct(MemoryManager::pageAllocator), this); + 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 diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 44c5281..3441a24 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -88,8 +88,14 @@ void GenerateAssetPack(const char* name) EncodeImage(basePath, "image-icon.png", data); AddEntryHeader("IBROKEN", entries, data); EncodeImage(basePath, "broken-image-icon.png", data); - AddEntryHeader("IBULLET", entries, data); - EncodeImage(basePath, "bullet.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("END", entries, data); From 7958e98db082784a7693c0d290d813ab6c673c98 Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 12 Apr 2024 11:43:11 +0100 Subject: [PATCH 92/98] Fixed 1bpp image drawing in 2bpp and 4bpp modes --- src/DOS/Surf4bpp.cpp | 36 +++++++++++++++++++++++++----------- src/Draw/Surf2bpp.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index 048d220..d8078be 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -468,16 +468,15 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int } else { - // Set write mode 3 + // Set write mode outp(GC_INDEX, GC_MODE); - outp(GC_DATA, 0x3); + outp(GC_DATA, 0x0); outp(GC_INDEX, GC_ROTATE); outp(GC_DATA, 0); - // colour 0 for black outp(GC_INDEX, GC_SET_RESET); - outp(GC_DATA, 0); + outp(GC_DATA, 0x0); // Set bit mask outp(GC_INDEX, GC_BITMASK); @@ -485,6 +484,8 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int // 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); @@ -492,14 +493,22 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int uint8_t srcMask = 0x80 >> (srcX & 7); uint8_t destMask = 0x80 >> (x & 7); uint8_t srcBuffer = *src++; - uint8_t destBuffer = 0x0; // *dest; + uint8_t writeBitMask = 0; + uint8_t destBuffer = *dest; + + outp(GC_DATA, writeBitMask); for (int i = 0; i < destWidth; i++) { - if (!(srcBuffer & srcMask)) + writeBitMask |= destMask; + if ((srcBuffer & srcMask)) { destBuffer |= destMask; } + else + { + destBuffer &= ~destMask; + } srcMask >>= 1; if (!srcMask) { @@ -509,14 +518,19 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int destMask >>= 1; if (!destMask) { - outp(GC_DATA, destBuffer); - *dest++ |= 0xff; - destBuffer = 0; + outp(GC_DATA, writeBitMask); + *dest++ = destBuffer; + destBuffer = *dest; destMask = 0x80; + writeBitMask = 0; } } - outp(GC_DATA, destBuffer); - *dest++ |= 0xff; + + if (writeBitMask) + { + outp(GC_DATA, writeBitMask); + *dest++ = destBuffer; + } } } } diff --git a/src/Draw/Surf2bpp.cpp b/src/Draw/Surf2bpp.cpp index 0f72f62..1374981 100644 --- a/src/Draw/Surf2bpp.cpp +++ b/src/Draw/Surf2bpp.cpp @@ -366,6 +366,47 @@ void DrawSurface_2BPP::BlitImage(DrawContext& context, Image* image, int x, int *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; + } + } } From 5f415d57bec0c33de4cf19fc5c6ec6400fc25ac7 Mon Sep 17 00:00:00 2001 From: James Howard Date: Fri, 12 Apr 2024 15:07:12 +0100 Subject: [PATCH 93/98] Functionality for checkboxes, radios and select nodes --- CGA.dat | Bin 20835 -> 20978 bytes Default.dat | Bin 38012 -> 38193 bytes EGA.dat | Bin 29745 -> 29916 bytes LowRes.dat | Bin 21295 -> 21448 bytes assets/CGA/down-icon.png | Bin 0 -> 6004 bytes assets/Default/down-icon.png | Bin 0 -> 7369 bytes assets/EGA/down-icon.png | Bin 0 -> 7088 bytes assets/LowRes/down-icon.png | Bin 0 -> 6736 bytes src/DataPack.cpp | 1 + src/DataPack.h | 1 + src/Interface.cpp | 17 +++- src/Interface.h | 2 +- src/Nodes/CheckBox.cpp | 186 ++++++++++++++++++++++++++++++++--- src/Nodes/CheckBox.h | 6 ++ src/Nodes/Field.cpp | 46 ++++++--- src/Nodes/Field.h | 1 + src/Nodes/Select.cpp | 113 ++++++++++++++++++++- src/Nodes/Select.h | 7 ++ tools/AssetGen.cpp | 2 + 19 files changed, 351 insertions(+), 31 deletions(-) create mode 100644 assets/CGA/down-icon.png create mode 100644 assets/Default/down-icon.png create mode 100644 assets/EGA/down-icon.png create mode 100644 assets/LowRes/down-icon.png diff --git a/CGA.dat b/CGA.dat index 68c0643e86d3a2b3b6b0cf8694ef1963dcf0518d..e8c628e0098900bc1bb959a41c518b5fb3e3401a 100644 GIT binary patch delta 326 zcmaF7i1E{6#(F^pHxE~zFhhoa=NK3uY$Jw$mpPzpV}^f!gn?{l|Ii?yny1Q8Hc-tm zZ73V4Cf$aC!P(b8G}x8lUq=9t?c?d^&G7Hv84x?fH6jGa76@cu@bvU`2QvSKfY?q! z{@y;WehfJvj*2aKYsv!zjk#1 delta 183 zcmeygnDOx<#(G``HxE~zFhhoa^B5Q)Y$Jw$i#ecdV}^fcgn?{l|Ii?ynyt!EHc-tp zZ73V4#@vR1!P(b8G}x8lUqk?q?c?d^&G7Hv91uIiH6jGaegI^9diuHpng3J*fo!KB we{UIAKL#5xC)CHsHH0Aq$aeK}0jc?yyz%tT0Dd+Gb_PZu{L{j~(DLUG09<`KCjbBd diff --git a/Default.dat b/Default.dat index 5600dca24a18c99a5b5cc29202b2358442f8b8b7..e346f8d64ce0bdeb1459b6bcd576ddd23d9938c9 100644 GIT binary patch delta 370 zcmeyff@$L_rg}jJHxE~zFhhoa=NK3uY$Jw$0{l?6F~dJaWgy$xKQsua#>o-N2C8xP zhq8fcp5!txIQ#mC2D>u+Yn=>a`*`|!GyMB^62uO1jR*mc%-|C)e{b@G>wlFf#oA(ZI3)1v+ft`2PbTVkb~NNCgiA z55$ZQA3nSmLu zAIb)**^o_50^y%OEkGACwEX$=2LKDz BKKTFu diff --git a/EGA.dat b/EGA.dat index d6b3a8bc9b4df65c9e5c0a9c8c58d859d6661016..337cc893ce2eb1a13d7b2b48a128fae147dc8538 100644 GIT binary patch delta 359 zcmdn^g7MBv#(F^pHxE~zFhhoa=NK3uY$Jw$i9ArYF~dJG86ex)KQsuaCfpdx2CCU% z2W11*M20aiIQ#mC2D>u+vnmF%eLVfV8UFpN1F=I~BSL`eLqN8tr>{GZ`H!sx$aV_y z_x5r1W6%O|oIPBfy$u=s;A|s?JPkf9IGHey%jpzd(fz9RGiy$*a0Lmqjh5!Hn delta 187 zcmccfl5yh;#(G``HxE~zFhhoa^B5Q)Y$Jw$hCEQVF~h%S(m=Mee`pX;jk+u+<0=NSeLVfV8UFqA1F=I~BSL`eNkF!zr>{GZ`R^Kt?G)ti zE#vCP@C(cd_3?2HVUR0fU~u(w0jc?Cxbbvzu`nA0Hv=OO{`u1abRa{^pFe*9HuXG> diff --git a/LowRes.dat b/LowRes.dat index ad9151d53b871a620a459e9c8605efd312526aa5..ec5ad33ffac2abd62e8ab67091125c3d6a0bebd9 100644 GIT binary patch delta 339 zcmZ3#jPb;B#(F^pHxE~zFhhoa=NK3uY$Jw$`aDp!F~h$YNg&(VKQsuaMn((D2CAtw zg|dNa<~cDiIQ#mC2D>u+iw^>_eLVfV8UFoS3}T14MuY&_&w*@DPhWQ+^PhGwknI%Y z@9pF2$6y2EID5D{dmA$N!r4X)nILwMql>4%Aww&iZN#t;#CGuy_XC==7sz(?a{-z8 z@5IL0O+nRM3|tIM42%r_4>UOJM}iFw{|}(b?Q_`cu-aiePz|cw!vpsYz;xa_@bCb# ZK8W0Ahs{7;5IJ@RW}t1147;wq0|0P@cEA7t delta 186 zcmX@HoN@g!#(G``HxE~zFhhoa^B5Q)Y$Jw$|GA-TV}^e^l0deze`pX;%^OW98>q(D z6v_sw$#Y_0aQ5{N4R&Strym4l`*`|!GyMBk3}T14MuY&_+ktFPPhWQ+^WSd}+bPK3 yTgKIofhQQm3H9-D4PlT4vR(aLKx+Q!Z#>-;B*eK#3MhL@_WQLdz1x5E@d!F({Pk z=H7_85_<=O-Unv$huLeTv~{h@vVPj_s&frr$#@mWk2sW?vIVmDo$R|nDjQHsST_VjEXB!Y$`GS zBO>osU4%zzrqz~i0~5tGgq7C&e@w28Pd|Fyt#Xg;;!84UgVI&OH?*<#s@=M=Yw~-{ zyCbM9>W}auJ`S}*b<3*OLvh7eOtA;;&gT9iuC52HQx%1pw zgHn2k?%R~Ear)2X_47*DXX=*RsaqO7y7$T1K4DkL_}{}_SH$x8H>0#H=63v$=D-uF zJV(#jBup^but%?l2 zv~Gq&McJOnU6}CH&@rzi{SdbYKeiaQejn$Vb&m^LwDoXTbhAdCc2W}3o@>Q0iTYK1r%q9|U#W+^ z;{gRcxuSgREj?4aqBi|--cNLDEZax#(Zl5dxpthLCOy85865p0ps#91OG@+(?MW|> zq}*OPuF-#6bg8f}fVZa@uA7tSYZmk~GkaKgn&{*WaYjLSQ0A?wG+Ro$YpTS%?AE5u zYhsSAiiyZJ+wq6fapx_8^|n!Gi)(yK4^O-!I+w$@sq@$!czNj#{gMEyCYohS*Lkpc z32o&oT%@kv67$}zCe!CG?QE#sZy}sp;h=Y~c$wQAMtbeh$+trd8rtfb8%kyp1nI4r z9hP7LC8B=Tu=~Y#W#a6S0Z%$&H*7zb8|IW;Q=4mCrB$bw8tb0B&)oTfU8ZVv=8H2+ zXN;jT6B4xNUVTiqD!7v1w$!p4^mqzfigAgubhUMhbS_ z!av{dk{dCP<|$~_UbZdGW}tAjd58;Ir}gdOWjmhn)@;cT4DT{B4!otDC%k~)o6|&f z*io^v$*P>+EgyOCXnwlyE*1O5?(q(NQCTfg$k3&5<+W<#-CK^I;~le{r@Pcw2hxwBFE(OWECbbzm=Wk{xQH;K@`YY5A$G?LT3TXPM|J;ML(>^~7hRg< z?(mGod$y4eNh!M*daDjr6}00o98W*MG*`<5xMz#MXpMYMRL;0`WtJ}u6_Ox!Qe2&^S6WDEK zsT+2l6xyAYb$;)PmXZ+{Z9FVZO~^03lZe_Ey5&Yc-hRH_t-iAJ!llCYpy6-4@6igZ z&AW$pHx%rYwoWoEU6(&P*L01Q%=6y#ef78#2{^h_SHs#2|MRBHem%|6tsmDAkQU$; zWHU1?{m<0mdc6nOKjLGA}o#NHjdjfHBx@Gx6abq9N~vg6fW z`RXgWXy&mw$F&R%7hl3aCG(F~79J4wUoU_6T01jDQjsp)y9I?Zngt_Aab!>+9TW+0 z09V9;a4LZqu?7m|=B^S0U_2y4b08ipWMH0Gp1`1CE&~(c97G5bGoe`6FG&KilY+y* zq^NSJAQXg3Krh92teJ4k_y7?@a@Oiah)l}aT}NydpJJUo#`qu~i8Jc)!w5LjuV zPzI>5LaCjaVt~UNl7bS%&#*{{R&xRzkzB^WV32k6Ai3J@J}P8#FoAlkp_j_Iczcw{vw=wq}{I;bL|Ctd}J@kAT}FA#hdA(i0){v0TE}V7MS2;zI&tBT|H!_!%CF{}}Ma%h8Z)gra+kK)IU5-HSxrFUQV=STM!<4oNJJWk54(IMB#A@vv!R56(LgWZ!y2j3Xifdn0Z^?X10w|#5La^x5FH1Wz*s=YgOEl! zSS26k;V&4L;v7Q-X;d1PLve9M3=0BSDwzsk0f>Xxk4uFJB--cfQc;Xd2}mGM9#Tfg zeMX8%bDztq+9^$TH*vLH0Z_hn!5vL5Kf%(ha&qT}c1nYtViFU(~(0oqz>7 literal 0 HcmV?d00001 diff --git a/assets/Default/down-icon.png b/assets/Default/down-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6ae84d877cbe4ef22734a5579245d5ad51d38594 GIT binary patch literal 7369 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 P6ab^zZnDX-@{9W~w?d6} literal 0 HcmV?d00001 diff --git a/src/DataPack.cpp b/src/DataPack.cpp index 0cf7a4e..a202d36 100644 --- a/src/DataPack.cpp +++ b/src/DataPack.cpp @@ -56,6 +56,7 @@ bool DataPack::Load(const char* path) 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"); diff --git a/src/DataPack.h b/src/DataPack.h index f8f5897..db03429 100644 --- a/src/DataPack.h +++ b/src/DataPack.h @@ -70,6 +70,7 @@ struct DataPack Image* checkboxTicked; Image* radio; Image* radioSelected; + Image* downIcon; Font* fonts[NUM_FONT_SIZES]; Font* monoFonts[NUM_FONT_SIZES]; diff --git a/src/Interface.cpp b/src/Interface.cpp index 1306caf..ab7119f 100644 --- a/src/Interface.cpp +++ b/src/Interface.cpp @@ -507,7 +507,7 @@ bool AppInterface::IsInterfaceNode(Node* node) return node == rootInterfaceNode || (node && node->parent == rootInterfaceNode); } -void AppInterface::FocusNode(Node* node) +bool AppInterface::FocusNode(Node* node) { if (node != focusedNode) { @@ -520,9 +520,15 @@ void AppInterface::FocusNode(Node* node) if (focusedNode) { - focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::Focus)); + if (!focusedNode->Handler().HandleEvent(focusedNode, Event(app, Event::Focus))) + { + focusedNode = nullptr; + return false; + } } } + + return true; } void AppInterface::OnBackButtonPressed(Node* node) @@ -644,12 +650,15 @@ void AppInterface::CycleNodes(int direction) { ScrollAbsolute(nodeRect.y + nodeRect.height - windowRect.height); } - FocusNode(node); - return; + + if(FocusNode(node)) + return; } } } } + + FocusNode(nullptr); } void AppInterface::OnScrollBarMoved(Node* node) diff --git a/src/Interface.h b/src/Interface.h index b09881e..bdff03e 100644 --- a/src/Interface.h +++ b/src/Interface.h @@ -44,7 +44,7 @@ class AppInterface void SetTitle(const char* title); - void FocusNode(Node* node); + bool FocusNode(Node* node); Node* GetFocusedNode() { return focusedNode; } Node* GetHoverNode() { return hoverNode; } Node* GetRootInterfaceNode() { return rootInterfaceNode; } diff --git a/src/Nodes/CheckBox.cpp b/src/Nodes/CheckBox.cpp index 8cd0afa..a46b5d3 100644 --- a/src/Nodes/CheckBox.cpp +++ b/src/Nodes/CheckBox.cpp @@ -5,7 +5,7 @@ #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) { @@ -53,10 +53,11 @@ void CheckBoxNode::Draw(DrawContext& context, Node* node) if (App::Get().ui.GetFocusedNode() == node) { uint8_t outlineColour = Platform::video->colourScheme.textColour; - context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + 1, node->size.x - 2, outlineColour); - context.surface->HLine(context, node->anchor.x + 1, node->anchor.y + node->size.y - 3, node->size.x - 2, outlineColour); - context.surface->VLine(context, node->anchor.x + 1, node->anchor.y + 2, node->size.y - 5, outlineColour); - context.surface->VLine(context, node->anchor.x + node->size.x - 2, node->anchor.y + 2, node->size.y - 5, outlineColour); + + 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); } } @@ -76,6 +77,60 @@ void CheckBoxNode::GenerateLayout(Layout& layout, Node* node) 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; @@ -93,16 +148,15 @@ bool CheckBoxNode::HandleEvent(Node* node, const Event& event) if (data->name) { + Node* formNode = node->FindParentOfType(Node::Form); + for (Node* n = App::Get().page.GetRootNode(); n; n = n->GetNextInTree()) { - if (n != node && n->type == Node::CheckBox) + if (n != node && IsPartOfRadioSet(node, n)) { CheckBoxNode::Data* otherData = static_cast(n->data); - if (otherData && otherData->isRadio && otherData->name && !strcmp(otherData->name, data->name)) - { - otherData->isChecked = false; - n->Redraw(); - } + otherData->isChecked = false; + n->Redraw(); } } } @@ -113,9 +167,117 @@ bool CheckBoxNode::HandleEvent(Node* node, const Event& event) 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; -} \ No newline at end of file +} + +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 index bccaa9e..24096d9 100644 --- a/src/Nodes/CheckBox.h +++ b/src/Nodes/CheckBox.h @@ -23,6 +23,12 @@ class CheckBoxNode : public NodeHandler 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 index 903a0f7..66ae2b4 100644 --- a/src/Nodes/Field.cpp +++ b/src/Nodes/Field.cpp @@ -10,6 +10,8 @@ #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); @@ -31,11 +33,11 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) { if (data->isPassword) { - DrawPasswordString(subContext, font, data->buffer + shiftPosition, node->anchor.x + 3, node->anchor.y + 2, textColour); + 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 + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + subContext.surface->DrawString(subContext, font, data->buffer + shiftPosition, node->anchor.x + LEFT_PADDING, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); } if (selectionLength > 0) @@ -46,20 +48,38 @@ void TextFieldNode::Draw(DrawContext& context, Node* node) { 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 + 3, node->anchor.y + 2, textColour); + 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 + 3, node->anchor.y + 2, textColour, node->GetStyle().fontStyle); + 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); @@ -163,7 +183,8 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) cursorPosition = pickedPosition != -1 ? pickedPosition : strlen(data->buffer); DrawCursor(context, node); } - break; + DrawHighlight(node, Platform::video->colourScheme.textColour); + return true; case Event::Unfocus: pickedPosition = -1; if (shiftPosition > 0) @@ -184,7 +205,8 @@ bool TextFieldNode::HandleEvent(Node* node, const Event& event) DrawCursor(context, node); } } - break; + DrawHighlight(node, Platform::video->colourScheme.pageColour); + return true; case Event::MouseClick: pickedPosition = PickPosition(node, event.x, event.y); if (App::Get().ui.GetFocusedNode() != node) @@ -391,7 +413,7 @@ void TextFieldNode::DrawCursor(DrawContext& context, Node* node) TextFieldNode::Data* data = static_cast(node->data); Font* font = node->GetStyleFont(); - int x = node->anchor.x + 3; + int x = node->anchor.x + LEFT_PADDING; int height = font->glyphHeight; x += GetBufferPixelWidth(node, shiftPosition, cursorPosition); @@ -428,7 +450,7 @@ void TextFieldNode::DrawSelection(DrawContext& context, Node* node) int selectionX2 = GetBufferPixelWidth(node, shiftPosition, selectionStartPosition + selectionLength); Platform::input->HideMouse(); - context.surface->InvertRect(context, selectionX1 + node->anchor.x + 3, node->anchor.y + 2, selectionX2 - selectionX1, font->glyphHeight); + context.surface->InvertRect(context, selectionX1 + node->anchor.x + LEFT_PADDING, node->anchor.y + 2, selectionX2 - selectionX1, font->glyphHeight); Platform::input->ShowMouse(); } @@ -441,14 +463,14 @@ void TextFieldNode::RedrawModified(Node* node, int position) uint8_t clearColour = Platform::video->colourScheme.pageColour; context.clipRight = node->anchor.x + node->size.x - 2; - int drawPosition = GetBufferPixelWidth(node, shiftPosition, position) + 3; - int clearWidth = node->size.x - 1 - drawPosition; + 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 + 1, clearWidth, node->size.y - 2, clearColour); + 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); @@ -518,7 +540,7 @@ void TextFieldNode::ClearSelection(Node* node) int TextFieldNode::PickPosition(Node* node, int x, int y) { - x -= node->anchor.x + 3; + x -= node->anchor.x + LEFT_PADDING; int result = shiftPosition; TextFieldNode::Data* data = static_cast(node->data); diff --git a/src/Nodes/Field.h b/src/Nodes/Field.h index f06f255..9e0ff55 100644 --- a/src/Nodes/Field.h +++ b/src/Nodes/Field.h @@ -54,6 +54,7 @@ class TextFieldNode : public NodeHandler 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/Select.cpp b/src/Nodes/Select.cpp index a9e9b3f..4306862 100644 --- a/src/Nodes/Select.cpp +++ b/src/Nodes/Select.cpp @@ -3,7 +3,8 @@ #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) @@ -34,8 +35,31 @@ void SelectNode::Draw(DrawContext& context, Node* node) { 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) + { + 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); @@ -53,7 +77,7 @@ void SelectNode::EndLayoutContext(Layout& layout, Node* node) } } - node->size.x += 6; + node->size.x += 8 + Assets.downIcon->width; if (layout.AvailableWidth() < node->size.x) { @@ -116,3 +140,88 @@ void OptionNode::GenerateLayout(Layout& layout, Node* node) } } } + +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 (data->selected) + { + data->selected = data->selected->next; + if (!data->selected) + { + data->selected = data->firstOption; + } + node->Redraw(); + } + } + return true; + case Event::Focus: + DrawHighlight(node, Platform::video->colourScheme.textColour); + return true; + case Event::Unfocus: + 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; + } + break; + + } + + return false; +} + +Node* SelectNode::Pick(Node* node, int x, int y) +{ + if (node && (!node->size.IsZero() && node->IsPointInsideNode(x, y))) + { + return node; + } + + return nullptr; +} diff --git a/src/Nodes/Select.h b/src/Nodes/Select.h index 9e55018..1c83979 100644 --- a/src/Nodes/Select.h +++ b/src/Nodes/Select.h @@ -35,6 +35,13 @@ class SelectNode : public NodeHandler 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); + }; #endif diff --git a/tools/AssetGen.cpp b/tools/AssetGen.cpp index 3441a24..942c914 100644 --- a/tools/AssetGen.cpp +++ b/tools/AssetGen.cpp @@ -96,6 +96,8 @@ void GenerateAssetPack(const char* name) 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); From 1dc8a6591ff9cdca5f66f5cfb3be6a84a53d610d Mon Sep 17 00:00:00 2001 From: James Howard Date: Sat, 13 Apr 2024 20:16:23 +0100 Subject: [PATCH 94/98] Added drop down UI for select nodes --- src/Nodes/Scroll.cpp | 3 + src/Nodes/Select.cpp | 161 ++++++++++++++++++++++++++++++++++++++++--- src/Nodes/Select.h | 12 ++++ src/Render.cpp | 52 ++++++++++++-- src/Render.h | 7 +- 5 files changed, 218 insertions(+), 17 deletions(-) diff --git a/src/Nodes/Scroll.cpp b/src/Nodes/Scroll.cpp index 355e9a0..9358073 100644 --- a/src/Nodes/Scroll.cpp +++ b/src/Nodes/Scroll.cpp @@ -70,6 +70,9 @@ bool ScrollBarNode::HandleEvent(Node* node, const Event& event) switch (event.type) { + case Event::Focus: + case Event::Unfocus: + return true; case Event::MouseClick: { if (event.y < widgetPosition + node->anchor.y) diff --git a/src/Nodes/Select.cpp b/src/Nodes/Select.cpp index 4306862..1e3f8b2 100644 --- a/src/Nodes/Select.cpp +++ b/src/Nodes/Select.cpp @@ -39,10 +39,17 @@ void SelectNode::Draw(DrawContext& context, Node* node) 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 - 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); + 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); + } } } @@ -150,22 +157,72 @@ bool SelectNode::HandleEvent(Node* node, const Event& event) { case Event::MouseClick: { - if (data->selected) + if (node == dropDownMenu.activeNode) { - data->selected = data->selected->next; - if (!data->selected) + int clickX = event.x; + int clickY = event.y + ui.GetScrollPositionY() - ui.windowRect.y; + if (node->IsPointInsideNode(clickX, clickY)) { - data->selected = data->firstOption; + 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 = Assets.GetFont(1, FontStyle::Regular); + 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(); + } } - 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: - DrawHighlight(node, Platform::video->colourScheme.textColour); + if (dropDownMenu.activeNode == node) + { + DrawDropDownMenu(); + } + else + { + DrawHighlight(node, Platform::video->colourScheme.textColour); + } return true; case Event::Unfocus: - DrawHighlight(node, Platform::video->colourScheme.pageColour); + if (dropDownMenu.activeNode == node) + { + CloseDropDownMenu(); + } + else + { + DrawHighlight(node, Platform::video->colourScheme.pageColour); + } return true; case Event::KeyPress: @@ -208,6 +265,12 @@ bool SelectNode::HandleEvent(Node* node, const Event& event) node->Redraw(); } return true; + + case KEYCODE_PAGE_UP: + case KEYCODE_PAGE_DOWN: + case KEYCODE_HOME: + case KEYCODE_END: + return node == dropDownMenu.activeNode; } break; @@ -218,6 +281,14 @@ bool SelectNode::HandleEvent(Node* node, const Event& event) 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; @@ -225,3 +296,71 @@ Node* SelectNode::Pick(Node* node, int x, int y) return nullptr; } + +void SelectNode::DrawDropDownMenu() +{ + Font* font = Assets.GetFont(1, FontStyle::Regular); + 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++; + } + + Font* font = Assets.GetFont(1, FontStyle::Regular); + 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; + + 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 index 1c83979..f2220ab 100644 --- a/src/Nodes/Select.h +++ b/src/Nodes/Select.h @@ -42,6 +42,18 @@ class SelectNode : public NodeHandler 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/Render.cpp b/src/Render.cpp index de0b7fb..32aae73 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -57,9 +57,33 @@ void PageRenderer::RefreshAll() renderQueue.Reset(); - FindOverlappingNodesInScreenRegion(windowRect.y, windowRect.y + windowRect.height); + FindOverlappingNodesInScreenRegion(windowRect.x, windowRect.y, windowRect.x + windowRect.width, 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(left, top, right, bottom); +} + + void PageRenderer::OnPageScroll(int scrollDelta) { if (scrollDelta == 0) @@ -71,6 +95,8 @@ void PageRenderer::OnPageScroll(int scrollDelta) 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++) { @@ -100,14 +126,14 @@ void PageRenderer::OnPageScroll(int scrollDelta) int top = maxWinY - scrollDelta; if (top < minWinY) top = minWinY; - FindOverlappingNodesInScreenRegion(top, maxWinY); + FindOverlappingNodesInScreenRegion(minWinX, top, maxWinX, maxWinY); } else if (scrollDelta < 0) { int bottom = minWinY - scrollDelta; if (bottom > maxWinY) bottom = maxWinY; - FindOverlappingNodesInScreenRegion(minWinY, bottom); + FindOverlappingNodesInScreenRegion(minWinX, minWinY, maxWinX, bottom); } Platform::input->HideMouse(); @@ -164,10 +190,16 @@ int PageRenderer::GetDrawOffsetY() return windowRect.y - app.ui.GetScrollPositionY(); } -void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) +void PageRenderer::FindOverlappingNodesInScreenRegion(int left, int top, int right, 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; @@ -185,7 +217,14 @@ void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) if(nodeBottom - nodeTop > 0) { - AddToQueue(node, nodeTop, nodeBottom); + int nodeLeft = node->anchor.x; + int nodeRight = nodeLeft + node->size.x; + bool outsideOfRegionX = nodeRight < left || nodeLeft >= right; + + if (!outsideOfRegionX) + { + AddToQueue(node, nodeTop, nodeBottom); + } } } @@ -199,6 +238,7 @@ void PageRenderer::Reset() renderQueue.Reset(); lastCompleteNode = nullptr; visiblePageHeight = 0; + isPaused = false; } bool PageRenderer::DoesOverlapWithContext(Node* node, DrawContext& context) @@ -227,6 +267,8 @@ void PageRenderer::Update() //if (rand() % 256) // return; #endif + if (isPaused) + return; int itemsToRender = 5; diff --git a/src/Render.h b/src/Render.h index 02324f6..3c9b3a1 100644 --- a/src/Render.h +++ b/src/Render.h @@ -77,12 +77,16 @@ class PageRenderer 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); + void FindOverlappingNodesInScreenRegion(int left, int top, int right, int bottom); bool DoesOverlapWithContext(Node* node, DrawContext& context); bool IsRenderableNode(Node* node); @@ -97,6 +101,7 @@ class PageRenderer Node* lastCompleteNode; int visiblePageHeight; + bool isPaused; }; #endif From 029fa3605f5516734cf22304befbadb6b709a36b Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 17 Apr 2024 16:07:58 +0100 Subject: [PATCH 95/98] Fixes for rendering glitches --- src/Nodes/Select.cpp | 47 ++++++++++++++++++++++++++++++++++++++++---- src/Nodes/Select.h | 1 + src/Render.cpp | 19 ++++++------------ src/Render.h | 3 +-- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/Nodes/Select.cpp b/src/Nodes/Select.cpp index 1e3f8b2..3c99981 100644 --- a/src/Nodes/Select.cpp +++ b/src/Nodes/Select.cpp @@ -17,6 +17,15 @@ Node* SelectNode::Construct(Allocator& allocator, const char* name) 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); @@ -41,7 +50,7 @@ void SelectNode::Draw(DrawContext& context, Node* node) { if (node == dropDownMenu.activeNode) { -// DrawDropDownMenu(); + DrawDropDownMenu(); } else { @@ -170,7 +179,7 @@ bool SelectNode::HandleEvent(Node* node, const Event& event) 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 = Assets.GetFont(1, FontStyle::Regular); + Font* font = node->GetStyleFont(); int index = (clickY - dropDownMenu.rect.y + 1) / font->glyphHeight; for (OptionNode::Data* option = data->firstOption; option; option = option->next) @@ -299,7 +308,7 @@ Node* SelectNode::Pick(Node* node, int x, int y) void SelectNode::DrawDropDownMenu() { - Font* font = Assets.GetFont(1, FontStyle::Regular); + Font* font = dropDownMenu.activeNode->GetStyleFont(); SelectNode::Data* data = static_cast(dropDownMenu.activeNode->data); DrawContext context; @@ -345,12 +354,42 @@ void SelectNode::ShowDropDownMenu(Node *node) dropDownMenu.numOptions++; } - Font* font = Assets.GetFont(1, FontStyle::Regular); + 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); } diff --git a/src/Nodes/Select.h b/src/Nodes/Select.h index f2220ab..6e5fda5 100644 --- a/src/Nodes/Select.h +++ b/src/Nodes/Select.h @@ -32,6 +32,7 @@ class SelectNode : public NodeHandler 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; diff --git a/src/Render.cpp b/src/Render.cpp index 32aae73..e0b2ea2 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -57,7 +57,7 @@ void PageRenderer::RefreshAll() renderQueue.Reset(); - FindOverlappingNodesInScreenRegion(windowRect.x, windowRect.y, windowRect.x + windowRect.width, windowRect.y + windowRect.height); + FindOverlappingNodesInScreenRegion(windowRect.y, windowRect.y + windowRect.height); } void PageRenderer::MarkScreenRegionDirty(int left, int top, int right, int bottom) @@ -80,7 +80,7 @@ void PageRenderer::MarkScreenRegionDirty(int left, int top, int right, int botto clearContext.surface->FillRect(clearContext, left, top, right - left, bottom - top, app.page.colourScheme.pageColour); Platform::input->ShowMouse(); - FindOverlappingNodesInScreenRegion(left, top, right, bottom); + FindOverlappingNodesInScreenRegion(top, bottom); } @@ -126,14 +126,14 @@ void PageRenderer::OnPageScroll(int scrollDelta) int top = maxWinY - scrollDelta; if (top < minWinY) top = minWinY; - FindOverlappingNodesInScreenRegion(minWinX, top, maxWinX, maxWinY); + FindOverlappingNodesInScreenRegion(top, maxWinY); } else if (scrollDelta < 0) { int bottom = minWinY - scrollDelta; if (bottom > maxWinY) bottom = maxWinY; - FindOverlappingNodesInScreenRegion(minWinX, minWinY, maxWinX, bottom); + FindOverlappingNodesInScreenRegion(minWinY, bottom); } Platform::input->HideMouse(); @@ -190,7 +190,7 @@ int PageRenderer::GetDrawOffsetY() return windowRect.y - app.ui.GetScrollPositionY(); } -void PageRenderer::FindOverlappingNodesInScreenRegion(int left, int top, int right, int bottom) +void PageRenderer::FindOverlappingNodesInScreenRegion(int top, int bottom) { Rect& windowRect = app.ui.windowRect; int drawOffsetY = GetDrawOffsetY(); @@ -217,14 +217,7 @@ void PageRenderer::FindOverlappingNodesInScreenRegion(int left, int top, int rig if(nodeBottom - nodeTop > 0) { - int nodeLeft = node->anchor.x; - int nodeRight = nodeLeft + node->size.x; - bool outsideOfRegionX = nodeRight < left || nodeLeft >= right; - - if (!outsideOfRegionX) - { - AddToQueue(node, nodeTop, nodeBottom); - } + AddToQueue(node, nodeTop, nodeBottom); } } diff --git a/src/Render.h b/src/Render.h index 3c9b3a1..194abf6 100644 --- a/src/Render.h +++ b/src/Render.h @@ -86,12 +86,11 @@ class PageRenderer void InitContext(DrawContext& context); void ClampContextToRect(DrawContext& context, Rect& rect); - void FindOverlappingNodesInScreenRegion(int left, int top, int right, int bottom); + void FindOverlappingNodesInScreenRegion(int top, int bottom); bool DoesOverlapWithContext(Node* node, DrawContext& context); bool IsRenderableNode(Node* node); - void AddToQueueIfVisible(Node* node); int GetDrawOffsetY(); App& app; From 790cba69661b7f8f7edaad45a1ec57fe3b95259e Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 17 Apr 2024 16:08:23 +0100 Subject: [PATCH 96/98] Improved palette LUT generation for 4 colour mode --- src/Palettes.inc | 32 +++++++-------- tools/PaletteGen.cpp | 96 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/Palettes.inc b/src/Palettes.inc index 40a4afe..cf08459 100644 --- a/src/Palettes.inc +++ b/src/Palettes.inc @@ -17,22 +17,22 @@ uint8_t egaPaletteLUT[] = { 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, 5, 0, 0, 0, 5, 0, 0, 5, 5, - 0, 0, 5, 5, 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, - 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 5, 5, - 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, - 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 5, 5, - 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, - 0, 0, 10, 10, 0, 0, 10, 5, 0, 10, 10, 5, 0, 10, 5, 5, - 10, 10, 5, 5, 10, 10, 5, 5, 10, 5, 5, 5, 10, 5, 5, 5, - 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 5, 10, 10, 10, 5, - 10, 10, 5, 5, 10, 10, 5, 5, 10, 10, 5, 5, 10, 5, 5, 5, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, - 10, 10, 10, 15, 10, 10, 15, 15, 10, 10, 15, 15, 10, 10, 15, 15, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, - 10, 10, 10, 15, 10, 10, 15, 15, 10, 10, 15, 15, 10, 10, 15, 15, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 15, - 10, 10, 10, 15, 10, 10, 15, 15, 10, 10, 15, 15, 10, 10, 15, 15 + 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, diff --git a/tools/PaletteGen.cpp b/tools/PaletteGen.cpp index 20d9577..0f9db0d 100644 --- a/tools/PaletteGen.cpp +++ b/tools/PaletteGen.cpp @@ -1,6 +1,7 @@ #include #include #include +#include const RGBQUAD egaPalette[] = { @@ -50,20 +51,103 @@ const RGBQUAD cgaCompositePalette[] = { 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; - int closestDistance = 0; + double closestDistance = 0; + bool useLABDistance = paletteSize < 16; for (int n = 0; n < paletteSize; n++) { - int distance = 0; - 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); + 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; } From 417b7e61d3bd74814175fa858f1a08b63aa95705 Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 17 Apr 2024 21:02:31 +0100 Subject: [PATCH 97/98] Added ASM for 4bpp image blitting inner loop --- src/DOS/Surf4bpp.cpp | 54 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/DOS/Surf4bpp.cpp b/src/DOS/Surf4bpp.cpp index d8078be..28347af 100644 --- a/src/DOS/Surf4bpp.cpp +++ b/src/DOS/Surf4bpp.cpp @@ -7,6 +7,8 @@ #include "../Memory/MemBlock.h" #include "../Colour.h" +#define USE_ASM_ROUTINES 1 + #define GC_INDEX 0x3ce #define GC_DATA 0x3cf @@ -373,6 +375,39 @@ void DrawSurface_4BPP::DrawString(DrawContext& context, Font* font, const char* } +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()) @@ -432,14 +467,22 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int 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; - uint8_t* destRow = lines[y + j] + (x >> 3); - uint8_t destMask = 0x80 >> (x & 7); + +#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++) { @@ -447,13 +490,13 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int 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); - outp(GC_INDEX, GC_SET_RESET); - outp(GC_DATA, colour); - *destRow |= 0xff; } @@ -464,6 +507,7 @@ void DrawSurface_4BPP::BlitImage(DrawContext& context, Image* image, int x, int destRow++; } } +#endif } } else From fc9841ccea6496e36db13c6ea48c3eca7cc523c6 Mon Sep 17 00:00:00 2001 From: James Howard Date: Wed, 17 Apr 2024 21:03:44 +0100 Subject: [PATCH 98/98] Updated floppy images --- floppy/freedos.boot.disk.360K.img | Bin 368640 -> 368640 bytes floppy/freedos.boot.disk.720K.img | Bin 737280 -> 737280 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/floppy/freedos.boot.disk.360K.img b/floppy/freedos.boot.disk.360K.img index fccb7cccdda211d8dd0a084e1237abccdce2f9f1..a97a539297da020b0c1034bc7583937ffb421989 100644 GIT binary patch delta 213800 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%0A+~cT< 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 {^cVIjd2Zn_gp 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 diff --git a/floppy/freedos.boot.disk.720K.img b/floppy/freedos.boot.disk.720K.img index 3533c89c9e4b318e506bebb09d410cd3ce9d44a1..ca476caf1dad143a0f0a8e279ecb598fe63dd340 100644 GIT binary patch delta 227235 zcmdp-hgTD880M1@N1tkw{$?X0Z- z@UUKg*m^^}wZl_uN1nCwkhLpek-OER%^r)k9A1RoT(s@!q8+^5i*^ky+D%yOYqfZf z$727(iv!~q?|-`Z0B`ZZp~ayDn=mVzBOW%`Ih*70HYcCjoaWh_9kMw`SaQK?$wiMP ztiwy9;+I^OU{`oct`02$B-jVMrW@hCjy?YW>woYz^`6ebSGV>Na8sRk9#Nl~ZT51v z`0wTEz8Cj@UO(8k_E88D-#a0+cr(u}40r#Xd;hO49qKNY1iQcPpz1DL{-1mCBJ?ru zJfg>&?c5#cw`I@&J0h) zSkJXq(Y11>Ce=mo(ZmHOHIlLniab}EqQy9zPh-li^n@xRjW47JVnF0qsb~xjWA+`Q zop$Bm_`%SSpzqCiq?3}a+T(lErSGq_o+>BqgblXvg=%*t6|4kXdTl#TjCn1?Oct2909r`-O|}!b8#y;nSsrr^riL+_=%Fu^e#- zE=uxfQ?oUG7Gg}on^huvw#`w~5d{^-6#5BDeC*m<28_oE@u&tiy9{<@0FtW~+R0#T z%WqUd#awkYVkv1|)tX=>+jSL_gz~?nhKQiQaom-~UfH87Q?Jg5#g`zRTud7k7N>%K4vKG<-@upJ`sw>1~KD7SiiSP2MQX5Nfv`JnW+J}VHgHX zK^w3lo>eN)n)#%`m1Zx}elocT&(zGs6-co)_2O`UsvtEW9h`})EfP~U>j$7BpFokw z?53O;E7u$(C@ZCzOK*!4VnsNXmSPff!7=qYA(I?$QKw2u5*nJOT|zloTu$mDXP3fM zKQVGPeVkp|Y+Y{q{lWo_$%bvn<$P&ed;Gu(E@9~iJ0p8waT=f z6m2IW>-;avZ<)dQlAhl>J?+=065C<*^P>u26&X8evd)dF)`q_%kD|ihyW)KXviVw- z1KO#rH?EJVilwP7Hy%xwJyc9=Ka$S98VQqao?=3i!Q;AgHbB&5QZ#FA^Q7r?S^05> z;-+TPA6lze8B$0)p7!B^Ey9=xNMCr+@S%rX@;rhz~gMyk{Ju^OMxwZK+6g?AYGan@h(Lec|iW z`HPGE#&EOj@tc!rXE!%BOTvpd zBQy}e0<5V(C5p=v2qpUg!aGr@fw-UuTy0dYGD16A0_RxykPpT!KjrTPI~vumK1DqMEnGmD}`OwF{Df zQjRwuqhPK=|9@pEL?`zM8;tK19c9Ii!Ea)jDhR>1p0SKRX4RJTzv!z-YUT-7vtTl} z^DV5mZhwU~?q!1fi+C)l4md9d(X=iho^ekAk(^-Tr_(jWH9`@0pjf6zZ;{hs3Rsy? z>CzGke-ZX@hkIbX0vHTXOyIc!U<{@q6)G)Fjp)fFNkXi`xedlKtsNC6UoRd~Kqw~? z_Qo&|$N`O{v7Od1$&Qgoq^7qqPB4<- zXW==*Q>wZjSm#g&GM+Qg0h6IHjQf)>{{mAh!46lInOlz}6R>2C9}=#z_vwK^vB~Fa z5^w?^CKA^u^{&d;Wmm&XnDarIW{0V)MuWSENoWd?}Vy#s|IyhYY=gPo;JMe)-m7p3X?awC(TYt6? zdt@0ZUdD`El1a@quBdj|x1m?Dp%+P`(HW=>C#yx78dLpqU7rFwXz>=N#-WSGe6<## zH|ld`^Emg@^`NP+bQK9~3m@YN2?bzRIIy5zrydQcgLMn*V6uEQvo5XN<{6(#xzs?$ z#hlh6NapsLN@#T*bnw7TY%6k=y%)ht6R2tZk)^(H6 z1?#%9m3n%1dI&{` zuZ{IeFwrn4Qi1;oD&s5{JNyjY7P%Ef<7)0_)b+Hj?KuI!r}Z*;pW0I&wl454cXD;=X zU_rSOm;gw*7PA)brIMJ7=`+V9*W+c0oyrAo&iT+6yqT5!*)X72(FTl(d-C?D$JL4q zxHdo;7jqF%Y1~zL>U$F{ox5!XbqFSlsFUKJ=R-r(e)l17b=*o7APriP2I!VcR&uK@ zvV9*z!VTCk_y8uAM|i3jFT~Y^1f{zQaB!b>%Xfz!1O^TWg<%8jG_gcAc@E-;x+<(@kQlL!_m>fxdU5E)z0#LHSYAq|PIFX^>-uZpkksvh#F z{+%`eEE~sHrgo*3Zw`-tW?dof`&DZLj@YE)O0r&dKZHq}hdPxun@(KY2~(?@cv`6y zd9FPLN*k4+8K4q;(z?@lgHM{DG*~MMx#;FEPsqIq_En#rSlV!$RGw#}lIrN$$ROYE zOLr=-ul1nCC-m|7p|~dkn!!Rbk5?TR;+g;E_k$zhT?6}W%XWPLoWd|(jSVAs26kK|TJVPvr=Y$X;PS#4IoVb9%Hm{?0h z9{)*Tf~Me3LXD>!BbG*xA6+EZdK%?erS3HW51H&e<_;43(>$prS+!nNzJgA*1QZ-6 z&MzAeDx!ge($jc0;4U(fIvf!w5C#gXXo-ysuXVxiJ_vDXBK) zl%1W*3Vmz3yF`b)0K zODYGN_NG-F z<1)W`1BdupJg4QxyY`m2$;-0oIqCX^PkLUzB~I4MrY7jab(t-tCcx+5lD*EJl)^8j zbCNFc%U@u>Jb&?b@1@8=Z7`4^+)oQMHR1%Cq({$5e*iJXchdX5xU~KwZU*Uz&DzZu zBxo4jlR8PS75Ixi7yd2F?TUdOsW_2ZPf9nW?)q^qj)x^JI$f0)$*CLZMmH$voT zJqgspFNKm#HB21mp`-Y(RsD`)9cM|Pvt)TMbI$x+5#|L(X*o2qMjRP(rml8^u%8{< z6d`8YAE>;U8-f%gM;v+KV$&D!d&|@dxy8SxwRaMMs?DmsrU-VUt(t{urO}?d(lZfe;2{M4P!JtU#BM1F5P#2 zf?<4Gcg?joTQ#Gl;+@gy>!eB7J4LH_HnQ~P&D1OHE!4PDEe%gHM%vQ*cM)4uFL4%M z1av{yv0D60@T&=*uy;e-HSE&#z|tuvJr>^}ZZ>qfT+VcNm3$Xl*L{U`b)kM` zB94nXG}+veGWEHzE{&=4FkUzStd}qkRz_BqEO3`LO4r}wc<1r=#pdzli>D=dgC<>h zqcKPstm`81{}K}kH*SALLd6eDT4VxAamV#V+d3yq=0S3?S8p*zQL>xF6mVh-Hg<51#T5}Ea()<{#veq7pe(0_5CVT>Xy-X0<%hfl_Ze%+zBq_fS$+ zh0Ce0CpA@m9KiefbpP(W@uM)iJWSI)u5Ax9QSI9nc%K8CGD zkKs9^*ZGen;3tOY2?3fbm!J|EL)w$ty;a;Qic%9(`bCOd8`>?Ah9l&(GRaXul&D?TZWA8;cZqQhp@>D_OZNRC5kp;~jcW_K$vT6QoI5~*YjkB6w|4OD{tX@;yHJ1 zHdc$Y*LKvRWcINE9NT+9Hh>py;sOqQSYL-PG#7GSZ{)fR!$wtqw#=Q$tKtg<@B3mu zv(verdtiF5c*XTHtgYZs;QiI+oFrTGvrDR~1&y~iXTf^y&tYBrs_zQXV;eAOt-134 zI`c{$XaN19L;mr)Iag9~hQFmn>JV?*55Y+dQjOGZa6qH1@9aKMxrYzfuqBVBc_J2M?YcZj*5Si;k{yh)tm|C^0@g!;0GWRg*#cc%|+o3k|$zglW&2=-S}A1dKXYpwo1BMl4Is%!K65mnL19SupiIGdM){Qiwim5 zC>AjZvFNF6pUQ^V8nXvf9W7R$cmC;C-!Dvmh9_&+J*#+Gjtox(sCC+WfGb`@b^@Y8 zAQFLH+F5fXn-v)R^uvYa;Iowq*Tm#1_Q)U`shcMXl`SO`aEr{hUl<%hgg$eepkMfY z3uKVB;2PSm(NF7#DXXA#ywK2lTQA%zVj1~xdv`8sZ|Q}}I>UcM{;jky@y%r{4QMNd zrJ7&=QLJT#YkA()a<>b3J|u_MVT)Wzb)zSKa|-?W^q;Vv!5s)PC#=)HLmo2)NNsUr z5-g#kB@>hO>qc<0t~V7A2-CXoZ5-EvHieLSf9%D>1JxdQFa5YiZKL897nO>3_Y~W; zeb5QC)3Qgk9uL(;?MLeqxSXzX-_gYH*wnfCDbH5}#H5=Dfmx=+Gp{9%{G&LkP(kf`P0Pfk$RF_;IOuKA7Zqv! zns%w^>D*ItWC45rfly-i!osJ!Bdo3=M`Xg@<%h_6d65CRceCO%YTi$|1p831BwI4W zM?awq0V9dnP1Qfc8J1hEZdt9czHLnwkkm8h4q9+>cSMCJ5pW_#E>|76hdc-kv1HFW zv2(nbeiUwkiS1ho9|zl%ZEhD z>&VEC5j6QIK#VMfe$;g;pO|u;ME4pY>2ev@qI%M-4K?cd{e9X^VoTT}Bm51M_Gr4W z3qQK{^xSg)*B zX-7zlv~ih!nmBII>1}U#D_W{XT7HPTo17wE1D+zz5CH3XBgw~9UZ*OrMf*tu9htg! zF34J%M-?=^16g(Wxp~Ik;)Dv#a}MHfcD*}NEJX0B9aHba^~uk0qGs?;hI+2xj5wGZ z{Y}6{?2J=@JP+|M@b1f+ot7Pz#5D*s0HIi!C@^aUny#^d&CftR0x z1J!RA2J3A2ZhT~feS5y0J5!s7{=(h}fBu9?8*J;Gtgh;?)|G{&29q}nz?5qOa+_Z~K#*sn4VMK9kj zSzRqYhLN^)1`|89p!1IYcbr+oi7@I-b5%@vt zqeyq#M-lxJ?xQ$Hr009K@nx9kF^h`(1IFgROc6} za*D`or3I6$Br`Rab|^_NzYeY(aic%sm{V)mPSz~X{$vJPK~*VKNBM3arOsN)%^8}` zEB7zI5r7!^DHQ-2RiaA96${bO}e^DjtRBA5-@{{ zwpD-fT1L@jiW!AHC-J#5_iheNGf^X_(>`UfDDl<)d4Jyi6UCSoM1wnp;)I%nvXWf% zRS%P#pY$)oN)GEII7K0*8tvO$Ey=~|1?8iZ^>O|8Eh}`4X zV=vF|h}CV*yf*hqw|0!+Nv#*5ZKaZ!;p>HUYL%|E2hE}_?4QM0Ea_>4sqJ3YjfRad zt;S0SFIoFpP>#fwv&&(Vc{lzlhYbkjFeSb`u^c8Fs(R1!%y_VY1`noO=YdAaz3mmS zk#z-ZK&*f%DHW;eC=y6suKt6j`nmzzywZAe%FWWtVIy1hp0wg%%I&xu-rWjVe^G^f z1x!=w6y@UOL}od5ul&K;Q|f*W|7%3O%J`STCpSl?!aNWf$PC9$u>g)_d)eSALzp=cW4imzHnU5X>dh zOLGF+OR;yQ`K7SY9`#~WvFGnnSoeMDM|E(Z6khx}XkBTMx_z(|Hkc`eX(gX>OH0*p zn0njS((?71>h@9fk|(0_N~=mg?bux<)Md1&0;{iR z=xtAjjV@GRo=sR|Q?t5l|0zA}=fRtvVGMLnJ_Ea6e zFAg?}2YBnl|Nn_>Xz5CSRP4&6b zDFhlf-(V-lL|?5QT$=w1`{ya|&x!RmI#p1+%514R>}ZfB`m=;wt^ zILjt@Zgvy)u?Zts?l{#%RBwCLgjftUAzI{sCY`1~n+;$7*=$hUyXzBgtQ;YiQ*OfK zbwzq_^?JWzBmZL5f1lACVZh`=BW%#sVesEIWm4LREn@QGv*z%^8 z>Zo_9nUovZgspC}C5rT)4F6Ppn@ilg=X3?Owqjic02}d#nvK-gpZ)91ijBUo5mDkl z(S&X4$!T&@e_GgtF`F=#rZ);V%b@f-3Zwk{u>SiDycZ~+QU)I_%*y5@#aMt0lzTva z^%HYpq@5{eCxLSLwq5R{M$Fv$qHvA9Tb9RM4PjWWI$Swx=9oN=|^?Mi!dIOXvZg6(g+R=<*v zk>K#7rNO6jb}SuAKe4bZDj%l(RI3@?iqpnm+q(7M$5l5>=KTr_#oJaP>5xkKcVW^;9eu!6Rq2gt&rbk!G-5Qr77Aa+YJ4T&R!Z-|b23)& zTHud0OK!uuDYuc8%TURb^G-e%sW4myin$u7pz3vWx=TMQ@cSw8IIwujnWt{lGfK%> ztmNC+S?<|$u!*j?oa{}}Rp`Zd3=ACT|1^#UqoXC0BGPMh=J#rhE{_7@Vd`WG7#Y^& z=fers;i1v5$)!9_=F{8#Pfoa_<>DKAPi@aqtAIXwrm?53V9H{Zg-ja*+Bktkj+d4r z17R6D2B56$QY_BFd*X6t?9ah>T(Qapi@yC7Cei!9igeGK#K6W*F)ABVSdV^rkFSUF zFY;fMr}ITFl5xOJ-XCg*Nzj^c;e`OM0$1F&*-{zQ!k1Kz_H!a-2jeO!*;S4S^2#? zTAFizOWXlN(Sr7!5lbUrz4>WDlKzX2hf+Ls9yAV;PTW01`EfHafQW>43;3IQ)6)j} zi;in(`toogBGPE1VC>3iIl5GMSUkYR4$DUC#fRndxfoYEUNtsRE#{kZB9XLULfN_G zBu{|ACE{CP6M@e}dJ9b1yfnOp)B@Ab#9)o68HTCv_Z|fM>_et%^Ww0n+Em2`G8f@p zX^=OjHeb}fYQa3+130Fm9;GJ!@ClmagzoU!BAA?VJ((ddCrg^q^GwZVk-*ZgS0q@l zuseI|K&-SF!>-5$wmIpc69(hJSpSbhKL_34cc;SEKYLP-t8D&NA9aHT$gAZexf;Hs^c%~q6#OC)w05_q3>xErDKr-Ir{l$kRS~T=jxA-k6$tP0!*Cb_ZLn@=<=#B z8my4}x~^imXAM!XH5Rt=WF~>MmmvHA-8I`Zj!w{P?A?XWWiW}olYkfU`tOQhN=u^9 zXUYC3D0@}slz}7HC?2MVL&2EZYaQJ0bs&qv3EUDgb_UscX>0)Znw{*asYSxp zq>Va*BpKK8y=YX9+W!7?o5We9zOE%juC$82zkTd_p%PK?mCQ{B3wlN;MIE!+E&^-Q zOXpnQx-N7>*#U|U`*2v#qN~xxmyL^2$yKiF{u6X-7zhqQ&*@LH5{?X_;?Z=uDn&l? zr43?mO?_p9oPeRW$-`XB>_8zhA{=#+&*d%^!t^mq_}o)Gl#GMe1nwGjdbF-h*#PTn zg$w<;j=$i!n!-&gGp?t)r6Dxu?oz)Kg~X|B1&+ICEv)aw@otTbJ>anon64dH(+ra7 za_abWMXhm5S?AFS68IeyODn-x*=EvPvHvu8|8v-}q%Nc8IZUc*36#W2-g3Di&tZM) zd_++YobD>-9-YMcVfrh+9cn7ZL(9?sL?#yf5J@3ZjJ;6>>LgEcLS{>@3-n5VhCM9p zd$qGOajFHL69hP6!7ZI251hS*F>(r*t)gD-^?Gs7E8ERe%Q8_RQG*?m3L2Nrjzu+x z^6|SX%9Gz-pJUgSj`kS}NbwE{5TW;oukDCWGqD*JPyb=EDLO#t|=I3lw4$zI3eDGH6ZKg)#pQ1+TeJ)wok&GPr z3lon+@U-&$=$USHt}fWrmY$}vV;OU-s%DJjUWexbnuGc$`j3hQtrG9C zN)cHktQ65i+@?wq9j=^-0StIjL(X0Ub8=)WGahV~8XnKZj+v%+r$Z4KHz5Jm-<9Cs z5LA}{FWR4r{Y+3?*qBf)^rW)s5Zw^>DCrTbAO0Z=6_=0Y>|&~ zd>&T`l*4mr3~WA9!+^e_T)Ns2rCXGp7MXIbf5ZBskvrdARf3Luv)s57Yy$94SyTUgWy zA49P}#-?`sfc5wBf53WL&_#?J*a@3ZIZ1-+17GXn=0n>%gO-GL!t~VuwASfXJMwyO zdy)SeGstWtd+d1J$n$$WQBfZyc?}~^EPMVPd19V*0T$@)YdEc~z9A;RQ@M$#CYh}= zF!bt`^|v;O(|Xuf-Qqu3@_KEt!kXpsrL5%=`Ll}Uq6LDE{x=VLMaa z`7EDPjRgonuY1ym@Q9GmaB*hnZ#o3ei9EUR(~*Q6 zWcl)(-SM8&uXy3@XOAb{HL2fvLa()3jvBN_+ta%T`RqTCZ6|blxb*&5WlM5GEbjbR zr(T4oF=y|*Dbdw8z09IiResueFC8$dK9DTTSqsk_sb7op$-{cIdQ__q21y971PF!> zP7ESV$Us;#0F!*disUZ-W$@d(MZ4Sir>?53A&%Fdf%D8|`@h?j+qEj*!4%==J-+Ca zpYUTXL(;zMM#zZKo^Kb6Y>~Nxjwc7>Vn=ysj8##6|8+TBw)ZSY1u^%>LE`DD!zKA`TOdDpyn+Ex_ z*Io~?ej)1Y4m}IbAe|-gT_iP-$S!%hk)r!_lhY;wauCbgv@dT{kR^N7U*hTwAkc-G z(oi9-h_I?KOiUF-;uE3@s)&Mn?a&a}v$u_eRdOLs+j6KC7pIV~nRbo?9vS9L>pCBs z!_vY;T!*8k#I)`N;4hYO(R|e83UXCt`ne0AE}UB=-!mVADVp0Mwub#PpR;}@8cL3l zJbQ-G_Sc;LaKs{f4 zmgVN-|AQU_bN2WJ6!F%0GKW_G>q>iZp`@xVg?cj>xObr{%ANp-)C!53tjl(HGIevy z^yw_j+irLE(`%UA2J7j(P%o`W^;;V#fW6Y=XhB+@155_8uK>4n_#p<*d{YBZ7@I+i z_HaU|9B+D&mWdvadEx|AvcRt-80VK6?fFJJ-z^$Z)mg)&XHOLT%-6$QD1ND3&kvtL zgXCeKLDC*N*URuxY9l%bQwIlX2k(p->8z^NZYEq5znTi}uq~nw?5_cX>Cd3u*q{Mb zV21~|6kKs*&c~r-`Hmya9Rf1#tve_MFwSlQ$2`;4FX^M5TzCo;VYG|_Lfg;+UKlIy8lBp(IXCMdl~e5Ys*bU@X8!Z5&yIm=qB9EORA-N}JSBts`Rk9Q$m8IajAc z0(C$yGW&Ch97!ha@x`J8OEhprWg3~XWbv=;o}!PKQij;4+;^K}D=+#aMm8rX3o%Lr zzoaX-XbLIDx!IQ6VWJ-;tTdkVb!fD8%1jkKlb_@qN5`61`%zwR$7Pz{Kg5T?2Nz zAzp+jRVeXvy+-abtCJHyRthh-n zUyl<{lXc#fxCRoN)tn9zNm1iu@LgWpnV#Bn0^7b z4>jyF>Qhf$F2eKApPq;*7A-U_mY5ZbNOXD@REOT*^9G)uAN>ZVeYE&!RdyZts8Yk1 znTHOv_z5Xfv`8u^kivO!S43HMS9(eTm{+#SLDAwt+^eTE3BOG%f#Ba$wj&nb)Pmk% z#=yy_Wp;I$C+(i1n_T(B>*}P}xfkC&JfqCyyMwh4@c%Ki zOF**wFOAuZ-$q@eW>a9eH~TlD_>DJ0{dn}z$z^al0G;D+ zpg)+ntwL5MZ4pfkqt$c4PW%9<*?Ad+H2euKV`bI;bRl!!4@0%Rc=boDqEgDPy)#p- z2R1eN?ARjox9d?;h(%IC`zfBgAA}M-0Vxg=@-G zN#-|UdPNH#tyW7U`0p3a+%ya%6uK3wpMN#|09wO)v?pM4KF8C<8N--5K>v?A5mm4e z7eXyI+o%53|M9#tq=xZ}mYtpXd?Yg`g&{fKLO-3WUDvR({87(Gv;NJob!ol~vH$jD zhUnYS4($p=RDs^SgE0*1s{WOvr%mq^EWz$?j<^ zlYYBl1ztN*k=m;H#F(b4%)I*{PVEw63Bs_uF-Z-k4sBOkyIOty)kCzRop;t$-p!{X z;{ zqWGE=;!v+?EGs1d9l$r^D};>`+L!DMI0S=ay&AAHxu?&JS$b778-)tdw1L*jLRLwu zSW8|JgMT9O7%5BZ{QR%|vrEyzq_^v7swD;W6S8iA{3157T=FVGzNSZg%QEM1Ws@Ti}J1n`{Ft^)Q8|3 zK_IXV4_7(iYh??@<)xR^-Z$8_7s1B0_l^wGd`E^IgH0}4Uc|+AU8tx@7m!;VawHxXKv%yTL%J4 zQQOH1VZ!^447=8YT$nWYisUcCe1UEhmoP~LvkVDfs*&pmp*GQqfMJS7iia012!_Rp zrV2BKNuz^ezJj8l{w`9`6$T1W^xRygqZ>5B%p0Q$?oqknZ~BYZuF~2ymrmK;^nRd# z^~1K?JKW%qaRA(R*zj+pw+``l*EdX(miLWy--Go^IT61%5_1#XP6IXyP8MZ>7bkVw zwYsD`#vSBRO{3skRvo$$))m84+@X5SdhL4E>ad2rDK+2c)>wJy{TLOIHoBnTDi1hx zZy(sC=E?nAe)fsx&TI~f`j~5+=V5>Gk@1Xbj-FE63MRj|g7xCYvv6?}Y@RNr?2h>o zm&!2(Lk_2Tn#Ti#8v!9|BPK`*MR!W}Wpg4AM}U5HF`R1~aRDIX*sQm|4xL6BXjDvP!04}WyZ2R93yPFfq+>Z5=Nz#x(rk8XL}ij`3;ffQecE740Lx>I) z7w2$7Ol6RFa1rlsFlIg!xPYp`)hmLjYIuIBBd15ivOe$os}&~kR2e+1hBhzfR`3{G z&3ORZvhTwuhEAzBajkJu1X+FsJFc15Q=|P`)nzbJETG6R);;X-?PAFNtHKtp(LAKw!-7oK)WF63 z{avUlba(=j!k@r;=0`oA1o;hbhk?Y+!-^Al9vnVZrj)N?c}&A3O^k6yDCN%obOnUx z&I1uOn6I@OrZc{Fi<{HBFf2!wRw2}}Nij|~1Zy^{b7ONiNAL42kz+_WOQ=Ta4bi7% z63x0kVlt880aDMkHGE)6r9um?nS;pi?;kfsXr{`q`11#nQ!opd{s+iYv-dXY?5l~c z`yY5u09#@$E7j!vay5DH5b4Qrh_D`msh3Pp9S2Q`S%gU>MP#xhPSF`@!Qlgph~KfC z$Vl(Bkk~jB!6yDUc7I8Y-Fp#*6Ti&H#32{q2)c+SK1FVYozz3^C? zVHAJIu&1ZbKs+~VU4bsW0KmV8DL{t;`w%0)6;u7tkWiDk69sIDs#!u`8}G0rtI zVO7}$6WlTe6FMh?$#_wy!a=rx_Z;;1Ek)xhr|^RW&SQ#CT+E7xZ6TZg1T>&^b(bCM z!@8Dzp_L$8y8kjFL|!h87jkwH>Hs8950cEPfqKEF$qhXN zv+=6+22k59?;FV^QSa|6F@}j%BxR`h)9m}@Bbo87t~7P!P|O|d>X2%h3f-S|#%+7h zLdT%g)Xmdg(~B|KE!7;3f?b){Af}6xU;!>-Nz~*I)H%G$L_Sx3sNHEesn4U2#=Hsn zh|GCs&)}H%psg?kt~#>SnS>iYF|5OPhsn=vXE}^dw(k9(C!s`SfV!!TzNy8)x??qV z0M0#R%29(`95uMLZhFCm{WMw`Upv9|BP{_SsCFqxHR84Q!U5r~e$^J?{eDj9r~O~* z3<>J8nYbKN0uktbEYBtr@I4c8H7CT0l+!_U2i~n)0MZK7Uc2xiG4QJ5Mh+X7R+yen z|Mcno>t)unZ!PjObn$$&fRg6yZn@6p)Y|c|aPXjTKb)l3co}s}BobnF$>;>ijMj{2 zbf1P;BT?s5F*$aF6jweWJs_nl^NC$Px@>HG1wLj6kURbA1xyyu*QV#aexWtzCeP@k zotqCO0J(COvCUfnW!zm#uR9=<+z7G8 z^IJ{7j*Dlj8Hm&Q<0qYd;*UGQBqTOSy;j7|)2aldHKh2#C-?zp-=sQUSA!r&RiaBW0g3pEgEaiKVP-6Q%P8KiR2xeq$P0}~=(aRBud5-(4BN41XXj*rp z;O>orvbm|_6uVx#I``+~LDwMI_!G6}6Oa+4+8+e34V*`)o&Wmat6K+6 zK?g4*U9l zWFwEJb&@HI9ekWBkND=YjFDaH(h>h8_D%n#U}s$IpfX!v*OxHbIMgZU^aN+ zr`u444QOcPrt^meTx#Xn>Wr=~N~Rh4__2>kaht!ePU2zF$?O0y#f!9x@*U!y zDERs^x;*?ynYI5YCqw`y-QwS0{v!DwIDj*fxYn-jP|J$E04Mw1#>X+OS2qP+!|AJjAH;EWC+bYQbQ)N$Vt_>YV0wbibm`C`MeSf#&2lqrC&u? zUR@ns2M3zl#8qneS~`JHwzhqhmT9MF4^n->c~j8tn(-lti^9J2I>)98PQS zWZoLB+9>fe{jqXCpEJd|X_xkBm@@~IURfI9vf=@`2#z?j1>qh9hEJ*u8WX>hi0#R> zM{tCy!gu#&`R>FkVQ1f|hCg?~!k}$fKF_vCJ7)2P~rz@e7$T{fmJ?e@Oo$t*2d+MH4 zz<(gpedremlTN|p>J8^T&sQ%DuY^8`!Jl>OY|b#;42HiN+&EBpur=&7a9xi8C7$#hWTGXYiNFJPu7Q z6Hjt73QGyMk(6tDCf-U0*{^T#-%69@6G#`d$#7l7Buw6_@+5eX{t}UH^$%~&I;MX1 zE(J`pu}5mqRi4&$BT_^Ut6|J|%0A$wO~K|va%vPy@O*XpX{l4>39w@1a(Ajvf6XrU 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%0D`L#WjWqr?p#8hGTMKM$ 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̥B*rtezTOGi z{h$HJ%EE9VdiK}-r(muF zyfL!-&lXk5DKu=AoLnw)>?IBhBM9TH_P=+&r-Fm|oZBQYTvK>es#?}Ah64sV?L^)t zpl7PflonDkbAVo!CL)2eiMmGk8?oQ%?3vUWo&PXDiSOx^+2h0rsFzi~%aN{<5@HP6 zw5OR)sBKO+6TgV@d^4|$rxkFBC|NbaI z>{=3s1qiAxm6XrBiXGL%@zvd)U#hPh(0lCmqAZ$yH-`2mv#C~>+bqZ(U?0OSW@sx_$^5w9z_GZ{2?E&q6ZTNau?N)7sb`Sc` zVcJmbE*eX_!@2DC7FO8SdSn_^(CGFB??sez1*6pmblmrZ6i|rsSuLGyHd{Tc( zZHHIeJxY}b9+uHUk;W+Yd>Ls>yJ4ep?!bW|c_tMGqJf&w2?-13}< zcjeTwSc(aHr#{^5fP%Qu8N9B~0b1?z@wLeKr-8Z@$O-XF)Zn5e@oc>Gg0r3-q9Dld zOneIBdFC>&TxNlL(Peebz|+f8VD#f`&xZj((3WWGTG9Fb=6g0f#l#tNbFUQIp)pQa zq6yX*X+nuRP>g;+(|(Pi#@(mgtt#pUD|!7)?b?~+iG^c+GWPg4JQ`uXvP$~NY4N3?kYT(8E zFK8|pzRKTWYFHZsy8y?pee%DIE&-?UPd9{JOKtH4mPpvFH-$$gAaYD63qN%krB8`+ z3+fZ=X|l-V{#97$^c@3GgITp_LqA5%-�uSIrM+C1g*44;EWt-5WjB_1CiJh_K@o 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

    R+K-bjZ#^B#?fuR=yPaou8VgTd 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 M4+a0%|NVdb|8`_EH2?qr