diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index 09ed63c2e..336ec9791 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -40,6 +40,8 @@ add_library(rmlui_core DecoratorNinePatch.h DecoratorShader.cpp DecoratorShader.h + DecoratorText.cpp + DecoratorText.h DecoratorTiled.cpp DecoratorTiled.h DecoratorTiledBox.cpp @@ -50,6 +52,8 @@ add_library(rmlui_core DecoratorTiledImage.h DecoratorTiledVertical.cpp DecoratorTiledVertical.h + DecoratorUtilities.cpp + DecoratorUtilities.h DocumentHeader.cpp DocumentHeader.h EffectSpecification.cpp diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index 81057f428..2ad28b664 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -149,32 +149,6 @@ static ColorStopList ResolveColorStops(Element* element, const float gradient_li return stops; } -// Compute a 2d-position property value into a percentage-length vector. -static Vector2Numeric ComputePosition(const Property* p_position[2]) -{ - Vector2Numeric position; - for (int dimension = 0; dimension < 2; dimension++) - { - NumericValue& value = position[dimension]; - const Property& property = *p_position[dimension]; - if (property.unit == Unit::KEYWORD) - { - enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; - switch (property.Get()) - { - case TOP_LEFT: value = NumericValue(0.f, Unit::PERCENT); break; - case CENTER: value = NumericValue(50.f, Unit::PERCENT); break; - case BOTTOM_RIGHT: value = NumericValue(100.f, Unit::PERCENT); break; - } - } - else - { - value = property.GetNumericValue(); - } - } - return position; -} - DecoratorStraightGradient::DecoratorStraightGradient() {} DecoratorStraightGradient::~DecoratorStraightGradient() {} @@ -596,7 +570,7 @@ SharedPtr DecoratorRadialGradientInstancer::InstanceDecorator(const S const Property* p_ending_shape = properties_.GetProperty(ids.ending_shape); const Property* p_size_x = properties_.GetProperty(ids.size_x); const Property* p_size_y = properties_.GetProperty(ids.size_y); - const Property* p_position[2] = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; + Array p_position = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); if (!p_ending_shape || !p_size_x || !p_size_y || !p_position[0] || !p_position[1] || !p_color_stop_list) @@ -727,7 +701,7 @@ SharedPtr DecoratorConicGradientInstancer::InstanceDecorator(const St const DecoratorInstancerInterface& /*interface_*/) { const Property* p_angle = properties_.GetProperty(ids.angle); - const Property* p_position[2] = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; + Array p_position = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); if (!p_angle || !p_position[0] || !p_position[1] || !p_color_stop_list) diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index cee2f7645..7679cfdf3 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -33,11 +33,10 @@ #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/ID.h" +#include "DecoratorUtilities.h" namespace Rml { -using Vector2Numeric = Vector2; - /** Straight gradient. diff --git a/Source/Core/DecoratorText.cpp b/Source/Core/DecoratorText.cpp new file mode 100644 index 000000000..19e3b39b3 --- /dev/null +++ b/Source/Core/DecoratorText.cpp @@ -0,0 +1,170 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2024 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "DecoratorText.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/TextShapingContext.h" + +namespace Rml { + +DecoratorText::DecoratorText() {} + +DecoratorText::~DecoratorText() {} + +void DecoratorText::Initialise(String in_text, bool in_inherit_color, Colourb in_color, Vector2Numeric in_align) +{ + text = std::move(in_text); + inherit_color = in_inherit_color; + color = in_color; + align = in_align; +} + +DecoratorDataHandle DecoratorText::GenerateElementData(Element* element, BoxArea paint_area) const +{ + ElementData* data = new ElementData{paint_area, {}, -1}; + + if (!GenerateGeometry(element, *data)) + { + Log::Message(Log::LT_WARNING, "Could not construct text decorator with text %s on element %s", text.c_str(), element->GetAddress().c_str()); + return {}; + } + + return reinterpret_cast(data); +} + +void DecoratorText::ReleaseElementData(DecoratorDataHandle element_data) const +{ + delete reinterpret_cast(element_data); +} + +void DecoratorText::RenderElement(Element* element, DecoratorDataHandle element_data) const +{ + ElementData* data = reinterpret_cast(element_data); + if (!GenerateGeometry(element, *data)) + return; + + const Vector2f translation = element->GetAbsoluteOffset(BoxArea::Border); + + for (size_t i = 0; i < data->textured_geometry.size(); ++i) + data->textured_geometry[i].geometry.Render(translation, data->textured_geometry[i].texture); +} + +bool DecoratorText::GenerateGeometry(Element* element, ElementData& element_data) const +{ + FontFaceHandle font_face_handle = element->GetFontFaceHandle(); + if (font_face_handle == 0) + return false; + + FontEngineInterface* font_engine_interface = GetFontEngineInterface(); + const int new_version = font_engine_interface->GetVersion(font_face_handle); + if (new_version == element_data.font_handle_version) + return true; + + const auto& computed = element->GetComputedValues(); + const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.letter_spacing()}; + + const int width = font_engine_interface->GetStringWidth(font_face_handle, text, text_shaping_context); + + const FontMetrics& metrics = font_engine_interface->GetFontMetrics(font_face_handle); + const RenderBox render_box = element->GetRenderBox(element_data.paint_area); + const Vector2f text_size = {float(width), metrics.ascent + metrics.descent}; + const Vector2f offset_to_align_area = render_box.GetFillOffset() + Vector2f{0, metrics.ascent}; + const Vector2f size_of_align_area = render_box.GetFillSize() - text_size; + + const Vector2f offset_within_align_area = + Vector2f{element->ResolveNumericValue(align.x, size_of_align_area.x), element->ResolveNumericValue(align.y, size_of_align_area.y)}; + + const Vector2f offset = offset_to_align_area + offset_within_align_area; + const float opacity = computed.opacity(); + const ColourbPremultiplied text_color = (inherit_color ? computed.color() : color).ToPremultiplied(opacity); + + RenderManager& render_manager = element->GetContext()->GetRenderManager(); + TexturedMeshList mesh_list; + font_engine_interface->GenerateString(render_manager, font_face_handle, {}, text, offset, text_color, opacity, text_shaping_context, mesh_list); + + if (mesh_list.empty()) + return false; + + Vector textured_geometry(mesh_list.size()); + for (size_t i = 0; i < textured_geometry.size(); i++) + { + textured_geometry[i].geometry = render_manager.MakeGeometry(std::move(mesh_list[i].mesh)); + textured_geometry[i].texture = mesh_list[i].texture; + } + + element_data = ElementData{ + element_data.paint_area, + std::move(textured_geometry), + font_engine_interface->GetVersion(font_face_handle), + }; + + return true; +} + +DecoratorTextInstancer::DecoratorTextInstancer() +{ + ids = {}; + ids.text = RegisterProperty("text", "").AddParser("string").GetId(); + ids.color = RegisterProperty("color", "inherit-color").AddParser("keyword", "inherit-color").AddParser("color").GetId(); + ids.align_x = RegisterProperty("align-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + ids.align_y = RegisterProperty("align-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + RegisterShorthand("decorator", "text, color, align-x, align-y, align-x", ShorthandType::FallThrough); +} + +DecoratorTextInstancer::~DecoratorTextInstancer() {} + +SharedPtr DecoratorTextInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& /*instancer_interface*/) +{ + const Property* p_text = properties.GetProperty(ids.text); + const Property* p_color = properties.GetProperty(ids.color); + Array p_align = {properties.GetProperty(ids.align_x), properties.GetProperty(ids.align_y)}; + if (!p_text || !p_color || !p_align[0] || !p_align[1]) + return nullptr; + + String text = StringUtilities::DecodeRml(p_text->Get()); + if (text.empty()) + return nullptr; + + const bool inherit_color = (p_color->unit == Unit::KEYWORD); + const Colourb color = (p_color->unit == Unit::COLOUR ? p_color->Get() : Colourb{}); + const Vector2Numeric align = ComputePosition(p_align); + + auto decorator = MakeShared(); + decorator->Initialise(std::move(text), inherit_color, color, align); + return decorator; +} + +} // namespace Rml diff --git a/Source/Core/DecoratorText.h b/Source/Core/DecoratorText.h new file mode 100644 index 000000000..ec895ee74 --- /dev/null +++ b/Source/Core/DecoratorText.h @@ -0,0 +1,86 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2024 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_DECORATORTEXT_H +#define RMLUI_CORE_DECORATORTEXT_H + +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "DecoratorUtilities.h" + +namespace Rml { + +class DecoratorText : public Decorator { +public: + DecoratorText(); + virtual ~DecoratorText(); + + void Initialise(String text, bool inherit_color, Colourb color, Vector2Numeric align); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + struct TexturedGeometry { + Geometry geometry; + Texture texture; + }; + struct ElementData { + BoxArea paint_area; + Vector textured_geometry; + int font_handle_version; + }; + + bool GenerateGeometry(Element* element, ElementData& element_data) const; + + String text; + bool inherit_color = false; + Colourb color; + Vector2Numeric align; +}; + +class DecoratorTextInstancer : public DecoratorInstancer { +public: + DecoratorTextInstancer(); + ~DecoratorTextInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct PropertyIds { + PropertyId text, color, align_x, align_y; + }; + PropertyIds ids; +}; + +} // namespace Rml +#endif diff --git a/Source/Core/DecoratorUtilities.cpp b/Source/Core/DecoratorUtilities.cpp new file mode 100644 index 000000000..facf3fdc4 --- /dev/null +++ b/Source/Core/DecoratorUtilities.cpp @@ -0,0 +1,60 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2024 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "DecoratorUtilities.h" +#include "../../Include/RmlUi/Core/Property.h" + +namespace Rml { + +// Compute a 2d-position property value into a percentage-length vector. +Vector2Numeric ComputePosition(Array p_position) +{ + Vector2Numeric position; + for (int dimension = 0; dimension < 2; dimension++) + { + NumericValue& value = position[dimension]; + const Property& property = *p_position[dimension]; + if (property.unit == Unit::KEYWORD) + { + enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; + switch (property.Get()) + { + case TOP_LEFT: value = NumericValue(0.f, Unit::PERCENT); break; + case CENTER: value = NumericValue(50.f, Unit::PERCENT); break; + case BOTTOM_RIGHT: value = NumericValue(100.f, Unit::PERCENT); break; + } + } + else + { + value = property.GetNumericValue(); + } + } + return position; +} + +} // namespace Rml diff --git a/Source/Core/DecoratorUtilities.h b/Source/Core/DecoratorUtilities.h new file mode 100644 index 000000000..cb092c18c --- /dev/null +++ b/Source/Core/DecoratorUtilities.h @@ -0,0 +1,43 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2024 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_DECORATORUTILITIES_H +#define RMLUI_CORE_DECORATORUTILITIES_H + +#include "../../Include/RmlUi/Core/NumericValue.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +using Vector2Numeric = Vector2; + +// Compute a 2d-position property value into a percentage-length vector. +Vector2Numeric ComputePosition(Array p_position); + +} // namespace Rml +#endif diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index a3ff7f0bd..1fd5fe40f 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -52,6 +52,7 @@ #include "DecoratorGradient.h" #include "DecoratorNinePatch.h" #include "DecoratorShader.h" +#include "DecoratorText.h" #include "DecoratorTiledBox.h" #include "DecoratorTiledHorizontal.h" #include "DecoratorTiledImage.h" @@ -109,6 +110,7 @@ struct DefaultInstancers { ElementInstancerGeneric progress; // Decorators + DecoratorTextInstancer decorator_text; DecoratorTiledHorizontalInstancer decorator_tiled_horizontal; DecoratorTiledVerticalInstancer decorator_tiled_vertical; DecoratorTiledBoxInstancer decorator_tiled_box; @@ -220,6 +222,7 @@ void Factory::Initialise() RegisterElementInstancer("progressbar", &default_instancers.progress); // Decorator instancers + RegisterDecoratorInstancer("text", &default_instancers.decorator_text); RegisterDecoratorInstancer("tiled-horizontal", &default_instancers.decorator_tiled_horizontal); RegisterDecoratorInstancer("tiled-vertical", &default_instancers.decorator_tiled_vertical); RegisterDecoratorInstancer("tiled-box", &default_instancers.decorator_tiled_box); diff --git a/Tests/Data/VisualTests/text_decorator.rml b/Tests/Data/VisualTests/text_decorator.rml new file mode 100644 index 000000000..d264d46bc --- /dev/null +++ b/Tests/Data/VisualTests/text_decorator.rml @@ -0,0 +1,87 @@ + + + Text decorator + + + + + + +Default properties [equivalent] + +
+
+
+
+ + +Top-left alignment [equivalent] + +
+
+
+
+
+ + +Blue text [equivalent] + +
+
+
+ + +Miscellaneous + +
+
+
+
+
+ + +