Skip to content

Commit

Permalink
Add better Item details format string parser
Browse files Browse the repository at this point in the history
This replaces with previous primitive Item details format string parser with a much better one using [lexy](https://github.com/foonathan/lexy).

For now, the library has been forked to fix Win32 (x86) builds in Visual Studio.
  • Loading branch information
reupen committed Nov 16, 2024
1 parent b95f737 commit 0cbe188
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 158 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@

- New `$set_format()` and `$reset_format()` title formatting functions were
added to Item details.
[[#1004](https://github.com/reupen/columns_ui/pull/1004)]
[[#1004](https://github.com/reupen/columns_ui/pull/1004),
[#1011](https://github.com/reupen/columns_ui/pull/1011)]

These serve as replacements for the older `$set_font()` and `$reset_font()`
functions.
Expand Down
3 changes: 3 additions & 0 deletions foo_ui_columns/foo_ui_columns.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@
<ClCompile Include="font_manager_v3.cpp" />
<ClCompile Include="gdi.cpp" />
<ClCompile Include="icons.cpp" />
<ClCompile Include="item_details_format_parser.cpp" />
<ClCompile Include="ng_playlist\ng_playlist_config.cpp" />
<ClCompile Include="playlist_selector.cpp" />
<ClCompile Include="resource_utils.cpp" />
Expand Down Expand Up @@ -452,8 +453,10 @@
<ClInclude Include="get_msg_hook.h" />
<ClInclude Include="icons.h" />
<ClInclude Include="item_details.h" />
<ClInclude Include="item_details_format_parser.h" />
<ClInclude Include="item_details_text.h" />
<ClInclude Include="item_properties.h" />
<ClInclude Include="lexy_user_config.hpp" />
<ClInclude Include="menu_helpers.h" />
<ClInclude Include="metadb_helpers.h" />
<ClInclude Include="mw_drop_target.h" />
Expand Down
7 changes: 7 additions & 0 deletions foo_ui_columns/foo_ui_columns.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@
<ClCompile Include="string.cpp">
<Filter>Utilities</Filter>
</ClCompile>
<ClCompile Include="item_details_format_parser.cpp">
<Filter>Item details</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
Expand Down Expand Up @@ -917,6 +920,10 @@
<ClInclude Include="string.h">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="item_details_format_parser.h">
<Filter>Item details</Filter>
</ClInclude>
<ClInclude Include="lexy_user_config.hpp" />
</ItemGroup>
<ItemGroup>
<None Include=".clang-format" />
Expand Down
39 changes: 16 additions & 23 deletions foo_ui_columns/item_details.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -904,8 +904,8 @@ void ItemDetails::recreate_text_format()

namespace {

template <class Value>
void process_simple_style_property(const std::vector<FontSegment>& segments, StylePropertyType property_type,
template <class Value, typename Member>
void process_simple_style_property(const std::vector<FontSegment>& segments, Member member,
std::function<void(const Value&, DWRITE_TEXT_RANGE text_range)> apply_to_layout)
{
struct CurrentValue {
Expand All @@ -932,14 +932,14 @@ void process_simple_style_property(const std::vector<FontSegment>& segments, Sty
};

for (auto& [properties, start_character, character_count] : segments) {
const auto iter = properties.find(property_type);
const auto& value = properties.*member;

if (iter == std::end(properties)) {
if (!value) {
apply_current_value();
continue;
}

const auto& segment_value = std::get<Value>(iter->second);
const auto& segment_value = std::get<Value>(*value);

if (current_value
&& (current_value->value != segment_value
Expand Down Expand Up @@ -1000,21 +1000,14 @@ void process_wss_style_property(const std::vector<FontSegment>& segments,
std::optional<DWRITE_FONT_STRETCH> stretch;
std::optional<DWRITE_FONT_STYLE> style;

for (auto&& [type, value] : properties) {
switch (type) {
case StylePropertyType::FontWeight:
weight = std::get<DWRITE_FONT_WEIGHT>(value);
break;
case StylePropertyType::FontStretch:
stretch = std::get<DWRITE_FONT_STRETCH>(value);
break;
case StylePropertyType::FontStyle:
style = std::get<DWRITE_FONT_STYLE>(value);
break;
default:
break;
}
}
if (properties.font_weight)
weight = std::get<DWRITE_FONT_WEIGHT>(*properties.font_weight);

if (properties.font_stretch)
stretch = std::get<DWRITE_FONT_STRETCH>(*properties.font_stretch);

if (properties.font_style)
style = std::get<DWRITE_FONT_STYLE>(*properties.font_style);

if (!(weight || stretch || style)) {
apply_current_value();
Expand Down Expand Up @@ -1070,16 +1063,16 @@ void ItemDetails::create_text_layout()
CATCH_LOG()
}

process_simple_style_property<std::wstring>(font_segments, StylePropertyType::FontFamily,
process_simple_style_property<std::wstring>(font_segments, &FormatProperties::font_family,
[&](auto&& value, auto&& text_range) { m_text_layout->set_family(value.c_str(), text_range); });

process_simple_style_property<float>(
font_segments, StylePropertyType::FontSize, [&](auto&& value, auto&& text_range) {
font_segments, &FormatProperties::font_size, [&](auto&& value, auto&& text_range) {
m_text_layout->set_size(uih::direct_write::pt_to_dip(value), text_range);
});

process_simple_style_property<TextDecorationType>(
font_segments, StylePropertyType::TextDecoration, [&](auto&& value, auto&& text_range) {
font_segments, &FormatProperties::text_decoration, [&](auto&& value, auto&& text_range) {
if (value == TextDecorationType::Underline)
m_text_layout->set_underline(true, text_range);
});
Expand Down
152 changes: 152 additions & 0 deletions foo_ui_columns/item_details_format_parser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include "pch.h"

#include "item_details_format_parser.h"

#ifdef __clang__
#pragma clang diagnostic warning "-Wshift-op-parentheses"
#endif

namespace cui::panels::item_details {

namespace {

namespace dsl = lexy::dsl;

struct Initial {
static constexpr auto rule = dsl::lit<"initial">;
static constexpr auto value = lexy::constant(InitialPropertyValue{});
};

struct Float {
static constexpr auto rule = [] {
constexpr auto whole_part = dsl::digits<>;
constexpr auto fractional_part = dsl::period >> dsl::digits<>;
constexpr auto decimal = dsl::token(whole_part + dsl::if_(fractional_part));
return dsl::capture(decimal);
}();

static constexpr auto value = lexy::as_string<std::wstring, lexy::utf16_encoding>
| lexy::callback<float>([](std::wstring&& str) { return std::stof(str); });
};

struct FontFamilyValue {
struct String : lexy::token_production {
static constexpr auto rule = dsl::list(dsl::capture(dsl::unicode::character - dsl::lit_c<';'>));
static constexpr auto value = lexy::as_string<std::wstring, lexy::utf16_encoding>;
};

static constexpr auto rule = dsl::p<Initial> | dsl::else_ >> dsl::p<String>;
static constexpr auto value = lexy::forward<decltype(FormatProperties::font_family)>;
};

struct FontSizeValue {
static constexpr auto rule = dsl::p<Initial> | dsl::p<Float>;
static constexpr auto value = lexy::forward<decltype(FormatProperties::font_size)>;
};

struct FontWeightValue {
struct Weight {
static constexpr auto rule = dsl::integer<int>;
static constexpr auto value = lexy::callback<DWRITE_FONT_WEIGHT>(
[](int value) { return static_cast<DWRITE_FONT_WEIGHT>(std::clamp(value, 1, 900)); });
};

static constexpr auto rule = dsl::p<Initial> | dsl::p<Weight>;
static constexpr auto value = lexy::forward<decltype(FormatProperties::font_weight)>;
};

struct FontStretchValue {
struct Stretch {
static constexpr auto rule = dsl::integer<int>;
static constexpr auto value = lexy::callback<DWRITE_FONT_STRETCH>(
[](int value) { return static_cast<DWRITE_FONT_STRETCH>(std::clamp(value, 1, 9)); });
};

static constexpr auto rule = dsl::p<Initial> | dsl::p<Stretch>;
static constexpr auto value = lexy::forward<decltype(FormatProperties::font_stretch)>;
};

struct FontStyleValue {
struct Normal {
static constexpr auto rule = dsl::lit<"normal">;
static constexpr auto value = lexy::constant(DWRITE_FONT_STYLE_NORMAL);
};

struct Italic {
static constexpr auto rule = dsl::lit<"italic">;
static constexpr auto value = lexy::constant(DWRITE_FONT_STYLE_ITALIC);
};

struct Oblique {
static constexpr auto rule = dsl::lit<"oblique">;
static constexpr auto value = lexy::constant(DWRITE_FONT_STYLE_OBLIQUE);
};

static constexpr auto rule = dsl::p<Initial> | dsl::p<Normal> | dsl::p<Italic> | dsl::p<Oblique>;
static constexpr auto value = lexy::forward<decltype(FormatProperties::font_style)>;
};

struct TextDecorationValue {
struct None {
static constexpr auto rule = dsl::lit<"none">;
static constexpr auto value = lexy::constant(TextDecorationType::None);
};

struct Underline {
static constexpr auto rule = dsl::lit<"underline">;
static constexpr auto value = lexy::constant(TextDecorationType::Underline);
};

static constexpr auto rule = dsl::p<Initial> | dsl::p<None> | dsl::p<Underline>;
static constexpr auto value = lexy::forward<decltype(FormatProperties::text_decoration)>;
};

struct UnknownProperty {
static constexpr auto rule = dsl::until(dsl::lit_c<';'>);
};

template <typename Name, typename ValueRule, typename Member>
constexpr auto make_property(Name name, ValueRule value_rule, Member member)
{
return name >> dsl::lit_c<':'>
+ dsl::try_((member = value_rule) + (dsl::lit_c<';'> | dsl::eof), dsl::until(dsl::lit_c<';'>).or_eof());
}

struct FormatPropertiesProduction {
static constexpr auto whitespace = dsl::unicode::space;

static constexpr auto rule = dsl::partial_combination(
make_property(dsl::lit<"font-family">, dsl::p<FontFamilyValue>, dsl::member<&FormatProperties::font_family>),
make_property(dsl::lit<"font-size">, dsl::p<FontSizeValue>, dsl::member<&FormatProperties::font_size>),
make_property(dsl::lit<"font-weight">, dsl::p<FontWeightValue>, dsl::member<&FormatProperties::font_weight>),
make_property(dsl::lit<"font-stretch">, dsl::p<FontStretchValue>, dsl::member<&FormatProperties::font_stretch>),
make_property(dsl::lit<"font-style">, dsl::p<FontStyleValue>, dsl::member<&FormatProperties::font_style>),
make_property(
dsl::lit<"text-decoration">, dsl::p<TextDecorationValue>, dsl::member<&FormatProperties::text_decoration>),
dsl::inline_<UnknownProperty>);

static constexpr auto value = lexy::as_aggregate<FormatProperties>;
};

} // namespace

std::optional<FormatProperties> parse_font_properties(std::wstring_view input)
{
auto input_buffer = lexy::string_input<lexy::utf16_encoding>(input);

#ifdef _DEBUG
std::ostringstream stream;
auto result = lexy::parse<FormatPropertiesProduction>(
input_buffer, lexy_ext::report_error.to(std::ostream_iterator<const char>(stream)));
auto errors = stream.str();

Check notice on line 141 in foo_ui_columns/item_details_format_parser.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

foo_ui_columns/item_details_format_parser.cpp#L141

Variable 'errors' is assigned a value that is never used.
#else
auto result = lexy::parse<FormatPropertiesProduction>(input_buffer, lexy::noop);
#endif

if (result.has_value())
return result.value();

return {};
}

} // namespace cui::panels::item_details
23 changes: 23 additions & 0 deletions foo_ui_columns/item_details_format_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

namespace cui::panels::item_details {

struct InitialPropertyValue {};

enum class TextDecorationType : int8_t {
None,
Underline,
};

struct FormatProperties {
std::optional<std::variant<std::wstring, InitialPropertyValue>> font_family;
std::optional<std::variant<float, InitialPropertyValue>> font_size;
std::optional<std::variant<DWRITE_FONT_WEIGHT, InitialPropertyValue>> font_weight;
std::optional<std::variant<DWRITE_FONT_STRETCH, InitialPropertyValue>> font_stretch;
std::optional<std::variant<DWRITE_FONT_STYLE, InitialPropertyValue>> font_style;
std::optional<std::variant<TextDecorationType, InitialPropertyValue>> text_decoration;
};

std::optional<FormatProperties> parse_font_properties(std::wstring_view input);

} // namespace cui::panels::item_details
Loading

0 comments on commit 0cbe188

Please sign in to comment.