69template <
typename MDS>
70concept mdspan_like =
requires(std::remove_cvref_t<MDS>
const& mds) {
71 typename std::remove_cvref_t<MDS>::element_type;
72 typename std::remove_cvref_t<MDS>::index_type;
73 { std::remove_cvref_t<MDS>::rank() } -> std::convertible_to<std::size_t>;
74 { mds.extent(std::size_t{0}) } -> std::convertible_to<std::size_t>;
85template <mdspan_like MDS,
typename ElementFormatter>
96template <mdspan_like MDS>
97[[nodiscard]] std::string
format_mdspan(MDS
const& mds, output_policy
const& policy,
98 mdspan_format_options
const& options = {});
108template <mdspan_like MDS,
typename ElementFormatter>
109 requires(!std::same_as<std::remove_cvref_t<ElementFormatter>, mdspan_preview_options>)
110[[nodiscard]] mdspan_preview_result
format_mdspan_preview(MDS
const& mds, output_policy
const& policy,
111 ElementFormatter&& formatter,
112 mdspan_preview_options
const& options = {});
120template <mdspan_like MDS>
121[[nodiscard]] mdspan_preview_result
format_mdspan_preview(MDS
const& mds, output_policy
const& policy,
122 mdspan_preview_options
const& options = {});
127template <
typename MDS>
using mdspan_type = std::remove_cvref_t<MDS>;
129template <
typename MDS>
inline constexpr std::size_t mdspan_rank_v = mdspan_type<MDS>::rank();
131template <
typename MDS>
using mdspan_index_type =
typename mdspan_type<MDS>::index_type;
133[[nodiscard]]
inline std::string render_piece(
semantic_glyph glyph, output_policy
const& policy)
138[[nodiscard]]
inline std::string render_piece(std::string_view
text, output_policy
const& policy)
143template <
typename MDS, std::
size_t Rank>
144decltype(
auto) mdspan_at(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>
const& indices)
146 static_assert(Rank == mdspan_rank_v<MDS>);
150template <
typename MDS,
typename ElementFormatter, std::
size_t Rank>
151[[nodiscard]] std::string format_element(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>
const& indices,
152 output_policy
const& policy, ElementFormatter& formatter)
154 decltype(
auto)
value = mdspan_at(mds, indices);
155 auto text = std::string(std::invoke(formatter,
value));
159[[nodiscard]]
inline std::string shape_string(std::vector<std::size_t>
const& extents)
161 std::string
out =
"shape=(";
162 for (std::size_t i = 0; i < extents.size(); ++i)
164 if (i > 0)
out +=
", ";
165 out += std::to_string(extents[i]);
171[[nodiscard]]
inline std::string element_count_string(std::size_t element_count)
173 return "elements=" + std::to_string(element_count);
176[[nodiscard]]
inline std::string preview_notice_string() {
return "preview elided"; }
178[[nodiscard]]
inline std::size_t checked_product(std::vector<std::size_t>
const& values)
184 return std::numeric_limits<std::size_t>::max();
190struct resolved_matrix_axes
193 std::size_t column = 1;
194 std::vector<std::size_t> slice_axes;
197[[nodiscard]]
inline resolved_matrix_axes resolve_matrix_axes(std::size_t rank, mdspan_format_options
const& options)
199 auto axes = options.matrix_axes.value_or(mdspan_matrix_axes{rank - 2, rank - 1});
200 if (axes.row >= rank || axes.column >= rank)
202 throw std::invalid_argument(
"mdspan matrix axes must be valid tensor axes");
204 if (axes.row == axes.column)
206 throw std::invalid_argument(
"mdspan matrix row and column axes must be distinct");
209 resolved_matrix_axes result{axes.row, axes.column, {}};
210 result.slice_axes.reserve(rank - 2);
211 for (std::size_t axis = 0; axis < rank; ++axis)
213 if (axis != axes.row && axis != axes.column)
215 result.slice_axes.push_back(axis);
221template <
typename MDS, std::
size_t Rank>
222[[nodiscard]] std::string slice_label(std::array<mdspan_index_type<MDS>, Rank>
const& indices,
223 resolved_matrix_axes
const& axes)
225 std::string
out =
"slice [";
226 for (std::size_t i = 0; i < Rank; ++i)
228 if (i > 0)
out +=
", ";
229 if (i == axes.row || i == axes.column)
235 out += std::to_string(
static_cast<std::size_t
>(indices[i]));
242[[nodiscard]]
inline std::string pad_cell(std::string
const&
value, std::size_t
width, output_policy
const& policy)
246 return std::string(
width - value_width,
' ') +
value;
249[[nodiscard]]
inline std::string join_row(std::vector<std::string>
const&
row,
250 std::vector<std::size_t>
const& column_widths, output_policy
const& policy)
253 for (std::size_t col = 0; col <
row.size(); ++col)
255 if (col > 0)
out.push_back(
' ');
256 out += pad_cell(
row[col], column_widths[col], policy);
261[[nodiscard]]
inline std::string matrix_left(std::size_t
row, std::size_t rows, output_policy
const& policy)
263 if (rows <= 1)
return "[";
269[[nodiscard]]
inline std::string matrix_right(std::size_t
row, std::size_t rows, output_policy
const& policy)
271 if (rows <= 1)
return "]";
277[[nodiscard]]
inline std::string format_matrix_rows(std::vector<std::vector<std::string>>
const& rows,
278 output_policy
const& policy)
280 if (
rows.empty())
return "[]";
286 if (columns == 0)
return "[]";
288 std::vector<std::size_t> column_widths(columns, 0);
291 for (std::size_t col = 0; col <
row.size(); ++col)
292 column_widths[col] = std::max(column_widths[col],
display_width(
row[col], policy));
298 if (
row > 0)
out.push_back(
'\n');
301 out += join_row(rows[
row], column_widths, policy);
303 out += matrix_right(
row,
rows.size(), policy);
308[[nodiscard]]
inline std::size_t rendered_max_line_width(std::string_view
text, output_policy
const& policy)
310 std::size_t max_width = 0;
311 std::size_t line_start = 0;
312 while (line_start <=
text.size())
314 auto const line_end =
text.find(
'\n', line_start);
316 line_end == std::string_view::npos ?
text.substr(line_start) :
text.substr(line_start, line_end - line_start);
317 max_width = std::max(max_width,
display_width(line, policy));
318 if (line_end == std::string_view::npos)
break;
319 line_start = line_end + 1;
324[[nodiscard]]
inline bool fits_wrap_width(std::string_view
text, output_policy
const& policy)
326 return !policy.wrap_width.has_value() || rendered_max_line_width(
text, policy) <= *policy.wrap_width;
329[[nodiscard]]
inline std::vector<std::optional<std::size_t>> selected_positions(std::size_t extent,
330 std::size_t edge_items)
332 std::vector<std::optional<std::size_t>> positions;
333 if (extent == 0)
return positions;
335 edge_items = std::max<std::size_t>(edge_items, 1);
336 if (extent <= 2 * edge_items)
338 positions.reserve(extent);
339 for (std::size_t i = 0; i < extent; ++i)
340 positions.push_back(i);
344 positions.reserve(2 * edge_items + 1);
345 for (std::size_t i = 0; i < edge_items; ++i)
346 positions.push_back(i);
347 positions.push_back(std::nullopt);
348 for (std::size_t i = extent - edge_items; i < extent; ++i)
349 positions.push_back(i);
353[[nodiscard]]
inline bool positions_elide(std::vector<std::optional<std::size_t>>
const& positions)
355 return std::any_of(positions.begin(), positions.end(), [](
auto const& position) { return !position.has_value(); });
358template <
typename MDS,
typename ElementFormatter, std::
size_t Rank>
359[[nodiscard]] std::vector<std::string> collect_vector(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>& indices,
360 output_policy
const& policy, ElementFormatter& formatter,
361 std::size_t column_axis)
363 std::vector<std::string>
row;
364 auto const cols =
static_cast<std::size_t
>(mds.extent(column_axis));
366 for (std::size_t col = 0; col < cols; ++col)
368 indices[column_axis] =
static_cast<mdspan_index_type<MDS>
>(col);
369 row.push_back(format_element(mds, indices, policy, formatter));
374template <
typename MDS,
typename ElementFormatter, std::
size_t Rank>
375[[nodiscard]] std::vector<std::string>
376collect_preview_vector(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>& indices, output_policy
const& policy,
377 ElementFormatter& formatter, std::size_t column_axis, std::size_t edge_items,
bool& elided)
379 std::vector<std::string>
row;
380 auto const cols =
static_cast<std::size_t
>(mds.extent(column_axis));
381 auto const positions = selected_positions(cols, edge_items);
382 elided = elided || positions_elide(positions);
383 row.reserve(positions.size());
385 for (
auto const& col : positions)
387 if (!col.has_value())
392 indices[column_axis] =
static_cast<mdspan_index_type<MDS>
>(*col);
393 row.push_back(format_element(mds, indices, policy, formatter));
398template <
typename MDS,
typename ElementFormatter, std::
size_t Rank>
399[[nodiscard]] std::vector<std::vector<std::string>>
400collect_matrix(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>& indices, output_policy
const& policy,
401 ElementFormatter& formatter, resolved_matrix_axes
const& axes)
403 std::vector<std::vector<std::string>>
rows;
404 auto const row_count =
static_cast<std::size_t
>(mds.extent(axes.row));
405 rows.reserve(row_count);
406 for (std::size_t
row = 0;
row < row_count; ++
row)
408 indices[axes.row] =
static_cast<mdspan_index_type<MDS>
>(
row);
409 rows.push_back(collect_vector(mds, indices, policy, formatter, axes.column));
414template <
typename MDS,
typename ElementFormatter, std::
size_t Rank>
415[[nodiscard]] std::vector<std::vector<std::string>>
416collect_preview_matrix(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>& indices, output_policy
const& policy,
417 ElementFormatter& formatter, resolved_matrix_axes
const& axes, std::size_t edge_items,
420 std::vector<std::vector<std::string>>
rows;
421 auto const row_count =
static_cast<std::size_t
>(mds.extent(axes.row));
422 auto const row_positions = selected_positions(row_count, edge_items);
423 auto const col_positions = selected_positions(
static_cast<std::size_t
>(mds.extent(axes.column)), edge_items);
424 elided = elided || positions_elide(row_positions) || positions_elide(col_positions);
425 rows.reserve(row_positions.size());
428 for (
auto const&
row : row_positions)
430 if (!
row.has_value())
432 std::vector<std::string> omitted_row;
433 omitted_row.assign(col_positions.size(),
ellipsis);
434 rows.push_back(std::move(omitted_row));
438 std::vector<std::string> rendered_row;
439 rendered_row.reserve(col_positions.size());
440 indices[axes.row] =
static_cast<mdspan_index_type<MDS>
>(*row);
441 for (
auto const& col : col_positions)
443 if (!col.has_value())
448 indices[axes.column] =
static_cast<mdspan_index_type<MDS>
>(*col);
449 rendered_row.push_back(format_element(mds, indices, policy, formatter));
451 rows.push_back(std::move(rendered_row));
457template <
typename MDS, std::
size_t Rank>
458void apply_slice_linear_index(MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>& indices,
459 resolved_matrix_axes
const& axes, std::size_t linear_index)
461 for (
auto it = axes.slice_axes.rbegin(); it != axes.slice_axes.rend(); ++it)
463 auto const axis = *it;
464 auto const extent =
static_cast<std::size_t
>(mds.extent(axis));
465 if (extent == 0)
return;
466 indices[axis] =
static_cast<mdspan_index_type<MDS>
>(linear_index % extent);
467 linear_index /= extent;
471template <
typename MDS> [[nodiscard]] std::size_t slice_count(MDS
const& mds, resolved_matrix_axes
const& axes)
473 std::vector<std::size_t> extents;
474 extents.reserve(axes.slice_axes.size());
475 for (
auto const axis : axes.slice_axes)
476 extents.push_back(static_cast<
std::size_t>(mds.extent(axis)));
477 return checked_product(extents);
480[[nodiscard]]
inline std::string fit_metadata_piece(std::string piece, output_policy
const& policy)
482 if (!policy.wrap_width.has_value() ||
display_width(piece, policy) <= *policy.wrap_width)
return piece;
486[[nodiscard]]
inline std::string preview_header(std::vector<std::size_t>
const& extents, std::size_t element_count,
487 bool elided, output_policy
const& policy)
489 std::vector<std::string> pieces;
490 pieces.push_back(render_piece(shape_string(extents), policy));
491 pieces.push_back(render_piece(element_count_string(element_count), policy));
492 if (elided) pieces.push_back(render_piece(preview_notice_string(), policy));
494 if (!policy.wrap_width.has_value())
497 for (
auto const& piece : pieces)
499 if (!header.empty()) header.push_back(
' ');
507 for (
auto piece : pieces)
509 piece = fit_metadata_piece(std::move(piece), policy);
512 line = std::move(piece);
516 auto candidate = line +
" " + piece;
519 line = std::move(candidate);
523 if (!header.empty()) header.push_back(
'\n');
525 line = std::move(piece);
530 if (!header.empty()) header.push_back(
'\n');
536template <
typename MDS,
typename ElementFormatter, std::
size_t Rank>
537void append_slices(std::string& out, MDS
const& mds, std::array<mdspan_index_type<MDS>, Rank>& indices,
538 output_policy
const& policy, ElementFormatter& formatter, mdspan_format_options
const& options,
539 resolved_matrix_axes
const& axes, std::size_t slice_pos)
541 if (slice_pos == axes.slice_axes.size())
543 if (!
out.empty())
out +=
"\n\n";
544 if (options.label_slices)
546 out += render_piece(slice_label<MDS>(indices, axes), policy);
549 out += format_matrix_rows(collect_matrix(mds, indices, policy, formatter, axes), policy);
553 auto const axis = axes.slice_axes[slice_pos];
554 auto const extent =
static_cast<std::size_t
>(mds.extent(axis));
555 for (std::size_t i = 0; i < extent; ++i)
557 indices[axis] =
static_cast<mdspan_index_type<MDS>
>(i);
558 append_slices(out, mds, indices, policy, formatter, options, axes, slice_pos + 1);
562template <
typename MDS> [[nodiscard]] std::vector<std::size_t> extents_vector(MDS
const& mds)
564 constexpr std::size_t rank = mdspan_rank_v<MDS>;
565 std::vector<std::size_t> extents;
566 extents.reserve(rank);
567 for (std::size_t dim = 0; dim < rank; ++dim)
568 extents.push_back(
static_cast<std::size_t
>(mds.extent(dim)));
572[[nodiscard]]
inline std::string with_shape_prefix(std::string body,
mdspan_shape_mode shape,
573 std::vector<std::size_t>
const& extents, std::size_t element_count,
574 bool elided, output_policy
const& policy)
578 return preview_header(extents, element_count, elided, policy) +
"\n" + body;
583template <mdspan_like MDS,
typename ElementFormatter>
584 requires(!std::same_as<std::remove_cvref_t<ElementFormatter>, mdspan_format_options>)
588 constexpr std::size_t rank = detail::mdspan_rank_v<MDS>;
589 using index_type = detail::mdspan_index_type<MDS>;
591 auto element_formatter = std::forward<ElementFormatter>(formatter);
593 std::array<index_type, rank> indices{};
595 if constexpr (rank == 0)
597 body = detail::format_element(mds, indices, policy, element_formatter);
599 else if constexpr (rank == 1)
601 std::vector<std::vector<std::string>> rows;
602 rows.push_back(detail::collect_vector(mds, indices, policy, element_formatter, 0));
603 body = detail::format_matrix_rows(rows, policy);
607 auto const axes = detail::resolve_matrix_axes(rank, options);
608 if (axes.slice_axes.empty())
610 body = detail::format_matrix_rows(detail::collect_matrix(mds, indices, policy, element_formatter, axes), policy);
614 detail::append_slices(body, mds, indices, policy, element_formatter, options, axes, 0);
616 if (body.empty()) body =
"[]";
620 return detail::render_piece(detail::shape_string(detail::extents_vector(mds)), policy) +
"\n" + body;
623template <mdspan_like MDS>
630template <mdspan_like MDS,
typename ElementFormatter>
631 requires(!std::same_as<std::remove_cvref_t<ElementFormatter>, mdspan_preview_options>)
635 constexpr std::size_t rank = detail::mdspan_rank_v<MDS>;
636 using index_type = detail::mdspan_index_type<MDS>;
638 auto element_formatter = std::forward<ElementFormatter>(formatter);
639 auto const extents = detail::extents_vector(mds);
640 auto const element_count = detail::checked_product(extents);
642 auto full_options = options.
format;
645 auto full_text =
format_mdspan(mds, policy, element_formatter, full_options);
646 if (detail::fits_wrap_width(full_text, policy))
654 auto build_body = [&](std::size_t edge_items) {
655 std::array<index_type, rank> indices{};
656 bool build_elided =
false;
659 if constexpr (rank == 0)
661 built = detail::format_element(mds, indices, policy, element_formatter);
663 else if constexpr (rank == 1)
665 std::vector<std::vector<std::string>> rows;
667 detail::collect_preview_vector(mds, indices, policy, element_formatter, 0, edge_items, build_elided));
668 built = detail::format_matrix_rows(rows, policy);
672 auto const axes = detail::resolve_matrix_axes(rank, full_options);
673 if (axes.slice_axes.empty())
675 built = detail::format_matrix_rows(
676 detail::collect_preview_matrix(mds, indices, policy, element_formatter, axes, edge_items, build_elided),
681 auto const total_slices = detail::slice_count(mds, axes);
682 auto const slice_positions =
683 detail::selected_positions(total_slices, std::max<std::size_t>(options.
max_slices / 2, 1));
684 build_elided = build_elided || detail::positions_elide(slice_positions);
686 for (
auto const& slice : slice_positions)
688 if (!built.empty()) built +=
"\n\n";
689 if (!slice.has_value())
694 detail::apply_slice_linear_index(mds, indices, axes, *slice);
695 if (full_options.label_slices)
697 built += detail::render_piece(detail::slice_label<MDS>(indices, axes), policy);
698 built.push_back(
'\n');
700 built += detail::format_matrix_rows(
701 detail::collect_preview_matrix(mds, indices, policy, element_formatter, axes, edge_items, build_elided),
704 if (built.empty()) built =
"[]";
708 return std::pair<std::string, bool>{std::move(built), build_elided};
711 auto edge_items = std::max<std::size_t>(options.
edge_items, 1);
712 bool body_elided =
false;
715 auto [candidate_body, candidate_elided] = build_body(edge_items);
717 detail::with_shape_prefix(candidate_body, full_options.shape, extents, element_count, candidate_elided, policy);
718 if (detail::fits_wrap_width(candidate, policy) || edge_items == 1)
720 body = std::move(candidate_body);
721 body_elided = candidate_elided;
727 auto text = detail::with_shape_prefix(body, full_options.shape, extents, element_count, body_elided, policy);
728 if (!detail::fits_wrap_width(
text, policy) && policy.
wrap_width.has_value())
730 text = detail::preview_header(extents, element_count,
true, policy) +
"\n" +
731 truncate_to_width(
"preview omitted: terminal width too narrow for one-cell tensor preview",
739template <mdspan_like MDS>
Detect mdspan-like objects without depending on trace or tensor.
Definition presentation_mdspan.hpp:70
locale_t value
Definition data_table.cpp:49
std::size_t width
Definition display.cpp:142
presentation::styled_text text
Definition display.cpp:141
T product(std::vector< T > const &values)
Definition mpreal_exact_example.cpp:13
int columns(std::FILE *stream)
Return the terminal column count associated with a C stream.
Definition terminal.cpp:493
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
int rows(std::FILE *stream)
Return the terminal row count associated with a C stream.
Definition terminal.cpp:482
auto values(Tensor &tensor)
Definition packed_matrix.hpp:29
Definition data_table.cpp:8
std::string format_mdspan(MDS const &mds, output_policy const &policy, ElementFormatter &&formatter, mdspan_format_options const &options={})
Render every mdspan/tensor element as display-cell-aligned text art.
Definition presentation_mdspan.hpp:585
semantic_glyph
Names semantic glyphs independently of their rendered spelling.
Definition presentation.hpp:117
std::string render_text(std::string_view text, output_policy const &policy)
Render raw UTF-8 text through charset and ASCII fallback policy.
Definition presentation.cpp:1231
std::string render_glyph(semantic_glyph glyph, output_policy const &policy)
Render a semantic glyph according to an output policy.
Definition presentation.cpp:1218
std::string style_nonfinite_scalar(std::string text, T const &value, output_policy const &policy)
Apply the default non-finite scalar style to already-formatted presentation text.
Definition presentation.hpp:977
mdspan_preview_result format_mdspan_preview(MDS const &mds, output_policy const &policy, ElementFormatter &&formatter, mdspan_preview_options const &options={})
Render a bounded mdspan/tensor preview without visiting omitted elements.
Definition presentation_mdspan.hpp:632
mdspan_shape_mode
Selects whether mdspan/tensor art includes shape metadata.
Definition presentation_mdspan.hpp:24
std::string format_scalar(T const &value, numeric_format_options const &options)
Format an arbitrary scalar-like value for presentation output.
Definition presentation.hpp:943
std::string truncate_to_width(std::string_view text, std::size_t max_width, output_policy const &policy, std::string_view marker, std::size_t initial_column)
Truncate rendered text to a target width and optionally append a marker.
Definition presentation.cpp:2635
std::size_t display_width(std::string_view text, output_policy const &policy, std::size_t initial_column)
Measure rendered text width under the selected policy.
Definition presentation.cpp:2560
std::ptrdiff_t index_type
Definition types.hpp:19
std::optional< semantic_glyph > glyph
Definition presentation.cpp:2435
Matrix axes used when rendering rank-2-or-higher tensors.
Definition presentation_mdspan.hpp:31
std::size_t column
Definition presentation_mdspan.hpp:33
std::size_t row
Definition presentation_mdspan.hpp:32
Bounded preview controls for mdspan and tensor display.
Definition presentation_mdspan.hpp:51
std::size_t max_slices
Definition presentation_mdspan.hpp:55
mdspan_format_options format
Definition presentation_mdspan.hpp:52
std::size_t full_element_limit
Definition presentation_mdspan.hpp:53
std::size_t edge_items
Definition presentation_mdspan.hpp:54
Result of bounded mdspan preview rendering.
Definition presentation_mdspan.hpp:60
std::string text
Definition presentation_mdspan.hpp:61
std::vector< std::size_t > extents
Definition presentation_mdspan.hpp:63
std::size_t element_count
Definition presentation_mdspan.hpp:64
bool elided
Definition presentation_mdspan.hpp:62
Output policy shared by presentation renderers and layout helpers.
Definition presentation.hpp:186
std::optional< std::size_t > wrap_width
Definition presentation.hpp:194