Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bold support for CJK characters #3069

Merged
merged 8 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions platform/darwin/src/local_glyph_rasterizer.mm
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#include <mbgl/text/local_glyph_rasterizer.hpp>
#include <mbgl/util/i18n.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/util/constants.hpp>

#include <mbgl/interface/native_apple_interface.h>

#include <unordered_map>

#import <Foundation/Foundation.h>
Expand Down Expand Up @@ -191,16 +194,28 @@ CFDictionaryRefHandle attributes(
@param font The font to apply to the codepoint.
@param metrics Upon return, the metrics match the font’s metrics for the glyph
representing the codepoint.
@param isBold use kCTFontBoldTrait if it is true.
@returns An image containing the glyph.
*/
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics) {
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics, BOOL isBold) {
CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1));
if (!string) {
throw std::runtime_error("Unable to create string from codepoint");
}

// Create a bold variant of the font
CTFontRefHandle boldFont(CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait));
if (!boldFont) {
CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font));
NSString* familyName = (__bridge NSString *)(*familyNameHandle);
std::string stdFamilyName(familyName.UTF8String);
Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName);
}

CTFontRef drawFont = isBold && boldFont ? *boldFont : font;

CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
CFTypeRef values[] = { drawFont };

CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
Expand Down Expand Up @@ -265,7 +280,7 @@ CGContextHandle context(CGBitmapContextCreate(
CGContextSetTextPosition(*context, 0.0, descent);

CTLineDraw(*line, *context);

return rgbaBitmap;
}

Expand All @@ -288,8 +303,16 @@ CGContextHandle context(CGBitmapContextCreate(
}

manufacturedGlyph.id = glyphID;
BOOL isBold = NO;
// Only check the first font name to detect if the user prefers using bold
if (!fontStack.empty()) {
std::string lowercaseFont = platform::lowercase(fontStack.front());
if (lowercaseFont.find("bold") != std::string::npos && lowercaseFont.find("semibold") == std::string::npos) {
isBold = YES;
}
}

PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics);
PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold);

Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
// Copy alpha values from RGBA bitmap into the AlphaImage output
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 21 additions & 48 deletions test/text/local_glyph_rasterizer.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/style/style.hpp>

#include <regex>

/*
LoadLocalCJKGlyph in glyph_manager.test.cpp exercises the
platform-independent part of LocalGlyphRasterizer. This test actually
exercises platform-dependent font loading code for whatever platform it runs
on. Different platforms have different default fonts, so adding a new
platform requires new "expected" fixtures.

At the time of writing, we don't run `mbgl-test` on iOS or Android, so the
only supported test platform is macOS. Supporting Android would require
adding a new test case (probably using the "Droid" font family). iOS should
theoretically work -- the "PingFang" font family used below is expected to be
available on all iOS devices, and we use a relatively high image diff
tolerance (0.05) to account for small changes between the many possible
variants of the PingFang family.
At the time of writing, we don't run this test on Android, that would require
adding a new test case (probably using the "Droid" font family).
*/

using namespace mbgl;
Expand All @@ -33,7 +30,14 @@ namespace {
class LocalGlyphRasterizerTest {
public:
LocalGlyphRasterizerTest(const std::optional<std::string> fontFamily)
: frontend(1, gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode::Unique, fontFamily) {}
: frontend(1, gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode::Unique, fontFamily) {
this->fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
}

util::RunLoop loop;
std::shared_ptr<StubFileSource> fileSource = std::make_shared<StubFileSource>();
Expand All @@ -59,12 +63,6 @@ class LocalGlyphRasterizerTest {
TEST(LocalGlyphRasterizer, PingFang) {
LocalGlyphRasterizerTest test(std::string("PingFang TC"));

test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
#if defined(__APPLE__) && !defined(__QT__)
test.checkRendering("ping_fang", 0.0161);
Expand All @@ -73,16 +71,19 @@ TEST(LocalGlyphRasterizer, PingFang) {
#endif // defined(__APPLE__)
}

TEST(LocalGlyphRasterizer, PingFangWithBoldInStyle) {
LocalGlyphRasterizerTest test(std::string("PingFang TC"));
std::stringstream ss;
ss << std::regex_replace(
util::read_file("test/fixtures/local_glyphs/mixed.json"), std::regex("NotoCJK"), "NotoCJK Bold");
test.map.getStyle().loadJSON(ss.str());
test.checkRendering("ping_fang_with_bold_in_style");
}

#if !defined(__QT__)
TEST(LocalGlyphRasterizer, PingFangSemibold) {
LocalGlyphRasterizerTest test(std::string("PingFang TC Semibold"));

test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
test.checkRendering("ping_fang_semibold", 0.0161);
}
Expand All @@ -94,13 +95,6 @@ TEST(LocalGlyphRasterizer, PingFangSemibold) {
TEST(LocalGlyphRasterizer, NotoSansCJK) {
LocalGlyphRasterizerTest test(std::string("Noto Sans CJK KR Regular"));

test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};

test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
test.checkRendering("noto_sans_cjk_kr_regular_qt");
}
Expand All @@ -110,13 +104,6 @@ TEST(LocalGlyphRasterizer, NoLocal) {
// Expectation: without any local fonts set, and without any CJK glyphs
// provided, the output should just contain basic latin characters.
LocalGlyphRasterizerTest test({});

test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
test.checkRendering("no_local", 0.001, 0.1);
}
Expand All @@ -126,13 +113,6 @@ TEST(LocalGlyphRasterizer, NoLocalWithContentInsets) {
// center. Rendered text should be on the same offset and keep the same size
// as with no offset.
LocalGlyphRasterizerTest test({});

test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
auto viewSize = test.frontend.getSize();
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));

Expand All @@ -149,13 +129,6 @@ TEST(LocalGlyphRasterizer, NoLocalWithContentInsetsAndPitch) {
// center. Rendered text should be on the same offset and keep the same size
// as with no offset.
LocalGlyphRasterizerTest test({});

test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
auto viewSize = test.frontend.getSize();
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));

Expand Down
Loading