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 config option to configure font fallback #1557

Merged
merged 1 commit into from
Aug 7, 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
1 change: 1 addition & 0 deletions metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<li>Add shell integration for bash shell.</li>
<li>Add better bell sound (#1378)</li>
<li>Add config entry to configure behaviour on exit from search mode</li>
<li>Add config entry to configure font fallback (#225)</li>
<li>Add handling of different input commands (#629)</li>
<li>Add key bindings disabled indicator for status line (#783)</li>
<li>When switching to normal mode screen will stay in same position (#808)</li>
Expand Down
19 changes: 19 additions & 0 deletions src/contour/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include <contour/Actions.h>
#include <contour/Config.h>

#include <text_shaper/font.h>

#include <crispy/StrongHash.h>
#include <crispy/escape.h>

Expand Down Expand Up @@ -1023,6 +1025,23 @@ void YAMLConfigReader::loadFromEntry(YAML::Node const& node,
loadFromEntry(child, "weight", where.weight);
loadFromEntry(child, "slant", where.slant);
loadFromEntry(child, "features", where.features);

if (child["fallback"])
{
if (child["fallback"].IsScalar() && (child["fallback"].as<std::string>() == "none"))
{
where.fontFallback = text::font_fallback_none {};
}
else if (child["fallback"].IsSequence())
{
where.fontFallback = text::font_fallback_list {};
auto& list = std::get<text::font_fallback_list>(where.fontFallback);
for (auto&& fallback: child["fallback"])
{
list.fallbackFonts.emplace_back(fallback.as<std::string>());
}
}
}
}
else // entries like emoji: "emoji"
{
Expand Down
7 changes: 7 additions & 0 deletions src/contour/ConfigDocumentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@ constexpr StringLiteral Fonts {
" {comment}\n"
" features: {}\n"
"\n"
" {comment} Customize fallback to use when rendering with the font\n"
" {comment} to disable any font fallback specify\n"
" {comment}fallback: none\n"
" {comment}\n"
" {comment} To specify a list of fallback fonts, use an array of strings.\n"
" {comment}fallback: [\"First\", \"Second\"]\n"
"\n"
" {comment} If bold/italic/bold_italic are not explicitly specified, the regular font with\n"
" {comment} the respective weight and slant will be used.\n"
" {comment}bold: \"monospace\"\n"
Expand Down
9 changes: 9 additions & 0 deletions src/crispy/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -565,4 +565,13 @@ inline bool beginsWith(std::basic_string_view<T> text, std::basic_string_view<T>

std::string threadName();

template <class... Ts>
struct overloaded: Ts...
{
using Ts::operator()...;
};

template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

} // namespace crispy
17 changes: 16 additions & 1 deletion src/text_shaper/coretext_locator.mm
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,24 @@ constexpr NSFontWeight makeFontWeight(font_weight value) noexcept
(CFArrayRef) NSLocale.preferredLanguages
);

if (cascadeList) {
if(std::holds_alternative<font_fallback_none>(description.fontFallback))
locatorLog()("Skipping fallback fonts as font fallback is set to none");
else if (cascadeList) {
for (CFIndex i = 0; i < CFArrayGetCount(cascadeList); i++) {
const auto* fallbackFont = (CTFontDescriptorRef) CFArrayGetValueAtIndex(cascadeList, i);

const auto* fallbackFontName = (NSString*) CTFontDescriptorCopyAttribute(fallbackFont, kCTFontFamilyNameAttribute);

if(const auto* list = std::get_if<font_fallback_list>(&description.fontFallback) )
{
locatorLog()("Checking if {} is in the list of allowed fallback fonts\n", std::string([fallbackFontName UTF8String]));
if( std::find(list->fallbackFonts.begin(), list->fallbackFonts.end(), std::string([fallbackFontName UTF8String])) == list->fallbackFonts.end())
{
locatorLog()("Skipping fallback font {} as it is not in the list of allowed fallback fonts", std::string([fallbackFontName UTF8String]));
continue;
}
}

if (fallbackFont) {
CTFontRef fallbackFontRef = CTFontCreateWithFontDescriptor(fallbackFont, 0.0, nullptr);
if (fallbackFontRef) {
Expand Down
12 changes: 12 additions & 0 deletions src/text_shaper/font.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>

namespace text
Expand Down Expand Up @@ -164,6 +165,14 @@ struct font_feature
font_feature& operator=(font_feature&&) = default;
};

struct font_fallback_none
{
};
struct font_fallback_list
{
std::vector<std::string> fallbackFonts;
};

struct font_description
{
std::string familyName { "regular" };
Expand All @@ -178,6 +187,9 @@ struct font_description

std::vector<font_feature> features {};

// std::monostate for the case when no fallback is defined.
std::variant<std::monostate, font_fallback_none, font_fallback_list> fontFallback { std::monostate {} };

// returns "familyName [weight] [slant]"
[[nodiscard]] std::string toPattern() const;

Expand Down
65 changes: 48 additions & 17 deletions src/text_shaper/fontconfig_locator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
#include <text_shaper/fontconfig_locator.h>

#include <crispy/assert.h>
#include <crispy/utils.h>

#include <range/v3/view/drop.hpp>
#include <range/v3/view/iota.hpp>

#include <fontconfig/fontconfig.h>

#include <string_view>
#include <variant>

using std::nullopt;
using std::optional;
Expand Down Expand Up @@ -214,23 +217,10 @@ font_source_list fontconfig_locator::locate(font_description const& description)
};
#endif

for (int i = 0; i < fs->nfont; ++i)
{
FcPattern* font = fs->fonts[i];

auto addFont = [&](auto const& font) {
FcChar8* file = nullptr;
if (FcPatternGetString(font, FC_FILE, 0, &file) != FcResultMatch)
continue;

#if defined(FC_COLOR) // Not available on macOS?
// FcBool color = FcFalse;
// FcPatternGetInteger(font, FC_COLOR, 0, &color);
// if (color && !color)
// {
// locatorLog()("Skipping font (contains color). {}", (char const*) file);
// continue;
// }
#endif
return;

int spacing = -1;
FcPatternGetInteger(font, FC_SPACING, 0, &spacing);
Expand All @@ -246,7 +236,7 @@ font_source_list fontconfig_locator::locate(font_description const& description)
(char const*) (file),
fcSpacingStr(spacing),
fcSpacingStr(FC_DUAL));
continue;
return;
}
}

Expand All @@ -270,7 +260,48 @@ font_source_list fontconfig_locator::locate(font_description const& description)
slant.has_value() ? fmt::format("{}", *slant) : "NONE",
spacing,
(char const*) file);
}
};

// First font is the primary font that is best matching for description.family, we always
// include that one.on.
addFont(fs->fonts[0]);

std::visit(crispy::overloaded {
[](font_fallback_none) {},
[&](font_fallback_list const& list) {
// find font in the fallback list and add it
for (auto&& fallbackFont: list.fallbackFonts)
{
for (auto i: ranges::views::ints(1, fs->nfont))
{
FcPattern* font = fs->fonts[i];

FcChar8* family = nullptr;
FcPatternGetString(font, FC_FAMILY, 0, &family);

// remove spaces from the fonts names
auto fallbackFontNoSpaces = fallbackFont;
fallbackFontNoSpaces.erase(
std::remove(fallbackFontNoSpaces.begin(), fallbackFontNoSpaces.end(), ' '),
fallbackFontNoSpaces.end());
std::string familyNoSpaces = (char const*) family;
familyNoSpaces.erase(
std::remove(familyNoSpaces.begin(), familyNoSpaces.end(), ' '),
familyNoSpaces.end());
if (fallbackFontNoSpaces == familyNoSpaces)
{
addFont(font);
break;
}
}
}
},
[&](std::monostate) {
for (auto i: ranges::views::ints(1, fs->nfont))
addFont(fs->fonts[i]);
},
},
description.fontFallback);

#if defined(_WIN32)
#define FONTDIR "C:\\Windows\\Fonts\\"
Expand Down
9 changes: 8 additions & 1 deletion test/font-fallback.txt
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
 🅝  
🅝
ئ
Х
Loading