mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-07 10:33:24 -08:00
The pre-#7595 add, remove and delete icons were filled shapes with a black outline and no disc. A tint cannot bring that back, and the badge disc is a different icon language, so add a third value to the existing style axis. IconStyle::Sticker draws the sentinel glyph twice: a near-black rim at the band width plus one rim on each side, then the tone pass clipped to the glyph and filled with a vertical gradient. Both treatments keep the disabled fallback to the line glyph. The fill rule moves into theme_icon_fill: the semantic tones are tuned for text, so a filled shape gives its tone a saturation floor, and a dark-scheme tone is pulled down rather than up. IconTint::resolve() applies the same floor to line glyphs, and the badge disc uses the same fill, so a line, a badge and a sticker next to each other carry the same green or red instead of a pale line beside a saturated glyph. The badge's glyph ink follows the scheme: white on light-scheme tones, as in the pre-#7595 art, and the saturated tone darkened to an outline-dark ink on dark-scheme tones, where the shared saturated disc is light enough that white would sit near 2.7:1. The new test pins both cases so the glyph cannot silently flip. resolveSvgStrokeWidth() moves the stroke-width rewrite into libcockatrice_utility so the badge and the sticker share it; the badge's local copy is gone. The sticker is opted into at 17 call sites: increment, decrement and remove_row in the deck editor, settings pages, token dialog, server dialogs and replay/deck storage tabs. The visual database's clear-filter button keeps its trash can (icons/delete) in the line treatment: that glyph is new in #7595, but it is the clearer metaphor, and a sticker rim closes its lid gap at toolbar sizes. The preview generators also gain a semantic-tint sheet for #7775 and a line/badge/sticker family strip, so the three PR descriptions can show the same rendered colors.
119 lines
3.5 KiB
C++
119 lines
3.5 KiB
C++
#include "libcockatrice/utility/svg_recolor.h"
|
|
|
|
#include <QByteArray>
|
|
#include <gtest/gtest.h>
|
|
|
|
namespace
|
|
{
|
|
|
|
TEST(SvgRecolor, ReplacesEverySentinel)
|
|
{
|
|
const QByteArray svg = R"(<svg><path fill="currentColor"/><path stroke="currentColor"/></svg>)";
|
|
const QByteArray result = resolveSvgColorSentinel(svg, "#ff8800");
|
|
|
|
EXPECT_EQ(result.count("#ff8800"), 2);
|
|
EXPECT_FALSE(result.contains("currentColor"));
|
|
}
|
|
|
|
TEST(SvgRecolor, ReplacesSentinelRegardlessOfCase)
|
|
{
|
|
const QByteArray svg = R"(<svg><path fill="CurrentColor"/><path stroke="CURRENTCOLOR"/></svg>)";
|
|
|
|
EXPECT_EQ(resolveSvgColorSentinel(svg, "#ff8800").count("#ff8800"), 2);
|
|
}
|
|
|
|
TEST(SvgRecolor, LeavesTheRestOfTheDocumentIntact)
|
|
{
|
|
const QByteArray svg =
|
|
R"(<svg viewBox="0 0 24 24"><!-- an ordinary comment --><path d="M0 0h24v24" fill="currentColor"/></svg>)";
|
|
const QByteArray result = resolveSvgColorSentinel(svg, "#123456");
|
|
|
|
EXPECT_TRUE(result.contains(R"(viewBox="0 0 24 24")"));
|
|
EXPECT_TRUE(result.contains(R"(d="M0 0h24v24")"));
|
|
EXPECT_TRUE(result.contains("an ordinary comment"));
|
|
}
|
|
|
|
TEST(SvgRecolor, DoesNotModifyTheInput)
|
|
{
|
|
const QByteArray svg = R"(<svg><path fill="currentColor"/></svg>)";
|
|
const QByteArray original = svg;
|
|
|
|
resolveSvgColorSentinel(svg, "#000000");
|
|
|
|
EXPECT_EQ(svg, original);
|
|
}
|
|
|
|
TEST(SvgRecolor, PreservesBytesThatAreNotValidUtf8)
|
|
{
|
|
QByteArray svg = "<svg>";
|
|
svg.append("\xff\xfe");
|
|
svg.append(R"(<path fill="currentColor"/></svg>)");
|
|
|
|
const QByteArray result = resolveSvgColorSentinel(svg, "#ff8800");
|
|
|
|
EXPECT_TRUE(result.contains("\xff\xfe"));
|
|
EXPECT_FALSE(result.contains("currentColor"));
|
|
EXPECT_EQ(result.count("#ff8800"), 1);
|
|
}
|
|
|
|
TEST(SvgRecolor, NoSentinelIsANoOp)
|
|
{
|
|
const QByteArray svg = R"(<svg><path fill="#abcdef"/></svg>)";
|
|
|
|
EXPECT_EQ(resolveSvgColorSentinel(svg, "#ffffff"), svg);
|
|
}
|
|
|
|
TEST(SvgStrokeWidth, RewritesEveryAttribute)
|
|
{
|
|
const QByteArray svg = R"(<svg stroke-width="2"><path stroke-width="1.5"/><path stroke-width='3'/></svg>)";
|
|
|
|
const QByteArray result = resolveSvgStrokeWidth(svg, 4.25);
|
|
|
|
EXPECT_EQ(result.count(R"(stroke-width="4.25")"), 2);
|
|
EXPECT_EQ(result.count(R"(stroke-width='4.25')"), 1);
|
|
EXPECT_FALSE(result.contains("1.5"));
|
|
EXPECT_FALSE(result.contains("3'/"));
|
|
}
|
|
|
|
TEST(SvgStrokeWidth, LeavesCssDeclarationsAlone)
|
|
{
|
|
const QByteArray svg = R"(<svg><path style="stroke-width:2"/><path stroke-width="2"/></svg>)";
|
|
|
|
const QByteArray result = resolveSvgStrokeWidth(svg, 3.0);
|
|
|
|
EXPECT_TRUE(result.contains(R"(style="stroke-width:2")"));
|
|
EXPECT_EQ(result.count(R"(stroke-width="3.00")"), 1);
|
|
}
|
|
|
|
TEST(SvgStrokeWidth, NoAttributeIsANoOp)
|
|
{
|
|
const QByteArray svg = R"(<svg stroke-linecap="round"><path d="M0 0h24"/></svg>)";
|
|
|
|
EXPECT_EQ(resolveSvgStrokeWidth(svg, 3.0), svg);
|
|
}
|
|
|
|
TEST(SvgStrokeWidth, DoesNotModifyTheInputAndPreservesBytes)
|
|
{
|
|
QByteArray svg = "<svg>";
|
|
svg.append("\xff\xfe");
|
|
svg.append(R"(<path stroke-width="2"/></svg>)");
|
|
const QByteArray original = svg;
|
|
|
|
const QByteArray result = resolveSvgStrokeWidth(svg, 3.5);
|
|
|
|
EXPECT_EQ(svg, original);
|
|
EXPECT_TRUE(result.contains("\xff\xfe"));
|
|
EXPECT_TRUE(result.contains(R"(stroke-width="3.50")"));
|
|
}
|
|
|
|
TEST(SvgStrokeWidth, TruncatedAttributeTerminates)
|
|
{
|
|
// A quote that is never closed must not over-read or hang.
|
|
const QByteArray svg = R"(<svg><path stroke-width="2/></svg>)";
|
|
|
|
const QByteArray result = resolveSvgStrokeWidth(svg, 3.0);
|
|
|
|
EXPECT_TRUE(result.startsWith("<svg>"));
|
|
}
|
|
|
|
} // namespace
|