From 8f4ef0fa2143434ee4a617b4f333c7bebdd36f97 Mon Sep 17 00:00:00 2001 From: Dom Chen Date: Tue, 21 Nov 2023 17:27:52 +0800 Subject: [PATCH] Add a drawSimpleText() method to the Canvas class. (#39) * Add a drawSimpleText() method to the Canvas class. * Fix build error. --- include/tgfx/core/Canvas.h | 9 +++++ include/tgfx/core/Font.h | 10 +++++- include/tgfx/core/TextBlob.h | 8 +++++ include/tgfx/core/Typeface.h | 17 +++++++-- include/tgfx/utils/UTF.h | 6 ++++ src/core/Canvas.cpp | 11 ++++++ src/core/SimpleTextBlob.cpp | 13 +++++++ src/core/Typeface.cpp | 35 +++++++++++++++++++ src/utils/SimpleTextShaper.cpp | 46 +++++++++++++++++++++++++ src/utils/SimpleTextShaper.h | 29 ++++++++++++++++ src/utils/UTF.cpp | 25 ++++++++++++++ src/vectors/coregraphics/CGTypeface.cpp | 6 ++-- src/vectors/coregraphics/CGTypeface.h | 2 +- src/vectors/freetype/FTTypeface.cpp | 13 ++----- src/vectors/freetype/FTTypeface.h | 2 +- src/vectors/web/WebTypeface.cpp | 15 ++++---- src/vectors/web/WebTypeface.h | 2 +- 17 files changed, 221 insertions(+), 28 deletions(-) create mode 100644 src/core/Typeface.cpp create mode 100644 src/utils/SimpleTextShaper.cpp create mode 100644 src/utils/SimpleTextShaper.h diff --git a/include/tgfx/core/Canvas.h b/include/tgfx/core/Canvas.h index 0b26f2da..046f527b 100644 --- a/include/tgfx/core/Canvas.h +++ b/include/tgfx/core/Canvas.h @@ -185,6 +185,15 @@ class Canvas { void drawImage(std::shared_ptr image, SamplingOptions sampling, const Paint* paint = nullptr); + /** + * Draws text, with origin at (x, y), using clip, matrix, font, and paint. The text must be in + * utf-8 encoding. This function uses the default character-to-glyph mapping from the Typeface in + * font. It does not perform typeface fallback for characters not found in the Typeface. Glyphs + * are positioned based on their default advances. + */ + void drawSimpleText(const std::string& text, float x, float y, const Font& font, + const Paint& paint); + /** * Draw an array of glyphs with specified font, using current alpha, blend mode, clip and Matrix. */ diff --git a/include/tgfx/core/Font.h b/include/tgfx/core/Font.h index 7da41658..cea2334b 100644 --- a/include/tgfx/core/Font.h +++ b/include/tgfx/core/Font.h @@ -103,12 +103,20 @@ class Font { /** * Returns the glyph ID corresponds to the specified glyph name. The glyph name must be in utf-8 - * encoding. Returns 0 if the glyph name is not associated with this typeface. + * encoding. Returns 0 if the glyph name is not in this Font. */ GlyphID getGlyphID(const std::string& name) const { return typeface->getGlyphID(name); } + /** + * Returns the glyph ID corresponds to the specified unicode code point. Returns 0 if the code + * point is not in this Font. + */ + GlyphID getGlyphID(Unichar unichar) const { + return typeface->getGlyphID(unichar); + } + /** * Returns the bounding box of the specified glyph. */ diff --git a/include/tgfx/core/TextBlob.h b/include/tgfx/core/TextBlob.h index d79709e2..de435f80 100644 --- a/include/tgfx/core/TextBlob.h +++ b/include/tgfx/core/TextBlob.h @@ -28,6 +28,14 @@ namespace tgfx { */ class TextBlob { public: + /** + * Creates a new TextBlob from the given text. The text must be in utf-8 encoding. This function + * uses the default character-to-glyph mapping from the Typeface in font. It does not perform + * typeface fallback for characters not found in the Typeface. Glyphs are positioned based on + * their default advances. + */ + static std::shared_ptr MakeFrom(const std::string& text, const Font& font); + /** * Creates a new TextBlob from the given glyphs, positions and text font. */ diff --git a/include/tgfx/core/Typeface.h b/include/tgfx/core/Typeface.h index 6a65b31d..2ba626ad 100644 --- a/include/tgfx/core/Typeface.h +++ b/include/tgfx/core/Typeface.h @@ -25,10 +25,15 @@ namespace tgfx { /** - * 16 bit unsigned integer to hold a glyph index + * 16 bit unsigned integer to hold a glyph index. */ typedef uint16_t GlyphID; +/** + * 32-bit signed integer to hold a UTF-32 code unit. + */ +typedef int32_t Unichar; + typedef uint32_t FontTableTag; /** @@ -101,9 +106,15 @@ class Typeface { /** * Returns the glyph ID corresponds to the specified glyph name. The glyph name must be in utf-8 - * encoding. Returns 0 if the glyph name is not associated with this typeface. + * encoding. Returns 0 if the glyph name is not in this typeface. + */ + GlyphID getGlyphID(const std::string& name) const; + + /** + * Returns the glyph ID corresponds to the specified unicode code point. Returns 0 if the code + * point is not in this typeface. */ - virtual GlyphID getGlyphID(const std::string& name) const = 0; + virtual GlyphID getGlyphID(Unichar unichar) const = 0; virtual std::shared_ptr getBytes() const = 0; diff --git a/include/tgfx/utils/UTF.h b/include/tgfx/utils/UTF.h index faca171a..e2133c8f 100644 --- a/include/tgfx/utils/UTF.h +++ b/include/tgfx/utils/UTF.h @@ -20,6 +20,7 @@ #include #include +#include namespace tgfx { class UTF { @@ -36,6 +37,11 @@ class UTF { * to end and return -1. */ static int32_t NextUTF8(const char** ptr, const char* end); + + /** + * Given a unicode codepoint, return the UTF-8 string. + */ + static std::string ToUTF8(int32_t unichar); }; } // namespace tgfx diff --git a/src/core/Canvas.cpp b/src/core/Canvas.cpp index 4d9fbece..456fdc33 100644 --- a/src/core/Canvas.cpp +++ b/src/core/Canvas.cpp @@ -34,7 +34,9 @@ #include "tgfx/core/PathEffect.h" #include "tgfx/core/TextBlob.h" #include "tgfx/gpu/Surface.h" +#include "tgfx/utils/UTF.h" #include "utils/MathExtra.h" +#include "utils/SimpleTextShaper.h" namespace tgfx { static uint32_t NextClipID() { @@ -547,6 +549,15 @@ void Canvas::drawMask(const Rect& bounds, std::shared_ptr mask, GpuPain setMatrix(oldMatrix); } +void Canvas::drawSimpleText(const std::string& text, float x, float y, const tgfx::Font& font, + const tgfx::Paint& paint) { + auto [glyphIDs, positions] = SimpleTextShaper::Shape(text, font); + for (auto& position : positions) { + position.offset(x, y); + } + drawGlyphs(glyphIDs.data(), positions.data(), glyphIDs.size(), font, paint); +} + void Canvas::drawGlyphs(const GlyphID glyphIDs[], const Point positions[], size_t glyphCount, const Font& font, const Paint& paint) { if (nothingToDraw(paint) || glyphCount == 0) { diff --git a/src/core/SimpleTextBlob.cpp b/src/core/SimpleTextBlob.cpp index 8dc5318f..1ee6f4ec 100644 --- a/src/core/SimpleTextBlob.cpp +++ b/src/core/SimpleTextBlob.cpp @@ -18,8 +18,21 @@ #include "core/SimpleTextBlob.h" #include "tgfx/core/PathEffect.h" +#include "utils/SimpleTextShaper.h" namespace tgfx { +std::shared_ptr TextBlob::MakeFrom(const std::string& text, const tgfx::Font& font) { + auto [glyphIDs, positions] = SimpleTextShaper::Shape(text, font); + if (glyphIDs.empty()) { + return nullptr; + } + auto textBlob = std::make_shared(); + textBlob->glyphIDs = std::move(glyphIDs); + textBlob->positions = std::move(positions); + textBlob->font = font; + return textBlob; +} + std::shared_ptr TextBlob::MakeFrom(const GlyphID glyphIDs[], const Point positions[], size_t glyphCount, const Font& font) { if (glyphCount == 0) { diff --git a/src/core/Typeface.cpp b/src/core/Typeface.cpp new file mode 100644 index 00000000..d54fa2d8 --- /dev/null +++ b/src/core/Typeface.cpp @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "tgfx/core/Typeface.h" +#include "tgfx/utils/UTF.h" + +namespace tgfx { +GlyphID Typeface::getGlyphID(const std::string& name) const { + if (name.empty()) { + return 0; + } + auto count = UTF::CountUTF8(name.c_str(), name.size()); + if (count <= 0) { + return 0; + } + const char* start = name.data(); + auto unichar = UTF::NextUTF8(&start, start + name.size()); + return getGlyphID(unichar); +} +} // namespace tgfx \ No newline at end of file diff --git a/src/utils/SimpleTextShaper.cpp b/src/utils/SimpleTextShaper.cpp new file mode 100644 index 00000000..b8ee89a9 --- /dev/null +++ b/src/utils/SimpleTextShaper.cpp @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "SimpleTextShaper.h" +#include "tgfx/utils/UTF.h" + +namespace tgfx { +std::pair, std::vector> SimpleTextShaper::Shape( + const std::string& text, const tgfx::Font& font) { + const char* textStart = text.data(); + const char* textStop = textStart + text.size(); + std::vector glyphs = {}; + std::vector positions = {}; + auto emptyGlyphID = font.getGlyphID(" "); + auto emptyAdvance = font.getAdvance(emptyGlyphID); + float xOffset = 0; + while (textStart < textStop) { + auto unichar = UTF::NextUTF8(&textStart, textStop); + auto glyphID = font.getGlyphID(unichar); + if (glyphID > 0) { + glyphs.push_back(glyphID); + positions.push_back(Point::Make(xOffset, 0.0f)); + auto advance = font.getAdvance(glyphID); + xOffset += advance; + } else { + xOffset += emptyAdvance; + } + } + return {glyphs, positions}; +} +} // namespace tgfx diff --git a/src/utils/SimpleTextShaper.h b/src/utils/SimpleTextShaper.h new file mode 100644 index 00000000..c2ad8cce --- /dev/null +++ b/src/utils/SimpleTextShaper.h @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "tgfx/core/Font.h" + +namespace tgfx { +class SimpleTextShaper { + public: + static std::pair, std::vector> Shape(const std::string& text, + const Font& font); +}; +} // namespace tgfx diff --git a/src/utils/UTF.cpp b/src/utils/UTF.cpp index d1b72c90..bcaa75ef 100644 --- a/src/utils/UTF.cpp +++ b/src/utils/UTF.cpp @@ -107,4 +107,29 @@ int32_t UTF::NextUTF8(const char** ptr, const char* end) { *ptr = (char*)p + 1; return c; } + +std::string UTF::ToUTF8(int32_t unichar) { + if ((uint32_t)unichar > 0x10FFFF) { + return ""; + } + if (unichar <= 127) { + return {(char)unichar}; + } + char tmp[4]; + char* p = tmp; + size_t count = 1; + while (unichar > 0x7F >> count) { + *p++ = (char)(0x80 | (unichar & 0x3F)); + unichar >>= 6; + count += 1; + } + char utf8[4]; + p = tmp; + char* to = utf8 + count; + while (p < tmp + count - 1) { + *--to = *p++; + } + *--to = (char)(~(0xFF >> count) | unichar); + return {utf8, count}; +} } // namespace tgfx diff --git a/src/vectors/coregraphics/CGTypeface.cpp b/src/vectors/coregraphics/CGTypeface.cpp index 124d98f9..72e1a865 100644 --- a/src/vectors/coregraphics/CGTypeface.cpp +++ b/src/vectors/coregraphics/CGTypeface.cpp @@ -185,11 +185,9 @@ static size_t ToUTF16(int32_t uni, uint16_t utf16[2]) { return 1 + extra; } -GlyphID CGTypeface::getGlyphID(const std::string& name) const { - const char* start = &(name[0]); - auto uni = UTF::NextUTF8(&start, start + name.size()); +GlyphID CGTypeface::getGlyphID(Unichar unichar) const { UniChar utf16[2] = {0, 0}; - auto srcCount = ToUTF16(uni, utf16); + auto srcCount = ToUTF16(unichar, utf16); GlyphID macGlyphs[2] = {0, 0}; CTFontGetGlyphsForCharacters(ctFont, utf16, macGlyphs, static_cast(srcCount)); return macGlyphs[0]; diff --git a/src/vectors/coregraphics/CGTypeface.h b/src/vectors/coregraphics/CGTypeface.h index 003ba9e4..0dabd4ab 100644 --- a/src/vectors/coregraphics/CGTypeface.h +++ b/src/vectors/coregraphics/CGTypeface.h @@ -48,7 +48,7 @@ class CGTypeface : public Typeface { bool hasColor() const override; - GlyphID getGlyphID(const std::string& name) const override; + GlyphID getGlyphID(Unichar unichar) const override; std::shared_ptr getBytes() const override; diff --git a/src/vectors/freetype/FTTypeface.cpp b/src/vectors/freetype/FTTypeface.cpp index 0b0449dc..e732126e 100644 --- a/src/vectors/freetype/FTTypeface.cpp +++ b/src/vectors/freetype/FTTypeface.cpp @@ -52,7 +52,7 @@ class EmptyTypeface : public Typeface { return false; } - GlyphID getGlyphID(const std::string&) const override { + GlyphID getGlyphID(Unichar) const override { return 0; } @@ -161,16 +161,7 @@ bool FTTypeface::hasColor() const { return FT_HAS_COLOR(_face->face); } -GlyphID FTTypeface::getGlyphID(const std::string& name) const { - if (name.empty()) { - return 0; - } - auto count = UTF::CountUTF8(name.c_str(), name.size()); - if (count > 1 || count <= 0) { - return 0; - } - const char* start = name.data(); - auto unichar = UTF::NextUTF8(&start, start + name.size()); +GlyphID FTTypeface::getGlyphID(Unichar unichar) const { return FT_Get_Char_Index(_face->face, static_cast(unichar)); } diff --git a/src/vectors/freetype/FTTypeface.h b/src/vectors/freetype/FTTypeface.h index de3e6e65..ac921cde 100644 --- a/src/vectors/freetype/FTTypeface.h +++ b/src/vectors/freetype/FTTypeface.h @@ -53,7 +53,7 @@ class FTTypeface : public Typeface { bool hasColor() const override; - GlyphID getGlyphID(const std::string& name) const override; + GlyphID getGlyphID(Unichar unichar) const override; std::shared_ptr getBytes() const override; diff --git a/src/vectors/web/WebTypeface.cpp b/src/vectors/web/WebTypeface.cpp index 16bf3d0c..4c6fbcef 100644 --- a/src/vectors/web/WebTypeface.cpp +++ b/src/vectors/web/WebTypeface.cpp @@ -19,6 +19,7 @@ #include "WebTypeface.h" #include #include "platform/web/WebImageBuffer.h" +#include "tgfx/utils/UTF.h" #include "utils/UniqueID.h" using namespace emscripten; @@ -74,24 +75,25 @@ bool WebTypeface::hasColor() const { } // The web side does not involve multithreading and does not require locking. -static std::unordered_map>& GlyphsMap() { - static auto& glyphs = *new std::unordered_map>; +static std::unordered_map>& GlyphsMap() { + static auto& glyphs = *new std::unordered_map>; return glyphs; } -GlyphID WebTypeface::getGlyphID(const std::string& text) const { +GlyphID WebTypeface::getGlyphID(Unichar unichar) const { + auto text = UTF::ToUTF8(unichar); if (!hasColor() && scalerContextClass.call("isEmoji", text)) { return 0; } auto& glyphs = GlyphsMap()[webFontFamily]; - auto iter = std::find(glyphs.begin(), glyphs.end(), text); + auto iter = std::find(glyphs.begin(), glyphs.end(), unichar); if (iter != glyphs.end()) { return iter - glyphs.begin() + 1; } if (glyphs.size() >= UINT16_MAX) { return 0; } - glyphs.push_back(text); + glyphs.push_back(unichar); return glyphs.size(); } @@ -107,7 +109,8 @@ std::string WebTypeface::getText(GlyphID glyphID) const { if (glyphID > glyphs.size()) { return ""; } - return glyphs.at(glyphID - 1); + auto unichar = glyphs.at(glyphID - 1); + return UTF::ToUTF8(unichar); } Point WebTypeface::getVerticalOffset(GlyphID glyphID, float size, bool fauxBold, diff --git a/src/vectors/web/WebTypeface.h b/src/vectors/web/WebTypeface.h index 161649db..905fc3f7 100644 --- a/src/vectors/web/WebTypeface.h +++ b/src/vectors/web/WebTypeface.h @@ -55,7 +55,7 @@ class WebTypeface : public Typeface { std::string getText(GlyphID glyphID) const; - GlyphID getGlyphID(const std::string&) const override; + GlyphID getGlyphID(Unichar unichar) const override; std::shared_ptr getBytes() const override;