Add ligatures button to Text tool (#4679)

Doesn't fully fix the issue as we cannot move the cursor between the
ligature clusters, but at least we can disable the ligatures now.
This commit is contained in:
David Capello 2024-09-25 16:37:41 -03:00
parent e700cce987
commit 5c7a3a41f6
14 changed files with 121 additions and 47 deletions

2
laf

@ -1 +1 @@
Subproject commit 851bdbc2454aa381131cd0972781aa86e149504a Subproject commit 61aba133e6de5197cdf490696ae7aba5f56273fd

View File

@ -26,24 +26,24 @@ FontInfo::FontInfo(Type type,
const std::string& name, const std::string& name,
const float size, const float size,
const text::FontStyle style, const text::FontStyle style,
const bool antialias) const Flags flags)
: m_type(type) : m_type(type)
, m_name(name) , m_name(name)
, m_size(size) , m_size(size)
, m_style(style) , m_style(style)
, m_antialias(antialias) , m_flags(flags)
{ {
} }
FontInfo::FontInfo(const FontInfo& other, FontInfo::FontInfo(const FontInfo& other,
const float size, const float size,
const text::FontStyle style, const text::FontStyle style,
const bool antialias) const Flags flags)
: m_type(other.type()) : m_type(other.type())
, m_name(other.name()) , m_name(other.name())
, m_size(size) , m_size(size)
, m_style(style) , m_style(style)
, m_antialias(antialias) , m_flags(flags)
{ {
} }
@ -89,7 +89,8 @@ FontInfo FontInfo::getFromPreferences()
pref.textTool.fontFace(), pref.textTool.fontFace(),
pref.textTool.fontSize(), pref.textTool.fontSize(),
text::FontStyle(), text::FontStyle(),
pref.textTool.antialias()); pref.textTool.antialias() ? FontInfo::Flags::Antialias :
FontInfo::Flags::None);
} }
// New configuration // New configuration
if (!pref.textTool.fontInfo().empty()) { if (!pref.textTool.fontInfo().empty()) {
@ -124,7 +125,7 @@ template<> app::FontInfo convert_to(const std::string& from)
float size = 0.0f; float size = 0.0f;
bool bold = false; bool bold = false;
bool italic = false; bool italic = false;
bool antialias = false; app::FontInfo::Flags flags = app::FontInfo::Flags::None;
if (!parts.empty()) { if (!parts.empty()) {
if (parts[0].compare(0, 5, "file=") == 0) { if (parts[0].compare(0, 5, "file=") == 0) {
@ -141,7 +142,9 @@ template<> app::FontInfo convert_to(const std::string& from)
} }
for (int i=1; i<parts.size(); ++i) { for (int i=1; i<parts.size(); ++i) {
if (parts[i] == "antialias") if (parts[i] == "antialias")
antialias = true; flags |= app::FontInfo::Flags::Antialias;
else if (parts[i] == "ligatures")
flags |= app::FontInfo::Flags::Ligatures;
else if (parts[i] == "bold") else if (parts[i] == "bold")
bold = true; bold = true;
else if (parts[i] == "italic") else if (parts[i] == "italic")
@ -157,7 +160,7 @@ template<> app::FontInfo convert_to(const std::string& from)
else if (bold) style = text::FontStyle::Bold(); else if (bold) style = text::FontStyle::Bold();
else if (italic) style = text::FontStyle::Italic(); else if (italic) style = text::FontStyle::Italic();
return app::FontInfo(type, name, size, style, antialias); return app::FontInfo(type, name, size, style, flags);
} }
template<> std::string convert_to(const app::FontInfo& from) template<> std::string convert_to(const app::FontInfo& from)
@ -186,6 +189,8 @@ template<> std::string convert_to(const app::FontInfo& from)
result += ",italic"; result += ",italic";
if (from.antialias()) if (from.antialias())
result += ",antialias"; result += ",antialias";
if (from.ligatures())
result += ",ligatures";
} }
return result; return result;
} }

View File

@ -9,6 +9,7 @@
#pragma once #pragma once
#include "base/convert_to.h" #include "base/convert_to.h"
#include "base/enum_flags.h"
#include "text/font_style.h" #include "text/font_style.h"
#include "text/fwd.h" #include "text/fwd.h"
#include "text/typeface.h" #include "text/typeface.h"
@ -28,18 +29,24 @@ namespace app {
System, System,
}; };
enum class Flags {
None = 0,
Antialias = 1,
Ligatures = 2,
};
static constexpr const float kDefaultSize = 0.0f; static constexpr const float kDefaultSize = 0.0f;
FontInfo(Type type = Type::Unknown, FontInfo(Type type = Type::Unknown,
const std::string& name = {}, const std::string& name = {},
float size = kDefaultSize, float size = kDefaultSize,
text::FontStyle style = text::FontStyle(), text::FontStyle style = text::FontStyle(),
bool antialias = false); Flags flags = Flags::None);
FontInfo(const FontInfo& other, FontInfo(const FontInfo& other,
float size, float size,
text::FontStyle style, text::FontStyle style,
bool antialias); Flags flags);
bool isValid() const { return m_type != Type::Unknown; } bool isValid() const { return m_type != Type::Unknown; }
bool useDefaultSize() const { return m_size == kDefaultSize; } bool useDefaultSize() const { return m_size == kDefaultSize; }
@ -57,7 +64,9 @@ namespace app {
float size() const { return m_size; } float size() const { return m_size; }
text::FontStyle style() const { return m_style; } text::FontStyle style() const { return m_style; }
bool antialias() const { return m_antialias; } Flags flags() const { return m_flags; }
bool antialias() const;
bool ligatures() const;
text::TypefaceRef findTypeface(const text::FontMgrRef& fontMgr) const; text::TypefaceRef findTypeface(const text::FontMgrRef& fontMgr) const;
@ -68,7 +77,7 @@ namespace app {
return (m_type == other.m_type && return (m_type == other.m_type &&
m_name == other.m_name && m_name == other.m_name &&
std::fabs(m_size-other.m_size) < 0.001f && std::fabs(m_size-other.m_size) < 0.001f &&
m_antialias == other.m_antialias); m_flags == other.m_flags);
} }
private: private:
@ -76,9 +85,19 @@ namespace app {
std::string m_name; std::string m_name;
float m_size = kDefaultSize; float m_size = kDefaultSize;
text::FontStyle m_style; text::FontStyle m_style;
bool m_antialias = false; Flags m_flags = Flags::None;
}; };
LAF_ENUM_FLAGS(FontInfo::Flags);
inline bool FontInfo::antialias() const {
return (m_flags & Flags::Antialias) == Flags::Antialias;
}
inline bool FontInfo::ligatures() const {
return (m_flags & Flags::Ligatures) == Flags::Ligatures;
}
} // namespace app } // namespace app
namespace base { namespace base {

View File

@ -47,9 +47,10 @@ TEST(FontInfo, ByFile)
TEST(FontInfo, BySystem) TEST(FontInfo, BySystem)
{ {
FontInfo a(FontInfo::Type::System, "FreeMono"); FontInfo a(FontInfo::Type::System, "FreeMono");
FontInfo b(FontInfo::Type::System, "DejaVu Serif", 12, text::FontStyle(), true); FontInfo b(FontInfo::Type::System, "DejaVu Serif", 12, text::FontStyle(),
FontInfo c(FontInfo::Type::System, "Arial", 14, text::FontStyle::Bold(), false); FontInfo::Flags::Antialias);
FontInfo d(FontInfo::Type::System, "Arial", 16, text::FontStyle::BoldItalic(), false); FontInfo c(FontInfo::Type::System, "Arial", 14, text::FontStyle::Bold());
FontInfo d(FontInfo::Type::System, "Arial", 16, text::FontStyle::BoldItalic());
EXPECT_EQ("system=FreeMono", base::convert_to<std::string>(a)); EXPECT_EQ("system=FreeMono", base::convert_to<std::string>(a));
EXPECT_EQ("system=DejaVu Serif,size=12,antialias", base::convert_to<std::string>(b)); EXPECT_EQ("system=DejaVu Serif,size=12,antialias", base::convert_to<std::string>(b));
EXPECT_EQ("system=Arial,size=14,bold", base::convert_to<std::string>(c)); EXPECT_EQ("system=Arial,size=14,bold", base::convert_to<std::string>(c));

View File

@ -148,6 +148,13 @@ private:
onNewTextBlob(); onNewTextBlob();
} }
text::ShaperFeatures onGetTextShaperFeatures() const override {
const FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
text::ShaperFeatures features;
features.ligatures = fontInfo.ligatures();
return features;
}
void onNewTextBlob() { void onNewTextBlob() {
text::TextBlobRef blob = textBlob(); text::TextBlobRef blob = textBlob();
if (!blob) if (!blob)

View File

@ -144,16 +144,25 @@ FontEntry::FontStyle::FontStyle()
setMultiMode(MultiMode::Set); setMultiMode(MultiMode::Set);
} }
FontEntry::FontLigatures::FontLigatures()
: ButtonSet(1, true)
{
addItem("fi");
setMultiMode(MultiMode::Set);
}
FontEntry::FontEntry() FontEntry::FontEntry()
: m_antialias("Antialias") : m_antialias("Antialias")
{ {
m_face.setExpansive(true); m_face.setExpansive(true);
m_size.setExpansive(false); m_size.setExpansive(false);
m_style.setExpansive(false); m_style.setExpansive(false);
m_ligatures.setExpansive(false);
m_antialias.setExpansive(false); m_antialias.setExpansive(false);
addChild(&m_face); addChild(&m_face);
addChild(&m_size); addChild(&m_size);
addChild(&m_style); addChild(&m_style);
addChild(&m_ligatures);
addChild(&m_antialias); addChild(&m_antialias);
m_face.setMinSize(gfx::Size(128*guiscale(), 0)); m_face.setMinSize(gfx::Size(128*guiscale(), 0));
@ -162,7 +171,7 @@ FontEntry::FontEntry()
setInfo(FontInfo(newTypeName, setInfo(FontInfo(newTypeName,
m_info.size(), m_info.size(),
m_info.style(), m_info.style(),
m_info.antialias()), m_info.flags()),
From::Face); From::Face);
invalidate(); invalidate();
}); });
@ -172,7 +181,7 @@ FontEntry::FontEntry()
setInfo(FontInfo(m_info, setInfo(FontInfo(m_info,
newSize, newSize,
m_info.style(), m_info.style(),
m_info.antialias()), m_info.flags()),
From::Size); From::Size);
}); });
@ -202,17 +211,21 @@ FontEntry::FontEntry()
setInfo(FontInfo(m_info, setInfo(FontInfo(m_info,
m_info.size(), m_info.size(),
style, style,
m_info.antialias()), m_info.flags()),
From::Style); From::Style);
}); });
m_antialias.Click.connect([this](){ auto flagsChange = [this]() {
setInfo(FontInfo(m_info, FontInfo::Flags flags = FontInfo::Flags::None;
m_info.size(), if (m_antialias.isSelected())
m_info.style(), flags |= FontInfo::Flags::Antialias;
m_antialias.isSelected()), if (m_ligatures.getItem(0)->isSelected())
From::Antialias); flags |= FontInfo::Flags::Ligatures;
}); setInfo(FontInfo(m_info, m_info.size(), m_info.style(), flags),
From::Flags);
};
m_ligatures.ItemChange.connect(flagsChange);
m_antialias.Click.connect(flagsChange);
} }
// Defined here as FontPopup type is not fully defined in the header // Defined here as FontPopup type is not fully defined in the header
@ -236,8 +249,10 @@ void FontEntry::setInfo(const FontInfo& info,
m_style.getItem(1)->setSelected(info.style().slant() != text::FontStyle::Slant::Upright); m_style.getItem(1)->setSelected(info.style().slant() != text::FontStyle::Slant::Upright);
} }
if (fromField != From::Antialias) if (fromField != From::Flags) {
m_ligatures.getItem(0)->setSelected(info.ligatures());
m_antialias.setSelected(info.antialias()); m_antialias.setSelected(info.antialias());
}
FontChange(m_info); FontChange(m_info);
} }

View File

@ -27,7 +27,7 @@ namespace app {
Face, Face,
Size, Size,
Style, Style,
Antialias, Flags,
}; };
FontEntry(); FontEntry();
@ -63,10 +63,16 @@ namespace app {
FontStyle(); FontStyle();
}; };
class FontLigatures : public ButtonSet {
public:
FontLigatures();
};
FontInfo m_info; FontInfo m_info;
FontFace m_face; FontFace m_face;
FontSize m_size; FontSize m_size;
FontStyle m_style; FontStyle m_style;
FontLigatures m_ligatures;
ui::CheckBox m_antialias; ui::CheckBox m_antialias;
}; };

View File

@ -64,7 +64,8 @@ public:
: ListItem(name) : ListItem(name)
, m_fontInfo(FontInfo::Type::Name, name, , m_fontInfo(FontInfo::Type::Name, name,
FontInfo::kDefaultSize, FontInfo::kDefaultSize,
text::FontStyle(), true) { text::FontStyle(),
FontInfo::Flags::Antialias) {
getCachedThumbnail(); getCachedThumbnail();
} }
@ -72,7 +73,8 @@ public:
: ListItem(base::get_file_title(fn)) : ListItem(base::get_file_title(fn))
, m_fontInfo(FontInfo::Type::File, fn, , m_fontInfo(FontInfo::Type::File, fn,
FontInfo::kDefaultSize, FontInfo::kDefaultSize,
text::FontStyle(), true) { text::FontStyle(),
FontInfo::Flags::Antialias) {
getCachedThumbnail(); getCachedThumbnail();
} }
@ -81,7 +83,8 @@ public:
const text::FontStyleSetRef& set) const text::FontStyleSetRef& set)
: ListItem(name) : ListItem(name)
, m_fontInfo(FontInfo::Type::System, name, , m_fontInfo(FontInfo::Type::System, name,
FontInfo::kDefaultSize, style, true) FontInfo::kDefaultSize, style,
FontInfo::Flags::Antialias)
, m_set(set) { , m_set(set) {
getCachedThumbnail(); getCachedThumbnail();
} }

View File

@ -180,8 +180,10 @@ doc::ImageRef render_text(
// fonts (e.g. if the given font is not enough to shape other code // fonts (e.g. if the given font is not enough to shape other code
// points/languages). // points/languages).
MeasureHandler handler; MeasureHandler handler;
text::ShaperFeatures features;
features.ligatures = fontInfo.ligatures();
text::TextBlobRef blob = text::TextBlobRef blob =
text::TextBlob::MakeWithShaper(fontMgr, font, text, &handler); text::TextBlob::MakeWithShaper(fontMgr, font, text, &handler, features);
if (!blob) if (!blob)
return nullptr; return nullptr;

View File

@ -958,7 +958,9 @@ void Entry::recalcCharBoxes(const std::string& text)
text::draw_text(nullptr, text::draw_text(nullptr,
theme()->fontMgr(), theme()->fontMgr(),
base::AddRef(font()), text, base::AddRef(font()), text,
gfx::ColorNone, gfx::ColorNone, 0, 0, &delegate).w; gfx::ColorNone, gfx::ColorNone, 0, 0,
&delegate,
onGetTextShaperFeatures()).w;
m_boxes = delegate.boxes(); m_boxes = delegate.boxes();
if (!m_boxes.empty()) { if (!m_boxes.empty()) {

View File

@ -26,6 +26,7 @@
#include "text/draw_text.h" #include "text/draw_text.h"
#include "text/font.h" #include "text/font.h"
#include "text/font_metrics.h" #include "text/font_metrics.h"
#include "text/shaper_features.h"
#include "ui/display.h" #include "ui/display.h"
#include "ui/scale.h" #include "ui/scale.h"
#include "ui/theme.h" #include "ui/theme.h"
@ -338,7 +339,8 @@ void Graphics::setFont(const text::FontRef& font)
void Graphics::drawText(const std::string& str, void Graphics::drawText(const std::string& str,
gfx::Color fg, gfx::Color bg, gfx::Color fg, gfx::Color bg,
const gfx::Point& origPt, const gfx::Point& origPt,
text::DrawTextDelegate* delegate) text::DrawTextDelegate* delegate,
text::ShaperFeatures features)
{ {
if (str.empty()) if (str.empty())
return; return;
@ -348,7 +350,7 @@ void Graphics::drawText(const std::string& str,
os::SurfaceLock lock(m_surface.get()); os::SurfaceLock lock(m_surface.get());
gfx::Rect textBounds = gfx::Rect textBounds =
text::draw_text(m_surface.get(), get_theme()->fontMgr(), text::draw_text(m_surface.get(), get_theme()->fontMgr(),
m_font, str, fg, bg, pt.x, pt.y, delegate); m_font, str, fg, bg, pt.x, pt.y, delegate, features);
dirty(gfx::Rect(pt.x, pt.y, textBounds.w, textBounds.h)); dirty(gfx::Rect(pt.x, pt.y, textBounds.w, textBounds.h));
} }

View File

@ -17,6 +17,8 @@
#include "gfx/size.h" #include "gfx/size.h"
#include "os/surface.h" #include "os/surface.h"
#include "text/font.h" #include "text/font.h"
#include "text/fwd.h"
#include "text/shaper_features.h"
#include "ui/paint.h" #include "ui/paint.h"
#include <memory> #include <memory>
@ -32,10 +34,6 @@ namespace os {
struct Sampling; struct Sampling;
} }
namespace text {
class DrawTextDelegate;
}
namespace ui { namespace ui {
class Display; class Display;
@ -110,7 +108,8 @@ namespace ui {
void drawText(const std::string& str, void drawText(const std::string& str,
gfx::Color fg, gfx::Color bg, gfx::Color fg, gfx::Color bg,
const gfx::Point& pt, const gfx::Point& pt,
text::DrawTextDelegate* delegate = nullptr); text::DrawTextDelegate* delegate = nullptr,
text::ShaperFeatures features = {});
void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, const int mnemonic); void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, const int mnemonic);
void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, const int align); void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, const int align);

View File

@ -929,12 +929,8 @@ void Widget::getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags)
text::TextBlobRef Widget::textBlob() const text::TextBlobRef Widget::textBlob() const
{ {
if (!m_blob && !m_text.empty()) { if (!m_blob && !m_text.empty())
m_blob = text::TextBlob::MakeWithShaper( m_blob = onMakeTextBlob();
theme()->fontMgr(),
AddRef(font()),
m_text);
}
return m_blob; return m_blob;
} }
@ -1789,6 +1785,21 @@ double Widget::onGetTextDouble() const
return std::strtod(m_text.c_str(), nullptr); return std::strtod(m_text.c_str(), nullptr);
} }
text::TextBlobRef Widget::onMakeTextBlob() const
{
return text::TextBlob::MakeWithShaper(
theme()->fontMgr(),
AddRef(font()),
text(),
nullptr,
onGetTextShaperFeatures());
}
text::ShaperFeatures Widget::onGetTextShaperFeatures() const
{
return text::ShaperFeatures();
}
void Widget::offsetWidgets(int dx, int dy) void Widget::offsetWidgets(int dx, int dy)
{ {
if (dx == 0 && dy == 0) if (dx == 0 && dy == 0)

View File

@ -442,6 +442,8 @@ namespace ui {
virtual void onSetBgColor(); virtual void onSetBgColor();
virtual int onGetTextInt() const; virtual int onGetTextInt() const;
virtual double onGetTextDouble() const; virtual double onGetTextDouble() const;
virtual text::TextBlobRef onMakeTextBlob() const;
virtual text::ShaperFeatures onGetTextShaperFeatures() const;
private: private:
void removeChild(const WidgetsList::iterator& it); void removeChild(const WidgetsList::iterator& it);