From 21db5d33c2ed0f89c0799980d0521deeeda63c23 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 12 Jan 2024 00:22:08 +0100 Subject: [PATCH] updated the game picker code. --- libraries/ZWidget/CMakeLists.txt | 9 +- .../include/zwidget/core/resourcedata.h | 5 +- .../ZWidget/include/zwidget/core/widget.h | 16 +- .../widgets/checkboxlabel/checkboxlabel.h | 4 +- .../zwidget/widgets/lineedit/lineedit.h | 6 +- .../zwidget/widgets/listview/listview.h | 7 +- .../zwidget/widgets/pushbutton/pushbutton.h | 4 +- .../zwidget/widgets/scrollbar/scrollbar.h | 4 +- .../zwidget/widgets/tabwidget/tabwidget.h | 124 ++ .../zwidget/widgets/textedit/textedit.h | 6 +- .../zwidget/widgets/textlabel/textlabel.h | 1 + libraries/ZWidget/src/core/canvas.cpp | 128 +- libraries/ZWidget/src/core/image.cpp | 4 +- libraries/ZWidget/src/core/nanosvg/nanosvg.h | 4 +- libraries/ZWidget/src/core/pathfill.cpp | 31 +- .../ZWidget/src/core/schrift/LICENSE.txt | 16 - .../ZWidget/src/core/schrift/schrift.cpp | 1576 ----------------- libraries/ZWidget/src/core/schrift/schrift.h | 95 - libraries/ZWidget/src/core/truetypefont.cpp | 1201 +++++++++++++ libraries/ZWidget/src/core/truetypefont.h | 527 ++++++ libraries/ZWidget/src/core/widget.cpp | 82 +- .../widgets/checkboxlabel/checkboxlabel.cpp | 6 +- .../ZWidget/src/widgets/lineedit/lineedit.cpp | 15 +- .../ZWidget/src/widgets/listview/listview.cpp | 12 +- .../src/widgets/pushbutton/pushbutton.cpp | 6 +- .../src/widgets/scrollbar/scrollbar.cpp | 6 +- .../src/widgets/tabwidget/tabwidget.cpp | 358 ++++ .../ZWidget/src/widgets/textedit/textedit.cpp | 9 +- .../src/widgets/textlabel/textlabel.cpp | 6 + source/CMakeLists.txt | 4 + source/common/audio/sound/alext.h | 643 ------- source/common/engine/stringtable.cpp | 17 +- source/common/engine/stringtable.h | 3 +- .../common/filesystem/source/filesystem.cpp | 2 +- source/common/platform/posix/sdl/i_system.cpp | 1 - source/common/textures/textureid.h | 9 + source/common/widgets/errorwindow.cpp | 3 +- source/common/widgets/errorwindow.h | 2 +- source/common/widgets/widgetresourcedata.cpp | 57 +- source/core/gamecontrol.cpp | 14 +- source/launcher/launcherbanner.cpp | 34 + source/launcher/launcherbanner.h | 21 + source/launcher/launcherbuttonbar.cpp | 46 + source/launcher/launcherbuttonbar.h | 25 + source/launcher/launcherwindow.cpp | 268 +-- source/launcher/launcherwindow.h | 54 +- source/launcher/playgamepage.cpp | 100 ++ source/launcher/playgamepage.h | 32 + source/launcher/settingspage.cpp | 215 +++ source/launcher/settingspage.h | 47 + wadsrc/static/language.0 | 48 +- wadsrc/static/language.csv | 38 +- wadsrc/static/widgets/banner_1.png | Bin 0 -> 105619 bytes wadsrc/static/widgets/banner_3.png | Bin 0 -> 122157 bytes 54 files changed, 3132 insertions(+), 2819 deletions(-) create mode 100644 libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h delete mode 100644 libraries/ZWidget/src/core/schrift/LICENSE.txt delete mode 100644 libraries/ZWidget/src/core/schrift/schrift.cpp delete mode 100644 libraries/ZWidget/src/core/schrift/schrift.h create mode 100644 libraries/ZWidget/src/core/truetypefont.cpp create mode 100644 libraries/ZWidget/src/core/truetypefont.h create mode 100644 libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp delete mode 100644 source/common/audio/sound/alext.h create mode 100644 source/launcher/launcherbanner.cpp create mode 100644 source/launcher/launcherbanner.h create mode 100644 source/launcher/launcherbuttonbar.cpp create mode 100644 source/launcher/launcherbuttonbar.h create mode 100644 source/launcher/playgamepage.cpp create mode 100644 source/launcher/playgamepage.h create mode 100644 source/launcher/settingspage.cpp create mode 100644 source/launcher/settingspage.h create mode 100644 wadsrc/static/widgets/banner_1.png create mode 100644 wadsrc/static/widgets/banner_3.png diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 823b6ee88..dd836cd6d 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -10,8 +10,8 @@ set(ZWIDGET_SOURCES src/core/widget.cpp src/core/utf8reader.cpp src/core/pathfill.cpp - src/core/schrift/schrift.cpp - src/core/schrift/schrift.h + src/core/truetypefont.cpp + src/core/truetypefont.h src/core/picopng/picopng.cpp src/core/picopng/picopng.h src/core/nanosvg/nanosvg.cpp @@ -30,6 +30,7 @@ set(ZWIDGET_SOURCES src/widgets/pushbutton/pushbutton.cpp src/widgets/checkboxlabel/checkboxlabel.cpp src/widgets/listview/listview.cpp + src/widgets/tabwidget/tabwidget.cpp src/window/window.cpp ) @@ -58,6 +59,7 @@ set(ZWIDGET_INCLUDES include/zwidget/widgets/pushbutton/pushbutton.h include/zwidget/widgets/checkboxlabel/checkboxlabel.h include/zwidget/widgets/listview/listview.h + include/zwidget/widgets/tabwidget/tabwidget.h include/zwidget/window/window.h ) @@ -76,7 +78,6 @@ set(ZWIDGET_SDL2_SOURCES source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+") source_group("src\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/.+") -source_group("src\\core\\schrift" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/schrift/.+") source_group("src\\core\\picopng" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/picopng/.+") source_group("src\\core\\nanosvg" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/core/nanosvg/.+") source_group("src\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/.+") @@ -92,6 +93,7 @@ source_group("src\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURC source_group("src\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/pushbutton/.+") source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/checkboxlabel/.+") source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+") +source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+") source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+") source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+") source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+") @@ -108,6 +110,7 @@ source_group("include\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S source_group("include\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/pushbutton/.+") source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/checkboxlabel/.+") source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+") +source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/tabwidget/.+") source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+") source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+") source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+") diff --git a/libraries/ZWidget/include/zwidget/core/resourcedata.h b/libraries/ZWidget/include/zwidget/core/resourcedata.h index 5eaedcfae..f29ab261e 100644 --- a/libraries/ZWidget/include/zwidget/core/resourcedata.h +++ b/libraries/ZWidget/include/zwidget/core/resourcedata.h @@ -7,9 +7,8 @@ struct SingleFontData { std::vector fontdata; - std::vector> ranges; - int language = -1; // mainly useful if we start supporting Chinese so that we can use another font there than for Japanese. + std::string language; }; std::vector LoadWidgetFontData(const std::string& name); -std::vector LoadWidgetImageData(const std::string& name); +std::vector LoadWidgetData(const std::string& name); diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index 69862fdb4..34faa14e9 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -39,6 +39,10 @@ public: // Widget noncontent area void SetNoncontentSizes(double left, double top, double right, double bottom); + double GetNoncontentLeft() const { return Noncontent.Left; } + double GetNoncontentTop() const { return Noncontent.Top; } + double GetNoncontentRight() const { return Noncontent.Right; } + double GetNoncontentBottom() const { return Noncontent.Bottom; } // Widget frame box Rect GetFrameGeometry() const; @@ -68,6 +72,7 @@ public: bool HasFocus(); bool IsEnabled(); bool IsVisible(); + bool IsHidden(); void SetFocus(); void SetEnabled(bool value); @@ -86,7 +91,7 @@ public: void SetClipboardText(const std::string& text); Widget* Window(); - Canvas* GetCanvas(); + Canvas* GetCanvas() const; Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); } Widget* ChildAt(const Point& pos); @@ -109,11 +114,11 @@ public: protected: virtual void OnPaintFrame(Canvas* canvas) { } virtual void OnPaint(Canvas* canvas) { } + virtual bool OnMouseDown(const Point& pos, int key) { return false; } + virtual bool OnMouseDoubleclick(const Point& pos, int key) { return false; } + virtual bool OnMouseUp(const Point& pos, int key) { return false; } + virtual bool OnMouseWheel(const Point& pos, EInputKey key) { return false; } virtual void OnMouseMove(const Point& pos) { } - virtual void OnMouseDown(const Point& pos, int key) { } - virtual void OnMouseDoubleclick(const Point& pos, int key) { } - virtual void OnMouseUp(const Point& pos, int key) { } - virtual void OnMouseWheel(const Point& pos, EInputKey key) { } virtual void OnMouseLeave() { } virtual void OnRawMouseMove(int dx, int dy) { } virtual void OnKeyChar(std::string chars) { } @@ -176,6 +181,7 @@ private: Widget* FocusWidget = nullptr; Widget* CaptureWidget = nullptr; Widget* HoverWidget = nullptr; + bool HiddenFlag = false; StandardCursor CurrentCursor = StandardCursor::arrow; diff --git a/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h b/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h index 03d9089eb..c3f3fb465 100644 --- a/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h +++ b/libraries/ZWidget/include/zwidget/widgets/checkboxlabel/checkboxlabel.h @@ -21,8 +21,8 @@ public: protected: void OnPaint(Canvas* canvas) override; - void OnMouseDown(const Point& pos, int key) override; - void OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseUp(const Point& pos, int key) override; void OnMouseLeave() override; void OnKeyUp(EInputKey key) override; diff --git a/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h b/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h index c34ab1190..526d9eee9 100644 --- a/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h +++ b/libraries/ZWidget/include/zwidget/widgets/lineedit/lineedit.h @@ -72,9 +72,9 @@ protected: void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; void OnMouseMove(const Point& pos) override; - void OnMouseDown(const Point& pos, int key) override; - void OnMouseDoubleclick(const Point& pos, int key) override; - void OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseDoubleclick(const Point& pos, int key) override; + bool OnMouseUp(const Point& pos, int key) override; void OnKeyChar(std::string chars) override; void OnKeyDown(EInputKey key) override; void OnKeyUp(EInputKey key) override; diff --git a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h index c7caca8d4..2b150abc0 100644 --- a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h +++ b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h @@ -19,14 +19,15 @@ public: void Activate(); + std::function OnChanged; std::function OnActivated; protected: void OnPaint(Canvas* canvas) override; void OnPaintFrame(Canvas* canvas) override; - void OnMouseDown(const Point& pos, int key) override; - void OnMouseDoubleclick(const Point& pos, int key) override; - void OnMouseWheel(const Point& pos, EInputKey key) override; + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseDoubleclick(const Point& pos, int key) override; + bool OnMouseWheel(const Point& pos, EInputKey key) override; void OnKeyDown(EInputKey key) override; void OnGeometryChanged() override; void OnScrollbarScroll(); diff --git a/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h b/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h index 80d827d39..b9b6c9654 100644 --- a/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h +++ b/libraries/ZWidget/include/zwidget/widgets/pushbutton/pushbutton.h @@ -21,9 +21,9 @@ public: protected: void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseUp(const Point& pos, int key) override; void OnMouseMove(const Point& pos) override; - void OnMouseDown(const Point& pos, int key) override; - void OnMouseUp(const Point& pos, int key) override; void OnMouseLeave() override; void OnKeyDown(EInputKey key) override; void OnKeyUp(EInputKey key) override; diff --git a/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h b/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h index 38afda383..5ec95c841 100644 --- a/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h +++ b/libraries/ZWidget/include/zwidget/widgets/scrollbar/scrollbar.h @@ -47,9 +47,9 @@ public: std::function FuncScrollEnd; protected: + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseUp(const Point& pos, int key) override; void OnMouseMove(const Point& pos) override; - void OnMouseDown(const Point& pos, int key) override; - void OnMouseUp(const Point& pos, int key) override; void OnMouseLeave() override; void OnPaint(Canvas* canvas) override; void OnEnableChanged() override; diff --git a/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h b/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h new file mode 100644 index 000000000..242094024 --- /dev/null +++ b/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h @@ -0,0 +1,124 @@ + +#pragma once + +#include "../../core/widget.h" +#include +#include +#include + +class TabBar; +class TabBarTab; +class TabWidgetStack; +class TextLabel; +class ImageBox; +class Image; + +class TabWidget : public Widget +{ +public: + TabWidget(Widget* parent); + + int AddTab(Widget* page, const std::string& label); + int AddTab(Widget* page, const std::shared_ptr& icon, const std::string& label); + + void SetTabText(int index, const std::string& text); + void SetTabText(Widget* page, const std::string& text); + void SetTabIcon(int index, const std::shared_ptr& icon); + void SetTabIcon(Widget* page, const std::shared_ptr& icon); + + int GetCurrentIndex() const; + Widget* GetCurrentWidget() const; + + int GetPageIndex(Widget* pageWidget) const; + + void SetCurrentIndex(int pageIndex); + void SetCurrentWidget(Widget* pageWidget); + + std::function OnCurrentChanged; + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnGeometryChanged() override; + +private: + void OnBarCurrentChanged(); + + TabBar* Bar = nullptr; + TabWidgetStack* PageStack = nullptr; + std::vector Pages; +}; + +class TabBar : public Widget +{ +public: + TabBar(Widget* parent); + + int AddTab(const std::string& label); + int AddTab(const std::shared_ptr& icon, const std::string& label); + + void SetTabText(int index, const std::string& text); + void SetTabIcon(int index, const std::shared_ptr& icon); + + int GetCurrentIndex() const; + void SetCurrentIndex(int pageIndex); + + double GetPreferredHeight() const { return 30.0; } + + std::function OnCurrentChanged; + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnGeometryChanged() override; + +private: + void OnTabClicked(TabBarTab* tab); + int GetTabIndex(TabBarTab* tab); + + int CurrentIndex = -1; + std::vector Tabs; +}; + +class TabBarTab : public Widget +{ +public: + TabBarTab(Widget* parent); + + void SetText(const std::string& text); + void SetIcon(const std::shared_ptr& icon); + void SetCurrent(bool value); + + double GetPreferredWidth() const; + + std::function OnClick; + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnGeometryChanged() override; + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseUp(const Point& pos, int key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + +private: + bool IsCurrent = false; + + ImageBox* Icon = nullptr; + TextLabel* Label = nullptr; + bool hot = false; +}; + +class TabWidgetStack : public Widget +{ +public: + TabWidgetStack(Widget* parent); + + void SetCurrentWidget(Widget* widget); + Widget* GetCurrentWidget() const { return CurrentWidget; } + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnGeometryChanged() override; + +private: + Widget* CurrentWidget = nullptr; +}; diff --git a/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h b/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h index e2c6c760c..ed80eec36 100644 --- a/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h +++ b/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h @@ -56,9 +56,9 @@ protected: void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; void OnMouseMove(const Point& pos) override; - void OnMouseDown(const Point& pos, int key) override; - void OnMouseDoubleclick(const Point& pos, int key) override; - void OnMouseUp(const Point& pos, int key) override; + bool OnMouseDown(const Point& pos, int key) override; + bool OnMouseDoubleclick(const Point& pos, int key) override; + bool OnMouseUp(const Point& pos, int key) override; void OnKeyChar(std::string chars) override; void OnKeyDown(EInputKey key) override; void OnKeyUp(EInputKey key) override; diff --git a/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h b/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h index 32f250c6c..45515dfcb 100644 --- a/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h +++ b/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h @@ -21,6 +21,7 @@ public: void SetTextAlignment(TextLabelAlignment alignment); TextLabelAlignment GetTextAlignment() const; + double GetPreferredWidth() const; double GetPreferredHeight() const; protected: diff --git a/libraries/ZWidget/src/core/canvas.cpp b/libraries/ZWidget/src/core/canvas.cpp index 26f137ca8..76c7a16e7 100644 --- a/libraries/ZWidget/src/core/canvas.cpp +++ b/libraries/ZWidget/src/core/canvas.cpp @@ -5,8 +5,9 @@ #include "core/utf8reader.h" #include "core/resourcedata.h" #include "core/image.h" +#include "core/truetypefont.h" +#include "core/pathfill.h" #include "window/window.h" -#include "schrift/schrift.h" #include #include #include @@ -23,8 +24,12 @@ public: class CanvasGlyph { public: - SFT_Glyph id; - SFT_GMetrics metrics; + struct + { + double leftSideBearing = 0.0; + double yOffset = 0.0; + double advanceWidth = 0.0; + } metrics; double u = 0.0; double v = 0.0; @@ -38,68 +43,39 @@ class CanvasFont public: CanvasFont(const std::string& fontname, double height, std::vector& _data) : fontname(fontname), height(height) { - data = std::move(_data); - loadFont(data.data(), data.size()); - - try - { - if (sft_lmetrics(&sft, &textmetrics) < 0) - throw std::runtime_error("Could not get truetype font metrics"); - } - catch (...) - { - sft_freefont(sft.font); - throw; - } + auto tdata = std::make_shared(_data); + ttf = std::make_unique(tdata); + textmetrics = ttf->GetTextMetrics(height); } ~CanvasFont() { - sft_freefont(sft.font); - sft.font = nullptr; } CanvasGlyph* getGlyph(uint32_t utfchar) { - auto& glyph = glyphs[utfchar]; + uint32_t glyphIndex = ttf->GetGlyphIndex(utfchar); + if (glyphIndex == 0) return nullptr; + + auto& glyph = glyphs[glyphIndex]; if (glyph) return glyph.get(); glyph = std::make_unique(); - if (sft_lookup(&sft, utfchar, &glyph->id) < 0) - return glyph.get(); - - if (sft_gmetrics(&sft, glyph->id, &glyph->metrics) < 0) - return glyph.get(); - - glyph->metrics.advanceWidth /= 3.0; - glyph->metrics.leftSideBearing /= 3.0; - - if (glyph->metrics.minWidth <= 0 || glyph->metrics.minHeight <= 0) - return glyph.get(); - - int w = (glyph->metrics.minWidth + 3) & ~3; - int h = glyph->metrics.minHeight; + TrueTypeGlyph ttfglyph = ttf->LoadGlyph(glyphIndex, height); + // Create final subpixel version + int w = ttfglyph.width; + int h = ttfglyph.height; int destwidth = (w + 2) / 3; - auto texture = std::make_shared(); texture->Width = destwidth; texture->Height = h; texture->Data.resize(destwidth * h); + + uint8_t* grayscale = ttfglyph.grayscale.get(); uint32_t* dest = (uint32_t*)texture->Data.data(); - - std::unique_ptr grayscalebuffer(new uint8_t[w * h]); - uint8_t* grayscale = grayscalebuffer.get(); - - SFT_Image img = {}; - img.width = w; - img.height = h; - img.pixels = grayscale; - if (sft_render(&sft, glyph->id, img) < 0) - return glyph.get(); - for (int y = 0; y < h; y++) { uint8_t* sline = grayscale + y * w; @@ -130,26 +106,20 @@ public: glyph->uvheight = h; glyph->texture = std::move(texture); + glyph->metrics.advanceWidth = (ttfglyph.advanceWidth + 2) / 3; + glyph->metrics.leftSideBearing = (ttfglyph.leftSideBearing + 2) / 3; + glyph->metrics.yOffset = ttfglyph.yOffset; + return glyph.get(); } + std::unique_ptr ttf; + std::string fontname; double height = 0.0; - SFT_LMetrics textmetrics = {}; + TrueTypeTextMetrics textmetrics; std::unordered_map> glyphs; - -private: - void loadFont(const void* data, size_t size) - { - sft.xScale = height * 3; - sft.yScale = height; - sft.flags = SFT_DOWNWARD_Y; - sft.font = sft_loadmem(data, size); - } - - SFT sft = {}; - std::vector data; }; class CanvasFontGroup @@ -158,8 +128,7 @@ public: struct SingleFont { std::unique_ptr font; - std::vector> ranges; - int language; // mainly useful if we start supporting Chinese so that we can use another font there than for Japanese. + std::string language; }; CanvasFontGroup(const std::string& fontname, double height) : height(height) { @@ -168,40 +137,28 @@ public: for (size_t i = 0; i < fonts.size(); i++) { fonts[i].font = std::make_unique(fontname, height, fontdata[i].fontdata); - fonts[i].ranges = std::move(fontdata[i].ranges); fonts[i].language = fontdata[i].language; } } - CanvasGlyph* getGlyph(uint32_t utfchar) + CanvasGlyph* getGlyph(uint32_t utfchar, const char* lang = nullptr) { - if (utfchar >= 0x530 && utfchar < 0x590) + for (int i = 0; i < 2; i++) { - int a = 0; - } - for (auto& fd : fonts) - { - bool get = false; - if (fd.ranges.size() == 0) get = true; - else for (auto r : fd.ranges) + for (auto& fd : fonts) { - if (utfchar >= r.first && utfchar <= r.second) + if (i == 1 || lang == nullptr || *lang == 0 || fd.language.empty() || fd.language == lang) { - get = true; - break; + auto g = fd.font->getGlyph(utfchar); + if (g) return g; } } - if (get) - { - auto g = fd.font->getGlyph(utfchar); - if (g) return g; - } } return nullptr; } - SFT_LMetrics& GetTextMetrics() + TrueTypeTextMetrics& GetTextMetrics() { return fonts[0].font->textmetrics; } @@ -263,6 +220,8 @@ public: int getClipMaxX() const; int getClipMaxY() const; + void setLanguage(const char* lang) { language = lang; } + std::unique_ptr createTexture(int width, int height, const void* pixels, ImageFormat format = ImageFormat::B8G8R8A8); template @@ -282,6 +241,7 @@ public: std::vector pixels; std::unordered_map, std::unique_ptr> imageTextures; + std::string language; }; BitmapCanvas::BitmapCanvas(DisplayWindow* window) : window(window) @@ -289,7 +249,7 @@ BitmapCanvas::BitmapCanvas(DisplayWindow* window) : window(window) uiscale = window->GetDpiScale(); uint32_t white = 0xffffffff; whiteTexture = createTexture(1, 1, &white); - font = std::make_unique("NotoSans", 16.0 * uiscale); + font = std::make_unique("NotoSans", 13.0 * uiscale); } BitmapCanvas::~BitmapCanvas() @@ -449,8 +409,8 @@ void BitmapCanvas::drawText(const Point& pos, const Colorf& color, const std::st UTF8Reader reader(text.data(), text.size()); while (!reader.is_end()) { - CanvasGlyph* glyph = font->getGlyph(reader.character()); - if (!glyph->texture) + CanvasGlyph* glyph = font->getGlyph(reader.character(), language.c_str()); + if (!glyph || !glyph->texture) { glyph = font->getGlyph(32); } @@ -475,8 +435,8 @@ Rect BitmapCanvas::measureText(const std::string& text) UTF8Reader reader(text.data(), text.size()); while (!reader.is_end()) { - CanvasGlyph* glyph = font->getGlyph(reader.character()); - if (!glyph->texture) + CanvasGlyph* glyph = font->getGlyph(reader.character(), language.c_str()); + if (!glyph || !glyph->texture) { glyph = font->getGlyph(32); } diff --git a/libraries/ZWidget/src/core/image.cpp b/libraries/ZWidget/src/core/image.cpp index 91e201a47..3998d78e8 100644 --- a/libraries/ZWidget/src/core/image.cpp +++ b/libraries/ZWidget/src/core/image.cpp @@ -62,7 +62,7 @@ std::shared_ptr Image::LoadResource(const std::string& resourcename, doub if (extension == "png") { - auto filedata = LoadWidgetImageData(resourcename); + auto filedata = LoadWidgetData(resourcename); std::vector pixels; unsigned long width = 0, height = 0; @@ -74,7 +74,7 @@ std::shared_ptr Image::LoadResource(const std::string& resourcename, doub } else if (extension == "svg") { - auto filedata = LoadWidgetImageData(resourcename); + auto filedata = LoadWidgetData(resourcename); filedata.push_back(0); NSVGimage* svgimage = nsvgParse((char*)filedata.data(), "px", (float)(96.0 * dpiscale)); diff --git a/libraries/ZWidget/src/core/nanosvg/nanosvg.h b/libraries/ZWidget/src/core/nanosvg/nanosvg.h index 36830f05c..55f9bc5ea 100644 --- a/libraries/ZWidget/src/core/nanosvg/nanosvg.h +++ b/libraries/ZWidget/src/core/nanosvg/nanosvg.h @@ -193,7 +193,7 @@ void nsvgDelete(NSVGimage* image); #include #ifdef _MSC_VER -#pragma warning(push); +#pragma warning(push) #pragma warning(disable:4244) #endif @@ -3099,7 +3099,7 @@ void nsvgDelete(NSVGimage* image) } #ifdef _MSC_VER -#pragma warning(pop); +#pragma warning(pop) #endif #endif // NANOSVG_IMPLEMENTATION diff --git a/libraries/ZWidget/src/core/pathfill.cpp b/libraries/ZWidget/src/core/pathfill.cpp index fb67951e6..bac3dfb50 100644 --- a/libraries/ZWidget/src/core/pathfill.cpp +++ b/libraries/ZWidget/src/core/pathfill.cpp @@ -122,6 +122,12 @@ private: void PathFillDesc::Rasterize(uint8_t* dest, int width, int height, bool blend) { + if (!blend) + { + memset(dest, 0, width * height); + } + PathFillRasterizer rasterizer; + rasterizer.Rasterize(*this, dest, width, height); } ///////////////////////////////////////////////////////////////////////////// @@ -131,15 +137,15 @@ void PathFillRasterizer::Rasterize(const PathFillDesc& path, uint8_t* dest, int Clear(); // For simplicity of the code, ensure the mask is always a multiple of MaskBlockSize - int block_width = MaskBlockSize * ((dest_width + MaskBlockSize - 1) / MaskBlockSize); - int block_height = MaskBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize); + int block_width = ScanlineBlockSize * ((dest_width + MaskBlockSize - 1) / MaskBlockSize); + int block_height = ScanlineBlockSize * ((dest_height + MaskBlockSize - 1) / MaskBlockSize); if (width != block_width || height != block_height) { width = block_width; height = block_height; - scanlines.resize(block_height * AntialiasLevel); + scanlines.resize(block_height); first_scanline = scanlines.size(); last_scanline = 0; } @@ -209,7 +215,7 @@ void PathFillRasterizer::Fill(PathFillMode mode, uint8_t* dest, int dest_width, { for (int i = 0; i < count_y; i++) { - uint8_t* dline = dest + (dest_y + i) * dest_width; + uint8_t* dline = dest + dest_x + (dest_y + i) * dest_width; memset(dline, 255, count_x); } } @@ -218,7 +224,7 @@ void PathFillRasterizer::Fill(PathFillMode mode, uint8_t* dest, int dest_width, for (int i = 0; i < count_y; i++) { const uint8_t* sline = mask_blocks.MaskBufferData + i * MaskBlockSize; - uint8_t* dline = dest + (dest_y + i) * dest_width; + uint8_t* dline = dest + dest_x + (dest_y + i) * dest_width; for (int j = 0; j < count_x; j++) { dline[j] = std::min((int)dline[j] + (int)sline[j], 255); @@ -238,16 +244,11 @@ PathFillRasterizer::Extent PathFillRasterizer::FindExtent(const PathScanline* sc if (scanline->edges.empty()) continue; - if (scanline->edges[0].x < extent.left) - extent.left = scanline->edges[0].x; - - if (scanline->edges[scanline->edges.size() - 1].x > extent.right) - extent.right = scanline->edges[scanline->edges.size() - 1].x; + extent.left = std::min(extent.left, (int)scanline->edges.front().x); + extent.right = std::max(extent.right, (int)scanline->edges.back().x); } - if (extent.left < 0) - extent.left = 0; - if (extent.right > max_width) - extent.right = max_width; + extent.left = std::max(extent.left, 0); + extent.right = std::min(extent.right, max_width); return extent; } @@ -397,7 +398,7 @@ void PathFillRasterizer::Line(double x1, double y1) int end_y = static_cast(std::floor(std::max(y0, y1) - 0.5f)) + 1; start_y = std::max(start_y, 0); - end_y = std::min(end_y, height * AntialiasLevel); + end_y = std::min(end_y, height); double rcp_dy = 1.0 / dy; diff --git a/libraries/ZWidget/src/core/schrift/LICENSE.txt b/libraries/ZWidget/src/core/schrift/LICENSE.txt deleted file mode 100644 index 6bb186fa9..000000000 --- a/libraries/ZWidget/src/core/schrift/LICENSE.txt +++ /dev/null @@ -1,16 +0,0 @@ -ISC License - -© 2019-2022 Thomas Oltmann and contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - diff --git a/libraries/ZWidget/src/core/schrift/schrift.cpp b/libraries/ZWidget/src/core/schrift/schrift.cpp deleted file mode 100644 index 3b2594135..000000000 --- a/libraries/ZWidget/src/core/schrift/schrift.cpp +++ /dev/null @@ -1,1576 +0,0 @@ -/* This file is part of libschrift. - * - * © 2019-2022 Thomas Oltmann and contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -# define restrict __restrict -#endif - -#if defined(_WIN32) -# define WIN32_LEAN_AND_MEAN 1 -# include -#else -#ifndef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 1 -#endif -# include -# include -# include -# include -#endif - -#include "schrift.h" - -#define SCHRIFT_VERSION "0.10.2" - -#define FILE_MAGIC_ONE 0x00010000 -#define FILE_MAGIC_TWO 0x74727565 - -#define HORIZONTAL_KERNING 0x01 -#define MINIMUM_KERNING 0x02 -#define CROSS_STREAM_KERNING 0x04 -#define OVERRIDE_KERNING 0x08 - -#define POINT_IS_ON_CURVE 0x01 -#define X_CHANGE_IS_SMALL 0x02 -#define Y_CHANGE_IS_SMALL 0x04 -#define REPEAT_FLAG 0x08 -#define X_CHANGE_IS_ZERO 0x10 -#define X_CHANGE_IS_POSITIVE 0x10 -#define Y_CHANGE_IS_ZERO 0x20 -#define Y_CHANGE_IS_POSITIVE 0x20 - -#define OFFSETS_ARE_LARGE 0x001 -#define ACTUAL_XY_OFFSETS 0x002 -#define GOT_A_SINGLE_SCALE 0x008 -#define THERE_ARE_MORE_COMPONENTS 0x020 -#define GOT_AN_X_AND_Y_SCALE 0x040 -#define GOT_A_SCALE_MATRIX 0x080 - -/* macros */ -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define SIGN(x) (((x) > 0) - ((x) < 0)) -/* Allocate values on the stack if they are small enough, else spill to heap. */ -#define STACK_ALLOC(var, type, thresh, count) \ - type var##_stack_[thresh]; \ - var = (type*)( (count) <= (thresh) ? var##_stack_ : calloc(sizeof(type), count) ); -#define STACK_FREE(var) \ - if (var != var##_stack_) free(var); - -enum { SrcMapping, SrcUser }; - -/* structs */ -typedef struct Point Point; -typedef struct Line Line; -typedef struct Curve Curve; -typedef struct Cell Cell; -typedef struct Outline Outline; -typedef struct Raster Raster; - -struct Point { double x, y; }; -struct Line { uint_least16_t beg, end; }; -struct Curve { uint_least16_t beg, end, ctrl; }; -struct Cell { double area, cover; }; - -struct Outline -{ - Point *points; - Curve *curves; - Line *lines; - uint_least16_t numPoints; - uint_least16_t capPoints; - uint_least16_t numCurves; - uint_least16_t capCurves; - uint_least16_t numLines; - uint_least16_t capLines; -}; - -struct Raster -{ - Cell *cells; - int width; - int height; -}; - -struct SFT_Font -{ - const uint8_t *memory; - uint_fast32_t size; -#if defined(_WIN32) - HANDLE mapping; -#endif - int source; - - uint_least16_t unitsPerEm; - int_least16_t locaFormat; - uint_least16_t numLongHmtx; -}; - -/* function declarations */ -/* generic utility functions */ -static void *reallocarray2(void *optr, size_t nmemb, size_t size); -static inline int fast_floor(double x); -static inline int fast_ceil (double x); -/* file loading */ -static int map_file (SFT_Font *font, const char *filename); -static void unmap_file(SFT_Font *font); -static int init_font (SFT_Font *font); -/* simple mathematical operations */ -static Point midpoint(Point a, Point b); -static void transform_points(unsigned int numPts, Point *points, double trf[6]); -static void clip_points(unsigned int numPts, Point *points, int width, int height); -/* 'outline' data structure management */ -static int init_outline(Outline *outl); -static void free_outline(Outline *outl); -static int grow_points (Outline *outl); -static int grow_curves (Outline *outl); -static int grow_lines (Outline *outl); -/* TTF parsing utilities */ -static inline int is_safe_offset(SFT_Font *font, uint_fast32_t offset, uint_fast32_t margin); -static void *csearch(const void *key, const void *base, - size_t nmemb, size_t size, int (*compar)(const void *, const void *)); -static int cmpu16(const void *a, const void *b); -static int cmpu32(const void *a, const void *b); -static inline uint_least8_t getu8 (SFT_Font *font, uint_fast32_t offset); -static inline int_least8_t geti8 (SFT_Font *font, uint_fast32_t offset); -static inline uint_least16_t getu16(SFT_Font *font, uint_fast32_t offset); -static inline int_least16_t geti16(SFT_Font *font, uint_fast32_t offset); -static inline uint_least32_t getu32(SFT_Font *font, uint_fast32_t offset); -static int gettable(SFT_Font *font, const char* tag, uint_fast32_t *offset); -/* codepoint to glyph id translation */ -static int cmap_fmt4(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, uint_fast32_t *glyph); -static int cmap_fmt6(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, uint_fast32_t *glyph); -static int glyph_id(SFT_Font *font, SFT_UChar charCode, uint_fast32_t *glyph); -/* glyph metrics lookup */ -static int hor_metrics(SFT_Font *font, uint_fast32_t glyph, int *advanceWidth, int *leftSideBearing); -static int glyph_bbox(const SFT *sft, uint_fast32_t outline, int box[4]); -/* decoding outlines */ -static int outline_offset(SFT_Font *font, uint_fast32_t glyph, uint_fast32_t *offset); -static int simple_flags(SFT_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags); -static int simple_points(SFT_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points); -static int decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl); -static int simple_outline(SFT_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl); -static int compound_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl); -static int decode_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl); -/* tesselation */ -static int is_flat(Outline *outl, Curve curve); -static int tesselate_curve(Curve curve, Outline *outl); -static int tesselate_curves(Outline *outl); -/* silhouette rasterization */ -static void draw_line(Raster buf, Point origin, Point goal); -static void draw_lines(Outline *outl, Raster buf); -/* post-processing */ -static void post_process(Raster buf, uint8_t *image); -/* glyph rendering */ -static int render_outline(Outline *outl, double transform[6], SFT_Image image); - -/* function implementations */ - -const char * -sft_version(void) -{ - return SCHRIFT_VERSION; -} - -/* Loads a font from a user-supplied memory range. */ -SFT_Font * -sft_loadmem(const void *mem, size_t size) -{ - SFT_Font *font; - if (size > UINT32_MAX) { - return NULL; - } - if (!(font = (SFT_Font*)calloc(1, sizeof *font))) { - return NULL; - } - font->memory = (const uint8_t*)mem; - font->size = (uint_fast32_t) size; - font->source = SrcUser; - if (init_font(font) < 0) { - sft_freefont(font); - return NULL; - } - return font; -} - -/* Loads a font from the file system. To do so, it has to map the entire font into memory. */ -SFT_Font * -sft_loadfile(char const *filename) -{ - SFT_Font *font; - if (!(font = (SFT_Font*)calloc(1, sizeof *font))) { - return NULL; - } - if (map_file(font, filename) < 0) { - free(font); - return NULL; - } - if (init_font(font) < 0) { - sft_freefont(font); - return NULL; - } - return font; -} - -void -sft_freefont(SFT_Font *font) -{ - if (!font) return; - /* Only unmap if we mapped it ourselves. */ - if (font->source == SrcMapping) - unmap_file(font); - free(font); -} - -int -sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics) -{ - double factor; - uint_fast32_t hhea; - memset(metrics, 0, sizeof *metrics); - if (gettable(sft->font, "hhea", &hhea) < 0) - return -1; - if (!is_safe_offset(sft->font, hhea, 36)) - return -1; - factor = sft->yScale / sft->font->unitsPerEm; - metrics->ascender = geti16(sft->font, hhea + 4) * factor; - metrics->descender = geti16(sft->font, hhea + 6) * factor; - metrics->lineGap = geti16(sft->font, hhea + 8) * factor; - return 0; -} - -int -sft_lookup(const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph) -{ - return glyph_id(sft->font, codepoint, glyph); -} - -int -sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics) -{ - int adv, lsb; - double xScale = sft->xScale / sft->font->unitsPerEm; - uint_fast32_t outline; - int bbox[4]; - - memset(metrics, 0, sizeof *metrics); - - if (hor_metrics(sft->font, glyph, &adv, &lsb) < 0) - return -1; - metrics->advanceWidth = adv * xScale; - metrics->leftSideBearing = lsb * xScale + sft->xOffset; - - if (outline_offset(sft->font, glyph, &outline) < 0) - return -1; - if (!outline) - return 0; - if (glyph_bbox(sft, outline, bbox) < 0) - return -1; - metrics->minWidth = bbox[2] - bbox[0] + 1; - metrics->minHeight = bbox[3] - bbox[1] + 1; - metrics->yOffset = sft->flags & SFT_DOWNWARD_Y ? -bbox[3] : bbox[1]; - - return 0; -} - -int -sft_kerning(const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph, - SFT_Kerning *kerning) -{ - void *match; - uint_fast32_t offset; - unsigned int numTables, numPairs, length, format, flags; - int value; - uint8_t key[4]; - - memset(kerning, 0, sizeof *kerning); - - if (gettable(sft->font, "kern", &offset) < 0) - return 0; - - /* Read kern table header. */ - if (!is_safe_offset(sft->font, offset, 4)) - return -1; - if (getu16(sft->font, offset) != 0) - return 0; - numTables = getu16(sft->font, offset + 2); - offset += 4; - - while (numTables > 0) { - /* Read subtable header. */ - if (!is_safe_offset(sft->font, offset, 6)) - return -1; - length = getu16(sft->font, offset + 2); - format = getu8 (sft->font, offset + 4); - flags = getu8 (sft->font, offset + 5); - offset += 6; - - if (format == 0 && (flags & HORIZONTAL_KERNING) && !(flags & MINIMUM_KERNING)) { - /* Read format 0 header. */ - if (!is_safe_offset(sft->font, offset, 8)) - return -1; - numPairs = getu16(sft->font, offset); - offset += 8; - /* Look up character code pair via binary search. */ - key[0] = (leftGlyph >> 8) & 0xFF; - key[1] = leftGlyph & 0xFF; - key[2] = (rightGlyph >> 8) & 0xFF; - key[3] = rightGlyph & 0xFF; - if ((match = bsearch(key, sft->font->memory + offset, - numPairs, 6, cmpu32)) != NULL) { - - value = geti16(sft->font, (uint_fast32_t) ((uint8_t *) match - sft->font->memory + 4)); - if (flags & CROSS_STREAM_KERNING) { - kerning->yShift += value; - } else { - kerning->xShift += value; - } - } - - } - - offset += length; - --numTables; - } - - kerning->xShift = kerning->xShift / sft->font->unitsPerEm * sft->xScale; - kerning->yShift = kerning->yShift / sft->font->unitsPerEm * sft->yScale; - - return 0; -} - -int -sft_render(const SFT *sft, SFT_Glyph glyph, SFT_Image image) -{ - uint_fast32_t outline; - double transform[6]; - int bbox[4]; - Outline outl; - - if (outline_offset(sft->font, glyph, &outline) < 0) - return -1; - if (!outline) - return 0; - if (glyph_bbox(sft, outline, bbox) < 0) - return -1; - /* Set up the transformation matrix such that - * the transformed bounding boxes min corner lines - * up with the (0, 0) point. */ - transform[0] = sft->xScale / sft->font->unitsPerEm; - transform[1] = 0.0; - transform[2] = 0.0; - transform[4] = sft->xOffset - bbox[0]; - if (sft->flags & SFT_DOWNWARD_Y) { - transform[3] = -sft->yScale / sft->font->unitsPerEm; - transform[5] = bbox[3] - sft->yOffset; - } else { - transform[3] = +sft->yScale / sft->font->unitsPerEm; - transform[5] = sft->yOffset - bbox[1]; - } - - memset(&outl, 0, sizeof outl); - if (init_outline(&outl) < 0) - goto failure; - - if (decode_outline(sft->font, outline, 0, &outl) < 0) - goto failure; - if (render_outline(&outl, transform, image) < 0) - goto failure; - - free_outline(&outl); - return 0; - -failure: - free_outline(&outl); - return -1; -} - -/* This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX - * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ -#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) - -/* OpenBSD's reallocarray() standard libary function. - * A wrapper for realloc() that takes two size args like calloc(). - * Useful because it eliminates common integer overflow bugs. */ -static void * -reallocarray2(void *optr, size_t nmemb, size_t size) -{ - if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && - nmemb > 0 && SIZE_MAX / nmemb < size) { - errno = ENOMEM; - return NULL; - } - return realloc(optr, size * nmemb); -} - -/* TODO maybe we should use long here instead of int. */ -static inline int -fast_floor(double x) -{ - int i = (int) x; - return i - (i > x); -} - -static inline int -fast_ceil(double x) -{ - int i = (int) x; - return i + (i < x); -} - -#if defined(_WIN32) - -static int -map_file(SFT_Font *font, const char *filename) -{ - HANDLE file; - DWORD high, low; - - font->mapping = NULL; - font->memory = NULL; - - file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (file == INVALID_HANDLE_VALUE) { - return -1; - } - - low = GetFileSize(file, &high); - if (low == INVALID_FILE_SIZE) { - CloseHandle(file); - return -1; - } - - font->size = (size_t)high << (8 * sizeof(DWORD)) | low; - - font->mapping = CreateFileMapping(file, NULL, PAGE_READONLY, high, low, NULL); - if (!font->mapping) { - CloseHandle(file); - return -1; - } - - CloseHandle(file); - - font->memory = (const uint8_t*)MapViewOfFile(font->mapping, FILE_MAP_READ, 0, 0, 0); - if (!font->memory) { - CloseHandle(font->mapping); - font->mapping = NULL; - return -1; - } - - return 0; -} - -static void -unmap_file(SFT_Font *font) -{ - if (font->memory) { - UnmapViewOfFile(font->memory); - font->memory = NULL; - } - if (font->mapping) { - CloseHandle(font->mapping); - font->mapping = NULL; - } -} - -#else - -static int -map_file(SFT_Font *font, const char *filename) -{ - struct stat info; - int fd; - font->memory = (const uint8_t*)MAP_FAILED; - font->size = 0; - font->source = SrcMapping; - if ((fd = open(filename, O_RDONLY)) < 0) { - return -1; - } - if (fstat(fd, &info) < 0) { - close(fd); - return -1; - } - /* FIXME do some basic validation on info.st_size maybe - it is signed for example, so it *could* be negative .. */ - font->memory = (const uint8_t*)mmap(NULL, (size_t) info.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - font->size = (uint_fast32_t) info.st_size; - close(fd); - return font->memory == MAP_FAILED ? -1 : 0; -} - -static void -unmap_file(SFT_Font *font) -{ - assert(font->memory != MAP_FAILED); - munmap((void *) font->memory, font->size); -} - -#endif - -static int -init_font(SFT_Font *font) -{ - uint_fast32_t scalerType, head, hhea; - - if (!is_safe_offset(font, 0, 12)) - return -1; - /* Check for a compatible scalerType (magic number). */ - scalerType = getu32(font, 0); - if (scalerType != FILE_MAGIC_ONE && scalerType != FILE_MAGIC_TWO) - return -1; - - if (gettable(font, "head", &head) < 0) - return -1; - if (!is_safe_offset(font, head, 54)) - return -1; - font->unitsPerEm = getu16(font, head + 18); - font->locaFormat = geti16(font, head + 50); - - if (gettable(font, "hhea", &hhea) < 0) - return -1; - if (!is_safe_offset(font, hhea, 36)) - return -1; - font->numLongHmtx = getu16(font, hhea + 34); - - return 0; -} - -static Point -midpoint(Point a, Point b) -{ - return { - 0.5 * (a.x + b.x), - 0.5 * (a.y + b.y) - }; -} - -/* Applies an affine linear transformation matrix to a set of points. */ -static void -transform_points(unsigned int numPts, Point *points, double trf[6]) -{ - Point pt; - unsigned int i; - for (i = 0; i < numPts; ++i) { - pt = points[i]; - points[i] = { - pt.x * trf[0] + pt.y * trf[2] + trf[4], - pt.x * trf[1] + pt.y * trf[3] + trf[5] - }; - } -} - -static void -clip_points(unsigned int numPts, Point *points, int width, int height) -{ - Point pt; - unsigned int i; - - for (i = 0; i < numPts; ++i) { - pt = points[i]; - - if (pt.x < 0.0) { - points[i].x = 0.0; - } - if (pt.x >= width) { - points[i].x = nextafter(width, 0.0); - } - if (pt.y < 0.0) { - points[i].y = 0.0; - } - if (pt.y >= height) { - points[i].y = nextafter(height, 0.0); - } - } -} - -static int -init_outline(Outline *outl) -{ - /* TODO Smaller initial allocations */ - outl->numPoints = 0; - outl->capPoints = 64; - if (!(outl->points = (Point*)malloc(outl->capPoints * sizeof *outl->points))) - return -1; - outl->numCurves = 0; - outl->capCurves = 64; - if (!(outl->curves = (Curve*)malloc(outl->capCurves * sizeof *outl->curves))) - return -1; - outl->numLines = 0; - outl->capLines = 64; - if (!(outl->lines = (Line*)malloc(outl->capLines * sizeof *outl->lines))) - return -1; - return 0; -} - -static void -free_outline(Outline *outl) -{ - free(outl->points); - free(outl->curves); - free(outl->lines); -} - -static int -grow_points(Outline *outl) -{ - void *mem; - uint_fast16_t cap; - assert(outl->capPoints); - /* Since we use uint_fast16_t for capacities, we have to be extra careful not to trigger integer overflow. */ - if (outl->capPoints > UINT16_MAX / 2) - return -1; - cap = (uint_fast16_t) (2U * outl->capPoints); - if (!(mem = reallocarray2(outl->points, cap, sizeof *outl->points))) - return -1; - outl->capPoints = (uint_least16_t) cap; - outl->points = (Point*)mem; - return 0; -} - -static int -grow_curves(Outline *outl) -{ - void *mem; - uint_fast16_t cap; - assert(outl->capCurves); - if (outl->capCurves > UINT16_MAX / 2) - return -1; - cap = (uint_fast16_t) (2U * outl->capCurves); - if (!(mem = reallocarray2(outl->curves, cap, sizeof *outl->curves))) - return -1; - outl->capCurves = (uint_least16_t) cap; - outl->curves = (Curve*)mem; - return 0; -} - -static int -grow_lines(Outline *outl) -{ - void *mem; - uint_fast16_t cap; - assert(outl->capLines); - if (outl->capLines > UINT16_MAX / 2) - return -1; - cap = (uint_fast16_t) (2U * outl->capLines); - if (!(mem = reallocarray2(outl->lines, cap, sizeof *outl->lines))) - return -1; - outl->capLines = (uint_least16_t) cap; - outl->lines = (Line*)mem; - return 0; -} - -static inline int -is_safe_offset(SFT_Font *font, uint_fast32_t offset, uint_fast32_t margin) -{ - if (offset > font->size) return 0; - if (font->size - offset < margin) return 0; - return 1; -} - -/* Like bsearch(), but returns the next highest element if key could not be found. */ -static void * -csearch(const void *key, const void *base, - size_t nmemb, size_t size, - int (*compar)(const void *, const void *)) -{ - const uint8_t *bytes = (const uint8_t*)base, *sample; - size_t low = 0, high = nmemb - 1, mid; - if (!nmemb) return NULL; - while (low != high) { - mid = low + (high - low) / 2; - sample = bytes + mid * size; - if (compar(key, sample) > 0) { - low = mid + 1; - } else { - high = mid; - } - } - return (uint8_t *) bytes + low * size; -} - -/* Used as a comparison function for [bc]search(). */ -static int -cmpu16(const void *a, const void *b) -{ - return memcmp(a, b, 2); -} - -/* Used as a comparison function for [bc]search(). */ -static int -cmpu32(const void *a, const void *b) -{ - return memcmp(a, b, 4); -} - -static inline uint_least8_t -getu8(SFT_Font *font, uint_fast32_t offset) -{ - assert(offset + 1 <= font->size); - return *(font->memory + offset); -} - -static inline int_least8_t -geti8(SFT_Font *font, uint_fast32_t offset) -{ - return (int_least8_t) getu8(font, offset); -} - -static inline uint_least16_t -getu16(SFT_Font *font, uint_fast32_t offset) -{ - assert(offset + 2 <= font->size); - const uint8_t *base = font->memory + offset; - uint_least16_t b1 = base[0], b0 = base[1]; - return (uint_least16_t) (b1 << 8 | b0); -} - -static inline int16_t -geti16(SFT_Font *font, uint_fast32_t offset) -{ - return (int_least16_t) getu16(font, offset); -} - -static inline uint32_t -getu32(SFT_Font *font, uint_fast32_t offset) -{ - assert(offset + 4 <= font->size); - const uint8_t *base = font->memory + offset; - uint_least32_t b3 = base[0], b2 = base[1], b1 = base[2], b0 = base[3]; - return (uint_least32_t) (b3 << 24 | b2 << 16 | b1 << 8 | b0); -} - -static int -gettable(SFT_Font *font, const char* tag, uint_fast32_t *offset) -{ - void *match; - unsigned int numTables; - /* No need to bounds-check access to the first 12 bytes - this gets already checked by init_font(). */ - numTables = getu16(font, 4); - if (!is_safe_offset(font, 12, (uint_fast32_t) numTables * 16)) - return -1; - if (!(match = bsearch(tag, font->memory + 12, numTables, 16, cmpu32))) - return -1; - *offset = getu32(font, (uint_fast32_t) ((uint8_t *) match - font->memory + 8)); - return 0; -} - -static int -cmap_fmt4(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph) -{ - const uint8_t *segPtr; - uint_fast32_t segIdxX2; - uint_fast32_t endCodes, startCodes, idDeltas, idRangeOffsets, idOffset; - uint_fast16_t segCountX2, idRangeOffset, startCode, shortCode, idDelta, id; - uint8_t key[2] = { (uint8_t) (charCode >> 8), (uint8_t) charCode }; - /* cmap format 4 only supports the Unicode BMP. */ - if (charCode > 0xFFFF) { - *glyph = 0; - return 0; - } - shortCode = (uint_fast16_t) charCode; - if (!is_safe_offset(font, table, 8)) - return -1; - segCountX2 = getu16(font, table); - if ((segCountX2 & 1) || !segCountX2) - return -1; - /* Find starting positions of the relevant arrays. */ - endCodes = table + 8; - startCodes = endCodes + segCountX2 + 2; - idDeltas = startCodes + segCountX2; - idRangeOffsets = idDeltas + segCountX2; - if (!is_safe_offset(font, idRangeOffsets, segCountX2)) - return -1; - /* Find the segment that contains shortCode by binary searching over - * the highest codes in the segments. */ - segPtr = (const uint8_t*)csearch(key, font->memory + endCodes, segCountX2 / 2, 2, cmpu16); - segIdxX2 = (uint_fast32_t) (segPtr - (font->memory + endCodes)); - /* Look up segment info from the arrays & short circuit if the spec requires. */ - if ((startCode = getu16(font, startCodes + segIdxX2)) > shortCode) - return 0; - idDelta = getu16(font, idDeltas + segIdxX2); - if (!(idRangeOffset = getu16(font, idRangeOffsets + segIdxX2))) { - /* Intentional integer under- and overflow. */ - *glyph = (shortCode + idDelta) & 0xFFFF; - return 0; - } - /* Calculate offset into glyph array and determine ultimate value. */ - idOffset = idRangeOffsets + segIdxX2 + idRangeOffset + 2U * (unsigned int) (shortCode - startCode); - if (!is_safe_offset(font, idOffset, 2)) - return -1; - id = getu16(font, idOffset); - /* Intentional integer under- and overflow. */ - *glyph = id ? (id + idDelta) & 0xFFFF : 0; - return 0; -} - -static int -cmap_fmt6(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph) -{ - unsigned int firstCode, entryCount; - /* cmap format 6 only supports the Unicode BMP. */ - if (charCode > 0xFFFF) { - *glyph = 0; - return 0; - } - if (!is_safe_offset(font, table, 4)) - return -1; - firstCode = getu16(font, table); - entryCount = getu16(font, table + 2); - if (!is_safe_offset(font, table, 4 + 2 * entryCount)) - return -1; - if (charCode < firstCode) - return -1; - charCode -= firstCode; - if (!(charCode < entryCount)) - return -1; - *glyph = getu16(font, table + 4 + 2 * charCode); - return 0; -} - -static int -cmap_fmt12_13(SFT_Font *font, uint_fast32_t table, SFT_UChar charCode, SFT_Glyph *glyph, int which) -{ - uint32_t len, numEntries; - uint_fast32_t i; - - *glyph = 0; - - /* check that the entire header is present */ - if (!is_safe_offset(font, table, 16)) - return -1; - - len = getu32(font, table + 4); - - /* A minimal header is 16 bytes */ - if (len < 16) - return -1; - - if (!is_safe_offset(font, table, len)) - return -1; - - numEntries = getu32(font, table + 12); - - for (i = 0; i < numEntries; ++i) { - uint32_t firstCode, lastCode, glyphOffset; - firstCode = getu32(font, table + (i * 12) + 16); - lastCode = getu32(font, table + (i * 12) + 16 + 4); - if (charCode < firstCode || charCode > lastCode) - continue; - glyphOffset = getu32(font, table + (i * 12) + 16 + 8); - if (which == 12) - *glyph = (charCode-firstCode) + glyphOffset; - else - *glyph = glyphOffset; - return 0; - } - - return 0; -} - -/* Maps Unicode code points to glyph indices. */ -static int -glyph_id(SFT_Font *font, SFT_UChar charCode, SFT_Glyph *glyph) -{ - uint_fast32_t cmap, entry, table; - unsigned int idx, numEntries; - int type, format; - - *glyph = 0; - - if (gettable(font, "cmap", &cmap) < 0) - return -1; - - if (!is_safe_offset(font, cmap, 4)) - return -1; - numEntries = getu16(font, cmap + 2); - - if (!is_safe_offset(font, cmap, 4 + numEntries * 8)) - return -1; - - /* First look for a 'full repertoire'/non-BMP map. */ - for (idx = 0; idx < numEntries; ++idx) { - entry = cmap + 4 + idx * 8; - type = getu16(font, entry) * 0100 + getu16(font, entry + 2); - /* Complete unicode map */ - if (type == 0004 || type == 0312) { - table = cmap + getu32(font, entry + 4); - if (!is_safe_offset(font, table, 8)) - return -1; - /* Dispatch based on cmap format. */ - format = getu16(font, table); - switch (format) { - case 12: - return cmap_fmt12_13(font, table, charCode, glyph, 12); - default: - return -1; - } - } - } - - /* If no 'full repertoire' cmap was found, try looking for a BMP map. */ - for (idx = 0; idx < numEntries; ++idx) { - entry = cmap + 4 + idx * 8; - type = getu16(font, entry) * 0100 + getu16(font, entry + 2); - /* Unicode BMP */ - if (type == 0003 || type == 0301) { - table = cmap + getu32(font, entry + 4); - if (!is_safe_offset(font, table, 6)) - return -1; - /* Dispatch based on cmap format. */ - switch (getu16(font, table)) { - case 4: - return cmap_fmt4(font, table + 6, charCode, glyph); - case 6: - return cmap_fmt6(font, table + 6, charCode, glyph); - default: - return -1; - } - } - } - - return -1; -} - -static int -hor_metrics(SFT_Font *font, SFT_Glyph glyph, int *advanceWidth, int *leftSideBearing) -{ - uint_fast32_t hmtx, offset, boundary; - if (gettable(font, "hmtx", &hmtx) < 0) - return -1; - if (glyph < font->numLongHmtx) { - /* glyph is inside long metrics segment. */ - offset = hmtx + 4 * glyph; - if (!is_safe_offset(font, offset, 4)) - return -1; - *advanceWidth = getu16(font, offset); - *leftSideBearing = geti16(font, offset + 2); - return 0; - } else { - /* glyph is inside short metrics segment. */ - boundary = hmtx + 4U * (uint_fast32_t) font->numLongHmtx; - if (boundary < 4) - return -1; - - offset = boundary - 4; - if (!is_safe_offset(font, offset, 4)) - return -1; - *advanceWidth = getu16(font, offset); - - offset = boundary + 2 * (glyph - font->numLongHmtx); - if (!is_safe_offset(font, offset, 2)) - return -1; - *leftSideBearing = geti16(font, offset); - return 0; - } -} - -static int -glyph_bbox(const SFT *sft, uint_fast32_t outline, int box[4]) -{ - double xScale, yScale; - /* Read the bounding box from the font file verbatim. */ - if (!is_safe_offset(sft->font, outline, 10)) - return -1; - box[0] = geti16(sft->font, outline + 2); - box[1] = geti16(sft->font, outline + 4); - box[2] = geti16(sft->font, outline + 6); - box[3] = geti16(sft->font, outline + 8); - if (box[2] <= box[0] || box[3] <= box[1]) - return -1; - /* Transform the bounding box into SFT coordinate space. */ - xScale = sft->xScale / sft->font->unitsPerEm; - yScale = sft->yScale / sft->font->unitsPerEm; - box[0] = (int) floor(box[0] * xScale + sft->xOffset); - box[1] = (int) floor(box[1] * yScale + sft->yOffset); - box[2] = (int) ceil (box[2] * xScale + sft->xOffset); - box[3] = (int) ceil (box[3] * yScale + sft->yOffset); - return 0; -} - -/* Returns the offset into the font that the glyph's outline is stored at. */ -static int -outline_offset(SFT_Font *font, SFT_Glyph glyph, uint_fast32_t *offset) -{ - uint_fast32_t loca, glyf; - uint_fast32_t base, this_, next; - - if (gettable(font, "loca", &loca) < 0) - return -1; - if (gettable(font, "glyf", &glyf) < 0) - return -1; - - if (font->locaFormat == 0) { - base = loca + 2 * glyph; - - if (!is_safe_offset(font, base, 4)) - return -1; - - this_ = 2U * (uint_fast32_t) getu16(font, base); - next = 2U * (uint_fast32_t) getu16(font, base + 2); - } else { - base = loca + 4 * glyph; - - if (!is_safe_offset(font, base, 8)) - return -1; - - this_ = getu32(font, base); - next = getu32(font, base + 4); - } - - *offset = this_ == next ? 0 : glyf + this_; - return 0; -} - -/* For a 'simple' outline, determines each point of the outline with a set of flags. */ -static int -simple_flags(SFT_Font *font, uint_fast32_t *offset, uint_fast16_t numPts, uint8_t *flags) -{ - uint_fast32_t off = *offset; - uint_fast16_t i; - uint8_t value = 0, repeat = 0; - for (i = 0; i < numPts; ++i) { - if (repeat) { - --repeat; - } else { - if (!is_safe_offset(font, off, 1)) - return -1; - value = getu8(font, off++); - if (value & REPEAT_FLAG) { - if (!is_safe_offset(font, off, 1)) - return -1; - repeat = getu8(font, off++); - } - } - flags[i] = value; - } - *offset = off; - return 0; -} - -/* For a 'simple' outline, decodes both X and Y coordinates for each point of the outline. */ -static int -simple_points(SFT_Font *font, uint_fast32_t offset, uint_fast16_t numPts, uint8_t *flags, Point *points) -{ - long accum, value, bit; - uint_fast16_t i; - - accum = 0L; - for (i = 0; i < numPts; ++i) { - if (flags[i] & X_CHANGE_IS_SMALL) { - if (!is_safe_offset(font, offset, 1)) - return -1; - value = (long) getu8(font, offset++); - bit = !!(flags[i] & X_CHANGE_IS_POSITIVE); - accum -= (value ^ -bit) + bit; - } else if (!(flags[i] & X_CHANGE_IS_ZERO)) { - if (!is_safe_offset(font, offset, 2)) - return -1; - accum += geti16(font, offset); - offset += 2; - } - points[i].x = (double) accum; - } - - accum = 0L; - for (i = 0; i < numPts; ++i) { - if (flags[i] & Y_CHANGE_IS_SMALL) { - if (!is_safe_offset(font, offset, 1)) - return -1; - value = (long) getu8(font, offset++); - bit = !!(flags[i] & Y_CHANGE_IS_POSITIVE); - accum -= (value ^ -bit) + bit; - } else if (!(flags[i] & Y_CHANGE_IS_ZERO)) { - if (!is_safe_offset(font, offset, 2)) - return -1; - accum += geti16(font, offset); - offset += 2; - } - points[i].y = (double) accum; - } - - return 0; -} - -static int -decode_contour(uint8_t *flags, uint_fast16_t basePoint, uint_fast16_t count, Outline *outl) -{ - uint_fast16_t i; - uint_least16_t looseEnd, beg, ctrl, center, cur; - unsigned int gotCtrl; - - /* Skip contours with less than two points, since the following algorithm can't handle them and - * they should appear invisible either way (because they don't have any area). */ - if (count < 2) return 0; - - assert(basePoint <= UINT16_MAX - count); - - if (flags[0] & POINT_IS_ON_CURVE) { - looseEnd = (uint_least16_t) basePoint++; - ++flags; - --count; - } else if (flags[count - 1] & POINT_IS_ON_CURVE) { - looseEnd = (uint_least16_t) (basePoint + --count); - } else { - if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0) - return -1; - - looseEnd = outl->numPoints; - outl->points[outl->numPoints++] = midpoint( - outl->points[basePoint], - outl->points[basePoint + count - 1]); - } - beg = looseEnd; - gotCtrl = 0; - for (i = 0; i < count; ++i) { - /* cur can't overflow because we ensure that basePoint + count < 0xFFFF before calling decode_contour(). */ - cur = (uint_least16_t) (basePoint + i); - /* NOTE clang-analyzer will often flag this and another piece of code because it thinks that flags and - * outl->points + basePoint don't always get properly initialized -- even when you explicitly loop over both - * and set every element to zero (but not when you use memset). This is a known clang-analyzer bug: - * http://clang-developers.42468.n3.nabble.com/StaticAnalyzer-False-positive-with-loop-handling-td4053875.html */ - if (flags[i] & POINT_IS_ON_CURVE) { - if (gotCtrl) { - if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0) - return -1; - outl->curves[outl->numCurves++] = { beg, cur, ctrl }; - } else { - if (outl->numLines >= outl->capLines && grow_lines(outl) < 0) - return -1; - outl->lines[outl->numLines++] = { beg, cur }; - } - beg = cur; - gotCtrl = 0; - } else { - if (gotCtrl) { - center = outl->numPoints; - if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0) - return -1; - outl->points[center] = midpoint(outl->points[ctrl], outl->points[cur]); - ++outl->numPoints; - - if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0) - return -1; - outl->curves[outl->numCurves++] = { beg, center, ctrl }; - - beg = center; - } - ctrl = cur; - gotCtrl = 1; - } - } - if (gotCtrl) { - if (outl->numCurves >= outl->capCurves && grow_curves(outl) < 0) - return -1; - outl->curves[outl->numCurves++] = { beg, looseEnd, ctrl }; - } else { - if (outl->numLines >= outl->capLines && grow_lines(outl) < 0) - return -1; - outl->lines[outl->numLines++] = { beg, looseEnd }; - } - - return 0; -} - -static int -simple_outline(SFT_Font *font, uint_fast32_t offset, unsigned int numContours, Outline *outl) -{ - uint_fast16_t *endPts = NULL; - uint8_t *flags = NULL; - uint_fast16_t numPts; - unsigned int i; - uint_fast16_t beg; - - assert(numContours > 0); - - uint_fast16_t basePoint = outl->numPoints; - - if (!is_safe_offset(font, offset, numContours * 2 + 2)) - goto failure; - numPts = getu16(font, offset + (numContours - 1) * 2); - if (numPts >= UINT16_MAX) - goto failure; - numPts++; - if (outl->numPoints > UINT16_MAX - numPts) - goto failure; - - while (outl->capPoints < basePoint + numPts) { - if (grow_points(outl) < 0) - goto failure; - } - - STACK_ALLOC(endPts, uint_fast16_t, 16, numContours); - if (endPts == NULL) - goto failure; - STACK_ALLOC(flags, uint8_t, 128, numPts); - if (flags == NULL) - goto failure; - - for (i = 0; i < numContours; ++i) { - endPts[i] = getu16(font, offset); - offset += 2; - } - /* Ensure that endPts are never falling. - * Falling endPts have no sensible interpretation and most likely only occur in malicious input. - * Therefore, we bail, should we ever encounter such input. */ - for (i = 0; i < numContours - 1; ++i) { - if (endPts[i + 1] < endPts[i] + 1) - goto failure; - } - offset += 2U + getu16(font, offset); - - if (simple_flags(font, &offset, numPts, flags) < 0) - goto failure; - if (simple_points(font, offset, numPts, flags, outl->points + basePoint) < 0) - goto failure; - outl->numPoints = (uint_least16_t) (outl->numPoints + numPts); - - beg = 0; - for (i = 0; i < numContours; ++i) { - uint_fast16_t count = endPts[i] - beg + 1; - if (decode_contour(flags + beg, basePoint + beg, count, outl) < 0) - goto failure; - beg = endPts[i] + 1; - } - - STACK_FREE(endPts); - STACK_FREE(flags); - return 0; -failure: - STACK_FREE(endPts); - STACK_FREE(flags); - return -1; -} - -static int -compound_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl) -{ - double local[6]; - uint_fast32_t outline; - unsigned int flags, glyph, basePoint; - /* Guard against infinite recursion (compound glyphs that have themselves as component). */ - if (recDepth >= 4) - return -1; - do { - memset(local, 0, sizeof local); - if (!is_safe_offset(font, offset, 4)) - return -1; - flags = getu16(font, offset); - glyph = getu16(font, offset + 2); - offset += 4; - /* We don't implement point matching, and neither does stb_truetype for that matter. */ - if (!(flags & ACTUAL_XY_OFFSETS)) - return -1; - /* Read additional X and Y offsets (in FUnits) of this component. */ - if (flags & OFFSETS_ARE_LARGE) { - if (!is_safe_offset(font, offset, 4)) - return -1; - local[4] = geti16(font, offset); - local[5] = geti16(font, offset + 2); - offset += 4; - } else { - if (!is_safe_offset(font, offset, 2)) - return -1; - local[4] = geti8(font, offset); - local[5] = geti8(font, offset + 1); - offset += 2; - } - if (flags & GOT_A_SINGLE_SCALE) { - if (!is_safe_offset(font, offset, 2)) - return -1; - local[0] = geti16(font, offset) / 16384.0; - local[3] = local[0]; - offset += 2; - } else if (flags & GOT_AN_X_AND_Y_SCALE) { - if (!is_safe_offset(font, offset, 4)) - return -1; - local[0] = geti16(font, offset + 0) / 16384.0; - local[3] = geti16(font, offset + 2) / 16384.0; - offset += 4; - } else if (flags & GOT_A_SCALE_MATRIX) { - if (!is_safe_offset(font, offset, 8)) - return -1; - local[0] = geti16(font, offset + 0) / 16384.0; - local[1] = geti16(font, offset + 2) / 16384.0; - local[2] = geti16(font, offset + 4) / 16384.0; - local[3] = geti16(font, offset + 6) / 16384.0; - offset += 8; - } else { - local[0] = 1.0; - local[3] = 1.0; - } - /* At this point, Apple's spec more or less tells you to scale the matrix by its own L1 norm. - * But stb_truetype scales by the L2 norm. And FreeType2 doesn't scale at all. - * Furthermore, Microsoft's spec doesn't even mention anything like this. - * It's almost as if nobody ever uses this feature anyway. */ - if (outline_offset(font, glyph, &outline) < 0) - return -1; - if (outline) { - basePoint = outl->numPoints; - if (decode_outline(font, outline, recDepth + 1, outl) < 0) - return -1; - transform_points(outl->numPoints - basePoint, outl->points + basePoint, local); - } - } while (flags & THERE_ARE_MORE_COMPONENTS); - - return 0; -} - -static int -decode_outline(SFT_Font *font, uint_fast32_t offset, int recDepth, Outline *outl) -{ - int numContours; - if (!is_safe_offset(font, offset, 10)) - return -1; - numContours = geti16(font, offset); - if (numContours > 0) { - /* Glyph has a 'simple' outline consisting of a number of contours. */ - return simple_outline(font, offset + 10, (unsigned int) numContours, outl); - } else if (numContours < 0) { - /* Glyph has a compound outline combined from mutiple other outlines. */ - return compound_outline(font, offset + 10, recDepth, outl); - } else { - return 0; - } -} - -/* A heuristic to tell whether a given curve can be approximated closely enough by a line. */ -static int -is_flat(Outline *outl, Curve curve) -{ - const double maxArea2 = 2.0; - Point a = outl->points[curve.beg]; - Point b = outl->points[curve.ctrl]; - Point c = outl->points[curve.end]; - Point g = { b.x-a.x, b.y-a.y }; - Point h = { c.x-a.x, c.y-a.y }; - double area2 = fabs(g.x*h.y-h.x*g.y); - return area2 <= maxArea2; -} - -static int -tesselate_curve(Curve curve, Outline *outl) -{ - /* From my tests I can conclude that this stack barely reaches a top height - * of 4 elements even for the largest font sizes I'm willing to support. And - * as space requirements should only grow logarithmically, I think 10 is - * more than enough. */ -#define STACK_SIZE 10 - Curve stack[STACK_SIZE]; - unsigned int top = 0; - for (;;) { - if (is_flat(outl, curve) || top >= STACK_SIZE) { - if (outl->numLines >= outl->capLines && grow_lines(outl) < 0) - return -1; - outl->lines[outl->numLines++] = { curve.beg, curve.end }; - if (top == 0) break; - curve = stack[--top]; - } else { - uint_least16_t ctrl0 = outl->numPoints; - if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0) - return -1; - outl->points[ctrl0] = midpoint(outl->points[curve.beg], outl->points[curve.ctrl]); - ++outl->numPoints; - - uint_least16_t ctrl1 = outl->numPoints; - if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0) - return -1; - outl->points[ctrl1] = midpoint(outl->points[curve.ctrl], outl->points[curve.end]); - ++outl->numPoints; - - uint_least16_t pivot = outl->numPoints; - if (outl->numPoints >= outl->capPoints && grow_points(outl) < 0) - return -1; - outl->points[pivot] = midpoint(outl->points[ctrl0], outl->points[ctrl1]); - ++outl->numPoints; - - stack[top++] = { curve.beg, pivot, ctrl0 }; - curve = { pivot, curve.end, ctrl1 }; - } - } - return 0; -#undef STACK_SIZE -} - -static int -tesselate_curves(Outline *outl) -{ - unsigned int i; - for (i = 0; i < outl->numCurves; ++i) { - if (tesselate_curve(outl->curves[i], outl) < 0) - return -1; - } - return 0; -} - -/* Draws a line into the buffer. Uses a custom 2D raycasting algorithm to do so. */ -static void -draw_line(Raster buf, Point origin, Point goal) -{ - Point delta; - Point nextCrossing; - Point crossingIncr; - double halfDeltaX; - double prevDistance = 0.0, nextDistance; - double xAverage, yDifference; - struct { int x, y; } pixel; - struct { int x, y; } dir; - int step, numSteps = 0; -#ifdef _MSC_VER - Cell *restrict cptr, cell; -#else - Cell *cptr, cell; -#endif - - delta.x = goal.x - origin.x; - delta.y = goal.y - origin.y; - dir.x = SIGN(delta.x); - dir.y = SIGN(delta.y); - - if (!dir.y) { - return; - } - - crossingIncr.x = dir.x ? fabs(1.0 / delta.x) : 1.0; - crossingIncr.y = fabs(1.0 / delta.y); - - if (!dir.x) { - pixel.x = fast_floor(origin.x); - nextCrossing.x = 100.0; - } else { - if (dir.x > 0) { - pixel.x = fast_floor(origin.x); - nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x; - nextCrossing.x = crossingIncr.x - nextCrossing.x; - numSteps += fast_ceil(goal.x) - fast_floor(origin.x) - 1; - } else { - pixel.x = fast_ceil(origin.x) - 1; - nextCrossing.x = (origin.x - pixel.x) * crossingIncr.x; - numSteps += fast_ceil(origin.x) - fast_floor(goal.x) - 1; - } - } - - if (dir.y > 0) { - pixel.y = fast_floor(origin.y); - nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y; - nextCrossing.y = crossingIncr.y - nextCrossing.y; - numSteps += fast_ceil(goal.y) - fast_floor(origin.y) - 1; - } else { - pixel.y = fast_ceil(origin.y) - 1; - nextCrossing.y = (origin.y - pixel.y) * crossingIncr.y; - numSteps += fast_ceil(origin.y) - fast_floor(goal.y) - 1; - } - - nextDistance = MIN(nextCrossing.x, nextCrossing.y); - halfDeltaX = 0.5 * delta.x; - - for (step = 0; step < numSteps; ++step) { - xAverage = origin.x + (prevDistance + nextDistance) * halfDeltaX; - yDifference = (nextDistance - prevDistance) * delta.y; - cptr = &buf.cells[pixel.y * buf.width + pixel.x]; - cell = *cptr; - cell.cover += yDifference; - xAverage -= (double) pixel.x; - cell.area += (1.0 - xAverage) * yDifference; - *cptr = cell; - prevDistance = nextDistance; - int alongX = nextCrossing.x < nextCrossing.y; - pixel.x += alongX ? dir.x : 0; - pixel.y += alongX ? 0 : dir.y; - nextCrossing.x += alongX ? crossingIncr.x : 0.0; - nextCrossing.y += alongX ? 0.0 : crossingIncr.y; - nextDistance = MIN(nextCrossing.x, nextCrossing.y); - } - - xAverage = origin.x + (prevDistance + 1.0) * halfDeltaX; - yDifference = (1.0 - prevDistance) * delta.y; - cptr = &buf.cells[pixel.y * buf.width + pixel.x]; - cell = *cptr; - cell.cover += yDifference; - xAverage -= (double) pixel.x; - cell.area += (1.0 - xAverage) * yDifference; - *cptr = cell; -} - -static void -draw_lines(Outline *outl, Raster buf) -{ - unsigned int i; - for (i = 0; i < outl->numLines; ++i) { - Line line = outl->lines[i]; - Point origin = outl->points[line.beg]; - Point goal = outl->points[line.end]; - draw_line(buf, origin, goal); - } -} - -/* Integrate the values in the buffer to arrive at the final grayscale image. */ -static void -post_process(Raster buf, uint8_t *image) -{ - Cell cell; - double accum = 0.0, value; - unsigned int i, num; - num = (unsigned int) buf.width * (unsigned int) buf.height; - for (i = 0; i < num; ++i) { - cell = buf.cells[i]; - value = fabs(accum + cell.area); - value = MIN(value, 1.0); - value = value * 255.0 + 0.5; - image[i] = (uint8_t) value; - accum += cell.cover; - } -} - -static int -render_outline(Outline *outl, double transform[6], SFT_Image image) -{ - Cell *cells = NULL; - Raster buf; - unsigned int numPixels; - - numPixels = (unsigned int) image.width * (unsigned int) image.height; - - STACK_ALLOC(cells, Cell, 32 * 32, numPixels); - if (!cells) { - return -1; - } - memset(cells, 0, numPixels * sizeof *cells); - buf.cells = cells; - buf.width = image.width; - buf.height = image.height; - - transform_points(outl->numPoints, outl->points, transform); - - clip_points(outl->numPoints, outl->points, image.width, image.height); - - if (tesselate_curves(outl) < 0) { - STACK_FREE(cells); - return -1; - } - - draw_lines(outl, buf); - - post_process(buf, (uint8_t*)image.pixels); - - STACK_FREE(cells); - return 0; -} - diff --git a/libraries/ZWidget/src/core/schrift/schrift.h b/libraries/ZWidget/src/core/schrift/schrift.h deleted file mode 100644 index 93d20516a..000000000 --- a/libraries/ZWidget/src/core/schrift/schrift.h +++ /dev/null @@ -1,95 +0,0 @@ -/* This file is part of libschrift. - * - * © 2019-2022 Thomas Oltmann and contributors - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - -#ifndef SCHRIFT_H -#define SCHRIFT_H 1 - -#include /* size_t */ -#include /* uint_fast32_t, uint_least32_t */ - -#ifdef __cplusplus -extern "C" { -#endif - -#define SFT_DOWNWARD_Y 0x01 - -typedef struct SFT SFT; -typedef struct SFT_Font SFT_Font; -typedef uint_least32_t SFT_UChar; /* Guaranteed to be compatible with char32_t. */ -typedef uint_fast32_t SFT_Glyph; -typedef struct SFT_LMetrics SFT_LMetrics; -typedef struct SFT_GMetrics SFT_GMetrics; -typedef struct SFT_Kerning SFT_Kerning; -typedef struct SFT_Image SFT_Image; - -struct SFT -{ - SFT_Font *font; - double xScale; - double yScale; - double xOffset; - double yOffset; - int flags; -}; - -struct SFT_LMetrics -{ - double ascender; - double descender; - double lineGap; -}; - -struct SFT_GMetrics -{ - double advanceWidth; - double leftSideBearing; - int yOffset; - int minWidth; - int minHeight; -}; - -struct SFT_Kerning -{ - double xShift; - double yShift; -}; - -struct SFT_Image -{ - void *pixels; - int width; - int height; -}; - -const char *sft_version(void); - -SFT_Font *sft_loadmem (const void *mem, size_t size); -SFT_Font *sft_loadfile(const char *filename); -void sft_freefont(SFT_Font *font); - -int sft_lmetrics(const SFT *sft, SFT_LMetrics *metrics); -int sft_lookup (const SFT *sft, SFT_UChar codepoint, SFT_Glyph *glyph); -int sft_gmetrics(const SFT *sft, SFT_Glyph glyph, SFT_GMetrics *metrics); -int sft_kerning (const SFT *sft, SFT_Glyph leftGlyph, SFT_Glyph rightGlyph, - SFT_Kerning *kerning); -int sft_render (const SFT *sft, SFT_Glyph glyph, SFT_Image image); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/libraries/ZWidget/src/core/truetypefont.cpp b/libraries/ZWidget/src/core/truetypefont.cpp new file mode 100644 index 000000000..3f71c058d --- /dev/null +++ b/libraries/ZWidget/src/core/truetypefont.cpp @@ -0,0 +1,1201 @@ + +// #define DUMP_GLYPH + +#include "core/truetypefont.h" +#include "core/pathfill.h" +#include +#include +#include + +#ifdef DUMP_GLYPH +#include +#endif + +TrueTypeFont::TrueTypeFont(std::shared_ptr& initdata, int ttcFontIndex) : data(initdata) +{ + if (data->size() > 0x7fffffff) + throw std::runtime_error("TTF file is larger than 2 gigabytes!"); + + TrueTypeFileReader reader(data->data(), data->size()); + ttf_Tag versionTag = reader.ReadTag(); + reader.Seek(0); + + if (memcmp(versionTag.data(), "ttcf", 4) == 0) // TTC header + { + ttcHeader.Load(reader); + if (ttcFontIndex >= ttcHeader.numFonts) + throw std::runtime_error("TTC font index out of bounds"); + reader.Seek(ttcHeader.tableDirectoryOffsets[ttcFontIndex]); + } + + directory.Load(reader); + + if (!directory.ContainsTTFOutlines()) + throw std::runtime_error("Only truetype outline fonts are supported"); + + // Load required tables: + + reader = directory.GetReader(data->data(), data->size(), "head"); + head.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "hhea"); + hhea.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "maxp"); + maxp.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "hmtx"); + hmtx.Load(hhea, maxp, reader); + + reader = directory.GetReader(data->data(), data->size(), "name"); + name.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "OS/2"); + os2.Load(reader); + + reader = directory.GetReader(data->data(), data->size(), "cmap"); + cmap.Load(reader); + + LoadCharacterMapEncoding(reader); + + // Load TTF Outlines: + + reader = directory.GetReader(data->data(), data->size(), "loca"); + loca.Load(head, maxp, reader); + + glyf = directory.GetRecord("glyf"); + +#ifdef DUMP_GLYPH + LoadGlyph(GetGlyphIndex('6'), 13.0); +#endif +} + +TrueTypeTextMetrics TrueTypeFont::GetTextMetrics(double height) const +{ + double scale = height / head.unitsPerEm; + + TrueTypeTextMetrics metrics; + metrics.ascender = os2.sTypoAscender * scale; + metrics.descender = os2.sTypoDescender * scale; + metrics.lineGap = os2.sTypoLineGap * scale; + return metrics; +} + +TrueTypeGlyph TrueTypeFont::LoadGlyph(uint32_t glyphIndex, double height) const +{ + double scale = height / head.unitsPerEm; + double scaleX = 3.0f; + double scaleY = -1.0f; + + ttf_uint16 advanceWidth = 0; + ttf_int16 lsb = 0; + if (glyphIndex >= hhea.numberOfHMetrics) + { + advanceWidth = hmtx.hMetrics[hhea.numberOfHMetrics - 1].advanceWidth; + lsb = hmtx.leftSideBearings[glyphIndex - hhea.numberOfHMetrics]; + } + else + { + advanceWidth = hmtx.hMetrics[glyphIndex].advanceWidth; + lsb = hmtx.hMetrics[glyphIndex].lsb; + } + + // Glyph is missing if the offset is the same as the next glyph (0 bytes glyph length) + bool missing = glyphIndex + 1 < loca.offsets.size() ? loca.offsets[glyphIndex] == loca.offsets[glyphIndex + 1] : false; + if (missing) + { + TrueTypeGlyph glyph; + + // TBD: gridfit or not? + glyph.advanceWidth = (int)std::round(advanceWidth * scale * scaleX); + glyph.leftSideBearing = (int)std::round(lsb * scale * scaleX); + glyph.yOffset = 0; + + return glyph; + } + + TTF_SimpleGlyph g; + LoadGlyph(g, glyphIndex); + + // Create glyph path: + PathFillDesc path; + path.fill_mode = PathFillMode::winding; + + int startPoint = 0; + int numberOfContours = g.endPtsOfContours.size(); + for (int i = 0; i < numberOfContours; i++) + { + int endPoint = g.endPtsOfContours[i]; + if (endPoint < startPoint) + throw std::runtime_error("Invalid glyph"); + + bool prevIsControlPoint; + bool isControlPoint = !(g.flags[endPoint] & TTF_ON_CURVE_POINT); + bool nextIsControlPoint = !(g.flags[startPoint] & TTF_ON_CURVE_POINT); + if (isControlPoint) + { + Point nextpoint(g.points[startPoint].x, g.points[startPoint].y); + if (nextIsControlPoint) + { + Point curpoint(g.points[endPoint].x, g.points[endPoint].y); + Point midpoint = (curpoint + nextpoint) / 2; + path.MoveTo(midpoint * scale); + prevIsControlPoint = isControlPoint; + } + else + { + path.MoveTo(Point(g.points[startPoint].x, g.points[startPoint].y) * scale); + prevIsControlPoint = false; + } + } + else + { + path.MoveTo(Point(g.points[endPoint].x, g.points[endPoint].y) * scale); + prevIsControlPoint = isControlPoint; + } + + int pos = startPoint; + while (pos <= endPoint) + { + int nextpos = pos + 1 <= endPoint ? pos + 1 : startPoint; + bool isControlPoint = !(g.flags[pos] & TTF_ON_CURVE_POINT); + bool nextIsControlPoint = !(g.flags[nextpos] & TTF_ON_CURVE_POINT); + Point curpoint(g.points[pos].x, g.points[pos].y); + if (isControlPoint) + { + Point nextpoint(g.points[nextpos].x, g.points[nextpos].y); + if (nextIsControlPoint) + { + Point midpoint = (curpoint + nextpoint) / 2; + path.BezierTo(curpoint * scale, midpoint * scale); + prevIsControlPoint = isControlPoint; + pos++; + } + else + { + path.BezierTo(curpoint * scale, nextpoint * scale); + prevIsControlPoint = false; + pos += 2; + } + } + else + { + if (!prevIsControlPoint) + path.LineTo(curpoint * scale); + prevIsControlPoint = isControlPoint; + pos++; + } + } + path.Close(); + + startPoint = endPoint + 1; + } + + // Transform and find the final bounding box + Point bboxMin, bboxMax; + if (!path.subpaths.front().points.empty()) + { + bboxMin = path.subpaths.front().points.front(); + bboxMax = path.subpaths.front().points.front(); + bboxMin.x *= scaleX; + bboxMin.y *= scaleY; + bboxMax.x *= scaleX; + bboxMax.y *= scaleY; + for (auto& subpath : path.subpaths) + { + for (auto& point : subpath.points) + { + point.x *= scaleX; + point.y *= scaleY; + bboxMin.x = std::min(bboxMin.x, point.x); + bboxMin.y = std::min(bboxMin.y, point.y); + bboxMax.x = std::max(bboxMax.x, point.x); + bboxMax.y = std::max(bboxMax.y, point.y); + } + } + } + + bboxMin.x = std::floor(bboxMin.x); + bboxMin.y = std::floor(bboxMin.y); + + // Reposition glyph to bitmap so it begins at (0,0) for our bitmap + for (auto& subpath : path.subpaths) + { + for (auto& point : subpath.points) + { + point.x -= bboxMin.x; + point.y -= bboxMin.y; + } + } + +#ifdef DUMP_GLYPH + std::string svgxmlstart = R"( + + + +)"; + + std::ofstream out("c:\\development\\glyph.svg"); + out << svgxmlstart; + + for (auto& subpath : path.subpaths) + { + size_t pos = 0; + out << "M" << subpath.points[pos].x << " " << subpath.points[pos].y << " "; + pos++; + for (PathFillCommand cmd : subpath.commands) + { + int count = 0; + if (cmd == PathFillCommand::line) + { + out << "L"; + count = 1; + } + else if (cmd == PathFillCommand::quadradic) + { + out << "Q"; + count = 2; + } + else if (cmd == PathFillCommand::cubic) + { + out << "C"; + count = 3; + } + + for (int i = 0; i < count; i++) + { + out << subpath.points[pos].x << " " << subpath.points[pos].y << " "; + pos++; + } + } + if (subpath.closed) + out << "Z"; + } + + out << svgxmlend; + out.close(); +#endif + + TrueTypeGlyph glyph; + + // Rasterize the glyph + glyph.width = (int)std::floor(bboxMax.x - bboxMin.x) + 1; + glyph.height = (int)std::floor(bboxMax.y - bboxMin.y) + 1; + glyph.grayscale.reset(new uint8_t[glyph.width * glyph.height]); + uint8_t* grayscale = glyph.grayscale.get(); + path.Rasterize(grayscale, glyph.width, glyph.height); + + // TBD: gridfit or not? + glyph.advanceWidth = (int)std::round(advanceWidth * scale * scaleX); + glyph.leftSideBearing = (int)std::round(bboxMin.x); + glyph.yOffset = (int)std::round(bboxMin.y); + + return glyph; +} + +void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compositeDepth) const +{ + if (glyphIndex >= loca.offsets.size()) + throw std::runtime_error("Glyph index out of bounds"); + + TrueTypeFileReader reader = glyf.GetReader(data->data(), data->size()); + reader.Seek(loca.offsets[glyphIndex]); + + ttf_int16 numberOfContours = reader.ReadInt16(); + ttf_int16 xMin = reader.ReadInt16(); + ttf_int16 yMin = reader.ReadInt16(); + ttf_int16 xMax = reader.ReadInt16(); + ttf_int16 yMax = reader.ReadInt16(); + + if (numberOfContours > 0) // Simple glyph + { + int pointsOffset = g.points.size(); + for (ttf_uint16 i = 0; i < numberOfContours; i++) + g.endPtsOfContours.push_back(pointsOffset + reader.ReadUInt16()); + + ttf_uint16 instructionLength = reader.ReadUInt16(); + std::vector instructions; + instructions.resize(instructionLength); + reader.Read(instructions.data(), instructions.size()); + + int numPoints = (int)g.endPtsOfContours.back() - pointsOffset + 1; + g.points.resize(pointsOffset + numPoints); + + while (g.flags.size() < g.points.size()) + { + ttf_uint8 flag = reader.ReadUInt8(); + if (flag & TTF_REPEAT_FLAG) + { + ttf_uint8 repeatcount = reader.ReadUInt8(); + for (ttf_uint8 i = 0; i < repeatcount; i++) + g.flags.push_back(flag); + } + g.flags.push_back(flag); + } + + for (int i = pointsOffset; i < pointsOffset + numPoints; i++) + { + if (g.flags[i] & TTF_X_SHORT_VECTOR) + { + ttf_int16 x = reader.ReadUInt8(); + g.points[i].x = (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) ? x : -x; + } + else if (g.flags[i] & TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) + { + g.points[i].x = 0; + } + else + { + g.points[i].x = reader.ReadInt16(); + } + } + + for (int i = pointsOffset; i < pointsOffset + numPoints; i++) + { + if (g.flags[i] & TTF_Y_SHORT_VECTOR) + { + ttf_int16 y = reader.ReadUInt8(); + g.points[i].y = (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) ? y : -y; + } + else if (g.flags[i] & TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) + { + g.points[i].y = 0; + } + else + { + g.points[i].y = reader.ReadInt16(); + } + } + + // Convert from relative coordinates to absolute + for (int i = pointsOffset + 1; i < pointsOffset + numPoints; i++) + { + g.points[i].x += g.points[i - 1].x; + g.points[i].y += g.points[i - 1].y; + } + } + else if (numberOfContours < 0) // Composite glyph + { + if (compositeDepth == 8) + throw std::runtime_error("Composite glyph recursion exceeded"); + + int parentPointsOffset = g.points.size(); + + bool weHaveInstructions = false; + while (true) + { + ttf_uint16 flags = reader.ReadUInt16(); + ttf_uint16 childGlyphIndex = reader.ReadUInt16(); + + int argument1, argument2; + if (flags & TTF_ARG_1_AND_2_ARE_WORDS) + { + if (flags & TTF_ARGS_ARE_XY_VALUES) + { + argument1 = reader.ReadInt16(); + argument2 = reader.ReadInt16(); + } + else + { + argument1 = reader.ReadUInt16(); + argument2 = reader.ReadUInt16(); + } + } + else + { + if (flags & TTF_ARGS_ARE_XY_VALUES) + { + argument1 = reader.ReadInt8(); + argument2 = reader.ReadInt8(); + } + else + { + argument1 = reader.ReadUInt8(); + argument2 = reader.ReadUInt8(); + } + } + + float mat2x2[4]; + bool transform = true; + if (flags & TTF_WE_HAVE_A_SCALE) + { + ttf_F2DOT14 scale = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[0] = scale; + mat2x2[1] = 0; + mat2x2[2] = 0; + mat2x2[3] = scale; + } + else if (flags & TTF_WE_HAVE_AN_X_AND_Y_SCALE) + { + mat2x2[0] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[1] = 0; + mat2x2[2] = 0; + mat2x2[3] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + } + else if (flags & TTF_WE_HAVE_A_TWO_BY_TWO) + { + mat2x2[0] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[1] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[2] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + mat2x2[3] = F2DOT14_ToFloat(reader.ReadF2DOT14()); + } + else + { + transform = false; + } + + int childPointsOffset = g.points.size(); + LoadGlyph(g, childGlyphIndex, compositeDepth + 1); + + if (transform) + { + for (int i = childPointsOffset; i < g.points.size(); i++) + { + float x = g.points[i].x * mat2x2[0] + g.points[i].y * mat2x2[1]; + float y = g.points[i].x * mat2x2[2] + g.points[i].y * mat2x2[3]; + g.points[i].x = x; + g.points[i].y = y; + } + } + + float dx, dy; + + if (flags & TTF_ARGS_ARE_XY_VALUES) + { + dx = argument1; + dy = argument2; + + // Spec states we must fall back to TTF_UNSCALED_COMPONENT_OFFSET if both flags are set + if ((flags & (TTF_SCALED_COMPONENT_OFFSET | TTF_UNSCALED_COMPONENT_OFFSET)) == TTF_SCALED_COMPONENT_OFFSET) + { + float x = dx * mat2x2[0] + dy * mat2x2[1]; + float y = dx * mat2x2[2] + dy * mat2x2[3]; + dx = x; + dy = y; + } + + if (flags & TTF_ROUND_XY_TO_GRID) + { + // To do: round the offset to the pixel grid + } + } + else + { + int parentPointIndex = parentPointsOffset + argument1; + int childPointIndex = childPointsOffset + argument2; + + if ((size_t)parentPointIndex >= g.points.size() || (size_t)childPointIndex >= g.points.size()) + throw std::runtime_error("Invalid glyph offset"); + + dx = g.points[parentPointIndex].x - g.points[childPointIndex].x; + dy = g.points[parentPointIndex].y - g.points[childPointIndex].y; + } + + for (int i = childPointsOffset; i < g.points.size(); i++) + { + g.points[i].x += dx; + g.points[i].y += dy; + } + + if (flags & TTF_USE_MY_METRICS) + { + // To do: this affects lsb + rsb calculations somehow + } + + if (flags & TTF_WE_HAVE_INSTRUCTIONS) + { + weHaveInstructions = true; + } + + if (!(flags & TTF_MORE_COMPONENTS)) + break; + } + + if (weHaveInstructions) + { + ttf_uint16 instructionLength = reader.ReadUInt16(); + std::vector instructions; + instructions.resize(instructionLength); + reader.Read(instructions.data(), instructions.size()); + } + } +} + +float TrueTypeFont::F2DOT14_ToFloat(ttf_F2DOT14 v) +{ + int a = ((ttf_int16)v) >> 14; + int b = (v & 0x3fff); + return a + b / (float)0x4000; +} + +uint32_t TrueTypeFont::GetGlyphIndex(uint32_t c) const +{ + auto it = std::lower_bound(Ranges.begin(), Ranges.end(), c, [](const TTF_GlyphRange& range, uint32_t c) { return range.endCharCode < c; }); + if (it != Ranges.end() && c >= it->startCharCode && c <= it->endCharCode) + { + return it->startGlyphID + (c - it->startCharCode); + } + + it = std::lower_bound(ManyToOneRanges.begin(), ManyToOneRanges.end(), c, [](const TTF_GlyphRange& range, uint32_t c) { return range.endCharCode < c; }); + if (it != ManyToOneRanges.end() && c >= it->startCharCode && c <= it->endCharCode) + { + return it->startGlyphID; + } + return 0; +} + +void TrueTypeFont::LoadCharacterMapEncoding(TrueTypeFileReader& reader) +{ + // Look for the best encoding available that we support + + TTF_EncodingRecord record; + if (!record.subtableOffset) record = cmap.GetEncoding(3, 12); + if (!record.subtableOffset) record = cmap.GetEncoding(0, 4); + if (!record.subtableOffset) record = cmap.GetEncoding(3, 1); + if (!record.subtableOffset) record = cmap.GetEncoding(0, 3); + if (!record.subtableOffset) + throw std::runtime_error("No supported cmap encoding found in truetype file"); + + reader.Seek(record.subtableOffset); + + ttf_uint16 format = reader.ReadUInt16(); + if (format == 4) + { + TTF_CMapSubtable4 subformat; + subformat.Load(reader); + + for (uint16_t i = 0; i < subformat.segCount; i++) + { + ttf_uint16 startCode = subformat.startCode[i]; + ttf_uint16 endCode = subformat.endCode[i]; + ttf_uint16 idDelta = subformat.idDelta[i]; + ttf_uint16 idRangeOffset = subformat.idRangeOffsets[i]; + if (idRangeOffset == 0) + { + ttf_uint16 glyphId = startCode + idDelta; // Note: relies on modulo 65536 + + TTF_GlyphRange range; + range.startCharCode = startCode; + range.endCharCode = endCode; + range.startGlyphID = glyphId; + Ranges.push_back(range); + } + else if (startCode <= endCode) + { + TTF_GlyphRange range; + range.startCharCode = startCode; + bool firstGlyph = true; + for (ttf_uint16 c = startCode; c <= endCode; c++) + { + int offset = idRangeOffset / 2 + (c - startCode) - ((int)subformat.segCount - i); + if (offset >= 0 && offset < subformat.glyphIdArray.size()) + { + int glyphId = subformat.glyphIdArray[offset]; + if (firstGlyph) + { + range.startGlyphID = glyphId; + firstGlyph = false; + } + else if (range.startGlyphID + (c - range.startCharCode) != glyphId) + { + range.endCharCode = c - 1; + Ranges.push_back(range); + range.startCharCode = c; + range.startGlyphID = glyphId; + } + } + } + if (!firstGlyph) + { + range.endCharCode = endCode; + Ranges.push_back(range); + } + } + } + } + else if (format == 12 || format == 3) + { + TTF_CMapSubtable12 subformat; + subformat.Load(reader); + Ranges = std::move(subformat.groups); + } + else if (format == 13 || format == 3) + { + TTF_CMapSubtable13 subformat; + subformat.Load(reader); + ManyToOneRanges = std::move(subformat.groups); + } +} + +std::vector TrueTypeFont::GetFontNames(const std::shared_ptr& data) +{ + if (data->size() > 0x7fffffff) + throw std::runtime_error("TTF file is larger than 2 gigabytes!"); + + TrueTypeFileReader reader(data->data(), data->size()); + ttf_Tag versionTag = reader.ReadTag(); + reader.Seek(0); + + std::vector names; + if (memcmp(versionTag.data(), "ttcf", 4) == 0) // TTC header + { + TTC_Header ttcHeader; + ttcHeader.Load(reader); + + for (size_t i = 0; i < ttcHeader.tableDirectoryOffsets.size(); i++) + { + reader.Seek(ttcHeader.tableDirectoryOffsets[i]); + + TTF_TableDirectory directory; + directory.Load(reader); + + TTF_NamingTable name; + auto name_reader = directory.GetReader(data->data(), data->size(), "name"); + name.Load(name_reader); + + names.push_back(name.GetFontName()); + } + } + else + { + TTF_TableDirectory directory; + directory.Load(reader); + + TTF_NamingTable name; + auto name_reader = directory.GetReader(data->data(), data->size(), "name"); + name.Load(name_reader); + + names.push_back(name.GetFontName()); + } + return names; +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMapSubtable0::Load(TrueTypeFileReader& reader) +{ + length = reader.ReadUInt16(); + language = reader.ReadUInt16(); + glyphIdArray.resize(256); + reader.Read(glyphIdArray.data(), glyphIdArray.size()); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMapSubtable4::Load(TrueTypeFileReader& reader) +{ + length = reader.ReadUInt16(); + language = reader.ReadUInt16(); + + segCount = reader.ReadUInt16() / 2; + ttf_uint16 searchRange = reader.ReadUInt16(); + ttf_uint16 entrySelector = reader.ReadUInt16(); + ttf_uint16 rangeShift = reader.ReadUInt16(); + + endCode.reserve(segCount); + startCode.reserve(segCount); + idDelta.reserve(segCount); + idRangeOffsets.reserve(segCount); + for (ttf_uint16 i = 0; i < segCount; i++) endCode.push_back(reader.ReadUInt16()); + reservedPad = reader.ReadUInt16(); + for (ttf_uint16 i = 0; i < segCount; i++) startCode.push_back(reader.ReadUInt16()); + for (ttf_uint16 i = 0; i < segCount; i++) idDelta.push_back(reader.ReadInt16()); + for (ttf_uint16 i = 0; i < segCount; i++) idRangeOffsets.push_back(reader.ReadUInt16()); + + int glyphIdArraySize = ((int)length - (8 + (int)segCount * 4) * sizeof(ttf_uint16)) / 2; + if (glyphIdArraySize < 0) + throw std::runtime_error("Invalid TTF cmap subtable 4 length"); + glyphIdArray.reserve(glyphIdArraySize); + for (int i = 0; i < glyphIdArraySize; i++) glyphIdArray.push_back(reader.ReadUInt16()); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMapSubtable12::Load(TrueTypeFileReader& reader) +{ + reserved = reader.ReadUInt16(); + length = reader.ReadUInt32(); + language = reader.ReadUInt32(); + numGroups = reader.ReadUInt32(); + for (ttf_uint32 i = 0; i < numGroups; i++) + { + TTF_GlyphRange range; + range.startCharCode = reader.ReadUInt32(); + range.endCharCode = reader.ReadUInt32(); + range.startGlyphID = reader.ReadUInt32(); + groups.push_back(range); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_TableRecord::Load(TrueTypeFileReader& reader) +{ + tableTag = reader.ReadTag(); + checksum = reader.ReadUInt32(); + offset = reader.ReadOffset32(); + length = reader.ReadUInt32(); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_TableDirectory::Load(TrueTypeFileReader& reader) +{ + sfntVersion = reader.ReadUInt32(); + numTables = reader.ReadUInt16(); + + // opentype spec says we can't use these for security reasons, so we pretend they never was part of the header + ttf_uint16 searchRange = reader.ReadUInt16(); + ttf_uint16 entrySelector = reader.ReadUInt16(); + ttf_uint16 rangeShift = reader.ReadUInt16(); + + for (ttf_uint16 i = 0; i < numTables; i++) + { + TTF_TableRecord record; + record.Load(reader); + tableRecords.push_back(record); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTC_Header::Load(TrueTypeFileReader& reader) +{ + ttcTag = reader.ReadTag(); + majorVersion = reader.ReadUInt16(); + minorVersion = reader.ReadUInt16(); + + if (!(majorVersion == 1 && minorVersion == 0) && !(majorVersion == 2 && minorVersion == 0)) + throw std::runtime_error("Unsupported TTC header version"); + + numFonts = reader.ReadUInt32(); + for (ttf_uint16 i = 0; i < numFonts; i++) + { + tableDirectoryOffsets.push_back(reader.ReadOffset32()); + } + + if (majorVersion == 2 && minorVersion == 0) + { + dsigTag = reader.ReadUInt32(); + dsigLength = reader.ReadUInt32(); + dsigOffset = reader.ReadUInt32(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_CMap::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadUInt16(); + numTables = reader.ReadUInt16(); + + for (ttf_uint16 i = 0; i < numTables; i++) + { + TTF_EncodingRecord record; + record.platformID = reader.ReadUInt16(); + record.encodingID = reader.ReadUInt16(); + record.subtableOffset = reader.ReadOffset32(); + encodingRecords.push_back(record); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_FontHeader::Load(TrueTypeFileReader& reader) +{ + majorVersion = reader.ReadUInt16(); + minorVersion = reader.ReadUInt16(); + fontRevision = reader.ReadFixed(); + checksumAdjustment = reader.ReadUInt32(); + magicNumber = reader.ReadUInt32(); + flags = reader.ReadUInt16(); + unitsPerEm = reader.ReadUInt16(); + created = reader.ReadLONGDATETIME(); + modified = reader.ReadLONGDATETIME(); + xMin = reader.ReadInt16(); + yMin = reader.ReadInt16(); + xMax = reader.ReadInt16(); + yMax = reader.ReadInt16(); + macStyle = reader.ReadUInt16(); + lowestRecPPEM = reader.ReadUInt16(); + fontDirectionHint = reader.ReadInt16(); + indexToLocFormat = reader.ReadInt16(); + glyphDataFormat = reader.ReadInt16(); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_HorizontalHeader::Load(TrueTypeFileReader& reader) +{ + majorVersion = reader.ReadUInt16(); + minorVersion = reader.ReadUInt16(); + ascender = reader.ReadFWORD(); + descender = reader.ReadFWORD(); + lineGap = reader.ReadFWORD(); + advanceWidthMax = reader.ReadUFWORD(); + minLeftSideBearing = reader.ReadFWORD(); + minRightSideBearing = reader.ReadFWORD(); + xMaxExtent = reader.ReadFWORD(); + caretSlopeRise = reader.ReadInt16(); + caretSlopeRun = reader.ReadInt16(); + caretOffset = reader.ReadInt16(); + reserved0 = reader.ReadInt16(); + reserved1 = reader.ReadInt16(); + reserved2 = reader.ReadInt16(); + reserved3 = reader.ReadInt16(); + metricDataFormat = reader.ReadInt16(); + numberOfHMetrics = reader.ReadUInt16(); +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_HorizontalMetrics::Load(const TTF_HorizontalHeader& hhea, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader) +{ + for (ttf_uint16 i = 0; i < hhea.numberOfHMetrics; i++) + { + longHorMetric metric; + metric.advanceWidth = reader.ReadUInt16(); + metric.lsb = reader.ReadInt16(); + hMetrics.push_back(metric); + } + + int count = (int)maxp.numGlyphs - (int)hhea.numberOfHMetrics; + if (count < 0) + throw std::runtime_error("Invalid TTF file"); + + for (int i = 0; i < count; i++) + { + leftSideBearings.push_back(reader.ReadInt16()); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_MaximumProfile::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadVersion16Dot16(); + numGlyphs = reader.ReadUInt16(); + + if (version == (1 << 16)) // v1 only + { + maxPoints = reader.ReadUInt16(); + maxContours = reader.ReadUInt16(); + maxCompositePoints = reader.ReadUInt16(); + maxCompositeContours = reader.ReadUInt16(); + maxZones = reader.ReadUInt16(); + maxTwilightPoints = reader.ReadUInt16(); + maxStorage = reader.ReadUInt16(); + maxFunctionDefs = reader.ReadUInt16(); + maxInstructionDefs = reader.ReadUInt16(); + maxStackElements = reader.ReadUInt16(); + maxSizeOfInstructions = reader.ReadUInt16(); + maxComponentElements = reader.ReadUInt16(); + maxComponentDepth = reader.ReadUInt16(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +static std::string unicode_to_utf8(unsigned int value) +{ + char text[8]; + + if ((value < 0x80) && (value > 0)) + { + text[0] = (char)value; + text[1] = 0; + } + else if (value < 0x800) + { + text[0] = (char)(0xc0 | (value >> 6)); + text[1] = (char)(0x80 | (value & 0x3f)); + text[2] = 0; + } + else if (value < 0x10000) + { + text[0] = (char)(0xe0 | (value >> 12)); + text[1] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[2] = (char)(0x80 | (value & 0x3f)); + text[3] = 0; + } + else if (value < 0x200000) + { + text[0] = (char)(0xf0 | (value >> 18)); + text[1] = (char)(0x80 | ((value >> 12) & 0x3f)); + text[2] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[3] = (char)(0x80 | (value & 0x3f)); + text[4] = 0; + } + else if (value < 0x4000000) + { + text[0] = (char)(0xf8 | (value >> 24)); + text[1] = (char)(0x80 | ((value >> 18) & 0x3f)); + text[2] = (char)(0x80 | ((value >> 12) & 0x3f)); + text[3] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[4] = (char)(0x80 | (value & 0x3f)); + text[5] = 0; + } + else if (value < 0x80000000) + { + text[0] = (char)(0xfc | (value >> 30)); + text[1] = (char)(0x80 | ((value >> 24) & 0x3f)); + text[2] = (char)(0x80 | ((value >> 18) & 0x3f)); + text[3] = (char)(0x80 | ((value >> 12) & 0x3f)); + text[4] = (char)(0x80 | ((value >> 6) & 0x3f)); + text[5] = (char)(0x80 | (value & 0x3f)); + text[6] = 0; + } + else + { + text[0] = 0; // Invalid wchar value + } + return text; +} + +void TTF_NamingTable::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadUInt16(); + count = reader.ReadUInt16(); + storageOffset = reader.ReadOffset16(); + for (ttf_uint16 i = 0; i < count; i++) + { + NameRecord record; + record.platformID = reader.ReadUInt16(); + record.encodingID = reader.ReadUInt16(); + record.languageID = reader.ReadUInt16(); + record.nameID = reader.ReadUInt16(); + record.length = reader.ReadUInt16(); + record.stringOffset = reader.ReadOffset16(); + nameRecord.push_back(record); + } + + if (version == 1) + { + langTagCount = reader.ReadUInt16(); + for (ttf_uint16 i = 0; i < langTagCount; i++) + { + LangTagRecord record; + ttf_uint16 length; + ttf_Offset16 langTagOffset; + langTagRecord.push_back(record); + } + } + + for (NameRecord& record : nameRecord) + { + if (record.length > 0 && record.platformID == 3 && record.encodingID == 1) + { + reader.Seek(storageOffset + record.stringOffset); + ttf_uint16 ucs2len = record.length / 2; + for (ttf_uint16 i = 0; i < ucs2len; i++) + { + record.text += unicode_to_utf8(reader.ReadUInt16()); + } + } + } +} + +TTCFontName TTF_NamingTable::GetFontName() const +{ + TTCFontName fname; + for (const auto& record : nameRecord) + { + if (record.nameID == 1) fname.FamilyName = record.text; + if (record.nameID == 2) fname.SubfamilyName = record.text; + if (record.nameID == 3) fname.UniqueID = record.text; + if (record.nameID == 4) fname.FullName = record.text; + if (record.nameID == 5) fname.VersionString = record.text; + if (record.nameID == 6) fname.PostscriptName = record.text; + } + return fname; +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_OS2Windows::Load(TrueTypeFileReader& reader) +{ + version = reader.ReadUInt16(); + xAvgCharWidth = reader.ReadInt16(); + usWeightClass = reader.ReadUInt16(); + usWidthClass = reader.ReadUInt16(); + fsType = reader.ReadUInt16(); + ySubscriptXSize = reader.ReadInt16(); + ySubscriptYSize = reader.ReadInt16(); + ySubscriptXOffset = reader.ReadInt16(); + ySubscriptYOffset = reader.ReadInt16(); + ySuperscriptXSize = reader.ReadInt16(); + ySuperscriptYSize = reader.ReadInt16(); + ySuperscriptXOffset = reader.ReadInt16(); + ySuperscriptYOffset = reader.ReadInt16(); + yStrikeoutSize = reader.ReadInt16(); + yStrikeoutPosition = reader.ReadInt16(); + sFamilyClass = reader.ReadInt16(); + for (int i = 0; i < 10; i++) + { + panose[i] = reader.ReadUInt8(); + } + ulUnicodeRange1 = reader.ReadUInt32(); + ulUnicodeRange2 = reader.ReadUInt32(); + ulUnicodeRange3 = reader.ReadUInt32(); + ulUnicodeRange4 = reader.ReadUInt32(); + achVendID = reader.ReadTag(); + fsSelection = reader.ReadUInt16(); + usFirstCharIndex = reader.ReadUInt16(); + usLastCharIndex = reader.ReadUInt16(); + sTypoAscender = reader.ReadInt16(); + sTypoDescender = reader.ReadInt16(); + sTypoLineGap = reader.ReadInt16(); + if (!reader.IsEndOfData()) // may be missing in v0 due to a bug in Apple's TTF documentation + { + usWinAscent = reader.ReadUInt16(); + usWinDescent = reader.ReadUInt16(); + } + if (version >= 1) + { + ulCodePageRange1 = reader.ReadUInt32(); + ulCodePageRange2 = reader.ReadUInt32(); + } + if (version >= 2) + { + sxHeight = reader.ReadInt16(); + sCapHeight = reader.ReadInt16(); + usDefaultChar = reader.ReadUInt16(); + usBreakChar = reader.ReadUInt16(); + usMaxContext = reader.ReadUInt16(); + } + if (version >= 5) + { + usLowerOpticalPointSize = reader.ReadUInt16(); + usUpperOpticalPointSize = reader.ReadUInt16(); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void TTF_IndexToLocation::Load(const TTF_FontHeader& head, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader) +{ + int count = (int)maxp.numGlyphs + 1; + if (head.indexToLocFormat == 0) + { + offsets.reserve(count); + for (int i = 0; i < count; i++) + { + offsets.push_back((ttf_Offset32)reader.ReadOffset16() * 2); + } + } + else + { + offsets.reserve(count); + for (int i = 0; i < count; i++) + { + offsets.push_back(reader.ReadOffset32()); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +ttf_uint8 TrueTypeFileReader::ReadUInt8() +{ + ttf_uint8 v; Read(&v, 1); return v; +} + +ttf_uint16 TrueTypeFileReader::ReadUInt16() +{ + ttf_uint8 v[2]; Read(v, 2); return (((ttf_uint16)v[0]) << 8) | (ttf_uint16)v[1]; +} + +ttf_uint24 TrueTypeFileReader::ReadUInt24() +{ + ttf_uint8 v[3]; Read(v, 3); return (((ttf_uint32)v[0]) << 16) | (((ttf_uint32)v[1]) << 8) | (ttf_uint32)v[2]; +} + +ttf_uint32 TrueTypeFileReader::ReadUInt32() +{ + ttf_uint8 v[4]; Read(v, 4); return (((ttf_uint32)v[0]) << 24) | (((ttf_uint32)v[1]) << 16) | (((ttf_uint32)v[2]) << 8) | (ttf_uint32)v[3]; +} + +ttf_int8 TrueTypeFileReader::ReadInt8() +{ + return ReadUInt8(); +} + +ttf_int16 TrueTypeFileReader::ReadInt16() +{ + return ReadUInt16(); +} + +ttf_int32 TrueTypeFileReader::ReadInt32() +{ + return ReadUInt32(); +} + +ttf_Fixed TrueTypeFileReader::ReadFixed() +{ + return ReadUInt32(); +} + +ttf_UFWORD TrueTypeFileReader::ReadUFWORD() +{ + return ReadUInt16(); +} + +ttf_FWORD TrueTypeFileReader::ReadFWORD() +{ + return ReadUInt16(); +} + +ttf_F2DOT14 TrueTypeFileReader::ReadF2DOT14() +{ + return ReadUInt16(); +} + +ttf_LONGDATETIME TrueTypeFileReader::ReadLONGDATETIME() +{ + ttf_uint8 v[8]; Read(v, 8); + return + (((ttf_LONGDATETIME)v[0]) << 56) | (((ttf_LONGDATETIME)v[1]) << 48) | (((ttf_LONGDATETIME)v[2]) << 40) | (((ttf_LONGDATETIME)v[3]) << 32) | + (((ttf_LONGDATETIME)v[4]) << 24) | (((ttf_LONGDATETIME)v[5]) << 16) | (((ttf_LONGDATETIME)v[6]) << 8) | (ttf_LONGDATETIME)v[7]; +} + +ttf_Tag TrueTypeFileReader::ReadTag() +{ + ttf_Tag v; Read(v.data(), v.size()); return v; +} + +ttf_Offset16 TrueTypeFileReader::ReadOffset16() +{ + return ReadUInt16(); +} + +ttf_Offset24 TrueTypeFileReader::ReadOffset24() +{ + return ReadUInt24(); +} + +ttf_Offset32 TrueTypeFileReader::ReadOffset32() +{ + return ReadUInt32(); +} + +ttf_Version16Dot16 TrueTypeFileReader::ReadVersion16Dot16() +{ + return ReadUInt32(); +} + +void TrueTypeFileReader::Seek(size_t newpos) +{ + if (newpos > size) + throw std::runtime_error("Invalid TTF file"); + + pos = newpos; +} + +void TrueTypeFileReader::Read(void* output, size_t count) +{ + if (pos + count > size) + throw std::runtime_error("Unexpected end of TTF file"); + memcpy(output, data + pos, count); + pos += count; +} diff --git a/libraries/ZWidget/src/core/truetypefont.h b/libraries/ZWidget/src/core/truetypefont.h new file mode 100644 index 000000000..5fd4c5099 --- /dev/null +++ b/libraries/ZWidget/src/core/truetypefont.h @@ -0,0 +1,527 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +typedef uint8_t ttf_uint8; +typedef uint16_t ttf_uint16; +typedef uint32_t ttf_uint24; // 24-bit unsigned integer +typedef uint32_t ttf_uint32; + +typedef int8_t ttf_int8; +typedef int16_t ttf_int16; +typedef int32_t ttf_int32; + +typedef uint32_t ttf_Fixed; // 32-bit signed fixed-point number (16.16) +typedef uint16_t ttf_UFWORD; // uint16 that describes a quantity in font design units +typedef int16_t ttf_FWORD; // int16 that describes a quantity in font design units +typedef uint16_t ttf_F2DOT14; // 16-bit signed fixed number with the low 14 bits of fraction (2.14) +typedef uint64_t ttf_LONGDATETIME; // number of seconds since 12:00 midnight, January 1, 1904, UTC + +typedef std::array ttf_Tag; // 4 byte identifier + +typedef uint16_t ttf_Offset16; // Short offset to a table, same as uint16, NULL offset = 0x0000 +typedef uint32_t ttf_Offset24; // 24-bit offset to a table, same as uint24, NULL offset = 0x000000 +typedef uint32_t ttf_Offset32; // Long offset to a table, same as uint32, NULL offset = 0x00000000 + +typedef uint32_t ttf_Version16Dot16; // Packed 32-bit value with major and minor version numbers + +class TrueTypeFileReader +{ +public: + TrueTypeFileReader() = default; + TrueTypeFileReader(const void* data, size_t size) : data(static_cast(data)), size(size) { } + + bool IsEndOfData() const { return pos == size; } + + ttf_uint8 ReadUInt8(); + ttf_uint16 ReadUInt16(); + ttf_uint24 ReadUInt24(); + ttf_uint32 ReadUInt32(); + ttf_int8 ReadInt8(); + ttf_int16 ReadInt16(); + ttf_int32 ReadInt32(); + ttf_Fixed ReadFixed(); + ttf_UFWORD ReadUFWORD(); + ttf_FWORD ReadFWORD(); + ttf_F2DOT14 ReadF2DOT14(); + ttf_LONGDATETIME ReadLONGDATETIME(); + ttf_Tag ReadTag(); + ttf_Offset16 ReadOffset16(); + ttf_Offset24 ReadOffset24(); + ttf_Offset32 ReadOffset32(); + ttf_Version16Dot16 ReadVersion16Dot16(); + + void Seek(size_t newpos); + void Read(void* output, size_t count); + +private: + const uint8_t* data = nullptr; + size_t size = 0; + size_t pos = 0; +}; + +struct TTF_TableRecord +{ + ttf_Tag tableTag = {}; + ttf_uint32 checksum = {}; + ttf_Offset32 offset = {}; + ttf_uint32 length = {}; + + void Load(TrueTypeFileReader& reader); + + TrueTypeFileReader GetReader(const void* filedata, size_t filesize) const + { + if ((size_t)offset + length > filesize) + throw std::runtime_error("Invalid TTF table directory record"); + + return TrueTypeFileReader((uint8_t*)filedata + offset, length); + } +}; + +struct TTF_TableDirectory +{ + ttf_uint32 sfntVersion = {}; + ttf_uint16 numTables = {}; + std::vector tableRecords; + + // To do: Apple TTF fonts allow 'true' and 'typ1' for sfntVersion as well. + bool ContainsTTFOutlines() const { return sfntVersion == 0x00010000; } + bool ContainsCFFData() const { return sfntVersion == 0x4F54544F; } + + void Load(TrueTypeFileReader& reader); + + const TTF_TableRecord& GetRecord(const char* tag) const + { + for (const auto& record : tableRecords) + { + if (memcmp(record.tableTag.data(), tag, 4) == 0) + { + return record; + } + } + throw std::runtime_error(std::string("Could not find required '") + tag + "' table entry"); + } + + TrueTypeFileReader GetReader(const void* filedata, size_t filesize, const char* tag) const + { + return GetRecord(tag).GetReader(filedata, filesize); + } +}; + +struct TTC_Header +{ + ttf_Tag ttcTag = {}; + ttf_uint16 majorVersion = {}; + ttf_uint16 minorVersion = {}; + ttf_uint32 numFonts = {}; + std::vector tableDirectoryOffsets; + + // majorVersion = 2, minorVersion = 0: + ttf_uint32 dsigTag = {}; + ttf_uint32 dsigLength = {}; + ttf_uint32 dsigOffset = {}; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_EncodingRecord +{ + ttf_uint16 platformID = {}; + ttf_uint16 encodingID = {}; + ttf_Offset32 subtableOffset = {}; +}; + +struct TTF_CMap // 'cmap' Character to glyph mapping +{ + ttf_uint16 version = {}; + ttf_uint16 numTables = {}; + std::vector encodingRecords; // [numTables] + + void Load(TrueTypeFileReader& reader); + + TTF_EncodingRecord GetEncoding(ttf_uint16 platformID, ttf_uint16 encodingID) const + { + for (const TTF_EncodingRecord& record : encodingRecords) + { + if (record.platformID == platformID && record.encodingID == encodingID) + { + return record; + } + } + return {}; + } +}; + +struct TTF_GlyphRange +{ + ttf_uint32 startCharCode = 0; + ttf_uint32 endCharCode = 0; + ttf_uint32 startGlyphID = 0; +}; + +struct TTF_CMapSubtable0 // Byte encoding table +{ + ttf_uint16 length = {}; + ttf_uint16 language = {}; + std::vector glyphIdArray; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_CMapSubtable4 // Segment mapping to delta values (U+0000 to U+FFFF) +{ + ttf_uint16 length = {}; + ttf_uint16 language = {}; + ttf_uint16 segCount = {}; + std::vector endCode; + ttf_uint16 reservedPad = {}; + std::vector startCode; + std::vector idDelta; + std::vector idRangeOffsets; + std::vector glyphIdArray; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_CMapSubtable12 // Segmented coverage (U+0000 to U+10FFFF) +{ + ttf_uint16 reserved; + ttf_uint32 length; + ttf_uint32 language; + ttf_uint32 numGroups; + std::vector groups; + + void Load(TrueTypeFileReader& reader); +}; + +typedef TTF_CMapSubtable12 TTF_CMapSubtable13; // Many-to-one range mappings + +struct TTF_FontHeader // 'head' Font header +{ + ttf_uint16 majorVersion = {}; + ttf_uint16 minorVersion = {}; + ttf_Fixed fontRevision = {}; + ttf_uint32 checksumAdjustment = {}; + ttf_uint32 magicNumber = {}; + ttf_uint16 flags = {}; + ttf_uint16 unitsPerEm = {}; + ttf_LONGDATETIME created = {}; + ttf_LONGDATETIME modified = {}; + ttf_int16 xMin = {}; + ttf_int16 yMin = {}; + ttf_int16 xMax = {}; + ttf_int16 yMax = {}; + ttf_uint16 macStyle = {}; + ttf_uint16 lowestRecPPEM = {}; + ttf_int16 fontDirectionHint = {}; + ttf_int16 indexToLocFormat = {}; + ttf_int16 glyphDataFormat = {}; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_HorizontalHeader // 'hhea' Horizontal header +{ + ttf_uint16 majorVersion = {}; + ttf_uint16 minorVersion = {}; + ttf_FWORD ascender = {}; + ttf_FWORD descender = {}; + ttf_FWORD lineGap = {}; + ttf_UFWORD advanceWidthMax = {}; + ttf_FWORD minLeftSideBearing = {}; + ttf_FWORD minRightSideBearing = {}; + ttf_FWORD xMaxExtent = {}; + ttf_int16 caretSlopeRise = {}; + ttf_int16 caretSlopeRun = {}; + ttf_int16 caretOffset = {}; + ttf_int16 reserved0 = {}; + ttf_int16 reserved1 = {}; + ttf_int16 reserved2 = {}; + ttf_int16 reserved3 = {}; + ttf_int16 metricDataFormat = {}; + ttf_uint16 numberOfHMetrics = {}; + + void Load(TrueTypeFileReader& reader); +}; + +struct TTF_MaximumProfile; + +struct TTF_HorizontalMetrics // 'hmtx' Horizontal metrics +{ + struct longHorMetric + { + ttf_uint16 advanceWidth = {}; + ttf_int16 lsb = {}; + }; + std::vector hMetrics; // [hhea.numberOfHMetrics] + std::vector leftSideBearings; // [maxp.numGlyphs - hhea.numberOfHMetrics] + + void Load(const TTF_HorizontalHeader& hhea, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader); +}; + +struct TTF_MaximumProfile // 'maxp' Maximum profile +{ + // v0.5 and v1: + ttf_Version16Dot16 version = {}; + ttf_uint16 numGlyphs = {}; + + // v1 only: + ttf_uint16 maxPoints = {}; + ttf_uint16 maxContours = {}; + ttf_uint16 maxCompositePoints = {}; + ttf_uint16 maxCompositeContours = {}; + ttf_uint16 maxZones = {}; + ttf_uint16 maxTwilightPoints = {}; + ttf_uint16 maxStorage = {}; + ttf_uint16 maxFunctionDefs = {}; + ttf_uint16 maxInstructionDefs = {}; + ttf_uint16 maxStackElements = {}; + ttf_uint16 maxSizeOfInstructions = {}; + ttf_uint16 maxComponentElements = {}; + ttf_uint16 maxComponentDepth = {}; + + void Load(TrueTypeFileReader& reader); +}; + +class TTCFontName +{ +public: + std::string FamilyName; // Arial + std::string SubfamilyName; // Regular + std::string FullName; // Arial Regular + std::string UniqueID; + std::string VersionString; + std::string PostscriptName; +}; + +struct TTF_NamingTable // 'name' Naming table +{ + struct NameRecord + { + ttf_uint16 platformID = {}; + ttf_uint16 encodingID = {}; + ttf_uint16 languageID = {}; + ttf_uint16 nameID = {}; + ttf_uint16 length = {}; + ttf_Offset16 stringOffset = {}; + std::string text; + }; + + struct LangTagRecord + { + ttf_uint16 length = {}; + ttf_Offset16 langTagOffset = {}; + }; + + // v0 and v1: + ttf_uint16 version = {}; + ttf_uint16 count = {}; + ttf_Offset16 storageOffset = {}; + std::vector nameRecord; // [count] + + // v1 only: + ttf_uint16 langTagCount = {}; + std::vector langTagRecord; // [langTagCount] + + void Load(TrueTypeFileReader& reader); + + TTCFontName GetFontName() const; +}; + +struct TTF_OS2Windows // 'OS/2' Windows specific metrics +{ + ttf_uint16 version = {}; + ttf_int16 xAvgCharWidth = {}; + ttf_uint16 usWeightClass = {}; + ttf_uint16 usWidthClass = {}; + ttf_uint16 fsType = {}; + ttf_int16 ySubscriptXSize = {}; + ttf_int16 ySubscriptYSize = {}; + ttf_int16 ySubscriptXOffset = {}; + ttf_int16 ySubscriptYOffset = {}; + ttf_int16 ySuperscriptXSize = {}; + ttf_int16 ySuperscriptYSize = {}; + ttf_int16 ySuperscriptXOffset = {}; + ttf_int16 ySuperscriptYOffset = {}; + ttf_int16 yStrikeoutSize = {}; + ttf_int16 yStrikeoutPosition = {}; + ttf_int16 sFamilyClass = {}; + ttf_uint8 panose[10] = {}; + ttf_uint32 ulUnicodeRange1 = {}; + ttf_uint32 ulUnicodeRange2 = {}; + ttf_uint32 ulUnicodeRange3 = {}; + ttf_uint32 ulUnicodeRange4 = {}; + ttf_Tag achVendID = {}; + ttf_uint16 fsSelection = {}; + ttf_uint16 usFirstCharIndex = {}; + ttf_uint16 usLastCharIndex = {}; + ttf_int16 sTypoAscender = {}; + ttf_int16 sTypoDescender = {}; + ttf_int16 sTypoLineGap = {}; + ttf_uint16 usWinAscent = {}; // may be missing in v0 due to bugs in Apple docs + ttf_uint16 usWinDescent = {}; // may be missing in v0 due to bugs in Apple docs + ttf_uint32 ulCodePageRange1 = {}; // v1 + ttf_uint32 ulCodePageRange2 = {}; + ttf_int16 sxHeight = {}; // v2, v3 and v4 + ttf_int16 sCapHeight = {}; + ttf_uint16 usDefaultChar = {}; + ttf_uint16 usBreakChar = {}; + ttf_uint16 usMaxContext = {}; + ttf_uint16 usLowerOpticalPointSize = {}; // v5 + ttf_uint16 usUpperOpticalPointSize = {}; + + void Load(TrueTypeFileReader& reader); +}; + +// Simple glyph flags: +#define TTF_ON_CURVE_POINT 0x01 +#define TTF_X_SHORT_VECTOR 0x02 +#define TTF_Y_SHORT_VECTOR 0x04 +#define TTF_REPEAT_FLAG 0x08 +#define TTF_X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR 0x10 +#define TTF_Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR 0x20 +#define TTF_OVERLAP_SIMPLE = 0x40 + +// Composite glyph flags: +#define TTF_ARG_1_AND_2_ARE_WORDS 0x0001 +#define TTF_ARGS_ARE_XY_VALUES 0x0002 +#define TTF_ROUND_XY_TO_GRID 0x0004 +#define TTF_WE_HAVE_A_SCALE 0x0008 +#define TTF_MORE_COMPONENTS 0x0020 +#define TTF_WE_HAVE_AN_X_AND_Y_SCALE 0x0040 +#define TTF_WE_HAVE_A_TWO_BY_TWO 0x0080 +#define TTF_WE_HAVE_INSTRUCTIONS 0x0100 +#define TTF_USE_MY_METRICS 0x0200 +#define TTF_OVERLAP_COMPOUND 0x0400 +#define TTF_SCALED_COMPONENT_OFFSET 0x0800 +#define TTF_UNSCALED_COMPONENT_OFFSET 0x1000 + +struct TTF_IndexToLocation // 'loca' Index to location +{ + std::vector offsets; + + void Load(const TTF_FontHeader& head, const TTF_MaximumProfile& maxp, TrueTypeFileReader& reader); +}; + +struct TTF_Point +{ + float x; + float y; +}; + +struct TTF_SimpleGlyph +{ + std::vector endPtsOfContours; + std::vector flags; + std::vector points; +}; + +class TrueTypeGlyph +{ +public: + int advanceWidth = 0; + int leftSideBearing = 0; + int yOffset = 0; + + int width = 0; + int height = 0; + std::unique_ptr grayscale; +}; + +class TrueTypeTextMetrics +{ +public: + double ascender = 0.0; + double descender = 0.0; + double lineGap = 0.0; +}; + +class TrueTypeFontFileData +{ +public: + TrueTypeFontFileData(std::vector& data) : dataVector(std::move(data)) + { + dataPtr = dataVector.data(); + dataSize = dataVector.size(); + } + + TrueTypeFontFileData(const void* data, size_t size, bool copyData = true) + { + dataSize = size; + if (copyData) + { + dataPtr = new uint8_t[size]; + deleteDataPtr = true; + memcpy(const_cast(dataPtr), data, size); + } + else + { + dataPtr = data; + } + } + + ~TrueTypeFontFileData() + { + if (deleteDataPtr) + { + delete[](uint8_t*)dataPtr; + } + dataPtr = nullptr; + } + + const void* data() const { return dataPtr; } + size_t size() const { return dataSize; } + +private: + std::vector dataVector; + const void* dataPtr = nullptr; + size_t dataSize = 0; + bool deleteDataPtr = false; + + TrueTypeFontFileData(const TrueTypeFontFileData&) = delete; + TrueTypeFontFileData& operator=(const TrueTypeFontFileData&) = delete; +}; + +class TrueTypeFont +{ +public: + TrueTypeFont(std::shared_ptr& data, int ttcFontIndex = 0); + + static std::vector GetFontNames(const std::shared_ptr& data); + + TrueTypeTextMetrics GetTextMetrics(double height) const; + uint32_t GetGlyphIndex(uint32_t codepoint) const; + TrueTypeGlyph LoadGlyph(uint32_t glyphIndex, double height) const; + +private: + void LoadCharacterMapEncoding(TrueTypeFileReader& reader); + void LoadGlyph(TTF_SimpleGlyph& glyph, uint32_t glyphIndex, int compositeDepth = 0) const; + static float F2DOT14_ToFloat(ttf_F2DOT14 v); + + std::shared_ptr data; + + TTC_Header ttcHeader; + TTF_TableDirectory directory; + + // Required for all OpenType fonts: + TTF_CMap cmap; + TTF_FontHeader head; + TTF_HorizontalHeader hhea; + TTF_HorizontalMetrics hmtx; + TTF_MaximumProfile maxp; + TTF_NamingTable name; + TTF_OS2Windows os2; + + // TrueType outlines: + TTF_TableRecord glyf; // Parsed on a per glyph basis using offsets from other tables + TTF_IndexToLocation loca; + + std::vector Ranges; + std::vector ManyToOneRanges; +}; diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 9c6dc6319..7ddc1ca59 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -172,6 +172,11 @@ void Widget::Show() { DispWindow->Show(); } + else if (HiddenFlag) + { + HiddenFlag = false; + Update(); + } } void Widget::ShowFullscreen() @@ -213,6 +218,11 @@ void Widget::Hide() if (DispWindow) DispWindow->Hide(); } + else if (!HiddenFlag) + { + HiddenFlag = true; + Update(); + } } void Widget::ActivateWindow() @@ -299,7 +309,7 @@ void Widget::Paint(Canvas* canvas) OnPaint(canvas); for (Widget* w = FirstChild(); w != nullptr; w = w->NextSibling()) { - if (w->Type == WidgetType::Child) + if (w->Type == WidgetType::Child && !w->HiddenFlag) w->Paint(canvas); } canvas->setOrigin(oldOrigin); @@ -323,9 +333,21 @@ bool Widget::IsEnabled() return true; } +bool Widget::IsHidden() +{ + return !IsVisible(); +} + bool Widget::IsVisible() { - return true; + if (Type != WidgetType::Child) + { + return true; // DispWindow->IsVisible(); + } + else + { + return !HiddenFlag; + } } void Widget::SetFocus() @@ -427,9 +449,9 @@ Widget* Widget::Window() return nullptr; } -Canvas* Widget::GetCanvas() +Canvas* Widget::GetCanvas() const { - for (Widget* w = this; w != nullptr; w = w->Parent()) + for (const Widget* w = this; w != nullptr; w = w->Parent()) { if (w->DispCanvas) return w->DispCanvas.get(); @@ -441,7 +463,7 @@ Widget* Widget::ChildAt(const Point& pos) { for (Widget* cur = LastChild(); cur != nullptr; cur = cur->PrevSibling()) { - if (cur->FrameGeometry.contains(pos)) + if (!cur->HiddenFlag && cur->FrameGeometry.contains(pos)) { Widget* cur2 = cur->ChildAt(pos - cur->ContentGeometry.topLeft()); return cur2 ? cur2 : cur; @@ -523,12 +545,26 @@ void Widget::OnWindowMouseMove(const Point& pos) if (HoverWidget != widget) { if (HoverWidget) - HoverWidget->OnMouseLeave(); + { + for (Widget* w = HoverWidget; w != widget && w != this; w = w->Parent()) + { + Widget* p = w->Parent(); + if (!w->FrameGeometry.contains(p->MapFrom(this, pos))) + { + w->OnMouseLeave(); + } + } + } HoverWidget = widget; } DispWindow->SetCursor(widget->CurrentCursor); - widget->OnMouseMove(widget->MapFrom(this, pos)); + + do + { + widget->OnMouseMove(widget->MapFrom(this, pos)); + widget = widget->Parent(); + } while (widget != this); } } @@ -543,7 +579,13 @@ void Widget::OnWindowMouseDown(const Point& pos, EInputKey key) Widget* widget = ChildAt(pos); if (!widget) widget = this; - widget->OnMouseDown(widget->MapFrom(this, pos), key); + while (widget) + { + bool stopPropagation = widget->OnMouseDown(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } } } @@ -558,7 +600,13 @@ void Widget::OnWindowMouseDoubleclick(const Point& pos, EInputKey key) Widget* widget = ChildAt(pos); if (!widget) widget = this; - widget->OnMouseDoubleclick(widget->MapFrom(this, pos), key); + while (widget) + { + bool stopPropagation = widget->OnMouseDoubleclick(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } } } @@ -573,7 +621,13 @@ void Widget::OnWindowMouseUp(const Point& pos, EInputKey key) Widget* widget = ChildAt(pos); if (!widget) widget = this; - widget->OnMouseUp(widget->MapFrom(this, pos), key); + while (widget) + { + bool stopPropagation = widget->OnMouseUp(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } } } @@ -588,7 +642,13 @@ void Widget::OnWindowMouseWheel(const Point& pos, EInputKey key) Widget* widget = ChildAt(pos); if (!widget) widget = this; - widget->OnMouseWheel(widget->MapFrom(this, pos), key); + while (widget) + { + bool stopPropagation = widget->OnMouseWheel(widget->MapFrom(this, pos), key); + if (stopPropagation || widget == this) + break; + widget = widget->Parent(); + } } } diff --git a/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp b/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp index deed9d507..6cf90badf 100644 --- a/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp +++ b/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp @@ -56,19 +56,21 @@ void CheckboxLabel::OnPaint(Canvas* canvas) canvas->drawText(Point(14.0, GetHeight() - 5.0), Colorf::fromRgba8(255, 255, 255), text); } -void CheckboxLabel::OnMouseDown(const Point& pos, int key) +bool CheckboxLabel::OnMouseDown(const Point& pos, int key) { mouseDownActive = true; SetFocus(); + return true; } -void CheckboxLabel::OnMouseUp(const Point& pos, int key) +bool CheckboxLabel::OnMouseUp(const Point& pos, int key) { if (mouseDownActive) { Toggle(); } mouseDownActive = false; + return true; } void CheckboxLabel::OnMouseLeave() diff --git a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp index bab04c3fb..34391fd19 100644 --- a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp +++ b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp @@ -123,11 +123,11 @@ void LineEdit::SetReadOnly(bool enable) } } -void LineEdit::SetAlignment(Alignment alignment) +void LineEdit::SetAlignment(Alignment newalignment) { - if (alignment != alignment) + if (alignment != newalignment) { - alignment = alignment; + alignment = newalignment; Update(); } } @@ -301,7 +301,7 @@ void LineEdit::OnMouseMove(const Point& pos) } } -void LineEdit::OnMouseDown(const Point& pos, int key) +bool LineEdit::OnMouseDown(const Point& pos, int key) { if (key == IK_LeftMouse) { @@ -318,13 +318,15 @@ void LineEdit::OnMouseDown(const Point& pos, int key) } Update(); } + return true; } -void LineEdit::OnMouseDoubleclick(const Point& pos, int key) +bool LineEdit::OnMouseDoubleclick(const Point& pos, int key) { + return true; } -void LineEdit::OnMouseUp(const Point& pos, int key) +bool LineEdit::OnMouseUp(const Point& pos, int key) { if (mouse_selecting && key == IK_LeftMouse) { @@ -346,6 +348,7 @@ void LineEdit::OnMouseUp(const Point& pos, int key) Update(); } } + return true; } void LineEdit::OnKeyChar(std::string chars) diff --git a/libraries/ZWidget/src/widgets/listview/listview.cpp b/libraries/ZWidget/src/widgets/listview/listview.cpp index fe73ebdfa..717f95827 100644 --- a/libraries/ZWidget/src/widgets/listview/listview.cpp +++ b/libraries/ZWidget/src/widgets/listview/listview.cpp @@ -27,6 +27,7 @@ void ListView::SetSelectedItem(int index) if (selectedItem != index && index >= 0 && index < items.size()) { selectedItem = index; + if (OnChanged) OnChanged(selectedItem); Update(); } } @@ -95,7 +96,7 @@ void ListView::OnPaintFrame(Canvas* canvas) canvas->fillRect(Rect::xywh(w - 1.0, 0.0, 1.0, h - 0.0), bordercolor); } -void ListView::OnMouseDown(const Point& pos, int key) +bool ListView::OnMouseDown(const Point& pos, int key) { SetFocus(); @@ -108,17 +109,19 @@ void ListView::OnMouseDown(const Point& pos, int key) ScrollToItem(selectedItem); } } + return true; } -void ListView::OnMouseDoubleclick(const Point& pos, int key) +bool ListView::OnMouseDoubleclick(const Point& pos, int key) { if (key == IK_LeftMouse) { Activate(); } + return true; } -void ListView::OnMouseWheel(const Point& pos, EInputKey key) +bool ListView::OnMouseWheel(const Point& pos, EInputKey key) { if (key == IK_MouseWheelUp) { @@ -126,8 +129,9 @@ void ListView::OnMouseWheel(const Point& pos, EInputKey key) } else if (key == IK_MouseWheelDown) { - scrollbar->SetPosition(std::max(scrollbar->GetPosition() + 20.0, scrollbar->GetMax())); + scrollbar->SetPosition(std::min(scrollbar->GetPosition() + 20.0, scrollbar->GetMax())); } + return true; } void ListView::OnKeyDown(EInputKey key) diff --git a/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp b/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp index 2e2e348cb..d838fe424 100644 --- a/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp +++ b/libraries/ZWidget/src/widgets/pushbutton/pushbutton.cpp @@ -57,14 +57,15 @@ void PushButton::OnMouseMove(const Point& pos) } } -void PushButton::OnMouseDown(const Point& pos, int key) +bool PushButton::OnMouseDown(const Point& pos, int key) { SetFocus(); buttonDown = true; Update(); + return true; } -void PushButton::OnMouseUp(const Point& pos, int key) +bool PushButton::OnMouseUp(const Point& pos, int key) { if (buttonDown) { @@ -73,6 +74,7 @@ void PushButton::OnMouseUp(const Point& pos, int key) Repaint(); Click(); } + return true; } void PushButton::OnMouseLeave() diff --git a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp index 146298163..489a66343 100644 --- a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp +++ b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp @@ -169,7 +169,7 @@ void Scrollbar::OnMouseMove(const Point& pos) Update(); } -void Scrollbar::OnMouseDown(const Point& pos, int key) +bool Scrollbar::OnMouseDown(const Point& pos, int key) { mouse_drag_start_pos = pos; @@ -254,9 +254,10 @@ void Scrollbar::OnMouseDown(const Point& pos, int key) Update(); CaptureMouse(); + return true; } -void Scrollbar::OnMouseUp(const Point& pos, int key) +bool Scrollbar::OnMouseUp(const Point& pos, int key) { if (mouse_down_mode == mouse_down_thumb_drag) { @@ -269,6 +270,7 @@ void Scrollbar::OnMouseUp(const Point& pos, int key) Update(); ReleaseMouseCapture(); + return true; } void Scrollbar::OnMouseLeave() diff --git a/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp new file mode 100644 index 000000000..cbac877aa --- /dev/null +++ b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp @@ -0,0 +1,358 @@ + +#include "widgets/tabwidget/tabwidget.h" +#include "widgets/textlabel/textlabel.h" +#include "widgets/imagebox/imagebox.h" +#include + +TabWidget::TabWidget(Widget* parent) : Widget(parent) +{ + Bar = new TabBar(this); + PageStack = new TabWidgetStack(this); + + Bar->OnCurrentChanged = [=]() { OnBarCurrentChanged(); }; +} + +int TabWidget::AddTab(Widget* page, const std::string& label) +{ + return AddTab(page, nullptr, label); +} + +int TabWidget::AddTab(Widget* page, const std::shared_ptr& icon, const std::string& label) +{ + int pageIndex = Bar->AddTab(label); + page->SetParent(PageStack); + page->SetVisible(false); + Pages.push_back(page); + if (Pages.size() == 1) + { + PageStack->SetCurrentWidget(page); + } + return pageIndex; +} + +void TabWidget::SetTabText(int index, const std::string& text) +{ + Bar->SetTabText(index, text); +} + +void TabWidget::SetTabText(Widget* page, const std::string& text) +{ + int index = GetPageIndex(page); + if (index != -1) + SetTabText(index, text); +} + +void TabWidget::SetTabIcon(int index, const std::shared_ptr& icon) +{ + Bar->SetTabIcon(index, icon); +} + +void TabWidget::SetTabIcon(Widget* page, const std::shared_ptr& icon) +{ + int index = GetPageIndex(page); + if (index != -1) + SetTabIcon(index, icon); +} + +int TabWidget::GetCurrentIndex() const +{ + return Bar->GetCurrentIndex(); +} + +Widget* TabWidget::GetCurrentWidget() const +{ + return Pages[Bar->GetCurrentIndex()]; +} + +void TabWidget::SetCurrentIndex(int pageIndex) +{ + if (Bar->GetCurrentIndex() != pageIndex) + { + Bar->SetCurrentIndex(pageIndex); + PageStack->SetCurrentWidget(Pages[pageIndex]); + } +} + +void TabWidget::SetCurrentWidget(Widget* pageWidget) +{ + int pageIndex = GetPageIndex(pageWidget); + if (pageIndex != -1) + SetCurrentIndex(pageIndex); +} + +int TabWidget::GetPageIndex(Widget* pageWidget) const +{ + for (size_t i = 0; i < Pages.size(); i++) + { + if (Pages[i] == pageWidget) + return i; + } + return -1; +} + +void TabWidget::OnBarCurrentChanged() +{ + int pageIndex = Bar->GetCurrentIndex(); + PageStack->SetCurrentWidget(Pages[pageIndex]); + if (OnCurrentChanged) + OnCurrentChanged(); +} + +void TabWidget::OnPaintFrame(Canvas* canvas) +{ +} + +void TabWidget::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double barHeight = Bar->GetPreferredHeight(); + Bar->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, barHeight)); + PageStack->SetFrameGeometry(Rect::xywh(0.0, barHeight, w, std::max(h - barHeight, 0.0))); +} + +///////////////////////////////////////////////////////////////////////////// + +TabBar::TabBar(Widget* parent) : Widget(parent) +{ + SetNoncontentSizes(20.0, 0.0, 20.0, 0.0); +} + +int TabBar::AddTab(const std::string& label) +{ + return AddTab(nullptr, label); +} + +int TabBar::AddTab(const std::shared_ptr& icon, const std::string& label) +{ + TabBarTab* tab = new TabBarTab(this); + tab->SetIcon(icon); + tab->SetText(label); + tab->OnClick = [=]() { OnTabClicked(tab); }; + int pageIndex = Tabs.size(); + Tabs.push_back(tab); + if (CurrentIndex == -1) + SetCurrentIndex(pageIndex); + OnGeometryChanged(); + return pageIndex; +} + +void TabBar::SetTabText(int index, const std::string& text) +{ + Tabs[index]->SetText(text); + OnGeometryChanged(); +} + +void TabBar::SetTabIcon(int index, const std::shared_ptr& icon) +{ + Tabs[index]->SetIcon(icon); + OnGeometryChanged(); +} + +int TabBar::GetCurrentIndex() const +{ + return CurrentIndex; +} + +void TabBar::SetCurrentIndex(int pageIndex) +{ + if (CurrentIndex != pageIndex) + { + if (CurrentIndex != -1) + Tabs[CurrentIndex]->SetCurrent(false); + CurrentIndex = pageIndex; + if (CurrentIndex != -1) + Tabs[CurrentIndex]->SetCurrent(true); + } +} + +void TabBar::OnTabClicked(TabBarTab* tab) +{ + int pageIndex = GetTabIndex(tab); + if (CurrentIndex != pageIndex) + { + SetCurrentIndex(pageIndex); + if (OnCurrentChanged) + OnCurrentChanged(); + } +} + +int TabBar::GetTabIndex(TabBarTab* tab) +{ + for (size_t i = 0; i < Tabs.size(); i++) + { + if (Tabs[i] == tab) + return i; + } + return -1; +} + +void TabBar::OnPaintFrame(Canvas* canvas) +{ + double w = GetFrameGeometry().width; + double h = GetFrameGeometry().height; + canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(38, 38, 38)); +} + +void TabBar::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double x = 0.0; + for (TabBarTab* tab : Tabs) + { + double tabWidth = tab->GetNoncontentLeft() + tab->GetPreferredWidth() + tab->GetNoncontentRight(); + tab->SetFrameGeometry(Rect::xywh(x, 0.0, tabWidth, h)); + x += tabWidth; + } +} + +///////////////////////////////////////////////////////////////////////////// + +TabBarTab::TabBarTab(Widget* parent) : Widget(parent) +{ + SetNoncontentSizes(15.0, 0.0, 15.0, 0.0); +} + +void TabBarTab::SetText(const std::string& text) +{ + if (!text.empty()) + { + if (!Label) + { + Label = new TextLabel(this); + OnGeometryChanged(); + } + Label->SetText(text); + } + else + { + delete Label; + Label = nullptr; + OnGeometryChanged(); + } +} + +void TabBarTab::SetIcon(const std::shared_ptr& image) +{ + if (image) + { + if (!Icon) + { + Icon = new ImageBox(this); + OnGeometryChanged(); + } + Icon->SetImage(image); + } + else + { + delete Icon; + Icon = nullptr; + OnGeometryChanged(); + } +} + +void TabBarTab::SetCurrent(bool value) +{ + if (IsCurrent != value) + { + IsCurrent = value; + Update(); + } +} + +double TabBarTab::GetPreferredWidth() const +{ + double x = Icon ? 32.0 + 5.0 : 0.0; + if (Label) x += Label->GetPreferredWidth(); + return x; +} + +void TabBarTab::OnPaintFrame(Canvas* canvas) +{ + double w = GetFrameGeometry().width; + double h = GetFrameGeometry().height; + if (IsCurrent) + { + canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(51, 51, 51)); + } + else if (hot) + { + canvas->fillRect(Rect::xywh(0.0, 0.0, w, h), Colorf::fromRgba8(45, 45, 45)); + } +} + +void TabBarTab::OnGeometryChanged() +{ + double x = 0.0; + double w = GetWidth(); + double h = GetHeight(); + if (Icon) + { + Icon->SetFrameGeometry(Rect::xywh(x, (h - 32.0) * 0.5, 32.0, 32.0)); + x = 32.0 + 5.0; + } + if (Label) + { + Label->SetFrameGeometry(Rect::xywh(x, (h - Label->GetPreferredHeight()) * 0.5, std::max(w - x, 0.0), Label->GetPreferredHeight())); + } +} + +void TabBarTab::OnMouseMove(const Point& pos) +{ + if (!hot) + { + hot = true; + Update(); + } +} + +bool TabBarTab::OnMouseDown(const Point& pos, int key) +{ + if (OnClick) + OnClick(); + return true; +} + +bool TabBarTab::OnMouseUp(const Point& pos, int key) +{ + return true; +} + +void TabBarTab::OnMouseLeave() +{ + hot = false; + Update(); +} + +///////////////////////////////////////////////////////////////////////////// + +TabWidgetStack::TabWidgetStack(Widget* parent) : Widget(parent) +{ + SetNoncontentSizes(20.0, 5.0, 20.0, 5.0); +} + +void TabWidgetStack::SetCurrentWidget(Widget* widget) +{ + if (widget != CurrentWidget) + { + if (CurrentWidget) + CurrentWidget->SetVisible(false); + CurrentWidget = widget; + if (CurrentWidget) + { + CurrentWidget->SetVisible(true); + OnGeometryChanged(); + } + } +} + +void TabWidgetStack::OnPaintFrame(Canvas* canvas) +{ +} + +void TabWidgetStack::OnGeometryChanged() +{ + if (CurrentWidget) + CurrentWidget->SetFrameGeometry(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight())); +} diff --git a/libraries/ZWidget/src/widgets/textedit/textedit.cpp b/libraries/ZWidget/src/widgets/textedit/textedit.cpp index 9ddb5fba4..dca840a1b 100644 --- a/libraries/ZWidget/src/widgets/textedit/textedit.cpp +++ b/libraries/ZWidget/src/widgets/textedit/textedit.cpp @@ -286,7 +286,7 @@ void TextEdit::OnMouseMove(const Point& pos) } } -void TextEdit::OnMouseDown(const Point& pos, int key) +bool TextEdit::OnMouseDown(const Point& pos, int key) { if (key == IK_LeftMouse) { @@ -298,13 +298,15 @@ void TextEdit::OnMouseDown(const Point& pos, int key) Update(); } + return true; } -void TextEdit::OnMouseDoubleclick(const Point& pos, int key) +bool TextEdit::OnMouseDoubleclick(const Point& pos, int key) { + return true; } -void TextEdit::OnMouseUp(const Point& pos, int key) +bool TextEdit::OnMouseUp(const Point& pos, int key) { if (mouse_selecting && key == IK_LeftMouse) { @@ -326,6 +328,7 @@ void TextEdit::OnMouseUp(const Point& pos, int key) Update(); } } + return true; } void TextEdit::OnKeyChar(std::string chars) diff --git a/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp b/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp index 3c034ddb2..55518f0a4 100644 --- a/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp +++ b/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp @@ -33,6 +33,12 @@ TextLabelAlignment TextLabel::GetTextAlignment() const return textAlignment; } +double TextLabel::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(text).width; +} + double TextLabel::GetPreferredHeight() const { return 20.0; diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 32d0659ba..068d579d9 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1022,6 +1022,10 @@ set (PCH_SOURCES core/console/c_notifybuffer.cpp core/console/d_event.cpp launcher/launcherwindow.cpp + launcher/launcherbanner.cpp + launcher/launcherbuttonbar.cpp + launcher/playgamepage.cpp + launcher/settingspage.cpp common/audio/sound/i_sound.cpp common/audio/sound/oalsound.cpp diff --git a/source/common/audio/sound/alext.h b/source/common/audio/sound/alext.h deleted file mode 100644 index edfae4735..000000000 --- a/source/common/audio/sound/alext.h +++ /dev/null @@ -1,643 +0,0 @@ -/** - * OpenAL cross platform audio library - * Copyright (C) 2008 by authors. - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * Or go to http://www.gnu.org/copyleft/lgpl.html - */ - -#ifndef AL_ALEXT_H -#define AL_ALEXT_H - -#include -/* Define int64 and uint64 types */ -#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ - (defined(__cplusplus) && __cplusplus >= 201103L) -#include -typedef int64_t _alsoft_int64_t; -typedef uint64_t _alsoft_uint64_t; -#elif defined(_WIN32) -typedef __int64 _alsoft_int64_t; -typedef unsigned __int64 _alsoft_uint64_t; -#else -/* Fallback if nothing above works */ -#include -typedef int64_t _alsoft_int64_t; -typedef uint64_t _alsoft_uint64_t; -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef AL_LOKI_IMA_ADPCM_format -#define AL_LOKI_IMA_ADPCM_format 1 -#define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 -#define AL_FORMAT_IMA_ADPCM_STEREO16_EXT 0x10001 -#endif - -#ifndef AL_LOKI_WAVE_format -#define AL_LOKI_WAVE_format 1 -#define AL_FORMAT_WAVE_EXT 0x10002 -#endif - -#ifndef AL_EXT_vorbis -#define AL_EXT_vorbis 1 -#define AL_FORMAT_VORBIS_EXT 0x10003 -#endif - -#ifndef AL_LOKI_quadriphonic -#define AL_LOKI_quadriphonic 1 -#define AL_FORMAT_QUAD8_LOKI 0x10004 -#define AL_FORMAT_QUAD16_LOKI 0x10005 -#endif - -#ifndef AL_EXT_float32 -#define AL_EXT_float32 1 -#define AL_FORMAT_MONO_FLOAT32 0x10010 -#define AL_FORMAT_STEREO_FLOAT32 0x10011 -#endif - -#ifndef AL_EXT_double -#define AL_EXT_double 1 -#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012 -#define AL_FORMAT_STEREO_DOUBLE_EXT 0x10013 -#endif - -#ifndef AL_EXT_MULAW -#define AL_EXT_MULAW 1 -#define AL_FORMAT_MONO_MULAW_EXT 0x10014 -#define AL_FORMAT_STEREO_MULAW_EXT 0x10015 -#endif - -#ifndef AL_EXT_ALAW -#define AL_EXT_ALAW 1 -#define AL_FORMAT_MONO_ALAW_EXT 0x10016 -#define AL_FORMAT_STEREO_ALAW_EXT 0x10017 -#endif - -#ifndef ALC_LOKI_audio_channel -#define ALC_LOKI_audio_channel 1 -#define ALC_CHAN_MAIN_LOKI 0x500001 -#define ALC_CHAN_PCM_LOKI 0x500002 -#define ALC_CHAN_CD_LOKI 0x500003 -#endif - -#ifndef AL_EXT_MCFORMATS -#define AL_EXT_MCFORMATS 1 -/* Provides support for surround sound buffer formats with 8, 16, and 32-bit - * samples. - * - * QUAD8: Unsigned 8-bit, Quadraphonic (Front Left, Front Right, Rear Left, - * Rear Right). - * QUAD16: Signed 16-bit, Quadraphonic. - * QUAD32: 32-bit float, Quadraphonic. - * REAR8: Unsigned 8-bit, Rear Stereo (Rear Left, Rear Right). - * REAR16: Signed 16-bit, Rear Stereo. - * REAR32: 32-bit float, Rear Stereo. - * 51CHN8: Unsigned 8-bit, 5.1 Surround (Front Left, Front Right, Front Center, - * LFE, Side Left, Side Right). Note that some audio systems may label - * 5.1's Side channels as Rear or Surround; they are equivalent for the - * purposes of this extension. - * 51CHN16: Signed 16-bit, 5.1 Surround. - * 51CHN32: 32-bit float, 5.1 Surround. - * 61CHN8: Unsigned 8-bit, 6.1 Surround (Front Left, Front Right, Front Center, - * LFE, Rear Center, Side Left, Side Right). - * 61CHN16: Signed 16-bit, 6.1 Surround. - * 61CHN32: 32-bit float, 6.1 Surround. - * 71CHN8: Unsigned 8-bit, 7.1 Surround (Front Left, Front Right, Front Center, - * LFE, Rear Left, Rear Right, Side Left, Side Right). - * 71CHN16: Signed 16-bit, 7.1 Surround. - * 71CHN32: 32-bit float, 7.1 Surround. - */ -#define AL_FORMAT_QUAD8 0x1204 -#define AL_FORMAT_QUAD16 0x1205 -#define AL_FORMAT_QUAD32 0x1206 -#define AL_FORMAT_REAR8 0x1207 -#define AL_FORMAT_REAR16 0x1208 -#define AL_FORMAT_REAR32 0x1209 -#define AL_FORMAT_51CHN8 0x120A -#define AL_FORMAT_51CHN16 0x120B -#define AL_FORMAT_51CHN32 0x120C -#define AL_FORMAT_61CHN8 0x120D -#define AL_FORMAT_61CHN16 0x120E -#define AL_FORMAT_61CHN32 0x120F -#define AL_FORMAT_71CHN8 0x1210 -#define AL_FORMAT_71CHN16 0x1211 -#define AL_FORMAT_71CHN32 0x1212 -#endif - -#ifndef AL_EXT_MULAW_MCFORMATS -#define AL_EXT_MULAW_MCFORMATS 1 -#define AL_FORMAT_MONO_MULAW 0x10014 -#define AL_FORMAT_STEREO_MULAW 0x10015 -#define AL_FORMAT_QUAD_MULAW 0x10021 -#define AL_FORMAT_REAR_MULAW 0x10022 -#define AL_FORMAT_51CHN_MULAW 0x10023 -#define AL_FORMAT_61CHN_MULAW 0x10024 -#define AL_FORMAT_71CHN_MULAW 0x10025 -#endif - -#ifndef AL_EXT_IMA4 -#define AL_EXT_IMA4 1 -#define AL_FORMAT_MONO_IMA4 0x1300 -#define AL_FORMAT_STEREO_IMA4 0x1301 -#endif - -#ifndef AL_EXT_STATIC_BUFFER -#define AL_EXT_STATIC_BUFFER 1 -typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALint,ALenum,ALvoid*,ALsizei,ALsizei); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferDataStatic(const ALint buffer, ALenum format, ALvoid *data, ALsizei len, ALsizei freq); -#endif -#endif - -#ifndef ALC_EXT_EFX -#define ALC_EXT_EFX 1 -#include "efx.h" -#endif - -#ifndef ALC_EXT_disconnect -#define ALC_EXT_disconnect 1 -#define ALC_CONNECTED 0x313 -#endif - -#ifndef ALC_EXT_thread_local_context -#define ALC_EXT_thread_local_context 1 -typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); -typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); -#ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); -#endif -#endif - -#ifndef AL_EXT_source_distance_model -#define AL_EXT_source_distance_model 1 -#define AL_SOURCE_DISTANCE_MODEL 0x200 -#endif - -#ifndef AL_SOFT_buffer_sub_data -#define AL_SOFT_buffer_sub_data 1 -#define AL_BYTE_RW_OFFSETS_SOFT 0x1031 -#define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 -typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); -#endif -#endif - -#ifndef AL_SOFT_loop_points -#define AL_SOFT_loop_points 1 -#define AL_LOOP_POINTS_SOFT 0x2015 -#endif - -#ifndef AL_EXT_FOLDBACK -#define AL_EXT_FOLDBACK 1 -#define AL_EXT_FOLDBACK_NAME "AL_EXT_FOLDBACK" -#define AL_FOLDBACK_EVENT_BLOCK 0x4112 -#define AL_FOLDBACK_EVENT_START 0x4111 -#define AL_FOLDBACK_EVENT_STOP 0x4113 -#define AL_FOLDBACK_MODE_MONO 0x4101 -#define AL_FOLDBACK_MODE_STEREO 0x4102 -typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback); -AL_API void AL_APIENTRY alRequestFoldbackStop(void); -#endif -#endif - -#ifndef ALC_EXT_DEDICATED -#define ALC_EXT_DEDICATED 1 -#define AL_DEDICATED_GAIN 0x0001 -#define AL_EFFECT_DEDICATED_DIALOGUE 0x9001 -#define AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT 0x9000 -#endif - -#ifndef AL_SOFT_buffer_samples -#define AL_SOFT_buffer_samples 1 -/* Channel configurations */ -#define AL_MONO_SOFT 0x1500 -#define AL_STEREO_SOFT 0x1501 -#define AL_REAR_SOFT 0x1502 -#define AL_QUAD_SOFT 0x1503 -#define AL_5POINT1_SOFT 0x1504 -#define AL_6POINT1_SOFT 0x1505 -#define AL_7POINT1_SOFT 0x1506 - -/* Sample types */ -#define AL_BYTE_SOFT 0x1400 -#define AL_UNSIGNED_BYTE_SOFT 0x1401 -#define AL_SHORT_SOFT 0x1402 -#define AL_UNSIGNED_SHORT_SOFT 0x1403 -#define AL_INT_SOFT 0x1404 -#define AL_UNSIGNED_INT_SOFT 0x1405 -#define AL_FLOAT_SOFT 0x1406 -#define AL_DOUBLE_SOFT 0x1407 -#define AL_BYTE3_SOFT 0x1408 -#define AL_UNSIGNED_BYTE3_SOFT 0x1409 - -/* Storage formats */ -#define AL_MONO8_SOFT 0x1100 -#define AL_MONO16_SOFT 0x1101 -#define AL_MONO32F_SOFT 0x10010 -#define AL_STEREO8_SOFT 0x1102 -#define AL_STEREO16_SOFT 0x1103 -#define AL_STEREO32F_SOFT 0x10011 -#define AL_QUAD8_SOFT 0x1204 -#define AL_QUAD16_SOFT 0x1205 -#define AL_QUAD32F_SOFT 0x1206 -#define AL_REAR8_SOFT 0x1207 -#define AL_REAR16_SOFT 0x1208 -#define AL_REAR32F_SOFT 0x1209 -#define AL_5POINT1_8_SOFT 0x120A -#define AL_5POINT1_16_SOFT 0x120B -#define AL_5POINT1_32F_SOFT 0x120C -#define AL_6POINT1_8_SOFT 0x120D -#define AL_6POINT1_16_SOFT 0x120E -#define AL_6POINT1_32F_SOFT 0x120F -#define AL_7POINT1_8_SOFT 0x1210 -#define AL_7POINT1_16_SOFT 0x1211 -#define AL_7POINT1_32F_SOFT 0x1212 - -/* Buffer attributes */ -#define AL_INTERNAL_FORMAT_SOFT 0x2008 -#define AL_BYTE_LENGTH_SOFT 0x2009 -#define AL_SAMPLE_LENGTH_SOFT 0x200A -#define AL_SEC_LENGTH_SOFT 0x200B - -typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*); -typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data); -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); -#endif -#endif - -#ifndef AL_SOFT_direct_channels -#define AL_SOFT_direct_channels 1 -#define AL_DIRECT_CHANNELS_SOFT 0x1033 -#endif - -#ifndef ALC_SOFT_loopback -#define ALC_SOFT_loopback 1 -#define ALC_FORMAT_CHANNELS_SOFT 0x1990 -#define ALC_FORMAT_TYPE_SOFT 0x1991 - -/* Sample types */ -#define ALC_BYTE_SOFT 0x1400 -#define ALC_UNSIGNED_BYTE_SOFT 0x1401 -#define ALC_SHORT_SOFT 0x1402 -#define ALC_UNSIGNED_SHORT_SOFT 0x1403 -#define ALC_INT_SOFT 0x1404 -#define ALC_UNSIGNED_INT_SOFT 0x1405 -#define ALC_FLOAT_SOFT 0x1406 - -/* Channel configurations */ -#define ALC_MONO_SOFT 0x1500 -#define ALC_STEREO_SOFT 0x1501 -#define ALC_QUAD_SOFT 0x1503 -#define ALC_5POINT1_SOFT 0x1504 -#define ALC_6POINT1_SOFT 0x1505 -#define ALC_7POINT1_SOFT 0x1506 - -typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); -typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum); -typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei); -#ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName); -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type); -ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); -#endif -#endif - -#ifndef AL_EXT_STEREO_ANGLES -#define AL_EXT_STEREO_ANGLES 1 -#define AL_STEREO_ANGLES 0x1030 -#endif - -#ifndef AL_EXT_SOURCE_RADIUS -#define AL_EXT_SOURCE_RADIUS 1 -#define AL_SOURCE_RADIUS 0x1031 -#endif - -#ifndef AL_SOFT_source_latency -#define AL_SOFT_source_latency 1 -#define AL_SAMPLE_OFFSET_LATENCY_SOFT 0x1200 -#define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 -typedef _alsoft_int64_t ALint64SOFT; -typedef _alsoft_uint64_t ALuint64SOFT; -typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); -AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values); -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value); -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3); -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values); -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values); -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value); -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3); -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values); -#endif -#endif - -#ifndef ALC_EXT_DEFAULT_FILTER_ORDER -#define ALC_EXT_DEFAULT_FILTER_ORDER 1 -#define ALC_DEFAULT_FILTER_ORDER 0x1100 -#endif - -#ifndef AL_SOFT_deferred_updates -#define AL_SOFT_deferred_updates 1 -#define AL_DEFERRED_UPDATES_SOFT 0xC002 -typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); -typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alDeferUpdatesSOFT(void); -AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); -#endif -#endif - -#ifndef AL_SOFT_block_alignment -#define AL_SOFT_block_alignment 1 -#define AL_UNPACK_BLOCK_ALIGNMENT_SOFT 0x200C -#define AL_PACK_BLOCK_ALIGNMENT_SOFT 0x200D -#endif - -#ifndef AL_SOFT_MSADPCM -#define AL_SOFT_MSADPCM 1 -#define AL_FORMAT_MONO_MSADPCM_SOFT 0x1302 -#define AL_FORMAT_STEREO_MSADPCM_SOFT 0x1303 -#endif - -#ifndef AL_SOFT_source_length -#define AL_SOFT_source_length 1 -/*#define AL_BYTE_LENGTH_SOFT 0x2009*/ -/*#define AL_SAMPLE_LENGTH_SOFT 0x200A*/ -/*#define AL_SEC_LENGTH_SOFT 0x200B*/ -#endif - -#ifndef ALC_SOFT_pause_device -#define ALC_SOFT_pause_device 1 -typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device); -typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device); -#ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device); -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); -#endif -#endif - -#ifndef AL_EXT_BFORMAT -#define AL_EXT_BFORMAT 1 -/* Provides support for B-Format ambisonic buffers (first-order, FuMa scaling - * and layout). - * - * BFORMAT2D_8: Unsigned 8-bit, 3-channel non-periphonic (WXY). - * BFORMAT2D_16: Signed 16-bit, 3-channel non-periphonic (WXY). - * BFORMAT2D_FLOAT32: 32-bit float, 3-channel non-periphonic (WXY). - * BFORMAT3D_8: Unsigned 8-bit, 4-channel periphonic (WXYZ). - * BFORMAT3D_16: Signed 16-bit, 4-channel periphonic (WXYZ). - * BFORMAT3D_FLOAT32: 32-bit float, 4-channel periphonic (WXYZ). - */ -#define AL_FORMAT_BFORMAT2D_8 0x20021 -#define AL_FORMAT_BFORMAT2D_16 0x20022 -#define AL_FORMAT_BFORMAT2D_FLOAT32 0x20023 -#define AL_FORMAT_BFORMAT3D_8 0x20031 -#define AL_FORMAT_BFORMAT3D_16 0x20032 -#define AL_FORMAT_BFORMAT3D_FLOAT32 0x20033 -#endif - -#ifndef AL_EXT_MULAW_BFORMAT -#define AL_EXT_MULAW_BFORMAT 1 -#define AL_FORMAT_BFORMAT2D_MULAW 0x10031 -#define AL_FORMAT_BFORMAT3D_MULAW 0x10032 -#endif - -#ifndef ALC_SOFT_HRTF -#define ALC_SOFT_HRTF 1 -#define ALC_HRTF_SOFT 0x1992 -#define ALC_DONT_CARE_SOFT 0x0002 -#define ALC_HRTF_STATUS_SOFT 0x1993 -#define ALC_HRTF_DISABLED_SOFT 0x0000 -#define ALC_HRTF_ENABLED_SOFT 0x0001 -#define ALC_HRTF_DENIED_SOFT 0x0002 -#define ALC_HRTF_REQUIRED_SOFT 0x0003 -#define ALC_HRTF_HEADPHONES_DETECTED_SOFT 0x0004 -#define ALC_HRTF_UNSUPPORTED_FORMAT_SOFT 0x0005 -#define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 -#define ALC_HRTF_SPECIFIER_SOFT 0x1995 -#define ALC_HRTF_ID_SOFT 0x1996 -typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); -typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); -#ifdef AL_ALEXT_PROTOTYPES -ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); -ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); -#endif -#endif - -#ifndef AL_SOFT_gain_clamp_ex -#define AL_SOFT_gain_clamp_ex 1 -#define AL_GAIN_LIMIT_SOFT 0x200E -#endif - -#ifndef AL_SOFT_source_resampler -#define AL_SOFT_source_resampler -#define AL_NUM_RESAMPLERS_SOFT 0x1210 -#define AL_DEFAULT_RESAMPLER_SOFT 0x1211 -#define AL_SOURCE_RESAMPLER_SOFT 0x1212 -#define AL_RESAMPLER_NAME_SOFT 0x1213 -typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); -#ifdef AL_ALEXT_PROTOTYPES -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); -#endif -#endif - -#ifndef AL_SOFT_source_spatialize -#define AL_SOFT_source_spatialize -#define AL_SOURCE_SPATIALIZE_SOFT 0x1214 -#define AL_AUTO_SOFT 0x0002 -#endif - -#ifndef ALC_SOFT_output_limiter -#define ALC_SOFT_output_limiter -#define ALC_OUTPUT_LIMITER_SOFT 0x199A -#endif - -#ifndef ALC_SOFT_device_clock -#define ALC_SOFT_device_clock 1 -typedef _alsoft_int64_t ALCint64SOFT; -typedef _alsoft_uint64_t ALCuint64SOFT; -#define ALC_DEVICE_CLOCK_SOFT 0x1600 -#define ALC_DEVICE_LATENCY_SOFT 0x1601 -#define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602 -#define AL_SAMPLE_OFFSET_CLOCK_SOFT 0x1202 -#define AL_SEC_OFFSET_CLOCK_SOFT 0x1203 -typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values); -#ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values); -#endif -#endif - -#ifndef AL_SOFT_direct_channels_remix -#define AL_SOFT_direct_channels_remix 1 -#define AL_DROP_UNMATCHED_SOFT 0x0001 -#define AL_REMIX_UNMATCHED_SOFT 0x0002 -#endif - -#ifndef AL_SOFT_bformat_ex -#define AL_SOFT_bformat_ex 1 -#define AL_AMBISONIC_LAYOUT_SOFT 0x1997 -#define AL_AMBISONIC_SCALING_SOFT 0x1998 - -/* Ambisonic layouts */ -#define AL_FUMA_SOFT 0x0000 -#define AL_ACN_SOFT 0x0001 - -/* Ambisonic scalings (normalization) */ -/*#define AL_FUMA_SOFT*/ -#define AL_SN3D_SOFT 0x0001 -#define AL_N3D_SOFT 0x0002 -#endif - -#ifndef ALC_SOFT_loopback_bformat -#define ALC_SOFT_loopback_bformat 1 -#define ALC_AMBISONIC_LAYOUT_SOFT 0x1997 -#define ALC_AMBISONIC_SCALING_SOFT 0x1998 -#define ALC_AMBISONIC_ORDER_SOFT 0x1999 -#define ALC_MAX_AMBISONIC_ORDER_SOFT 0x199B - -#define ALC_BFORMAT3D_SOFT 0x1507 - -/* Ambisonic layouts */ -#define ALC_FUMA_SOFT 0x0000 -#define ALC_ACN_SOFT 0x0001 - -/* Ambisonic scalings (normalization) */ -/*#define ALC_FUMA_SOFT*/ -#define ALC_SN3D_SOFT 0x0001 -#define ALC_N3D_SOFT 0x0002 -#endif - -#ifndef AL_SOFT_effect_target -#define AL_SOFT_effect_target -#define AL_EFFECTSLOT_TARGET_SOFT 0x199C -#endif - -#ifndef AL_SOFT_events -#define AL_SOFT_events 1 -#define AL_EVENT_CALLBACK_FUNCTION_SOFT 0x19A2 -#define AL_EVENT_CALLBACK_USER_PARAM_SOFT 0x19A3 -#define AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT 0x19A4 -#define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x19A5 -#define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x19A6 -typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); -#endif -#endif - -#ifndef ALC_SOFT_reopen_device -#define ALC_SOFT_reopen_device -typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, - const ALCchar *deviceName, const ALCint *attribs); -#ifdef AL_ALEXT_PROTOTYPES -ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, - const ALCint *attribs); -#endif -#endif - -#ifndef AL_SOFT_callback_buffer -#define AL_SOFT_callback_buffer -#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 -#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); -AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr); -AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2); -AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr); -#endif -#endif - -#ifndef AL_SOFT_UHJ -#define AL_SOFT_UHJ -#define AL_FORMAT_UHJ2CHN8_SOFT 0x19A2 -#define AL_FORMAT_UHJ2CHN16_SOFT 0x19A3 -#define AL_FORMAT_UHJ2CHN_FLOAT32_SOFT 0x19A4 -#define AL_FORMAT_UHJ3CHN8_SOFT 0x19A5 -#define AL_FORMAT_UHJ3CHN16_SOFT 0x19A6 -#define AL_FORMAT_UHJ3CHN_FLOAT32_SOFT 0x19A7 -#define AL_FORMAT_UHJ4CHN8_SOFT 0x19A8 -#define AL_FORMAT_UHJ4CHN16_SOFT 0x19A9 -#define AL_FORMAT_UHJ4CHN_FLOAT32_SOFT 0x19AA - -#define AL_STEREO_MODE_SOFT 0x19B0 -#define AL_NORMAL_SOFT 0x0000 -#define AL_SUPER_STEREO_SOFT 0x0001 -#define AL_SUPER_STEREO_WIDTH_SOFT 0x19B1 -#endif - -#ifndef ALC_SOFT_output_mode -#define ALC_SOFT_output_mode -#define ALC_OUTPUT_MODE_SOFT 0x19AC -#define ALC_ANY_SOFT 0x19AD -/*#define ALC_MONO_SOFT 0x1500*/ -/*#define ALC_STEREO_SOFT 0x1501*/ -#define ALC_STEREO_BASIC_SOFT 0x19AE -#define ALC_STEREO_UHJ_SOFT 0x19AF -#define ALC_STEREO_HRTF_SOFT 0x19B2 -/*#define ALC_QUAD_SOFT 0x1503*/ -#define ALC_SURROUND_5_1_SOFT 0x1504 -#define ALC_SURROUND_6_1_SOFT 0x1505 -#define ALC_SURROUND_7_1_SOFT 0x1506 -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/source/common/engine/stringtable.cpp b/source/common/engine/stringtable.cpp index 5fd020ed0..52d1f3543 100644 --- a/source/common/engine/stringtable.cpp +++ b/source/common/engine/stringtable.cpp @@ -47,27 +47,29 @@ // //========================================================================== -void FStringTable::LoadStrings (const char *language) +void FStringTable::LoadStrings (FileSys::FileSystem& fileSystem_, const char *language) { int lastlump, lump; + fileSystem = &fileSystem_; allStrings.Clear(); lastlump = 0; - while ((lump = fileSystem.FindLump("LMACROS", &lastlump)) != -1) + while ((lump = fileSystem->FindLump("LMACROS", &lastlump)) != -1) { readMacros(lump); } lastlump = 0; - while ((lump = fileSystem.FindLump ("LANGUAGE", &lastlump)) != -1) + while ((lump = fileSystem->FindLump ("LANGUAGE", &lastlump)) != -1) { - auto lumpdata = fileSystem.ReadFile(lump); + auto lumpdata = fileSystem->ReadFile(lump); if (!ParseLanguageCSV(lump, lumpdata.string(), lumpdata.size())) LoadLanguage (lump, lumpdata.string(), lumpdata.size()); } UpdateLanguage(language); allMacros.Clear(); + fileSystem = nullptr; } @@ -159,9 +161,10 @@ TArray> FStringTable::parseCSV(const char* buffer, size_t size) bool FStringTable::readMacros(int lumpnum) { - auto lumpdata = fileSystem.ReadFile(lumpnum); + auto lumpdata = fileSystem->ReadFile(lumpnum); auto data = parseCSV(lumpdata.string(), lumpdata.size()); + allMacros.Clear(); for (unsigned i = 1; i < data.Size(); i++) { auto macroname = data[i][0]; @@ -410,7 +413,7 @@ void FStringTable::DeleteForLabel(int lumpnum, FName label) { decltype(allStrings)::Iterator it(allStrings); decltype(allStrings)::Pair *pair; - auto filenum = fileSystem.GetFileContainer(lumpnum); + auto filenum = fileSystem->GetFileContainer(lumpnum); while (it.NextPair(pair)) { @@ -432,7 +435,7 @@ void FStringTable::DeleteForLabel(int lumpnum, FName label) void FStringTable::InsertString(int lumpnum, int langid, FName label, const FString &string) { const char *strlangid = (const char *)&langid; - TableElement te = { fileSystem.GetFileContainer(lumpnum), { string, string, string, string } }; + TableElement te = { fileSystem->GetFileContainer(lumpnum), { string, string, string, string } }; ptrdiff_t index; while ((index = te.strings[0].IndexOf("@[")) >= 0) { diff --git a/source/common/engine/stringtable.h b/source/common/engine/stringtable.h index 57827aa04..0e0476353 100644 --- a/source/common/engine/stringtable.h +++ b/source/common/engine/stringtable.h @@ -84,7 +84,7 @@ public: using LangMap = TMap; using StringMacroMap = TMap; - void LoadStrings(const char *language); + void LoadStrings(FileSys::FileSystem& fileSystem, const char *language); void UpdateLanguage(const char* language); StringMap GetDefaultStrings() { return allStrings[default_table]; } // Dehacked needs these for comparison void SetOverrideStrings(StringMap & map) @@ -108,6 +108,7 @@ public: private: + FileSys::FileSystem* fileSystem; FString activeLanguage; StringMacroMap allMacros; LangMap allStrings; diff --git a/source/common/filesystem/source/filesystem.cpp b/source/common/filesystem/source/filesystem.cpp index f011c4c57..a8418956f 100644 --- a/source/common/filesystem/source/filesystem.cpp +++ b/source/common/filesystem/source/filesystem.cpp @@ -252,7 +252,7 @@ bool FileSystem::InitMultipleFiles (std::vector& filenames, LumpFil stringpool->shared = true; // will be used by all owned resource files. // first, check for duplicates - if (allowduplicates) + if (!allowduplicates) { for (size_t i=0;i +#include "tarray.h" enum class ETextureType : uint8_t { @@ -66,3 +67,11 @@ public: constexpr FSetTextureID(int v) : FTextureID(v) {} }; +template<> struct THashTraits +{ + + hash_t Hash(const FTextureID key) { return (hash_t)key.GetIndex(); } + + // Compares two keys, returning zero if they are the same. + int Compare(const FTextureID left, const FTextureID right) { return left != right; } +}; diff --git a/source/common/widgets/errorwindow.cpp b/source/common/widgets/errorwindow.cpp index 6b6b71035..e0af0730b 100644 --- a/source/common/widgets/errorwindow.cpp +++ b/source/common/widgets/errorwindow.cpp @@ -212,7 +212,7 @@ void LogViewer::OnPaint(Canvas* canvas) } } -void LogViewer::OnMouseWheel(const Point& pos, EInputKey key) +bool LogViewer::OnMouseWheel(const Point& pos, EInputKey key) { if (key == IK_MouseWheelUp) { @@ -222,6 +222,7 @@ void LogViewer::OnMouseWheel(const Point& pos, EInputKey key) { ScrollDown(4); } + return true; } void LogViewer::OnKeyDown(EInputKey key) diff --git a/source/common/widgets/errorwindow.h b/source/common/widgets/errorwindow.h index 63304b33f..f38eb676c 100644 --- a/source/common/widgets/errorwindow.h +++ b/source/common/widgets/errorwindow.h @@ -43,7 +43,7 @@ public: protected: void OnPaintFrame(Canvas* canvas) override; void OnPaint(Canvas* canvas) override; - void OnMouseWheel(const Point& pos, EInputKey key) override; + bool OnMouseWheel(const Point& pos, EInputKey key) override; void OnKeyDown(EInputKey key) override; void OnGeometryChanged() override; diff --git a/source/common/widgets/widgetresourcedata.cpp b/source/common/widgets/widgetresourcedata.cpp index fd416e1d1..85ee3e249 100644 --- a/source/common/widgets/widgetresourcedata.cpp +++ b/source/common/widgets/widgetresourcedata.cpp @@ -2,6 +2,12 @@ #include #include "filesystem.h" #include "printf.h" +#include "zstring.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif FResourceFile* WidgetResources; @@ -17,11 +23,11 @@ void CloseWidgetResources() if (WidgetResources) delete WidgetResources; } -static std::vector LoadFile(const std::string& name) +static std::vector LoadFile(const char* name) { - auto lump = WidgetResources->FindEntry(name.c_str()); + auto lump = WidgetResources->FindEntry(name); if (lump == -1) - I_FatalError("Unable to find %s", name.c_str()); + I_FatalError("Unable to find %s", name); auto reader = WidgetResources->GetEntryReader(lump, FileSys::READER_SHARED); std::vector buffer(reader.GetLength()); @@ -29,6 +35,19 @@ static std::vector LoadFile(const std::string& name) return buffer; } +// this must be allowed to fail without throwing. +static std::vector LoadDiskFile(const char* name) +{ + std::vector buffer; + FileSys::FileReader lump; + if (lump.OpenFile(name)) + { + buffer.resize(lump.GetLength()); + lump.Read(buffer.data(), buffer.size()); + } + return buffer; +} + // This interface will later require some significant redesign. std::vector LoadWidgetFontData(const std::string& name) { @@ -36,24 +55,34 @@ std::vector LoadWidgetFontData(const std::string& name) if (!stricmp(name.c_str(), "notosans")) { returnv.resize(3); - returnv[2].fontdata = LoadFile("widgets/noto/notosans-regular.ttf"); - returnv[0].fontdata = LoadFile("widgets/noto/notosansarmenian-regular.ttf"); - returnv[1].fontdata = LoadFile("widgets/noto/notosansgeorgian-regular.ttf"); - returnv[0].ranges.push_back(std::make_pair(0x531, 0x58f)); - returnv[1].ranges.push_back(std::make_pair(0x10a0, 0x10ff)); - returnv[1].ranges.push_back(std::make_pair(0x1c90, 0x1cbf)); - returnv[1].ranges.push_back(std::make_pair(0x2d00, 0x2d2f)); - // todo: load system CJK fonts here + returnv[0].fontdata = LoadFile("widgets/noto/notosans-regular.ttf"); + returnv[1].fontdata = LoadFile("widgets/noto/notosansarmenian-regular.ttf"); + returnv[2].fontdata = LoadFile("widgets/noto/notosansgeorgian-regular.ttf"); +#ifdef _WIN32 + wchar_t wbuffer[256]; + if (GetWindowsDirectoryW(wbuffer, 256)) + { + returnv.resize(5); + FString windir(wbuffer); + returnv[3].fontdata = LoadDiskFile((windir + "/fonts/yugothm.ttc").GetChars()); + returnv[3].language = "ja"; + returnv[4].fontdata = LoadDiskFile((windir + "/fonts/malgun.ttf").GetChars()); + returnv[4].language = "ko"; + // Don't fail if these cannot be found + if (returnv[4].fontdata.size() == 0) returnv.erase(returnv.begin() + 4); + if (returnv[3].fontdata.size() == 0) returnv.erase(returnv.begin() + 3); + } +#endif return returnv; } returnv.resize(1); std::string fn = "widgets/font/" +name + ".ttf"; - returnv[0].fontdata = LoadFile(fn); + returnv[0].fontdata = LoadFile(fn.c_str()); return returnv; } -std::vector LoadWidgetImageData(const std::string& name) +std::vector LoadWidgetData(const std::string& name) { - return LoadFile(name); + return LoadFile(name.c_str()); } diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp index 21fa5df7e..9f07ed859 100644 --- a/source/core/gamecontrol.cpp +++ b/source/core/gamecontrol.cpp @@ -903,12 +903,6 @@ static TArray SetupGame() // //========================================================================== -void InitLanguages() -{ - GStrings.LoadStrings(language); -} - - void CreateStatusBar() { auto stbarclass = PClass::FindClass(globalCutscenes.StatusBarClass); @@ -1034,6 +1028,12 @@ int RunGame() LoadHexFont(wad); // load hex font early so we have it during startup. InitWidgetResources(wad); + // load strings for picker window. + FileSys::FileSystem lang_fs; + std::vector base_fn = { wad }; + lang_fs.InitMultipleFiles(base_fn); + GStrings.LoadStrings(lang_fs, language); + // Set up the console before anything else so that it can receive text. C_InitConsole(1024, 768, true); @@ -1107,7 +1107,7 @@ int RunGame() G_ReadConfig(currentGame.GetChars()); V_InitFontColors(); - InitLanguages(); + GStrings.LoadStrings(fileSystem, language); CheckCPUID(&CPU); diff --git a/source/launcher/launcherbanner.cpp b/source/launcher/launcherbanner.cpp new file mode 100644 index 000000000..69f7e7ee1 --- /dev/null +++ b/source/launcher/launcherbanner.cpp @@ -0,0 +1,34 @@ + +#include "launcherbanner.h" +#include "gstrings.h" +#include "version.h" +#include +#include +#include + +LauncherBanner::LauncherBanner(Widget* parent) : Widget(parent) +{ + Logo = new ImageBox(this); + VersionLabel = new TextLabel(this); + VersionLabel->SetTextAlignment(TextLabelAlignment::Right); + + Logo->SetImage(Image::LoadResource("widgets/banner.png")); +} + +void LauncherBanner::UpdateLanguage() +{ + FString versionText = GStrings("PICKER_VERSION"); + versionText.Substitute("%s", GetVersionString()); + VersionLabel->SetText(versionText.GetChars()); +} + +double LauncherBanner::GetPreferredHeight() const +{ + return Logo->GetPreferredHeight(); +} + +void LauncherBanner::OnGeometryChanged() +{ + Logo->SetFrameGeometry(0.0, 0.0, GetWidth(), Logo->GetPreferredHeight()); + VersionLabel->SetFrameGeometry(20.0, GetHeight() - 10.0 - VersionLabel->GetPreferredHeight(), GetWidth() - 40.0, VersionLabel->GetPreferredHeight()); +} diff --git a/source/launcher/launcherbanner.h b/source/launcher/launcherbanner.h new file mode 100644 index 000000000..9a27a5694 --- /dev/null +++ b/source/launcher/launcherbanner.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +class ImageBox; +class TextLabel; + +class LauncherBanner : public Widget +{ +public: + LauncherBanner(Widget* parent); + void UpdateLanguage(); + + double GetPreferredHeight() const; + +private: + void OnGeometryChanged() override; + + ImageBox* Logo = nullptr; + TextLabel* VersionLabel = nullptr; +}; diff --git a/source/launcher/launcherbuttonbar.cpp b/source/launcher/launcherbuttonbar.cpp new file mode 100644 index 000000000..d542ea51e --- /dev/null +++ b/source/launcher/launcherbuttonbar.cpp @@ -0,0 +1,46 @@ + +#include "launcherbuttonbar.h" +#include "launcherwindow.h" +#include "gstrings.h" +#include + +LauncherButtonbar::LauncherButtonbar(LauncherWindow* parent) : Widget(parent) +{ + PlayButton = new PushButton(this); + ExitButton = new PushButton(this); + + PlayButton->OnClick = [=]() { OnPlayButtonClicked(); }; + ExitButton->OnClick = [=]() { OnExitButtonClicked(); }; +} + +void LauncherButtonbar::UpdateLanguage() +{ + PlayButton->SetText(GStrings("PICKER_PLAY")); + ExitButton->SetText(GStrings("PICKER_EXIT")); +} + +double LauncherButtonbar::GetPreferredHeight() const +{ + return 20.0 + std::max(PlayButton->GetPreferredHeight(), ExitButton->GetPreferredHeight()); +} + +void LauncherButtonbar::OnGeometryChanged() +{ + PlayButton->SetFrameGeometry(20.0, 10.0, 120.0, PlayButton->GetPreferredHeight()); + ExitButton->SetFrameGeometry(GetWidth() - 20.0 - 120.0, 10.0, 120.0, PlayButton->GetPreferredHeight()); +} + +void LauncherButtonbar::OnPlayButtonClicked() +{ + GetLauncher()->Start(); +} + +void LauncherButtonbar::OnExitButtonClicked() +{ + GetLauncher()->Exit(); +} + +LauncherWindow* LauncherButtonbar::GetLauncher() const +{ + return static_cast(Parent()); +} diff --git a/source/launcher/launcherbuttonbar.h b/source/launcher/launcherbuttonbar.h new file mode 100644 index 000000000..4872cb612 --- /dev/null +++ b/source/launcher/launcherbuttonbar.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class LauncherWindow; +class PushButton; + +class LauncherButtonbar : public Widget +{ +public: + LauncherButtonbar(LauncherWindow* parent); + void UpdateLanguage(); + + double GetPreferredHeight() const; + +private: + void OnGeometryChanged() override; + void OnPlayButtonClicked(); + void OnExitButtonClicked(); + + LauncherWindow* GetLauncher() const; + + PushButton* PlayButton = nullptr; + PushButton* ExitButton = nullptr; +}; diff --git a/source/launcher/launcherwindow.cpp b/source/launcher/launcherwindow.cpp index 8e40b20e5..96039ea08 100644 --- a/source/launcher/launcherwindow.cpp +++ b/source/launcher/launcherwindow.cpp @@ -1,28 +1,21 @@ #include "launcherwindow.h" +#include "launcherbanner.h" +#include "launcherbuttonbar.h" +#include "playgamepage.h" +#include "settingspage.h" #include "v_video.h" #include "version.h" #include "i_interface.h" -#include +#include "gstrings.h" +#include #include -#include -#include -#include -#include -#include -#include -#include - -#ifdef RENDER_BACKENDS -EXTERN_CVAR(Int, vid_preferbackend); -#endif - -EXTERN_CVAR(Bool, queryiwad); +#include int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) { Size screenSize = GetScreenSize(); double windowWidth = 615.0; - double windowHeight = 668.0; + double windowHeight = 700.0; auto launcher = std::make_unique(wads, numwads, defaultiwad, autoloadflags); launcher->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight); @@ -33,7 +26,7 @@ int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* return launcher->ExecResult; } -LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) : Widget(nullptr, WidgetType::Window), AutoloadFlags(autoloadflags) +LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) : Widget(nullptr, WidgetType::Window) { SetWindowBackground(Colorf::fromRgba8(51, 51, 51)); SetWindowBorderColor(Colorf::fromRgba8(51, 51, 51)); @@ -41,242 +34,67 @@ LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int SetWindowCaptionTextColor(Colorf::fromRgba8(226, 223, 219)); SetWindowTitle(GAMENAME); - Logo = new ImageBox(this); - WelcomeLabel = new TextLabel(this); - VersionLabel = new TextLabel(this); - SelectLabel = new TextLabel(this); - GeneralLabel = new TextLabel(this); - //ExtrasLabel = new TextLabel(this); - ParametersLabel = new TextLabel(this); - FullscreenCheckbox = new CheckboxLabel(this); - DisableAutoloadCheckbox = new CheckboxLabel(this); - DontAskAgainCheckbox = new CheckboxLabel(this); - /* - LightsCheckbox = new CheckboxLabel(this); - BrightmapsCheckbox = new CheckboxLabel(this); - WidescreenCheckbox = new CheckboxLabel(this); - */ - PlayButton = new PushButton(this); - ExitButton = new PushButton(this); - GamesList = new ListView(this); - ParametersEdit = new LineEdit(this); + Banner = new LauncherBanner(this); + Pages = new TabWidget(this); + Buttonbar = new LauncherButtonbar(this); - PlayButton->OnClick = [=]() { OnPlayButtonClicked(); }; - ExitButton->OnClick = [=]() { OnExitButtonClicked(); }; - GamesList->OnActivated = [=]() { OnGamesListActivated(); }; + PlayGame = new PlayGamePage(this, wads, numwads, defaultiwad); + Settings = new SettingsPage(this, autoloadflags); - SelectLabel->SetText("Select which game file to run."); - PlayButton->SetText("Play Game"); - ExitButton->SetText("Exit"); + Pages->AddTab(PlayGame, "Play"); + Pages->AddTab(Settings, "Settings"); - GeneralLabel->SetText("General"); - //ExtrasLabel->SetText("Extra Graphics"); - FullscreenCheckbox->SetText("Fullscreen"); - DisableAutoloadCheckbox->SetText("Disable autoload"); - DontAskAgainCheckbox->SetText("Don't ask me again"); - /* - LightsCheckbox->SetText("Lights"); - BrightmapsCheckbox->SetText("Brightmaps"); - WidescreenCheckbox->SetText("Widescreen"); - */ - ParametersLabel->SetText("Additional Parameters:"); + UpdateLanguage(); -#ifdef RENDER_BACKENDS - BackendLabel = new TextLabel(this); - VulkanCheckbox = new CheckboxLabel(this); - OpenGLCheckbox = new CheckboxLabel(this); - GLESCheckbox = new CheckboxLabel(this); - BackendLabel->SetText("Render Backend"); - VulkanCheckbox->SetText("Vulkan"); - OpenGLCheckbox->SetText("OpenGL"); - GLESCheckbox->SetText("OpenGL ES"); -#endif - - FString welcomeText, versionText; - welcomeText.Format("Welcome to %s!", GAMENAME); - versionText.Format("Version %s.", GetVersionString()); - WelcomeLabel->SetText(welcomeText.GetChars()); - VersionLabel->SetText(versionText.GetChars()); - - FullscreenCheckbox->SetChecked(vid_fullscreen); - DontAskAgainCheckbox->SetChecked(!queryiwad); - - int flags = *autoloadflags; - DisableAutoloadCheckbox->SetChecked(flags & 1); - /* - LightsCheckbox->SetChecked(flags & 2); - BrightmapsCheckbox->SetChecked(flags & 4); - WidescreenCheckbox->SetChecked(flags & 8); - */ - -#ifdef RENDER_BACKENDS - OpenGLCheckbox->SetRadioStyle(true); - VulkanCheckbox->SetRadioStyle(true); - GLESCheckbox->SetRadioStyle(true); - OpenGLCheckbox->FuncChanged = [this](bool on) { if (on) { VulkanCheckbox->SetChecked(false); GLESCheckbox->SetChecked(false); }}; - VulkanCheckbox->FuncChanged = [this](bool on) { if (on) { OpenGLCheckbox->SetChecked(false); GLESCheckbox->SetChecked(false); }}; - GLESCheckbox->FuncChanged = [this](bool on) { if (on) { VulkanCheckbox->SetChecked(false); OpenGLCheckbox->SetChecked(false); }}; - switch (vid_preferbackend) - { - case 0: - OpenGLCheckbox->SetChecked(true); - break; - case 1: - VulkanCheckbox->SetChecked(true); - break; - case 2: - GLESCheckbox->SetChecked(true); - break; - } -#endif - - for (int i = 0; i < numwads; i++) - { - const char* filepart = strrchr(wads[i].Path.GetChars(), '/'); - if (filepart == NULL) - filepart = wads[i].Path.GetChars(); - else - filepart++; - - FString work; - if (*filepart) work.Format("%s (%s)", wads[i].Name.GetChars(), filepart); - else work = wads[i].Name.GetChars(); - - GamesList->AddItem(work.GetChars()); - } - - if (defaultiwad >= 0 && defaultiwad < numwads) - { - GamesList->SetSelectedItem(defaultiwad); - GamesList->ScrollToItem(defaultiwad); - } - - Logo->SetImage(Image::LoadResource("widgets/banner.png")); - - GamesList->SetFocus(); + Pages->SetCurrentWidget(PlayGame); + PlayGame->SetFocus(); } -void LauncherWindow::OnClose() +void LauncherWindow::Start() { - OnExitButtonClicked(); -} + Settings->Save(); -void LauncherWindow::OnPlayButtonClicked() -{ - vid_fullscreen = FullscreenCheckbox->GetChecked(); - queryiwad = !DontAskAgainCheckbox->GetChecked(); - - int flags = 0; - if (DisableAutoloadCheckbox->GetChecked()) flags |= 1; - /* - if (LightsCheckbox->GetChecked()) flags |= 2; - if (BrightmapsCheckbox->GetChecked()) flags |= 4; - if (WidescreenCheckbox->GetChecked()) flags |= 8; - */ - *AutoloadFlags = flags; - -#ifdef RENDER_BACKENDS - int v = 1; - if (OpenGLCheckbox->GetChecked()) v = 0; - else if (VulkanCheckbox->GetChecked()) v = 1; - else if (GLESCheckbox->GetChecked()) v = 2; - if (v != vid_preferbackend) vid_preferbackend = v; -#endif - - std::string extraargs = ParametersEdit->GetText(); + std::string extraargs = PlayGame->GetExtraArgs(); if (!extraargs.empty()) { // To do: restart the process like the cocoa backend is doing? } - ExecResult = GamesList->GetSelectedItem(); + ExecResult = PlayGame->GetSelectedGame(); DisplayWindow::ExitLoop(); } -void LauncherWindow::OnExitButtonClicked() +void LauncherWindow::Exit() { ExecResult = -1; DisplayWindow::ExitLoop(); } -void LauncherWindow::OnGamesListActivated() +void LauncherWindow::UpdateLanguage() { - OnPlayButtonClicked(); + Pages->SetTabText(PlayGame, GStrings("PICKER_TAB_PLAY")); + Pages->SetTabText(Settings, GStrings("OPTMNU_TITLE")); + Banner->UpdateLanguage(); + PlayGame->UpdateLanguage(); + Settings->UpdateLanguage(); + Buttonbar->UpdateLanguage(); +} + +void LauncherWindow::OnClose() +{ + Exit(); } void LauncherWindow::OnGeometryChanged() { - double y = 0.0; + double top = 0.0; + double bottom = GetHeight(); - Logo->SetFrameGeometry(0.0, y, GetWidth(), Logo->GetPreferredHeight()); - y += Logo->GetPreferredHeight(); + Banner->SetFrameGeometry(0.0, top, GetWidth(), Banner->GetPreferredHeight()); + top += Banner->GetPreferredHeight(); - y += 10.0; + bottom -= Buttonbar->GetPreferredHeight(); + Buttonbar->SetFrameGeometry(0.0, bottom, GetWidth(), Buttonbar->GetPreferredHeight()); - WelcomeLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, WelcomeLabel->GetPreferredHeight()); - y += WelcomeLabel->GetPreferredHeight(); - - VersionLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, VersionLabel->GetPreferredHeight()); - y += VersionLabel->GetPreferredHeight(); - - y += 10.0; - - SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, SelectLabel->GetPreferredHeight()); - y += SelectLabel->GetPreferredHeight(); - - double listViewTop = y + 10.0; - - y = GetHeight() - 15.0 - PlayButton->GetPreferredHeight(); - PlayButton->SetFrameGeometry(20.0, y, 120.0, PlayButton->GetPreferredHeight()); - ExitButton->SetFrameGeometry(GetWidth() - 20.0 - 120.0, y, 120.0, PlayButton->GetPreferredHeight()); - - y -= 20.0; - - double editHeight = 24.0; - y -= editHeight; - ParametersEdit->SetFrameGeometry(20.0, y, GetWidth() - 40.0, editHeight); - y -= 5.0; - - double labelHeight = ParametersLabel->GetPreferredHeight(); - y -= labelHeight; - ParametersLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, labelHeight); - y -= 10.0; - - double panelWidth = 150.0; - -#ifdef RENDER_BACKENDS - auto yy = y; - y -= GLESCheckbox->GetPreferredHeight(); - double x = GetWidth() - 20.0 - panelWidth; - GLESCheckbox->SetFrameGeometry(x, y, 190.0, GLESCheckbox->GetPreferredHeight()); - - y -= OpenGLCheckbox->GetPreferredHeight(); - OpenGLCheckbox->SetFrameGeometry(x, y, 190.0, OpenGLCheckbox->GetPreferredHeight()); - - y -= VulkanCheckbox->GetPreferredHeight(); - VulkanCheckbox->SetFrameGeometry(x, y, 190.0, VulkanCheckbox->GetPreferredHeight()); - - y -= BackendLabel->GetPreferredHeight(); - BackendLabel->SetFrameGeometry(x, y, 190.0, BackendLabel->GetPreferredHeight()); - - y = yy; -#endif - y -= DontAskAgainCheckbox->GetPreferredHeight(); - DontAskAgainCheckbox->SetFrameGeometry(20.0, y, 190.0, DontAskAgainCheckbox->GetPreferredHeight()); - //WidescreenCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, WidescreenCheckbox->GetPreferredHeight()); - - y -= DisableAutoloadCheckbox->GetPreferredHeight(); - DisableAutoloadCheckbox->SetFrameGeometry(20.0, y, 190.0, DisableAutoloadCheckbox->GetPreferredHeight()); - //BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, BrightmapsCheckbox->GetPreferredHeight()); - - y -= FullscreenCheckbox->GetPreferredHeight(); - FullscreenCheckbox->SetFrameGeometry(20.0, y, 190.0, FullscreenCheckbox->GetPreferredHeight()); - //LightsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, LightsCheckbox->GetPreferredHeight()); - - y -= GeneralLabel->GetPreferredHeight(); - GeneralLabel->SetFrameGeometry(20.0, y, 190.0, GeneralLabel->GetPreferredHeight()); - //ExtrasLabel->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, ExtrasLabel->GetPreferredHeight()); - - double listViewBottom = y - 10.0; - GamesList->SetFrameGeometry(20.0, listViewTop, GetWidth() - 40.0, std::max(listViewBottom - listViewTop, 0.0)); + Pages->SetFrameGeometry(0.0, top, GetWidth(), std::max(bottom - top, 0.0)); } diff --git a/source/launcher/launcherwindow.h b/source/launcher/launcherwindow.h index 6f48a0525..8cdbc6ce7 100644 --- a/source/launcher/launcherwindow.h +++ b/source/launcher/launcherwindow.h @@ -1,16 +1,14 @@ #pragma once #include +#include "tarray.h" +#include "zstring.h" - -#define RENDER_BACKENDS - -class ImageBox; -class TextLabel; -class CheckboxLabel; -class PushButton; -class ListView; -class LineEdit; +class TabWidget; +class LauncherBanner; +class LauncherButtonbar; +class PlayGamePage; +class SettingsPage; struct WadStuff; class LauncherWindow : public Widget @@ -19,39 +17,21 @@ public: static int ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags); LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags); + void UpdateLanguage(); + + void Start(); + void Exit(); private: - void OnPlayButtonClicked(); - void OnExitButtonClicked(); - void OnGamesListActivated(); - void OnClose() override; void OnGeometryChanged() override; - ImageBox* Logo = nullptr; - TextLabel* WelcomeLabel = nullptr; - TextLabel* VersionLabel = nullptr; - TextLabel* SelectLabel = nullptr; - TextLabel* GeneralLabel = nullptr; - TextLabel* ExtrasLabel = nullptr; - TextLabel* ParametersLabel = nullptr; - CheckboxLabel* FullscreenCheckbox = nullptr; - CheckboxLabel* DisableAutoloadCheckbox = nullptr; - CheckboxLabel* DontAskAgainCheckbox = nullptr; - CheckboxLabel* LightsCheckbox = nullptr; - CheckboxLabel* BrightmapsCheckbox = nullptr; - CheckboxLabel* WidescreenCheckbox = nullptr; -#ifdef RENDER_BACKENDS - TextLabel* BackendLabel = nullptr; - CheckboxLabel* VulkanCheckbox = nullptr; - CheckboxLabel* OpenGLCheckbox = nullptr; - CheckboxLabel* GLESCheckbox = nullptr; -#endif - PushButton* PlayButton = nullptr; - PushButton* ExitButton = nullptr; - ListView* GamesList = nullptr; - LineEdit* ParametersEdit = nullptr; + LauncherBanner* Banner = nullptr; + TabWidget* Pages = nullptr; + LauncherButtonbar* Buttonbar = nullptr; + + PlayGamePage* PlayGame = nullptr; + SettingsPage* Settings = nullptr; - int* AutoloadFlags = nullptr; int ExecResult = -1; }; diff --git a/source/launcher/playgamepage.cpp b/source/launcher/playgamepage.cpp new file mode 100644 index 000000000..5c585c9e5 --- /dev/null +++ b/source/launcher/playgamepage.cpp @@ -0,0 +1,100 @@ + +#include "playgamepage.h" +#include "launcherwindow.h" +#include "i_interface.h" +#include "gstrings.h" +#include "version.h" +#include +#include +#include + +PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads, int defaultiwad) : Widget(nullptr), Launcher(launcher) +{ + WelcomeLabel = new TextLabel(this); + SelectLabel = new TextLabel(this); + ParametersLabel = new TextLabel(this); + GamesList = new ListView(this); + ParametersEdit = new LineEdit(this); + + for (int i = 0; i < numwads; i++) + { + const char* filepart = strrchr(wads[i].Path.GetChars(), '/'); + if (filepart == NULL) + filepart = wads[i].Path.GetChars(); + else + filepart++; + + FString work; + if (*filepart) work.Format("%s (%s)", wads[i].Name.GetChars(), filepart); + else work = wads[i].Name.GetChars(); + + GamesList->AddItem(work.GetChars()); + } + + if (defaultiwad >= 0 && defaultiwad < numwads) + { + GamesList->SetSelectedItem(defaultiwad); + GamesList->ScrollToItem(defaultiwad); + } + + GamesList->OnActivated = [=]() { OnGamesListActivated(); }; +} + +std::string PlayGamePage::GetExtraArgs() +{ + return ParametersEdit->GetText(); +} + +int PlayGamePage::GetSelectedGame() +{ + return GamesList->GetSelectedItem(); +} + +void PlayGamePage::UpdateLanguage() +{ + SelectLabel->SetText(GStrings("PICKER_SELECT")); + ParametersLabel->SetText(GStrings("PICKER_ADDPARM")); + FString welcomeText = GStrings("PICKER_WELCOME"); + welcomeText.Substitute("%s", GAMENAME); + WelcomeLabel->SetText(welcomeText.GetChars()); +} + +void PlayGamePage::OnGamesListActivated() +{ + Launcher->Start(); +} + +void PlayGamePage::OnSetFocus() +{ + GamesList->SetFocus(); +} + +void PlayGamePage::OnGeometryChanged() +{ + double y = 10.0; + + WelcomeLabel->SetFrameGeometry(0.0, y, GetWidth(), WelcomeLabel->GetPreferredHeight()); + y += WelcomeLabel->GetPreferredHeight(); + + y += 10.0; + + SelectLabel->SetFrameGeometry(0.0, y, GetWidth(), SelectLabel->GetPreferredHeight()); + y += SelectLabel->GetPreferredHeight(); + + double listViewTop = y; + + y = GetHeight() - 10.0; + + double editHeight = 24.0; + y -= editHeight; + ParametersEdit->SetFrameGeometry(0.0, y, GetWidth(), editHeight); + y -= 5.0; + + double labelHeight = ParametersLabel->GetPreferredHeight(); + y -= labelHeight; + ParametersLabel->SetFrameGeometry(0.0, y, GetWidth(), labelHeight); + y -= 10.0; + + double listViewBottom = y - 10.0; + GamesList->SetFrameGeometry(0.0, listViewTop, GetWidth(), std::max(listViewBottom - listViewTop, 0.0)); +} diff --git a/source/launcher/playgamepage.h b/source/launcher/playgamepage.h new file mode 100644 index 000000000..af0e95bc3 --- /dev/null +++ b/source/launcher/playgamepage.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class LauncherWindow; +class TextLabel; +class ListView; +class LineEdit; +struct WadStuff; + +class PlayGamePage : public Widget +{ +public: + PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads, int defaultiwad); + void UpdateLanguage(); + + std::string GetExtraArgs(); + int GetSelectedGame(); + +private: + void OnGeometryChanged() override; + void OnSetFocus() override; + void OnGamesListActivated(); + + LauncherWindow* Launcher = nullptr; + + TextLabel* WelcomeLabel = nullptr; + TextLabel* SelectLabel = nullptr; + TextLabel* ParametersLabel = nullptr; + ListView* GamesList = nullptr; + LineEdit* ParametersEdit = nullptr; +}; diff --git a/source/launcher/settingspage.cpp b/source/launcher/settingspage.cpp new file mode 100644 index 000000000..e50f994e8 --- /dev/null +++ b/source/launcher/settingspage.cpp @@ -0,0 +1,215 @@ + +#include "settingspage.h" +#include "launcherwindow.h" +#include "gstrings.h" +#include "i_interface.h" +#include "v_video.h" +#include +#include +#include +#include + +#ifdef RENDER_BACKENDS +EXTERN_CVAR(Int, vid_preferbackend); +#endif + +EXTERN_CVAR(String, language) +EXTERN_CVAR(Bool, queryiwad); + +SettingsPage::SettingsPage(LauncherWindow* launcher, int* autoloadflags) : Widget(nullptr), Launcher(launcher), AutoloadFlags(autoloadflags) +{ + LangLabel = new TextLabel(this); + GeneralLabel = new TextLabel(this); + //ExtrasLabel = new TextLabel(this); + FullscreenCheckbox = new CheckboxLabel(this); + DisableAutoloadCheckbox = new CheckboxLabel(this); + DontAskAgainCheckbox = new CheckboxLabel(this); + /* + LightsCheckbox = new CheckboxLabel(this); + BrightmapsCheckbox = new CheckboxLabel(this); + WidescreenCheckbox = new CheckboxLabel(this); + */ + +#ifdef RENDER_BACKENDS + BackendLabel = new TextLabel(this); + VulkanCheckbox = new CheckboxLabel(this); + OpenGLCheckbox = new CheckboxLabel(this); + GLESCheckbox = new CheckboxLabel(this); +#endif + + FullscreenCheckbox->SetChecked(vid_fullscreen); + DontAskAgainCheckbox->SetChecked(!queryiwad); + + int flags = *autoloadflags; + DisableAutoloadCheckbox->SetChecked(flags & 1); + /* + LightsCheckbox->SetChecked(flags & 2); + BrightmapsCheckbox->SetChecked(flags & 4); + WidescreenCheckbox->SetChecked(flags & 8); + */ + +#ifdef RENDER_BACKENDS + OpenGLCheckbox->SetRadioStyle(true); + VulkanCheckbox->SetRadioStyle(true); + GLESCheckbox->SetRadioStyle(true); + OpenGLCheckbox->FuncChanged = [this](bool on) { if (on) { VulkanCheckbox->SetChecked(false); GLESCheckbox->SetChecked(false); }}; + VulkanCheckbox->FuncChanged = [this](bool on) { if (on) { OpenGLCheckbox->SetChecked(false); GLESCheckbox->SetChecked(false); }}; + GLESCheckbox->FuncChanged = [this](bool on) { if (on) { VulkanCheckbox->SetChecked(false); OpenGLCheckbox->SetChecked(false); }}; + switch (vid_preferbackend) + { + case 0: + OpenGLCheckbox->SetChecked(true); + break; + case 1: + VulkanCheckbox->SetChecked(true); + break; + case 2: + GLESCheckbox->SetChecked(true); + break; + } +#endif + + LangList = new ListView(this); + + try + { + auto data = LoadWidgetData("menudef.txt"); + FScanner sc; + sc.OpenMem("menudef.txt", data); + while (sc.GetString()) + { + if (sc.Compare("OptionString")) + { + sc.MustGetString(); + if (sc.Compare("LanguageOptions")) + { + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + FString iso = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + if(iso.CompareNoCase("auto")) + languages.push_back(std::make_pair(iso, FString(sc.String))); + } + } + } + } + } + catch (const std::exception& ex) + { + hideLanguage = true; + } + int i = 0; + for (auto& l : languages) + { + LangList->AddItem(l.second.GetChars()); + if (!l.first.CompareNoCase(::language)) + LangList->SetSelectedItem(i); + i++; + } + + LangList->OnChanged = [=](int i) { OnLanguageChanged(i); }; +} + +void SettingsPage::Save() +{ + vid_fullscreen = FullscreenCheckbox->GetChecked(); + queryiwad = !DontAskAgainCheckbox->GetChecked(); + + int flags = 0; + if (DisableAutoloadCheckbox->GetChecked()) flags |= 1; + /* + if (LightsCheckbox->GetChecked()) flags |= 2; + if (BrightmapsCheckbox->GetChecked()) flags |= 4; + if (WidescreenCheckbox->GetChecked()) flags |= 8; + */ + *AutoloadFlags = flags; + +#ifdef RENDER_BACKENDS + int v = 1; + if (OpenGLCheckbox->GetChecked()) v = 0; + else if (VulkanCheckbox->GetChecked()) v = 1; + else if (GLESCheckbox->GetChecked()) v = 2; + if (v != vid_preferbackend) vid_preferbackend = v; +#endif +} + +void SettingsPage::UpdateLanguage() +{ + LangLabel->SetText(GStrings("OPTMNU_LANGUAGE")); + GeneralLabel->SetText(GStrings("PICKER_GENERAL")); +// ExtrasLabel->SetText(GStrings("PICKER_EXTRA")); + FullscreenCheckbox->SetText(GStrings("PICKER_FULLSCREEN")); + DisableAutoloadCheckbox->SetText(GStrings("PICKER_NOAUTOLOAD")); + DontAskAgainCheckbox->SetText(GStrings("PICKER_DONTASK")); + /* + LightsCheckbox->SetText(GStrings("PICKER_LIGHTS")); + BrightmapsCheckbox->SetText(GStrings("PICKER_BRIGHTMAPS")); + WidescreenCheckbox->SetText(GStrings("PICKER_WIDESCREEN")); + */ + +#ifdef RENDER_BACKENDS + BackendLabel->SetText(GStrings("PICKER_PREFERBACKEND")); + VulkanCheckbox->SetText(GStrings("OPTVAL_VULKAN")); + OpenGLCheckbox->SetText(GStrings("OPTVAL_OPENGL")); + GLESCheckbox->SetText(GStrings("OPTVAL_OPENGLES")); +#endif +} + +void SettingsPage::OnLanguageChanged(int i) +{ + ::language = languages[i].first.GetChars(); + GStrings.UpdateLanguage(::language); // CVAR callbacks are not active yet. + UpdateLanguage(); + Update(); + Launcher->UpdateLanguage(); +} + +void SettingsPage::OnGeometryChanged() +{ + double panelWidth = 200.0; + double y = 0.0; + double w = GetWidth(); + double h = GetHeight(); + + GeneralLabel->SetFrameGeometry(0.0, y, 190.0, GeneralLabel->GetPreferredHeight()); + //ExtrasLabel->SetFrameGeometry(w - panelWidth, y, panelWidth, ExtrasLabel->GetPreferredHeight()); + y += GeneralLabel->GetPreferredHeight(); + + FullscreenCheckbox->SetFrameGeometry(0.0, y, 190.0, FullscreenCheckbox->GetPreferredHeight()); + //LightsCheckbox->SetFrameGeometry(w - panelWidth, y, panelWidth, LightsCheckbox->GetPreferredHeight()); + y += FullscreenCheckbox->GetPreferredHeight(); + + DisableAutoloadCheckbox->SetFrameGeometry(0.0, y, 190.0, DisableAutoloadCheckbox->GetPreferredHeight()); + //BrightmapsCheckbox->SetFrameGeometry(w - panelWidth, y, panelWidth, BrightmapsCheckbox->GetPreferredHeight()); + y += DisableAutoloadCheckbox->GetPreferredHeight(); + + DontAskAgainCheckbox->SetFrameGeometry(0.0, y, 190.0, DontAskAgainCheckbox->GetPreferredHeight()); + //WidescreenCheckbox->SetFrameGeometry(w - panelWidth, y, panelWidth, WidescreenCheckbox->GetPreferredHeight()); + y += DontAskAgainCheckbox->GetPreferredHeight(); + +#ifdef RENDER_BACKENDS + double x = GetWidth() - 20.0 - panelWidth; + y = 0; + BackendLabel->SetFrameGeometry(x, y, 190.0, BackendLabel->GetPreferredHeight()); + y += BackendLabel->GetPreferredHeight(); + + VulkanCheckbox->SetFrameGeometry(x, y, 190.0, VulkanCheckbox->GetPreferredHeight()); + y += VulkanCheckbox->GetPreferredHeight(); + + OpenGLCheckbox->SetFrameGeometry(x, y, 190.0, OpenGLCheckbox->GetPreferredHeight()); + y += OpenGLCheckbox->GetPreferredHeight(); + + GLESCheckbox->SetFrameGeometry(x, y, 190.0, GLESCheckbox->GetPreferredHeight()); + y += GLESCheckbox->GetPreferredHeight(); +#endif + + if (!hideLanguage) + { + LangLabel->SetFrameGeometry(0.0, y, w, LangLabel->GetPreferredHeight()); + y += LangLabel->GetPreferredHeight(); + LangList->SetFrameGeometry(0.0, y, w, std::max(h - y, 0.0)); + } +} diff --git a/source/launcher/settingspage.h b/source/launcher/settingspage.h new file mode 100644 index 000000000..5c0753194 --- /dev/null +++ b/source/launcher/settingspage.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#define RENDER_BACKENDS + +class LauncherWindow; +class TextLabel; +class CheckboxLabel; +class ListView; + +class SettingsPage : public Widget +{ +public: + SettingsPage(LauncherWindow* launcher, int* autoloadflags); + void UpdateLanguage(); + + void Save(); + +private: + void OnLanguageChanged(int i); + void OnGeometryChanged() override; + + LauncherWindow* Launcher = nullptr; + + TextLabel* LangLabel = nullptr; + TextLabel* GeneralLabel = nullptr; + TextLabel* ExtrasLabel = nullptr; + CheckboxLabel* FullscreenCheckbox = nullptr; + CheckboxLabel* DisableAutoloadCheckbox = nullptr; + CheckboxLabel* DontAskAgainCheckbox = nullptr; + CheckboxLabel* LightsCheckbox = nullptr; + CheckboxLabel* BrightmapsCheckbox = nullptr; + CheckboxLabel* WidescreenCheckbox = nullptr; +#ifdef RENDER_BACKENDS + TextLabel* BackendLabel = nullptr; + CheckboxLabel* VulkanCheckbox = nullptr; + CheckboxLabel* OpenGLCheckbox = nullptr; + CheckboxLabel* GLESCheckbox = nullptr; +#endif + ListView* LangList = nullptr; + + int* AutoloadFlags = nullptr; + + TArray> languages; + bool hideLanguage = false; +}; diff --git a/wadsrc/static/language.0 b/wadsrc/static/language.0 index dc145779c..b669c46c1 100644 --- a/wadsrc/static/language.0 +++ b/wadsrc/static/language.0 @@ -601,7 +601,7 @@ DMX (Without AM voice bug),ADLVLMODEL_DMX_FIXED,,,,DMX (Bez AM hlasové chyby),D Apogee (Without AM voice bug),ADLVLMODEL_APOGEE_FIXED,,,,Apogee (Bez AM hlasové chyby),Apogee (uden AM-stemmefejl),Apogee (Ohne AM-Stimmenfehler),,Apogee (Sen cimo de AM-voĉo),Apogee (Sin bug de voz AM),,Apogee (ilman AM-äänivikaa),Apogee (avec bug canaux AM),Apogee (AM hanghiba nélkül),,Apogee(AMボイスバグ無し),,Apogee (zonder AM voice bug),Apogee (uten AM-stemmefeil),Apogee (bez głosowego błędu AM),Apogee (sem bug de voz de AM),,Apogee (Fără probleme în AM),Apogee (Без ошибки АМ),,Apogee (utan AM-röstfel),Apogee (AM ses hatası olmadan),Apogee (Без помилки АМ), IBM Audio Library Interface,ADLVLMODEL_AIL,,,,,,,,IBM Aŭdbibiloteka Interfaco,,,,,IBM Hang Könyvtár Interfész,,,,,,Interfejs Biblioteki Dźwięków IBM,Interface de Biblioteca de Áudio IBM,,Interfață de Bibliotecă IBM,IBM AIL,,IBM Audio Library Interface (gränssnitt för ljudbibliotek),IBM Ses Kitaplığı Arayüzü,, HMI Sound Operating System,ADLVLMODEL_HMI,,,,,,,,HMI Sonoperaciumo,,,,,HMI Hang Operációs Rendszer,,,,,,System Operacyjny Dźwięków HMI,Sistema Operacional de Som HMI,,Sistem Sonor HMI,HMI SOS,,HMI-ljud Operativsystem,HMI Ses İşletim Sistemi,, -"HMI SOS (Old, with bugs)",ADLVLMODEL_HMIOLD,,,,"HMI SOS (Starý, s chybami)","HMI SOS (gammelt, med fejl)","HMI SOS (alt, mit Fehlern)",,"HMI SOS (Malnova, kun cimoj)","HMI SOS (Viejo, con bugs)",,"HMI SOS (vanha, vikainen)",HMI SOS (vieux et buggé),"HMI SOS (Régi, bugokkal)",,"HMI SOS(旧式,バグ含む)",,"HMI SOS (oud, met bugs)","HMI SOS (gammel, med feil)","HMI SOS (Stare, z błędami)","SOS HMI (antigo, com bugs)",,"HMI SOS (Vechi, cu probleme)",HMI SOS (Старый вариант с ошибками),,"HMI SOS (gammalt, med fel)","HMI SOS (Eski, hatalı)",HMI SOS (Старий варіант з помилками), +"HMI SOS (Old, with bugs)",ADLVLMODEL_HMIOLD,,,,"HMI SOS (Starý, s chybami)","HMI SOS (gammelt, med fejl)","HMI SOS (alt, mit Fehlern)",,"HMI SOS (Malnova, kun cimoj)","HMI SOS (Viejo, con bugs)",,"HMI SOS (vanha, vikainen)",HMI SOS (vieux et buggé),"HMI SOS (Régi, bugokkal)",,HMI SOS(旧式、バグ含む),,"HMI SOS (oud, met bugs)","HMI SOS (gammel, med feil)","HMI SOS (Stare, z błędami)","SOS HMI (antigo, com bugs)",,"HMI SOS (Vechi, cu probleme)",HMI SOS (Старый вариант с ошибками),,"HMI SOS (gammalt, med fel)","HMI SOS (Eski, hatalı)",HMI SOS (Старий варіант з помилками), Unlimited,OPTVAL_UNLIMITED,,,,Neomezené,Ubegrænset,Unlimitiert,,Senlima,Ilimitado,,Rajoittamaton,Illimité,Végtelen,Illimitato,無制限,무제한,Onbeperkt,Ubegrenset,Nieskończone,Sem limites,,Nelimitat,Без ограничений,Бескрајно,Obegränsad,Sınırsız,Безмежно, 256K,OPTVAL_256K,,,,,,,,,,,,,,,,,,,,,,,,,,,, 512K,OPTVAL_512K,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -657,12 +657,12 @@ Enable hires textures,GLTEXMNU_ENABLEHIRES,,,,Povolit textury ve vysokém rozli High Quality Resize mode,GLTEXMNU_HQRESIZE,,,,Režim zvětšovače textur,Tilstand til ændring af størrelse i høj kvalitet,Texturskalierungsmodus,,Reĝimo de Altdistingivigo-Skalilo,Modo de ajuste de alta calidad,,Korkealaatuinen kuvakoon muutostapa,Mise à l'échelle haute résolution,Magas minőségű újraméretező mód,Modalità resize alta qualità,高品質リサイズ モード,고퀄리티 리사이즈 모드,Textuurschaalmodus,Høy kvalitet Endre størrelse-modus,Tryb Wysokiej Jakości Zmieniania Rozmiaru,Modo de Redimensionamento de Alta Qualidade,,Mod Redimensionare de Înaltă Calitate,Режим высококачественного масштабирования текстур,Промена величине високог квалитета мод,Högkvalitativt storleksanpassningsläge,Yüksek Kalite Yeniden Boyutlandırma modu,Масштабування текстур, High Quality Resize multiplier,GLTEXMNU_HQRESIZEMULT,,,,Faktor zvětšovače textur,Multiplikator for ændring af størrelse i høj kvalitet,Texturskaluerungsfaktor,,Obligilo de Altdistingivigo-Skalilo,Multiplicador de ajuste de alta calidad,,Korkealaatuisen kuvakokomuutoksen kerroin,Multiplicateur de mise à l'échelle,Magas minőségű újraméretezés szorzó,Moltiplicatore resize alta qualità,高品質リサイズ乗数,고퀄리티 리사이징 승수,Textuurschaalfactor,Multiplikator for størrelsesendring av høy kvalitet,Mnożnik Wysokiej Jakośći Rozmiaru,Multiplicador de Redimensionamento de Alta Qualidade,,Factor de Scalare,Множитель высококачественного масштабирования,Промена величине високог квалитета мултипликатор,Multiplikator för storleksändring av hög kvalitet,Yüksek Kalite Yeniden Boyutlandırma çarpanı,Множник масштабування, This mode requires %d times more video memory,GLTEXMNU_HQRESIZEWARN,,,,Tento režim potřebuje %dkrát více paměti,Denne tilstand kræver %d gange mere videohukommelse,Dieser Modus benötigt das %d-fache an Videospeicher,Αυτή η λειτουργία χρειάζετε %d περισσότερες φορές μνήμη βίντεο,Ĉi tiu reĝimo bezonas %d-oble pli da ekranmemoro.,Este modo requiere %d veces más memoria de vídeo,,Tämä tila vaatii %d kertaa enemmän videomuistia,Ce mode nécessite %d fois plus de mémoire vidéo,Ez a mód a jelenlegi videó memória %d-szeresét igényli,Questa modalità richiede %d volte la memoria video,このモードでは %d 倍以上のビデオメモリが必要です!,이 설정은 비디오 메모리의 %d 배가 더 필요합니다.,Deze modus vereist %d keer meer videogeheugen.,Denne modusen krever %d ganger mer videominne,Ten tryb wymaga %d razy więcej pamięci wideo,Este modo precisa de %d vezes mais memória de vídeo,,Acest mod necesită de %d mai multă memorie video,Данный режим требует в %d раз больше видеопамяти,Овај мод тражи %d пута више видео меморије,Det här läget kräver %d gånger mer videominne,Bu mod %d kat daha fazla video belleği gerektirir,Цьому типу потрібно в %d більше відеопам'яті, -Resize textures,GLTEXMNU_RESIZETEX,,,,Škálovat textury,Ændre størrelse på teksturer,Texturen skalieren,,Regrandigi teksturojn,Ajustar texturas,,Muuta pintakuviointien kokoa,Mise à l'échelle textures,Textúrák újraméretezése,Resize delle texture,リサイズ テクスチャー,텍스쳐 리사이징,Texturen schalen,Endre størrelse på teksturer,Zmień rozmiar tekstur,Redimensionar texturas,,Redimensionare texturi,Масштабирование текстур,Промена величине текстура,Ändra storlek på texturer,Dokuları yeniden boyutlandırma,Масштабування текстур, -Resize sprites,GLTEXMNU_RESIZESPR,,,,Škálovat sprity,Ændre størrelse på sprites,Sprites skalieren,,Regrandigi mov-rastrumojn,Ajustar sprites,,Muuta spritejen kokoa,Mise à l'échelle sprites,Sprite-ok újraméretezése,Resize degli sprite,リサイズ スプライト,스프라이트 리사이징,Sprites schalen,Endre størrelse på sprites,Zmień rozmiar sprite'ów,Redimensionar sprites,,Redimensionare sprite-uri,Масштабирование спрайтов,Промена величине спрајтова,Ändra storlek på sprites,Sprite'ları yeniden boyutlandırma,Масштабування спрайтів, -Resize fonts,GLTEXMNU_RESIZEFNT,,,,Škálovat fonty,Ændre størrelse på skrifttyper,Zeichensätze skalieren,,Regrandigi tiparojn,Ajustar fuentes,,Muuta kirjasinten kokoa,Mise à l'échelle texte,Betűk újraméretezése,Resize dei font,リサイズ フォント,폰트 리사이징,Lettertypen schalen,Endre størrelse på skrifter,Zmień rozmiar czcionek,Redimensionar fontes,,Redimensionare fonturi,Масштабирование шрифтов,Промена величине фонта,Ändra storlek på teckensnitt,Yazı tiplerini yeniden boyutlandırma,Масштабування шрифтів, -Resize model skins,GLTEXMNU_RESIZESKN,,,,Škálovat skiny modelů,Ændre størrelsen på modelskins,,,Regrandigi haŭtojn de modeloj,Ajustar texturas de modelo,,Muuta ulkoasujen kokoa,Mise à l'échelle des skins des modèles 3D,Model fazon újraméretezése,Ridimensiona skin modello,リサイズ モデルスキン,,,Endre størrelse på modellskinn,Zmień rozmiar modelu,Redimensionar skins de modelos,,Redimensionare modele,Масштабирование обликов моделей,,Ändra storlek på modellskinn,Model kaplamalarını yeniden boyutlandırma,Масштабування текстур моделей, -Precache GL textures,GLTEXMNU_PRECACHETEX,,,,Přednačíst GL textury do cache,Precache GL-teksturer,GL Texturen zwischenspeichern,,Antaŭkaŝmemorigi GL-teksturojn,Precaché de texturas GL,,Kirjoita GL-pintakuvioinnit välimuistiin,Mise en cache des textures,Előcachelt GL textúrák,Precache texture GL,プリキャッシュ GLテクスチャー,지엘 텍스쳐 미리 캐싱함,Precache GL texturen,Precache GL-teksturer,Tekstury GL pamięci podręcznej,Precachê de texturas GL,,Preîncărcarcă texturile GL,Кэшировать GL-текстуры,Прикеширане GL текстуре,Precache GL-texturer,GL dokularını önceden önbelleğe alma,Кешувати GL-текстури, -Video Mode,VIDMNU_TITLE,,,,Režim displeje,Videotilstand,Videomodus,Λειτουργία βίντεο,Video-reĝimo,Modos de vídeo,Modos de video,Videotila,Mode Vidéo,Videó mód,Modalità video,ビデオ 調整,화면 설정,Videomodus,Videomodus,Tryb Wideo,Modo de vídeo,,Mod Video,Настройки видеорежима,Видео мод,Videoläge,Video Modu,Налаштування відеорежиму, +Resize textures,GLTEXMNU_RESIZETEX,,,,Škálovat textury,Ændre størrelse på teksturer,Texturen skalieren,,Regrandigi teksturojn,Ajustar texturas,,Muuta pintakuviointien kokoa,Mise à l'échelle textures,Textúrák újraméretezése,Resize delle texture,テクスチャー類のリサイズ,텍스쳐 리사이징,Texturen schalen,Endre størrelse på teksturer,Zmień rozmiar tekstur,Redimensionar texturas,,Redimensionare texturi,Масштабирование текстур,Промена величине текстура,Ändra storlek på texturer,Dokuları yeniden boyutlandırma,Масштабування текстур, +Resize sprites,GLTEXMNU_RESIZESPR,,,,Škálovat sprity,Ændre størrelse på sprites,Sprites skalieren,,Regrandigi mov-rastrumojn,Ajustar sprites,,Muuta spritejen kokoa,Mise à l'échelle sprites,Sprite-ok újraméretezése,Resize degli sprite,スプライト類のリサイズ,스프라이트 리사이징,Sprites schalen,Endre størrelse på sprites,Zmień rozmiar sprite'ów,Redimensionar sprites,,Redimensionare sprite-uri,Масштабирование спрайтов,Промена величине спрајтова,Ändra storlek på sprites,Sprite'ları yeniden boyutlandırma,Масштабування спрайтів, +Resize fonts,GLTEXMNU_RESIZEFNT,,,,Škálovat fonty,Ændre størrelse på skrifttyper,Zeichensätze skalieren,,Regrandigi tiparojn,Ajustar fuentes,,Muuta kirjasinten kokoa,Mise à l'échelle texte,Betűk újraméretezése,Resize dei font,フォント類のリサイズ,폰트 리사이징,Lettertypen schalen,Endre størrelse på skrifter,Zmień rozmiar czcionek,Redimensionar fontes,,Redimensionare fonturi,Масштабирование шрифтов,Промена величине фонта,Ändra storlek på teckensnitt,Yazı tiplerini yeniden boyutlandırma,Масштабування шрифтів, +Resize model skins,GLTEXMNU_RESIZESKN,,,,Škálovat skiny modelů,Ændre størrelsen på modelskins,,,Regrandigi haŭtojn de modeloj,Ajustar texturas de modelo,,Muuta ulkoasujen kokoa,Mise à l'échelle des skins des modèles 3D,Model fazon újraméretezése,Ridimensiona skin modello,モデルスキン類のリサイズ,,,Endre størrelse på modellskinn,Zmień rozmiar modelu,Redimensionar skins de modelos,,Redimensionare modele,Масштабирование обликов моделей,,Ändra storlek på modellskinn,Model kaplamalarını yeniden boyutlandırma,Масштабування текстур моделей, +Precache GL textures,GLTEXMNU_PRECACHETEX,,,,Přednačíst GL textury do cache,Precache GL-teksturer,GL Texturen zwischenspeichern,,Antaŭkaŝmemorigi GL-teksturojn,Precaché de texturas GL,,Kirjoita GL-pintakuvioinnit välimuistiin,Mise en cache des textures,Előcachelt GL textúrák,Precache texture GL,プリキャッシュGLテクスチャー,지엘 텍스쳐 미리 캐싱함,Precache GL texturen,Precache GL-teksturer,Tekstury GL pamięci podręcznej,Precachê de texturas GL,,Preîncărcarcă texturile GL,Кэшировать GL-текстуры,Прикеширане GL текстуре,Precache GL-texturer,GL dokularını önceden önbelleğe alma,Кешувати GL-текстури, +Video Mode,VIDMNU_TITLE,,,,Režim displeje,Videotilstand,Videomodus,Λειτουργία βίντεο,Video-reĝimo,Modos de vídeo,Modos de video,Videotila,Mode Vidéo,Videó mód,Modalità video,ビデオ調整,화면 설정,Videomodus,Videomodus,Tryb Wideo,Modo de vídeo,,Mod Video,Настройки видеорежима,Видео мод,Videoläge,Video Modu,Налаштування відеорежиму, Scaled (Nearest),OPTVAL_SCALENEAREST,,,,Škálován (nejbližší),Skaleret (Nærmeste),Skaliert (nächster Nachbar),,Skalita (Plej proksime),Escalado (Lo más cerca),,Skaalattu (läheisin),Mis à l'échelle (Proche Voisin),Átméretezett (Közeli),Scalato (più vicino),スケーリング (最寄り),확대 (가깝게),Geschaald (Dichtstbijzijnd),Skalert (nærmeste),Przeskalowany (Najbliższy),Redimensionado (mais próximo),Redimensionado (Apróximado),Redimensionat (Cel mai aproape),Масштабировать (ближайшее),Скалиран (најближи),Skalad (närmast),Ölçeklendirilmiş (En Yakın),Збільшення (ближнє), Scaled (Linear),OPTVAL_SCALELINEAR,,,,Škálován (lineární),Skaleret (lineær),Skaliert(linear),,Skalita (Linie),Escalado (Lineal),,Skaalattu (lineaarinen),Mis à l'échelle (Linéaire),Átméretezett (Lineáris),Scalato (lineare),スケーリング (リニア),확대 (선형 식),Geschaald (Lineair),Skalert (lineær),Przeskalowany (Liniowy),Redimensionado (linear),,Redimensionat (Liniar),Масштабировать (линейное),Скалиран (линеарно),Skalad (linjär),Ölçeklendirilmiş (Doğrusal),Збільшення (лінійне), Letterbox,OPTVAL_LETTERBOX,,,,,,,,Leterkesto,Barras negras,,Mustat reunat,,Levágott,Bande nere,レターボックス,레터박스,,,,Barras pretas,,Ecran Parțial,Экранное каше,Поштанско сандуче,Brevlåda,,Техніка каше, @@ -843,7 +843,7 @@ Simple options menu,OPTMNU_SIMPLEON,,,,Zjednodušená nastavení,Enkel menu med Browse Game Config,OPTMNU_OPENCONFIG,,,,Procházet konfigurační soubory,Gennemse spilkonfiguration,Konfigurationsdatei anzeigen,,Foliumi agordojn de ludo,Abrir carpeta de configuración de juego,,Selaa pelin asetuksia,Parcourir configuration,Játék konfiguráció böngészése,Apri le Configurazioni di Gioco,ゲームコンフィグ参照,게임 환경설정 찾아보기,Door gameconfiguratie bladeren,Bla gjennom spillkonfigurasjon,Przeglądaj Ustawienia Gry,Abrir pasta de configuração de jogo,,Caută Configurația Jocului,Открыть файл настроек игры,,Bläddra i spelkonfigurationen,Oyun Yapılandırmasına Gözat,Перегляд налаштувань гри, Browse Screenshots,OPTMNU_OPENSCREENSHOTS,,,,Procházet snímky obrazovky,Gennemse skærmbilleder,Screenshots anzeigen,,Foliumi ekrankopiojn,Abrir carpeta de capturas de pantalla,,Selaa kuvakaappauksia,Parcourir les captures d'écran,Képernyőképek böngészése,Apri gli Screenshot,クリーンショット参照,스크린샷 찾아보기,Door schermafbeeldingen bladeren,Bla gjennom skjermbilder,Przeglądaj Zrzuty Ekranu,Abrir pasta de capturas de tela,,Caută Capturi de Ecran,Просмотр снимков экрана,,Bläddra bland skärmdumpar,Ekran Görüntülerine Gözat,Перегляд скріншотів, Browse Saved Games,OPTMNU_OPENSAVES,,,,Procházet uložené hry,Gennemse gemte spil,Spielstände anzeigen,,Foliumi konservitajn ludadojn,Abrir carpeta de partidas guardadas,,Selaa tallennettuja pelejä,Parcourir sauvegardes,Elmentett játékok böngészése,Apri i Giochi Salvati,セーブしたゲーム参照,저장된 게임 찾아보기,Door opgeslagen games bladeren,Bla gjennom lagrede spill,Przeglądaj Zapisy Gry,Abrir pasta de jogos salvos,,Caută Salvări,Просмотр сохранённых игр,,Bläddra bland sparade spel,Kaydedilen Oyunlara Gözat,Перегляд збережень ігр, -Swap mouse buttons,MOUSEMNU_SWAPBUTTONS,,,,Prohodit tlačítka myši,Udskift museknapper,Maustasten vertauschen,,Permuti musbutonojn,Alternar botones de ratón,,Vaihda hiiren painikkeita,Permuter les boutons de la souris,Egérgombok felcserélése,Inverti i comandi del mouse,マウスボタンを反転,마우스 버튼 바꾸기,Muisknoppen verwisselen,Bytt museknapper,Zamień Przyciski Myszki,Trocar botões do mouse,,Schimbă Butoanele Mouse-ului între Ele,Поменять местами кнопки мыши,,Byta musknappar,Fare düğmelerini değiştirme,Поміняти місцями кнопки миші, +Swap mouse buttons,MOUSEMNU_SWAPBUTTONS,,,,Prohodit tlačítka myši,Udskift museknapper,Maustasten vertauschen,,Permuti musbutonojn,Alternar botones de ratón,,Vaihda hiiren painikkeita,Permuter les boutons de la souris,Egérgombok felcserélése,Inverti i comandi del mouse,マウスボタンを反転,마우스 버튼 바꾸기,Muisknoppen verwisselen,Bytt museknapper,Zamień Przyciski Myszki,Trocar botões do mouse,,Schimbă Butoanele Mouse-ului între Ele,Поменять местами кнопки мыши,Замените дугмад миша,Byta musknappar,Fare düğmelerini değiştirme,Поміняти місцями кнопки миші, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,, Only modified,OPTVAL_ONLYMODIFIED,,,,Pouze upravené,Kun ændret,Nur modfizierte,,Nur modifitaj,Solo modificados,,Vain muunneltu,Modifié seulement,Csak módosított,Solo modificato,モディファイのみ,수정된 것만,Alleen gewijzigd,Bare endret,Tylko zmodyfikowane,Somente modificado,,Numai modificat,Только изменённый,Само модификовано,Endast ändrat,Yalnızca değiştirilmiş,Тільки в модифікаціях, Unknown,TXT_UNKNOWN,,,,Neznámé,Ukendt,Unbekannt,Άχνωστο,Nekonata,Desconocido,,Tuntematon,Inconnue,Ismeretlen,Sconosciuto,不明,알 수 없음,Onbekend,Ukjent,Nieznane,Desconhecido,,Necunoscut,Неизвестно,Непознат,Okänd,Bilinmiyor,Невідомо, @@ -851,11 +851,27 @@ HUD,OPTVAL_HUD,,,,,,,,,,,,ATH,,,,,,,,,,,Отображение информац Automap,OPTVAL_AUTOMAP,,,,Automapa,,,,Aŭtomata mapo,Automapa,,,Carte,Auto-térkép,Automappa,オートマップ,오토맵,,,Automapa,Automapa,,Hartă Computerizată,Автокарта,Аутомап,,,Автокарта, HUD + Automap,OPTVAL_HUDANDMAP,,,,HUD + Automapa,,,,HUD + Aŭtomata mapo,HUD + Automapa,,,ATH + Carte,HUD + Auto-térkép,HUD + Automappa,HUD+オートマップ,HUD + 오토맵,,,HUD + Automapa,HUD + Automapa,,Interfață + Hartă Computerizată,Интерфейс + автокарта,HUD + Аутомап,,,Інтерфейс + Автокарта, This savegame needs these files,TXT_SAVEGAMENEEDS,,,,Uložená hra potřebuje tyto soubory,Dette savegame har brug for disse filer,Dieser Spielstand benötigt die folgenden Dateien,Το αρχείο αποθήκευσης χριάζετε αυτά τα αρχεία,Ĉi tiu konservita ludo bezonas ĉi tiujn dosierojn,Esta partida guardada necesita los siguientes archivos,,Tämä pelitallenne tarvitsee nämä tiedostot,Cette sauvegarde nécessite les fichiers suivants:,Ehhez a mentéshez a következő fájlok kellenek:,Questo salvataggio ha bisogno di questi file,このセーブデータには 必要なファイルがある,이 저장 된 게임은 해당 파일이 필요합니다.,Dit spel heeft de volgende bestanden nodig,Dette lagringsspillet trenger disse filene,Ten zapis gry potrzebuje tych plików,Este jogo salvo precisa destes arquivos,Este jogo guardado precisa destes arquivos,Acest joc salvat necesită următoarele fișiere,Данное сохранение требует следующие файлы,Овој сачуваној игри требају ови фајлови,Detta sparande behöver dessa filer,Bu kayıt oyunu şu dosyalara ihtiyaç duyar,Цій грі потрібні такі файли, -Multiplayer Options,OPTMNU_MULTIPLAYER,,,,Multiplayer,Multiplayer-indstillinger,Mehrspieleroptionen,,Agordoj de la plurludanta reĝimo,Opciones del multijugador,,,,,,,,Multiplayer Opties,Alternativer for flerspiller,,Opções de multijogador,,,Настройки сетевой игры,,Alternativ för flera spelare,Çok Oyunculu Seçenekler,Параметри багатокористувацької гри, -Input Options,OPTMNU_INPUT,,,,Vstupní zařízení,Input-indstillinger,Eingabeoptionen,,Enig-agordoj,Opciones de entrada (input),,,,,,,,In1voeropties,Alternativer for inndata,,Opções de entrada,,,Настройки ввода,,Inmatningsalternativ,Girdi Seçenekleri,Параметри введення, -System Options,OPTMNU_SYSTEM,,,,Systém,Systemindstillinger,Systemoptionen,,Agordoj de la sistemo,Opciones del sistema,,,,,,,,Systeem Opties,Systemalternativer,,Opções de sistema,,,Настройки системы,,Systemalternativ,Sistem Seçenekleri,Системні налаштування, -Light Options,OPTMNU_LIGHT,,,,Osvětlení,Lysindstillinger,Beleuchtungsoptionen,,Lum-agordoj,Opciones de iluminación,,,,,,,,Licht Opties,Alternativer for lys,,Opções de iluminação,,,Настройки освещения,,Alternativ för ljus,Işık Seçenekleri,Параметри освітлення, -Sprite Options,OPTMNU_SPRITE,,,,Sprity,Sprite-indstillinger,Spriteoptionen,,Agordoj de la mov-rastrumoj,Opciones de los «sprites»,,,,,,,,Sprite-opties,Sprite-alternativer,,Opções de sprite,,,Настройки спрайтов,,Alternativ för sprite,Sprite Seçenekleri,Параметри спрайтів, -Coronas,GLPREFMNU_CORONAS,,,,Záře,Coronas,,,Lum-ampoloj,Focos de luz,,,,,,,,Corona's,Koronaer,,,,,Лампы,,Koronor,Koronalar,Корони, -Appearance,DSPLYMNU_APPEARANCE,,,,Vzhled,Udseende,Spieldarstellung,,Aspekto,Apariencia,,,,,,,,Uiterlijk,Utseende,,Aparência,,,Внешность,,Utseende,Görünüş,Зовнішній вигляд, -Advanced Display Options,DSPLYMNU_ADVANCED,,,,Grafika (pokročilé),Avancerede visningsindstillinger,Erweiterte Anzeigeoptionen,,Altnivelaj ekran-agordoj,Opciones avanzadas de visualización,,,,,,,,Geavanceerde Weergave Opties,Avanserte visningsalternativer,,Opções avançadas de vídeo,,,Расширенные настройки экрана,,Avancerade visningsalternativ,Gelişmiş Görüntüleme Seçenekleri,Додаткові параметри відображення, \ No newline at end of file +Multiplayer Options,OPTMNU_MULTIPLAYER,,,,Multiplayer,Multiplayer-indstillinger,Mehrspieleroptionen,,Agordoj de la plurludanta reĝimo,Opciones del multijugador,,,Options multijoueurs,,,マルチプレイ オプション,,Multiplayer Opties,Alternativer for flerspiller,Opcje Trybu Wielu Graczy,Opções de multijogador,,,Настройки сетевой игры,Опције за више играча,Alternativ för flera spelare,Çok Oyunculu Seçenekler,Параметри багатокористувацької гри, +Input Options,OPTMNU_INPUT,,,,Vstupní zařízení,Input-indstillinger,Eingabeoptionen,,Enig-agordoj,Opciones de entrada (input),,,Options d'entrée,,,入力オプション,,In1voeropties,Alternativer for inndata,Opcje Sterowania,Opções de entrada,,,Настройки ввода,Опције уноса,Inmatningsalternativ,Girdi Seçenekleri,Параметри введення, +System Options,OPTMNU_SYSTEM,,,,Systém,Systemindstillinger,Systemoptionen,,Agordoj de la sistemo,Opciones del sistema,,,Options du système,,,システムオプション,,Systeem Opties,Systemalternativer,Opcje Systemowe,Opções de sistema,,,Настройки системы,Системске опције,Systemalternativ,Sistem Seçenekleri,Системні налаштування, +Light Options,OPTMNU_LIGHT,,,,Osvětlení,Lysindstillinger,Beleuchtungsoptionen,,Lum-agordoj,Opciones de iluminación,,,Options de lumière,,,光源オプション,,Licht Opties,Alternativer for lys,Opcje Oświetlenia,Opções de iluminação,,,Настройки освещения,Опције осветљења,Alternativ för ljus,Işık Seçenekleri,Параметри освітлення, +Sprite Options,OPTMNU_SPRITE,,,,Sprity,Sprite-indstillinger,Spriteoptionen,,Agordoj de la mov-rastrumoj,Opciones de los «sprites»,,,Options des sprites,,,スプライトオプション,,Sprite-opties,Sprite-alternativer,Opcje Sprite'ów,Opções de sprite,,,Настройки спрайтов,Сприте опције,Alternativ för sprite,Sprite Seçenekleri,Параметри спрайтів, +Coronas,GLPREFMNU_CORONAS,,,,Záře,Coronas,,,Lum-ampoloj,Focos de luz,,,Corons,,,光冠,,Corona's,Koronaer,Korony,,,,Короны,Цоронас,Koronor,Koronalar,Корони, +Appearance,DSPLYMNU_APPEARANCE,,,,Vzhled,Udseende,Spieldarstellung,,Aspekto,Apariencia,,,Apparence,,,アピアランス,,Uiterlijk,Utseende,Wygląd,Aparência,,,Внешность,Изглед,Utseende,Görünüş,Зовнішній вигляд, +Advanced Display Options,DSPLYMNU_ADVANCED,,,,Grafika (pokročilé),Avancerede visningsindstillinger,Erweiterte Anzeigeoptionen,,Altnivelaj ekran-agordoj,Opciones avanzadas de visualización,,,Options d'affichage avancées,,,高度なディスプレイオプション,,Geavanceerde Weergave Opties,Avanserte visningsalternativer,Zaawansowane Opcje Wyświetlania,Opções de vídeo avançadas,,,Расширенные настройки экрана,Напредне опције приказа,Avancerade visningsalternativ,Gelişmiş Görüntüleme Seçenekleri,Додаткові параметри відображення, +Select which game file to run.,PICKER_SELECT,,,,,"Vælg, hvilket spil du vil spille",Bitte wähle ein Spiel aus.,,,,,,Sélectionner le jeu à jouer,,,,,Selecteer welk spel je wilt spelen,Velg hvilket spill du vil spille,,Selecione o arquivo de jogo para rodar.,,,Выбор файла игры для запуска.,Изаберите коју датотеку игре желите да покренете.,Välj vilket spel du vill spela,Hangi oyunu oynayacağınızı seçin,, +Play Game,PICKER_PLAY,,,,,Start spil,Spielen,,,,,,Démarrer le jeu,,,,,Spel starten,Start spill,,Jogar,,,Играть,Играј игру,Starta spel,Oyunu Başlat,, +Exit,PICKER_EXIT,,,,,Afslut,Verlassen,,,,,,Quitter,,,,,Verlaten,Avslutt,,Sair,,,Выход,Изађи,Avsluta,Çıkış,, +General,PICKER_GENERAL,,,,,Generelt,Allgemein,,,,,,Général,,,,,Algemeen,Generelt,,Geral,,,Общее,Генерал,Allmänt,Genel,, +Extra Graphics,PICKER_EXTRA,,,,,Ekstra grafik,Extragrafiken,,,,,,Graphiques supplémentaires,,,,,Extra afbeeldingen,Ekstra grafikk,,Gráficos extra,,,Доп. графика,Ектра Грапхицс,Extra grafik,Ekstra Grafikler,, +Fullscreen,PICKER_FULLSCREEN,,,,,Fuld skærm,Vollbild,,,,,,Plein écran,,,,,Volledig scherm,Fullskjerm,,Tela cheia,,,Полный экран,Цео екран,Fullskärm,Tam Ekran,, +Disable autoload,PICKER_NOAUTOLOAD,,,,,Deaktiver autoload,Autoload deaktivieren,,,,,,Désactiver le chargement automatique,,,,,Autoload uitschakelen,Deaktiver autolading,,Desativar autocarregamento,,,Отключить автозагрузку,Онемогући аутоматско учитавање,Inaktivera autoload,Otomatik yükleme yok,, +Don't ask me again,PICKER_DONTASK,,,,,Spørg mig ikke igen,Nicht nochmal fragen,,,,,,Ne me demandez plus rien,,,,,Vraag me niet opnieuw,Ikke spør meg igjen,,Não me pergunte de novo,,,Не спрашивать снова,Не питај ме поново,Fråga mig inte igen,Bir daha sorma.,, +Lights,PICKER_LIGHTS,,,,,Lys,Lichtdefinitionen,,,,,,Lumières,,,,,Verlichting,Lysdefinisjoner,,Luzes,,,Освещение,Светла,Definitioner av ljus,Işık tanımları,, +Brightmaps,PICKER_BRIGHTMAPS,,,,,,,,,,,,Cartes lumineuses,,,,,Heldermaps,Lyskart,,,,,Карты освещения,Бригхтмапс,Ljuskartor,Brightmaps,, +Widescreen,PICKER_WIDESCREEN,,,,,,Breitbildunterstützung,,,,,,Écran large,,,,,Breedbeeld,Bredskjerm,,,,,Широкий экран,Широки екран,Bredbildsskärm,Geniş Ekran,, +Additional Parameters:,PICKER_ADDPARM,,,,,Yderligere parametre:,Zusätzliche Parameter,,,,,,Paramètres supplémentaires :,,,,,Extra parameters:,Ytterligere parametere:,,Parâmetros adicionais:,,,Доп. параметры:,Додатни параметри:,Ytterligare parametrar:,Ek Parametreler:,, +Welcome to %s!,PICKER_WELCOME,,,,,Velkommen til %s!,Willkommen bei %s!,,,,,,Bienvenue à %s !,,,,,Welkom bij %s!,Velkommen til %s!,,Boas vindas ao %s!,,,Добро пожаловать в %s!,Добродошли у %s!,Välkommen till %s!,S'ye hoş geldiniz!,, +Version %s,PICKER_VERSION,,,,,,,,,,,,,,,,,Versie %s,Versjon %s,,Versão %s,,,Версия %s,Верзија %s,,Sürüm %s,, +Rendering API,PICKER_PREFERBACKEND,,,,API vykreslování,,Render API,,Preferita bildigado de API,API de renderizado,,Renderöinti API,API de rendu,Renderelő API,API di rendering,優先レンダリングAPI,기본적인 API 랜더링,,,API renderowania,API de renderização,,API Video Preferat,API для рендеринга,АПИ приказивања,API för rendering,,API для візуалізації, +Game,PICKER_TAB_PLAY,,,,Hra,Spil,Spiel,,Ludo,Juego,,Peli,Jeu,Játék,Gioco,ゲーム,게임,Spel,Spill,Gra,Jogo,,Joc,Игра,Игра,Spel,Oyun,Гра, \ No newline at end of file diff --git a/wadsrc/static/language.csv b/wadsrc/static/language.csv index ec27ef81e..0802f49e1 100644 --- a/wadsrc/static/language.csv +++ b/wadsrc/static/language.csv @@ -402,7 +402,7 @@ versão demo de Blood.",,Salvarea și încărcarea jocului nu este suportată de не поддерживается в данной демо-версии игры Blood.",,"Laddning och sparande av spel stöds inte i denna demoversion av Blood.",Blood'ın bu demo sürümünde oyun yükleme ve kaydetme desteklenmiyor. -Are you sure you want to quit this game?,CONFIRM_QUITMSG,,,,"Jsi si jistý, že chceš z této hry odejít?","Er du sikker på, at du vil stoppe med dette spil?","Bist du dir sicher, dass du gehen willst?",,"Ĉu vi certas, ke vi volas forlasi ĉi tiun ludon?",¿Estás segur@[ao_esp] de que quieres salir de este juego?,,Haluatko varmasti lopettaa?,Êtes vous sûr de vouloir quitter ?,Biztos vagy benne hogy ki akarsz lépni?,Sei sicuro di voler uscire?,本当に終了するのか?,정말 종료하시겠습니까?,Weet je zeker dat je wilt stoppen?,Er du sikker på at du vil avslutte dette spillet?,Czy jesteś pewien że chcesz wyjść?,Tem certeza que quer sair?,Tens a certeza que queres sair?,Ești sigur că vrei să ieși din joc?,Вы действительно желаете выйти?,Да ли сте сигурни да желите да одустанеш?,Är du säker på att du vill avsluta det här spelet?,Bu oyundan çıkmak istediğinizden emin misiniz? +Are you sure you want to quit this game?,CONFIRM_QUITMSG,,,,"Jsi si jistý, že chceš z této hry odejít?","Er du sikker på, at du vil stoppe med dette spil?","Bist du dir sicher, dass du gehen willst?",,"Ĉu vi certas, ke vi volas forlasi ĉi tiun ludon?",¿Estás segur@[ao_esp] de que quieres salir de este juego?,,Haluatko varmasti lopettaa?,Êtes vous sûr de vouloir quitter ?,Biztos vagy benne hogy ki akarsz lépni?,Sei sicuro di voler uscire?,本当に終了するのか?,정말 종료하시겠습니까?,Weet je zeker dat je wilt stoppen?,Er du sikker på at du vil avslutte dette spillet?,Czy jesteś pewien że chcesz wyjść?,Deseja mesmo sair deste jogo?,Tens a certeza que queres sair?,Ești sigur că vrei să ieși din joc?,Вы действительно желаете выйти?,Да ли сте сигурни да желите да одустанеш?,Är du säker på att du vill avsluta det här spelet?,Bu oyundan çıkmak istediğinizden emin misiniz? Reset controls to defaults?,CONFIRM_CTRL1,,,,Obnovit ovládání na výchozí?,Nulstille kontrollen til standardindstillingerne?,Steuerung auf Standard zurücksetzen?,,,¿Resetear controles por defecto?,,Haluatko palauttaa oletusohjaukset?,Remettre les contrôles à zéro?,Vissza akarod állítani a billentyű kiosztást az alapra?,Ripristinare i controlli alle impostazioni predefinite?,,,Besturingen terugzetten naar standaard?,Tilbakestille kontrollene til standardinnstillingene?,Przywrócić domyślne sterowanie?,Redefinir controles para o modo padrão?,,Revenire la schema de control implicită?,Сбросить настройки управления по умолчанию?,,Återställa kontrollerna till standardvärdena?,Kontrolleri varsayılanlara sıfırla? Reset controls to classic defaults?,CONFIRM_CTRL2,,,,Obnovit ovládání na klasické?,Nulstille kontrollen til de klassiske standardindstillinger?,Steuerung auf klassischen Standard zurücksetzen?,,,¿Resetear controles clásicos por defecto?,,Haluatko palauttaa alkuperäiset oletusohjaukset?,Remettre les contrôles à zéro mode classique?,Vissza akarod állítani a billentyű kiosztást a klasszikus alapra?,Ripristinare i controlli alle impostazioni predefinite classiche?,,,Besturingen terugzetten naar klassieke standaard?,Tilbakestill kontroller til klassiske standardinnstillinger?,Przywrócić klasyczne sterowanie?,Redefinir controles para o modo padrão clássico?,,Revenire la schema de control clasică?,Сбросить настройки управления по умолчанию классических?,,Återställa kontrollerna till klassiska standardvärden?,Kontrolleri klasik varsayılanlara sıfırla? Reset controls to left-handed defaults?,CONFIRM_CTRL3,,,,Obnovit ovládání na výchozí pro leváky?,Nulstille kontrol til standardindstillingerne for venstrehåndede?,Steuerung auf linkshändigen Standard zurücksetzen?,,,¿Resetear controles zurdos por defecto?,,Haluatko palauttaa vasenkätisen oletusohjaukset?,Remettre les contrôles à zéro mode gaucher?,Vissza akarod állítani a billentyű kiosztást a balkezes alapra?,Ripristinare i comandi alle impostazioni predefinite per i mancini?,,,Besturingen terugzetten naar standaard voor linkshandigen?,Tilbakestill kontroller til venstrehendt standard?,Przywrócić sterowanie dla leworęcznych?,Redefinir controles para o modo padrão canhoto?,,Revenire la schema de control pentru stângaci?,Сбросить настройки управления по умолчанию левши?,,Återställa kontrollerna till standardinställningar för vänsterhänta?,Kontrolleri sol el varsayılanlarına sıfırla? @@ -628,7 +628,7 @@ Derelict,DERELICT,,,,Derelikt,Vraget,Wrack,,Ŝippereo,Barco Abandonado,,Hylky,En The Queen,THE QUEEN,,,,Královna,Dronningen,Die Königin,,La Reĝino,La Reina,,Kuningatar,La Reine,A Királynő,La Regina,ザ・クィーン,,De Koningin,Dronningen,Królowa,A Rainha,,Regina,Королева,,Drottningen,Kraliçe Area 51,AREA 51,,,,Oblast 51,,,,Areo 51,Área 51,,Alue 51,Zone 51,51-es Körzet,,エリア51,,,,Strefa 51,Área 51,,Zona 51,Зона №51,,,51. Bölge ,,,,,,,,,,,,,,,,,,,,,,,,,,, -High Times,#E5L1,,,,Dobrý časy,Store tider,Hochgefühl,,Altfojoj,Un Bong Momento,,Korkeat ajat,Le Bong Moment,Fűre tépni szabad,Un Pò di Sballo,ハイ・タイムズ,,Hoge Tijden,Høye tider,Na Haju,Altas Horas,,E Timpul,Кайфовое время,,Högtidliga tider,Harika Zamanlar +High Times,#E5L1,,,,Dobrý časy,Store tider,Hochgefühl,,Altfojoj,Un Bong Momento,,Korkeat ajat,Le Bong Moment,Fűre tépni szabad,Un Po' di Sballo,ハイ・タイムズ,,Hoge Tijden,Høye tider,Na Haju,Altas Horas,,E Timpul,Кайфовое время,,Högtidliga tider,Harika Zamanlar Red Ruckus,#E5L2,,,,Rudý rozruch,Rødt brag,Roter Krawall,,Ruĝa Tumulto,Jaleo Rojo,,Punainen hälinä,Boucan Sanglant,Ruszki Ricsaj,Rissa Rossa,赤い騒動,,Rood Vuur,Rød rabalder,Czerwony Jazgot,Baderna Vermelha,,Confuzie Sângeroasă,Кровавый кавардак,,Röd ruckus,Kırmızı Patırtı Bloody Hell,#E5L3,,,,K čertu,Blodigt helvede,Zur Hölle,,Sanga Infero,Infierno Sangriento,,Hurmehorna,Bon Sang,Angol Őrület,Inferno di Sangue,なんてこった,,Bloedige hel,Blodig helvete,Kurczę Blaszka,Que Inferno,,Lumea Interlopă,В сердце преисподней,,Blodigt helvete,Kanlı Cehennem Mirage Barrage,#E5L4,,,,Fata záplava,,Trügerischer Prügelhagel,,Miraĝa Batado,Descarga Milagrosa,,Kangastuskangistus,Barrage Mirage,Megtévesztő Zárótűz,Sbarramento Miraggio,ミラージュ・バラージ,,,Fata Morgana,Fatamorgana,Barragem de Miragem,,Baraj Miraj,Опасный мираж,,,Mirage Barajı @@ -674,10 +674,10 @@ Nuclear Winter,NUCLEAR WINTER,,,,Jaderná zima,Nuklear vinter,Nuklearer Winter,, Deja Vu,DEJA VU,,,,,,,,Deĵavuo,Déjà vu,,Entiselämys,Déjà Vu,Deja Vu,Deja Vu,,,,Déjà Vu,,,,,Опыт прошлого,,,Deja Vu Where It All Began,WHERE IT ALL BEGAN,,,,Kde to všechno začalo,Hvor det hele begyndte,Wo es alles anfing,,,Donde Todo Comenzó,,Missä kaikki alkoi,Où Tout A Commencé,Ahol az Egész Kezdődött,Dove Tutto è Cominciato,,,Waar het allemaal begon,Der det hele begynte,Gdzie się Wszystko Zaczęło,Onde Tudo Começou,,Unde A Început Totul,С чего всё началось,,Där allting började,Her Şeyin Başladığı Yer Land Of Forgotten Toys,LAND OF FORGOTTEN TOYS,,,,Země ztracených hraček,Landet med det glemte legetøj,Land der vergessenen Spielzeuge,,,Tierra de los Juguetes Perdidos,,Unohdettujen lelujen maa,Pays des Jouets Perdus,Az Elfelejtett Játékok Földje,La terra dei giocattoli dimenticati,,,Land van Vergeten Speelgoed,De glemte lekenes land,Kraina Zapomnianych Zabawek,Terra dos Brinquedos Esquecidos,,Tărâmul Jucăriilor Uitate,Сказка о забытых игрушках,,Landet med bortglömda leksaker,Unutulmuş Oyuncaklar Ülkesi -Santa's Corporate HQ,SANTA'S CORPORATE HQ,,,,Santovo firemní sídlo,Julemandens hovedkvarter,Santas Hauptquartier,,,Cuartel Corporativo de Santa,,Pukin firman päämaja,QG du Père Noël,A Télapó Székhelye,Quartier generale di Babbo Natale,,,Kerstman's bedrijfshoofdkwartier,Julenissens hovedkvarter,Korporacja Mikołaja,QG Corporativo do Papai Noel,,Sediul Moșului,Штаб-квартира Санты,,Jultomtens högkvarter,Noel Baba'nın Şirket Merkezi +Santa's Corporate HQ,SANTA'S CORPORATE HQ,,,,Santovo firemní sídlo,Julemandens hovedkvarter,Santas Hauptquartier,,,Cuartel Corporativo de Santa,,Pukin firman päämaja,QG du Père Noël,A Télapó Székhelye,Quartier generale di Babbo Natale,,,Kerstman's bedrijfshoofdkwartier,Julenissens hovedkvarter,Siedziba Mikołaja,QG Corporativo do Papai Noel,,Sediul Moșului,Штаб-квартира Санты,,Jultomtens högkvarter,Noel Baba'nın Şirket Merkezi The Backdoor,THE BACKDOOR,,,,Zadní vrátka,Bagdøren,Die Hintertür,,,La Puerta Trasera,,Takaovi,L'entrée de derrière,A Hátsó Bejárat,La porta sul retro,,,De achterdeur,Bakdøren,Tylne Wejście,A Porta dos Fundos,,Ușa din Spate,Чёрный ход,,Bakdörren,Arka Kapı Christmas Village,CHRISTMAS VILLAGE,,,,Vánoční vesnička,Julebyen,Weihnchtsdorf,,,Aldea de la Navidad,,Joulukylä,Village de Noël,Karácsonyi Falu,Villaggio di Natale,,,Kerstdorp,Julelandsbyen,Świąteczna Wioska,Vilarejo Natalino,,Sat de Crăciun,Рождественская деревня,,Julbyn,Noel Köyü -Here Comes Santa Claws,HERE COMES SANTA CLAWS,,,,Satan Klaus přichází,Her kommer julemandens kløer,Hier kommt Santas Klaue,,,Aquí viene Santa Clavos,,Joulupukki puskuun jo käy,Voilà la Peur Noël,Itt Jön Rémapó,Arriva Babbo Letale,,,Hier komt de Kerstman,Her kommer julenisseklørne,Pazurki Mikołaja,Lá Vem o Mau Velhinho,,Vine Moș Gheare,Когти Санты,,Här kommer tomteklorna,İşte Noel Baba Geliyor +Here Comes Santa Claws,HERE COMES SANTA CLAWS,,,,Satan Klaus přichází,Her kommer julemandens kløer,Hier kommt Santas Klaue,,,Aquí viene Santa Clavos,,Joulupukki puskuun jo käy,Voilà la Peur Noël,Itt Jön Rémapó,Arriva Babbo Letale,,,Hier komt de Kerstman,Her kommer julenisseklørne,Nadchodzi Walnięty Mikołaj,Lá Vem o Mau Velhinho,,Vine Moș Gheare,Когти Санты,,Här kommer tomteklorna,İşte Noel Baba Geliyor Santamatch,SANTAMATCH,,,,,,,,,,,Pukkimatsi,,,Match di Babbo Natale,,,,,Mecz Mikołajów,Natalmatch,,,Матч Санты,,,Noel Baba Maçı ,,NAM,,,,,,,,,,,,,,,,,,,,,,,,, Inventory Item,INVENTORY ITEM,Looks like a placeholder,,,Předmět inventáře,Inventargenstand,Inventar,,,Objeto de Inventario,,Varuste,Objet Inventaire,Leltári Tárgy,Oggetto dell'inventario,Inventario,,Inventaris Item,Inventargjenstand,Przedmiot w Ekwipunku,Item de inventário,,Obiect de Inventar,Предмет инвентаря,,Inventarieobjekt,Envanter Öğesi @@ -1303,7 +1303,7 @@ Dead Reckoning,Dead Reckoning,https://www.imdb.com/title/tt0039305,Blood,,Zúčt BloodBath,BloodBath,,Blood,,Krvavá koupel,Blodbad,Blutbad,,,Baño de Sangre,,,Bain de sang,Vérfürdő,Bagno di sangue,,,Bloedbad,,Rozlew Krwi,Banho de Sangue,,Baie de Sânge,Кровавая баня,,Blodbad,Kan Banyosu Post Mortem,Post Mortem,,Blood,,,,,,,,,,,Halottszemle,,,,,,,,,,Пост Мортем,,, Cradle to Grave,Cradle to Grave,,Blood,,Z kolébky do rakve,Vugge til grav,Von der Wiege bis zum Grabe,,,De la Cuna a la Tumba,,Kehto haudasta hautaan,Du berceau à la tombe,Bölcsőtöl a Koporsóig,Dalla culla alla tomba,,,Wieg tot Graf,Fra vugge til grav,Od Kołyski aż po Grób,Do Berço à Cova,,Lumânare la Mormânt,От колыбели до могилы,,Från vagga till grav,Beşikten Mezara -Hit the switch to end the level.,Hit the switch to end the level.,,Blood,,Stiskni páku pro ukončení úrovně.,Tryk på kontakten for at afslutte niveauet.,Drücke den Schalter um das Level zu beenden.,,,Dale al interruptor para terminar el nivel.,,Paina kytkintä tason päättämiseksi.,Appuyez sur le bouton pour terminer le niveau.,Húzd meg a kart a továbblépéshez.,Premi l'interruttore per terminare il livello.,,,Druk op de schakelaar om het level te beëindigen.,Trykk på bryteren for å avslutte nivået.,Wciśnij przycisk by ukończyć poziom.,Aperte para terminar a fase,,Apasă butonul pentru a încheia nivelul.,"Нажмите на переключатель, чтобы закончить уровень.",,Tryck på knappen för att avsluta nivån.,Seviyeyi bitirmek için düğmeye basın. +Hit the switch to end the level.,Hit the switch to end the level.,,Blood,,Stiskni páku pro ukončení úrovně.,Tryk på kontakten for at afslutte niveauet.,Drücke den Schalter um das Level zu beenden.,,,Dale al interruptor para terminar el nivel.,,Paina kytkintä tason päättämiseksi.,Appuyez sur le bouton pour terminer le niveau.,Húzd meg a kart a továbblépéshez.,Premi l'interruttore per terminare il livello.,,,Druk op de schakelaar om het level te beëindigen.,Trykk på bryteren for å avslutte nivået.,Wciśnij przycisk by ukończyć poziom.,Aperte o botão para terminar a fase.,,Apasă butonul pentru a încheia nivelul.,"Нажмите на переключатель, чтобы закончить уровень.",,Tryck på knappen för att avsluta nivån.,Seviyeyi bitirmek için düğmeye basın. "Slurp, slurp . . .","Slurp, slurp . . .",,Blood,,"Hlt, hlt...","Slurp, slurp . . .","Schlürf, schlürf...",,,,,"Slurp, slurp...","Slurp, slurp...","Szürcs, szürcs . . .","Slurp, slurp...",,,"Slurp, slurp...",,"Siorb, siorb . . .",,,"Soarbe, soarbe . . .",Хлюп-хлюп . . .,,,"Slurp, slurp ..." OUT! OUT! DAMN SPOT!,OUT! OUT! DAMN SPOT!,http://shakespeare.mit.edu/macbeth/macbeth.5.1.html,Blood,,"PRYČ S TEBOU, PROKLETÁ SKVRNO!",UD! UD! FANDME SPOT!,RAUS! RAUS! VERDAMMT!!,,,"¡FUERA, MANCHA MALDITA! ¡FUERA, TE DIGO!",,ULOS! OUT! PERKELEEN PAIKKA!,OUSTE ! DEHORS ! MAUDIT POINT !,"El, átkozott folt! Mondom el!",FUORI! FUORI! DANNATO SPOT!,,,OUT! UIT! DAMN SPOT!,UT! UT! JÆVLA FLEKK!,"PRECZ STĄD, PRZEKLĘTA PLAMO!",SAI! SAI! MANCHA MALDITA!,,IEȘI! IEȘI! PATĂ AFURISITĂ!,"УБИРАЙСЯ ОТСЮДА, ЧЁРТОВО ПЯТНО!",,UT! UT! JÄVLA PLATS!,ÇIK! ÇIK! LANET NOKTA! They'll need more of these.,They'll need more of these.,,Blood,,Těch budou potřebovat víc.,De får brug for flere af disse.,Die werden mehr davon brauchen.,,,Necesitarán más de eso.,,Näitä tarvitaan lisää.,Ils vont avoir besoin de plus de ça.,Többre lesz szükségük.,Ne serviranno altri.,,,Hiervan hebben ze er meer nodig.,De trenger flere av disse.,Będą potrzebować ich więcej.,Vão precisar de mais destes.,,Vor avea nevoie de mai multe.,Им понадобится больше этих штук.,,De kommer att behöva fler sådana här.,Bunlardan daha çok lazım olacak. @@ -1410,8 +1410,8 @@ Four levels (Shareware Version),TXTS_EPD1,,,,Čtyři úrovně (shareware verze), Eighteen levels (Full Version Only),TXTS_EPD2,,,,Osmnáct úrovní (plná verze),Atten niveauer (kun den fulde version),Achtzehn Level (Vollversion),,,Dieciocho niveles (Solo en la versión completa),,Kahdeksantoista tasoa (Vain täysversio),Dix-huit niveaux (version complète uniquement),Tizennyolc Pálya (Csak Teljes Verzió),Diciotto livelli (versione Completa),,,Achttien levels (Alleen volledige versie),Atten nivåer (kun fullversjon),Osiemnaście Poziomów (Tylko Pełna Wersja),Dezoito fases (somente na versão completa),,Optsprezece nivele (Doar Versiunea Completă),Восемнадцать уровней (только в полной версии),,Arton nivåer (endast den fullständiga versionen),On sekiz seviye (Sadece Tam Sürüm) Tiny Grasshopper,TXTS_SK1,,,,Malá kobylka,Lille græshoppe,Kleiner Grashüpfer,,Saltuleto,Pequeño saltamontes,,Pieni heinäsirkka,Petite Sauterelle,Kicsiny Szöcske,Piccola Cavalletta,,,Kleine sprinkhaan,Liten gresshoppe,Tyci Pasikonik,Pequeno Gafanhoto,,Lăcustă Mititică,Маленький кузнечик,,Liten gräshoppa,Minik Çekirge I Have No Fear,TXTS_SK2,,,,Nebojím se,Jeg har ingen frygt,Ich habe keine Angst,,Mi ne timas,No tengo miedo,,En pelkää,Je n'ai pas peur,Nincs bennem félelem,Non Conosco la Paura,,,Ik heb geen angst,Jeg har ingen frykt,Nie Wiem Co To Strach,Não Tenho Medo,,Nu Am Nicio Teamă,Я ничего не боюсь,,Jag är inte rädd,Korkum Yok -Who Wants Wang,TXTS_SK3,,,,Kdo chce Wanga,Hvem vil have Wang,Wer will Wang,,Kiu volas Lo-Vangon?,¿Quién quiere Wang?,,Kuka haluaa Wangin,Qui veut Wang,Kinek kell egy kis Wang,Chi Vuole un Pò di Wang,,,Wie wil er Wang,Hvem vil ha Wang,Kto Chce Wanga,Quem Quer Wang,,Cine Vrea Niște Wang,Кто хочет Ванга,,Vem vill ha Wang,Wang'ı Kim İstiyor -No Pain No Gain,TXTS_SK4,,,,Bez bolesti to nejde,Ingen smerte ingen gevinst,Kein Schmerz kein Gewinn,,"Kiu ne riskas, tiu ne gajnas",El que no arriesga no gana,,Ei kipua ei voittoa,"Pas de Souffrance, pas de Succès",A Siker Titka a Kemény Munka,Nessun Dolore Nessun Guadagno,,,"Geen pijn, geen winst","Ingen smerte, ingen gevinst",Bez Pracy Nie Ma Kołaczy,Só Se Vence com Garra,,Nu Riști Nu Câștigi,"Нет боли, нет выгоды",,No Pain No Gain,Acı Yok Kazanç Var +Who Wants Wang,TXTS_SK3,,,,Kdo chce Wanga,Hvem vil have Wang,Wer will Wang,,Kiu volas Lo-Vangon?,¿Quién quiere Wang?,,Kuka haluaa Wangin,Qui veut Wang,Kinek kell egy kis Wang,Chi Vuole un Po' di Wang,,,Wie wil er Wang,Hvem vil ha Wang,Kto Chce Wanga,Quem Quer Wang,,Cine Vrea Niște Wang,Кто хочет Ванга,,Vem vill ha Wang,Wang'ı Kim İstiyor +No Pain No Gain,TXTS_SK4,,,,Bez bolesti to nejde,Ingen smerte ingen gevinst,Kein Schmerz kein Gewinn,,"Kiu ne riskas, tiu ne gajnas",El que no arriesga no gana,,Ei kipua ei voittoa,"Pas de Souffrance, pas de Succès",A Siker Titka a Kemény Munka,Nessun Dolore Nessun Guadagno,,,"Geen pijn, geen winst","Ingen smerte, ingen gevinst",Bez Pracy Nie Ma Kołaczy,Sofrer para Vencer,,Nu Riști Nu Câștigi,"Нет боли, нет выгоды",,No Pain No Gain,Acı Yok Kazanç Var Got the RED key!,TXTS_KEY1,,,,ČERVENÝ klíč!,Du fik den RØDE nøgle!,Der ROTE Schlüssel!,,,¡Tienes la llave ROJA!,,Sain PUNAISEN avaimen!,Ramassé la clé ROUGE !,Megszerezted a PIROS kulcsot!,Hai ottenuto la chiave ROSSA!,,,Je hebt de RODE sleutel!,Har den RØDE nøkkelen!,Zdobyto CZERWONY klucz!,Pegou a chave VERMELHA!,,Ai cheia ROȘIE!,Получен КРАСНЫЙ ключ!,,Du har den RÖDA nyckeln!,KIRMIZI anahtarı aldım! Got the BLUE key!,TXTS_KEY2,,,,MODRÝ klíč!,Du fik den BLÅ nøgle!,Der BLAUE Schlüssel!,,,¡Tienes la llave AZUL!,,Sain SINISEN avaimen!,Ramassé la clé BLEUE !,Megszerezted a KÉK kulcsot!,Hai ottenuto la chiave BLU!,,,Je hebt de BLAUWE sleutel!,Har den BLÅ nøkkelen!,Zdobyto NIEBIESKI klucz!,Pegou a chave AZUL!,,Ai cheia ALBASTRĂ!,Получен СИНИЙ ключ!,,Du har den BLÅ nyckeln!,MAVİ anahtarı aldım! Got the GREEN key!,TXTS_KEY3,,,,ZELENÝ klíč!,Du fik den GRØNNE nøgle!,Der GRÜNE Schlüssel!,,,¡Tienes la llave VERDE!,,Sain VIHREÄN avaimen!,Ramassé la clé VERTE !,Megszerezted a ZÖLD kulcsot!,Hai ottenuto la chiave VERDE!,,,Je hebt de GROENE sleutel!,Har den GRØNNE nøkkelen!,Zdobyto ZIELONY klucz!,Pegou a chave VERDE!,,AI cheia VERDE!,Получен ЗЕЛЁНЫЙ ключ!,,Du har den GRÖNA nyckeln!,YEŞİL anahtarı aldım! @@ -1564,12 +1564,12 @@ Serpent God Protector,Serpent God Protector,,ShadowWarrior,,Ochránce Zmijího b Flames,Flames,,ShadowWarrior,,Plameny,Flammer,Flammen,,,Llamas,,Liekit,Flammes,Lángok,Fiamme,,,Vlammen,Flammer,Płomienie,Chamas,,Flăcări,Огонь,,Flames,Alevler Radiation,Radiation,,ShadowWarrior,,Radiace,Stråling,Strahlung,,,Radiación,,Säteily,Rayonnement,Sugárzás,Radiazioni,,,Straling,Stråling,Promieniowanie,Radiação,,Radiație,Радиация,,Strålning,Radyasyon Caltrops,Caltrops,,ShadowWarrior,,Rozsocháče,,Krähenfüße,,,Trampas,,,,Vassúlyom,Piedi di corvo,,,,Caltrops,Kolczatki,Estrepes,,Ciulini de Fier,Шипы,,Caltrops,Caltrops -Seppuku Station,TXTS_MAP01,https://en.wikipedia.org/wiki/Seppuku,,,Stanice Seppuku,Seppuku-station,,,Stacio de harakiro,Estación de seppuku,,Seppuku Station,,Szeppuku Szállás,Stazione Seppuku,,,Seppuku Station,Seppuku-stasjonen,Stacja Seppuku,Estação Seppuku,,Stația Seppuku,Станция Сеппуку,,,Seppuku İstasyonu +Seppuku Station,TXTS_MAP01,https://en.wikipedia.org/wiki/Seppuku,,,Stanice Seppuku,Seppuku-station,,,Stacio de harakiro,Estación de harakiri,,Seppuku Station,,Szeppuku Szállás,Stazione Seppuku,,,Seppuku Station,Seppuku-stasjonen,Stacja Seppuku,Estação Seppuku,,Stația Seppuku,Станция Сеппуку,,,Seppuku İstasyonu Zilla Construction,TXTS_MAP02,,,,Staveniště Zilly,Zilla-konstruktion,Zilla Bauunternehmen,,Konstruaĵo Zilla,Construcción Zilla,,Zilla Construction,,Zilla Építkezés,Cantiere Zilla,,,Zilla Bouw,Zilla Byggefirma,Gmach Zilli,Zilla Construções,,Șantierul Zilla,Здание Зилла,,,Zilla İnşaat Master Leep's Temple,TXTS_MAP03,,,,Chrám mistra Skoka,Mester Leeps tempel,Meister Leeps Tempel,,Templo de la Mastro Leep,Templo del Maestro Leep,,Mestari Leepin temppeli,Le temple de Maître Leep,Leep Mester Temploma,Tempio del Maestro Leep,,,Meester Leep's Tempel,Mester Leeps tempel,Świątynia Mistrza Leep'a,Templo do Mestre Leep,,Templul Maestrului Leep,Храм мастера Липа,,Mästare Leeps tempel,Usta Leep'in Tapınağı Dark Woods of the Serpent,TXTS_MAP04,,,,Temný hvozd zmijí,Slangens mørke skove,Dunkler Schlangenwald,,Malhela Arbaro de la Serpento,Bosque Oscuro de la Serpiente,,Käärmeen pimeät metsät,Bois sombres du serpent,A Kígyó Sötét Erdelye,Bosco Oscuro del Serpente,,,Donkere bossen van de slang,Slangens mørke skog,Las Węża,Floresta Negra da Serpente,,Pădurile Tenebroase ale Șarpelui,Тёмные леса змея,,Ormens mörka skogar,Yılanın Karanlık Ormanı Rising Son,TXTS_MAP05,Reference to Japan's nickname: Land of the Rising Sun,,,Synovo povstání,Stigende søn,Aufstrebender Sohn,,Origino de la Puno,Tierra del Lo Wang-ciente,,Nouseva poika,Le Sordide Levant,A Felkelő Nap Fia,Figlio Nascente,,,Rijzende zoon,Zilla Byggefirma,Zew Wojownika,Filho Nascente,,Fiul Renaște,Зов воина,,Den uppstigande sonen,Yükselen Oğul -Killing Fields,TXTS_MAP06,https://en.wikipedia.org/wiki/Killing_Fields,,,Pole smrti,Drabsmarker,Schlachtfelder,,Ruĝa buĉejo,Campos de la Muerte,,Tappavat kentät,Champs de la mort,Öldöklés Mezeje,Campi di Sterminio,,,Dodende Velden,Dødsmarker,Pola Śmierci,Campos de Execução,,Câmpiile de Execuție,Поля смерти,,Dödande fält,Ölüm Tarlaları +Killing Fields,TXTS_MAP06,https://en.wikipedia.org/wiki/Killing_Fields,,,Pole smrti,Drabsmarker,Schlachtfelder,,Ruĝaj murdokampoj,Campos de la Muerte,,Tappavat kentät,Champs de la mort,Öldöklés Mezeje,Campi di Sterminio,,,Dodende Velden,Dødsmarker,Pola Śmierci,Campos de Execução,,Câmpiile de Execuție,Поля смерти,,Dödande fält,Ölüm Tarlaları Hara-Kiri Harbor,TXTS_MAP07,hara-kiri = seppuku,,,Přístav Hara-Kiri,Hara-Kiri-havnen,Hara-Kiri Hafen,,Haveno de harakiro,Puerto de harakiri,,Hara-Kirin satama,Port de Hara-Kiri,Hara-Kiri Hajópihenő,Porto di Hara-Kiri,,,Hara-Kiri haven,Hara-Kiri havn,Port Hara-Kiri,Porto Harakiri,,Portul Hara-Kiri,Гавань Харакири,,Hara-Kiri-hamnen,Hara-Kiri Limanı Zilla's Villa,TXTS_MAP08,,,,Zillova vila,Zillas villa,Zillas Villa,,Vilaĝo de Zilla-ĝo,Villa de Zilla,,Zillan huvila,Villa de Zilla,Zilla Villája,Villa di Zilla,,,Zilla's Villa,Zillas villa,Willa Zilli,Mansão Zilla,,Complexul lui Zilla,Вилла Зилла,,Zillas villa,Zilla'nın Villası Monastery,TXTS_MAP09,,,,Klášter,Kloster,Kloster,,Monaĥejo,Monasterio,,Luostari,Monastère,Apátság,Monastero,,,Klooster,Klosteret,Klasztor,Mosteiro,,Mănăstirea,Монастырь,,Klostret,Manastır @@ -1579,7 +1579,7 @@ Bath House,TXTS_MAP12,,,,Lázně,Badehus,Badehaus,,Banejo,Baños públicos,,Kylp Unfriendly Skies,TXTS_MAP13,,,,Nevlídné nebe,Uvenlige himmelstrøg,Unfreundlicher Himmel,,Malamikaj ĉieloj,Cielos hostiles,,Epäystävällinen taivas,Ciel hostile,Barátságtalan Égbolt,Cieli Ostili,,,Onvriendelijke hemel,Uvennlig himmel,Wróg Na Horyzoncie,Céus Nada Amigáveis,,Orizonturi Ostile,Враг на горизонте,,Ovänlig himmel,Düşmanca Gökyüzü Crude Oil,TXTS_MAP14,,,,Surová ropa,Råolie,Rohöl,,Nafto,Petróleo,,Raakaöljy,Huile Noire,Kőolaj,Petrolio grezzo,,,Ruwe olie,Råolje,Ropa,Puro Petróleo,,Ulei Nerafinat,Нефть,,Råolja,Ham Petrol Coolie Mines,TXTS_MAP15,,,,Důl kuliů,Coolie Miner,Kuli Minen,,Minejo de Coolie,Minas Coolie,,Koolien kaivokset,Champs de la mort,Csíkszem Bányák,Miniere di Coolie,,,Coolie Mijnen,Coolie-gruver,Kopalnie Kulisów,Minas dos Coolies,,Minele Coolie,Шахты Кули,,Coolie gruvor,Coolie Madenleri -Subpen 7,TXTS_MAP16,,,,Ponorkový bunkr 7,,Gebäude 7,,,,,Subpen 7,Enclos Sous Marin 7,7-es Tengeralattjáró Kikötő,Base Sottomarina,,,,Subpen 7,Podwodna Baza 7,Base de Submarinos 7,,Baza de Submarine 7,Подводная база №7,,Ubåtsbrygga 7,Denizaltı rıhtımı 7 +Subpen 7,TXTS_MAP16,,,,Ponorkový bunkr 7,,Gebäude 7,,,,,Subpen 7,Enclos Sous Marin 7,7-es Tengeralattjáró Kikötő,Base Sottomarina,,,,Subpen 7,Podwodna Baza Nr 7,Base de Submarinos 7,,Baza de Submarine 7,Подводная база №7,,Ubåtsbrygga 7,Denizaltı rıhtımı 7 The Great Escape,TXTS_MAP17,https://www.imdb.com/title/tt0057115/,,,Velký útěk,Den store flugt,Die große Flucht,,,La gran evasión,El gran escape,Suuri pako,La Grande évasion,A Nagy Szökés,La Grande Fuga,,,De grote ontsnapping,Den store flukten,Wielka Ucieczka,Fugindo do Inferno,,Marea Evadare,Большой побег,,Den stora flykten,Büyük Kaçış Floating Fortress,TXTS_MAP18,,,,Létající pevnost,Flydende fæstning,Schwebende Festung,,Ŝvebanta fortreso,Fortaleza flotante,,Kelluva linnoitus,Forteresse flottante,Lebegő Erőd,Fortezza Galleggiante,,,Drijvend fort,Flytende festning,Latająca Forteca,Fortaleza Flutuante,,Fortăreața Plutitoare,Парящая крепость,,Flytande fästning,Yüzen Kale Water Torture,TXTS_MAP19,,,,Vodní mučení,Vandtortur,Wasserfolter,,Akva torturo,Tortura de agua,,Vesikidutus,Torture par l'eau,Vizes Kínzás,Tortura dell'Acqua,,,Watermarteling,Vanntortur,Wodna Tortura,Tortura d'Água,,Tortura Apei,Пытка водой,,Vatten tortyr,Su İşkencesi @@ -1629,7 +1629,7 @@ Twin Dragon,TXTS_T_TITLE,https://www.imdb.com/title/tt0105399/,,,Dračí dvojče Lo Wang is waiting for other players...,TXTS_NETWAIT1,currently unused,,,Lo Wang čeká na ostatní hráče...,Lo Wang venter på andre spillere...,,,Lo-Vango atendas pliajn ludontojn...,Lo Wang está esperando otros jugadores...,,Lo Wang odottaa muita pelaajia...,Lo Wang attend les autres joueurs...,Lo Wang a többi játékosra vár...,Lo Wang sta aspettando gli altri giocatori...,,,Lo Wang wacht op andere spelers...,Lo Wang venter på andre spillere...,Lo Wang czeka na innych graczy...,Lo Wang está esperando os outros jogadores...,,Lo Wang așteaptă restul jucătorilor...,Ло Ванг ожидает других игроков...,,Lo Wang väntar på andra spelare...,Lo Wang diğer oyuncuları bekliyor... They are afraid!,TXTS_NETWAIT2,currently unused,,,Oni se bojí!,De er bange!,,,Ili timas!,¡Tienen miedo!,,He pelkäävät!,Ils ont peur !,Be vannak szarva!,Hanno paura!,,,Ze zijn bang!,De er redde!,Boją się!,Eles estão com medo!,,Sunt lași!,Они боятся!,,De är rädda!,Korkuyorlar! ,,Exhumed,,,,,,,,,,,,,,,,,,,,,,,,, -Training,TXT_EX_MAP00,,,,Výcvik,Uddannelse,,,,Entrenamiento,,Koulutus,Entraînement,Kiképzés,Formazione,,,Opleiding,Trening,Trening,Treinamento,,Antrenament,Обучение,,Utbildning,Eğitim +Training,TXT_EX_MAP00,,,,Výcvik,Uddannelse,,,Trejnejo,Entrenamiento,,Koulutus,Entraînement,Kiképzés,Formazione,,,Opleiding,Trening,Trening,Treinamento,,Antrenament,Обучение,,Utbildning,Eğitim Abu Simbel,TXT_EX_MAP01,,,,Abú Simbel,,,,,,,,,,,,,,,,,,,Абу-Симбел,,,Ebu Simbel Dendur,TXT_EX_MAP02,,,,Dendúr,,,,,,,,,,,,,,,,,,,Дендур,,,Dendur Kalabsh,TXT_EX_MAP03,,,,Kalabš,,,,,,,,,,,,,,,,,,,Калабаш,,,Kalabsh @@ -2104,7 +2104,7 @@ lykkes, kan jeg ikke beskytte sivilisasjonen, og kaos vil råde. Jeg slites mellom to verdener, og dette indre eksperimentet -må stoppes.","Złe siły zwane też Kilmaatem +må stoppes.","Złe siły zwane Kilmaatem zbeszcześciły mój święty pałac i łaszą się na to co pozostało po mojej śmierci. @@ -3070,7 +3070,7 @@ Explosions Behavior,EXPLOS_BEHAVIOR,,,,Chování při výbuchu,Adfærd ved ekspl Torch is lit,TXT_EX_TORCHLIT,,,,Pochodeň plane,Faklen er tændt,Die Fackel leuchtet,,,Antorcha encendida,,Soihtu syttyy,La torche est allumée,Fáklya ég,La torcia è accesa,,,Zaklamp brandt,Fakkel er tent,Rozpalono pochodnię,Tocha acesa,,Torța e aprinsă,Факел зажжён,,Facklan är tänd,Meşale yanıyor Torch is out,TXT_EX_TORCHOUT,,,,Pochodeň vyhasla,Faklen er slukket,Die Fackel ist erloschen,,,Antorcha apagada,,Soihtu on sammunut,La torche est éteinte,Fáklya nem ég,La torcia è spenta,,,Fakkel is uit,Fakkelen er slukket,Pochodnia zgasła,Tocha apagada,,Torța e stinsă,Факел потух,,Facklan är släckt,Meşale çıktı Natural,GLPREFMNU_NATURAL,,,,Přirozené,Naturlig,Natürlich,,,,,Luonnollinen,Naturel,Természetes,Naturale,,,Natuurlijk,Naturlig,Naturalne,,,,Естественная,,Naturlig,Doğal -High Contrast,GLPREFMNU_HIGHCONTRAST,,,,Vysoký kontrast,Høj kontrast,Hoher Kontrast,,,Alto contraste,,Korkea kontrasti,Contraste élevé,Magas Kontrasztú,Alto contrasto,,,Hoog contrast,Høy kontrast,Duży Kontrast,Alto Contraste,,De Înalt Contrast,Высокая контрастность,,Hög kontrast,Yüksek Kontrast +High Contrast,GLPREFMNU_HIGHCONTRAST,,,,Vysoký kontrast,Høj kontrast,Hoher Kontrast,,,Alto contraste,,Korkea kontrasti,Contraste élevé,Magas Kontrasztú,Alto contrasto,,,Hoog contrast,Høy kontrast,Duży Kontrast,Alto contraste,,De Înalt Contrast,Высокая контрастность,,Hög kontrast,Yüksek Kontrast Original,GLPREFMNU_ORIGINAL,,,,Původní,,,,,,,Alkuperäinen,,Eredeti,Originale,,,Origineel,,Oryginalne,,,,Обычная,,,Orijinal ,,,,,,,,,,,,,,,,,,,,,,,,,,, You need the key for this door,TXT_EX_NEEDKEY,,,,K těmto dveřím potřebuješ klíč,Du skal bruge nøglen til denne dør,Du brauchst einen Schlüssel für diese Tür,,,Necesitas la llave para esta puerta,,Tarvitset avaimen tähän oveen,Vous avez besoin de la clé pour cette porte,Kell egy kulcs az ajtóhoz,Ti serve la chiave per questa porta,,,Je hebt de sleutel nodig voor deze deur.,Du trenger nøkkelen til denne døren,Potrzebujesz klucza do tych drzwi,Você precisa da chave para esta porta,,Ai nevoie de cheie pentru ușa asta,Нужен ключ от этой двери,,Du behöver nyckeln till den här dörren,Bu kapının anahtarına ihtiyacın var. @@ -3080,18 +3080,18 @@ Invisibility is about to expire,TXT_EX_INVISEX,,,,Neviditelnost brzy vyprchá,Us Invincibility is about to expire,TXT_EX_INVINCEX,,,,Nesmrtelnost brzy vyprchá,Uovervindelighed er ved at udløbe,Unverwundbarkeit läuft bald ab,,,Invencibilidad a punto de expirar,,Näkymättömyys on päättymässä.,L'invincibilité est sur le point d'expirer,Sérthetetlenség hamarosan lejár,L'invincibilità sta per scadere,,,Onoverwinnelijkheid is bijna voorbij.,Uovervinnelighet er i ferd med å utløpe.,Nietykalność kończy się,Invencibilidade está prestes a expirar,,Invincibilitatea expiră curând,Неуязвимость скоро закончится,,Oövervinnbarhet är på väg att upphöra,Yenilmezlik sona ermek üzere Weapon power is about to expire,TXT_EX_WEAPONEX,,,,Síla zbraně brzy vyprchá,Våbenkraft er ved at udløbe,Waffenenergie läuft bald ab,,,El poder de las armas está a punto de expirar,,Aseiden teho on päättymässä,La puissance de l'arme est sur le point d'expirer,Fegyver túlerő hamarosan lejár,Il potere delle armi sta per scadere,,,Wapen kracht is bijna verlopen.,Våpenkraft er i ferd med å utløpe,Wzmocnienie broni kończy się,Poder da arma está prestes a expirar,,Puterea armei expiră curând,Сила оружия скоро иссякнет,,Vapenkraften är snart slut,Silah gücü sona ermek üzere Mask is about to expire,TXT_EX_MASKEX,,,,Maska brzy vyprchá,Maske er ved at udløbe,Maske läuft bald ab,,,La máscara está a punto de caducar,,Naamio on päättymässä,Le masque est sur le point d'expirer,Maszk hamarosan lejár,La maschera sta per scadere,,,Masker gaat vervallen,Mask er i ferd med å utløpe,Maska traci swą moc,Máscara está prestes a expirar,,Masca expiră curând,Действие маски скоро закончится,,Masken är på väg att upphöra att gälla.,Maskenin süresi dolmak üzere -Full Viewing Pitch (Experimental),PLRMNU_FULLVIEWPITCH,,,,Plný pohled nahoru/dolů (experimentální),Fuld visningsafstand (eksperimentelt),Volles Sichtfeld (experimentell),,,Campo de visión completo (Experimental),,Täysi näkökenttä (kokeellinen),Angle de vue complet (expérimental),Teljes szabad egér nézet (Kísérleti),Visuale completa (sperimentale),,,Volledige kijkhoogte (experimenteel),Full visningshøyde (eksperimentell),Pełny Widok w Pionie (Eksperymentalne),Inclinação total de visão (experimental),,Privire Sus-Jos la 90° (Prototip),Полный угол обзора (экспериментальные),,Full sikt (experimentell),Tam Görüntüleme Aralığı (Deneysel) +Full Viewing Pitch (Experimental),PLRMNU_FULLVIEWPITCH,,,,Plný pohled nahoru/dolů (experimentální),Fuld visningsafstand (eksperimentelt),Volles Sichtfeld (experimentell),,,Campo de visión completo (Experimental),,Täysi näkökenttä (kokeellinen),Angle de vue complet (expérimental),Teljes szabad egér nézet (Kísérleti),Visuale completa (sperimentale),,,Volledige kijkhoogte (experimenteel),Full visningshøyde (eksperimentell),Pełny Widok w Pionie (Eksperymentalne),Inclinação total da visão (experimental),,Privire Sus-Jos la 90° (Prototip),Полный угол обзора (экспериментальные),,Full sikt (experimentell),Tam Görüntüleme Aralığı (Deneysel) Lock pitch while centering view,PLRMNU_DUKEPITCHLOCKRET,,,,Uzamknout pohled nahoru/dolů při vycentrování pohledu,"Lås banen, mens du centrerer visningen",Neigung während der Zentrierung der Ansicht sperren,,,Bloqueo del cabeceo al centrar la vista,,Lukitse sävelkorkeus keskittäessäsi näkymää,Verrouiller l'assiette pendant le centrage de la vue,A nézet központosítása közbeni állás rögzítése,Blocca l'inclinazione mentre centra la visuale,,,Vergrendel toonhoogte tijdens centreren zicht,Lås pitch mens du sentrerer visningen,Blokada nachylenia podczas centrowania widoku,Travar inclinação ao centralizar visão,,Blocați pasul în timp ce centrați vederea,Фиксировать угол обзора при отцентрированном взгляде,,Låsa tonhöjden medan du centrerar vyn,Görünümü ortalarken eğimi kilitleme Adjust pitch upon hard landing,PLRMNU_DUKEPITCHHARDLAND,,,,Upravit pohled při tvrdém přistání,Juster banen ved hård landing,Neigung bei harter Landung anpassen,,,Ajustar el cabeceo tras un aterrizaje duro,,Säädä korkeutta kovan laskeutumisen yhteydessä,Ajuster l'assiette en cas d'atterrissage brutal,Állítsa be a dőlésszöget kemény leszálláskor,Regola l'inclinazione in caso di atterraggio brusco,,,Pas toonhoogte aan bij harde landing,Juster pitch ved hard landing,Regulacja nachylenia po lądowaniu,Ajustar ângulo após cair,,Reglați pasul la aterizarea bruscă,Отрегулировать угол обзора при жёстком приземлении,,Justera lutningen vid hård landning,Sert inişte eğimi ayarlayın Return to center after a hard landing,PLRMNU_DUKEPITCHLANDRET,,,,Vystředit pohled po tvrdém přistání,Tilbage til centrum efter en hård landing,Zentrieren nach harter Landung,,,Volver al centro después de un aterrizaje duro,,Palaa keskelle kovan laskeutumisen jälkeen,Retour au centre après un atterrissage brutal,Visszatérés a középpontba kemény leszállás után,Ritorna al centro dopo un atterraggio difficile,,,Terugkeren naar het midden na een harde landing,Gå tilbake til senter etter en hard landing,Wycentruj po lądowaniu,Voltar ao centro após cair,,Revenirea la centru după o aterizare bruscă,Возврат угла обзора в центр после жёсткого приземления,,Återgå till centrum efter en hård landning,Sert bir inişten sonra merkeze dönüş -Jump rebounding effect,PLRMNU_EXJUMPREBOUND,,,,Efekt zpětného rázu při skoku,Effekt af springrebounding,Sprung-Rückprall-Effekt,,,Efecto de rebote de salto,,Hyppyjen palautusvaikutus,Effet de rebond des sauts,Ugrás visszapattanó hatás,Effetto rimbalzo del salto,,,Spring rebound effect,Hopp tilbakevirkende effekt,Efekt odbicia skoku,Efeito de ricochete de saltos,,Efectul de ricoșeu de salt,Эффект отскока в прыжке,,Effekten av hopprebounding,Sıçrama geri tepme etkisi +Jump rebounding effect,PLRMNU_EXJUMPREBOUND,,,,Efekt zpětného rázu při skoku,Effekt af springrebounding,Sprung-Rückprall-Effekt,,,Efecto de rebote de salto,,Hyppyjen palautusvaikutus,Effet de rebond des sauts,Ugrás visszapattanó hatás,Effetto rimbalzo del salto,,,Spring rebound effect,Hopp tilbakevirkende effekt,Efekt odbijania się skoku,Efeito de ricochete de saltos,,Efectul de ricoșeu de salt,Эффект отскока в прыжке,,Effekten av hopprebounding,Sıçrama geri tepme etkisi Powerslave Exhumed,DSPLYMNU_VIEWBOBPSE,,,,,,,,,,,,,,,,,,,,,,,,,, Duke 3D,DSPLYMNU_VIEWBOBDUKE,,,,,,,,,,,,,,,,,,,,,,,,,, View Bobbing Speed,DSPLYMNU_VIEWBOBSPEED,,,,Rychlost pohupování pohledu,,,,,,,,Vitesse du chaloupage,,,,,,,Szybkość bujania kamery,Velocidade do balanço da visão,,Viteză Oscilare Perspectivă,Скорость покачивания камеры,,, View Bobbing Height,DSPLYMNU_VIEWBOBHEIGHT,,,,Výška pohupování pohledu,,,,,,,,Hauteur du chaloupage,,,,,,,Wysokość bujania kamery,Altura do balanço da visão,,Înălțime Oscilare Perspectivă,Высота покачивания камеры,,, Turn tilting (Powerslave Exhumed),DSPLYMNU_VIEWTILTTURN,,,,Naklánět při otáčení (Powerslave Exhumed),Drejning af hældning (Powerslave Exhumed),Neigen beim Drehen (Powerslave Exhumed),,,Inclinación de giro (Powerslave Exhumed),,Kääntökallistus (Powerslave Exhumed),Inclinaison en virage (Powerslave Exhumed),Fordulás billenés (Powerslave Exhumed),Inclinazione dei turni (Powerslave Exhumed),,,Kantelen (Powerslave Exhumed),Slå vipping (Powerslave Exhumed),Odchylanie przy rozglądaniu (Powerslave Exhumed),Giro de inclinação (Powerslave Exhumed),Virar inclinado (Powerslave Exhumed),Înclinare de întoarcere (Powerslave Exhumed),Наклон при повороте (Powerslave Exhumed),,Vändning med lutning (Powerslave Exhumed),Devirme (Powerslave Exhumed) -Strafe tilting (Quake),DSPLYMNU_VIEWTILTSTRAFE,,,,Naklánět při úkroku (Quake),Strafe tilting (Quake),Neigen bei Seitwärtsbewegung (Quake),,,Inclinación de Strafe (Quake),,Strafe-kallistus (Quake),Inclinaison en pas de côté (Quake),Strafe billenés (Quake),Inclinazione della strafe (Quake),,,Strafe kantelen (Quake),Strafe vipping (Quake),Odchylanie przy chodzeniu w bok (Quake),Inclinação de Strafe (Quake),,Înclinare Strafe (Quake),Наклон при движении боком (Quake),,Vändning av strafe (Quake),Strafe eğme (Quake) +Strafe tilting (Quake),DSPLYMNU_VIEWTILTSTRAFE,,,,Naklánět při úkroku (Quake),Strafe tilting (Quake),Neigen bei Seitwärtsbewegung (Quake),,,Inclinación de Strafe (Quake),,Strafe-kallistus (Quake),Inclinaison en pas de côté (Quake),Strafe billenés (Quake),Inclinazione della strafe (Quake),,,Strafe kantelen (Quake),Strafe vipping (Quake),Odchylanie przy uniku (Quake),Inclinação de movimento lateral (Quake),,Înclinare Strafe (Quake),Наклон при движении боком (Quake),,Vändning av strafe (Quake),Strafe eğme (Quake) Movement tilting,DSPLYMNU_VIEWTILTMOVEMENT,,,,Naklánět při pohybu,Bevægelse tilting,Neigen beim Bewegen,,,Movimiento basculante,,Liikkeen kallistaminen,Inclinaison du mouvement,Mozgás billenés,Inclinazione del movimento,,,Beweging kantelen,Bevegelse vipping,Odchylanie przy poruszaniu się,Inclinação do movimento,,Înclinarea mișcării,Наклон при движении,,Rörelsestyrning,Hareket devirme -View tilting mode,DSPLYMNU_VIEWTILTING,,,,Režim naklánění pohledu,Tilstand til vending af visningen,Neigungsmodus,,,Modo de inclinación de la vista,,Näkymän kallistustila,Mode de basculement de la vue,Nézet-dőlés mód,Modalità di inclinazione della vista,,,Kantelmodus,Modus for vippevisning,Tryb odchylania widoku,Modo de inclinação da vista,,Modul de înclinare a vederii,Режим наклона обзора,,Läge för lutning av vyn,Görünüm eğme modu -View tilting scale,DSPLYMNU_VIEWTILTSCALE,,,,Měřítko naklánění pohledu,Skala til vipning af visningen,Neigungsfaktor,,,Escala de inclinación de la vista,,Näkymän kallistusasteikko,Échelle de basculement de la vue,Nézet-dőlés skála,Scala di inclinazione della vista,,,Kantelschaal,Skala for vippevisning,Skala odchylenia widoku,Escala de inclinação da vista,,Scala de înclinare a vederii,Масштабирование наклона обзора,,Skala för lutning av vyn,Görünüm eğme ölçeği -Vehicle tilting,DSPLYMNU_VEHTILTING,,,,Naklánět vozidla,,Fahrzeugneigung,,,,,Ajoneuvon kallistaminen,,,,,,,,,Inclinação do veículo,,Înclinare vehicul,Наклон в транспорте,,Fordonets lutning, \ No newline at end of file +View tilting mode,DSPLYMNU_VIEWTILTING,,,,Režim naklánění pohledu,Tilstand til vending af visningen,Neigungsmodus,,,Modo de inclinación de la vista,,Näkymän kallistustila,Mode de basculement de la vue,Nézet-dőlés mód,Modalità di inclinazione della vista,,,Kantelmodus,Modus for vippevisning,Tryb odchylania widoku,Modo de inclinação da visão,,Modul de înclinare a vederii,Режим наклона обзора,,Läge för lutning av vyn,Görünüm eğme modu +View tilting scale,DSPLYMNU_VIEWTILTSCALE,,,,Měřítko naklánění pohledu,Skala til vipning af visningen,Neigungsfaktor,,,Escala de inclinación de la vista,,Näkymän kallistusasteikko,Échelle de basculement de la vue,Nézet-dőlés skála,Scala di inclinazione della vista,,,Kantelschaal,Skala for vippevisning,Skala odchylenia widoku,Escala de inclinação da visão,,Scala de înclinare a vederii,Масштабирование наклона обзора,,Skala för lutning av vyn,Görünüm eğme ölçeği +Vehicle tilting,DSPLYMNU_VEHTILTING,,,,Naklánět vozidla,,Fahrzeugneigung,,,,,Ajoneuvon kallistaminen,,,,,,,,Odchylanie w pojazdach,Inclinação de veículo,,Înclinare vehicul,Наклон в транспорте,,Fordonets lutning, \ No newline at end of file diff --git a/wadsrc/static/widgets/banner_1.png b/wadsrc/static/widgets/banner_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8b792e51a16ab5e6d34cf9776c6e632b03403de6 GIT binary patch literal 105619 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 literal 0 HcmV?d00001 diff --git a/wadsrc/static/widgets/banner_3.png b/wadsrc/static/widgets/banner_3.png new file mode 100644 index 0000000000000000000000000000000000000000..ff1fb953e4a772ab00d554bfb7f9757e3f0c791a GIT binary patch literal 122157 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