mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-03 03:31:04 +03:00
Add configurable OpenType font features to text rendering:
Support explicit liga, calt, clig, and dlig shaping features and include text hints in layout caching, measurement, drawing, and character position calculations. Propagate scene-wide font feature defaults through UI text producers, with granular widget overrides and recursive cache invalidation. Update UICodeEditor and UIConsole to use shaped positioning when font features are enabled, including with monospaced fonts. Add separate UI and editor font feature settings to ecode, persist them as comma-separated OpenType tags, and apply changes immediately. Add localized menu labels, reusable Text feature conversion helpers, and regression coverage for shaping, positioning, propagation, and parsing.
This commit is contained in:
@@ -266,6 +266,16 @@ Soll es jetzt heruntergeladen werden?</string>
|
|||||||
<string name="edit_build">Build bearbeiten</string>
|
<string name="edit_build">Build bearbeiten</string>
|
||||||
<string name="edit_custom_output_parser">Angewählten individuellen Output-Parser bearbeiten</string>
|
<string name="edit_custom_output_parser">Angewählten individuellen Output-Parser bearbeiten</string>
|
||||||
<string name="editor_font_and_size_ellipsis">Editor-Schriftart und -größe...</string>
|
<string name="editor_font_and_size_ellipsis">Editor-Schriftart und -größe...</string>
|
||||||
|
<string name="editor_font_features">Editor-Schriftfunktionen</string>
|
||||||
|
<string name="ui_font_features">UI-Schriftfunktionen</string>
|
||||||
|
<string name="contextual_alternates">Kontextvarianten (calt)</string>
|
||||||
|
<string name="contextual_alternates_desc">Kontextabhängige Varianten, einschließlich vieler Programmierligaturen.</string>
|
||||||
|
<string name="standard_ligatures">Standardligaturen (liga)</string>
|
||||||
|
<string name="standard_ligatures_desc">Typografische Kombinationen wie fi, fl und ffi, abhängig von der Schriftart.</string>
|
||||||
|
<string name="contextual_ligatures">Kontextligaturen (clig)</string>
|
||||||
|
<string name="contextual_ligatures_desc">Ligaturen, die in bestimmten Kontexten die Lesbarkeit verbessern.</string>
|
||||||
|
<string name="discretionary_ligatures">Optionale Ligaturen (dlig)</string>
|
||||||
|
<string name="discretionary_ligatures_desc">Optionale dekorative oder stilistische Ligaturen der Schriftart.</string>
|
||||||
<string name="editor_font_size">Editor-Schriftgröße</string>
|
<string name="editor_font_size">Editor-Schriftgröße</string>
|
||||||
<string name="editor_go_back">Editor: Schritt zurück</string>
|
<string name="editor_go_back">Editor: Schritt zurück</string>
|
||||||
<string name="editor_go_forward">Editor: Schritt nach vorn</string>
|
<string name="editor_go_forward">Editor: Schritt nach vorn</string>
|
||||||
|
|||||||
@@ -250,6 +250,16 @@ Do you want to download it now?</string>
|
|||||||
<string name="edit_build">Edit Build</string>
|
<string name="edit_build">Edit Build</string>
|
||||||
<string name="edit_custom_output_parser">Edit Selected Custom Output Parser</string>
|
<string name="edit_custom_output_parser">Edit Selected Custom Output Parser</string>
|
||||||
<string name="editor_font_and_size_ellipsis">Editor Font & Size...</string>
|
<string name="editor_font_and_size_ellipsis">Editor Font & Size...</string>
|
||||||
|
<string name="editor_font_features">Editor Font Features</string>
|
||||||
|
<string name="ui_font_features">UI Font Features</string>
|
||||||
|
<string name="contextual_alternates">Contextual Alternates (calt)</string>
|
||||||
|
<string name="contextual_alternates_desc">Context-dependent alternatives, including many programming ligatures.</string>
|
||||||
|
<string name="standard_ligatures">Standard Ligatures (liga)</string>
|
||||||
|
<string name="standard_ligatures_desc">Typographic combinations such as fi, fl, and ffi, depending on the font.</string>
|
||||||
|
<string name="contextual_ligatures">Contextual Ligatures (clig)</string>
|
||||||
|
<string name="contextual_ligatures_desc">Ligatures applied in specific contexts to improve readability.</string>
|
||||||
|
<string name="discretionary_ligatures">Discretionary Ligatures (dlig)</string>
|
||||||
|
<string name="discretionary_ligatures_desc">Optional decorative or stylistic ligatures provided by the font.</string>
|
||||||
<string name="editor_font_size">Editor Font Size</string>
|
<string name="editor_font_size">Editor Font Size</string>
|
||||||
<string name="editor_go_back">Editor Go Back</string>
|
<string name="editor_go_back">Editor Go Back</string>
|
||||||
<string name="editor_go_forward">Editor Go Forward</string>
|
<string name="editor_go_forward">Editor Go Forward</string>
|
||||||
|
|||||||
@@ -251,6 +251,16 @@ Voulez-vous le télécharger maintenant ?</string>
|
|||||||
<string name="edit_build">Modifier la construction</string>
|
<string name="edit_build">Modifier la construction</string>
|
||||||
<string name="edit_custom_output_parser">Modifier l'analyseur de sortie personnalisé sélectionné</string>
|
<string name="edit_custom_output_parser">Modifier l'analyseur de sortie personnalisé sélectionné</string>
|
||||||
<string name="editor_font_and_size_ellipsis">Police et taille de l'éditeur...</string>
|
<string name="editor_font_and_size_ellipsis">Police et taille de l'éditeur...</string>
|
||||||
|
<string name="editor_font_features">Fonctionnalités de la police de l'éditeur</string>
|
||||||
|
<string name="ui_font_features">Fonctionnalités de la police de l'interface</string>
|
||||||
|
<string name="contextual_alternates">Variantes contextuelles (calt)</string>
|
||||||
|
<string name="contextual_alternates_desc">Variantes dépendant du contexte, dont de nombreuses ligatures de programmation.</string>
|
||||||
|
<string name="standard_ligatures">Ligatures standard (liga)</string>
|
||||||
|
<string name="standard_ligatures_desc">Combinaisons typographiques telles que fi, fl et ffi, selon la police.</string>
|
||||||
|
<string name="contextual_ligatures">Ligatures contextuelles (clig)</string>
|
||||||
|
<string name="contextual_ligatures_desc">Ligatures appliquées dans certains contextes pour améliorer la lisibilité.</string>
|
||||||
|
<string name="discretionary_ligatures">Ligatures discrétionnaires (dlig)</string>
|
||||||
|
<string name="discretionary_ligatures_desc">Ligatures décoratives ou stylistiques facultatives fournies par la police.</string>
|
||||||
<string name="editor_font_size">Taille de la police de l'éditeur</string>
|
<string name="editor_font_size">Taille de la police de l'éditeur</string>
|
||||||
<string name="editor_go_back">Éditeur : retour en arrière</string>
|
<string name="editor_go_back">Éditeur : retour en arrière</string>
|
||||||
<string name="editor_go_forward">Éditeur : avancer</string>
|
<string name="editor_go_forward">Éditeur : avancer</string>
|
||||||
|
|||||||
@@ -185,6 +185,16 @@
|
|||||||
<string name="edit_build">编辑构建</string>
|
<string name="edit_build">编辑构建</string>
|
||||||
<string name="edit_custom_output_parser">编辑已选的自定义输出解析器</string>
|
<string name="edit_custom_output_parser">编辑已选的自定义输出解析器</string>
|
||||||
<string name="editor_font_and_size_ellipsis">编辑器字体和大小...</string>
|
<string name="editor_font_and_size_ellipsis">编辑器字体和大小...</string>
|
||||||
|
<string name="editor_font_features">编辑器字体特性</string>
|
||||||
|
<string name="ui_font_features">界面字体特性</string>
|
||||||
|
<string name="contextual_alternates">上下文替代 (calt)</string>
|
||||||
|
<string name="contextual_alternates_desc">根据上下文应用的替代字形,包括许多编程连字。</string>
|
||||||
|
<string name="standard_ligatures">标准连字 (liga)</string>
|
||||||
|
<string name="standard_ligatures_desc">根据字体显示 fi、fl 和 ffi 等排版组合。</string>
|
||||||
|
<string name="contextual_ligatures">上下文连字 (clig)</string>
|
||||||
|
<string name="contextual_ligatures_desc">在特定上下文中应用连字以提高可读性。</string>
|
||||||
|
<string name="discretionary_ligatures">任意连字 (dlig)</string>
|
||||||
|
<string name="discretionary_ligatures_desc">字体提供的可选装饰性或风格化连字。</string>
|
||||||
<string name="editor_font_size">编辑器字体大小</string>
|
<string name="editor_font_size">编辑器字体大小</string>
|
||||||
<string name="editor_go_back">编辑器撤销</string>
|
<string name="editor_go_back">编辑器撤销</string>
|
||||||
<string name="editor_go_forward">编辑器重做</string>
|
<string name="editor_go_forward">编辑器重做</string>
|
||||||
|
|||||||
@@ -1363,6 +1363,12 @@ struct TextHints {
|
|||||||
AllAscii = 1 << 0,
|
AllAscii = 1 << 0,
|
||||||
AllLatin1 = 1 << 1,
|
AllLatin1 = 1 << 1,
|
||||||
NoKerning = 1 << 2,
|
NoKerning = 1 << 2,
|
||||||
|
StandardLigatures = 1 << 3,
|
||||||
|
ContextualAlternates = 1 << 4,
|
||||||
|
ContextualLigatures = 1 << 5,
|
||||||
|
DiscretionaryLigatures = 1 << 6,
|
||||||
|
OpenTypeFeatures =
|
||||||
|
StandardLigatures | ContextualAlternates | ContextualLigatures | DiscretionaryLigatures,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,10 @@ class EE_API RichText : public Drawable {
|
|||||||
|
|
||||||
Uint32 getTabWidth() const { return mTabWidth; }
|
Uint32 getTabWidth() const { return mTabWidth; }
|
||||||
|
|
||||||
|
void setTextHints( Uint32 textHints );
|
||||||
|
|
||||||
|
Uint32 getTextHints() const { return mTextHints; }
|
||||||
|
|
||||||
bool setExternalFloatExclusions( const std::vector<FloatExclusion>& exclusions );
|
bool setExternalFloatExclusions( const std::vector<FloatExclusion>& exclusions );
|
||||||
|
|
||||||
const std::vector<FloatExclusion>& getExternalFloatExclusions() const {
|
const std::vector<FloatExclusion>& getExternalFloatExclusions() const {
|
||||||
@@ -482,6 +486,7 @@ class EE_API RichText : public Drawable {
|
|||||||
bool mLineWrap{ true };
|
bool mLineWrap{ true };
|
||||||
WhiteSpaceWrapMode mWhiteSpaceWrapMode{ WhiteSpaceWrapMode::Normal };
|
WhiteSpaceWrapMode mWhiteSpaceWrapMode{ WhiteSpaceWrapMode::Normal };
|
||||||
Uint32 mTabWidth{ 8 };
|
Uint32 mTabWidth{ 8 };
|
||||||
|
Uint32 mTextHints{ 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace EE::Graphics
|
}} // namespace EE::Graphics
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class EE_API Text {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static inline bool canSkipShaping( Uint32 textDrawHints ) {
|
static inline bool canSkipShaping( Uint32 textDrawHints ) {
|
||||||
return Text::TextShaperOptimizations &&
|
return Text::TextShaperOptimizations && !( textDrawHints & TextHints::OpenTypeFeatures ) &&
|
||||||
( textDrawHints & ( TextHints::AllLatin1 | TextHints::AllAscii ) ) != 0;
|
( textDrawHints & ( TextHints::AllLatin1 | TextHints::AllAscii ) ) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,10 @@ class EE_API Text {
|
|||||||
|
|
||||||
static FontWeight stringToFontWeight( const std::string& str );
|
static FontWeight stringToFontWeight( const std::string& str );
|
||||||
|
|
||||||
|
static Uint32 fontFeaturesFromString( const std::string& value );
|
||||||
|
|
||||||
|
static std::string fontFeaturesToString( Uint32 features );
|
||||||
|
|
||||||
static Float getTextWidth( Font* font, const Uint32& fontSize, const String& string,
|
static Float getTextWidth( Font* font, const Uint32& fontSize, const String& string,
|
||||||
const Uint32& style, const Uint32& tabWidth = 4,
|
const Uint32& style, const Uint32& tabWidth = 4,
|
||||||
const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0,
|
const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0,
|
||||||
@@ -416,6 +420,7 @@ class EE_API Text {
|
|||||||
LineWrapMode mLineWrapMode{ LineWrapMode::NoWrap };
|
LineWrapMode mLineWrapMode{ LineWrapMode::NoWrap };
|
||||||
TextDirection mDirection{ TextDirection::Unspecified };
|
TextDirection mDirection{ TextDirection::Unspecified };
|
||||||
Vector2f mInitialOffset{ 0.f, 0.f };
|
Vector2f mInitialOffset{ 0.f, 0.f };
|
||||||
|
Uint32 mTextDrawHints{ 0 };
|
||||||
|
|
||||||
mutable SmallVector<Int64, 4> mVisualLines;
|
mutable SmallVector<Int64, 4> mVisualLines;
|
||||||
mutable SmallVector<Float, 4> mLinesWidth;
|
mutable SmallVector<Float, 4> mLinesWidth;
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ class EE_API DocumentView {
|
|||||||
bool keepIndentation{ true };
|
bool keepIndentation{ true };
|
||||||
Uint32 tabWidth{ 4 };
|
Uint32 tabWidth{ 4 };
|
||||||
std::optional<Uint32> maxCharactersWidth{};
|
std::optional<Uint32> maxCharactersWidth{};
|
||||||
|
Uint32 textHints{ 0 };
|
||||||
bool tabStops{ false };
|
bool tabStops{ false };
|
||||||
bool operator==( const Config& other ) {
|
bool operator==( const Config& other ) {
|
||||||
return mode == other.mode && keepIndentation == other.keepIndentation &&
|
return mode == other.mode && keepIndentation == other.keepIndentation &&
|
||||||
tabWidth == other.tabWidth && maxCharactersWidth == other.maxCharactersWidth;
|
tabWidth == other.tabWidth && maxCharactersWidth == other.maxCharactersWidth &&
|
||||||
|
textHints == other.textHints && tabStops == other.tabStops;
|
||||||
}
|
}
|
||||||
bool operator!=( const Config& other ) { return !( *this == other ); }
|
bool operator!=( const Config& other ) { return !( *this == other ); }
|
||||||
};
|
};
|
||||||
@@ -49,7 +51,8 @@ class EE_API DocumentView {
|
|||||||
const FontStyleConfig& fontStyle, Float maxWidth,
|
const FontStyleConfig& fontStyle, Float maxWidth,
|
||||||
LineWrapMode mode, bool keepIndentation,
|
LineWrapMode mode, bool keepIndentation,
|
||||||
Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f,
|
Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f,
|
||||||
bool tabStops = false, Float initialXOffset = 0.f );
|
bool tabStops = false, Float initialXOffset = 0.f,
|
||||||
|
Uint32 textHints = 0 );
|
||||||
|
|
||||||
DocumentView( std::shared_ptr<TextDocument> doc, FontStyleConfig fontStyle, Config config );
|
DocumentView( std::shared_ptr<TextDocument> doc, FontStyleConfig fontStyle, Config config );
|
||||||
|
|
||||||
@@ -137,6 +140,8 @@ class EE_API DocumentView {
|
|||||||
|
|
||||||
void setTabStops( bool enabled );
|
void setTabStops( bool enabled );
|
||||||
|
|
||||||
|
void setTextHints( Uint32 textHints );
|
||||||
|
|
||||||
bool usesTabStops() const { return mConfig.tabStops; }
|
bool usesTabStops() const { return mConfig.tabStops; }
|
||||||
|
|
||||||
const std::vector<Int64> getDocLineToVisibleIndex() const { return mDocLineToVisibleIndex; }
|
const std::vector<Int64> getDocLineToVisibleIndex() const { return mDocLineToVisibleIndex; }
|
||||||
|
|||||||
@@ -841,6 +841,14 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
|||||||
|
|
||||||
bool isKerningEnabled() const;
|
bool isKerningEnabled() const;
|
||||||
|
|
||||||
|
void setLigatureFeatures( Uint32 features );
|
||||||
|
|
||||||
|
Uint32 getLigatureFeatures() const;
|
||||||
|
|
||||||
|
void clearLigaturesOverride();
|
||||||
|
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
void setTextDirection( TextDirection direction );
|
void setTextDirection( TextDirection direction );
|
||||||
|
|
||||||
TextDirection getTextDirection() const;
|
TextDirection getTextDirection() const;
|
||||||
@@ -909,6 +917,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
|||||||
bool mAllowSelectingTextFromGutter{ true };
|
bool mAllowSelectingTextFromGutter{ true };
|
||||||
bool mTabStops{ false };
|
bool mTabStops{ false };
|
||||||
bool mKerningEnabled{ false };
|
bool mKerningEnabled{ false };
|
||||||
|
bool mLigaturesOverride{ false };
|
||||||
bool mDisableScrollInvalidation{ false };
|
bool mDisableScrollInvalidation{ false };
|
||||||
bool mDynamicTheming{ false };
|
bool mDynamicTheming{ false };
|
||||||
bool mUpdatingScrollBar{ false };
|
bool mUpdatingScrollBar{ false };
|
||||||
@@ -917,6 +926,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
|||||||
Time mBlinkTime;
|
Time mBlinkTime;
|
||||||
Time mFoldsRefreshTime;
|
Time mFoldsRefreshTime;
|
||||||
Uint32 mTabWidth;
|
Uint32 mTabWidth;
|
||||||
|
Uint32 mLigatureFeatures{ 0 };
|
||||||
std::atomic<size_t> mHighlightWordProcessing{ false };
|
std::atomic<size_t> mHighlightWordProcessing{ false };
|
||||||
TextRange mLinkPosition;
|
TextRange mLinkPosition;
|
||||||
String mLink;
|
String mLink;
|
||||||
@@ -1231,7 +1241,11 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
|||||||
void addCursorsFromCurrentToMousePosition();
|
void addCursorsFromCurrentToMousePosition();
|
||||||
|
|
||||||
inline Uint32 getWidgetTextDrawHints() const {
|
inline Uint32 getWidgetTextDrawHints() const {
|
||||||
return mKerningEnabled ? 0 : TextHints::NoKerning;
|
const Uint32 ligatureFeatures = mLigaturesOverride
|
||||||
|
? mLigatureFeatures
|
||||||
|
: getDefaultTextHints() & TextHints::OpenTypeFeatures;
|
||||||
|
return ( mKerningEnabled ? 0 : TextHints::NoKerning ) |
|
||||||
|
( ligatureFeatures & TextHints::OpenTypeFeatures );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setInternalFontSize( const Float& size );
|
bool setInternalFontSize( const Float& size );
|
||||||
|
|||||||
@@ -153,6 +153,12 @@ class EE_API UIConsole : public UIWidget,
|
|||||||
|
|
||||||
TextDocument& getDoc();
|
TextDocument& getDoc();
|
||||||
|
|
||||||
|
void setLigatureFeatures( Uint32 features );
|
||||||
|
Uint32 getLigatureFeatures() const;
|
||||||
|
void clearLigaturesOverride();
|
||||||
|
Uint32 getTextHints() const;
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
|
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -171,6 +177,7 @@ class EE_API UIConsole : public UIWidget,
|
|||||||
std::vector<TextCache> mTextCache;
|
std::vector<TextCache> mTextCache;
|
||||||
UIFontStyleConfig mFontStyleConfig;
|
UIFontStyleConfig mFontStyleConfig;
|
||||||
Uint32 mMaxLogLines{ 8192 };
|
Uint32 mMaxLogLines{ 8192 };
|
||||||
|
Uint32 mLigatureFeatures{ 0 };
|
||||||
TextDocument mDoc;
|
TextDocument mDoc;
|
||||||
KeyBindings mKeyBindings;
|
KeyBindings mKeyBindings;
|
||||||
TextRange mSelection;
|
TextRange mSelection;
|
||||||
@@ -192,6 +199,7 @@ class EE_API UIConsole : public UIWidget,
|
|||||||
Clock mBlinkTimer;
|
Clock mBlinkTimer;
|
||||||
Time mBlinkTime{ Seconds( 0.f ) };
|
Time mBlinkTime{ Seconds( 0.f ) };
|
||||||
bool mCursorVisible{ true };
|
bool mCursorVisible{ true };
|
||||||
|
bool mLigaturesOverride{ false };
|
||||||
int mLastLogPos{ 0 };
|
int mLastLogPos{ 0 };
|
||||||
#if EE_PLATFORM == EE_PLATFORM_ANDROID || EE_PLATFORM == EE_PLATFORM_IOS
|
#if EE_PLATFORM == EE_PLATFORM_ANDROID || EE_PLATFORM == EE_PLATFORM_IOS
|
||||||
Float mQuakeModeHeightPercent{ 0.5f };
|
Float mQuakeModeHeightPercent{ 0.5f };
|
||||||
|
|||||||
@@ -185,6 +185,14 @@ class EE_API UIRichText : public UIHTMLWidget {
|
|||||||
|
|
||||||
virtual RichText* getRichTextPtr() { return &mRichText; }
|
virtual RichText* getRichTextPtr() { return &mRichText; }
|
||||||
|
|
||||||
|
void setTextHintsOverride( Uint32 value, Uint32 mask = TextHints::OpenTypeFeatures );
|
||||||
|
|
||||||
|
void clearTextHintsOverride();
|
||||||
|
|
||||||
|
Uint32 getTextHints() const;
|
||||||
|
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RichText mRichText;
|
RichText mRichText;
|
||||||
Int64 mSelCurInit{ 0 };
|
Int64 mSelCurInit{ 0 };
|
||||||
@@ -197,6 +205,8 @@ class EE_API UIRichText : public UIHTMLWidget {
|
|||||||
mutable Float mTextIndentPxCache{ 0 };
|
mutable Float mTextIndentPxCache{ 0 };
|
||||||
mutable bool mTextIndentPxDirty{ true };
|
mutable bool mTextIndentPxDirty{ true };
|
||||||
Uint32 mTabSize{ 8 };
|
Uint32 mTabSize{ 8 };
|
||||||
|
Uint32 mTextHintsOverride{ 0 };
|
||||||
|
Uint32 mTextHintsOverrideMask{ 0 };
|
||||||
WhiteSpaceCollapse mWhiteSpaceCollapse{ WhiteSpaceCollapse::Collapse };
|
WhiteSpaceCollapse mWhiteSpaceCollapse{ WhiteSpaceCollapse::Collapse };
|
||||||
bool mLineWrap{ true };
|
bool mLineWrap{ true };
|
||||||
TextTransform::Value mTextTransform{ TextTransform::None };
|
TextTransform::Value mTextTransform{ TextTransform::None };
|
||||||
|
|||||||
@@ -742,6 +742,14 @@ class EE_API UISceneNode : public SceneNode {
|
|||||||
*/
|
*/
|
||||||
void setContrastPreference( const ContrastPreference& contrastPreference );
|
void setContrastPreference( const ContrastPreference& contrastPreference );
|
||||||
|
|
||||||
|
/** Sets the OpenType feature hints inherited by text-producing widgets in this scene. */
|
||||||
|
void setDefaultTextHints( Uint32 textHints );
|
||||||
|
|
||||||
|
Uint32 getDefaultTextHints() const;
|
||||||
|
|
||||||
|
static Uint32 resolveTextHints( Uint32 defaultHints, Uint32 overrideValue,
|
||||||
|
Uint32 overrideMask );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gets the maximum invalidation depth.
|
* @brief Gets the maximum invalidation depth.
|
||||||
*
|
*
|
||||||
@@ -983,6 +991,7 @@ class EE_API UISceneNode : public SceneNode {
|
|||||||
std::vector<UISceneNode*> mChildUISceneNodes;
|
std::vector<UISceneNode*> mChildUISceneNodes;
|
||||||
ColorSchemePreference mColorSchemePreference{ ColorSchemePreference::Dark };
|
ColorSchemePreference mColorSchemePreference{ ColorSchemePreference::Dark };
|
||||||
ContrastPreference mContrastPreference{ ContrastPreference::NoPreference };
|
ContrastPreference mContrastPreference{ ContrastPreference::NoPreference };
|
||||||
|
Uint32 mDefaultTextHints{ 0 };
|
||||||
Uint32 mMaxInvalidationDepth{ 3 };
|
Uint32 mMaxInvalidationDepth{ 3 };
|
||||||
Node* mCurParent{ nullptr };
|
Node* mCurParent{ nullptr };
|
||||||
UISceneNode* mHostUISceneNode{ nullptr };
|
UISceneNode* mHostUISceneNode{ nullptr };
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ class EE_API UITextInput : public UITextView, public TextDocument::Client {
|
|||||||
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
|
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
TextDocument mDoc;
|
TextDocument mDoc;
|
||||||
Float mWaitCursorTime;
|
Float mWaitCursorTime;
|
||||||
Vector2f mCurPos;
|
Vector2f mCurPos;
|
||||||
|
|||||||
@@ -37,10 +37,17 @@ class EE_API UITextNode : public UIWidget {
|
|||||||
|
|
||||||
Float getBaseline() const;
|
Float getBaseline() const;
|
||||||
|
|
||||||
|
void setTextHintsOverride( Uint32 value, Uint32 mask = TextHints::OpenTypeFeatures );
|
||||||
|
void clearTextHintsOverride();
|
||||||
|
Uint32 getTextHints() const;
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
String mText;
|
String mText;
|
||||||
size_t mLayoutCharCount{ 0 };
|
size_t mLayoutCharCount{ 0 };
|
||||||
Text* mFlexText{ nullptr };
|
Text* mFlexText{ nullptr };
|
||||||
|
Uint32 mTextHintsOverride{ 0 };
|
||||||
|
Uint32 mTextHintsOverrideMask{ 0 };
|
||||||
|
|
||||||
UITextNode();
|
UITextNode();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -134,6 +134,14 @@ class EE_API UITextView : public UIWidget {
|
|||||||
|
|
||||||
void setTextSelectionRange( TextSelectionRange range );
|
void setTextSelectionRange( TextSelectionRange range );
|
||||||
|
|
||||||
|
void setTextHintsOverride( Uint32 value, Uint32 mask = TextHints::OpenTypeFeatures );
|
||||||
|
|
||||||
|
void clearTextHintsOverride();
|
||||||
|
|
||||||
|
Uint32 getTextHints() const;
|
||||||
|
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Text mTextCache;
|
Text mTextCache;
|
||||||
String mString;
|
String mString;
|
||||||
@@ -142,6 +150,8 @@ class EE_API UITextView : public UIWidget {
|
|||||||
Int32 mSelCurInit;
|
Int32 mSelCurInit;
|
||||||
Int32 mSelCurEnd;
|
Int32 mSelCurEnd;
|
||||||
Uint32 mTextDrawHints{ 0 };
|
Uint32 mTextDrawHints{ 0 };
|
||||||
|
Uint32 mTextHintsOverride{ 0 };
|
||||||
|
Uint32 mTextHintsOverrideMask{ 0 };
|
||||||
SmallVector<Rectf> mSelRectsCache;
|
SmallVector<Rectf> mSelRectsCache;
|
||||||
Int32 mLastSelCurInit;
|
Int32 mLastSelCurInit;
|
||||||
Int32 mLastSelCurEnd;
|
Int32 mLastSelCurEnd;
|
||||||
|
|||||||
@@ -135,6 +135,14 @@ class EE_API UITooltip : public UIWidget {
|
|||||||
|
|
||||||
bool isWordWrap() const;
|
bool isWordWrap() const;
|
||||||
|
|
||||||
|
void setTextHintsOverride( Uint32 value, Uint32 mask = TextHints::OpenTypeFeatures );
|
||||||
|
|
||||||
|
void clearTextHintsOverride();
|
||||||
|
|
||||||
|
Uint32 getTextHints() const;
|
||||||
|
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Text* mTextCache{ nullptr };
|
Text* mTextCache{ nullptr };
|
||||||
UIFontStyleConfig mStyleConfig;
|
UIFontStyleConfig mStyleConfig;
|
||||||
@@ -145,6 +153,8 @@ class EE_API UITooltip : public UIWidget {
|
|||||||
TextTransform::Value mTextTransform{ TextTransform::None };
|
TextTransform::Value mTextTransform{ TextTransform::None };
|
||||||
bool mDontAutoHideOnMouseMove{ false };
|
bool mDontAutoHideOnMouseMove{ false };
|
||||||
bool mUsingCustomStyling{ false };
|
bool mUsingCustomStyling{ false };
|
||||||
|
Uint32 mTextHintsOverride{ 0 };
|
||||||
|
Uint32 mTextHintsOverrideMask{ 0 };
|
||||||
|
|
||||||
UITooltip();
|
UITooltip();
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ class EE_API UIWidget : public UINode {
|
|||||||
return UIWidget::getType() == type || UINode::isType( type );
|
return UIWidget::getType() == type || UINode::isType( type );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the scene-level text rendering hints inherited by this widget. */
|
||||||
|
Uint32 getDefaultTextHints() const;
|
||||||
|
|
||||||
|
/** Called when the scene-level text rendering hints change. */
|
||||||
|
virtual void onTextHintsChanged();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sets multiple flags on the widget.
|
* @brief Sets multiple flags on the widget.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -502,6 +502,17 @@ static void setInlineItemTextTabWidth( std::vector<RichText::InlineItem>& items,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void setInlineItemTextHints( std::vector<RichText::InlineItem>& items, Uint32 textHints ) {
|
||||||
|
for ( auto& item : items ) {
|
||||||
|
if ( item.isTextRun() ) {
|
||||||
|
if ( item.asTextRun().text )
|
||||||
|
item.asTextRun().text->setTextHints( textHints );
|
||||||
|
} else if ( item.isBox() ) {
|
||||||
|
setInlineItemTextHints( item.asBox().children, textHints );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InlineAncestorRef {
|
struct InlineAncestorRef {
|
||||||
RichText::RenderSpan::InlinePath path;
|
RichText::RenderSpan::InlinePath path;
|
||||||
const RichText::InlineItem::Box* box{ nullptr };
|
const RichText::InlineItem::Box* box{ nullptr };
|
||||||
@@ -1947,6 +1958,7 @@ class RichTextInlineLayouter {
|
|||||||
renderStyle.Style |= inlineAncestorTextDecoration( inlineItems, payload.inlinePath );
|
renderStyle.Style |= inlineAncestorTextDecoration( inlineItems, payload.inlinePath );
|
||||||
renderSpanText->setStyleConfig( renderStyle );
|
renderSpanText->setStyleConfig( renderStyle );
|
||||||
renderSpanText->setTabWidth( payload.text->getTabWidth() );
|
renderSpanText->setTabWidth( payload.text->getTabWidth() );
|
||||||
|
renderSpanText->setTextHints( payload.text->getTextHints() & TextHints::OpenTypeFeatures );
|
||||||
|
|
||||||
Float height = getTextVisualLineHeight( payload.text, payload.lineHeight );
|
Float height = getTextVisualLineHeight( payload.text, payload.lineHeight );
|
||||||
Float lineHeight = getTextRunLineHeight( payload.text, payload.lineHeight );
|
Float lineHeight = getTextRunLineHeight( payload.text, payload.lineHeight );
|
||||||
@@ -2313,6 +2325,7 @@ void RichText::addInlineTextImpl( TextType&& text, const FontStyleConfig& style,
|
|||||||
setTextString( *run.text, std::forward<TextType>( text ) );
|
setTextString( *run.text, std::forward<TextType>( text ) );
|
||||||
run.text->setStyleConfig( style );
|
run.text->setStyleConfig( style );
|
||||||
run.text->setTabWidth( mTabWidth );
|
run.text->setTabWidth( mTabWidth );
|
||||||
|
run.text->setTextHints( mTextHints );
|
||||||
run.source = source;
|
run.source = source;
|
||||||
run.margin = margin;
|
run.margin = margin;
|
||||||
run.padding = padding;
|
run.padding = padding;
|
||||||
@@ -2444,6 +2457,14 @@ void RichText::setTabWidth( Uint32 tabWidth ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RichText::setTextHints( Uint32 textHints ) {
|
||||||
|
if ( mTextHints != textHints ) {
|
||||||
|
mTextHints = textHints;
|
||||||
|
setInlineItemTextHints( mInlineItems, textHints );
|
||||||
|
invalidateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool RichText::setExternalFloatExclusions( const std::vector<FloatExclusion>& exclusions ) {
|
bool RichText::setExternalFloatExclusions( const std::vector<FloatExclusion>& exclusions ) {
|
||||||
if ( mExternalFloatExclusions == exclusions )
|
if ( mExternalFloatExclusions == exclusions )
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+127
-10
@@ -24,6 +24,43 @@ bool Text::TextShaperEnabled = false;
|
|||||||
bool Text::TextShaperOptimizations = true;
|
bool Text::TextShaperOptimizations = true;
|
||||||
Uint32 Text::GlobalInvalidationId = 0;
|
Uint32 Text::GlobalInvalidationId = 0;
|
||||||
|
|
||||||
|
Uint32 Text::fontFeaturesFromString( const std::string& value ) {
|
||||||
|
Uint32 features = 0;
|
||||||
|
String::splitCb(
|
||||||
|
[&features]( std::string_view feature ) {
|
||||||
|
feature = String::trim( feature, " \t'\"" );
|
||||||
|
if ( String::iequals( feature, "liga" ) )
|
||||||
|
features |= TextHints::StandardLigatures;
|
||||||
|
else if ( String::iequals( feature, "calt" ) )
|
||||||
|
features |= TextHints::ContextualAlternates;
|
||||||
|
else if ( String::iequals( feature, "clig" ) )
|
||||||
|
features |= TextHints::ContextualLigatures;
|
||||||
|
else if ( String::iequals( feature, "dlig" ) )
|
||||||
|
features |= TextHints::DiscretionaryLigatures;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
value, ",", "", "" );
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Text::fontFeaturesToString( Uint32 features ) {
|
||||||
|
std::string value;
|
||||||
|
const auto append = [&value]( const char* feature ) {
|
||||||
|
if ( !value.empty() )
|
||||||
|
value += ',';
|
||||||
|
value += feature;
|
||||||
|
};
|
||||||
|
if ( features & TextHints::StandardLigatures )
|
||||||
|
append( "liga" );
|
||||||
|
if ( features & TextHints::ContextualAlternates )
|
||||||
|
append( "calt" );
|
||||||
|
if ( features & TextHints::ContextualLigatures )
|
||||||
|
append( "clig" );
|
||||||
|
if ( features & TextHints::DiscretionaryLigatures )
|
||||||
|
append( "dlig" );
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
Float Text::tabAdvance( Float hspace, Uint32 tabWidth, std::optional<Float> tabOffset ) {
|
Float Text::tabAdvance( Float hspace, Uint32 tabWidth, std::optional<Float> tabOffset ) {
|
||||||
Float advance = hspace * tabWidth;
|
Float advance = hspace * tabWidth;
|
||||||
if ( tabOffset ) {
|
if ( tabOffset ) {
|
||||||
@@ -661,7 +698,7 @@ void Text::onNewString() {
|
|||||||
mGeometryNeedUpdate = true;
|
mGeometryNeedUpdate = true;
|
||||||
mCachedWidthNeedUpdate = true;
|
mCachedWidthNeedUpdate = true;
|
||||||
mVisualLinesNeedUpdate = true;
|
mVisualLinesNeedUpdate = true;
|
||||||
mTextHints = mString.getTextHints();
|
mTextHints = mString.getTextHints() | mTextDrawHints;
|
||||||
checkColorEmojis();
|
checkColorEmojis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1197,6 +1234,61 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo
|
|||||||
.trunc();
|
.trunc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HarfBuzz can map several source characters to a single glyph cluster. Find the cluster
|
||||||
|
// surrounding the requested grapheme boundary in one pass, regardless of visual order.
|
||||||
|
const ShapedGlyph* caretCluster = nullptr;
|
||||||
|
std::size_t clusterStart = 0;
|
||||||
|
std::size_t clusterEnd = string.size();
|
||||||
|
Float clusterLeft = 0;
|
||||||
|
Float clusterRight = 0;
|
||||||
|
bool hasExactCluster = false;
|
||||||
|
for ( const ShapedTextParagraph& sp : layout->paragraphs ) {
|
||||||
|
for ( std::size_t i = 0; i < sp.shapedGlyphs.size(); ) {
|
||||||
|
const std::size_t currentStart = sp.shapedGlyphs[i].stringIndex;
|
||||||
|
Float currentLeft = sp.shapedGlyphs[i].position.x;
|
||||||
|
Float currentRight = currentLeft + sp.shapedGlyphs[i].advance.x;
|
||||||
|
std::size_t j = i + 1;
|
||||||
|
while ( j < sp.shapedGlyphs.size() &&
|
||||||
|
sp.shapedGlyphs[j].stringIndex == currentStart ) {
|
||||||
|
currentLeft = std::min( currentLeft, sp.shapedGlyphs[j].position.x );
|
||||||
|
currentRight = std::max( currentRight, sp.shapedGlyphs[j].position.x +
|
||||||
|
sp.shapedGlyphs[j].advance.x );
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
if ( currentStart < index && ( !caretCluster || currentStart > clusterStart ) ) {
|
||||||
|
caretCluster = &sp.shapedGlyphs[i];
|
||||||
|
clusterStart = currentStart;
|
||||||
|
clusterLeft = currentLeft;
|
||||||
|
clusterRight = currentRight;
|
||||||
|
}
|
||||||
|
if ( currentStart == index )
|
||||||
|
hasExactCluster = true;
|
||||||
|
if ( currentStart > index )
|
||||||
|
clusterEnd = std::min( clusterEnd, currentStart );
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( caretCluster && !hasExactCluster && index < clusterEnd &&
|
||||||
|
string.isGraphemeBoundary( index ) ) {
|
||||||
|
std::size_t boundaryCount = 0;
|
||||||
|
std::size_t boundaryIndex = 0;
|
||||||
|
for ( std::size_t boundary = clusterStart + 1; boundary <= clusterEnd; ++boundary ) {
|
||||||
|
if ( string.isGraphemeBoundary( boundary ) ) {
|
||||||
|
++boundaryCount;
|
||||||
|
if ( boundary <= index )
|
||||||
|
boundaryIndex = boundaryCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( boundaryCount > 0 ) {
|
||||||
|
const Float ratio =
|
||||||
|
static_cast<Float>( boundaryIndex ) / static_cast<Float>( boundaryCount );
|
||||||
|
const Float x = caretCluster->direction == TextDirection::RightToLeft
|
||||||
|
? clusterRight - ( clusterRight - clusterLeft ) * ratio
|
||||||
|
: clusterLeft + ( clusterRight - clusterLeft ) * ratio;
|
||||||
|
return Vector2f{ x, caretCluster->position.y + initialOffset.y }.trunc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Uint32 maxStringIndex = 0;
|
Uint32 maxStringIndex = 0;
|
||||||
Uint32 closestDist = std::numeric_limits<Uint32>::max();
|
Uint32 closestDist = std::numeric_limits<Uint32>::max();
|
||||||
|
|
||||||
@@ -1389,6 +1481,7 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font*
|
|||||||
|
|
||||||
for ( auto i = 0; i < sgs; i++ ) {
|
for ( auto i = 0; i < sgs; i++ ) {
|
||||||
const ShapedGlyph* sg = &sp.shapedGlyphs[i];
|
const ShapedGlyph* sg = &sp.shapedGlyphs[i];
|
||||||
|
const ShapedGlyph* firstClusterGlyph = sg;
|
||||||
|
|
||||||
charLeft = sg->position.x;
|
charLeft = sg->position.x;
|
||||||
charTop = sg->position.y;
|
charTop = sg->position.y;
|
||||||
@@ -1399,20 +1492,21 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font*
|
|||||||
while ( i + 1 < sgs && sp.shapedGlyphs[i + 1].stringIndex == sg->stringIndex ) {
|
while ( i + 1 < sgs && sp.shapedGlyphs[i + 1].stringIndex == sg->stringIndex ) {
|
||||||
i++;
|
i++;
|
||||||
sg = &sp.shapedGlyphs[i];
|
sg = &sp.shapedGlyphs[i];
|
||||||
|
charLeft = std::min( charLeft, sg->position.x );
|
||||||
charBottom = sg->position.y + vspace;
|
charBottom = sg->position.y + vspace;
|
||||||
charRight = sg->position.x + sg->advance.x;
|
charRight = std::max( charRight, sg->position.x + sg->advance.x );
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( fpos.y >= charTop && fpos.y <= charBottom ) {
|
if ( fpos.y >= charTop && fpos.y <= charBottom ) {
|
||||||
auto findNextInsertionIndex = [&]() -> Int32 {
|
auto findNextInsertionIndex = [&]() -> Int32 {
|
||||||
if ( layout->isRTL() ) {
|
if ( firstClusterGlyph->direction == TextDirection::RightToLeft ) {
|
||||||
if ( i > 0 ) {
|
if ( i > 0 ) {
|
||||||
for ( auto j = i - 1; j >= 0; j-- ) {
|
for ( auto j = i - 1; j >= 0; j-- ) {
|
||||||
if ( sp.shapedGlyphs[j].stringIndex > sg->stringIndex )
|
if ( sp.shapedGlyphs[j].stringIndex > sg->stringIndex )
|
||||||
return sp.shapedGlyphs[j].stringIndex;
|
return sp.shapedGlyphs[j].stringIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return tSize;
|
||||||
} else {
|
} else {
|
||||||
for ( auto j = i + 1; j < sgs; ++j ) {
|
for ( auto j = i + 1; j < sgs; ++j ) {
|
||||||
if ( sp.shapedGlyphs[j].stringIndex > sg->stringIndex )
|
if ( sp.shapedGlyphs[j].stringIndex > sg->stringIndex )
|
||||||
@@ -1423,12 +1517,31 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font*
|
|||||||
};
|
};
|
||||||
|
|
||||||
if ( fpos.x >= charLeft && fpos.x < charRight ) {
|
if ( fpos.x >= charLeft && fpos.x < charRight ) {
|
||||||
Float midPoint = charLeft + ( charRight - charLeft ) * 0.5f;
|
const Int32 nextInsertionIndex = findNextInsertionIndex();
|
||||||
if ( fpos.x < midPoint ) {
|
const std::size_t clusterStart = firstClusterGlyph->stringIndex;
|
||||||
return sg->stringIndex;
|
const std::size_t clusterEnd = std::max<std::size_t>(
|
||||||
} else {
|
static_cast<std::size_t>( nextInsertionIndex ), clusterStart + 1 );
|
||||||
return findNextInsertionIndex();
|
std::size_t boundaryCount = 1;
|
||||||
|
for ( std::size_t boundary = clusterStart + 1; boundary < clusterEnd;
|
||||||
|
++boundary ) {
|
||||||
|
if ( string.isGraphemeBoundary( boundary ) )
|
||||||
|
++boundaryCount;
|
||||||
}
|
}
|
||||||
|
const Float width = charRight - charLeft;
|
||||||
|
Float ratio = width > 0 ? ( fpos.x - charLeft ) / width : 0.f;
|
||||||
|
if ( firstClusterGlyph->direction == TextDirection::RightToLeft )
|
||||||
|
ratio = 1.f - ratio;
|
||||||
|
const std::size_t caret = static_cast<std::size_t>(
|
||||||
|
std::round( ratio * static_cast<Float>( boundaryCount ) ) );
|
||||||
|
if ( caret == 0 )
|
||||||
|
return clusterStart;
|
||||||
|
std::size_t boundaryIndex = 0;
|
||||||
|
for ( std::size_t boundary = clusterStart + 1; boundary < clusterEnd;
|
||||||
|
++boundary ) {
|
||||||
|
if ( string.isGraphemeBoundary( boundary ) && ++boundaryIndex == caret )
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
return clusterEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1721,7 +1834,11 @@ Uint32 Text::getTextHints() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Text::setTextHints( Uint32 textHints ) {
|
void Text::setTextHints( Uint32 textHints ) {
|
||||||
mTextHints = textHints;
|
if ( mTextDrawHints != textHints ) {
|
||||||
|
mTextDrawHints = textHints;
|
||||||
|
mTextHints = mString.getTextHints() | mTextDrawHints;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Float& rotation,
|
void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Float& rotation,
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ static void segmentString( TextLayout& result, String::View input, Callable cb,
|
|||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
static void shapeAndRun( TextLayout& result, const String& string, FontTrueType* font,
|
static void shapeAndRun( TextLayout& result, const String& string, FontTrueType* font,
|
||||||
Uint32 characterSize, Uint32 style, Float outlineThickness,
|
Uint32 characterSize, Uint32 style, Float outlineThickness,
|
||||||
TextDirection baseDirection, Callable cb ) {
|
Uint32 textDrawHints, TextDirection baseDirection, Callable cb ) {
|
||||||
String::View input = string.view();
|
String::View input = string.view();
|
||||||
hb_buffer_t* hbBuffer = getThreadLocalHbBuffer();
|
hb_buffer_t* hbBuffer = getThreadLocalHbBuffer();
|
||||||
|
|
||||||
@@ -178,17 +178,28 @@ static void shapeAndRun( TextLayout& result, const String& string, FontTrueType*
|
|||||||
hb_buffer_guess_segment_properties( hbBuffer );
|
hb_buffer_guess_segment_properties( hbBuffer );
|
||||||
hb_segment_properties_t props;
|
hb_segment_properties_t props;
|
||||||
hb_buffer_get_segment_properties( hbBuffer, &props );
|
hb_buffer_get_segment_properties( hbBuffer, &props );
|
||||||
std::uint32_t featuresEnabled = !isSimpleScript( segment.script ) ? 1 : 0;
|
const bool complexShapingFeaturesEnabled = !isSimpleScript( segment.script );
|
||||||
|
const auto featureEnabled = [complexShapingFeaturesEnabled,
|
||||||
|
textDrawHints]( Uint32 textHint ) -> std::uint32_t {
|
||||||
|
return static_cast<std::uint32_t>( complexShapingFeaturesEnabled ||
|
||||||
|
( textDrawHints & textHint ) );
|
||||||
|
};
|
||||||
|
|
||||||
// We use our own kerning algo
|
// We use our own kerning algo
|
||||||
const hb_feature_t features[] = {
|
const hb_feature_t features[] = {
|
||||||
hb_feature_t{ HB_TAG( 'k', 'e', 'r', 'n' ), featuresEnabled,
|
hb_feature_t{ HB_TAG( 'k', 'e', 'r', 'n' ), complexShapingFeaturesEnabled,
|
||||||
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
||||||
hb_feature_t{ HB_TAG( 'l', 'i', 'g', 'a' ), featuresEnabled,
|
hb_feature_t{ HB_TAG( 'l', 'i', 'g', 'a' ),
|
||||||
|
featureEnabled( TextHints::StandardLigatures ),
|
||||||
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
||||||
hb_feature_t{ HB_TAG( 'c', 'l', 'i', 'g' ), featuresEnabled,
|
hb_feature_t{ HB_TAG( 'c', 'l', 'i', 'g' ),
|
||||||
|
featureEnabled( TextHints::ContextualLigatures ),
|
||||||
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
||||||
hb_feature_t{ HB_TAG( 'd', 'l', 'i', 'g' ), featuresEnabled,
|
hb_feature_t{ HB_TAG( 'd', 'l', 'i', 'g' ),
|
||||||
|
featureEnabled( TextHints::DiscretionaryLigatures ),
|
||||||
|
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
||||||
|
hb_feature_t{ HB_TAG( 'c', 'a', 'l', 't' ),
|
||||||
|
featureEnabled( TextHints::ContextualAlternates ),
|
||||||
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,13 +237,16 @@ static void shapeAndRun( TextLayout& result, const String& string, FontTrueType*
|
|||||||
static inline Uint64 textLayoutHash( const String::View& string, Font* font,
|
static inline Uint64 textLayoutHash( const String::View& string, Font* font,
|
||||||
const Uint32& characterSize, const Uint32& style,
|
const Uint32& characterSize, const Uint32& style,
|
||||||
const Uint32& tabWidth, const Float& outlineThickness,
|
const Uint32& tabWidth, const Float& outlineThickness,
|
||||||
std::optional<Float> tabOffset, TextDirection direction,
|
std::optional<Float> tabOffset, Uint32 textDrawHints,
|
||||||
LineWrapMode wrapMode, Uint32 wrapWidth, bool keepIndentation,
|
TextDirection direction, LineWrapMode wrapMode,
|
||||||
|
Uint32 wrapWidth, bool keepIndentation,
|
||||||
Float initialXOffset ) {
|
Float initialXOffset ) {
|
||||||
return hashCombine( std::hash<String::View>()( string ), std::hash<Font*>()( font ),
|
return hashCombine( std::hash<String::View>()( string ), std::hash<Font*>()( font ),
|
||||||
std::hash<Uint32>()( characterSize ), std::hash<Uint32>()( style ),
|
std::hash<Uint32>()( characterSize ), std::hash<Uint32>()( style ),
|
||||||
std::hash<Uint32>()( tabWidth ), std::hash<Float>()( outlineThickness ),
|
std::hash<Uint32>()( tabWidth ), std::hash<Float>()( outlineThickness ),
|
||||||
std::hash<std::optional<Float>>()( tabOffset ),
|
std::hash<std::optional<Float>>()( tabOffset ),
|
||||||
|
std::hash<Uint32>()( textDrawHints & ( TextHints::NoKerning |
|
||||||
|
TextHints::OpenTypeFeatures ) ),
|
||||||
std::hash<std::underlying_type_t<TextDirection>>()(
|
std::hash<std::underlying_type_t<TextDirection>>()(
|
||||||
static_cast<std::underlying_type_t<TextDirection>>( direction ) ),
|
static_cast<std::underlying_type_t<TextDirection>>( direction ) ),
|
||||||
std::hash<std::underlying_type_t<LineWrapMode>>()(
|
std::hash<std::underlying_type_t<LineWrapMode>>()(
|
||||||
@@ -285,8 +299,8 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font,
|
|||||||
Uint64 hash = 0;
|
Uint64 hash = 0;
|
||||||
if ( !Text::canSkipShaping( textDrawHints ) ) {
|
if ( !Text::canSkipShaping( textDrawHints ) ) {
|
||||||
hash = textLayoutHash( string, font, characterSize, style, tabWidth, outlineThickness,
|
hash = textLayoutHash( string, font, characterSize, style, tabWidth, outlineThickness,
|
||||||
tabOffset, baseDirection, wrapMode, wrapWidth, keepIndentation,
|
tabOffset, textDrawHints, baseDirection, wrapMode, wrapWidth,
|
||||||
initialXOffset );
|
keepIndentation, initialXOffset );
|
||||||
|
|
||||||
auto cacheHit = getLayoutCache().get( hash );
|
auto cacheHit = getLayoutCache().get( hash );
|
||||||
if ( cacheHit.has_value() )
|
if ( cacheHit.has_value() )
|
||||||
@@ -320,7 +334,8 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font,
|
|||||||
!Text::canSkipShaping( textDrawHints ) ) {
|
!Text::canSkipShaping( textDrawHints ) ) {
|
||||||
FontTrueType* rFont = static_cast<FontTrueType*>( font );
|
FontTrueType* rFont = static_cast<FontTrueType*>( font );
|
||||||
shapeAndRun(
|
shapeAndRun(
|
||||||
result, string, rFont, characterSize, style, outlineThickness, baseDirection,
|
result, string, rFont, characterSize, style, outlineThickness, textDrawHints,
|
||||||
|
baseDirection,
|
||||||
[&]( hb_glyph_info_t* glyphInfo, hb_glyph_position_t* glyphPos, Uint32 glyphCount,
|
[&]( hb_glyph_info_t* glyphInfo, hb_glyph_position_t* glyphPos, Uint32 glyphCount,
|
||||||
const hb_segment_properties_t& props, const TextSegment& segment,
|
const hb_segment_properties_t& props, const TextSegment& segment,
|
||||||
TextShapeRun& run ) {
|
TextShapeRun& run ) {
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ LineWrapInfo DocumentView::computeLineBreaks( const TextDocument& doc, size_t li
|
|||||||
const FontStyleConfig& fontStyle, Float maxWidth,
|
const FontStyleConfig& fontStyle, Float maxWidth,
|
||||||
LineWrapMode mode, bool keepIndentation,
|
LineWrapMode mode, bool keepIndentation,
|
||||||
Uint32 tabWidth, Float whiteSpaceWidth, bool tabStops,
|
Uint32 tabWidth, Float whiteSpaceWidth, bool tabStops,
|
||||||
Float initialXOffset ) {
|
Float initialXOffset, Uint32 textHints ) {
|
||||||
if ( line >= doc.linesCount() )
|
if ( line >= doc.linesCount() )
|
||||||
return {};
|
return {};
|
||||||
const auto& docLine = doc.line( line );
|
const auto& docLine = doc.line( line );
|
||||||
const auto& text = docLine.getText();
|
const auto& text = docLine.getText();
|
||||||
return LineWrap::computeLineBreaks( text.view().substr( 0, text.size() - 1 ), fontStyle,
|
return LineWrap::computeLineBreaks(
|
||||||
maxWidth, mode, keepIndentation, tabWidth, whiteSpaceWidth,
|
text.view().substr( 0, text.size() - 1 ), fontStyle, maxWidth, mode, keepIndentation,
|
||||||
docLine.getTextHints(), tabStops, initialXOffset );
|
tabWidth, whiteSpaceWidth, docLine.getTextHints() | textHints, tabStops, initialXOffset );
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentView::DocumentView( std::shared_ptr<TextDocument> doc, FontStyleConfig fontStyle,
|
DocumentView::DocumentView( std::shared_ptr<TextDocument> doc, FontStyleConfig fontStyle,
|
||||||
@@ -124,7 +124,8 @@ void DocumentView::invalidateCache() {
|
|||||||
} else {
|
} else {
|
||||||
auto lb = wrap ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
|
auto lb = wrap ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
|
||||||
mConfig.keepIndentation, mConfig.tabWidth,
|
mConfig.keepIndentation, mConfig.tabWidth,
|
||||||
mWhiteSpaceWidth, mConfig.tabStops )
|
mWhiteSpaceWidth, mConfig.tabStops, 0.f,
|
||||||
|
mConfig.textHints )
|
||||||
: LineWrapInfo{ { 0 }, 0.f };
|
: LineWrapInfo{ { 0 }, 0.f };
|
||||||
mVisibleLinesOffset.emplace_back( lb.paddingStart );
|
mVisibleLinesOffset.emplace_back( lb.paddingStart );
|
||||||
bool first = true;
|
bool first = true;
|
||||||
@@ -349,8 +350,8 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Safety check: ensure fromLine and toLine are within bounds of the old state
|
// Safety check: ensure fromLine and toLine are within bounds of the old state
|
||||||
if ( fromLine < 0 || fromLine >= (Int64)mVisibleLinesOffset.size() ||
|
if ( fromLine < 0 || fromLine >= (Int64)mVisibleLinesOffset.size() || toLine < 0 ||
|
||||||
toLine < 0 || toLine >= (Int64)mVisibleLinesOffset.size() || fromLine > toLine ) {
|
toLine >= (Int64)mVisibleLinesOffset.size() || fromLine > toLine ) {
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -370,10 +371,8 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
|
|||||||
Int64 oldIdxTo = static_cast<Int64>( toVisibleIndex( toLine, true ) );
|
Int64 oldIdxTo = static_cast<Int64>( toVisibleIndex( toLine, true ) );
|
||||||
|
|
||||||
if ( oldIdxFrom == static_cast<Int64>( VisibleIndex::invalid ) ||
|
if ( oldIdxFrom == static_cast<Int64>( VisibleIndex::invalid ) ||
|
||||||
oldIdxTo == static_cast<Int64>( VisibleIndex::invalid ) ||
|
oldIdxTo == static_cast<Int64>( VisibleIndex::invalid ) || oldIdxFrom > oldIdxTo ||
|
||||||
oldIdxFrom > oldIdxTo ||
|
oldIdxFrom >= (Int64)mVisibleLines.size() || oldIdxTo >= (Int64)mVisibleLines.size() ) {
|
||||||
oldIdxFrom >= (Int64)mVisibleLines.size() ||
|
|
||||||
oldIdxTo >= (Int64)mVisibleLines.size() ) {
|
|
||||||
invalidateCache();
|
invalidateCache();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -412,9 +411,9 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
|
|||||||
eemax( mMaxWidth - mWhiteSpaceWidth, mWhiteSpaceWidth ) ) );
|
eemax( mMaxWidth - mWhiteSpaceWidth, mWhiteSpaceWidth ) ) );
|
||||||
mDocLineToVisibleIndex[i] = static_cast<Int64>( VisibleIndex::invalid );
|
mDocLineToVisibleIndex[i] = static_cast<Int64>( VisibleIndex::invalid );
|
||||||
} else {
|
} else {
|
||||||
auto lb = computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
|
auto lb = computeLineBreaks(
|
||||||
mConfig.keepIndentation, mConfig.tabWidth,
|
*mDoc, i, mFontStyle, mMaxWidth, mConfig.mode, mConfig.keepIndentation,
|
||||||
mWhiteSpaceWidth, mConfig.tabStops );
|
mConfig.tabWidth, mWhiteSpaceWidth, mConfig.tabStops, 0.f, mConfig.textHints );
|
||||||
|
|
||||||
mVisibleLinesOffset.insert( mVisibleLinesOffset.begin() + i, lb.paddingStart );
|
mVisibleLinesOffset.insert( mVisibleLinesOffset.begin() + i, lb.paddingStart );
|
||||||
|
|
||||||
@@ -460,7 +459,8 @@ void DocumentView::recomputeDocLineToVisibleIndex( Int64 fromVisibleIndex, bool
|
|||||||
}
|
}
|
||||||
if ( visibleLine.line() < (Int64)mDocLineToVisibleIndex.size() )
|
if ( visibleLine.line() < (Int64)mDocLineToVisibleIndex.size() )
|
||||||
mDocLineToVisibleIndex[visibleLine.line()] =
|
mDocLineToVisibleIndex[visibleLine.line()] =
|
||||||
isFolded( visibleLine.line(), true ) ? static_cast<Int64>( VisibleIndex::invalid )
|
isFolded( visibleLine.line(), true )
|
||||||
|
? static_cast<Int64>( VisibleIndex::invalid )
|
||||||
: visibleIdx;
|
: visibleIdx;
|
||||||
previousLineIdx = visibleLine.line();
|
previousLineIdx = visibleLine.line();
|
||||||
}
|
}
|
||||||
@@ -599,7 +599,8 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi
|
|||||||
auto lb = isWrapEnabled()
|
auto lb = isWrapEnabled()
|
||||||
? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
|
? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
|
||||||
mConfig.keepIndentation, mConfig.tabWidth,
|
mConfig.keepIndentation, mConfig.tabWidth,
|
||||||
mWhiteSpaceWidth, mConfig.tabStops )
|
mWhiteSpaceWidth, mConfig.tabStops, 0.f,
|
||||||
|
mConfig.textHints )
|
||||||
: LineWrapInfo{ { 0 }, 0 };
|
: LineWrapInfo{ { 0 }, 0 };
|
||||||
if ( recomputeOffset && i < (Int64)mVisibleLinesOffset.size() )
|
if ( recomputeOffset && i < (Int64)mVisibleLinesOffset.size() )
|
||||||
mVisibleLinesOffset[i] = lb.paddingStart;
|
mVisibleLinesOffset[i] = lb.paddingStart;
|
||||||
@@ -618,9 +619,8 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi
|
|||||||
Int64 oldIdxFrom = static_cast<Int64>( oldIdxFromVI );
|
Int64 oldIdxFrom = static_cast<Int64>( oldIdxFromVI );
|
||||||
Int64 oldIdxTo = static_cast<Int64>( oldIdxToVI );
|
Int64 oldIdxTo = static_cast<Int64>( oldIdxToVI );
|
||||||
if ( VisibleIndex::invalid == oldIdxFromVI || VisibleIndex::invalid == oldIdxToVI ||
|
if ( VisibleIndex::invalid == oldIdxFromVI || VisibleIndex::invalid == oldIdxToVI ||
|
||||||
oldIdxFrom < 0 || oldIdxTo < 0 ||
|
oldIdxFrom < 0 || oldIdxTo < 0 || oldIdxFrom > oldIdxTo ||
|
||||||
oldIdxFrom > oldIdxTo || oldIdxFrom >= (Int64)mVisibleLines.size() ||
|
oldIdxFrom >= (Int64)mVisibleLines.size() || oldIdxTo >= (Int64)mVisibleLines.size() )
|
||||||
oldIdxTo >= (Int64)mVisibleLines.size() )
|
|
||||||
return;
|
return;
|
||||||
mVisibleLines.erase( mVisibleLines.begin() + oldIdxFrom,
|
mVisibleLines.erase( mVisibleLines.begin() + oldIdxFrom,
|
||||||
mVisibleLines.begin() + oldIdxTo + 1 );
|
mVisibleLines.begin() + oldIdxTo + 1 );
|
||||||
@@ -761,4 +761,11 @@ void DocumentView::setTabStops( bool enabled ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DocumentView::setTextHints( Uint32 textHints ) {
|
||||||
|
if ( textHints != mConfig.textHints ) {
|
||||||
|
mConfig.textHints = textHints;
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}}} // namespace EE::UI::Doc
|
}}} // namespace EE::UI::Doc
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ class UIDiffEditorPlugin : public UICodeEditorPlugin {
|
|||||||
screenStart.y + textOffsetY );
|
screenStart.y + textOffsetY );
|
||||||
|
|
||||||
Text::draw( mView->getFileName(), pos, font, fontSize, textColor, 0, 0.f, Color::Black,
|
Text::draw( mView->getFileName(), pos, font, fontSize, textColor, 0, 0.f, Color::Black,
|
||||||
Color::Black, { 1, 1 }, 4, mView->getFileName().getTextHints() );
|
Color::Black, { 1, 1 }, 4,
|
||||||
|
mView->getFileName().getTextHints() | mView->getDefaultTextHints() );
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawBeforeLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
|
void drawBeforeLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
|
||||||
@@ -286,14 +287,15 @@ class UIDiffEditorPlugin : public UICodeEditorPlugin {
|
|||||||
FontStyleConfig config = editor->getFontStyleConfig();
|
FontStyleConfig config = editor->getFontStyleConfig();
|
||||||
config.FontColor = editor->getColorScheme().getEditorColor( SyntaxStyleTypes::LineNumber );
|
config.FontColor = editor->getColorScheme().getEditorColor( SyntaxStyleTypes::LineNumber );
|
||||||
|
|
||||||
Float textWidth = Text::getTextWidth( text, config, 4, TextHints::AllAscii );
|
const Uint32 textHints = TextHints::AllAscii | editor->getDefaultTextHints();
|
||||||
|
Float textWidth = Text::getTextWidth( text, config, 4, textHints );
|
||||||
|
|
||||||
Vector2f pos( screenStart.x + std::floor( ( mGutterWidth - textWidth ) * 0.5f ),
|
Vector2f pos( screenStart.x + std::floor( ( mGutterWidth - textWidth ) * 0.5f ),
|
||||||
screenStart.y + std::floor( ( lineHeight - config.Font->getLineSpacing(
|
screenStart.y + std::floor( ( lineHeight - config.Font->getLineSpacing(
|
||||||
config.CharacterSize ) ) *
|
config.CharacterSize ) ) *
|
||||||
0.5f ) );
|
0.5f ) );
|
||||||
|
|
||||||
Text::draw( text, pos, config, 4, TextHints::AllAscii );
|
Text::draw( text, pos, config, 4, textHints );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis
|
|||||||
UIWidget( elementTag ),
|
UIWidget( elementTag ),
|
||||||
mFont( getUISceneNode()->getResourceScope()->findFont( "monospace" ).get() ),
|
mFont( getUISceneNode()->getResourceScope()->findFont( "monospace" ).get() ),
|
||||||
mDoc( std::make_shared<TextDocument>() ),
|
mDoc( std::make_shared<TextDocument>() ),
|
||||||
mDocView( mDoc, mFontStyleConfig, { .tabStops = mTabStops } ),
|
mDocView( mDoc, mFontStyleConfig,
|
||||||
|
{ .textHints = TextHints::NoKerning, .tabStops = mTabStops } ),
|
||||||
mBlinkTime( Seconds( 0.5f ) ),
|
mBlinkTime( Seconds( 0.5f ) ),
|
||||||
mFoldsRefreshTime( Seconds( 2.f ) ),
|
mFoldsRefreshTime( Seconds( 2.f ) ),
|
||||||
mTabWidth( 4 ),
|
mTabWidth( 4 ),
|
||||||
@@ -2388,7 +2389,8 @@ void UICodeEditor::onDocumentLineMove( const Int64& fromLine, const Int64& toLin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !mFont || mFont->isMonospace() || mLinesWidthCache.empty() )
|
if ( !mFont || ( mFont->isMonospace() && getLigatureFeatures() == 0 ) ||
|
||||||
|
mLinesWidthCache.empty() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Int64 linesCount = mDoc->linesCount();
|
Int64 linesCount = mDoc->linesCount();
|
||||||
@@ -4333,22 +4335,27 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo
|
|||||||
subText,
|
subText,
|
||||||
{ position.x + start * getGlyphWidth(), position.y + lineOffset },
|
{ position.x + start * getGlyphWidth(), position.y + lineOffset },
|
||||||
fontStyle, mTabWidth,
|
fontStyle, mTabWidth,
|
||||||
getChunkHints ? String::getTextHints( subText ) : drawHints,
|
getChunkHints
|
||||||
|
? String::getTextHints( subText ) | getWidgetTextDrawHints()
|
||||||
|
: drawHints,
|
||||||
mTextDirection, whitespaceDisplayConfig );
|
mTextDirection, whitespaceDisplayConfig );
|
||||||
if ( minimumCharsToCoverScreen == end )
|
if ( minimumCharsToCoverScreen == end )
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String::View subText( text.substr( 0, eemin( curCharsWidth, maxWidth ) ) );
|
String::View subText( text.substr( 0, eemin( curCharsWidth, maxWidth ) ) );
|
||||||
size = Text::draw(
|
size = Text::draw( subText, { position.x, position.y + lineOffset },
|
||||||
subText, { position.x, position.y + lineOffset }, fontStyle, mTabWidth,
|
fontStyle, mTabWidth,
|
||||||
getChunkHints ? String::getTextHints( subText ) : drawHints,
|
getChunkHints ? String::getTextHints( subText ) |
|
||||||
|
getWidgetTextDrawHints()
|
||||||
|
: drawHints,
|
||||||
mTextDirection, whitespaceDisplayConfig );
|
mTextDirection, whitespaceDisplayConfig );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size = Text::draw( text, { position.x, position.y + lineOffset }, fontStyle,
|
size = Text::draw(
|
||||||
mTabWidth,
|
text, { position.x, position.y + lineOffset }, fontStyle, mTabWidth,
|
||||||
getChunkHints ? String::getTextHints( text ) : drawHints,
|
getChunkHints ? String::getTextHints( text ) | getWidgetTextDrawHints()
|
||||||
|
: drawHints,
|
||||||
mTextDirection, whitespaceDisplayConfig );
|
mTextDirection, whitespaceDisplayConfig );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4596,7 +4603,8 @@ void UICodeEditor::drawLineNumbers( const DocumentLineRange& lineRange, const Ve
|
|||||||
: mLineNumberFontColor,
|
: mLineNumberFontColor,
|
||||||
mFontStyleConfig.Style, mFontStyleConfig.OutlineThickness,
|
mFontStyleConfig.Style, mFontStyleConfig.OutlineThickness,
|
||||||
mFontStyleConfig.OutlineColor, mFontStyleConfig.ShadowColor,
|
mFontStyleConfig.OutlineColor, mFontStyleConfig.ShadowColor,
|
||||||
mFontStyleConfig.ShadowOffset, 4, TextHints::AllAscii );
|
mFontStyleConfig.ShadowOffset, 4,
|
||||||
|
TextHints::AllAscii | getWidgetTextDrawHints() );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( foldVisible && mDoc->getFoldRangeService().isFoldingRegionInLine( i ) ) {
|
if ( foldVisible && mDoc->getFoldRangeService().isFoldingRegionInLine( i ) ) {
|
||||||
@@ -4664,7 +4672,7 @@ void UICodeEditor::drawLineNumbers( const DocumentLineRange& lineRange, const Ve
|
|||||||
.asFloat();
|
.asFloat();
|
||||||
|
|
||||||
Text::draw( String( (String::StringBaseType)0x2026 /* … */ ), offset, fontStyle,
|
Text::draw( String( (String::StringBaseType)0x2026 /* … */ ), offset, fontStyle,
|
||||||
mTabWidth );
|
mTabWidth, getWidgetTextDrawHints() );
|
||||||
|
|
||||||
primitives.setColor( mLineBreakColumnColor );
|
primitives.setColor( mLineBreakColumnColor );
|
||||||
primitives.drawLine(
|
primitives.drawLine(
|
||||||
@@ -5694,7 +5702,8 @@ void UICodeEditor::refreshTag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool UICodeEditor::isNotMonospace() const {
|
bool UICodeEditor::isNotMonospace() const {
|
||||||
return ( mFont && !mFont->isMonospace() ) || Text::TextShaperEnabled;
|
return getLigatureFeatures() != 0 || ( mFont && !mFont->isMonospace() ) ||
|
||||||
|
Text::TextShaperEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UICodeEditor::updateMouseCursor( const Vector2f& position ) {
|
void UICodeEditor::updateMouseCursor( const Vector2f& position ) {
|
||||||
@@ -5723,7 +5732,8 @@ void UICodeEditor::setTabIndentAlignment( CharacterAlignment alignment ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool UICodeEditor::isMonospaceLine( Int64 lineIndex ) const {
|
bool UICodeEditor::isMonospaceLine( Int64 lineIndex ) const {
|
||||||
return mFont && ( ( mFont->isMonospace() &&
|
return getLigatureFeatures() == 0 && mFont &&
|
||||||
|
( ( mFont->isMonospace() &&
|
||||||
( !Text::TextShaperEnabled || mDoc->line( lineIndex ).isAscii() ) ) ||
|
( !Text::TextShaperEnabled || mDoc->line( lineIndex ).isAscii() ) ) ||
|
||||||
( mFont->getType() == FontType::TTF &&
|
( mFont->getType() == FontType::TTF &&
|
||||||
static_cast<FontTrueType*>( mFont )->isIdentifiedAsMonospace() &&
|
static_cast<FontTrueType*>( mFont )->isIdentifiedAsMonospace() &&
|
||||||
@@ -5781,6 +5791,9 @@ void UICodeEditor::setTabStops( bool enabled ) {
|
|||||||
void UICodeEditor::setKerningEnabled( bool enabled ) {
|
void UICodeEditor::setKerningEnabled( bool enabled ) {
|
||||||
if ( mKerningEnabled != enabled ) {
|
if ( mKerningEnabled != enabled ) {
|
||||||
mKerningEnabled = enabled;
|
mKerningEnabled = enabled;
|
||||||
|
mDocView.setTextHints( getWidgetTextDrawHints() );
|
||||||
|
mLinesWidthCache.clear();
|
||||||
|
invalidateLongestLineWidth();
|
||||||
invalidateDraw();
|
invalidateDraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5789,6 +5802,33 @@ bool UICodeEditor::isKerningEnabled() const {
|
|||||||
return mKerningEnabled;
|
return mKerningEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UICodeEditor::setLigatureFeatures( Uint32 features ) {
|
||||||
|
features &= TextHints::OpenTypeFeatures;
|
||||||
|
if ( !mLigaturesOverride || mLigatureFeatures != features ) {
|
||||||
|
mLigaturesOverride = true;
|
||||||
|
mLigatureFeatures = features;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UICodeEditor::getLigatureFeatures() const {
|
||||||
|
return getWidgetTextDrawHints() & TextHints::OpenTypeFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UICodeEditor::clearLigaturesOverride() {
|
||||||
|
if ( mLigaturesOverride ) {
|
||||||
|
mLigaturesOverride = false;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UICodeEditor::onTextHintsChanged() {
|
||||||
|
mDocView.setTextHints( getWidgetTextDrawHints() );
|
||||||
|
mLinesWidthCache.clear();
|
||||||
|
invalidateLongestLineWidth();
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
void UICodeEditor::setTextDirection( TextDirection direction ) {
|
void UICodeEditor::setTextDirection( TextDirection direction ) {
|
||||||
if ( direction == mTextDirection )
|
if ( direction == mTextDirection )
|
||||||
return;
|
return;
|
||||||
|
|||||||
+75
-28
@@ -66,6 +66,7 @@ UIConsole::UIConsole( Font* font, const bool& makeDefaultCommands, const bool& a
|
|||||||
createDefaultCommands();
|
createDefaultCommands();
|
||||||
|
|
||||||
mTextCache.resize( maxLinesOnScreen() );
|
mTextCache.resize( maxLinesOnScreen() );
|
||||||
|
onTextHintsChanged();
|
||||||
|
|
||||||
cmdGetLog();
|
cmdGetLog();
|
||||||
|
|
||||||
@@ -147,6 +148,38 @@ TextDocument& UIConsole::getDoc() {
|
|||||||
return mDoc;
|
return mDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UIConsole::setLigatureFeatures( Uint32 features ) {
|
||||||
|
features &= TextHints::OpenTypeFeatures;
|
||||||
|
if ( !mLigaturesOverride || mLigatureFeatures != features ) {
|
||||||
|
mLigaturesOverride = true;
|
||||||
|
mLigatureFeatures = features;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UIConsole::getLigatureFeatures() const {
|
||||||
|
return mLigaturesOverride ? mLigatureFeatures
|
||||||
|
: getDefaultTextHints() & TextHints::OpenTypeFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIConsole::clearLigaturesOverride() {
|
||||||
|
if ( mLigaturesOverride ) {
|
||||||
|
mLigaturesOverride = false;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UIConsole::getTextHints() const {
|
||||||
|
return TextHints::NoKerning | getLigatureFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIConsole::onTextHintsChanged() {
|
||||||
|
const Uint32 textHints = getTextHints();
|
||||||
|
for ( auto& cache : mTextCache )
|
||||||
|
cache.text.setTextHints( textHints );
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
Font* UIConsole::getFont() const {
|
Font* UIConsole::getFont() const {
|
||||||
return mFontStyleConfig.Font;
|
return mFontStyleConfig.Font;
|
||||||
}
|
}
|
||||||
@@ -491,6 +524,13 @@ void UIConsole::draw() {
|
|||||||
|
|
||||||
Primitives p;
|
Primitives p;
|
||||||
p.setColor( Color( mFontStyleConfig.FontSelectionBackColor ).blendAlpha( (Uint8)mAlpha ) );
|
p.setColor( Color( mFontStyleConfig.FontSelectionBackColor ).blendAlpha( (Uint8)mAlpha ) );
|
||||||
|
const auto characterPos = [this]( const String& string, std::size_t index ) {
|
||||||
|
return Text::findCharacterPos( index, mFontStyleConfig.Font, mFontStyleConfig.CharacterSize,
|
||||||
|
string, mFontStyleConfig.Style, 4,
|
||||||
|
mFontStyleConfig.OutlineThickness, {}, false,
|
||||||
|
getTextHints() )
|
||||||
|
.x;
|
||||||
|
};
|
||||||
auto to = eemax( mCon.min - mCon.modif, 0 );
|
auto to = eemax( mCon.min - mCon.modif, 0 );
|
||||||
auto from = eemin( mCon.max - mCon.modif, (int)mCmdLog.size() - 1 );
|
auto from = eemin( mCon.max - mCon.modif, (int)mCmdLog.size() - 1 );
|
||||||
|
|
||||||
@@ -505,23 +545,14 @@ void UIConsole::draw() {
|
|||||||
auto endCol = eemin( (Int64)mCmdLog[i].log.size(), selNorm.end().column() );
|
auto endCol = eemin( (Int64)mCmdLog[i].log.size(), selNorm.end().column() );
|
||||||
|
|
||||||
if ( i == selNorm.start().line() ) {
|
if ( i == selNorm.start().line() ) {
|
||||||
auto tsubstr = mCmdLog[i].log.view().substr(
|
const Int64 selectionEnd =
|
||||||
startCol, selNorm.end().line() == i ? eemax( (Int64)0, endCol - startCol )
|
selNorm.end().line() == i ? endCol : mCmdLog[i].log.size();
|
||||||
: (Int64)mCmdLog[i].log.size() - startCol );
|
const Float fwidth = characterPos( mCmdLog[i].log, startCol );
|
||||||
auto twidth =
|
const Float twidth = characterPos( mCmdLog[i].log, selectionEnd ) - fwidth;
|
||||||
Text::getTextWidth( mFontStyleConfig.Font, mFontStyleConfig.CharacterSize,
|
|
||||||
tsubstr, mFontStyleConfig.Style );
|
|
||||||
auto fsubstr = mCmdLog[i].log.view().substr( 0, startCol );
|
|
||||||
auto fwidth =
|
|
||||||
Text::getTextWidth( mFontStyleConfig.Font, mFontStyleConfig.CharacterSize,
|
|
||||||
fsubstr, mFontStyleConfig.Style );
|
|
||||||
p.drawRectangle( Rectf( { mScreenPos.x + mPaddingPx.Left + fwidth, curY },
|
p.drawRectangle( Rectf( { mScreenPos.x + mPaddingPx.Left + fwidth, curY },
|
||||||
{ twidth, lineHeight } ) );
|
{ twidth, lineHeight } ) );
|
||||||
} else if ( i == selNorm.end().line() ) {
|
} else if ( i == selNorm.end().line() ) {
|
||||||
auto fsubstr = mCmdLog[i].log.view().substr( 0, endCol );
|
const Float fwidth = characterPos( mCmdLog[i].log, endCol );
|
||||||
auto fwidth =
|
|
||||||
Text::getTextWidth( mFontStyleConfig.Font, mFontStyleConfig.CharacterSize,
|
|
||||||
fsubstr, mFontStyleConfig.Style );
|
|
||||||
p.drawRectangle(
|
p.drawRectangle(
|
||||||
Rectf( { mScreenPos.x + mPaddingPx.Left, curY }, { fwidth, lineHeight } ) );
|
Rectf( { mScreenPos.x + mPaddingPx.Left, curY }, { fwidth, lineHeight } ) );
|
||||||
} else {
|
} else {
|
||||||
@@ -534,14 +565,26 @@ void UIConsole::draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text& text = mTextCache[pos].text;
|
Text& text = mTextCache[pos].text;
|
||||||
|
text.setTextHints( getTextHints() );
|
||||||
text.setStyleConfig( mFontStyleConfig );
|
text.setStyleConfig( mFontStyleConfig );
|
||||||
text.setFillColor( fontColor );
|
text.setFillColor( fontColor );
|
||||||
if ( mCmdLog[i].hash != mTextCache[pos].hash ) {
|
if ( mCmdLog[i].hash != mTextCache[pos].hash ) {
|
||||||
if ( mCmdLog[i].log.size() * cw <= mSize.getWidth() ) {
|
std::size_t visibleCharacters = mCmdLog[i].log.size();
|
||||||
|
if ( getLigatureFeatures() != 0 ) {
|
||||||
|
visibleCharacters = eemax<Int32>(
|
||||||
|
0, Text::findCharacterFromPos(
|
||||||
|
{ static_cast<Int32>( mSize.getWidth() + 8 * cw ), 0 }, true,
|
||||||
|
mFontStyleConfig.Font, mFontStyleConfig.CharacterSize, mCmdLog[i].log,
|
||||||
|
mFontStyleConfig.Style, 4, mFontStyleConfig.OutlineThickness, {},
|
||||||
|
getTextHints() ) );
|
||||||
|
} else if ( mCmdLog[i].log.size() * cw > mSize.getWidth() ) {
|
||||||
|
visibleCharacters = ( mSize.getWidth() + 8 * cw ) / cw;
|
||||||
|
}
|
||||||
|
if ( visibleCharacters >= mCmdLog[i].log.size() ) {
|
||||||
text.setString( mCmdLog[i].log );
|
text.setString( mCmdLog[i].log );
|
||||||
mTextCache[pos].hash = mCmdLog[i].hash;
|
mTextCache[pos].hash = mCmdLog[i].hash;
|
||||||
} else {
|
} else {
|
||||||
auto substr = mCmdLog[i].log.substr( 0, ( mSize.getWidth() + 8 * cw ) / cw );
|
auto substr = mCmdLog[i].log.substr( 0, visibleCharacters );
|
||||||
mTextCache[pos].hash = String::hash( substr );
|
mTextCache[pos].hash = String::hash( substr );
|
||||||
text.setString( substr );
|
text.setString( substr );
|
||||||
}
|
}
|
||||||
@@ -552,19 +595,22 @@ void UIConsole::draw() {
|
|||||||
|
|
||||||
curY = mScreenPos.y + getPixelsSize().getHeight() - mPaddingPx.Bottom - lineHeight - 1;
|
curY = mScreenPos.y + getPixelsSize().getHeight() - mPaddingPx.Bottom - lineHeight - 1;
|
||||||
|
|
||||||
auto editCharWidth = Text::getTextWidth( String( "> " ), mFontStyleConfig );
|
auto editCharWidth = Text::getTextWidth( String( "> " ), mFontStyleConfig, 4, getTextHints() );
|
||||||
|
const String& inputLine = mDoc.getCurrentLine().getText();
|
||||||
|
|
||||||
if ( mDoc.hasSelection() ) {
|
if ( mDoc.hasSelection() ) {
|
||||||
Float selStartPos =
|
const Float selectionStart =
|
||||||
editCharWidth + Text::getTextWidth( mDoc.getCurrentLine().getText().view().substr(
|
characterPos( inputLine, mDoc.getSelection( true ).start().column() );
|
||||||
0, mDoc.getSelection( true ).start().column() ),
|
const Float selectionEnd =
|
||||||
mFontStyleConfig );
|
characterPos( inputLine, mDoc.getSelection( true ).end().column() );
|
||||||
Float selWidth = Text::getTextWidth( mDoc.getSelectedText(), mFontStyleConfig );
|
Float selStartPos = editCharWidth + selectionStart;
|
||||||
|
Float selWidth = selectionEnd - selectionStart;
|
||||||
p.drawRectangle( Rectf( { mScreenPos.x + mPaddingPx.Left + selStartPos, curY },
|
p.drawRectangle( Rectf( { mScreenPos.x + mPaddingPx.Left + selStartPos, curY },
|
||||||
{ selWidth, lineHeight } ) );
|
{ selWidth, lineHeight } ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
Text& text = mTextCache[mTextCache.size() - 1].text;
|
Text& text = mTextCache[mTextCache.size() - 1].text;
|
||||||
|
text.setTextHints( getTextHints() );
|
||||||
text.setStyleConfig( mFontStyleConfig );
|
text.setStyleConfig( mFontStyleConfig );
|
||||||
text.setFillColor( fontColor );
|
text.setFillColor( fontColor );
|
||||||
text.setString( "> " + mDoc.getCurrentLine().getTextWithoutNewLine() );
|
text.setString( "> " + mDoc.getCurrentLine().getTextWithoutNewLine() );
|
||||||
@@ -572,9 +618,7 @@ void UIConsole::draw() {
|
|||||||
|
|
||||||
if ( mCursorVisible ) {
|
if ( mCursorVisible ) {
|
||||||
Float cursorPos =
|
Float cursorPos =
|
||||||
editCharWidth + Text::getTextWidth( mDoc.getCurrentLine().getText().view().substr(
|
editCharWidth + characterPos( inputLine, mDoc.getSelection().start().column() );
|
||||||
0, mDoc.getSelection().start().column() ),
|
|
||||||
mFontStyleConfig );
|
|
||||||
Rectf r( { mScreenPos.x + mPaddingPx.Left + cursorPos, curY }, { cursorPos, lineHeight } );
|
Rectf r( { mScreenPos.x + mPaddingPx.Left + cursorPos, curY }, { cursorPos, lineHeight } );
|
||||||
updateIMELocation( r );
|
updateIMELocation( r );
|
||||||
if ( hasFocus() && getUISceneNode()->getWindow()->getIME().isEditing() ) {
|
if ( hasFocus() && getUISceneNode()->getWindow()->getIME().isEditing() ) {
|
||||||
@@ -585,6 +629,7 @@ void UIConsole::draw() {
|
|||||||
Color( fontColor ).blendAlpha( mAlpha ) );
|
Color( fontColor ).blendAlpha( mAlpha ) );
|
||||||
} else {
|
} else {
|
||||||
Text& text2 = mTextCache[mTextCache.size() - 2].text;
|
Text& text2 = mTextCache[mTextCache.size() - 2].text;
|
||||||
|
text2.setTextHints( getTextHints() );
|
||||||
text2.setStyleConfig( mFontStyleConfig );
|
text2.setStyleConfig( mFontStyleConfig );
|
||||||
text2.setFillColor( fontColor );
|
text2.setFillColor( fontColor );
|
||||||
text2.setString( "_" );
|
text2.setString( "_" );
|
||||||
@@ -597,6 +642,7 @@ void UIConsole::draw() {
|
|||||||
mFontStyleConfig.Font->getGlyph( '_', mFontStyleConfig.CharacterSize, false, false )
|
mFontStyleConfig.Font->getGlyph( '_', mFontStyleConfig.CharacterSize, false, false )
|
||||||
.advance;
|
.advance;
|
||||||
Text& text = mTextCache[mTextCache.size() - 3].text;
|
Text& text = mTextCache[mTextCache.size() - 3].text;
|
||||||
|
text.setTextHints( getTextHints() );
|
||||||
Color OldColor1( text.getColor() );
|
Color OldColor1( text.getColor() );
|
||||||
text.setStyleConfig( mFontStyleConfig );
|
text.setStyleConfig( mFontStyleConfig );
|
||||||
text.setFillColor( fontColor );
|
text.setFillColor( fontColor );
|
||||||
@@ -1103,9 +1149,10 @@ TextPosition UIConsole::getPositionOnScreen( Vector2f position ) {
|
|||||||
Int64 line = eeclamp( (Int64)eefloor( ( position.y - startOffset ) / lineHeight + 1 ), (Int64)0,
|
Int64 line = eeclamp( (Int64)eefloor( ( position.y - startOffset ) / lineHeight + 1 ), (Int64)0,
|
||||||
(Int64)mCmdLog.size() - 1 );
|
(Int64)mCmdLog.size() - 1 );
|
||||||
Int64 fline = eeclamp( firstVisibleLine + line, (Int64)0, (Int64)mCmdLog.size() - 1 );
|
Int64 fline = eeclamp( firstVisibleLine + line, (Int64)0, (Int64)mCmdLog.size() - 1 );
|
||||||
Int64 col = Text::findCharacterFromPos(
|
Int64 col = Text::findCharacterFromPos( { (int)eefloor( position.x - mPaddingPx.Left ), 0 },
|
||||||
{ (int)eefloor( position.x - mPaddingPx.Left ), 0 }, true, mFontStyleConfig.Font,
|
true, mFontStyleConfig.Font,
|
||||||
mFontStyleConfig.CharacterSize, mCmdLog[fline].log, mFontStyleConfig.Style );
|
mFontStyleConfig.CharacterSize, mCmdLog[fline].log,
|
||||||
|
mFontStyleConfig.Style, 4, 0.f, {}, getTextHints() );
|
||||||
return { fline, col };
|
return { fline, col };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,14 +110,15 @@ void UIHTMLImage::draw() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
style.FontColor = { color.r, color.g, color.b, static_cast<Uint8>( mAlpha ) };
|
style.FontColor = { color.r, color.g, color.b, static_cast<Uint8>( mAlpha ) };
|
||||||
Float width = Text::getTextWidth( mAlt, style );
|
const Uint32 textHints = getDefaultTextHints();
|
||||||
|
Float width = Text::getTextWidth( mAlt, style, 4, textHints );
|
||||||
Float available = mSize.x - mPaddingPx.Left - mPaddingPx.Right;
|
Float available = mSize.x - mPaddingPx.Left - mPaddingPx.Right;
|
||||||
Float x = mScreenPos.x + mPaddingPx.Left + eemax( 0.f, ( available - width ) * 0.5f );
|
Float x = mScreenPos.x + mPaddingPx.Left + eemax( 0.f, ( available - width ) * 0.5f );
|
||||||
Float y = mScreenPos.y + mPaddingPx.Top +
|
Float y = mScreenPos.y + mPaddingPx.Top +
|
||||||
( mSize.y - mPaddingPx.Top - mPaddingPx.Bottom -
|
( mSize.y - mPaddingPx.Top - mPaddingPx.Bottom -
|
||||||
PixelDensity::getPixelDensity() * style.CharacterSize ) *
|
PixelDensity::getPixelDensity() * style.CharacterSize ) *
|
||||||
0.5f;
|
0.5f;
|
||||||
Text::draw( String( mAlt ), { x, y }, style );
|
Text::draw( String( mAlt ), { x, y }, style, 4, textHints );
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIHTMLImage::setAlpha( const Float& alpha ) {
|
void UIHTMLImage::setAlpha( const Float& alpha ) {
|
||||||
|
|||||||
@@ -446,6 +446,7 @@ UIRichText::UIRichText( const std::string& tag ) : UIHTMLWidget( tag ) {
|
|||||||
|
|
||||||
mRichText.getFontStyleConfig().FontColor = Color::Black;
|
mRichText.getFontStyleConfig().FontColor = Color::Black;
|
||||||
mRichText.setTabWidth( mTabSize );
|
mRichText.setTabWidth( mTabSize );
|
||||||
|
mRichText.setTextHints( getTextHints() );
|
||||||
|
|
||||||
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
|
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
|
||||||
}
|
}
|
||||||
@@ -454,6 +455,32 @@ const RichText& UIRichText::getRichText() {
|
|||||||
return mRichText;
|
return mRichText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UIRichText::setTextHintsOverride( Uint32 value, Uint32 mask ) {
|
||||||
|
mask &= TextHints::OpenTypeFeatures;
|
||||||
|
value &= mask;
|
||||||
|
if ( mTextHintsOverride != value || mTextHintsOverrideMask != mask ) {
|
||||||
|
mTextHintsOverride = value;
|
||||||
|
mTextHintsOverrideMask = mask;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIRichText::clearTextHintsOverride() {
|
||||||
|
setTextHintsOverride( 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UIRichText::getTextHints() const {
|
||||||
|
return UISceneNode::resolveTextHints( getDefaultTextHints(), mTextHintsOverride,
|
||||||
|
mTextHintsOverrideMask );
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIRichText::onTextHintsChanged() {
|
||||||
|
mRichText.setTextHints( getTextHints() );
|
||||||
|
notifyLayoutAttrChange( LayoutInvalidation::TextFormatting );
|
||||||
|
notifyLayoutAttrChangeParent( LayoutInvalidation::ParentReplacedFormatting );
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
void UIRichText::draw() {
|
void UIRichText::draw() {
|
||||||
if ( mVisible && 0.f != mAlpha ) {
|
if ( mVisible && 0.f != mAlpha ) {
|
||||||
UIWidget::draw();
|
UIWidget::draw();
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ void UISceneNode::initializeEmbeddedFromHost( UISceneNode* hostScene ) {
|
|||||||
mThreadPool = hostScene->getThreadPool();
|
mThreadPool = hostScene->getThreadPool();
|
||||||
mColorSchemePreference = hostScene->getColorSchemePreference();
|
mColorSchemePreference = hostScene->getColorSchemePreference();
|
||||||
mContrastPreference = hostScene->getContrastPreference();
|
mContrastPreference = hostScene->getContrastPreference();
|
||||||
|
mDefaultTextHints = hostScene->getDefaultTextHints();
|
||||||
const FontService& hostFontService = hostScene->getResourceScope()->getFontService();
|
const FontService& hostFontService = hostScene->getResourceScope()->getFontService();
|
||||||
FontService& fontService = mResourceScope->getFontService();
|
FontService& fontService = mResourceScope->getFontService();
|
||||||
fontService.setHinting( hostFontService.getHinting() );
|
fontService.setHinting( hostFontService.getHinting() );
|
||||||
@@ -343,6 +344,37 @@ const std::vector<UISceneNode*>& UISceneNode::getChildUISceneNodes() const {
|
|||||||
return mChildUISceneNodes;
|
return mChildUISceneNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UISceneNode::setDefaultTextHints( Uint32 textHints ) {
|
||||||
|
textHints &= TextHints::OpenTypeFeatures;
|
||||||
|
if ( mDefaultTextHints == textHints )
|
||||||
|
return;
|
||||||
|
mDefaultTextHints = textHints;
|
||||||
|
const auto notifyTextHintsChanged = []( auto&& self, Node* node ) -> void {
|
||||||
|
if ( node->isType( UI_TYPE_WIDGET ) ) {
|
||||||
|
UIWidget* widget = static_cast<UIWidget*>( node );
|
||||||
|
widget->onTextHintsChanged();
|
||||||
|
if ( widget->getTooltip() )
|
||||||
|
widget->getTooltip()->onTextHintsChanged();
|
||||||
|
}
|
||||||
|
for ( Uint32 i = 0; i < node->getChildCount(); ++i )
|
||||||
|
self( self, node->getChildAt( i ) );
|
||||||
|
};
|
||||||
|
notifyTextHintsChanged( notifyTextHintsChanged, this );
|
||||||
|
for ( auto* sceneNode : mChildUISceneNodes )
|
||||||
|
sceneNode->setDefaultTextHints( textHints );
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UISceneNode::getDefaultTextHints() const {
|
||||||
|
return mDefaultTextHints;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UISceneNode::resolveTextHints( Uint32 defaultHints, Uint32 overrideValue,
|
||||||
|
Uint32 overrideMask ) {
|
||||||
|
const Uint32 featureMask = TextHints::OpenTypeFeatures;
|
||||||
|
overrideMask &= featureMask;
|
||||||
|
return ( defaultHints & featureMask & ~overrideMask ) | ( overrideValue & overrideMask );
|
||||||
|
}
|
||||||
|
|
||||||
void UISceneNode::setHighlightOverRecursive( bool highlight ) {
|
void UISceneNode::setHighlightOverRecursive( bool highlight ) {
|
||||||
setHighlightOver( highlight );
|
setHighlightOver( highlight );
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ UITextInput::UITextInput( const std::string& tag ) :
|
|||||||
mMouseDown( false ),
|
mMouseDown( false ),
|
||||||
mKeyBindings( getInput() ) {
|
mKeyBindings( getInput() ) {
|
||||||
mHintCache = Text::New();
|
mHintCache = Text::New();
|
||||||
|
mHintCache->setTextHints( getTextHints() );
|
||||||
|
|
||||||
UITheme* theme = getUISceneNode()->getUIThemeManager()->getDefaultTheme();
|
UITheme* theme = getUISceneNode()->getUIThemeManager()->getDefaultTheme();
|
||||||
|
|
||||||
@@ -321,6 +322,7 @@ UITextInput* UITextInput::setMode( TextInputMode mode ) {
|
|||||||
if ( mMode == TextInputMode::Password ) {
|
if ( mMode == TextInputMode::Password ) {
|
||||||
if ( !mPassCache ) {
|
if ( !mPassCache ) {
|
||||||
mPassCache = Text::New();
|
mPassCache = Text::New();
|
||||||
|
mPassCache->setTextHints( getTextHints() );
|
||||||
updateFontStyleConfig();
|
updateFontStyleConfig();
|
||||||
}
|
}
|
||||||
updatePass();
|
updatePass();
|
||||||
@@ -330,6 +332,14 @@ UITextInput* UITextInput::setMode( TextInputMode mode ) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UITextInput::onTextHintsChanged() {
|
||||||
|
UITextView::onTextHintsChanged();
|
||||||
|
if ( mHintCache )
|
||||||
|
mHintCache->setTextHints( getTextHints() );
|
||||||
|
if ( mPassCache )
|
||||||
|
mPassCache->setTextHints( getTextHints() );
|
||||||
|
}
|
||||||
|
|
||||||
UITextInput::TextInputMode UITextInput::getMode() const {
|
UITextInput::TextInputMode UITextInput::getMode() const {
|
||||||
return mMode;
|
return mMode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <eepp/graphics/text.hpp>
|
#include <eepp/graphics/text.hpp>
|
||||||
#include <eepp/ui/uirichtext.hpp>
|
#include <eepp/ui/uirichtext.hpp>
|
||||||
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/ui/uistyle.hpp>
|
#include <eepp/ui/uistyle.hpp>
|
||||||
#include <eepp/ui/uitextnode.hpp>
|
#include <eepp/ui/uitextnode.hpp>
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ void UITextNode::draw() {
|
|||||||
parent && parent->isType( UI_TYPE_HTML_WIDGET ) && parent->asType<UIHTMLWidget>()->isFlex();
|
parent && parent->isType( UI_TYPE_HTML_WIDGET ) && parent->asType<UIHTMLWidget>()->isFlex();
|
||||||
if ( isFlexItem ) {
|
if ( isFlexItem ) {
|
||||||
Text* flexText = getFlexText();
|
Text* flexText = getFlexText();
|
||||||
|
flexText->setTextHints( getTextHints() );
|
||||||
if ( flexText->getFont() ) {
|
if ( flexText->getFont() ) {
|
||||||
flexText->setMaxWrapWidth( getPixelsSize().getWidth() );
|
flexText->setMaxWrapWidth( getPixelsSize().getWidth() );
|
||||||
Float alpha = getAlpha();
|
Float alpha = getAlpha();
|
||||||
@@ -53,7 +55,7 @@ void UITextNode::draw() {
|
|||||||
Float alpha = getAlpha();
|
Float alpha = getAlpha();
|
||||||
if ( alpha < 1.f )
|
if ( alpha < 1.f )
|
||||||
fc.FontColor.a = (Uint8)( (Float)fc.FontColor.a * alpha );
|
fc.FontColor.a = (Uint8)( (Float)fc.FontColor.a * alpha );
|
||||||
Text::draw( mText, mScreenPos.trunc(), fc );
|
Text::draw( mText, mScreenPos.trunc(), fc, 4, getTextHints() );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -129,9 +131,37 @@ Float UITextNode::getBaseline() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text* UITextNode::getFlexText() {
|
Text* UITextNode::getFlexText() {
|
||||||
if ( mFlexText == nullptr )
|
if ( mFlexText == nullptr ) {
|
||||||
mFlexText = Text::New();
|
mFlexText = Text::New();
|
||||||
|
mFlexText->setTextHints( getTextHints() );
|
||||||
|
}
|
||||||
return mFlexText;
|
return mFlexText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UITextNode::setTextHintsOverride( Uint32 value, Uint32 mask ) {
|
||||||
|
mask &= TextHints::OpenTypeFeatures;
|
||||||
|
value &= mask;
|
||||||
|
if ( mTextHintsOverride != value || mTextHintsOverrideMask != mask ) {
|
||||||
|
mTextHintsOverride = value;
|
||||||
|
mTextHintsOverrideMask = mask;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextNode::clearTextHintsOverride() {
|
||||||
|
setTextHintsOverride( 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UITextNode::getTextHints() const {
|
||||||
|
return UISceneNode::resolveTextHints( getDefaultTextHints(), mTextHintsOverride,
|
||||||
|
mTextHintsOverrideMask );
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextNode::onTextHintsChanged() {
|
||||||
|
if ( mFlexText )
|
||||||
|
mFlexText->setTextHints( getTextHints() );
|
||||||
|
notifyLayoutAttrChange( LayoutInvalidation::TextFormatting );
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
}} // namespace EE::UI
|
}} // namespace EE::UI
|
||||||
|
|||||||
@@ -243,7 +243,8 @@ const String& UITextView::getText() const {
|
|||||||
UITextView* UITextView::setText( const String& text ) {
|
UITextView* UITextView::setText( const String& text ) {
|
||||||
if ( mString != text ) {
|
if ( mString != text ) {
|
||||||
mString = text;
|
mString = text;
|
||||||
mTextDrawHints = mString.getTextHints();
|
mTextDrawHints = mString.getTextHints() | getTextHints();
|
||||||
|
mTextCache.setTextHints( getTextHints() );
|
||||||
mTextCache.setString( mString );
|
mTextCache.setString( mString );
|
||||||
|
|
||||||
recalculate();
|
recalculate();
|
||||||
@@ -257,7 +258,8 @@ UITextView* UITextView::setText( const String& text ) {
|
|||||||
UITextView* UITextView::setText( String&& text ) {
|
UITextView* UITextView::setText( String&& text ) {
|
||||||
if ( mString != text ) {
|
if ( mString != text ) {
|
||||||
mString = std::move( text );
|
mString = std::move( text );
|
||||||
mTextDrawHints = mString.getTextHints();
|
mTextDrawHints = mString.getTextHints() | getTextHints();
|
||||||
|
mTextCache.setTextHints( getTextHints() );
|
||||||
mTextCache.setString( mString );
|
mTextCache.setString( mString );
|
||||||
|
|
||||||
recalculate();
|
recalculate();
|
||||||
@@ -268,6 +270,33 @@ UITextView* UITextView::setText( String&& text ) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UITextView::setTextHintsOverride( Uint32 value, Uint32 mask ) {
|
||||||
|
mask &= TextHints::OpenTypeFeatures;
|
||||||
|
value &= mask;
|
||||||
|
if ( mTextHintsOverride != value || mTextHintsOverrideMask != mask ) {
|
||||||
|
mTextHintsOverride = value;
|
||||||
|
mTextHintsOverrideMask = mask;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextView::clearTextHintsOverride() {
|
||||||
|
setTextHintsOverride( 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UITextView::getTextHints() const {
|
||||||
|
return UISceneNode::resolveTextHints( getDefaultTextHints(), mTextHintsOverride,
|
||||||
|
mTextHintsOverrideMask );
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITextView::onTextHintsChanged() {
|
||||||
|
mTextDrawHints = mString.getTextHints() | getTextHints();
|
||||||
|
mTextCache.setTextHints( getTextHints() );
|
||||||
|
recalculate();
|
||||||
|
notifyLayoutAttrChange( LayoutInvalidation::TextFormatting );
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
const Color& UITextView::getFontColor() const {
|
const Color& UITextView::getFontColor() const {
|
||||||
return mFontStyleConfig.FontColor;
|
return mFontStyleConfig.FontColor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ UITooltip::UITooltip() :
|
|||||||
UIWidget( "tooltip" ), mAlignOffset( 0.f, 0.f ), mTooltipTime( Time::Zero ), mTooltipOf() {
|
UIWidget( "tooltip" ), mAlignOffset( 0.f, 0.f ), mTooltipTime( Time::Zero ), mTooltipOf() {
|
||||||
|
|
||||||
mTextCache = Text::New();
|
mTextCache = Text::New();
|
||||||
|
mTextCache->setTextHints( getTextHints() );
|
||||||
mEnabled = false;
|
mEnabled = false;
|
||||||
|
|
||||||
setFlags( UI_NODE_DEFAULT_FLAGS_CENTERED | UI_AUTO_PADDING | UI_AUTO_SIZE );
|
setFlags( UI_NODE_DEFAULT_FLAGS_CENTERED | UI_AUTO_PADDING | UI_AUTO_SIZE );
|
||||||
@@ -712,4 +713,30 @@ bool UITooltip::isWordWrap() const {
|
|||||||
return mFlags & UI_WORD_WRAP;
|
return mFlags & UI_WORD_WRAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UITooltip::setTextHintsOverride( Uint32 value, Uint32 mask ) {
|
||||||
|
mask &= TextHints::OpenTypeFeatures;
|
||||||
|
value &= mask;
|
||||||
|
if ( mTextHintsOverride != value || mTextHintsOverrideMask != mask ) {
|
||||||
|
mTextHintsOverride = value;
|
||||||
|
mTextHintsOverrideMask = mask;
|
||||||
|
onTextHintsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITooltip::clearTextHintsOverride() {
|
||||||
|
setTextHintsOverride( 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 UITooltip::getTextHints() const {
|
||||||
|
return UISceneNode::resolveTextHints( getDefaultTextHints(), mTextHintsOverride,
|
||||||
|
mTextHintsOverrideMask );
|
||||||
|
}
|
||||||
|
|
||||||
|
void UITooltip::onTextHintsChanged() {
|
||||||
|
if ( mTextCache )
|
||||||
|
mTextCache->setTextHints( getTextHints() );
|
||||||
|
onAutoSize();
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
}} // namespace EE::UI
|
}} // namespace EE::UI
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ using namespace EE::Window;
|
|||||||
|
|
||||||
namespace EE { namespace UI {
|
namespace EE { namespace UI {
|
||||||
|
|
||||||
|
Uint32 UIWidget::getDefaultTextHints() const {
|
||||||
|
const UISceneNode* sceneNode = getUISceneNode();
|
||||||
|
return sceneNode ? sceneNode->getDefaultTextHints() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIWidget::onTextHintsChanged() {
|
||||||
|
invalidateDraw();
|
||||||
|
}
|
||||||
|
|
||||||
static bool isDataAttributeName( std::string_view name ) {
|
static bool isDataAttributeName( std::string_view name ) {
|
||||||
return String::istartsWith( String::trim( name ), "data-" );
|
return String::istartsWith( String::trim( name ), "data-" );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,15 @@
|
|||||||
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
||||||
#include <eepp/ui/uiapplication.hpp>
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
#include <eepp/ui/uicodeeditor.hpp>
|
#include <eepp/ui/uicodeeditor.hpp>
|
||||||
|
#include <eepp/ui/uiconsole.hpp>
|
||||||
#include <eepp/ui/uiicon.hpp>
|
#include <eepp/ui/uiicon.hpp>
|
||||||
|
#include <eepp/ui/uirichtext.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/ui/uitextedit.hpp>
|
#include <eepp/ui/uitextedit.hpp>
|
||||||
|
#include <eepp/ui/uitextnode.hpp>
|
||||||
#include <eepp/ui/uitextview.hpp>
|
#include <eepp/ui/uitextview.hpp>
|
||||||
#include <eepp/ui/uithememanager.hpp>
|
#include <eepp/ui/uithememanager.hpp>
|
||||||
|
#include <eepp/ui/uitooltip.hpp>
|
||||||
#include <eepp/window/engine.hpp>
|
#include <eepp/window/engine.hpp>
|
||||||
|
|
||||||
using namespace EE;
|
using namespace EE;
|
||||||
@@ -335,6 +339,159 @@ UTEST( FontRendering, destroyingFontInvalidatesTextLayoutCache ) {
|
|||||||
EXPECT_FALSE( retainedLayoutWeak.expired() );
|
EXPECT_FALSE( retainedLayoutWeak.expired() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( FontRendering, fontFeaturesStringConversion ) {
|
||||||
|
const Uint32 allFeatures = TextHints::StandardLigatures | TextHints::ContextualAlternates |
|
||||||
|
TextHints::ContextualLigatures | TextHints::DiscretionaryLigatures;
|
||||||
|
EXPECT_EQ( allFeatures,
|
||||||
|
Text::fontFeaturesFromString( "'liga', CALT, \"clig\", dlig, unsupported" ) );
|
||||||
|
EXPECT_TRUE( Text::fontFeaturesToString( allFeatures ) == "liga,calt,clig,dlig" );
|
||||||
|
EXPECT_EQ( 0u, Text::fontFeaturesFromString( "" ) );
|
||||||
|
EXPECT_TRUE( Text::fontFeaturesToString( 1u << 31 ).empty() );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef EE_TEXT_SHAPER_ENABLED
|
||||||
|
UTEST( FontRendering, latinOpenTypeFeaturesAreExplicitAndCachedByTextHints ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 320, 240, "eepp - Latin Ligatures Test", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32 ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
ResourceScope& scope = *app.getUI()->getResourceScope();
|
||||||
|
FontTrueTypePtr font = FontTrueType::New( "LatinLigatures-Regular", scope );
|
||||||
|
ASSERT_TRUE(
|
||||||
|
font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/NotoSans-Regular.ttf" ) );
|
||||||
|
|
||||||
|
const String text( "fi" );
|
||||||
|
const Uint32 latinHints = text.getTextHints();
|
||||||
|
ASSERT_TRUE( latinHints & TextHints::AllLatin1 );
|
||||||
|
EXPECT_TRUE( Text::canSkipShaping( latinHints ) );
|
||||||
|
EXPECT_FALSE( Text::canSkipShaping( latinHints | TextHints::StandardLigatures ) );
|
||||||
|
EXPECT_FALSE( Text::canSkipShaping( latinHints | TextHints::ContextualAlternates ) );
|
||||||
|
|
||||||
|
TextLayout::Cache unshaped =
|
||||||
|
TextLayout::layout( text, font.get(), 24, Text::Regular, 4, 0, {}, latinHints );
|
||||||
|
{
|
||||||
|
BoolScopedOp shapingOptimizations( Text::TextShaperOptimizations, false );
|
||||||
|
TextLayout::Cache shapedWithoutLigatures =
|
||||||
|
TextLayout::layout( text, font.get(), 24, Text::Regular, 4, 0, {}, latinHints );
|
||||||
|
ASSERT_EQ( 1u, shapedWithoutLigatures->paragraphs.size() );
|
||||||
|
EXPECT_EQ( 2u, shapedWithoutLigatures->paragraphs.front().shapedGlyphs.size() );
|
||||||
|
}
|
||||||
|
TextLayout::Cache standardLigatures = TextLayout::layout(
|
||||||
|
text, font.get(), 24, Text::Regular, 4, 0, {}, latinHints | TextHints::StandardLigatures );
|
||||||
|
TextLayout::Cache contextualAlternates =
|
||||||
|
TextLayout::layout( text, font.get(), 24, Text::Regular, 4, 0, {},
|
||||||
|
latinHints | TextHints::ContextualAlternates );
|
||||||
|
ASSERT_EQ( 1u, unshaped->paragraphs.size() );
|
||||||
|
ASSERT_EQ( 1u, standardLigatures->paragraphs.size() );
|
||||||
|
ASSERT_EQ( 1u, contextualAlternates->paragraphs.size() );
|
||||||
|
EXPECT_EQ( 2u, unshaped->paragraphs.front().shapedGlyphs.size() );
|
||||||
|
EXPECT_EQ( 1u, standardLigatures->paragraphs.front().shapedGlyphs.size() );
|
||||||
|
EXPECT_EQ( 2u, contextualAlternates->paragraphs.front().shapedGlyphs.size() );
|
||||||
|
|
||||||
|
TextLayout::Cache cachedStandardLigatures = TextLayout::layout(
|
||||||
|
text, font.get(), 24, Text::Regular, 4, 0, {}, latinHints | TextHints::StandardLigatures );
|
||||||
|
EXPECT_EQ( standardLigatures.get(), cachedStandardLigatures.get() );
|
||||||
|
EXPECT_NE( standardLigatures.get(), contextualAlternates.get() );
|
||||||
|
TextLayout::Cache noKerningStandardLigatures =
|
||||||
|
TextLayout::layout( text, font.get(), 24, Text::Regular, 4, 0, {},
|
||||||
|
latinHints | TextHints::StandardLigatures | TextHints::NoKerning );
|
||||||
|
EXPECT_NE( standardLigatures.get(), noKerningStandardLigatures.get() );
|
||||||
|
|
||||||
|
const Uint32 ligatureHints = latinHints | TextHints::StandardLigatures;
|
||||||
|
const Vector2f beforeLigature = Text::findCharacterPos( 0, font.get(), 24, text, Text::Regular,
|
||||||
|
4, 0, {}, false, ligatureHints );
|
||||||
|
const Vector2f insideLigature = Text::findCharacterPos( 1, font.get(), 24, text, Text::Regular,
|
||||||
|
4, 0, {}, false, ligatureHints );
|
||||||
|
const Vector2f afterLigature = Text::findCharacterPos( 2, font.get(), 24, text, Text::Regular,
|
||||||
|
4, 0, {}, false, ligatureHints );
|
||||||
|
EXPECT_LT( beforeLigature.x, insideLigature.x );
|
||||||
|
EXPECT_LT( insideLigature.x, afterLigature.x );
|
||||||
|
EXPECT_EQ( 1, Text::findCharacterFromPos( insideLigature.asInt(), true, font.get(), 24, text,
|
||||||
|
Text::Regular, 4, 0, {}, ligatureHints ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( FontRendering, codeEditorUsesSelectedLigatureFeatures ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 320, 240, "eepp - Monospace Ligature Positioning Test",
|
||||||
|
WindowStyle::Default, WindowBackend::Default, 32 ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
ResourceScope& scope = *app.getUI()->getResourceScope();
|
||||||
|
FontTrueTypePtr font = FontTrueType::New( "MonospaceLigatures-Regular", scope );
|
||||||
|
ASSERT_TRUE(
|
||||||
|
font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/DejaVuSansMono.ttf" ) );
|
||||||
|
ASSERT_TRUE( font->isMonospace() );
|
||||||
|
|
||||||
|
auto* editor = UICodeEditor::New();
|
||||||
|
editor->setParent( app.getUI() );
|
||||||
|
editor->setFont( font.get() );
|
||||||
|
editor->setFontSize( 24 );
|
||||||
|
editor->getDocument().textInput( "fi" );
|
||||||
|
editor->setLigatureFeatures( TextHints::ContextualAlternates );
|
||||||
|
|
||||||
|
const Vector2d editorOffset = editor->getTextPositionOffset( { 0, 2 } );
|
||||||
|
const Vector2f shapedOffset = Text::findCharacterPos(
|
||||||
|
2, font.get(), editor->getCharacterSize(), editor->getDocument().line( 0 ).getText(),
|
||||||
|
Text::Regular, editor->getTabWidth(), 0.f, {}, false,
|
||||||
|
editor->getDocument().line( 0 ).getTextHints() | TextHints::ContextualAlternates |
|
||||||
|
TextHints::NoKerning );
|
||||||
|
EXPECT_NEAR( shapedOffset.x, editorOffset.x, 0.01 );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( FontRendering, sceneTextHintsPropagateAndWidgetsCanOverrideThem ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings( 320, 240, "eepp - Scene Text Hints Test", WindowStyle::Default,
|
||||||
|
WindowBackend::Default, 32 ),
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
UISceneNode* scene = app.getUI();
|
||||||
|
|
||||||
|
Text text;
|
||||||
|
text.setTextHints( TextHints::StandardLigatures );
|
||||||
|
text.setString( "fi" );
|
||||||
|
EXPECT_TRUE( text.getTextHints() & TextHints::StandardLigatures );
|
||||||
|
|
||||||
|
auto* textView = UITextView::New();
|
||||||
|
textView->setParent( scene );
|
||||||
|
auto* tooltip = textView->createTooltip();
|
||||||
|
auto* richText = UIRichText::New();
|
||||||
|
richText->setParent( scene );
|
||||||
|
auto* textNode = UITextNode::New();
|
||||||
|
textNode->setParent( scene );
|
||||||
|
auto* console = UIConsole::NewOpt( nullptr, false, false );
|
||||||
|
console->setParent( scene );
|
||||||
|
auto* editor = UICodeEditor::New();
|
||||||
|
editor->setParent( scene );
|
||||||
|
|
||||||
|
scene->setDefaultTextHints( TextHints::StandardLigatures | TextHints::ContextualAlternates );
|
||||||
|
EXPECT_TRUE( textView->getTextCache()->getTextHints() & TextHints::StandardLigatures );
|
||||||
|
EXPECT_TRUE( tooltip->getTextCache()->getTextHints() & TextHints::StandardLigatures );
|
||||||
|
EXPECT_TRUE( richText->getRichText().getTextHints() & TextHints::StandardLigatures );
|
||||||
|
EXPECT_TRUE( textNode->getTextHints() & TextHints::StandardLigatures );
|
||||||
|
EXPECT_EQ(
|
||||||
|
static_cast<Uint32>( TextHints::StandardLigatures | TextHints::ContextualAlternates ),
|
||||||
|
console->getLigatureFeatures() );
|
||||||
|
EXPECT_EQ(
|
||||||
|
static_cast<Uint32>( TextHints::StandardLigatures | TextHints::ContextualAlternates ),
|
||||||
|
editor->getLigatureFeatures() );
|
||||||
|
|
||||||
|
textView->setTextHintsOverride( 0, TextHints::StandardLigatures );
|
||||||
|
EXPECT_FALSE( textView->getTextCache()->getTextHints() & TextHints::StandardLigatures );
|
||||||
|
EXPECT_TRUE( textView->getTextCache()->getTextHints() & TextHints::ContextualAlternates );
|
||||||
|
editor->setLigatureFeatures( TextHints::StandardLigatures );
|
||||||
|
EXPECT_EQ( static_cast<Uint32>( TextHints::StandardLigatures ), editor->getLigatureFeatures() );
|
||||||
|
editor->clearLigaturesOverride();
|
||||||
|
EXPECT_EQ(
|
||||||
|
static_cast<Uint32>( TextHints::StandardLigatures | TextHints::ContextualAlternates ),
|
||||||
|
editor->getLigatureFeatures() );
|
||||||
|
console->setLigatureFeatures( TextHints::DiscretionaryLigatures );
|
||||||
|
EXPECT_EQ( static_cast<Uint32>( TextHints::DiscretionaryLigatures ),
|
||||||
|
console->getLigatureFeatures() );
|
||||||
|
console->clearLigaturesOverride();
|
||||||
|
EXPECT_EQ(
|
||||||
|
static_cast<Uint32>( TextHints::StandardLigatures | TextHints::ContextualAlternates ),
|
||||||
|
console->getLigatureFeatures() );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
UTEST( FontRendering, fontsTest ) {
|
UTEST( FontRendering, fontsTest ) {
|
||||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
|
|||||||
editor.horizontalScrollbar = ini.getValueB( "editor", "horizontal_scrollbar", true );
|
editor.horizontalScrollbar = ini.getValueB( "editor", "horizontal_scrollbar", true );
|
||||||
editor.openDocumentsInMainSplit =
|
editor.openDocumentsInMainSplit =
|
||||||
ini.getValueB( "editor", "open_documents_in_main_split", false );
|
ini.getValueB( "editor", "open_documents_in_main_split", false );
|
||||||
|
editor.fontFeatures =
|
||||||
|
Graphics::Text::fontFeaturesFromString( ini.getValue( "editor", "font_features" ) );
|
||||||
ui.fontSize = ini.getValue( "ui", "font_size", "11dp" );
|
ui.fontSize = ini.getValue( "ui", "font_size", "11dp" );
|
||||||
ui.panelFontSize = ini.getValue( "ui", "panel_font_size", "11dp" );
|
ui.panelFontSize = ini.getValue( "ui", "panel_font_size", "11dp" );
|
||||||
ui.showSidePanel = ini.getValueB( "ui", "show_side_panel", true );
|
ui.showSidePanel = ini.getValueB( "ui", "show_side_panel", true );
|
||||||
@@ -169,6 +171,8 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
|
|||||||
FontTrueType::fontHintingFromString( ini.getValue( "ui", "font_hinting", "full" ) );
|
FontTrueType::fontHintingFromString( ini.getValue( "ui", "font_hinting", "full" ) );
|
||||||
ui.fontAntialiasing = FontTrueType::fontAntialiasingFromString(
|
ui.fontAntialiasing = FontTrueType::fontAntialiasingFromString(
|
||||||
ini.getValue( "ui", "font_antialiasing", "grayscale" ) );
|
ini.getValue( "ui", "font_antialiasing", "grayscale" ) );
|
||||||
|
ui.fontFeatures =
|
||||||
|
Graphics::Text::fontFeaturesFromString( ini.getValue( "ui", "font_features" ) );
|
||||||
ui.editorFontInInputFields = ini.getValueB( "ui", "editor_font_in_input_fields", true );
|
ui.editorFontInInputFields = ini.getValueB( "ui", "editor_font_in_input_fields", true );
|
||||||
|
|
||||||
doc.trimTrailingWhitespaces = ini.getValueB( "document", "trim_trailing_whitespaces", false );
|
doc.trimTrailingWhitespaces = ini.getValueB( "document", "trim_trailing_whitespaces", false );
|
||||||
@@ -350,6 +354,8 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
|
|||||||
ini.setValueB( "editor", "vertical_scrollbar", editor.verticalScrollbar );
|
ini.setValueB( "editor", "vertical_scrollbar", editor.verticalScrollbar );
|
||||||
ini.setValueB( "editor", "horizontal_scrollbar", editor.horizontalScrollbar );
|
ini.setValueB( "editor", "horizontal_scrollbar", editor.horizontalScrollbar );
|
||||||
ini.setValueB( "editor", "open_documents_in_main_split", editor.openDocumentsInMainSplit );
|
ini.setValueB( "editor", "open_documents_in_main_split", editor.openDocumentsInMainSplit );
|
||||||
|
ini.setValue( "editor", "font_features",
|
||||||
|
Graphics::Text::fontFeaturesToString( editor.fontFeatures ) );
|
||||||
ini.setValue( "editor", "font_size", editor.fontSize.toString() );
|
ini.setValue( "editor", "font_size", editor.fontSize.toString() );
|
||||||
|
|
||||||
ini.setValue( "ui", "font_size", ui.fontSize.toString() );
|
ini.setValue( "ui", "font_size", ui.fontSize.toString() );
|
||||||
@@ -374,6 +380,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
|
|||||||
ini.setValue( "ui", "font_hinting", FontTrueType::fontHintingToString( ui.fontHinting ) );
|
ini.setValue( "ui", "font_hinting", FontTrueType::fontHintingToString( ui.fontHinting ) );
|
||||||
ini.setValue( "ui", "font_antialiasing",
|
ini.setValue( "ui", "font_antialiasing",
|
||||||
FontTrueType::fontAntialiasingToString( ui.fontAntialiasing ) );
|
FontTrueType::fontAntialiasingToString( ui.fontAntialiasing ) );
|
||||||
|
ini.setValue( "ui", "font_features", Graphics::Text::fontFeaturesToString( ui.fontFeatures ) );
|
||||||
ini.setValue( "screenshots", "save_path", screenshot.savePath );
|
ini.setValue( "screenshots", "save_path", screenshot.savePath );
|
||||||
ini.setValue( "screenshots", "filename_pattern", screenshot.filenamePattern );
|
ini.setValue( "screenshots", "filename_pattern", screenshot.filenamePattern );
|
||||||
ini.setValue( "screenshots", "save_format", screenshot.saveFormat );
|
ini.setValue( "screenshots", "save_format", screenshot.saveFormat );
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ struct UIConfig {
|
|||||||
std::string language;
|
std::string language;
|
||||||
FontHinting fontHinting{ FontHinting::Full };
|
FontHinting fontHinting{ FontHinting::Full };
|
||||||
FontAntialiasing fontAntialiasing{ FontAntialiasing::Grayscale };
|
FontAntialiasing fontAntialiasing{ FontAntialiasing::Grayscale };
|
||||||
|
Uint32 fontFeatures{ 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WindowStateConfig {
|
struct WindowStateConfig {
|
||||||
@@ -107,6 +108,7 @@ struct CodeEditorConfig {
|
|||||||
std::string colorScheme{ "ecode" };
|
std::string colorScheme{ "ecode" };
|
||||||
StyleSheetLength fontSize{ 11, StyleSheetLength::Dp };
|
StyleSheetLength fontSize{ 11, StyleSheetLength::Dp };
|
||||||
StyleSheetLength lineSpacing{ 0, StyleSheetLength::Dp };
|
StyleSheetLength lineSpacing{ 0, StyleSheetLength::Dp };
|
||||||
|
Uint32 fontFeatures{ 0 };
|
||||||
bool showLineNumbers{ true };
|
bool showLineNumbers{ true };
|
||||||
bool showWhiteSpaces{ true };
|
bool showWhiteSpaces{ true };
|
||||||
bool showLineEndings{ false };
|
bool showLineEndings{ false };
|
||||||
|
|||||||
@@ -1981,6 +1981,7 @@ void App::setTheme( const std::string& path ) {
|
|||||||
->add( theme );
|
->add( theme );
|
||||||
|
|
||||||
mUISceneNode->setTheme( theme.get() );
|
mUISceneNode->setTheme( theme.get() );
|
||||||
|
mUISceneNode->setDefaultTextHints( mConfig.ui.fontFeatures );
|
||||||
|
|
||||||
mUISceneNode->getRoot()->addClass( "appbackground" );
|
mUISceneNode->getRoot()->addClass( "appbackground" );
|
||||||
|
|
||||||
@@ -2986,6 +2987,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
|
|||||||
editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) );
|
editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) );
|
||||||
editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) );
|
editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) );
|
||||||
editor->setTabStops( mConfig.doc.tabStops );
|
editor->setTabStops( mConfig.doc.tabStops );
|
||||||
|
editor->setLigatureFeatures( mConfig.editor.fontFeatures );
|
||||||
editor->setEnableInlineColorBoxes( config.inlineColorBoxes );
|
editor->setEnableInlineColorBoxes( config.inlineColorBoxes );
|
||||||
|
|
||||||
doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() );
|
doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() );
|
||||||
|
|||||||
@@ -3316,6 +3316,64 @@ UIMenu* SettingsMenu::createFontAntiAliasingMenu() {
|
|||||||
|
|
||||||
UIMenu* SettingsMenu::createFontsMenu() {
|
UIMenu* SettingsMenu::createFontsMenu() {
|
||||||
mFontsMenu = UIPopUpMenu::New();
|
mFontsMenu = UIPopUpMenu::New();
|
||||||
|
const auto createFontFeaturesMenu = [this]( bool editorFeatures ) {
|
||||||
|
auto* menu = UIPopUpMenu::New();
|
||||||
|
const Uint32 features = editorFeatures ? mApp->getConfig().editor.fontFeatures
|
||||||
|
: mApp->getConfig().ui.fontFeatures;
|
||||||
|
menu->addCheckBox( i18n( "standard_ligatures", "Standard Ligatures (liga)" ),
|
||||||
|
features & TextHints::StandardLigatures )
|
||||||
|
->setTooltipText(
|
||||||
|
i18n( "standard_ligatures_desc",
|
||||||
|
"Typographic combinations such as fi, fl, and ffi, depending on the font." ) )
|
||||||
|
->setId( "liga" );
|
||||||
|
menu->addCheckBox( i18n( "contextual_alternates", "Contextual Alternates (calt)" ),
|
||||||
|
features & TextHints::ContextualAlternates )
|
||||||
|
->setTooltipText(
|
||||||
|
i18n( "contextual_alternates_desc",
|
||||||
|
"Context-dependent alternatives, including many programming ligatures." ) )
|
||||||
|
->setId( "calt" );
|
||||||
|
menu->addCheckBox( i18n( "contextual_ligatures", "Contextual Ligatures (clig)" ),
|
||||||
|
features & TextHints::ContextualLigatures )
|
||||||
|
->setTooltipText(
|
||||||
|
i18n( "contextual_ligatures_desc",
|
||||||
|
"Ligatures applied in specific contexts to improve readability." ) )
|
||||||
|
->setId( "clig" );
|
||||||
|
menu->addCheckBox( i18n( "discretionary_ligatures", "Discretionary Ligatures (dlig)" ),
|
||||||
|
features & TextHints::DiscretionaryLigatures )
|
||||||
|
->setTooltipText(
|
||||||
|
i18n( "discretionary_ligatures_desc",
|
||||||
|
"Optional decorative or stylistic ligatures provided by the font." ) )
|
||||||
|
->setId( "dlig" );
|
||||||
|
menu->on( Event::OnItemClicked, [this, editorFeatures]( const Event* event ) {
|
||||||
|
if ( !event->getNode()->isType( UI_TYPE_MENUCHECKBOX ) )
|
||||||
|
return;
|
||||||
|
auto* item = event->getNode()->asType<UIMenuCheckBox>();
|
||||||
|
const String& id = item->getId();
|
||||||
|
const Uint32 feature = id == "liga" ? TextHints::StandardLigatures
|
||||||
|
: id == "calt" ? TextHints::ContextualAlternates
|
||||||
|
: id == "clig" ? TextHints::ContextualLigatures
|
||||||
|
: id == "dlig" ? TextHints::DiscretionaryLigatures
|
||||||
|
: 0;
|
||||||
|
if ( feature == 0 )
|
||||||
|
return;
|
||||||
|
Uint32& features = editorFeatures ? mApp->getConfig().editor.fontFeatures
|
||||||
|
: mApp->getConfig().ui.fontFeatures;
|
||||||
|
if ( item->isActive() )
|
||||||
|
features |= feature;
|
||||||
|
else
|
||||||
|
features &= ~feature;
|
||||||
|
if ( editorFeatures ) {
|
||||||
|
mSplitter->forEachEditor( [features]( UICodeEditor* editor ) {
|
||||||
|
editor->setLigatureFeatures( features );
|
||||||
|
} );
|
||||||
|
} else {
|
||||||
|
mApp->getUISceneNode()->setDefaultTextHints( features );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
return menu;
|
||||||
|
};
|
||||||
|
auto* uiFontFeaturesMenu = createFontFeaturesMenu( false );
|
||||||
|
auto* editorFontFeaturesMenu = createFontFeaturesMenu( true );
|
||||||
mFontsMenu->addSubMenu( i18n( "ui_font_hint", "Font Hint" ), findIcon( "font-size" ),
|
mFontsMenu->addSubMenu( i18n( "ui_font_hint", "Font Hint" ), findIcon( "font-size" ),
|
||||||
createFontHintMenu() );
|
createFontHintMenu() );
|
||||||
mFontsMenu->addSubMenu( i18n( "ui_font_antialiasing", "Font Anti-Aliasing" ),
|
mFontsMenu->addSubMenu( i18n( "ui_font_antialiasing", "Font Anti-Aliasing" ),
|
||||||
@@ -3325,10 +3383,14 @@ UIMenu* SettingsMenu::createFontsMenu() {
|
|||||||
mFontsMenu
|
mFontsMenu
|
||||||
->add( i18n( "ui_font_and_size_ellipsis", "UI Font & Size..." ), findIcon( "font-size" ) )
|
->add( i18n( "ui_font_and_size_ellipsis", "UI Font & Size..." ), findIcon( "font-size" ) )
|
||||||
->setId( "sans-serif-font" );
|
->setId( "sans-serif-font" );
|
||||||
|
mFontsMenu->addSubMenu( i18n( "ui_font_features", "UI Font Features" ), findIcon( "font-size" ),
|
||||||
|
uiFontFeaturesMenu );
|
||||||
mFontsMenu
|
mFontsMenu
|
||||||
->add( i18n( "editor_font_and_size_ellipsis", "Editor Font & Size..." ),
|
->add( i18n( "editor_font_and_size_ellipsis", "Editor Font & Size..." ),
|
||||||
findIcon( "font-size" ) )
|
findIcon( "font-size" ) )
|
||||||
->setId( "editor-font" );
|
->setId( "editor-font" );
|
||||||
|
mFontsMenu->addSubMenu( i18n( "editor_font_features", "Editor Font Features" ),
|
||||||
|
findIcon( "font-size" ), editorFontFeaturesMenu );
|
||||||
mFontsMenu
|
mFontsMenu
|
||||||
->add( i18n( "terminal_font_and_size_ellipsis", "Terminal Font & Size..." ),
|
->add( i18n( "terminal_font_and_size_ellipsis", "Terminal Font & Size..." ),
|
||||||
findIcon( "font-size" ) )
|
findIcon( "font-size" ) )
|
||||||
|
|||||||
Reference in New Issue
Block a user