Skip to content

Commit

Permalink
Add bold support for CJK characters (#3069)
Browse files Browse the repository at this point in the history
Co-authored-by: Yingfang <[email protected]>
Co-authored-by: Bart Louwers <[email protected]>
  • Loading branch information
3 people authored Dec 16, 2024
1 parent b44b644 commit fa27705
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 52 deletions.
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

0 comments on commit fa27705

Please sign in to comment.