Skip to content
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 .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
working-directory: ${{runner.workspace}}/build
run: |
cmake $GITHUB_WORKSPACE \
-DCMAKE_CXX_STANDARD=20 \
-DCOMPILE_WARNING_AS_ERROR=ON \
-DACTSVG_BUILD_TESTING=ON \
-DACTSVG_BUILD_META=ON \
Expand Down
14 changes: 12 additions & 2 deletions core/include/actsvg/core/svg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@

#include <algorithm>
#include <array>
#include <functional>
#include <limits>
#include <map>
#include <optional>
#include <ranges>
#include <sstream>
#include <string>
#include <vector>

Expand Down Expand Up @@ -177,7 +180,7 @@ struct file {

/// Default header tail definitions
std::string _html_head = "<html>\n<body>\n";
std::string _svg_head = "<svg version=\"1.1\"";
std::string _svg_head = "<svg version=\"1.2\"";

std::string _svg_def_end =
" xmlns=\"http://www.w3.org/2000/svg\" "
Expand Down Expand Up @@ -221,7 +224,14 @@ struct file {
*/
void set_view_box(const std::array<scalar, 4> &vb_, bool adjust_ = true);

/** Write to ostream */
/** Dump file into a string stream */
std::stringstream to_stream() const;

/** Produce a checksum for this file using a stable crc32 implementation
*/
std::uint32_t checksum() const;

/** Write to ostream, it uses to_stream() */
friend std::ostream &operator<<(std::ostream &os_, const file &f_);
};

Expand Down
26 changes: 26 additions & 0 deletions core/include/actsvg/core/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

#pragma once

#include <array>
#include <cmath>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <sstream>
Expand Down Expand Up @@ -314,6 +316,30 @@ point3_container place_vertices(const point3_container &pc_,
return placed;
}

/***
* @brief Computes the CRC32 checksum of a given string.
*
* @param data The input string.
* @return The CRC32 checksum.
*/
static inline std::uint32_t crc32(const std::string &data) {
static constexpr std::array<std::uint32_t, 256> table = [] {
std::array<std::uint32_t, 256> tab{};
for (std::uint32_t i = 0; i < 256; i++) {
std::uint32_t c = i;
for (int j = 0; j < 8; j++)
c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
tab[i] = c;
}
return tab;
}();

std::uint32_t crc = 0xFFFFFFFFu;
for (unsigned char ch : data)
crc = table[(crc ^ ch) & 0xFFu] ^ (crc >> 8);
return crc ^ 0xFFFFFFFFu;
}

} // namespace utils

} // namespace actsvg
51 changes: 32 additions & 19 deletions core/src/core/svg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "actsvg/core.hpp"
#include "actsvg/core/defs.hpp"
#include "actsvg/core/style.hpp"
#include "actsvg/core/utils.hpp"

namespace actsvg::svg {
bool object::is_defined() const {
Expand Down Expand Up @@ -166,44 +167,56 @@ void file::set_view_box(const std::array<scalar, 4> &vbox_, bool adjust_) {
}
}

std::ostream &operator<<(std::ostream &os_, const file &f_) {
std::stringstream file::to_stream() const {
std::stringstream sstr;

std::map<std::string, svg::object> definitions;
for (const auto &o : f_._objects) {
for (const auto &o : _objects) {
if (o._active) {
for (const auto &d : o._definitions) {
definitions[d._id] = d;
}
}
}

std::array<scalar, 4> vbox = f_._view_box.value_or(f_.view_box());
std::array<scalar, 4> vbox = _view_box.value_or(view_box());

if (f_._add_html) {
os_ << f_._html_head;
if (_add_html) {
sstr << _html_head;
}
os_ << f_._svg_head;
os_ << " width=\"" << f_._width << "\" height=\"" << f_._height << "\"";
os_ << " viewBox=\"" << vbox[0] << " " << vbox[1] << " " << vbox[2] << " "
<< vbox[3] << "\"";
os_ << f_._svg_def_end;
sstr << _svg_head;
sstr << " width=\"" << static_cast<int>(_width) << "\" height=\""
<< static_cast<int>(_height) << "\"";
sstr << " viewBox=\"" << static_cast<int>(vbox[0]) << " "
<< static_cast<int>(vbox[1]) << " " << static_cast<int>(vbox[2]) << " "
<< static_cast<int>(vbox[3]) << "\"";
sstr << _svg_def_end;

// Write the definitions first
if (not definitions.empty()) {
os_ << "<defs>";
sstr << "<defs>";
for (auto [key, value] : definitions) {
os_ << value;
sstr << value;
}
os_ << "</defs>";
sstr << "</defs>";
}

// Now write the objects
for (auto &o : f_._objects) {
os_ << o;
}
os_ << f_._svg_tail;
if (f_._add_html) {
os_ << f_._html_tail;
std::ranges::for_each(_objects, [&sstr](const auto &o) { sstr << o; });

sstr << _svg_tail;
if (_add_html) {
sstr << _html_tail;
}
return sstr;
}

std::uint32_t file::checksum() const {
return utils::crc32(to_stream().str());
}

std::ostream &operator<<(std::ostream &os_, const file &f_) {
os_ << f_.to_stream().str();
return os_;
}

Expand Down
113 changes: 113 additions & 0 deletions tests/common/test_checksum.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// This file is part of the actsvg package.
//
// Copyright (C) 2025 CERN for the benefit of the ACTS project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#pragma once

#include <fstream>
#include <iostream>
#include <map>
#include <string>

namespace actsvg::test {

// clang-format off
/// @brief Map of map to allow for multiple files per test
static std::map<std::string, std::map<std::string, std::uint32_t>>
checksum_references = {
{"arc_plain", {{"test_core_arc.svg", 598987246u}}},
{"arrows", {{"test_core_arrows.svg", 156197267u}}},
{"barrel_x_y_view", {{"test_core_barrel_xy.svg", 475857493u}}},
{"barrel_z_phi_view_grid", {{"test_core_barrel_grid_zphi.svg", 846567370u}}},
{"barrel_z_phi_view", {{"test_core_barrel_zphi.svg", 2866154162u}}},
{"bezier_single_segment", {{"test_core_bezier_single.svg", 1452396398u}}},
{"bezier_spiral", {{"test_core_bezier_spiral.svg", 3456691438u}}},
{"cartesian_grid", {{"test_core_cartesian_grid.svg", 2070215038u}}},
{"circle_plain", {{"test_core_circle.svg", 3148444685u}}},
{"circle_scaled", {{"test_core_circle_scaled.svg", 3227732129u}}},
{"circle_shifted", {{"test_core_circle_shifted.svg", 1574532577u}}},
{"copy_triangle", {{"test_core_copy_triangle.svg", 325380468u}}},
{"disc_sector", {{"test_core_sector.svg", 2519980248u}}},
{"endcap_x_y_view_grid", {{"test_core_endcap_grid_xy.svg", 2340117177u}}},
{"endcap_x_y_view", {{"test_core_endcap_xy.svg", 3832402486u}}},
{"endcap_z_r_view", {{"test_core_endcap_zr.svg", 1171393426u}}},
{"fan_grid", {{"test_core_fan_grid.svg", 4027289050u}}},
{"gradient_box_horizontal_label", {{"test_core_gradient_box_horizontal_label.svg", 474900462u}}},
{"gradient_box_horizontal", {{"test_core_gradient_box_horizontal.svg", 305729971u}}},
{"gradient_box_linear_x", {{"test_core_gradient_box.svg", 3168005651u}}},
{"gradient_box_vertical_label", {{"test_core_gradient_box_vertical_label.svg", 4211281398u}}},
{"gradient_box_vertical", {{"test_core_gradient_box_vertical.svg", 4211281398u}}},
{"info_box", {{"test_core_infobox.svg", 391203306u}}},
{"label_center_center", {{"test_core_label_center_center.svg", 2273812319u}}},
{"label_left_bottom", {{"test_core_label_left_bottom.svg", 2284580567u}}},
{"label_left_center", {{"test_core_label_left_center.svg", 3350259941u}}},
{"label_left_top", {{"test_core_label_left_top.svg", 2653912820u}}},
{"label_right_bottom", {{"test_core_label_right_bottom.svg", 858078508u}}},
{"label_right_center", {{"test_core_label_right_center.svg", 3086957164u}}},
{"label_right_top", {{"test_core_label_right_top.svg", 812048451u}}},
{"line_plain", {{"test_core_line.svg", 2384224490u}}},
{"line_scaled", {{"test_core_line_scaled.svg", 2130133868u}}},
{"line_shifted", {{"test_core_line_shifted.svg", 681977029u}}},
{"markers", {{"test_core_markers.svg", 1722066658u}}},
{"measure", {{"test_core_measures.svg", 2274566291u}}},
{"multiline_text_outside_playground", {{"test_core_text_multiline_range.svg", 660718925u}}},
{"multiline_text", {{"test_core_text_multiline.svg", 986506060u}}},
{"polar_grid", {{"test_core_polar_grid.svg", 4052172164u}}},
{"polyline", {{"test_core_polyline.svg", 2285432013u}}},
{"rectangle", {{"test_core_rectangle.svg", 3386630377u}}},
{"tiled_cartesian_grid", {{"test_core_tiled_cartesian_grid.svg", 3030659065u}}},
{"tiled_fan_grid", {{"test_core_tiled_fan_grid.svg", 232443122u}}},
{"tiled_polar_grid", {{"test_core_tiled_polar_grid.svg", 4084096140u}}},
{"triangle_highligh", {{"test_core_triangle_highlight.svg", 3994778312u}}},
{"triangle_rotated_shifted", {{"test_core_triangle_rotated_shifted.svg", 3296491611u}}},
{"triangle_rotated", {{"test_core_triangle_rotated.svg", 1263086013u}}},
{"triangle_scaled", {{"test_core_triangle_scaled.svg", 3282708057u}}},
{"triangle_shifted", {{"test_core_triangle_shifted.svg", 1124525001u}}},
{"triangle", {{"test_core_triangle.svg", 42086428u}}},
{"unconnected_text", {{"test_core_text.svg", 3211069024u}}},
};
// clang-format on

/** @brief Checksum test against reference
*
* @param test_name Name of the test
* @param file_name Name of the file
* @param checksum Checksum value to test from reference
*
* @return True if the checksum matches the reference, false otherwise
*/
static inline bool checksum(const std::string &test_name,
const std::string &file_name,
std::size_t checksum) {
auto test_it = checksum_references.find(test_name);
if (test_it != checksum_references.end()) {
auto file_it = test_it->second.find(file_name);
if (file_it != test_it->second.end()) {
bool result = (file_it->second == checksum);
if (not result) {
std::fstream fs;
fs.open("checksum_mismatch_" + test_name + ".log",
std::ios::app);
fs << "Checksum mismatch! For updating, use this line: "
<< '\n';
fs << "{\"" << test_name << "\", {{\"" << file_name << "\", "
<< checksum << "u}}}," << '\n';
fs.close();
}
return result;
}
}
std::fstream fs;
fs.open("checksum_not_found_" + test_name + ".log", std::ios::app);
fs << "Reference not found! If you want to add it, use this line: " << '\n';
fs << "{\"" << test_name << "\", {{\"" << file_name << "\", " << checksum
<< "u}}}," << '\n';
fs.close();
return false;
}

} // namespace actsvg::test
10 changes: 9 additions & 1 deletion tests/core/arc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <sstream>

#include "../common/playground.hpp"
#include "../common/test_checksum.hpp"
#include "actsvg/core/draw.hpp"

using namespace actsvg;
Expand All @@ -22,7 +23,8 @@ TEST(core, arc_plain) {

// Write out the file
std::ofstream fo;
fo.open("test_core_arc.svg");
std::string file_name = "test_core_arc.svg";
fo.open(file_name);

scalar phi_min = -0.25;
scalar phi_max = 0.75;
Expand All @@ -44,4 +46,10 @@ TEST(core, arc_plain) {
of.add_object(descr);
fo << of;
fo.close();

/// Checksum test against reference
std::string test_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
std::uint32_t file_checksum = of.checksum();
EXPECT_TRUE(test::checksum(test_name, file_name, file_checksum));
}
12 changes: 10 additions & 2 deletions tests/core/arrows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
#include <fstream>

#include "../common/playground.hpp"
#include "../common/test_checksum.hpp"
#include "actsvg/core.hpp"

using namespace actsvg;

TEST(draw, arrows) {
TEST(core, arrows) {

// Set a playground
auto pg = test::playground({-400, -400}, {400, 400});
Expand All @@ -39,7 +40,14 @@ TEST(draw, arrows) {
mfile.add_object(a2);

std::ofstream tstream;
tstream.open("test_core_arrows.svg");
std::string file_name = "test_core_arrows.svg";
tstream.open(file_name);
tstream << mfile;
tstream.close();

/// Checksum test against reference
std::string test_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
std::uint32_t file_checksum = mfile.checksum();
EXPECT_TRUE(test::checksum(test_name, file_name, file_checksum));
}
Loading
Loading