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 ***** ****** *****/