diff --git a/projectfiles/CodeBlocks/wesnoth.cbp b/projectfiles/CodeBlocks/wesnoth.cbp
index 415187ceb3f..faf63ed9bd1 100644
--- a/projectfiles/CodeBlocks/wesnoth.cbp
+++ b/projectfiles/CodeBlocks/wesnoth.cbp
@@ -370,6 +370,8 @@
+
+
diff --git a/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj b/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj
index 5c6fb4a7bc4..88ee289cf7d 100644
--- a/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj
+++ b/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj
@@ -19,6 +19,7 @@
04C748F7835C62498D27442D /* edit_pbl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6FA542D78393E8FF067775DA /* edit_pbl.cpp */; };
04DC4E59AEDBC1A0AFDCA8CC /* units_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CAF74C3AB8D456EA3E756396 /* units_dialog.cpp */; };
0554467DB5FE99D85ABCDCA0 /* edit_pbl_translation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00574699A982AA23F12B39E0 /* edit_pbl_translation.cpp */; };
+ 0813449DBC67700714FA3ACD /* attributes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57724A59B2F7D072D91DD787 /* attributes.cpp */; };
08964907BF0C2F261FC984DC /* reachmap_options.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 231C4A6BB2F1A717F0D6E2E2 /* reachmap_options.hpp */; };
0DA840E1AD033775DD626F42 /* markup.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D7B540678519F0EBD7C19A17 /* markup.hpp */; };
1234567890ABCDEF12345678 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
@@ -35,6 +36,7 @@
365D4F89BD511BC074E639D7 /* migrate_version_selection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3DE4F95AF72C6F6BC37E695 /* migrate_version_selection.hpp */; };
36B146FAA79A55E9F43723B1 /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
36D74F7F8D7655ACCABE562D /* edit_pbl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6FA542D78393E8FF067775DA /* edit_pbl.cpp */; };
+ 38C444C497ECBE2DF9BB2319 /* attributes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 57724A59B2F7D072D91DD787 /* attributes.cpp */; };
393E4C9DAEE19E12B2B168B5 /* edit_unit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A05D48F0A2C022FC128C8B3E /* edit_unit.cpp */; };
3C0F4FFA9A0331ED6846D216 /* spritesheet_generator.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3D284B9A81882806D8B25006 /* spritesheet_generator.hpp */; };
3C254DF5B7DF196F2041955F /* mp_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58C649488B3014E6F7254B62 /* mp_report.cpp */; };
@@ -653,6 +655,7 @@
7A0347D48BDB52B1430D9E79 /* migrate_version_selection.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B3DE4F95AF72C6F6BC37E695 /* migrate_version_selection.hpp */; };
7A7146D7893AA09891352019 /* test_schema_validator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7CF14AB694764953E2CB3AF7 /* test_schema_validator.cpp */; };
7BFC4DF5BFF8CF75855BA662 /* prompt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 67044415B63F5888193BD7A6 /* prompt.cpp */; };
+ 7C4740B081A838A09779FAAA /* attributes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B56948C5B90E74C62F2D078F /* attributes.hpp */; };
7FDF4E8D9C94E7DA8F41F7BB /* tod_new_schedule.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */; };
805143B8BABF92CA79BEC8F5 /* gui_test_dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7FBD4033B4B52E9424819B5F /* gui_test_dialog.cpp */; };
80724CBB88A3B6C36D1E3199 /* spritesheet_generator.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3D284B9A81882806D8B25006 /* spritesheet_generator.hpp */; };
@@ -1143,6 +1146,7 @@
9C6342BC8A95B6D23D384486 /* gui_test_dialog.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0110429EAA81AED07D53B749 /* gui_test_dialog.hpp */; };
9FE64884AE8121CDBABF7D8A /* preferences.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1D64406873FEB11E9758A05 /* preferences.cpp */; };
AC4242F78B39C571E34AF48F /* edit_unit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A05D48F0A2C022FC128C8B3E /* edit_unit.cpp */; };
+ B14E4163984EED844169EF4F /* attributes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B56948C5B90E74C62F2D078F /* attributes.hpp */; };
B45C431C9B7250C3321F8BC2 /* preferences.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 2CFD4922B64EA6C9F71F71A2 /* preferences.hpp */; };
B508D193100146E300B12852 /* engine_fai.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B508D191100146E300B12852 /* engine_fai.cpp */; };
B513B2290ED36BFB0006E551 /* libcairo.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = B513B2270ED36BFB0006E551 /* libcairo.2.dylib */; };
@@ -2119,6 +2123,7 @@
4944F41A1354FBFF0027E614 /* teleport.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = teleport.cpp; sourceTree = ""; };
49478712172FF6F8002B7ABA /* tristate_button.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tristate_button.cpp; sourceTree = ""; };
49478713172FF6F8002B7ABA /* tristate_button.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tristate_button.hpp; sourceTree = ""; };
+ 57724A59B2F7D072D91DD787 /* attributes.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = attributes.cpp; path = font/attributes.cpp; sourceTree = ""; };
58C649488B3014E6F7254B62 /* mp_report.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mp_report.cpp; sourceTree = ""; };
5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tod_new_schedule.hpp; sourceTree = ""; };
620A386215E9364E00A4F513 /* attack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = attack.cpp; sourceTree = ""; };
@@ -2687,6 +2692,7 @@
B55BE04A11234B1A00154E6C /* lobby_info.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = lobby_info.hpp; sourceTree = ""; };
B561F366104B1042001369F5 /* component.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = component.cpp; sourceTree = ""; };
B561F367104B1042001369F5 /* component.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = component.hpp; sourceTree = ""; };
+ B56948C5B90E74C62F2D078F /* attributes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = attributes.hpp; path = font/attributes.hpp; sourceTree = ""; };
B5951A811013BB0800C10B66 /* chat_events.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = chat_events.hpp; sourceTree = ""; };
B5951A831013BB0800C10B66 /* multiplayer_error_codes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = multiplayer_error_codes.hpp; sourceTree = ""; };
B5951A841013BB0800C10B66 /* resources.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resources.cpp; sourceTree = ""; };
@@ -5145,6 +5151,8 @@
46F92EF42174FEBD00602C1C /* standard_colors.hpp */,
46F92F0C2174FEC000602C1C /* text.cpp */,
46F92F0D2174FEC000602C1C /* text.hpp */,
+ 57724A59B2F7D072D91DD787 /* attributes.cpp */,
+ B56948C5B90E74C62F2D078F /* attributes.hpp */,
);
name = font;
sourceTree = "";
@@ -5201,6 +5209,7 @@
F7524948ADF9BC097E6D8DBC /* charconv.hpp in Headers */,
00424091A60B5901585B212F /* units_dialog.hpp in Headers */,
48C54CF8AD9615C43EB823E7 /* addon_server_info.hpp in Headers */,
+ B14E4163984EED844169EF4F /* attributes.hpp in Headers */,
08964907BF0C2F261FC984DC /* reachmap_options.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -5226,6 +5235,7 @@
92644A76AAB2F29A77107DC0 /* charconv.hpp in Headers */,
96D34041A501AB7F5B7AD596 /* units_dialog.hpp in Headers */,
E60E437B8712EC8D22CA2608 /* addon_server_info.hpp in Headers */,
+ 7C4740B081A838A09779FAAA /* attributes.hpp in Headers */,
63B0402A889C6663911DC677 /* reachmap_options.hpp in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -5963,6 +5973,7 @@
99494BD0ABBAE79FB3814E00 /* charconv.cpp in Sources */,
04DC4E59AEDBC1A0AFDCA8CC /* units_dialog.cpp in Sources */,
DA2B4478B9C7AB102479C322 /* addon_server_info.cpp in Sources */,
+ 0813449DBC67700714FA3ACD /* attributes.cpp in Sources */,
6C4A4F7982769422C51DC3E6 /* reachmap_options.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -6637,6 +6648,7 @@
355942A786D57DD0A6A93E2A /* units_dialog.cpp in Sources */,
52074B55B6C7AC8A1AE8BEA8 /* addon_server_info.cpp in Sources */,
144E49509EAC409649899BD4 /* test_lua_ptr.cpp in Sources */,
+ 38C444C497ECBE2DF9BB2319 /* attributes.cpp in Sources */,
E875402885AA34096C34E3B0 /* reachmap_options.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/source_lists/libwesnoth b/source_lists/libwesnoth
index dae08d68ad5..4e22972939d 100644
--- a/source_lists/libwesnoth
+++ b/source_lists/libwesnoth
@@ -8,6 +8,7 @@ display.cpp
display_context.cpp
events.cpp
floating_label.cpp
+font/attributes.cpp
font/font_config.cpp
font/sdl_ttf_compat.cpp
font/standard_colors.cpp
diff --git a/src/font/attributes.cpp b/src/font/attributes.cpp
new file mode 100644
index 00000000000..c2b7c1b7141
--- /dev/null
+++ b/src/font/attributes.cpp
@@ -0,0 +1,174 @@
+/*
+ Copyright (C) 2025
+ Part of the Battle for Wesnoth Project https://www.wesnoth.org/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY.
+
+ See the COPYING file for more details.
+*/
+
+#include "font/attributes.hpp"
+#include "font/font_config.hpp"
+
+#include "color.hpp"
+#include "gui/core/log.hpp"
+#include "preferences/preferences.hpp"
+#include "tstring.hpp"
+#include "video.hpp"
+
+#include
+
+namespace font
+{
+namespace
+{
+/**
+ * Private helper class to manage a single PangoAttribute.
+ *
+ * This object owns its attribute until relinquished to an attribute_list
+ * by calling @ref add_to or @ref modify_in.
+ */
+class attribute
+{
+public:
+ attribute(PangoAttribute* attr, unsigned offset_start, unsigned offset_end)
+ : value_(attr, &pango_attribute_destroy)
+ {
+ attr->start_index = offset_start;
+ attr->end_index = offset_end;
+ }
+
+ void add_to(font::attribute_list& list)
+ {
+ list.insert(value_.release());
+ }
+
+ void modify_in(font::attribute_list& list)
+ {
+ list.modify(value_.release());
+ }
+
+private:
+ std::unique_ptr value_;
+};
+
+/** Pango sometimes handles colors as 16 bit integers. */
+constexpr std::tuple color_to_uint16(const color_t& color)
+{
+ return {
+ color.r / 255.0 * std::numeric_limits::max(),
+ color.g / 255.0 * std::numeric_limits::max(),
+ color.b / 255.0 * std::numeric_limits::max()
+ };
+}
+
+} // anon namespace
+
+void add_attribute_size(attribute_list& list, unsigned offset_start, unsigned offset_end, int size)
+{
+ // TODO: we shouldn't be doing scaling stuff here...
+ size = prefs::get().font_scaled(size) * video::get_pixel_scale();
+
+ attribute attr {
+ pango_attr_size_new_absolute(PANGO_SCALE * size),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "attribute: size";
+ DBG_GUI_D << "attribute start: " << offset_start << " end : " << offset_end;
+
+ attr.add_to(list);
+}
+
+void add_attribute_weight(attribute_list& list, unsigned offset_start, unsigned offset_end, PangoWeight weight)
+{
+ attribute attr {
+ pango_attr_weight_new(weight),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "attribute: weight";
+ DBG_GUI_D << "attribute start: " << offset_start << " end : " << offset_end;
+
+ attr.add_to(list);
+}
+
+void add_attribute_style(attribute_list& list, unsigned offset_start, unsigned offset_end, PangoStyle style)
+{
+ attribute attr {
+ pango_attr_style_new(style),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "attribute: style";
+ DBG_GUI_D << "attribute start: " << offset_start << " end : " << offset_end;
+
+ attr.add_to(list);
+}
+
+void add_attribute_underline(attribute_list& list, unsigned offset_start, unsigned offset_end, PangoUnderline underline)
+{
+ attribute attr {
+ pango_attr_underline_new(underline),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "attribute: underline";
+ DBG_GUI_D << "attribute start: " << offset_start << " end : " << offset_end;
+
+ attr.add_to(list);
+}
+
+void add_attribute_fg_color(attribute_list& list, unsigned offset_start, unsigned offset_end, const color_t& color)
+{
+ auto [col_r, col_g, col_b] = color_to_uint16(color);
+
+ attribute attr {
+ pango_attr_foreground_new(col_r, col_g, col_b),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "attribute: fg color";
+ DBG_GUI_D << "attribute start: " << offset_start << " end : " << offset_end;
+ DBG_GUI_D << "color: " << col_r << "," << col_g << "," << col_b;
+
+ attr.add_to(list);
+}
+
+void add_attribute_bg_color(attribute_list& list, unsigned offset_start, unsigned offset_end, const color_t& color)
+{
+ auto [col_r, col_g, col_b] = color_to_uint16(color);
+
+ attribute attr {
+ pango_attr_background_new(col_r, col_g, col_b),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "highlight start: " << offset_start << "end : " << offset_end;
+ DBG_GUI_D << "highlight color: " << col_r << "," << col_g << "," << col_b;
+
+ attr.modify_in(list);
+}
+
+void add_attribute_font_family(attribute_list& list, unsigned offset_start, unsigned offset_end, font::family_class family)
+{
+ const t_string& family_name = get_font_families(family);
+
+ attribute attr {
+ pango_attr_family_new(family_name.c_str()),
+ offset_start, offset_end
+ };
+
+ DBG_GUI_D << "attribute: font family";
+ DBG_GUI_D << "attribute start: " << offset_start << " end : " << offset_end;
+ DBG_GUI_D << "font family: " << family;
+
+ attr.add_to(list);
+}
+
+} // namespace font
diff --git a/src/font/attributes.hpp b/src/font/attributes.hpp
new file mode 100644
index 00000000000..a83352d1a72
--- /dev/null
+++ b/src/font/attributes.hpp
@@ -0,0 +1,148 @@
+/*
+ Copyright (C) 2025
+ Part of the Battle for Wesnoth Project https://www.wesnoth.org/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY.
+
+ See the COPYING file for more details.
+*/
+
+#pragma once
+
+#include
+
+#include "font/font_options.hpp"
+
+struct color_t;
+
+namespace font
+{
+/** Helper class to encapsulate the management of a PangoAttrList. */
+class attribute_list
+{
+public:
+ attribute_list()
+ : attributes_(pango_attr_list_new())
+ {
+ }
+
+ ~attribute_list()
+ {
+ pango_attr_list_unref(attributes_);
+ }
+
+ attribute_list(const attribute_list&) = delete;
+ attribute_list& operator=(const attribute_list&) = delete;
+
+ void insert(PangoAttribute* attr)
+ {
+ pango_attr_list_insert(attributes_, attr);
+ }
+
+ void modify(PangoAttribute* attr)
+ {
+ pango_attr_list_change(attributes_, attr);
+ }
+
+ void apply_to(PangoLayout* layout) const
+ {
+ pango_layout_set_attributes(layout, attributes_);
+ }
+
+ void splice_into(PangoAttrList* target) const
+ {
+ pango_attr_list_splice(target, attributes_, 0, 0);
+ }
+
+private:
+ PangoAttrList* attributes_;
+};
+
+//
+// The following free functions are thin wrappers around the corresponding
+// pango_attr_new methods. For more details, refer to the Pango docs.
+//
+
+/**
+ * Add Pango font weight attribute to a specific portion of text. This changes the font weight
+ * of the corresponding part of the text.
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where font weight change starts
+ * @param offset_end Byte index of the cursor where font weight change ends
+ * @param weight Pango font weight
+ */
+void add_attribute_weight(attribute_list& list, unsigned offset_start, unsigned offset_end, PangoWeight weight);
+
+/**
+ * Add Pango font style attribute to a specific portion of text, used to set italic/oblique text
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where font style change starts
+ * @param offset_end Byte index of the cursor where font style change ends
+ * @param style Pango font style (normal/italic/oblique)
+ */
+void add_attribute_style(attribute_list& list, unsigned offset_start, unsigned offset_end, PangoStyle style);
+
+/**
+ * Add Pango underline attribute to a specific portion of text. This adds an underline to the
+ * corresponding part of the text.
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where underline starts
+ * @param offset_end Byte index of the cursor where underline change ends
+ * @param underline Pango underline style
+ */
+void add_attribute_underline(attribute_list& list, unsigned offset_start, unsigned offset_end, PangoUnderline underline);
+
+/**
+ * Add Pango fg color attribute to a specific portion of text. This changes the foreground
+ * color of the corresponding part of the text.
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where color change starts
+ * @param offset_end Byte index of the cursor where color change ends
+ * @param color Foreground color
+ */
+void add_attribute_fg_color(attribute_list& list, unsigned offset_start, unsigned offset_end, const color_t& color);
+
+/**
+ * Mark a specific portion of text for highlighting. Used for selection box.
+ * BGColor is set in set_text(), this just marks the area to be colored.
+ * Markup not used because the user may enter their own markup or special characters
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where selection/highlight starts
+ * @param offset_end Byte index of the cursor where selection/highlight ends
+ * @param color Highlight/Background color
+ */
+void add_attribute_bg_color(attribute_list& list, unsigned offset_start, unsigned offset_end, const color_t& color);
+
+/**
+ * Add Pango font size attribute to a specific portion of text. This changes the font size
+ * of the corresponding part of the text.
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where size change starts
+ * @param offset_end Byte index of the cursor where size change ends
+ * @param size Font size
+ */
+void add_attribute_size(attribute_list& list, unsigned offset_start, unsigned offset_end, int size);
+
+/**
+ * Add Pango font family attribute to a specific portion of text. This changes
+ * the font family of the corresponding part of the text.
+ *
+ * @param list The attribute list to which to append this attribute.
+ * @param offset_start Byte index of the cursor where size change starts
+ * @param offset_end Byte index of the cursor where size change ends
+ * @param family The font family
+ */
+void add_attribute_font_family(attribute_list& list, unsigned offset_start, unsigned offset_end, font::family_class family);
+
+} // namespace font
diff --git a/src/font/text.cpp b/src/font/text.cpp
index 3652d6cc898..c8ca985f332 100644
--- a/src/font/text.cpp
+++ b/src/font/text.cpp
@@ -17,6 +17,7 @@
#include "font/text.hpp"
+#include "font/attributes.hpp"
#include "font/font_config.hpp"
#include "font/pango/escape.hpp"
@@ -32,7 +33,6 @@
#include "preferences/preferences.hpp"
#include "video.hpp"
-
#include
#include
#include
@@ -66,9 +66,6 @@ pango_text::pango_text()
, pixel_scale_(1)
, surface_buffer_()
{
- // Initialize global list
- global_attribute_list_ = pango_attr_list_new();
-
// With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
pango_cairo_context_set_resolution(context_.get(), 72.0);
@@ -305,138 +302,20 @@ int pango_text::xy_to_index(const point& position) const
return index;
}
-void pango_text::add_attribute_size(const unsigned start_offset, const unsigned end_offset, int size)
+void pango_text::clear_attributes()
{
- size = prefs::get().font_scaled(size) * pixel_scale_;
+ pango_layout_set_attributes(layout_.get(), nullptr);
+}
- if (start_offset != end_offset) {
- PangoAttribute *attr = pango_attr_size_new_absolute(PANGO_SCALE * size);
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- DBG_GUI_D << "attribute: size";
- DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
-
- // Insert all attributes
- pango_attr_list_insert(global_attribute_list_, attr);
+void pango_text::apply_attributes(const font::attribute_list& attrs)
+{
+ if(PangoAttrList* current_attrs = pango_layout_get_attributes(layout_.get())) {
+ attrs.splice_into(current_attrs);
+ } else {
+ attrs.apply_to(layout_.get());
}
}
-void pango_text::add_attribute_weight(const unsigned start_offset, const unsigned end_offset, PangoWeight weight)
-{
- if (start_offset != end_offset) {
- PangoAttribute *attr = pango_attr_weight_new(weight);
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- DBG_GUI_D << "attribute: weight";
- DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
-
- // Insert all attributes
- pango_attr_list_insert(global_attribute_list_, attr);
- }
-}
-
-void pango_text::add_attribute_style(const unsigned start_offset, const unsigned end_offset, PangoStyle style)
-{
- if (start_offset != end_offset) {
- PangoAttribute *attr = pango_attr_style_new(style);
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- DBG_GUI_D << "attribute: style";
- DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
-
- // Insert all attributes
- pango_attr_list_insert(global_attribute_list_, attr);
- }
-}
-
-void pango_text::add_attribute_underline(const unsigned start_offset, const unsigned end_offset, PangoUnderline underline)
-{
- if (start_offset != end_offset) {
- PangoAttribute *attr = pango_attr_underline_new(underline);
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- DBG_GUI_D << "attribute: underline";
- DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
-
- // Insert all attributes
- pango_attr_list_insert(global_attribute_list_, attr);
- }
-}
-
-namespace
-{
-std::tuple color_to_uint16(const color_t& color)
-{
- return {
- color.r / 255.0 * std::numeric_limits::max(),
- color.g / 255.0 * std::numeric_limits::max(),
- color.b / 255.0 * std::numeric_limits::max()
- };
-}
-
-} // end anon namespace
-
-void pango_text::add_attribute_fg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color)
-{
- if (start_offset != end_offset) {
- auto [col_r, col_g, col_b] = color_to_uint16(color);
- PangoAttribute *attr = pango_attr_foreground_new(col_r, col_g, col_b);
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- DBG_GUI_D << "attribute: fg color";
- DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
- DBG_GUI_D << "color: " << col_r << "," << col_g << "," << col_b;
-
- // Insert all attributes
- pango_attr_list_insert(global_attribute_list_, attr);
- }
-}
-
-void pango_text::add_attribute_font_family(const unsigned start_offset, const unsigned end_offset, font::family_class family)
-{
- if (start_offset != end_offset) {
- const t_string& family_name = get_font_families(family);
- PangoAttribute *attr = pango_attr_family_new(family_name.c_str());
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- DBG_GUI_D << "attribute: font family";
- DBG_GUI_D << "attribute start: " << start_offset << " end : " << end_offset;
- DBG_GUI_D << "font family: " << family;
-
- // Insert all attributes
- pango_attr_list_insert(global_attribute_list_, attr);
- }
-}
-
-void pango_text::add_attribute_bg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color)
-{
- // Highlight
- int col_r = color.r / 255.0 * 65535.0;
- int col_g = color.g / 255.0 * 65535.0;
- int col_b = color.b / 255.0 * 65535.0;
-
- DBG_GUI_D << "highlight start: " << start_offset << "end : " << end_offset;
- DBG_GUI_D << "highlight color: " << col_r << "," << col_g << "," << col_b;
-
- PangoAttribute *attr = pango_attr_background_new(col_r, col_g, col_b);
- attr->start_index = start_offset;
- attr->end_index = end_offset;
-
- // Insert all attributes
- pango_attr_list_change(global_attribute_list_, attr);
-}
-
-void pango_text::clear_attribute_list() {
- global_attribute_list_ = pango_attr_list_new();
- pango_layout_set_attributes(layout_.get(), global_attribute_list_);
-}
-
bool pango_text::set_text(const std::string& text, const bool markedup)
{
if(markedup != markedup_text_ || text != text_) {
@@ -461,11 +340,6 @@ bool pango_text::set_text(const std::string& text, const bool markedup)
pango_layout_set_text(layout_.get(), narrow.c_str(), narrow.size());
}
- pango_layout_set_attributes(layout_.get(), global_attribute_list_);
-
- // Clear list. Using pango_attr_list_unref() causes segfault
- global_attribute_list_ = pango_attr_list_new();
-
text_ = narrow;
length_ = wide.size();
markedup_text_ = markedup;
@@ -951,14 +825,6 @@ bool pango_text::set_markup(std::string_view text, PangoLayout& layout)
} else {
pango_layout_set_markup(&layout, text.data(), text.size());
}
-
- // append any manual attributes to those generated by pango_layout_set_markup
- PangoAttrList* markup_list = pango_layout_get_attributes(&layout);
- for (auto* l = pango_attr_list_get_attributes(global_attribute_list_); l != nullptr; l = l->next) {
- PangoAttribute* attr = static_cast(l->data);
- pango_attr_list_change(markup_list, attr);
- }
- global_attribute_list_ = markup_list;
}
return valid;
diff --git a/src/font/text.hpp b/src/font/text.hpp
index 676e5361fdf..79371ea6de7 100644
--- a/src/font/text.hpp
+++ b/src/font/text.hpp
@@ -35,7 +35,9 @@
struct point;
-namespace font {
+namespace font
+{
+class attribute_list;
// add background color and also font markup.
@@ -315,74 +317,8 @@ public:
pango_text& set_add_outline(bool do_add);
- // The following add attribute methods are thin wrappers around the corresponding pango
- // add attribute methods. For more details, refer to the Pango docs.
-
- /**
- * Add pango font weight attribute to a specific portion of text. This changes the font weight
- * of the corresponding part of the text.
- * @param start_offset Byte index of the cursor where font weight change starts
- * @param end_offset Byte index of the cursor where font weight change ends
- * @param weight Pango font weight
- */
- void add_attribute_weight(const unsigned start_offset, const unsigned end_offset, PangoWeight weight);
-
- /**
- * Add pango font style attribute to a specific portion of text, used to set italic/oblique text
- * @param start_offset Byte index of the cursor where font style change starts
- * @param end_offset Byte index of the cursor where font style change ends
- * @param style Pango font style (normal/italic/oblique)
- */
- void add_attribute_style(const unsigned start_offset, const unsigned end_offset, PangoStyle style);
-
- /**
- * Add pango underline attribute to a specific portion of text. This adds an underline to the
- * corresponding part of the text.
- * @param start_offset Byte index of the cursor where underline starts
- * @param end_offset Byte index of the cursor where underline change ends
- * @param underline Pango underline style
- */
- void add_attribute_underline(const unsigned start_offset, const unsigned end_offset, PangoUnderline underline);
-
- /**
- * Add pango fg color attribute to a specific portion of text. This changes the foreground
- * color of the corresponding part of the text.
- * @param start_offset Byte index of the cursor where color change starts
- * @param end_offset Byte index of the cursor where color change ends
- * @param color Foreground color
- */
- void add_attribute_fg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color);
-
- /**
- * Mark a specific portion of text for highlighting. Used for selection box.
- * BGColor is set in set_text(), this just marks the area to be colored.
- * Markup not used because the user may enter their own markup or special characters
- * @param start_offset Byte index of the cursor where selection/highlight starts
- * @param end_offset Byte index of the cursor where selection/highlight ends
- * @param color Highlight/Background color
- */
- void add_attribute_bg_color(const unsigned start_offset, const unsigned end_offset, const color_t& color);
-
- /**
- * Add pango font size attribute to a specific portion of text. This changes the font size
- * of the corresponding part of the text.
- * @param start_offset Byte index of the cursor where size change starts
- * @param end_offset Byte index of the cursor where size change ends
- * @param size Font size
- */
- void add_attribute_size(const unsigned start_offset, const unsigned end_offset, int size);
-
- /**
- * Add pango font family attribute to a specific portion of text. This changes
- * the font family of the corresponding part of the text.
- * @param start_offset Byte index of the cursor where size change starts
- * @param end_offset Byte index of the cursor where size change ends
- * @param family The font family
- */
- void add_attribute_font_family(const unsigned start_offset, const unsigned end_offset, font::family_class family);
-
- /** Clears all attributes from the global attribute list */
- void clear_attribute_list();
+ void clear_attributes();
+ void apply_attributes(const font::attribute_list& attrs);
private:
@@ -481,12 +417,6 @@ private:
/** Length of the text. */
mutable std::size_t length_;
- /**
- * Global pango attribute list. All attributes in this list
- * will be applied one by one to the text
- */
- PangoAttrList* global_attribute_list_;
-
/** The pixel scale, used to render high-DPI text. */
int pixel_scale_;
diff --git a/src/gui/core/canvas.cpp b/src/gui/core/canvas.cpp
index 549f7040c62..1d1dfd85806 100644
--- a/src/gui/core/canvas.cpp
+++ b/src/gui/core/canvas.cpp
@@ -25,6 +25,7 @@
#include "draw.hpp"
#include "draw_manager.hpp"
+#include "font/attributes.hpp"
#include "font/text.hpp"
#include "formatter.hpp"
#include "gettext.hpp"
@@ -438,8 +439,7 @@ void text_shape::draw(wfl::map_formula_callable& variables)
return;
}
- font::pango_text& text_renderer = font::get_text_renderer();
- text_renderer.clear_attribute_list();
+ font::attribute_list text_attributes;
//
// Highlight
@@ -450,7 +450,7 @@ void text_shape::draw(wfl::map_formula_callable& variables)
for(size_t i = 0; i < std::min(starts.size(), stops.size()); i++) {
typed_formula hstart(starts.at(i));
typed_formula hstop(stops.at(i));
- text_renderer.add_attribute_bg_color(hstart(variables), hstop(variables), highlight_color_(variables));
+ add_attribute_bg_color(text_attributes, hstart(variables), hstop(variables), highlight_color_(variables));
}
//
@@ -467,30 +467,33 @@ void text_shape::draw(wfl::map_formula_callable& variables)
const unsigned end = attr["end"].to_int(text.size());
if (name == "color" || name == "fgcolor" || name == "foreground") {
- text_renderer.add_attribute_fg_color(start, end, attr["value"].empty() ? font::NORMAL_COLOR : font::string_to_color(attr["value"]));
- } else if (name == "bgcolor"||name == "background") {
- text_renderer.add_attribute_bg_color(start, end, attr["value"].empty() ? font::GOOD_COLOR : font::string_to_color(attr["value"]));
- } else if (name == "font_size"||name == "size") {
- text_renderer.add_attribute_size(start, end, attr["value"].to_int(font::SIZE_NORMAL));
- } else if (name == "font_family"||name == "face") {
- text_renderer.add_attribute_font_family(start, end, font::str_to_family_class(attr["value"]));
+ add_attribute_fg_color(text_attributes, start, end, attr["value"].empty() ? font::NORMAL_COLOR : font::string_to_color(attr["value"]));
+ } else if (name == "bgcolor" || name == "background") {
+ add_attribute_bg_color(text_attributes, start, end, attr["value"].empty() ? font::GOOD_COLOR : font::string_to_color(attr["value"]));
+ } else if (name == "font_size" || name == "size") {
+ add_attribute_size(text_attributes, start, end, attr["value"].to_int(font::SIZE_NORMAL));
+ } else if (name == "font_family" || name == "face") {
+ add_attribute_font_family(text_attributes, start, end, font::str_to_family_class(attr["value"]));
} else if (name == "weight") {
- text_renderer.add_attribute_weight(start, end, decode_text_weight(attr["value"]));
+ add_attribute_weight(text_attributes, start, end, decode_text_weight(attr["value"]));
} else if (name == "style") {
- text_renderer.add_attribute_style(start, end, decode_text_style(attr["value"]));
+ add_attribute_style(text_attributes, start, end, decode_text_style(attr["value"]));
} else if (name == "bold" || name == "b") {
- text_renderer.add_attribute_weight(start, end, PANGO_WEIGHT_BOLD);
+ add_attribute_weight(text_attributes, start, end, PANGO_WEIGHT_BOLD);
} else if (name == "italic" || name == "i") {
- text_renderer.add_attribute_style(start, end, PANGO_STYLE_ITALIC);
+ add_attribute_style(text_attributes, start, end, PANGO_STYLE_ITALIC);
} else if (name == "underline" || name == "u") {
- text_renderer.add_attribute_underline(start, end, PANGO_UNDERLINE_SINGLE);
+ add_attribute_underline(text_attributes, start, end, PANGO_UNDERLINE_SINGLE);
} else {
// Unsupported formatting or normal text
- text_renderer.add_attribute_weight(start, end, PANGO_WEIGHT_NORMAL);
- text_renderer.add_attribute_style(start, end, PANGO_STYLE_NORMAL);
+ add_attribute_weight(text_attributes, start, end, PANGO_WEIGHT_NORMAL);
+ add_attribute_style(text_attributes, start, end, PANGO_STYLE_NORMAL);
}
}
+ font::pango_text& text_renderer = font::get_text_renderer();
+ text_renderer.clear_attributes();
+
text_renderer
.set_link_aware(link_aware_(variables))
.set_link_color(link_color_(variables))
@@ -509,6 +512,9 @@ void text_shape::draw(wfl::map_formula_callable& variables)
.set_characters_per_line(characters_per_line_)
.set_add_outline(outline_(variables));
+ // Do this last so it can merge with attributes from markup
+ text_renderer.apply_attributes(text_attributes);
+
wfl::map_formula_callable local_variables(variables);
const auto [tw, th] = text_renderer.get_size();
diff --git a/src/gui/widgets/text_box_base.cpp b/src/gui/widgets/text_box_base.cpp
index d177e17bd12..9b4bc362b30 100644
--- a/src/gui/widgets/text_box_base.cpp
+++ b/src/gui/widgets/text_box_base.cpp
@@ -19,6 +19,7 @@
#include "cursor.hpp"
#include "desktop/clipboard.hpp"
+#include "font/attributes.hpp"
#include "gui/core/gui_definition.hpp"
#include "gui/core/log.hpp"
#include "gui/core/timer.hpp"
@@ -122,6 +123,13 @@ void text_box_base::set_maximum_length(const std::size_t maximum_length)
}
}
+void text_box_base::set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color)
+{
+ font::attribute_list attrs;
+ add_attribute_bg_color(attrs, start_offset, end_offset, color);
+ text_.apply_attributes(attrs);
+}
+
void text_box_base::set_value(const std::string& text)
{
if(text != text_.text()) {
diff --git a/src/gui/widgets/text_box_base.hpp b/src/gui/widgets/text_box_base.hpp
index b77531633c2..93628685ef1 100644
--- a/src/gui/widgets/text_box_base.hpp
+++ b/src/gui/widgets/text_box_base.hpp
@@ -121,12 +121,9 @@ public:
/**
* Wrapper function, sets the area between column start and end
* offset to be highlighted in a specific color.
- * See @ref font::pango_text::add_attribute_bg_color.
+ * See @ref font::add_attribute_bg_color.
*/
- void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color)
- {
- text_.add_attribute_bg_color(start_offset, end_offset, color);
- }
+ void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color);
/***** ***** ***** setters / getters for members ***** ****** *****/