diff options
126 files changed, 1422 insertions, 6145 deletions
@@ -6,25 +6,35 @@ Andreas Haas <liu.gam3@gmail.com> Andreas Haas <liu.gam3@gmail.com> <hinsbart@gmail.com> Andreas Haas <liu.gam3@gmail.com> <hinsbart@users.noreply.github.com> Andreas Haas <liu.gam3@gmail.com> <entenflugstuhl@gmail.com> +Anish Bhobe <anishbhobe@hotmail.com> +Anutrix <numaanzaheerahmed@yahoo.com> +Aren Villanueva <arenvillanueva@yomogi-soft.com> <aren@displaysweet.com> Ariel Manzur <ariel@godotengine.org> Ariel Manzur <ariel@godotengine.org> <punto@godotengine.org> Ariel Manzur <ariel@godotengine.org> <ariel@okamstudio.com> Ariel Manzur <ariel@godotengine.org> <punto@Ariels-Mac-mini.local> Ariel Manzur <ariel@godotengine.org> <punto@Ariels-Mac-mini-2.local> Bastiaan Olij <mux213@gmail.com> +Benjamin <mafortion.benjamin@gmail.com> Bernhard Liebl <Bernhard.Liebl@gmx.org> <poke1024@gmx.de> Bernhard Liebl <Bernhard.Liebl@gmx.org> <poke1024@gmx.org> +Chaosus <chaosus89@gmail.com> Chris Bradfield <chris@kidscancode.org> <cb@scribe.net> Clay John <claynjohn@gmail.com> Clay John <claynjohn@gmail.com> <clayjohn@shaw.ca> Dana Olson <dana@shineuponthee.com> <adolson@gmail.com> Daniel J. Ramirez <djrmuv@gmail.com> +Fabian <supagu@gmail.com> Ferenc Arn <tagcup@yahoo.com> Ferenc Arn <tagcup@yahoo.com> <tagcup@users.noreply.github.com> Geequlim <geequlim@gmail.com> Gilles Roudiere <gilles.roudiere@gmail.com> Gilles Roudiere <gilles.roudiere@gmail.com> <gilles.roudiere@laas.fr> Guilherme Felipe <guilhermefelipecgs@gmail.com> +Hanif Bin Ariffin <hanif.ariffin.4326@gmail.com> +Hein-Pieter van Braam-Stewart <hp@tmm.cx> +Hubert Jarosz <marqin.pl@gmail.com> +Hubert Jarosz <marqin.pl@gmail.com> <marqin.pl+git@gmail.com> Hugo Locurcio <hugo.locurcio@hugo.pro> <hugo.l@openmailbox.org> Hugo Locurcio <hugo.locurcio@hugo.pro> <Calinou@users.noreply.github.com> Hugo Locurcio <hugo.locurcio@hugo.pro> Calinou <calinou@opmbx.org> @@ -36,34 +46,62 @@ J08nY <johny@neuromancer.sk> <jancar.jj@gmail.com> J08nY <johny@neuromancer.sk> <J08nY@users.noreply.github.com> Jakub Grzesik <kubecz3k@gmail.com> Jérôme Gully <jerome.gully0@gmail.com> +JFonS <joan.fonssanchez@gmail.com> Juan Linietsky <reduzio@gmail.com> Juan Linietsky <reduzio@gmail.com> <juan@godotengine.org> Juan Linietsky <reduzio@gmail.com> <juan@okamstudio.com> Juan Linietsky <reduzio@gmail.com> <reduz@Juans-MBP.fibertel.com.ar> Juan Linietsky <reduzio@gmail.com> <red@kyoko> Julian Murgia <the.straton@gmail.com> +Kanabenki <lucien.menassol@gmail.com> <18357657+Kanabenki@users.noreply.github.com> Kelly Thomas <kelly.thomas@hotmail.com.au> +K. S. Ernest (iFire) Lee <ernest.lee@chibifire.com> Leon Krause <lk@leonkrause.com> <eska@eska.me> Leon Krause <lk@leonkrause.com> <eska014@users.noreply.github.com> Marcelo Fernandez <marcelofg55@gmail.com> Marcin Zawiejski <dragmz@gmail.com> Mariano Javier Suligoy <marianognu.easyrpg@gmail.com> Mario Schlack <m4r10.5ch14ck@gmail.com> +marxin <mliska@suse.cz> +marynate <mary.w.nate@gmail.com> <marynate@github.com> Max Hilbrunner <m.hilbrunner@gmail.com> Max Hilbrunner <m.hilbrunner@gmail.com> <mhilbrunner@users.noreply.github.com> +Michael Alexsander Silva Dias <michaelalexsander@protonmail.com> +Nathan Lovato <nathan@gdquest.com> Nathan Warden <nathan@nathanwarden.com> <nathanwardenlee@icloud.com> +Nils ANDRÉ-CHANG <nils@nilsand.re> +Nils ANDRÉ-CHANG <nils@nilsand.re> <nils.andre.chang@gmail.com> Nuno Donato <nunodonato@gmail.com> <n.donato@estrelasustentavel.pt> Pedro J. Estébanez <pedrojrulez@gmail.com> <RandomShaper@users.noreply.github.com> Paul Batty <p_batty@hotmail.co.uk> Paul Batty <p_batty@hotmail.co.uk> <Paulb23@users.noreply.github.com> +Pawel Kowal <pkowal1982@gmail.com> <pawel.kowal@javart.eu> Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Pieter-Jan Briers <pieterjan.briers+git@gmail.com> <pieterjan.briers@gmail.com> Poommetee Ketson <poommetee@protonmail.com> +PrzemysÅ‚aw Gołąb (n-pigeon) <golab.przemyslaw@gmail.com> +Ralf Hölzemer <r.hoelzemer@posteo.de> <rollenrolm@posteo.de> +Ralf Hölzemer <r.hoelzemer@posteo.de> <rollenrolm@users.noreply.github.com> +Ramesh Ravone <ramesh.maran443@gmail.com> +RaphaelHunter <raphael10241024@gmail.com> +RaphaelHunter <raphael10241024@gmail.com> <Raphael10241024@gmail.com> +RaphaelHunter <raphael10241024@gmail.com> <raphael20141024@gmail.com> Rémi Verschelde <rverschelde@gmail.com> <remi@verschelde.fr> +Rhody Lugo <rhodylugo@gmail.com> <rhodylugo@me.com> +Robin Hübner <profan@prfn.se> <robinhubner@gmail.com> +romulox_x <romulox_x@yahoo.com> Ruslan Mustakov <r.mustakov@gmail.com> <ruslan.mustakov@xored.com> Saracen <SaracenOne@gmail.com> +sheepandshepherd <sheepandshepherd@hotmail.com> <sheepandshepherd@users.noreply.github.com> +Swarnim Arun <swarnimarun11@gmail.com> Theo Hallenius <redsymbzone@hotmail.com> Thomas Herzog <therzog@mail.de> Thomas Herzog <therzog@mail.de> <thomas.herzog@mail.com> Thomas Herzog <therzog@mail.de> <thomas.herzog@simedis.com> +Twarit <wtwarit@gmail.com> +V.VamsiKrishna <vk@bsb.in> <vamsikrishna.v@gmail.com> +Wilhem Barbier <nounoursheureux@openmailbox.org> <wilhem.b@free.fr> +Wilhem Barbier <nounoursheureux@openmailbox.org> <schtroumps31@gmail.com> +Will Nations <willnationsdev@gmail.com> +yg2f <yoann@terminajones.com> Zher Huei Lee <lee.zh.92@gmail.com> diff --git a/AUTHORS.md b/AUTHORS.md index 523f253228..4ae6db35d7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -41,11 +41,12 @@ name is available. Ariel Manzur (punto-) Bastiaan Olij (BastiaanOlij) Ben Brookshire (sheepandshepherd) - Benjamin (Nallebeorn) + Benjamin Larsson (Nallebeorn) Bernard Liebl (poke1024) BÅ‚ażej SzczygieÅ‚ (zaps166) Bojidar Marinov (bojidar-bg) bruvzg + Cameron Reikes (creikey) Camille Mohr-Daurat (pouleyKetchoupp) Carl Olsson (not-surt) Carter Anderson (cart) @@ -57,7 +58,7 @@ name is available. Dharkael (lupoDharkael) Dmitry Koteroff (Krakean) DualMatrix - Emmanuel Barroga (sparkart) + Emmanuel Barroga (codecustard) Emmanuel Leblond (touilleMan) Eric Lasota (elasota) est31 @@ -70,8 +71,8 @@ name is available. Gerrit Großkopf (Grosskopf) Gilles Roudiere (groud) Guilherme Felipe de C. G. da Silva (guilhermefelipecgs) - Hanif A (hbina) - Hein-Pieter van Braam (hpvb) + Hanif Bin Ariffin (hbina) + Hein-Pieter van Braam-Stewart (hpvb) Hiroshi Ogawa (hi-ogawa) homer666 Hubert Jarosz (Marqin) @@ -109,8 +110,10 @@ name is available. Max Hilbrunner (mhilbrunner) merumelu Michael Alexsander Silva Dias (YeldhamDev) + MichiRecRoom (LikeLakers2) mrezai Nathan Warden (NathanWarden) + Nils André-Chang (NilsIrl) Nuno Donato (nunodonato) Ovnuniarchos Pascal Richter (ShyRed) @@ -125,10 +128,12 @@ name is available. RafaÅ‚ Mikrut (qarmin) Ralf Hölzemer (rollenrolm) Ramesh Ravone (RameshRavone) + raphael10241024 Ray Koopa (RayKoopa) Rémi Verschelde (akien-mga) Rhody Lugo (rraallvv) Roberto F. Arroyo (robfram) + Robin Hübner (profan) romulox-x Ruslan Mustakov (endragor) Saniko (sanikoyes) @@ -136,11 +141,13 @@ name is available. SaracenOne sersoong Simon Wenner (swenner) + Swarnim Arun (minraws) Theo Hallenius (TheoXD) Thomas Herzog (karroffel) Timo (toger5) Timo Schwarzer (timoschwarzer) Tomasz Chabora (KoBeWi) + Twarit Waikar (IronicallySerious) Vinzenz Feenstra (vinzenz) 박한얼 (volzhs) V. Vamsi Krishna (vkbsb) @@ -149,7 +156,6 @@ name is available. Wilson E. Alvarez (Rubonnek) Xavier Cho (mysticfall) yg2f (SuperUserNameMan) - Yuri Roubinski (Chaosus) + Yuri Roubinsky (Chaosus) Zher Huei Lee (leezh) ZuBsPaCe - Дмитрий Сальников (DmitriySalnikov) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 94e182e47e..f86b8563ef 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -298,12 +298,6 @@ Comment: SMAZ Copyright: 2006-2009, Salvatore Sanfilippo License: BSD-3-clause -Files: ./thirdparty/misc/stb_truetype.h - ./thirdparty/misc/stb_vorbis.c -Comment: stb libraries -Copyright: 2007-2019, Sean Barrett -License: public-domain - Files: ./thirdparty/misc/triangulator.cpp ./thirdparty/misc/triangulator.h Comment: PolyPartition @@ -13,6 +13,7 @@ generous deed immortalized in the next stable release of Godot Engine. ## Platinum sponsors Enjin Coin <https://enjincoin.io> + Heroic Labs <https://heroiclabs.com> ## Gold sponsors @@ -29,6 +30,7 @@ generous deed immortalized in the next stable release of Godot Engine. Christian Chipont Christian Uldall Pedersen Christoph Woinke + Darkhan Baimyrza Denis Malyavin Edward Flick Gamechuck @@ -38,23 +40,25 @@ generous deed immortalized in the next stable release of Godot Engine. Hein-Pieter van Braam Jacob McKenney Javary Co. + Jeffery Chiu Jeppe Zapp Justin Arnold Justo Delgado Baudà Kyle Szklenski Leonard Meagher + Mariano Suligoy Matthieu Huvé Maxim Karsten Mike King Nathan Warden Neal Gompa (Conan Kudo) Patrick Aarstad + ScottMakesGames Slobodan Milnovic Stephan Lanfermann Steve + Tristan Pemble VilliHaukka - Xananax - Zashi ## Gold donors @@ -63,8 +67,11 @@ generous deed immortalized in the next stable release of Godot Engine. Daniel Hartmann Dave David Gehrig + David Snopek Ed Morley Florian Krick + Florian Rämisch + GiulianoB Jakub Grzesik K9Kraken Manuele Finocchiaro @@ -72,30 +79,31 @@ generous deed immortalized in the next stable release of Godot Engine. Retro Village Ronan Zeegers Sofox + Taylor Ritenour Zaven Muradyan Alexander Trey Saunders Asher Glick Austen McRae + Bernhard Werner beVR Brian van der Stel Cameron MacNair Carlo Cabanilla Daniel James David Giardi - David Snopek Default Name Edward E Florian Breisch + Gamejunkey Gero - GiulianoB Javier Roman Jay Horton - Jonathan Turner + Joel Höglund Jon Woodward Jose Fernando Alexandre Karl Werf - Kommentgames + Lex Steers Luke Maciej Pendolski Matthew Hillier @@ -107,11 +115,11 @@ generous deed immortalized in the next stable release of Godot Engine. Scott Wadden Sergey Shawn Yu - Svenne Krap thechris Tom Langwaldt tukon - William Wold + Unseen Domains + wmww Alex Khayrullin Branwyn Tylwyth @@ -122,7 +130,6 @@ generous deed immortalized in the next stable release of Godot Engine. Craig Smith Darrian Little Dean Harmon - Ian Richard Kunert Ivan Trombley Joan Fons Joshua Flores @@ -136,10 +143,7 @@ generous deed immortalized in the next stable release of Godot Engine. Rami Reneator Robert Willes - Robin Arys Ronnie Ashlock - ScottMakesGames - Tad C Johnson Thomas Bjarnelöf Vincent Henderson Wojciech Chojnacki @@ -148,12 +152,13 @@ generous deed immortalized in the next stable release of Godot Engine. Adam Neumann Alexander J Maynard Alexey Dyadchenko + Andreas Funke André Frélicot andres eduardo lopez Andrew Bowen - Asdf Ben Botwin Carlos de Sousa Marques + Chase Taranto Chris Petrich Christian Leth Jeppesen Christoph Schröder @@ -162,9 +167,10 @@ generous deed immortalized in the next stable release of Godot Engine. Daniel Eichler David White Eric - Eric Churches Eric Monson + Erik Hatfield Eugenio Hugo Salgüero Jáñez + Fain flesk gavlig GGGames.org @@ -173,14 +179,17 @@ generous deed immortalized in the next stable release of Godot Engine. Hysteria Idzard Kwadijk Jared White + Jeremy Sims + Jerry Ling Joe Flood Jose Malheiro Joshua Lesperance Juan T Chen + Juan Velandia Juraj Móza Kasper Jeppesen kinfox - Klaus The. + Klaus The Klavdij Voncina Maarten Elings Marcelo Dornbusch Lopes @@ -191,7 +200,7 @@ generous deed immortalized in the next stable release of Godot Engine. Matt Eunson Max Bulai Max R.R. Collada - M H + MuffinManKen Nick Nikitin Oliver Dick Patrick Ting @@ -200,7 +209,11 @@ generous deed immortalized in the next stable release of Godot Engine. Pete Goodwin pl Ranoller + Robert Larnach + Robin Arys + Rocknight Studios Romildo Franco + Ryan Samuel Judd Scott Pilet spilldata @@ -213,11 +226,13 @@ generous deed immortalized in the next stable release of Godot Engine. ## Silver donors 1D_Inc - Abby Jones Abraham Haskins + Adam Adam Brunnmeier Adam Carr + Adam Long Adam Nakonieczny + Adam N Webber Adam Smeltzer Adisibio Adrian Demetrescu @@ -228,14 +243,15 @@ generous deed immortalized in the next stable release of Godot Engine. Alder Stefano Alessandro Senese Alexander Koppe + Alexandre Alex Davies-Moore Allen Schade Andreas Evers Andreas Krampitz - Andreas Lundmark Andreas Schüle André Simões Andrés RodrÃguez + Andrew Thomas Andrzej Skalski Anthony Bongiovanni Anthony Staunton @@ -243,13 +259,16 @@ generous deed immortalized in the next stable release of Godot Engine. Antony K. Jones AP Condomines Arda Erol + Arseniy M Arthur S. Muszynski + Atilla Kiran Aubrey Falconer Avencherus B A Balázs Batári Beliar Benedikt + Ben G Ben Phelan Ben Vercammen Bernd Jänichen @@ -257,7 +276,7 @@ generous deed immortalized in the next stable release of Godot Engine. Blair Allen Bobby CC Wong Bryan Stevenson - Caleb Dumitry + Carl van der Geest Carwyn Edwards Chris Brown Chris Chapin @@ -268,21 +287,20 @@ generous deed immortalized in the next stable release of Godot Engine. Clay Heaton Cobaltum Collin Shooltz - Dag Sundin Söderström - Dan H. Bentsen Daniel Johnson DanielMaximiano Daniel Pontillo Daniel Reed - Daniel Tebbutt - David Bullock David Cravens David May David Rapisarda David Woodard Dominic Cooney Dominik Wetzel + Donn Eddy Donovan Hutcheon + Dragontrapper + Dr Ewan Murray Duobix Eduardo Teixeira Edward Herbert @@ -318,6 +336,8 @@ generous deed immortalized in the next stable release of Godot Engine. Iiari IndustrialRobot Isaac Morton + Jack Newley + Jaiden Gerig Jaime Ruiz-Borau Vizárraga Jako Danar James A F Manley @@ -325,37 +345,41 @@ generous deed immortalized in the next stable release of Godot Engine. Jed Jeff Hungerford Jeff Nyte + Jennifer Graves Jeremy Kahn Jesse Dubay + Joao Senerchia Joe Alden Joel Fivat Joel Setterberg - Johannes Eichler Johannes Wuensch + John Selig Jomei Jackson Jonas Rudlang Jonas Yamazaki Jonathan G - Jonathan Nieto Jonathon Jon Bonazza Jon Sully Jose Aleman Jose Andrés Mejias Rojas + Jose C. Rubio Joseph Catrambone Josh 'Cheeseness' Bush Juanfran Juan Negrier - Juan Velandia Judd - Jueast Julian Murgia + JungleRobba Justin Spedding Kaiser Bald0 Kamuna Kauzig KC Chan Keedong Park + Keith Bradner + Kevin Kolcheck + Kevin McPhillips kickmaniac Kiyohiro Kawamura (kyorohiro) Kjetil Haugland @@ -378,12 +402,15 @@ generous deed immortalized in the next stable release of Godot Engine. Marcus Richter Markus Lohaus Markus Michael Egger + Martin Candela Martin Holas Martin LiÅ¡ka - Matt Edwards Matthew Little + Maxime Blade Maxwell medecau + Melissa Mears + M H mhilbrunner Michael Dürwald Michael Gringauz @@ -392,11 +419,10 @@ generous deed immortalized in the next stable release of Godot Engine. Mikael Olsson Mikayla Hutchinson Mike Birkhead - Mike Cunningham Mitchell J. Wagner MoM - MuffinManKen Nathan Fish + Nathan W Natrim nee Neil Blakey-Milner @@ -410,13 +436,12 @@ generous deed immortalized in the next stable release of Godot Engine. Nima Farid Nithin Jino NZ + Oleg Reva Olivier Omar Delarosa - omzee Oscar Norlander Pafka Pan Ip - Pat LaBine Patrick Forringer Patrick Nafarrete Paul Gieske @@ -429,24 +454,25 @@ generous deed immortalized in the next stable release of Godot Engine. Poryg Rafa Laguna Rafal Wyszomirski + rainerLinux Raphael Leroux Remi Rampin Rémi Verschelde Rezgi Ricardo Alcantara + Richard Diss + Richard Ivánek Robert Farr (Larington) Robert Hernandez - Robert Larnach Rodrigo Loli Roger Smith Roland RzÄ…sa Roman Tinkov Ronan Jouchet - Ryan - Ryan Brooks Ryan Groom Ryan Hentz Saad Khoudmi + Sam Edson Samuele Zolfanelli Sanka.X Sasori Olkof @@ -459,20 +485,22 @@ generous deed immortalized in the next stable release of Godot Engine. Simon Ledam Simon Wenner SK + smbe19 Sootstone - Taylor Fahlman + Stonepyre + Svenne Krap + The Architect thomas - Thomas Bell + Thomas Bechtold Thomas Kelly Thomas Kurz tiansheng li Tim Drumheller - Tim Gudex Timothy B. MacDonald + TJRHTK Tobbun Tom Fulp Tom Glenn - Tom Larrow Torsten Crass Travis O'Brien Trent Skinner @@ -487,7 +515,6 @@ generous deed immortalized in the next stable release of Godot Engine. Vigilant Watch Vincent Cloutier waka nya - Walter Byers Wayne Haak werner mendizabal Wiley Thompson diff --git a/core/cowdata.h b/core/cowdata.h index 3e40ad0f4b..c92e20920c 100644 --- a/core/cowdata.h +++ b/core/cowdata.h @@ -33,6 +33,7 @@ #include <string.h> +#include "core/error_macros.h" #include "core/os/memory.h" #include "core/safe_refcount.h" diff --git a/core/io/config_file.cpp b/core/io/config_file.cpp index 271e5c5a0b..5684c82d1c 100644 --- a/core/io/config_file.cpp +++ b/core/io/config_file.cpp @@ -123,6 +123,13 @@ void ConfigFile::erase_section(const String &p_section) { values.erase(p_section); } +void ConfigFile::erase_section_key(const String &p_section, const String &p_key) { + + ERR_FAIL_COND_MSG(!values.has(p_section), "Cannot erase key from nonexistent section '" + p_section + "'."); + + values[p_section].erase(p_key); +} + Error ConfigFile::save(const String &p_path) { Error err; @@ -293,6 +300,7 @@ void ConfigFile::_bind_methods() { ClassDB::bind_method(D_METHOD("get_section_keys", "section"), &ConfigFile::_get_section_keys); ClassDB::bind_method(D_METHOD("erase_section", "section"), &ConfigFile::erase_section); + ClassDB::bind_method(D_METHOD("erase_section_key", "section", "key"), &ConfigFile::erase_section_key); ClassDB::bind_method(D_METHOD("load", "path"), &ConfigFile::load); ClassDB::bind_method(D_METHOD("save", "path"), &ConfigFile::save); diff --git a/core/io/config_file.h b/core/io/config_file.h index 3ab6fef868..d927779f9c 100644 --- a/core/io/config_file.h +++ b/core/io/config_file.h @@ -60,6 +60,7 @@ public: void get_section_keys(const String &p_section, List<String> *r_keys) const; void erase_section(const String &p_section); + void erase_section_key(const String &p_section, const String &p_key); Error save(const String &p_path); Error load(const String &p_path); diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 54ef753b7c..34d3eb5344 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -151,6 +151,7 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) magic = f->get_32(); if (magic != 0x43504447) { + f->close(); memdelete(f); return false; } @@ -162,6 +163,7 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) magic = f->get_32(); if (magic != 0x43504447) { + f->close(); memdelete(f); return false; } @@ -172,8 +174,16 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) uint32_t ver_minor = f->get_32(); f->get_32(); // ver_rev - ERR_FAIL_COND_V_MSG(version != PACK_VERSION, false, "Pack version unsupported: " + itos(version) + "."); - ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + "."); + if (version != PACK_VERSION) { + f->close(); + memdelete(f); + ERR_FAIL_V_MSG(false, "Pack version unsupported: " + itos(version) + "."); + } + if (ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR)) { + f->close(); + memdelete(f); + ERR_FAIL_V_MSG(false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + "."); + } for (int i = 0; i < 16; i++) { //reserved @@ -200,6 +210,8 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files) PackedData::get_singleton()->add_path(p_path, path, ofs, size, md5, this, p_replace_files); }; + f->close(); + memdelete(f); return true; }; diff --git a/core/list.h b/core/list.h index d1b528562d..c46888e01c 100644 --- a/core/list.h +++ b/core/list.h @@ -31,6 +31,7 @@ #ifndef GLOBALS_LIST_H #define GLOBALS_LIST_H +#include "core/error_macros.h" #include "core/os/memory.h" #include "core/sort_array.h" diff --git a/core/map.h b/core/map.h index c87ee42e1b..77e73d70cb 100644 --- a/core/map.h +++ b/core/map.h @@ -31,6 +31,7 @@ #ifndef MAP_H #define MAP_H +#include "core/error_macros.h" #include "core/set.h" // based on the very nice implementation of rb-trees by: diff --git a/core/math/basis.h b/core/math/basis.h index 053effda69..4be4ea4cd3 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -28,13 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -// Circular dependency between Vector3 and Basis :/ -#include "core/math/vector3.h" - #ifndef BASIS_H #define BASIS_H #include "core/math/quat.h" +#include "core/math/vector3.h" class Basis { public: diff --git a/core/math/math_funcs.cpp b/core/math/math_funcs.cpp index f04e40cb6c..50fcdb2c13 100644 --- a/core/math/math_funcs.cpp +++ b/core/math/math_funcs.cpp @@ -30,6 +30,8 @@ #include "math_funcs.h" +#include "core/error_macros.h" + RandomPCG Math::default_rand(RandomPCG::DEFAULT_SEED, RandomPCG::DEFAULT_INC); #define PHI 0x9e3779b9 diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp index 73927821cf..ebc1599820 100644 --- a/core/math/vector3.cpp +++ b/core/math/vector3.cpp @@ -134,6 +134,21 @@ Vector3 Vector3::move_toward(const Vector3 &p_to, const real_t p_delta) const { return len <= p_delta || len < CMP_EPSILON ? p_to : v + vd / len * p_delta; } +Basis Vector3::outer(const Vector3 &p_b) const { + + Vector3 row0(x * p_b.x, x * p_b.y, x * p_b.z); + Vector3 row1(y * p_b.x, y * p_b.y, y * p_b.z); + Vector3 row2(z * p_b.x, z * p_b.y, z * p_b.z); + + return Basis(row0, row1, row2); +} + +Basis Vector3::to_diagonal_matrix() const { + return Basis(x, 0, 0, + 0, y, 0, + 0, 0, z); +} + Vector3::operator String() const { return (rtos(x) + ", " + rtos(y) + ", " + rtos(z)); diff --git a/core/math/vector3.h b/core/math/vector3.h index c68b075613..de1743d88f 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -96,8 +96,8 @@ struct Vector3 { _FORCE_INLINE_ Vector3 cross(const Vector3 &p_b) const; _FORCE_INLINE_ real_t dot(const Vector3 &p_b) const; - _FORCE_INLINE_ Basis outer(const Vector3 &p_b) const; - _FORCE_INLINE_ Basis to_diagonal_matrix() const; + Basis outer(const Vector3 &p_b) const; + Basis to_diagonal_matrix() const; _FORCE_INLINE_ Vector3 abs() const; _FORCE_INLINE_ Vector3 floor() const; @@ -154,9 +154,6 @@ struct Vector3 { _FORCE_INLINE_ Vector3() { x = y = z = 0; } }; -// Should be included after class definition, otherwise we get circular refs -#include "core/math/basis.h" - Vector3 Vector3::cross(const Vector3 &p_b) const { Vector3 ret( @@ -172,21 +169,6 @@ real_t Vector3::dot(const Vector3 &p_b) const { return x * p_b.x + y * p_b.y + z * p_b.z; } -Basis Vector3::outer(const Vector3 &p_b) const { - - Vector3 row0(x * p_b.x, x * p_b.y, x * p_b.z); - Vector3 row1(y * p_b.x, y * p_b.y, y * p_b.z); - Vector3 row2(z * p_b.x, z * p_b.y, z * p_b.z); - - return Basis(row0, row1, row2); -} - -Basis Vector3::to_diagonal_matrix() const { - return Basis(x, 0, 0, - 0, y, 0, - 0, 0, z); -} - Vector3 Vector3::abs() const { return Vector3(Math::abs(x), Math::abs(y), Math::abs(z)); diff --git a/core/os/memory.h b/core/os/memory.h index 8778cb63ad..a68a359546 100644 --- a/core/os/memory.h +++ b/core/os/memory.h @@ -31,6 +31,7 @@ #ifndef MEMORY_H #define MEMORY_H +#include "core/error_macros.h" #include "core/safe_refcount.h" #include <stddef.h> diff --git a/core/self_list.h b/core/self_list.h index 314d440977..07abcd1d53 100644 --- a/core/self_list.h +++ b/core/self_list.h @@ -31,6 +31,7 @@ #ifndef SELF_LIST_H #define SELF_LIST_H +#include "core/error_macros.h" #include "core/typedefs.h" template <class T> diff --git a/core/sort_array.h b/core/sort_array.h index 8660ee3333..d330e0c647 100644 --- a/core/sort_array.h +++ b/core/sort_array.h @@ -31,6 +31,7 @@ #ifndef SORT_ARRAY_H #define SORT_ARRAY_H +#include "core/error_macros.h" #include "core/typedefs.h" #define ERR_BAD_COMPARE(cond) \ diff --git a/core/string_builder.cpp b/core/string_builder.cpp index 35526e0d70..22eed70f8b 100644 --- a/core/string_builder.cpp +++ b/core/string_builder.cpp @@ -34,6 +34,9 @@ StringBuilder &StringBuilder::append(const String &p_string) { + if (p_string == String()) + return *this; + strings.push_back(p_string); appended_strings.push_back(-1); diff --git a/core/typedefs.h b/core/typedefs.h index 660139b90a..767a97ac38 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -108,7 +108,6 @@ T *_nullptr() { #include "core/int_types.h" #include "core/error_list.h" -#include "core/error_macros.h" /** Generic ABS function, for math uses please use Math::abs */ diff --git a/core/ustring.cpp b/core/ustring.cpp index 07caa3a018..f029ae4200 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -3288,9 +3288,7 @@ String String::simplify_path() const { static int _humanize_digits(int p_num) { - if (p_num < 10) - return 2; - else if (p_num < 100) + if (p_num < 100) return 2; else if (p_num < 1024) return 1; @@ -3298,7 +3296,7 @@ static int _humanize_digits(int p_num) { return 0; } -String String::humanize_size(size_t p_size) { +String String::humanize_size(uint64_t p_size) { uint64_t _div = 1; Vector<String> prefixes; diff --git a/core/ustring.h b/core/ustring.h index 87a14bfad7..15e2c07d9f 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -322,7 +322,7 @@ public: String path_to_file(const String &p_path) const; String get_base_dir() const; String get_file() const; - static String humanize_size(size_t p_size); + static String humanize_size(uint64_t p_size); String simplify_path() const; String xml_escape(bool p_escape_quotes = false) const; diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 53f64fcde6..1b5ca9d3e5 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -284,6 +284,7 @@ struct _VariantCall { VCALL_LOCALMEM0R(String, sha1_buffer); VCALL_LOCALMEM0R(String, sha256_buffer); VCALL_LOCALMEM0R(String, empty); + VCALL_LOCALMEM1R(String, humanize_size); VCALL_LOCALMEM0R(String, is_abs_path); VCALL_LOCALMEM0R(String, is_rel_path); VCALL_LOCALMEM0R(String, get_base_dir); @@ -1561,6 +1562,7 @@ void register_variant_methods() { ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, sha1_buffer, varray()); ADDFUNC0R(STRING, POOL_BYTE_ARRAY, String, sha256_buffer, varray()); ADDFUNC0R(STRING, BOOL, String, empty, varray()); + ADDFUNC1R(STRING, STRING, String, humanize_size, INT, "size", varray()); ADDFUNC0R(STRING, BOOL, String, is_abs_path, varray()); ADDFUNC0R(STRING, BOOL, String, is_rel_path, varray()); ADDFUNC0R(STRING, STRING, String, get_base_dir, varray()); diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index aeeebdf00d..6f1627e296 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -51,6 +51,7 @@ <argument index="0" name="margin" type="int" enum="Margin"> </argument> <description> + Returns the specified margin. See also [member drag_margin_bottom], [member drag_margin_top], [member drag_margin_left], and [member drag_margin_right]. </description> </method> <method name="get_limit" qualifiers="const"> @@ -59,6 +60,7 @@ <argument index="0" name="margin" type="int" enum="Margin"> </argument> <description> + Returns the specified camera limit. See also [member limit_bottom], [member limit_top], [member limit_left], and [member limit_right]. </description> </method> <method name="make_current"> @@ -84,6 +86,7 @@ <argument index="1" name="drag_margin" type="float"> </argument> <description> + Sets the specified margin. See also [member drag_margin_bottom], [member drag_margin_top], [member drag_margin_left], and [member drag_margin_right]. </description> </method> <method name="set_limit"> @@ -94,6 +97,7 @@ <argument index="1" name="limit" type="int"> </argument> <description> + Sets the specified camera limit. See also [member limit_bottom], [member limit_top], [member limit_left], and [member limit_right]. </description> </method> </methods> @@ -161,6 +165,7 @@ [b]Note:[/b] Used the same as [member offset_h]. </member> <member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="Camera2D.Camera2DProcessMode" default="1"> + The camera's process callback. See [enum Camera2DProcessMode]. </member> <member name="rotating" type="bool" setter="set_rotating" getter="is_rotating" default="false"> If [code]true[/code], the camera rotates with the target. @@ -183,8 +188,10 @@ The camera's position takes into account vertical/horizontal offsets and the screen size. </constant> <constant name="CAMERA2D_PROCESS_PHYSICS" value="0" enum="Camera2DProcessMode"> + The camera updates with the [code]_physics_process[/code] callback. </constant> <constant name="CAMERA2D_PROCESS_IDLE" value="1" enum="Camera2DProcessMode"> + The camera updates with the [code]_process[/code] callback. </constant> </constants> </class> diff --git a/doc/classes/CameraFeed.xml b/doc/classes/CameraFeed.xml index 6d7757f9f5..f490faf369 100644 --- a/doc/classes/CameraFeed.xml +++ b/doc/classes/CameraFeed.xml @@ -14,28 +14,30 @@ <return type="int"> </return> <description> - Gets the unique ID for this feed. + Returns the unique ID for this feed. </description> </method> <method name="get_name" qualifiers="const"> <return type="String"> </return> <description> - Gets the camera's name. + Returns the camera's name. </description> </method> <method name="get_position" qualifiers="const"> <return type="int" enum="CameraFeed.FeedPosition"> </return> <description> - Position of camera on the device. + Returns the position of camera on the device. </description> </method> </methods> <members> <member name="feed_is_active" type="bool" setter="set_active" getter="is_active" default="false"> + If [code]true[/code], the feed is active. </member> <member name="feed_transform" type="Transform2D" setter="set_transform" getter="get_transform" default="Transform2D( 1, 0, 0, -1, 0, 1 )"> + The transform applied to the camera's image. </member> </members> <constants> diff --git a/doc/classes/ClippedCamera.xml b/doc/classes/ClippedCamera.xml index f6a2a3bc11..9eda79bbae 100644 --- a/doc/classes/ClippedCamera.xml +++ b/doc/classes/ClippedCamera.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ClippedCamera" inherits="Camera" category="Core" version="3.2"> <brief_description> + A [Camera] that includes collision. </brief_description> <description> + This node extends [Camera] to add collisions with [Area] and/or [PhysicsBody] nodes. The camera cannot move through colliding objects. </description> <tutorials> </tutorials> @@ -13,6 +15,7 @@ <argument index="0" name="node" type="Object"> </argument> <description> + Adds a collision exception so the camera does not collide with the specified node. </description> </method> <method name="add_exception_rid"> @@ -21,18 +24,21 @@ <argument index="0" name="rid" type="RID"> </argument> <description> + Adds a collision exception so the camera does not collide with the specified [RID]. </description> </method> <method name="clear_exceptions"> <return type="void"> </return> <description> + Removes all collision exceptions. </description> </method> <method name="get_clip_offset" qualifiers="const"> <return type="float"> </return> <description> + Returns the distance the camera has been offset due to a collision. </description> </method> <method name="get_collision_mask_bit" qualifiers="const"> @@ -41,6 +47,8 @@ <argument index="0" name="bit" type="int"> </argument> <description> + Returns [code]true[/code] if the specified bit index is on. + [b]Note:[/b] Bit indices range from 0-19. </description> </method> <method name="remove_exception"> @@ -49,6 +57,7 @@ <argument index="0" name="node" type="Object"> </argument> <description> + Removes a collision exception with the specified node. </description> </method> <method name="remove_exception_rid"> @@ -57,6 +66,7 @@ <argument index="0" name="rid" type="RID"> </argument> <description> + Removes a collision exception with the specified [RID]. </description> </method> <method name="set_collision_mask_bit"> @@ -67,25 +77,34 @@ <argument index="1" name="value" type="bool"> </argument> <description> + Sets the specified bit index to the [code]value[/code]. + [b]Note:[/b] Bit indices range from 0-19. </description> </method> </methods> <members> <member name="clip_to_areas" type="bool" setter="set_clip_to_areas" getter="is_clip_to_areas_enabled" default="false"> + If [code]true[/code], the camera stops on contact with [Area]s. </member> <member name="clip_to_bodies" type="bool" setter="set_clip_to_bodies" getter="is_clip_to_bodies_enabled" default="true"> + If [code]true[/code], the camera stops on contact with [PhysicsBody]s. </member> <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> + The camera's collision mask. Only objects in at least one collision layer matching the mask will be detected. </member> <member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.0"> + The camera's collision margin. The camera can't get closer than this distance to a colliding object. </member> <member name="process_mode" type="int" setter="set_process_mode" getter="get_process_mode" enum="ClippedCamera.ProcessMode" default="0"> + The camera's process callback. See [enum ProcessMode]. </member> </members> <constants> <constant name="CLIP_PROCESS_PHYSICS" value="0" enum="ProcessMode"> + The camera updates with the [code]_physics_process[/code] callback. </constant> <constant name="CLIP_PROCESS_IDLE" value="1" enum="ProcessMode"> + The camera updates with the [code]_process[/code] callback. </constant> </constants> </class> diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index cd36f4fdf0..07ce76fdb2 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -51,8 +51,10 @@ [b]Note:[/b] Cannot be enabled if raw mode is on. </member> <member name="presets_enabled" type="bool" setter="set_presets_enabled" getter="are_presets_enabled" default="true"> + If [code]true[/code], the "add preset" button is enabled. </member> <member name="presets_visible" type="bool" setter="set_presets_visible" getter="are_presets_visible" default="true"> + If [code]true[/code], saved color presets are visible. </member> <member name="raw_mode" type="bool" setter="set_raw_mode" getter="is_raw_mode" default="false"> If [code]true[/code], allows the color R, G, B component values to go beyond 1.0, which can be used for certain special operations that require it (like tinting without darkening or rendering sprites in HDR). diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml index c91ddccaa4..1a2d5cab81 100644 --- a/doc/classes/HTTPClient.xml +++ b/doc/classes/HTTPClient.xml @@ -69,7 +69,7 @@ [codeblock] { "content-length": 12, - "Content-Type": "application/json; charset=UTF-8", + "Content-Type": "application/json; charset=UTF-8", } [/codeblock] </description> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index afb3977849..6d4adfad51 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -71,6 +71,7 @@ <return type="int" enum="Input.CursorShape"> </return> <description> + Returns the currently assigned cursor shape (see [enum CursorShape]). </description> </method> <method name="get_gravity" qualifiers="const"> diff --git a/doc/classes/Mutex.xml b/doc/classes/Mutex.xml index 793696321b..8a294425e6 100644 --- a/doc/classes/Mutex.xml +++ b/doc/classes/Mutex.xml @@ -7,6 +7,7 @@ A synchronization mutex (mutual exclusion). This is used to synchronize multiple [Thread]s, and is equivalent to a binary [Semaphore]. It guarantees that only one thread can ever acquire the lock at a time. A mutex can be used to protect a critical section; however, be careful to avoid deadlocks. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/tutorials/threads/using_multiple_threads.html</link> </tutorials> <methods> <method name="lock"> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index d9400088dd..50b300d216 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -89,6 +89,36 @@ An [code]id[/code] can optionally be provided, as well as an accelerator ([code]accel[/code]). If no [code]id[/code] is provided, one will be created from the index. If no [code]accel[/code] is provided then the default [code]0[/code] will be assigned to it. See [method get_item_accelerator] for more info on accelerators. </description> </method> + <method name="add_icon_radio_check_item"> + <return type="void"> + </return> + <argument index="0" name="texture" type="Texture"> + </argument> + <argument index="1" name="label" type="String"> + </argument> + <argument index="2" name="id" type="int" default="-1"> + </argument> + <argument index="3" name="accel" type="int" default="0"> + </argument> + <description> + Same as [method add_icon_check_item], but uses a radio check button. + </description> + </method> + <method name="add_icon_radio_check_shortcut"> + <return type="void"> + </return> + <argument index="0" name="texture" type="Texture"> + </argument> + <argument index="1" name="shortcut" type="ShortCut"> + </argument> + <argument index="2" name="id" type="int" default="-1"> + </argument> + <argument index="3" name="global" type="bool" default="false"> + </argument> + <description> + Same as [method add_icon_check_shortcut], but uses a radio check button. + </description> + </method> <method name="add_icon_shortcut"> <return type="void"> </return> @@ -119,6 +149,25 @@ An [code]id[/code] can optionally be provided, as well as an accelerator ([code]accel[/code]). If no [code]id[/code] is provided, one will be created from the index. If no [code]accel[/code] is provided then the default [code]0[/code] will be assigned to it. See [method get_item_accelerator] for more info on accelerators. </description> </method> + <method name="add_multistate_item"> + <return type="void"> + </return> + <argument index="0" name="label" type="String"> + </argument> + <argument index="1" name="max_states" type="int"> + </argument> + <argument index="2" name="default_state" type="int"> + </argument> + <argument index="3" name="id" type="int" default="-1"> + </argument> + <argument index="4" name="accel" type="int" default="0"> + </argument> + <description> + Adds a new multistate item with text [code]label[/code]. + Contrarily to normal binary items, multistate items can have more than two states, as defined by [code]max_states[/code]. Each press or activate of the item will increase the state by one. The default value is defined by [code]default_state[/code]. + An [code]id[/code] can optionally be provided, as well as an accelerator ([code]accel[/code]). If no [code]id[/code] is provided, one will be created from the index. If no [code]accel[/code] is provided then the default [code]0[/code] will be assigned to it. See [method get_item_accelerator] for more info on accelerators. + </description> + </method> <method name="add_radio_check_item"> <return type="void"> </return> @@ -129,7 +178,7 @@ <argument index="2" name="accel" type="int" default="0"> </argument> <description> - Adds a new radio button with text [code]label[/code]. + Adds a new radio check button with text [code]label[/code]. An [code]id[/code] can optionally be provided, as well as an accelerator ([code]accel[/code]). If no [code]id[/code] is provided, one will be created from the index. If no [code]accel[/code] is provided then the default [code]0[/code] will be assigned to it. See [method get_item_accelerator] for more info on accelerators. [b]Note:[/b] Checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method set_item_checked] for more info on how to control it. </description> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 7d009252c0..9657982016 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -403,19 +403,19 @@ Sets the window to full screen when it starts. </member> <member name="display/window/size/height" type="int" setter="" getter="" default="600"> - Sets the main window height. On desktop, this is the default window size. Stretch mode settings use this also as a reference when enabled. + Sets the game's main viewport height. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled. </member> <member name="display/window/size/resizable" type="bool" setter="" getter="" default="true"> Allows the window to be resizable by default. </member> <member name="display/window/size/test_height" type="int" setter="" getter="" default="0"> - If greater than zero, uses a different height for the window when running from the editor. The main use for this is to test with stretch modes. + If greater than zero, overrides the window height when running the game. Useful for testing stretch modes. </member> <member name="display/window/size/test_width" type="int" setter="" getter="" default="0"> - If greater than zero, uses a different width for the window when running from the editor. The main use for this is to test with stretch modes. + If greater than zero, overrides the window width when running the game. Useful for testing stretch modes. </member> <member name="display/window/size/width" type="int" setter="" getter="" default="1024"> - Sets the main window width. On desktop platforms, this is the default window size. Stretch mode settings use this also as a reference when enabled. + Sets the game's main viewport width. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled. </member> <member name="display/window/vsync/use_vsync" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables vertical synchronization. This eliminates tearing that may appear in moving scenes, at the cost of higher input latency and stuttering at lower framerates. If [code]false[/code], vertical synchronization will be disabled, however, many platforms will enforce it regardless (such as mobile platforms and HTML5). diff --git a/doc/classes/RigidBody.xml b/doc/classes/RigidBody.xml index 4378fc3ffe..624b576f4d 100644 --- a/doc/classes/RigidBody.xml +++ b/doc/classes/RigidBody.xml @@ -88,6 +88,7 @@ <argument index="0" name="axis" type="int" enum="PhysicsServer.BodyAxis"> </argument> <description> + Returns [code]true[/code] if the specified linear or rotational axis is locked. </description> </method> <method name="get_colliding_bodies" qualifiers="const"> @@ -106,6 +107,7 @@ <argument index="1" name="lock" type="bool"> </argument> <description> + Locks the specified linear or rotational axis. </description> </method> <method name="set_axis_velocity"> @@ -183,6 +185,8 @@ The body mode. See [enum Mode] for possible values. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> + The physics material override for the body. + If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> <member name="sleeping" type="bool" setter="set_sleeping" getter="is_sleeping" default="false"> If [code]true[/code], the body is sleeping and will not calculate forces until woken up by a collision or the [code]apply_impulse[/code] method. diff --git a/doc/classes/RigidBody2D.xml b/doc/classes/RigidBody2D.xml index 32a1634f77..c960ef5aee 100644 --- a/doc/classes/RigidBody2D.xml +++ b/doc/classes/RigidBody2D.xml @@ -169,6 +169,8 @@ The body's mode. See [enum Mode] for possible values. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> + The physics material override for the body. + If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> <member name="sleeping" type="bool" setter="set_sleeping" getter="is_sleeping" default="false"> If [code]true[/code], the body is sleeping and will not calculate forces until woken up by a collision or by using [method apply_impulse] or [method add_force]. diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index ed43f83f05..bd81a48ff5 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -69,9 +69,9 @@ Commonly used to create a one-shot delay timer as in the following example: [codeblock] func some_function(): - print("start") - yield(get_tree().create_timer(1.0), "timeout") - print("end") + print("start") + yield(get_tree().create_timer(1.0), "timeout") + print("end") [/codeblock] </description> </method> diff --git a/doc/classes/Semaphore.xml b/doc/classes/Semaphore.xml index 7aee5c2951..74970be8b4 100644 --- a/doc/classes/Semaphore.xml +++ b/doc/classes/Semaphore.xml @@ -7,6 +7,7 @@ A synchronization semaphore which can be used to synchronize multiple [Thread]s. Initialized to zero on creation. Be careful to avoid deadlocks. For a binary version, see [Mutex]. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/tutorials/threads/using_multiple_threads.html</link> </tutorials> <methods> <method name="post"> diff --git a/doc/classes/StaticBody.xml b/doc/classes/StaticBody.xml index a9709d00df..f8840ddc14 100644 --- a/doc/classes/StaticBody.xml +++ b/doc/classes/StaticBody.xml @@ -28,6 +28,8 @@ Deprecated, use [member PhysicsMaterial.friction] instead via [member physics_material_override]. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> + The physics material override for the body. + If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> </members> <constants> diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml index 4a7f71b667..34276ec535 100644 --- a/doc/classes/StaticBody2D.xml +++ b/doc/classes/StaticBody2D.xml @@ -27,6 +27,8 @@ Deprecated, use [member PhysicsMaterial.friction] instead via [member physics_material_override]. </member> <member name="physics_material_override" type="PhysicsMaterial" setter="set_physics_material_override" getter="get_physics_material_override"> + The physics material override for the body. + If a material is assigned to this property, it will be used instead of any other physics material, such as an inherited one. </member> </members> <constants> diff --git a/doc/classes/StreamPeer.xml b/doc/classes/StreamPeer.xml index 678c587f8d..2a1919071a 100644 --- a/doc/classes/StreamPeer.xml +++ b/doc/classes/StreamPeer.xml @@ -211,6 +211,10 @@ </argument> <description> Puts a zero-terminated ASCII string into the stream prepended by a 32-bit unsigned integer representing its size. + Note: To put an ASCII string without prepending its size, you can use [method put_data]: + [codeblock] + put_data("Hello world".to_ascii()) + [/codeblock] </description> </method> <method name="put_u16"> @@ -256,6 +260,10 @@ </argument> <description> Puts a zero-terminated UTF-8 string into the stream prepended by a 32 bits unsigned integer representing its size. + Note: To put an UTF-8 string without prepending its size, you can use [method put_data]: + [codeblock] + put_data("Hello world".to_utf8()) + [/codeblock] </description> </method> <method name="put_var"> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 03bc2095c0..cf152e716e 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -437,6 +437,20 @@ [/codeblock] </description> </method> + <method name="humanize_size"> + <return type="String"> + </return> + <argument index="0" name="size" type="int"> + </argument> + <description> + Converts [code]size[/code] represented as number of bytes to human-readable format using internationalized set of data size units, namely: B, KiB, MiB, GiB, TiB, PiB, EiB. Note that the next smallest unit is picked automatically to hold at most 1024 units. + [codeblock] + var bytes = 133790307 + var size = String.humanize_size(bytes) + print(size) # prints "127.5 MiB" + [/codeblock] + </description> + </method> <method name="insert"> <return type="String"> </return> diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml index 8f96ab0aed..25e40d4c1f 100644 --- a/doc/classes/Thread.xml +++ b/doc/classes/Thread.xml @@ -7,6 +7,7 @@ A unit of execution in a process. Can run methods on [Object]s simultaneously. The use of synchronization via [Mutex] or [Semaphore] is advised if working with shared objects. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/tutorials/threads/using_multiple_threads.html</link> </tutorials> <methods> <method name="get_id" qualifiers="const"> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 1ab5c58a30..a4c976591f 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -120,6 +120,15 @@ Returns the custom background color of column [code]column[/code]. </description> </method> + <method name="get_custom_color" qualifiers="const"> + <return type="Color"> + </return> + <argument index="0" name="column" type="int"> + </argument> + <description> + Returns the custom color of column [code]column[/code]. + </description> + </method> <method name="get_expand_right" qualifiers="const"> <return type="bool"> </return> @@ -585,6 +594,15 @@ Sets the given column's tooltip text. </description> </method> + <method name="call_recursive" qualifiers="vararg"> + <return type="Variant"> + </return> + <argument index="0" name="method" type="String"> + </argument> + <description> + Calls the [code]method[/code] on the actual TreeItem and its children recursively. Pass parameters as a comma separated list. + </description> + </method> </methods> <members> <member name="collapsed" type="bool" setter="set_collapsed" getter="is_collapsed"> diff --git a/doc/classes/VideoPlayer.xml b/doc/classes/VideoPlayer.xml index 18a85d496f..2215f26c23 100644 --- a/doc/classes/VideoPlayer.xml +++ b/doc/classes/VideoPlayer.xml @@ -65,6 +65,7 @@ If [code]true[/code], the video is paused. </member> <member name="stream" type="VideoStream" setter="set_stream" getter="get_stream"> + The assigned video stream. See description for supported formats. </member> <member name="stream_position" type="float" setter="set_stream_position" getter="get_stream_position"> The current position of the stream, in seconds. diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py index 8eddc35352..1b27e4a35a 100755 --- a/doc/tools/makerst.py +++ b/doc/tools/makerst.py @@ -608,8 +608,10 @@ def rstize_text(text, state): # type: (str, State) -> str break pre_text = text[:pos] + indent_level = 0 while text[pos + 1] == '\t': pos += 1 + indent_level += 1 post_text = text[pos + 1:] # Handle codeblocks @@ -633,6 +635,9 @@ def rstize_text(text, state): # type: (str, State) -> str while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == '\t': to_skip += 1 + if to_skip > indent_level: + print_error("Four spaces should be used for indentation within [codeblock], file: {}".format(state.current_class), state) + if len(code_text[code_pos + to_skip + 1:]) == 0: code_text = code_text[:code_pos] + "\n" code_pos += 1 diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index 9530ed08c9..088ce3d198 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -2905,16 +2905,10 @@ void RasterizerSceneGLES2::_post_process(Environment *env, const CameraMatrix &p glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->depth); glActiveTexture(GL_TEXTURE0); - if (!storage->frame.current_rt->used_dof_blur_near) { - if (storage->frame.current_rt->mip_maps[0].color) { - glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].color); - } else { - for (int i = 0; i < storage->frame.current_rt->mip_maps[i].sizes.size(); i++) { - glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[i].color); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, storage->frame.current_rt->mip_maps[0].sizes[i].width, storage->frame.current_rt->mip_maps[0].sizes[i].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - } - glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[0].color); - } + if (storage->frame.current_rt->mip_maps[0].color) { + glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].color); + } else { + glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->mip_maps[0].sizes[0].color); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -3303,6 +3297,12 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const reflection_probe_count = 0; } + if (env && env->bg_mode == VS::ENV_BG_CANVAS) { + // If using canvas background, copy 2d to screen copy texture + // TODO: When GLES2 renders to current_rt->mip_maps[], this copy will no longer be needed + _copy_texture_to_buffer(storage->frame.current_rt->color, storage->frame.current_rt->copy_screen_effect.fbo); + } + // render list stuff render_list.clear(); @@ -3439,8 +3439,11 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const clear_color = Color(0.0, 1.0, 0.0, 1.0); } } break; + case VS::ENV_BG_CANVAS: { + // use screen copy as background + _copy_texture_to_buffer(storage->frame.current_rt->copy_screen_effect.color, current_fb); + } break; default: { - // FIXME: implement other background modes } break; } } @@ -3509,6 +3512,11 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const _render_render_list(&render_list.elements[render_list.max_elements - render_list.alpha_element_count], render_list.alpha_element_count, cam_transform, p_cam_projection, p_shadow_atlas, env, env_radiance_tex, 0.0, 0.0, reverse_cull, true, false); + if (p_reflection_probe.is_valid()) { + // Rendering to a probe so no need for post_processing + return; + } + //post process _post_process(env, p_cam_projection); diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 1584295b4c..451d6adaa9 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -1482,8 +1482,9 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const { } Error err = shaders.compiler.compile(p_shader->mode, p_shader->code, actions, p_shader->path, gen_code); - - ERR_FAIL_COND(err != OK); + if (err != OK) { + return; + } p_shader->shader->set_custom_shader_code(p_shader->custom_code_id, gen_code.vertex, gen_code.vertex_global, gen_code.fragment, gen_code.light, gen_code.fragment_global, gen_code.uniforms, gen_code.texture_uniforms, gen_code.custom_defines); @@ -4886,7 +4887,6 @@ void RasterizerStorageGLES2::_render_target_allocate(RenderTarget *rt) { } glClearColor(1.0, 0.0, 1.0, 0.0); - glViewport(0, 0, rt->mip_maps[i].sizes[j].width, rt->mip_maps[i].sizes[j].height); glClear(GL_COLOR_BUFFER_BIT); if (used_depth) { glClearDepth(1.0); @@ -5757,7 +5757,8 @@ void RasterizerStorageGLES2::initialize() { config.multisample_supported = config.extensions.has("GL_EXT_framebuffer_multisample") || config.extensions.has("GL_EXT_multisampled_render_to_texture") || config.extensions.has("GL_APPLE_framebuffer_multisample"); #ifdef GLES_OVER_GL - config.render_to_mipmap_supported = true; + //TODO: causes huge problems with desktop video drivers. Making false for now, needs to be true to render SCREEN_TEXTURE mipmaps + config.render_to_mipmap_supported = false; #else //check if mipmaps can be used for SCREEN_TEXTURE and Glow on Mobile and web platforms config.render_to_mipmap_supported = config.extensions.has("GL_OES_fbo_render_mipmap") && config.extensions.has("GL_EXT_texture_lod"); diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index a29831e3f5..95be67a5b7 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -2168,8 +2168,9 @@ void RasterizerStorageGLES3::_update_shader(Shader *p_shader) const { } Error err = shaders.compiler.compile(p_shader->mode, p_shader->code, actions, p_shader->path, gen_code); - - ERR_FAIL_COND(err != OK); + if (err != OK) { + return; + } p_shader->shader->set_custom_shader_code(p_shader->custom_code_id, gen_code.vertex, gen_code.vertex_global, gen_code.fragment, gen_code.light, gen_code.fragment_global, gen_code.uniforms, gen_code.texture_uniforms, gen_code.defines); diff --git a/drivers/unix/ip_unix.cpp b/drivers/unix/ip_unix.cpp index ac7195abc1..cf47cdc7e8 100644 --- a/drivers/unix/ip_unix.cpp +++ b/drivers/unix/ip_unix.cpp @@ -152,7 +152,7 @@ void IP_Unix::get_local_interfaces(Map<String, Interface_Info> *r_interfaces) co Interface_Info info; info.name = name; info.name_friendly = hostname->DisplayName->Data(); - info.index = 0; + info.index = String::num_uint64(0); E = r_interfaces->insert(name, info); ERR_CONTINUE(!E); } diff --git a/drivers/unix/net_socket_posix.cpp b/drivers/unix/net_socket_posix.cpp index 6a57a2e562..da46b393c6 100644 --- a/drivers/unix/net_socket_posix.cpp +++ b/drivers/unix/net_socket_posix.cpp @@ -30,6 +30,7 @@ #include "net_socket_posix.h" +#ifndef UNIX_SOCKET_UNAVAILABLE #if defined(UNIX_ENABLED) #include <errno.h> @@ -280,6 +281,21 @@ void NetSocketPosix::_set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_ _sock = p_sock; _ip_type = p_ip_type; _is_stream = p_is_stream; + // Disable descriptor sharing with subprocesses. + _set_close_exec_enabled(true); +} + +void NetSocketPosix::_set_close_exec_enabled(bool p_enabled) { +#ifndef WINDOWS_ENABLED + // Enable close on exec to avoid sharing with subprocesses. Off by default on Windows. +#if defined(NO_FCNTL) + unsigned long par = p_enabled ? 1 : 0; + SOCK_IOCTL(_sock, FIOCLEX, &par); +#else + int opts = fcntl(_sock, F_GETFD); + fcntl(_sock, F_SETFD, opts | FD_CLOEXEC); +#endif +#endif } Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) { @@ -320,6 +336,9 @@ Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) { _is_stream = p_sock_type == TYPE_TCP; + // Disable descriptor sharing with subprocesses. + _set_close_exec_enabled(true); + #if defined(WINDOWS_ENABLED) if (!_is_stream) { // Disable windows feature/bug reporting WSAECONNRESET/WSAENETRESET when @@ -691,3 +710,4 @@ Error NetSocketPosix::join_multicast_group(const IP_Address &p_multi_address, St Error NetSocketPosix::leave_multicast_group(const IP_Address &p_multi_address, String p_if_name) { return _change_multicast_group(p_multi_address, p_if_name, false); } +#endif diff --git a/drivers/unix/net_socket_posix.h b/drivers/unix/net_socket_posix.h index 40406b241a..e549ea1d6a 100644 --- a/drivers/unix/net_socket_posix.h +++ b/drivers/unix/net_socket_posix.h @@ -61,6 +61,7 @@ private: NetError _get_socket_error(); void _set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_is_stream); _FORCE_INLINE_ Error _change_multicast_group(IP_Address p_ip, String p_if_name, bool p_add); + _FORCE_INLINE_ void _set_close_exec_enabled(bool p_enabled); protected: static NetSocket *_create_func(); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index b2567249d8..8a03292708 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -141,7 +141,7 @@ EditorAbout::EditorAbout() { hbc->add_child(about_text); TabContainer *tc = memnew(TabContainer); - tc->set_custom_minimum_size(Size2(630, 240) * EDSCALE); + tc->set_custom_minimum_size(Size2(950, 400) * EDSCALE); tc->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbc->add_child(tc); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 5ea7081667..3663bdee27 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1411,9 +1411,6 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St } void EditorFileSystem::_scan_script_classes(EditorFileSystemDirectory *p_dir) { - if (p_dir->parent && p_dir->parent->name == "addons" && !EditorNode::get_singleton()->is_addon_plugin_enabled(p_dir->name)) { - return; - } int filecount = p_dir->files.size(); const EditorFileSystemDirectory::FileInfo *const *files = p_dir->files.ptr(); for (int i = 0; i < filecount; i++) { diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index 55cae35a4a..b6d27d84e0 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -232,6 +232,7 @@ void editor_register_fonts(Ref<Theme> p_theme) { // Default font MAKE_DEFAULT_FONT(df, default_font_size); p_theme->set_default_theme_font(df); + p_theme->set_font("main", "EditorFonts", df); // Bold font MAKE_BOLD_FONT(df_bold, default_font_size); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 2b58d105de..83434a6d9f 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1107,37 +1107,47 @@ void EditorHelp::_update_doc() { class_desc->add_newline(); class_desc->add_newline(); - for (int i = 0; i < methods.size(); i++) { + for (int pass = 0; pass < 2; pass++) { + Vector<DocData::MethodDoc> methods_filtered; - class_desc->push_font(doc_code_font); - _add_method(methods[i], false); - class_desc->pop(); + for (int i = 0; i < methods.size(); i++) { + const String &q = methods[i].qualifiers; + if ((pass == 0 && q.find("virtual") != -1) || (pass == 1 && q.find("virtual") == -1)) { + methods_filtered.push_back(methods[i]); + } + } - class_desc->add_newline(); - class_desc->add_newline(); + for (int i = 0; i < methods_filtered.size(); i++) { - class_desc->push_color(text_color); - class_desc->push_font(doc_font); - class_desc->push_indent(1); - if (methods[i].description.strip_edges() != String()) { - _add_text(methods[i].description); - } else { - class_desc->add_image(get_icon("Error", "EditorIcons")); - class_desc->add_text(" "); - class_desc->push_color(comment_color); - class_desc->append_bbcode(TTR("There is currently no description for this method. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + class_desc->push_font(doc_code_font); + _add_method(methods_filtered[i], false); class_desc->pop(); - } - class_desc->pop(); - class_desc->pop(); - class_desc->pop(); - class_desc->add_newline(); - class_desc->add_newline(); - class_desc->add_newline(); + class_desc->add_newline(); + class_desc->add_newline(); + + class_desc->push_color(text_color); + class_desc->push_font(doc_font); + class_desc->push_indent(1); + if (methods_filtered[i].description.strip_edges() != String()) { + _add_text(methods_filtered[i].description); + } else { + class_desc->add_image(get_icon("Error", "EditorIcons")); + class_desc->add_text(" "); + class_desc->push_color(comment_color); + class_desc->append_bbcode(TTR("There is currently no description for this method. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + class_desc->pop(); + } + + class_desc->pop(); + class_desc->pop(); + class_desc->pop(); + class_desc->add_newline(); + class_desc->add_newline(); + class_desc->add_newline(); + } } } - scroll_locked = false; } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 690b7a8ec4..460f75eef0 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2649,6 +2649,7 @@ void EditorPropertyResource::update_property() { if (res == RES()) { assign->set_icon(Ref<Texture>()); assign->set_text(TTR("[empty]")); + assign->set_tooltip(""); } else { assign->set_icon(EditorNode::get_singleton()->get_object_icon(res.operator->(), "Node")); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 60ef88d2e3..80353bab01 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -484,6 +484,8 @@ double AnimationPlayerEditor::_get_editor_step() const { if (track_editor->is_snap_enabled()) { const String current = player->get_assigned_animation(); const Ref<Animation> anim = player->get_animation(current); + ERR_FAIL_COND_V(!anim.is_valid(), 0.0); + // Use more precise snapping when holding Shift return Input::get_singleton()->is_key_pressed(KEY_SHIFT) ? anim->get_step() * 0.25 : anim->get_step(); } @@ -1077,12 +1079,16 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) if (!is_visible_in_tree()) return; + if (!player) return; if (player->is_playing()) return; + if (!player->has_animation(player->get_assigned_animation())) + return; + Ref<Animation> anim = player->get_animation(player->get_assigned_animation()); updating = true; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index b1e8ce20d6..0d388512f4 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -72,7 +72,7 @@ class SnapDialog : public ConfirmationDialog { public: SnapDialog() { - const int SPIN_BOX_GRID_RANGE = 256; + const int SPIN_BOX_GRID_RANGE = 16384; const int SPIN_BOX_ROTATION_RANGE = 360; Label *label; VBoxContainer *container; @@ -96,6 +96,8 @@ public: grid_offset_x = memnew(SpinBox); grid_offset_x->set_min(-SPIN_BOX_GRID_RANGE); grid_offset_x->set_max(SPIN_BOX_GRID_RANGE); + grid_offset_x->set_allow_lesser(true); + grid_offset_x->set_allow_greater(true); grid_offset_x->set_suffix("px"); grid_offset_x->set_h_size_flags(SIZE_EXPAND_FILL); child_container->add_child(grid_offset_x); @@ -103,6 +105,8 @@ public: grid_offset_y = memnew(SpinBox); grid_offset_y->set_min(-SPIN_BOX_GRID_RANGE); grid_offset_y->set_max(SPIN_BOX_GRID_RANGE); + grid_offset_y->set_allow_lesser(true); + grid_offset_y->set_allow_greater(true); grid_offset_y->set_suffix("px"); grid_offset_y->set_h_size_flags(SIZE_EXPAND_FILL); child_container->add_child(grid_offset_y); @@ -115,6 +119,7 @@ public: grid_step_x = memnew(SpinBox); grid_step_x->set_min(0.01); grid_step_x->set_max(SPIN_BOX_GRID_RANGE); + grid_step_x->set_allow_greater(true); grid_step_x->set_suffix("px"); grid_step_x->set_h_size_flags(SIZE_EXPAND_FILL); child_container->add_child(grid_step_x); @@ -122,6 +127,7 @@ public: grid_step_y = memnew(SpinBox); grid_step_y->set_min(0.01); grid_step_y->set_max(SPIN_BOX_GRID_RANGE); + grid_step_y->set_allow_greater(true); grid_step_y->set_suffix("px"); grid_step_y->set_h_size_flags(SIZE_EXPAND_FILL); child_container->add_child(grid_step_y); @@ -2708,6 +2714,7 @@ void CanvasItemEditor::_draw_ruler_tool() { font_secondary_color.a = 0.5; float text_height = font->get_height(); const float text_width = 76; + const float angle_text_width = 54; Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2); text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5); @@ -2715,14 +2722,38 @@ void CanvasItemEditor::_draw_ruler_tool() { viewport->draw_string(font, text_pos, vformat("%.2f px", length_vector.length()), font_color); if (draw_secondary_lines) { + int horizontal_axis_angle = round(180 * atan2(length_vector.y, length_vector.x) / Math_PI); + int vertictal_axis_angle = 90 - horizontal_axis_angle; Point2 text_pos2 = text_pos; text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2); viewport->draw_string(font, text_pos2, vformat("%.2f px", length_vector.y), font_secondary_color); + Point2 v_angle_text_pos = Point2(); + v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); + v_angle_text_pos.y = begin.y < end.y ? MIN(text_pos2.y - 2 * text_height, begin.y - text_height * 0.5) : MAX(text_pos2.y + text_height * 3, begin.y + text_height * 1.5); + viewport->draw_string(font, v_angle_text_pos, vformat("%d deg", vertictal_axis_angle), font_secondary_color); + text_pos2 = text_pos; text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2); viewport->draw_string(font, text_pos2, vformat("%.2f px", length_vector.x), font_secondary_color); + + Point2 h_angle_text_pos = Point2(); + h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); + if (begin.y < end.y) { + h_angle_text_pos.y = end.y + text_height * 1.5; + if (ABS(text_pos2.x - h_angle_text_pos.x) < text_width) { + int height_multiplier = 1.5 + (int)is_snap_active; + h_angle_text_pos.y = MAX(text_pos.y + height_multiplier * text_height, MAX(end.y + text_height * 1.5, text_pos2.y + height_multiplier * text_height)); + } + } else { + h_angle_text_pos.y = end.y - text_height * 0.5; + if (ABS(text_pos2.x - h_angle_text_pos.x) < text_width) { + int height_multiplier = 1 + (int)is_snap_active; + h_angle_text_pos.y = MIN(text_pos.y - height_multiplier * text_height, MIN(end.y - text_height * 0.5, text_pos2.y - height_multiplier * text_height)); + } + } + viewport->draw_string(font, h_angle_text_pos, vformat("%d deg", horizontal_axis_angle), font_secondary_color); } if (is_snap_active) { @@ -5135,6 +5166,8 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { warning_child_of_container = memnew(Label); warning_child_of_container->hide(); warning_child_of_container->set_text(TTR("Warning: Children of a container get their position and size determined only by their parent.")); + warning_child_of_container->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("warning_color", "Editor")); + warning_child_of_container->add_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_font("main", "EditorFonts")); add_control_to_info_overlay(warning_child_of_container); h_scroll = memnew(HScrollBar); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 80f63b4f48..97f194e40f 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -198,9 +198,7 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptCo ShaderLanguage sl; String calltip; - Error err = sl.complete(p_code, ShaderTypes::get_singleton()->get_functions(VisualServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_types(), r_options, calltip); - if (err != OK) - ERR_PRINT("Shaderlang complete failed"); + sl.complete(p_code, ShaderTypes::get_singleton()->get_functions(VisualServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(VisualServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_types(), r_options, calltip); get_text_edit()->set_code_hint(calltip); } diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 127cead57f..095350d0e1 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -2172,7 +2172,7 @@ void SpatialEditorViewport::_notification(int p_what) { VisualInstance *vi = Object::cast_to<VisualInstance>(sp); - se->aabb = vi ? vi->get_aabb() : AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); + se->aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); Transform t = sp->get_global_gizmo_transform(); t.translate(se->aabb.position); @@ -3209,20 +3209,35 @@ Vector3 SpatialEditorViewport::_get_instance_position(const Point2 &p_pos) const return point + offset; } -AABB SpatialEditorViewport::_calculate_spatial_bounds(const Spatial *p_parent, const AABB &p_bounds) { - AABB bounds = p_bounds; +AABB SpatialEditorViewport::_calculate_spatial_bounds(const Spatial *p_parent, bool p_exclude_toplevel_transform) { + AABB bounds; + + const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(p_parent); + if (mesh_instance) { + bounds = mesh_instance->get_aabb(); + } + for (int i = 0; i < p_parent->get_child_count(); i++) { Spatial *child = Object::cast_to<Spatial>(p_parent->get_child(i)); if (child) { - MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(child); - if (mesh_instance) { - AABB mesh_instance_bounds = mesh_instance->get_aabb(); - mesh_instance_bounds.position += mesh_instance->get_global_gizmo_transform().origin - p_parent->get_global_gizmo_transform().origin; - bounds.merge_with(mesh_instance_bounds); + AABB child_bounds = _calculate_spatial_bounds(child, false); + + if (bounds.size == Vector3() && p_parent->get_class_name() == StringName("Spatial")) { + bounds = child_bounds; + } else { + bounds.merge_with(child_bounds); } - bounds = _calculate_spatial_bounds(child, bounds); } } + + if (bounds.size == Vector3() && p_parent->get_class_name() != StringName("Spatial")) { + bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); + } + + if (!p_exclude_toplevel_transform) { + bounds = p_parent->get_transform().xform(bounds); + } + return bounds; } @@ -3249,7 +3264,7 @@ void SpatialEditorViewport::_create_preview(const Vector<String> &files) const { editor->get_scene_root()->add_child(preview_node); } } - *preview_bounds = _calculate_spatial_bounds(preview_node, AABB()); + *preview_bounds = _calculate_spatial_bounds(preview_node); } void SpatialEditorViewport::_remove_preview() { diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index 208495c2f5..fe91c33642 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -375,7 +375,7 @@ private: Point2i _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const; Vector3 _get_instance_position(const Point2 &p_pos) const; - static AABB _calculate_spatial_bounds(const Spatial *p_parent, const AABB &p_bounds); + static AABB _calculate_spatial_bounds(const Spatial *p_parent, bool p_exclude_toplevel_transform = true); void _create_preview(const Vector<String> &files) const; void _remove_preview(); bool _cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node); diff --git a/editor/plugins/sprite_editor_plugin.cpp b/editor/plugins/sprite_editor_plugin.cpp index 69fd592652..40734cffc4 100644 --- a/editor/plugins/sprite_editor_plugin.cpp +++ b/editor/plugins/sprite_editor_plugin.cpp @@ -178,6 +178,7 @@ void SpriteEditor::_update_mesh_data() { err_dialog->popup_centered_minsize(); return; } + Ref<Image> image = texture->get_data(); ERR_FAIL_COND(image.is_null()); Rect2 rect; @@ -190,7 +191,12 @@ void SpriteEditor::_update_mesh_data() { bm.instance(); bm->create_from_image_alpha(image); - int grow = island_merging->get_value(); + int shrink = shrink_pixels->get_value(); + if (shrink > 0) { + bm->shrink_mask(shrink, rect); + } + + int grow = grow_pixels->get_value(); if (grow > 0) { bm->grow_mask(grow, rect); } @@ -338,6 +344,13 @@ void SpriteEditor::_convert_to_mesh_2d_node() { } void SpriteEditor::_convert_to_polygon_2d_node() { + + if (computed_outline_lines.empty()) { + err_dialog->set_text(TTR("Invalid geometry, can't create polygon.")); + err_dialog->popup_centered_minsize(); + return; + } + Polygon2D *polygon_2d_instance = memnew(Polygon2D); int total_point_count = 0; @@ -362,12 +375,6 @@ void SpriteEditor::_convert_to_polygon_2d_node() { Vector<Vector2> outline = computed_outline_lines[i]; Vector<Vector2> uv_outline = outline_lines[i]; - if (outline.size() < 3) { - err_dialog->set_text(TTR("Invalid geometry, can't create polygon.")); - err_dialog->popup_centered_minsize(); - return; - } - PoolIntArray pia; pia.resize(outline.size()); PoolIntArray::Write pia_write = pia.write(); @@ -396,16 +403,17 @@ void SpriteEditor::_convert_to_polygon_2d_node() { } void SpriteEditor::_create_collision_polygon_2d_node() { + + if (computed_outline_lines.empty()) { + err_dialog->set_text(TTR("Invalid geometry, can't create collision polygon.")); + err_dialog->popup_centered_minsize(); + return; + } + for (int i = 0; i < computed_outline_lines.size(); i++) { Vector<Vector2> outline = computed_outline_lines[i]; - if (outline.size() < 3) { - err_dialog->set_text(TTR("Invalid geometry, can't create collision polygon.")); - err_dialog->popup_centered_minsize(); - continue; - } - CollisionPolygon2D *collision_polygon_2d_instance = memnew(CollisionPolygon2D); collision_polygon_2d_instance->set_polygon(outline); @@ -419,16 +427,17 @@ void SpriteEditor::_create_collision_polygon_2d_node() { } void SpriteEditor::_create_light_occluder_2d_node() { + + if (computed_outline_lines.empty()) { + err_dialog->set_text(TTR("Invalid geometry, can't create light occluder.")); + err_dialog->popup_centered_minsize(); + return; + } + for (int i = 0; i < computed_outline_lines.size(); i++) { Vector<Vector2> outline = computed_outline_lines[i]; - if (outline.size() < 3) { - err_dialog->set_text(TTR("Invalid geometry, can't create light occluder.")); - err_dialog->popup_centered_minsize(); - continue; - } - Ref<OccluderPolygon2D> polygon; polygon.instance(); @@ -531,10 +540,14 @@ void SpriteEditor::_debug_uv_draw() { Ref<Texture> tex = node->get_texture(); ERR_FAIL_COND(!tex.is_valid()); + + Point2 draw_pos_offset = Point2(1.0, 1.0); + Size2 draw_size_offset = Size2(2.0, 2.0); + debug_uv->set_clip_contents(true); - debug_uv->draw_texture(tex, Point2()); - debug_uv->set_custom_minimum_size(tex->get_size()); - //debug_uv->draw_set_transform(Vector2(), 0, debug_uv->get_size()); + debug_uv->draw_texture(tex, draw_pos_offset); + debug_uv->set_custom_minimum_size(tex->get_size() + draw_size_offset); + debug_uv->draw_set_transform(draw_pos_offset, 0, Size2(1.0, 1.0)); Color color = Color(1.0, 0.8, 0.7); @@ -604,13 +617,21 @@ SpriteEditor::SpriteEditor() { simplification->set_value(2); hb->add_child(simplification); hb->add_spacer(); + hb->add_child(memnew(Label(TTR("Shrink (Pixels): ")))); + shrink_pixels = memnew(SpinBox); + shrink_pixels->set_min(0); + shrink_pixels->set_max(10); + shrink_pixels->set_step(1); + shrink_pixels->set_value(0); + hb->add_child(shrink_pixels); + hb->add_spacer(); hb->add_child(memnew(Label(TTR("Grow (Pixels): ")))); - island_merging = memnew(SpinBox); - island_merging->set_min(0); - island_merging->set_max(10); - island_merging->set_step(1); - island_merging->set_value(2); - hb->add_child(island_merging); + grow_pixels = memnew(SpinBox); + grow_pixels->set_min(0); + grow_pixels->set_max(10); + grow_pixels->set_step(1); + grow_pixels->set_value(2); + hb->add_child(grow_pixels); hb->add_spacer(); update_preview = memnew(Button); update_preview->set_text(TTR("Update Preview")); diff --git a/editor/plugins/sprite_editor_plugin.h b/editor/plugins/sprite_editor_plugin.h index 81be4a19e9..4ca7bca1a8 100644 --- a/editor/plugins/sprite_editor_plugin.h +++ b/editor/plugins/sprite_editor_plugin.h @@ -67,7 +67,8 @@ class SpriteEditor : public Control { Vector<int> computed_indices; SpinBox *simplification; - SpinBox *island_merging; + SpinBox *grow_pixels; + SpinBox *shrink_pixels; Button *update_preview; void _menu_option(int p_option); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index c5b84f0af1..c962751c7a 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -88,7 +88,6 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { } else { if (changed) { // to avoid tree collapse _clear_buffer(); - _update_custom_nodes(); _update_options_menu(); _update_preview(); } @@ -178,7 +177,7 @@ bool VisualShaderEditor::_is_available(int p_mode) { return (p_mode == -1 || (p_mode & current_mode) != 0); } -void VisualShaderEditor::_update_custom_nodes() { +void VisualShaderEditor::update_custom_nodes() { clear_custom_types(); List<StringName> class_list; ScriptServer::get_global_class_list(&class_list); @@ -228,6 +227,7 @@ void VisualShaderEditor::_update_custom_nodes() { add_custom_type(name, script, description, return_icon_type, category, sub_category); } } + _update_options_menu(); } String VisualShaderEditor::_get_description(int p_idx) { @@ -665,6 +665,15 @@ void VisualShaderEditor::_update_graph() { label->set_text(name_left); label->add_style_override("normal", label_style); //more compact hb->add_child(label); + + if (vsnode->get_input_port_default_hint(i) != "" && !port_left_used) { + + Label *hint_label = memnew(Label); + hint_label->set_text("[" + vsnode->get_input_port_default_hint(i) + "]"); + hint_label->add_color_override("font_color", get_color("font_color_readonly", "TextEdit")); + hint_label->add_style_override("normal", label_style); + hb->add_child(hint_label); + } } } @@ -1219,6 +1228,23 @@ void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node, editing_port = p_port; } +void VisualShaderEditor::_add_custom_node(const String &p_path) { + + int idx = -1; + + for (int i = custom_node_option_idx; i < add_options.size(); i++) { + if (add_options[i].script.is_valid()) { + if (add_options[i].script->get_path() == p_path) { + idx = i; + break; + } + } + } + if (idx != -1) { + _add_node(idx); + } +} + void VisualShaderEditor::_add_texture_node(const String &p_path) { VisualShaderNodeTexture *texture = (VisualShaderNodeTexture *)_add_node(texture_node_option_idx, -1); texture->set_texture(ResourceLoader::load(p_path)); @@ -2072,14 +2098,24 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } else if (d.has("files")) { if (d["files"].get_type() == Variant::POOL_STRING_ARRAY) { + int j = 0; PoolStringArray arr = d["files"]; for (int i = 0; i < arr.size(); i++) { String type = ResourceLoader::get_resource_type(arr[i]); - if (ClassDB::get_parent_class(type) == "Texture") { - saved_node_pos = p_point + Vector2(0, i * 210 * EDSCALE); + if (type == "GDScript") { + Ref<Script> script = ResourceLoader::load(arr[i]); + if (script->get_instance_base_type() == "VisualShaderNodeCustom") { + saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos_dirty = true; + _add_custom_node(arr[i]); + j++; + } + } else if (ClassDB::get_parent_class(type) == "Texture") { + saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); saved_node_pos_dirty = true; _add_texture_node(arr[i]); + j++; } } } @@ -2698,6 +2734,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("DdYS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'y' using local differencing."), VisualShaderNodeScalarDerivativeFunc::FUNC_Y, VisualShaderNode::PORT_TYPE_SCALAR, VisualShader::TYPE_FRAGMENT | VisualShader::TYPE_LIGHT, -1, -1, true)); add_options.push_back(AddOption("Sum", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), VisualShaderNodeVectorDerivativeFunc::FUNC_SUM, VisualShaderNode::PORT_TYPE_VECTOR, VisualShader::TYPE_FRAGMENT | VisualShader::TYPE_LIGHT, -1, -1, true)); add_options.push_back(AddOption("SumS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Sum of absolute derivative in 'x' and 'y'."), VisualShaderNodeScalarDerivativeFunc::FUNC_SUM, VisualShaderNode::PORT_TYPE_SCALAR, VisualShader::TYPE_FRAGMENT | VisualShader::TYPE_LIGHT, -1, -1, true)); + custom_node_option_idx = add_options.size(); ///////////////////////////////////////////////////////////////////// @@ -2739,6 +2776,7 @@ void VisualShaderEditorPlugin::make_visible(bool p_visible) { //editor->animation_panel_make_visible(true); button->show(); editor->make_bottom_panel_item_visible(visual_shader_editor); + visual_shader_editor->update_custom_nodes(); visual_shader_editor->set_process_input(true); //visual_shader_editor->set_process(true); } else { diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index d56e223057..6f77641936 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -137,6 +137,7 @@ class VisualShaderEditor : public VBoxContainer { category = p_category; sub_category = p_sub_category; description = p_description; + sub_func = 0; sub_func_str = p_sub_func; return_type = p_return_type; mode = p_mode; @@ -149,13 +150,14 @@ class VisualShaderEditor : public VBoxContainer { Vector<AddOption> add_options; int texture_node_option_idx; + int custom_node_option_idx; List<String> keyword_list; void _draw_color_over_button(Object *obj, Color p_color); + void _add_custom_node(const String &p_path); void _add_texture_node(const String &p_path); VisualShaderNode *_add_node(int p_idx, int p_op_idx = -1); - void _update_custom_nodes(); void _update_options_menu(); void _show_preview_text(); @@ -255,6 +257,7 @@ protected: static void _bind_methods(); public: + void update_custom_nodes(); void add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); void remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); diff --git a/editor/project_export.cpp b/editor/project_export.cpp index c54103f6f7..7456396460 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -1149,11 +1149,15 @@ ProjectExportDialog::ProjectExportDialog() { include_files->connect("item_edited", this, "_tree_changed"); include_filters = memnew(LineEdit); - resources_vb->add_margin_child(TTR("Filters to export non-resource files (comma separated, e.g: *.json, *.txt)"), include_filters); + resources_vb->add_margin_child( + TTR("Filters to export non-resource files/folders\n(comma-separated, e.g: *.json, *.txt, docs/*)"), + include_filters); include_filters->connect("text_changed", this, "_filter_changed"); exclude_filters = memnew(LineEdit); - resources_vb->add_margin_child(TTR("Filters to exclude files from project (comma separated, e.g: *.json, *.txt)"), exclude_filters); + resources_vb->add_margin_child( + TTR("Filters to exclude files/folders from project\n(comma-separated, e.g: *.json, *.txt, docs/*)"), + exclude_filters); exclude_filters->connect("text_changed", this, "_filter_changed"); VBoxContainer *patch_vb = memnew(VBoxContainer); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index a56cfede34..35187214a6 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -74,6 +74,26 @@ static const char *_axis_names[JOY_AXIS_MAX * 2] = { "", " (R2)" }; +void ProjectSettingsEditor::_unhandled_input(const Ref<InputEvent> &p_event) { + + const Ref<InputEventKey> k = p_event; + + if (k.is_valid() && is_window_modal_on_top() && k->is_pressed()) { + + if (k->get_scancode_with_modifiers() == (KEY_MASK_CMD | KEY_F)) { + if (search_button->is_pressed()) { + search_box->grab_focus(); + search_box->select_all(); + } else { + // This toggles the search bar display while giving the button its "pressed" appearance + search_button->set_pressed(true); + } + + accept_event(); + } + } +} + void ProjectSettingsEditor::_notification(int p_what) { switch (p_what) { @@ -116,6 +136,7 @@ void ProjectSettingsEditor::_notification(int p_what) { } break; case NOTIFICATION_POPUP_HIDE: { EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", get_rect()); + set_process_unhandled_input(false); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { search_button->set_icon(get_icon("Search", "EditorIcons")); @@ -800,6 +821,7 @@ void ProjectSettingsEditor::popup_project_settings() { _update_translations(); autoload_settings->update_autoload(); plugin_settings->update_plugins(); + set_process_unhandled_input(true); } void ProjectSettingsEditor::update_plugins() { @@ -823,13 +845,9 @@ void ProjectSettingsEditor::_item_adds(String) { void ProjectSettingsEditor::_item_add() { - Variant value; - switch (type->get_selected()) { - case 0: value = false; break; - case 1: value = 0; break; - case 2: value = 0.0; break; - case 3: value = ""; break; - } + // Initialize the property with the default value for the given type + Variant::CallError ce; + const Variant value = Variant::construct(Variant::Type(type->get_selected()), NULL, 0, ce); String catname = category->get_text().strip_edges(); String propname = property->get_text().strip_edges(); @@ -1697,6 +1715,7 @@ void ProjectSettingsEditor::_editor_restart_close() { void ProjectSettingsEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_unhandled_input"), &ProjectSettingsEditor::_unhandled_input); ClassDB::bind_method(D_METHOD("_item_selected"), &ProjectSettingsEditor::_item_selected); ClassDB::bind_method(D_METHOD("_item_add"), &ProjectSettingsEditor::_item_add); ClassDB::bind_method(D_METHOD("_item_adds"), &ProjectSettingsEditor::_item_adds); @@ -1811,10 +1830,11 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { type = memnew(OptionButton); type->set_h_size_flags(Control::SIZE_EXPAND_FILL); add_prop_bar->add_child(type); - type->add_item("bool"); - type->add_item("int"); - type->add_item("float"); - type->add_item("string"); + + // Start at 1 to avoid adding "Nil" as an option + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + type->add_item(Variant::get_type_name(Variant::Type(i)), i); + } Button *add = memnew(Button); add_prop_bar->add_child(add); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index 4dfd8ba602..c164b49d0e 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -178,6 +178,7 @@ class ProjectSettingsEditor : public AcceptDialog { void _editor_restart_close(); protected: + void _unhandled_input(const Ref<InputEvent> &p_event); void _notification(int p_what); static void _bind_methods(); diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index e96dfdd7b9..89d275a90b 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -728,6 +728,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } variables->add_property("Members/" + n, v, h, hs); + + if (n == "self") { + _scene_tree_property_select_object(v); + } } ofs += mcount * 2; diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index c9c1f9c3e0..f8425ebe22 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -140,32 +140,35 @@ void EditorSettingsDialog::_notification(int p_what) { void EditorSettingsDialog::_unhandled_input(const Ref<InputEvent> &p_event) { - Ref<InputEventKey> k = p_event; + const Ref<InputEventKey> k = p_event; - if (k.is_valid() && is_window_modal_on_top()) { + if (k.is_valid() && is_window_modal_on_top() && k->is_pressed()) { - if (k->is_pressed()) { + bool handled = false; - bool handled = false; + if (ED_IS_SHORTCUT("editor/undo", p_event)) { + String action = undo_redo->get_current_action_name(); + if (action != "") + EditorNode::get_log()->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR); + undo_redo->undo(); + handled = true; + } - if (ED_IS_SHORTCUT("editor/undo", p_event)) { - String action = undo_redo->get_current_action_name(); - if (action != "") - EditorNode::get_log()->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR); - undo_redo->undo(); - handled = true; - } - if (ED_IS_SHORTCUT("editor/redo", p_event)) { - undo_redo->redo(); - String action = undo_redo->get_current_action_name(); - if (action != "") - EditorNode::get_log()->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR); - handled = true; - } + if (ED_IS_SHORTCUT("editor/redo", p_event)) { + undo_redo->redo(); + String action = undo_redo->get_current_action_name(); + if (action != "") + EditorNode::get_log()->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR); + handled = true; + } - if (handled) { - accept_event(); - } + if (k->get_scancode_with_modifiers() == (KEY_MASK_CMD | KEY_F)) { + _focus_current_search_box(); + handled = true; + } + + if (handled) { + accept_event(); } } } @@ -408,7 +411,6 @@ EditorSettingsDialog::EditorSettingsDialog() { tabs->set_tab_align(TabContainer::ALIGN_LEFT); tabs->connect("tab_changed", this, "_tabs_tab_changed"); add_child(tabs); - //set_child_rect(tabs); // General Tab @@ -425,7 +427,6 @@ EditorSettingsDialog::EditorSettingsDialog() { hbc->add_child(search_box); inspector = memnew(SectionedInspector); - //inspector->hide_top_label(); inspector->get_inspector()->set_use_filter(true); inspector->register_search_box(search_box); inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -474,7 +475,6 @@ EditorSettingsDialog::EditorSettingsDialog() { shortcuts->set_v_size_flags(SIZE_EXPAND_FILL); shortcuts->set_columns(2); shortcuts->set_hide_root(true); - //shortcuts->set_hide_folding(true); shortcuts->set_column_titles_visible(true); shortcuts->set_column_title(0, TTR("Name")); shortcuts->set_column_title(1, TTR("Binding")); @@ -495,9 +495,7 @@ EditorSettingsDialog::EditorSettingsDialog() { press_a_key->connect("gui_input", this, "_wait_for_key"); press_a_key->connect("confirmed", this, "_press_a_key_confirm"); - //get_ok()->set_text("Apply"); set_hide_on_ok(true); - //get_cancel()->set_text("Close"); timer = memnew(Timer); timer->set_wait_time(1.5); diff --git a/main/main.cpp b/main/main.cpp index 6df02af3a5..42c90fc027 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -982,10 +982,13 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph video_mode.height = GLOBAL_GET("display/window/size/height"); if (globals->has_setting("display/window/size/test_width") && globals->has_setting("display/window/size/test_height")) { + int tw = globals->get("display/window/size/test_width"); - int th = globals->get("display/window/size/test_height"); - if (tw > 0 && th > 0) { + if (tw > 0) { video_mode.width = tw; + } + int th = globals->get("display/window/size/test_height"); + if (th > 0) { video_mode.height = th; } } diff --git a/main/splash_editor.png b/main/splash_editor.png Binary files differindex d8677f1749..ab10716a2e 100644 --- a/main/splash_editor.png +++ b/main/splash_editor.png diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml index 7e1cac243a..aa48ab44f2 100644 --- a/modules/gdnative/doc_classes/GDNativeLibrary.xml +++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml @@ -1,35 +1,50 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GDNativeLibrary" inherits="Resource" category="Core" version="3.2"> <brief_description> + An external library containing functions or script classes to use in Godot. </brief_description> <description> + A GDNative library can implement [NativeScript]s, global functions to call with the [GDNative] class, or low-level engine extensions through interfaces such as [ARVRInterfaceGDNative]. The library must be compiled for each platform and architecture that the project will run on. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-c-example.html</link> + <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-cpp-example.html</link> </tutorials> <methods> <method name="get_current_dependencies" qualifiers="const"> <return type="PoolStringArray"> </return> <description> + Returns paths to all dependency libraries for the current platform and architecture. </description> </method> <method name="get_current_library_path" qualifiers="const"> <return type="String"> </return> <description> + Returns the path to the dynamic library file for the current platform and architecture. </description> </method> </methods> <members> <member name="config_file" type="ConfigFile" setter="set_config_file" getter="get_config_file"> + This resource in INI-style [ConfigFile] format, as in [code].gdnlib[/code] files. </member> <member name="load_once" type="bool" setter="set_load_once" getter="should_load_once" default="true"> + If [code]true[/code], Godot loads only one copy of the library and each script that references the library will share static data like static or global variables. + If [code]false[/code], Godot loads a separate copy of the library into memory for each script that references it. </member> <member name="reloadable" type="bool" setter="set_reloadable" getter="is_reloadable" default="true"> + If [code]true[/code], the editor will temporarily unload the library whenever the user switches away from the editor window, allowing the user to recompile the library without restarting Godot. + [b]Note:[/b] If the library defines tool scripts that run inside the editor, [code]reloadable[/code] must be [code]false[/code]. Otherwise, the editor will attempt to unload the tool scripts while they're in use and crash. </member> <member name="singleton" type="bool" setter="set_singleton" getter="is_singleton" default="false"> + If [code]true[/code], Godot loads the library at startup rather than the first time a script uses the library, calling [code]{prefix}gdnative_singleton[/code] after initializing the library (where [code]{prefix}[/code] is the value of [member symbol_prefix]). The library remains loaded as long as Godot is running. + [b]Note:[/b] A singleton library cannot be [member reloadable]. </member> <member name="symbol_prefix" type="String" setter="set_symbol_prefix" getter="get_symbol_prefix" default=""godot_""> + The prefix this library's entry point functions begin with. For example, a GDNativeLibrary would declare its [code]gdnative_init[/code] function as [code]godot_gdnative_init[/code] by default. + On platforms that require statically linking libraries (currently only iOS), each library must have a different [code]symbol_prefix[/code]. </member> </members> <constants> diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 783ad4e147..ee9e71d4a0 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -339,7 +339,7 @@ bool GDNative::initialize() { if (err || !library_init) { OS::get_singleton()->close_dynamic_library(native_handle); native_handle = NULL; - ERR_PRINT("Failed to obtain godot_gdnative_init symbol"); + ERR_PRINTS("Failed to obtain " + library->get_symbol_prefix() + "gdnative_init symbol"); return false; } diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 6ff6262b56..0194199133 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -277,7 +277,7 @@ void register_gdnative_types() { proc_ptr); if (err != OK) { - ERR_PRINT((String("No godot_gdnative_singleton in \"" + singleton->get_library()->get_current_library_path()) + "\" found").utf8().get_data()); + ERR_PRINTS("No " + lib->get_symbol_prefix() + "gdnative_singleton in \"" + singleton->get_library()->get_current_library_path() + "\" found"); } else { singleton_gdnatives.push_back(singleton); ((void (*)())proc_ptr)(); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 788db7fb86..1d0567dd8d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -590,10 +590,10 @@ extends Sprite var elapsed = 0.0 func _process(delta): - var min_angle = deg2rad(0.0) - var max_angle = deg2rad(90.0) - rotation = lerp_angle(min_angle, max_angle, elapsed) - elapsed += delta + var min_angle = deg2rad(0.0) + var max_angle = deg2rad(90.0) + rotation = lerp_angle(min_angle, max_angle, elapsed) + elapsed += delta [/codeblock] </description> </method> diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index bdeea9cef3..83d02e4977 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1419,7 +1419,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!container->iter_init(*counter, valid)) { #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "'."; + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; OPCODE_BREAK; } #endif @@ -1432,7 +1432,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *iterator = container->iter_get(*counter, valid); #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "'."; + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; OPCODE_BREAK; } #endif @@ -1452,7 +1452,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!container->iter_next(*counter, valid)) { #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; OPCODE_BREAK; } #endif @@ -1465,7 +1465,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *iterator = container->iter_get(*counter, valid); #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; OPCODE_BREAK; } #endif diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 6db8cb2c88..03d731a5f0 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -105,6 +105,40 @@ void ExtendGDScriptParser::update_symbols() { } } +void ExtendGDScriptParser::update_document_links(const String &p_code) { + document_links.clear(); + + GDScriptTokenizerText tokenizer; + FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); + tokenizer.set_code(p_code); + while (true) { + if (tokenizer.get_token() == GDScriptTokenizer::TK_EOF) { + break; + } else if (tokenizer.get_token() == GDScriptTokenizer::TK_CONSTANT) { + Variant const_val = tokenizer.get_token_constant(); + if (const_val.get_type() == Variant::STRING) { + String path = const_val; + bool exists = fs->file_exists(path); + if (!exists) { + path = get_path().get_base_dir() + "/" + path; + exists = fs->file_exists(path); + } + if (exists) { + String value = const_val; + lsp::DocumentLink link; + link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); + link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line()); + link.range.end.line = link.range.start.line; + link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column()); + link.range.start.character = link.range.end.character - value.length(); + document_links.push_back(link); + } + } + } + tokenizer.advance(); + } +} + void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); @@ -123,7 +157,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.selectionRange.start.line = r_symbol.range.start.line; r_symbol.detail = "class " + r_symbol.name; bool is_root_class = &r_symbol == &class_symbol; - r_symbol.documentation = parse_documentation_as_markdown(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); + r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); for (int i = 0; i < p_class->variables.size(); ++i) { @@ -150,7 +184,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += " = " + JSON::print(m.default_value); } - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); symbol.uri = uri; symbol.script_path = path; @@ -170,7 +204,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.range.end.line = symbol.range.start.line; symbol.range.end.character = lines[line].length(); symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); symbol.uri = uri; symbol.script_path = path; symbol.detail = "signal " + signal.name + "("; @@ -199,7 +233,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.range.end.line = symbol.range.start.line; symbol.range.end.character = lines[line].length(); symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); symbol.uri = uri; symbol.script_path = path; @@ -267,7 +301,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN r_symbol.range.end.line = MAX(p_func->body->end_line - 2, p_func->body->line); r_symbol.range.end.character = lines[r_symbol.range.end.line].length(); r_symbol.selectionRange.start.line = r_symbol.range.start.line; - r_symbol.documentation = parse_documentation_as_markdown(line); + r_symbol.documentation = parse_documentation(line); r_symbol.uri = uri; r_symbol.script_path = path; @@ -325,65 +359,11 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) { symbol.detail += ": " + var->datatype.to_string(); } - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); r_symbol.children.push_back(symbol); } } -String ExtendGDScriptParser::marked_documentation(const String &p_bbcode) { - - String markdown = p_bbcode.strip_edges(); - - Vector<String> lines = markdown.split("\n"); - bool in_code_block = false; - int code_block_indent = -1; - - markdown = ""; - for (int i = 0; i < lines.size(); i++) { - String line = lines[i]; - int block_start = line.find("[codeblock]"); - if (block_start != -1) { - code_block_indent = block_start; - in_code_block = true; - line = "'''gdscript\n"; - } else if (in_code_block) { - line = "\t" + line.substr(code_block_indent, line.length()); - } - - if (in_code_block && line.find("[/codeblock]") != -1) { - line = "'''\n\n"; - in_code_block = false; - } - - if (!in_code_block) { - line = line.strip_edges(); - line = line.replace("[code]", "`"); - line = line.replace("[/code]", "`"); - line = line.replace("[i]", "*"); - line = line.replace("[/i]", "*"); - line = line.replace("[b]", "**"); - line = line.replace("[/b]", "**"); - line = line.replace("[u]", "__"); - line = line.replace("[/u]", "__"); - line = line.replace("[method ", "`"); - line = line.replace("[member ", "`"); - line = line.replace("[signal ", "`"); - line = line.replace("[enum ", "`"); - line = line.replace("[constant ", "`"); - line = line.replace("[", "`"); - line = line.replace("]", "`"); - } - - if (!in_code_block && i < lines.size() - 1) { - line += "\n\n"; - } else if (i < lines.size() - 1) { - line += "\n"; - } - markdown += line; - } - return markdown; -} - String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { ERR_FAIL_INDEX_V(p_line, lines.size(), String()); @@ -542,10 +522,6 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(i return ret; } -String ExtendGDScriptParser::parse_documentation_as_markdown(int p_line, bool p_docs_down) { - return marked_documentation(parse_documentation(p_line, p_docs_down)); -} - const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const { if (p_line <= 0) { return &class_symbol; @@ -572,6 +548,10 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String return NULL; } +const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const { + return document_links; +} + const Array &ExtendGDScriptParser::get_member_completions() { if (member_completions.empty()) { @@ -755,5 +735,6 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false); update_diagnostics(); update_symbols(); + update_document_links(p_code); return err; } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index dd0453d3ff..a6e0ca5534 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -56,12 +56,14 @@ class ExtendGDScriptParser : public GDScriptParser { lsp::DocumentSymbol class_symbol; Vector<lsp::Diagnostic> diagnostics; + List<lsp::DocumentLink> document_links; ClassMembers members; HashMap<String, ClassMembers> inner_classes; void update_diagnostics(); void update_symbols(); + void update_document_links(const String &p_code); void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol); void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol); @@ -73,11 +75,6 @@ class ExtendGDScriptParser : public GDScriptParser { Array member_completions; - String parse_documentation_as_markdown(int p_line, bool p_docs_down = false); - -public: - static String marked_documentation(const String &p_bbcode); - public: _FORCE_INLINE_ const String &get_path() const { return path; } _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; } @@ -93,6 +90,7 @@ public: const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const; + const List<lsp::DocumentLink> &get_document_links() const; const Array &get_member_completions(); Dictionary generate_api() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index ce3de9bc3b..ae2aaf6aee 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -153,7 +153,13 @@ Error GDScriptLanguageProtocol::start(int p_port) { } void GDScriptLanguageProtocol::stop() { + const int *ptr = clients.next(NULL); + while (ptr) { + clients.get(*ptr)->close(); + ptr = clients.next(ptr); + } server->stop(); + clients.clear(); } void GDScriptLanguageProtocol::notify_all_clients(const String &p_method, const Variant &p_params) { diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 7c58c7aa15..b83db718b8 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -39,6 +39,7 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); + ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol); ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); @@ -75,6 +76,11 @@ lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_ return doc; } +void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol *symbol) { + ERR_FAIL_NULL(symbol); + GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true)); +} + void GDScriptTextDocument::initialize() { if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { @@ -102,6 +108,21 @@ void GDScriptTextDocument::initialize() { } } +Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) { + + Variant ret; + + lsp::NativeSymbolInspectParams params; + params.load(p_params); + + if (const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_native_symbol(params)) { + ret = symbol->to_json(true); + notify_client_show_symbol(symbol); + } + + return ret; +} + Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { Dictionary params = p_params["textDocument"]; String uri = params["uri"]; @@ -271,8 +292,17 @@ Array GDScriptTextDocument::codeLens(const Dictionary &p_params) { return arr; } -Variant GDScriptTextDocument::documentLink(const Dictionary &p_params) { - Variant ret; +Array GDScriptTextDocument::documentLink(const Dictionary &p_params) { + Array ret; + + lsp::DocumentLinkParams params; + params.load(p_params); + + List<lsp::DocumentLink> links; + GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links); + for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { + ret.push_back(E->get().to_json()); + } return ret; } @@ -326,31 +356,35 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) { const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri); if (file_checker->file_exists(path)) { arr.push_back(location.to_json()); - } else if (!symbol->native_class.empty() && GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { - String id; - switch (symbol->kind) { - case lsp::SymbolKind::Class: - id = "class_name:" + symbol->name; - break; - case lsp::SymbolKind::Constant: - id = "class_constant:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Property: - case lsp::SymbolKind::Variable: - id = "class_property:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Enum: - id = "class_enum:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Method: - case lsp::SymbolKind::Function: - id = "class_method:" + symbol->native_class + ":" + symbol->name; - break; - default: - id = "class_global:" + symbol->native_class + ":" + symbol->name; - break; + } else if (!symbol->native_class.empty()) { + if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { + String id; + switch (symbol->kind) { + case lsp::SymbolKind::Class: + id = "class_name:" + symbol->name; + break; + case lsp::SymbolKind::Constant: + id = "class_constant:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Property: + case lsp::SymbolKind::Variable: + id = "class_property:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Enum: + id = "class_enum:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Function: + id = "class_method:" + symbol->native_class + ":" + symbol->name; + break; + default: + id = "class_global:" + symbol->native_class + ":" + symbol->name; + break; + } + call_deferred("show_native_symbol_in_editor", id); + } else { + notify_client_show_symbol(symbol); } - call_deferred("show_native_symbol_in_editor", id); } } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index d15022d2c4..235e2c3f6e 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -52,14 +52,16 @@ protected: private: lsp::TextDocumentItem load_document_item(const Variant &p_param); + void notify_client_show_symbol(const lsp::DocumentSymbol *symbol); public: + Variant nativeSymbol(const Dictionary &p_params); Array documentSymbol(const Dictionary &p_params); Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); Array foldingRange(const Dictionary &p_params); Array codeLens(const Dictionary &p_params); - Variant documentLink(const Dictionary &p_params); + Array documentLink(const Dictionary &p_params); Array colorPresentation(const Dictionary &p_params); Variant hover(const Dictionary &p_params); Array definition(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 1901daacff..6baa7e4219 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -198,7 +198,7 @@ Error GDScriptWorkspace::initialize() { if (!class_data.inherits.empty()) { class_symbol.detail += " extends " + class_data.inherits; } - class_symbol.documentation = ExtendGDScriptParser::marked_documentation(class_data.brief_description) + "\n" + ExtendGDScriptParser::marked_documentation(class_data.description); + class_symbol.documentation = class_data.brief_description + "\n" + class_data.description; for (int i = 0; i < class_data.constants.size(); i++) { const DocData::ConstantDoc &const_data = class_data.constants[i]; @@ -211,7 +211,7 @@ Error GDScriptWorkspace::initialize() { symbol.detail += ": " + const_data.enumeration; } symbol.detail += " = " + const_data.value; - symbol.documentation = ExtendGDScriptParser::marked_documentation(const_data.description); + symbol.documentation = const_data.description; class_symbol.children.push_back(symbol); } @@ -232,7 +232,7 @@ Error GDScriptWorkspace::initialize() { } else { symbol.detail += ": " + data.type; } - symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description); + symbol.documentation = data.description; class_symbol.children.push_back(symbol); } @@ -270,7 +270,7 @@ Error GDScriptWorkspace::initialize() { } symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + data.return_type; - symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description); + symbol.documentation = data.description; class_symbol.children.push_back(symbol); } @@ -475,6 +475,33 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP } } +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { + + if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) { + const lsp::DocumentSymbol &symbol = E->get(); + if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) { + return &symbol; + } + + for (int i = 0; i < symbol.children.size(); ++i) { + if (symbol.children[i].name == p_params.symbol_name) { + return &(symbol.children[i]); + } + } + } + + return NULL; +} + +void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) { + if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) { + const List<lsp::DocumentLink> &links = parser->get_document_links(); + for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { + r_list.push_back(E->get()); + } + } +} + Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) { Dictionary api; if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) { diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index adce169d4b..a416ae1075 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -53,7 +53,6 @@ protected: ExtendGDScriptParser *get_parse_successed_script(const String &p_path); ExtendGDScriptParser *get_parse_result(const String &p_path); - void strip_flat_symbols(const String &p_branch); void list_script_files(const String &p_root_dir, List<String> &r_files); public: @@ -81,7 +80,8 @@ public: const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); - + const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params); + void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); Dictionary generate_script_api(const String &p_path); GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 3e57b6ee7e..61a0980c41 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -37,6 +37,9 @@ namespace lsp { typedef String DocumentUri; +/** Format BBCode documentation from DocData to markdown */ +static String marked_documentation(const String &p_bbcode); + /** * Text documents are identified using a URI. On the protocol level, URIs are passed as strings. */ @@ -199,6 +202,41 @@ struct TextDocumentPositionParams { } }; +struct DocumentLinkParams { + /** + * The document to provide document links for. + */ + TextDocumentIdentifier textDocument; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + textDocument.load(p_params["textDocument"]); + } +}; + +/** + * A document link is a range in a text document that links to an internal or external resource, like another + * text document or a web site. + */ +struct DocumentLink { + + /** + * The range this link applies to. + */ + Range range; + + /** + * The uri this link points to. If missing a resolve request is sent later. + */ + DocumentUri target; + + Dictionary to_json() const { + Dictionary dict; + dict["range"] = range.to_json(); + dict["target"] = target; + return dict; + } +}; + /** * A textual edit applicable to a text document. */ @@ -247,6 +285,9 @@ struct Command { } }; +// Use namespace instead of enumeration to follow the LSP specifications +// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not + namespace TextDocumentSyncKind { /** * Documents should not be synced at all. @@ -557,6 +598,7 @@ struct TextDocumentContentChangeEvent { } }; +// Use namespace instead of enumeration to follow the LSP specifications namespace DiagnosticSeverity { /** * Reports an error. @@ -657,6 +699,7 @@ struct Diagnostic { } }; +// Use namespace instead of enumeration to follow the LSP specifications /** * Describes the content type that a client supports in various * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. @@ -721,6 +764,9 @@ struct MarkupContent { } }; +// Use namespace instead of enumeration to follow the LSP specifications +// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not +// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc. /** * The kind of a completion entry. */ @@ -752,6 +798,7 @@ static const int Operator = 24; static const int TypeParameter = 25; }; // namespace CompletionItemKind +// Use namespace instead of enumeration to follow the LSP specifications /** * Defines whether the insert text in a completion item should be interpreted as * plain text or a snippet. @@ -944,36 +991,39 @@ struct CompletionList { Vector<CompletionItem> items; }; +// Use namespace instead of enumeration to follow the LSP specifications +// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not +// And here C++ compilers are unhappy with our enumeration name like String, Array, Object etc /** * A symbol kind. */ namespace SymbolKind { -static const int File = 1; -static const int Module = 2; -static const int Namespace = 3; -static const int Package = 4; -static const int Class = 5; -static const int Method = 6; -static const int Property = 7; -static const int Field = 8; -static const int Constructor = 9; -static const int Enum = 10; -static const int Interface = 11; -static const int Function = 12; -static const int Variable = 13; -static const int Constant = 14; -static const int String = 15; -static const int Number = 16; -static const int Boolean = 17; -static const int Array = 18; -static const int Object = 19; -static const int Key = 20; -static const int Null = 21; -static const int EnumMember = 22; -static const int Struct = 23; -static const int Event = 24; -static const int Operator = 25; -static const int TypeParameter = 26; +static const int File = 0; +static const int Module = 1; +static const int Namespace = 2; +static const int Package = 3; +static const int Class = 4; +static const int Method = 5; +static const int Property = 6; +static const int Field = 7; +static const int Constructor = 8; +static const int Enum = 9; +static const int Interface = 10; +static const int Function = 11; +static const int Variable = 12; +static const int Constant = 13; +static const int String = 14; +static const int Number = 15; +static const int Boolean = 16; +static const int Array = 17; +static const int Object = 18; +static const int Key = 19; +static const int Null = 20; +static const int EnumMember = 21; +static const int Struct = 22; +static const int Event = 23; +static const int Operator = 24; +static const int TypeParameter = 25; }; // namespace SymbolKind /** @@ -1099,7 +1149,7 @@ struct DocumentSymbol { */ Vector<DocumentSymbol> children; - _FORCE_INLINE_ Dictionary to_json() const { + Dictionary to_json(bool with_doc = false) const { Dictionary dict; dict["name"] = name; dict["detail"] = detail; @@ -1107,10 +1157,14 @@ struct DocumentSymbol { dict["deprecated"] = deprecated; dict["range"] = range.to_json(); dict["selectionRange"] = selectionRange.to_json(); + if (with_doc) { + dict["documentation"] = documentation; + dict["native_class"] = native_class; + } Array arr; arr.resize(children.size()); for (int i = 0; i < children.size(); i++) { - arr[i] = children[i].to_json(); + arr[i] = children[i].to_json(with_doc); } dict["children"] = arr; return dict; @@ -1142,7 +1196,7 @@ struct DocumentSymbol { markdown.value = "\t" + detail + "\n\n"; } if (documentation.length()) { - markdown.value += documentation + "\n\n"; + markdown.value += marked_documentation(documentation) + "\n\n"; } if (script_path.length()) { markdown.value += "Defined in [" + script_path + "](" + uri + ")"; @@ -1194,6 +1248,17 @@ struct DocumentSymbol { } }; +struct NativeSymbolInspectParams { + + String native_class; + String symbol_name; + + void load(const Dictionary &p_params) { + native_class = p_params["native_class"]; + symbol_name = p_params["symbol_name"]; + } +}; + /** * Enum of known range kinds */ @@ -1254,6 +1319,7 @@ struct FoldingRange { } }; +// Use namespace instead of enumeration to follow the LSP specifications /** * How a completion was triggered */ @@ -1501,6 +1567,61 @@ struct InitializeResult { } }; +/** Format BBCode documentation from DocData to markdown */ +static String marked_documentation(const String &p_bbcode) { + + String markdown = p_bbcode.strip_edges(); + + Vector<String> lines = markdown.split("\n"); + bool in_code_block = false; + int code_block_indent = -1; + + markdown = ""; + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + int block_start = line.find("[codeblock]"); + if (block_start != -1) { + code_block_indent = block_start; + in_code_block = true; + line = "\n"; + } else if (in_code_block) { + line = "\t" + line.substr(code_block_indent, line.length()); + } + + if (in_code_block && line.find("[/codeblock]") != -1) { + line = "\n"; + in_code_block = false; + } + + if (!in_code_block) { + line = line.strip_edges(); + line = line.replace("[code]", "`"); + line = line.replace("[/code]", "`"); + line = line.replace("[i]", "*"); + line = line.replace("[/i]", "*"); + line = line.replace("[b]", "**"); + line = line.replace("[/b]", "**"); + line = line.replace("[u]", "__"); + line = line.replace("[/u]", "__"); + line = line.replace("[method ", "`"); + line = line.replace("[member ", "`"); + line = line.replace("[signal ", "`"); + line = line.replace("[enum ", "`"); + line = line.replace("[constant ", "`"); + line = line.replace("[", "`"); + line = line.replace("]", "`"); + } + + if (!in_code_block && i < lines.size() - 1) { + line += "\n\n"; + } else if (i < lines.size() - 1) { + line += "\n"; + } + markdown += line; + } + return markdown; +} + } // namespace lsp #endif diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 4c1e47ecad..eb2c2dd77c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -34,7 +34,7 @@ namespace GodotTools.Build if (_msbuildToolsPath.Empty()) { - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'."); } } @@ -142,7 +142,7 @@ namespace GodotTools.Build int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, blocking: true, output: (Godot.Collections.Array) outputArray); - if (exitCode == 0) + if (exitCode != 0) return string.Empty; if (outputArray.Count == 0) diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 94dffd8a84..b61575e2aa 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -1290,7 +1290,7 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false)); @@ -1875,7 +1875,7 @@ public: new_file += "<!--CHUNK_" + text + "_BEGIN-->\n"; if (!found) { - ERR_PRINTS("No end marker found in AndroidManifest.conf for chunk: " + text); + ERR_PRINTS("No end marker found in AndroidManifest.xml for chunk: " + text); f->seek(pos); } else { //add chunk lines @@ -1894,7 +1894,7 @@ public: String last_tag = "android:icon=\"@drawable/icon\""; int last_tag_pos = l.find(last_tag); if (last_tag_pos == -1) { - WARN_PRINTS("No adding of application tags because could not find last tag for <application: " + last_tag); + ERR_PRINTS("Not adding application attributes as the expected tag was not found in '<application': " + last_tag); new_file += l + "\n"; } else { String base = l.substr(0, last_tag_pos + last_tag.length()); @@ -1980,9 +1980,9 @@ public: return ERR_CANT_CREATE; } if (p_debug) { - src_apk = build_path.plus_file("build/outputs/apk/debug/build-debug-unsigned.apk"); + src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk"); } else { - src_apk = build_path.plus_file("build/outputs/apk/release/build-release-unsigned.apk"); + src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk"); } if (!FileAccess::exists(src_apk)) { diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index d5f4ba18d6..ba01ec313b 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -26,11 +26,8 @@ <!-- Any tag in this line after android:icon will be erased when doing custom builds. --> <!-- If you want to add tags manually, do before it. --> - <application - android:label="@string/godot_project_name_string" - android:allowBackup="false" - tools:ignore="GoogleAppIndexingWarning" - android:icon="@drawable/icon" > + <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. --> + <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@drawable/icon" > <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> <!-- Do these changes in the export preset. Adding new ones is fine. --> diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 067fa6f4b9..67dce172dc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -95,6 +95,11 @@ public class GodotLib { public static native void touch(int what, int pointer, int howmany, int[] arr); /** + * Forward hover events from the main thread to the GL thread. + */ + public static native void hover(int type, int x, int y); + + /** * Forward accelerometer sensor events from the main thread to the GL thread. * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 2beca67922..2756ca6c83 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -188,7 +188,18 @@ public class GodotInputHandler implements InputDeviceListener { } return true; } - }; + } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) { + final int x = Math.round(event.getX()); + final int y = Math.round(event.getY()); + final int type = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.hover(type, x, y); + } + }); + return true; + } return false; } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 30676783db..7daea19961 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -826,6 +826,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jo */ } +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) { + if (step == 0) + return; + + os_android->process_hover(p_type, Point2(p_x, p_y)); +} + /* * Android Key codes. */ diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 11bda94f8d..a564bbd4a1 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -45,6 +45,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 49ab0ea84a..91bd6cbdd2 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -477,6 +477,23 @@ void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> } } +void OS_Android::process_hover(int p_type, Point2 p_pos) { + // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER + switch (p_type) { + case 7: // hover move + case 9: // hover enter + case 10: { // hover exit + Ref<InputEventMouseMotion> ev; + ev.instance(); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_relative(p_pos - hover_prev_pos); + input->parse_input_event(ev); + hover_prev_pos = p_pos; + } break; + } +} + void OS_Android::process_accelerometer(const Vector3 &p_accelerometer) { input->set_accelerometer(p_accelerometer); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index a17941f7c0..9bad9b2e01 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -70,6 +70,7 @@ public: private: Vector<TouchPos> touch; + Point2 hover_prev_pos; // needed to calculate the relative position on hover events bool use_gl2; bool use_apk_expansion; @@ -186,6 +187,7 @@ public: void process_magnetometer(const Vector3 &p_magnetometer); void process_gyroscope(const Vector3 &p_gyroscope); void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points); + void process_hover(int p_type, Point2 p_pos); void process_joy_event(JoypadEvent p_event); void process_event(Ref<InputEvent> p_event); void init_video_mode(int p_video_width, int p_video_height); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 0179bf813d..b0661cb4dd 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -574,6 +574,8 @@ void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_s }, cursors[p_shape].utf8().get_data()); /* clang-format on */ cursors[p_shape] = ""; + + cursors_cache.erase(p_shape); } set_cursor_shape(cursor_shape); diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 94090bcdc1..c8ecbd5a2d 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -133,7 +133,9 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) #ifdef OSX_ENABLED r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); #endif r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); @@ -360,9 +362,17 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { List<String> args; + if (p_preset->get("codesign/timestamp")) { + args.push_back("--timestamp"); + } + if (p_preset->get("codesign/hardened_runtime")) { + args.push_back("--options"); + args.push_back("runtime"); + } + if (p_preset->get("codesign/entitlements") != "") { /* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */ - args.push_back("-entitlements"); + args.push_back("--entitlements"); args.push_back(p_preset->get("codesign/entitlements")); } args.push_back("-s"); @@ -379,6 +389,10 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese EditorNode::add_io_error("codesign: no identity found"); return FAILED; } + if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) { + EditorNode::add_io_error("codesign: invalid entitlements file"); + return FAILED; + } return OK; } @@ -386,7 +400,9 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) { List<String> args; - OS::get_singleton()->move_to_trash(p_dmg_path); + if (FileAccess::exists(p_dmg_path)) { + OS::get_singleton()->move_to_trash(p_dmg_path); + } args.push_back("create"); args.push_back(p_dmg_path); @@ -673,19 +689,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign } - if (err == OK && identity != "") { - // we should probably loop through all resources and sign them? - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Resources/icon.icns"); - } - - if (err == OK && identity != "") { - err = _code_sign(p_preset, pack_path); - } - - if (err == OK && identity != "") { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Info.plist"); - } - // and finally create a DMG if (err == OK) { if (ep.step("Making DMG", 3)) { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index f48d4a307d..d30cb1c092 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -1973,11 +1973,16 @@ void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c [nsimage release]; } else { // Reset to default system cursor - cursors[p_shape] = NULL; + if (cursors[p_shape] != NULL) { + [cursors[p_shape] release]; + cursors[p_shape] = NULL; + } CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; set_cursor_shape(c); + + cursors_cache.erase(p_shape); } } diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index facf5b8d91..81b8d08b3d 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -2485,11 +2485,16 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap DeleteObject(bitmap); } else { // Reset to default system cursor - cursors[p_shape] = NULL; + if (cursors[p_shape]) { + DestroyIcon(cursors[p_shape]); + cursors[p_shape] = NULL; + } CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; set_cursor_shape(c); + + cursors_cache.erase(p_shape); } } diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 687981f32b..39160ee720 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -2996,6 +2996,8 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c CursorShape c = current_cursor; current_cursor = CURSOR_MAX; set_cursor_shape(c); + + cursors_cache.erase(p_shape); } } diff --git a/scene/2d/sprite.cpp b/scene/2d/sprite.cpp index af9ce2a4bf..83f92ce2bc 100644 --- a/scene/2d/sprite.cpp +++ b/scene/2d/sprite.cpp @@ -135,12 +135,12 @@ void Sprite::set_texture(const Ref<Texture> &p_texture) { return; if (texture.is_valid()) - texture->remove_change_receptor(this); + texture->disconnect(CoreStringNames::get_singleton()->changed, this, "_texture_changed"); texture = p_texture; if (texture.is_valid()) - texture->add_change_receptor(this); + texture->connect(CoreStringNames::get_singleton()->changed, this, "_texture_changed"); update(); emit_signal("texture_changed"); @@ -389,11 +389,11 @@ void Sprite::_validate_property(PropertyInfo &property) const { } } -void Sprite::_changed_callback(Object *p_changed, const char *p_prop) { +void Sprite::_texture_changed() { // Changes to the texture need to trigger an update to make // the editor redraw the sprite with the updated texture. - if (texture.is_valid() && texture.ptr() == p_changed) { + if (texture.is_valid()) { update(); } } @@ -443,6 +443,8 @@ void Sprite::_bind_methods() { ClassDB::bind_method(D_METHOD("get_rect"), &Sprite::get_rect); + ClassDB::bind_method(D_METHOD("_texture_changed"), &Sprite::_texture_changed); + ADD_SIGNAL(MethodInfo("frame_changed")); ADD_SIGNAL(MethodInfo("texture_changed")); @@ -480,6 +482,4 @@ Sprite::Sprite() { } Sprite::~Sprite() { - if (texture.is_valid()) - texture->remove_change_receptor(this); } diff --git a/scene/2d/sprite.h b/scene/2d/sprite.h index 5e6717a3f5..d3c0112e62 100644 --- a/scene/2d/sprite.h +++ b/scene/2d/sprite.h @@ -57,6 +57,8 @@ class Sprite : public Node2D { void _get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_clip) const; + void _texture_changed(); + protected: void _notification(int p_what); @@ -64,8 +66,6 @@ protected: virtual void _validate_property(PropertyInfo &property) const; - virtual void _changed_callback(Object *p_changed, const char *p_prop); - public: virtual Dictionary _edit_get_state() const; virtual void _edit_set_state(const Dictionary &p_state); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 2bfdfd7d02..c9ba51bafb 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1050,6 +1050,7 @@ void TileMap::update_dirty_bitmask() { void TileMap::fix_invalid_tiles() { + ERR_FAIL_COND_MSG(tile_set.is_null(), "Cannot fix invalid tiles if Tileset is not open."); for (Map<PosKey, Cell>::Element *E = tile_map.front(); E; E = E->next()) { if (!tile_set->has_tile(get_cell(E->key().x, E->key().y))) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index fc67b28095..fafbcf0c55 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1412,6 +1412,9 @@ void Control::_size_changed() { } void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bool p_push_opposite_anchor) { + + ERR_FAIL_INDEX((int)p_margin, 4); + Rect2 parent_rect = get_parent_anchorable_rect(); float parent_range = (p_margin == MARGIN_LEFT || p_margin == MARGIN_RIGHT) ? parent_rect.size.x : parent_rect.size.y; float previous_margin_pos = data.margin[p_margin] + data.anchor[p_margin] * parent_range; @@ -1456,6 +1459,9 @@ void Control::set_anchor_and_margin(Margin p_margin, float p_anchor, float p_pos } void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins) { + + ERR_FAIL_INDEX((int)p_preset, 16); + //Left switch (p_preset) { case PRESET_TOP_LEFT: @@ -1570,6 +1576,10 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins) { } void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode, int p_margin) { + + ERR_FAIL_INDEX((int)p_preset, 16); + ERR_FAIL_INDEX((int)p_resize_mode, 4); + // Calculate the size if the node is not resized Size2 min_size = get_minimum_size(); Size2 new_size = get_size(); @@ -1704,6 +1714,8 @@ void Control::set_anchors_and_margins_preset(LayoutPreset p_preset, LayoutPreset float Control::get_anchor(Margin p_margin) const { + ERR_FAIL_INDEX_V(int(p_margin), 4, 0.0); + return data.anchor[p_margin]; } @@ -1720,6 +1732,8 @@ void Control::_change_notify_margins() { void Control::set_margin(Margin p_margin, float p_value) { + ERR_FAIL_INDEX((int)p_margin, 4); + data.margin[p_margin] = p_value; _size_changed(); } @@ -1740,6 +1754,8 @@ void Control::set_end(const Size2 &p_point) { float Control::get_margin(Margin p_margin) const { + ERR_FAIL_INDEX_V((int)p_margin, 4, 0); + return data.margin[p_margin]; } @@ -1951,6 +1967,8 @@ void Control::add_constant_override(const StringName &p_name, int p_constant) { void Control::set_focus_mode(FocusMode p_focus_mode) { + ERR_FAIL_INDEX((int)p_focus_mode, 3); + if (is_inside_tree() && p_focus_mode == FOCUS_NONE && data.focus_mode != FOCUS_NONE && has_focus()) release_focus(); @@ -2298,6 +2316,8 @@ Control *Control::make_custom_tooltip(const String &p_text) const { void Control::set_default_cursor_shape(CursorShape p_shape) { + ERR_FAIL_INDEX(int(p_shape), CURSOR_MAX); + data.default_cursor = p_shape; } @@ -2358,6 +2378,8 @@ NodePath Control::get_focus_previous() const { Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { + ERR_FAIL_INDEX_V((int)p_margin, 4, NULL); + if (p_count >= MAX_NEIGHBOUR_SEARCH_COUNT) return NULL; if (!data.focus_neighbour[p_margin].is_empty()) { @@ -2760,6 +2782,8 @@ bool Control::is_clipping_contents() { void Control::set_h_grow_direction(GrowDirection p_direction) { + ERR_FAIL_INDEX((int)p_direction, 3); + data.h_grow = p_direction; _size_changed(); } @@ -2771,6 +2795,8 @@ Control::GrowDirection Control::get_h_grow_direction() const { void Control::set_v_grow_direction(GrowDirection p_direction) { + ERR_FAIL_INDEX((int)p_direction, 3); + data.v_grow = p_direction; _size_changed(); } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 9bc593ea3b..24c046457b 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -637,6 +637,8 @@ bool FileDialog::is_mode_overriding_title() const { void FileDialog::set_mode(Mode p_mode) { + ERR_FAIL_INDEX((int)p_mode, 5); + mode = p_mode; switch (mode) { diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 000b6e3bbb..1a0539effa 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -430,6 +430,7 @@ ItemList::SelectMode ItemList::get_select_mode() const { void ItemList::set_icon_mode(IconMode p_mode) { + ERR_FAIL_INDEX((int)p_mode, 2); icon_mode = p_mode; update(); shape_changed = true; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index a7c6c5ccab..08faaf7d45 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -79,7 +79,7 @@ Size2 PopupMenu::get_minimum_size() const { if (items[i].checkable_type) has_check = true; - String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].xl_text; + String text = items[i].xl_text; size.width += font->get_string_size(text).width; if (i > 0) size.height += vseparation; @@ -519,7 +519,7 @@ void PopupMenu::_notification(int p_what) { hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(get_size().width - style->get_minimum_size().width + hseparation * 2, h + vseparation))); } - String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].xl_text; + String text = items[i].xl_text; item_ofs.x += items[i].h_ofs; if (items[i].separator) { @@ -627,63 +627,50 @@ void PopupMenu::_notification(int p_what) { } } -void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +/* Methods to add items with or without icon, checkbox, shortcut. + * Be sure to keep them in sync when adding new properties in the Item struct. + */ - Item item; - item.icon = p_icon; - item.text = p_label; - item.xl_text = tr(p_label); +#define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \ + item.text = p_label; \ + item.xl_text = tr(p_label); \ + item.id = p_id == -1 ? items.size() : p_id; \ item.accel = p_accel; - item.id = p_id; - items.push_back(item); - update(); - minimum_size_changed(); -} + void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) { Item item; - item.text = p_label; - item.xl_text = tr(p_label); - item.accel = p_accel; - item.id = p_id == -1 ? items.size() : p_id; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { +void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { Item item; - item.text = p_label; - item.xl_text = tr(p_label); - item.id = p_id; - item.submenu = p_submenu; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); + item.icon = p_icon; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) { Item item; - item.icon = p_icon; - item.text = p_label; - item.xl_text = tr(p_label); - item.accel = p_accel; - item.id = p_id; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { Item item; - item.text = p_label; - item.xl_text = tr(p_label); - item.accel = p_accel; - item.id = p_id == -1 ? items.size() : p_id; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); + item.icon = p_icon; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); @@ -692,63 +679,59 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p_accel) { - add_check_item(p_label, p_id, p_accel); - items.write[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + Item item; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); + item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + items.push_back(item); update(); minimum_size_changed(); } void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { - add_icon_check_item(p_icon, p_label, p_id, p_accel); - items.write[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + Item item; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); + item.icon = p_icon; + item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { - - ERR_FAIL_COND(p_shortcut.is_null()); - - _ref_shortcut(p_shortcut); +void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) { Item item; - item.id = p_id; - item.icon = p_icon; - item.shortcut = p_shortcut; - item.shortcut_is_global = p_global; + ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); + item.max_states = p_max_states; + item.state = p_default_state; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { - - ERR_FAIL_COND(p_shortcut.is_null()); +#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \ + ERR_FAIL_COND_MSG(p_shortcut.is_null(), "Cannot add item with invalid ShortCut."); \ + _ref_shortcut(p_shortcut); \ + item.text = p_shortcut->get_name(); \ + item.xl_text = tr(item.text); \ + item.id = p_id == -1 ? items.size() : p_id; \ + item.shortcut = p_shortcut; \ + item.shortcut_is_global = p_global; - _ref_shortcut(p_shortcut); +void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { Item item; - item.id = p_id; - item.shortcut = p_shortcut; - item.shortcut_is_global = p_global; + ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { - - ERR_FAIL_COND(p_shortcut.is_null()); - - _ref_shortcut(p_shortcut); +void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { Item item; - item.id = p_id; - item.shortcut = p_shortcut; - item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; + ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); item.icon = p_icon; - item.shortcut_is_global = p_global; items.push_back(item); update(); minimum_size_changed(); @@ -756,14 +739,19 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<Sh void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { - ERR_FAIL_COND(p_shortcut.is_null()); + Item item; + ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); + item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; + items.push_back(item); + update(); + minimum_size_changed(); +} - _ref_shortcut(p_shortcut); +void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { Item item; - item.id = p_id; - item.shortcut = p_shortcut; - item.shortcut_is_global = p_global; + ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); + item.icon = p_icon; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); @@ -772,26 +760,42 @@ void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bo void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { - add_check_shortcut(p_shortcut, p_id, p_global); - items.write[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + Item item; + ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); + item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { + + Item item; + ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); + item.icon = p_icon; + item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; + items.push_back(item); + update(); + minimum_size_changed(); +} + +void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { Item item; item.text = p_label; item.xl_text = tr(p_label); - item.accel = p_accel; - item.id = p_id; - item.max_states = p_max_states; - item.state = p_default_state; + item.id = p_id == -1 ? items.size() : p_id; + item.submenu = p_submenu; items.push_back(item); update(); minimum_size_changed(); } +#undef ITEM_SETUP_WITH_ACCEL +#undef ITEM_SETUP_WITH_SHORTCUT + +/* Methods to modify existing items. */ + void PopupMenu::set_item_text(int p_idx, const String &p_text) { ERR_FAIL_INDEX(p_idx, items.size()); @@ -1380,18 +1384,24 @@ void PopupMenu::clear_autohide_areas() { void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &PopupMenu::_gui_input); - ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_radio_check_item", "label", "id", "accel"), &PopupMenu::add_radio_check_item, DEFVAL(-1), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_icon_radio_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_radio_check_item, DEFVAL(-1), DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("add_multistate_item", "label", "max_states", "default_state", "id", "accel"), &PopupMenu::add_multistate_item, DEFVAL(0), DEFVAL(-1), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false)); ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_icon_radio_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_radio_check_shortcut, DEFVAL(-1), DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &PopupMenu::set_item_text); ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 8bfe8fc607..8c33178b09 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -120,21 +120,23 @@ protected: static void _bind_methods(); public: - void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_radio_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); void add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_submenu_item(const String &p_label, const String &p_submenu, int p_id = -1); - void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, uint32_t p_accel = 0); + void add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); - void add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); void add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); void add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_icon_radio_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); - void add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id = -1, uint32_t p_accel = 0); + void add_submenu_item(const String &p_label, const String &p_submenu, int p_id = -1); void set_item_text(int p_idx, const String &p_text); void set_item_icon(int p_idx, const Ref<Texture> &p_icon); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index c5330c78e1..42cb89b2d6 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -143,6 +143,8 @@ Rect2 RichTextLabel::_get_text_rect() { int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Color &p_font_color_shadow, bool p_shadow_as_outline, const Point2 &shadow_ofs, const Point2i &p_click_pos, Item **r_click_item, int *r_click_char, bool *r_outside, int p_char_count) { + ERR_FAIL_INDEX_V((int)p_mode, 3, 0); + RID ci; if (r_outside) *r_outside = false; @@ -1416,6 +1418,9 @@ bool RichTextLabel::_find_strikethrough(Item *p_item) { } bool RichTextLabel::_find_by_type(Item *p_item, ItemType p_type) { + + ERR_FAIL_INDEX_V((int)p_type, 19, false); + Item *item = p_item; while (item) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e2bb4e3e91..c0b0944e47 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1271,7 +1271,8 @@ void TextEdit::_notification(int p_what) { } // Loop through characters in one line. - for (int j = 0; j < str.length(); j++) { + int j = 0; + for (; j < str.length(); j++) { if (syntax_coloring) { if (color_map.has(last_wrap_column + j)) { @@ -1501,7 +1502,7 @@ void TextEdit::_notification(int p_what) { } } - if (cursor.column == last_wrap_column + str.length() && cursor.line == line && cursor_wrap_index == line_wrap_index && (char_ofs + char_margin) >= xmargin_beg) { + if (cursor.column == (last_wrap_column + j) && cursor.line == line && cursor_wrap_index == line_wrap_index && (char_ofs + char_margin) >= xmargin_beg) { cursor_pos = Point2i(char_ofs + char_margin + ofs_x, ofs_y); cursor_pos.y += (get_row_height() - cache.font->get_height()) / 2; diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp index 423794d8ba..473cee5ca1 100644 --- a/scene/gui/texture_rect.cpp +++ b/scene/gui/texture_rect.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "texture_rect.h" +#include "core/core_string_names.h" #include "servers/visual_server.h" void TextureRect::_notification(int p_what) { @@ -123,6 +124,7 @@ void TextureRect::_bind_methods() { ClassDB::bind_method(D_METHOD("is_flipped_v"), &TextureRect::is_flipped_v); ClassDB::bind_method(D_METHOD("set_stretch_mode", "stretch_mode"), &TextureRect::set_stretch_mode); ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureRect::get_stretch_mode); + ClassDB::bind_method(D_METHOD("_texture_changed"), &TextureRect::_texture_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand"); @@ -140,9 +142,27 @@ void TextureRect::_bind_methods() { BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT_COVERED); } +void TextureRect::_texture_changed() { + + if (texture.is_valid()) { + update(); + minimum_size_changed(); + } +} + void TextureRect::set_texture(const Ref<Texture> &p_tex) { + if (p_tex == texture) + return; + + if (texture.is_valid()) + texture->disconnect(CoreStringNames::get_singleton()->changed, this, "_texture_changed"); + texture = p_tex; + + if (texture.is_valid()) + texture->connect(CoreStringNames::get_singleton()->changed, this, "_texture_changed"); + update(); /* if (texture.is_valid()) diff --git a/scene/gui/texture_rect.h b/scene/gui/texture_rect.h index 1c5bd9d99c..d144020562 100644 --- a/scene/gui/texture_rect.h +++ b/scene/gui/texture_rect.h @@ -56,6 +56,8 @@ private: Ref<Texture> texture; StretchMode stretch_mode; + void _texture_changed(); + protected: void _notification(int p_what); virtual Size2 get_minimum_size() const; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 2fc4be6900..c9d1295557 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -729,6 +729,43 @@ bool TreeItem::is_folding_disabled() const { return disable_folding; } +Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + + if (p_argcount < 1) { + r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 0; + return Variant(); + } + + if (p_args[0]->get_type() != Variant::STRING) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::STRING; + return Variant(); + } + + StringName method = *p_args[0]; + + call_recursive(method, &p_args[1], p_argcount - 1, r_error); + return Variant(); +} + +void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + if (!p_item) { + return; + } + p_item->call(p_method, p_args, p_argcount, r_error); + TreeItem *c = p_item->get_children(); + while (c) { + recursive_call_aux(c, p_method, p_args, p_argcount, r_error); + c = c->get_next(); + } +} + +void TreeItem::call_recursive(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + recursive_call_aux(this, p_method, p_args, p_argcount, r_error); +} + void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_mode", "column", "mode"), &TreeItem::set_cell_mode); @@ -790,6 +827,7 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_color", "column", "color"), &TreeItem::set_custom_color); ClassDB::bind_method(D_METHOD("clear_custom_color", "column"), &TreeItem::clear_custom_color); + ClassDB::bind_method(D_METHOD("get_custom_color", "column"), &TreeItem::get_custom_color); ClassDB::bind_method(D_METHOD("set_custom_bg_color", "column", "color", "just_outline"), &TreeItem::set_custom_bg_color, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_custom_bg_color", "column"), &TreeItem::clear_custom_bg_color); @@ -820,6 +858,14 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disable_folding", "disable"), &TreeItem::set_disable_folding); ClassDB::bind_method(D_METHOD("is_folding_disabled"), &TreeItem::is_folding_disabled); + { + MethodInfo mi; + mi.name = "call_recursive"; + mi.arguments.push_back(PropertyInfo(Variant::STRING, "method")); + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "call_recursive", &TreeItem::_call_recursive_bind, mi); + } + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_folding"), "set_disable_folding", "is_folding_disabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_minimum_height", PROPERTY_HINT_RANGE, "0,1000,1"), "set_custom_minimum_height", "get_custom_minimum_height"); @@ -2677,11 +2723,21 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } break; case BUTTON_WHEEL_UP: { + double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); + if (v_scroll->get_value() != prev_value) { + accept_event(); + } + } break; case BUTTON_WHEEL_DOWN: { + double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); + if (v_scroll->get_value() != prev_value) { + accept_event(); + } + } break; } } @@ -2689,7 +2745,11 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { Ref<InputEventPanGesture> pan_gesture = p_event; if (pan_gesture.is_valid()) { + double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); + if (v_scroll->get_value() != prev_value) { + accept_event(); + } } } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 47befb0c15..361830173b 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -172,6 +172,8 @@ protected: remove_child(Object::cast_to<TreeItem>(p_child)); } + Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Variant::CallError &r_error); + public: /* cell mode */ void set_cell_mode(int p_column, TreeCellMode p_mode); @@ -280,6 +282,8 @@ public: void set_disable_folding(bool p_disable); bool is_folding_disabled() const; + void call_recursive(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); + ~TreeItem(); }; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 418ee6af0e..314fc721fc 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -139,7 +139,6 @@ #include "scene/resources/cylinder_shape.h" #include "scene/resources/default_theme/default_theme.h" #include "scene/resources/dynamic_font.h" -#include "scene/resources/dynamic_font_stb.h" #include "scene/resources/gradient.h" #include "scene/resources/height_map_shape.h" #include "scene/resources/line_shape_2d.h" diff --git a/scene/resources/bit_map.cpp b/scene/resources/bit_map.cpp index e4a64a1de1..b5354bc3e2 100644 --- a/scene/resources/bit_map.cpp +++ b/scene/resources/bit_map.cpp @@ -197,16 +197,14 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) | 4 | 8 | <- current pixel (curx,cury) +---+---+ */ - //NOTE: due to the way we pick points from texture, rect needs to be smaller, otherwise it goes outside 1 pixel - Rect2i fixed_rect = Rect2i(rect.position, rect.size - Size2i(2, 2)); Point2i tl = Point2i(curx - 1, cury - 1); - sv += (fixed_rect.has_point(tl) && get_bit(tl)) ? 1 : 0; + sv += (rect.has_point(tl) && get_bit(tl)) ? 1 : 0; Point2i tr = Point2i(curx, cury - 1); - sv += (fixed_rect.has_point(tr) && get_bit(tr)) ? 2 : 0; + sv += (rect.has_point(tr) && get_bit(tr)) ? 2 : 0; Point2i bl = Point2i(curx - 1, cury); - sv += (fixed_rect.has_point(bl) && get_bit(bl)) ? 4 : 0; + sv += (rect.has_point(bl) && get_bit(bl)) ? 4 : 0; Point2i br = Point2i(curx, cury); - sv += (fixed_rect.has_point(br) && get_bit(br)) ? 8 : 0; + sv += (rect.has_point(br) && get_bit(br)) ? 8 : 0; ERR_FAIL_COND_V(sv == 0 || sv == 15, Vector<Vector2>()); } @@ -300,15 +298,15 @@ Vector<Vector2> BitMap::_march_square(const Rect2i &rect, const Point2i &start) +---+---+ | 4 | | +---+---+ - this normally go RIGHT, but if its coming from UP, it should go LEFT + this normally go RIGHT, but if its coming from RIGHT, it should go LEFT */ if (case6s.has(Point2i(curx, cury))) { - //found, so we go down, and delete from case6s; + //found, so we go left, and delete from case6s; stepx = -1; stepy = 0; case6s.erase(Point2i(curx, cury)); } else { - //not found, we go up, and add to case6s; + //not found, we go right, and add to case6s; stepx = 1; stepy = 0; case6s.insert(Point2i(curx, cury)); @@ -510,12 +508,19 @@ Vector<Vector<Vector2> > BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, fl for (int j = r.position.x; j < r.position.x + r.size.width; j++) { if (!fill->get_bit(Point2(j, i)) && get_bit(Point2(j, i))) { + fill_bits(this, fill, Point2i(j, i), r); + Vector<Vector2> polygon = _march_square(r, Point2i(j, i)); print_verbose("BitMap: Pre reduce: " + itos(polygon.size())); polygon = reduce(polygon, r, p_epsilon); print_verbose("BitMap: Post reduce: " + itos(polygon.size())); + + if (polygon.size() < 3) { + print_verbose("Invalid polygon, skipped"); + continue; + } + polygons.push_back(polygon); - fill_bits(this, fill, Point2i(j, i), r); } } } @@ -525,6 +530,13 @@ Vector<Vector<Vector2> > BitMap::clip_opaque_to_polygons(const Rect2 &p_rect, fl void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { + if (p_pixels == 0) { + return; + } + + bool bit_value = (p_pixels > 0) ? true : false; + p_pixels = Math::abs(p_pixels); + Rect2i r = Rect2i(0, 0, width, height).clip(p_rect); Ref<BitMap> copy; @@ -534,7 +546,7 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { for (int i = r.position.y; i < r.position.y + r.size.height; i++) { for (int j = r.position.x; j < r.position.x + r.size.width; j++) { - if (copy->get_bit(Point2(j, i))) + if (bit_value == get_bit(Point2(j, i))) continue; bool found = false; @@ -542,16 +554,21 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { for (int y = i - p_pixels; y <= i + p_pixels; y++) { for (int x = j - p_pixels; x <= j + p_pixels; x++) { - if (x < p_rect.position.x || x >= p_rect.position.x + p_rect.size.x) - continue; - if (y < p_rect.position.y || y >= p_rect.position.y + p_rect.size.y) - continue; + bool outside = false; + + if ((x < p_rect.position.x) || (x >= p_rect.position.x + p_rect.size.x) || (y < p_rect.position.y) || (y >= p_rect.position.y + p_rect.size.y)) { + // outside of rectangle counts as bit not set + if (!bit_value) + outside = true; + else + continue; + } float d = Point2(j, i).distance_to(Point2(x, y)) - CMP_EPSILON; if (d > p_pixels) continue; - if (copy->get_bit(Point2(x, y))) { + if (outside || (bit_value == copy->get_bit(Point2(x, y)))) { found = true; break; } @@ -561,12 +578,17 @@ void BitMap::grow_mask(int p_pixels, const Rect2 &p_rect) { } if (found) { - set_bit(Point2(j, i), true); + set_bit(Point2(j, i), bit_value); } } } } +void BitMap::shrink_mask(int p_pixels, const Rect2 &p_rect) { + + grow_mask(-p_pixels, p_rect); +} + Array BitMap::_opaque_to_polygons_bind(const Rect2 &p_rect, float p_epsilon) const { Vector<Vector<Vector2> > result = clip_opaque_to_polygons(p_rect, p_epsilon); diff --git a/scene/resources/bit_map.h b/scene/resources/bit_map.h index daf24affb1..b062dd7376 100644 --- a/scene/resources/bit_map.h +++ b/scene/resources/bit_map.h @@ -67,6 +67,7 @@ public: void resize(const Size2 &p_new_size); void grow_mask(int p_pixels, const Rect2 &p_rect); + void shrink_mask(int p_pixels, const Rect2 &p_rect); void blit(const Vector2 &p_pos, const Ref<BitMap> &p_bitmap); Ref<Image> convert_to_image() const; diff --git a/scene/resources/dynamic_font_stb.cpp b/scene/resources/dynamic_font_stb.cpp deleted file mode 100644 index 412bffa5dc..0000000000 --- a/scene/resources/dynamic_font_stb.cpp +++ /dev/null @@ -1,524 +0,0 @@ -/*************************************************************************/ -/* dynamic_font_stb.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#include "dynamic_font_stb.h" - -#ifndef FREETYPE_ENABLED - -#define STB_TRUETYPE_IMPLEMENTATION -#include "core/os/file_access.h" - -void DynamicFontData::lock() { - - fr = font_data.read(); - - if (fr.ptr() != last_data_ptr) { - - last_data_ptr = fr.ptr(); - - if (!stbtt_InitFont(&info, last_data_ptr, 0)) { - valid = false; - } else { - valid = true; - } - - last_data_ptr = fr.ptr(); - } -} - -void DynamicFontData::unlock() { - - fr.release(); -} - -void DynamicFontData::set_font_data(const PoolVector<uint8_t> &p_font) { - //clear caches and stuff - ERR_FAIL_COND(font_data.size()); - font_data = p_font; - - lock(); - - if (valid) { - stbtt_GetFontVMetrics(&info, &ascent, &descent, &linegap); - descent = -descent + linegap; - - for (int i = 32; i < 1024; i++) { - for (int j = 32; j < 1024; j++) { - - int kern = stbtt_GetCodepointKernAdvance(&info, i, j); - if (kern != 0) { - KerningPairKey kpk; - kpk.A = i; - kpk.B = j; - kerning_map[kpk] = kern; - } - } - } - } - - unlock(); - //clear existing stuff - - ERR_FAIL_COND(!valid); -} - -Ref<DynamicFontAtSize> DynamicFontData::_get_dynamic_font_at_size(int p_size) { - - ERR_FAIL_COND_V(!valid, Ref<DynamicFontAtSize>()); - - if (size_cache.has(p_size)) { - return Ref<DynamicFontAtSize>(size_cache[p_size]); - } - - Ref<DynamicFontAtSize> dfas; - dfas.instance(); - - dfas->font = Ref<DynamicFontData>(this); - - size_cache[p_size] = dfas.ptr(); - - dfas->size = p_size; - - lock(); - - dfas->scale = stbtt_ScaleForPixelHeight(&info, p_size); - - unlock(); - - return dfas; -} - -DynamicFontData::DynamicFontData() { - last_data_ptr = NULL; - valid = false; -} - -DynamicFontData::~DynamicFontData() { -} - -//////////////////// - -float DynamicFontAtSize::get_height() const { - - return (font->ascent + font->descent) * scale; -} - -float DynamicFontAtSize::get_ascent() const { - - return font->ascent * scale; -} -float DynamicFontAtSize::get_descent() const { - - return font->descent * scale; -} - -Size2 DynamicFontAtSize::get_char_size(CharType p_char, CharType p_next) const { - - const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - - const Character *c = char_map.getptr(p_char); - ERR_FAIL_COND_V(!c, Size2()); - - Size2 ret(c->advance, get_height()); - - if (p_next) { - DynamicFontData::KerningPairKey kpk; - kpk.A = p_char; - kpk.B = p_next; - - const Map<DynamicFontData::KerningPairKey, int>::Element *K = font->kerning_map.find(kpk); - if (K) { - ret.x += K->get() * scale; - } - } - - return ret; -} - -float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { - - const_cast<DynamicFontAtSize *>(this)->_update_char(p_char); - - const Character *c = char_map.getptr(p_char); - - if (!c) { - return 0; - } - - if (!p_outline) { - Point2 cpos = p_pos; - cpos.x += c->h_align; - cpos.y -= get_ascent(); - cpos.y += c->v_align; - ERR_FAIL_COND_V(c->texture_idx < -1 || c->texture_idx >= textures.size(), 0); - if (c->texture_idx != -1) - VisualServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, Rect2(cpos, c->rect.size), textures[c->texture_idx].texture->get_rid(), c->rect, p_modulate); - } - - //textures[c->texture_idx].texture->draw(p_canvas_item,Vector2()); - - float ret = c->advance; - if (p_next) { - DynamicFontData::KerningPairKey kpk; - kpk.A = p_char; - kpk.B = p_next; - - const Map<DynamicFontData::KerningPairKey, int>::Element *K = font->kerning_map.find(kpk); - if (K) { - ret += K->get() * scale; - } - } - - return ret; -} - -void DynamicFontAtSize::_update_char(CharType p_char) { - - if (char_map.has(p_char)) - return; - - font->lock(); - - int w, h, xofs, yofs; - unsigned char *cpbitmap = stbtt_GetCodepointBitmap(&font->info, scale, scale, p_char, &w, &h, &xofs, &yofs); - - if (!cpbitmap) { - //no glyph - - int advance; - stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); - Character ch; - ch.texture_idx = -1; - ch.advance = advance * scale; - ch.h_align = 0; - ch.v_align = 0; - - char_map[p_char] = ch; - - font->unlock(); - - return; - } - - int mw = w + rect_margin * 2; - int mh = h + rect_margin * 2; - - if (mw > 4096 || mh > 4096) { - - stbtt_FreeBitmap(cpbitmap, NULL); - font->unlock(); - ERR_FAIL_COND(mw > 4096); - ERR_FAIL_COND(mh > 4096); - } - - //find a texture to fit this... - - int tex_index = -1; - int tex_x = 0; - int tex_y = 0; - - for (int i = 0; i < textures.size(); i++) { - - CharTexture &ct = textures[i]; - - if (mw > ct.texture_size || mh > ct.texture_size) //too big for this texture - continue; - - tex_y = 0x7FFFFFFF; - tex_x = 0; - - for (int j = 0; j < ct.texture_size - mw; j++) { - - int max_y = 0; - - for (int k = j; k < j + mw; k++) { - - int y = ct.offsets[k]; - if (y > max_y) - max_y = y; - } - - if (max_y < tex_y) { - tex_y = max_y; - tex_x = j; - } - } - - if (tex_y == 0x7FFFFFFF || tex_y + mh > ct.texture_size) - continue; //fail, could not fit it here - - tex_index = i; - break; - } - - if (tex_index == -1) { - //could not find texture to fit, create one - tex_x = 0; - tex_y = 0; - - int texsize = MAX(size * 8, 256); - if (mw > texsize) - texsize = mw; //special case, adapt to it? - if (mh > texsize) - texsize = mh; //special case, adapt to it? - - texsize = next_power_of_2(texsize); - - texsize = MIN(texsize, 4096); - - CharTexture tex; - tex.texture_size = texsize; - tex.imgdata.resize(texsize * texsize * 2); //grayscale alpha - - { - //zero texture - PoolVector<uint8_t>::Write w = tex.imgdata.write(); - ERR_FAIL_COND(texsize * texsize * 2 > tex.imgdata.size()); - for (int i = 0; i < texsize * texsize * 2; i++) { - w[i] = 0; - } - } - tex.offsets.resize(texsize); - for (int i = 0; i < texsize; i++) //zero offsets - tex.offsets[i] = 0; - - textures.push_back(tex); - tex_index = textures.size() - 1; - } - - //fit character in char texture - - CharTexture &tex = textures[tex_index]; - - { - PoolVector<uint8_t>::Write wr = tex.imgdata.write(); - - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - - int ofs = ((i + tex_y + rect_margin) * tex.texture_size + j + tex_x + rect_margin) * 2; - ERR_FAIL_COND(ofs >= tex.imgdata.size()); - wr[ofs + 0] = 255; //grayscale as 1 - wr[ofs + 1] = cpbitmap[i * w + j]; //alpha as 0 - } - } - } - - //blit to image and texture - { - Ref<Image> img = memnew(Image(tex.texture_size, tex.texture_size, 0, Image::FORMAT_LA8, tex.imgdata)); - - if (tex.texture.is_null()) { - tex.texture.instance(); - tex.texture->create_from_image(img, Texture::FLAG_FILTER); - } else { - tex.texture->set_data(img); //update - } - } - - // update height array - - for (int k = tex_x; k < tex_x + mw; k++) { - - tex.offsets[k] = tex_y + mh; - } - - int advance; - stbtt_GetCodepointHMetrics(&font->info, p_char, &advance, 0); - - Character chr; - chr.h_align = xofs; - chr.v_align = yofs + get_ascent(); - chr.advance = advance * scale; - chr.texture_idx = tex_index; - - chr.rect = Rect2(tex_x + rect_margin, tex_y + rect_margin, w, h); - - char_map[p_char] = chr; - - stbtt_FreeBitmap(cpbitmap, NULL); - - font->unlock(); -} - -DynamicFontAtSize::DynamicFontAtSize() { - - rect_margin = 1; -} - -DynamicFontAtSize::~DynamicFontAtSize() { - - ERR_FAIL_COND(!font.ptr()); - font->size_cache.erase(size); -} - -///////////////////////// - -void DynamicFont::_bind_methods() { - - ClassDB::bind_method(D_METHOD("set_font_data", "data"), &DynamicFont::set_font_data); - ClassDB::bind_method(D_METHOD("get_font_data"), &DynamicFont::get_font_data); - - ClassDB::bind_method(D_METHOD("set_size", "data"), &DynamicFont::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &DynamicFont::get_size); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "font/size"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font/font", PROPERTY_HINT_RESOURCE_TYPE, "DynamicFontData"), "set_font_data", "get_font_data"); -} - -void DynamicFont::set_font_data(const Ref<DynamicFontData> &p_data) { - - data = p_data; - data_at_size = data->_get_dynamic_font_at_size(size); -} - -Ref<DynamicFontData> DynamicFont::get_font_data() const { - - return data; -} - -void DynamicFont::set_size(int p_size) { - - if (size == p_size) - return; - size = p_size; - ERR_FAIL_COND(p_size < 1); - if (!data.is_valid()) - return; - data_at_size = data->_get_dynamic_font_at_size(size); -} -int DynamicFont::get_size() const { - - return size; -} - -float DynamicFont::get_height() const { - - if (!data_at_size.is_valid()) - return 1; - - return data_at_size->get_height(); -} - -float DynamicFont::get_ascent() const { - - if (!data_at_size.is_valid()) - return 1; - - return data_at_size->get_ascent(); -} - -float DynamicFont::get_descent() const { - - if (!data_at_size.is_valid()) - return 1; - - return data_at_size->get_descent(); -} - -Size2 DynamicFont::get_char_size(CharType p_char, CharType p_next) const { - - if (!data_at_size.is_valid()) - return Size2(1, 1); - - return data_at_size->get_char_size(p_char, p_next); -} - -bool DynamicFont::is_distance_field_hint() const { - - return false; -} - -float DynamicFont::draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next, const Color &p_modulate, bool p_outline) const { - - if (!data_at_size.is_valid()) - return 0; - - return data_at_size->draw_char(p_canvas_item, p_pos, p_char, p_next, p_modulate, p_outline); -} - -DynamicFont::DynamicFont() { - - size = 16; -} - -DynamicFont::~DynamicFont() { -} - -///////////////////////// - -RES ResourceFormatLoaderDynamicFont::load(const String &p_path, const String &p_original_path, Error *r_error) { - - if (r_error) - *r_error = ERR_FILE_CANT_OPEN; - - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, RES(), "Cannot load font from file '" + p_path + "'."); - - PoolVector<uint8_t> data; - - data.resize(f->get_len()); - - ERR_FAIL_COND_V(data.size() == 0, RES()); - - { - PoolVector<uint8_t>::Write w = data.write(); - f->get_buffer(w.ptr(), data.size()); - } - - Ref<DynamicFontData> dfd; - dfd.instance(); - dfd->set_font_data(data); - - if (r_error) - *r_error = OK; - - return dfd; -} - -void ResourceFormatLoaderDynamicFont::get_recognized_extensions(List<String> *p_extensions) const { - - p_extensions->push_back("ttf"); -} - -bool ResourceFormatLoaderDynamicFont::handles_type(const String &p_type) const { - - return (p_type == "DynamicFontData"); -} - -String ResourceFormatLoaderDynamicFont::get_resource_type(const String &p_path) const { - - String el = p_path.get_extension().to_lower(); - if (el == "ttf") - return "DynamicFontData"; - return ""; -} - -#endif diff --git a/scene/resources/dynamic_font_stb.h b/scene/resources/dynamic_font_stb.h deleted file mode 100644 index caee6e7e32..0000000000 --- a/scene/resources/dynamic_font_stb.h +++ /dev/null @@ -1,191 +0,0 @@ -/*************************************************************************/ -/* dynamic_font_stb.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifndef DYNAMICFONT_STB_H -#define DYNAMICFONT_STB_H - -#ifndef FREETYPE_ENABLED - -#include "core/io/resource_loader.h" -#include "font.h" - -#include "thirdparty/misc/stb_truetype.h" - -class DynamicFontAtSize; -class DynamicFont; - -class DynamicFontData : public Resource { - - GDCLASS(DynamicFontData, Resource); - - bool valid; - - PoolVector<uint8_t> font_data; - PoolVector<uint8_t>::Read fr; - const uint8_t *last_data_ptr; - - struct KerningPairKey { - - union { - struct { - uint32_t A, B; - }; - - uint64_t pair; - }; - - _FORCE_INLINE_ bool operator<(const KerningPairKey &p_r) const { return pair < p_r.pair; } - }; - - Map<KerningPairKey, int> kerning_map; - - Map<int, DynamicFontAtSize *> size_cache; - - friend class DynamicFontAtSize; - - stbtt_fontinfo info; - int ascent; - int descent; - int linegap; - - void lock(); - void unlock(); - - friend class DynamicFont; - - Ref<DynamicFontAtSize> _get_dynamic_font_at_size(int p_size); - -public: - void set_font_data(const PoolVector<uint8_t> &p_font); - DynamicFontData(); - ~DynamicFontData(); -}; - -class DynamicFontAtSize : public Reference { - - GDCLASS(DynamicFontAtSize, Reference); - - int rect_margin; - - struct CharTexture { - - PoolVector<uint8_t> imgdata; - int texture_size; - Vector<int> offsets; - Ref<ImageTexture> texture; - }; - - Vector<CharTexture> textures; - - struct Character { - - int texture_idx; - Rect2 rect; - float v_align; - float h_align; - float advance; - - Character() { - texture_idx = 0; - v_align = 0; - } - }; - - HashMap<CharType, Character> char_map; - - _FORCE_INLINE_ void _update_char(CharType p_char); - - friend class DynamicFontData; - Ref<DynamicFontData> font; - float scale; - int size; - -protected: -public: - float get_height() const; - - float get_ascent() const; - float get_descent() const; - - Size2 get_char_size(CharType p_char, CharType p_next = 0) const; - - float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; - - DynamicFontAtSize(); - ~DynamicFontAtSize(); -}; - -/////////////// - -class DynamicFont : public Font { - - GDCLASS(DynamicFont, Font); - - Ref<DynamicFontData> data; - Ref<DynamicFontAtSize> data_at_size; - int size; - -protected: - static void _bind_methods(); - -public: - void set_font_data(const Ref<DynamicFontData> &p_data); - Ref<DynamicFontData> get_font_data() const; - - void set_size(int p_size); - int get_size() const; - - virtual float get_height() const; - - virtual float get_ascent() const; - virtual float get_descent() const; - - virtual Size2 get_char_size(CharType p_char, CharType p_next = 0) const; - - virtual bool is_distance_field_hint() const; - - virtual float draw_char(RID p_canvas_item, const Point2 &p_pos, CharType p_char, CharType p_next = 0, const Color &p_modulate = Color(1, 1, 1), bool p_outline = false) const; - - DynamicFont(); - ~DynamicFont(); -}; - -///////////// - -class ResourceFormatLoaderDynamicFont : public ResourceFormatLoader { -public: - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; -}; - -#endif -#endif // DYNAMICFONT_H diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index d15a972358..c2e2f85723 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -117,6 +117,7 @@ void ImageTexture::reload_from_file() { } else { Resource::reload_from_file(); _change_notify(); + emit_changed(); } } @@ -180,6 +181,7 @@ void ImageTexture::_reload_hook(const RID &p_hook) { VisualServer::get_singleton()->texture_set_data(texture, img); _change_notify(); + emit_changed(); } void ImageTexture::create(int p_width, int p_height, Image::Format p_format, uint32_t p_flags) { @@ -190,6 +192,7 @@ void ImageTexture::create(int p_width, int p_height, Image::Format p_format, uin w = p_width; h = p_height; _change_notify(); + emit_changed(); } void ImageTexture::create_from_image(const Ref<Image> &p_image, uint32_t p_flags) { @@ -202,23 +205,23 @@ void ImageTexture::create_from_image(const Ref<Image> &p_image, uint32_t p_flags VisualServer::get_singleton()->texture_allocate(texture, p_image->get_width(), p_image->get_height(), 0, p_image->get_format(), VS::TEXTURE_TYPE_2D, p_flags); VisualServer::get_singleton()->texture_set_data(texture, p_image); _change_notify(); + emit_changed(); image_stored = true; } void ImageTexture::set_flags(uint32_t p_flags) { - /* uint32_t cube = flags & FLAG_CUBEMAP; - if (flags == p_flags&cube) + if (flags == p_flags) return; - flags=p_flags|cube; */ flags = p_flags; if (w == 0 || h == 0) { return; //uninitialized, do not set to texture } VisualServer::get_singleton()->texture_set_flags(texture, p_flags); _change_notify("flags"); + emit_changed(); } uint32_t ImageTexture::get_flags() const { @@ -250,6 +253,8 @@ void ImageTexture::set_data(const Ref<Image> &p_image) { VisualServer::get_singleton()->texture_set_data(texture, p_image); _change_notify(); + emit_changed(); + alpha_cache.unref(); image_stored = true; } @@ -736,6 +741,7 @@ Error StreamTexture::load(const String &p_path) { format = image->get_format(); _change_notify(); + emit_changed(); return OK; } String StreamTexture::get_load_path() const { @@ -827,6 +833,7 @@ void StreamTexture::set_flags(uint32_t p_flags) { flags = p_flags; VS::get_singleton()->texture_set_flags(texture, flags); _change_notify("flags"); + emit_changed(); } void StreamTexture::reload_from_file() { diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 1dba0c5b09..58bbf86241 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -103,6 +103,10 @@ String VisualShaderNode::get_warning(Shader::Mode p_mode, VisualShader::Type p_t return String(); } +String VisualShaderNode::get_input_port_default_hint(int p_port) const { + return ""; +} + void VisualShaderNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_output_port_for_preview", "port"), &VisualShaderNode::set_output_port_for_preview); diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index d9f089586d..8b6b659836 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -201,6 +201,8 @@ public: virtual PortType get_output_port_type(int p_port) const = 0; virtual String get_output_port_name(int p_port) const = 0; + virtual String get_input_port_default_hint(int p_port) const; + void set_output_port_for_preview(int p_index); int get_output_port_for_preview() const; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 24e436e61c..2e58c512b8 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -408,6 +408,13 @@ String VisualShaderNodeTexture::get_output_port_name(int p_port) const { return p_port == 0 ? "rgb" : "alpha"; } +String VisualShaderNodeTexture::get_input_port_default_hint(int p_port) const { + if (p_port == 0) { + return "UV.xy"; + } + return ""; +} + static String make_unique_id(VisualShader::Type p_type, int p_id, const String &p_name) { static const char *typepf[VisualShader::TYPE_MAX] = { "vtx", "frg", "lgt" }; @@ -444,10 +451,9 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (source == SOURCE_TEXTURE) { String id = make_unique_id(p_type, p_id, "tex"); String code; - if (p_input_vars[0] == String()) { //none bound, do nothing - - code += "\tvec4 " + id + "_read = vec4(0.0);\n"; + if (p_input_vars[0] == String()) { // Use UV by default. + code += "\tvec4 " + id + "_read = texture( " + id + " , UV.xy );\n"; } else if (p_input_vars[1] == String()) { //no lod code += "\tvec4 " + id + "_read = texture( " + id + " , " + p_input_vars[0] + ".xy );\n"; @@ -466,9 +472,9 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (id == String()) { code += "\tvec4 " + id + "_tex_read = vec4(0.0);\n"; } else { - if (p_input_vars[0] == String()) { //none bound, do nothing + if (p_input_vars[0] == String()) { // Use UV by default. - code += "\tvec4 " + id + "_tex_read = vec4(0.0);\n"; + code += "\tvec4 " + id + "_tex_read = texture( " + id + " , UV.xy );\n"; } else if (p_input_vars[1] == String()) { //no lod @@ -486,9 +492,9 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (source == SOURCE_SCREEN && (p_mode == Shader::MODE_SPATIAL || p_mode == Shader::MODE_CANVAS_ITEM) && p_type == VisualShader::TYPE_FRAGMENT) { String code = "\t{\n"; - if (p_input_vars[0] == String() || p_for_preview) { //none bound, do nothing + if (p_input_vars[0] == String() || p_for_preview) { // Use UV by default. - code += "\t\tvec4 _tex_read = vec4(0.0);\n"; + code += "\t\tvec4 _tex_read = textureLod( SCREEN_TEXTURE , UV.xy, 0.0 );\n"; } else if (p_input_vars[1] == String()) { //no lod @@ -506,9 +512,9 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (source == SOURCE_2D_TEXTURE && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { String code = "\t{\n"; - if (p_input_vars[0] == String()) { //none bound, do nothing + if (p_input_vars[0] == String()) { // Use UV by default. - code += "\t\tvec4 _tex_read = vec4(0.0);\n"; + code += "\t\tvec4 _tex_read = texture( TEXTURE , UV.xy );\n"; } else if (p_input_vars[1] == String()) { //no lod @@ -526,9 +532,9 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (source == SOURCE_2D_NORMAL && p_mode == Shader::MODE_CANVAS_ITEM && p_type == VisualShader::TYPE_FRAGMENT) { String code = "\t{\n"; - if (p_input_vars[0] == String()) { //none bound, do nothing + if (p_input_vars[0] == String()) { // Use UV by default. - code += "\t\tvec4 _tex_read = vec4(0.0);\n"; + code += "\t\tvec4 _tex_read = texture( NORMAL_TEXTURE , UV.xy );\n"; } else if (p_input_vars[1] == String()) { //no lod @@ -556,9 +562,9 @@ String VisualShaderNodeTexture::generate_code(Shader::Mode p_mode, VisualShader: if (source == SOURCE_DEPTH && p_mode == Shader::MODE_SPATIAL && p_type == VisualShader::TYPE_FRAGMENT) { String code = "\t{\n"; - if (p_input_vars[0] == String()) { //none bound, do nothing + if (p_input_vars[0] == String()) { // Use UV by default. - code += "\t\tfloat _depth = 0.0;\n"; + code += "\t\tfloat _depth = texture( DEPTH_TEXTURE , UV.xy ).r;\n"; } else if (p_input_vars[1] == String()) { //no lod @@ -3128,9 +3134,9 @@ String VisualShaderNodeTextureUniform::generate_code(Shader::Mode p_mode, Visual String id = get_uniform_name(); String code = "\t{\n"; - if (p_input_vars[0] == String()) { //none bound, do nothing + if (p_input_vars[0] == String()) { // Use UV by default. - code += "\t\tvec4 n_tex_read = vec4(0.0);\n"; + code += "\t\tvec4 n_tex_read = texture( " + id + " , UV.xy );\n"; } else if (p_input_vars[1] == String()) { //no lod code += "\t\tvec4 n_tex_read = texture( " + id + " , " + p_input_vars[0] + ".xy );\n"; @@ -3189,6 +3195,13 @@ void VisualShaderNodeTextureUniform::_bind_methods() { BIND_ENUM_CONSTANT(COLOR_DEFAULT_BLACK); } +String VisualShaderNodeTextureUniform::get_input_port_default_hint(int p_port) const { + if (p_port == 0) { + return "UV.xy"; + } + return ""; +} + VisualShaderNodeTextureUniform::VisualShaderNodeTextureUniform() { texture_type = TYPE_DATA; color_default = COLOR_DEFAULT_WHITE; @@ -3283,6 +3296,15 @@ String VisualShaderNodeTextureUniformTriplanar::generate_code(Shader::Mode p_mod return code; } +String VisualShaderNodeTextureUniformTriplanar::get_input_port_default_hint(int p_port) const { + if (p_port == 0) { + return "default"; + } else if (p_port == 1) { + return "default"; + } + return ""; +} + VisualShaderNodeTextureUniformTriplanar::VisualShaderNodeTextureUniformTriplanar() { } diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 1e4608444c..d5ee990191 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -227,6 +227,8 @@ public: virtual PortType get_output_port_type(int p_port) const; virtual String get_output_port_name(int p_port) const; + virtual String get_input_port_default_hint(int p_port) const; + virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const; virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty @@ -1423,6 +1425,7 @@ public: virtual int get_input_port_count() const; virtual PortType get_input_port_type(int p_port) const; virtual String get_input_port_name(int p_port) const; + virtual String get_input_port_default_hint(int p_port) const; virtual int get_output_port_count() const; virtual PortType get_output_port_type(int p_port) const; @@ -1457,6 +1460,8 @@ public: virtual PortType get_input_port_type(int p_port) const; virtual String get_input_port_name(int p_port) const; + virtual String get_input_port_default_hint(int p_port) const; + virtual String generate_global_per_node(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual String generate_global_per_func(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const; virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty diff --git a/scene/resources/world.cpp b/scene/resources/world.cpp index 0ca5d7eb36..2c22f45f9d 100644 --- a/scene/resources/world.cpp +++ b/scene/resources/world.cpp @@ -332,7 +332,9 @@ World::World() { PhysicsServer::get_singleton()->area_set_param(space, PhysicsServer::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/3d/default_gravity", 9.8)); PhysicsServer::get_singleton()->area_set_param(space, PhysicsServer::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF("physics/3d/default_gravity_vector", Vector3(0, -1, 0))); PhysicsServer::get_singleton()->area_set_param(space, PhysicsServer::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF("physics/3d/default_linear_damp", 0.1)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_linear_damp", PropertyInfo(Variant::REAL, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater")); PhysicsServer::get_singleton()->area_set_param(space, PhysicsServer::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/3d/default_angular_damp", 0.1)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/3d/default_angular_damp", PropertyInfo(Variant::REAL, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater")); #ifdef _3D_DISABLED indexer = NULL; diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp index 13b45f58dc..b5743ad416 100644 --- a/scene/resources/world_2d.cpp +++ b/scene/resources/world_2d.cpp @@ -394,7 +394,9 @@ World2D::World2D() { Physics2DServer::get_singleton()->area_set_param(space, Physics2DServer::AREA_PARAM_GRAVITY, GLOBAL_DEF("physics/2d/default_gravity", 98)); Physics2DServer::get_singleton()->area_set_param(space, Physics2DServer::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF("physics/2d/default_gravity_vector", Vector2(0, 1))); Physics2DServer::get_singleton()->area_set_param(space, Physics2DServer::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF("physics/2d/default_linear_damp", 0.1)); - Physics2DServer::get_singleton()->area_set_param(space, Physics2DServer::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/2d/default_angular_damp", 1)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/default_linear_damp", PropertyInfo(Variant::REAL, "physics/2d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater")); + Physics2DServer::get_singleton()->area_set_param(space, Physics2DServer::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF("physics/2d/default_angular_damp", 1.0)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/2d/default_angular_damp", PropertyInfo(Variant::REAL, "physics/2d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater")); indexer = memnew(SpatialIndexer2D); } diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp index 1b9c3e2eff..ae99d64eee 100644 --- a/servers/visual/shader_language.cpp +++ b/servers/visual/shader_language.cpp @@ -2914,6 +2914,16 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons bool ok = _parse_function_arguments(p_block, p_builtin_types, func, &carg); + // Check if block has a variable with the same name as function to prevent shader crash. + ShaderLanguage::BlockNode *bnode = p_block; + while (bnode) { + if (bnode->variables.has(name)) { + _set_error("Expected function name"); + return NULL; + } + bnode = bnode->parent_block; + } + //test if function was parsed first for (int i = 0; i < shader->functions.size(); i++) { if (shader->functions[i].name == name) { @@ -3842,9 +3852,12 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui } StringName name = tk.text; - if (_find_identifier(p_block, p_builtin_types, name)) { - _set_error("Redefinition of '" + String(name) + "'"); - return ERR_PARSE_ERROR; + ShaderLanguage::IdentifierType itype; + if (_find_identifier(p_block, p_builtin_types, name, (ShaderLanguage::DataType *)0, &itype)) { + if (itype != IDENTIFIER_FUNCTION) { + _set_error("Redefinition of '" + String(name) + "'"); + return ERR_PARSE_ERROR; + } } BlockNode::Variable var; @@ -4002,6 +4015,11 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui return ERR_PARSE_ERROR; } + if (node->is_const && n->type == Node::TYPE_OPERATOR && ((OperatorNode *)n)->op == OP_CALL) { + _set_error("Expected constant expression"); + return ERR_PARSE_ERROR; + } + if (var.type != n->get_datatype()) { _set_error("Invalid assignment of '" + get_datatype_name(n->get_datatype()) + "' to '" + get_datatype_name(var.type) + "'"); return ERR_PARSE_ERROR; @@ -4062,7 +4080,10 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui Node *n = _parse_and_reduce_expression(p_block, p_builtin_types); if (!n) return ERR_PARSE_ERROR; - + if (node->is_const && n->type == Node::TYPE_OPERATOR && ((OperatorNode *)n)->op == OP_CALL) { + _set_error("Expected constant expression after '='"); + return ERR_PARSE_ERROR; + } decl.initializer = n; if (var.type != n->get_datatype()) { @@ -5121,9 +5142,12 @@ Error ShaderLanguage::_parse_shader(const Map<StringName, FunctionInfo> &p_funct pname = tk.text; - if (_find_identifier(func_node->body, builtin_types, pname)) { - _set_error("Redefinition of '" + String(pname) + "'"); - return ERR_PARSE_ERROR; + ShaderLanguage::IdentifierType itype; + if (_find_identifier(func_node->body, builtin_types, pname, (ShaderLanguage::DataType *)0, &itype)) { + if (itype != IDENTIFIER_FUNCTION) { + _set_error("Redefinition of '" + String(pname) + "'"); + return ERR_PARSE_ERROR; + } } FunctionNode::Argument arg; arg.type = ptype; @@ -5348,9 +5372,7 @@ Error ShaderLanguage::complete(const String &p_code, const Map<StringName, Funct nodes = NULL; shader = alloc_node<ShaderNode>(); - Error err = _parse_shader(p_functions, p_render_modes, p_shader_types); - if (err != OK) - ERR_PRINT("Failed to parse shader"); + _parse_shader(p_functions, p_render_modes, p_shader_types); switch (completion_type) { diff --git a/thirdparty/README.md b/thirdparty/README.md index 3f2fc6d8f9..7c7f331657 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -374,10 +374,6 @@ Collection of single-file libraries used in Godot components. * Upstream: https://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps * Version: 1.0 * License: zlib -- `stb_truetype.h` - * Upstream: https://github.com/nothings/stb - * Version: 1.21 - * License: Public Domain (Unlicense) or MIT - `stb_vorbis.c` * Upstream: https://github.com/nothings/stb * Version: 1.16 diff --git a/thirdparty/misc/stb_truetype.h b/thirdparty/misc/stb_truetype.h deleted file mode 100644 index 72299ea86d..0000000000 --- a/thirdparty/misc/stb_truetype.h +++ /dev/null @@ -1,4882 +0,0 @@ -// stb_truetype.h - v1.21 - public domain -// authored from 2009-2016 by Sean Barrett / RAD Game Tools -// -// 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 -// Cass Everitt Martins Mozeiko -// stoiko (Haemimont Games) Cap Petschulat -// Brian Hook Omar Cornut -// Walter van Niftrik github:aloucks -// 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. github:oyvindjam -// Brian Costabile github:vassvik -// -// VERSION HISTORY -// -// 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 <current_point, baseline>. I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1). -// -// Advancing for the next character: -// Call GlyphHMetrics, and compute 'current_point += SF * advance'. -// -// -// ADVANCED USAGE -// -// Quality: -// -// - Use the functions with Subpixel at the end to allow your characters -// to have subpixel positioning. Since the font is anti-aliased, not -// hinted, this is very import for quality. (This is not possible with -// baked fonts.) -// -// - Kerning is now supported, and if you're supporting subpixel rendering -// then kerning is worth using to give your text a polished look. -// -// Performance: -// -// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; -// if you don't do this, stb_truetype is forced to do the conversion on -// every call. -// -// - There are a lot of memory allocations. We should modify it to take -// a temp buffer and allocate from the temp buffer (without freeing), -// should help performance a lot. -// -// NOTES -// -// The system uses the raw data found in the .ttf file without changing it -// and without building auxiliary data structures. This is a bit inefficient -// on little-endian systems (the data is big-endian), but assuming you're -// caching the bitmaps or glyph shapes this shouldn't be a big deal. -// -// It appears to be very hard to programmatically determine what font a -// given file is in a general way. I provide an API for this, but I don't -// recommend it. -// -// -// PERFORMANCE MEASUREMENTS FOR 1.06: -// -// 32-bit 64-bit -// Previous release: 8.83 s 7.68 s -// Pool allocations: 7.72 s 6.34 s -// Inline sort : 6.54 s 5.65 s -// New rasterizer : 5.63 s 5.00 s - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// SAMPLE PROGRAMS -//// -// -// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless -// -#if 0 -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -unsigned char ttf_buffer[1<<20]; -unsigned char temp_bitmap[512*512]; - -stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs -GLuint ftex; - -void my_stbtt_initfont(void) -{ - fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); - stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! - // can free ttf_buffer at this point - glGenTextures(1, &ftex); - glBindTexture(GL_TEXTURE_2D, ftex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); - // can free temp_bitmap at this point - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -} - -void my_stbtt_print(float x, float y, char *text) -{ - // assume orthographic projection with units = screen pixels, origin at top left - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, ftex); - glBegin(GL_QUADS); - while (*text) { - if (*text >= 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 <stdio.h> -#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 <math.h> - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include <math.h> - #define STBTT_sqrt(x) sqrt(x) - #define STBTT_pow(x,y) pow(x,y) - #endif - - #ifndef STBTT_fmod - #include <math.h> - #define STBTT_fmod(x,y) fmod(x,y) - #endif - - #ifndef STBTT_cos - #include <math.h> - #define STBTT_cos(x) cos(x) - #define STBTT_acos(x) acos(x) - #endif - - #ifndef STBTT_fabs - #include <math.h> - #define STBTT_fabs(x) fabs(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include <stdlib.h> - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) ((void)(u),free(x)) - #endif - - #ifndef STBTT_assert - #include <assert.h> - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include <string.h> - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include <string.h> - #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; // 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 - - -////////////////////////////////////////////////////////////////////////////// -// -// 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 - -////////////////////////////////////////////////////////////////////////////// -// -// 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); -} - -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; - - // 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 == -1) { - // 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 if (numberOfContours < 0) { - // @TODO other compound variations? - STBTT_assert(0); - } 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)); - } -} - -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<lookupCount; ++i) { - stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); - stbtt_uint8 *lookupTable = lookupList + lookupOffset; - - stbtt_uint16 lookupType = ttUSHORT(lookupTable); - stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); - stbtt_uint8 *subTableOffsets = lookupTable + 6; - switch(lookupType) { - case 2: { // Pair Adjustment Positioning Subtable - stbtt_int32 sti; - for (sti=0; sti<subTableCount; sti++) { - stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); - stbtt_uint8 *table = lookupTable + subtableOffset; - stbtt_uint16 posFormat = ttUSHORT(table); - stbtt_uint16 coverageOffset = ttUSHORT(table + 2); - stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1); - if (coverageIndex == -1) continue; - - switch (posFormat) { - case 1: { - stbtt_int32 l, r, m; - int straw, needle; - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - stbtt_int32 valueRecordPairSizeInBytes = 2; - stbtt_uint16 pairSetCount = ttUSHORT(table + 8); - stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex); - stbtt_uint8 *pairValueTable = table + pairPosOffset; - stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); - stbtt_uint8 *pairValueArray = pairValueTable + 2; - // 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; - - STBTT_assert(coverageIndex < pairSetCount); - STBTT__NOTUSED(pairSetCount); - - needle=glyph2; - r=pairValueCount-1; - l=0; - - // Binary search. - while (l <= r) { - stbtt_uint16 secondGlyph; - stbtt_uint8 *pairValue; - m = (l + r) >> 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); - - 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); -} - -////////////////////////////////////////////////////////////////////////////// -// -// 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 && mid<n: 0>n => n; 0<n => 0 */ - /* 0<mid && mid>n: 0>n => 0; 0<n => 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; - - 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) { - 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); - } - ++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, 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; - } 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 one scale is 0, use same scale for both - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) return NULL; // if both scales are 0, return NULL - scale_y = scale_x; - } - - 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. ------------------------------------------------------------------------------- -*/ |