diff --git a/CMakeLists.txt b/CMakeLists.txt index dd63b7d45..f20c66fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ if(MEGA) set(LOVE_LINK_LIBRARIES ${MEGA_FREETYPE} + ${MEGA_HARFBUZZ} ${MEGA_LIBOGG} ${MEGA_LIBVORBISFILE} ${MEGA_LIBVORBIS} @@ -173,6 +174,7 @@ Please see https://github.com/love2d/megasource endif() find_package(Freetype REQUIRED) + find_package(harfbuzz REQUIRED) find_package(ModPlug REQUIRED) find_package(OpenAL REQUIRED) find_package(OpenGL REQUIRED) @@ -200,6 +202,7 @@ Please see https://github.com/love2d/megasource ${OPENGL_gl_LIBRARY} ${SDL2_LIBRARY} ${FREETYPE_LIBRARY} + ${HARFBUZZ_LIBRARY} ${OPENAL_LIBRARY} ${MODPLUG_LIBRARY} ${THEORA_LIBRARY} @@ -477,12 +480,16 @@ set(LOVE_SRC_MODULE_FONT_ROOT src/modules/font/BMFontRasterizer.h src/modules/font/Font.cpp src/modules/font/Font.h + src/modules/font/GenericShaper.cpp + src/modules/font/GenericShaper.h src/modules/font/GlyphData.cpp src/modules/font/GlyphData.h src/modules/font/ImageRasterizer.cpp src/modules/font/ImageRasterizer.h src/modules/font/Rasterizer.cpp src/modules/font/Rasterizer.h + src/modules/font/TextShaper.cpp + src/modules/font/TextShaper.h src/modules/font/TrueTypeRasterizer.cpp src/modules/font/TrueTypeRasterizer.h src/modules/font/wrap_Font.cpp @@ -496,6 +503,8 @@ set(LOVE_SRC_MODULE_FONT_ROOT set(LOVE_SRC_MODULE_FONT_FREETYPE src/modules/font/freetype/Font.cpp src/modules/font/freetype/Font.h + src/modules/font/freetype/HarfbuzzShaper.cpp + src/modules/font/freetype/HarfbuzzShaper.h src/modules/font/freetype/TrueTypeRasterizer.cpp src/modules/font/freetype/TrueTypeRasterizer.h ) diff --git a/platform/unix/configure.ac b/platform/unix/configure.ac index fd639a91e..210d54d41 100644 --- a/platform/unix/configure.ac +++ b/platform/unix/configure.ac @@ -67,7 +67,10 @@ ACLOVE_DEP_PTHREAD # Conditional dependencies AS_VAR_IF([enable_module_audio], [yes], [ACLOVE_DEP_OPENAL], []) -AS_VAR_IF([enable_module_font], [yes], [ACLOVE_DEP_FREETYPE2], []) +AS_VAR_IF([enable_module_font], [yes], [ + ACLOVE_DEP_FREETYPE2 + ACLOVE_DEP_HARFBUZZ +], []) AS_VAR_IF([enable_module_sound], [yes], [ ACLOVE_DEP_LIBMODPLUG ACLOVE_DEP_VORBISFILE diff --git a/platform/unix/debian/control.in b/platform/unix/debian/control.in index e2dc757fc..e435df0ff 100644 --- a/platform/unix/debian/control.in +++ b/platform/unix/debian/control.in @@ -8,12 +8,13 @@ Build-Depends: debhelper (>= 9), libtool, g++ (>= 4.7.0), libfreetype6-dev, + libharfbuzz-dev, luajit, libluajit-5.1-dev, libmodplug-dev, libopenal-dev, libphysfs-dev, - libsdl2-dev (>= 2.0.1), + libsdl2-dev (>= 2.0.9), libogg-dev, libvorbis-dev, libtheora-dev, diff --git a/platform/unix/deps.m4 b/platform/unix/deps.m4 index e087a88e8..868bd0244 100644 --- a/platform/unix/deps.m4 +++ b/platform/unix/deps.m4 @@ -1,6 +1,9 @@ AC_DEFUN([ACLOVE_DEP_FREETYPE2], [ PKG_CHECK_MODULES([freetype2], [freetype2], [], [LOVE_MSG_ERROR([FreeType2])])]) +AC_DEFUN([ACLOVE_DEP_HARFBUZZ], [ + PKG_CHECK_MODULES([harfbuzz], [harfbuzz], [], [LOVE_MSG_ERROR([Harfbuzz])])]) + AC_DEFUN([ACLOVE_DEP_OPENAL], [ PKG_CHECK_MODULES([openal], [openal], [], [LOVE_MSG_ERROR([OpenAL])])]) diff --git a/platform/unix/genmodules b/platform/unix/genmodules index 78551842b..3d7cdca0d 100644 --- a/platform/unix/genmodules +++ b/platform/unix/genmodules @@ -107,7 +107,7 @@ inc_libraries="$inc_current/libraries" cat > src/Makefile.am << EOF AM_CPPFLAGS = -I$inc_current -I$inc_modules -I$inc_libraries -I$inc_libraries/enet/libenet/include -I$inc_libraries/box2d \$(LOVE_INCLUDES) \$(FILE_OFFSET)\ - \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS)\ + \$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS) \$(harfbuzz_CFLAGS)\ \$(openal_CFLAGS) \$(zlib_CFLAGS) \$(libmodplug_CFLAGS)\ \$(vorbisfile_CFLAGS) \$(theora_CFLAGS) AUTOMAKE_OPTIONS = subdir-objects @@ -139,7 +139,7 @@ endif lib_LTLIBRARIES = liblove${love_suffix}.la liblove${love_amsuffix}_la_LDFLAGS = -module -export-dynamic \$(LDFLAGS) -release \$(PACKAGE_VERSION) liblove${love_amsuffix}_la_LIBADD = \ - \$(SDL_LIBS) \$(freetype2_LIBS) \$(lua_LIBS)\ + \$(SDL_LIBS) \$(freetype2_LIBS) \$(harfbuzz_LIBS) \$(lua_LIBS)\ \$(openal_LIBS) \$(zlib_LIBS) \$(libmodplug_LIBS)\ \$(vorbisfile_LIBS) \$(theora_LIBS) diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 43bdc7230..eef3d95b9 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -50,6 +50,17 @@ 217DFC101D9F6D490055D849 /* url.lua.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD41D9F6D490055D849 /* url.lua.h */; }; 217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; }; 217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; }; + D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; }; + D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */; }; + D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; + D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */; }; + D9DAB9292961F10000C64820 /* GenericShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9252961F0FF00C64820 /* GenericShaper.h */; }; + D9DAB92A2961F10000C64820 /* GenericShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */; }; + D9DAB92B2961F10000C64820 /* GenericShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */; }; + D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */ = {isa = PBXBuildFile; fileRef = D9DAB9272961F0FF00C64820 /* TextShaper.h */; }; + D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; + D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D9DAB9282961F10000C64820 /* TextShaper.cpp */; }; + D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9312963CD7500C64820 /* harfbuzz.framework */; }; FA0A3A5F23366CE9001C269E /* floattypes.h in Headers */ = {isa = PBXBuildFile; fileRef = FA0A3A5D23366CE9001C269E /* floattypes.h */; }; FA0A3A6023366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; FA0A3A6123366CE9001C269E /* floattypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA0A3A5E23366CE9001C269E /* floattypes.cpp */; }; @@ -1391,6 +1402,14 @@ 217DFBD41D9F6D490055D849 /* url.lua.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = url.lua.h; sourceTree = ""; }; 217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = ""; }; 217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = ""; }; + D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = ""; }; + D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HarfbuzzShaper.h; sourceTree = ""; }; + D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HarfbuzzShaper.cpp; sourceTree = ""; }; + D9DAB9252961F0FF00C64820 /* GenericShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GenericShaper.h; sourceTree = ""; }; + D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GenericShaper.cpp; sourceTree = ""; }; + D9DAB9272961F0FF00C64820 /* TextShaper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextShaper.h; sourceTree = ""; }; + D9DAB9282961F10000C64820 /* TextShaper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextShaper.cpp; sourceTree = ""; }; + D9DAB9312963CD7500C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = ""; }; FA08F5AE16C7525600F007B5 /* liblove-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "liblove-macosx.plist"; path = "macosx/liblove-macosx.plist"; sourceTree = ""; }; FA0A3A5D23366CE9001C269E /* floattypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = floattypes.h; sourceTree = ""; }; FA0A3A5E23366CE9001C269E /* floattypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = floattypes.cpp; sourceTree = ""; }; @@ -2288,6 +2307,7 @@ buildActionMask = 2147483647; files = ( FACFB751276D7E3B0089F78D /* freetype.xcframework in Frameworks */, + D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */, FA84DE7A277D4C88002674C6 /* modplug.xcframework in Frameworks */, FA84DE7C277E045E002674C6 /* ogg.xcframework in Frameworks */, FACFB753276D7F860089F78D /* Lua.xcframework in Frameworks */, @@ -2311,6 +2331,7 @@ FA577AC516C7513400860150 /* libmodplug.framework in Frameworks */, FADF4CC62663D0EC004F95C1 /* libz.tbd in Frameworks */, FA577AC816C7513C00860150 /* ogg.framework in Frameworks */, + D9DAB9322963CD7500C64820 /* harfbuzz.framework in Frameworks */, FA577ACA16C7514100860150 /* OpenGL.framework in Frameworks */, FA577ACD16C7514C00860150 /* vorbis.framework in Frameworks */, ); @@ -2810,6 +2831,8 @@ FA0B7B731A95902C000E1D17 /* Font.cpp */, FA0B7B741A95902C000E1D17 /* Font.h */, FA0B7B751A95902C000E1D17 /* freetype */, + D9DAB9262961F0FF00C64820 /* GenericShaper.cpp */, + D9DAB9252961F0FF00C64820 /* GenericShaper.h */, FA0B7B7A1A95902C000E1D17 /* GlyphData.cpp */, FA0B7B7B1A95902C000E1D17 /* GlyphData.h */, FA0B7B7C1A95902C000E1D17 /* ImageRasterizer.cpp */, @@ -2817,6 +2840,8 @@ FA522D5923FA5ED40059EE3C /* NotoSans-Regular.ttf.gzip.h */, FA0B7B7E1A95902C000E1D17 /* Rasterizer.cpp */, FA0B7B7F1A95902C000E1D17 /* Rasterizer.h */, + D9DAB9282961F10000C64820 /* TextShaper.cpp */, + D9DAB9272961F0FF00C64820 /* TextShaper.h */, FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */, FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */, FA0B7B811A95902C000E1D17 /* wrap_Font.cpp */, @@ -2834,6 +2859,8 @@ children = ( FA0B7B761A95902C000E1D17 /* Font.cpp */, FA0B7B771A95902C000E1D17 /* Font.h */, + D9DAB9212961F0EE00C64820 /* HarfbuzzShaper.cpp */, + D9DAB9202961F0EE00C64820 /* HarfbuzzShaper.h */, FA0B7B781A95902C000E1D17 /* TrueTypeRasterizer.cpp */, FA0B7B791A95902C000E1D17 /* TrueTypeRasterizer.h */, ); @@ -3366,6 +3393,7 @@ FA577A7916C71A1700860150 /* Cocoa.framework */, FAA627CD18E7E1560080752D /* CoreServices.framework */, FAD43ECB1FF312D800831BB8 /* freetype.framework */, + D9DAB9312963CD7500C64820 /* harfbuzz.framework */, FA577A8216C71A5300860150 /* libmodplug.framework */, FADF4CC52663D0EC004F95C1 /* libz.tbd */, FA577A6D16C719EA00860150 /* Lua.framework */, @@ -3533,6 +3561,7 @@ FA5D24A31A96D2C300C6FC8F /* ios */ = { isa = PBXGroup; children = ( + D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */, FACFB750276D7E2B0089F78D /* freetype.xcframework */, FACFB752276D7F6F0089F78D /* Lua.xcframework */, FA84DE79277D4C88002674C6 /* modplug.xcframework */, @@ -4100,6 +4129,7 @@ FABDA9B82552448300B5C523 /* b2_motor_joint.h in Headers */, FA0B7AC11A958EA3000E1D17 /* callbacks.h in Headers */, FA3C5E491F8D80CA0003C579 /* ShaderStage.h in Headers */, + D9DAB9292961F10000C64820 /* GenericShaper.h in Headers */, FA0B7D8F1A95902C000E1D17 /* ddsHandler.h in Headers */, FAB2D5AC1AABDD8A008224A4 /* TrueTypeRasterizer.h in Headers */, FABDAA042552448300B5C523 /* b2_edge_shape.h in Headers */, @@ -4137,6 +4167,7 @@ FA0B7EDD1A95902D000E1D17 /* Touch.h in Headers */, FA0B7EDE1A95902D000E1D17 /* Touch.h in Headers */, FAC7CD861FE35E95006A60C7 /* physfs.h in Headers */, + D9DAB92C2961F10000C64820 /* TextShaper.h in Headers */, FAF6C9E523C2DE2900D7B5BC /* spirv.hpp in Headers */, FA522D4F23F9FE380059EE3C /* MP3Decoder.h in Headers */, 217DFBEE1D9F6D490055D849 /* luasocket.h in Headers */, @@ -4181,6 +4212,7 @@ FAF6C9E823C2DE2900D7B5BC /* GLSL.ext.EXT.h in Headers */, FACA02F71F5E396B0084B28F /* wrap_DataModule.h in Headers */, FABDA9BE2552448300B5C523 /* Box2D.h in Headers */, + D9DAB9222961F0EE00C64820 /* HarfbuzzShaper.h in Headers */, FA56AA3A1FAFF02000A43D5F /* memory.h in Headers */, FA0B7E441A95902C000E1D17 /* wrap_CircleShape.h in Headers */, FA0B7EB41A95902C000E1D17 /* System.h in Headers */, @@ -4660,6 +4692,7 @@ FADF540E1E3D7CDD00012CC0 /* wrap_Video.cpp in Sources */, FA0B7D4C1A95902C000E1D17 /* Shader.cpp in Sources */, FA0B792A1A958E3B000E1D17 /* Matrix.cpp in Sources */, + D9DAB92B2961F10000C64820 /* GenericShaper.cpp in Sources */, FAF140981E20934C00F898D2 /* PpTokens.cpp in Sources */, FAF140AA1E20934C00F898D2 /* SymbolTable.cpp in Sources */, FABDA9892552448300B5C523 /* b2_contact.cpp in Sources */, @@ -4778,6 +4811,7 @@ FA59A2D31C06481400328DBA /* ParticleSystem.cpp in Sources */, FA0B7E131A95902C000E1D17 /* GearJoint.cpp in Sources */, FABDA99B2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, + D9DAB9242961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */, FA0B7DC21A95902C000E1D17 /* wrap_Joystick.cpp in Sources */, FA0B7CD41A95902C000E1D17 /* Source.cpp in Sources */, FAA3A9AF1B7D465A00CED060 /* android.cpp in Sources */, @@ -4926,6 +4960,7 @@ FABDA9FE2552448300B5C523 /* b2_edge_shape.cpp in Sources */, FA0B7D7A1A95902C000E1D17 /* Quad.cpp in Sources */, FA620A3B1AA305F6005DB4C2 /* types.cpp in Sources */, + D9DAB92E2961F10000C64820 /* TextShaper.cpp in Sources */, FA0B7DD41A95902C000E1D17 /* BezierCurve.cpp in Sources */, FA0B7E7C1A95902C000E1D17 /* wrap_World.cpp in Sources */, FAF6C9F923C2DE2900D7B5BC /* doc.cpp in Sources */, @@ -5087,6 +5122,7 @@ FA0B7EA31A95902C000E1D17 /* SoundData.cpp in Sources */, FA0B79291A958E3B000E1D17 /* Matrix.cpp in Sources */, FA8951A21AA2EDF300EC385A /* wrap_Event.cpp in Sources */, + D9DAB92A2961F10000C64820 /* GenericShaper.cpp in Sources */, FAF140691E20934C00F898D2 /* glslang_tab.cpp in Sources */, FA0B7ABF1A958EA3000E1D17 /* host.c in Sources */, FA0B7D4B1A95902C000E1D17 /* Shader.cpp in Sources */, @@ -5205,6 +5241,7 @@ FAF140551E20934C00F898D2 /* Link.cpp in Sources */, FABDA9792552448200B5C523 /* b2_joint.cpp in Sources */, FAF140841E20934C00F898D2 /* ParseHelper.cpp in Sources */, + D9DAB9232961F0EE00C64820 /* HarfbuzzShaper.cpp in Sources */, FA0B7D7F1A95902C000E1D17 /* Volatile.cpp in Sources */, FA1BA0B11E16FD0800AA2803 /* Shader.cpp in Sources */, FABDA99A2552448300B5C523 /* b2_polygon_contact.cpp in Sources */, @@ -5353,6 +5390,7 @@ FA0B7E881A95902C000E1D17 /* Decoder.cpp in Sources */, FA0B7E3C1A95902C000E1D17 /* wrap_Body.cpp in Sources */, FA0B7D791A95902C000E1D17 /* Quad.cpp in Sources */, + D9DAB92D2961F10000C64820 /* TextShaper.cpp in Sources */, FABDA9FD2552448300B5C523 /* b2_edge_shape.cpp in Sources */, FAC756F51E4F99B400B91289 /* Effect.cpp in Sources */, FA620A3A1AA305F6005DB4C2 /* types.cpp in Sources */, @@ -5716,6 +5754,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5751,6 +5790,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; @@ -5787,6 +5827,7 @@ "$(PROJECT_DIR)/macosx/Frameworks/freetype.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/Lua.framework/Headers", "$(PROJECT_DIR)/macosx/Frameworks/SDL2.framework/Headers", + "$(PROJECT_DIR)/macosx/Frameworks/harfbuzz.framework/Headers", ); INFOPLIST_FILE = "macosx/liblove-macosx.plist"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; diff --git a/platform/xcode/love.xcodeproj/project.pbxproj b/platform/xcode/love.xcodeproj/project.pbxproj index e91c5bec8..0d7b4911a 100644 --- a/platform/xcode/love.xcodeproj/project.pbxproj +++ b/platform/xcode/love.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ A93E6EED10420BA8007D418B /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; }; A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F16926109E7BAD00FC83D1 /* libmodplug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CE73F8001EEB64150052DAB3 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */; }; + D9DAB9372963CF6900C64820 /* harfbuzz.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D9DAB9352963CF5F00C64820 /* harfbuzz.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; FA0797991BF480A200034B7C /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0797981BF480A200034B7C /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; FA08F69616C766E000F007B5 /* love.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; }; FA08F69716C766E700F007B5 /* love.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -87,6 +88,7 @@ FAD4B1731C1F50A3004CF150 /* theora.framework in Copy Frameworks */, FAAFF04716CB120000CCDE45 /* OpenAL-Soft.framework in Copy Frameworks */, FAD43ED01FF3136500831BB8 /* freetype.framework in Copy Frameworks */, + D9DAB9372963CF6900C64820 /* harfbuzz.framework in Copy Frameworks */, A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */, A9255F58104324E100BA1496 /* ogg.framework in Copy Frameworks */, A9255E031043195A00BA1496 /* vorbis.framework in Copy Frameworks */, @@ -108,6 +110,7 @@ A97E3842132A9EDE00198A2F /* love-macosx.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "love-macosx.plist"; path = "macosx/love-macosx.plist"; sourceTree = ""; }; A9F16926109E7BAD00FC83D1 /* libmodplug.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libmodplug.framework; path = macosx/Frameworks/libmodplug.framework; sourceTree = ""; }; CE73F7FF1EEB64150052DAB3 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/AVFoundation.framework; sourceTree = DEVELOPER_DIR; }; + D9DAB9352963CF5F00C64820 /* harfbuzz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = harfbuzz.framework; path = macosx/Frameworks/harfbuzz.framework; sourceTree = ""; }; FA0797981BF480A200034B7C /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.1.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; }; FA08F69116C765A200F007B5 /* love.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = love.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FA0B7F061A95AAF3000E1D17 /* love.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = love.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -233,6 +236,7 @@ children = ( 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, FAD43ECF1FF3133700831BB8 /* freetype.framework */, + D9DAB9352963CF5F00C64820 /* harfbuzz.framework */, A9F16926109E7BAD00FC83D1 /* libmodplug.framework */, FA08F69116C765A200F007B5 /* love.framework */, A93E6E5310420B57007D418B /* Lua.framework */, diff --git a/src/modules/font/BMFontRasterizer.cpp b/src/modules/font/BMFontRasterizer.cpp index 4d734a31c..660c4ce35 100644 --- a/src/modules/font/BMFontRasterizer.cpp +++ b/src/modules/font/BMFontRasterizer.cpp @@ -20,6 +20,7 @@ // LOVE #include "BMFontRasterizer.h" +#include "GenericShaper.h" #include "filesystem/Filesystem.h" #include "image/Image.h" @@ -164,6 +165,14 @@ BMFontRasterizer::~BMFontRasterizer() void BMFontRasterizer::parseConfig(const std::string &configtext) { + { + BMFontCharacter nullchar = {}; + nullchar.page = -1; + nullchar.glyph = 0; + characters.push_back(nullchar); + characterIndices[0] = (int)characters.size() - 1; + } + std::stringstream ss(configtext); std::string line; @@ -237,7 +246,10 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) c.metrics.bearingY = -cline.getAttributeInt("yoffset"); c.metrics.advance = cline.getAttributeInt("xadvance"); - characters[id] = c; + c.glyph = id; + + characters.push_back(c); + characterIndices[id] = (int) characters.size() - 1; } else if (tag == "kerning") { @@ -257,13 +269,15 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) bool guessheight = lineHeight == 0; // Verify the glyph character attributes. - for (const auto &cpair : characters) + for (const auto &c : characters) { - const BMFontCharacter &c = cpair.second; + if (c.glyph == 0) + continue; + int width = c.metrics.width; int height = c.metrics.height; - if (!unicode && cpair.first > 127) + if (!unicode && c.glyph > 127) throw love::Exception("Invalid BMFont character id (only unicode and ASCII are supported)"); if (c.page < 0 || images[c.page].get() == nullptr) @@ -272,13 +286,13 @@ void BMFontRasterizer::parseConfig(const std::string &configtext) const image::ImageData *id = images[c.page].get(); if (!id->inside(c.x, c.y)) - throw love::Exception("Invalid coordinates for BMFont character %u.", cpair.first); + throw love::Exception("Invalid coordinates for BMFont character %u.", c.glyph); if (width > 0 && !id->inside(c.x + width - 1, c.y)) - throw love::Exception("Invalid width %d for BMFont character %u.", width, cpair.first); + throw love::Exception("Invalid width %d for BMFont character %u.", width, c.glyph); if (height > 0 && !id->inside(c.x, c.y + height - 1)) - throw love::Exception("Invalid height %d for BMFont character %u.", height, cpair.first); + throw love::Exception("Invalid height %d for BMFont character %u.", height, c.glyph); if (guessheight) lineHeight = std::max(lineHeight, c.metrics.height); @@ -292,22 +306,37 @@ int BMFontRasterizer::getLineHeight() const return lineHeight; } -GlyphData *BMFontRasterizer::getGlyphData(uint32 glyph) const +int BMFontRasterizer::getGlyphSpacing(uint32 glyph) const { - auto it = characters.find(glyph); + auto it = characterIndices.find(glyph); + if (it == characterIndices.end()) + return 0; + return characters[it->second].metrics.advance; +} + +int BMFontRasterizer::getGlyphIndex(uint32 glyph) const +{ + auto it = characterIndices.find(glyph); + if (it == characterIndices.end()) + return 0; + return it->second; +} + +GlyphData *BMFontRasterizer::getGlyphDataForIndex(int index) const +{ // Return an empty GlyphData if we don't have the glyph character. - if (it == characters.end()) - return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); + if (index < 0 || index >= (int) characters.size()) + return new GlyphData(0, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); - const BMFontCharacter &c = it->second; + const BMFontCharacter& c = characters[index]; const auto &imagepair = images.find(c.page); if (imagepair == images.end()) - return new GlyphData(glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); + return new GlyphData(c.glyph, GlyphMetrics(), PIXELFORMAT_RGBA8_UNORM); image::ImageData *imagedata = imagepair->second.get(); - GlyphData *g = new GlyphData(glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM); + GlyphData *g = new GlyphData(c.glyph, c.metrics, PIXELFORMAT_RGBA8_UNORM); size_t pixelsize = imagedata->getPixelSize(); @@ -333,7 +362,7 @@ int BMFontRasterizer::getGlyphCount() const bool BMFontRasterizer::hasGlyph(uint32 glyph) const { - return characters.find(glyph) != characters.end(); + return characterIndices.find(glyph) != characterIndices.end(); } float BMFontRasterizer::getKerning(uint32 leftglyph, uint32 rightglyph) const @@ -352,6 +381,11 @@ Rasterizer::DataType BMFontRasterizer::getDataType() const return DATA_IMAGE; } +TextShaper *BMFontRasterizer::newTextShaper() +{ + return new GenericShaper(this); +} + bool BMFontRasterizer::accepts(love::filesystem::FileData *fontdef) { const char *data = (const char *) fontdef->getData(); diff --git a/src/modules/font/BMFontRasterizer.h b/src/modules/font/BMFontRasterizer.h index 54335635f..3d4a452f8 100644 --- a/src/modules/font/BMFontRasterizer.h +++ b/src/modules/font/BMFontRasterizer.h @@ -47,11 +47,14 @@ public: // Implements Rasterizer. int getLineHeight() const override; - GlyphData *getGlyphData(uint32 glyph) const override; + int getGlyphSpacing(uint32 glyph) const override; + int getGlyphIndex(uint32 glyph) const override; + GlyphData *getGlyphDataForIndex(int index) const override; int getGlyphCount() const override; bool hasGlyph(uint32 glyph) const override; float getKerning(uint32 leftglyph, uint32 rightglyph) const override; DataType getDataType() const override; + TextShaper *newTextShaper() override; static bool accepts(love::filesystem::FileData *fontdef); @@ -63,6 +66,7 @@ private: int y; int page; GlyphMetrics metrics; + uint32 glyph; }; void parseConfig(const std::string &config); @@ -72,8 +76,10 @@ private: // Image pages, indexed by their page id. std::unordered_map> images; - // Glyph characters, indexed by their glyph id. - std::unordered_map characters; + std::vector characters; + + // Glyph character indices, indexed by their glyph id. + std::unordered_map characterIndices; // Kerning information, indexed by two (packed) characters. std::unordered_map kerning; diff --git a/src/modules/font/GenericShaper.cpp b/src/modules/font/GenericShaper.cpp new file mode 100644 index 000000000..ef5854602 --- /dev/null +++ b/src/modules/font/GenericShaper.cpp @@ -0,0 +1,203 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "GenericShaper.h" +#include "Rasterizer.h" +#include "common/Optional.h" + +namespace love +{ +namespace font +{ + +GenericShaper::GenericShaper(Rasterizer *rasterizer) + : TextShaper(rasterizer) +{ +} + +GenericShaper::~GenericShaper() +{ +} + +void GenericShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + if (rasterizers[0]->getDataType() == Rasterizer::DATA_TRUETYPE) + offset.y += getBaseline(); + + // Spacing counter and newline handling. + Vector2 curpos = offset; + + int maxwidth = 0; + uint32 prevglyph = 0; + + if (positions) + positions->reserve(range.getSize()); + + int colorindex = 0; + int ncolors = (int) codepoints.colors.size(); + Optional colorToAdd; + + // Make sure the right color is applied to the start of the glyph list, + // when the start isn't 0. + if (colors && range.getOffset() > 0 && !codepoints.colors.empty()) + { + for (; colorindex < ncolors; colorindex++) + { + if (codepoints.colors[colorindex].index >= (int) range.getOffset()) + break; + colorToAdd.set(codepoints.colors[colorindex].color); + } + } + + for (int i = (int) range.getMin(); i <= (int) range.getMax(); i++) + { + uint32 g = codepoints.cps[i]; + + // Do this before anything else so we don't miss colors corresponding + // to newlines. The actual add to the list happens after newline + // handling, to make sure the resulting index is valid in the positions + // array. + if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == i) + { + colorToAdd.set(codepoints.colors[colorindex].color); + colorindex++; + } + + if (g == '\n') + { + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + // Wrap newline, but do not output a position for it. + curpos.y += floorf(getHeight() * getLineHeight() + 0.5f); + curpos.x = offset.x; + prevglyph = 0; + continue; + } + + // Ignore carriage returns + if (g == '\r') + { + prevglyph = g; + continue; + } + + if (colorToAdd.hasValue && colors && positions) + { + IndexedColor c = {colorToAdd.value, (int) positions->size()}; + colors->push_back(c); + colorToAdd.clear(); + } + + // Add kerning to the current horizontal offset. + curpos.x += getKerning(prevglyph, g); + + GlyphIndex glyphindex; + int advance = getGlyphAdvance(g, &glyphindex); + + if (positions) + positions->push_back({ Vector2(curpos.x, curpos.y), glyphindex }); + + // Advance the x position for the next glyph. + curpos.x += advance; + + // Account for extra spacing given to space characters. + if (g == ' ' && extraspacing != 0.0f) + curpos.x = floorf(curpos.x + extraspacing); + + prevglyph = g; + } + + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + if (info != nullptr) + { + info->width = maxwidth - offset.x; + info->height = curpos.y - offset.y; + if (curpos.x > offset.x) + info->height += floorf(getHeight() * getLineHeight() + 0.5f); + } +} + +int GenericShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + uint32 prevglyph = 0; + + float w = 0.0f; + float outwidth = 0.0f; + float widthbeforelastspace = 0.0f; + int wrapindex = -1; + int lastspaceindex = -1; + + for (int i = (int)range.getMin(); i <= (int)range.getMax(); i++) + { + uint32 g = codepoints.cps[i]; + + if (g == '\r') + { + prevglyph = g; + continue; + } + + float newwidth = w + getKerning(prevglyph, g) + getGlyphAdvance(g); + + // Only wrap when there's a non-space character. + if (newwidth > wraplimit && !isWhitespace(g)) + { + // Rewind to the last seen space when wrapping. + if (lastspaceindex != -1) + { + wrapindex = lastspaceindex; + outwidth = widthbeforelastspace; + } + break; + } + + // Don't count trailing spaces in the output width. + if (isWhitespace(g)) + { + lastspaceindex = i; + if (!isWhitespace(prevglyph)) + widthbeforelastspace = w; + } + else + outwidth = newwidth; + + w = newwidth; + prevglyph = g; + wrapindex = i; + } + + if (width) + *width = outwidth; + + return wrapindex; +} + +} // font +} // love diff --git a/src/modules/font/GenericShaper.h b/src/modules/font/GenericShaper.h new file mode 100644 index 000000000..c77b5b309 --- /dev/null +++ b/src/modules/font/GenericShaper.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#pragma once + +// LOVE +#include "TextShaper.h" + +namespace love +{ +namespace font +{ + +class GenericShaper : public love::font::TextShaper +{ +public: + + GenericShaper(Rasterizer *rasterizer); + virtual ~GenericShaper(); + + void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; + int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override; + +private: + +}; // GenericShaper + +} // font +} // love diff --git a/src/modules/font/GlyphData.cpp b/src/modules/font/GlyphData.cpp index a5536bbf9..8b1d4c285 100644 --- a/src/modules/font/GlyphData.cpp +++ b/src/modules/font/GlyphData.cpp @@ -24,10 +24,6 @@ // UTF-8 #include "libraries/utf8/utf8.h" -// stdlib -#include -#include - namespace love { namespace font diff --git a/src/modules/font/ImageRasterizer.cpp b/src/modules/font/ImageRasterizer.cpp index 6c9a059fc..93b71b894 100644 --- a/src/modules/font/ImageRasterizer.cpp +++ b/src/modules/font/ImageRasterizer.cpp @@ -20,8 +20,9 @@ // LOVE #include "ImageRasterizer.h" - +#include "GenericShaper.h" #include "common/Exception.h" + #include namespace love @@ -31,10 +32,9 @@ namespace font static_assert(sizeof(Color32) == 4, "sizeof(Color32) must equal 4 bytes!"); -ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale) +ImageRasterizer::ImageRasterizer(love::image::ImageData *data, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale) : imageData(data) - , glyphs(glyphs) - , numglyphs(numglyphs) + , numglyphs(numglyphs + 1) // Always have a null glyph at the start of the array. , extraSpacing(extraspacing) { this->dpiScale = dpiscale; @@ -42,7 +42,7 @@ ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, i if (data->getFormat() != PIXELFORMAT_RGBA8_UNORM) throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!"); - load(); + load(glyphs, numglyphs); } ImageRasterizer::~ImageRasterizer() @@ -54,16 +54,33 @@ int ImageRasterizer::getLineHeight() const return getHeight(); } -GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const +int ImageRasterizer::getGlyphSpacing(uint32 glyph) const +{ + auto it = glyphIndices.find(glyph); + if (it == glyphIndices.end()) + return 0; + return imageGlyphs[it->second].width + extraSpacing; +} + +int ImageRasterizer::getGlyphIndex(uint32 glyph) const +{ + auto it = glyphIndices.find(glyph); + if (it == glyphIndices.end()) + return 0; + return it->second; +} + +GlyphData *ImageRasterizer::getGlyphDataForIndex(int index) const { GlyphMetrics gm = {}; + uint32 glyph = 0; // Set relevant glyph metrics if the glyph is in this ImageFont - std::map::const_iterator it = imageGlyphs.find(glyph); - if (it != imageGlyphs.end()) + if (index >= 0 && index < (int) imageGlyphs.size()) { - gm.width = it->second.width; - gm.advance = it->second.width + extraSpacing; + gm.width = imageGlyphs[index].width; + gm.advance = imageGlyphs[index].width + extraSpacing; + glyph = imageGlyphs[index].glyph; } gm.height = metrics.height; @@ -82,7 +99,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const // copy glyph pixels from imagedata to glyphdata for (int i = 0; i < g->getWidth() * g->getHeight(); i++) { - Color32 p = imagepixels[it->second.x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))]; + Color32 p = imagepixels[imageGlyphs[index].x + (i % gm.width) + (imageData->getWidth() * (i / gm.width))]; // Use transparency instead of the spacer color if (p == spacer) @@ -94,7 +111,7 @@ GlyphData *ImageRasterizer::getGlyphData(uint32 glyph) const return g; } -void ImageRasterizer::load() +void ImageRasterizer::load(const uint32 *glyphs, int glyphcount) { auto pixels = (const Color32 *) imageData->getData(); @@ -113,7 +130,16 @@ void ImageRasterizer::load() int start = 0; int end = 0; - for (int i = 0; i < numglyphs; ++i) + { + ImageGlyphData nullglyph; + nullglyph.x = 0; + nullglyph.width = 0; + nullglyph.glyph = 0; + imageGlyphs.push_back(nullglyph); + glyphIndices[0] = (int) imageGlyphs.size() - 1; + } + + for (int i = 0; i < glyphcount; ++i) { start = end; @@ -133,8 +159,10 @@ void ImageRasterizer::load() ImageGlyphData imageGlyph; imageGlyph.x = start; imageGlyph.width = end - start; + imageGlyph.glyph = glyphs[i]; - imageGlyphs[glyphs[i]] = imageGlyph; + imageGlyphs.push_back(imageGlyph); + glyphIndices[glyphs[i]] = (int) imageGlyphs.size() - 1; } } @@ -145,7 +173,7 @@ int ImageRasterizer::getGlyphCount() const bool ImageRasterizer::hasGlyph(uint32 glyph) const { - return imageGlyphs.find(glyph) != imageGlyphs.end(); + return glyphIndices.find(glyph) != glyphIndices.end(); } Rasterizer::DataType ImageRasterizer::getDataType() const @@ -153,5 +181,10 @@ Rasterizer::DataType ImageRasterizer::getDataType() const return DATA_IMAGE; } +TextShaper *ImageRasterizer::newTextShaper() +{ + return new GenericShaper(this); +} + } // font } // love diff --git a/src/modules/font/ImageRasterizer.h b/src/modules/font/ImageRasterizer.h index 10a04da0d..538b6dbd5 100644 --- a/src/modules/font/ImageRasterizer.h +++ b/src/modules/font/ImageRasterizer.h @@ -39,15 +39,18 @@ namespace font class ImageRasterizer : public Rasterizer { public: - ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale); + ImageRasterizer(love::image::ImageData *imageData, const uint32 *glyphs, int numglyphs, int extraspacing, float dpiscale); virtual ~ImageRasterizer(); // Implement Rasterizer int getLineHeight() const override; - GlyphData *getGlyphData(uint32 glyph) const override; + int getGlyphSpacing(uint32 glyph) const override; + int getGlyphIndex(uint32 glyph) const override; + GlyphData *getGlyphDataForIndex(int index) const override; int getGlyphCount() const override; bool hasGlyph(uint32 glyph) const override; DataType getDataType() const override; + TextShaper *newTextShaper() override; private: @@ -57,23 +60,23 @@ private: { int x; int width; + uint32 glyph; }; // Load all the glyph positions into memory - void load(); + void load(const uint32 *glyphs, int glyphcount); // The image data StrongRef imageData; - // The glyphs in the font - uint32 *glyphs; - // Number of glyphs in the font int numglyphs; int extraSpacing; - std::map imageGlyphs; + + std::vector imageGlyphs; + std::map glyphIndices; // Color used to identify glyph separation in the source ImageData Color32 spacer; diff --git a/src/modules/font/Rasterizer.cpp b/src/modules/font/Rasterizer.cpp index a5dfcef8e..53fa2d7f3 100644 --- a/src/modules/font/Rasterizer.cpp +++ b/src/modules/font/Rasterizer.cpp @@ -55,6 +55,11 @@ int Rasterizer::getDescent() const return metrics.descent; } +GlyphData *Rasterizer::getGlyphData(uint32 glyph) const +{ + return getGlyphDataForIndex(getGlyphIndex(glyph)); +} + GlyphData *Rasterizer::getGlyphData(const std::string &text) const { uint32 codepoint = 0; diff --git a/src/modules/font/Rasterizer.h b/src/modules/font/Rasterizer.h index dee0a74be..67575edd6 100644 --- a/src/modules/font/Rasterizer.h +++ b/src/modules/font/Rasterizer.h @@ -31,6 +31,8 @@ namespace love namespace font { +class TextShaper; + /** * Holds the specific font metrics. **/ @@ -84,17 +86,32 @@ public: **/ virtual int getLineHeight() const = 0; + /** + * Gets the spacing of the given unicode glyph. + **/ + virtual int getGlyphSpacing(uint32 glyph) const = 0; + + /** + * Gets a rasterizer-specific index associated with the given glyph. + **/ + virtual int getGlyphIndex(uint32 glyph) const = 0; + /** * Gets a specific glyph. * @param glyph The (UNICODE) glyph codepoint to get data for. **/ - virtual GlyphData *getGlyphData(uint32 glyph) const = 0; + GlyphData *getGlyphData(uint32 glyph) const; /** * Gets a specific glyph. * @param text The (UNICODE) glyph character to get the data for. **/ - virtual GlyphData *getGlyphData(const std::string &text) const; + GlyphData *getGlyphData(const std::string &text) const; + + /** + * Gets a specific glyph for the given rasterizer glyph index. + **/ + virtual GlyphData *getGlyphDataForIndex(int index) const = 0; /** * Gets the number of glyphs the rasterizer has data for. @@ -120,6 +137,10 @@ public: virtual DataType getDataType() const = 0; + virtual ptrdiff_t getHandle() const { return 0; } + + virtual TextShaper *newTextShaper() = 0; + float getDPIScale() const; protected: diff --git a/src/modules/font/TextShaper.cpp b/src/modules/font/TextShaper.cpp new file mode 100644 index 000000000..43854e719 --- /dev/null +++ b/src/modules/font/TextShaper.cpp @@ -0,0 +1,381 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "TextShaper.h" +#include "Rasterizer.h" +#include "common/Exception.h" + +#include "libraries/utf8/utf8.h" + +namespace love +{ +namespace font +{ + +void getCodepointsFromString(const std::string &text, std::vector &codepoints) +{ + codepoints.reserve(text.size()); + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (i != end) + { + uint32 g = *i++; + codepoints.push_back(g); + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } +} + +void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) +{ + if (strs.empty()) + return; + + codepoints.cps.reserve(strs[0].str.size()); + + for (const ColoredString &cstr : strs) + { + // No need to add the color if the string is empty anyway, and the code + // further on assumes no two colors share the same starting position. + if (cstr.str.size() == 0) + continue; + + IndexedColor c = { cstr.color, (int)codepoints.cps.size() }; + codepoints.colors.push_back(c); + + getCodepointsFromString(cstr.str, codepoints.cps); + } + + if (codepoints.colors.size() == 1) + { + IndexedColor c = codepoints.colors[0]; + + if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f)) + codepoints.colors.pop_back(); + } +} + +love::Type TextShaper::type("TextShaper", &Object::type); + +TextShaper::TextShaper(Rasterizer *rasterizer) + : rasterizers{rasterizer} + , dpiScales{rasterizer->getDPIScale()} + , height(floorf(rasterizer->getHeight() / rasterizer->getDPIScale() + 0.5f)) + , lineHeight(1) + , useSpacesForTab(false) +{ + if (!rasterizer->hasGlyph('\t')) + useSpacesForTab = true; +} + +TextShaper::~TextShaper() +{ +} + +float TextShaper::getHeight() const +{ + return height; +} + +void TextShaper::setLineHeight(float h) +{ + lineHeight = h; +} + +float TextShaper::getLineHeight() const +{ + return lineHeight; +} + +int TextShaper::getAscent() const +{ + return floorf(rasterizers[0]->getAscent() / rasterizers[0]->getDPIScale() + 0.5f); +} + +int TextShaper::getDescent() const +{ + return floorf(rasterizers[0]->getDescent() / rasterizers[0]->getDPIScale() + 0.5f); +} + +float TextShaper::getBaseline() const +{ + float ascent = getAscent(); + if (ascent != 0.0f) + return ascent; + else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) + return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts + else + return 0.0f; +} + +bool TextShaper::hasGlyph(uint32 glyph) const +{ + for (const StrongRef &r : rasterizers) + { + if (r->hasGlyph(glyph)) + return true; + } + + return false; +} + +bool TextShaper::hasGlyphs(const std::string &text) const +{ + if (text.size() == 0) + return false; + + try + { + utf8::iterator i(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (i != end) + { + uint32 codepoint = *i++; + + if (!hasGlyph(codepoint)) + return false; + } + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return true; +} + +float TextShaper::getKerning(uint32 leftglyph, uint32 rightglyph) +{ + uint64 packedglyphs = ((uint64)leftglyph << 32) | (uint64)rightglyph; + + const auto it = kerning.find(packedglyphs); + if (it != kerning.end()) + return it->second; + + float k = 0.0f; + bool found = false; + + for (const auto &r : rasterizers) + { + if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) + { + found = true; + k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f); + break; + } + } + + if (!found) + k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / rasterizers[0]->getDPIScale() + 0.5f); + + kerning[packedglyphs] = k; + return k; +} + +float TextShaper::getKerning(const std::string &leftchar, const std::string &rightchar) +{ + uint32 left = 0; + uint32 right = 0; + + try + { + left = utf8::peek_next(leftchar.begin(), leftchar.end()); + right = utf8::peek_next(rightchar.begin(), rightchar.end()); + } + catch (utf8::exception &e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return getKerning(left, right); +} + +int TextShaper::getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex) +{ + const auto it = glyphAdvances.find(glyph); + if (it != glyphAdvances.end()) + { + if (glyphindex) + *glyphindex = it->second.second; + return it->second.first; + } + + int rasterizeri = 0; + uint32 realglyph = glyph; + + if (glyph == '\t' && isUsingSpacesForTab()) + realglyph = ' '; + + for (size_t i = 0; i < rasterizers.size(); i++) + { + if (rasterizers[i]->hasGlyph(realglyph)) + { + rasterizeri = (int) i; + break; + } + } + + const auto &r = rasterizers[rasterizeri]; + int advance = floorf(r->getGlyphSpacing(realglyph) / r->getDPIScale() + 0.5f); + + if (glyph == '\t' && realglyph == ' ') + advance *= SPACES_PER_TAB; + + GlyphIndex glyphi = {r->getGlyphIndex(realglyph), rasterizeri}; + + glyphAdvances[glyph] = std::make_pair(advance, glyphi); + if (glyphindex) + *glyphindex = glyphi; + return advance; +} + +int TextShaper::getWidth(const std::string &str) +{ + if (str.size() == 0) return 0; + + ColoredCodepoints codepoints; + getCodepointsFromString(str, codepoints.cps); + + TextInfo info; + computeGlyphPositions(codepoints, Range(), Vector2(0.0f, 0.0f), 0.0f, nullptr, nullptr, &info); + + return info.width; +} + +static size_t findNewline(const ColoredCodepoints &codepoints, size_t start) +{ + for (size_t i = start; i < codepoints.cps.size(); i++) + { + if (codepoints.cps[i] == '\n') + { + return i; + } + } + + return codepoints.cps.size(); +} + +void TextShaper::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths) +{ + size_t nextnewline = findNewline(codepoints, 0); + + for (size_t i = 0; i < codepoints.cps.size();) + { + if (nextnewline < i) + nextnewline = findNewline(codepoints, i); + + if (nextnewline == i) // Empty line. + { + lineranges.push_back(Range()); + if (linewidths) + linewidths->push_back(0); + i++; + } + else + { + Range r(i, nextnewline - i); + float width = 0.0f; + int wrapindex = computeWordWrapIndex(codepoints, r, wraplimit, &width); + + if (wrapindex >= (int) i) + { + r = Range(i, (size_t) wrapindex + 1 - i); + i = (size_t)wrapindex + 1; + } + else + { + r = Range(); + i++; + } + + // We've already handled this line, skip the newline character. + if (nextnewline == i) + i++; + + lineranges.push_back(r); + if (linewidths) + linewidths->push_back(width); + } + } +} + +void TextShaper::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) +{ + ColoredCodepoints cps; + getCodepointsFromString(text, cps); + + std::vector codepointranges; + getWrap(cps, wraplimit, codepointranges, linewidths); + + std::string line; + + for (const auto &range : codepointranges) + { + line.clear(); + + if (range.isValid()) + { + line.reserve(range.getSize()); + + for (size_t i = range.getMin(); i <= range.getMax(); i++) + { + char character[5] = { '\0' }; + char *end = utf8::unchecked::append(cps.cps[i], character); + line.append(character, end - character); + } + } + + lines.push_back(line); + } +} + +void TextShaper::setFallbacks(const std::vector &fallbacks) +{ + for (Rasterizer *r : fallbacks) + { + if (r->getDataType() != rasterizers[0]->getDataType()) + throw love::Exception("Font fallbacks must be of the same font type."); + } + + // Clear caches. + kerning.clear(); + glyphAdvances.clear(); + + rasterizers.resize(1); + dpiScales.resize(1); + + for (Rasterizer *r : fallbacks) + { + rasterizers.push_back(r); + dpiScales.push_back(r->getDPIScale()); + } +} + +} // font +} // love diff --git a/src/modules/font/TextShaper.h b/src/modules/font/TextShaper.h new file mode 100644 index 000000000..b5417514c --- /dev/null +++ b/src/modules/font/TextShaper.h @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#pragma once + +// LOVE +#include "common/Object.h" +#include "common/Vector.h" +#include "common/int.h" +#include "common/Color.h" +#include "common/Range.h" + +#include +#include +#include + +namespace love +{ +namespace font +{ + +class Rasterizer; + +struct ColoredString +{ + std::string str; + Colorf color; +}; + +struct IndexedColor +{ + Colorf color; + int index; +}; + +struct ColoredCodepoints +{ + std::vector cps; + std::vector colors; +}; + +void getCodepointsFromString(const std::string &str, std::vector &codepoints); +void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); + +class TextShaper : public Object +{ +public: + + struct GlyphIndex + { + int index; + int rasterizerIndex; + }; + + struct GlyphPosition + { + Vector2 position; + GlyphIndex glyphIndex; + }; + + struct TextInfo + { + int width; + int height; + }; + + // This will be used if the Rasterizer doesn't have a tab character itself. + static const int SPACES_PER_TAB = 4; + + static love::Type type; + + virtual ~TextShaper(); + + const std::vector> &getRasterizers() const { return rasterizers; } + bool isUsingSpacesForTab() const { return useSpacesForTab; } + + float getHeight() const; + + /** + * Sets the line height (which should be a number to multiply the font size by, + * example: line height = 1.2 and size = 12 means that rendered line height = 12*1.2) + * @param height The new line height. + **/ + void setLineHeight(float height); + + /** + * Returns the line height. + **/ + float getLineHeight() const; + + // Extra font metrics + int getAscent() const; + int getDescent() const; + float getBaseline() const; + + bool hasGlyph(uint32 glyph) const; + bool hasGlyphs(const std::string &text) const; + + float getKerning(uint32 leftglyph, uint32 rightglyph); + float getKerning(const std::string &leftchar, const std::string &rightchar); + + int getGlyphAdvance(uint32 glyph, GlyphIndex *glyphindex = nullptr); + + int getWidth(const std::string &str); + + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths = nullptr); + void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lineranges, std::vector *linewidths = nullptr); + + virtual void setFallbacks(const std::vector &fallbacks); + + virtual void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) = 0; + virtual int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) = 0; + +protected: + + TextShaper(Rasterizer *rasterizer); + + static inline bool isWhitespace(uint32 codepoint) { return codepoint == ' ' || codepoint == '\t'; } + + std::vector> rasterizers; + std::vector dpiScales; + +private: + + int height; + float lineHeight; + + bool useSpacesForTab; + + // maps glyphs to advance and glyph+rasterizer index. + std::unordered_map> glyphAdvances; + + // map of left/right glyph pairs to horizontal kerning. + std::unordered_map kerning; + +}; // TextShaper + +} // font +} // love diff --git a/src/modules/font/freetype/HarfbuzzShaper.cpp b/src/modules/font/freetype/HarfbuzzShaper.cpp new file mode 100644 index 000000000..8b6ad01c5 --- /dev/null +++ b/src/modules/font/freetype/HarfbuzzShaper.cpp @@ -0,0 +1,409 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +// LOVE +#include "HarfbuzzShaper.h" +#include "TrueTypeRasterizer.h" +#include "common/Optional.h" + +// harfbuzz +#include +#include + +namespace love +{ +namespace font +{ +namespace freetype +{ + +HarfbuzzShaper::HarfbuzzShaper(TrueTypeRasterizer *rasterizer) + : TextShaper(rasterizer) + , spaceGlyphIndex() + , tabSpacesAdvanceX(0) + , tabSpacesAdvanceY(0) +{ + hbFonts.push_back(hb_ft_font_create_referenced((FT_Face)rasterizer->getHandle())); + hbBuffers.push_back(hb_buffer_create()); + + if (hbFonts[0] == nullptr || hbFonts[0] == hb_font_get_empty()) + throw love::Exception("Could not create Harfbuzz font object."); + + if (hbBuffers[0] == nullptr || hbBuffers[0] == hb_buffer_get_empty()) + throw love::Exception("Could not create Harfbuzz buffer object."); + + updateSpacesForTabInfo(); +} + +HarfbuzzShaper::~HarfbuzzShaper() +{ + for (hb_buffer_t *buffer : hbBuffers) + hb_buffer_destroy(buffer); + for (hb_font_t *font : hbFonts) + hb_font_destroy(font); +} + +void HarfbuzzShaper::setFallbacks(const std::vector &fallbacks) +{ + for (size_t i = 1; i < rasterizers.size(); i++) + { + hb_buffer_destroy(hbBuffers[i]); + hb_font_destroy(hbFonts[i]); + } + + TextShaper::setFallbacks(fallbacks); + + hbFonts.resize(rasterizers.size()); + hbBuffers.resize(rasterizers.size()); + + for (size_t i = 1; i < rasterizers.size(); i++) + { + hbFonts[i] = hb_ft_font_create_referenced((FT_Face)rasterizers[i]->getHandle()); + hbBuffers[i] = hb_buffer_create(); + } + + updateSpacesForTabInfo(); +} + +void HarfbuzzShaper::updateSpacesForTabInfo() +{ + if (!isUsingSpacesForTab()) + return; + + hb_codepoint_t glyphid = 0; + for (size_t i = 0; i < hbFonts.size(); i++) + { + hb_font_t *hbfont = hbFonts[i]; + if (hb_font_get_glyph(hbfont, ' ', 0, &glyphid)) + { + spaceGlyphIndex.index = glyphid; + spaceGlyphIndex.rasterizerIndex = i; + tabSpacesAdvanceX = hb_font_get_glyph_h_advance(hbfont, glyphid) * SPACES_PER_TAB; + tabSpacesAdvanceY = hb_font_get_glyph_v_advance(hbfont, glyphid) * SPACES_PER_TAB; + break; + } + } +} + +bool HarfbuzzShaper::isValidGlyph(uint32 glyphindex, const std::vector &codepoints, uint32 codepointindex) +{ + if (glyphindex != 0) + return true; + + uint32 codepoint = codepoints[codepointindex]; + if (codepoint == '\n' || codepoint == '\r' || (codepoint == '\t' && isUsingSpacesForTab())) + return true; + + return false; +} + +void HarfbuzzShaper::computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector &bufferranges) +{ + bufferranges.clear(); + + // Less computation for the typical case (no fallback fonts). + if (rasterizers.size() == 1) + { + hb_buffer_reset(hbBuffers[0]); + hb_buffer_add_codepoints(hbBuffers[0], codepoints.cps.data(), codepoints.cps.size(), (unsigned int)range.getOffset(), (int)range.getSize()); + + // TODO: Expose APIs for direction and script? + hb_buffer_guess_segment_properties(hbBuffers[0]); + + hb_shape(hbFonts[0], hbBuffers[0], nullptr, 0); + + bufferranges.push_back({0, (int) range.first, Range(0, hb_buffer_get_length(hbBuffers[0]))}); + return; + } + + std::vector fallbackranges = { range }; + + // For each font, figure out the ranges of valid glyphs in the given string, + // and add the rest to a list to be shaped by the next fallback font. + // Harfbuzz doesn't have its own fallback API. + for (size_t rasti = 0; rasti < rasterizers.size(); rasti++) + { + hb_buffer_t *hbb = hbBuffers[rasti]; + hb_buffer_reset(hbb); + + for (Range r : fallbackranges) + hb_buffer_add_codepoints(hbb, codepoints.cps.data(), codepoints.cps.size(), (unsigned int)r.getOffset(), (int)r.getSize()); + + hb_buffer_guess_segment_properties(hbb); + + hb_shape(hbFonts[rasti], hbb, nullptr, 0); + + int glyphcount = (int)hb_buffer_get_length(hbb); + const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbb, nullptr); + + fallbackranges.clear(); + + for (int i = 0; i < glyphcount; i++) + { + if (isValidGlyph(glyphinfos[i].codepoint, codepoints.cps, glyphinfos[i].cluster)) + { + if (bufferranges.empty() || bufferranges.back().index != rasti || bufferranges.back().range.getMax() != i) + bufferranges.push_back({(int)rasti, (int)glyphinfos[i].cluster, Range(i, 1)}); + else + bufferranges.back().range.last++; + } + else if (rasti == rasterizers.size() - 1) + { + // Use the first font for remaining invalid glyphs when no + // fallback font supports them. + if (bufferranges.empty() || bufferranges.back().index != 0 || bufferranges.back().range.getMax() != i) + bufferranges.push_back({0, (int)glyphinfos[i].cluster, Range(i, 1)}); + else + bufferranges.back().range.last++; + } + else + { + if (fallbackranges.empty() || fallbackranges.back().getMax() != glyphinfos[i - 1].cluster) + fallbackranges.push_back(Range(glyphinfos[i].cluster, 1)); + else + fallbackranges.back().encapsulate(glyphinfos[i].cluster); + } + } + } + + std::sort(bufferranges.begin(), bufferranges.end(), [](const BufferRange &a, const BufferRange &b) + { + if (a.codepointStart != b.codepointStart) + return a.codepointStart < b.codepointStart; + if (a.index != b.index) + return a.index < b.index; + return a.range.first < b.range.first; + }); +} + +void HarfbuzzShaper::computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + offset.y += getBaseline(); + Vector2 curpos = offset; + + int colorindex = 0; + int ncolors = (int)codepoints.colors.size(); + Optional colorToAdd; + + // Make sure the right color is applied to the start of the glyph list, + // when the start isn't 0. + if (colors && range.getOffset() > 0 && !codepoints.colors.empty()) + { + for (; colorindex < ncolors; colorindex++) + { + if (codepoints.colors[colorindex].index >= (int) range.getOffset()) + break; + colorToAdd.set(codepoints.colors[colorindex].color); + } + } + + std::vector bufferranges; + computeBufferRanges(codepoints, range, bufferranges); + + int maxwidth = (int)curpos.x; + + for (const auto &bufferrange : bufferranges) + { + if (positions) + positions->reserve(positions->size() + bufferrange.range.getSize()); + + hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index]; + + const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); + hb_direction_t direction = hb_buffer_get_direction(hbbuffer); + + for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++) + { + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; + + // TODO: this doesn't handle situations where the user inserted a color + // change in the middle of some characters that get combined into a single + // cluster. + if (colors && colorindex < ncolors && codepoints.colors[colorindex].index == info.cluster) + { + colorToAdd.set(codepoints.colors[colorindex].color); + colorindex++; + } + + uint32 clustercodepoint = codepoints.cps[info.cluster]; + + // Harfbuzz doesn't handle newlines itself, but it does leave them in + // the glyph list so we can do it manually. + if (clustercodepoint == '\n') + { + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + // Wrap newline, but do not output a position for it. + curpos.y += floorf(getHeight() * getLineHeight() + 0.5f); + curpos.x = offset.x; + continue; + } + + // Ignore carriage returns + if (clustercodepoint == '\r') + continue; + + // This is a glyph index at this point, despite the name. + GlyphIndex gindex = { (int) info.codepoint, bufferrange.index }; + + if (clustercodepoint == '\t' && isUsingSpacesForTab()) + { + gindex = spaceGlyphIndex; + + // This should be safe to overwrite. + // TODO: RTL support? + glyphpos.x_offset = 0; + glyphpos.y_offset = 0; + glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0; + glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0; + } + + if (colorToAdd.hasValue && colors && positions) + { + IndexedColor c = {colorToAdd.value, (int) positions->size()}; + colors->push_back(c); + colorToAdd.clear(); + } + + if (positions) + { + GlyphPosition p = { curpos, gindex }; + + // Harfbuzz position coordinate systems are based on the given font. + // Freetype uses 26.6 fixed point coordinates, so harfbuzz does too. + p.position.x += floorf((glyphpos.x_offset >> 6) / dpiScales[0] + 0.5f); + p.position.y += floorf((glyphpos.y_offset >> 6) / dpiScales[0] + 0.5f); + + positions->push_back(p); + } + + curpos.x += floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f); + curpos.y += floorf((glyphpos.y_advance >> 6) / dpiScales[0] + 0.5f); + + // Account for extra spacing given to space characters. + if (clustercodepoint == ' ' && extraspacing != 0.0f) + curpos.x = floorf(curpos.x + extraspacing); + } + } + + if (curpos.x > maxwidth) + maxwidth = (int)curpos.x; + + if (info != nullptr) + { + info->width = maxwidth - offset.x; + info->height = curpos.y - offset.y; + if (curpos.x > offset.x) + info->height += floorf(getHeight() * getLineHeight() + 0.5f); + } +} + +int HarfbuzzShaper::computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) +{ + if (!range.isValid()) + range = Range(0, codepoints.cps.size()); + + float w = 0.0f; + float outwidth = 0.0f; + float widthbeforelastspace = 0.0f; + int wrapindex = -1; + int lastspaceindex = -1; + + uint32 prevcodepoint = 0; + + std::vector bufferranges; + computeBufferRanges(codepoints, range, bufferranges); + + for (const auto &bufferrange : bufferranges) + { + hb_buffer_t *hbbuffer = hbBuffers[bufferrange.index]; + + const hb_glyph_info_t *glyphinfos = hb_buffer_get_glyph_infos(hbbuffer, nullptr); + hb_glyph_position_t *glyphpositions = hb_buffer_get_glyph_positions(hbbuffer, nullptr); + hb_direction_t direction = hb_buffer_get_direction(hbbuffer); + + for (size_t i = bufferrange.range.first; i <= bufferrange.range.last; i++) + { + const hb_glyph_info_t &info = glyphinfos[i]; + hb_glyph_position_t &glyphpos = glyphpositions[i]; + + uint32 clustercodepoint = codepoints.cps[info.cluster]; + + if (clustercodepoint == '\r') + { + prevcodepoint = clustercodepoint; + continue; + } + + if (clustercodepoint == '\t' && isUsingSpacesForTab()) + { + // This should be safe to overwrite. + // TODO: RTL support? + glyphpos.x_offset = 0; + glyphpos.y_offset = 0; + glyphpos.x_advance = HB_DIRECTION_IS_HORIZONTAL(direction) ? tabSpacesAdvanceX : 0; + glyphpos.y_advance = HB_DIRECTION_IS_VERTICAL(direction) ? tabSpacesAdvanceY : 0; + } + + float newwidth = w + floorf((glyphpos.x_advance >> 6) / dpiScales[0] + 0.5f); + + // Only wrap when there's a non-space character. + if (newwidth > wraplimit && !isWhitespace(clustercodepoint)) + { + // Rewind to the last seen space when wrapping. + if (lastspaceindex != -1) + { + wrapindex = lastspaceindex; + outwidth = widthbeforelastspace; + } + break; + } + + // Don't count trailing spaces in the output width. + if (isWhitespace(clustercodepoint)) + { + lastspaceindex = info.cluster; + if (!isWhitespace(prevcodepoint)) + widthbeforelastspace = w; + } + else + outwidth = newwidth; + + w = newwidth; + prevcodepoint = clustercodepoint; + wrapindex = info.cluster; + } + } + + if (width) + *width = outwidth; + + return wrapindex; +} + +} // freetype +} // font +} // love diff --git a/src/modules/font/freetype/HarfbuzzShaper.h b/src/modules/font/freetype/HarfbuzzShaper.h new file mode 100644 index 000000000..3a71a5b8b --- /dev/null +++ b/src/modules/font/freetype/HarfbuzzShaper.h @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#pragma once + +// LOVE +#include "font/TextShaper.h" + +extern "C" +{ +typedef struct hb_font_t hb_font_t; +typedef struct hb_buffer_t hb_buffer_t; +} + +namespace love +{ +namespace font +{ +namespace freetype +{ + +class TrueTypeRasterizer; + +class HarfbuzzShaper : public love::font::TextShaper +{ +public: + + HarfbuzzShaper(TrueTypeRasterizer *rasterizer); + virtual ~HarfbuzzShaper(); + + void setFallbacks(const std::vector &fallbacks) override; + void computeGlyphPositions(const ColoredCodepoints &codepoints, Range range, Vector2 offset, float extraspacing, std::vector *positions, std::vector *colors, TextInfo *info) override; + int computeWordWrapIndex(const ColoredCodepoints &codepoints, Range range, float wraplimit, float *width) override; + +private: + + struct BufferRange + { + int index; + int codepointStart; + Range range; + }; + + void updateSpacesForTabInfo(); + bool isValidGlyph(uint32 glyphindex, const std::vector &codepoints, uint32 codepointindex); + void computeBufferRanges(const ColoredCodepoints &codepoints, Range range, std::vector &bufferranges); + + std::vector hbFonts; + std::vector hbBuffers; + + GlyphIndex spaceGlyphIndex; + int tabSpacesAdvanceX; + int tabSpacesAdvanceY; + +}; // HarfbuzzShaper + +} // freetype +} // font +} // love diff --git a/src/modules/font/freetype/TrueTypeRasterizer.cpp b/src/modules/font/freetype/TrueTypeRasterizer.cpp index 64dae54bd..b48f63cb2 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.cpp +++ b/src/modules/font/freetype/TrueTypeRasterizer.cpp @@ -20,6 +20,7 @@ // LOVE #include "TrueTypeRasterizer.h" +#include "HarfbuzzShaper.h" #include "common/Exception.h" // C @@ -75,7 +76,30 @@ int TrueTypeRasterizer::getLineHeight() const return (int)(getHeight() * 1.25); } -GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const +int TrueTypeRasterizer::getGlyphSpacing(uint32 glyph) const +{ + FT_Glyph ftglyph; + FT_Error err = FT_Err_Ok; + FT_UInt loadoption = hintingToLoadOption(hinting); + + // Initialize + err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption); + if (err != FT_Err_Ok) + return 0; + + err = FT_Get_Glyph(face->glyph, &ftglyph); + if (err != FT_Err_Ok) + return 0; + + return (int)(ftglyph->advance.x >> 16); +} + +int TrueTypeRasterizer::getGlyphIndex(uint32 glyph) const +{ + return FT_Get_Char_Index(face, glyph); +} + +GlyphData *TrueTypeRasterizer::getGlyphDataForIndex(int index) const { love::font::GlyphMetrics glyphMetrics = {}; FT_Glyph ftglyph; @@ -84,7 +108,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const FT_UInt loadoption = hintingToLoadOption(hinting); // Initialize - err = FT_Load_Glyph(face, FT_Get_Char_Index(face, glyph), FT_LOAD_DEFAULT | loadoption); + err = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT | loadoption); if (err != FT_Err_Ok) throw love::Exception("TrueType Font glyph error: FT_Load_Glyph failed (0x%x)", err); @@ -104,7 +128,7 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const throw love::Exception("TrueType Font glyph error: FT_Glyph_To_Bitmap failed (0x%x)", err); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) ftglyph; - FT_Bitmap &bitmap = bitmap_glyph->bitmap; //just to make things easier + const FT_Bitmap &bitmap = bitmap_glyph->bitmap; //just to make things easier // Get metrics glyphMetrics.bearingX = bitmap_glyph->left; @@ -113,7 +137,8 @@ GlyphData *TrueTypeRasterizer::getGlyphData(uint32 glyph) const glyphMetrics.width = bitmap.width; glyphMetrics.advance = (int) (ftglyph->advance.x >> 16); - GlyphData *glyphData = new GlyphData(glyph, glyphMetrics, PIXELFORMAT_LA8_UNORM); + // TODO: https://stackoverflow.com/questions/60526004/how-to-get-glyph-unicode-using-freetype/69730502#69730502 + GlyphData *glyphData = new GlyphData(0, glyphMetrics, PIXELFORMAT_LA8_UNORM); const uint8 *pixels = bitmap.buffer; uint8 *dest = (uint8 *) glyphData->getData(); @@ -186,6 +211,11 @@ Rasterizer::DataType TrueTypeRasterizer::getDataType() const return DATA_TRUETYPE; } +TextShaper *TrueTypeRasterizer::newTextShaper() +{ + return new HarfbuzzShaper(this); +} + bool TrueTypeRasterizer::accepts(FT_Library library, love::Data *data) { const FT_Byte *fbase = (const FT_Byte *) data->getData(); diff --git a/src/modules/font/freetype/TrueTypeRasterizer.h b/src/modules/font/freetype/TrueTypeRasterizer.h index 6bcdf552d..e97e91967 100644 --- a/src/modules/font/freetype/TrueTypeRasterizer.h +++ b/src/modules/font/freetype/TrueTypeRasterizer.h @@ -49,11 +49,16 @@ public: // Implement Rasterizer int getLineHeight() const override; - GlyphData *getGlyphData(uint32 glyph) const override; + int getGlyphSpacing(uint32 glyph) const override; + int getGlyphIndex(uint32 glyph) const override; + GlyphData *getGlyphDataForIndex(int index) const override; int getGlyphCount() const override; bool hasGlyph(uint32 glyph) const override; float getKerning(uint32 leftglyph, uint32 rightglyph) const override; DataType getDataType() const override; + TextShaper *newTextShaper() override; + + ptrdiff_t getHandle() const override { return (ptrdiff_t) face; } static bool accepts(FT_Library library, love::Data *data); diff --git a/src/modules/graphics/Deprecations.cpp b/src/modules/graphics/Deprecations.cpp index aacc20f78..92a5d51c5 100644 --- a/src/modules/graphics/Deprecations.cpp +++ b/src/modules/graphics/Deprecations.cpp @@ -90,7 +90,7 @@ void Deprecations::draw(Graphics *gfx) int maxcount = 4; int remaining = std::max(0, total - maxcount); - std::vector strings; + std::vector strings; Colorf white(1, 1, 1, 1); // Grab the newest deprecation notices first. diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index 03b06d46d..a4e3485b0 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -21,8 +21,6 @@ #include "Font.h" #include "font/GlyphData.h" -#include "libraries/utf8/utf8.h" - #include "common/math.h" #include "common/Matrix.h" #include "Graphics.h" @@ -42,20 +40,27 @@ static inline uint16 normToUint16(double n) return (uint16) (n * LOVE_UINT16_MAX); } +static inline uint64 packGlyphIndex(love::font::TextShaper::GlyphIndex glyphindex) +{ + return ((uint64)glyphindex.rasterizerIndex << 32) | (uint64)glyphindex.index; +} + +static inline love::font::TextShaper::GlyphIndex unpackGlyphIndex(uint64 packedindex) +{ + return {(int) (packedindex & 0xFFFFFFFF), (int) (packedindex >> 32)}; +} + love::Type Font::type("Font", &Object::type); int Font::fontCount = 0; const CommonFormat Font::vertexFormat = CommonFormat::XYf_STus_RGBAub; Font::Font(love::font::Rasterizer *r, const SamplerState &s) - : rasterizers({r}) - , height(r->getHeight()) - , lineHeight(1) + : shaper(r->newTextShaper(), Acquire::NORETAIN) , textureWidth(128) , textureHeight(128) , samplerState() , dpiScale(r->getDPIScale()) - , useSpacesAsTab(false) , textureCacheID(0) { samplerState.minFilter = s.minFilter; @@ -66,7 +71,7 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) // largest texture size if no rough match is found. while (true) { - if ((height * 0.8) * height * 30 <= textureWidth * textureHeight) + if ((shaper->getHeight() * 0.8) * shaper->getHeight() * 30 <= textureWidth * textureHeight) break; TextureSize nextsize = getNextTextureSize(); @@ -86,9 +91,6 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) if (pixelFormat == PIXELFORMAT_LA8_UNORM && !gfx->isPixelFormatSupported(pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE)) pixelFormat = PIXELFORMAT_RGBA8_UNORM; - if (!r->hasGlyph(9)) // No tab character in the Rasterizer. - useSpacesAsTab = true; - loadVolatile(); ++fontCount; } @@ -170,7 +172,7 @@ void Font::createTexture() // and transparent black otherwise. std::vector emptydata(datasize, 0); - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) + if (shaper->getRasterizers()[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) { if (pixelFormat == PIXELFORMAT_LA8_UNORM) { @@ -204,15 +206,15 @@ void Font::createTexture() { textureCacheID++; - std::vector glyphstoadd; + std::vector glyphstoadd; for (const auto &glyphpair : glyphs) - glyphstoadd.push_back(glyphpair.first); + glyphstoadd.push_back(unpackGlyphIndex(glyphpair.first)); glyphs.clear(); - for (uint32 g : glyphstoadd) - addGlyph(g); + for (auto glyphindex : glyphstoadd) + addGlyph(glyphindex); } } @@ -222,42 +224,17 @@ void Font::unloadVolatile() textures.clear(); } -love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph, float &dpiscale) +love::font::GlyphData *Font::getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale) { - // Use spaces for the tab 'glyph'. - if (glyph == 9 && useSpacesAsTab) - { - love::font::GlyphData *spacegd = rasterizers[0]->getGlyphData(32); - PixelFormat fmt = spacegd->getFormat(); - - love::font::GlyphMetrics gm = {}; - gm.advance = spacegd->getAdvance() * SPACES_PER_TAB; - gm.bearingX = spacegd->getBearingX(); - gm.bearingY = spacegd->getBearingY(); - - spacegd->release(); - - dpiscale = rasterizers[0]->getDPIScale(); - return new love::font::GlyphData(glyph, gm, fmt); - } - - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - { - dpiscale = r->getDPIScale(); - return r->getGlyphData(glyph); - } - } - - dpiscale = rasterizers[0]->getDPIScale(); - return rasterizers[0]->getGlyphData(glyph); + const auto &r = shaper->getRasterizers()[glyphindex.rasterizerIndex]; + dpiscale = r->getDPIScale(); + return r->getGlyphDataForIndex(glyphindex.index); } -const Font::Glyph &Font::addGlyph(uint32 glyph) +const Font::Glyph &Font::addGlyph(love::font::TextShaper::GlyphIndex glyphindex) { float glyphdpiscale = getDPIScale(); - StrongRef gd(getRasterizerGlyphData(glyph, glyphdpiscale), Acquire::NORETAIN); + StrongRef gd(getRasterizerGlyphData(glyphindex, glyphdpiscale), Acquire::NORETAIN); int w = gd->getWidth(); int h = gd->getHeight(); @@ -279,15 +256,13 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) // Makes sure the above code for checking if the glyph can fit at // the current position in the texture is run again for this glyph. - return addGlyph(glyph); + return addGlyph(glyphindex); } } Glyph g; - g.texture = 0; - g.spacing = floorf(gd->getAdvance() / glyphdpiscale + 0.5f); - + g.texture = nullptr; memset(g.vertices, 0, sizeof(GlyphVertex) * 4); // Don't waste space for empty glyphs. @@ -357,151 +332,77 @@ const Font::Glyph &Font::addGlyph(uint32 glyph) rowHeight = std::max(rowHeight, h + TEXTURE_PADDING); } - glyphs[glyph] = g; - return glyphs[glyph]; + uint64 packedindex = packGlyphIndex(glyphindex); + glyphs[packedindex] = g; + return glyphs[packedindex]; } -const Font::Glyph &Font::findGlyph(uint32 glyph) +const Font::Glyph &Font::findGlyph(love::font::TextShaper::GlyphIndex glyphindex) { - const auto it = glyphs.find(glyph); + uint64 packedindex = packGlyphIndex(glyphindex); + const auto it = glyphs.find(packedindex); if (it != glyphs.end()) return it->second; - return addGlyph(glyph); + return addGlyph(glyphindex); } float Font::getKerning(uint32 leftglyph, uint32 rightglyph) { - uint64 packedglyphs = ((uint64) leftglyph << 32) | (uint64) rightglyph; - - const auto it = kerning.find(packedglyphs); - if (it != kerning.end()) - return it->second; - - float k = floorf(rasterizers[0]->getKerning(leftglyph, rightglyph) / dpiScale + 0.5f); - - for (const auto &r : rasterizers) - { - if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph)) - { - k = floorf(r->getKerning(leftglyph, rightglyph) / r->getDPIScale() + 0.5f); - break; - } - } - - kerning[packedglyphs] = k; - return k; + return shaper->getKerning(leftglyph, rightglyph); } float Font::getKerning(const std::string &leftchar, const std::string &rightchar) { - uint32 left = 0; - uint32 right = 0; - - try - { - left = utf8::peek_next(leftchar.begin(), leftchar.end()); - right = utf8::peek_next(rightchar.begin(), rightchar.end()); - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - return getKerning(left, right); -} - -void Font::getCodepointsFromString(const std::string &text, Codepoints &codepoints) -{ - codepoints.reserve(text.size()); - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); - - while (i != end) - { - uint32 g = *i++; - codepoints.push_back(g); - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } -} - -void Font::getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints) -{ - if (strs.empty()) - return; - - codepoints.cps.reserve(strs[0].str.size()); - - for (const ColoredString &cstr : strs) - { - // No need to add the color if the string is empty anyway, and the code - // further on assumes no two colors share the same starting position. - if (cstr.str.size() == 0) - continue; - - IndexedColor c = {cstr.color, (int) codepoints.cps.size()}; - codepoints.colors.push_back(c); - - getCodepointsFromString(cstr.str, codepoints.cps); - } - - if (codepoints.colors.size() == 1) - { - IndexedColor c = codepoints.colors[0]; - - if (c.index == 0 && c.color == Colorf(1.0f, 1.0f, 1.0f, 1.0f)) - codepoints.colors.pop_back(); - } + return shaper->getKerning(leftchar, rightchar); } float Font::getHeight() const { - return (float) floorf(height / dpiScale + 0.5f); + return shaper->getHeight(); } -std::vector Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector2 offset, TextInfo *info) +std::vector Font::generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantcolor, std::vector &vertices, float extra_spacing, Vector2 offset, love::font::TextShaper::TextInfo *info) { - // Spacing counter and newline handling. - float dx = offset.x; - float dy = offset.y; + std::vector glyphpositions; + std::vector colors; + shaper->computeGlyphPositions(codepoints, range, offset, extra_spacing, &glyphpositions, &colors, info); - float heightoffset = 0.0f; + size_t vertstartsize = vertices.size(); + vertices.reserve(vertstartsize + glyphpositions.size() * 4); - if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) - heightoffset = getBaseline(); + Colorf linearconstantcolor = gammaCorrectColor(constantcolor); + Color32 curcolor = toColor32(constantcolor); - int maxwidth = 0; + int curcolori = 0; + int ncolors = (int)colors.size(); // Keeps track of when we need to switch textures in our vertex array. std::vector commands; - // Pre-allocate space for the maximum possible number of vertices. - size_t vertstartsize = vertices.size(); - vertices.reserve(vertstartsize + codepoints.cps.size() * 4); - - uint32 prevglyph = 0; - - Colorf linearconstantcolor = gammaCorrectColor(constantcolor); - - Color32 curcolor = toColor32(constantcolor); - int curcolori = -1; - int ncolors = (int) codepoints.colors.size(); - - for (int i = 0; i < (int) codepoints.cps.size(); i++) + for (int i = 0; i < (int) glyphpositions.size(); i++) { - uint32 g = codepoints.cps[i]; + const auto &info = glyphpositions[i]; - if (curcolori + 1 < ncolors && codepoints.colors[curcolori + 1].index == i) + uint32 cacheid = textureCacheID; + + const Glyph &glyph = findGlyph(info.glyphIndex); + + // If findGlyph invalidates the texture cache, restart the loop. + if (cacheid != textureCacheID) { - Colorf c = codepoints.colors[++curcolori].color; + i = -1; // The next iteration will increment this to 0. + commands.clear(); + vertices.resize(vertstartsize); + curcolori = 0; + curcolor = toColor32(constantcolor); + continue; + } + + if (curcolori < ncolors && colors[curcolori].index == i) + { + Colorf c = colors[curcolori].color; c.r = std::min(std::max(c.r, 0.0f), 1.0f); c.g = std::min(std::max(c.g, 0.0f), 1.0f); @@ -513,54 +414,17 @@ std::vector Font::generateVertices(const ColoredCodepoints &c unGammaCorrectColor(c); curcolor = toColor32(c); + curcolori++; } - if (g == '\n') - { - if (dx > maxwidth) - maxwidth = (int) dx; - - // Wrap newline, but do not print it. - dy += floorf(getHeight() * getLineHeight() + 0.5f); - dx = offset.x; - prevglyph = 0; - continue; - } - - // Ignore carriage returns - if (g == '\r') - continue; - - uint32 cacheid = textureCacheID; - - const Glyph &glyph = findGlyph(g); - - // If findGlyph invalidates the texture cache, re-start the loop. - if (cacheid != textureCacheID) - { - i = -1; // The next iteration will increment this to 0. - maxwidth = 0; - dx = offset.x; - dy = offset.y; - commands.clear(); - vertices.resize(vertstartsize); - prevglyph = 0; - curcolori = -1; - curcolor = toColor32(constantcolor); - continue; - } - - // Add kerning to the current horizontal offset. - dx += getKerning(prevglyph, g); - if (glyph.texture != nullptr) { // Copy the vertices and set their colors and relative positions. for (int j = 0; j < 4; j++) { vertices.push_back(glyph.vertices[j]); - vertices.back().x += dx; - vertices.back().y += dy + heightoffset; + vertices.back().x += info.position.x; + vertices.back().y += info.position.y; vertices.back().color = curcolor; } @@ -569,7 +433,7 @@ std::vector Font::generateVertices(const ColoredCodepoints &c { // Add a new draw command if the texture has changed. DrawCommand cmd; - cmd.startvertex = (int) vertices.size() - 4; + cmd.startvertex = (int)vertices.size() - 4; cmd.vertexcount = 0; cmd.texture = glyph.texture; commands.push_back(cmd); @@ -577,15 +441,6 @@ std::vector Font::generateVertices(const ColoredCodepoints &c commands.back().vertexcount += 4; } - - // Advance the x position for the next glyph. - dx += glyph.spacing; - - // Account for extra spacing given to space characters. - if (g == ' ' && extra_spacing != 0.0f) - dx = floorf(dx + extra_spacing); - - prevglyph = g; } const auto drawsort = [](const DrawCommand &a, const DrawCommand &b) -> bool @@ -599,19 +454,10 @@ std::vector Font::generateVertices(const ColoredCodepoints &c std::sort(commands.begin(), commands.end(), drawsort); - if (dx > maxwidth) - maxwidth = (int) dx; - - if (info != nullptr) - { - info->width = maxwidth - offset.x; - info->height = (int) dy + (dx > 0.0f ? floorf(getHeight() * getLineHeight() + 0.5f) : 0) - offset.y; - } - return commands; } -std::vector Font::generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, TextInfo *info) +std::vector Font::generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantcolor, float wrap, AlignMode align, std::vector &vertices, love::font::TextShaper::TextInfo *info) { wrap = std::max(wrap, 0.0f); @@ -620,17 +466,22 @@ std::vector Font::generateVerticesFormatted(const ColoredCode std::vector drawcommands; vertices.reserve(text.cps.size() * 4); + std::vector ranges; std::vector widths; - std::vector lines; - - getWrap(text, wrap, lines, &widths); + shaper->getWrap(text, wrap, ranges, &widths); float y = 0.0f; float maxwidth = 0.0f; - for (int i = 0; i < (int) lines.size(); i++) + for (int i = 0; i < (int)ranges.size(); i++) { - const auto &line = lines[i]; + const auto& range = ranges[i]; + + if (!range.isValid()) + { + y += getHeight() * getLineHeight(); + continue; + } float width = (float) widths[i]; love::Vector2 offset(0.0f, floorf(y)); @@ -648,7 +499,9 @@ std::vector Font::generateVerticesFormatted(const ColoredCode break; case ALIGN_JUSTIFY: { - float numspaces = (float) std::count(line.cps.begin(), line.cps.end(), ' '); + auto start = text.cps.begin() + range.getOffset(); + auto end = start + range.getSize(); + float numspaces = std::count(start, end, ' '); if (width < wrap && numspaces >= 1) extraspacing = (wrap - width) / numspaces; else @@ -660,7 +513,7 @@ std::vector Font::generateVerticesFormatted(const ColoredCode break; } - std::vector newcommands = generateVertices(line, constantcolor, vertices, extraspacing, offset); + std::vector newcommands = generateVertices(text, range, constantcolor, vertices, extraspacing, offset); if (!newcommands.empty()) { @@ -724,21 +577,21 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) +void Font::print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantcolor) { - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); std::vector vertices; - std::vector drawcommands = generateVertices(codepoints, constantcolor, vertices); + std::vector drawcommands = generateVertices(codepoints, Range(), constantcolor, vertices); printv(gfx, m, drawcommands, vertices); } -void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) +void Font::printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantcolor) { - ColoredCodepoints codepoints; - getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); std::vector vertices; std::vector drawcommands = generateVerticesFormatted(codepoints, constantcolor, wrap, align, vertices); @@ -748,241 +601,32 @@ void Font::printf(graphics::Graphics *gfx, const std::vector &tex int Font::getWidth(const std::string &str) { - if (str.size() == 0) return 0; - - std::istringstream iss(str); - std::string line; - int max_width = 0; - - while (getline(iss, line, '\n')) - { - int width = 0; - uint32 prevglyph = 0; - try - { - utf8::iterator i(line.begin(), line.begin(), line.end()); - utf8::iterator end(line.end(), line.begin(), line.end()); - - while (i != end) - { - uint32 c = *i++; - - // Ignore carriage returns - if (c == '\r') - continue; - - const Glyph &g = findGlyph(c); - width += g.spacing + getKerning(prevglyph, c); - - prevglyph = c; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - max_width = std::max(max_width, width); - } - - return max_width; + return shaper->getWidth(str); } int Font::getWidth(uint32 glyph) { - const Glyph &g = findGlyph(glyph); - return g.spacing; + return shaper->getGlyphAdvance(glyph); } -void Font::getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *linewidths) +void Font::getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector &ranges, std::vector *linewidths) { - // Per-line info. - float width = 0.0f; - float widthbeforelastspace = 0.0f; - float widthoftrailingspace = 0.0f; - uint32 prevglyph = 0; - - int lastspaceindex = -1; - - // Keeping the indexed colors "in sync" is a bit tricky, since we split - // things up and we might skip some glyphs but we don't want to skip any - // color which starts at those indices. - Colorf curcolor(1.0f, 1.0f, 1.0f, 1.0f); - bool addcurcolor = false; - int curcolori = -1; - int endcolori = (int) codepoints.colors.size() - 1; - - // A wrapped line of text. - ColoredCodepoints wline; - - int i = 0; - while (i < (int) codepoints.cps.size()) - { - uint32 c = codepoints.cps[i]; - - // Determine the current color before doing anything else, to make sure - // it's still applied to future glyphs even if this one is skipped. - if (curcolori < endcolori && codepoints.colors[curcolori + 1].index == i) - { - curcolor = codepoints.colors[curcolori + 1].color; - curcolori++; - addcurcolor = true; - } - - // Split text at newlines. - if (c == '\n') - { - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); - - // Make sure the new line keeps any color that was set previously. - addcurcolor = true; - - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - prevglyph = 0; // Reset kerning information. - lastspaceindex = -1; - wline.cps.clear(); - wline.colors.clear(); - i++; - - continue; - } - - // Ignore carriage returns - if (c == '\r') - { - i++; - continue; - } - - const Glyph &g = findGlyph(c); - float charwidth = g.spacing + getKerning(prevglyph, c); - float newwidth = width + charwidth; - - // Wrap the line if it exceeds the wrap limit. Don't wrap yet if we're - // processing a newline character, though. - if (c != ' ' && newwidth > wraplimit) - { - // If this is the first character in the line and it exceeds the - // limit, skip it completely. - if (wline.cps.empty()) - i++; - else if (lastspaceindex != -1) - { - // 'Rewind' to the last seen space, if the line has one. - // FIXME: This could be more efficient... - while (!wline.cps.empty() && wline.cps.back() != ' ') - wline.cps.pop_back(); - - while (!wline.colors.empty() && wline.colors.back().index >= (int) wline.cps.size()) - wline.colors.pop_back(); - - // Also 'rewind' to the color that the last character is using. - for (int colori = curcolori; colori >= 0; colori--) - { - if (codepoints.colors[colori].index <= lastspaceindex) - { - curcolor = codepoints.colors[colori].color; - curcolori = colori; - break; - } - } - - // Ignore the width of trailing spaces in wrapped lines. - width = widthbeforelastspace; - - i = lastspaceindex; - i++; // Start the next line after the space. - } - - lines.push_back(wline); - - if (linewidths) - linewidths->push_back(width); - - addcurcolor = true; - - prevglyph = 0; - width = widthbeforelastspace = widthoftrailingspace = 0.0f; - wline.cps.clear(); - wline.colors.clear(); - lastspaceindex = -1; - - continue; - } - - if (prevglyph != ' ' && c == ' ') - widthbeforelastspace = width; - - width = newwidth; - prevglyph = c; - - if (addcurcolor) - { - wline.colors.push_back({curcolor, (int) wline.cps.size()}); - addcurcolor = false; - } - - wline.cps.push_back(c); - - // Keep track of the last seen space, so we can "rewind" to it when - // wrapping. - if (c == ' ') - { - lastspaceindex = i; - widthoftrailingspace += charwidth; - } - else if (c != '\n') - widthoftrailingspace = 0.0f; - - i++; - } - - // Push the last line. - lines.push_back(wline); - - // Ignore the width of any trailing spaces, for individual lines. - if (linewidths) - linewidths->push_back(width - widthoftrailingspace); + shaper->getWrap(codepoints, wraplimit, ranges, linewidths); } -void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) +void Font::getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *linewidths) { - ColoredCodepoints cps; - getCodepointsFromString(text, cps); - - std::vector codepointlines; - getWrap(cps, wraplimit, codepointlines, linewidths); - - std::string line; - - for (const ColoredCodepoints &codepoints : codepointlines) - { - line.clear(); - line.reserve(codepoints.cps.size()); - - for (uint32 codepoint : codepoints.cps) - { - char character[5] = {'\0'}; - char *end = utf8::unchecked::append(codepoint, character); - line.append(character, end - character); - } - - lines.push_back(line); - } + shaper->getWrap(text, wraplimit, lines, linewidths); } void Font::setLineHeight(float height) { - lineHeight = height; + shaper->setLineHeight(height); } float Font::getLineHeight() const { - return lineHeight; + return shaper->getLineHeight(); } void Font::setSamplerState(const SamplerState &s) @@ -1002,75 +646,44 @@ const SamplerState &Font::getSamplerState() const int Font::getAscent() const { - return floorf(rasterizers[0]->getAscent() / dpiScale + 0.5f); + return shaper->getAscent(); } int Font::getDescent() const { - return floorf(rasterizers[0]->getDescent() / dpiScale + 0.5f); + return shaper->getDescent(); } float Font::getBaseline() const { - float ascent = getAscent(); - if (ascent != 0.0f) - return ascent; - else if (rasterizers[0]->getDataType() == font::Rasterizer::DATA_TRUETYPE) - return floorf(getHeight() / 1.25f + 0.5f); // 1.25 is magic line height for true type fonts - else - return 0.0f; + return shaper->getBaseline(); } bool Font::hasGlyph(uint32 glyph) const { - for (const StrongRef &r : rasterizers) - { - if (r->hasGlyph(glyph)) - return true; - } - - return false; + return shaper->hasGlyph(glyph); } bool Font::hasGlyphs(const std::string &text) const { - if (text.size() == 0) - return false; - - try - { - utf8::iterator i(text.begin(), text.begin(), text.end()); - utf8::iterator end(text.end(), text.begin(), text.end()); - - while (i != end) - { - uint32 codepoint = *i++; - - if (!hasGlyph(codepoint)) - return false; - } - } - catch (utf8::exception &e) - { - throw love::Exception("UTF-8 decoding error: %s", e.what()); - } - - return true; + return shaper->hasGlyphs(text); } void Font::setFallbacks(const std::vector &fallbacks) { - for (const Font *f : fallbacks) - { - if (f->rasterizers[0]->getDataType() != this->rasterizers[0]->getDataType()) - throw love::Exception("Font fallbacks must be of the same font type."); - } + std::vector rasterizerfallbacks; + for (const Font* f : fallbacks) + rasterizerfallbacks.push_back(f->shaper->getRasterizers()[0]); - rasterizers.resize(1); + shaper->setFallbacks(rasterizerfallbacks); - // NOTE: this won't invalidate already-rasterized glyphs. - for (const Font *f : fallbacks) - rasterizers.push_back(f->rasterizers[0]); + // Invalidate existing textures. + textureCacheID++; + glyphs.clear(); + while (textures.size() > 1) + textures.pop_back(); + + rowHeight = textureX = textureY = TEXTURE_PADDING; } float Font::getDPIScale() const diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index 90d9bb920..873fcc696 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -33,6 +33,7 @@ #include "common/Vector.h" #include "font/Rasterizer.h" +#include "font/TextShaper.h" #include "Texture.h" #include "vertex.h" #include "Volatile.h" @@ -64,30 +65,6 @@ public: ALIGN_MAX_ENUM }; - struct ColoredString - { - std::string str; - Colorf color; - }; - - struct IndexedColor - { - Colorf color; - int index; - }; - - struct ColoredCodepoints - { - std::vector cps; - std::vector colors; - }; - - struct TextInfo - { - int width; - int height; - }; - // Used to determine when to change textures in the generated vertex array. struct DrawCommand { @@ -100,20 +77,17 @@ public: virtual ~Font(); - std::vector generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantColor, std::vector &vertices, - float extra_spacing = 0.0f, Vector2 offset = {}, TextInfo *info = nullptr); + std::vector generateVertices(const love::font::ColoredCodepoints &codepoints, Range range, const Colorf &constantColor, std::vector &vertices, + float extra_spacing = 0.0f, Vector2 offset = {}, love::font::TextShaper::TextInfo *info = nullptr); - std::vector generateVerticesFormatted(const ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, - std::vector &vertices, TextInfo *info = nullptr); - - static void getCodepointsFromString(const std::string &str, Codepoints &codepoints); - static void getCodepointsFromString(const std::vector &strs, ColoredCodepoints &codepoints); + std::vector generateVerticesFormatted(const love::font::ColoredCodepoints &text, const Colorf &constantColor, float wrap, AlignMode align, + std::vector &vertices, love::font::TextShaper::TextInfo *info = nullptr); /** * Draws the specified text. **/ - void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); - void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); + void print(graphics::Graphics *gfx, const std::vector &text, const Matrix4 &m, const Colorf &constantColor); + void printf(graphics::Graphics *gfx, const std::vector &text, float wrap, AlignMode align, const Matrix4 &m, const Colorf &constantColor); /** * Returns the height of the font. @@ -141,8 +115,8 @@ public: * @param max_width Optional output of the maximum width * Returns a vector with the lines. **/ - void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); - void getWrap(const ColoredCodepoints &codepoints, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const std::vector &text, float wraplimit, std::vector &lines, std::vector *line_widths = nullptr); + void getWrap(const love::font::ColoredCodepoints &codepoints, float wraplimit, std::vector &ranges, std::vector *line_widths = nullptr); /** * Sets the line height (which should be a number to multiply the font size by, @@ -191,7 +165,6 @@ private: struct Glyph { Texture *texture; - int spacing; GlyphVertex vertices[4]; }; @@ -204,26 +177,20 @@ private: void createTexture(); TextureSize getNextTextureSize() const; - love::font::GlyphData *getRasterizerGlyphData(uint32 glyph, float &dpiscale); - const Glyph &addGlyph(uint32 glyph); - const Glyph &findGlyph(uint32 glyph); + love::font::GlyphData *getRasterizerGlyphData(love::font::TextShaper::GlyphIndex glyphindex, float &dpiscale); + const Glyph &addGlyph(love::font::TextShaper::GlyphIndex glyphindex); + const Glyph &findGlyph(love::font::TextShaper::GlyphIndex glyphindex); void printv(Graphics *gfx, const Matrix4 &t, const std::vector &drawcommands, const std::vector &vertices); - std::vector> rasterizers; - - int height; - float lineHeight; + StrongRef shaper; int textureWidth; int textureHeight; - std::vector> textures; + std::vector> textures; - // maps glyphs to glyph texture information - std::unordered_map glyphs; - - // map of left/right glyph pairs to horizontal kerning. - std::unordered_map kerning; + // maps packed glyph index values to glyph texture information + std::unordered_map glyphs; PixelFormat pixelFormat; @@ -234,8 +201,6 @@ private: int textureX, textureY; int rowHeight; - bool useSpacesAsTab; - // ID which is incremented when the texture cache is invalidated. uint32 textureCacheID; @@ -244,9 +209,6 @@ private: // use, for edge antialiasing. static const int TEXTURE_PADDING = 2; - // This will be used if the Rasterizer doesn't have a tab character itself. - static const int SPACES_PER_TAB = 4; - static StringMap::Entry alignModeEntries[]; static StringMap alignModes; diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 4be1b2351..895cc7dab 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -439,7 +439,7 @@ Mesh *Graphics::newMesh(const std::vector &attributes, Pr return new Mesh(attributes, drawmode); } -love::graphics::TextBatch *Graphics::newTextBatch(graphics::Font *font, const std::vector &text) +love::graphics::TextBatch *Graphics::newTextBatch(graphics::Font *font, const std::vector &text) { return new TextBatch(font, text); } @@ -1893,7 +1893,7 @@ void Graphics::drawShaderVertices(Buffer *indexbuffer, int indexcount, int insta draw(cmd); } -void Graphics::print(const std::vector &str, const Matrix4 &m) +void Graphics::print(const std::vector &str, const Matrix4 &m) { checkSetDefaultFont(); @@ -1901,12 +1901,12 @@ void Graphics::print(const std::vector &str, const Matrix4 print(str, states.back().font.get(), m); } -void Graphics::print(const std::vector &str, Font *font, const Matrix4 &m) +void Graphics::print(const std::vector &str, Font *font, const Matrix4 &m) { font->print(this, str, m, states.back().color); } -void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) +void Graphics::printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m) { checkSetDefaultFont(); @@ -1914,7 +1914,7 @@ void Graphics::printf(const std::vector &str, float wrap, F printf(str, states.back().font.get(), wrap, align, m); } -void Graphics::printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m) +void Graphics::printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m) { font->printf(this, str, wrap, align, m, states.back().color); } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 1e11aab0a..328665ac9 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -460,7 +460,7 @@ public: Mesh *newMesh(const std::vector &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage); Mesh *newMesh(const std::vector &attributes, PrimitiveType drawmode); - TextBatch *newTextBatch(Font *font, const std::vector &text = {}); + TextBatch *newTextBatch(Font *font, const std::vector &text = {}); data::ByteData *readbackBuffer(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); GraphicsReadback *readbackBufferAsync(Buffer *buffer, size_t offset, size_t size, data::ByteData *dest, size_t destoffset); @@ -702,14 +702,14 @@ public: /** * Draws text at the specified coordinates **/ - void print(const std::vector &str, const Matrix4 &m); - void print(const std::vector &str, Font *font, const Matrix4 &m); + void print(const std::vector &str, const Matrix4 &m); + void print(const std::vector &str, Font *font, const Matrix4 &m); /** * Draws formatted text on screen at the specified coordinates. **/ - void printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m); - void printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m); + void printf(const std::vector &str, float wrap, Font::AlignMode align, const Matrix4 &m); + void printf(const std::vector &str, Font *font, float wrap, Font::AlignMode align, const Matrix4 &m); /** * Draws a series of points at the specified positions. diff --git a/src/modules/graphics/TextBatch.cpp b/src/modules/graphics/TextBatch.cpp index 4c9e810a4..797e68bb1 100644 --- a/src/modules/graphics/TextBatch.cpp +++ b/src/modules/graphics/TextBatch.cpp @@ -30,7 +30,7 @@ namespace graphics love::Type TextBatch::type("TextBatch", &Drawable::type); -TextBatch::TextBatch(Font *font, const std::vector &text) +TextBatch::TextBatch(Font *font, const std::vector &text) : font(font) , vertexAttributes(Font::vertexFormat, 0) , vertexData(nullptr) @@ -112,13 +112,13 @@ void TextBatch::addTextData(const TextData &t) std::vector vertices; std::vector newcommands; - Font::TextInfo textinfo; + love::font::TextShaper::TextInfo textinfo; Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f); // We only have formatted text if the align mode is valid. if (t.align == Font::ALIGN_MAX_ENUM) - newcommands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); + newcommands = font->generateVertices(t.codepoints, Range(), constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); else newcommands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &textinfo); @@ -172,31 +172,31 @@ void TextBatch::addTextData(const TextData &t) regenerateVertices(); } -void TextBatch::set(const std::vector &text) +void TextBatch::set(const std::vector &text) { return set(text, -1.0f, Font::ALIGN_MAX_ENUM); } -void TextBatch::set(const std::vector &text, float wrap, Font::AlignMode align) +void TextBatch::set(const std::vector &text, float wrap, Font::AlignMode align) { if (text.empty() || (text.size() == 1 && text[0].str.empty())) return clear(); - Font::ColoredCodepoints codepoints; - Font::getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); addTextData({codepoints, wrap, align, {}, false, false, Matrix4()}); } -int TextBatch::add(const std::vector &text, const Matrix4 &m) +int TextBatch::add(const std::vector &text, const Matrix4 &m) { return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m); } -int TextBatch::addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m) +int TextBatch::addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m) { - Font::ColoredCodepoints codepoints; - Font::getCodepointsFromString(text, codepoints); + love::font::ColoredCodepoints codepoints; + love::font::getCodepointsFromString(text, codepoints); addTextData({codepoints, wrap, align, {}, true, true, m}); diff --git a/src/modules/graphics/TextBatch.h b/src/modules/graphics/TextBatch.h index 7284af9d5..82763fc27 100644 --- a/src/modules/graphics/TextBatch.h +++ b/src/modules/graphics/TextBatch.h @@ -40,14 +40,14 @@ public: static love::Type type; - TextBatch(Font *font, const std::vector &text = {}); + TextBatch(Font *font, const std::vector &text = {}); virtual ~TextBatch(); - void set(const std::vector &text); - void set(const std::vector &text, float wrap, Font::AlignMode align); + void set(const std::vector &text); + void set(const std::vector &text, float wrap, Font::AlignMode align); - int add(const std::vector &text, const Matrix4 &m); - int addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m); + int add(const std::vector &text, const Matrix4 &m); + int addf(const std::vector &text, float wrap, Font::AlignMode align, const Matrix4 &m); void clear(); @@ -71,10 +71,10 @@ private: struct TextData { - Font::ColoredCodepoints codepoints; + love::font::ColoredCodepoints codepoints; float wrap; Font::AlignMode align; - Font::TextInfo textInfo; + love::font::TextShaper::TextInfo textInfo; bool useMatrix; bool appendVertices; Matrix4 matrix; diff --git a/src/modules/graphics/wrap_Font.cpp b/src/modules/graphics/wrap_Font.cpp index 9814f0482..807feefbd 100644 --- a/src/modules/graphics/wrap_Font.cpp +++ b/src/modules/graphics/wrap_Font.cpp @@ -30,9 +30,9 @@ namespace love namespace graphics { -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings) { - Font::ColoredString coloredstr; + love::font::ColoredString coloredstr; coloredstr.color = Colorf(1.0f, 1.0f, 1.0f, 1.0f); if (lua_istable(L, idx)) @@ -103,7 +103,7 @@ int w_Font_getWrap(lua_State *L) { Font *t = luax_checkfont(L, 1); - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); float wrap = (float) luaL_checknumber(L, 3); diff --git a/src/modules/graphics/wrap_Font.h b/src/modules/graphics/wrap_Font.h index 39635d92b..69ff068d6 100644 --- a/src/modules/graphics/wrap_Font.h +++ b/src/modules/graphics/wrap_Font.h @@ -30,7 +30,7 @@ namespace graphics { Font *luax_checkfont(lua_State *L, int idx); -void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); +void luax_checkcoloredstring(lua_State *L, int idx, std::vector &strings); extern "C" int luaopen_font(lua_State *L); } // graphics diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 176335352..d50c19d24 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -2112,7 +2112,7 @@ int w_newTextBatch(lua_State *L) luax_catchexcept(L, [&](){ t = instance()->newTextBatch(font); }); else { - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); luax_catchexcept(L, [&](){ t = instance()->newTextBatch(font, text); }); @@ -3132,7 +3132,7 @@ int w_drawShaderVertices(lua_State *L) int w_print(lua_State *L) { - std::vector str; + std::vector str; luax_checkcoloredstring(L, 1, str); if (luax_istype(L, 2, Font::type)) @@ -3157,7 +3157,7 @@ int w_print(lua_State *L) int w_printf(lua_State *L) { - std::vector str; + std::vector str; luax_checkcoloredstring(L, 1, str); Font *font = nullptr; diff --git a/src/modules/graphics/wrap_TextBatch.cpp b/src/modules/graphics/wrap_TextBatch.cpp index c69cb0650..6a21da769 100644 --- a/src/modules/graphics/wrap_TextBatch.cpp +++ b/src/modules/graphics/wrap_TextBatch.cpp @@ -36,7 +36,7 @@ int w_TextBatch_set(lua_State *L) { TextBatch *t = luax_checktextbatch(L, 1); - std::vector newtext; + std::vector newtext; luax_checkcoloredstring(L, 2, newtext); luax_catchexcept(L, [&](){ t->set(newtext); }); @@ -54,7 +54,7 @@ int w_TextBatch_setf(lua_State *L) if (!Font::getConstant(alignstr, align)) return luax_enumerror(L, "align mode", Font::getConstants(align), alignstr); - std::vector newtext; + std::vector newtext; luax_checkcoloredstring(L, 2, newtext); luax_catchexcept(L, [&](){ t->set(newtext, wraplimit, align); }); @@ -68,7 +68,7 @@ int w_TextBatch_add(lua_State *L) int index = 0; - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); if (luax_istype(L, 3, math::Transform::type)) @@ -102,7 +102,7 @@ int w_TextBatch_addf(lua_State *L) int index = 0; - std::vector text; + std::vector text; luax_checkcoloredstring(L, 2, text); float wrap = (float) luaL_checknumber(L, 3);