Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS content property and unicode escape sequences #399

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/RmlUi/Core/ComputedValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ namespace Style {
LengthPercentage row_gap() const { return LengthPercentage(rare.row_gap_type, rare.row_gap); }
LengthPercentage column_gap() const { return LengthPercentage(rare.column_gap_type, rare.column_gap); }
float scrollbar_margin() const { return rare.scrollbar_margin; }
const String* content() const;

// -- Assignment --
// Common
Expand Down
6 changes: 5 additions & 1 deletion Include/RmlUi/Core/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,12 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E

/// Removes all transitions that are no longer part of the element's 'transition' property.
void HandleTransitionProperty();

/// Starts new animations and removes animations no longer part of the element's 'animation' property.
void HandleAnimationProperty();

/// Updates the elements inner content according to the element's 'content' property.
void HandleContentProperty();

/// Advances the animations (including transitions) forward in time.
void AdvanceAnimations();
Expand All @@ -732,6 +735,7 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
bool dirty_transition : 1;
bool dirty_transform : 1;
bool dirty_perspective : 1;
bool dirty_content : 1;

OwnedElementList children;
int num_non_dom_children;
Expand Down
1 change: 1 addition & 0 deletions Include/RmlUi/Core/ID.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ enum class PropertyId : uint8_t
FlexShrink,
FlexWrap,
JustifyContent,
Content,

NumDefinedIds,
FirstCustomId = NumDefinedIds,
Expand Down
10 changes: 10 additions & 0 deletions Source/Core/ComputedValues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ const TransitionList* Style::ComputedValues::transition() const
return nullptr;
}

const String* ComputedValues::content() const
{
if (auto p = element->GetLocalProperty(PropertyId::Content))
{
if (p->unit == Property::STRING)
return &(p->value.GetReference<String>());
}
return nullptr;
}

String Style::ComputedValues::font_family() const
{
if (auto p = element->GetProperty(PropertyId::FontFamily))
Expand Down
43 changes: 37 additions & 6 deletions Source/Core/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,26 @@ struct ElementMeta
static Pool< ElementMeta > element_meta_chunk_pool(200, true);

Element::Element(const String& tag) :
local_stacking_context(false), local_stacking_context_forced(false), stacking_context_dirty(false), computed_values_are_default_initialized(true),
visible(true), offset_fixed(false), absolute_offset_dirty(true), dirty_definition(false), dirty_child_definitions(false), dirty_animation(false),
dirty_transition(false), dirty_transform(false), dirty_perspective(false),

tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0), content_offset(0, 0),
local_stacking_context(false),
local_stacking_context_forced(false),
stacking_context_dirty(false),
computed_values_are_default_initialized(true),
visible(true),
offset_fixed(false),
absolute_offset_dirty(true),
dirty_definition(false),
dirty_child_definitions(false),
dirty_animation(false),
dirty_transition(false),
dirty_transform(false),
dirty_perspective(false),
dirty_content(false),
tag(tag),
relative_offset_base(0, 0),
relative_offset_position(0, 0),
absolute_offset(0, 0),
scroll_offset(0, 0),
content_offset(0, 0),
content_box(0, 0)
{
RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
Expand Down Expand Up @@ -164,7 +179,8 @@ void Element::Update(float dp_ratio, Vector2f vp_dimensions)
#endif

OnUpdate();


HandleContentProperty();
HandleTransitionProperty();
HandleAnimationProperty();
AdvanceAnimations();
Expand Down Expand Up @@ -1940,6 +1956,11 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
{
dirty_transition = true;
}
// Check for `content' changes
if (changed_properties.Contains(PropertyId::Content))
{
dirty_content = true;
}
}

void Element::OnPseudoClassChange(const String& /*pseudo_class*/, bool /*activate*/)
Expand Down Expand Up @@ -2713,6 +2734,16 @@ void Element::HandleAnimationProperty()
}
}

void Element::HandleContentProperty()
{
if (dirty_content)
{
auto content = GetComputedValues().content();
SetInnerRML(content ? *content : "");
dirty_content = false;
}
}

void Element::AdvanceAnimations()
{
if (!animations.empty())
Expand Down
1 change: 1 addition & 0 deletions Source/Core/ElementStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
break;

// Fetched from element's properties.
case PropertyId::Content:
case PropertyId::Cursor:
case PropertyId::Transform:
case PropertyId::Transition:
Expand Down
37 changes: 34 additions & 3 deletions Source/Core/PropertyParserString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
*
*/

#include <sstream>

#include "PropertyParserString.h"

namespace Rml {
Expand All @@ -42,10 +44,39 @@ PropertyParserString::~PropertyParserString()
bool PropertyParserString::ParseValue(Property& property, const String& value, const ParameterMap& RMLUI_UNUSED_PARAMETER(parameters)) const
{
RMLUI_UNUSED(parameters);

property.value = Variant(value);

// parse escaped unicode literals according to https://www.w3.org/TR/CSS2/syndata.html#characters

std::string str = value;
std::string::size_type startIdx = 0;
do
{
startIdx = str.find("\\", startIdx);
if (startIdx == std::string::npos)
break;

std::string::size_type endIdx = str.find_first_not_of("0123456789abcdefABCDEF", startIdx + 1);
if (endIdx == std::string::npos)
endIdx = str.length();

std::string tmpStr = str.substr(startIdx + 1, endIdx - (startIdx + 1));
std::istringstream iss(tmpStr);

uint32_t cp;
if (iss >> std::hex >> cp)
{
std::string utf8 = StringUtilities::ToUTF8(static_cast<Character>(cp));
str.replace(startIdx, 1 + tmpStr.length(), utf8);
startIdx += utf8.length();
}
else
startIdx += 1;
}
while (true);

property.value = Variant(str);
property.unit = Property::STRING;

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions Source/Core/StyleSheetSpecification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,8 @@ void StyleSheetSpecification::RegisterDefaultProperties()
RegisterProperty(PropertyId::FlexShrink, "flex-shrink", "1", false, true).AddParser("number");
RegisterProperty(PropertyId::FlexWrap, "flex-wrap", "nowrap", false, true).AddParser("keyword", "nowrap, wrap, wrap-reverse");
RegisterProperty(PropertyId::JustifyContent, "justify-content", "flex-start", false, true).AddParser("keyword", "flex-start, flex-end, center, space-between, space-around");

RegisterProperty(PropertyId::Content, "content", "", false, true).AddParser("string");

RegisterShorthand(ShorthandId::Flex, "flex", "flex-grow, flex-shrink, flex-basis", ShorthandType::Flex);
RegisterShorthand(ShorthandId::FlexFlow, "flex-flow", "flex-direction, flex-wrap", ShorthandType::FallThrough);
Expand Down
66 changes: 65 additions & 1 deletion Tests/Source/UnitTests/ElementStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,47 @@ static const String document_decorator_rml = R"(
</rml>
)";

static const String document_content_rml = R"(
<rml>
<head>
<title>Test</title>
<style>
body
{
font-family: LatoLatin;
font-weight: normal;
font-style: normal;
font-size: 15dp;
color: white;
}
div {
content: "Content from RCSS";
}
p {
content: "\2193"; /* unicode arrow pointing down (↓) */
}
span {
content: "A \ B"; /* sanity check against unescape logic */
}
</style>
</head>

<body>
<div id="div" />
<p id="p" />
<span id="span" />
</body>
</rml>
)";


TEST_CASE("elementstyle.inline_decorator_images")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);

// There should be no warnings loading this document. There should be three images visible.
ElementDocument* document = context->LoadDocumentFromMemory(document_decorator_rml, "assets/");
ElementDocument* document = context->LoadDocumentFromMemory(document_decorator_rml);
REQUIRE(document);
document->Show();

Expand All @@ -85,3 +118,34 @@ TEST_CASE("elementstyle.inline_decorator_images")

TestsShell::ShutdownShell();
}

TEST_CASE("elementstyle.content")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);

ElementDocument* document = context->LoadDocumentFromMemory(document_content_rml, "assets/");
REQUIRE(document);
document->Show();

auto p = document->GetElementById("p");
CHECK(p->GetProperty(PropertyId::Content)->ToString() == "↓");

auto span = document->GetElementById("span");
CHECK(span->GetProperty(PropertyId::Content)->ToString() == "A \\ B");

auto div = document->GetElementById("div");
CHECK(div->GetProperty(PropertyId::Content)->ToString() == "Content from RCSS");
div->SetProperty(PropertyId::Content, Property("Different content", Property::STRING));

context->Update();
context->Render();

TestsShell::RenderLoop();

CHECK(div->GetProperty(PropertyId::Content)->ToString() == "Different content");

document->Close();

TestsShell::ShutdownShell();
}