From e8bf34367dc1d7118451f9352a698afc1144f52e Mon Sep 17 00:00:00 2001 From: p-sam Date: Wed, 17 Jun 2020 13:29:34 +0000 Subject: [PATCH] And sys-clk overlay --- build.sh | 8 + overlay/.gitignore | 2 + overlay/Makefile | 154 + overlay/data/logo_rgba.bin | Bin 0 -> 17160 bytes overlay/lib/tesla/.github/FUNDING.yml | 5 + overlay/lib/tesla/.gitignore | 12 + overlay/lib/tesla/.gitrepo | 12 + .../lib/tesla/.vscode/c_cpp_properties.json | 45 + overlay/lib/tesla/.vscode/settings.json | 58 + overlay/lib/tesla/LICENSE | 339 ++ overlay/lib/tesla/README.md | 32 + overlay/lib/tesla/example/Makefile | 209 + overlay/lib/tesla/example/source/main.cpp | 100 + overlay/lib/tesla/include/stb_truetype.h | 5011 +++++++++++++++++ overlay/lib/tesla/include/tesla.hpp | 3636 ++++++++++++ overlay/scripts/make_logo.sh | 16 + overlay/src/ipc.h | 23 + overlay/src/main.cpp | 75 + overlay/src/overlay.h | 0 overlay/src/ui/elements/base_frame.h | 31 + overlay/src/ui/format.h | 28 + overlay/src/ui/gui/app_profile_gui.cpp | 105 + overlay/src/ui/gui/app_profile_gui.h | 33 + overlay/src/ui/gui/base_gui.cpp | 46 + overlay/src/ui/gui/base_gui.h | 27 + overlay/src/ui/gui/base_menu_gui.cpp | 114 + overlay/src/ui/gui/base_menu_gui.h | 30 + overlay/src/ui/gui/fatal_gui.cpp | 67 + overlay/src/ui/gui/fatal_gui.h | 29 + overlay/src/ui/gui/freq_choice_gui.cpp | 52 + overlay/src/ui/gui/freq_choice_gui.h | 33 + overlay/src/ui/gui/global_override_gui.cpp | 83 + overlay/src/ui/gui/global_override_gui.h | 31 + overlay/src/ui/gui/main_gui.cpp | 67 + overlay/src/ui/gui/main_gui.h | 25 + overlay/src/ui/style.h | 20 + 36 files changed, 10558 insertions(+) create mode 100644 overlay/.gitignore create mode 100644 overlay/Makefile create mode 100644 overlay/data/logo_rgba.bin create mode 100644 overlay/lib/tesla/.github/FUNDING.yml create mode 100644 overlay/lib/tesla/.gitignore create mode 100644 overlay/lib/tesla/.gitrepo create mode 100644 overlay/lib/tesla/.vscode/c_cpp_properties.json create mode 100644 overlay/lib/tesla/.vscode/settings.json create mode 100644 overlay/lib/tesla/LICENSE create mode 100644 overlay/lib/tesla/README.md create mode 100644 overlay/lib/tesla/example/Makefile create mode 100644 overlay/lib/tesla/example/source/main.cpp create mode 100644 overlay/lib/tesla/include/stb_truetype.h create mode 100644 overlay/lib/tesla/include/tesla.hpp create mode 100755 overlay/scripts/make_logo.sh create mode 100644 overlay/src/ipc.h create mode 100644 overlay/src/main.cpp create mode 100644 overlay/src/overlay.h create mode 100644 overlay/src/ui/elements/base_frame.h create mode 100644 overlay/src/ui/format.h create mode 100644 overlay/src/ui/gui/app_profile_gui.cpp create mode 100644 overlay/src/ui/gui/app_profile_gui.h create mode 100644 overlay/src/ui/gui/base_gui.cpp create mode 100644 overlay/src/ui/gui/base_gui.h create mode 100644 overlay/src/ui/gui/base_menu_gui.cpp create mode 100644 overlay/src/ui/gui/base_menu_gui.h create mode 100644 overlay/src/ui/gui/fatal_gui.cpp create mode 100644 overlay/src/ui/gui/fatal_gui.h create mode 100644 overlay/src/ui/gui/freq_choice_gui.cpp create mode 100644 overlay/src/ui/gui/freq_choice_gui.h create mode 100644 overlay/src/ui/gui/global_override_gui.cpp create mode 100644 overlay/src/ui/gui/global_override_gui.h create mode 100644 overlay/src/ui/gui/main_gui.cpp create mode 100644 overlay/src/ui/gui/main_gui.h create mode 100644 overlay/src/ui/style.h diff --git a/build.sh b/build.sh index 4456c7d4..ea290db9 100755 --- a/build.sh +++ b/build.sh @@ -31,6 +31,14 @@ popd > /dev/null mkdir -p "$DIST_DIR/switch" cp -vf "$ROOT_DIR/manager/sys-clk-manager.nro" "$DIST_DIR/switch/sys-clk-manager.nro" +echo "*** overlay ***" +pushd "$ROOT_DIR/overlay" +make -j$CORES +popd > /dev/null + +mkdir -p "$DIST_DIR/switch/.overlays" +cp -vf "$ROOT_DIR/overlay/out/sys-clk-overlay.ovl" "$DIST_DIR/switch/.overlays/sys-clk-overlay.ovl" + echo "*** assets ***" mkdir -p "$DIST_DIR/config/sys-clk" cp -vf "$ROOT_DIR/config.ini.template" "$DIST_DIR/config/sys-clk/config.ini.template" diff --git a/overlay/.gitignore b/overlay/.gitignore new file mode 100644 index 00000000..36a52c92 --- /dev/null +++ b/overlay/.gitignore @@ -0,0 +1,2 @@ +/out +/build diff --git a/overlay/Makefile b/overlay/Makefile new file mode 100644 index 00000000..5ff03b99 --- /dev/null +++ b/overlay/Makefile @@ -0,0 +1,154 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +#--------------------------------------------------------------------------------- +TARGET := sys-clk-overlay +BUILD := build +OUTDIR := out +RESOURCES := res +SOURCES := src src/ui/gui src/ui/elements ../common/src ../common/src/client +DATA := data +INCLUDES := ../common/include +EXEFS_SRC := exefs_src + +APP_TITLE := sys-clk +NO_ICON := 1 + +#--------------------------------------------------------------------------------- +# version control constants +#--------------------------------------------------------------------------------- +TARGET_VERSION := $(shell git describe --dirty --always --tags) +APP_VERSION := $(TARGET_VERSION) + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +DEFINES := -DDISABLE_IPC -DTARGET="\"$(TARGET)\"" -DTARGET_VERSION="\"$(TARGET_VERSION)\"" + +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-exceptions -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(TOPDIR)/lib/tesla + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(OUTDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @[ -d $(OUTDIR) ] || mkdir -p $(OUTDIR) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nacp $(TARGET).nso $(TARGET).elf $(OUTDIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +all: $(OUTPUT).ovl + +$(OUTPUT).ovl: $(OUTPUT).elf $(OUTPUT).nacp + @elf2nro $< $@ --nacp=$(OUTPUT).nacp + @echo "built ... $(notdir $(OUTPUT).ovl)" + +$(OUTPUT).elf: $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/overlay/data/logo_rgba.bin b/overlay/data/logo_rgba.bin new file mode 100644 index 0000000000000000000000000000000000000000..d5b9ced7955408287a22583253cbde7de3d89d04 GIT binary patch literal 17160 zcmeI2S*TTI6vv%%ykLY4mKIn_SZG>=1xjcn6!l<84^adWCMA}b^;8s65Jg4NLsC!} zSqNS#AIyS6DS{h?B{Uc|k!6+@G!M7mU(Xl+v+mya?0wGh`0_3I^Q~{qcdb32J7Pqp z+=1MI+=1MI+=1MI+<|Hx7#tiNQ^m5XU6okbrhW(L|Eb_5@E$lIz9=KP*d~3lMXqz!X<^c7MbUN*9;aQN{AYrLUSJ8YWBW4C!@etXBhFPJ=PT^X zu3x$?{ABi;)lER}h$F#i;6|_;>;O%1*k1yiHv8X{cnoySNu=w}DARRRLyFVxN-g?3 z-%iCf)=ycl0dv7Za5vDmkOyNPWF60q7^i?AKqB>MYRr}P=*~!njdfSj`HF5dIL>9_ zk&OjEgGBoaY|c)~#`=`oAnT|GkJS!O{-vQl&1Xo@kWB$&V-92;%?*DYH`{^Jh-KG_f{pJv3w)>#d{oLt_pzpW_k5`wcuxGqD_`%zL;oJvPGchM89tLC!Md@bXr5~U8*In zO~0|A)HTX$m;H3Ftrd@b%w74rGQRP6UDhg}>+wGwya@EXR7ScUPIAA*Bj0IG`%FIH zE;@l<|7!A3P$69@lf6QHHZtFw-H`zBm`+2T19v}7YHn0Q4 z;}Fr{#v)(jw@Ya>OLpwnn(S}*DMkJ+!=`o(_{82>+9o#6?U*L*;myfjPb(gmn7hv0 zCgT5js9{B`oWtpfH4QuwV(NT6Df%JPb=v6$*&x_z!Z)H{t3AGzYiJd#OivCyKRPWa zvn`V2*!J8M5C3JJ9A5X86N+-xkr88eh`YGR9~=MH@GSc=z~2$p6!}$=5vNiORboXO zV*9bhcJPj9Gu$5gj^?J=#Mq|9MupckuLIlZp!66+x69&*ZTi-gVj+LX?7o6uV0*DM zl5Jekb)|+Fx5}Q}*Q+?Aeegxs6n$fz7-J%vYrbN)MjP#s_XWHEfA2;<3+P*<(~_b% zjabKGsAo^z%XE$`0gA8ZEcMc7!S>MkQ}%4KSO!K<8vlMTYADMwoSYiR1ATXN`jXA9 zK;MIwgQtPsj~mL83IsIk)`e>i_*mSLi6tnJ14U0fL-mUy5_EL|JM(;ZNn=B4*27+CyxlO`N$BWZl z9wYG>Oa<}!?tY2a3<_hUSX!~!lpMl(p4&N(T~}-H*mZd7xCne5>W*8TmrdWql>^!8 zXvkjAa3`i3;D?=CBaY5(C%r$4|tq=B*OT5(o;k+N4WI>3B%e_jWip6f;K zoTGjR7}7j&Gl=hbx<7RM-e13>>`|oy%;q47r2i3`SH(Zu9Gwm@pLc9%x}WufX-B7; koV98Pn9;pJ?`>;9rT2xZv9n$7K<+^9K<+^9K-~`f2SO)gxBvhE literal 0 HcmV?d00001 diff --git a/overlay/lib/tesla/.github/FUNDING.yml b/overlay/lib/tesla/.github/FUNDING.yml new file mode 100644 index 00000000..c5522058 --- /dev/null +++ b/overlay/lib/tesla/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +patreon: werwolv +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KP7XRJAND9KWU&source=url +github: WerWolv diff --git a/overlay/lib/tesla/.gitignore b/overlay/lib/tesla/.gitignore new file mode 100644 index 00000000..334d5b8e --- /dev/null +++ b/overlay/lib/tesla/.gitignore @@ -0,0 +1,12 @@ +debug +release +lib +*.bz2 + +example/build/ + +*.elf + +*.nacp + +*.ovl diff --git a/overlay/lib/tesla/.gitrepo b/overlay/lib/tesla/.gitrepo new file mode 100644 index 00000000..64184c33 --- /dev/null +++ b/overlay/lib/tesla/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = https://github.com/WerWolv/libtesla + branch = master + commit = 66285245361a02e5480c7bb7dac9ef6449ae6181 + parent = ba418a1a85f89e4a4cb913c718d0133edd7ff2fe + method = merge + cmdver = 0.4.0 diff --git a/overlay/lib/tesla/.vscode/c_cpp_properties.json b/overlay/lib/tesla/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..5c18c1bf --- /dev/null +++ b/overlay/lib/tesla/.vscode/c_cpp_properties.json @@ -0,0 +1,45 @@ +{ + "configurations": [ + { + "name": "DKP Aarch64 Windows", + "includePath": [ + "C:/devkitPro/devkitA64/aarch64-none-elf/include/**", + "C:/devkitPro/devkitA64/lib/gcc/aarch64-none-elf/9.2.0/include/**", + "C:/devkitPro/libnx/include/**", + "C:/devkitPro/portlibs/switch/include/**", + "${workspaceFolder}/include/**" + ], + "defines": [ + "SWITCH", + "VERSION=\"\"", + "__SWITCH__", + "__aarch64__" + ], + "compilerPath": "C:/devkitPro/devkitA64/bin/aarch64-none-elf-g++", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "gcc-x64" + }, + { + "name": "DKP Aarch64 Linux", + "includePath": [ + "/opt/devkitpro/devkitA64/aarch64-none-elf/include/**", + "/opt/devkitpro/devkitA64/lib/gcc/aarch64-none-elf/9.2.0/include/**", + "/opt/devkitpro/libnx/include/**", + "/opt/devkitpro/portlibs/switch/include/**", + "${workspaceFolder}/include/**" + ], + "defines": [ + "SWITCH", + "VERSION=\"\"", + "__SWITCH__", + "__aarch64__" + ], + "compilerPath": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/overlay/lib/tesla/.vscode/settings.json b/overlay/lib/tesla/.vscode/settings.json new file mode 100644 index 00000000..7e8dd025 --- /dev/null +++ b/overlay/lib/tesla/.vscode/settings.json @@ -0,0 +1,58 @@ +{ + "files.associations": { + "*.tcc": "cpp", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "map": "cpp", + "stack": "cpp", + "list": "cpp" + } +} \ No newline at end of file diff --git a/overlay/lib/tesla/LICENSE b/overlay/lib/tesla/LICENSE new file mode 100644 index 00000000..89e08fb0 --- /dev/null +++ b/overlay/lib/tesla/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/overlay/lib/tesla/README.md b/overlay/lib/tesla/README.md new file mode 100644 index 00000000..5782d161 --- /dev/null +++ b/overlay/lib/tesla/README.md @@ -0,0 +1,32 @@ +# libtesla + +

+ +

+ +libtesla is the interface between the Tesla overlay loader and user-made Overlays. It handles all layer creation, UI creation, drawing and input management. +It's main goal is to make sure all overlays look and feel similar and don't differenciate themselves from the switch's native overlays. + +## Screenshots + +
+ + +
+ +`Overlays do NOT show up on Screenshots. These pictures were taken using a capture card` + +## Example + +An example for how to use libtesla can be found here: https://github.com/WerWolv/libtesla/tree/master/example +To create your own Overlay, please consider creating a new repository using the official Tesla overlay template: https://github.com/WerWolv/Tesla-Template + +**Please Note:** While it is possible to create overlays without libtesla, it's highly recommended to not do so. libtesla handles showing and hiding of overlays, button combo detection, layer creation and a lot more. Not using it will lead to an inconsistent user experience when using multiple different overlays ultimately making it worse for the end user. If something's missing, please consider opening a PR here. + +## Credits + +- **switchbrew** for nx-hbloader which is used as basis for overlay loading +- **kardch** for the amazing icon +- **All the devs on AtlasNX, RetroNX and Switchbrew** for their feedback +- **All overlay devs** for making something awesome out of this :) + diff --git a/overlay/lib/tesla/example/Makefile b/overlay/lib/tesla/example/Makefile new file mode 100644 index 00000000..1c36d4cc --- /dev/null +++ b/overlay/lib/tesla/example/Makefile @@ -0,0 +1,209 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- + +APP_TITLE := Tesla Example +APP_VERSION := 1.3.0 + +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := ../include + +NO_ICON := 1 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-exceptions -std=c++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).ovl + +$(OUTPUT).ovl : $(OUTPUT).elf $(OUTPUT).nacp + @elf2nro $< $@ $(NROFLAGS) + @echo "built ... $(notdir $(OUTPUT).ovl)" + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/overlay/lib/tesla/example/source/main.cpp b/overlay/lib/tesla/example/source/main.cpp new file mode 100644 index 00000000..d2415ea8 --- /dev/null +++ b/overlay/lib/tesla/example/source/main.cpp @@ -0,0 +1,100 @@ +#define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one +#include // The Tesla Header + + +class GuiSecondary : public tsl::Gui { +public: + GuiSecondary() {} + + virtual tsl::elm::Element* createUI() override { + auto *rootFrame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.2 - Secondary Gui"); + + rootFrame->setContent(new tsl::elm::DebugRectangle(tsl::Color{ 0x8, 0x3, 0x8, 0xF })); + + return rootFrame; + } +}; + +class GuiTest : public tsl::Gui { +public: + GuiTest(u8 arg1, u8 arg2, bool arg3) { } + + // Called when this Gui gets loaded to create the UI + // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore + virtual tsl::elm::Element* createUI() override { + // A OverlayFrame is the base element every overlay consists of. This will draw the default Title and Subtitle. + // If you need more information in the header or want to change it's look, use a HeaderOverlayFrame. + auto frame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.2"); + + // A list that can contain sub elements and handles scrolling + auto list = new tsl::elm::List(); + + // List Items + list->addItem(new tsl::elm::CategoryHeader("List items")); + + auto *clickableListItem = new tsl::elm::ListItem("Clickable List Item", "..."); + clickableListItem->setClickListener([](u64 keys) { + if (keys & KEY_A) { + tsl::changeTo(); + return true; + } + + return false; + }); + + list->addItem(clickableListItem); + list->addItem(new tsl::elm::ListItem("Default List Item")); + list->addItem(new tsl::elm::ListItem("Default List Item with an extra long name to trigger truncation and scrolling")); + list->addItem(new tsl::elm::ToggleListItem("Toggle List Item", true)); + + // Custom Drawer, a element that gives direct access to the renderer + list->addItem(new tsl::elm::CategoryHeader("Custom Drawer", true)); + list->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawCircle(x + 40, y + 40, 20, true, renderer->a(0xF00F)); + renderer->drawCircle(x + 50, y + 50, 20, true, renderer->a(0xF0F0)); + renderer->drawRect(x + 130, y + 30, 60, 40, renderer->a(0xFF00)); + renderer->drawString("Hello :)", false, x + 250, y + 70, 20, renderer->a(0xFF0F)); + renderer->drawRect(x + 40, y + 90, 300, 10, renderer->a(0xF0FF)); + }), 100); + + // Track bars + list->addItem(new tsl::elm::CategoryHeader("Track bars")); + list->addItem(new tsl::elm::TrackBar("\u2600")); + list->addItem(new tsl::elm::StepTrackBar("\uE13C", 20)); + list->addItem(new tsl::elm::NamedStepTrackBar("\uE132", { "Selection 1", "Selection 2", "Selection 3" })); + + // Add the list to the frame for it to be drawn + frame->setContent(list); + + // Return the frame to have it become the top level element of this Gui + return frame; + } + + // Called once every frame to update values + virtual void update() override { + + } + + // Called once every frame to handle inputs not handled by other UI elements + virtual bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) override { + return false; // Return true here to singal the inputs have been consumed + } +}; + +class OverlayTest : public tsl::Overlay { +public: + // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys + virtual void initServices() override {} // Called at the start to initialize all services necessary for this Overlay + virtual void exitServices() override {} // Callet at the end to clean up all services previously initialized + + virtual void onShow() override {} // Called before overlay wants to change from invisible to visible state + virtual void onHide() override {} // Called before overlay wants to change from visible to invisible state + + virtual std::unique_ptr loadInitialGui() override { + return initially(1, 2, true); // Initial Gui to load. It's possible to pass arguments to it's constructor like this + } +}; + +int main(int argc, char **argv) { + return tsl::loop(argc, argv); +} diff --git a/overlay/lib/tesla/include/stb_truetype.h b/overlay/lib/tesla/include/stb_truetype.h new file mode 100644 index 00000000..f2fbea59 --- /dev/null +++ b/overlay/lib/tesla/include/stb_truetype.h @@ -0,0 +1,5011 @@ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/overlay/lib/tesla/include/tesla.hpp b/overlay/lib/tesla/include/tesla.hpp new file mode 100644 index 00000000..9fa43fff --- /dev/null +++ b/overlay/lib/tesla/include/tesla.hpp @@ -0,0 +1,3636 @@ +/** + * Copyright (C) 2020 werwolv + * + * This file is part of libtesla. + * + * libtesla is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * libtesla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libtesla. If not, see . + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Define this makro before including tesla.hpp in your main file. If you intend +// to use the tesla.hpp header in more than one source file, only define it once! +// #define TESLA_INIT_IMPL + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + +#ifdef TESLA_INIT_IMPL + #define STB_TRUETYPE_IMPLEMENTATION +#endif +#include "stb_truetype.h" + +#pragma GCC diagnostic pop + +#define ELEMENT_BOUNDS(elem) elem->getX(), elem->getY(), elem->getWidth(), elem->getHeight() + +#define ASSERT_EXIT(x) if (R_FAILED(x)) std::exit(1) +#define ASSERT_FATAL(x) if (Result res = x; R_FAILED(res)) fatalThrow(res) + +#define PACKED __attribute__((packed)) +#define ALWAYS_INLINE inline __attribute__((always_inline)) + +/// Evaluates an expression that returns a result, and returns the result if it would fail. +#define R_TRY(resultExpr) \ + ({ \ + const auto result = resultExpr; \ + if (R_FAILED(result)) { \ + return result; \ + } \ + }) + +using namespace std::literals::string_literals; +using namespace std::literals::chrono_literals; + +namespace tsl { + + // Constants + + namespace cfg { + + constexpr u32 ScreenWidth = 1920; ///< Width of the Screen + constexpr u32 ScreenHeight = 1080; ///< Height of the Screen + + extern u16 LayerWidth; ///< Width of the Tesla layer + extern u16 LayerHeight; ///< Height of the Tesla layer + extern u16 LayerPosX; ///< X position of the Tesla layer + extern u16 LayerPosY; ///< Y position of the Tesla layer + extern u16 FramebufferWidth; ///< Width of the framebuffer + extern u16 FramebufferHeight; ///< Height of the framebuffer + extern u64 launchCombo; ///< Overlay activation key combo + + } + + /** + * @brief RGBA4444 Color structure + */ + struct Color { + + union { + struct { + u16 r: 4, g: 4, b: 4, a: 4; + } PACKED; + u16 rgba; + }; + + constexpr inline Color(u16 raw): rgba(raw) {} + constexpr inline Color(u8 r, u8 g, u8 b, u8 a): r(r), g(g), b(b), a(a) {} + }; + + namespace style { + constexpr u32 ListItemDefaultHeight = 70; ///< Standard list item height + constexpr u32 TrackBarDefaultHeight = 90; ///< Standard track bar height + constexpr u8 ListItemHighlightSaturation = 6; ///< Maximum saturation of Listitem highlights + constexpr u8 ListItemHighlightLength = 22; ///< Maximum length of Listitem highlights + + namespace color { + constexpr Color ColorFrameBackground = { 0x0, 0x0, 0x0, 0xD }; ///< Overlay frame background color + constexpr Color ColorTransparent = { 0x0, 0x0, 0x0, 0x0 }; ///< Transparent color + constexpr Color ColorHighlight = { 0x0, 0xF, 0xD, 0xF }; ///< Greenish highlight color + constexpr Color ColorFrame = { 0x7, 0x7, 0x7, 0xF }; ///< Outer boarder color + constexpr Color ColorHandle = { 0x5, 0x5, 0x5, 0xF }; ///< Track bar handle color + constexpr Color ColorText = { 0xF, 0xF, 0xF, 0xF }; ///< Standard text color + constexpr Color ColorDescription = { 0xA, 0xA, 0xA, 0xF }; ///< Description text color + constexpr Color ColorHeaderBar = { 0xC, 0xC, 0xC, 0xF }; ///< Category header rectangle color + constexpr Color ColorClickAnimation = { 0x0, 0x2, 0x2, 0xF }; ///< Element click animation color + } + } + + // Declarations + + /** + * @brief Direction in which focus moved before landing on + * the currently focused element + */ + enum class FocusDirection { + None, ///< Focus was placed on the element programatically without user input + Up, ///< Focus moved upwards + Down, ///< Focus moved downwards + Left, ///< Focus moved from left to rigth + Right ///< Focus moved from right to left + }; + + /** + * @brief Current input controll mode + * + */ + enum class InputMode { + Controller, ///< Input from controller + Touch, ///< Touch input + TouchScroll ///< Moving/scrolling touch input + }; + + class Overlay; + namespace elm { class Element; } + + namespace impl { + + /** + * @brief Overlay launch parameters + */ + enum class LaunchFlags : u8 { + None = 0, ///< Do nothing special at launch + CloseOnExit = BIT(0) ///< Close the overlay the last Gui gets poped from the stack + }; + + [[maybe_unused]] static constexpr LaunchFlags operator|(LaunchFlags lhs, LaunchFlags rhs) { + return static_cast(u8(lhs) | u8(rhs)); + } + + /** + * @brief Combo key mapping + */ + struct KeyInfo { + u64 key; + const char* name; + const char* glyph; + }; + + /** + * @brief Combo key mappings + * + * Ordered as they should be displayed + */ + static const std::vector KEYS_INFO = { + { KEY_L, "L", "\uE0A4" }, { KEY_R, "R", "\uE0A5" }, + { KEY_ZL, "ZL", "\uE0A6" }, { KEY_ZR, "ZR", "\uE0A7" }, + { KEY_SL, "SL", "\uE0A8" }, { KEY_SR, "SR", "\uE0A9" }, + { KEY_DLEFT, "DLEFT", "\uE07B" }, { KEY_DUP, "DUP", "\uE079" }, { KEY_DRIGHT, "DRIGHT", "\uE07C" }, { KEY_DDOWN, "DDOWN", "\uE07A" }, + { KEY_A, "A", "\uE0A0" }, { KEY_B, "B", "\uE0A1" }, { KEY_X, "X", "\uE0A2" }, { KEY_Y, "Y", "\uE0A3" }, + { KEY_LSTICK, "LS", "\uE08A" }, { KEY_RSTICK, "RS", "\uE08B" }, + { KEY_MINUS, "MINUS", "\uE0B6" }, { KEY_PLUS, "PLUS", "\uE0B5" } + }; + + } + + [[maybe_unused]] static void goBack(); + + [[maybe_unused]] static void setNextOverlay(const std::string& ovlPath, std::string args = ""); + + template + int loop(int argc, char** argv); + + // Helpers + + namespace hlp { + + /** + * @brief Wrapper for service initialization + * + * @param f wrapped function + */ + static inline void doWithSmSession(std::function f) { + smInitialize(); + f(); + smExit(); + } + + /** + * @brief Wrapper for sd card access using stdio + * @note Consider using raw fs calls instead as they are faster and need less space + * + * @param f wrapped function + */ + static inline void doWithSDCardHandle(std::function f) { + fsdevMountSdmc(); + f(); + fsdevUnmountDevice("sdmc"); + } + + /** + * @brief Guard that will execute a passed function at the end of the current scope + * + * @param f wrapped function + */ + class ScopeGuard { + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + private: + std::function f; + public: + ALWAYS_INLINE ScopeGuard(std::function f) : f(std::move(f)) { } + ALWAYS_INLINE ~ScopeGuard() { if (f) { f(); } } + void dismiss() { f = nullptr; } + }; + + /** + * @brief libnx hid:sys shim that gives or takes away frocus to or from the process with the given aruid + * + * @param enable Give focus or take focus + * @param aruid Aruid of the process to focus/unfocus + * @return Result Result + */ + static Result hidsysEnableAppletToGetInput(bool enable, u64 aruid) { + const struct { + u8 permitInput; + u64 appletResourceUserId; + } in = { enable != 0, aruid }; + + return serviceDispatchIn(hidsysGetServiceSession(), 503, in); + } + + /** + * @brief Toggles focus between the Tesla overlay and the rest of the system + * + * @param enabled Focus Tesla? + */ + static void requestForeground(bool enabled) { + u64 applicationAruid = 0, appletAruid = 0; + + for (u64 programId = 0x0100000000001000UL; programId < 0x0100000000001020UL; programId++) { + pmdmntGetProcessId(&appletAruid, programId); + + if (appletAruid != 0) + hidsysEnableAppletToGetInput(!enabled, appletAruid); + } + + pmdmntGetApplicationProcessId(&applicationAruid); + hidsysEnableAppletToGetInput(!enabled, applicationAruid); + + hidsysEnableAppletToGetInput(true, 0); + } + + /** + * @brief Splits a string at the given delimeters + * + * @param str String to split + * @param delim Delimeter + * @return Vector containing the split tokens + */ + static std::vector split(const std::string& str, char delim = ' ') { + std::vector out; + + std::size_t current, previous = 0; + current = str.find(delim); + while (current != std::string::npos) { + out.push_back(str.substr(previous, current - previous)); + previous = current + 1; + current = str.find(delim, previous); + } + out.push_back(str.substr(previous, current - previous)); + + return out; + } + + namespace ini { + + /** + * @brief Ini file type + */ + using IniData = std::map>; + + /** + * @brief Tesla config file + */ + static const char* CONFIG_FILE = "/config/tesla/config.ini"; + + /** + * @brief Parses a ini string + * + * @param str String to parse + * @return Parsed data + */ + static IniData parseIni(const std::string &str) { + IniData iniData; + + auto lines = split(str, '\n'); + + std::string lastHeader = ""; + for (auto& line : lines) { + line.erase(std::remove_if(line.begin(), line.end(), ::isspace), line.end()); + + if (line[0] == '[' && line[line.size() - 1] == ']') { + lastHeader = line.substr(1, line.size() - 2); + iniData.emplace(lastHeader, std::map{}); + } + else if (auto keyValuePair = split(line, '='); keyValuePair.size() == 2) { + iniData[lastHeader].emplace(keyValuePair[0], keyValuePair[1]); + } + } + + return iniData; + } + + /** + * @brief Unparses ini data into a string + * + * @param iniData Ini data + * @return Ini string + */ + static std::string unparseIni(IniData const &iniData) { + std::string string; + bool addSectionGap = false; + for (auto §ion : iniData) { + if (addSectionGap) + string += "\n"; + string += "["s + section.first + "]\n"s; + for (auto &keyValue : section.second) { + string += keyValue.first + "="s + keyValue.second + "\n"s; + } + } + return string; + } + + /** + * @brief Read Tesla settings file + * + * @return Settings data + */ + static IniData readOverlaySettings() { + /* Open Sd card filesystem. */ + FsFileSystem fsSdmc; + if (R_FAILED(fsOpenSdCardFileSystem(&fsSdmc))) + return {}; + hlp::ScopeGuard fsGuard([&] { fsFsClose(&fsSdmc); }); + + /* Open config file. */ + FsFile fileConfig; + if (R_FAILED(fsFsOpenFile(&fsSdmc, CONFIG_FILE, FsOpenMode_Read, &fileConfig))) + return {}; + hlp::ScopeGuard fileGuard([&] { fsFileClose(&fileConfig); }); + + /* Get config file size. */ + s64 configFileSize; + if (R_FAILED(fsFileGetSize(&fileConfig, &configFileSize))) + return {}; + + /* Read and parse config file. */ + std::string configFileData(configFileSize, '\0'); + u64 readSize; + Result rc = fsFileRead(&fileConfig, 0, configFileData.data(), configFileSize, FsReadOption_None, &readSize); + if (R_FAILED(rc) || readSize != static_cast(configFileSize)) + return {}; + + return parseIni(configFileData); + } + + /** + * @brief Replace Tesla settings file with new data + * + * @param iniData new data + */ + static void writeOverlaySettings(IniData const &iniData) { + /* Open Sd card filesystem. */ + FsFileSystem fsSdmc; + if (R_FAILED(fsOpenSdCardFileSystem(&fsSdmc))) + return; + hlp::ScopeGuard fsGuard([&] { fsFsClose(&fsSdmc); }); + + /* Open config file. */ + FsFile fileConfig; + if (R_FAILED(fsFsOpenFile(&fsSdmc, CONFIG_FILE, FsOpenMode_Write, &fileConfig))) + return; + hlp::ScopeGuard fileGuard([&] { fsFileClose(&fileConfig); }); + + std::string iniString = unparseIni(iniData); + + fsFileWrite(&fileConfig, 0, iniString.c_str(), iniString.length(), FsWriteOption_Flush); + } + + /** + * @brief Merge and save changes into Tesla settings file + * + * @param changes setting values to add or update + */ + static void updateOverlaySettings(IniData const &changes) { + hlp::ini::IniData iniData = hlp::ini::readOverlaySettings(); + for (auto §ion : changes) { + for (auto &keyValue : section.second) { + iniData[section.first][keyValue.first] = keyValue.second; + } + } + writeOverlaySettings(iniData); + } + + } + + /** + * @brief Decodes a key string into it's key code + * + * @param value Key string + * @return Key code + */ + static u64 stringToKeyCode(const std::string &value) { + for (auto &keyInfo : impl::KEYS_INFO) { + if (strcasecmp(value.c_str(), keyInfo.name) == 0) + return keyInfo.key; + } + return 0; + } + + /** + * @brief Decodes a combo string into key codes + * + * @param value Combo string + * @return Key codes + */ + static u64 comboStringToKeys(const std::string &value) { + u64 keyCombo = 0x00; + for (std::string key : hlp::split(value, '+')) { + keyCombo |= hlp::stringToKeyCode(key); + } + return keyCombo; + } + + /** + * @brief Encodes key codes into a combo string + * + * @param keys Key codes + * @return Combo string + */ + static std::string keysToComboString(u64 keys) { + std::string str; + for (auto &keyInfo : impl::KEYS_INFO) { + if (keys & keyInfo.key) { + if (!str.empty()) + str.append("+"); + str.append(keyInfo.name); + } + } + return str; + } + + } + + // Renderer + + namespace gfx { + + extern "C" u64 __nx_vi_layer_id; + + struct ScissoringConfig { + s32 x, y, w, h; + }; + + /** + * @brief Manages the Tesla layer and draws raw data to the screen + */ + class Renderer final { + public: + Renderer& operator=(Renderer&) = delete; + + friend class tsl::Overlay; + + /** + * @brief Handles opacity of drawn colors for fadeout. Pass all colors through this function in order to apply opacity properly + * + * @param c Original color + * @return Color with applied opacity + */ + static Color a(const Color &c) { + return (c.rgba & 0x0FFF) | (static_cast(c.a * Renderer::s_opacity) << 12); + } + + /** + * @brief Enables scissoring, discarding of any draw outside the given boundaries + * + * @param x x pos + * @param y y pos + * @param w Width + * @param h Height + */ + inline void enableScissoring(s32 x, s32 y, s32 w, s32 h) { + if (this->m_scissoring) + this->m_scissoringStack.push_back(this->m_currScissorConfig); + else + this->m_scissoring = true; + + this->m_currScissorConfig = { x, y, w, h }; + } + + /** + * @brief Disables scissoring + */ + inline void disableScissoring() { + if (this->m_scissoringStack.size() > 0) { + this->m_currScissorConfig = this->m_scissoringStack.back(); + this->m_scissoringStack.pop_back(); + } + else { + this->m_scissoring = false; + this->m_currScissorConfig = { 0 }; + } + } + + + // Drawing functions + + /** + * @brief Draw a single pixel onto the screen + * + * @param x X pos + * @param y Y pos + * @param color Color + */ + inline void setPixel(s32 x, s32 y, Color color) { + if (x < 0 || y < 0 || x >= cfg::FramebufferWidth || y >= cfg::FramebufferHeight) + return; + + u32 offset = this->getPixelOffset(x, y); + + if (offset != UINT32_MAX) + static_cast(this->getCurrentFramebuffer())[offset] = color; + } + + /** + * @brief Blends two colors + * + * @param src Source color + * @param dst Destination color + * @param alpha Opacity + * @return Blended color + */ + inline u8 blendColor(u8 src, u8 dst, u8 alpha) { + u8 oneMinusAlpha = 0x0F - alpha; + + return (dst * alpha + src * oneMinusAlpha) / float(0xF); + } + + /** + * @brief Draws a single source blended pixel onto the screen + * + * @param x X pos + * @param y Y pos + * @param color Color + */ + inline void setPixelBlendSrc(s32 x, s32 y, Color color) { + if (x < 0 || y < 0 || x >= cfg::FramebufferWidth || y >= cfg::FramebufferHeight) + return; + + u32 offset = this->getPixelOffset(x, y); + + if (offset == UINT32_MAX) + return; + + Color src((static_cast(this->getCurrentFramebuffer()))[offset]); + Color dst(color); + Color end(0); + + end.r = this->blendColor(src.r, dst.r, dst.a); + end.g = this->blendColor(src.g, dst.g, dst.a); + end.b = this->blendColor(src.b, dst.b, dst.a); + end.a = src.a; + + this->setPixel(x, y, end); + } + + /** + * @brief Draws a single destination blended pixel onto the screen + * + * @param x X pos + * @param y Y pos + * @param color Color + */ + inline void setPixelBlendDst(s32 x, s32 y, Color color) { + if (x < 0 || y < 0 || x >= cfg::FramebufferWidth || y >= cfg::FramebufferHeight) + return; + + u32 offset = this->getPixelOffset(x, y); + + if (offset == UINT32_MAX) + return; + + Color src((static_cast(this->getCurrentFramebuffer()))[offset]); + Color dst(color); + Color end(0); + + end.r = this->blendColor(src.r, dst.r, dst.a); + end.g = this->blendColor(src.g, dst.g, dst.a); + end.b = this->blendColor(src.b, dst.b, dst.a); + end.a = std::min(dst.a + src.a, 0xF); + + this->setPixel(x, y, end); + } + + /** + * @brief Draws a rectangle of given sizes + * + * @param x X pos + * @param y Y pos + * @param w Width + * @param h Height + * @param color Color + */ + inline void drawRect(s32 x, s32 y, s32 w, s32 h, Color color) { + for (s32 x1 = x; x1 < (x + w); x1++) + for (s32 y1 = y; y1 < (y + h); y1++) + this->setPixelBlendDst(x1, y1, color); + } + + void drawCircle(s32 centerX, s32 centerY, u16 radius, bool filled, Color color) { + s32 x = radius; + s32 y = 0; + s32 radiusError = 0; + s32 xChange = 1 - (radius << 1); + s32 yChange = 0; + + while (x >= y) { + if(filled) { + for (s32 i = centerX - x; i <= centerX + x; i++) { + s32 y0 = centerY + y; + s32 y1 = centerY - y; + s32 x0 = i; + + this->setPixelBlendDst(x0, y0, color); + this->setPixelBlendDst(x0, y1, color); + } + + for (s32 i = centerX - y; i <= centerX + y; i++) { + s32 y0 = centerY + x; + s32 y1 = centerY - x; + s32 x0 = i; + + this->setPixelBlendDst(x0, y0, color); + this->setPixelBlendDst(x0, y1, color); + } + + y++; + radiusError += yChange; + yChange += 2; + if (((radiusError << 1) + xChange) > 0) { + x--; + radiusError += xChange; + xChange += 2; + } + } else { + this->setPixelBlendDst(centerX + x, centerY + y, color); + this->setPixelBlendDst(centerX + y, centerY + x, color); + this->setPixelBlendDst(centerX - y, centerY + x, color); + this->setPixelBlendDst(centerX - x, centerY + y, color); + this->setPixelBlendDst(centerX - x, centerY - y, color); + this->setPixelBlendDst(centerX - y, centerY - x, color); + this->setPixelBlendDst(centerX + y, centerY - x, color); + this->setPixelBlendDst(centerX + x, centerY - y, color); + + if(radiusError <= 0) { + y++; + radiusError += 2 * y + 1; + } else { + x--; + radiusError -= 2 * x + 1; + } + } + } + } + + /** + * @brief Draws a RGBA8888 bitmap from memory + * + * @param x X start position + * @param y Y start position + * @param w Bitmap width + * @param h Bitmap height + * @param bmp Pointer to bitmap data + */ + void drawBitmap(s32 x, s32 y, s32 w, s32 h, const u8 *bmp) { + for (s32 y1 = 0; y1 < h; y1++) { + for (s32 x1 = 0; x1 < w; x1++) { + const Color color = { static_cast(bmp[0] >> 4), static_cast(bmp[1] >> 4), static_cast(bmp[2] >> 4), static_cast(bmp[3] >> 4) }; + setPixelBlendSrc(x + x1, y + y1, a(color)); + bmp += 4; + } + } + } + + /** + * @brief Fills the entire layer with a given color + * + * @param color Color + */ + inline void fillScreen(Color color) { + std::fill_n(static_cast(this->getCurrentFramebuffer()), this->getFramebufferSize() / sizeof(Color), color); + } + + /** + * @brief Clears the layer (With transparency) + * + */ + inline void clearScreen() { + this->fillScreen({ 0x00, 0x00, 0x00, 0x00 }); + } + + /** + * @brief Draws a string + * + * @param string String to draw + * @param monospace Draw string in monospace font + * @param x X pos + * @param y Y pos + * @param fontSize Height of the text drawn in pixels + * @param color Text color. Use transparent color to skip drawing and only get the string's dimensions + * @return Dimensions of drawn string + */ + std::pair drawString(const char* string, bool monospace, s32 x, s32 y, float fontSize, Color color, ssize_t maxWidth = 0) { + s32 maxX = x; + s32 currX = x; + s32 currY = y; + + do { + if (maxWidth > 0 && maxWidth < (currX - x)) + break; + + u32 currCharacter; + ssize_t codepointWidth = decode_utf8(&currCharacter, reinterpret_cast(string)); + + if (codepointWidth <= 0) + break; + + string += codepointWidth; + + stbtt_fontinfo *currFont = nullptr; + + if (stbtt_FindGlyphIndex(&this->m_extFont, currCharacter)) + currFont = &this->m_extFont; + else + currFont = &this->m_stdFont; + + float currFontSize = stbtt_ScaleForPixelHeight(currFont, fontSize); + + int bounds[4] = { 0 }; + stbtt_GetCodepointBitmapBoxSubpixel(currFont, currCharacter, currFontSize, currFontSize, + 0, 0, &bounds[0], &bounds[1], &bounds[2], &bounds[3]); + + int xAdvance = 0, yAdvance = 0; + stbtt_GetCodepointHMetrics(currFont, monospace ? 'W' : currCharacter, &xAdvance, &yAdvance); + + if (currCharacter == '\n') { + maxX = std::max(currX, maxX); + + currX = x; + currY += fontSize; + + continue; + } + + if (!std::iswspace(currCharacter) && fontSize > 0 && color.a != 0x0) + this->drawGlyph(currCharacter, currX + bounds[0], currY + bounds[1], color, currFont, currFontSize); + + currX += static_cast(xAdvance * currFontSize); + + } while (*string != '\0'); + + maxX = std::max(currX, maxX); + + return { maxX - x, currY - y }; + } + + /** + * @brief Limit a strings length and end it with "…" + * + * @param string String to truncate + * @param maxLength Maximum length of string + */ + std::string limitStringLength(std::string string, bool monospace, float fontSize, s32 maxLength) { + if (string.size() < 2) + return string; + + s32 currX = 0; + ssize_t strPos = 0; + ssize_t codepointWidth; + + do { + u32 currCharacter; + codepointWidth = decode_utf8(&currCharacter, reinterpret_cast(&string[strPos])); + + if (codepointWidth <= 0) + break; + + strPos += codepointWidth; + + stbtt_fontinfo *currFont = nullptr; + + if (stbtt_FindGlyphIndex(&this->m_extFont, currCharacter)) + currFont = &this->m_extFont; + else + currFont = &this->m_stdFont; + + float currFontSize = stbtt_ScaleForPixelHeight(currFont, fontSize); + + int xAdvance = 0, yAdvance = 0; + stbtt_GetCodepointHMetrics(currFont, monospace ? 'W' : currCharacter, &xAdvance, &yAdvance); + + currX += static_cast(xAdvance * currFontSize); + + } while (string[strPos] != '\0' && string[strPos] != '\n' && currX < maxLength); + + std::strcpy(&string[strPos - codepointWidth], "…"); + string.shrink_to_fit(); + + return string; + } + + private: + Renderer() {} + + /** + * @brief Gets the renderer instance + * + * @return Renderer + */ + static Renderer& get() { + static Renderer renderer; + + return renderer; + } + + /** + * @brief Sets the opacity of the layer + * + * @param opacity Opacity + */ + static void setOpacity(float opacity) { + opacity = std::clamp(opacity, 0.0F, 1.0F); + + Renderer::s_opacity = opacity; + } + + bool m_initialized = false; + ViDisplay m_display; + ViLayer m_layer; + Event m_vsyncEvent; + + NWindow m_window; + Framebuffer m_framebuffer; + void *m_currentFramebuffer = nullptr; + + bool m_scissoring = false; + ScissoringConfig m_currScissorConfig; + std::vector m_scissoringStack; + + stbtt_fontinfo m_stdFont, m_extFont; + + static inline float s_opacity = 1.0F; + + /** + * @brief Get the current framebuffer address + * + * @return Framebuffer address + */ + inline void* getCurrentFramebuffer() { + return this->m_currentFramebuffer; + } + + /** + * @brief Get the next framebuffer address + * + * @return Next framebuffer address + */ + inline void* getNextFramebuffer() { + return static_cast(this->m_framebuffer.buf) + this->getNextFramebufferSlot() * this->getFramebufferSize(); + } + + /** + * @brief Get the framebuffer size + * + * @return Framebuffer size + */ + inline size_t getFramebufferSize() { + return this->m_framebuffer.fb_size; + } + + /** + * @brief Get the number of framebuffers in use + * + * @return Number of framebuffers + */ + inline size_t getFramebufferCount() { + return this->m_framebuffer.num_fbs; + } + + /** + * @brief Get the currently used framebuffer's slot + * + * @return Slot + */ + inline u8 getCurrentFramebufferSlot() { + return this->m_window.cur_slot; + } + + /** + * @brief Get the next framebuffer's slot + * + * @return Next slot + */ + inline u8 getNextFramebufferSlot() { + return (this->getCurrentFramebufferSlot() + 1) % this->getFramebufferCount(); + } + + /** + * @brief Waits for the vsync event + * + */ + inline void waitForVSync() { + eventWait(&this->m_vsyncEvent, UINT64_MAX); + } + + /** + * @brief Decodes a x and y coordinate into a offset into the swizzled framebuffer + * + * @param x X pos + * @param y Y Pos + * @return Offset + */ + u32 getPixelOffset(s32 x, s32 y) { + if (this->m_scissoring) { + if (x < this->m_currScissorConfig.x || + y < this->m_currScissorConfig.y || + x > this->m_currScissorConfig.x + this->m_currScissorConfig.w || + y > this->m_currScissorConfig.y + this->m_currScissorConfig.h) + return UINT32_MAX; + } + + u32 tmpPos = ((y & 127) / 16) + (x / 32 * 8) + ((y / 16 / 8) * (((cfg::FramebufferWidth / 2) / 16 * 8))); + tmpPos *= 16 * 16 * 4; + + tmpPos += ((y % 16) / 8) * 512 + ((x % 32) / 16) * 256 + ((y % 8) / 2) * 64 + ((x % 16) / 8) * 32 + (y % 2) * 16 + (x % 8) * 2; + + return tmpPos / 2; + } + + /** + * @brief Initializes the renderer and layers + * + */ + void init() { + + cfg::LayerPosX = 0; + cfg::LayerPosY = 0; + cfg::FramebufferWidth = 448; + cfg::FramebufferHeight = 720; + cfg::LayerWidth = cfg::ScreenHeight * (float(cfg::FramebufferWidth) / float(cfg::FramebufferHeight)); + cfg::LayerHeight = cfg::ScreenHeight; + + if (this->m_initialized) + return; + + tsl::hlp::doWithSmSession([this]{ + ASSERT_FATAL(viInitialize(ViServiceType_Manager)); + ASSERT_FATAL(viOpenDefaultDisplay(&this->m_display)); + ASSERT_FATAL(viGetDisplayVsyncEvent(&this->m_display, &this->m_vsyncEvent)); + ASSERT_FATAL(viCreateManagedLayer(&this->m_display, static_cast(0), 0, &__nx_vi_layer_id)); + ASSERT_FATAL(viCreateLayer(&this->m_display, &this->m_layer)); + ASSERT_FATAL(viSetLayerScalingMode(&this->m_layer, ViScalingMode_PreserveAspectRatio)); + + if (s32 layerZ = 0; R_SUCCEEDED(viGetZOrderCountMax(&this->m_display, &layerZ)) && layerZ > 0) + ASSERT_FATAL(viSetLayerZ(&this->m_layer, layerZ)); + + ASSERT_FATAL(viSetLayerSize(&this->m_layer, cfg::LayerWidth, cfg::LayerHeight)); + ASSERT_FATAL(viSetLayerPosition(&this->m_layer, cfg::LayerPosX, cfg::LayerPosY)); + ASSERT_FATAL(nwindowCreateFromLayer(&this->m_window, &this->m_layer)); + ASSERT_FATAL(framebufferCreate(&this->m_framebuffer, &this->m_window, cfg::FramebufferWidth, cfg::FramebufferHeight, PIXEL_FORMAT_RGBA_4444, 2)); + ASSERT_FATAL(this->initFonts()); + }); + + this->m_initialized = true; + } + + /** + * @brief Exits the renderer and layer + * + */ + void exit() { + if (!this->m_initialized) + return; + + framebufferClose(&this->m_framebuffer); + nwindowClose(&this->m_window); + viDestroyManagedLayer(&this->m_layer); + viCloseDisplay(&this->m_display); + eventClose(&this->m_vsyncEvent); + viExit(); + } + + /** + * @brief Initializes Nintendo's shared fonts. Default and Extended + * + * @return Result + */ + Result initFonts() { + static PlFontData stdFontData, extFontData; + + // Nintendo's default font + R_TRY(plGetSharedFontByType(&stdFontData, PlSharedFontType_Standard)); + + u8 *fontBuffer = reinterpret_cast(stdFontData.address); + stbtt_InitFont(&this->m_stdFont, fontBuffer, stbtt_GetFontOffsetForIndex(fontBuffer, 0)); + + // Nintendo's extended font containing a bunch of icons + R_TRY(plGetSharedFontByType(&extFontData, PlSharedFontType_NintendoExt)); + + fontBuffer = reinterpret_cast(extFontData.address); + stbtt_InitFont(&this->m_extFont, fontBuffer, stbtt_GetFontOffsetForIndex(fontBuffer, 0)); + + return 0; + } + + /** + * @brief Start a new frame + * @warning Don't call this more than once before calling \ref endFrame + */ + inline void startFrame() { + this->m_currentFramebuffer = framebufferBegin(&this->m_framebuffer, nullptr); + } + + /** + * @brief End the current frame + * @warning Don't call this before calling \ref startFrame once + */ + inline void endFrame() { + this->waitForVSync(); + framebufferEnd(&this->m_framebuffer); + + this->m_currentFramebuffer = nullptr; + } + + /** + * @brief Draws a single font glyph + * + * @param codepoint Unicode codepoint to draw + * @param x X pos + * @param y Y pos + * @param color Color + * @param font STB Font to use + * @param fontSize Font size + */ + inline void drawGlyph(s32 codepoint, s32 x, s32 y, Color color, stbtt_fontinfo *font, float fontSize) { + int width = 10, height = 10; + + u8 *glyphBmp = stbtt_GetCodepointBitmap(font, fontSize, fontSize, codepoint, &width, &height, nullptr, nullptr); + + if (glyphBmp == nullptr) + return; + + for (s32 bmpY = 0; bmpY < height; bmpY++) { + for (s32 bmpX = 0; bmpX < width; bmpX++) { + Color tmpColor = color; + tmpColor.a = (glyphBmp[width * bmpY + bmpX] >> 4) * (float(tmpColor.a) / 0xF); + this->setPixelBlendDst(x + bmpX, y + bmpY, tmpColor); + } + } + + std::free(glyphBmp); + + } + }; + + } + + // Elements + + namespace elm { + + enum class TouchEvent { + Touch, + Hold, + Scroll, + Release + }; + + /** + * @brief The top level Element of the libtesla UI library + * @note When creating your own elements, extend from this or one of it's sub classes + */ + class Element { + public: + Element() {} + virtual ~Element() { } + + /** + * @brief Handles focus requesting + * @note This function should return the element to focus. + * When this element should be focused, return `this`. + * When one of it's child should be focused, return `this->child->requestFocus(oldFocus, direction)` + * When this element is not focusable, return `nullptr` + * + * @param oldFocus Previously focused element + * @param direction Direction in which focus moved. \ref FocusDirection::None is passed for the initial load + * @return Element to focus + */ + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) { + return nullptr; + } + + /** + * @brief Function called when a joycon button got pressed + * + * @param keys Keys pressed in the last frame + * @return true when button press has been consumed + * @return false when button press should be passed on to the parent + */ + virtual bool onClick(u64 keys) { + return m_clickListener(keys); + } + + /** + * @brief Called once per frame with the latest HID inputs + * + * @param keysDown Buttons pressed in the last frame + * @param keysHeld Buttons held down longer than one frame + * @param touchInput Last touch position + * @param leftJoyStick Left joystick position + * @param rightJoyStick Right joystick position + * @return Weather or not the input has been consumed + */ + virtual bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchPos, JoystickPosition joyStickPosLeft, JoystickPosition joyStickPosRight) { + return false; + } + + /** + * @brief Function called when the element got touched + * @todo Not yet implemented + * + * @param x X pos + * @param y Y pos + * @return true when touch input has been consumed + * @return false when touch input should be passed on to the parent + */ + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + return false; + } + + /** + * @brief Called once per frame to draw the element + * @warning Do not call this yourself. Use \ref Element::frame(gfx::Renderer *renderer) + * + * @param renderer Renderer + */ + virtual void draw(gfx::Renderer *renderer) = 0; + + /** + * @brief Called when the underlying Gui gets created and after calling \ref Gui::invalidate() to calculate positions and boundaries of the element + * @warning Do not call this yourself. Use \ref Element::invalidate() + * + * @param parentX Parent X pos + * @param parentY Parent Y pos + * @param parentWidth Parent Width + * @param parentHeight Parent Height + */ + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) = 0; + + /** + * @brief Draws highlighting and the element itself + * @note When drawing children of a element in \ref Element::draw(gfx::Renderer *renderer), use `this->child->frame(renderer)` instead of calling draw directly + * + * @param renderer + */ + virtual void frame(gfx::Renderer *renderer) final { + renderer->enableScissoring(0, 0, tsl::cfg::FramebufferWidth, tsl::cfg::FramebufferHeight); + + if (this->m_focused) + this->drawFocusBackground(renderer); + + renderer->disableScissoring(); + + this->draw(renderer); + + renderer->enableScissoring(0, 0, tsl::cfg::FramebufferWidth, tsl::cfg::FramebufferHeight); + + if (this->m_focused) + this->drawHighlight(renderer); + + renderer->disableScissoring(); + } + + /** + * @brief Forces a layout recreation of a element + * + */ + virtual void invalidate() final { + const auto& parent = this->getParent(); + + if (parent == nullptr) + this->layout(0, 0, cfg::FramebufferWidth, cfg::FramebufferHeight); + else + this->layout(ELEMENT_BOUNDS(parent)); + } + + /** + * @brief Shake the highlight in the given direction to signal that the focus cannot move there + * + * @param direction Direction to shake highlight in + */ + virtual void shakeHighlight(FocusDirection direction) final { + this->m_highlightShaking = true; + this->m_highlightShakingDirection = direction; + this->m_highlightShakingStartTime = std::chrono::system_clock::now(); + } + + /** + * @brief Triggers the blue click animation to signal a element has been clicked on + * + */ + virtual void triggerClickAnimation() final { + this->m_clickAnimationProgress = tsl::style::ListItemHighlightLength; + } + + /** + * @brief Draws the blue highlight animation when clicking on a button + * @note Override this if you have a element that e.g requires a non-rectangular animation or a different color + * + * @param renderer Renderer + */ + virtual void drawClickAnimation(gfx::Renderer *renderer) { + Color animColor = tsl::style::color::ColorClickAnimation; + u8 saturation = tsl::style::ListItemHighlightSaturation * (float(this->m_clickAnimationProgress) / float(tsl::style::ListItemHighlightLength)); + + animColor.g = saturation; + animColor.b = saturation; + + renderer->drawRect(ELEMENT_BOUNDS(this), a(animColor)); + } + + /** + * @brief Draws the back background when a element is highlighted + * @note Override this if you have a element that e.g requires a non-rectangular focus + * + * @param renderer Renderer + */ + virtual void drawFocusBackground(gfx::Renderer *renderer) { + renderer->drawRect(ELEMENT_BOUNDS(this), a(0xF000)); + + if (this->m_clickAnimationProgress > 0) { + this->drawClickAnimation(renderer); + this->m_clickAnimationProgress--; + } + } + + /** + * @brief Draws the blue boarder when a element is highlighted + * @note Override this if you have a element that e.g requires a non-rectangular focus + * + * @param renderer Renderer + */ + virtual void drawHighlight(gfx::Renderer *renderer) { + static float counter = 0; + const float progress = (std::sin(counter) + 1) / 2; + Color highlightColor = { static_cast((0x2 - 0x8) * progress + 0x8), + static_cast((0x8 - 0xF) * progress + 0xF), + static_cast((0xC - 0xF) * progress + 0xF), + 0xF }; + + counter += 0.1F; + + s32 x = 0, y = 0; + + if (this->m_highlightShaking) { + auto t = (std::chrono::system_clock::now() - this->m_highlightShakingStartTime); + if (t >= 100ms) + this->m_highlightShaking = false; + else { + s32 amplitude = std::rand() % 5 + 5; + + switch (this->m_highlightShakingDirection) { + case FocusDirection::Up: + y -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Down: + y += shakeAnimation(t, amplitude); + break; + case FocusDirection::Left: + x -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Right: + x += shakeAnimation(t, amplitude); + break; + default: + break; + } + + x = std::clamp(x, -amplitude, amplitude); + y = std::clamp(y, -amplitude, amplitude); + } + } + + renderer->drawRect(this->getX() + x - 4, this->getY() + y - 4, this->getWidth() + 8, 4, a(highlightColor)); + renderer->drawRect(this->getX() + x - 4, this->getY() + y + this->getHeight(), this->getWidth() + 8, 4, a(highlightColor)); + renderer->drawRect(this->getX() + x - 4, this->getY() + y, 4, this->getHeight(), a(highlightColor)); + renderer->drawRect(this->getX() + x + this->getWidth(), this->getY() + y, 4, this->getHeight(), a(highlightColor)); + + } + + /** + * @brief Sets the boundaries of this view + * + * @param x Start X pos + * @param y Start Y pos + * @param width Width + * @param height Height + */ + void setBoundaries(s32 x, s32 y, s32 width, s32 height) { + this->m_x = x; + this->m_y = y; + this->m_width = width; + this->m_height = height; + } + + /** + * @brief Adds a click listener to the element + * + * @param clickListener Click listener called with keys that were pressed last frame. Callback should return true if keys got consumed + */ + virtual void setClickListener(std::function clickListener) { + this->m_clickListener = clickListener; + } + + /** + * @brief Gets the element's X position + * + * @return X position + */ + inline s32 getX() { return this->m_x; } + /** + * @brief Gets the element's Y position + * + * @return Y position + */ + inline s32 getY() { return this->m_y; } + /** + * @brief Gets the element's Width + * + * @return Width + */ + inline s32 getWidth() { return this->m_width; } + /** + * @brief Gets the element's Height + * + * @return Height + */ + inline s32 getHeight() { return this->m_height; } + + inline s32 getTopBound() { return this->getY(); } + inline s32 getLeftBound() { return this->getX(); } + inline s32 getRightBound() { return this->getX() + this->getWidth(); } + inline s32 getBottomBound() { return this->getY() + this->getHeight(); } + + /** + * @brief Check if the coordinates are in the elements bounds + * + * @return true if coordinates are in bounds, false otherwise + */ + bool inBounds(s32 touchX, s32 touchY) { + return touchX >= this->getLeftBound() && touchX <= this->getRightBound() && touchY >= this->getTopBound() && touchY <= this->getBottomBound(); + } + + /** + * @brief Sets the element's parent + * @note This is required to handle focus and button downpassing properly + * + * @param parent Parent + */ + inline void setParent(Element *parent) { this->m_parent = parent; } + + /** + * @brief Get the element's parent + * + * @return Parent + */ + inline Element* getParent() { return this->m_parent; } + + /** + * @brief Marks this element as focused or unfocused to draw the highlight + * + * @param focused Focused + */ + virtual inline void setFocused(bool focused) { + this->m_focused = focused; + this->m_clickAnimationProgress = 0; + } + + + static InputMode getInputMode() { return Element::s_inputMode; } + + static void setInputMode(InputMode mode) { Element::s_inputMode = mode; } + + protected: + constexpr static inline auto a = &gfx::Renderer::a; + bool m_focused = false; + u8 m_clickAnimationProgress = 0; + + // Highlight shake animation + bool m_highlightShaking = false; + std::chrono::system_clock::time_point m_highlightShakingStartTime; + FocusDirection m_highlightShakingDirection; + + static inline InputMode s_inputMode; + + /** + * @brief Shake animation callculation based on a damped sine wave + * + * @param t Passed time + * @param a Amplitude + * @return Damped sine wave output + */ + int shakeAnimation(std::chrono::system_clock::duration t, float a) { + float w = 0.2F; + float tau = 0.05F; + + int t_ = t.count() / 1'000'000; + + return roundf(a * exp(-(tau * t_) * sin(w * t_))); + } + + private: + friend class Gui; + + s32 m_x = 0, m_y = 0, m_width = 0, m_height = 0; + Element *m_parent = nullptr; + + std::function m_clickListener = [](u64) { return false; }; + + }; + + /** + * @brief A Element that exposes the renderer directly to draw custom views easily + */ + class CustomDrawer : public Element { + public: + /** + * @brief Constructor + * @note This element should only be used to draw static things the user cannot interact with e.g info text, images, etc. + * + * @param renderFunc Callback that will be called once every frame to draw this view + */ + CustomDrawer(std::function renderFunc) : Element(), m_renderFunc(renderFunc) {} + virtual ~CustomDrawer() {} + + virtual void draw(gfx::Renderer* renderer) override { + renderer->enableScissoring(ELEMENT_BOUNDS(this)); + this->m_renderFunc(renderer, ELEMENT_BOUNDS(this)); + renderer->disableScissoring(); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + + } + + private: + std::function m_renderFunc; + }; + + + /** + * @brief The base frame which can contain another view + * + */ + class OverlayFrame : public Element { + public: + /** + * @brief Constructor + * + * @param title Name of the Overlay drawn bolt at the top + * @param subtitle Subtitle drawn bellow the title e.g version number + */ + OverlayFrame(const std::string& title, const std::string& subtitle) : Element(), m_title(title), m_subtitle(subtitle) {} + virtual ~OverlayFrame() { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + } + + virtual void draw(gfx::Renderer *renderer) override { + renderer->fillScreen(a(tsl::style::color::ColorFrameBackground)); + renderer->drawRect(tsl::cfg::FramebufferWidth - 1, 0, 1, tsl::cfg::FramebufferHeight, a(0xF222)); + + renderer->drawString(this->m_title.c_str(), false, 20, 50, 30, a(tsl::style::color::ColorText)); + renderer->drawString(this->m_subtitle.c_str(), false, 20, 70, 15, a(tsl::style::color::ColorDescription)); + + renderer->drawRect(15, tsl::cfg::FramebufferHeight - 73, tsl::cfg::FramebufferWidth - 30, 1, a(tsl::style::color::ColorText)); + + renderer->drawString("\uE0E1 Back \uE0E0 OK", false, 30, 693, 23, a(tsl::style::color::ColorText)); + + if (this->m_contentElement != nullptr) + this->m_contentElement->frame(renderer); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(parentX, parentY, parentWidth, parentHeight); + + if (this->m_contentElement != nullptr) { + this->m_contentElement->setBoundaries(parentX + 35, parentY + 125, parentWidth - 85, parentHeight - 73 - 125); + this->m_contentElement->invalidate(); + } + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + if (this->m_contentElement != nullptr) + return this->m_contentElement->requestFocus(oldFocus, direction); + else + return nullptr; + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + // Discard touches outside bounds + if (!this->m_contentElement->inBounds(currX, currY)) + return false; + + if (this->m_contentElement != nullptr) + return this->m_contentElement->onTouch(event, currX, currY, prevX, prevY, initialX, initialY); + else return false; + } + + /** + * @brief Sets the content of the frame + * + * @param content Element + */ + virtual void setContent(Element *content) final { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + + this->m_contentElement = content; + + if (content != nullptr) { + this->m_contentElement->setParent(this); + this->invalidate(); + } + } + + /** + * @brief Changes the title of the menu + * + * @param title Title to change to + */ + virtual void setTitle(const std::string &title) final { + this->m_title = title; + } + + /** + * @brief Changes the subtitle of the menu + * + * @param title Subtitle to change to + */ + virtual void setSubtitle(const std::string &subtitle) final { + this->m_subtitle = subtitle; + } + + protected: + Element *m_contentElement = nullptr; + + std::string m_title, m_subtitle; + }; + + /** + * @brief The base frame which can contain another view with a customizable header + * + */ + class HeaderOverlayFrame : public Element { + public: + HeaderOverlayFrame(u16 headerHeight = 175) : Element(), m_headerHeight(headerHeight) {} + virtual ~HeaderOverlayFrame() { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + + if (this->m_header != nullptr) + delete this->m_header; + } + + virtual void draw(gfx::Renderer *renderer) override { + renderer->fillScreen(a(tsl::style::color::ColorFrameBackground)); + renderer->drawRect(tsl::cfg::FramebufferWidth - 1, 0, 1, tsl::cfg::FramebufferHeight, a(0xF222)); + + renderer->drawRect(15, tsl::cfg::FramebufferHeight - 73, tsl::cfg::FramebufferWidth - 30, 1, a(tsl::style::color::ColorText)); + + renderer->drawString("\uE0E1 Back \uE0E0 OK", false, 30, 693, 23, a(tsl::style::color::ColorText)); + + if (this->m_header != nullptr) + this->m_header->frame(renderer); + + if (this->m_contentElement != nullptr) + this->m_contentElement->frame(renderer); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(parentX, parentY, parentWidth, parentHeight); + + if (this->m_contentElement != nullptr) { + this->m_contentElement->setBoundaries(parentX + 35, parentY + this->m_headerHeight, parentWidth - 85, parentHeight - 73 - this->m_headerHeight); + this->m_contentElement->invalidate(); + } + + if (this->m_header != nullptr) { + this->m_header->setBoundaries(parentX, parentY, parentWidth, this->m_headerHeight); + this->m_header->invalidate(); + } + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + // Discard touches outside bounds + if (!this->m_contentElement->inBounds(currX, currY)) + return false; + + if (this->m_contentElement != nullptr) + return this->m_contentElement->onTouch(event, currX, currY, prevX, prevY, initialX, initialY); + else return false; + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + if (this->m_contentElement != nullptr) + return this->m_contentElement->requestFocus(oldFocus, direction); + else + return nullptr; + } + + /** + * @brief Sets the content of the frame + * + * @param content Element + */ + virtual void setContent(Element *content) final { + if (this->m_contentElement != nullptr) + delete this->m_contentElement; + + this->m_contentElement = content; + + if (content != nullptr) { + this->m_contentElement->setParent(this); + this->invalidate(); + } + } + + /** + * @brief Sets the header of the frame + * + * @param header Header custom drawer + */ + virtual void setHeader(CustomDrawer *header) final { + if (this->m_header != nullptr) + delete this->m_header; + + this->m_header = header; + + if (header != nullptr) { + this->m_header->setParent(this); + this->invalidate(); + } + } + + protected: + Element *m_contentElement = nullptr; + CustomDrawer *m_header = nullptr; + + u16 m_headerHeight; + }; + + /** + * @brief Single color rectangle element mainly used for debugging to visualize boundaries + * + */ + class DebugRectangle : public Element { + public: + /** + * @brief Constructor + * + * @param color Color of the rectangle + */ + DebugRectangle(Color color) : Element(), m_color(color) {} + virtual ~DebugRectangle() {} + + virtual void draw(gfx::Renderer *renderer) override { + renderer->drawRect(ELEMENT_BOUNDS(this), a(this->m_color)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override {} + + private: + Color m_color; + }; + + + /** + * @brief A List containing list items + * + */ + class List : public Element { + public: + /** + * @brief Constructor + * + */ + List() : Element() {} + virtual ~List() { + for (auto& item : this->m_items) + delete item; + } + + virtual void draw(gfx::Renderer *renderer) override { + if (this->m_clearList) { + for (auto& item : this->m_items) + delete item; + + this->m_items.clear(); + this->m_offset = 0; + this->m_focusedIndex = 0; + this->invalidate(); + this->m_clearList = false; + } + + for (auto [index, element] : this->m_itemsToAdd) { + element->invalidate(); + if (index >= 0 && (this->m_items.size() > static_cast(index))) { + const auto& it = this->m_items.cbegin() + static_cast(index); + this->m_items.insert(it, element); + } else { + this->m_items.push_back(element); + } + this->invalidate(); + this->updateScrollOffset(); + } + this->m_itemsToAdd.clear(); + + for (auto element : this->m_itemsToRemove) { + for (auto it = m_items.cbegin(); it != m_items.cend(); ++it) { + if (*it == element) { + this->m_items.erase(it); + if (this->m_focusedIndex >= (it - this->m_items.cbegin())) { + this->m_focusedIndex--; + } + this->invalidate(); + this->updateScrollOffset(); + delete element; + break; + } + } + } + this->m_itemsToRemove.clear(); + + renderer->enableScissoring(this->getLeftBound(), this->getTopBound() - 5, this->getWidth(), this->getHeight() + 4); + + for (auto &entry : this->m_items) { + if (entry->getBottomBound() > this->getTopBound() && entry->getTopBound() < this->getBottomBound()) { + entry->frame(renderer); + } + } + + renderer->disableScissoring(); + + if (this->m_listHeight > this->getHeight()) { + float scrollbarHeight = static_cast(this->getHeight() * this->getHeight()) / this->m_listHeight; + float scrollbarOffset = (static_cast(this->m_offset)) / static_cast(this->m_listHeight - this->getHeight()) * (this->getHeight() - std::ceil(scrollbarHeight)); + + renderer->drawRect(this->getRightBound() + 10, this->getY() + scrollbarOffset, 5, scrollbarHeight - 50, a(tsl::style::color::ColorHandle)); + renderer->drawCircle(this->getRightBound() + 12, this->getY() + scrollbarOffset, 2, true, a(tsl::style::color::ColorHandle)); + renderer->drawCircle(this->getRightBound() + 12, this->getY() + scrollbarOffset + scrollbarHeight - 50, 2, true, a(tsl::style::color::ColorHandle)); + + float prevOffset = this->m_offset; + + if (Element::getInputMode() == InputMode::Controller) + this->m_offset += ((this->m_nextOffset) - this->m_offset) * 0.1F; + else if (Element::getInputMode() == InputMode::TouchScroll) + this->m_offset += ((this->m_nextOffset) - this->m_offset); + + if (static_cast(prevOffset) != static_cast(this->m_offset)) + this->invalidate(); + } + + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + s32 y = this->getY() - this->m_offset; + + this->m_listHeight = 0; + for (auto &entry : this->m_items) + this->m_listHeight += entry->getHeight(); + + for (auto &entry : this->m_items) { + entry->setBoundaries(this->getX(), y, this->getWidth(), entry->getHeight()); + entry->invalidate(); + y += entry->getHeight(); + } + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) { + bool handled = false; + + // Discard touches out of bounds + if (!this->inBounds(currX, currY)) + return false; + + // Direct touches to all children + for (auto &item : this->m_items) + handled |= item->onTouch(event, currX, currY, prevX, prevY, initialX, initialY); + + if (handled) + return true; + + // Handle scrolling + if (event != TouchEvent::Release && Element::getInputMode() == InputMode::TouchScroll) { + if (prevX != 0 && prevY != 0) + this->m_nextOffset += (prevY - currY); + + if (this->m_nextOffset < 0) + this->m_nextOffset = 0; + + if (this->m_nextOffset > (this->m_listHeight - this->getHeight()) + 50) + this->m_nextOffset = (this->m_listHeight - this->getHeight() + 50); + + return true; + } + + return false; + } + + /** + * @brief Adds a new item to the list before the next frame starts + * + * @param element Element to add + * @param index Index in the list where the item should be inserted. -1 or greater list size will insert it at the end + * @param height Height of the element. Don't set this parameter for libtesla to try and figure out the size based on the type + */ + virtual void addItem(Element *element, u16 height = 0, ssize_t index = -1) final { + if (element != nullptr) { + if (height != 0) + element->setBoundaries(this->getX(), this->getY(), this->getWidth(), height); + + element->setParent(this); + element->invalidate(); + + this->m_itemsToAdd.emplace_back(index, element); + } + } + + /** + * @brief Removes an item form the list and deletes it + * @note Item will only be deleted if it was found in the list + * + * @param element Element to remove from list. Call \ref Gui::removeFocus before. + */ + virtual void removeItem(Element *element) { + if (element != nullptr) + this->m_itemsToRemove.emplace_back(element); + } + + /** + * @brief Try to remove an item from the list + * + * @param index Index of element in list. Call \ref Gui::removeFocus before. + */ + virtual void removeIndex(size_t index) { + if (index < this->m_items.size()) + removeItem(this->m_items[index]); + } + + /** + * @brief Removes all children from the list later on + * @warning When clearing a list, make sure none of the its children are focused. Call \ref Gui::removeFocus before. + */ + virtual void clear() final { + this->m_clearList = true; + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + Element *newFocus = nullptr; + + if (this->m_clearList || this->m_itemsToAdd.size() > 0) + return nullptr; + + if (direction == FocusDirection::None) { + u16 i = 0; + + if (oldFocus == nullptr) { + s32 elementHeight = 0; + while (elementHeight < this->m_offset && i < this->m_items.size() - 1) { + i++; + elementHeight += this->m_items[i]->getHeight(); + } + } + + for (; i < this->m_items.size(); i++) { + newFocus = this->m_items[i]->requestFocus(oldFocus, direction); + + if (newFocus != nullptr) { + this->m_focusedIndex = i; + + this->updateScrollOffset(); + return newFocus; + } + } + } else { + if (direction == FocusDirection::Down) { + + for (u16 i = this->m_focusedIndex + 1; i < this->m_items.size(); i++) { + newFocus = this->m_items[i]->requestFocus(oldFocus, direction); + + if (newFocus != nullptr && newFocus != oldFocus) { + this->m_focusedIndex = i; + + this->updateScrollOffset(); + return newFocus; + } + } + + return oldFocus; + } else if (direction == FocusDirection::Up) { + if (this->m_focusedIndex > 0) { + + for (u16 i = this->m_focusedIndex - 1; i >= 0; i--) { + if (i > this->m_items.size() || this->m_items[i] == nullptr) + return oldFocus; + else + newFocus = this->m_items[i]->requestFocus(oldFocus, direction); + + if (newFocus != nullptr && newFocus != oldFocus) { + this->m_focusedIndex = i; + + this->updateScrollOffset(); + return newFocus; + } + } + } + + return oldFocus; + } + } + + return oldFocus; + } + + /** + * @brief Gets the item at the index in the list + * + * @param index Index position in list + * @return Element from list. nullptr for if the index is out of bounds + */ + virtual Element* getItemAtIndex(u32 index) { + if (this->m_items.size() <= index) + return nullptr; + + return this->m_items[index]; + } + + /** + * @brief Gets the index in the list of the element passed in + * + * @param element Element to check + * @return Index in list. -1 for if the element isn't a member of the list + */ + virtual s32 getIndexInList(Element *element) { + auto it = std::find(this->m_items.begin(), this->m_items.end(), element); + + if (it == this->m_items.end()) + return -1; + + return it - this->m_items.begin(); + } + + virtual void setFocusedIndex(u32 index) { + if (this->m_items.size() > index) { + m_focusedIndex = index; + this->updateScrollOffset(); + } + } + + protected: + std::vector m_items; + u16 m_focusedIndex = 0; + + float m_offset = 0, m_nextOffset = 0; + s32 m_listHeight = 0; + + bool m_clearList = false; + std::vector m_itemsToRemove; + std::vector> m_itemsToAdd; + + private: + + virtual void updateScrollOffset() { + if (this->getInputMode() != InputMode::Controller) + return; + + if (this->m_listHeight <= this->getHeight()) { + this->m_nextOffset = 0; + this->m_offset = 0; + + return; + } + + this->m_nextOffset = 0; + for (u16 i = 0; i < this->m_focusedIndex; i++) + this->m_nextOffset += this->m_items[i]->getHeight(); + + this->m_nextOffset -= this->getHeight() / 3; + + if (this->m_nextOffset < 0) + this->m_nextOffset = 0; + + if (this->m_nextOffset > (this->m_listHeight - this->getHeight()) + 50) + this->m_nextOffset = (this->m_listHeight - this->getHeight() + 50); + } + }; + + /** + * @brief A item that goes into a list + * + */ + class ListItem : public Element { + public: + /** + * @brief Constructor + * + * @param text Initial description text + */ + ListItem(const std::string& text, const std::string& value = "") + : Element(), m_text(text), m_value(value) { + } + virtual ~ListItem() {} + + virtual void draw(gfx::Renderer *renderer) override { + if (this->m_touched && Element::getInputMode() == InputMode::Touch) { + renderer->drawRect(ELEMENT_BOUNDS(this), a(tsl::style::color::ColorClickAnimation)); + } + + if (this->m_maxWidth == 0) { + if (this->m_value.length() > 0) { + auto [valueWidth, valueHeight] = renderer->drawString(this->m_value.c_str(), false, 0, 0, 20, tsl::style::color::ColorTransparent); + this->m_maxWidth = this->getWidth() - valueWidth - 70; + } else { + this->m_maxWidth = this->getWidth() - 40; + } + + auto [width, height] = renderer->drawString(this->m_text.c_str(), false, 0, 0, 23, tsl::style::color::ColorTransparent); + this->m_trunctuated = width > this->m_maxWidth; + + if (this->m_trunctuated) { + this->m_scrollText = this->m_text + " "; + auto [width, height] = renderer->drawString(this->m_scrollText.c_str(), false, 0, 0, 23, tsl::style::color::ColorTransparent); + this->m_scrollText += this->m_text; + this->m_textWidth = width; + this->m_ellipsisText = renderer->limitStringLength(this->m_text, false, 22, this->m_maxWidth); + } else { + this->m_textWidth = width; + } + } + + renderer->drawRect(this->getX(), this->getY(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX(), this->getTopBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + + if (this->m_trunctuated) { + if (this->m_focused) { + renderer->enableScissoring(this->getX(), this->getY(), this->m_maxWidth + 40, this->getHeight()); + renderer->drawString(this->m_scrollText.c_str(), false, this->getX() + 20 - this->m_scrollOffset, this->getY() + 45, 23, tsl::style::color::ColorText); + renderer->disableScissoring(); + if (this->m_scrollAnimationCounter == 90) { + if (this->m_scrollOffset == this->m_textWidth) { + this->m_scrollOffset = 0; + this->m_scrollAnimationCounter = 0; + } else { + this->m_scrollOffset++; + } + } else { + this->m_scrollAnimationCounter++; + } + } else { + renderer->drawString(this->m_ellipsisText.c_str(), false, this->getX() + 20, this->getY() + 45, 23, a(tsl::style::color::ColorText)); + } + } else { + renderer->drawString(this->m_text.c_str(), false, this->getX() + 20, this->getY() + 45, 23, a(tsl::style::color::ColorText)); + } + + renderer->drawString(this->m_value.c_str(), false, this->getX() + this->m_maxWidth + 45, this->getY() + 45, 20, this->m_faint ? a(tsl::style::color::ColorDescription) : a(tsl::style::color::ColorHighlight)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::ListItemDefaultHeight); + } + + virtual bool onClick(u64 keys) override { + if (keys & KEY_A) + this->triggerClickAnimation(); + else if (keys & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)) + this->m_clickAnimationProgress = 0; + + return Element::onClick(keys); + } + + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) override { + if (event == TouchEvent::Touch) + this->m_touched = this->inBounds(currX, currY); + + if (event == TouchEvent::Release && this->m_touched) { + this->m_touched = false; + + if (Element::getInputMode() == InputMode::Touch) { + bool handled = this->onClick(KEY_A); + + this->m_clickAnimationProgress = 0; + return handled; + } + } + + + return false; + } + + + virtual void setFocused(bool state) override { + this->m_scroll = false; + this->m_scrollOffset = 0; + this->m_scrollAnimationCounter = 0; + Element::setFocused(state); + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + return this; + } + + /** + * @brief Sets the left hand description text of the list item + * + * @param text Text + */ + inline void setText(const std::string& text) { + this->m_text = text; + this->m_scrollText = ""; + this->m_ellipsisText = ""; + this->m_maxWidth = 0; + } + + /** + * @brief Sets the right hand value text of the list item + * + * @param value Text + * @param faint Should the text be drawn in a glowing green or a faint gray + */ + inline void setValue(const std::string& value, bool faint = false) { + this->m_value = value; + this->m_faint = faint; + this->m_maxWidth = 0; + } + + /** + * @brief Gets the left hand description text of the list item + * + * @return Text + */ + inline const std::string& getText() const { + return this->m_text; + } + + /** + * @brief Gets the right hand value text of the list item + * + * @return Value + */ + inline const std::string& getValue() { + return this->m_value; + } + + protected: + std::string m_text; + std::string m_value = ""; + std::string m_scrollText = ""; + std::string m_ellipsisText = ""; + + bool m_scroll = false; + bool m_trunctuated = false; + bool m_faint = false; + + bool m_touched = false; + + u16 m_maxScroll = 0; + u16 m_scrollOffset = 0; + u32 m_maxWidth = 0; + u32 m_textWidth = 0; + u16 m_scrollAnimationCounter = 0; + }; + + /** + * @brief A toggleable list item that changes the state from On to Off when the A button gets pressed + * + */ + class ToggleListItem : public ListItem { + public: + /** + * @brief Constructor + * + * @param text Initial description text + * @param initialState Is the toggle set to On or Off initially + * @param onValue Value drawn if the toggle is on + * @param offValue Value drawn if the toggle is off + */ + ToggleListItem(const std::string& text, bool initialState, const std::string& onValue = "On", const std::string& offValue = "Off") + : ListItem(text), m_state(initialState), m_onValue(onValue), m_offValue(offValue) { + + this->setState(this->m_state); + } + + virtual ~ToggleListItem() {} + + virtual bool onClick(u64 keys) override { + if (keys & KEY_A) { + this->m_state = !this->m_state; + + this->setState(this->m_state); + this->m_stateChangedListener(this->m_state); + + return ListItem::onClick(keys); + } + + return false; + } + + /** + * @brief Gets the current state of the toggle + * + * @return State + */ + virtual inline bool getState() { + return this->m_state; + } + + /** + * @brief Sets the current state of the toggle. Updates the Value + * + * @param state State + */ + virtual void setState(bool state) { + this->m_state = state; + + if (state) + this->setValue(this->m_onValue, false); + else + this->setValue(this->m_offValue, true); + } + + /** + * @brief Adds a listener that gets called whenever the state of the toggle changes + * + * @param stateChangedListener Listener with the current state passed in as parameter + */ + void setStateChangedListener(std::function stateChangedListener) { + this->m_stateChangedListener = stateChangedListener; + } + + protected: + bool m_state = true; + std::string m_onValue, m_offValue; + + std::function m_stateChangedListener = [](bool){}; + }; + + class CategoryHeader : public Element { + public: + CategoryHeader(const std::string &title, bool hasSeparator = false) : m_text(title), m_hasSeparator(hasSeparator) {} + virtual ~CategoryHeader() {} + + virtual void draw(gfx::Renderer *renderer) override { + renderer->drawRect(this->getX() - 2, this->getBottomBound() - 30, 5, 23, a(tsl::style::color::ColorHeaderBar)); + renderer->drawString(this->m_text.c_str(), false, this->getX() + 13, this->getBottomBound() - 12, 15, a(tsl::style::color::ColorText)); + + if (this->m_hasSeparator) + renderer->drawRect(this->getX(), this->getBottomBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + // Check if the CategoryHeader is part of a list and if it's the first entry in it, half it's height + if (List *list = dynamic_cast(this->getParent()); list != nullptr) { + if (list->getIndexInList(this) == 0) { + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::ListItemDefaultHeight / 2); + return; + } + } + + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::ListItemDefaultHeight); + } + + virtual bool onClick(u64 keys) { + return false; + } + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) override { + return nullptr; + } + + private: + std::string m_text; + bool m_hasSeparator; + }; + + /** + * @brief A customizable analog trackbar going from 0% to 100% (like the brightness slider) + * + */ + class TrackBar : public Element { + public: + /** + * @brief Constructor + * + * @param icon Icon shown next to the track bar + */ + TrackBar(const char icon[3]) : m_icon(icon) { } + + virtual ~TrackBar() {} + + virtual Element* requestFocus(Element *oldFocus, FocusDirection direction) { + return this; + } + + virtual bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) override { + if (keysHeld & KEY_LEFT && keysHeld & KEY_RIGHT) + return true; + + if (keysHeld & KEY_LEFT) { + if (this->m_value > 0) { + this->m_value--; + this->m_valueChangedListener(this->m_value); + return true; + } + } + + if (keysHeld & KEY_RIGHT) { + if (this->m_value < 100) { + this->m_value++; + this->m_valueChangedListener(this->m_value); + return true; + } + } + + return false; + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) override { + if (event == TouchEvent::Release) { + this->m_interactionLocked = false; + return false; + } + + + if (!this->m_interactionLocked && this->inBounds(initialX, initialY)) { + if (currX > this->getLeftBound() + 50 && currX < this->getRightBound() && currY > this->getTopBound() && currY < this->getBottomBound()) { + s16 newValue = (static_cast(currX - (this->getX() + 60)) / static_cast(this->getWidth() - 95)) * 100; + + if (newValue < 0) { + newValue = 0; + } else if (newValue > 100) { + newValue = 100; + } + + if (newValue != this->m_value) { + this->m_value = newValue; + this->m_valueChangedListener(this->getProgress()); + } + + return true; + } + } + else + this->m_interactionLocked = true; + + return false; + } + + virtual void draw(gfx::Renderer *renderer) override { + renderer->drawRect(this->getX(), this->getY(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX(), this->getBottomBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame)); + + renderer->drawString(this->m_icon, false, this->getX() + 15, this->getY() + 50, 23, a(tsl::style::color::ColorText)); + + u16 handlePos = (this->getWidth() - 95) * static_cast(this->m_value) / 100; + renderer->drawCircle(this->getX() + 60, this->getY() + 42, 2, true, a(tsl::style::color::ColorHighlight)); + renderer->drawCircle(this->getX() + 60 + this->getWidth() - 95, this->getY() + 42, 2, true, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX() + 60 + handlePos, this->getY() + 40, this->getWidth() - 95 - handlePos, 5, a(tsl::style::color::ColorFrame)); + renderer->drawRect(this->getX() + 60, this->getY() + 40, handlePos, 5, a(tsl::style::color::ColorHighlight)); + + renderer->drawCircle(this->getX() + 62 + handlePos, this->getY() + 42, 18, true, a(tsl::style::color::ColorHandle)); + renderer->drawCircle(this->getX() + 62 + handlePos, this->getY() + 42, 18, false, a(tsl::style::color::ColorFrame)); + } + + virtual void layout(u16 parentX, u16 parentY, u16 parentWidth, u16 parentHeight) override { + this->setBoundaries(this->getX(), this->getY(), this->getWidth(), tsl::style::TrackBarDefaultHeight); + } + + virtual void drawFocusBackground(gfx::Renderer *renderer) { + // No background drawn here in HOS + } + + virtual void drawHighlight(gfx::Renderer *renderer) override { + static float counter = 0; + const float progress = (std::sin(counter) + 1) / 2; + Color highlightColor = { static_cast((0x2 - 0x8) * progress + 0x8), + static_cast((0x8 - 0xF) * progress + 0xF), + static_cast((0xC - 0xF) * progress + 0xF), + static_cast((0x6 - 0xD) * progress + 0xD) }; + + counter += 0.1F; + + u16 handlePos = (this->getWidth() - 95) * static_cast(this->m_value) / 100; + + s32 x = 0; + s32 y = 0; + + if (Element::m_highlightShaking) { + auto t = (std::chrono::system_clock::now() - Element::m_highlightShakingStartTime); + if (t >= 100ms) + Element::m_highlightShaking = false; + else { + s32 amplitude = std::rand() % 5 + 5; + + switch (Element::m_highlightShakingDirection) { + case FocusDirection::Up: + y -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Down: + y += shakeAnimation(t, amplitude); + break; + case FocusDirection::Left: + x -= shakeAnimation(t, amplitude); + break; + case FocusDirection::Right: + x += shakeAnimation(t, amplitude); + break; + default: + break; + } + + x = std::clamp(x, -amplitude, amplitude); + y = std::clamp(y, -amplitude, amplitude); + } + } + + for (u8 i = 16; i <= 19; i++) { + renderer->drawCircle(this->getX() + 62 + x + handlePos, this->getY() + 42 + y, i, false, a(highlightColor)); + } + } + + /** + * @brief Gets the current value of the trackbar + * + * @return State + */ + virtual inline u8 getProgress() { + return this->m_value; + } + + /** + * @brief Sets the current state of the toggle. Updates the Value + * + * @param state State + */ + virtual void setProgress(u8 value) { + this->m_value = value; + } + + /** + * @brief Adds a listener that gets called whenever the state of the toggle changes + * + * @param stateChangedListener Listener with the current state passed in as parameter + */ + void setValueChangedListener(std::function valueChangedListener) { + this->m_valueChangedListener = valueChangedListener; + } + + protected: + const char *m_icon = nullptr; + s16 m_value = 0; + bool m_interactionLocked = false; + + std::function m_valueChangedListener = [](u8){}; + }; + + + /** + * @brief A customizable analog trackbar going from 0% to 100% but using discrete steps (Like the volume slider) + * + */ + class StepTrackBar : public TrackBar { + public: + /** + * @brief Constructor + * + * @param icon Icon shown next to the track bar + * @param numSteps Number of steps the track bar has + */ + StepTrackBar(const char icon[3], size_t numSteps) + : TrackBar(icon), m_numSteps(numSteps) { } + + virtual ~StepTrackBar() {} + + virtual bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) override { + static u32 tick = 0; + + if (keysHeld & KEY_LEFT && keysHeld & KEY_RIGHT) { + tick = 0; + return true; + } + + if (keysHeld & (KEY_LEFT | KEY_RIGHT)) { + if ((tick == 0 || tick > 20) && (tick % 3) == 0) { + if (keysHeld & KEY_LEFT && this->m_value > 0) { + this->m_value = std::max(this->m_value - (100 / (this->m_numSteps - 1)), 0); + } else if (keysHeld & KEY_RIGHT && this->m_value < 100) { + this->m_value = std::min(this->m_value + (100 / (this->m_numSteps - 1)), 100); + } else { + return false; + } + this->m_valueChangedListener(this->getProgress()); + } + tick++; + return true; + } else { + tick = 0; + } + + return false; + } + + virtual bool onTouch(TouchEvent event, s32 currX, s32 currY, s32 prevX, s32 prevY, s32 initialX, s32 initialY) override { + if (this->inBounds(initialX, initialY)) { + if (currY > this->getTopBound() && currY < this->getBottomBound()) { + s16 newValue = (static_cast(currX - (this->getX() + 60)) / static_cast(this->getWidth() - 95)) * 100; + + if (newValue < 0) { + newValue = 0; + } else if (newValue > 100) { + newValue = 100; + } else { + newValue = std::round(newValue / (100.0F / (this->m_numSteps - 1))) * (100.0F / (this->m_numSteps - 1)); + } + + if (newValue != this->m_value) { + this->m_value = newValue; + this->m_valueChangedListener(this->getProgress()); + } + + return true; + } + } + + return false; + } + + /** + * @brief Gets the current value of the trackbar + * + * @return State + */ + virtual inline u8 getProgress() override { + return this->m_value / (100 / (this->m_numSteps - 1)); + } + + /** + * @brief Sets the current state of the toggle. Updates the Value + * + * @param state State + */ + virtual void setProgress(u8 value) override { + value = std::min(value, u8(this->m_numSteps - 1)); + this->m_value = value * (100 / (this->m_numSteps - 1)); + } + + protected: + u8 m_numSteps = 1; + }; + + + /** + * @brief A customizable trackbar with multiple discrete steps with specific names. Name gets displayed above the bar + * + */ + class NamedStepTrackBar : public StepTrackBar { + public: + /** + * @brief Constructor + * + * @param icon Icon shown next to the track bar + * @param stepDescriptions Step names displayed above the track bar + */ + NamedStepTrackBar(const char icon[3], std::initializer_list stepDescriptions) + : StepTrackBar(icon, stepDescriptions.size()), m_stepDescriptions(stepDescriptions.begin(), stepDescriptions.end()) { } + + virtual ~NamedStepTrackBar() {} + + virtual void draw(gfx::Renderer *renderer) override { + + u16 trackBarWidth = this->getWidth() - 95; + u16 stepWidth = trackBarWidth / (this->m_numSteps - 1); + + for (u8 i = 0; i < this->m_numSteps; i++) { + renderer->drawRect(this->getX() + 60 + stepWidth * i, this->getY() + 50, 1, 10, a(tsl::style::color::ColorFrame)); + } + + u8 currentDescIndex = std::clamp(this->m_value / (100 / (this->m_numSteps - 1)), 0, this->m_numSteps - 1); + + auto [descWidth, descHeight] = renderer->drawString(this->m_stepDescriptions[currentDescIndex].c_str(), false, 0, 0, 15, tsl::style::color::ColorTransparent); + renderer->drawString(this->m_stepDescriptions[currentDescIndex].c_str(), false, ((this->getX() + 60) + (this->getWidth() - 95) / 2) - (descWidth / 2), this->getY() + 20, 15, a(tsl::style::color::ColorDescription)); + + StepTrackBar::draw(renderer); + } + + protected: + std::vector m_stepDescriptions; + }; + + } + + // GUI + + /** + * @brief The top level Gui class + * @note The main menu and every sub menu are a separate Gui. Create your own Gui class that extends from this one to create your own menus + * + */ + class Gui { + public: + Gui() { } + + virtual ~Gui() { + if (this->m_topElement != nullptr) + delete this->m_topElement; + } + + /** + * @brief Creates all elements present in this Gui + * @note Implement this function and let it return a heap allocated element used as the top level element. This is usually some kind of frame e.g \ref OverlayFrame + * + * @return Top level element + */ + virtual elm::Element* createUI() = 0; + + /** + * @brief Called once per frame to update values + * + */ + virtual void update() {} + + /** + * @brief Called once per frame with the latest HID inputs + * + * @param keysDown Buttons pressed in the last frame + * @param keysHeld Buttons held down longer than one frame + * @param touchInput Last touch position + * @param leftJoyStick Left joystick position + * @param rightJoyStick Right joystick position + * @return Weather or not the input has been consumed + */ + virtual bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) { + return false; + } + + /** + * @brief Gets the top level element + * + * @return Top level element + */ + virtual elm::Element* getTopElement() final { + return this->m_topElement; + } + + /** + * @brief Get the currently focused element + * + * @return Focused element + */ + virtual elm::Element* getFocusedElement() final { + return this->m_focusedElement; + } + + /** + * @brief Requests focus to a element + * @note Use this function when focusing a element outside of a element's requestFocus function + * + * @param element Element to focus + * @param direction Focus direction + */ + virtual void requestFocus(elm::Element *element, FocusDirection direction, bool shake = true) final { + elm::Element *oldFocus = this->m_focusedElement; + + if (element != nullptr) { + this->m_focusedElement = element->requestFocus(oldFocus, direction); + + if (oldFocus != nullptr) + oldFocus->setFocused(false); + + if (this->m_focusedElement != nullptr) { + this->m_focusedElement->setFocused(true); + } + } + + if (shake && oldFocus == this->m_focusedElement && this->m_focusedElement != nullptr) + this->m_focusedElement->shakeHighlight(direction); + } + + /** + * @brief Removes focus from a element + * + * @param element Element to remove focus from. Pass nullptr to remove the focus unconditionally + */ + virtual void removeFocus(elm::Element* element = nullptr) final { + if (element == nullptr || element == this->m_focusedElement) { + if (this->m_focusedElement != nullptr) { + this->m_focusedElement->setFocused(false); + this->m_focusedElement = nullptr; + } + } + } + + virtual void restoreFocus() final { + this->m_initialFocusSet = false; + } + + protected: + constexpr static inline auto a = &gfx::Renderer::a; + + private: + elm::Element *m_focusedElement = nullptr; + elm::Element *m_topElement = nullptr; + + bool m_initialFocusSet = false; + + friend class Overlay; + friend class gfx::Renderer; + + /** + * @brief Draws the Gui + * + * @param renderer + */ + virtual void draw(gfx::Renderer *renderer) final { + if (this->m_topElement != nullptr) + this->m_topElement->draw(renderer); + } + + virtual bool initialFocusSet() final { + return this->m_initialFocusSet; + } + + virtual void markInitialFocusSet() final { + this->m_initialFocusSet = true; + } + + }; + + + // Overlay + + /** + * @brief The top level Overlay class + * @note Every Tesla overlay should have exactly one Overlay class initializing services and loading the default Gui + */ + class Overlay { + protected: + /** + * @brief Constructor + * @note Called once when the Overlay gets loaded + */ + Overlay() {} + public: + /** + * @brief Deconstructor + * @note Called once when the Overlay exits + * + */ + virtual ~Overlay() {} + + /** + * @brief Initializes services + * @note Called once at the start to initializes services. You have a sm session available during this call, no need to initialize sm yourself + */ + virtual void initServices() {} + + /** + * @brief Exits services + * @note Make sure to exit all services you initialized in \ref Overlay::initServices() here to prevent leaking handles + */ + virtual void exitServices() {} + + /** + * @brief Called before overlay changes from invisible to visible state + * + */ + virtual void onShow() {} + + /** + * @brief Called before overlay changes from visible to invisible state + * + */ + virtual void onHide() {} + + /** + * @brief Loads the default Gui + * @note This function should return the initial Gui to load using the \ref Gui::initially(Args.. args) function + * e.g `return initially();` + * + * @return Default Gui + */ + virtual std::unique_ptr loadInitialGui() = 0; + + /** + * @brief Gets a reference to the current Gui on top of the Gui stack + * + * @return Current Gui reference + */ + virtual std::unique_ptr& getCurrentGui() final { + return this->m_guiStack.top(); + } + + /** + * @brief Shows the Gui + * + */ + virtual void show() final { + if (this->m_disableNextAnimation) { + this->m_animationCounter = 5; + this->m_disableNextAnimation = false; + } + else { + this->m_fadeInAnimationPlaying = true; + this->m_animationCounter = 0; + } + + this->onShow(); + + if (auto& currGui = this->getCurrentGui(); currGui != nullptr) + currGui->restoreFocus(); + } + + /** + * @brief Hides the Gui + * + */ + virtual void hide() final { + if (this->m_disableNextAnimation) { + this->m_animationCounter = 0; + this->m_disableNextAnimation = false; + } + else { + this->m_fadeOutAnimationPlaying = true; + this->m_animationCounter = 5; + } + + this->onHide(); + } + + /** + * @brief Returns whether fade animation is playing + * + * @return whether fade animation is playing + */ + virtual bool fadeAnimationPlaying() final { + return this->m_fadeInAnimationPlaying || this->m_fadeOutAnimationPlaying; + } + + /** + * @brief Closes the Gui + * @note This makes the Tesla overlay exit and return back to the Tesla-Menu + * + */ + virtual void close() final { + this->m_shouldClose = true; + } + + /** + * @brief Gets the Overlay instance + * + * @return Overlay instance + */ + static inline Overlay* const get() { + return Overlay::s_overlayInstance; + } + + /** + * @brief Creates the initial Gui of an Overlay and moves the object to the Gui stack + * + * @tparam T + * @tparam Args + * @param args + * @return constexpr std::unique_ptr + */ + template + constexpr inline std::unique_ptr initially(Args&&... args) { + return std::move(std::make_unique(args...)); + } + + private: + using GuiPtr = std::unique_ptr; + std::stack> m_guiStack; + static inline Overlay *s_overlayInstance = nullptr; + + bool m_fadeInAnimationPlaying = true, m_fadeOutAnimationPlaying = false; + u8 m_animationCounter = 0; + + bool m_shouldHide = false; + bool m_shouldClose = false; + + bool m_disableNextAnimation = false; + + bool m_closeOnExit; + + /** + * @brief Initializes the Renderer + * + */ + virtual void initScreen() final { + gfx::Renderer::get().init(); + } + + /** + * @brief Exits the Renderer + * + */ + virtual void exitScreen() final { + gfx::Renderer::get().exit(); + } + + /** + * @brief Weather or not the Gui should get hidden + * + * @return should hide + */ + virtual bool shouldHide() final { + return this->m_shouldHide; + } + + /** + * @brief Weather or not hte Gui should get closed + * + * @return should close + */ + virtual bool shouldClose() final { + return this->m_shouldClose; + } + + /** + * @brief Handles fade in and fade out animations of the Overlay + * + */ + virtual void animationLoop() final { + if (this->m_fadeInAnimationPlaying) { + this->m_animationCounter++; + + if (this->m_animationCounter >= 5) + this->m_fadeInAnimationPlaying = false; + } + + if (this->m_fadeOutAnimationPlaying) { + this->m_animationCounter--; + + if (this->m_animationCounter == 0) { + this->m_fadeOutAnimationPlaying = false; + this->m_shouldHide = true; + } + } + + gfx::Renderer::setOpacity(0.2 * this->m_animationCounter); + } + + /** + * @brief Main loop + * + */ + virtual void loop() final { + auto& renderer = gfx::Renderer::get(); + + renderer.startFrame(); + + this->animationLoop(); + this->getCurrentGui()->update(); + this->getCurrentGui()->draw(&renderer); + + renderer.endFrame(); + } + + /** + * @brief Called once per frame with the latest HID inputs + * + * @param keysDown Buttons pressed in the last frame + * @param keysHeld Buttons held down longer than one frame + * @param touchInput Last touch position + * @param leftJoyStick Left joystick position + * @param rightJoyStick Right joystick position + * @return Weather or not the input has been consumed + */ + virtual void handleInput(u64 keysDown, u64 keysHeld, bool touchDetected, touchPosition touchPos, JoystickPosition joyStickPosLeft, JoystickPosition joyStickPosRight) final { + static touchPosition initialTouchPos = { 0 }; + static touchPosition oldTouchPos = { 0 }; + static bool oldTouchDetected = false; + static elm::TouchEvent touchEvent; + static u32 repeatTick = 0; + + auto& currentGui = this->getCurrentGui(); + + if (currentGui == nullptr) + return; + + auto currentFocus = currentGui->getFocusedElement(); + auto topElement = currentGui->getTopElement(); + + if (currentFocus == nullptr) { + if (keysDown & KEY_B) { + if (!currentGui->handleInput(KEY_B, 0,{},{},{})) + this->goBack(); + return; + } + + if (topElement == nullptr) + return; + else if (currentGui != nullptr) { + if (!currentGui->initialFocusSet() || keysDown & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)) { + currentGui->requestFocus(topElement, FocusDirection::None); + currentGui->markInitialFocusSet(); + repeatTick = 1; + } + } + } + + bool handled = false; + elm::Element *parentElement = currentFocus; + + while (!handled && parentElement != nullptr) { + handled = parentElement->onClick(keysDown); + parentElement = parentElement->getParent(); + } + + parentElement = currentFocus; + while (!handled && parentElement != nullptr) { + handled = parentElement->handleInput(keysDown, keysHeld, touchPos, joyStickPosLeft, joyStickPosRight); + parentElement = parentElement->getParent(); + } + + if (currentGui != this->getCurrentGui()) + return; + + handled = handled | currentGui->handleInput(keysDown, keysHeld, touchPos, joyStickPosLeft, joyStickPosRight); + + if (!handled && currentFocus != nullptr) { + static bool shouldShake = true; + + if ((((keysHeld & KEY_UP) != 0) + ((keysHeld & KEY_DOWN) != 0) + ((keysHeld & KEY_LEFT) != 0) + ((keysHeld & KEY_RIGHT) != 0)) == 1) { + if ((repeatTick == 0 || repeatTick > 20) && (repeatTick % 4) == 0) { + if (keysHeld & KEY_UP) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Up, shouldShake); + else if (keysHeld & KEY_DOWN) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Down, shouldShake); + else if (keysHeld & KEY_LEFT) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Left, shouldShake); + else if (keysHeld & KEY_RIGHT) + currentGui->requestFocus(currentFocus->getParent(), FocusDirection::Right, shouldShake); + + shouldShake = currentGui->getFocusedElement() != currentFocus; + } + repeatTick++; + } else { + if (keysDown & KEY_B) + this->goBack(); + repeatTick = 0; + shouldShake = true; + } + } + + if (!touchDetected && oldTouchDetected) { + if (currentGui != nullptr && topElement != nullptr) + topElement->onTouch(elm::TouchEvent::Release, oldTouchPos.px, oldTouchPos.py, oldTouchPos.px, oldTouchPos.py, initialTouchPos.px, initialTouchPos.py); + } + + if (touchDetected) { + + u32 xDistance = std::abs(static_cast(initialTouchPos.px) - static_cast(touchPos.px)); + u32 yDistance = std::abs(static_cast(initialTouchPos.py) - static_cast(touchPos.py)); + + xDistance *= xDistance; + yDistance *= yDistance; + + if ((xDistance + yDistance) > 1000) { + elm::Element::setInputMode(InputMode::TouchScroll); + touchEvent = elm::TouchEvent::Scroll; + } else { + if (touchEvent != elm::TouchEvent::Scroll) + touchEvent = elm::TouchEvent::Hold; + } + + if (!oldTouchDetected) { + initialTouchPos = touchPos; + elm::Element::setInputMode(InputMode::Touch); + currentGui->removeFocus(); + touchEvent = elm::TouchEvent::Touch; + } + + + if (currentGui != nullptr && topElement != nullptr) + topElement->onTouch(touchEvent, touchPos.px, touchPos.py, oldTouchPos.px, oldTouchPos.py, initialTouchPos.px, initialTouchPos.py); + + oldTouchPos = touchPos; + + // Hide overlay when touching out of bounds + if (touchPos.px >= cfg::FramebufferWidth) { + if (tsl::elm::Element::getInputMode() == tsl::InputMode::Touch) { + oldTouchPos = { 0 }; + initialTouchPos = { 0 }; + + this->hide(); + } + } + } else { + if (oldTouchPos.px < 150U && oldTouchPos.py > cfg::FramebufferHeight - 73U) + if (initialTouchPos.px < 150U && initialTouchPos.py > cfg::FramebufferHeight - 73U) + if (!currentGui->handleInput(KEY_B, 0,{},{},{})) + this->goBack(); + + elm::Element::setInputMode(InputMode::Controller); + + oldTouchPos = { 0 }; + initialTouchPos = { 0 }; + } + + oldTouchDetected = touchDetected; + } + + /** + * @brief Clears the screen + * + */ + virtual void clearScreen() final { + auto& renderer = gfx::Renderer::get(); + + renderer.startFrame(); + renderer.clearScreen(); + renderer.endFrame(); + } + + /** + * @brief Reset hide and close flags that were previously set by \ref Overlay::close() or \ref Overlay::hide() + * + */ + virtual void resetFlags() final { + this->m_shouldHide = false; + this->m_shouldClose = false; + } + + /** + * @brief Disables the next animation that would play + * + */ + virtual void disableNextAnimation() final { + this->m_disableNextAnimation = true; + } + + /** + * @brief Changes to a different Gui + * + * @param gui Gui to change to + * @return Reference to the Gui + */ + std::unique_ptr& changeTo(std::unique_ptr&& gui) { + gui->m_topElement = gui->createUI(); + + this->m_guiStack.push(std::move(gui)); + + return this->m_guiStack.top(); + } + + /** + * @brief Creates a new Gui and changes to it + * + * @tparam G Gui to create + * @tparam Args Arguments to pass to the Gui + * @param args Arguments to pass to the Gui + * @return Reference to the newly created Gui + */ + template + std::unique_ptr& changeTo(Args&&... args) { + return this->changeTo(std::make_unique(std::forward(args)...)); + } + + /** + * @brief Pops the top Gui from the stack and goes back to the last one + * @note The Overlay gets closes once there are no more Guis on the stack + */ + void goBack() { + if (!this->m_closeOnExit && this->m_guiStack.size() == 1) { + this->hide(); + return; + } + + if (!this->m_guiStack.empty()) + this->m_guiStack.pop(); + + if (this->m_guiStack.empty()) + this->close(); + } + + template + friend std::unique_ptr& changeTo(Args&&... args); + + friend void goBack(); + + template + friend int loop(int argc, char** argv); + + friend class tsl::Gui; + }; + + + namespace impl { + + /** + * @brief Data shared between the different threads + * + */ + struct SharedThreadData { + bool running = false; + + Event comboEvent = { 0 }, homeButtonPressEvent = { 0 }, powerButtonPressEvent = { 0 }; + + bool overlayOpen = false; + + std::mutex dataMutex; + u64 keysDown = 0; + u64 keysDownPending = 0; + u64 keysHeld = 0; + touchPosition touchPos = { 0 }; + u32 touchCount = 0; + JoystickPosition joyStickPosLeft = { 0 }, joyStickPosRight = { 0 }; + }; + + + /** + * @brief Extract values from Tesla settings file + * + */ + static void parseOverlaySettings() { + hlp::ini::IniData parsedConfig = hlp::ini::readOverlaySettings(); + + u64 decodedKeys = hlp::comboStringToKeys(parsedConfig["tesla"]["key_combo"]); + if (decodedKeys) + tsl::cfg::launchCombo = decodedKeys; + } + + /** + * @brief Update and save launch combo keys + * + * @param keys the new combo keys + */ + [[maybe_unused]] static void updateCombo(u64 keys) { + tsl::cfg::launchCombo = keys; + hlp::ini::updateOverlaySettings({ + { "tesla", { + { "key_combo", tsl::hlp::keysToComboString(keys) } + }} + }); + } + + /** + * @brief Input polling loop thread + * + * @tparam launchFlags Launch flags + * @param args Used to pass in a pointer to a \ref SharedThreadData struct + */ + template + static void hidInputPoller(void *args) { + SharedThreadData *shData = static_cast(args); + + // Parse Tesla settings + impl::parseOverlaySettings(); + + // Drop all inputs from the previous overlay + hidScanInput(); + + while (shData->running) { + + // Scan for input changes + hidScanInput(); + + // Read in HID values + { + std::scoped_lock lock(shData->dataMutex); + + shData->keysDown = 0; + shData->keysHeld = 0; + shData->joyStickPosLeft = { 0 }; + shData->joyStickPosRight = { 0 }; + + // Combine input from all controllers + for (u8 controller = 0; controller < 8; controller++) { + if (hidIsControllerConnected(static_cast(controller))) { + shData->keysDown |= hidKeysDown(static_cast(controller)); + shData->keysHeld |= hidKeysHeld(static_cast(controller)); + + JoystickPosition joyStickPosLeft, joyStickPosRight; + hidJoystickRead(&joyStickPosLeft, static_cast(controller), HidControllerJoystick::JOYSTICK_LEFT); + hidJoystickRead(&joyStickPosRight, static_cast(controller), HidControllerJoystick::JOYSTICK_RIGHT); + + if (joyStickPosLeft.dx > 0 && joyStickPosLeft.dx > shData->joyStickPosLeft.dx) + shData->joyStickPosLeft.dx = joyStickPosLeft.dx; + if (joyStickPosLeft.dx < 0 && joyStickPosLeft.dx < shData->joyStickPosLeft.dx) + shData->joyStickPosLeft.dx = joyStickPosLeft.dx; + if (joyStickPosLeft.dy > 0 && joyStickPosLeft.dy > shData->joyStickPosLeft.dy) + shData->joyStickPosLeft.dy = joyStickPosLeft.dy; + if (joyStickPosLeft.dy < 0 && joyStickPosLeft.dy < shData->joyStickPosLeft.dy) + shData->joyStickPosLeft.dy = joyStickPosLeft.dy; + if (joyStickPosRight.dx > 0 && joyStickPosRight.dx > shData->joyStickPosRight.dx) + shData->joyStickPosRight.dx = joyStickPosRight.dx; + if (joyStickPosRight.dx < 0 && joyStickPosRight.dx < shData->joyStickPosRight.dx) + shData->joyStickPosRight.dx = joyStickPosRight.dx; + if (joyStickPosRight.dy > 0 && joyStickPosRight.dy > shData->joyStickPosRight.dy) + shData->joyStickPosRight.dy = joyStickPosRight.dy; + if (joyStickPosRight.dy < 0 && joyStickPosRight.dy < shData->joyStickPosRight.dy) + shData->joyStickPosRight.dy = joyStickPosRight.dy; + } + } + + if (hidIsControllerConnected(CONTROLLER_HANDHELD)) { + shData->keysDown |= hidKeysDown(CONTROLLER_HANDHELD); + shData->keysHeld |= hidKeysHeld(CONTROLLER_HANDHELD); + + hidJoystickRead(&shData->joyStickPosLeft, CONTROLLER_HANDHELD, HidControllerJoystick::JOYSTICK_LEFT); + hidJoystickRead(&shData->joyStickPosRight, CONTROLLER_HANDHELD, HidControllerJoystick::JOYSTICK_RIGHT); + } + + // Read in touch positions + shData->touchCount = hidTouchCount(); + if (shData->touchCount > 0) + hidTouchRead(&shData->touchPos, 0); + else + shData->touchPos = { 0 }; + + if (((shData->keysHeld & tsl::cfg::launchCombo) == tsl::cfg::launchCombo) && shData->keysDown & tsl::cfg::launchCombo) { + if (shData->overlayOpen) { + tsl::Overlay::get()->hide(); + shData->overlayOpen = false; + } + else + eventFire(&shData->comboEvent); + } + + shData->keysDownPending |= shData->keysDown; + } + + //20 ms + svcSleepThread(20'000'000ul); + } + } + + /** + * @brief Home button detection loop thread + * @note This makes sure that focus cannot glitch out when pressing the home button + * + * @param args Used to pass in a pointer to a \ref SharedThreadData struct + */ + static void homeButtonDetector(void *args) { + SharedThreadData *shData = static_cast(args); + + // To prevent focus glitchout, close the overlay immediately when the home button gets pressed + hidsysAcquireHomeButtonEventHandle(&shData->homeButtonPressEvent); + eventClear(&shData->homeButtonPressEvent); + + while (shData->running) { + if (R_SUCCEEDED(eventWait(&shData->homeButtonPressEvent, 100'000'000))) { + eventClear(&shData->homeButtonPressEvent); + + if (shData->overlayOpen) { + tsl::Overlay::get()->hide(); + shData->overlayOpen = false; + } + } + } + + } + + /** + * @brief Power button detection loop thread + * @note This makes sure that focus cannot glitch out when pressing the power button + * + * @param args Used to pass in a pointer to a \ref SharedThreadData struct + */ + static void powerButtonDetector(void *args) { + SharedThreadData *shData = static_cast(args); + + // To prevent focus glitchout, close the overlay immediately when the power button gets pressed + hidsysAcquireSleepButtonEventHandle(&shData->powerButtonPressEvent); + eventClear(&shData->powerButtonPressEvent); + + while (shData->running) { + if (R_SUCCEEDED(eventWait(&shData->powerButtonPressEvent, 100'000'000))) { + eventClear(&shData->powerButtonPressEvent); + + if (shData->overlayOpen) { + tsl::Overlay::get()->hide(); + shData->overlayOpen = false; + } + } + } + + } + + } + + /** + * @brief Creates a new Gui and changes to it + * + * @tparam G Gui to create + * @tparam Args Arguments to pass to the Gui + * @param args Arguments to pass to the Gui + * @return Reference to the newly created Gui + */ + template + std::unique_ptr& changeTo(Args&&... args) { + return Overlay::get()->changeTo(std::forward(args)...); + } + + /** + * @brief Pops the top Gui from the stack and goes back to the last one + * @note The Overlay gets closes once there are no more Guis on the stack + */ + static void goBack() { + Overlay::get()->goBack(); + } + + static void setNextOverlay(const std::string& ovlPath, std::string args) { + + args += " --skipCombo"; + + envSetNextLoad(ovlPath.c_str(), args.c_str()); + } + + + + /** + * @brief libtesla's main function + * @note Call it directly from main passing in argc and argv and returning it e.g `return tsl::loop(argc, argv);` + * + * @tparam TOverlay Your overlay class + * @tparam launchFlags \ref LaunchFlags + * @param argc argc + * @param argv argv + * @return int result + */ + template + static inline int loop(int argc, char** argv) { + static_assert(std::is_base_of_v, "tsl::loop expects a type derived from tsl::Overlay"); + + impl::SharedThreadData shData; + + shData.running = true; + + Thread hidPollerThread, homeButtonDetectorThread, powerButtonDetectorThread; + threadCreate(&hidPollerThread, impl::hidInputPoller, &shData, nullptr, 0x1000, 0x10, -2); + threadCreate(&homeButtonDetectorThread, impl::homeButtonDetector, &shData, nullptr, 0x1000, 0x2C, -2); + threadCreate(&powerButtonDetectorThread, impl::powerButtonDetector, &shData, nullptr, 0x1000, 0x2C, -2); + threadStart(&hidPollerThread); + threadStart(&homeButtonDetectorThread); + threadStart(&powerButtonDetectorThread); + + eventCreate(&shData.comboEvent, false); + + + + auto& overlay = tsl::Overlay::s_overlayInstance; + overlay = new TOverlay(); + overlay->m_closeOnExit = (u8(launchFlags) & u8(impl::LaunchFlags::CloseOnExit)) == u8(impl::LaunchFlags::CloseOnExit); + + tsl::hlp::doWithSmSession([&overlay]{ overlay->initServices(); }); + overlay->initScreen(); + overlay->changeTo(overlay->loadInitialGui()); + + + // Argument parsing + for (u8 arg = 0; arg < argc; arg++) { + if (strcasecmp(argv[arg], "--skipCombo") == 0) { + eventFire(&shData.comboEvent); + overlay->disableNextAnimation(); + } + } + + + while (shData.running) { + + eventWait(&shData.comboEvent, UINT64_MAX); + eventClear(&shData.comboEvent); + shData.overlayOpen = true; + + + hlp::requestForeground(true); + + overlay->show(); + overlay->clearScreen(); + + + while (shData.running) { + overlay->loop(); + + { + std::scoped_lock lock(shData.dataMutex); + if (!overlay->fadeAnimationPlaying()) { + overlay->handleInput(shData.keysDownPending, shData.keysHeld, shData.touchCount > 0, shData.touchPos, shData.joyStickPosLeft, shData.joyStickPosRight); + } + shData.keysDownPending = 0; + } + + if (overlay->shouldHide()) + break; + + if (overlay->shouldClose()) + shData.running = false; + } + + overlay->clearScreen(); + overlay->resetFlags(); + + hlp::requestForeground(false); + + shData.overlayOpen = false; + eventClear(&shData.comboEvent); + } + + eventClose(&shData.homeButtonPressEvent); + eventClose(&shData.powerButtonPressEvent); + eventClose(&shData.comboEvent); + + threadWaitForExit(&hidPollerThread); + threadClose(&hidPollerThread); + threadWaitForExit(&homeButtonDetectorThread); + threadClose(&homeButtonDetectorThread); + threadWaitForExit(&powerButtonDetectorThread); + threadClose(&powerButtonDetectorThread); + + overlay->exitScreen(); + overlay->exitServices(); + + delete overlay; + + return 0; + } + +} + + +#ifdef TESLA_INIT_IMPL + +namespace tsl::cfg { + + u16 LayerWidth = 0; + u16 LayerHeight = 0; + u16 LayerPosX = 0; + u16 LayerPosY = 0; + u16 FramebufferWidth = 0; + u16 FramebufferHeight = 0; + u64 launchCombo = KEY_L | KEY_DDOWN | KEY_RSTICK; +} + +extern "C" { + + u32 __nx_applet_type = AppletType_None; + u32 __nx_fs_num_sessions = 1; + u32 __nx_nv_transfermem_size = 0x40000; + ViLayerFlags __nx_vi_stray_layer_flags = (ViLayerFlags)0; + + /** + * @brief libtesla service initializing function to override libnx's + * + */ + void __appInit(void) { + tsl::hlp::doWithSmSession([]{ + ASSERT_FATAL(fsInitialize()); + ASSERT_FATAL(hidInitialize()); // Controller inputs and Touch + ASSERT_FATAL(plInitialize(PlServiceType_System)); // Font data. Use pl:s to prevent qlaunch/overlaydisp session exhaustion + ASSERT_FATAL(pmdmntInitialize()); // PID querying + ASSERT_FATAL(hidsysInitialize()); // Focus control + ASSERT_FATAL(setsysInitialize()); // Settings querying + }); + } + + /** + * @brief libtesla service exiting function to override libnx's + * + */ + void __appExit(void) { + fsExit(); + hidExit(); + plExit(); + pmdmntExit(); + hidsysExit(); + setsysExit(); + } + +} + +#endif diff --git a/overlay/scripts/make_logo.sh b/overlay/scripts/make_logo.sh new file mode 100755 index 00000000..bbd01a7f --- /dev/null +++ b/overlay/scripts/make_logo.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +DEST="$CURRENT_DIR/../data/logo_rgba.bin" +FONT="$CURRENT_DIR/../../manager/resources/fira/FiraSans-Medium-rnx.ttf" +FONT_SIZE="30.5" +TEXT="sys-clk" + +function render() { + convert -background transparent -colorspace RGB -depth 8 -fill white -font "$1" -pointsize "$2" "label:$3" "$4" +} + +render "$FONT" "$FONT_SIZE" "$TEXT" info: +render "$FONT" "$FONT_SIZE" "$TEXT" "RGBA:$DEST" \ No newline at end of file diff --git a/overlay/src/ipc.h b/overlay/src/ipc.h new file mode 100644 index 00000000..56c9bcdf --- /dev/null +++ b/overlay/src/ipc.h @@ -0,0 +1,23 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#if defined(__cplusplus) +extern "C" +{ +#endif + +#include +#include + +#if defined(__cplusplus) +} +#endif diff --git a/overlay/src/main.cpp b/overlay/src/main.cpp new file mode 100644 index 00000000..aa25b3e2 --- /dev/null +++ b/overlay/src/main.cpp @@ -0,0 +1,75 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#define TESLA_INIT_IMPL +#include + +#include "ui/gui/fatal_gui.h" +#include "ui/gui/main_gui.h" + +class AppOverlay : public tsl::Overlay +{ + public: + AppOverlay() {} + ~AppOverlay() {} + + virtual void exitServices() override { + sysclkIpcExit(); + } + + virtual std::unique_ptr loadInitialGui() override + { + uint32_t apiVersion; + smInitialize(); + + tsl::hlp::ScopeGuard smGuard([] { smExit(); }); + + if(!sysclkIpcRunning()) + { + return initially( + "sys-clk is not running.\n\n" + "\n" + "Please make sure it is correctly\n\n" + "installed and enabled.", + "" + ); + } + + if(R_FAILED(sysclkIpcInitialize()) || R_FAILED(sysclkIpcGetAPIVersion(&apiVersion))) + { + return initially( + "Could not connect to sys-clk.\n\n" + "\n" + "Please make sure it is correctly\n\n" + "installed and enabled.", + "" + ); + } + + if(SYSCLK_IPC_API_VERSION != apiVersion) + { + return initially( + "Overlay not compatible with\n\n" + "the running sys-clk version.\n\n" + "\n" + "Please make sure everything is\n\n" + "installed and up to date.", + "" + ); + } + + return initially(); + } +}; + +int main(int argc, char **argv) +{ + return tsl::loop(argc, argv); +} diff --git a/overlay/src/overlay.h b/overlay/src/overlay.h new file mode 100644 index 00000000..e69de29b diff --git a/overlay/src/ui/elements/base_frame.h b/overlay/src/ui/elements/base_frame.h new file mode 100644 index 00000000..0d4d9ecc --- /dev/null +++ b/overlay/src/ui/elements/base_frame.h @@ -0,0 +1,31 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include +#include "../gui/base_gui.h" + +class BaseFrame : public tsl::elm::HeaderOverlayFrame +{ + public: + BaseFrame(BaseGui* gui) : tsl::elm::HeaderOverlayFrame() { + this->gui = gui; + } + + void draw(tsl::gfx::Renderer* renderer) override + { + tsl::elm::HeaderOverlayFrame::draw(renderer); + this->gui->preDraw(renderer); + } + + protected: + BaseGui* gui; +}; diff --git a/overlay/src/ui/format.h b/overlay/src/ui/format.h new file mode 100644 index 00000000..efbd57bb --- /dev/null +++ b/overlay/src/ui/format.h @@ -0,0 +1,28 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#define FREQ_DEFAULT_TEXT "Do not override" + +static inline std::string formatListFreqMhz(std::uint32_t mhz) +{ + if(mhz == 0) + { + return FREQ_DEFAULT_TEXT; + } + + char buf[10]; + return std::string(buf, snprintf(buf, sizeof(buf), "%u Mhz", mhz)); +} + +static inline std::string formatListFreqHz(std::uint32_t hz) { return formatListFreqMhz(hz / 1000000); } diff --git a/overlay/src/ui/gui/app_profile_gui.cpp b/overlay/src/ui/gui/app_profile_gui.cpp new file mode 100644 index 00000000..29adfe50 --- /dev/null +++ b/overlay/src/ui/gui/app_profile_gui.cpp @@ -0,0 +1,105 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "app_profile_gui.h" + +#include "../format.h" +#include "fatal_gui.h" + +AppProfileGui::AppProfileGui(std::uint64_t applicationId, SysClkTitleProfileList* profileList) +{ + this->applicationId = applicationId; + this->profileList = profileList; +} + +AppProfileGui::~AppProfileGui() +{ + delete this->profileList; +} + +void AppProfileGui::openFreqChoiceGui(tsl::elm::ListItem* listItem, SysClkProfile profile, SysClkModule module, std::uint32_t* hzList) +{ + tsl::changeTo(this->profileList->mhzMap[profile][module] * 1000000, hzList, [this, listItem, profile, module](std::uint32_t hz) { + this->profileList->mhzMap[profile][module] = hz / 1000000; + listItem->setValue(formatListFreqMhz(this->profileList->mhzMap[profile][module])); + Result rc = sysclkIpcSetProfiles(this->applicationId, this->profileList); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcSetProfiles", rc); + return false; + } + + return true; + }); +} + +void AppProfileGui::addModuleListItem(SysClkProfile profile, SysClkModule module, std::uint32_t* hzList) +{ + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(sysclkFormatModule(module, true)); + listItem->setValue(formatListFreqMhz(this->profileList->mhzMap[profile][module])); + listItem->setClickListener([this, listItem, profile, module, hzList](s64 keys) { + if((keys & KEY_A) == KEY_A) + { + this->openFreqChoiceGui(listItem, profile, module, hzList); + return true; + } + + return false; + }); + + this->listElement->addItem(listItem); +} + +void AppProfileGui::addProfileUI(SysClkProfile profile) +{ + this->listElement->addItem(new tsl::elm::CategoryHeader(sysclkFormatProfile(profile, true))); + this->addModuleListItem(profile, SysClkModule_CPU, &sysclk_g_freq_table_cpu_hz[0]); + this->addModuleListItem(profile, SysClkModule_GPU, &sysclk_g_freq_table_gpu_hz[0]); + this->addModuleListItem(profile, SysClkModule_MEM, &sysclk_g_freq_table_mem_hz[0]); +} + +void AppProfileGui::listUI() +{ + this->addProfileUI(SysClkProfile_Docked); + this->addProfileUI(SysClkProfile_Handheld); + this->addProfileUI(SysClkProfile_HandheldCharging); + this->addProfileUI(SysClkProfile_HandheldChargingOfficial); + this->addProfileUI(SysClkProfile_HandheldChargingUSB); +} + +void AppProfileGui::changeTo(std::uint64_t applicationId) +{ + SysClkTitleProfileList* profileList = new SysClkTitleProfileList; + Result rc = sysclkIpcGetProfiles(applicationId, profileList); + if(R_FAILED(rc)) + { + delete profileList; + FatalGui::openWithResultCode("sysclkIpcGetProfiles", rc); + return; + } + + tsl::changeTo(applicationId, profileList); +} + +void AppProfileGui::update() +{ + BaseMenuGui::update(); + + if(this->context && this->applicationId != this->context->applicationId) + { + tsl::changeTo( + "Application changed\n\n" + "\n" + "The running application changed\n\n" + "while editing was going on.", + "" + ); + } +} diff --git a/overlay/src/ui/gui/app_profile_gui.h b/overlay/src/ui/gui/app_profile_gui.h new file mode 100644 index 00000000..982585bb --- /dev/null +++ b/overlay/src/ui/gui/app_profile_gui.h @@ -0,0 +1,33 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_menu_gui.h" +#include "freq_choice_gui.h" + +class AppProfileGui : public BaseMenuGui +{ + protected: + std::uint64_t applicationId; + SysClkTitleProfileList* profileList; + + void openFreqChoiceGui(tsl::elm::ListItem* listItem, SysClkProfile profile, SysClkModule module, std::uint32_t* hzList); + void addModuleListItem(SysClkProfile profile, SysClkModule module, std::uint32_t* hzList); + void addProfileUI(SysClkProfile profile); + + public: + AppProfileGui(std::uint64_t applicationId, SysClkTitleProfileList* profileList); + ~AppProfileGui(); + void listUI() override; + static void changeTo(std::uint64_t applicationId); + void update() override; +}; diff --git a/overlay/src/ui/gui/base_gui.cpp b/overlay/src/ui/gui/base_gui.cpp new file mode 100644 index 00000000..9b427c68 --- /dev/null +++ b/overlay/src/ui/gui/base_gui.cpp @@ -0,0 +1,46 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "base_gui.h" + +#include "../elements/base_frame.h" +#include "logo_rgba_bin.h" + +#define LOGO_WIDTH 110 +#define LOGO_HEIGHT 39 +#define LOGO_X 18 +#define LOGO_Y 21 + +#define LOGO_LABEL_X (LOGO_X + LOGO_WIDTH + 6) +#define LOGO_LABEL_Y 50 +#define LOGO_LABEL_FONT_SIZE 28 + +#define VERSION_X (LOGO_LABEL_X + 110) +#define VERSION_Y LOGO_LABEL_Y +#define VERSION_FONT_SIZE 15 + +void BaseGui::preDraw(tsl::gfx::Renderer* renderer) +{ + renderer->drawBitmap(LOGO_X, LOGO_Y, LOGO_WIDTH, LOGO_HEIGHT, logo_rgba_bin); + renderer->drawString("overlay", false, LOGO_LABEL_X, LOGO_LABEL_Y, LOGO_LABEL_FONT_SIZE, TEXT_COLOR); + renderer->drawString(TARGET_VERSION, false, VERSION_X, VERSION_Y, VERSION_FONT_SIZE, DESC_COLOR); +} + +tsl::elm::Element* BaseGui::createUI() +{ + BaseFrame* rootFrame = new BaseFrame(this); + rootFrame->setContent(this->baseUI()); + return rootFrame; +} + +void BaseGui::update() +{ + this->refresh(); +} diff --git a/overlay/src/ui/gui/base_gui.h b/overlay/src/ui/gui/base_gui.h new file mode 100644 index 00000000..59cb9170 --- /dev/null +++ b/overlay/src/ui/gui/base_gui.h @@ -0,0 +1,27 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "../style.h" + +class BaseGui : public tsl::Gui +{ + public: + BaseGui() {} + ~BaseGui() {} + virtual void preDraw(tsl::gfx::Renderer* renderer); + void update() override; + tsl::elm::Element* createUI() override; + virtual tsl::elm::Element* baseUI() = 0; + virtual void refresh() {} +}; diff --git a/overlay/src/ui/gui/base_menu_gui.cpp b/overlay/src/ui/gui/base_menu_gui.cpp new file mode 100644 index 00000000..45e159bf --- /dev/null +++ b/overlay/src/ui/gui/base_menu_gui.cpp @@ -0,0 +1,114 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "base_menu_gui.h" + +#include "fatal_gui.h" + +BaseMenuGui::BaseMenuGui() +{ + this->context = nullptr; + this->lastContextUpdate = 0; + this->listElement = nullptr; +} + +BaseMenuGui::~BaseMenuGui() +{ + if(this->context) + { + delete this->context; + } +} + +void BaseMenuGui::preDraw(tsl::gfx::Renderer* renderer) +{ + BaseGui::preDraw(renderer); + if(this->context) + { + char buf[32]; + + renderer->drawString("App ID: ", false, 20, 90, SMALL_TEXT_SIZE, DESC_COLOR); + snprintf(buf, sizeof(buf), "%016lX", context->applicationId); + renderer->drawString(buf, false, 81, 90, SMALL_TEXT_SIZE, VALUE_COLOR); + + renderer->drawString("Profile: ", false, 246, 90, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString(sysclkFormatProfile(context->profile, true), false, 302, 90, SMALL_TEXT_SIZE, VALUE_COLOR); + + static struct + { + SysClkModule m; + std::uint32_t x; + } freqOffsets[SysClkModule_EnumMax] = { + { SysClkModule_CPU, 61 }, + { SysClkModule_GPU, 204 }, + { SysClkModule_MEM, 342 }, + }; + + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + std::uint32_t hz = this->context->freqs[freqOffsets[i].m]; + snprintf(buf, sizeof(buf), "%u.%u Mhz", hz / 1000000, hz / 100000 - hz / 1000000 * 10); + renderer->drawString(buf, false, freqOffsets[i].x, 115, SMALL_TEXT_SIZE, VALUE_COLOR); + } + renderer->drawString("CPU:", false, 20, 115, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("GPU:", false, 162, 115, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("MEM:", false, 295, 115, SMALL_TEXT_SIZE, DESC_COLOR); + + static struct + { + SysClkThermalSensor s; + std::uint32_t x; + } tempOffsets[SysClkModule_EnumMax] = { + { SysClkThermalSensor_SOC, 60 }, + { SysClkThermalSensor_PCB, 165 }, + { SysClkThermalSensor_Skin, 268 }, + }; + + renderer->drawString("SOC:", false, 20, 140, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("PCB:", false, 125, 140, SMALL_TEXT_SIZE, DESC_COLOR); + renderer->drawString("Skin:", false, 230, 140, SMALL_TEXT_SIZE, DESC_COLOR); + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + std::uint32_t millis = this->context->temps[tempOffsets[i].s]; + snprintf(buf, sizeof(buf), "%u.%u °C", millis / 1000, (millis - millis / 1000 * 1000) / 100); + renderer->drawString(buf, false, tempOffsets[i].x, 140, SMALL_TEXT_SIZE, VALUE_COLOR); + } + } +} + +void BaseMenuGui::refresh() +{ + std::uint64_t ticks = armGetSystemTick(); + + if(armTicksToNs(ticks - this->lastContextUpdate) > 500000000UL) + { + this->lastContextUpdate = ticks; + if(!this->context) + { + this->context = new SysClkContext; + } + + Result rc = sysclkIpcGetCurrentContext(this->context); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcGetCurrentContext", rc); + return; + } + } +} + +tsl::elm::Element* BaseMenuGui::baseUI() +{ + tsl::elm::List* list = new tsl::elm::List(); + this->listElement = list; + this->listUI(); + + return list; +} diff --git a/overlay/src/ui/gui/base_menu_gui.h b/overlay/src/ui/gui/base_menu_gui.h new file mode 100644 index 00000000..4b576195 --- /dev/null +++ b/overlay/src/ui/gui/base_menu_gui.h @@ -0,0 +1,30 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_gui.h" + +class BaseMenuGui : public BaseGui +{ + protected: + SysClkContext* context; + std::uint64_t lastContextUpdate; + tsl::elm::List* listElement; + + public: + BaseMenuGui(); + ~BaseMenuGui(); + void preDraw(tsl::gfx::Renderer* renderer) override; + tsl::elm::Element* baseUI() override; + void refresh() override; + virtual void listUI() = 0; +}; diff --git a/overlay/src/ui/gui/fatal_gui.cpp b/overlay/src/ui/gui/fatal_gui.cpp new file mode 100644 index 00000000..717412c0 --- /dev/null +++ b/overlay/src/ui/gui/fatal_gui.cpp @@ -0,0 +1,67 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "fatal_gui.h" + +FatalGui::FatalGui(const std::string message, const std::string info) +{ + this->message = message; + this->info = info; +} + +void FatalGui::openWithResultCode(std::string tag, Result rc) +{ + char rcStr[32]; + std::string info = tag; + info.append(rcStr, snprintf(rcStr, sizeof(rcStr), "\n\n[0x%x] %04d-%04d", rc, R_MODULE(rc), R_DESCRIPTION(rc))); + + tsl::changeTo( + "Could not connect to sys-clk.\n\n" + "\n" + "Please make sure everything is\n\n" + "correctly installed and enabled.", + info + ); +} + +tsl::elm::Element* FatalGui::baseUI() +{ + tsl::elm::CustomDrawer* drawer = new tsl::elm::CustomDrawer([this](tsl::gfx::Renderer* renderer, u16 x, u16 y, u16 w, u16 h) { + renderer->drawString("\uE150", false, 40, 210, 40, TEXT_COLOR); + renderer->drawString("Fatal error", false, 100, 210, 30, TEXT_COLOR); + + std::uint32_t txtY = 255; + if(!this->message.empty()) + { + txtY += renderer->drawString(this->message.c_str(), false, 40, txtY, 23, TEXT_COLOR).second; + txtY += 55; + } + + if(!this->info.empty()) + { + renderer->drawString(this->info.c_str(), false, 40, txtY, 18, DESC_COLOR); + } + }); + + return drawer; +} + +bool FatalGui::handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) +{ + if((keysDown & KEY_A) == KEY_A || (keysDown & KEY_B) == KEY_B) + { + while(tsl::Overlay::get()->getCurrentGui() != nullptr) { + tsl::goBack(); + } + return true; + } + + return false; +} diff --git a/overlay/src/ui/gui/fatal_gui.h b/overlay/src/ui/gui/fatal_gui.h new file mode 100644 index 00000000..fe743dc2 --- /dev/null +++ b/overlay/src/ui/gui/fatal_gui.h @@ -0,0 +1,29 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "base_gui.h" + +class FatalGui : public BaseGui +{ + protected: + std::string message; + std::string info; + + public: + FatalGui(const std::string message, const std::string info); + ~FatalGui() {} + tsl::elm::Element* baseUI() override; + bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick); + static void openWithResultCode(std::string tag, Result rc); +}; diff --git a/overlay/src/ui/gui/freq_choice_gui.cpp b/overlay/src/ui/gui/freq_choice_gui.cpp new file mode 100644 index 00000000..4cddc319 --- /dev/null +++ b/overlay/src/ui/gui/freq_choice_gui.cpp @@ -0,0 +1,52 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "freq_choice_gui.h" + +#include "../format.h" + +FreqChoiceGui::FreqChoiceGui(std::uint32_t selectedHz, std::uint32_t* hzList, FreqChoiceListener listener) +{ + this->selectedHz = selectedHz; + this->hzList = hzList; + this->listener = listener; +} + +tsl::elm::ListItem* FreqChoiceGui::createFreqListItem(std::uint32_t hz, bool selected) +{ + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(formatListFreqHz(hz)); + listItem->setValue(selected ? "\uE14B" : ""); + + listItem->setClickListener([this, hz](s64 keys) { + if((keys & KEY_A) == KEY_A && this->listener) + { + if(this->listener(hz)) + { + tsl::goBack(); + } + return true; + } + + return false; + }); + + return listItem; +} + +void FreqChoiceGui::listUI() +{ + std::uint32_t* hzPtr = this->hzList; + this->listElement->addItem(this->createFreqListItem(0, this->selectedHz == 0)); + while(*hzPtr) + { + this->listElement->addItem(this->createFreqListItem(*hzPtr, (*hzPtr / 1000000) == (this->selectedHz / 1000000))); + hzPtr++; + } +} diff --git a/overlay/src/ui/gui/freq_choice_gui.h b/overlay/src/ui/gui/freq_choice_gui.h new file mode 100644 index 00000000..f11c0826 --- /dev/null +++ b/overlay/src/ui/gui/freq_choice_gui.h @@ -0,0 +1,33 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#include "base_menu_gui.h" + +using FreqChoiceListener = std::function; + +#define FREQ_DEFAULT_TEXT "Do not override" + +class FreqChoiceGui : public BaseMenuGui +{ + protected: + std::uint32_t selectedHz; + std::uint32_t* hzList; + FreqChoiceListener listener; + tsl::elm::ListItem* createFreqListItem(std::uint32_t hz, bool selected); + + public: + FreqChoiceGui(std::uint32_t selectedHz, std::uint32_t* hzList, FreqChoiceListener listener); + ~FreqChoiceGui() {} + void listUI() override; +}; diff --git a/overlay/src/ui/gui/global_override_gui.cpp b/overlay/src/ui/gui/global_override_gui.cpp new file mode 100644 index 00000000..3ea98e4b --- /dev/null +++ b/overlay/src/ui/gui/global_override_gui.cpp @@ -0,0 +1,83 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "global_override_gui.h" + +#include "fatal_gui.h" +#include "../format.h" + +GlobalOverrideGui::GlobalOverrideGui() +{ + for(std::uint16_t m = 0; m < SysClkModule_EnumMax; m++) + { + this->listItems[m] = nullptr; + this->listHz[m] = 0; + } +} + +void GlobalOverrideGui::openFreqChoiceGui(SysClkModule module, std::uint32_t* hzList) +{ + tsl::changeTo(this->context->overrideFreqs[module], hzList, [this, module](std::uint32_t hz) { + Result rc = sysclkIpcSetOverride(module, hz); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcSetOverride", rc); + return false; + } + + this->lastContextUpdate = armGetSystemTick(); + this->context->overrideFreqs[module] = hz; + + return true; + }); +} + +void GlobalOverrideGui::addModuleListItem(SysClkModule module, std::uint32_t* hzList) +{ + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(sysclkFormatModule(module, true)); + listItem->setValue(formatListFreqMhz(0)); + + listItem->setClickListener([this, module, hzList](s64 keys) { + if((keys & KEY_A) == KEY_A) + { + this->openFreqChoiceGui(module, hzList); + return true; + } + + return false; + }); + + this->listElement->addItem(listItem); + this->listItems[module] = listItem; +} + +void GlobalOverrideGui::listUI() +{ + this->addModuleListItem(SysClkModule_CPU, &sysclk_g_freq_table_cpu_hz[0]); + this->addModuleListItem(SysClkModule_GPU, &sysclk_g_freq_table_gpu_hz[0]); + this->addModuleListItem(SysClkModule_MEM, &sysclk_g_freq_table_mem_hz[0]); +} + +void GlobalOverrideGui::refresh() +{ + BaseMenuGui::refresh(); + + if(this->context) + { + for(std::uint16_t m = 0; m < SysClkModule_EnumMax; m++) + { + if(this->listItems[m] != nullptr && this->listHz[m] != this->context->overrideFreqs[m]) + { + this->listItems[m]->setValue(formatListFreqHz(this->context->overrideFreqs[m])); + this->listHz[m] = this->context->overrideFreqs[m]; + } + } + } +} diff --git a/overlay/src/ui/gui/global_override_gui.h b/overlay/src/ui/gui/global_override_gui.h new file mode 100644 index 00000000..fb389ffb --- /dev/null +++ b/overlay/src/ui/gui/global_override_gui.h @@ -0,0 +1,31 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "../../ipc.h" +#include "base_menu_gui.h" +#include "freq_choice_gui.h" + +class GlobalOverrideGui : public BaseMenuGui +{ + protected: + tsl::elm::ListItem* listItems[SysClkModule_EnumMax]; + std::uint32_t listHz[SysClkModule_EnumMax]; + + void openFreqChoiceGui(SysClkModule module, std::uint32_t* hzList); + void addModuleListItem(SysClkModule module, std::uint32_t* hzList); + + public: + GlobalOverrideGui(); + ~GlobalOverrideGui() {} + void listUI() override; + void refresh() override; +}; diff --git a/overlay/src/ui/gui/main_gui.cpp b/overlay/src/ui/gui/main_gui.cpp new file mode 100644 index 00000000..b64b022d --- /dev/null +++ b/overlay/src/ui/gui/main_gui.cpp @@ -0,0 +1,67 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include "main_gui.h" + +#include "fatal_gui.h" +#include "app_profile_gui.h" +#include "global_override_gui.h" + +void MainGui::listUI() +{ + this->enabledToggle = new tsl::elm::ToggleListItem("Enable", false); + enabledToggle->setStateChangedListener([this](bool state) { + Result rc = sysclkIpcSetEnabled(state); + if(R_FAILED(rc)) + { + FatalGui::openWithResultCode("sysclkIpcSetEnabled", rc); + } + + this->lastContextUpdate = armGetSystemTick(); + this->context->enabled = state; + }); + this->listElement->addItem(this->enabledToggle); + + tsl::elm::ListItem* appProfileItem = new tsl::elm::ListItem("Edit app profile"); + appProfileItem->setClickListener([this](s64 keys) { + if((keys & KEY_A) == KEY_A && this->context) + { + AppProfileGui::changeTo(this->context->applicationId); + return true; + } + + return false; + }); + this->listElement->addItem(appProfileItem); + + this->listElement->addItem(new tsl::elm::CategoryHeader("Advanced")); + + tsl::elm::ListItem* globalOverrideItem = new tsl::elm::ListItem("Temporary overrides"); + globalOverrideItem->setClickListener([this](s64 keys) { + if((keys & KEY_A) == KEY_A) + { + tsl::changeTo(); + return true; + } + + return false; + }); + this->listElement->addItem(globalOverrideItem); +} + +void MainGui::refresh() +{ + BaseMenuGui::refresh(); + + if(this->context) + { + this->enabledToggle->setState(this->context->enabled); + } +} diff --git a/overlay/src/ui/gui/main_gui.h b/overlay/src/ui/gui/main_gui.h new file mode 100644 index 00000000..cfd0fac9 --- /dev/null +++ b/overlay/src/ui/gui/main_gui.h @@ -0,0 +1,25 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include "base_menu_gui.h" + +class MainGui : public BaseMenuGui +{ + protected: + tsl::elm::ToggleListItem* enabledToggle; + + public: + MainGui() {} + ~MainGui() {} + void listUI() override; + void refresh() override; +}; diff --git a/overlay/src/ui/style.h b/overlay/src/ui/style.h new file mode 100644 index 00000000..86b56d89 --- /dev/null +++ b/overlay/src/ui/style.h @@ -0,0 +1,20 @@ +/* + * -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#define TEXT_COLOR tsl::gfx::Renderer::a(0xFFFF) +#define DESC_COLOR tsl::gfx::Renderer::a({ 0xC, 0xC, 0xC, 0xF }) +#define VALUE_COLOR tsl::gfx::Renderer::a({ 0x5, 0xC, 0xA, 0xF }) +#define SMALL_TEXT_SIZE 15 +#define LABEL_SPACING 7 +#define LABEL_FONT_SIZE 15