[2/3] diagnostics: add support for "text art" diagrams
Checks
Commit Message
Existing text output in GCC has to be implemented by writing
sequentially to a pretty_printer instance. This makes it
hard to implement some kinds of diagnostic output (see e.g.
diagnostic-show-locus.cc).
This patch adds more flexible ways of creating text output:
- a canvas class, which can be "painted" to via random-access (rather
that sequentially)
- a table class for 2D grid layout, supporting items that span
multiple rows/columns
- a widget class for organizing diagrams hierarchically.
The patch also expands GCC's diagnostics subsystem so that diagnostics
can have "text art" diagrams - think ASCII art, but potentially
including some Unicode characters, such as box-drawing chars.
The new code is in a new "gcc/text-art" subdirectory and "text_art"
namespace.
The patch adds a new "-fdiagnostics-text-art-charset=VAL" option, with
values:
- "none": don't emit diagrams (added to -fdiagnostics-plain-output)
- "ascii": use pure ASCII in diagrams
- "unicode": allow for conservative use of unicode drawing characters
(such as box-drawing characters).
- "emoji" (the default): as "unicode", but potentially allow for
conservative use of emoji in the output (such as U+26A0 WARNING SIGN).
I made it possible to disable emoji separately from unicode as I believe
there's a generation gap in acceptance of these characters (some older
programmers have a visceral reaction against them, whereas younger
programmers may have no problem with them).
Diagrams are emitted to stderr by default. With SARIF output they are
captured as a location in "relatedLocations", with the diagram as a
code block in Markdown within a "markdown" property of a message.
This patch doesn't add any such diagram usage to GCC, saving that for
followups, apart from adding a plugin to the test suite to exercise the
functionality.
One issue is that in various places in the seltftests I've embedded
UTF-8 encoded characters in the source code. Is that acceptable, or
do I need to e.g. move those tests to DejaGnu?
contrib/ChangeLog:
* unicode/gen-box-drawing-chars.py: New file.
* unicode/gen-combining-chars.py: New file.
* unicode/gen-printable-chars.py: New file.
gcc/ChangeLog:
* Makefile.in (OBJS-libcommon): Add text-art/box-drawing.o,
text-art/canvas.o, text-art/ruler.o, text-art/selftests.o,
text-art/style.o, text-art/styled-string.o, text-art/table.o,
text-art/theme.o, and text-art/widget.o.
* color-macros.h (COLOR_FG_BRIGHT_BLACK): New.
(COLOR_FG_BRIGHT_RED): New.
(COLOR_FG_BRIGHT_GREEN): New.
(COLOR_FG_BRIGHT_YELLOW): New.
(COLOR_FG_BRIGHT_BLUE): New.
(COLOR_FG_BRIGHT_MAGENTA): New.
(COLOR_FG_BRIGHT_CYAN): New.
(COLOR_FG_BRIGHT_WHITE): New.
(COLOR_BG_BRIGHT_BLACK): New.
(COLOR_BG_BRIGHT_RED): New.
(COLOR_BG_BRIGHT_GREEN): New.
(COLOR_BG_BRIGHT_YELLOW): New.
(COLOR_BG_BRIGHT_BLUE): New.
(COLOR_BG_BRIGHT_MAGENTA): New.
(COLOR_BG_BRIGHT_CYAN): New.
(COLOR_BG_BRIGHT_WHITE): New.
* common.opt (fdiagnostics-text-art-charset=): New option.
(diagnostic-text-art.h): New SourceInclude.
(diagnostic_text_art_charset) New Enum and EnumValues.
* configure: Regenerate.
* configure.ac (gccdepdir): Add text-art to loop.
* diagnostic-diagram.h: New file.
* diagnostic-format-json.cc (json_emit_diagram): New.
(diagnostic_output_format_init_json): Wire it up to
context->m_diagrams.m_emission_cb.
* diagnostic-format-sarif.cc: Include "diagnostic-diagram.h" and
"text-art/canvas.h".
(sarif_result::on_nested_diagnostic): Move code to...
(sarif_result::add_related_location): ...this new function.
(sarif_result::on_diagram): New.
(sarif_builder::emit_diagram): New.
(sarif_builder::make_message_object_for_diagram): New.
(sarif_emit_diagram): New.
(diagnostic_output_format_init_sarif): Set
context->m_diagrams.m_emission_cb to sarif_emit_diagram.
* diagnostic-text-art.h: New file.
* diagnostic.cc: Include "diagnostic-text-art.h",
"diagnostic-diagram.h", and "text-art/theme.h".
(diagnostic_initialize): Initialize context->m_diagrams and
call diagnostics_text_art_charset_init.
(diagnostic_finish): Clean up context->m_diagrams.m_theme.
(diagnostic_emit_diagram): New.
(diagnostics_text_art_charset_init): New.
* diagnostic.h (text_art::theme): New forward decl.
(class diagnostic_diagram): Likewise.
(diagnostic_context::m_diagrams): New field.
(diagnostic_emit_diagram): New decl.
* doc/invoke.texi (Diagnostic Message Formatting Options): Add
-fdiagnostics-text-art-charset=.
(-fdiagnostics-plain-output): Add
-fdiagnostics-text-art-charset=none.
* gcc.cc: Include "diagnostic-text-art.h".
(driver_handle_option): Handle OPT_fdiagnostics_text_art_charset_.
* opts-common.cc (decode_cmdline_options_to_array): Add
"-fdiagnostics-text-art-charset=none" to expanded_args for
-fdiagnostics-plain-output.
* opts.cc: Include "diagnostic-text-art.h".
(common_handle_option): Handle OPT_fdiagnostics_text_art_charset_.
* pretty-print.cc (pp_unicode_character): New.
* pretty-print.h (pp_unicode_character): New decl.
* selftest-run-tests.cc: Include "text-art/selftests.h".
(selftest::run_tests): Call text_art_tests.
* text-art/box-drawing-chars.inc: New file, generated by
contrib/unicode/gen-box-drawing-chars.py.
* text-art/box-drawing.cc: New file.
* text-art/box-drawing.h: New file.
* text-art/canvas.cc: New file.
* text-art/canvas.h: New file.
* text-art/ruler.cc: New file.
* text-art/ruler.h: New file.
* text-art/selftests.cc: New file.
* text-art/selftests.h: New file.
* text-art/style.cc: New file.
* text-art/styled-string.cc: New file.
* text-art/table.cc: New file.
* text-art/table.h: New file.
* text-art/theme.cc: New file.
* text-art/theme.h: New file.
* text-art/types.h: New file.
* text-art/widget.cc: New file.
* text-art/widget.h: New file.
gcc/testsuite/ChangeLog:
* gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c: New test.
* gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c: New test.
* gcc.dg/plugin/diagnostic-test-text-art-none.c: New test.
* gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c: New test.
* gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c: New test.
* gcc.dg/plugin/diagnostic_plugin_test_text_art.c: New test plugin.
* gcc.dg/plugin/plugin.exp (plugin_test_list): Add them.
libcpp/ChangeLog:
* charset.cc (get_cppchar_property): New function template, based
on...
(cpp_wcwidth): ...this function. Rework to use the above.
Include "combining-chars.inc".
(cpp_is_combining_char): New function
Include "printable-chars.inc".
(cpp_is_printable_char): New function
* combining-chars.inc: New file, generated by
contrib/unicode/gen-combining-chars.py.
* include/cpplib.h (cpp_is_combining_char): New function decl.
(cpp_is_printable_char): New function decl.
* printable-chars.inc: New file, generated by
contrib/unicode/gen-printable-chars.py.
---
contrib/unicode/gen-box-drawing-chars.py | 94 ++
contrib/unicode/gen-combining-chars.py | 75 +
contrib/unicode/gen-printable-chars.py | 77 +
gcc/Makefile.in | 11 +-
gcc/color-macros.h | 16 +
gcc/common.opt | 23 +
gcc/configure | 2 +-
gcc/configure.ac | 2 +-
gcc/diagnostic-diagram.h | 51 +
gcc/diagnostic-format-json.cc | 10 +
gcc/diagnostic-format-sarif.cc | 106 +-
gcc/diagnostic-text-art.h | 49 +
gcc/diagnostic.cc | 72 +
gcc/diagnostic.h | 21 +
gcc/doc/invoke.texi | 25 +-
gcc/gcc.cc | 6 +
gcc/opts-common.cc | 1 +
gcc/opts.cc | 6 +
gcc/pretty-print.cc | 29 +
gcc/pretty-print.h | 1 +
gcc/selftest-run-tests.cc | 3 +
.../diagnostic-test-text-art-ascii-bw.c | 57 +
.../diagnostic-test-text-art-ascii-color.c | 58 +
.../plugin/diagnostic-test-text-art-none.c | 5 +
.../diagnostic-test-text-art-unicode-bw.c | 58 +
.../diagnostic-test-text-art-unicode-color.c | 59 +
.../plugin/diagnostic_plugin_test_text_art.c | 257 ++++
gcc/testsuite/gcc.dg/plugin/plugin.exp | 6 +
gcc/text-art/box-drawing-chars.inc | 18 +
gcc/text-art/box-drawing.cc | 72 +
gcc/text-art/box-drawing.h | 32 +
gcc/text-art/canvas.cc | 437 ++++++
gcc/text-art/canvas.h | 74 +
gcc/text-art/ruler.cc | 723 ++++++++++
gcc/text-art/ruler.h | 125 ++
gcc/text-art/selftests.cc | 77 +
gcc/text-art/selftests.h | 60 +
gcc/text-art/style.cc | 632 ++++++++
gcc/text-art/styled-string.cc | 1107 ++++++++++++++
gcc/text-art/table.cc | 1272 +++++++++++++++++
gcc/text-art/table.h | 262 ++++
gcc/text-art/theme.cc | 183 +++
gcc/text-art/theme.h | 123 ++
gcc/text-art/types.h | 504 +++++++
gcc/text-art/widget.cc | 275 ++++
gcc/text-art/widget.h | 246 ++++
libcpp/charset.cc | 89 +-
libcpp/combining-chars.inc | 68 +
libcpp/include/cpplib.h | 3 +
libcpp/printable-chars.inc | 231 +++
50 files changed, 7760 insertions(+), 33 deletions(-)
create mode 100755 contrib/unicode/gen-box-drawing-chars.py
create mode 100755 contrib/unicode/gen-combining-chars.py
create mode 100755 contrib/unicode/gen-printable-chars.py
create mode 100644 gcc/diagnostic-diagram.h
create mode 100644 gcc/diagnostic-text-art.h
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c
create mode 100644 gcc/text-art/box-drawing-chars.inc
create mode 100644 gcc/text-art/box-drawing.cc
create mode 100644 gcc/text-art/box-drawing.h
create mode 100644 gcc/text-art/canvas.cc
create mode 100644 gcc/text-art/canvas.h
create mode 100644 gcc/text-art/ruler.cc
create mode 100644 gcc/text-art/ruler.h
create mode 100644 gcc/text-art/selftests.cc
create mode 100644 gcc/text-art/selftests.h
create mode 100644 gcc/text-art/style.cc
create mode 100644 gcc/text-art/styled-string.cc
create mode 100644 gcc/text-art/table.cc
create mode 100644 gcc/text-art/table.h
create mode 100644 gcc/text-art/theme.cc
create mode 100644 gcc/text-art/theme.h
create mode 100644 gcc/text-art/types.h
create mode 100644 gcc/text-art/widget.cc
create mode 100644 gcc/text-art/widget.h
create mode 100644 libcpp/combining-chars.inc
create mode 100644 libcpp/printable-chars.inc
Comments
Hi David,
On 31/05/2023 14:06, David Malcolm via Gcc-patches wrote:
> Existing text output in GCC has to be implemented by writing
> sequentially to a pretty_printer instance. This makes it
> hard to implement some kinds of diagnostic output (see e.g.
> diagnostic-show-locus.cc).
>
> This patch adds more flexible ways of creating text output:
> - a canvas class, which can be "painted" to via random-access (rather
> that sequentially)
> - a table class for 2D grid layout, supporting items that span
> multiple rows/columns
> - a widget class for organizing diagrams hierarchically.
>
> The patch also expands GCC's diagnostics subsystem so that diagnostics
> can have "text art" diagrams - think ASCII art, but potentially
> including some Unicode characters, such as box-drawing chars.
>
> The new code is in a new "gcc/text-art" subdirectory and "text_art"
> namespace.
It looks like this patch breaks bootstrap on Darwin. I tried a bootstrap on
x86_64-apple-darwin and got errors building selftest-run-tests.cc:
In file included from /Users/alecop01/toolchain/src/gcc/gcc/selftest-run-tests.cc:31:
In file included from /Users/alecop01/toolchain/src/gcc/gcc/text-art/selftests.h:25:
In file included from /Users/alecop01/toolchain/src/gcc/gcc/text-art/types.h:26:
In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/vector:276:
In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__bit_reference:15:
In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/algorithm:653:
In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/memory:670:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/typeinfo:377:5: error: no member named 'fancy_abort' in namespace 'std::__1'; did you mean simply 'fancy_abort'?
_VSTD::abort();
^~~~~~~
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/__config:856:15: note: expanded from macro '_VSTD'
#define _VSTD std::_LIBCPP_ABI_NAMESPACE
^
/Users/alecop01/toolchain/src/gcc/gcc/system.h:811:13: note: 'fancy_abort' declared here
extern void fancy_abort (const char *, int, const char *)
^
Please could you take a look?
Thanks,
Alex
>
> The patch adds a new "-fdiagnostics-text-art-charset=VAL" option, with
> values:
> - "none": don't emit diagrams (added to -fdiagnostics-plain-output)
> - "ascii": use pure ASCII in diagrams
> - "unicode": allow for conservative use of unicode drawing characters
> (such as box-drawing characters).
> - "emoji" (the default): as "unicode", but potentially allow for
> conservative use of emoji in the output (such as U+26A0 WARNING SIGN).
> I made it possible to disable emoji separately from unicode as I believe
> there's a generation gap in acceptance of these characters (some older
> programmers have a visceral reaction against them, whereas younger
> programmers may have no problem with them).
>
> Diagrams are emitted to stderr by default. With SARIF output they are
> captured as a location in "relatedLocations", with the diagram as a
> code block in Markdown within a "markdown" property of a message.
>
> This patch doesn't add any such diagram usage to GCC, saving that for
> followups, apart from adding a plugin to the test suite to exercise the
> functionality.
>
> One issue is that in various places in the seltftests I've embedded
> UTF-8 encoded characters in the source code. Is that acceptable, or
> do I need to e.g. move those tests to DejaGnu?
>
> contrib/ChangeLog:
> * unicode/gen-box-drawing-chars.py: New file.
> * unicode/gen-combining-chars.py: New file.
> * unicode/gen-printable-chars.py: New file.
>
> gcc/ChangeLog:
> * Makefile.in (OBJS-libcommon): Add text-art/box-drawing.o,
> text-art/canvas.o, text-art/ruler.o, text-art/selftests.o,
> text-art/style.o, text-art/styled-string.o, text-art/table.o,
> text-art/theme.o, and text-art/widget.o.
> * color-macros.h (COLOR_FG_BRIGHT_BLACK): New.
> (COLOR_FG_BRIGHT_RED): New.
> (COLOR_FG_BRIGHT_GREEN): New.
> (COLOR_FG_BRIGHT_YELLOW): New.
> (COLOR_FG_BRIGHT_BLUE): New.
> (COLOR_FG_BRIGHT_MAGENTA): New.
> (COLOR_FG_BRIGHT_CYAN): New.
> (COLOR_FG_BRIGHT_WHITE): New.
> (COLOR_BG_BRIGHT_BLACK): New.
> (COLOR_BG_BRIGHT_RED): New.
> (COLOR_BG_BRIGHT_GREEN): New.
> (COLOR_BG_BRIGHT_YELLOW): New.
> (COLOR_BG_BRIGHT_BLUE): New.
> (COLOR_BG_BRIGHT_MAGENTA): New.
> (COLOR_BG_BRIGHT_CYAN): New.
> (COLOR_BG_BRIGHT_WHITE): New.
> * common.opt (fdiagnostics-text-art-charset=): New option.
> (diagnostic-text-art.h): New SourceInclude.
> (diagnostic_text_art_charset) New Enum and EnumValues.
> * configure: Regenerate.
> * configure.ac (gccdepdir): Add text-art to loop.
> * diagnostic-diagram.h: New file.
> * diagnostic-format-json.cc (json_emit_diagram): New.
> (diagnostic_output_format_init_json): Wire it up to
> context->m_diagrams.m_emission_cb.
> * diagnostic-format-sarif.cc: Include "diagnostic-diagram.h" and
> "text-art/canvas.h".
> (sarif_result::on_nested_diagnostic): Move code to...
> (sarif_result::add_related_location): ...this new function.
> (sarif_result::on_diagram): New.
> (sarif_builder::emit_diagram): New.
> (sarif_builder::make_message_object_for_diagram): New.
> (sarif_emit_diagram): New.
> (diagnostic_output_format_init_sarif): Set
> context->m_diagrams.m_emission_cb to sarif_emit_diagram.
> * diagnostic-text-art.h: New file.
> * diagnostic.cc: Include "diagnostic-text-art.h",
> "diagnostic-diagram.h", and "text-art/theme.h".
> (diagnostic_initialize): Initialize context->m_diagrams and
> call diagnostics_text_art_charset_init.
> (diagnostic_finish): Clean up context->m_diagrams.m_theme.
> (diagnostic_emit_diagram): New.
> (diagnostics_text_art_charset_init): New.
> * diagnostic.h (text_art::theme): New forward decl.
> (class diagnostic_diagram): Likewise.
> (diagnostic_context::m_diagrams): New field.
> (diagnostic_emit_diagram): New decl.
> * doc/invoke.texi (Diagnostic Message Formatting Options): Add
> -fdiagnostics-text-art-charset=.
> (-fdiagnostics-plain-output): Add
> -fdiagnostics-text-art-charset=none.
> * gcc.cc: Include "diagnostic-text-art.h".
> (driver_handle_option): Handle OPT_fdiagnostics_text_art_charset_.
> * opts-common.cc (decode_cmdline_options_to_array): Add
> "-fdiagnostics-text-art-charset=none" to expanded_args for
> -fdiagnostics-plain-output.
> * opts.cc: Include "diagnostic-text-art.h".
> (common_handle_option): Handle OPT_fdiagnostics_text_art_charset_.
> * pretty-print.cc (pp_unicode_character): New.
> * pretty-print.h (pp_unicode_character): New decl.
> * selftest-run-tests.cc: Include "text-art/selftests.h".
> (selftest::run_tests): Call text_art_tests.
> * text-art/box-drawing-chars.inc: New file, generated by
> contrib/unicode/gen-box-drawing-chars.py.
> * text-art/box-drawing.cc: New file.
> * text-art/box-drawing.h: New file.
> * text-art/canvas.cc: New file.
> * text-art/canvas.h: New file.
> * text-art/ruler.cc: New file.
> * text-art/ruler.h: New file.
> * text-art/selftests.cc: New file.
> * text-art/selftests.h: New file.
> * text-art/style.cc: New file.
> * text-art/styled-string.cc: New file.
> * text-art/table.cc: New file.
> * text-art/table.h: New file.
> * text-art/theme.cc: New file.
> * text-art/theme.h: New file.
> * text-art/types.h: New file.
> * text-art/widget.cc: New file.
> * text-art/widget.h: New file.
>
> gcc/testsuite/ChangeLog:
> * gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c: New test.
> * gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c: New test.
> * gcc.dg/plugin/diagnostic-test-text-art-none.c: New test.
> * gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c: New test.
> * gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c: New test.
> * gcc.dg/plugin/diagnostic_plugin_test_text_art.c: New test plugin.
> * gcc.dg/plugin/plugin.exp (plugin_test_list): Add them.
>
> libcpp/ChangeLog:
> * charset.cc (get_cppchar_property): New function template, based
> on...
> (cpp_wcwidth): ...this function. Rework to use the above.
> Include "combining-chars.inc".
> (cpp_is_combining_char): New function
> Include "printable-chars.inc".
> (cpp_is_printable_char): New function
> * combining-chars.inc: New file, generated by
> contrib/unicode/gen-combining-chars.py.
> * include/cpplib.h (cpp_is_combining_char): New function decl.
> (cpp_is_printable_char): New function decl.
> * printable-chars.inc: New file, generated by
> contrib/unicode/gen-printable-chars.py.
> ---
> contrib/unicode/gen-box-drawing-chars.py | 94 ++
> contrib/unicode/gen-combining-chars.py | 75 +
> contrib/unicode/gen-printable-chars.py | 77 +
> gcc/Makefile.in | 11 +-
> gcc/color-macros.h | 16 +
> gcc/common.opt | 23 +
> gcc/configure | 2 +-
> gcc/configure.ac | 2 +-
> gcc/diagnostic-diagram.h | 51 +
> gcc/diagnostic-format-json.cc | 10 +
> gcc/diagnostic-format-sarif.cc | 106 +-
> gcc/diagnostic-text-art.h | 49 +
> gcc/diagnostic.cc | 72 +
> gcc/diagnostic.h | 21 +
> gcc/doc/invoke.texi | 25 +-
> gcc/gcc.cc | 6 +
> gcc/opts-common.cc | 1 +
> gcc/opts.cc | 6 +
> gcc/pretty-print.cc | 29 +
> gcc/pretty-print.h | 1 +
> gcc/selftest-run-tests.cc | 3 +
> .../diagnostic-test-text-art-ascii-bw.c | 57 +
> .../diagnostic-test-text-art-ascii-color.c | 58 +
> .../plugin/diagnostic-test-text-art-none.c | 5 +
> .../diagnostic-test-text-art-unicode-bw.c | 58 +
> .../diagnostic-test-text-art-unicode-color.c | 59 +
> .../plugin/diagnostic_plugin_test_text_art.c | 257 ++++
> gcc/testsuite/gcc.dg/plugin/plugin.exp | 6 +
> gcc/text-art/box-drawing-chars.inc | 18 +
> gcc/text-art/box-drawing.cc | 72 +
> gcc/text-art/box-drawing.h | 32 +
> gcc/text-art/canvas.cc | 437 ++++++
> gcc/text-art/canvas.h | 74 +
> gcc/text-art/ruler.cc | 723 ++++++++++
> gcc/text-art/ruler.h | 125 ++
> gcc/text-art/selftests.cc | 77 +
> gcc/text-art/selftests.h | 60 +
> gcc/text-art/style.cc | 632 ++++++++
> gcc/text-art/styled-string.cc | 1107 ++++++++++++++
> gcc/text-art/table.cc | 1272 +++++++++++++++++
> gcc/text-art/table.h | 262 ++++
> gcc/text-art/theme.cc | 183 +++
> gcc/text-art/theme.h | 123 ++
> gcc/text-art/types.h | 504 +++++++
> gcc/text-art/widget.cc | 275 ++++
> gcc/text-art/widget.h | 246 ++++
> libcpp/charset.cc | 89 +-
> libcpp/combining-chars.inc | 68 +
> libcpp/include/cpplib.h | 3 +
> libcpp/printable-chars.inc | 231 +++
> 50 files changed, 7760 insertions(+), 33 deletions(-)
> create mode 100755 contrib/unicode/gen-box-drawing-chars.py
> create mode 100755 contrib/unicode/gen-combining-chars.py
> create mode 100755 contrib/unicode/gen-printable-chars.py
> create mode 100644 gcc/diagnostic-diagram.h
> create mode 100644 gcc/diagnostic-text-art.h
> create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c
> create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c
> create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c
> create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c
> create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c
> create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c
> create mode 100644 gcc/text-art/box-drawing-chars.inc
> create mode 100644 gcc/text-art/box-drawing.cc
> create mode 100644 gcc/text-art/box-drawing.h
> create mode 100644 gcc/text-art/canvas.cc
> create mode 100644 gcc/text-art/canvas.h
> create mode 100644 gcc/text-art/ruler.cc
> create mode 100644 gcc/text-art/ruler.h
> create mode 100644 gcc/text-art/selftests.cc
> create mode 100644 gcc/text-art/selftests.h
> create mode 100644 gcc/text-art/style.cc
> create mode 100644 gcc/text-art/styled-string.cc
> create mode 100644 gcc/text-art/table.cc
> create mode 100644 gcc/text-art/table.h
> create mode 100644 gcc/text-art/theme.cc
> create mode 100644 gcc/text-art/theme.h
> create mode 100644 gcc/text-art/types.h
> create mode 100644 gcc/text-art/widget.cc
> create mode 100644 gcc/text-art/widget.h
> create mode 100644 libcpp/combining-chars.inc
> create mode 100644 libcpp/printable-chars.inc
>
> diff --git a/contrib/unicode/gen-box-drawing-chars.py b/contrib/unicode/gen-box-drawing-chars.py
> new file mode 100755
> index 00000000000..9a55266ab84
> --- /dev/null
> +++ b/contrib/unicode/gen-box-drawing-chars.py
> @@ -0,0 +1,94 @@
> +#!/usr/bin/env python3
> +#
> +# Script to generate gcc/text-art/box-drawing-chars.inc
> +#
> +# This file is part of GCC.
> +#
> +# GCC is free software; you can redistribute it and/or modify it under
> +# the terms of the GNU General Public License as published by the Free
> +# Software Foundation; either version 3, or (at your option) any later
> +# version.
> +#
> +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +# WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +# for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with GCC; see the file COPYING3. If not see
> +# <http://www.gnu.org/licenses/>. */
> +
> +import unicodedata
> +
> +def get_box_drawing_char_name(up: bool,
> + down: bool,
> + left: bool,
> + right: bool) -> str:
> + if 0:
> + print(f'{locals()=}')
> + if up and down:
> + vertical = True
> + up = False
> + down = False
> + else:
> + vertical = False
> +
> + if left and right:
> + horizontal = True
> + left = False
> + right = False
> + else:
> + horizontal = False
> +
> + weights = []
> + heavy = []
> + light = []
> + dirs = []
> + for dir_name in ('up', 'down', 'vertical', 'left', 'right', 'horizontal'):
> + val = locals()[dir_name]
> + if val:
> + dirs.append(dir_name.upper())
> +
> + if not dirs:
> + return 'SPACE'
> +
> + name = 'BOX DRAWINGS'
> + #print(f'{light=} {heavy=}')
> +
> + if 0:
> + print(dirs)
> +
> + def weights_frag(weight: str, dirs: list, prefix: bool):
> + """
> + Generate a fragment where all directions share the same weight, e.g.:
> + 'HEAVY HORIZONTAL'
> + 'DOWN LIGHT'
> + 'LEFT DOWN HEAVY'
> + 'HEAVY DOWN AND RIGHT'
> + """
> + assert len(dirs) >= 1
> + assert len(dirs) <= 2
> + if prefix:
> + return f' {weight} ' + (' AND '.join(dirs))
> + else:
> + return ' ' + (' '.join(dirs)) + f' {weight}'
> +
> + assert(len(dirs) >= 1 and len(dirs) <= 2)
> + name += weights_frag('LIGHT', dirs, True)
> +
> + return name
> +
> +print('/* Generated by contrib/unicode/gen-box-drawing-chars.py. */')
> +print()
> +for i in range(16):
> + up = (i & 8)
> + down = (i & 4)
> + left = (i & 2)
> + right = (i & 1)
> + name = get_box_drawing_char_name(up, down, left, right)
> + if i < 15:
> + trailing_comma = ','
> + else:
> + trailing_comma = ' '
> + unichar = unicodedata.lookup(name)
> + print(f'0x{ord(unichar):04X}{trailing_comma} /* "{unichar}": U+{ord(unichar):04X}: {name} */')
> diff --git a/contrib/unicode/gen-combining-chars.py b/contrib/unicode/gen-combining-chars.py
> new file mode 100755
> index 00000000000..fb5ef50ba4c
> --- /dev/null
> +++ b/contrib/unicode/gen-combining-chars.py
> @@ -0,0 +1,75 @@
> +#!/usr/bin/env python3
> +#
> +# Script to generate libcpp/combining-chars.inc
> +#
> +# This file is part of GCC.
> +#
> +# GCC is free software; you can redistribute it and/or modify it under
> +# the terms of the GNU General Public License as published by the Free
> +# Software Foundation; either version 3, or (at your option) any later
> +# version.
> +#
> +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +# WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +# for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with GCC; see the file COPYING3. If not see
> +# <http://www.gnu.org/licenses/>. */
> +
> +from pprint import pprint
> +import unicodedata
> +
> +def is_combining_char(code_point) -> bool:
> + return unicodedata.combining(chr(code_point)) != 0
> +
> +class Range:
> + def __init__(self, start, end, value):
> + self.start = start
> + self.end = end
> + self.value = value
> +
> + def __repr__(self):
> + return f'Range({self.start:x}, {self.end:x}, {self.value})'
> +
> +def make_ranges(value_callback):
> + ranges = []
> + for code_point in range(0x10FFFF):
> + value = is_combining_char(code_point)
> + if 0:
> + print(f'{code_point=:x} {value=}')
> + if ranges and ranges[-1].value == value:
> + # Extend current range
> + ranges[-1].end = code_point
> + else:
> + # Start a new range
> + ranges.append(Range(code_point, code_point, value))
> + return ranges
> +
> +ranges = make_ranges(is_combining_char)
> +if 0:
> + pprint(ranges)
> +
> +print(f"/* Generated by contrib/unicode/gen-combining-chars.py")
> +print(f" using version {unicodedata.unidata_version}"
> + " of the Unicode standard. */")
> +print("\nstatic const cppchar_t combining_range_ends[] = {", end="")
> +for i, r in enumerate(ranges):
> + if i % 8:
> + print(" ", end="")
> + else:
> + print("\n ", end="")
> + print("0x%x," % r.end, end="")
> +print("\n};\n")
> +print("static const bool is_combining[] = {", end="")
> +for i, r in enumerate(ranges):
> + if i % 24:
> + print(" ", end="")
> + else:
> + print("\n ", end="")
> + if r.value:
> + print("1,", end="")
> + else:
> + print("0,", end="")
> +print("\n};")
> diff --git a/contrib/unicode/gen-printable-chars.py b/contrib/unicode/gen-printable-chars.py
> new file mode 100755
> index 00000000000..7684c086638
> --- /dev/null
> +++ b/contrib/unicode/gen-printable-chars.py
> @@ -0,0 +1,77 @@
> +#!/usr/bin/env python3
> +#
> +# Script to generate libcpp/printable-chars.inc
> +#
> +# This file is part of GCC.
> +#
> +# GCC is free software; you can redistribute it and/or modify it under
> +# the terms of the GNU General Public License as published by the Free
> +# Software Foundation; either version 3, or (at your option) any later
> +# version.
> +#
> +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +# WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +# for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with GCC; see the file COPYING3. If not see
> +# <http://www.gnu.org/licenses/>. */
> +
> +from pprint import pprint
> +import unicodedata
> +
> +def is_printable_char(code_point) -> bool:
> + category = unicodedata.category(chr(code_point))
> + # "Cc" is "control" and "Cf" is "format"
> + return category[0] != 'C'
> +
> +class Range:
> + def __init__(self, start, end, value):
> + self.start = start
> + self.end = end
> + self.value = value
> +
> + def __repr__(self):
> + return f'Range({self.start:x}, {self.end:x}, {self.value})'
> +
> +def make_ranges(value_callback):
> + ranges = []
> + for code_point in range(0x10FFFF):
> + value = is_printable_char(code_point)
> + if 0:
> + print(f'{code_point=:x} {value=}')
> + if ranges and ranges[-1].value == value:
> + # Extend current range
> + ranges[-1].end = code_point
> + else:
> + # Start a new range
> + ranges.append(Range(code_point, code_point, value))
> + return ranges
> +
> +ranges = make_ranges(is_printable_char)
> +if 0:
> + pprint(ranges)
> +
> +print(f"/* Generated by contrib/unicode/gen-printable-chars.py")
> +print(f" using version {unicodedata.unidata_version}"
> + " of the Unicode standard. */")
> +print("\nstatic const cppchar_t printable_range_ends[] = {", end="")
> +for i, r in enumerate(ranges):
> + if i % 8:
> + print(" ", end="")
> + else:
> + print("\n ", end="")
> + print("0x%x," % r.end, end="")
> +print("\n};\n")
> +print("static const bool is_printable[] = {", end="")
> +for i, r in enumerate(ranges):
> + if i % 24:
> + print(" ", end="")
> + else:
> + print("\n ", end="")
> + if r.value:
> + print("1,", end="")
> + else:
> + print("0,", end="")
> +print("\n};")
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index 1d39e6dd3f8..c1e7257ed24 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1781,7 +1781,16 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
> json.o \
> sbitmap.o \
> vec.o input.o hash-table.o ggc-none.o memory-block.o \
> - selftest.o selftest-diagnostic.o sort.o
> + selftest.o selftest-diagnostic.o sort.o \
> + text-art/box-drawing.o \
> + text-art/canvas.o \
> + text-art/ruler.o \
> + text-art/selftests.o \
> + text-art/style.o \
> + text-art/styled-string.o \
> + text-art/table.o \
> + text-art/theme.o \
> + text-art/widget.o
>
> # Objects in libcommon-target.a, used by drivers and by the core
> # compiler and containing target-dependent code.
> diff --git a/gcc/color-macros.h b/gcc/color-macros.h
> index fcd79d09c01..9688f92110a 100644
> --- a/gcc/color-macros.h
> +++ b/gcc/color-macros.h
> @@ -92,6 +92,14 @@ along with GCC; see the file COPYING3. If not see
> #define COLOR_FG_MAGENTA "35"
> #define COLOR_FG_CYAN "36"
> #define COLOR_FG_WHITE "37"
> +#define COLOR_FG_BRIGHT_BLACK "90"
> +#define COLOR_FG_BRIGHT_RED "91"
> +#define COLOR_FG_BRIGHT_GREEN "92"
> +#define COLOR_FG_BRIGHT_YELLOW "93"
> +#define COLOR_FG_BRIGHT_BLUE "94"
> +#define COLOR_FG_BRIGHT_MAGENTA "95"
> +#define COLOR_FG_BRIGHT_CYAN "96"
> +#define COLOR_FG_BRIGHT_WHITE "97"
> #define COLOR_BG_BLACK "40"
> #define COLOR_BG_RED "41"
> #define COLOR_BG_GREEN "42"
> @@ -100,6 +108,14 @@ along with GCC; see the file COPYING3. If not see
> #define COLOR_BG_MAGENTA "45"
> #define COLOR_BG_CYAN "46"
> #define COLOR_BG_WHITE "47"
> +#define COLOR_BG_BRIGHT_BLACK "100"
> +#define COLOR_BG_BRIGHT_RED "101"
> +#define COLOR_BG_BRIGHT_GREEN "102"
> +#define COLOR_BG_BRIGHT_YELLOW "103"
> +#define COLOR_BG_BRIGHT_BLUE "104"
> +#define COLOR_BG_BRIGHT_MAGENTA "105"
> +#define COLOR_BG_BRIGHT_CYAN "106"
> +#define COLOR_BG_BRIGHT_WHITE "107"
> #define SGR_START "\33["
> #define SGR_END "m\33[K"
> #define SGR_SEQ(str) SGR_START str SGR_END
> diff --git a/gcc/common.opt b/gcc/common.opt
> index a28ca13385a..b3c82b8607c 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -1502,6 +1502,29 @@ fdiagnostics-show-path-depths
> Common Var(flag_diagnostics_show_path_depths) Init(0)
> Show stack depths of events in paths.
>
> +fdiagnostics-text-art-charset=
> +Driver Common Joined RejectNegative Var(flag_diagnostics_text_art_charset) Enum(diagnostic_text_art_charset) Init(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI)
> +-fdiagnostics-text-art-charset=[none|ascii|unicode|emoji] Determine which characters to use in text arg diagrams.
> +
> +; Required for these enum values.
> +SourceInclude
> +diagnostic-text-art.h
> +
> +Enum
> +Name(diagnostic_text_art_charset) Type(int)
> +
> +EnumValue
> +Enum(diagnostic_text_art_charset) String(none) Value(DIAGNOSTICS_TEXT_ART_CHARSET_NONE)
> +
> +EnumValue
> +Enum(diagnostic_text_art_charset) String(ascii) Value(DIAGNOSTICS_TEXT_ART_CHARSET_ASCII)
> +
> +EnumValue
> +Enum(diagnostic_text_art_charset) String(unicode) Value(DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE)
> +
> +EnumValue
> +Enum(diagnostic_text_art_charset) String(emoji) Value(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI)
> +
> fdiagnostics-minimum-margin-width=
> Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
> Set minimum width of left margin of source code when showing source.
> diff --git a/gcc/configure b/gcc/configure
> index 5f67808b774..e061d2b1949 100755
> --- a/gcc/configure
> +++ b/gcc/configure
> @@ -33995,7 +33995,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
> "depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;;
> "gccdepdir":C)
> ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
> - for lang in $subdirs c-family common analyzer rtl-ssa
> + for lang in $subdirs c-family common analyzer text-art rtl-ssa
> do
> ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
> done ;;
> diff --git a/gcc/configure.ac b/gcc/configure.ac
> index cc8dd9e20bf..350d245c89f 100644
> --- a/gcc/configure.ac
> +++ b/gcc/configure.ac
> @@ -1384,7 +1384,7 @@ AC_CHECK_HEADERS(ext/hash_map)
> ZW_CREATE_DEPDIR
> AC_CONFIG_COMMANDS([gccdepdir],[
> ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
> - for lang in $subdirs c-family common analyzer rtl-ssa
> + for lang in $subdirs c-family common analyzer text-art rtl-ssa
> do
> ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
> done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR])
> diff --git a/gcc/diagnostic-diagram.h b/gcc/diagnostic-diagram.h
> new file mode 100644
> index 00000000000..fc923c512ed
> --- /dev/null
> +++ b/gcc/diagnostic-diagram.h
> @@ -0,0 +1,51 @@
> +/* Support for diagrams within diagnostics.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_DIAGNOSTIC_DIAGRAM_H
> +#define GCC_DIAGNOSTIC_DIAGRAM_H
> +
> +namespace text_art
> +{
> + class canvas;
> +} // namespace text_art
> +
> +/* A text art diagram, along with an "alternative text" string
> + describing it. */
> +
> +class diagnostic_diagram
> +{
> + public:
> + diagnostic_diagram (const text_art::canvas &canvas,
> + const char *alt_text)
> + : m_canvas (canvas),
> + m_alt_text (alt_text)
> + {
> + gcc_assert (alt_text);
> + }
> +
> + const text_art::canvas &get_canvas () const { return m_canvas; }
> + const char *get_alt_text () const { return m_alt_text; }
> +
> + private:
> + const text_art::canvas &m_canvas;
> + const char *const m_alt_text;
> +};
> +
> +#endif /* ! GCC_DIAGNOSTIC_DIAGRAM_H */
> diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
> index 694dddca9e8..539b98b5e74 100644
> --- a/gcc/diagnostic-format-json.cc
> +++ b/gcc/diagnostic-format-json.cc
> @@ -324,6 +324,15 @@ json_file_final_cb (diagnostic_context *)
> free (filename);
> }
>
> +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */
> +
> +static void
> +json_emit_diagram (diagnostic_context *,
> + const diagnostic_diagram &)
> +{
> + /* No-op. */
> +}
> +
> /* Populate CONTEXT in preparation for JSON output (either to stderr, or
> to a file). */
>
> @@ -340,6 +349,7 @@ diagnostic_output_format_init_json (diagnostic_context *context)
> context->begin_group_cb = json_begin_group;
> context->end_group_cb = json_end_group;
> context->print_path = NULL; /* handled in json_end_diagnostic. */
> + context->m_diagrams.m_emission_cb = json_emit_diagram;
>
> /* The metadata is handled in JSON format, rather than as text. */
> context->show_cwe = false;
> diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
> index fd29ac2ca3b..ac2f5b844e3 100644
> --- a/gcc/diagnostic-format-sarif.cc
> +++ b/gcc/diagnostic-format-sarif.cc
> @@ -29,6 +29,8 @@ along with GCC; see the file COPYING3. If not see
> #include "cpplib.h"
> #include "logical-location.h"
> #include "diagnostic-client-data-hooks.h"
> +#include "diagnostic-diagram.h"
> +#include "text-art/canvas.h"
>
> class sarif_builder;
>
> @@ -66,8 +68,13 @@ public:
> diagnostic_info *diagnostic,
> diagnostic_t orig_diag_kind,
> sarif_builder *builder);
> + void on_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram,
> + sarif_builder *builder);
>
> private:
> + void add_related_location (json::object *location_obj);
> +
> json::array *m_related_locations_arr;
> };
>
> @@ -135,7 +142,8 @@ public:
>
> void end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
> diagnostic_t orig_diag_kind);
> -
> + void emit_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram);
> void end_group ();
>
> void flush_to_file (FILE *outf);
> @@ -144,6 +152,9 @@ public:
> json::object *make_location_object (const rich_location &rich_loc,
> const logical_location *logical_loc);
> json::object *make_message_object (const char *msg) const;
> + json::object *
> + make_message_object_for_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram);
>
> private:
> sarif_result *make_result_object (diagnostic_context *context,
> @@ -261,12 +272,6 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context,
> diagnostic_t /*orig_diag_kind*/,
> sarif_builder *builder)
> {
> - if (!m_related_locations_arr)
> - {
> - m_related_locations_arr = new json::array ();
> - set ("relatedLocations", m_related_locations_arr);
> - }
> -
> /* We don't yet generate meaningful logical locations for notes;
> sometimes these will related to current_function_decl, but
> often they won't. */
> @@ -277,6 +282,39 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context,
> pp_clear_output_area (context->printer);
> location_obj->set ("message", message_obj);
>
> + add_related_location (location_obj);
> +}
> +
> +/* Handle diagrams that occur within a diagnostic group.
> + The closest thing in SARIF seems to be to add a location to the
> + "releatedLocations" property (SARIF v2.1.0 section 3.27.22),
> + and to put the diagram into the "message" property of that location
> + (SARIF v2.1.0 section 3.28.5). */
> +
> +void
> +sarif_result::on_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram,
> + sarif_builder *builder)
> +{
> + json::object *location_obj = new json::object ();
> + json::object *message_obj
> + = builder->make_message_object_for_diagram (context, diagram);
> + location_obj->set ("message", message_obj);
> +
> + add_related_location (location_obj);
> +}
> +
> +/* Add LOCATION_OBJ to this result's "relatedLocations" array,
> + creating it if it doesn't yet exist. */
> +
> +void
> +sarif_result::add_related_location (json::object *location_obj)
> +{
> + if (!m_related_locations_arr)
> + {
> + m_related_locations_arr = new json::array ();
> + set ("relatedLocations", m_related_locations_arr);
> + }
> m_related_locations_arr->append (location_obj);
> }
>
> @@ -348,6 +386,18 @@ sarif_builder::end_diagnostic (diagnostic_context *context,
> }
> }
>
> +/* Implementation of diagnostic_context::m_diagrams.m_emission_cb
> + for SARIF output. */
> +
> +void
> +sarif_builder::emit_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram)
> +{
> + /* We must be within the emission of a top-level diagnostic. */
> + gcc_assert (m_cur_group_result);
> + m_cur_group_result->on_diagram (context, diagram, this);
> +}
> +
> /* Implementation of "end_group_cb" for SARIF output. */
>
> void
> @@ -1115,6 +1165,37 @@ sarif_builder::make_message_object (const char *msg) const
> return message_obj;
> }
>
> +/* Make a message object (SARIF v2.1.0 section 3.11) for DIAGRAM.
> + We emit the diagram as a code block within the Markdown part
> + of the message. */
> +
> +json::object *
> +sarif_builder::make_message_object_for_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram)
> +{
> + json::object *message_obj = new json::object ();
> +
> + /* "text" property (SARIF v2.1.0 section 3.11.8). */
> + message_obj->set ("text", new json::string (diagram.get_alt_text ()));
> +
> + char *saved_prefix = pp_take_prefix (context->printer);
> + pp_set_prefix (context->printer, NULL);
> +
> + /* "To produce a code block in Markdown, simply indent every line of
> + the block by at least 4 spaces or 1 tab."
> + Here we use 4 spaces. */
> + diagram.get_canvas ().print_to_pp (context->printer, " ");
> + pp_set_prefix (context->printer, saved_prefix);
> +
> + /* "markdown" property (SARIF v2.1.0 section 3.11.9). */
> + message_obj->set ("markdown",
> + new json::string (pp_formatted_text (context->printer)));
> +
> + pp_clear_output_area (context->printer);
> +
> + return message_obj;
> +}
> +
> /* Make a multiformatMessageString object (SARIF v2.1.0 section 3.12)
> for MSG. */
>
> @@ -1630,6 +1711,16 @@ sarif_ice_handler (diagnostic_context *context)
> fnotice (stderr, "Internal compiler error:\n");
> }
>
> +/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */
> +
> +static void
> +sarif_emit_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram)
> +{
> + gcc_assert (the_builder);
> + the_builder->emit_diagram (context, diagram);
> +}
> +
> /* Populate CONTEXT in preparation for SARIF output (either to stderr, or
> to a file). */
>
> @@ -1645,6 +1736,7 @@ diagnostic_output_format_init_sarif (diagnostic_context *context)
> context->end_group_cb = sarif_end_group;
> context->print_path = NULL; /* handled in sarif_end_diagnostic. */
> context->ice_handler_cb = sarif_ice_handler;
> + context->m_diagrams.m_emission_cb = sarif_emit_diagram;
>
> /* The metadata is handled in SARIF format, rather than as text. */
> context->show_cwe = false;
> diff --git a/gcc/diagnostic-text-art.h b/gcc/diagnostic-text-art.h
> new file mode 100644
> index 00000000000..a0d8a78f52a
> --- /dev/null
> +++ b/gcc/diagnostic-text-art.h
> @@ -0,0 +1,49 @@
> +/* Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_DIAGNOSTIC_TEXT_ART_H
> +#define GCC_DIAGNOSTIC_TEXT_ART_H
> +
> +/* Values for -fdiagnostics-text-art-charset=. */
> +
> +enum diagnostic_text_art_charset
> +{
> + /* No text art diagrams shall be emitted. */
> + DIAGNOSTICS_TEXT_ART_CHARSET_NONE,
> +
> + /* Use pure ASCII for text art diagrams. */
> + DIAGNOSTICS_TEXT_ART_CHARSET_ASCII,
> +
> + /* Use ASCII + conservative use of other unicode characters
> + in text art diagrams. */
> + DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE,
> +
> + /* Use Emoji. */
> + DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI
> +};
> +
> +const enum diagnostic_text_art_charset DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT
> + = DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI;
> +
> +extern void
> +diagnostics_text_art_charset_init (diagnostic_context *context,
> + enum diagnostic_text_art_charset charset);
> +
> +
> +#endif /* ! GCC_DIAGNOSTIC_TEXT_ART_H */
> diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
> index 0f093081161..7c2289f0634 100644
> --- a/gcc/diagnostic.cc
> +++ b/gcc/diagnostic.cc
> @@ -35,11 +35,14 @@ along with GCC; see the file COPYING3. If not see
> #include "diagnostic-metadata.h"
> #include "diagnostic-path.h"
> #include "diagnostic-client-data-hooks.h"
> +#include "diagnostic-text-art.h"
> +#include "diagnostic-diagram.h"
> #include "edit-context.h"
> #include "selftest.h"
> #include "selftest-diagnostic.h"
> #include "opts.h"
> #include "cpplib.h"
> +#include "text-art/theme.h"
>
> #ifdef HAVE_TERMIOS_H
> # include <termios.h>
> @@ -244,6 +247,10 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
> context->ice_handler_cb = NULL;
> context->includes_seen = NULL;
> context->m_client_data_hooks = NULL;
> + context->m_diagrams.m_theme = NULL;
> + context->m_diagrams.m_emission_cb = NULL;
> + diagnostics_text_art_charset_init (context,
> + DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT);
> }
>
> /* Maybe initialize the color support. We require clients to do this
> @@ -320,6 +327,12 @@ diagnostic_finish (diagnostic_context *context)
> if (context->final_cb)
> context->final_cb (context);
>
> + if (context->m_diagrams.m_theme)
> + {
> + delete context->m_diagrams.m_theme;
> + context->m_diagrams.m_theme = NULL;
> + }
> +
> diagnostic_file_cache_fini ();
>
> XDELETEVEC (context->classify_diagnostic);
> @@ -2174,6 +2187,33 @@ internal_error_no_backtrace (const char *gmsgid, ...)
>
> gcc_unreachable ();
> }
> +
> +/* Emit DIAGRAM to CONTEXT, respecting the output format. */
> +
> +void
> +diagnostic_emit_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram)
> +{
> + if (context->m_diagrams.m_theme == nullptr)
> + return;
> +
> + if (context->m_diagrams.m_emission_cb)
> + {
> + context->m_diagrams.m_emission_cb (context, diagram);
> + return;
> + }
> +
> + /* Default implementation. */
> + char *saved_prefix = pp_take_prefix (context->printer);
> + pp_set_prefix (context->printer, NULL);
> + /* Use a newline before and after and a two-space indent
> + to make the diagram stand out a little from the wall of text. */
> + pp_newline (context->printer);
> + diagram.get_canvas ().print_to_pp (context->printer, " ");
> + pp_newline (context->printer);
> + pp_set_prefix (context->printer, saved_prefix);
> + pp_flush (context->printer);
> +}
>
> /* Special case error functions. Most are implemented in terms of the
> above, or should be. */
> @@ -2316,6 +2356,38 @@ diagnostic_output_format_init (diagnostic_context *context,
> }
> }
>
> +/* Initialize CONTEXT->m_diagrams based on CHARSET.
> + Specifically, make a text_art::theme object for m_diagrams.m_theme,
> + (or NULL for "no diagrams"). */
> +
> +void
> +diagnostics_text_art_charset_init (diagnostic_context *context,
> + enum diagnostic_text_art_charset charset)
> +{
> + delete context->m_diagrams.m_theme;
> + switch (charset)
> + {
> + default:
> + gcc_unreachable ();
> +
> + case DIAGNOSTICS_TEXT_ART_CHARSET_NONE:
> + context->m_diagrams.m_theme = NULL;
> + break;
> +
> + case DIAGNOSTICS_TEXT_ART_CHARSET_ASCII:
> + context->m_diagrams.m_theme = new text_art::ascii_theme ();
> + break;
> +
> + case DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE:
> + context->m_diagrams.m_theme = new text_art::unicode_theme ();
> + break;
> +
> + case DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI:
> + context->m_diagrams.m_theme = new text_art::emoji_theme ();
> + break;
> + }
> +}
> +
> /* Implementation of diagnostic_path::num_events vfunc for
> simple_diagnostic_path: simply get the number of events in the vec. */
>
> diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
> index 9a51097f146..00b828f230d 100644
> --- a/gcc/diagnostic.h
> +++ b/gcc/diagnostic.h
> @@ -24,6 +24,11 @@ along with GCC; see the file COPYING3. If not see
> #include "pretty-print.h"
> #include "diagnostic-core.h"
>
> +namespace text_art
> +{
> + class theme;
> +} // namespace text_art
> +
> /* An enum for controlling what units to use for the column number
> when diagnostics are output, used by the -fdiagnostics-column-unit option.
> Tabs will be expanded or not according to the value of -ftabstop. The origin
> @@ -170,6 +175,7 @@ class edit_context;
> namespace json { class value; }
> class diagnostic_client_data_hooks;
> class logical_location;
> +class diagnostic_diagram;
>
> /* This data structure bundles altogether any information relevant to
> the context of a diagnostic message. */
> @@ -417,6 +423,18 @@ struct diagnostic_context
> Used by SARIF output to give metadata about the client that's
> producing diagnostics. */
> diagnostic_client_data_hooks *m_client_data_hooks;
> +
> + /* Support for diagrams. */
> + struct
> + {
> + /* Theme to use when generating diagrams.
> + Can be NULL (if text art is disabled). */
> + text_art::theme *m_theme;
> +
> + /* Callback for emitting diagrams. */
> + void (*m_emission_cb) (diagnostic_context *context,
> + const diagnostic_diagram &diagram);
> + } m_diagrams;
> };
>
> inline void
> @@ -619,4 +637,7 @@ extern bool warning_enabled_at (location_t, int);
>
> extern char *get_cwe_url (int cwe);
>
> +extern void diagnostic_emit_diagram (diagnostic_context *context,
> + const diagnostic_diagram &diagram);
> +
> #endif /* ! GCC_DIAGNOSTIC_H */
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 898a88ce33e..023a56a647e 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -316,7 +316,8 @@ Objective-C and Objective-C++ Dialects}.
> -fno-show-column
> -fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]}
> -fdiagnostics-column-origin=@var{origin}
> --fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}}
> +-fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}
> +-fdiagnostics-text-art-charset=@r{[}none@r{|}ascii@r{|}unicode@r{|}emoji@r{]}}
>
> @item Warning Options
> @xref{Warning Options,,Options to Request or Suppress Warnings}.
> @@ -5066,7 +5067,8 @@ options:
> -fno-diagnostics-show-line-numbers
> -fdiagnostics-color=never
> -fdiagnostics-urls=never
> --fdiagnostics-path-format=separate-events}
> +-fdiagnostics-path-format=separate-events
> +-fdiagnostics-text-art-charset=none}
> In the future, if GCC changes the default appearance of its diagnostics, the
> corresponding option to disable the new behavior will be added to this list.
>
> @@ -5592,6 +5594,25 @@ Unicode characters. For the example above, the following will be printed:
> before<CF><80><BF>after
> @end smallexample
>
> +@opindex fdiagnostics-text-art-charset
> +@item -fdiagnostics-text-art-charset=@var{CHARSET}
> +Some diagnostics can contain ``text art'' diagrams: visualizations created
> +from text, intended to be viewed in a monospaced font.
> +
> +This option selects which characters should be used for printing such
> +diagrams, if any. @var{CHARSET} is @samp{none}, @samp{ascii}, @samp{unicode},
> +or @samp{emoji}.
> +
> +The @samp{none} value suppresses the printing of such diagrams.
> +The @samp{ascii} value will ensure that such diagrams are pure ASCII
> +(``ASCII art''). The @samp{unicode} value will allow for conservative use of
> +unicode drawing characters (such as box-drawing characters). The @samp{emoji}
> +value further adds the possibility of emoji in the output (such as emitting
> +U+26A0 WARNING SIGN followed by U+FE0F VARIATION SELECTOR-16 to select the
> +emoji variant of the character).
> +
> +The default is @samp{emoji}.
> +
> @opindex fdiagnostics-format
> @item -fdiagnostics-format=@var{FORMAT}
> Select a different format for printing diagnostics.
> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
> index 2ccca00d603..f9f0a7eaad4 100644
> --- a/gcc/gcc.cc
> +++ b/gcc/gcc.cc
> @@ -46,6 +46,7 @@ compilation is specified by a string called a "spec". */
> #include "spellcheck.h"
> #include "opts-jobserver.h"
> #include "common/common-target.h"
> +#include "diagnostic-text-art.h"
>
>
>
> @@ -4299,6 +4300,11 @@ driver_handle_option (struct gcc_options *opts,
> break;
> }
>
> + case OPT_fdiagnostics_text_art_charset_:
> + diagnostics_text_art_charset_init (dc,
> + (enum diagnostic_text_art_charset)value);
> + break;
> +
> case OPT_Wa_:
> {
> int prev, j;
> diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc
> index 23ddcaa3b55..f0c5f483665 100644
> --- a/gcc/opts-common.cc
> +++ b/gcc/opts-common.cc
> @@ -1068,6 +1068,7 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv,
> "-fdiagnostics-color=never",
> "-fdiagnostics-urls=never",
> "-fdiagnostics-path-format=separate-events",
> + "-fdiagnostics-text-art-charset=none"
> };
> const int num_expanded = ARRAY_SIZE (expanded_args);
> opt_array_len += num_expanded - 1;
> diff --git a/gcc/opts.cc b/gcc/opts.cc
> index 86b94d62b58..3087bdac2c6 100644
> --- a/gcc/opts.cc
> +++ b/gcc/opts.cc
> @@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see
> #include "version.h"
> #include "selftest.h"
> #include "file-prefix-map.h"
> +#include "diagnostic-text-art.h"
>
> /* In this file all option sets are explicit. */
> #undef OPTION_SET_P
> @@ -2887,6 +2888,11 @@ common_handle_option (struct gcc_options *opts,
> break;
> }
>
> + case OPT_fdiagnostics_text_art_charset_:
> + diagnostics_text_art_charset_init (dc,
> + (enum diagnostic_text_art_charset)value);
> + break;
> +
> case OPT_fdiagnostics_parseable_fixits:
> dc->extra_output_kind = (value
> ? EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1
> diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc
> index 7d294717f50..3d789a23812 100644
> --- a/gcc/pretty-print.cc
> +++ b/gcc/pretty-print.cc
> @@ -1828,6 +1828,35 @@ pp_string (pretty_printer *pp, const char *str)
> pp_maybe_wrap_text (pp, str, str + strlen (str));
> }
>
> +/* Append code point C to the output area of PRETTY-PRINTER, encoding it
> + as UTF-8. */
> +
> +void
> +pp_unicode_character (pretty_printer *pp, unsigned c)
> +{
> + static const uchar masks[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
> + static const uchar limits[6] = { 0x80, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE };
> + size_t nbytes;
> + uchar buf[6], *p = &buf[6];
> +
> + nbytes = 1;
> + if (c < 0x80)
> + *--p = c;
> + else
> + {
> + do
> + {
> + *--p = ((c & 0x3F) | 0x80);
> + c >>= 6;
> + nbytes++;
> + }
> + while (c >= 0x3F || (c & limits[nbytes-1]));
> + *--p = (c | masks[nbytes-1]);
> + }
> +
> + pp_append_r (pp, (const char *)p, nbytes);
> +}
> +
> /* Append the leading N characters of STRING to the output area of
> PRETTY-PRINTER, quoting in hexadecimal non-printable characters.
> Setting N = -1 is as if N were set to strlen (STRING). The STRING
> diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
> index 0230a289df5..369be6e7ba7 100644
> --- a/gcc/pretty-print.h
> +++ b/gcc/pretty-print.h
> @@ -401,6 +401,7 @@ extern void pp_indent (pretty_printer *);
> extern void pp_newline (pretty_printer *);
> extern void pp_character (pretty_printer *, int);
> extern void pp_string (pretty_printer *, const char *);
> +extern void pp_unicode_character (pretty_printer *, unsigned);
>
> extern void pp_write_text_to_stream (pretty_printer *);
> extern void pp_write_text_as_dot_label_to_stream (pretty_printer *, bool);
> diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc
> index 915f2129702..e2fc8f84b1b 100644
> --- a/gcc/selftest-run-tests.cc
> +++ b/gcc/selftest-run-tests.cc
> @@ -28,6 +28,7 @@ along with GCC; see the file COPYING3. If not see
> #include "stringpool.h"
> #include "attribs.h"
> #include "analyzer/analyzer-selftests.h"
> +#include "text-art/selftests.h"
>
> /* This function needed to be split out from selftest.cc as it references
> tests from the whole source tree, and so is within
> @@ -118,6 +119,8 @@ selftest::run_tests ()
> /* Run any lang-specific selftests. */
> lang_hooks.run_lang_selftests ();
>
> + text_art_tests ();
> +
> /* Run the analyzer selftests (if enabled). */
> ana::selftest::run_analyzer_selftests ();
>
> diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c
> new file mode 100644
> index 00000000000..e4239aab032
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-bw.c
> @@ -0,0 +1,57 @@
> +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=never" } */
> +
> +int non_empty;
> +
> +/* { dg-begin-multiline-output "" }
> +
> + A
> + B
> + C
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
> + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
> +
> +
> +
> +
> + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
> + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + +--+
> + |🙂|
> + +--+
> +
> + { dg-end-multiline-output "" } */
> +/* { dg-begin-multiline-output "" }
> +
> + +-------+-----+---------------+---------------------+-----------------------+-----------------------+
> + |Offsets|Octet| 0 | 1 | 2 | 3 |
> + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
> + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
> + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
> + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length |
> + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+
> + | 4 | 32 | Identification | Flags | Fragment Offset |
> + +-------+-----+---------------+---------------------+--------+--------------------------------------+
> + | 8 | 64 | Time To Live | Protocol | Header Checksum |
> + +-------+-----+---------------+---------------------+-----------------------------------------------+
> + | 12 | 96 | Source IP Address |
> + +-------+-----+-------------------------------------------------------------------------------------+
> + | 16 | 128 | Destination IP Address |
> + +-------+-----+-------------------------------------------------------------------------------------+
> + | 20 | 160 | |
> + +-------+-----+ |
> + | ... | ... | Options |
> + +-------+-----+ |
> + | 56 | 448 | |
> + +-------+-----+-------------------------------------------------------------------------------------+
> +
> + { dg-end-multiline-output "" } */
> diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c
> new file mode 100644
> index 00000000000..0650428b1ce
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-ascii-color.c
> @@ -0,0 +1,58 @@
> +/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=always" } */
> +
> +int non_empty;
> +
> +/* { dg-begin-multiline-output "" }
> +
> + A
> + B
> + C
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K
> + [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K
> + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
> + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
> + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
> + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
> + [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K
> + [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + +--+
> + |🙂|
> + +--+
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + +-------+-----+---------------+---------------------+-----------------------+-----------------------+
> + |Offsets|Octet| 0 | 1 | 2 | 3 |
> + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
> + | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
> + +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
> + | 0 | 0 |Version| IHL | DSCP | ECN | Total Length |
> + +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+
> + | 4 | 32 | Identification | Flags | Fragment Offset |
> + +-------+-----+---------------+---------------------+--------+--------------------------------------+
> + | 8 | 64 | Time To Live | Protocol | Header Checksum |
> + +-------+-----+---------------+---------------------+-----------------------------------------------+
> + | 12 | 96 | Source IP Address |
> + +-------+-----+-------------------------------------------------------------------------------------+
> + | 16 | 128 | Destination IP Address |
> + +-------+-----+-------------------------------------------------------------------------------------+
> + | 20 | 160 | |
> + +-------+-----+ |
> + | ... | ... | Options |
> + +-------+-----+ |
> + | 56 | 448 | |
> + +-------+-----+-------------------------------------------------------------------------------------+
> +
> + { dg-end-multiline-output "" } */
> diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c
> new file mode 100644
> index 00000000000..c8118b46759
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-none.c
> @@ -0,0 +1,5 @@
> +/* { dg-additional-options "-fdiagnostics-text-art-charset=none" } */
> +
> +int non_empty;
> +
> +/* We expect no output. */
> diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c
> new file mode 100644
> index 00000000000..c9f5b36571a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-bw.c
> @@ -0,0 +1,58 @@
> +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=never" } */
> +
> +int non_empty;
> +
> +/* { dg-begin-multiline-output "" }
> +
> + A
> + B
> + C
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
> + ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
> +
> +
> +
> +
> + ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
> + ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + ┌──┐
> + │🙂│
> + └──┘
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐
> + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │
> + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤
> + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│
> + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤
> + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │
> + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤
> + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │
> + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤
> + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │
> + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤
> + │ 12 │ 96 │ Source IP Address │
> + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
> + │ 16 │ 128 │ Destination IP Address │
> + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
> + │ 20 │ 160 │ │
> + ├───────┼─────┤ │
> + │ ... │ ... │ Options │
> + ├───────┼─────┤ │
> + │ 56 │ 448 │ │
> + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘
> +
> + { dg-end-multiline-output "" } */
> diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c
> new file mode 100644
> index 00000000000..f402836f889
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-text-art-unicode-color.c
> @@ -0,0 +1,59 @@
> +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=always" } */
> +
> +int non_empty;
> +
> +
> +/* { dg-begin-multiline-output "" }
> +
> + A
> + B
> + C
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K
> + [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K
> + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
> + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
> + [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
> + [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
> + [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K
> + [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + ┌──┐
> + │🙂│
> + └──┘
> +
> + { dg-end-multiline-output "" } */
> +
> +/* { dg-begin-multiline-output "" }
> +
> + ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐
> + │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │
> + ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤
> + │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│
> + ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤
> + │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │
> + ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤
> + │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │
> + ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤
> + │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │
> + ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤
> + │ 12 │ 96 │ Source IP Address │
> + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
> + │ 16 │ 128 │ Destination IP Address │
> + ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
> + │ 20 │ 160 │ │
> + ├───────┼─────┤ │
> + │ ... │ ... │ Options │
> + ├───────┼─────┤ │
> + │ 56 │ 448 │ │
> + └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘
> +
> + { dg-end-multiline-output "" } */
> diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c
> new file mode 100644
> index 00000000000..27c341b9f2f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_text_art.c
> @@ -0,0 +1,257 @@
> +/* { dg-options "-O" } */
> +
> +/* This plugin exercises the text_art code. */
> +
> +#include "gcc-plugin.h"
> +#include "config.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "plugin-version.h"
> +#include "diagnostic.h"
> +#include "diagnostic-diagram.h"
> +#include "text-art/canvas.h"
> +#include "text-art/table.h"
> +
> +int plugin_is_GPL_compatible;
> +
> +using namespace text_art;
> +
> +/* Canvas tests. */
> +
> +static void
> +emit_canvas (const canvas &c, const char *alt_text)
> +{
> + diagnostic_diagram diagram (c, alt_text);
> + diagnostic_emit_diagram (global_dc, diagram);
> +}
> +
> +static void
> +test_abc ()
> +{
> + style_manager sm;
> + canvas c (canvas::size_t (3, 3), sm);
> + c.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
> + c.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
> + c.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
> + emit_canvas (c, "test_abc");
> +}
> +
> +/* Test of procedural art using 24-bit color: chess starting position. */
> +
> +static void
> +test_chessboard ()
> +{
> + /* With the exception of NONE, these are in order of the chess symbols
> + in the Unicode Miscellaneous Symbols block. */
> + enum class piece { KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN, NONE };
> + enum class color { BLACK, WHITE, NONE };
> +
> + style_manager sm;
> +
> + /* We assume double-column chars for the pieces, so allow two canvas
> + columns per square. */
> + canvas canvas (canvas::size_t (16, 8), sm);
> +
> + for (int x = 0; x < 8; x++)
> + for (int y = 0; y < 8; y++)
> + {
> + enum piece piece_kind;
> + enum color piece_color;
> + switch (y)
> + {
> + case 0:
> + case 7:
> + switch (x)
> + {
> + default:
> + gcc_unreachable ();
> + case 0:
> + piece_kind = piece::ROOK;
> + break;
> + case 1:
> + piece_kind = piece::KNIGHT;
> + break;
> + case 2:
> + piece_kind = piece::BISHOP;
> + break;
> + case 3:
> + piece_kind = piece::QUEEN;
> + break;
> + case 4:
> + piece_kind = piece::KING;
> + break;
> + case 5:
> + piece_kind = piece::BISHOP;
> + break;
> + case 6:
> + piece_kind = piece::KNIGHT;
> + break;
> + case 7:
> + piece_kind = piece::ROOK;
> + break;
> + }
> + piece_color = (y == 0) ? color::BLACK : color::WHITE;
> + break;
> + case 1:
> + case 6:
> + piece_kind = piece::PAWN;
> + piece_color = (y == 1) ? color::BLACK : color::WHITE;
> + break;
> + default:
> + piece_kind = piece::NONE;
> + piece_color = color::NONE;
> + break;
> + }
> +
> + style s;
> + const bool white_square = (x + y) % 2 == 0;
> + if (white_square)
> + s.m_bg_color = style::color (0xf0, 0xd9, 0xb5);
> + else
> + s.m_bg_color = style::color (0xb5, 0x88, 0x63);
> + switch (piece_color)
> + {
> + default:
> + gcc_unreachable ();
> + case color::WHITE:
> + s.m_fg_color = style::color (0xff, 0xff, 0xff);
> + break;
> + case color::BLACK:
> + s.m_fg_color = style::color (0x00, 0x00, 0x00);
> + break;
> + case color::NONE:
> + break;
> + }
> + style::id_t style_id = sm.get_or_create_id (s);
> +
> + cppchar_t ch;
> + if (piece_kind == piece::NONE)
> + ch = ' ';
> + else
> + {
> + const cppchar_t WHITE_KING = 0x2654;
> + const cppchar_t BLACK_KING = 0x265A;
> + cppchar_t base ((piece_color == color::WHITE)
> + ? WHITE_KING : BLACK_KING);
> + ch = base + ((int)piece_kind - (int)piece::KING);
> + }
> + canvas.paint (canvas::coord_t (x * 2, y),
> + canvas::cell_t (ch, false, style_id));
> + canvas.paint (canvas::coord_t (x * 2 + 1, y),
> + canvas::cell_t (' ', false, style_id));
> + }
> + emit_canvas (canvas, "test_chessboard");
> +}
> +
> +/* Table tests. */
> +
> +static void
> +emit_table (const table &table, const style_manager &sm, const char *alt_text)
> +{
> + const text_art::theme *theme = global_dc->m_diagrams.m_theme;
> + if (!theme)
> + return;
> + canvas c (table.to_canvas (*theme, sm));
> + emit_canvas (c, alt_text);
> +}
> +
> +static void
> +test_double_width_chars ()
> +{
> + style_manager sm;
> + table table (table::size_t (1, 1));
> + table.set_cell (table::coord_t (0,0),
> + styled_string ((cppchar_t)0x1f642));
> +
> + emit_table (table, sm, "test_double_width_chars");
> +}
> +
> +static void
> +test_ipv4_header ()
> +{
> + style_manager sm;
> + table table (table::size_t (34, 10));
> + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets"));
> + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet"));
> + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet"));
> + for (int octet = 0; octet < 4; octet++)
> + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0),
> + table::size_t (8, 1)),
> + styled_string::from_fmt (sm, nullptr, "%i", octet));
> + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit"));
> + for (int bit = 0; bit < 32; bit++)
> + table.set_cell (table::coord_t (bit + 2, 1),
> + styled_string::from_fmt (sm, nullptr, "%i", bit));
> + for (int word = 0; word < 6; word++)
> + {
> + table.set_cell (table::coord_t (0, word + 2),
> + styled_string::from_fmt (sm, nullptr, "%i", word * 4));
> + table.set_cell (table::coord_t (1, word + 2),
> + styled_string::from_fmt (sm, nullptr, "%i", word * 32));
> + }
> +
> + table.set_cell (table::coord_t (0, 8), styled_string (sm, "..."));
> + table.set_cell (table::coord_t (1, 8), styled_string (sm, "..."));
> + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56"));
> + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448"));
> +
> +#define SET_BITS(FIRST, LAST, NAME) \
> + do { \
> + const int first = (FIRST); \
> + const int last = (LAST); \
> + const char *name = (NAME); \
> + const int row = first / 32; \
> + gcc_assert (last / 32 == row); \
> + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \
> + table::size_t (last + 1 - first , 1)); \
> + table.set_cell_span (rect, styled_string (sm, name)); \
> + } while (0)
> +
> + SET_BITS (0, 3, "Version");
> + SET_BITS (4, 7, "IHL");
> + SET_BITS (8, 13, "DSCP");
> + SET_BITS (14, 15, "ECN");
> + SET_BITS (16, 31, "Total Length");
> +
> + SET_BITS (32 + 0, 32 + 15, "Identification");
> + SET_BITS (32 + 16, 32 + 18, "Flags");
> + SET_BITS (32 + 19, 32 + 31, "Fragment Offset");
> +
> + SET_BITS (64 + 0, 64 + 7, "Time To Live");
> + SET_BITS (64 + 8, 64 + 15, "Protocol");
> + SET_BITS (64 + 16, 64 + 31, "Header Checksum");
> +
> + SET_BITS (96 + 0, 96 + 31, "Source IP Address");
> + SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
> +
> + table.set_cell_span(table::rect_t (table::coord_t (2, 7),
> + table::size_t (32, 3)),
> + styled_string (sm, "Options"));
> +
> + emit_table (table, sm, "test_ipv4_header");
> +}
> +
> +static void
> +show_diagrams ()
> +{
> + test_abc ();
> + test_chessboard ();
> + test_double_width_chars ();
> + test_ipv4_header ();
> +}
> +
> +int
> +plugin_init (struct plugin_name_args *plugin_info,
> + struct plugin_gcc_version *version)
> +{
> + const char *plugin_name = plugin_info->base_name;
> + int argc = plugin_info->argc;
> + struct plugin_argument *argv = plugin_info->argv;
> +
> + if (!plugin_default_version_check (version, &gcc_version))
> + return 1;
> +
> + show_diagrams ();
> +
> + return 0;
> +}
> diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
> index 4d6304cd100..60723a20eda 100644
> --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
> +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
> @@ -114,6 +114,12 @@ set plugin_test_list [list \
> diagnostic-path-format-inline-events-1.c \
> diagnostic-path-format-inline-events-2.c \
> diagnostic-path-format-inline-events-3.c } \
> + { diagnostic_plugin_test_text_art.c \
> + diagnostic-test-text-art-none.c \
> + diagnostic-test-text-art-ascii-bw.c \
> + diagnostic-test-text-art-ascii-color.c \
> + diagnostic-test-text-art-unicode-bw.c \
> + diagnostic-test-text-art-unicode-color.c } \
> { location_overflow_plugin.c \
> location-overflow-test-1.c \
> location-overflow-test-2.c \
> diff --git a/gcc/text-art/box-drawing-chars.inc b/gcc/text-art/box-drawing-chars.inc
> new file mode 100644
> index 00000000000..a370255d56d
> --- /dev/null
> +++ b/gcc/text-art/box-drawing-chars.inc
> @@ -0,0 +1,18 @@
> +/* Generated by contrib/unicode/gen-box-drawing-chars.py. */
> +
> +0x0020, /* " ": U+0020: SPACE */
> +0x2576, /* "╶": U+2576: BOX DRAWINGS LIGHT RIGHT */
> +0x2574, /* "╴": U+2574: BOX DRAWINGS LIGHT LEFT */
> +0x2500, /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
> +0x2577, /* "╷": U+2577: BOX DRAWINGS LIGHT DOWN */
> +0x250C, /* "┌": U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */
> +0x2510, /* "┐": U+2510: BOX DRAWINGS LIGHT DOWN AND LEFT */
> +0x252C, /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
> +0x2575, /* "╵": U+2575: BOX DRAWINGS LIGHT UP */
> +0x2514, /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT */
> +0x2518, /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT */
> +0x2534, /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */
> +0x2502, /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
> +0x251C, /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
> +0x2524, /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */
> +0x253C /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
> diff --git a/gcc/text-art/box-drawing.cc b/gcc/text-art/box-drawing.cc
> new file mode 100644
> index 00000000000..981d0b095cf
> --- /dev/null
> +++ b/gcc/text-art/box-drawing.cc
> @@ -0,0 +1,72 @@
> +/* Procedural lookup of box drawing characters.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "text-art/box-drawing.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +
> +
> +/* According to
> + https://en.wikipedia.org/wiki/Box-drawing_character#Character_code
> + "DOS line- and box-drawing characters are not ordered in any programmatic
> + manner, so calculating a particular character shape needs to use a look-up
> + table. "
> + Hence this array. */
> +static const cppchar_t box_drawing_chars[] = {
> +#include "text-art/box-drawing-chars.inc"
> +};
> +
> +cppchar_t
> +text_art::get_box_drawing_char (directions line_dirs)
> +{
> + const size_t idx = line_dirs.as_index ();
> + gcc_assert (idx < 16);
> + return box_drawing_chars[idx];
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_box_drawing_cc_tests ()
> +{
> + ASSERT_EQ (text_art::get_box_drawing_char
> + (text_art::directions (false, false, false, false)),
> + ' ');
> + ASSERT_EQ (text_art::get_box_drawing_char
> + (text_art::directions (false, false, true, true)),
> + 0x2500); /* BOX DRAWINGS LIGHT HORIZONTAL */
> + ASSERT_EQ (text_art::get_box_drawing_char
> + (text_art::directions (true, true, false, false)),
> + 0x2502); /* BOX DRAWINGS LIGHT VERTICAL */
> + ASSERT_EQ (text_art::get_box_drawing_char
> + (text_art::directions (true, false, true, false)),
> + 0x2518); /* BOX DRAWINGS LIGHT UP AND LEFT */
> +}
> +
> +} // namespace selftest
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/box-drawing.h b/gcc/text-art/box-drawing.h
> new file mode 100644
> index 00000000000..29f4d9921b3
> --- /dev/null
> +++ b/gcc/text-art/box-drawing.h
> @@ -0,0 +1,32 @@
> +/* Procedural lookup of box drawing characters.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_BOX_DRAWING_H
> +#define GCC_TEXT_ART_BOX_DRAWING_H
> +
> +#include "text-art/types.h"
> +
> +namespace text_art {
> +
> +extern cppchar_t get_box_drawing_char (directions line_dirs);
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_BOX_DRAWING_H */
> diff --git a/gcc/text-art/canvas.cc b/gcc/text-art/canvas.cc
> new file mode 100644
> index 00000000000..f229612c919
> --- /dev/null
> +++ b/gcc/text-art/canvas.cc
> @@ -0,0 +1,437 @@
> +/* Canvas for random-access procedural text art.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "pretty-print.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +#include "text-art/canvas.h"
> +
> +using namespace text_art;
> +
> +canvas::canvas (size_t size, const style_manager &style_mgr)
> +: m_cells (size_t (size.w, size.h)),
> + m_style_mgr (style_mgr)
> +{
> + m_cells.fill (cell_t (' '));
> +}
> +
> +void
> +canvas::paint (coord_t coord, styled_unichar ch)
> +{
> + m_cells.set (coord, std::move (ch));
> +}
> +
> +void
> +canvas::paint_text (coord_t coord, const styled_string &text)
> +{
> + for (auto ch : text)
> + {
> + paint (coord, ch);
> + if (ch.double_width_p ())
> + coord.x += 2;
> + else
> + coord.x++;
> + }
> +}
> +
> +void
> +canvas::fill (rect_t rect, cell_t c)
> +{
> + for (int y = rect.get_min_y (); y < rect.get_next_y (); y++)
> + for (int x = rect.get_min_x (); x < rect.get_next_x (); x++)
> + paint(coord_t (x, y), c);
> +}
> +
> +void
> +canvas::debug_fill ()
> +{
> + fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*'));
> +}
> +
> +void
> +canvas::print_to_pp (pretty_printer *pp,
> + const char *per_line_prefix) const
> +{
> + for (int y = 0; y < m_cells.get_size ().h; y++)
> + {
> + style::id_t curr_style_id = 0;
> + if (per_line_prefix)
> + pp_string (pp, per_line_prefix);
> +
> + pretty_printer line_pp;
> + line_pp.show_color = pp->show_color;
> + line_pp.url_format = pp->url_format;
> + const int final_x_in_row = get_final_x_in_row (y);
> + for (int x = 0; x <= final_x_in_row; x++)
> + {
> + if (x > 0)
> + {
> + const cell_t prev_cell = m_cells.get (coord_t (x - 1, y));
> + if (prev_cell.double_width_p ())
> + /* This cell is just a placeholder for the
> + 2nd column of a double width cell; skip it. */
> + continue;
> + }
> + const cell_t cell = m_cells.get (coord_t (x, y));
> + if (cell.get_style_id () != curr_style_id)
> + {
> + m_style_mgr.print_any_style_changes (&line_pp,
> + curr_style_id,
> + cell.get_style_id ());
> + curr_style_id = cell.get_style_id ();
> + }
> + pp_unicode_character (&line_pp, cell.get_code ());
> + if (cell.emoji_variant_p ())
> + /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
> + variation of the char. */
> + pp_unicode_character (&line_pp, 0xFE0F);
> + }
> + /* Reset the style at the end of each line. */
> + m_style_mgr.print_any_style_changes (&line_pp, curr_style_id, 0);
> +
> + /* Print from line_pp to pp, stripping trailing whitespace from
> + the line. */
> + const char *line_buf = pp_formatted_text (&line_pp);
> + ::size_t len = strlen (line_buf);
> + while (len > 0)
> + {
> + if (line_buf[len - 1] == ' ')
> + len--;
> + else
> + break;
> + }
> + pp_append_text (pp, line_buf, line_buf + len);
> + pp_newline (pp);
> + }
> +}
> +
> +DEBUG_FUNCTION void
> +canvas::debug (bool styled) const
> +{
> + pretty_printer pp;
> + if (styled)
> + {
> + pp_show_color (&pp) = true;
> + pp.url_format = determine_url_format (DIAGNOSTICS_URL_AUTO);
> + }
> + print_to_pp (&pp);
> + fprintf (stderr, "%s\n", pp_formatted_text (&pp));
> +}
> +
> +/* Find right-most non-default cell in this row,
> + or -1 if all are default. */
> +
> +int
> +canvas::get_final_x_in_row (int y) const
> +{
> + for (int x = m_cells.get_size ().w - 1; x >= 0; x--)
> + {
> + cell_t cell = m_cells.get (coord_t (x, y));
> + if (cell.get_code () != ' '
> + || cell.get_style_id () != style::id_plain)
> + return x;
> + }
> + return -1;
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +static void
> +test_blank ()
> +{
> + style_manager sm;
> + canvas c (canvas::size_t (5, 5), sm);
> + ASSERT_CANVAS_STREQ (c, false,
> + ("\n"
> + "\n"
> + "\n"
> + "\n"
> + "\n"));
> +}
> +
> +static void
> +test_abc ()
> +{
> + style_manager sm;
> + canvas c (canvas::size_t (3, 3), sm);
> + c.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
> + c.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
> + c.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
> +
> + ASSERT_CANVAS_STREQ (c, false,
> + "A\n B\n C\n");
> +}
> +
> +static void
> +test_debug_fill ()
> +{
> + style_manager sm;
> + canvas c (canvas::size_t (5, 3), sm);
> + c.debug_fill();
> + ASSERT_CANVAS_STREQ (c, false,
> + ("*****\n"
> + "*****\n"
> + "*****\n"));
> +}
> +
> +static void
> +test_text ()
> +{
> + style_manager sm;
> + canvas c (canvas::size_t (6, 1), sm);
> + c.paint_text (canvas::coord_t (0, 0), styled_string (sm, "012345"));
> + ASSERT_CANVAS_STREQ (c, false,
> + ("012345\n"));
> +
> + /* Paint an emoji character that should occupy two canvas columns when
> + printed. */
> + c.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t)0x1f642));
> + ASSERT_CANVAS_STREQ (c, false,
> + ("01🙂45\n"));
> +}
> +
> +static void
> +test_circle ()
> +{
> + canvas::size_t sz (30, 30);
> + style_manager sm;
> + canvas canvas (sz, sm);
> + canvas::coord_t center (sz.w / 2, sz.h / 2);
> + const int radius = 12;
> + const int radius_squared = radius * radius;
> + for (int x = 0; x < sz.w; x++)
> + for (int y = 0; y < sz.h; y++)
> + {
> + int dx = x - center.x;
> + int dy = y - center.y;
> + char ch = "AB"[(x + y) % 2];
> + if (dx * dx + dy * dy < radius_squared)
> + canvas.paint (canvas::coord_t (x, y), styled_unichar (ch));
> + }
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("\n"
> + "\n"
> + "\n"
> + "\n"
> + " BABABABAB\n"
> + " ABABABABABABA\n"
> + " ABABABABABABABA\n"
> + " ABABABABABABABABA\n"
> + " ABABABABABABABABABA\n"
> + " ABABABABABABABABABABA\n"
> + " BABABABABABABABABABAB\n"
> + " BABABABABABABABABABABAB\n"
> + " ABABABABABABABABABABABA\n"
> + " BABABABABABABABABABABAB\n"
> + " ABABABABABABABABABABABA\n"
> + " BABABABABABABABABABABAB\n"
> + " ABABABABABABABABABABABA\n"
> + " BABABABABABABABABABABAB\n"
> + " ABABABABABABABABABABABA\n"
> + " BABABABABABABABABABABAB\n"
> + " BABABABABABABABABABAB\n"
> + " ABABABABABABABABABABA\n"
> + " ABABABABABABABABABA\n"
> + " ABABABABABABABABA\n"
> + " ABABABABABABABA\n"
> + " ABABABABABABA\n"
> + " BABABABAB\n"
> + "\n"
> + "\n"
> + "\n"));
> +}
> +
> +static void
> +test_color_circle ()
> +{
> + const canvas::size_t sz (10, 10);
> + const canvas::coord_t center (sz.w / 2, sz.h / 2);
> + const int outer_r2 = 25;
> + const int inner_r2 = 10;
> + style_manager sm;
> + canvas c (sz, sm);
> + for (int x = 0; x < sz.w; x++)
> + for (int y = 0; y < sz.h; y++)
> + {
> + const int dist_from_center_squared
> + = ((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y));
> + if (dist_from_center_squared < outer_r2)
> + {
> + style s;
> + if (dist_from_center_squared < inner_r2)
> + s.m_fg_color = style::named_color::RED;
> + else
> + s.m_fg_color = style::named_color::GREEN;
> + c.paint (canvas::coord_t (x, y),
> + styled_unichar ('*', false, sm.get_or_create_id (s)));
> + }
> + }
> + ASSERT_EQ (sm.get_num_styles (), 3);
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("\n"
> + " *****\n"
> + " *******\n"
> + " *********\n"
> + " *********\n"
> + " *********\n"
> + " *********\n"
> + " *********\n"
> + " *******\n"
> + " *****\n"));
> + ASSERT_CANVAS_STREQ
> + (c, true,
> + ("\n"
> + " [32m[K*****[m[K\n"
> + " [32m[K***[31m[K*[32m[K***[m[K\n"
> + " [32m[K**[31m[K*****[32m[K**[m[K\n"
> + " [32m[K**[31m[K*****[32m[K**[m[K\n"
> + " [32m[K*[31m[K*******[32m[K*[m[K\n"
> + " [32m[K**[31m[K*****[32m[K**[m[K\n"
> + " [32m[K**[31m[K*****[32m[K**[m[K\n"
> + " [32m[K***[31m[K*[32m[K***[m[K\n"
> + " [32m[K*****[m[K\n"));
> +}
> +
> +static void
> +test_bold ()
> +{
> + auto_fix_quotes fix_quotes;
> + style_manager sm;
> + styled_string s (styled_string::from_fmt (sm, nullptr,
> + "before %qs after", "foo"));
> + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
> + c.paint_text (canvas::coord_t (0, 0), s);
> + ASSERT_CANVAS_STREQ (c, false,
> + "before `foo' after\n");
> + ASSERT_CANVAS_STREQ (c, true,
> + "before `[00;01m[Kfoo[00m[K' after\n");
> +}
> +
> +static void
> +test_emoji ()
> +{
> + style_manager sm;
> + styled_string s (0x26A0, /* U+26A0 WARNING SIGN. */
> + true);
> + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
> + c.paint_text (canvas::coord_t (0, 0), s);
> + ASSERT_CANVAS_STREQ (c, false, "⚠️\n");
> + ASSERT_CANVAS_STREQ (c, true, "⚠️\n");
> +}
> +
> +static void
> +test_emoji_2 ()
> +{
> + style_manager sm;
> + styled_string s;
> + s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
> + true));
> + s.append (styled_string (sm, "test"));
> + ASSERT_EQ (s.size (), 5);
> + ASSERT_EQ (s.calc_canvas_width (), 5);
> + canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
> + c.paint_text (canvas::coord_t (0, 0), s);
> + ASSERT_CANVAS_STREQ (c, false,
> + /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */
> + "\xE2\x9A\xA0"
> + /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */
> + "\xEF\xB8\x8F"
> + "test\n");
> +}
> +
> +static void
> +test_canvas_urls ()
> +{
> + style_manager sm;
> + canvas canvas (canvas::size_t (9, 3), sm);
> + styled_string foo_ss (sm, "foo");
> + foo_ss.set_url (sm, "https://www.example.com/foo");
> + styled_string bar_ss (sm, "bar");
> + bar_ss.set_url (sm, "https://www.example.com/bar");
> + canvas.paint_text(canvas::coord_t (1, 1), foo_ss);
> + canvas.paint_text(canvas::coord_t (5, 1), bar_ss);
> +
> + ASSERT_CANVAS_STREQ (canvas, false,
> + ("\n"
> + " foo bar\n"
> + "\n"));
> + {
> + pretty_printer pp;
> + pp_show_color (&pp) = true;
> + pp.url_format = URL_FORMAT_ST;
> + assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
> + (/* Line 1. */
> + "\n"
> + /* Line 2. */
> + " "
> + "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\"
> + " "
> + "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\"
> + "\n"
> + /* Line 3. */
> + "\n"));
> + }
> +
> + {
> + pretty_printer pp;
> + pp_show_color (&pp) = true;
> + pp.url_format = URL_FORMAT_BEL;
> + assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
> + (/* Line 1. */
> + "\n"
> + /* Line 2. */
> + " "
> + "\33]8;;https://www.example.com/foo\afoo\33]8;;\a"
> + " "
> + "\33]8;;https://www.example.com/bar\abar\33]8;;\a"
> + "\n"
> + /* Line 3. */
> + "\n"));
> + }
> +}
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_canvas_cc_tests ()
> +{
> + test_blank ();
> + test_abc ();
> + test_debug_fill ();
> + test_text ();
> + test_circle ();
> + test_color_circle ();
> + test_bold ();
> + test_emoji ();
> + test_emoji_2 ();
> + test_canvas_urls ();
> +}
> +
> +} // namespace selftest
> +
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/canvas.h b/gcc/text-art/canvas.h
> new file mode 100644
> index 00000000000..495497754f5
> --- /dev/null
> +++ b/gcc/text-art/canvas.h
> @@ -0,0 +1,74 @@
> +/* Canvas for random-access procedural text art.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_CANVAS_H
> +#define GCC_TEXT_ART_CANVAS_H
> +
> +#include "text-art/types.h"
> +
> +namespace text_art {
> +
> +class canvas;
> +
> +/* A 2 dimensional grid of text cells (a "canvas"), which
> + can be written to ("painted") via random access, and then
> + written out to a pretty_printer once the picture is complete.
> +
> + Each text cell can be styled independently (colorization,
> + URLs, etc). */
> +
> +class canvas
> +{
> + public:
> + typedef styled_unichar cell_t;
> + typedef size<class canvas> size_t;
> + typedef coord<class canvas> coord_t;
> + typedef range<class canvas> range_t;
> + typedef rect<class canvas> rect_t;
> +
> + canvas (size_t size, const style_manager &style_mgr);
> +
> + size_t get_size () const { return m_cells.get_size (); }
> +
> + void paint (coord_t coord, cell_t c);
> + void paint_text (coord_t coord, const styled_string &text);
> +
> + void fill (rect_t rect, cell_t c);
> + void debug_fill ();
> +
> + void print_to_pp (pretty_printer *pp,
> + const char *per_line_prefix = NULL) const;
> + void debug (bool styled) const;
> +
> + const cell_t &get (coord_t coord) const
> + {
> + return m_cells.get (coord);
> + }
> +
> + private:
> + int get_final_x_in_row (int y) const;
> +
> + array2<cell_t, size_t, coord_t> m_cells;
> + const style_manager &m_style_mgr;
> +};
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_CANVAS_H */
> diff --git a/gcc/text-art/ruler.cc b/gcc/text-art/ruler.cc
> new file mode 100644
> index 00000000000..80c623f77ba
> --- /dev/null
> +++ b/gcc/text-art/ruler.cc
> @@ -0,0 +1,723 @@
> +/* Classes for printing labelled rulers.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#define INCLUDE_ALGORITHM
> +#include "system.h"
> +#include "coretypes.h"
> +#include "pretty-print.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +#include "text-art/ruler.h"
> +#include "text-art/theme.h"
> +
> +using namespace text_art;
> +
> +void
> +x_ruler::add_label (const canvas::range_t &r,
> + styled_string text,
> + style::id_t style_id,
> + label_kind kind)
> +{
> + m_labels.push_back (label (r, std::move (text), style_id, kind));
> + m_has_layout = false;
> +}
> +
> +int
> +x_ruler::get_canvas_y (int rel_y) const
> +{
> + gcc_assert (rel_y >= 0);
> + gcc_assert (rel_y < m_size.h);
> + switch (m_label_dir)
> + {
> + default:
> + gcc_unreachable ();
> + case label_dir::ABOVE:
> + return m_size.h - (rel_y + 1);
> + case label_dir::BELOW:
> + return rel_y;
> + }
> +}
> +
> +void
> +x_ruler::paint_to_canvas (canvas &canvas,
> + canvas::coord_t offset,
> + const theme &theme)
> +{
> + ensure_layout ();
> +
> + if (0)
> + canvas.fill (canvas::rect_t (offset, m_size),
> + canvas::cell_t ('*'));
> +
> + for (size_t idx = 0; idx < m_labels.size (); idx++)
> + {
> + const label &iter_label = m_labels[idx];
> +
> + /* Paint the ruler itself. */
> + const int ruler_rel_y = get_canvas_y (0);
> + for (int rel_x = iter_label.m_range.start;
> + rel_x < iter_label.m_range.next;
> + rel_x++)
> + {
> + enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE;
> +
> + if (rel_x == iter_label.m_range.start)
> + {
> + kind = theme::cell_kind::X_RULER_LEFT_EDGE;
> + if (idx > 0)
> + {
> + const label &prev_label = m_labels[idx - 1];
> + if (prev_label.m_range.get_max () == iter_label.m_range.start)
> + kind = theme::cell_kind::X_RULER_INTERNAL_EDGE;
> + }
> + }
> + else if (rel_x == iter_label.m_range.get_max ())
> + kind = theme::cell_kind::X_RULER_RIGHT_EDGE;
> + else if (rel_x == iter_label.m_connector_x)
> + {
> + switch (m_label_dir)
> + {
> + default:
> + gcc_unreachable ();
> + case label_dir::ABOVE:
> + kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
> + break;
> + case label_dir::BELOW:
> + kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
> + break;
> + }
> + }
> + canvas.paint (canvas::coord_t (rel_x, ruler_rel_y) + offset,
> + theme.get_cell (kind, iter_label.m_style_id));
> + }
> +
> + /* Paint the connector to the text. */
> + for (int connector_rel_y = 1;
> + connector_rel_y < iter_label.m_text_rect.get_min_y ();
> + connector_rel_y++)
> + {
> + canvas.paint
> + ((canvas::coord_t (iter_label.m_connector_x,
> + get_canvas_y (connector_rel_y))
> + + offset),
> + theme.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR,
> + iter_label.m_style_id));
> + }
> +
> + /* Paint the text. */
> + switch (iter_label.m_kind)
> + {
> + default:
> + gcc_unreachable ();
> + case x_ruler::label_kind::TEXT:
> + canvas.paint_text
> + ((canvas::coord_t (iter_label.m_text_rect.get_min_x (),
> + get_canvas_y (iter_label.m_text_rect.get_min_y ()))
> + + offset),
> + iter_label.m_text);
> + break;
> +
> + case x_ruler::label_kind::TEXT_WITH_BORDER:
> + {
> + const canvas::range_t rel_x_range
> + (iter_label.m_text_rect.get_x_range ());
> +
> + enum theme::cell_kind inner_left_kind;
> + enum theme::cell_kind inner_connector_kind;
> + enum theme::cell_kind inner_right_kind;
> + enum theme::cell_kind outer_left_kind;
> + enum theme::cell_kind outer_right_kind;
> +
> + switch (m_label_dir)
> + {
> + default:
> + gcc_unreachable ();
> + case label_dir::ABOVE:
> + outer_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
> + outer_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
> + inner_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
> + inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
> + inner_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
> + break;
> + case label_dir::BELOW:
> + inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
> + inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
> + inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
> + outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
> + outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
> + break;
> + }
> + /* Inner border. */
> + {
> + const int rel_canvas_y
> + = get_canvas_y (iter_label.m_text_rect.get_min_y ());
> + /* Left corner. */
> + canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
> + rel_canvas_y)
> + + offset),
> + theme.get_cell (inner_left_kind,
> + iter_label.m_style_id));
> + /* Edge. */
> + const canvas::cell_t edge_border_cell
> + = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
> + iter_label.m_style_id);
> + const canvas::cell_t connector_border_cell
> + = theme.get_cell (inner_connector_kind,
> + iter_label.m_style_id);
> + for (int rel_x = rel_x_range.get_min () + 1;
> + rel_x < rel_x_range.get_max ();
> + rel_x++)
> + if (rel_x == iter_label.m_connector_x)
> + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
> + + offset),
> + connector_border_cell);
> + else
> + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
> + + offset),
> + edge_border_cell);
> +
> + /* Right corner. */
> + canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
> + rel_canvas_y)
> + + offset),
> + theme.get_cell (inner_right_kind,
> + iter_label.m_style_id));
> + }
> +
> + {
> + const int rel_canvas_y
> + = get_canvas_y (iter_label.m_text_rect.get_min_y () + 1);
> + const canvas::cell_t border_cell
> + = theme.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL,
> + iter_label.m_style_id);
> +
> + /* Left border. */
> + canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
> + rel_canvas_y)
> + + offset),
> + border_cell);
> + /* Text. */
> + canvas.paint_text ((canvas::coord_t (rel_x_range.get_min () + 1,
> + rel_canvas_y)
> + + offset),
> + iter_label.m_text);
> + /* Right border. */
> + canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
> + rel_canvas_y)
> + + offset),
> + border_cell);
> + }
> +
> + /* Outer border. */
> + {
> + const int rel_canvas_y
> + = get_canvas_y (iter_label.m_text_rect.get_max_y ());
> + /* Left corner. */
> + canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
> + rel_canvas_y)
> + + offset),
> + theme.get_cell (outer_left_kind,
> + iter_label.m_style_id));
> + /* Edge. */
> + const canvas::cell_t border_cell
> + = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
> + iter_label.m_style_id);
> + for (int rel_x = rel_x_range.get_min () + 1;
> + rel_x < rel_x_range.get_max ();
> + rel_x++)
> + canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
> + + offset),
> + border_cell);
> +
> + /* Right corner. */
> + canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
> + rel_canvas_y)
> + + offset),
> + theme.get_cell (outer_right_kind,
> + iter_label.m_style_id));
> + }
> + }
> + break;
> + }
> + }
> +}
> +
> +DEBUG_FUNCTION void
> +x_ruler::debug (const style_manager &sm)
> +{
> + canvas c (get_size (), sm);
> + paint_to_canvas (c, canvas::coord_t (0, 0), unicode_theme ());
> + c.debug (true);
> +}
> +
> +x_ruler::label::label (const canvas::range_t &range,
> + styled_string text,
> + style::id_t style_id,
> + label_kind kind)
> +: m_range (range),
> + m_text (std::move (text)),
> + m_style_id (style_id),
> + m_kind (kind),
> + m_text_rect (canvas::coord_t (0, 0),
> + canvas::size_t (m_text.calc_canvas_width (), 1)),
> + m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2)
> +{
> + if (kind == label_kind::TEXT_WITH_BORDER)
> + {
> + m_text_rect.m_size.w += 2;
> + m_text_rect.m_size.h += 2;
> + }
> +}
> +
> +bool
> +x_ruler::label::operator< (const label &other) const
> +{
> + int cmp = m_range.start - other.m_range.start;
> + if (cmp)
> + return cmp < 0;
> + return m_range.next < other.m_range.next;
> +}
> +
> +void
> +x_ruler::ensure_layout ()
> +{
> + if (m_has_layout)
> + return;
> + update_layout ();
> + m_has_layout = true;
> +}
> +
> +void
> +x_ruler::update_layout ()
> +{
> + if (m_labels.empty ())
> + return;
> +
> + std::sort (m_labels.begin (), m_labels.end ());
> +
> + /* Place labels. */
> + int ruler_width = m_labels.back ().m_range.get_next ();
> + int width_with_labels = ruler_width;
> +
> + /* Get x coordinates of text parts of each label
> + (m_text_rect.m_top_left.x for each label). */
> + for (size_t idx = 0; idx < m_labels.size (); idx++)
> + {
> + label &iter_label = m_labels[idx];
> + /* Attempt to center the text label. */
> + int min_x;
> + if (idx > 0)
> + {
> + /* ...but don't overlap with the connector to the left. */
> + int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x;
> + min_x = left_neighbor_connector_x + 1;
> + }
> + else
> + {
> + /* ...or go beyond the leftmost column. */
> + min_x = 0;
> + }
> + int connector_x = iter_label.m_connector_x;
> + int centered_x
> + = connector_x - ((int)iter_label.m_text_rect.get_width () / 2);
> + int text_x = std::max (min_x, centered_x);
> + iter_label.m_text_rect.m_top_left.x = text_x;
> + }
> +
> + /* Now walk backwards trying to place them vertically,
> + setting m_text_rect.m_top_left.y for each label,
> + consolidating the rows where possible.
> + The y cooordinates are stored with respect to label_dir::BELOW. */
> + int label_y = 2;
> + for (int idx = m_labels.size () - 1; idx >= 0; idx--)
> + {
> + label &iter_label = m_labels[idx];
> + /* Does it fit on the same row as the text label to the right? */
> + size_t text_len = iter_label.m_text_rect.get_width ();
> + /* Get the x-coord of immediately beyond iter_label's text. */
> + int next_x = iter_label.m_text_rect.get_min_x () + text_len;
> + if (idx < (int)m_labels.size () - 1)
> + {
> + if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ())
> + {
> + /* If not, start a new row. */
> + label_y += m_labels[idx + 1].m_text_rect.get_height ();
> + }
> + }
> + iter_label.m_text_rect.m_top_left.y = label_y;
> + width_with_labels = std::max (width_with_labels, next_x);
> + }
> +
> + m_size = canvas::size_t (width_with_labels,
> + label_y + m_labels[0].m_text_rect.get_height ());
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +static void
> +assert_x_ruler_streq (const location &loc,
> + x_ruler &ruler,
> + const theme &theme,
> + const style_manager &sm,
> + bool styled,
> + const char *expected_str)
> +{
> + canvas c (ruler.get_size (), sm);
> + ruler.paint_to_canvas (c, canvas::coord_t (0, 0), theme);
> + if (0)
> + c.debug (styled);
> + assert_canvas_streq (loc, c, styled, expected_str);
> +}
> +
> +#define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + assert_x_ruler_streq ((SELFTEST_LOCATION), \
> + (RULER), \
> + (THEME), \
> + (SM), \
> + (STYLED), \
> + (EXPECTED_STR)); \
> + SELFTEST_END_STMT
> +
> +static void
> +test_single ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
> + style::id_plain, x_ruler::label_kind::TEXT);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + ("|~~~~+~~~~|\n"
> + " |\n"
> + " foo\n"));
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├────┬────┤\n"
> + " │\n"
> + " foo\n"));
> +}
> +
> +static void
> +test_single_above ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::ABOVE);
> + r.add_label (canvas::range_t (0, 11), styled_string (sm, "hello world"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + ("hello world\n"
> + " |\n"
> + "|~~~~+~~~~|\n"));
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("hello world\n"
> + " │\n"
> + "├────┴────┤\n"));
> +}
> +
> +static void
> +test_multiple_contiguous ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + ("|~~~~+~~~~|~+~~|\n"
> + " | |\n"
> + " foo bar\n"));
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├────┬────┼─┬──┤\n"
> + " │ │\n"
> + " foo bar\n"));
> +}
> +
> +static void
> +test_multiple_contiguous_above ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::ABOVE);
> + r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + (" foo bar\n"
> + " | |\n"
> + "|~~~~+~~~~|~+~~|\n"));
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + (" foo bar\n"
> + " │ │\n"
> + "├────┴────┼─┴──┤\n"));
> +}
> +
> +static void
> +test_multiple_contiguous_abutting_labels ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 11), styled_string (sm, "12345678"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 16), styled_string (sm, "1234678"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├────┬────┼─┬──┤\n"
> + " │ │\n"
> + " │ 1234678\n"
> + " 12345678\n"));
> +}
> +
> +static void
> +test_multiple_contiguous_overlapping_labels ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 11), styled_string (sm, "123456789"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 16), styled_string (sm, "12346789"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├────┬────┼─┬──┤\n"
> + " │ │\n"
> + " │ 12346789\n"
> + " 123456789\n"));
> +}
> +static void
> +test_abutting_left_border ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 6),
> + styled_string (sm, "this is a long label"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├─┬──┤\n"
> + " │\n"
> + "this is a long label\n"));
> +}
> +
> +static void
> +test_too_long_to_consolidate_vertically ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 11),
> + styled_string (sm, "long string A"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 16),
> + styled_string (sm, "long string B"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├────┬────┼─┬──┤\n"
> + " │ │\n"
> + " │long string B\n"
> + "long string A\n"));
> +}
> +
> +static void
> +test_abutting_neighbor ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 11),
> + styled_string (sm, "very long string A"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 16),
> + styled_string (sm, "very long string B"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + ("├────┬────┼─┬──┤\n"
> + " │ │\n"
> + " │very long string B\n"
> + "very long string A\n"));
> +}
> +
> +static void
> +test_gaps ()
> +{
> + style_manager sm;
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 5),
> + styled_string (sm, "foo"),
> + style::id_plain);
> + r.add_label (canvas::range_t (10, 15),
> + styled_string (sm, "bar"),
> + style::id_plain);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + ("|~+~| |~+~|\n"
> + " | |\n"
> + " foo bar\n"));
> +}
> +
> +static void
> +test_styled ()
> +{
> + style_manager sm;
> + style s1, s2;
> + s1.m_bold = true;
> + s1.m_fg_color = style::named_color::YELLOW;
> + s2.m_bold = true;
> + s2.m_fg_color = style::named_color::BLUE;
> + style::id_t sid1 = sm.get_or_create_id (s1);
> + style::id_t sid2 = sm.get_or_create_id (s2);
> +
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 5), styled_string (sm, "foo"), sid1);
> + r.add_label (canvas::range_t (10, 15), styled_string (sm, "bar"), sid2);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + ("[00;01;33m[K|~+~|[00m[K [00;01;34m[K|~+~|[00m[K\n"
> + " [00;01;33m[K|[00m[K [00;01;34m[K|[00m[K\n"
> + " foo bar\n"));
> +}
> +
> +static void
> +test_borders ()
> +{
> + style_manager sm;
> + {
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 5),
> + styled_string (sm, "label 1"),
> + style::id_plain,
> + x_ruler::label_kind::TEXT_WITH_BORDER);
> + r.add_label (canvas::range_t (10, 15),
> + styled_string (sm, "label 2"),
> + style::id_plain);
> + r.add_label (canvas::range_t (20, 25),
> + styled_string (sm, "label 3"),
> + style::id_plain,
> + x_ruler::label_kind::TEXT_WITH_BORDER);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + "|~+~| |~+~| |~+~|\n"
> + " | | |\n"
> + " | label 2 +---+---+\n"
> + "+-+-----+ |label 3|\n"
> + "|label 1| +-------+\n"
> + "+-------+\n");
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + "├─┬─┤ ├─┬─┤ ├─┬─┤\n"
> + " │ │ │\n"
> + " │ label 2 ╭───┴───╮\n"
> + "╭─┴─────╮ │label 3│\n"
> + "│label 1│ ╰───────╯\n"
> + "╰───────╯\n");
> + }
> + {
> + x_ruler r (x_ruler::label_dir::ABOVE);
> + r.add_label (canvas::range_t (0, 5),
> + styled_string (sm, "label 1"),
> + style::id_plain,
> + x_ruler::label_kind::TEXT_WITH_BORDER);
> + r.add_label (canvas::range_t (10, 15),
> + styled_string (sm, "label 2"),
> + style::id_plain);
> + r.add_label (canvas::range_t (20, 25),
> + styled_string (sm, "label 3"),
> + style::id_plain,
> + x_ruler::label_kind::TEXT_WITH_BORDER);
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + "+-------+\n"
> + "|label 1| +-------+\n"
> + "+-+-----+ |label 3|\n"
> + " | label 2 +---+---+\n"
> + " | | |\n"
> + "|~+~| |~+~| |~+~|\n");
> + ASSERT_X_RULER_STREQ
> + (r, unicode_theme (), sm, true,
> + "╭───────╮\n"
> + "│label 1│ ╭───────╮\n"
> + "╰─┬─────╯ │label 3│\n"
> + " │ label 2 ╰───┬───╯\n"
> + " │ │ │\n"
> + "├─┴─┤ ├─┴─┤ ├─┴─┤\n");
> + }
> +}
> +
> +static void
> +test_emoji ()
> +{
> + style_manager sm;
> +
> + styled_string s;
> + s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
> + true));
> + s.append (styled_string (sm, " "));
> + s.append (styled_string (sm, "this is a warning"));
> +
> + x_ruler r (x_ruler::label_dir::BELOW);
> + r.add_label (canvas::range_t (0, 5),
> + std::move (s),
> + style::id_plain,
> + x_ruler::label_kind::TEXT_WITH_BORDER);
> +
> + ASSERT_X_RULER_STREQ
> + (r, ascii_theme (), sm, true,
> + "|~+~|\n"
> + " |\n"
> + "+-+------------------+\n"
> + "|⚠️ this is a warning|\n"
> + "+--------------------+\n");
> +}
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_ruler_cc_tests ()
> +{
> + test_single ();
> + test_single_above ();
> + test_multiple_contiguous ();
> + test_multiple_contiguous_above ();
> + test_multiple_contiguous_abutting_labels ();
> + test_multiple_contiguous_overlapping_labels ();
> + test_abutting_left_border ();
> + test_too_long_to_consolidate_vertically ();
> + test_abutting_neighbor ();
> + test_gaps ();
> + test_styled ();
> + test_borders ();
> + test_emoji ();
> +}
> +
> +} // namespace selftest
> +
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/ruler.h b/gcc/text-art/ruler.h
> new file mode 100644
> index 00000000000..31f53549836
> --- /dev/null
> +++ b/gcc/text-art/ruler.h
> @@ -0,0 +1,125 @@
> +/* Classes for printing labelled rulers.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_RULER_H
> +#define GCC_TEXT_ART_RULER_H
> +
> +#include "text-art/canvas.h"
> +
> +namespace text_art {
> +
> +/* A way to annotate a series of ranges of canvas coordinates
> + with text labels either above or, in this example, below:
> + ├───────┬───────┼───────┬───────┼───────┬───────┤
> + │ │ │
> + label A label B label C
> + with logic to ensure that the text labels don't overlap
> + when printed. */
> +
> +class x_ruler
> +{
> + public:
> + enum class label_dir { ABOVE, BELOW };
> + enum class label_kind
> + {
> + TEXT,
> + TEXT_WITH_BORDER
> + };
> +
> + x_ruler (label_dir dir)
> + : m_label_dir (dir),
> + m_size (canvas::size_t (0, 0)),
> + m_has_layout (false)
> + {}
> +
> + void add_label (const canvas::range_t &r,
> + styled_string text,
> + style::id_t style_id,
> + label_kind kind = label_kind::TEXT);
> +
> + canvas::size_t get_size ()
> + {
> + ensure_layout ();
> + return m_size;
> + }
> +
> + void paint_to_canvas (canvas &canvas,
> + canvas::coord_t offset,
> + const theme &theme);
> +
> + void debug (const style_manager &sm);
> +
> + private:
> + /* A particular label within an x_ruler.
> + Consider e.g.:
> +
> + # x: 01234567890123456789012345678901234567890123456789
> + # y: 0: ├───────┬───────┼───────┬───────┼───────┬───────┤
> + # 1: │ │ │
> + # 2: label A label B label C
> + #
> +
> + Then "label A" is:
> +
> + # m_connector_x == 8
> + # V
> + # x: 0123456789012
> + # y: 0: ┬
> + # 1: │
> + # 2: label A
> + # x: 0123456789012
> + # ^
> + # m_text_coord.x == 6
> +
> + and m_text_coord is (2, 6).
> + The y cooordinates are stored with respect to label_dir::BELOW;
> + for label_dir::ABOVE we flip them when painting the ruler. */
> + class label
> + {
> + friend class x_ruler;
> + public:
> + label (const canvas::range_t &range, styled_string text, style::id_t style_id,
> + label_kind kind);
> +
> + bool operator< (const label &other) const;
> +
> + private:
> + canvas::range_t m_range;
> + styled_string m_text;
> + style::id_t m_style_id;
> + label_kind m_kind;
> + canvas::rect_t m_text_rect; // includes any border
> + int m_connector_x;
> + };
> +
> + void ensure_layout ();
> + void update_layout ();
> + int get_canvas_y (int rel_y) const;
> +
> + label_dir m_label_dir;
> + std::vector<label> m_labels;
> + canvas::size_t m_size;
> + bool m_has_layout = false;
> +
> +};
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_RULER_H */
> diff --git a/gcc/text-art/selftests.cc b/gcc/text-art/selftests.cc
> new file mode 100644
> index 00000000000..60ad003b549
> --- /dev/null
> +++ b/gcc/text-art/selftests.cc
> @@ -0,0 +1,77 @@
> +/* Selftests for text art.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "selftest.h"
> +#include "pretty-print.h"
> +#include "text-art/selftests.h"
> +#include "text-art/canvas.h"
> +
> +#if CHECKING_P
> +
> +/* Run all tests, aborting if any fail. */
> +
> +void
> +selftest::text_art_tests ()
> +{
> + text_art_style_cc_tests ();
> + text_art_styled_string_cc_tests ();
> +
> + text_art_box_drawing_cc_tests ();
> + text_art_canvas_cc_tests ();
> + text_art_ruler_cc_tests ();
> + text_art_table_cc_tests ();
> + text_art_widget_cc_tests ();
> +}
> +
> +/* Implementation detail of ASSERT_CANVAS_STREQ. */
> +
> +void
> +selftest::assert_canvas_streq (const location &loc,
> + const text_art::canvas &canvas,
> + pretty_printer *pp,
> + const char *expected_str)
> +{
> + canvas.print_to_pp (pp);
> + if (0)
> + fprintf (stderr, "%s\n", pp_formatted_text (pp));
> + ASSERT_STREQ_AT (loc, pp_formatted_text (pp), expected_str);
> +}
> +
> +/* Implementation detail of ASSERT_CANVAS_STREQ. */
> +
> +void
> +selftest::assert_canvas_streq (const location &loc,
> + const text_art::canvas &canvas,
> + bool styled,
> + const char *expected_str)
> +{
> + pretty_printer pp;
> + if (styled)
> + {
> + pp_show_color (&pp) = true;
> + pp.url_format = URL_FORMAT_DEFAULT;
> + }
> + assert_canvas_streq (loc, canvas, &pp, expected_str);
> +}
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/selftests.h b/gcc/text-art/selftests.h
> new file mode 100644
> index 00000000000..706a1d8b5d6
> --- /dev/null
> +++ b/gcc/text-art/selftests.h
> @@ -0,0 +1,60 @@
> +/* Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_SELFTESTS_H
> +#define GCC_TEXT_ART_SELFTESTS_H
> +
> +#if CHECKING_P
> +
> +#include "text-art/types.h"
> +
> +namespace selftest {
> +
> +extern void text_art_box_drawing_cc_tests ();
> +extern void text_art_canvas_cc_tests ();
> +extern void text_art_ruler_cc_tests ();
> +extern void text_art_style_cc_tests ();
> +extern void text_art_styled_string_cc_tests ();
> +extern void text_art_table_cc_tests ();
> +extern void text_art_widget_cc_tests ();
> +
> +extern void text_art_tests ();
> +
> +extern void assert_canvas_streq (const location &loc,
> + const text_art::canvas &canvas,
> + pretty_printer *pp,
> + const char *expected_str);
> +extern void assert_canvas_streq (const location &loc,
> + const text_art::canvas &canvas,
> + bool styled,
> + const char *expected_str);
> +
> +#define ASSERT_CANVAS_STREQ(CANVAS, STYLED, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + assert_canvas_streq ((SELFTEST_LOCATION), \
> + (CANVAS), \
> + (STYLED), \
> + (EXPECTED_STR)); \
> + SELFTEST_END_STMT
> +
> +} /* end of namespace selftest. */
> +
> +#endif /* #if CHECKING_P */
> +
> +#endif /* GCC_TEXT_ART_SELFTESTS_H */
> diff --git a/gcc/text-art/style.cc b/gcc/text-art/style.cc
> new file mode 100644
> index 00000000000..00b056336fc
> --- /dev/null
> +++ b/gcc/text-art/style.cc
> @@ -0,0 +1,632 @@
> +/* Classes for styling text cells (color, URLs).
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#define INCLUDE_ALGORITHM
> +#define INCLUDE_MEMORY
> +#include "system.h"
> +#include "coretypes.h"
> +#include "make-unique.h"
> +#include "pretty-print.h"
> +#include "intl.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +#include "text-art/types.h"
> +#include "color-macros.h"
> +
> +using namespace text_art;
> +
> +/* class text_art::style. */
> +
> +style &
> +style::set_style_url (const char *url)
> +{
> + m_url.clear ();
> + while (*url)
> + m_url.push_back (*(url++));
> + return *this;
> +}
> +
> +/* class text_art::style::color. */
> +
> +bool
> +style::color::operator== (const style::color &other) const
> +{
> + if (m_kind != other.m_kind)
> + return false;
> + switch (m_kind)
> + {
> + default:
> + gcc_unreachable ();
> + case kind::NAMED:
> + return (u.m_named.m_name == other.u.m_named.m_name
> + && u.m_named.m_bright == other.u.m_named.m_bright);
> + case kind::BITS_8:
> + return u.m_8bit == other.u.m_8bit;
> + case kind::BITS_24:
> + return (u.m_24bit.r == other.u.m_24bit.r
> + && u.m_24bit.g == other.u.m_24bit.g
> + && u.m_24bit.b == other.u.m_24bit.b);
> + }
> +}
> +
> +static void
> +ensure_separator (pretty_printer *pp, bool &need_separator)
> +{
> + if (need_separator)
> + pp_string (pp, COLOR_SEPARATOR);
> + need_separator = true;
> +}
> +
> +void
> +style::color::print_sgr (pretty_printer *pp,
> + bool fg,
> + bool &need_separator) const
> +{
> + switch (m_kind)
> + {
> + default:
> + gcc_unreachable ();
> + case kind::NAMED:
> + {
> + static const char * const fg_normal[] = {"", // reset, for DEFAULT
> + COLOR_FG_BLACK,
> + COLOR_FG_RED,
> + COLOR_FG_GREEN,
> + COLOR_FG_YELLOW,
> + COLOR_FG_BLUE,
> + COLOR_FG_MAGENTA,
> + COLOR_FG_CYAN,
> + COLOR_FG_WHITE};
> + static const char * const fg_bright[] = {"", // reset, for DEFAULT
> + COLOR_FG_BRIGHT_BLACK,
> + COLOR_FG_BRIGHT_RED,
> + COLOR_FG_BRIGHT_GREEN,
> + COLOR_FG_BRIGHT_YELLOW,
> + COLOR_FG_BRIGHT_BLUE,
> + COLOR_FG_BRIGHT_MAGENTA,
> + COLOR_FG_BRIGHT_CYAN,
> + COLOR_FG_BRIGHT_WHITE};
> + static const char * const bg_normal[] = {"", // reset, for DEFAULT
> + COLOR_BG_BLACK,
> + COLOR_BG_RED,
> + COLOR_BG_GREEN,
> + COLOR_BG_YELLOW,
> + COLOR_BG_BLUE,
> + COLOR_BG_MAGENTA,
> + COLOR_BG_CYAN,
> + COLOR_BG_WHITE};
> + static const char * const bg_bright[] = {"", // reset, for DEFAULT
> + COLOR_BG_BRIGHT_BLACK,
> + COLOR_BG_BRIGHT_RED,
> + COLOR_BG_BRIGHT_GREEN,
> + COLOR_BG_BRIGHT_YELLOW,
> + COLOR_BG_BRIGHT_BLUE,
> + COLOR_BG_BRIGHT_MAGENTA,
> + COLOR_BG_BRIGHT_CYAN,
> + COLOR_BG_BRIGHT_WHITE};
> + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright));
> + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal));
> + STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright));
> + gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal));
> + const char *const *arr;
> + if (fg)
> + arr = u.m_named.m_bright ? fg_bright : fg_normal;
> + else
> + arr = u.m_named.m_bright ? bg_bright : bg_normal;
> + const char *str = arr[(size_t)u.m_named.m_name];
> + if (strlen (str) > 0)
> + {
> + ensure_separator (pp, need_separator);
> + pp_string (pp, str);
> + }
> + }
> + break;
> + case kind::BITS_8:
> + {
> + ensure_separator (pp, need_separator);
> + if (fg)
> + pp_string (pp, "38");
> + else
> + pp_string (pp, "48");
> + pp_printf (pp, ";5;%i", (int)u.m_8bit);
> + }
> + break;
> + case kind::BITS_24:
> + {
> + ensure_separator (pp, need_separator);
> + if (fg)
> + pp_string (pp, "38");
> + else
> + pp_string (pp, "48");
> + pp_printf (pp, ";2;%i;%i;%i",
> + (int)u.m_24bit.r,
> + (int)u.m_24bit.g,
> + (int)u.m_24bit.b);
> + }
> + break;
> + }
> +}
> +
> +/* class text_art::style. */
> +
> +/* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
> + GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or
> + "CUMULATIVE", which affects whether we need to respecify all attributes
> + at each SGR, or can accumulate them. Looks like we can't rely on the value
> + of this, so we have to emit a single SGR for all changes, with a "0" reset
> + at the front, forcing it to be effectively replacing. */
> +
> +void
> +style::print_changes (pretty_printer *pp,
> + const style &old_style,
> + const style &new_style)
> +{
> + if (pp_show_color (pp))
> + {
> + bool needs_sgr = ((old_style.m_bold != new_style.m_bold)
> + || (old_style.m_underscore != new_style.m_underscore)
> + || (old_style.m_blink != new_style.m_blink)
> + || (old_style.m_fg_color != new_style.m_fg_color)
> + || (old_style.m_bg_color != new_style.m_bg_color));
> + if (needs_sgr)
> + {
> + bool emit_reset = (old_style.m_bold
> + || new_style.m_bold
> + || old_style.m_underscore
> + || new_style.m_underscore
> + || old_style.m_blink
> + || new_style.m_blink);
> + bool need_separator = false;
> +
> + pp_string (pp, SGR_START);
> + if (emit_reset)
> + {
> + pp_string (pp, COLOR_NONE);
> + need_separator = true;
> + }
> + if (new_style.m_bold)
> + {
> + gcc_assert (emit_reset);
> + ensure_separator (pp, need_separator);
> + pp_string (pp, COLOR_BOLD);
> + }
> + if (new_style.m_underscore)
> + {
> + gcc_assert (emit_reset);
> + ensure_separator (pp, need_separator);
> + pp_string (pp, COLOR_UNDERSCORE);
> + }
> + if (new_style.m_blink)
> + {
> + gcc_assert (emit_reset);
> + ensure_separator (pp, need_separator);
> + pp_string (pp, COLOR_BLINK);
> + }
> + new_style.m_fg_color.print_sgr (pp, true, need_separator);
> + new_style.m_bg_color.print_sgr (pp, false, need_separator);
> + pp_string (pp, SGR_END);
> + }
> + }
> +
> + if (old_style.m_url != new_style.m_url)
> + {
> + if (!old_style.m_url.empty ())
> + pp_end_url (pp);
> + if (pp->url_format != URL_FORMAT_NONE
> + && !new_style.m_url.empty ())
> + {
> + /* Adapted from pp_begin_url, but encoding the
> + chars to UTF-8 on the fly, rather than converting
> + to a buffer. */
> + pp_string (pp, "\33]8;;");
> + for (auto ch : new_style.m_url)
> + pp_unicode_character (pp, ch);
> + switch (pp->url_format)
> + {
> + default:
> + case URL_FORMAT_NONE:
> + gcc_unreachable ();
> + case URL_FORMAT_ST:
> + pp_string (pp, "\33\\");
> + break;
> + case URL_FORMAT_BEL:
> + pp_string (pp, "\a");
> + break;
> + }
> + }
> + }
> +}
> +
> +/* class text_art::style_manager. */
> +
> +style_manager::style_manager ()
> +{
> + // index 0 will be the default style
> + m_styles.push_back (style ());
> +}
> +
> +style::id_t
> +style_manager::get_or_create_id (const style &s)
> +{
> + // For now, linear search
> + std::vector<style>::iterator existing
> + (std::find (m_styles.begin (), m_styles.end (), s));
> +
> + /* If found, return index of slot. */
> + if (existing != m_styles.end ())
> + return std::distance (m_styles.begin (), existing);
> +
> + /* Not found. */
> +
> + /* styled_str uses 7 bits for style information, so we can only support
> + up to 128 different style combinations.
> + Gracefully fail by turning off styling when this limit is reached. */
> + if (m_styles.size () >= 127)
> + return 0;
> +
> + m_styles.push_back (s);
> + return m_styles.size () - 1;
> +}
> +
> +void
> +style_manager::print_any_style_changes (pretty_printer *pp,
> + style::id_t old_id,
> + style::id_t new_id) const
> +{
> + gcc_assert (pp);
> + if (old_id == new_id)
> + return;
> +
> + const style &old_style = m_styles[old_id];
> + const style &new_style = m_styles[new_id];
> + gcc_assert (!(old_style == new_style));
> + style::print_changes (pp, old_style, new_style);
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +void
> +assert_style_change_streq (const location &loc,
> + const style &old_style,
> + const style &new_style,
> + const char *expected_str)
> +{
> + pretty_printer pp;
> + pp_show_color (&pp) = true;
> + style::print_changes (&pp, old_style, new_style);
> + ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str);
> +}
> +
> +#define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + assert_style_change_streq ((SELFTEST_LOCATION), \
> + (OLD_STYLE), \
> + (NEW_STYLE), \
> + (EXPECTED_STR)); \
> + SELFTEST_END_STMT
> +
> +static void
> +test_bold ()
> +{
> + style_manager sm;
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style plain;
> + ASSERT_EQ (sm.get_or_create_id (plain), 0);
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style bold;
> + bold.m_bold = true;
> +
> + ASSERT_EQ (sm.get_or_create_id (bold), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + ASSERT_EQ (sm.get_or_create_id (bold), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> +
> + ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K");
> + ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K");
> +}
> +
> +static void
> +test_underscore ()
> +{
> + style_manager sm;
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style plain;
> + ASSERT_EQ (sm.get_or_create_id (plain), 0);
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style underscore;
> + underscore.m_underscore = true;
> +
> + ASSERT_EQ (sm.get_or_create_id (underscore), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + ASSERT_EQ (sm.get_or_create_id (underscore), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> +
> + ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K");
> + ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K");
> +}
> +
> +static void
> +test_blink ()
> +{
> + style_manager sm;
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style plain;
> + ASSERT_EQ (sm.get_or_create_id (plain), 0);
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style blink;
> + blink.m_blink = true;
> +
> + ASSERT_EQ (sm.get_or_create_id (blink), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + ASSERT_EQ (sm.get_or_create_id (blink), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> +
> + ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K");
> + ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K");
> +}
> +
> +#define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + { \
> + style plain; \
> + style s; \
> + if (FG) \
> + s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
> + else \
> + s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
> + assert_style_change_streq ((SELFTEST_LOCATION), \
> + plain, \
> + s, \
> + (EXPECTED_STR)); \
> + } \
> + SELFTEST_END_STMT
> +
> +static void
> +test_named_colors ()
> +{
> + /* Foreground colors. */
> + {
> + const bool fg = true;
> + {
> + const bool bright = false;
> + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
> + "[30m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
> + "[31m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
> + "[32m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
> + "[33m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
> + "[34m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
> + "[35m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
> + "[36m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
> + "[37m[K");
> + }
> + {
> + const bool bright = true;
> + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
> + "[m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
> + "[90m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
> + "[91m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
> + "[92m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
> + "[93m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
> + "[94m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
> + "[95m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
> + "[96m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
> + "[97m[K");
> + }
> + }
> +
> + /* Background colors. */
> + {
> + const bool fg = false;
> + {
> + const bool bright = false;
> + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
> + "[40m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
> + "[41m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
> + "[42m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
> + "[43m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
> + "[44m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
> + "[45m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
> + "[46m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
> + "[47m[K");
> + }
> + {
> + const bool bright = true;
> + ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
> + "[m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
> + "[100m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
> + "[101m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
> + "[102m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
> + "[103m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
> + "[104m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
> + "[105m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
> + "[106m[K");
> + ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
> + "[107m[K");
> + }
> + }
> +}
> +
> +#define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + { \
> + style plain; \
> + style s; \
> + if (FG) \
> + s.m_fg_color = style::color (COL_VAL); \
> + else \
> + s.m_bg_color = style::color (COL_VAL); \
> + assert_style_change_streq ((SELFTEST_LOCATION), \
> + plain, \
> + s, \
> + (EXPECTED_STR)); \
> + } \
> + SELFTEST_END_STMT
> +
> +static void
> +test_8_bit_colors ()
> +{
> + /* Foreground colors. */
> + {
> + const bool fg = true;
> + /* 0-15: standard and high-intensity standard colors. */
> + ASSERT_8_BIT_COL_STREQ (0, fg, "[38;5;0m[K");
> + ASSERT_8_BIT_COL_STREQ (15, fg, "[38;5;15m[K");
> + /* 16-231: 6x6x6 color cube. */
> + ASSERT_8_BIT_COL_STREQ (16, fg, "[38;5;16m[K");
> + ASSERT_8_BIT_COL_STREQ (231, fg, "[38;5;231m[K");
> + /* 232-255: grayscale. */
> + ASSERT_8_BIT_COL_STREQ (232, fg, "[38;5;232m[K");
> + ASSERT_8_BIT_COL_STREQ (255, fg, "[38;5;255m[K");
> + }
> + /* Background colors. */
> + {
> + const bool fg = false;
> + /* 0-15: standard and high-intensity standard colors. */
> + ASSERT_8_BIT_COL_STREQ (0, fg, "[48;5;0m[K");
> + ASSERT_8_BIT_COL_STREQ (15, fg, "[48;5;15m[K");
> + /* 16-231: 6x6x6 color cube. */
> + ASSERT_8_BIT_COL_STREQ (16, fg, "[48;5;16m[K");
> + ASSERT_8_BIT_COL_STREQ (231, fg, "[48;5;231m[K");
> + /* 232-255: grayscale. */
> + ASSERT_8_BIT_COL_STREQ (232, fg, "[48;5;232m[K");
> + ASSERT_8_BIT_COL_STREQ (255, fg, "[48;5;255m[K");
> + }
> +}
> +
> +#define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + { \
> + style plain; \
> + style s; \
> + if (FG) \
> + s.m_fg_color = style::color ((R), (G), (B)); \
> + else \
> + s.m_bg_color = style::color ((R), (G), (B)); \
> + assert_style_change_streq ((SELFTEST_LOCATION), \
> + plain, \
> + s, \
> + (EXPECTED_STR)); \
> + } \
> + SELFTEST_END_STMT
> +
> +static void
> +test_24_bit_colors ()
> +{
> + /* Foreground colors. */
> + {
> + const bool fg = true;
> + // #F3FAF2:
> + ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg,
> + "[38;2;243;250;242m[K");
> + }
> + /* Background colors. */
> + {
> + const bool fg = false;
> + // #FDF7E7
> + ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg,
> + "[48;2;253;247;231m[K");
> + }
> +}
> +
> +static void
> +test_style_combinations ()
> +{
> + style_manager sm;
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style plain;
> + ASSERT_EQ (sm.get_or_create_id (plain), 0);
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + style bold;
> + bold.m_bold = true;
> +
> + ASSERT_EQ (sm.get_or_create_id (bold), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + ASSERT_EQ (sm.get_or_create_id (bold), 1);
> + ASSERT_EQ (sm.get_num_styles (), 2);
> +
> + style magenta_on_blue;
> + magenta_on_blue.m_fg_color = style::named_color::MAGENTA;
> + magenta_on_blue.m_bg_color = style::named_color::BLUE;
> + ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
> + ASSERT_EQ (sm.get_num_styles (), 3);
> + ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
> + ASSERT_EQ (sm.get_num_styles (), 3);
> +}
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_style_cc_tests ()
> +{
> + test_bold ();
> + test_underscore ();
> + test_blink ();
> + test_named_colors ();
> + test_8_bit_colors ();
> + test_24_bit_colors ();
> + test_style_combinations ();
> +}
> +
> +} // namespace selftest
> +
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/styled-string.cc b/gcc/text-art/styled-string.cc
> new file mode 100644
> index 00000000000..cd176b2313f
> --- /dev/null
> +++ b/gcc/text-art/styled-string.cc
> @@ -0,0 +1,1107 @@
> +/* Implementation of text_art::styled_string.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#define INCLUDE_MEMORY
> +#include "system.h"
> +#include "coretypes.h"
> +#include "make-unique.h"
> +#include "pretty-print.h"
> +#include "intl.h"
> +#include "diagnostic.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +#include "text-art/types.h"
> +#include "color-macros.h"
> +
> +using namespace text_art;
> +
> +namespace {
> +
> +/* Support class for parsing text containing escape codes.
> + See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code
> + We only support the codes that pretty-print.cc can generate. */
> +
> +class escape_code_parser
> +{
> +public:
> + escape_code_parser (style_manager &sm,
> + std::vector<styled_unichar> &out)
> + : m_sm (sm),
> + m_out (out),
> + m_cur_style_obj (),
> + m_cur_style_id (style::id_plain),
> + m_state (state::START)
> + {
> + }
> +
> + void on_char (cppchar_t ch)
> + {
> + switch (m_state)
> + {
> + default:
> + gcc_unreachable ();
> + case state::START:
> + if (ch == '\033')
> + {
> + /* The start of an escape sequence. */
> + m_state = state::AFTER_ESC;
> + return;
> + }
> + break;
> + case state::AFTER_ESC:
> + if (ch == '[')
> + {
> + /* ESC [ is a Control Sequence Introducer. */
> + m_state = state::CS_PARAMETER_BYTES;
> + return;
> + }
> + else if (ch == ']')
> + {
> + /* ESC ] is an Operating System Command. */
> + m_state = state::WITHIN_OSC;
> + return;
> + }
> + break;
> + case state::CS_PARAMETER_BYTES:
> + if (parameter_byte_p (ch))
> + {
> + m_parameter_bytes.push_back ((char)ch);
> + return;
> + }
> + else if (intermediate_byte_p (ch))
> + {
> + m_intermediate_bytes.push_back ((char)ch);
> + m_state = state::CS_INTERMEDIATE_BYTES;
> + return;
> + }
> + else if (final_byte_p (ch))
> + {
> + on_final_csi_char (ch);
> + return;
> + }
> + break;
> + case state::CS_INTERMEDIATE_BYTES:
> + /* Expect zero or more intermediate bytes. */
> + if (intermediate_byte_p (ch))
> + {
> + m_intermediate_bytes.push_back ((char)ch);
> + return;
> + }
> + else if (final_byte_p (ch))
> + {
> + on_final_csi_char (ch);
> + return;
> + }
> + break;
> + case state::WITHIN_OSC:
> + /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */
> + {
> + /* Check for ESC \, the String Terminator (aka "ST"). */
> + if (ch == '\\'
> + && m_osc_string.size () > 0
> + && m_osc_string.back () == '\033')
> + {
> + m_osc_string.pop_back ();
> + on_final_osc_char ();
> + return;
> + }
> + else if (ch == '\a')
> + {
> + // BEL
> + on_final_osc_char ();
> + return;
> + }
> + m_osc_string.push_back (ch);
> + return;
> + }
> + break;
> + }
> +
> + /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
> + variation for the previous character. */
> + if (ch == 0xFE0F)
> + {
> + if (m_out.size () > 0)
> + m_out.back ().set_emoji_variant ();
> + return;
> + }
> +
> + if (cpp_is_combining_char (ch))
> + {
> + if (m_out.size () > 0)
> + {
> + m_out.back ().add_combining_char (ch);
> + return;
> + }
> + }
> + /* By default, add the char. */
> + m_out.push_back (styled_unichar (ch, false, m_cur_style_id));
> + }
> +
> +private:
> + void on_final_csi_char (cppchar_t ch)
> + {
> + switch (ch)
> + {
> + default:
> + /* Unrecognized. */
> + break;
> + case 'm':
> + {
> + /* SGR control sequence. */
> + if (m_parameter_bytes.empty ())
> + reset_style ();
> + std::vector<int> params (params_from_decimal ());
> + for (auto iter = params.begin (); iter != params.end (); )
> + {
> + const int param = *iter;
> + switch (param)
> + {
> + default:
> + /* Unrecognized SGR parameter. */
> + break;
> + case 0:
> + reset_style ();
> + break;
> + case 1:
> + set_style_bold ();
> + break;
> + case 4:
> + set_style_underscore ();
> + break;
> + case 5:
> + set_style_blink ();
> + break;
> +
> + /* Named foreground colors. */
> + case 30:
> + set_style_fg_color (style::named_color::BLACK);
> + break;
> + case 31:
> + set_style_fg_color (style::named_color::RED);
> + break;
> + case 32:
> + set_style_fg_color (style::named_color::GREEN);
> + break;
> + case 33:
> + set_style_fg_color (style::named_color::YELLOW);
> + break;
> + case 34:
> + set_style_fg_color (style::named_color::BLUE);
> + break;
> + case 35:
> + set_style_fg_color (style::named_color::MAGENTA);
> + break;
> + case 36:
> + set_style_fg_color (style::named_color::CYAN);
> + break;
> + case 37:
> + set_style_fg_color (style::named_color::WHITE);
> + break;
> +
> + /* 8-bit and 24-bit color */
> + case 38:
> + case 48:
> + {
> + const bool fg = (param == 38);
> + iter++;
> + if (iter != params.end ())
> + switch (*(iter++))
> + {
> + default:
> + break;
> + case 5:
> + /* 8-bit color. */
> + if (iter != params.end ())
> + {
> + const uint8_t col = *(iter++);
> + if (fg)
> + set_style_fg_color (style::color (col));
> + else
> + set_style_bg_color (style::color (col));
> + }
> + continue;
> + case 2:
> + /* 24-bit color. */
> + if (iter != params.end ())
> + {
> + const uint8_t r = *(iter++);
> + if (iter != params.end ())
> + {
> + const uint8_t g = *(iter++);
> + if (iter != params.end ())
> + {
> + const uint8_t b = *(iter++);
> + if (fg)
> + set_style_fg_color (style::color (r,
> + g,
> + b));
> + else
> + set_style_bg_color (style::color (r,
> + g,
> + b));
> + }
> + }
> + }
> + continue;
> + }
> + continue;
> + }
> + break;
> +
> + /* Named background colors. */
> + case 40:
> + set_style_bg_color (style::named_color::BLACK);
> + break;
> + case 41:
> + set_style_bg_color (style::named_color::RED);
> + break;
> + case 42:
> + set_style_bg_color (style::named_color::GREEN);
> + break;
> + case 43:
> + set_style_bg_color (style::named_color::YELLOW);
> + break;
> + case 44:
> + set_style_bg_color (style::named_color::BLUE);
> + break;
> + case 45:
> + set_style_bg_color (style::named_color::MAGENTA);
> + break;
> + case 46:
> + set_style_bg_color (style::named_color::CYAN);
> + break;
> + case 47:
> + set_style_bg_color (style::named_color::WHITE);
> + break;
> +
> + /* Named foreground colors, bright. */
> + case 90:
> + set_style_fg_color (style::color (style::named_color::BLACK,
> + true));
> + break;
> + case 91:
> + set_style_fg_color (style::color (style::named_color::RED,
> + true));
> + break;
> + case 92:
> + set_style_fg_color (style::color (style::named_color::GREEN,
> + true));
> + break;
> + case 93:
> + set_style_fg_color (style::color (style::named_color::YELLOW,
> + true));
> + break;
> + case 94:
> + set_style_fg_color (style::color (style::named_color::BLUE,
> + true));
> + break;
> + case 95:
> + set_style_fg_color (style::color (style::named_color::MAGENTA,
> + true));
> + break;
> + case 96:
> + set_style_fg_color (style::color (style::named_color::CYAN,
> + true));
> + break;
> + case 97:
> + set_style_fg_color (style::color (style::named_color::WHITE,
> + true));
> + break;
> +
> + /* Named foreground colors, bright. */
> + case 100:
> + set_style_bg_color (style::color (style::named_color::BLACK,
> + true));
> + break;
> + case 101:
> + set_style_bg_color (style::color (style::named_color::RED,
> + true));
> + break;
> + case 102:
> + set_style_bg_color (style::color (style::named_color::GREEN,
> + true));
> + break;
> + case 103:
> + set_style_bg_color (style::color (style::named_color::YELLOW,
> + true));
> + break;
> + case 104:
> + set_style_bg_color (style::color (style::named_color::BLUE,
> + true));
> + break;
> + case 105:
> + set_style_bg_color (style::color (style::named_color::MAGENTA,
> + true));
> + break;
> + case 106:
> + set_style_bg_color (style::color (style::named_color::CYAN,
> + true));
> + break;
> + case 107:
> + set_style_bg_color (style::color (style::named_color::WHITE,
> + true));
> + break;
> + }
> + ++iter;
> + }
> + }
> + break;
> + }
> + m_parameter_bytes.clear ();
> + m_intermediate_bytes.clear ();
> + m_state = state::START;
> + }
> +
> + void on_final_osc_char ()
> + {
> + if (!m_osc_string.empty ())
> + {
> + switch (m_osc_string[0])
> + {
> + default:
> + break;
> + case '8':
> + /* Hyperlink support; see:
> + https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
> + We don't support params, so we expect either:
> + (a) "8;;URL" to begin a url (see pp_begin_url), or
> + (b) "8;;" to end a URL (see pp_end_url). */
> + if (m_osc_string.size () >= 3
> + && m_osc_string[1] == ';'
> + && m_osc_string[2] == ';')
> + {
> + set_style_url (m_osc_string.begin () + 3,
> + m_osc_string.end ());
> + }
> + break;
> + }
> + }
> + m_osc_string.clear ();
> + m_state = state::START;
> + }
> +
> + std::vector<int> params_from_decimal () const
> + {
> + std::vector<int> result;
> +
> + int curr_int = -1;
> + for (auto param_ch : m_parameter_bytes)
> + {
> + if (param_ch >= '0' && param_ch <= '9')
> + {
> + if (curr_int == -1)
> + curr_int = 0;
> + else
> + curr_int *= 10;
> + curr_int += param_ch - '0';
> + }
> + else
> + {
> + if (curr_int != -1)
> + {
> + result.push_back (curr_int);
> + curr_int = -1;
> + }
> + }
> + }
> + if (curr_int != -1)
> + result.push_back (curr_int);
> + return result;
> + }
> +
> + void refresh_style_id ()
> + {
> + m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj);
> + }
> + void reset_style ()
> + {
> + m_cur_style_obj = style ();
> + refresh_style_id ();
> + }
> + void set_style_bold ()
> + {
> + m_cur_style_obj.m_bold = true;
> + refresh_style_id ();
> + }
> + void set_style_underscore ()
> + {
> + m_cur_style_obj.m_underscore = true;
> + refresh_style_id ();
> + }
> + void set_style_blink ()
> + {
> + m_cur_style_obj.m_blink = true;
> + refresh_style_id ();
> + }
> + void set_style_fg_color (style::color color)
> + {
> + m_cur_style_obj.m_fg_color = color;
> + refresh_style_id ();
> + }
> + void set_style_bg_color (style::color color)
> + {
> + m_cur_style_obj.m_bg_color = color;
> + refresh_style_id ();
> + }
> + void set_style_url (std::vector<cppchar_t>::iterator begin,
> + std::vector<cppchar_t>::iterator end)
> + {
> + // The empty string means "no URL"
> + m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end);
> + refresh_style_id ();
> + }
> +
> + static bool parameter_byte_p (cppchar_t ch)
> + {
> + return ch >= 0x30 && ch <= 0x3F;
> + }
> +
> + static bool intermediate_byte_p (cppchar_t ch)
> + {
> + return ch >= 0x20 && ch <= 0x2F;
> + }
> +
> + static bool final_byte_p (cppchar_t ch)
> + {
> + return ch >= 0x40 && ch <= 0x7E;
> + }
> +
> + style_manager &m_sm;
> + std::vector<styled_unichar> &m_out;
> +
> + style m_cur_style_obj;
> + style::id_t m_cur_style_id;
> +
> + /* Handling of control sequences. */
> + enum class state
> + {
> + START,
> +
> + /* After ESC, expecting '['. */
> + AFTER_ESC,
> +
> + /* Expecting zero or more parameter bytes, an
> + intermediate byte, or a final byte. */
> + CS_PARAMETER_BYTES,
> +
> + /* Expecting zero or more intermediate bytes, or a final byte. */
> + CS_INTERMEDIATE_BYTES,
> +
> + /* Within OSC. */
> + WITHIN_OSC
> +
> + } m_state;
> + std::vector<char> m_parameter_bytes;
> + std::vector<char> m_intermediate_bytes;
> + std::vector<cppchar_t> m_osc_string;
> +};
> +
> +} // anon namespace
> +
> +/* class text_art::styled_string. */
> +
> +/* Construct a styled_string from STR.
> + STR is assumed to be UTF-8 encoded and 0-terminated.
> +
> + Parse SGR formatting chars from being in-band (within in the sequence
> + of chars) to being out-of-band, as style elements.
> + We only support parsing the subset of SGR chars that can be emitted
> + by pretty-print.cc */
> +
> +styled_string::styled_string (style_manager &sm, const char *str)
> +: m_chars ()
> +{
> + escape_code_parser parser (sm, m_chars);
> +
> + /* We don't actually want the display widths here, but
> + it's an easy way to decode UTF-8. */
> + cpp_char_column_policy policy (8, cpp_wcwidth);
> + cpp_display_width_computation dw (str, strlen (str), policy);
> + while (!dw.done ())
> + {
> + cpp_decoded_char decoded_char;
> + dw.process_next_codepoint (&decoded_char);
> +
> + if (!decoded_char.m_valid_ch)
> + /* Skip bytes that aren't valid UTF-8. */
> + continue;
> +
> + /* Decode SGR formatting. */
> + cppchar_t ch = decoded_char.m_ch;
> + parser.on_char (ch);
> + }
> +}
> +
> +styled_string::styled_string (cppchar_t cppchar, bool emoji)
> +{
> + m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain));
> +}
> +
> +styled_string
> +styled_string::from_fmt_va (style_manager &sm,
> + printer_fn format_decoder,
> + const char *fmt,
> + va_list *args)
> +{
> + text_info text;
> + text.err_no = errno;
> + text.args_ptr = args;
> + text.format_spec = fmt;
> + pretty_printer pp;
> + pp_show_color (&pp) = true;
> + pp.url_format = URL_FORMAT_DEFAULT;
> + pp_format_decoder (&pp) = format_decoder;
> + pp_format (&pp, &text);
> + pp_output_formatted_text (&pp);
> + styled_string result (sm, pp_formatted_text (&pp));
> + return result;
> +}
> +
> +styled_string
> +styled_string::from_fmt (style_manager &sm,
> + printer_fn format_decoder,
> + const char *fmt, ...)
> +{
> + va_list ap;
> + va_start (ap, fmt);
> + styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap);
> + va_end (ap);
> + return result;
> +}
> +
> +int
> +styled_string::calc_canvas_width () const
> +{
> + int result = 0;
> + for (auto ch : m_chars)
> + result += ch.get_canvas_width ();
> + return result;
> +}
> +
> +void
> +styled_string::append (const styled_string &suffix)
> +{
> + m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (),
> + suffix.begin (),
> + suffix.end ());
> +}
> +
> +void
> +styled_string::set_url (style_manager &sm, const char *url)
> +{
> + for (auto& ch : m_chars)
> + {
> + const style &existing_style = sm.get_style (ch.get_style_id ());
> + style with_url (existing_style);
> + with_url.set_style_url (url);
> + ch.m_style_id = sm.get_or_create_id (with_url);
> + }
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +static void
> +test_combining_chars ()
> +{
> + /* This really ought to be in libcpp, but we don't have
> + selftests there. */
> + ASSERT_FALSE (cpp_is_combining_char (0));
> + ASSERT_FALSE (cpp_is_combining_char ('a'));
> +
> + /* COMBINING BREVE (U+0306). */
> + ASSERT_TRUE (cpp_is_combining_char (0x0306));
> +
> + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */
> + ASSERT_FALSE (cpp_is_combining_char (0x5B57));
> +
> + /* U+FE0F VARIATION SELECTOR-16. */
> + ASSERT_FALSE (cpp_is_combining_char (0xFE0F));
> +}
> +
> +static void
> +test_empty ()
> +{
> + style_manager sm;
> + styled_string s (sm, "");
> + ASSERT_EQ (s.size (), 0);
> + ASSERT_EQ (s.calc_canvas_width (), 0);
> +}
> +
> +/* Test of a pure ASCII string with no escape codes. */
> +
> +static void
> +test_simple ()
> +{
> + const char *c_str = "hello world!";
> + style_manager sm;
> + styled_string s (sm, c_str);
> + ASSERT_EQ (s.size (), strlen (c_str));
> + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str));
> + for (size_t i = 0; i < strlen (c_str); i++)
> + {
> + ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]);
> + ASSERT_EQ (s[i].get_style_id (), 0);
> + }
> +}
> +
> +/* Test of decoding UTF-8. */
> +
> +static void
> +test_pi_from_utf8 ()
> +{
> + /* U+03C0 "GREEK SMALL LETTER PI". */
> + const char * const pi_utf8 = "\xCF\x80";
> +
> + style_manager sm;
> + styled_string s (sm, pi_utf8);
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s.calc_canvas_width (), 1);
> + ASSERT_EQ (s[0].get_code (), 0x03c0);
> + ASSERT_EQ (s[0].emoji_variant_p (), false);
> + ASSERT_EQ (s[0].double_width_p (), false);
> + ASSERT_EQ (s[0].get_style_id (), 0);
> +}
> +
> +/* Test of double-width character. */
> +
> +static void
> +test_emoji_from_utf8 ()
> +{
> + /* U+1F642 "SLIGHTLY SMILING FACE". */
> + const char * const emoji_utf8 = "\xF0\x9F\x99\x82";
> +
> + style_manager sm;
> + styled_string s (sm, emoji_utf8);
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s.calc_canvas_width (), 2);
> + ASSERT_EQ (s[0].get_code (), 0x1f642);
> + ASSERT_EQ (s[0].double_width_p (), true);
> + ASSERT_EQ (s[0].get_style_id (), 0);
> +}
> +
> +/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
> + variation for the previous character. */
> +
> +static void
> +test_emoji_variant_from_utf8 ()
> +{
> + const char * const emoji_utf8
> + = (/* U+26A0 WARNING SIGN. */
> + "\xE2\x9A\xA0"
> + /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */
> + "\xEF\xB8\x8F");
> +
> + style_manager sm;
> + styled_string s (sm, emoji_utf8);
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s.calc_canvas_width (), 1);
> + ASSERT_EQ (s[0].get_code (), 0x26a0);
> + ASSERT_EQ (s[0].emoji_variant_p (), true);
> + ASSERT_EQ (s[0].double_width_p (), false);
> + ASSERT_EQ (s[0].get_style_id (), 0);
> +}
> +
> +static void
> +test_emoji_from_codepoint ()
> +{
> + styled_string s ((cppchar_t)0x1f642);
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s.calc_canvas_width (), 2);
> + ASSERT_EQ (s[0].get_code (), 0x1f642);
> + ASSERT_EQ (s[0].double_width_p (), true);
> + ASSERT_EQ (s[0].get_style_id (), 0);
> +}
> +
> +static void
> +test_from_mixed_width_utf8 ()
> +{
> + /* This UTF-8 string literal is of the form
> + before mojibake after
> + where the Japanese word "mojibake" is written as the following
> + four unicode code points:
> + U+6587 CJK UNIFIED IDEOGRAPH-6587
> + U+5B57 CJK UNIFIED IDEOGRAPH-5B57
> + U+5316 CJK UNIFIED IDEOGRAPH-5316
> + U+3051 HIRAGANA LETTER KE.
> + Each of these is 3 bytes wide when encoded in UTF-8, whereas the
> + "before" and "after" are 1 byte per unicode character. */
> + const char * const mixed_width_utf8
> + = ("before "
> +
> + /* U+6587 CJK UNIFIED IDEOGRAPH-6587
> + UTF-8: 0xE6 0x96 0x87
> + C octal escaped UTF-8: \346\226\207. */
> + "\346\226\207"
> +
> + /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57
> + UTF-8: 0xE5 0xAD 0x97
> + C octal escaped UTF-8: \345\255\227. */
> + "\345\255\227"
> +
> + /* U+5316 CJK UNIFIED IDEOGRAPH-5316
> + UTF-8: 0xE5 0x8C 0x96
> + C octal escaped UTF-8: \345\214\226. */
> + "\345\214\226"
> +
> + /* U+3051 HIRAGANA LETTER KE
> + UTF-8: 0xE3 0x81 0x91
> + C octal escaped UTF-8: \343\201\221. */
> + "\343\201\221"
> +
> + " after");
> +
> + style_manager sm;
> + styled_string s (sm, mixed_width_utf8);
> + ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5);
> + ASSERT_EQ (sm.get_num_styles (), 1);
> +
> + // We expect the Japanese characters to be double width.
> + ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5);
> +
> + ASSERT_EQ (s[0].get_code (), 'b');
> + ASSERT_EQ (s[0].double_width_p (), false);
> + ASSERT_EQ (s[1].get_code (), 'e');
> + ASSERT_EQ (s[2].get_code (), 'f');
> + ASSERT_EQ (s[3].get_code (), 'o');
> + ASSERT_EQ (s[4].get_code (), 'r');
> + ASSERT_EQ (s[5].get_code (), 'e');
> + ASSERT_EQ (s[6].get_code (), ' ');
> + ASSERT_EQ (s[7].get_code (), 0x6587);
> + ASSERT_EQ (s[7].double_width_p (), true);
> + ASSERT_EQ (s[8].get_code (), 0x5B57);
> + ASSERT_EQ (s[9].get_code (), 0x5316);
> + ASSERT_EQ (s[10].get_code (), 0x3051);
> + ASSERT_EQ (s[11].get_code (), ' ');
> + ASSERT_EQ (s[12].get_code (), 'a');
> + ASSERT_EQ (s[13].get_code (), 'f');
> + ASSERT_EQ (s[14].get_code (), 't');
> + ASSERT_EQ (s[15].get_code (), 'e');
> + ASSERT_EQ (s[16].get_code (), 'r');
> +
> + ASSERT_EQ (s[0].get_style_id (), 0);
> +}
> +
> +static void
> +assert_style_urleq (const location &loc,
> + const style &s,
> + const char *expected_str)
> +{
> + ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str));
> + for (size_t i = 0; i < s.m_url.size (); i++)
> + ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]);
> +}
> +
> +#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \
> + assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR))
> +
> +static void
> +test_url ()
> +{
> + // URL_FORMAT_ST
> + {
> + style_manager sm;
> + styled_string s
> + (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\");
> + const char *expected = "This is a link";
> + ASSERT_EQ (s.size (), strlen (expected));
> + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + for (size_t i = 0; i < strlen (expected); i++)
> + {
> + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
> + ASSERT_EQ (s[i].get_style_id (), 1);
> + }
> + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
> + }
> +
> + // URL_FORMAT_BEL
> + {
> + style_manager sm;
> + styled_string s
> + (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a");
> + const char *expected = "This is a link";
> + ASSERT_EQ (s.size (), strlen (expected));
> + ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + for (size_t i = 0; i < strlen (expected); i++)
> + {
> + ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
> + ASSERT_EQ (s[i].get_style_id (), 1);
> + }
> + ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
> + }
> +}
> +
> +static void
> +test_from_fmt ()
> +{
> + style_manager sm;
> + styled_string s (styled_string::from_fmt (sm, NULL, "%%i: %i", 42));
> + ASSERT_EQ (s[0].get_code (), '%');
> + ASSERT_EQ (s[1].get_code (), 'i');
> + ASSERT_EQ (s[2].get_code (), ':');
> + ASSERT_EQ (s[3].get_code (), ' ');
> + ASSERT_EQ (s[4].get_code (), '4');
> + ASSERT_EQ (s[5].get_code (), '2');
> + ASSERT_EQ (s.size (), 6);
> + ASSERT_EQ (s.calc_canvas_width (), 6);
> +}
> +
> +static void
> +test_from_fmt_qs ()
> +{
> + auto_fix_quotes fix_quotes;
> + open_quote = "\xe2\x80\x98";
> + close_quote = "\xe2\x80\x99";
> +
> + style_manager sm;
> + styled_string s (styled_string::from_fmt (sm, NULL, "%qs", "msg"));
> + ASSERT_EQ (sm.get_num_styles (), 2);
> + ASSERT_EQ (s[0].get_code (), 0x2018);
> + ASSERT_EQ (s[0].get_style_id (), 0);
> + ASSERT_EQ (s[1].get_code (), 'm');
> + ASSERT_EQ (s[1].get_style_id (), 1);
> + ASSERT_EQ (s[2].get_code (), 's');
> + ASSERT_EQ (s[2].get_style_id (), 1);
> + ASSERT_EQ (s[3].get_code (), 'g');
> + ASSERT_EQ (s[3].get_style_id (), 1);
> + ASSERT_EQ (s[4].get_code (), 0x2019);
> + ASSERT_EQ (s[4].get_style_id (), 0);
> + ASSERT_EQ (s.size (), 5);
> +}
> +
> +// Test of parsing SGR codes.
> +
> +static void
> +test_from_str_with_bold ()
> +{
> + style_manager sm;
> + /* This is the result of pp_printf (pp, "%qs", "foo")
> + with auto_fix_quotes. */
> + styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'");
> + ASSERT_EQ (s[0].get_code (), '`');
> + ASSERT_EQ (s[0].get_style_id (), 0);
> + ASSERT_EQ (s[1].get_code (), 'f');
> + ASSERT_EQ (s[1].get_style_id (), 1);
> + ASSERT_EQ (s[2].get_code (), 'o');
> + ASSERT_EQ (s[2].get_style_id (), 1);
> + ASSERT_EQ (s[3].get_code (), 'o');
> + ASSERT_EQ (s[3].get_style_id (), 1);
> + ASSERT_EQ (s[4].get_code (), '\'');
> + ASSERT_EQ (s[4].get_style_id (), 0);
> + ASSERT_EQ (s.size (), 5);
> + ASSERT_TRUE (sm.get_style (1).m_bold);
> +}
> +
> +static void
> +test_from_str_with_underscore ()
> +{
> + style_manager sm;
> + styled_string s (sm, "\33[04m\33[KA");
> + ASSERT_EQ (s[0].get_code (), 'A');
> + ASSERT_EQ (s[0].get_style_id (), 1);
> + ASSERT_TRUE (sm.get_style (1).m_underscore);
> +}
> +
> +static void
> +test_from_str_with_blink ()
> +{
> + style_manager sm;
> + styled_string s (sm, "\33[05m\33[KA");
> + ASSERT_EQ (s[0].get_code (), 'A');
> + ASSERT_EQ (s[0].get_style_id (), 1);
> + ASSERT_TRUE (sm.get_style (1).m_blink);
> +}
> +
> +// Test of parsing SGR codes.
> +
> +static void
> +test_from_str_with_color ()
> +{
> + style_manager sm;
> +
> + styled_string s (sm,
> + ("0"
> + SGR_SEQ (COLOR_FG_RED)
> + "R"
> + SGR_RESET
> + "2"
> + SGR_SEQ (COLOR_FG_GREEN)
> + "G"
> + SGR_RESET
> + "4"));
> + ASSERT_EQ (s.size (), 5);
> + ASSERT_EQ (sm.get_num_styles (), 3);
> + ASSERT_EQ (s[0].get_code (), '0');
> + ASSERT_EQ (s[0].get_style_id (), 0);
> + ASSERT_EQ (s[1].get_code (), 'R');
> + ASSERT_EQ (s[1].get_style_id (), 1);
> + ASSERT_EQ (s[2].get_code (), '2');
> + ASSERT_EQ (s[2].get_style_id (), 0);
> + ASSERT_EQ (s[3].get_code (), 'G');
> + ASSERT_EQ (s[3].get_style_id (), 2);
> + ASSERT_EQ (s[4].get_code (), '4');
> + ASSERT_EQ (s[4].get_style_id (), 0);
> + ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED);
> + ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN);
> +}
> +
> +static void
> +test_from_str_with_named_color ()
> +{
> + style_manager sm;
> + styled_string s (sm,
> + ("F"
> + SGR_SEQ (COLOR_FG_BLACK) "F"
> + SGR_SEQ (COLOR_FG_RED) "F"
> + SGR_SEQ (COLOR_FG_GREEN) "F"
> + SGR_SEQ (COLOR_FG_YELLOW) "F"
> + SGR_SEQ (COLOR_FG_BLUE) "F"
> + SGR_SEQ (COLOR_FG_MAGENTA) "F"
> + SGR_SEQ (COLOR_FG_CYAN) "F"
> + SGR_SEQ (COLOR_FG_WHITE) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_RED) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F"
> + SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F"
> + SGR_SEQ (COLOR_BG_BLACK) "B"
> + SGR_SEQ (COLOR_BG_RED) "B"
> + SGR_SEQ (COLOR_BG_GREEN) "B"
> + SGR_SEQ (COLOR_BG_YELLOW) "B"
> + SGR_SEQ (COLOR_BG_BLUE) "B"
> + SGR_SEQ (COLOR_BG_MAGENTA) "B"
> + SGR_SEQ (COLOR_BG_CYAN) "B"
> + SGR_SEQ (COLOR_BG_WHITE) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_RED) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B"
> + SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B"));
> + ASSERT_EQ (s.size (), 33);
> + for (size_t i = 0; i < s.size (); i++)
> + ASSERT_EQ (s[i].get_style_id (), i);
> + for (size_t i = 0; i < 17; i++)
> + ASSERT_EQ (s[i].get_code (), 'F');
> + for (size_t i = 17; i < 33; i++)
> + ASSERT_EQ (s[i].get_code (), 'B');
> +}
> +
> +static void
> +test_from_str_with_8_bit_color ()
> +{
> + {
> + style_manager sm;
> + styled_string s (sm,
> + ("[38;5;232m[KF"));
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s[0].get_code (), 'F');
> + ASSERT_EQ (s[0].get_style_id (), 1);
> + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232));
> + }
> + {
> + style_manager sm;
> + styled_string s (sm,
> + ("[48;5;231m[KB"));
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s[0].get_code (), 'B');
> + ASSERT_EQ (s[0].get_style_id (), 1);
> + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231));
> + }
> +}
> +
> +static void
> +test_from_str_with_24_bit_color ()
> +{
> + {
> + style_manager sm;
> + styled_string s (sm,
> + ("[38;2;243;250;242m[KF"));
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s[0].get_code (), 'F');
> + ASSERT_EQ (s[0].get_style_id (), 1);
> + ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242));
> + }
> + {
> + style_manager sm;
> + styled_string s (sm,
> + ("[48;2;253;247;231m[KB"));
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s[0].get_code (), 'B');
> + ASSERT_EQ (s[0].get_style_id (), 1);
> + ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231));
> + }
> +}
> +
> +static void
> +test_from_str_combining_characters ()
> +{
> + style_manager sm;
> + styled_string s (sm,
> + /* CYRILLIC CAPITAL LETTER U (U+0423). */
> + "\xD0\xA3"
> + /* COMBINING BREVE (U+0306). */
> + "\xCC\x86");
> + ASSERT_EQ (s.size (), 1);
> + ASSERT_EQ (s[0].get_code (), 0x423);
> + ASSERT_EQ (s[0].get_combining_chars ().size (), 1);
> + ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306);
> +}
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_styled_string_cc_tests ()
> +{
> + test_combining_chars ();
> + test_empty ();
> + test_simple ();
> + test_pi_from_utf8 ();
> + test_emoji_from_utf8 ();
> + test_emoji_variant_from_utf8 ();
> + test_emoji_from_codepoint ();
> + test_from_mixed_width_utf8 ();
> + test_url ();
> + test_from_fmt ();
> + test_from_fmt_qs ();
> + test_from_str_with_bold ();
> + test_from_str_with_underscore ();
> + test_from_str_with_blink ();
> + test_from_str_with_color ();
> + test_from_str_with_named_color ();
> + test_from_str_with_8_bit_color ();
> + test_from_str_with_24_bit_color ();
> + test_from_str_combining_characters ();
> +}
> +
> +} // namespace selftest
> +
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/table.cc b/gcc/text-art/table.cc
> new file mode 100644
> index 00000000000..42cc4228ea6
> --- /dev/null
> +++ b/gcc/text-art/table.cc
> @@ -0,0 +1,1272 @@
> +/* Support for tabular/grid-based content.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#define INCLUDE_MEMORY
> +#include "system.h"
> +#include "coretypes.h"
> +#include "make-unique.h"
> +#include "pretty-print.h"
> +#include "diagnostic.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +#include "text-art/table.h"
> +
> +using namespace text_art;
> +
> +/* class text_art::table_cell_content. */
> +
> +table_cell_content::table_cell_content (styled_string &&s)
> +: m_str (std::move (s)),
> + /* We assume here that the content occupies a single canvas row. */
> + m_size (m_str.calc_canvas_width (), 1)
> +{
> +}
> +
> +void
> +table_cell_content::paint_to_canvas (canvas &canvas,
> + canvas::coord_t top_left) const
> +{
> + canvas.paint_text (top_left, m_str);
> +}
> +
> +/* struct text_art::table_dimension_sizes. */
> +
> +table_dimension_sizes::table_dimension_sizes (unsigned num)
> +: m_requirements (num, 0)
> +{
> +}
> +
> +/* class text_art::table::cell_placement. */
> +
> +void
> +table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg) const
> +{
> + const canvas::size_t req_canvas_size = get_min_canvas_size ();
> + const canvas::size_t alloc_canvas_size = tg.get_canvas_size (m_rect);
> + gcc_assert (req_canvas_size.w <= alloc_canvas_size.w);
> + gcc_assert (req_canvas_size.h <= alloc_canvas_size.h);
> + const int x_padding = alloc_canvas_size.w - req_canvas_size.w;
> + const int y_padding = alloc_canvas_size.h - req_canvas_size.h;
> + const table::coord_t table_top_left = m_rect.m_top_left;
> + const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_top_left);
> +
> + gcc_assert (x_padding >= 0);
> + int x_align_offset;
> + switch (m_x_align)
> + {
> + default:
> + gcc_unreachable ();
> + case x_align::LEFT:
> + x_align_offset = 0;
> + break;
> + case x_align::CENTER:
> + x_align_offset = x_padding / 2;
> + break;
> + case x_align::RIGHT:
> + x_align_offset = x_padding;
> + break;
> + }
> +
> + gcc_assert (y_padding >= 0);
> + int y_align_offset;
> + switch (m_y_align)
> + {
> + default:
> + gcc_unreachable ();
> + case y_align::TOP:
> + y_align_offset = 0;
> + break;
> + case y_align::CENTER:
> + y_align_offset = y_padding / 2;
> + break;
> + case y_align::BOTTOM:
> + y_align_offset = y_padding;
> + break;
> + }
> + const canvas::coord_t content_rel_coord
> + (canvas_top_left.x + 1 + x_align_offset,
> + canvas_top_left.y + 1 + y_align_offset);
> + m_content.paint_to_canvas (canvas, offset + content_rel_coord);
> +}
> +
> +/* class text_art::table. */
> +
> +
> +table::table (size_t size)
> +: m_size (size),
> + m_placements (),
> + m_occupancy (size)
> +{
> + m_occupancy.fill (-1);
> +}
> +
> +void
> +table::set_cell (coord_t coord,
> + table_cell_content &&content,
> + enum x_align x_align,
> + enum y_align y_align)
> +{
> + set_cell_span (rect_t (coord, table::size_t (1, 1)),
> + std::move (content), x_align, y_align);
> +}
> +
> +void
> +table::set_cell_span (rect_t span,
> + table_cell_content &&content,
> + enum x_align x_align,
> + enum y_align y_align)
> +{
> + gcc_assert (span.m_size.w > 0);
> + gcc_assert (span.m_size.h > 0);
> + int placement_idx = m_placements.size ();
> + m_placements.emplace_back (cell_placement (span, std::move (content),
> + x_align, y_align));
> + for (int y = span.get_min_y (); y < span.get_next_y (); y++)
> + for (int x = span.get_min_x (); x < span.get_next_x (); x++)
> + {
> + gcc_assert (m_occupancy.get (coord_t (x, y)) == -1);
> + m_occupancy.set (coord_t (x, y), placement_idx);
> + }
> +}
> +
> +canvas
> +table::to_canvas (const theme &theme, const style_manager &sm) const
> +{
> + table_dimension_sizes col_widths (m_size.w);
> + table_dimension_sizes row_heights (m_size.h);
> + table_cell_sizes cell_sizes (col_widths, row_heights);
> + cell_sizes.pass_1 (*this);
> + cell_sizes.pass_2 (*this);
> + table_geometry tg (*this, cell_sizes);
> + canvas canvas (tg.get_canvas_size (), sm);
> + paint_to_canvas (canvas, canvas::coord_t (0, 0), tg, theme);
> + return canvas;
> +}
> +
> +void
> +table::paint_to_canvas (canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg,
> + const theme &theme) const
> +{
> + canvas.fill (canvas::rect_t (offset, tg.get_canvas_size ()),
> + styled_unichar (' '));
> + paint_cell_borders_to_canvas (canvas, offset, tg, theme);
> + paint_cell_contents_to_canvas (canvas, offset, tg);
> +}
> +
> +/* Print this table to stderr. */
> +
> +DEBUG_FUNCTION void
> +table::debug () const
> +{
> + /* Use a temporary style manager.
> + Styles in the table will be meaningless, so
> + print the canvas with styling disabled. */
> + style_manager sm;
> + canvas canvas (to_canvas (unicode_theme (), sm));
> + canvas.debug (false);
> +}
> +
> +const table::cell_placement *
> +table::get_placement_at (coord_t coord) const
> +{
> + const int placement_idx = m_occupancy.get (coord);
> + if (placement_idx == -1)
> + return nullptr;
> + return &m_placements[placement_idx];
> +}
> +
> +int
> +table::get_occupancy_safe (coord_t coord) const
> +{
> + if (coord.x < 0)
> + return -1;
> + if (coord.x >= m_size.w)
> + return -1;
> + if (coord.y < 0)
> + return -1;
> + if (coord.y >= m_size.h)
> + return -1;
> + return m_occupancy.get (coord);
> +}
> +
> +/* Determine if the "?" edges need borders for table cell D
> + in the following, for the directions relative to "X", based
> + on whether each of table cell boundaries AB, CD, AC, and BD
> + are boundaries between cell spans:
> +
> + # up?
> + # +-----+-----+
> + # | |
> + # | ? |
> + # | A ? B |
> + # | ? |
> + # | |
> + # left?+ ??? X ??? + right?
> + # | |
> + # | ? |
> + # | C ? D |
> + # | ? |
> + # | |
> + # +-----+-----+
> + # down?
> +*/
> +
> +directions
> +table::get_connections (int table_x, int table_y) const
> +{
> + int cell_a = get_occupancy_safe (coord_t (table_x - 1, table_y - 1));
> + int cell_b = get_occupancy_safe (coord_t (table_x, table_y - 1));
> + int cell_c = get_occupancy_safe (coord_t (table_x - 1, table_y));
> + int cell_d = get_occupancy_safe (coord_t (table_x, table_y));
> + const bool up = (cell_a != cell_b);
> + const bool down = (cell_c != cell_d);
> + const bool left = (cell_a != cell_c);
> + const bool right = (cell_b != cell_d);
> + return directions (up, down, left, right);
> +}
> +
> +/* Paint the grid lines.
> +
> + Consider painting
> + - a grid of cells,
> + - plus a right-hand border
> + - and a bottom border
> +
> + Then we need to paint to the canvas like this:
> +
> + # PER-TABLE-COLUMN R BORDER
> + # +-------------------+ +-----+
> + #
> + # TABLE CELL WIDTH (in canvas units)
> + # +-------------+
> + # . . . . . . .
> + # ...+-----+-----+.+-----+...+-----+ +
> + # | U | |.| | | U | |
> + # | U | |.| | | U | |
> + # |LL+RR|RRRRR|.|RRRRR| |LL+ | |
> + # | D | |.| | | D | |
> + # | D | |.| | | D | |
> + # ...+-----+-----+.+-----+...+-----+ |
> + # ..................... ...... +-- PER-TABLE-ROW
> + # ...+-----+-----+.+-----+...+-----+ | +
> + # | D | |.| | | D | | |
> + # | D | |.| | | D | | |
> + # | D | |.| | | D | | +---- TABLE CELL HEIGHT (in canvas units)
> + # | D | |.| | | D | | |
> + # | D | |.| | | D | | |
> + # ...+-----+-----+.+-----+...+-----+ + +
> + # . . . . . .
> + # ...+-----+-----+.+-----+...+-----+ +
> + # | D | |.| | | U | |
> + # | D | |.| | | U | |
> + # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER
> + # | | |.| | | | |
> + # | | |.| | | | |
> + # ...+-----+-----+.+-----+...+-----+ +
> +
> + where each:
> +
> + # +-----+
> + # | |
> + # | |
> + # | |
> + # | |
> + # | |
> + # +-----+
> +
> + is a canvas cell, and the U, L, R, D express the connections
> + that are present with neighboring table cells. These affect
> + the kinds of borders that we draw for a particular table cell. */
> +
> +void
> +table::paint_cell_borders_to_canvas (canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg,
> + const theme &theme) const
> +{
> + /* The per-table-cell left and top borders are either drawn or not,
> + but if they are, they aren't affected by per-table-cell connections. */
> + const canvas::cell_t left_border
> + = theme.get_line_art (directions (true, /* up */
> + true, /* down */
> + false, /* left */
> + false /* right */));
> + const canvas::cell_t top_border
> + = theme.get_line_art (directions (false, /* up */
> + false, /* down */
> + true, /* left */
> + true)); /* right */
> + for (int table_y = 0; table_y < m_size.h; table_y++)
> + {
> + const int canvas_y = tg.table_y_to_canvas_y (table_y);
> + for (int table_x = 0; table_x < m_size.w; table_x++)
> + {
> + canvas::coord_t canvas_top_left
> + = tg.table_to_canvas(table::coord_t (table_x, table_y));
> +
> + const directions c (get_connections (table_x, table_y));
> +
> + /* Paint top-left corner of border, if any. */
> + canvas.paint (offset + canvas_top_left,
> + theme.get_line_art (c));
> +
> + /* Paint remainder of left border of cell, if any.
> + We assume here that the content occupies a single canvas row. */
> + if (c.m_down)
> + canvas.paint (offset + canvas::coord_t (canvas_top_left.x,
> + canvas_y + 1),
> + left_border);
> +
> + /* Paint remainder of top border of cell, if any. */
> + if (c.m_right)
> + {
> + const int col_width = tg.get_col_width (table_x);
> + for (int x_offset = 0; x_offset < col_width; x_offset++)
> + {
> + const int canvas_x = canvas_top_left.x + 1 + x_offset;
> + canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
> + top_border);
> + }
> + }
> + }
> +
> + /* Paint right-hand border of row. */
> + const int table_x = m_size.w;
> + const int canvas_x = tg.table_x_to_canvas_x (table_x);
> + const directions c (get_connections (m_size.w, table_y));
> + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
> + theme.get_line_art (directions (c.m_up,
> + c.m_down,
> + c.m_left,
> + false))); /* right */
> + /* We assume here that the content occupies a single canvas row. */
> + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y + 1),
> + theme.get_line_art (directions (c.m_down, /* up */
> + c.m_down, /* down */
> + false, /* left */
> + false))); /* right */
> + }
> +
> + /* Draw bottom border of table. */
> + {
> + const int canvas_y = tg.get_canvas_size ().h - 1;
> + for (int table_x = 0; table_x < m_size.w; table_x++)
> + {
> + const directions c (get_connections (table_x, m_size.h));
> + const int left_canvas_x = tg.table_x_to_canvas_x (table_x);
> + canvas.paint (offset + canvas::coord_t (left_canvas_x, canvas_y),
> + theme.get_line_art (directions (c.m_up,
> + false, /* down */
> + c.m_left,
> + c.m_right)));
> + const int col_width = tg.get_col_width (table_x);
> + for (int x_offset = 0; x_offset < col_width; x_offset++)
> + {
> + const int canvas_x = left_canvas_x + 1 + x_offset;
> + canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
> + theme.get_line_art (directions (false, // up
> + false, // down
> + c.m_right, // left
> + c.m_right))); // right
> + }
> + }
> +
> + /* Bottom-right corner of table. */
> + const int table_x = m_size.w;
> + const int canvas_x = tg.table_x_to_canvas_x (table_x);
> + const directions c (get_connections (m_size.w, m_size.h));
> + canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
> + theme.get_line_art (directions (c.m_up, // up
> + false, // down
> + c.m_left, // left
> + false))); // right
> + }
> +}
> +
> +void
> +table::paint_cell_contents_to_canvas(canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg) const
> +{
> + for (auto &placement : m_placements)
> + placement.paint_cell_contents_to_canvas (canvas, offset, tg);
> +}
> +
> +/* class table_cell_sizes. */
> +
> +/* Consider 1x1 cells. */
> +
> +void
> +table_cell_sizes::pass_1 (const table &table)
> +{
> + for (auto &placement : table.m_placements)
> + if (placement.one_by_one_p ())
> + {
> + canvas::size_t canvas_size (placement.get_min_canvas_size ());
> + table::coord_t table_coord (placement.m_rect.m_top_left);
> + m_col_widths.require (table_coord.x, canvas_size.w);
> + m_row_heights.require (table_coord.y, canvas_size.h);
> + }
> +}
> +
> +/* Consider cells that span more than one row or column. */
> +
> +void
> +table_cell_sizes::pass_2 (const table &table)
> +{
> + for (auto &placement : table.m_placements)
> + if (!placement.one_by_one_p ())
> + {
> + const canvas::size_t req_canvas_size (placement.get_min_canvas_size ());
> + const canvas::size_t current_canvas_size
> + = get_canvas_size (placement.m_rect);
> + /* Grow columns as necessary. */
> + if (req_canvas_size.w > current_canvas_size.w)
> + {
> + /* Spread the deficit amongst the columns. */
> + int deficit = req_canvas_size.w - current_canvas_size.w;
> + const int per_col = deficit / placement.m_rect.m_size.w;
> + for (int table_x = placement.m_rect.get_min_x ();
> + table_x < placement.m_rect.get_next_x ();
> + table_x++)
> + {
> + m_col_widths.m_requirements[table_x] += per_col;
> + deficit -= per_col;
> + }
> + /* Make sure we allocate all of the deficit. */
> + if (deficit > 0)
> + {
> + const int table_x = placement.m_rect.get_max_x ();
> + m_col_widths.m_requirements[table_x] += deficit;
> + }
> + }
> + /* Grow rows as necessary. */
> + if (req_canvas_size.h > current_canvas_size.h)
> + {
> + /* Spread the deficit amongst the rows. */
> + int deficit = req_canvas_size.h - current_canvas_size.h;
> + const int per_row = deficit / placement.m_rect.m_size.h;
> + for (int table_y = placement.m_rect.get_min_y ();
> + table_y < placement.m_rect.get_next_y ();
> + table_y++)
> + {
> + m_row_heights.m_requirements[table_y] += per_row;
> + deficit -= per_row;
> + }
> + /* Make sure we allocate all of the deficit. */
> + if (deficit > 0)
> + {
> + const int table_y = placement.m_rect.get_max_y ();
> + m_row_heights.m_requirements[table_y] += deficit;
> + }
> + }
> + }
> +}
> +
> +canvas::size_t
> +table_cell_sizes::get_canvas_size (const table::rect_t &rect) const
> +{
> + canvas::size_t result (0, 0);
> + for (int table_x = rect.get_min_x ();
> + table_x < rect.get_next_x ();
> + table_x ++)
> + result.w += m_col_widths.m_requirements[table_x];
> + for (int table_y = rect.get_min_y ();
> + table_y < rect.get_next_y ();
> + table_y ++)
> + result.h += m_row_heights.m_requirements[table_y];
> + /* Allow space for the borders. */
> + result.w += rect.m_size.w - 1;
> + result.h += rect.m_size.h - 1;
> + return result;
> +}
> +
> +/* class text_art::table_geometry. */
> +
> +table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes)
> +: m_table (table),
> + m_cell_sizes (cell_sizes),
> + m_canvas_size (canvas::size_t (0, 0)),
> + m_col_start_x (table.get_size ().w),
> + m_row_start_y (table.get_size ().h)
> +{
> + recalc_coords ();
> +}
> +
> +void
> +table_geometry::recalc_coords ()
> +{
> + /* Start canvas column of table cell, including leading border. */
> + m_col_start_x.clear ();
> + int iter_canvas_x = 0;
> + for (auto w : m_cell_sizes.m_col_widths.m_requirements)
> + {
> + m_col_start_x.push_back (iter_canvas_x);
> + iter_canvas_x += w + 1;
> + }
> +
> + /* Start canvas row of table cell, including leading border. */
> + m_row_start_y.clear ();
> + int iter_canvas_y = 0;
> + for (auto h : m_cell_sizes.m_row_heights.m_requirements)
> + {
> + m_row_start_y.push_back (iter_canvas_y);
> + iter_canvas_y += h + 1;
> + }
> +
> + m_canvas_size = canvas::size_t (iter_canvas_x + 1,
> + iter_canvas_y + 1);
> +}
> +
> +/* Get the TL corner of the table cell at TABLE_COORD
> + in canvas coords (including the border). */
> +
> +canvas::coord_t
> +table_geometry::table_to_canvas (table::coord_t table_coord) const
> +{
> + return canvas::coord_t (table_x_to_canvas_x (table_coord.x),
> + table_y_to_canvas_y (table_coord.y));
> +}
> +
> +/* Get the left border of the table cell at column TABLE_X
> + in canvas coords (including the border). */
> +
> +int
> +table_geometry::table_x_to_canvas_x (int table_x) const
> +{
> + /* Allow one beyond the end, for the right-hand border of the table. */
> + if (table_x == m_col_start_x.size ())
> + return m_canvas_size.w - 1;
> + return m_col_start_x[table_x];
> +}
> +
> +/* Get the top border of the table cell at column TABLE_Y
> + in canvas coords (including the border). */
> +
> +int
> +table_geometry::table_y_to_canvas_y (int table_y) const
> +{
> + /* Allow one beyond the end, for the right-hand border of the table. */
> + if (table_y == m_row_start_y.size ())
> + return m_canvas_size.h - 1;
> + return m_row_start_y[table_y];
> +}
> +
> +/* class text_art::simple_table_geometry. */
> +
> +simple_table_geometry::simple_table_geometry (const table &table)
> +: m_col_widths (table.get_size ().w),
> + m_row_heights (table.get_size ().h),
> + m_cell_sizes (m_col_widths, m_row_heights),
> + m_tg (table, m_cell_sizes)
> +{
> + m_cell_sizes.pass_1 (table);
> + m_cell_sizes.pass_2 (table);
> + m_tg.recalc_coords ();
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +static void
> +test_tic_tac_toe ()
> +{
> + style_manager sm;
> + table t (table::size_t (3, 3));
> + t.set_cell (table::coord_t (0, 0), styled_string (sm, "X"));
> + t.set_cell (table::coord_t (1, 0), styled_string (sm, ""));
> + t.set_cell (table::coord_t (2, 0), styled_string (sm, ""));
> + t.set_cell (table::coord_t (0, 1), styled_string (sm, "O"));
> + t.set_cell (table::coord_t (1, 1), styled_string (sm, "O"));
> + t.set_cell (table::coord_t (2, 1), styled_string (sm, ""));
> + t.set_cell (table::coord_t (0, 2), styled_string (sm, "X"));
> + t.set_cell (table::coord_t (1, 2), styled_string (sm, ""));
> + t.set_cell (table::coord_t (2, 2), styled_string (sm, "O"));
> +
> + {
> + canvas canvas (t.to_canvas (ascii_theme (), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+-+-+-+\n"
> + "|X| | |\n"
> + "+-+-+-+\n"
> + "|O|O| |\n"
> + "+-+-+-+\n"
> + "|X| |O|\n"
> + "+-+-+-+\n"));
> + }
> +
> + {
> + canvas canvas (t.to_canvas (unicode_theme (), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌─┬─┬─┐\n"
> + "│X│ │ │\n"
> + "├─┼─┼─┤\n"
> + "│O│O│ │\n"
> + "├─┼─┼─┤\n"
> + "│X│ │O│\n"
> + "└─┴─┴─┘\n"));
> + }
> +}
> +
> +static table
> +make_text_table ()
> +{
> + style_manager sm;
> + table t (table::size_t (3, 3));
> + t.set_cell (table::coord_t (0, 0), styled_string (sm, "top left"));
> + t.set_cell (table::coord_t (1, 0), styled_string (sm, "top middle"));
> + t.set_cell (table::coord_t (2, 0), styled_string (sm, "top right"));
> + t.set_cell (table::coord_t (0, 1), styled_string (sm, "middle left"));
> + t.set_cell (table::coord_t (1, 1), styled_string (sm, "middle middle"));
> + t.set_cell (table::coord_t (2, 1), styled_string (sm, "middle right"));
> + t.set_cell (table::coord_t (0, 2), styled_string (sm, "bottom left"));
> + t.set_cell (table::coord_t (1, 2), styled_string (sm, "bottom middle"));
> + t.set_cell (table::coord_t (2, 2), styled_string (sm, "bottom right"));
> + return t;
> +}
> +
> +static void
> +test_text_table ()
> +{
> + style_manager sm;
> + table table = make_text_table ();
> + {
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+-----------+-------------+------------+\n"
> + "| top left | top middle | top right |\n"
> + "+-----------+-------------+------------+\n"
> + "|middle left|middle middle|middle right|\n"
> + "+-----------+-------------+------------+\n"
> + "|bottom left|bottom middle|bottom right|\n"
> + "+-----------+-------------+------------+\n"));
> + }
> + {
> + canvas canvas (table.to_canvas (unicode_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌───────────┬─────────────┬────────────┐\n"
> + "│ top left │ top middle │ top right │\n"
> + "├───────────┼─────────────┼────────────┤\n"
> + "│middle left│middle middle│middle right│\n"
> + "├───────────┼─────────────┼────────────┤\n"
> + "│bottom left│bottom middle│bottom right│\n"
> + "└───────────┴─────────────┴────────────┘\n"));
> + }
> +}
> +
> +static void
> +test_offset_table ()
> +{
> + style_manager sm;
> + table table = make_text_table ();
> + simple_table_geometry stg (table);
> + const canvas::size_t tcs = stg.m_tg.get_canvas_size();
> + {
> + canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
> + canvas.debug_fill ();
> + table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
> + stg.m_tg,
> + ascii_theme());
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("*********************************************\n"
> + "*********************************************\n"
> + "*********************************************\n"
> + "***+-----------+-------------+------------+**\n"
> + "***| top left | top middle | top right |**\n"
> + "***+-----------+-------------+------------+**\n"
> + "***|middle left|middle middle|middle right|**\n"
> + "***+-----------+-------------+------------+**\n"
> + "***|bottom left|bottom middle|bottom right|**\n"
> + "***+-----------+-------------+------------+**\n"
> + "*********************************************\n"
> + "*********************************************\n"));
> + }
> + {
> + canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
> + canvas.debug_fill ();
> + table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
> + stg.m_tg,
> + unicode_theme());
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("*********************************************\n"
> + "*********************************************\n"
> + "*********************************************\n"
> + "***┌───────────┬─────────────┬────────────┐**\n"
> + "***│ top left │ top middle │ top right │**\n"
> + "***├───────────┼─────────────┼────────────┤**\n"
> + "***│middle left│middle middle│middle right│**\n"
> + "***├───────────┼─────────────┼────────────┤**\n"
> + "***│bottom left│bottom middle│bottom right│**\n"
> + "***└───────────┴─────────────┴────────────┘**\n"
> + "*********************************************\n"
> + "*********************************************\n"));
> + }
> +}
> +
> +#define ASSERT_TABLE_CELL_STREQ(TABLE, TABLE_X, TABLE_Y, EXPECTED_STR) \
> + SELFTEST_BEGIN_STMT \
> + table::coord_t coord ((TABLE_X), (TABLE_Y)); \
> + const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
> + ASSERT_NE (cp, nullptr); \
> + ASSERT_EQ (cp->get_content (), styled_string (sm, EXPECTED_STR)); \
> + SELFTEST_END_STMT
> +
> +#define ASSERT_TABLE_NULL_CELL(TABLE, TABLE_X, TABLE_Y) \
> + SELFTEST_BEGIN_STMT \
> + table::coord_t coord ((TABLE_X), (TABLE_Y)); \
> + const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
> + ASSERT_EQ (cp, nullptr); \
> + SELFTEST_END_STMT
> +
> +static void
> +test_spans ()
> +{
> + style_manager sm;
> + table table (table::size_t (3, 3));
> + table.set_cell_span (table::rect_t (table::coord_t (0, 0),
> + table::size_t (3, 1)),
> + styled_string (sm, "ABC"));
> + table.set_cell_span (table::rect_t (table::coord_t (0, 1),
> + table::size_t (2, 1)),
> + styled_string (sm, "DE"));
> + table.set_cell_span (table::rect_t (table::coord_t (2, 1),
> + table::size_t (1, 1)),
> + styled_string (sm, "F"));
> + table.set_cell (table::coord_t (0, 2), styled_string (sm, "G"));
> + table.set_cell (table::coord_t (1, 2), styled_string (sm, "H"));
> + table.set_cell (table::coord_t (2, 2), styled_string (sm, "I"));
> + {
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+-----+\n"
> + "| ABC |\n"
> + "+---+-+\n"
> + "|DE |F|\n"
> + "+-+-+-+\n"
> + "|G|H|I|\n"
> + "+-+-+-+\n"));
> + }
> + {
> + canvas canvas (table.to_canvas (unicode_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌─────┐\n"
> + "│ ABC │\n"
> + "├───┬─┤\n"
> + "│DE │F│\n"
> + "├─┬─┼─┤\n"
> + "│G│H│I│\n"
> + "└─┴─┴─┘\n"));
> + }
> +}
> +
> +/* Verify building this 5x5 table with spans:
> + |0|1|2|3|4|
> + +-+-+-+-+-+
> + 0|A A A|B|C|0
> + + +-+ +
> + 1|A A A|D|C|1
> + + +-+-+
> + 2|A A A|E|F|2
> + +-+-+-+-+-+
> + 3|G G|H|I I|3
> + | | +-+-+
> + 4|G G|H|J J|4
> + +-+-+-+-+-+
> + |0|1|2|3|4|
> +*/
> +
> +static void
> +test_spans_2 ()
> +{
> + style_manager sm;
> + table table (table::size_t (5, 5));
> + table.set_cell_span (table::rect_t (table::coord_t (0, 0),
> + table::size_t (3, 3)),
> + styled_string (sm, "A"));
> + table.set_cell_span (table::rect_t (table::coord_t (3, 0),
> + table::size_t (1, 1)),
> + styled_string (sm, "B"));
> + table.set_cell_span (table::rect_t (table::coord_t (4, 0),
> + table::size_t (1, 2)),
> + styled_string (sm, "C"));
> + table.set_cell_span (table::rect_t (table::coord_t (3, 1),
> + table::size_t (1, 1)),
> + styled_string (sm, "D"));
> + table.set_cell_span (table::rect_t (table::coord_t (3, 2),
> + table::size_t (1, 1)),
> + styled_string (sm, "E"));
> + table.set_cell_span (table::rect_t (table::coord_t (4, 2),
> + table::size_t (1, 1)),
> + styled_string (sm, "F"));
> + table.set_cell_span (table::rect_t (table::coord_t (0, 3),
> + table::size_t (2, 2)),
> + styled_string (sm, "G"));
> + table.set_cell_span (table::rect_t (table::coord_t (2, 3),
> + table::size_t (1, 2)),
> + styled_string (sm, "H"));
> + table.set_cell_span (table::rect_t (table::coord_t (3, 3),
> + table::size_t (2, 1)),
> + styled_string (sm, "I"));
> + table.set_cell_span (table::rect_t (table::coord_t (3, 4),
> + table::size_t (2, 1)),
> + styled_string (sm, "J"));
> +
> + /* Check occupancy at each table coordinate. */
> + ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B");
> + ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C");
> +
> + ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D");
> + ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C");
> +
> + ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A");
> + ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E");
> + ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F");
> +
> + ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G");
> + ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G");
> + ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H");
> + ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I");
> + ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I");
> +
> + ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G");
> + ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G");
> + ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H");
> + ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J");
> + ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J");
> +
> + {
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+---+-+-+\n"
> + "| |B| |\n"
> + "| +-+C|\n"
> + "| A |D| |\n"
> + "| +-+-+\n"
> + "| |E|F|\n"
> + "+-+-+-+-+\n"
> + "| | | I |\n"
> + "|G|H+---+\n"
> + "| | | J |\n"
> + "+-+-+---+\n"));
> + }
> + {
> + canvas canvas (table.to_canvas (unicode_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌───┬─┬─┐\n"
> + "│ │B│ │\n"
> + "│ ├─┤C│\n"
> + "│ A │D│ │\n"
> + "│ ├─┼─┤\n"
> + "│ │E│F│\n"
> + "├─┬─┼─┴─┤\n"
> + "│ │ │ I │\n"
> + "│G│H├───┤\n"
> + "│ │ │ J │\n"
> + "└─┴─┴───┘\n"));
> + }
> +}
> +
> +/* Experiment with adding a 1-table-column gap at the boundary between
> + valid vs invalid for visualizing a buffer overflow. */
> +static void
> +test_spans_3 ()
> +{
> + const char * const str = "hello world!";
> + const size_t buf_size = 10;
> + const size_t str_size = strlen (str) + 1;
> +
> + style_manager sm;
> + table table (table::size_t (str_size + 1, 3));
> +
> + table.set_cell_span (table::rect_t (table::coord_t (0, 0),
> + table::size_t (str_size + 1, 1)),
> + styled_string (sm, "String literal"));
> +
> + for (size_t i = 0; i < str_size; i++)
> + {
> + table::coord_t c (i, 1);
> + if (i >= buf_size)
> + c.x++;
> + if (str[i] == '\0')
> + table.set_cell (c, styled_string (sm, "NUL"));
> + else
> + table.set_cell (c, styled_string ((cppchar_t)str[i]));
> + }
> +
> + table.set_cell_span (table::rect_t (table::coord_t (0, 2),
> + table::size_t (buf_size, 1)),
> + styled_string::from_fmt (sm,
> + nullptr,
> + "'buf' (char[%i])",
> + (int)buf_size));
> + table.set_cell_span (table::rect_t (table::coord_t (buf_size + 1, 2),
> + table::size_t (str_size - buf_size, 1)),
> + styled_string (sm, "overflow"));
> +
> + {
> + canvas canvas (table.to_canvas (ascii_theme (), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + "+-----------------------------+\n"
> + "| String literal |\n"
> + "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
> + "|h|e|l|l|o| |w|o|r|l||d|!|NUL |\n"
> + "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
> + "| 'buf' (char[10]) ||overflow|\n"
> + "+-------------------++--------+\n");
> + }
> + {
> + canvas canvas (table.to_canvas (unicode_theme (), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌─────────────────────────────┐\n"
> + "│ String literal │\n"
> + "├─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬┬─┬─┬────┤\n"
> + "│h│e│l│l│o│ │w│o│r│l││d│!│NUL │\n"
> + "├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤├─┴─┴────┤\n"
> + "│ 'buf' (char[10]) ││overflow│\n"
> + "└───────────────────┘└────────┘\n"));
> + }
> +}
> +
> +static void
> +test_double_width_chars ()
> +{
> + table_cell_content tcc (styled_string ((cppchar_t)0x1f642));
> + ASSERT_EQ (tcc.get_canvas_size ().w, 2);
> + ASSERT_EQ (tcc.get_canvas_size ().h, 1);
> +
> + style_manager sm;
> + table table (table::size_t (1, 1));
> + table.set_cell (table::coord_t (0,0),
> + styled_string ((cppchar_t)0x1f642));
> +
> + canvas canvas (table.to_canvas (unicode_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌──┐\n"
> + "│🙂│\n"
> + "└──┘\n"));
> +}
> +
> +static void
> +test_ipv4_header ()
> +{
> + style_manager sm;
> + table table (table::size_t (34, 10));
> + table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets"));
> + table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet"));
> + table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet"));
> + for (int octet = 0; octet < 4; octet++)
> + table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0),
> + table::size_t (8, 1)),
> + styled_string::from_fmt (sm, nullptr, "%i", octet));
> + table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit"));
> + for (int bit = 0; bit < 32; bit++)
> + table.set_cell (table::coord_t (bit + 2, 1),
> + styled_string::from_fmt (sm, nullptr, "%i", bit));
> + for (int word = 0; word < 6; word++)
> + {
> + table.set_cell (table::coord_t (0, word + 2),
> + styled_string::from_fmt (sm, nullptr, "%i", word * 4));
> + table.set_cell (table::coord_t (1, word + 2),
> + styled_string::from_fmt (sm, nullptr, "%i", word * 32));
> + }
> +
> + table.set_cell (table::coord_t (0, 8), styled_string (sm, "..."));
> + table.set_cell (table::coord_t (1, 8), styled_string (sm, "..."));
> + table.set_cell (table::coord_t (0, 9), styled_string (sm, "56"));
> + table.set_cell (table::coord_t (1, 9), styled_string (sm, "448"));
> +
> +#define SET_BITS(FIRST, LAST, NAME) \
> + do { \
> + const int first = (FIRST); \
> + const int last = (LAST); \
> + const char *name = (NAME); \
> + const int row = first / 32; \
> + gcc_assert (last / 32 == row); \
> + table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \
> + table::size_t (last + 1 - first , 1)); \
> + table.set_cell_span (rect, styled_string (sm, name)); \
> + } while (0)
> +
> + SET_BITS (0, 3, "Version");
> + SET_BITS (4, 7, "IHL");
> + SET_BITS (8, 13, "DSCP");
> + SET_BITS (14, 15, "ECN");
> + SET_BITS (16, 31, "Total Length");
> +
> + SET_BITS (32 + 0, 32 + 15, "Identification");
> + SET_BITS (32 + 16, 32 + 18, "Flags");
> + SET_BITS (32 + 19, 32 + 31, "Fragment Offset");
> +
> + SET_BITS (64 + 0, 64 + 7, "Time To Live");
> + SET_BITS (64 + 8, 64 + 15, "Protocol");
> + SET_BITS (64 + 16, 64 + 31, "Header Checksum");
> +
> + SET_BITS (96 + 0, 96 + 31, "Source IP Address");
> + SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
> +
> + table.set_cell_span(table::rect_t (table::coord_t (2, 7),
> + table::size_t (32, 3)),
> + styled_string (sm, "Options"));
> + {
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+-------+-----+---------------+---------------------+-----------------------+-----------------------+\n"
> + "|Offsets|Octet| 0 | 1 | 2 | 3 |\n"
> + "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
> + "| Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|\n"
> + "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
> + "| 0 | 0 |Version| IHL | DSCP | ECN | Total Length |\n"
> + "+-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+\n"
> + "| 4 | 32 | Identification | Flags | Fragment Offset |\n"
> + "+-------+-----+---------------+---------------------+--------+--------------------------------------+\n"
> + "| 8 | 64 | Time To Live | Protocol | Header Checksum |\n"
> + "+-------+-----+---------------+---------------------+-----------------------------------------------+\n"
> + "| 12 | 96 | Source IP Address |\n"
> + "+-------+-----+-------------------------------------------------------------------------------------+\n"
> + "| 16 | 128 | Destination IP Address |\n"
> + "+-------+-----+-------------------------------------------------------------------------------------+\n"
> + "| 20 | 160 | |\n"
> + "+-------+-----+ |\n"
> + "| ... | ... | Options |\n"
> + "+-------+-----+ |\n"
> + "| 56 | 448 | |\n"
> + "+-------+-----+-------------------------------------------------------------------------------------+\n"));
> + }
> + {
> + canvas canvas (table.to_canvas (unicode_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + // FIXME: are we allowed unicode chars in string literals in our source?
> + ("┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐\n"
> + "│Offsets│Octet│ 0 │ 1 │ 2 │ 3 │\n"
> + "├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤\n"
> + "│ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│\n"
> + "├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤\n"
> + "│ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │\n"
> + "├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤\n"
> + "│ 4 │ 32 │ Identification │ Flags │ Fragment Offset │\n"
> + "├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤\n"
> + "│ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │\n"
> + "├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤\n"
> + "│ 12 │ 96 │ Source IP Address │\n"
> + "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
> + "│ 16 │ 128 │ Destination IP Address │\n"
> + "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
> + "│ 20 │ 160 │ │\n"
> + "├───────┼─────┤ │\n"
> + "│ ... │ ... │ Options │\n"
> + "├───────┼─────┤ │\n"
> + "│ 56 │ 448 │ │\n"
> + "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n"));
> + }
> +}
> +
> +static void
> +test_missing_cells ()
> +{
> + style_manager sm;
> + table table (table::size_t (3, 3));
> + table.set_cell (table::coord_t (1, 0), styled_string (sm, "A"));
> + table.set_cell (table::coord_t (0, 1), styled_string (sm, "B"));
> + table.set_cell (table::coord_t (1, 1), styled_string (sm, "C"));
> + table.set_cell (table::coord_t (2, 1), styled_string (sm, "D"));
> + table.set_cell (table::coord_t (1, 2), styled_string (sm, "E"));
> +
> + ASSERT_TABLE_NULL_CELL (table, 0, 0);
> + ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
> + ASSERT_TABLE_NULL_CELL (table, 2, 0);
> +
> + ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B");
> + ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C");
> + ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D");
> +
> + ASSERT_TABLE_NULL_CELL (table, 0, 2);
> + ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E");
> + ASSERT_TABLE_NULL_CELL (table, 2, 2);
> +
> + {
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + (" +-+\n"
> + " |A|\n"
> + "+-+-+-+\n"
> + "|B|C|D|\n"
> + "+-+-+-+\n"
> + " |E|\n"
> + " +-+\n"));
> + }
> + {
> + canvas canvas (table.to_canvas (unicode_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + (" ┌─┐\n"
> + " │A│\n"
> + "┌─┼─┼─┐\n"
> + "│B│C│D│\n"
> + "└─┼─┼─┘\n"
> + " │E│\n"
> + " └─┘\n"));
> + }
> +}
> +
> +static void
> +test_add_row ()
> +{
> + style_manager sm;
> + table table (table::size_t (3, 0));
> + for (int i = 0; i < 5; i++)
> + {
> + const int y = table.add_row ();
> + for (int x = 0; x < 3; x++)
> + table.set_cell (table::coord_t (x, y),
> + styled_string::from_fmt (sm, nullptr,
> + "%i, %i", x, y));
> + }
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+----+----+----+\n"
> + "|0, 0|1, 0|2, 0|\n"
> + "+----+----+----+\n"
> + "|0, 1|1, 1|2, 1|\n"
> + "+----+----+----+\n"
> + "|0, 2|1, 2|2, 2|\n"
> + "+----+----+----+\n"
> + "|0, 3|1, 3|2, 3|\n"
> + "+----+----+----+\n"
> + "|0, 4|1, 4|2, 4|\n"
> + "+----+----+----+\n"));
> +}
> +
> +static void
> +test_alignment ()
> +{
> + style_manager sm;
> + table table (table::size_t (9, 9));
> + table.set_cell_span (table::rect_t (table::coord_t (0, 0),
> + table::size_t (3, 3)),
> + styled_string (sm, "left top"),
> + x_align::LEFT, y_align::TOP);
> + table.set_cell_span (table::rect_t (table::coord_t (3, 0),
> + table::size_t (3, 3)),
> + styled_string (sm, "center top"),
> + x_align::CENTER, y_align::TOP);
> + table.set_cell_span (table::rect_t (table::coord_t (6, 0),
> + table::size_t (3, 3)),
> + styled_string (sm, "right top"),
> + x_align::RIGHT, y_align::TOP);
> + table.set_cell_span (table::rect_t (table::coord_t (0, 3),
> + table::size_t (3, 3)),
> + styled_string (sm, "left center"),
> + x_align::LEFT, y_align::CENTER);
> + table.set_cell_span (table::rect_t (table::coord_t (3, 3),
> + table::size_t (3, 3)),
> + styled_string (sm, "center center"),
> + x_align::CENTER, y_align::CENTER);
> + table.set_cell_span (table::rect_t (table::coord_t (6, 3),
> + table::size_t (3, 3)),
> + styled_string (sm, "right center"),
> + x_align::RIGHT, y_align::CENTER);
> + table.set_cell_span (table::rect_t (table::coord_t (0, 6),
> + table::size_t (3, 3)),
> + styled_string (sm, "left bottom"),
> + x_align::LEFT, y_align::BOTTOM);
> + table.set_cell_span (table::rect_t (table::coord_t (3, 6),
> + table::size_t (3, 3)),
> + styled_string (sm, "center bottom"),
> + x_align::CENTER, y_align::BOTTOM);
> + table.set_cell_span (table::rect_t (table::coord_t (6, 6),
> + table::size_t (3, 3)),
> + styled_string (sm, "right bottom"),
> + x_align::RIGHT, y_align::BOTTOM);
> +
> + canvas canvas (table.to_canvas (ascii_theme(), sm));
> + ASSERT_CANVAS_STREQ
> + (canvas, false,
> + ("+-----------+-------------+------------+\n"
> + "|left top | center top | right top|\n"
> + "| | | |\n"
> + "+-----------+-------------+------------+\n"
> + "|left center|center center|right center|\n"
> + "| | | |\n"
> + "+-----------+-------------+------------+\n"
> + "| | | |\n"
> + "|left bottom|center bottom|right bottom|\n"
> + "+-----------+-------------+------------+\n"));
> +}
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_table_cc_tests ()
> +{
> + test_tic_tac_toe ();
> + test_text_table ();
> + test_offset_table ();
> + test_spans ();
> + test_spans_2 ();
> + test_spans_3 ();
> + test_double_width_chars ();
> + test_ipv4_header ();
> + test_missing_cells ();
> + test_add_row ();
> + test_alignment ();
> +}
> +
> +} // namespace selftest
> +
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/table.h b/gcc/text-art/table.h
> new file mode 100644
> index 00000000000..5e6c8ffb836
> --- /dev/null
> +++ b/gcc/text-art/table.h
> @@ -0,0 +1,262 @@
> +/* Support for tabular/grid-based content.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_TABLE_H
> +#define GCC_TEXT_ART_TABLE_H
> +
> +#include "text-art/canvas.h"
> +#include "text-art/theme.h"
> +#include <vector>
> +
> +namespace text_art {
> +
> +class table;
> +class table_geometry;
> +
> +/* A class representing the content of a particular table cell,
> + or of a span of table cells. */
> +
> +class table_cell_content
> +{
> + public:
> + table_cell_content () : m_str (), m_size (0, 0) {}
> + table_cell_content (styled_string &&s);
> +
> + bool operator== (const table_cell_content &other) const
> + {
> + return m_str == other.m_str;
> + }
> +
> + canvas::size_t get_canvas_size () const { return m_size; }
> +
> + void paint_to_canvas (canvas &canvas,
> + canvas::coord_t top_left) const;
> +
> + private:
> + styled_string m_str;
> + canvas::size_t m_size;
> +};
> +
> +/* A list of required sizes of table rows or columns
> + in canvas units (row heights or column widths). */
> +
> +struct table_dimension_sizes
> +{
> + table_dimension_sizes (unsigned num);
> +
> + void require (unsigned idx, int amount)
> + {
> + m_requirements[idx] = std::max (m_requirements[idx], amount);
> + }
> +
> + std::vector<int> m_requirements;
> +};
> +
> +/* A 2D grid of cells. Instances of table_cell_content can be assigned
> + to individual table cells, and to rectangular spans of cells. Such
> + assignments do not have to fully cover the 2D grid, but they must not
> + overlap. */
> +
> +class table
> +{
> + public:
> + typedef size<class table> size_t;
> + typedef coord<class table> coord_t;
> + typedef range<class table> range_t;
> + typedef rect<class table> rect_t;
> +
> + /* A record of how a table_cell_content was placed at a table::rect_t
> + with a certain alignment. */
> + class cell_placement
> + {
> + public:
> + cell_placement (rect_t rect,
> + table_cell_content &&content,
> + x_align x_align,
> + y_align y_align)
> + : m_rect (rect),
> + m_content (std::move (content)),
> + m_x_align (x_align),
> + m_y_align (y_align)
> + {
> + }
> +
> + bool one_by_one_p () const
> + {
> + return m_rect.m_size.w == 1 && m_rect.m_size.h == 1;
> + }
> +
> + canvas::size_t get_min_canvas_size () const
> + {
> + // Doesn't include border
> + return m_content.get_canvas_size ();
> + }
> +
> + void paint_cell_contents_to_canvas(canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg) const;
> +
> + const table_cell_content &get_content () const { return m_content; }
> +
> + private:
> + friend class table_cell_sizes;
> + rect_t m_rect;
> + table_cell_content m_content;
> + x_align m_x_align;
> + y_align m_y_align;
> + };
> +
> + table (size_t size);
> + ~table () = default;
> + table (table &&) = default;
> + table (const table &) = delete;
> + table &operator= (const table &) = delete;
> +
> + const size_t &get_size () const { return m_size; }
> +
> + int add_row ()
> + {
> + m_size.h++;
> + m_occupancy.add_row (-1);
> + return m_size.h - 1; // return the table_y of the newly-added row
> + }
> +
> + void set_cell (coord_t coord,
> + table_cell_content &&content,
> + enum x_align x_align = x_align::CENTER,
> + enum y_align y_align = y_align::CENTER);
> +
> + void set_cell_span (rect_t span,
> + table_cell_content &&content,
> + enum x_align x_align = x_align::CENTER,
> + enum y_align y_align = y_align::CENTER);
> +
> + canvas to_canvas (const theme &theme, const style_manager &sm) const;
> +
> + void paint_to_canvas(canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg,
> + const theme &theme) const;
> +
> + void debug () const;
> +
> + /* Self-test support. */
> + const cell_placement *get_placement_at (coord_t coord) const;
> +
> + private:
> + int get_occupancy_safe (coord_t coord) const;
> + directions get_connections (int table_x, int table_y) const;
> + void paint_cell_borders_to_canvas(canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg,
> + const theme &theme) const;
> + void paint_cell_contents_to_canvas(canvas &canvas,
> + canvas::coord_t offset,
> + const table_geometry &tg) const;
> +
> + friend class table_cell_sizes;
> +
> + size_t m_size;
> + std::vector<cell_placement> m_placements;
> + array2<int, size_t, coord_t> m_occupancy; /* indices into the m_placements vec. */
> +};
> +
> +/* A workspace for computing the row heights and column widths
> + of a table (in canvas units).
> + The col_widths and row_heights could be shared between multiple
> + instances, for aligning multiple tables vertically or horizontally. */
> +
> +class table_cell_sizes
> +{
> + public:
> + table_cell_sizes (table_dimension_sizes &col_widths,
> + table_dimension_sizes &row_heights)
> + : m_col_widths (col_widths),
> + m_row_heights (row_heights)
> + {
> + }
> +
> + void pass_1 (const table &table);
> + void pass_2 (const table &table);
> +
> + canvas::size_t get_canvas_size (const table::rect_t &rect) const;
> +
> + table_dimension_sizes &m_col_widths;
> + table_dimension_sizes &m_row_heights;
> +};
> +
> +/* A class responsible for mapping from table cell coords
> + to canvas coords, handling column widths.
> + It's the result of solving "how big are all the table cells and where
> + do they go?"
> + The cell_sizes are passed in, for handling aligning multiple tables,
> + sharing column widths or row heights. */
> +
> +class table_geometry
> +{
> + public:
> + table_geometry (const table &table, table_cell_sizes &cell_sizes);
> +
> + void recalc_coords ();
> +
> + const canvas::size_t get_canvas_size () const { return m_canvas_size; }
> +
> + canvas::coord_t table_to_canvas (table::coord_t table_coord) const;
> + int table_x_to_canvas_x (int table_x) const;
> + int table_y_to_canvas_y (int table_y) const;
> +
> + int get_col_width (int table_x) const
> + {
> + return m_cell_sizes.m_col_widths.m_requirements[table_x];
> + }
> +
> + canvas::size_t get_canvas_size (const table::rect_t &rect) const
> + {
> + return m_cell_sizes.get_canvas_size (rect);
> + }
> +
> + private:
> + const table &m_table;
> + table_cell_sizes &m_cell_sizes;
> + canvas::size_t m_canvas_size;
> +
> + /* Start canvas column of table cell, including leading border. */
> + std::vector<int> m_col_start_x;
> +
> + /* Start canvas row of table cell, including leading border. */
> + std::vector<int> m_row_start_y;
> +};
> +
> +/* Helper class for handling the simple case of a single table
> + that doesn't need to be aligned with respect to anything else. */
> +
> +struct simple_table_geometry
> +{
> + simple_table_geometry (const table &table);
> +
> + table_dimension_sizes m_col_widths;
> + table_dimension_sizes m_row_heights;
> + table_cell_sizes m_cell_sizes;
> + table_geometry m_tg;
> +};
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_TABLE_H */
> diff --git a/gcc/text-art/theme.cc b/gcc/text-art/theme.cc
> new file mode 100644
> index 00000000000..54dfe7c9213
> --- /dev/null
> +++ b/gcc/text-art/theme.cc
> @@ -0,0 +1,183 @@
> +/* Classes for abstracting ascii vs unicode output.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#include "system.h"
> +#include "coretypes.h"
> +#include "pretty-print.h"
> +#include "selftest.h"
> +#include "text-art/selftests.h"
> +#include "text-art/ruler.h"
> +#include "text-art/theme.h"
> +
> +using namespace text_art;
> +
> +/* class theme. */
> +
> +void
> +theme::paint_y_arrow (canvas &canvas,
> + int canvas_x,
> + canvas::range_t y_range,
> + y_arrow_dir dir,
> + style::id_t style_id) const
> +{
> + int canvas_y;
> + int delta_y;
> + const canvas::cell_t head (get_cppchar (dir == y_arrow_dir::UP
> + ? cell_kind::Y_ARROW_UP_HEAD
> + : cell_kind::Y_ARROW_DOWN_HEAD),
> + false, style_id);
> + const canvas::cell_t tail (get_cppchar (dir == y_arrow_dir::UP
> + ? cell_kind::Y_ARROW_UP_TAIL
> + : cell_kind::Y_ARROW_DOWN_TAIL),
> + false, style_id);
> + if (dir == y_arrow_dir::UP)
> + {
> + canvas_y = y_range.get_max ();
> + delta_y = -1;
> + }
> + else
> + {
> + canvas_y = y_range.get_min ();
> + delta_y = 1;
> + }
> + for (int len = y_range.get_size (); len; len--)
> + {
> + const canvas::cell_t cell = (len > 1) ? tail : head;
> + canvas.paint (canvas::coord_t (canvas_x, canvas_y), cell);
> + canvas_y += delta_y;
> + }
> +}
> +
> +/* class ascii_theme : public theme. */
> +
> +canvas::cell_t
> +ascii_theme::get_line_art (directions line_dirs) const
> +{
> + if (line_dirs.m_up
> + && line_dirs.m_down
> + && !(line_dirs.m_left || line_dirs.m_right))
> + return canvas::cell_t ('|');
> + if (line_dirs.m_left
> + && line_dirs.m_right
> + && !(line_dirs.m_up || line_dirs.m_down))
> + return canvas::cell_t ('-');
> + if (line_dirs.m_up
> + || line_dirs.m_down
> + || line_dirs.m_left
> + || line_dirs.m_right)
> + return canvas::cell_t ('+');
> + return canvas::cell_t (' ');
> +}
> +
> +cppchar_t
> +ascii_theme::get_cppchar (enum cell_kind kind) const
> +{
> + switch (kind)
> + {
> + default:
> + gcc_unreachable ();
> + case cell_kind::X_RULER_LEFT_EDGE:
> + return '|';
> + case cell_kind::X_RULER_MIDDLE:
> + return '~';
> + case cell_kind::X_RULER_INTERNAL_EDGE:
> + return '|';
> + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW:
> + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE:
> + return '+';
> + case cell_kind::X_RULER_RIGHT_EDGE:
> + return '|';
> + case cell_kind::X_RULER_VERTICAL_CONNECTOR:
> + return '|';
> +
> + case cell_kind::TEXT_BORDER_HORIZONTAL:
> + return '-';
> + case cell_kind::TEXT_BORDER_VERTICAL:
> + return '|';
> + case cell_kind::TEXT_BORDER_TOP_LEFT:
> + case cell_kind::TEXT_BORDER_TOP_RIGHT:
> + case cell_kind::TEXT_BORDER_BOTTOM_LEFT:
> + case cell_kind::TEXT_BORDER_BOTTOM_RIGHT:
> + return '+';
> +
> + case cell_kind::Y_ARROW_UP_HEAD: return '^';
> + case cell_kind::Y_ARROW_DOWN_HEAD: return 'v';
> +
> + case cell_kind::Y_ARROW_UP_TAIL:
> + case cell_kind::Y_ARROW_DOWN_TAIL:
> + return '|';
> + }
> +}
> +
> +/* class unicode_theme : public theme. */
> +
> +canvas::cell_t
> +unicode_theme::get_line_art (directions line_dirs) const
> +{
> + return canvas::cell_t (get_box_drawing_char (line_dirs));
> +}
> +
> +cppchar_t
> +unicode_theme::get_cppchar (enum cell_kind kind) const
> +{
> + switch (kind)
> + {
> + default:
> + gcc_unreachable ();
> + case cell_kind::X_RULER_LEFT_EDGE:
> + return 0x251C; /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
> + case cell_kind::X_RULER_MIDDLE:
> + return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
> + case cell_kind::X_RULER_INTERNAL_EDGE:
> + return 0x253C; /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
> + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW:
> + return 0x252C; /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
> + case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE:
> + return 0x2534; /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */
> + case cell_kind::X_RULER_RIGHT_EDGE:
> + return 0x2524; /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */
> + case cell_kind::X_RULER_VERTICAL_CONNECTOR:
> + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
> +
> + case cell_kind::TEXT_BORDER_HORIZONTAL:
> + return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
> + case cell_kind::TEXT_BORDER_VERTICAL:
> + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
> +
> + /* Round corners. */
> + case cell_kind::TEXT_BORDER_TOP_LEFT:
> + return 0x256D; /* "╭": U+256D BOX DRAWINGS LIGHT ARC DOWN AND RIGHT. */
> + case cell_kind::TEXT_BORDER_TOP_RIGHT:
> + return 0x256E; /* "╮": U+256E BOX DRAWINGS LIGHT ARC DOWN AND LEFT. */
> + case cell_kind::TEXT_BORDER_BOTTOM_LEFT:
> + return 0x2570; /* "╰": U+2570 BOX DRAWINGS LIGHT ARC UP AND RIGHT. */
> + case cell_kind::TEXT_BORDER_BOTTOM_RIGHT:
> + return 0x256F; /* "╯": U+256F BOX DRAWINGS LIGHT ARC UP AND LEFT. */
> +
> + case cell_kind::Y_ARROW_UP_HEAD:
> + return '^';
> + case cell_kind::Y_ARROW_DOWN_HEAD:
> + return 'v';
> + case cell_kind::Y_ARROW_UP_TAIL:
> + case cell_kind::Y_ARROW_DOWN_TAIL:
> + return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
> + }
> +}
> diff --git a/gcc/text-art/theme.h b/gcc/text-art/theme.h
> new file mode 100644
> index 00000000000..8edbc6efc76
> --- /dev/null
> +++ b/gcc/text-art/theme.h
> @@ -0,0 +1,123 @@
> +/* Classes for abstracting ascii vs unicode output.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_THEME_H
> +#define GCC_TEXT_ART_THEME_H
> +
> +#include "text-art/canvas.h"
> +#include "text-art/box-drawing.h"
> +
> +namespace text_art {
> +
> +class theme
> +{
> + public:
> + enum class cell_kind
> + {
> + /* A left-hand edge of a range e.g. "├". */
> + X_RULER_LEFT_EDGE,
> +
> + /* Within a range e.g. "─". */
> + X_RULER_MIDDLE,
> +
> + /* A border between two neighboring ranges e.g. "┼". */
> + X_RULER_INTERNAL_EDGE,
> +
> + /* The connector with the text label within a range e.g. "┬". */
> + X_RULER_CONNECTOR_TO_LABEL_BELOW,
> +
> + /* As above, but when the text label is above the ruler. */
> + X_RULER_CONNECTOR_TO_LABEL_ABOVE,
> +
> + /* The vertical connection to a text label. */
> + X_RULER_VERTICAL_CONNECTOR,
> +
> + /* A right-hand edge of a range e.g. "┤". */
> + X_RULER_RIGHT_EDGE,
> +
> + TEXT_BORDER_HORIZONTAL,
> + TEXT_BORDER_VERTICAL,
> + TEXT_BORDER_TOP_LEFT,
> + TEXT_BORDER_TOP_RIGHT,
> + TEXT_BORDER_BOTTOM_LEFT,
> + TEXT_BORDER_BOTTOM_RIGHT,
> +
> + Y_ARROW_UP_HEAD,
> + Y_ARROW_UP_TAIL,
> + Y_ARROW_DOWN_HEAD,
> + Y_ARROW_DOWN_TAIL,
> + };
> +
> + virtual ~theme () = default;
> +
> + virtual bool unicode_p () const = 0;
> + virtual bool emojis_p () const = 0;
> +
> + virtual canvas::cell_t
> + get_line_art (directions line_dirs) const = 0;
> +
> + canvas::cell_t get_cell (enum cell_kind kind, unsigned style_idx) const
> + {
> + return canvas::cell_t (get_cppchar (kind), false, style_idx);
> + }
> +
> + virtual cppchar_t get_cppchar (enum cell_kind kind) const = 0;
> +
> + enum class y_arrow_dir { UP, DOWN };
> + void paint_y_arrow (canvas &canvas,
> + int x,
> + canvas::range_t y_range,
> + y_arrow_dir dir,
> + style::id_t style_id) const;
> +};
> +
> +class ascii_theme : public theme
> +{
> + public:
> + bool unicode_p () const final override { return false; }
> + bool emojis_p () const final override { return false; }
> +
> + canvas::cell_t
> + get_line_art (directions line_dirs) const final override;
> +
> + cppchar_t get_cppchar (enum cell_kind kind) const final override;
> +};
> +
> +class unicode_theme : public theme
> +{
> + public:
> + bool unicode_p () const final override { return true; }
> + bool emojis_p () const override { return false; }
> +
> + canvas::cell_t
> + get_line_art (directions line_dirs) const final override;
> +
> + cppchar_t get_cppchar (enum cell_kind kind) const final override;
> +};
> +
> +class emoji_theme : public unicode_theme
> +{
> +public:
> + bool emojis_p () const final override { return true; }
> +};
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_THEME_H */
> diff --git a/gcc/text-art/types.h b/gcc/text-art/types.h
> new file mode 100644
> index 00000000000..b66188ae19c
> --- /dev/null
> +++ b/gcc/text-art/types.h
> @@ -0,0 +1,504 @@
> +/* Types for drawing 2d "text art".
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_TYPES_H
> +#define GCC_TEXT_ART_TYPES_H
> +
> +#include "cpplib.h"
> +#include "pretty-print.h"
> +#include <vector>
> +#include <string>
> +
> +namespace text_art {
> +
> +/* Forward decls. */
> +
> +class canvas;
> +class table;
> +class theme;
> +
> +/* Classes for geometry.
> + We use templates to avoid mixing up e.g. canvas coordinates
> + with table coordinates. */
> +
> +template <typename CoordinateSystem>
> +struct size
> +{
> + size (int w_, int h_) : w (w_), h (h_) {}
> + int w;
> + int h;
> +};
> +
> +template <typename CoordinateSystem>
> +struct coord
> +{
> + coord (int x_, int y_) : x (x_), y (y_) {}
> + int x;
> + int y;
> +};
> +
> +template <typename CoordinateSystem>
> +coord<CoordinateSystem> operator+ (coord<CoordinateSystem> a,
> + coord<CoordinateSystem> b)
> +{
> + return coord<CoordinateSystem> (a.x + b.x, a.y + b.y);
> +}
> +
> +/* A half-open range [start, next) of int. */
> +
> +template <typename CoordinateSystem>
> +struct range
> +{
> + range (int start_, int next_)
> + : start (start_), next (next_)
> + {}
> +
> + int get_min () const { return start; }
> + int get_max () const { return next - 1; }
> + int get_next () const { return next; }
> + int get_size () const { return next - start; }
> +
> + int get_midpoint () const { return get_min () + get_size () / 2; }
> +
> + int start;
> + int next;
> +};
> +
> +/* A rectangle area within CoordinateSystem. */
> +
> +template <typename CoordinateSystem>
> +struct rect
> +{
> + rect (coord<CoordinateSystem> top_left,
> + size<CoordinateSystem> size)
> + : m_top_left (top_left),
> + m_size (size)
> + {
> + }
> +
> + rect (range<CoordinateSystem> x_range,
> + range<CoordinateSystem> y_range)
> + : m_top_left (x_range.get_min (), y_range.get_min ()),
> + m_size (x_range.get_size (), y_range.get_size ())
> + {
> + }
> +
> + int get_min_x () const { return m_top_left.x; }
> + int get_min_y () const { return m_top_left.y; }
> + int get_max_x () const { return m_top_left.x + m_size.w - 1; }
> + int get_max_y () const { return m_top_left.y + m_size.h - 1; }
> + int get_next_x () const { return m_top_left.x + m_size.w; }
> + int get_next_y () const { return m_top_left.y + m_size.h; }
> +
> + range<CoordinateSystem> get_x_range () const
> + {
> + return range<CoordinateSystem> (get_min_x (), get_next_x ());
> + }
> + range<CoordinateSystem> get_y_range () const
> + {
> + return range<CoordinateSystem> (get_min_y (), get_next_y ());
> + }
> +
> + int get_width () const { return m_size.w; }
> + int get_height () const { return m_size.h; }
> +
> + coord<CoordinateSystem> m_top_left;
> + size<CoordinateSystem> m_size;
> +};
> +
> +template <typename ElementType, typename SizeType, typename CoordType>
> +class array2
> +{
> + public:
> + typedef ElementType element_t;
> + typedef SizeType size_t;
> + typedef CoordType coord_t;
> +
> + array2 (size_t sz)
> + : m_size (sz),
> + m_elements (sz.w * sz.h)
> + {
> + }
> + array2 (array2 &&other)
> + : m_size (other.m_size),
> + m_elements (std::move (other.m_elements))
> + {
> + }
> +
> + /* Move assignment not implemented or used. */
> + array2 &operator== (array2 &&other) = delete;
> +
> + /* No copy ctor or assignment op. */
> + array2 (const array2 &other) = delete;
> + array2 &operator= (const array2 &other) = delete;
> +
> +
> + const size_t &get_size () const { return m_size; }
> +
> + void add_row (const element_t &element)
> + {
> + m_size.h++;
> + m_elements.insert (m_elements.end (), m_size.w, element);
> + }
> +
> + const element_t &get (const coord_t &coord) const
> + {
> + ::size_t idx = get_idx (coord);
> + return m_elements[idx];
> + }
> +
> + void set (const coord_t &coord, const element_t &element)
> + {
> + ::size_t idx = get_idx (coord);
> + m_elements[idx] = element;
> + }
> +
> + void fill (element_t element)
> + {
> + for (int y = 0; y < m_size.h; y++)
> + for (int x = 0; x < m_size.w; x++)
> + set (coord_t (x, y), element);
> + }
> +
> + private:
> + ::size_t get_idx (const coord_t &coord) const
> + {
> + gcc_assert (coord.x >= 0);
> + gcc_assert (coord.x < m_size.w);
> + gcc_assert (coord.y >= 0);
> + gcc_assert (coord.y < m_size.h);
> + return (coord.y * m_size.w) + coord.x;
> + }
> +
> + size_t m_size;
> + std::vector<element_t> m_elements;
> +};
> +
> +/* A combination of attributes describing how to style a text cell.
> + We only support those attributes mentioned in invoke.texi:
> + - bold,
> + - underscore,
> + - blink,
> + - inverse,
> + - colors for foreground and background:
> + - default color
> + - named colors
> + - 16-color mode colors (the "bright" variants)
> + - 88-color mode
> + - 256-color mode
> + plus URLs. */
> +
> +struct style
> +{
> + typedef unsigned char id_t;
> + static const id_t id_plain = 0;
> +
> + /* Colors. */
> + enum class named_color
> + {
> + DEFAULT,
> + // ANSI order
> + BLACK,
> + RED,
> + GREEN,
> + YELLOW,
> + BLUE,
> + MAGENTA,
> + CYAN,
> + WHITE
> + };
> +
> +
> + struct color
> + {
> + enum class kind
> + {
> + NAMED,
> + BITS_8,
> + BITS_24,
> + } m_kind;
> +
> + union
> + {
> + struct {
> + enum named_color m_name;
> + bool m_bright;
> + } m_named;
> + uint8_t m_8bit;
> + struct {
> + uint8_t r;
> + uint8_t g;
> + uint8_t b;
> + } m_24bit;
> + } u;
> +
> + /* Constructor for named colors. */
> + color (enum named_color name = named_color::DEFAULT,
> + bool bright = false)
> + : m_kind (kind::NAMED)
> + {
> + u.m_named.m_name = name;
> + u.m_named.m_bright = bright;
> + }
> +
> + /* Constructor for 8-bit colors. */
> + color (uint8_t col_val)
> + : m_kind (kind::BITS_8)
> + {
> + u.m_8bit = col_val;
> + }
> +
> + /* Constructor for 24-bit colors. */
> + color (uint8_t r, uint8_t g, uint8_t b)
> + : m_kind (kind::BITS_24)
> + {
> + u.m_24bit.r = r;
> + u.m_24bit.g = g;
> + u.m_24bit.b = b;
> + }
> +
> + bool operator== (const color &other) const;
> + bool operator!= (const color &other) const
> + {
> + return !(*this == other);
> + }
> +
> + void print_sgr (pretty_printer *pp, bool fg, bool &need_separator) const;
> + };
> +
> + style ()
> + : m_bold (false),
> + m_underscore (false),
> + m_blink (false),
> + m_reverse (false),
> + m_fg_color (named_color::DEFAULT),
> + m_bg_color (named_color::DEFAULT),
> + m_url ()
> + {}
> +
> + bool operator== (const style &other) const
> + {
> + return (m_bold == other.m_bold
> + && m_underscore == other.m_underscore
> + && m_blink == other.m_blink
> + && m_reverse == other.m_reverse
> + && m_fg_color == other.m_fg_color
> + && m_bg_color == other.m_bg_color
> + && m_url == other.m_url);
> + }
> +
> + style &set_style_url (const char *url);
> +
> + static void print_changes (pretty_printer *pp,
> + const style &old_style,
> + const style &new_style);
> +
> + bool m_bold;
> + bool m_underscore;
> + bool m_blink;
> + bool m_reverse;
> + color m_fg_color;
> + color m_bg_color;
> + std::vector<cppchar_t> m_url; // empty = no URL
> +};
> +
> +/* A class to keep track of all the styles in use in a drawing, so that
> + we can refer to them via the compact style::id_t type, rather than
> + via e.g. pointers. */
> +
> +class style_manager
> +{
> + public:
> + style_manager ();
> + style::id_t get_or_create_id (const style &style);
> + const style &get_style (style::id_t id) const
> + {
> + return m_styles[id];
> + }
> + void print_any_style_changes (pretty_printer *pp,
> + style::id_t old_id,
> + style::id_t new_id) const;
> + unsigned get_num_styles () const { return m_styles.size (); }
> +
> +private:
> + std::vector<style> m_styles;
> +};
> +
> +class styled_unichar
> +{
> + public:
> + friend class styled_string;
> +
> + explicit styled_unichar ()
> + : m_code (0),
> + m_style_id (style::id_plain)
> + {}
> + explicit styled_unichar (cppchar_t ch)
> + : m_code (ch),
> + m_emoji_variant_p (false),
> + m_style_id (style::id_plain)
> + {}
> + explicit styled_unichar (cppchar_t ch, bool emoji, style::id_t style_id)
> + : m_code (ch),
> + m_emoji_variant_p (emoji),
> + m_style_id (style_id)
> + {
> + gcc_assert (style_id <= 0x7f);
> + }
> +
> + cppchar_t get_code () const { return m_code; }
> + bool emoji_variant_p () const { return m_emoji_variant_p; }
> + style::id_t get_style_id () const { return m_style_id; }
> +
> + bool double_width_p () const
> + {
> + int width = cpp_wcwidth (get_code ());
> + gcc_assert (width == 1 || width == 2);
> + return width == 2;
> + }
> +
> + bool operator== (const styled_unichar &other) const
> + {
> + return (m_code == other.m_code
> + && m_emoji_variant_p == other.m_emoji_variant_p
> + && m_style_id == other.m_style_id);
> + }
> +
> + void set_emoji_variant () { m_emoji_variant_p = true; }
> +
> + int get_canvas_width () const
> + {
> + return cpp_wcwidth (m_code);
> + }
> +
> + void add_combining_char (cppchar_t ch)
> + {
> + m_combining_chars.push_back (ch);
> + }
> +
> + const std::vector<cppchar_t> get_combining_chars () const
> + {
> + return m_combining_chars;
> + }
> +
> +private:
> + cppchar_t m_code : 24;
> + bool m_emoji_variant_p : 1;
> + style::id_t m_style_id : 7;
> + std::vector<cppchar_t> m_combining_chars;
> +};
> +
> +class styled_string
> +{
> + public:
> + explicit styled_string () = default;
> + explicit styled_string (style_manager &sm, const char *str);
> + explicit styled_string (cppchar_t cppchar, bool emoji = false);
> +
> + styled_string (styled_string &&) = default;
> + styled_string &operator= (styled_string &&) = default;
> +
> + /* No copy ctor or assignment op. */
> + styled_string (const styled_string &) = delete;
> + styled_string &operator= (const styled_string &) = delete;
> +
> + /* For the few cases where copying is required, spell it out explicitly. */
> + styled_string copy () const
> + {
> + styled_string result;
> + result.m_chars = m_chars;
> + return result;
> + }
> +
> + bool operator== (const styled_string &other) const
> + {
> + return m_chars == other.m_chars;
> + }
> +
> + static styled_string from_fmt (style_manager &sm,
> + printer_fn format_decoder,
> + const char *fmt, ...)
> + ATTRIBUTE_GCC_PPDIAG(3, 4);
> + static styled_string from_fmt_va (style_manager &sm,
> + printer_fn format_decoder,
> + const char *fmt,
> + va_list *args)
> + ATTRIBUTE_GCC_PPDIAG(3, 0);
> +
> + size_t size () const { return m_chars.size (); }
> + styled_unichar operator[] (size_t idx) const { return m_chars[idx]; }
> +
> + std::vector<styled_unichar>::const_iterator begin () const
> + {
> + return m_chars.begin ();
> + }
> + std::vector<styled_unichar>::const_iterator end () const
> + {
> + return m_chars.end ();
> + }
> +
> + int calc_canvas_width () const;
> +
> + void append (const styled_string &suffix);
> +
> + void set_url (style_manager &sm, const char *url);
> +
> +private:
> + std::vector<styled_unichar> m_chars;
> +};
> +
> +enum class x_align
> +{
> + LEFT,
> + CENTER,
> + RIGHT
> +};
> +
> +enum class y_align
> +{
> + TOP,
> + CENTER,
> + BOTTOM
> +};
> +
> +/* A set of cardinal directions within a canvas or table. */
> +
> +struct directions
> +{
> +public:
> + directions (bool up, bool down, bool left, bool right)
> + : m_up (up), m_down (down), m_left (left), m_right (right)
> + {
> + }
> +
> + size_t as_index () const
> + {
> + return (m_up << 3) | (m_down << 2) | (m_left << 1) | m_right;
> + }
> +
> + bool m_up: 1;
> + bool m_down: 1;
> + bool m_left: 1;
> + bool m_right: 1;
> +};
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_TYPES_H */
> diff --git a/gcc/text-art/widget.cc b/gcc/text-art/widget.cc
> new file mode 100644
> index 00000000000..e6e544d5035
> --- /dev/null
> +++ b/gcc/text-art/widget.cc
> @@ -0,0 +1,275 @@
> +/* Hierarchical diagram elements.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it under
> +the terms of the GNU General Public License as published by the Free
> +Software Foundation; either version 3, or (at your option) any later
> +version.
> +
> +GCC is distributed in the hope that it will be useful, but WITHOUT ANY
> +WARRANTY; without even the implied warranty of MERCHANTABILITY or
> +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> +for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#include "config.h"
> +#define INCLUDE_MEMORY
> +#include "system.h"
> +#include "coretypes.h"
> +#include "pretty-print.h"
> +#include "selftest.h"
> +#include "make-unique.h"
> +#include "text-art/selftests.h"
> +#include "text-art/widget.h"
> +
> +using namespace text_art;
> +
> +/* class text_art::widget. */
> +
> +canvas
> +widget::to_canvas (const style_manager &style_mgr)
> +{
> + const canvas::size_t req_size = get_req_size ();
> +
> + /* For now we don't constrain the allocation; we give
> + the widget the full size it requested, and widgets
> + assume they got their full size request. */
> + const canvas::size_t alloc_size = req_size;
> +
> + set_alloc_rect (canvas::rect_t (canvas::coord_t (0, 0), alloc_size));
> + canvas c (alloc_size, style_mgr);
> + paint_to_canvas (c);
> + return c;
> +}
> +
> +/* class text_art::vbox_widget : public text_art::container_widget. */
> +
> +const char *
> +vbox_widget::get_desc () const
> +{
> + return "vbox_widget";
> +}
> +
> +canvas::size_t
> +vbox_widget::calc_req_size ()
> +{
> + canvas::size_t result (0, 0);
> + for (auto &child : m_children)
> + {
> + canvas::size_t child_req_size = child->get_req_size();
> + result.h += child_req_size.h;
> + result.w = std::max (result.w, child_req_size.w);
> + }
> + return result;
> +}
> +
> +void
> +vbox_widget::update_child_alloc_rects ()
> +{
> + const int x = get_min_x ();
> + int y = get_min_y ();
> + for (auto &child : m_children)
> + {
> + child->set_alloc_rect
> + (canvas::rect_t (canvas::coord_t (x, y),
> + canvas::size_t (get_alloc_w (),
> + child->get_req_h ())));
> + y += child->get_req_h ();
> + }
> +}
> +
> +/* class text_art::text_widget : public text_art::leaf_widget. */
> +
> +const char *
> +text_widget::get_desc () const
> +{
> + return "text_widget";
> +}
> +
> +canvas::size_t
> +text_widget::calc_req_size ()
> +{
> + return canvas::size_t (m_str.size (), 1);
> +}
> +
> +void
> +text_widget::paint_to_canvas (canvas &canvas)
> +{
> + canvas.paint_text (get_top_left (), m_str);
> +}
> +
> +/* class text_art::canvas_widget : public text_art::leaf_widget. */
> +
> +const char *
> +canvas_widget::get_desc () const
> +{
> + return "canvas_widget";
> +}
> +
> +canvas::size_t
> +canvas_widget::calc_req_size ()
> +{
> + return m_canvas.get_size ();
> +}
> +
> +void
> +canvas_widget::paint_to_canvas (canvas &canvas)
> +{
> + for (int y = 0; y < m_canvas.get_size ().h; y++)
> + for (int x = 0; x < m_canvas.get_size ().w; x++)
> + {
> + canvas::coord_t rel_xy (x, y);
> + canvas.paint (get_top_left () + rel_xy,
> + m_canvas.get (rel_xy));
> + }
> +}
> +
> +#if CHECKING_P
> +
> +namespace selftest {
> +
> +/* Concrete widget subclass for writing selftests.
> + Requests a hard-coded size, and fills its allocated rectangle
> + with a specific character. */
> +
> +class test_widget : public leaf_widget
> +{
> +public:
> + test_widget (canvas::size_t size, char ch)
> + : m_test_size (size), m_ch (ch)
> + {}
> +
> + const char *get_desc () const final override
> + {
> + return "test_widget";
> + }
> + canvas::size_t calc_req_size () final override
> + {
> + return m_test_size;
> + }
> + void paint_to_canvas (canvas &canvas) final override
> + {
> + canvas.fill (get_alloc_rect (), canvas::cell_t (m_ch));
> + }
> +
> +private:
> + canvas::size_t m_test_size;
> + char m_ch;
> +};
> +
> +static void
> +test_test_widget ()
> +{
> + style_manager sm;
> + test_widget w (canvas::size_t (3, 3), 'A');
> + canvas c (w.to_canvas (sm));
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("AAA\n"
> + "AAA\n"
> + "AAA\n"));
> +}
> +
> +static void
> +test_text_widget ()
> +{
> + style_manager sm;
> + text_widget w (styled_string (sm, "hello world"));
> + canvas c (w.to_canvas (sm));
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("hello world\n"));
> +}
> +
> +static void
> +test_wrapper_widget ()
> +{
> + style_manager sm;
> + wrapper_widget w (::make_unique<test_widget> (canvas::size_t (3, 3), 'B'));
> + canvas c (w.to_canvas (sm));
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("BBB\n"
> + "BBB\n"
> + "BBB\n"));
> +}
> +
> +static void
> +test_vbox_1 ()
> +{
> + style_manager sm;
> + vbox_widget w;
> + for (int i = 0; i < 5; i++)
> + w.add_child
> + (::make_unique <text_widget>
> + (styled_string::from_fmt (sm, nullptr,
> + "this is line %i", i)));
> + canvas c (w.to_canvas (sm));
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("this is line 0\n"
> + "this is line 1\n"
> + "this is line 2\n"
> + "this is line 3\n"
> + "this is line 4\n"));
> +}
> +
> +static void
> +test_vbox_2 ()
> +{
> + style_manager sm;
> + vbox_widget w;
> + w.add_child (::make_unique<test_widget> (canvas::size_t (1, 3), 'A'));
> + w.add_child (::make_unique<test_widget> (canvas::size_t (4, 1), 'B'));
> + w.add_child (::make_unique<test_widget> (canvas::size_t (1, 2), 'C'));
> + canvas c (w.to_canvas (sm));
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("AAAA\n"
> + "AAAA\n"
> + "AAAA\n"
> + "BBBB\n"
> + "CCCC\n"
> + "CCCC\n"));
> +}
> +
> +static void
> +test_canvas_widget ()
> +{
> + style_manager sm;
> + canvas inner_canvas (canvas::size_t (5, 3), sm);
> + inner_canvas.fill (canvas::rect_t (canvas::coord_t (0, 0),
> + canvas::size_t (5, 3)),
> + canvas::cell_t ('a'));
> + canvas_widget cw (std::move (inner_canvas));
> + canvas c (cw.to_canvas (sm));
> + ASSERT_CANVAS_STREQ
> + (c, false,
> + ("aaaaa\n"
> + "aaaaa\n"
> + "aaaaa\n"));
> +}
> +
> +/* Run all selftests in this file. */
> +
> +void
> +text_art_widget_cc_tests ()
> +{
> + test_test_widget ();
> + test_text_widget ();
> + test_wrapper_widget ();
> + test_vbox_1 ();
> + test_vbox_2 ();
> + test_canvas_widget ();
> +}
> +
> +} // namespace selftest
> +
> +
> +#endif /* #if CHECKING_P */
> diff --git a/gcc/text-art/widget.h b/gcc/text-art/widget.h
> new file mode 100644
> index 00000000000..91209444bf7
> --- /dev/null
> +++ b/gcc/text-art/widget.h
> @@ -0,0 +1,246 @@
> +/* Hierarchical diagram elements.
> + Copyright (C) 2023 Free Software Foundation, Inc.
> + Contributed by David Malcolm <dmalcolm@redhat.com>.
> +
> +This file is part of GCC.
> +
> +GCC is free software; you can redistribute it and/or modify it
> +under the terms of the GNU General Public License as published by
> +the Free Software Foundation; either version 3, or (at your option)
> +any later version.
> +
> +GCC is distributed in the hope that it will be useful, but
> +WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> +General Public License for more details.
> +
> +You should have received a copy of the GNU General Public License
> +along with GCC; see the file COPYING3. If not see
> +<http://www.gnu.org/licenses/>. */
> +
> +#ifndef GCC_TEXT_ART_WIDGET_H
> +#define GCC_TEXT_ART_WIDGET_H
> +
> +#include <vector>
> +#include "text-art/canvas.h"
> +#include "text-art/table.h"
> +
> +namespace text_art {
> +
> +/* Abstract base class: something that knows how to size itself and
> + how to paint itself to a canvas, potentially with children, with
> + support for hierarchical sizing and positioning.
> +
> + Widgets have a two-phase sizing/positioning algorithm.
> +
> + Step 1: size requests: the root widget is asked for its size request i.e
> + how big it wants to be. This is handled by recursively asking child
> + widgets for their requested sizes. Each widget subclass can implement
> + their own logic for this in the "calc_req_size" vfunc, and the result
> + is cached in m_req_size.
> +
> + Step 2: rect allocation: the root widget is set a canvas::rect_t as
> + its "allocated" rectangle. Each widget subclass can then place its
> + children recursively using the "update_child_alloc_rects" vfunc.
> + For simplicity, all coordinates in the hierarchy are within the same
> + coordinate system (rather than attempting to store per-child offsets).
> +
> + Widget subclasses are responsible for managing their own children. */
> +
> +/* Subclasses in this header, with indentation indicating inheritance. */
> +
> +class widget; /* Abstract base class. */
> + class wrapper_widget; /* Concrete subclass: a widget with a single child. */
> + class container_widget; /* Abstract subclass: widgets with an arbitrary
> + number of children. */
> + class vbox_widget; /* Concrete widget subclass: lay out children
> + vertically. */
> + class leaf_widget; /* Abstract subclass: a widget with no children. */
> + class text_widget; /* Concrete subclass: a text string. */
> + class canvas_widget; /* Concrete subclass: a pre-rendered canvas. */
> +
> +class widget
> +{
> + public:
> + /* This can be very useful for debugging when implementing new
> + widget subclasses. */
> + static const bool DEBUG_GEOMETRY = false;
> +
> + virtual ~widget () {}
> +
> + canvas to_canvas (const style_manager &style_mgr);
> +
> + canvas::size_t get_req_size ()
> + {
> + m_req_size = calc_req_size();
> + if (DEBUG_GEOMETRY)
> + fprintf (stderr, "calc_req_size (%s) -> (w:%i, h:%i)\n",
> + get_desc (),
> + m_req_size.w, m_req_size.h);
> + return m_req_size;
> + }
> +
> + void set_alloc_rect (const canvas::rect_t &rect)
> + {
> + if (DEBUG_GEOMETRY)
> + fprintf (stderr, "set_alloc_rect (%s): ((x:%i, y:%i), (w:%i, h:%i))\n",
> + get_desc (),
> + rect.m_top_left.x, rect.m_top_left.y,
> + rect.m_size.w, rect.m_size.h);
> + m_alloc_rect = rect;
> + update_child_alloc_rects ();
> + }
> +
> + virtual const char *get_desc () const = 0;
> + virtual canvas::size_t calc_req_size () = 0;
> + virtual void update_child_alloc_rects () = 0;
> + virtual void paint_to_canvas (canvas &canvas) = 0;
> +
> + /* Access to the cached size request of this widget. */
> + const canvas::size_t get_req_size () const { return m_req_size; }
> + int get_req_w () const { return m_req_size.w; }
> + int get_req_h () const { return m_req_size.h; }
> +
> + /* Access to the allocated canvas coordinates of this widget. */
> + const canvas::rect_t &get_alloc_rect () const { return m_alloc_rect; }
> + int get_alloc_w () const { return m_alloc_rect.get_width (); }
> + int get_alloc_h () const { return m_alloc_rect.get_height (); }
> + int get_min_x () const { return m_alloc_rect.get_min_x (); }
> + int get_max_x () const { return m_alloc_rect.get_max_x (); }
> + int get_next_x () const { return m_alloc_rect.get_next_x (); }
> + int get_min_y () const { return m_alloc_rect.get_min_y (); }
> + int get_max_y () const { return m_alloc_rect.get_max_y (); }
> + int get_next_y () const { return m_alloc_rect.get_max_y (); }
> + canvas::range_t get_x_range () const { return m_alloc_rect.get_x_range (); }
> + canvas::range_t get_y_range () const { return m_alloc_rect.get_y_range (); }
> + const canvas::coord_t &get_top_left () const
> + {
> + return m_alloc_rect.m_top_left;
> + }
> +
> + protected:
> + widget ()
> + : m_req_size (0, 0),
> + m_alloc_rect (canvas::coord_t (0, 0),
> + canvas::size_t (0, 0))
> + {}
> +
> +private:
> + /* How much size this widget requested. */
> + canvas::size_t m_req_size;
> + /* Where (and how big) this widget was allocated. */
> + canvas::rect_t m_alloc_rect;
> +};
> +
> +/* Concrete subclass for a widget with a single child. */
> +
> +class wrapper_widget : public widget
> +{
> + public:
> + wrapper_widget (std::unique_ptr<widget> child)
> + : m_child (std::move (child))
> + {}
> +
> + const char *get_desc () const override
> + {
> + return "wrapper_widget";
> + }
> + canvas::size_t calc_req_size () override
> + {
> + return m_child->get_req_size ();
> + }
> + void update_child_alloc_rects ()
> + {
> + m_child->set_alloc_rect (get_alloc_rect ());
> + }
> + void paint_to_canvas (canvas &canvas) override
> + {
> + m_child->paint_to_canvas (canvas);
> + }
> + private:
> + std::unique_ptr<widget> m_child;
> +};
> +
> +/* Abstract subclass for widgets with an arbitrary number of children. */
> +
> +class container_widget : public widget
> +{
> + public:
> + void add_child (std::unique_ptr<widget> child)
> + {
> + m_children.push_back (std::move (child));
> + }
> +
> + void paint_to_canvas (canvas &canvas) final override
> + {
> + for (auto &child : m_children)
> + child->paint_to_canvas (canvas);
> + }
> +
> + protected:
> + std::vector<std::unique_ptr<widget>> m_children;
> +};
> +
> +/* Concrete widget subclass: lay out children vertically. */
> +
> +class vbox_widget : public container_widget
> +{
> + public:
> + const char *get_desc () const override;
> + canvas::size_t calc_req_size () override;
> + void update_child_alloc_rects () final override;
> +};
> +
> +/* Abstract subclass for widgets with no children. */
> +
> +class leaf_widget : public widget
> +{
> + public:
> + void update_child_alloc_rects () final override
> + {
> + /* no-op. */
> + }
> +
> + protected:
> + leaf_widget () : widget () {}
> +};
> +
> +/* Concrete widget subclass for a text string. */
> +
> +class text_widget : public leaf_widget
> +{
> + public:
> + text_widget (styled_string str)
> + : leaf_widget (), m_str (std::move (str))
> + {
> + }
> +
> + const char *get_desc () const override;
> + canvas::size_t calc_req_size () final override;
> + void paint_to_canvas (canvas &canvas) final override;
> +
> +private:
> + styled_string m_str;
> +};
> +
> +/* Concrete widget subclass for a pre-rendered canvas. */
> +
> +class canvas_widget : public leaf_widget
> +{
> + public:
> + canvas_widget (canvas &&c)
> + : leaf_widget (), m_canvas (std::move (c))
> + {
> + }
> +
> + const char *get_desc () const override;
> + canvas::size_t calc_req_size () final override;
> + void paint_to_canvas (canvas &canvas) final override;
> +
> +private:
> + canvas m_canvas;
> +};
> +
> +} // namespace text_art
> +
> +#endif /* GCC_TEXT_ART_WIDGET_H */
> diff --git a/libcpp/charset.cc b/libcpp/charset.cc
> index d7f323b2cd5..a0bd2ede11c 100644
> --- a/libcpp/charset.cc
> +++ b/libcpp/charset.cc
> @@ -3147,34 +3147,26 @@ cpp_display_column_to_byte_column (const char *data, int data_length,
> return dw.bytes_processed () + MAX (0, display_col - avail_display);
> }
>
> -/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc,
> - because that will inspect the user's locale, and in particular in an ASCII
> - locale, it will not return anything useful for extended characters. But GCC
> - in other respects (see e.g. _cpp_default_encoding()) behaves as if
> - everything is UTF-8. We also make some tweaks that are useful for the way
> - GCC needs to use this data, e.g. tabs and other control characters should be
> - treated as having width 1. The lookup tables are generated from
> - contrib/unicode/gen_wcwidth.py and were made by simply calling glibc
> - wcwidth() on all codepoints, then applying the small tweaks. These tables
> - are not highly optimized, but for the present purpose of outputting
> - diagnostics, they are sufficient. */
> -
> -#include "generated_cpp_wcwidth.h"
> -int cpp_wcwidth (cppchar_t c)
> +template <typename PropertyType>
> +PropertyType
> +get_cppchar_property (cppchar_t c,
> + const cppchar_t *range_ends,
> + const PropertyType *range_values,
> + size_t num_ranges,
> + PropertyType default_value)
> {
> - if (__builtin_expect (c <= wcwidth_range_ends[0], true))
> - return wcwidth_widths[0];
> + if (__builtin_expect (c <= range_ends[0], true))
> + return range_values[0];
>
> /* Binary search the tables. */
> int begin = 1;
> - static const int end
> - = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends);
> + static const int end = num_ranges;
> int len = end - begin;
> do
> {
> int half = len/2;
> int middle = begin + half;
> - if (c > wcwidth_range_ends[middle])
> + if (c > range_ends[middle])
> {
> begin = middle + 1;
> len -= half + 1;
> @@ -3184,6 +3176,61 @@ int cpp_wcwidth (cppchar_t c)
> } while (len);
>
> if (__builtin_expect (begin != end, true))
> - return wcwidth_widths[begin];
> - return 1;
> + return range_values[begin];
> +
> + return default_value;
> +}
> +
> +/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc,
> + because that will inspect the user's locale, and in particular in an ASCII
> + locale, it will not return anything useful for extended characters. But GCC
> + in other respects (see e.g. _cpp_default_encoding()) behaves as if
> + everything is UTF-8. We also make some tweaks that are useful for the way
> + GCC needs to use this data, e.g. tabs and other control characters should be
> + treated as having width 1. The lookup tables are generated from
> + contrib/unicode/gen_wcwidth.py and were made by simply calling glibc
> + wcwidth() on all codepoints, then applying the small tweaks. These tables
> + are not highly optimized, but for the present purpose of outputting
> + diagnostics, they are sufficient. */
> +
> +#include "generated_cpp_wcwidth.h"
> +
> +int
> +cpp_wcwidth (cppchar_t c)
> +{
> + const size_t num_ranges
> + = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends);
> + return get_cppchar_property<unsigned char > (c,
> + &wcwidth_range_ends[0],
> + &wcwidth_widths[0],
> + num_ranges,
> + 1);
> +}
> +
> +#include "combining-chars.inc"
> +
> +bool
> +cpp_is_combining_char (cppchar_t c)
> +{
> + const size_t num_ranges
> + = sizeof combining_range_ends / sizeof (*combining_range_ends);
> + return get_cppchar_property<bool> (c,
> + &combining_range_ends[0],
> + &is_combining[0],
> + num_ranges,
> + false);
> +}
> +
> +#include "printable-chars.inc"
> +
> +bool
> +cpp_is_printable_char (cppchar_t c)
> +{
> + const size_t num_ranges
> + = sizeof printable_range_ends / sizeof (*printable_range_ends);
> + return get_cppchar_property<bool> (c,
> + &printable_range_ends[0],
> + &is_printable[0],
> + num_ranges,
> + false);
> }
> diff --git a/libcpp/combining-chars.inc b/libcpp/combining-chars.inc
> new file mode 100644
> index 00000000000..dfec966970a
> --- /dev/null
> +++ b/libcpp/combining-chars.inc
> @@ -0,0 +1,68 @@
> +/* Generated by contrib/unicode/gen-combining-chars.py
> + using version 12.1.0 of the Unicode standard. */
> +
> +static const cppchar_t combining_range_ends[] = {
> + 0x2ff, 0x34e, 0x34f, 0x36f, 0x482, 0x487, 0x590, 0x5bd,
> + 0x5be, 0x5bf, 0x5c0, 0x5c2, 0x5c3, 0x5c5, 0x5c6, 0x5c7,
> + 0x60f, 0x61a, 0x64a, 0x65f, 0x66f, 0x670, 0x6d5, 0x6dc,
> + 0x6de, 0x6e4, 0x6e6, 0x6e8, 0x6e9, 0x6ed, 0x710, 0x711,
> + 0x72f, 0x74a, 0x7ea, 0x7f3, 0x7fc, 0x7fd, 0x815, 0x819,
> + 0x81a, 0x823, 0x824, 0x827, 0x828, 0x82d, 0x858, 0x85b,
> + 0x8d2, 0x8e1, 0x8e2, 0x8ff, 0x93b, 0x93c, 0x94c, 0x94d,
> + 0x950, 0x954, 0x9bb, 0x9bc, 0x9cc, 0x9cd, 0x9fd, 0x9fe,
> + 0xa3b, 0xa3c, 0xa4c, 0xa4d, 0xabb, 0xabc, 0xacc, 0xacd,
> + 0xb3b, 0xb3c, 0xb4c, 0xb4d, 0xbcc, 0xbcd, 0xc4c, 0xc4d,
> + 0xc54, 0xc56, 0xcbb, 0xcbc, 0xccc, 0xccd, 0xd3a, 0xd3c,
> + 0xd4c, 0xd4d, 0xdc9, 0xdca, 0xe37, 0xe3a, 0xe47, 0xe4b,
> + 0xeb7, 0xeba, 0xec7, 0xecb, 0xf17, 0xf19, 0xf34, 0xf35,
> + 0xf36, 0xf37, 0xf38, 0xf39, 0xf70, 0xf72, 0xf73, 0xf74,
> + 0xf79, 0xf7d, 0xf7f, 0xf80, 0xf81, 0xf84, 0xf85, 0xf87,
> + 0xfc5, 0xfc6, 0x1036, 0x1037, 0x1038, 0x103a, 0x108c, 0x108d,
> + 0x135c, 0x135f, 0x1713, 0x1714, 0x1733, 0x1734, 0x17d1, 0x17d2,
> + 0x17dc, 0x17dd, 0x18a8, 0x18a9, 0x1938, 0x193b, 0x1a16, 0x1a18,
> + 0x1a5f, 0x1a60, 0x1a74, 0x1a7c, 0x1a7e, 0x1a7f, 0x1aaf, 0x1abd,
> + 0x1b33, 0x1b34, 0x1b43, 0x1b44, 0x1b6a, 0x1b73, 0x1ba9, 0x1bab,
> + 0x1be5, 0x1be6, 0x1bf1, 0x1bf3, 0x1c36, 0x1c37, 0x1ccf, 0x1cd2,
> + 0x1cd3, 0x1ce0, 0x1ce1, 0x1ce8, 0x1cec, 0x1ced, 0x1cf3, 0x1cf4,
> + 0x1cf7, 0x1cf9, 0x1dbf, 0x1df9, 0x1dfa, 0x1dff, 0x20cf, 0x20dc,
> + 0x20e0, 0x20e1, 0x20e4, 0x20f0, 0x2cee, 0x2cf1, 0x2d7e, 0x2d7f,
> + 0x2ddf, 0x2dff, 0x3029, 0x302f, 0x3098, 0x309a, 0xa66e, 0xa66f,
> + 0xa673, 0xa67d, 0xa69d, 0xa69f, 0xa6ef, 0xa6f1, 0xa805, 0xa806,
> + 0xa8c3, 0xa8c4, 0xa8df, 0xa8f1, 0xa92a, 0xa92d, 0xa952, 0xa953,
> + 0xa9b2, 0xa9b3, 0xa9bf, 0xa9c0, 0xaaaf, 0xaab0, 0xaab1, 0xaab4,
> + 0xaab6, 0xaab8, 0xaabd, 0xaabf, 0xaac0, 0xaac1, 0xaaf5, 0xaaf6,
> + 0xabec, 0xabed, 0xfb1d, 0xfb1e, 0xfe1f, 0xfe2f, 0x101fc, 0x101fd,
> + 0x102df, 0x102e0, 0x10375, 0x1037a, 0x10a0c, 0x10a0d, 0x10a0e, 0x10a0f,
> + 0x10a37, 0x10a3a, 0x10a3e, 0x10a3f, 0x10ae4, 0x10ae6, 0x10d23, 0x10d27,
> + 0x10f45, 0x10f50, 0x11045, 0x11046, 0x1107e, 0x1107f, 0x110b8, 0x110ba,
> + 0x110ff, 0x11102, 0x11132, 0x11134, 0x11172, 0x11173, 0x111bf, 0x111c0,
> + 0x111c9, 0x111ca, 0x11234, 0x11236, 0x112e8, 0x112ea, 0x1133a, 0x1133c,
> + 0x1134c, 0x1134d, 0x11365, 0x1136c, 0x1136f, 0x11374, 0x11441, 0x11442,
> + 0x11445, 0x11446, 0x1145d, 0x1145e, 0x114c1, 0x114c3, 0x115be, 0x115c0,
> + 0x1163e, 0x1163f, 0x116b5, 0x116b7, 0x1172a, 0x1172b, 0x11838, 0x1183a,
> + 0x119df, 0x119e0, 0x11a33, 0x11a34, 0x11a46, 0x11a47, 0x11a98, 0x11a99,
> + 0x11c3e, 0x11c3f, 0x11d41, 0x11d42, 0x11d43, 0x11d45, 0x11d96, 0x11d97,
> + 0x16aef, 0x16af4, 0x16b2f, 0x16b36, 0x1bc9d, 0x1bc9e, 0x1d164, 0x1d169,
> + 0x1d16c, 0x1d172, 0x1d17a, 0x1d182, 0x1d184, 0x1d18b, 0x1d1a9, 0x1d1ad,
> + 0x1d241, 0x1d244, 0x1dfff, 0x1e006, 0x1e007, 0x1e018, 0x1e01a, 0x1e021,
> + 0x1e022, 0x1e024, 0x1e025, 0x1e02a, 0x1e12f, 0x1e136, 0x1e2eb, 0x1e2ef,
> + 0x1e8cf, 0x1e8d6, 0x1e943, 0x1e94a, 0x10fffe,
> +};
> +
> +static const bool is_combining[] = {
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
> +};
> diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
> index a6f0abd894c..d326f5aa316 100644
> --- a/libcpp/include/cpplib.h
> +++ b/libcpp/include/cpplib.h
> @@ -1602,4 +1602,7 @@ bool cpp_input_conversion_is_trivial (const char *input_charset);
> int cpp_check_utf8_bom (const char *data, size_t data_length);
> bool cpp_valid_utf8_p (const char *data, size_t num_bytes);
>
> +bool cpp_is_combining_char (cppchar_t c);
> +bool cpp_is_printable_char (cppchar_t c);
> +
> #endif /* ! LIBCPP_CPPLIB_H */
> diff --git a/libcpp/printable-chars.inc b/libcpp/printable-chars.inc
> new file mode 100644
> index 00000000000..470b1eef331
> --- /dev/null
> +++ b/libcpp/printable-chars.inc
> @@ -0,0 +1,231 @@
> +/* Generated by contrib/unicode/gen-printable-chars.py
> + using version 12.1.0 of the Unicode standard. */
> +
> +static const cppchar_t printable_range_ends[] = {
> + 0x1f, 0x7e, 0x9f, 0xac, 0xad, 0x377, 0x379, 0x37f,
> + 0x383, 0x38a, 0x38b, 0x38c, 0x38d, 0x3a1, 0x3a2, 0x52f,
> + 0x530, 0x556, 0x558, 0x58a, 0x58c, 0x58f, 0x590, 0x5c7,
> + 0x5cf, 0x5ea, 0x5ee, 0x5f4, 0x605, 0x61b, 0x61d, 0x6dc,
> + 0x6dd, 0x70d, 0x70f, 0x74a, 0x74c, 0x7b1, 0x7bf, 0x7fa,
> + 0x7fc, 0x82d, 0x82f, 0x83e, 0x83f, 0x85b, 0x85d, 0x85e,
> + 0x85f, 0x86a, 0x89f, 0x8b4, 0x8b5, 0x8bd, 0x8d2, 0x8e1,
> + 0x8e2, 0x983, 0x984, 0x98c, 0x98e, 0x990, 0x992, 0x9a8,
> + 0x9a9, 0x9b0, 0x9b1, 0x9b2, 0x9b5, 0x9b9, 0x9bb, 0x9c4,
> + 0x9c6, 0x9c8, 0x9ca, 0x9ce, 0x9d6, 0x9d7, 0x9db, 0x9dd,
> + 0x9de, 0x9e3, 0x9e5, 0x9fe, 0xa00, 0xa03, 0xa04, 0xa0a,
> + 0xa0e, 0xa10, 0xa12, 0xa28, 0xa29, 0xa30, 0xa31, 0xa33,
> + 0xa34, 0xa36, 0xa37, 0xa39, 0xa3b, 0xa3c, 0xa3d, 0xa42,
> + 0xa46, 0xa48, 0xa4a, 0xa4d, 0xa50, 0xa51, 0xa58, 0xa5c,
> + 0xa5d, 0xa5e, 0xa65, 0xa76, 0xa80, 0xa83, 0xa84, 0xa8d,
> + 0xa8e, 0xa91, 0xa92, 0xaa8, 0xaa9, 0xab0, 0xab1, 0xab3,
> + 0xab4, 0xab9, 0xabb, 0xac5, 0xac6, 0xac9, 0xaca, 0xacd,
> + 0xacf, 0xad0, 0xadf, 0xae3, 0xae5, 0xaf1, 0xaf8, 0xaff,
> + 0xb00, 0xb03, 0xb04, 0xb0c, 0xb0e, 0xb10, 0xb12, 0xb28,
> + 0xb29, 0xb30, 0xb31, 0xb33, 0xb34, 0xb39, 0xb3b, 0xb44,
> + 0xb46, 0xb48, 0xb4a, 0xb4d, 0xb55, 0xb57, 0xb5b, 0xb5d,
> + 0xb5e, 0xb63, 0xb65, 0xb77, 0xb81, 0xb83, 0xb84, 0xb8a,
> + 0xb8d, 0xb90, 0xb91, 0xb95, 0xb98, 0xb9a, 0xb9b, 0xb9c,
> + 0xb9d, 0xb9f, 0xba2, 0xba4, 0xba7, 0xbaa, 0xbad, 0xbb9,
> + 0xbbd, 0xbc2, 0xbc5, 0xbc8, 0xbc9, 0xbcd, 0xbcf, 0xbd0,
> + 0xbd6, 0xbd7, 0xbe5, 0xbfa, 0xbff, 0xc0c, 0xc0d, 0xc10,
> + 0xc11, 0xc28, 0xc29, 0xc39, 0xc3c, 0xc44, 0xc45, 0xc48,
> + 0xc49, 0xc4d, 0xc54, 0xc56, 0xc57, 0xc5a, 0xc5f, 0xc63,
> + 0xc65, 0xc6f, 0xc76, 0xc8c, 0xc8d, 0xc90, 0xc91, 0xca8,
> + 0xca9, 0xcb3, 0xcb4, 0xcb9, 0xcbb, 0xcc4, 0xcc5, 0xcc8,
> + 0xcc9, 0xccd, 0xcd4, 0xcd6, 0xcdd, 0xcde, 0xcdf, 0xce3,
> + 0xce5, 0xcef, 0xcf0, 0xcf2, 0xcff, 0xd03, 0xd04, 0xd0c,
> + 0xd0d, 0xd10, 0xd11, 0xd44, 0xd45, 0xd48, 0xd49, 0xd4f,
> + 0xd53, 0xd63, 0xd65, 0xd7f, 0xd81, 0xd83, 0xd84, 0xd96,
> + 0xd99, 0xdb1, 0xdb2, 0xdbb, 0xdbc, 0xdbd, 0xdbf, 0xdc6,
> + 0xdc9, 0xdca, 0xdce, 0xdd4, 0xdd5, 0xdd6, 0xdd7, 0xddf,
> + 0xde5, 0xdef, 0xdf1, 0xdf4, 0xe00, 0xe3a, 0xe3e, 0xe5b,
> + 0xe80, 0xe82, 0xe83, 0xe84, 0xe85, 0xe8a, 0xe8b, 0xea3,
> + 0xea4, 0xea5, 0xea6, 0xebd, 0xebf, 0xec4, 0xec5, 0xec6,
> + 0xec7, 0xecd, 0xecf, 0xed9, 0xedb, 0xedf, 0xeff, 0xf47,
> + 0xf48, 0xf6c, 0xf70, 0xf97, 0xf98, 0xfbc, 0xfbd, 0xfcc,
> + 0xfcd, 0xfda, 0xfff, 0x10c5, 0x10c6, 0x10c7, 0x10cc, 0x10cd,
> + 0x10cf, 0x1248, 0x1249, 0x124d, 0x124f, 0x1256, 0x1257, 0x1258,
> + 0x1259, 0x125d, 0x125f, 0x1288, 0x1289, 0x128d, 0x128f, 0x12b0,
> + 0x12b1, 0x12b5, 0x12b7, 0x12be, 0x12bf, 0x12c0, 0x12c1, 0x12c5,
> + 0x12c7, 0x12d6, 0x12d7, 0x1310, 0x1311, 0x1315, 0x1317, 0x135a,
> + 0x135c, 0x137c, 0x137f, 0x1399, 0x139f, 0x13f5, 0x13f7, 0x13fd,
> + 0x13ff, 0x169c, 0x169f, 0x16f8, 0x16ff, 0x170c, 0x170d, 0x1714,
> + 0x171f, 0x1736, 0x173f, 0x1753, 0x175f, 0x176c, 0x176d, 0x1770,
> + 0x1771, 0x1773, 0x177f, 0x17dd, 0x17df, 0x17e9, 0x17ef, 0x17f9,
> + 0x17ff, 0x180d, 0x180f, 0x1819, 0x181f, 0x1878, 0x187f, 0x18aa,
> + 0x18af, 0x18f5, 0x18ff, 0x191e, 0x191f, 0x192b, 0x192f, 0x193b,
> + 0x193f, 0x1940, 0x1943, 0x196d, 0x196f, 0x1974, 0x197f, 0x19ab,
> + 0x19af, 0x19c9, 0x19cf, 0x19da, 0x19dd, 0x1a1b, 0x1a1d, 0x1a5e,
> + 0x1a5f, 0x1a7c, 0x1a7e, 0x1a89, 0x1a8f, 0x1a99, 0x1a9f, 0x1aad,
> + 0x1aaf, 0x1abe, 0x1aff, 0x1b4b, 0x1b4f, 0x1b7c, 0x1b7f, 0x1bf3,
> + 0x1bfb, 0x1c37, 0x1c3a, 0x1c49, 0x1c4c, 0x1c88, 0x1c8f, 0x1cba,
> + 0x1cbc, 0x1cc7, 0x1ccf, 0x1cfa, 0x1cff, 0x1df9, 0x1dfa, 0x1f15,
> + 0x1f17, 0x1f1d, 0x1f1f, 0x1f45, 0x1f47, 0x1f4d, 0x1f4f, 0x1f57,
> + 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f7d,
> + 0x1f7f, 0x1fb4, 0x1fb5, 0x1fc4, 0x1fc5, 0x1fd3, 0x1fd5, 0x1fdb,
> + 0x1fdc, 0x1fef, 0x1ff1, 0x1ff4, 0x1ff5, 0x1ffe, 0x1fff, 0x200a,
> + 0x200f, 0x2029, 0x202e, 0x205f, 0x206f, 0x2071, 0x2073, 0x208e,
> + 0x208f, 0x209c, 0x209f, 0x20bf, 0x20cf, 0x20f0, 0x20ff, 0x218b,
> + 0x218f, 0x2426, 0x243f, 0x244a, 0x245f, 0x2b73, 0x2b75, 0x2b95,
> + 0x2b97, 0x2c2e, 0x2c2f, 0x2c5e, 0x2c5f, 0x2cf3, 0x2cf8, 0x2d25,
> + 0x2d26, 0x2d27, 0x2d2c, 0x2d2d, 0x2d2f, 0x2d67, 0x2d6e, 0x2d70,
> + 0x2d7e, 0x2d96, 0x2d9f, 0x2da6, 0x2da7, 0x2dae, 0x2daf, 0x2db6,
> + 0x2db7, 0x2dbe, 0x2dbf, 0x2dc6, 0x2dc7, 0x2dce, 0x2dcf, 0x2dd6,
> + 0x2dd7, 0x2dde, 0x2ddf, 0x2e4f, 0x2e7f, 0x2e99, 0x2e9a, 0x2ef3,
> + 0x2eff, 0x2fd5, 0x2fef, 0x2ffb, 0x2fff, 0x303f, 0x3040, 0x3096,
> + 0x3098, 0x30ff, 0x3104, 0x312f, 0x3130, 0x318e, 0x318f, 0x31ba,
> + 0x31bf, 0x31e3, 0x31ef, 0x321e, 0x321f, 0x4db5, 0x4dbf, 0x9fef,
> + 0x9fff, 0xa48c, 0xa48f, 0xa4c6, 0xa4cf, 0xa62b, 0xa63f, 0xa6f7,
> + 0xa6ff, 0xa7bf, 0xa7c1, 0xa7c6, 0xa7f6, 0xa82b, 0xa82f, 0xa839,
> + 0xa83f, 0xa877, 0xa87f, 0xa8c5, 0xa8cd, 0xa8d9, 0xa8df, 0xa953,
> + 0xa95e, 0xa97c, 0xa97f, 0xa9cd, 0xa9ce, 0xa9d9, 0xa9dd, 0xa9fe,
> + 0xa9ff, 0xaa36, 0xaa3f, 0xaa4d, 0xaa4f, 0xaa59, 0xaa5b, 0xaac2,
> + 0xaada, 0xaaf6, 0xab00, 0xab06, 0xab08, 0xab0e, 0xab10, 0xab16,
> + 0xab1f, 0xab26, 0xab27, 0xab2e, 0xab2f, 0xab67, 0xab6f, 0xabed,
> + 0xabef, 0xabf9, 0xabff, 0xd7a3, 0xd7af, 0xd7c6, 0xd7ca, 0xd7fb,
> + 0xf8ff, 0xfa6d, 0xfa6f, 0xfad9, 0xfaff, 0xfb06, 0xfb12, 0xfb17,
> + 0xfb1c, 0xfb36, 0xfb37, 0xfb3c, 0xfb3d, 0xfb3e, 0xfb3f, 0xfb41,
> + 0xfb42, 0xfb44, 0xfb45, 0xfbc1, 0xfbd2, 0xfd3f, 0xfd4f, 0xfd8f,
> + 0xfd91, 0xfdc7, 0xfdef, 0xfdfd, 0xfdff, 0xfe19, 0xfe1f, 0xfe52,
> + 0xfe53, 0xfe66, 0xfe67, 0xfe6b, 0xfe6f, 0xfe74, 0xfe75, 0xfefc,
> + 0xff00, 0xffbe, 0xffc1, 0xffc7, 0xffc9, 0xffcf, 0xffd1, 0xffd7,
> + 0xffd9, 0xffdc, 0xffdf, 0xffe6, 0xffe7, 0xffee, 0xfffb, 0xfffd,
> + 0xffff, 0x1000b, 0x1000c, 0x10026, 0x10027, 0x1003a, 0x1003b, 0x1003d,
> + 0x1003e, 0x1004d, 0x1004f, 0x1005d, 0x1007f, 0x100fa, 0x100ff, 0x10102,
> + 0x10106, 0x10133, 0x10136, 0x1018e, 0x1018f, 0x1019b, 0x1019f, 0x101a0,
> + 0x101cf, 0x101fd, 0x1027f, 0x1029c, 0x1029f, 0x102d0, 0x102df, 0x102fb,
> + 0x102ff, 0x10323, 0x1032c, 0x1034a, 0x1034f, 0x1037a, 0x1037f, 0x1039d,
> + 0x1039e, 0x103c3, 0x103c7, 0x103d5, 0x103ff, 0x1049d, 0x1049f, 0x104a9,
> + 0x104af, 0x104d3, 0x104d7, 0x104fb, 0x104ff, 0x10527, 0x1052f, 0x10563,
> + 0x1056e, 0x1056f, 0x105ff, 0x10736, 0x1073f, 0x10755, 0x1075f, 0x10767,
> + 0x107ff, 0x10805, 0x10807, 0x10808, 0x10809, 0x10835, 0x10836, 0x10838,
> + 0x1083b, 0x1083c, 0x1083e, 0x10855, 0x10856, 0x1089e, 0x108a6, 0x108af,
> + 0x108df, 0x108f2, 0x108f3, 0x108f5, 0x108fa, 0x1091b, 0x1091e, 0x10939,
> + 0x1093e, 0x1093f, 0x1097f, 0x109b7, 0x109bb, 0x109cf, 0x109d1, 0x10a03,
> + 0x10a04, 0x10a06, 0x10a0b, 0x10a13, 0x10a14, 0x10a17, 0x10a18, 0x10a35,
> + 0x10a37, 0x10a3a, 0x10a3e, 0x10a48, 0x10a4f, 0x10a58, 0x10a5f, 0x10a9f,
> + 0x10abf, 0x10ae6, 0x10aea, 0x10af6, 0x10aff, 0x10b35, 0x10b38, 0x10b55,
> + 0x10b57, 0x10b72, 0x10b77, 0x10b91, 0x10b98, 0x10b9c, 0x10ba8, 0x10baf,
> + 0x10bff, 0x10c48, 0x10c7f, 0x10cb2, 0x10cbf, 0x10cf2, 0x10cf9, 0x10d27,
> + 0x10d2f, 0x10d39, 0x10e5f, 0x10e7e, 0x10eff, 0x10f27, 0x10f2f, 0x10f59,
> + 0x10fdf, 0x10ff6, 0x10fff, 0x1104d, 0x11051, 0x1106f, 0x1107e, 0x110bc,
> + 0x110bd, 0x110c1, 0x110cf, 0x110e8, 0x110ef, 0x110f9, 0x110ff, 0x11134,
> + 0x11135, 0x11146, 0x1114f, 0x11176, 0x1117f, 0x111cd, 0x111cf, 0x111df,
> + 0x111e0, 0x111f4, 0x111ff, 0x11211, 0x11212, 0x1123e, 0x1127f, 0x11286,
> + 0x11287, 0x11288, 0x11289, 0x1128d, 0x1128e, 0x1129d, 0x1129e, 0x112a9,
> + 0x112af, 0x112ea, 0x112ef, 0x112f9, 0x112ff, 0x11303, 0x11304, 0x1130c,
> + 0x1130e, 0x11310, 0x11312, 0x11328, 0x11329, 0x11330, 0x11331, 0x11333,
> + 0x11334, 0x11339, 0x1133a, 0x11344, 0x11346, 0x11348, 0x1134a, 0x1134d,
> + 0x1134f, 0x11350, 0x11356, 0x11357, 0x1135c, 0x11363, 0x11365, 0x1136c,
> + 0x1136f, 0x11374, 0x113ff, 0x11459, 0x1145a, 0x1145b, 0x1145c, 0x1145f,
> + 0x1147f, 0x114c7, 0x114cf, 0x114d9, 0x1157f, 0x115b5, 0x115b7, 0x115dd,
> + 0x115ff, 0x11644, 0x1164f, 0x11659, 0x1165f, 0x1166c, 0x1167f, 0x116b8,
> + 0x116bf, 0x116c9, 0x116ff, 0x1171a, 0x1171c, 0x1172b, 0x1172f, 0x1173f,
> + 0x117ff, 0x1183b, 0x1189f, 0x118f2, 0x118fe, 0x118ff, 0x1199f, 0x119a7,
> + 0x119a9, 0x119d7, 0x119d9, 0x119e4, 0x119ff, 0x11a47, 0x11a4f, 0x11aa2,
> + 0x11abf, 0x11af8, 0x11bff, 0x11c08, 0x11c09, 0x11c36, 0x11c37, 0x11c45,
> + 0x11c4f, 0x11c6c, 0x11c6f, 0x11c8f, 0x11c91, 0x11ca7, 0x11ca8, 0x11cb6,
> + 0x11cff, 0x11d06, 0x11d07, 0x11d09, 0x11d0a, 0x11d36, 0x11d39, 0x11d3a,
> + 0x11d3b, 0x11d3d, 0x11d3e, 0x11d47, 0x11d4f, 0x11d59, 0x11d5f, 0x11d65,
> + 0x11d66, 0x11d68, 0x11d69, 0x11d8e, 0x11d8f, 0x11d91, 0x11d92, 0x11d98,
> + 0x11d9f, 0x11da9, 0x11edf, 0x11ef8, 0x11fbf, 0x11ff1, 0x11ffe, 0x12399,
> + 0x123ff, 0x1246e, 0x1246f, 0x12474, 0x1247f, 0x12543, 0x12fff, 0x1342e,
> + 0x143ff, 0x14646, 0x167ff, 0x16a38, 0x16a3f, 0x16a5e, 0x16a5f, 0x16a69,
> + 0x16a6d, 0x16a6f, 0x16acf, 0x16aed, 0x16aef, 0x16af5, 0x16aff, 0x16b45,
> + 0x16b4f, 0x16b59, 0x16b5a, 0x16b61, 0x16b62, 0x16b77, 0x16b7c, 0x16b8f,
> + 0x16e3f, 0x16e9a, 0x16eff, 0x16f4a, 0x16f4e, 0x16f87, 0x16f8e, 0x16f9f,
> + 0x16fdf, 0x16fe3, 0x16fff, 0x187f7, 0x187ff, 0x18af2, 0x1afff, 0x1b11e,
> + 0x1b14f, 0x1b152, 0x1b163, 0x1b167, 0x1b16f, 0x1b2fb, 0x1bbff, 0x1bc6a,
> + 0x1bc6f, 0x1bc7c, 0x1bc7f, 0x1bc88, 0x1bc8f, 0x1bc99, 0x1bc9b, 0x1bc9f,
> + 0x1cfff, 0x1d0f5, 0x1d0ff, 0x1d126, 0x1d128, 0x1d172, 0x1d17a, 0x1d1e8,
> + 0x1d1ff, 0x1d245, 0x1d2df, 0x1d2f3, 0x1d2ff, 0x1d356, 0x1d35f, 0x1d378,
> + 0x1d3ff, 0x1d454, 0x1d455, 0x1d49c, 0x1d49d, 0x1d49f, 0x1d4a1, 0x1d4a2,
> + 0x1d4a4, 0x1d4a6, 0x1d4a8, 0x1d4ac, 0x1d4ad, 0x1d4b9, 0x1d4ba, 0x1d4bb,
> + 0x1d4bc, 0x1d4c3, 0x1d4c4, 0x1d505, 0x1d506, 0x1d50a, 0x1d50c, 0x1d514,
> + 0x1d515, 0x1d51c, 0x1d51d, 0x1d539, 0x1d53a, 0x1d53e, 0x1d53f, 0x1d544,
> + 0x1d545, 0x1d546, 0x1d549, 0x1d550, 0x1d551, 0x1d6a5, 0x1d6a7, 0x1d7cb,
> + 0x1d7cd, 0x1da8b, 0x1da9a, 0x1da9f, 0x1daa0, 0x1daaf, 0x1dfff, 0x1e006,
> + 0x1e007, 0x1e018, 0x1e01a, 0x1e021, 0x1e022, 0x1e024, 0x1e025, 0x1e02a,
> + 0x1e0ff, 0x1e12c, 0x1e12f, 0x1e13d, 0x1e13f, 0x1e149, 0x1e14d, 0x1e14f,
> + 0x1e2bf, 0x1e2f9, 0x1e2fe, 0x1e2ff, 0x1e7ff, 0x1e8c4, 0x1e8c6, 0x1e8d6,
> + 0x1e8ff, 0x1e94b, 0x1e94f, 0x1e959, 0x1e95d, 0x1e95f, 0x1ec70, 0x1ecb4,
> + 0x1ed00, 0x1ed3d, 0x1edff, 0x1ee03, 0x1ee04, 0x1ee1f, 0x1ee20, 0x1ee22,
> + 0x1ee23, 0x1ee24, 0x1ee26, 0x1ee27, 0x1ee28, 0x1ee32, 0x1ee33, 0x1ee37,
> + 0x1ee38, 0x1ee39, 0x1ee3a, 0x1ee3b, 0x1ee41, 0x1ee42, 0x1ee46, 0x1ee47,
> + 0x1ee48, 0x1ee49, 0x1ee4a, 0x1ee4b, 0x1ee4c, 0x1ee4f, 0x1ee50, 0x1ee52,
> + 0x1ee53, 0x1ee54, 0x1ee56, 0x1ee57, 0x1ee58, 0x1ee59, 0x1ee5a, 0x1ee5b,
> + 0x1ee5c, 0x1ee5d, 0x1ee5e, 0x1ee5f, 0x1ee60, 0x1ee62, 0x1ee63, 0x1ee64,
> + 0x1ee66, 0x1ee6a, 0x1ee6b, 0x1ee72, 0x1ee73, 0x1ee77, 0x1ee78, 0x1ee7c,
> + 0x1ee7d, 0x1ee7e, 0x1ee7f, 0x1ee89, 0x1ee8a, 0x1ee9b, 0x1eea0, 0x1eea3,
> + 0x1eea4, 0x1eea9, 0x1eeaa, 0x1eebb, 0x1eeef, 0x1eef1, 0x1efff, 0x1f02b,
> + 0x1f02f, 0x1f093, 0x1f09f, 0x1f0ae, 0x1f0b0, 0x1f0bf, 0x1f0c0, 0x1f0cf,
> + 0x1f0d0, 0x1f0f5, 0x1f0ff, 0x1f10c, 0x1f10f, 0x1f16c, 0x1f16f, 0x1f1ac,
> + 0x1f1e5, 0x1f202, 0x1f20f, 0x1f23b, 0x1f23f, 0x1f248, 0x1f24f, 0x1f251,
> + 0x1f25f, 0x1f265, 0x1f2ff, 0x1f6d5, 0x1f6df, 0x1f6ec, 0x1f6ef, 0x1f6fa,
> + 0x1f6ff, 0x1f773, 0x1f77f, 0x1f7d8, 0x1f7df, 0x1f7eb, 0x1f7ff, 0x1f80b,
> + 0x1f80f, 0x1f847, 0x1f84f, 0x1f859, 0x1f85f, 0x1f887, 0x1f88f, 0x1f8ad,
> + 0x1f8ff, 0x1f90b, 0x1f90c, 0x1f971, 0x1f972, 0x1f976, 0x1f979, 0x1f9a2,
> + 0x1f9a4, 0x1f9aa, 0x1f9ad, 0x1f9ca, 0x1f9cc, 0x1fa53, 0x1fa5f, 0x1fa6d,
> + 0x1fa6f, 0x1fa73, 0x1fa77, 0x1fa7a, 0x1fa7f, 0x1fa82, 0x1fa8f, 0x1fa95,
> + 0x1ffff, 0x2a6d6, 0x2a6ff, 0x2b734, 0x2b73f, 0x2b81d, 0x2b81f, 0x2cea1,
> + 0x2ceaf, 0x2ebe0, 0x2f7ff, 0x2fa1d, 0xe00ff, 0xe01ef, 0x10fffe,
> +};
> +
> +static const bool is_printable[] = {
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
> + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
> +};
> --
> 2.26.3
>
new file mode 100755
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+#
+# Script to generate gcc/text-art/box-drawing-chars.inc
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3. If not see
+# <http://www.gnu.org/licenses/>. */
+
+import unicodedata
+
+def get_box_drawing_char_name(up: bool,
+ down: bool,
+ left: bool,
+ right: bool) -> str:
+ if 0:
+ print(f'{locals()=}')
+ if up and down:
+ vertical = True
+ up = False
+ down = False
+ else:
+ vertical = False
+
+ if left and right:
+ horizontal = True
+ left = False
+ right = False
+ else:
+ horizontal = False
+
+ weights = []
+ heavy = []
+ light = []
+ dirs = []
+ for dir_name in ('up', 'down', 'vertical', 'left', 'right', 'horizontal'):
+ val = locals()[dir_name]
+ if val:
+ dirs.append(dir_name.upper())
+
+ if not dirs:
+ return 'SPACE'
+
+ name = 'BOX DRAWINGS'
+ #print(f'{light=} {heavy=}')
+
+ if 0:
+ print(dirs)
+
+ def weights_frag(weight: str, dirs: list, prefix: bool):
+ """
+ Generate a fragment where all directions share the same weight, e.g.:
+ 'HEAVY HORIZONTAL'
+ 'DOWN LIGHT'
+ 'LEFT DOWN HEAVY'
+ 'HEAVY DOWN AND RIGHT'
+ """
+ assert len(dirs) >= 1
+ assert len(dirs) <= 2
+ if prefix:
+ return f' {weight} ' + (' AND '.join(dirs))
+ else:
+ return ' ' + (' '.join(dirs)) + f' {weight}'
+
+ assert(len(dirs) >= 1 and len(dirs) <= 2)
+ name += weights_frag('LIGHT', dirs, True)
+
+ return name
+
+print('/* Generated by contrib/unicode/gen-box-drawing-chars.py. */')
+print()
+for i in range(16):
+ up = (i & 8)
+ down = (i & 4)
+ left = (i & 2)
+ right = (i & 1)
+ name = get_box_drawing_char_name(up, down, left, right)
+ if i < 15:
+ trailing_comma = ','
+ else:
+ trailing_comma = ' '
+ unichar = unicodedata.lookup(name)
+ print(f'0x{ord(unichar):04X}{trailing_comma} /* "{unichar}": U+{ord(unichar):04X}: {name} */')
new file mode 100755
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# Script to generate libcpp/combining-chars.inc
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3. If not see
+# <http://www.gnu.org/licenses/>. */
+
+from pprint import pprint
+import unicodedata
+
+def is_combining_char(code_point) -> bool:
+ return unicodedata.combining(chr(code_point)) != 0
+
+class Range:
+ def __init__(self, start, end, value):
+ self.start = start
+ self.end = end
+ self.value = value
+
+ def __repr__(self):
+ return f'Range({self.start:x}, {self.end:x}, {self.value})'
+
+def make_ranges(value_callback):
+ ranges = []
+ for code_point in range(0x10FFFF):
+ value = is_combining_char(code_point)
+ if 0:
+ print(f'{code_point=:x} {value=}')
+ if ranges and ranges[-1].value == value:
+ # Extend current range
+ ranges[-1].end = code_point
+ else:
+ # Start a new range
+ ranges.append(Range(code_point, code_point, value))
+ return ranges
+
+ranges = make_ranges(is_combining_char)
+if 0:
+ pprint(ranges)
+
+print(f"/* Generated by contrib/unicode/gen-combining-chars.py")
+print(f" using version {unicodedata.unidata_version}"
+ " of the Unicode standard. */")
+print("\nstatic const cppchar_t combining_range_ends[] = {", end="")
+for i, r in enumerate(ranges):
+ if i % 8:
+ print(" ", end="")
+ else:
+ print("\n ", end="")
+ print("0x%x," % r.end, end="")
+print("\n};\n")
+print("static const bool is_combining[] = {", end="")
+for i, r in enumerate(ranges):
+ if i % 24:
+ print(" ", end="")
+ else:
+ print("\n ", end="")
+ if r.value:
+ print("1,", end="")
+ else:
+ print("0,", end="")
+print("\n};")
new file mode 100755
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Script to generate libcpp/printable-chars.inc
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3. If not see
+# <http://www.gnu.org/licenses/>. */
+
+from pprint import pprint
+import unicodedata
+
+def is_printable_char(code_point) -> bool:
+ category = unicodedata.category(chr(code_point))
+ # "Cc" is "control" and "Cf" is "format"
+ return category[0] != 'C'
+
+class Range:
+ def __init__(self, start, end, value):
+ self.start = start
+ self.end = end
+ self.value = value
+
+ def __repr__(self):
+ return f'Range({self.start:x}, {self.end:x}, {self.value})'
+
+def make_ranges(value_callback):
+ ranges = []
+ for code_point in range(0x10FFFF):
+ value = is_printable_char(code_point)
+ if 0:
+ print(f'{code_point=:x} {value=}')
+ if ranges and ranges[-1].value == value:
+ # Extend current range
+ ranges[-1].end = code_point
+ else:
+ # Start a new range
+ ranges.append(Range(code_point, code_point, value))
+ return ranges
+
+ranges = make_ranges(is_printable_char)
+if 0:
+ pprint(ranges)
+
+print(f"/* Generated by contrib/unicode/gen-printable-chars.py")
+print(f" using version {unicodedata.unidata_version}"
+ " of the Unicode standard. */")
+print("\nstatic const cppchar_t printable_range_ends[] = {", end="")
+for i, r in enumerate(ranges):
+ if i % 8:
+ print(" ", end="")
+ else:
+ print("\n ", end="")
+ print("0x%x," % r.end, end="")
+print("\n};\n")
+print("static const bool is_printable[] = {", end="")
+for i, r in enumerate(ranges):
+ if i % 24:
+ print(" ", end="")
+ else:
+ print("\n ", end="")
+ if r.value:
+ print("1,", end="")
+ else:
+ print("0,", end="")
+print("\n};")
@@ -1781,7 +1781,16 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
json.o \
sbitmap.o \
vec.o input.o hash-table.o ggc-none.o memory-block.o \
- selftest.o selftest-diagnostic.o sort.o
+ selftest.o selftest-diagnostic.o sort.o \
+ text-art/box-drawing.o \
+ text-art/canvas.o \
+ text-art/ruler.o \
+ text-art/selftests.o \
+ text-art/style.o \
+ text-art/styled-string.o \
+ text-art/table.o \
+ text-art/theme.o \
+ text-art/widget.o
# Objects in libcommon-target.a, used by drivers and by the core
# compiler and containing target-dependent code.
@@ -92,6 +92,14 @@ along with GCC; see the file COPYING3. If not see
#define COLOR_FG_MAGENTA "35"
#define COLOR_FG_CYAN "36"
#define COLOR_FG_WHITE "37"
+#define COLOR_FG_BRIGHT_BLACK "90"
+#define COLOR_FG_BRIGHT_RED "91"
+#define COLOR_FG_BRIGHT_GREEN "92"
+#define COLOR_FG_BRIGHT_YELLOW "93"
+#define COLOR_FG_BRIGHT_BLUE "94"
+#define COLOR_FG_BRIGHT_MAGENTA "95"
+#define COLOR_FG_BRIGHT_CYAN "96"
+#define COLOR_FG_BRIGHT_WHITE "97"
#define COLOR_BG_BLACK "40"
#define COLOR_BG_RED "41"
#define COLOR_BG_GREEN "42"
@@ -100,6 +108,14 @@ along with GCC; see the file COPYING3. If not see
#define COLOR_BG_MAGENTA "45"
#define COLOR_BG_CYAN "46"
#define COLOR_BG_WHITE "47"
+#define COLOR_BG_BRIGHT_BLACK "100"
+#define COLOR_BG_BRIGHT_RED "101"
+#define COLOR_BG_BRIGHT_GREEN "102"
+#define COLOR_BG_BRIGHT_YELLOW "103"
+#define COLOR_BG_BRIGHT_BLUE "104"
+#define COLOR_BG_BRIGHT_MAGENTA "105"
+#define COLOR_BG_BRIGHT_CYAN "106"
+#define COLOR_BG_BRIGHT_WHITE "107"
#define SGR_START "\33["
#define SGR_END "m\33[K"
#define SGR_SEQ(str) SGR_START str SGR_END
@@ -1502,6 +1502,29 @@ fdiagnostics-show-path-depths
Common Var(flag_diagnostics_show_path_depths) Init(0)
Show stack depths of events in paths.
+fdiagnostics-text-art-charset=
+Driver Common Joined RejectNegative Var(flag_diagnostics_text_art_charset) Enum(diagnostic_text_art_charset) Init(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI)
+-fdiagnostics-text-art-charset=[none|ascii|unicode|emoji] Determine which characters to use in text arg diagrams.
+
+; Required for these enum values.
+SourceInclude
+diagnostic-text-art.h
+
+Enum
+Name(diagnostic_text_art_charset) Type(int)
+
+EnumValue
+Enum(diagnostic_text_art_charset) String(none) Value(DIAGNOSTICS_TEXT_ART_CHARSET_NONE)
+
+EnumValue
+Enum(diagnostic_text_art_charset) String(ascii) Value(DIAGNOSTICS_TEXT_ART_CHARSET_ASCII)
+
+EnumValue
+Enum(diagnostic_text_art_charset) String(unicode) Value(DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE)
+
+EnumValue
+Enum(diagnostic_text_art_charset) String(emoji) Value(DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI)
+
fdiagnostics-minimum-margin-width=
Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
Set minimum width of left margin of source code when showing source.
@@ -33995,7 +33995,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
"depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;;
"gccdepdir":C)
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
- for lang in $subdirs c-family common analyzer rtl-ssa
+ for lang in $subdirs c-family common analyzer text-art rtl-ssa
do
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
done ;;
@@ -1384,7 +1384,7 @@ AC_CHECK_HEADERS(ext/hash_map)
ZW_CREATE_DEPDIR
AC_CONFIG_COMMANDS([gccdepdir],[
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
- for lang in $subdirs c-family common analyzer rtl-ssa
+ for lang in $subdirs c-family common analyzer text-art rtl-ssa
do
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR])
new file mode 100644
@@ -0,0 +1,51 @@
+/* Support for diagrams within diagnostics.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DIAGNOSTIC_DIAGRAM_H
+#define GCC_DIAGNOSTIC_DIAGRAM_H
+
+namespace text_art
+{
+ class canvas;
+} // namespace text_art
+
+/* A text art diagram, along with an "alternative text" string
+ describing it. */
+
+class diagnostic_diagram
+{
+ public:
+ diagnostic_diagram (const text_art::canvas &canvas,
+ const char *alt_text)
+ : m_canvas (canvas),
+ m_alt_text (alt_text)
+ {
+ gcc_assert (alt_text);
+ }
+
+ const text_art::canvas &get_canvas () const { return m_canvas; }
+ const char *get_alt_text () const { return m_alt_text; }
+
+ private:
+ const text_art::canvas &m_canvas;
+ const char *const m_alt_text;
+};
+
+#endif /* ! GCC_DIAGNOSTIC_DIAGRAM_H */
@@ -324,6 +324,15 @@ json_file_final_cb (diagnostic_context *)
free (filename);
}
+/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */
+
+static void
+json_emit_diagram (diagnostic_context *,
+ const diagnostic_diagram &)
+{
+ /* No-op. */
+}
+
/* Populate CONTEXT in preparation for JSON output (either to stderr, or
to a file). */
@@ -340,6 +349,7 @@ diagnostic_output_format_init_json (diagnostic_context *context)
context->begin_group_cb = json_begin_group;
context->end_group_cb = json_end_group;
context->print_path = NULL; /* handled in json_end_diagnostic. */
+ context->m_diagrams.m_emission_cb = json_emit_diagram;
/* The metadata is handled in JSON format, rather than as text. */
context->show_cwe = false;
@@ -29,6 +29,8 @@ along with GCC; see the file COPYING3. If not see
#include "cpplib.h"
#include "logical-location.h"
#include "diagnostic-client-data-hooks.h"
+#include "diagnostic-diagram.h"
+#include "text-art/canvas.h"
class sarif_builder;
@@ -66,8 +68,13 @@ public:
diagnostic_info *diagnostic,
diagnostic_t orig_diag_kind,
sarif_builder *builder);
+ void on_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram,
+ sarif_builder *builder);
private:
+ void add_related_location (json::object *location_obj);
+
json::array *m_related_locations_arr;
};
@@ -135,7 +142,8 @@ public:
void end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
diagnostic_t orig_diag_kind);
-
+ void emit_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram);
void end_group ();
void flush_to_file (FILE *outf);
@@ -144,6 +152,9 @@ public:
json::object *make_location_object (const rich_location &rich_loc,
const logical_location *logical_loc);
json::object *make_message_object (const char *msg) const;
+ json::object *
+ make_message_object_for_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram);
private:
sarif_result *make_result_object (diagnostic_context *context,
@@ -261,12 +272,6 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context,
diagnostic_t /*orig_diag_kind*/,
sarif_builder *builder)
{
- if (!m_related_locations_arr)
- {
- m_related_locations_arr = new json::array ();
- set ("relatedLocations", m_related_locations_arr);
- }
-
/* We don't yet generate meaningful logical locations for notes;
sometimes these will related to current_function_decl, but
often they won't. */
@@ -277,6 +282,39 @@ sarif_result::on_nested_diagnostic (diagnostic_context *context,
pp_clear_output_area (context->printer);
location_obj->set ("message", message_obj);
+ add_related_location (location_obj);
+}
+
+/* Handle diagrams that occur within a diagnostic group.
+ The closest thing in SARIF seems to be to add a location to the
+ "releatedLocations" property (SARIF v2.1.0 section 3.27.22),
+ and to put the diagram into the "message" property of that location
+ (SARIF v2.1.0 section 3.28.5). */
+
+void
+sarif_result::on_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram,
+ sarif_builder *builder)
+{
+ json::object *location_obj = new json::object ();
+ json::object *message_obj
+ = builder->make_message_object_for_diagram (context, diagram);
+ location_obj->set ("message", message_obj);
+
+ add_related_location (location_obj);
+}
+
+/* Add LOCATION_OBJ to this result's "relatedLocations" array,
+ creating it if it doesn't yet exist. */
+
+void
+sarif_result::add_related_location (json::object *location_obj)
+{
+ if (!m_related_locations_arr)
+ {
+ m_related_locations_arr = new json::array ();
+ set ("relatedLocations", m_related_locations_arr);
+ }
m_related_locations_arr->append (location_obj);
}
@@ -348,6 +386,18 @@ sarif_builder::end_diagnostic (diagnostic_context *context,
}
}
+/* Implementation of diagnostic_context::m_diagrams.m_emission_cb
+ for SARIF output. */
+
+void
+sarif_builder::emit_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram)
+{
+ /* We must be within the emission of a top-level diagnostic. */
+ gcc_assert (m_cur_group_result);
+ m_cur_group_result->on_diagram (context, diagram, this);
+}
+
/* Implementation of "end_group_cb" for SARIF output. */
void
@@ -1115,6 +1165,37 @@ sarif_builder::make_message_object (const char *msg) const
return message_obj;
}
+/* Make a message object (SARIF v2.1.0 section 3.11) for DIAGRAM.
+ We emit the diagram as a code block within the Markdown part
+ of the message. */
+
+json::object *
+sarif_builder::make_message_object_for_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram)
+{
+ json::object *message_obj = new json::object ();
+
+ /* "text" property (SARIF v2.1.0 section 3.11.8). */
+ message_obj->set ("text", new json::string (diagram.get_alt_text ()));
+
+ char *saved_prefix = pp_take_prefix (context->printer);
+ pp_set_prefix (context->printer, NULL);
+
+ /* "To produce a code block in Markdown, simply indent every line of
+ the block by at least 4 spaces or 1 tab."
+ Here we use 4 spaces. */
+ diagram.get_canvas ().print_to_pp (context->printer, " ");
+ pp_set_prefix (context->printer, saved_prefix);
+
+ /* "markdown" property (SARIF v2.1.0 section 3.11.9). */
+ message_obj->set ("markdown",
+ new json::string (pp_formatted_text (context->printer)));
+
+ pp_clear_output_area (context->printer);
+
+ return message_obj;
+}
+
/* Make a multiformatMessageString object (SARIF v2.1.0 section 3.12)
for MSG. */
@@ -1630,6 +1711,16 @@ sarif_ice_handler (diagnostic_context *context)
fnotice (stderr, "Internal compiler error:\n");
}
+/* Callback for diagnostic_context::m_diagrams.m_emission_cb. */
+
+static void
+sarif_emit_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram)
+{
+ gcc_assert (the_builder);
+ the_builder->emit_diagram (context, diagram);
+}
+
/* Populate CONTEXT in preparation for SARIF output (either to stderr, or
to a file). */
@@ -1645,6 +1736,7 @@ diagnostic_output_format_init_sarif (diagnostic_context *context)
context->end_group_cb = sarif_end_group;
context->print_path = NULL; /* handled in sarif_end_diagnostic. */
context->ice_handler_cb = sarif_ice_handler;
+ context->m_diagrams.m_emission_cb = sarif_emit_diagram;
/* The metadata is handled in SARIF format, rather than as text. */
context->show_cwe = false;
new file mode 100644
@@ -0,0 +1,49 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DIAGNOSTIC_TEXT_ART_H
+#define GCC_DIAGNOSTIC_TEXT_ART_H
+
+/* Values for -fdiagnostics-text-art-charset=. */
+
+enum diagnostic_text_art_charset
+{
+ /* No text art diagrams shall be emitted. */
+ DIAGNOSTICS_TEXT_ART_CHARSET_NONE,
+
+ /* Use pure ASCII for text art diagrams. */
+ DIAGNOSTICS_TEXT_ART_CHARSET_ASCII,
+
+ /* Use ASCII + conservative use of other unicode characters
+ in text art diagrams. */
+ DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE,
+
+ /* Use Emoji. */
+ DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI
+};
+
+const enum diagnostic_text_art_charset DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT
+ = DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI;
+
+extern void
+diagnostics_text_art_charset_init (diagnostic_context *context,
+ enum diagnostic_text_art_charset charset);
+
+
+#endif /* ! GCC_DIAGNOSTIC_TEXT_ART_H */
@@ -35,11 +35,14 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostic-metadata.h"
#include "diagnostic-path.h"
#include "diagnostic-client-data-hooks.h"
+#include "diagnostic-text-art.h"
+#include "diagnostic-diagram.h"
#include "edit-context.h"
#include "selftest.h"
#include "selftest-diagnostic.h"
#include "opts.h"
#include "cpplib.h"
+#include "text-art/theme.h"
#ifdef HAVE_TERMIOS_H
# include <termios.h>
@@ -244,6 +247,10 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
context->ice_handler_cb = NULL;
context->includes_seen = NULL;
context->m_client_data_hooks = NULL;
+ context->m_diagrams.m_theme = NULL;
+ context->m_diagrams.m_emission_cb = NULL;
+ diagnostics_text_art_charset_init (context,
+ DIAGNOSTICS_TEXT_ART_CHARSET_DEFAULT);
}
/* Maybe initialize the color support. We require clients to do this
@@ -320,6 +327,12 @@ diagnostic_finish (diagnostic_context *context)
if (context->final_cb)
context->final_cb (context);
+ if (context->m_diagrams.m_theme)
+ {
+ delete context->m_diagrams.m_theme;
+ context->m_diagrams.m_theme = NULL;
+ }
+
diagnostic_file_cache_fini ();
XDELETEVEC (context->classify_diagnostic);
@@ -2174,6 +2187,33 @@ internal_error_no_backtrace (const char *gmsgid, ...)
gcc_unreachable ();
}
+
+/* Emit DIAGRAM to CONTEXT, respecting the output format. */
+
+void
+diagnostic_emit_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram)
+{
+ if (context->m_diagrams.m_theme == nullptr)
+ return;
+
+ if (context->m_diagrams.m_emission_cb)
+ {
+ context->m_diagrams.m_emission_cb (context, diagram);
+ return;
+ }
+
+ /* Default implementation. */
+ char *saved_prefix = pp_take_prefix (context->printer);
+ pp_set_prefix (context->printer, NULL);
+ /* Use a newline before and after and a two-space indent
+ to make the diagram stand out a little from the wall of text. */
+ pp_newline (context->printer);
+ diagram.get_canvas ().print_to_pp (context->printer, " ");
+ pp_newline (context->printer);
+ pp_set_prefix (context->printer, saved_prefix);
+ pp_flush (context->printer);
+}
/* Special case error functions. Most are implemented in terms of the
above, or should be. */
@@ -2316,6 +2356,38 @@ diagnostic_output_format_init (diagnostic_context *context,
}
}
+/* Initialize CONTEXT->m_diagrams based on CHARSET.
+ Specifically, make a text_art::theme object for m_diagrams.m_theme,
+ (or NULL for "no diagrams"). */
+
+void
+diagnostics_text_art_charset_init (diagnostic_context *context,
+ enum diagnostic_text_art_charset charset)
+{
+ delete context->m_diagrams.m_theme;
+ switch (charset)
+ {
+ default:
+ gcc_unreachable ();
+
+ case DIAGNOSTICS_TEXT_ART_CHARSET_NONE:
+ context->m_diagrams.m_theme = NULL;
+ break;
+
+ case DIAGNOSTICS_TEXT_ART_CHARSET_ASCII:
+ context->m_diagrams.m_theme = new text_art::ascii_theme ();
+ break;
+
+ case DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE:
+ context->m_diagrams.m_theme = new text_art::unicode_theme ();
+ break;
+
+ case DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI:
+ context->m_diagrams.m_theme = new text_art::emoji_theme ();
+ break;
+ }
+}
+
/* Implementation of diagnostic_path::num_events vfunc for
simple_diagnostic_path: simply get the number of events in the vec. */
@@ -24,6 +24,11 @@ along with GCC; see the file COPYING3. If not see
#include "pretty-print.h"
#include "diagnostic-core.h"
+namespace text_art
+{
+ class theme;
+} // namespace text_art
+
/* An enum for controlling what units to use for the column number
when diagnostics are output, used by the -fdiagnostics-column-unit option.
Tabs will be expanded or not according to the value of -ftabstop. The origin
@@ -170,6 +175,7 @@ class edit_context;
namespace json { class value; }
class diagnostic_client_data_hooks;
class logical_location;
+class diagnostic_diagram;
/* This data structure bundles altogether any information relevant to
the context of a diagnostic message. */
@@ -417,6 +423,18 @@ struct diagnostic_context
Used by SARIF output to give metadata about the client that's
producing diagnostics. */
diagnostic_client_data_hooks *m_client_data_hooks;
+
+ /* Support for diagrams. */
+ struct
+ {
+ /* Theme to use when generating diagrams.
+ Can be NULL (if text art is disabled). */
+ text_art::theme *m_theme;
+
+ /* Callback for emitting diagrams. */
+ void (*m_emission_cb) (diagnostic_context *context,
+ const diagnostic_diagram &diagram);
+ } m_diagrams;
};
inline void
@@ -619,4 +637,7 @@ extern bool warning_enabled_at (location_t, int);
extern char *get_cwe_url (int cwe);
+extern void diagnostic_emit_diagram (diagnostic_context *context,
+ const diagnostic_diagram &diagram);
+
#endif /* ! GCC_DIAGNOSTIC_H */
@@ -316,7 +316,8 @@ Objective-C and Objective-C++ Dialects}.
-fno-show-column
-fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]}
-fdiagnostics-column-origin=@var{origin}
--fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}}
+-fdiagnostics-escape-format=@r{[}unicode@r{|}bytes@r{]}
+-fdiagnostics-text-art-charset=@r{[}none@r{|}ascii@r{|}unicode@r{|}emoji@r{]}}
@item Warning Options
@xref{Warning Options,,Options to Request or Suppress Warnings}.
@@ -5066,7 +5067,8 @@ options:
-fno-diagnostics-show-line-numbers
-fdiagnostics-color=never
-fdiagnostics-urls=never
--fdiagnostics-path-format=separate-events}
+-fdiagnostics-path-format=separate-events
+-fdiagnostics-text-art-charset=none}
In the future, if GCC changes the default appearance of its diagnostics, the
corresponding option to disable the new behavior will be added to this list.
@@ -5592,6 +5594,25 @@ Unicode characters. For the example above, the following will be printed:
before<CF><80><BF>after
@end smallexample
+@opindex fdiagnostics-text-art-charset
+@item -fdiagnostics-text-art-charset=@var{CHARSET}
+Some diagnostics can contain ``text art'' diagrams: visualizations created
+from text, intended to be viewed in a monospaced font.
+
+This option selects which characters should be used for printing such
+diagrams, if any. @var{CHARSET} is @samp{none}, @samp{ascii}, @samp{unicode},
+or @samp{emoji}.
+
+The @samp{none} value suppresses the printing of such diagrams.
+The @samp{ascii} value will ensure that such diagrams are pure ASCII
+(``ASCII art''). The @samp{unicode} value will allow for conservative use of
+unicode drawing characters (such as box-drawing characters). The @samp{emoji}
+value further adds the possibility of emoji in the output (such as emitting
+U+26A0 WARNING SIGN followed by U+FE0F VARIATION SELECTOR-16 to select the
+emoji variant of the character).
+
+The default is @samp{emoji}.
+
@opindex fdiagnostics-format
@item -fdiagnostics-format=@var{FORMAT}
Select a different format for printing diagnostics.
@@ -46,6 +46,7 @@ compilation is specified by a string called a "spec". */
#include "spellcheck.h"
#include "opts-jobserver.h"
#include "common/common-target.h"
+#include "diagnostic-text-art.h"
@@ -4299,6 +4300,11 @@ driver_handle_option (struct gcc_options *opts,
break;
}
+ case OPT_fdiagnostics_text_art_charset_:
+ diagnostics_text_art_charset_init (dc,
+ (enum diagnostic_text_art_charset)value);
+ break;
+
case OPT_Wa_:
{
int prev, j;
@@ -1068,6 +1068,7 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv,
"-fdiagnostics-color=never",
"-fdiagnostics-urls=never",
"-fdiagnostics-path-format=separate-events",
+ "-fdiagnostics-text-art-charset=none"
};
const int num_expanded = ARRAY_SIZE (expanded_args);
opt_array_len += num_expanded - 1;
@@ -35,6 +35,7 @@ along with GCC; see the file COPYING3. If not see
#include "version.h"
#include "selftest.h"
#include "file-prefix-map.h"
+#include "diagnostic-text-art.h"
/* In this file all option sets are explicit. */
#undef OPTION_SET_P
@@ -2887,6 +2888,11 @@ common_handle_option (struct gcc_options *opts,
break;
}
+ case OPT_fdiagnostics_text_art_charset_:
+ diagnostics_text_art_charset_init (dc,
+ (enum diagnostic_text_art_charset)value);
+ break;
+
case OPT_fdiagnostics_parseable_fixits:
dc->extra_output_kind = (value
? EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1
@@ -1828,6 +1828,35 @@ pp_string (pretty_printer *pp, const char *str)
pp_maybe_wrap_text (pp, str, str + strlen (str));
}
+/* Append code point C to the output area of PRETTY-PRINTER, encoding it
+ as UTF-8. */
+
+void
+pp_unicode_character (pretty_printer *pp, unsigned c)
+{
+ static const uchar masks[6] = { 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+ static const uchar limits[6] = { 0x80, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE };
+ size_t nbytes;
+ uchar buf[6], *p = &buf[6];
+
+ nbytes = 1;
+ if (c < 0x80)
+ *--p = c;
+ else
+ {
+ do
+ {
+ *--p = ((c & 0x3F) | 0x80);
+ c >>= 6;
+ nbytes++;
+ }
+ while (c >= 0x3F || (c & limits[nbytes-1]));
+ *--p = (c | masks[nbytes-1]);
+ }
+
+ pp_append_r (pp, (const char *)p, nbytes);
+}
+
/* Append the leading N characters of STRING to the output area of
PRETTY-PRINTER, quoting in hexadecimal non-printable characters.
Setting N = -1 is as if N were set to strlen (STRING). The STRING
@@ -401,6 +401,7 @@ extern void pp_indent (pretty_printer *);
extern void pp_newline (pretty_printer *);
extern void pp_character (pretty_printer *, int);
extern void pp_string (pretty_printer *, const char *);
+extern void pp_unicode_character (pretty_printer *, unsigned);
extern void pp_write_text_to_stream (pretty_printer *);
extern void pp_write_text_as_dot_label_to_stream (pretty_printer *, bool);
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3. If not see
#include "stringpool.h"
#include "attribs.h"
#include "analyzer/analyzer-selftests.h"
+#include "text-art/selftests.h"
/* This function needed to be split out from selftest.cc as it references
tests from the whole source tree, and so is within
@@ -118,6 +119,8 @@ selftest::run_tests ()
/* Run any lang-specific selftests. */
lang_hooks.run_lang_selftests ();
+ text_art_tests ();
+
/* Run the analyzer selftests (if enabled). */
ana::selftest::run_analyzer_selftests ();
new file mode 100644
@@ -0,0 +1,57 @@
+/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=never" } */
+
+int non_empty;
+
+/* { dg-begin-multiline-output "" }
+
+ A
+ B
+ C
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
+ ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
+
+
+
+
+ ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
+ ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ +--+
+ |🙂|
+ +--+
+
+ { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+
+ +-------+-----+---------------+---------------------+-----------------------+-----------------------+
+ |Offsets|Octet| 0 | 1 | 2 | 3 |
+ +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
+ +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | 0 | 0 |Version| IHL | DSCP | ECN | Total Length |
+ +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+
+ | 4 | 32 | Identification | Flags | Fragment Offset |
+ +-------+-----+---------------+---------------------+--------+--------------------------------------+
+ | 8 | 64 | Time To Live | Protocol | Header Checksum |
+ +-------+-----+---------------+---------------------+-----------------------------------------------+
+ | 12 | 96 | Source IP Address |
+ +-------+-----+-------------------------------------------------------------------------------------+
+ | 16 | 128 | Destination IP Address |
+ +-------+-----+-------------------------------------------------------------------------------------+
+ | 20 | 160 | |
+ +-------+-----+ |
+ | ... | ... | Options |
+ +-------+-----+ |
+ | 56 | 448 | |
+ +-------+-----+-------------------------------------------------------------------------------------+
+
+ { dg-end-multiline-output "" } */
new file mode 100644
@@ -0,0 +1,58 @@
+/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fdiagnostics-color=always" } */
+
+int non_empty;
+
+/* { dg-begin-multiline-output "" }
+
+ A
+ B
+ C
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K
+ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K
+ [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
+ [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
+ [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
+ [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
+ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K
+ [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ +--+
+ |🙂|
+ +--+
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ +-------+-----+---------------+---------------------+-----------------------+-----------------------+
+ |Offsets|Octet| 0 | 1 | 2 | 3 |
+ +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|
+ +-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ | 0 | 0 |Version| IHL | DSCP | ECN | Total Length |
+ +-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+
+ | 4 | 32 | Identification | Flags | Fragment Offset |
+ +-------+-----+---------------+---------------------+--------+--------------------------------------+
+ | 8 | 64 | Time To Live | Protocol | Header Checksum |
+ +-------+-----+---------------+---------------------+-----------------------------------------------+
+ | 12 | 96 | Source IP Address |
+ +-------+-----+-------------------------------------------------------------------------------------+
+ | 16 | 128 | Destination IP Address |
+ +-------+-----+-------------------------------------------------------------------------------------+
+ | 20 | 160 | |
+ +-------+-----+ |
+ | ... | ... | Options |
+ +-------+-----+ |
+ | 56 | 448 | |
+ +-------+-----+-------------------------------------------------------------------------------------+
+
+ { dg-end-multiline-output "" } */
new file mode 100644
@@ -0,0 +1,5 @@
+/* { dg-additional-options "-fdiagnostics-text-art-charset=none" } */
+
+int non_empty;
+
+/* We expect no output. */
new file mode 100644
@@ -0,0 +1,58 @@
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=never" } */
+
+int non_empty;
+
+/* { dg-begin-multiline-output "" }
+
+ A
+ B
+ C
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
+ ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
+
+
+
+
+ ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
+ ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ ┌──┐
+ │🙂│
+ └──┘
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐
+ │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │
+ ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤
+ │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│
+ ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤
+ │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │
+ ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤
+ │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │
+ ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤
+ │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │
+ ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤
+ │ 12 │ 96 │ Source IP Address │
+ ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
+ │ 16 │ 128 │ Destination IP Address │
+ ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
+ │ 20 │ 160 │ │
+ ├───────┼─────┤ │
+ │ ... │ ... │ Options │
+ ├───────┼─────┤ │
+ │ 56 │ 448 │ │
+ └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘
+
+ { dg-end-multiline-output "" } */
new file mode 100644
@@ -0,0 +1,59 @@
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode -fdiagnostics-color=always" } */
+
+int non_empty;
+
+
+/* { dg-begin-multiline-output "" }
+
+ A
+ B
+ C
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ [38;2;0;0;0;48;2;240;217;181m[K♜ [38;2;0;0;0;48;2;181;136;99m[K♞ [38;2;0;0;0;48;2;240;217;181m[K♝ [38;2;0;0;0;48;2;181;136;99m[K♛ [38;2;0;0;0;48;2;240;217;181m[K♚ [38;2;0;0;0;48;2;181;136;99m[K♝ [38;2;0;0;0;48;2;240;217;181m[K♞ [38;2;0;0;0;48;2;181;136;99m[K♜ [m[K
+ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [38;2;0;0;0;48;2;181;136;99m[K♟ [38;2;0;0;0;48;2;240;217;181m[K♟ [m[K
+ [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
+ [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
+ [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [m[K
+ [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [48;2;181;136;99m[K [48;2;240;217;181m[K [m[K
+ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [38;2;255;255;255;48;2;240;217;181m[K♙ [38;2;255;255;255;48;2;181;136;99m[K♙ [m[K
+ [38;2;255;255;255;48;2;181;136;99m[K♖ [38;2;255;255;255;48;2;240;217;181m[K♘ [38;2;255;255;255;48;2;181;136;99m[K♗ [38;2;255;255;255;48;2;240;217;181m[K♕ [38;2;255;255;255;48;2;181;136;99m[K♔ [38;2;255;255;255;48;2;240;217;181m[K♗ [38;2;255;255;255;48;2;181;136;99m[K♘ [38;2;255;255;255;48;2;240;217;181m[K♖ [m[K
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ ┌──┐
+ │🙂│
+ └──┘
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+ ┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐
+ │Offsets│Octet│ 0 │ 1 │ 2 │ 3 │
+ ├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤
+ │ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│
+ ├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤
+ │ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │
+ ├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤
+ │ 4 │ 32 │ Identification │ Flags │ Fragment Offset │
+ ├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤
+ │ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │
+ ├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤
+ │ 12 │ 96 │ Source IP Address │
+ ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
+ │ 16 │ 128 │ Destination IP Address │
+ ├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤
+ │ 20 │ 160 │ │
+ ├───────┼─────┤ │
+ │ ... │ ... │ Options │
+ ├───────┼─────┤ │
+ │ 56 │ 448 │ │
+ └───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘
+
+ { dg-end-multiline-output "" } */
new file mode 100644
@@ -0,0 +1,257 @@
+/* { dg-options "-O" } */
+
+/* This plugin exercises the text_art code. */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "diagnostic-diagram.h"
+#include "text-art/canvas.h"
+#include "text-art/table.h"
+
+int plugin_is_GPL_compatible;
+
+using namespace text_art;
+
+/* Canvas tests. */
+
+static void
+emit_canvas (const canvas &c, const char *alt_text)
+{
+ diagnostic_diagram diagram (c, alt_text);
+ diagnostic_emit_diagram (global_dc, diagram);
+}
+
+static void
+test_abc ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (3, 3), sm);
+ c.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
+ c.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
+ c.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
+ emit_canvas (c, "test_abc");
+}
+
+/* Test of procedural art using 24-bit color: chess starting position. */
+
+static void
+test_chessboard ()
+{
+ /* With the exception of NONE, these are in order of the chess symbols
+ in the Unicode Miscellaneous Symbols block. */
+ enum class piece { KING, QUEEN, ROOK, BISHOP, KNIGHT, PAWN, NONE };
+ enum class color { BLACK, WHITE, NONE };
+
+ style_manager sm;
+
+ /* We assume double-column chars for the pieces, so allow two canvas
+ columns per square. */
+ canvas canvas (canvas::size_t (16, 8), sm);
+
+ for (int x = 0; x < 8; x++)
+ for (int y = 0; y < 8; y++)
+ {
+ enum piece piece_kind;
+ enum color piece_color;
+ switch (y)
+ {
+ case 0:
+ case 7:
+ switch (x)
+ {
+ default:
+ gcc_unreachable ();
+ case 0:
+ piece_kind = piece::ROOK;
+ break;
+ case 1:
+ piece_kind = piece::KNIGHT;
+ break;
+ case 2:
+ piece_kind = piece::BISHOP;
+ break;
+ case 3:
+ piece_kind = piece::QUEEN;
+ break;
+ case 4:
+ piece_kind = piece::KING;
+ break;
+ case 5:
+ piece_kind = piece::BISHOP;
+ break;
+ case 6:
+ piece_kind = piece::KNIGHT;
+ break;
+ case 7:
+ piece_kind = piece::ROOK;
+ break;
+ }
+ piece_color = (y == 0) ? color::BLACK : color::WHITE;
+ break;
+ case 1:
+ case 6:
+ piece_kind = piece::PAWN;
+ piece_color = (y == 1) ? color::BLACK : color::WHITE;
+ break;
+ default:
+ piece_kind = piece::NONE;
+ piece_color = color::NONE;
+ break;
+ }
+
+ style s;
+ const bool white_square = (x + y) % 2 == 0;
+ if (white_square)
+ s.m_bg_color = style::color (0xf0, 0xd9, 0xb5);
+ else
+ s.m_bg_color = style::color (0xb5, 0x88, 0x63);
+ switch (piece_color)
+ {
+ default:
+ gcc_unreachable ();
+ case color::WHITE:
+ s.m_fg_color = style::color (0xff, 0xff, 0xff);
+ break;
+ case color::BLACK:
+ s.m_fg_color = style::color (0x00, 0x00, 0x00);
+ break;
+ case color::NONE:
+ break;
+ }
+ style::id_t style_id = sm.get_or_create_id (s);
+
+ cppchar_t ch;
+ if (piece_kind == piece::NONE)
+ ch = ' ';
+ else
+ {
+ const cppchar_t WHITE_KING = 0x2654;
+ const cppchar_t BLACK_KING = 0x265A;
+ cppchar_t base ((piece_color == color::WHITE)
+ ? WHITE_KING : BLACK_KING);
+ ch = base + ((int)piece_kind - (int)piece::KING);
+ }
+ canvas.paint (canvas::coord_t (x * 2, y),
+ canvas::cell_t (ch, false, style_id));
+ canvas.paint (canvas::coord_t (x * 2 + 1, y),
+ canvas::cell_t (' ', false, style_id));
+ }
+ emit_canvas (canvas, "test_chessboard");
+}
+
+/* Table tests. */
+
+static void
+emit_table (const table &table, const style_manager &sm, const char *alt_text)
+{
+ const text_art::theme *theme = global_dc->m_diagrams.m_theme;
+ if (!theme)
+ return;
+ canvas c (table.to_canvas (*theme, sm));
+ emit_canvas (c, alt_text);
+}
+
+static void
+test_double_width_chars ()
+{
+ style_manager sm;
+ table table (table::size_t (1, 1));
+ table.set_cell (table::coord_t (0,0),
+ styled_string ((cppchar_t)0x1f642));
+
+ emit_table (table, sm, "test_double_width_chars");
+}
+
+static void
+test_ipv4_header ()
+{
+ style_manager sm;
+ table table (table::size_t (34, 10));
+ table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets"));
+ table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet"));
+ table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet"));
+ for (int octet = 0; octet < 4; octet++)
+ table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0),
+ table::size_t (8, 1)),
+ styled_string::from_fmt (sm, nullptr, "%i", octet));
+ table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit"));
+ for (int bit = 0; bit < 32; bit++)
+ table.set_cell (table::coord_t (bit + 2, 1),
+ styled_string::from_fmt (sm, nullptr, "%i", bit));
+ for (int word = 0; word < 6; word++)
+ {
+ table.set_cell (table::coord_t (0, word + 2),
+ styled_string::from_fmt (sm, nullptr, "%i", word * 4));
+ table.set_cell (table::coord_t (1, word + 2),
+ styled_string::from_fmt (sm, nullptr, "%i", word * 32));
+ }
+
+ table.set_cell (table::coord_t (0, 8), styled_string (sm, "..."));
+ table.set_cell (table::coord_t (1, 8), styled_string (sm, "..."));
+ table.set_cell (table::coord_t (0, 9), styled_string (sm, "56"));
+ table.set_cell (table::coord_t (1, 9), styled_string (sm, "448"));
+
+#define SET_BITS(FIRST, LAST, NAME) \
+ do { \
+ const int first = (FIRST); \
+ const int last = (LAST); \
+ const char *name = (NAME); \
+ const int row = first / 32; \
+ gcc_assert (last / 32 == row); \
+ table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \
+ table::size_t (last + 1 - first , 1)); \
+ table.set_cell_span (rect, styled_string (sm, name)); \
+ } while (0)
+
+ SET_BITS (0, 3, "Version");
+ SET_BITS (4, 7, "IHL");
+ SET_BITS (8, 13, "DSCP");
+ SET_BITS (14, 15, "ECN");
+ SET_BITS (16, 31, "Total Length");
+
+ SET_BITS (32 + 0, 32 + 15, "Identification");
+ SET_BITS (32 + 16, 32 + 18, "Flags");
+ SET_BITS (32 + 19, 32 + 31, "Fragment Offset");
+
+ SET_BITS (64 + 0, 64 + 7, "Time To Live");
+ SET_BITS (64 + 8, 64 + 15, "Protocol");
+ SET_BITS (64 + 16, 64 + 31, "Header Checksum");
+
+ SET_BITS (96 + 0, 96 + 31, "Source IP Address");
+ SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
+
+ table.set_cell_span(table::rect_t (table::coord_t (2, 7),
+ table::size_t (32, 3)),
+ styled_string (sm, "Options"));
+
+ emit_table (table, sm, "test_ipv4_header");
+}
+
+static void
+show_diagrams ()
+{
+ test_abc ();
+ test_chessboard ();
+ test_double_width_chars ();
+ test_ipv4_header ();
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+ struct plugin_gcc_version *version)
+{
+ const char *plugin_name = plugin_info->base_name;
+ int argc = plugin_info->argc;
+ struct plugin_argument *argv = plugin_info->argv;
+
+ if (!plugin_default_version_check (version, &gcc_version))
+ return 1;
+
+ show_diagrams ();
+
+ return 0;
+}
@@ -114,6 +114,12 @@ set plugin_test_list [list \
diagnostic-path-format-inline-events-1.c \
diagnostic-path-format-inline-events-2.c \
diagnostic-path-format-inline-events-3.c } \
+ { diagnostic_plugin_test_text_art.c \
+ diagnostic-test-text-art-none.c \
+ diagnostic-test-text-art-ascii-bw.c \
+ diagnostic-test-text-art-ascii-color.c \
+ diagnostic-test-text-art-unicode-bw.c \
+ diagnostic-test-text-art-unicode-color.c } \
{ location_overflow_plugin.c \
location-overflow-test-1.c \
location-overflow-test-2.c \
new file mode 100644
@@ -0,0 +1,18 @@
+/* Generated by contrib/unicode/gen-box-drawing-chars.py. */
+
+0x0020, /* " ": U+0020: SPACE */
+0x2576, /* "╶": U+2576: BOX DRAWINGS LIGHT RIGHT */
+0x2574, /* "╴": U+2574: BOX DRAWINGS LIGHT LEFT */
+0x2500, /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+0x2577, /* "╷": U+2577: BOX DRAWINGS LIGHT DOWN */
+0x250C, /* "┌": U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */
+0x2510, /* "┐": U+2510: BOX DRAWINGS LIGHT DOWN AND LEFT */
+0x252C, /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+0x2575, /* "╵": U+2575: BOX DRAWINGS LIGHT UP */
+0x2514, /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT */
+0x2518, /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT */
+0x2534, /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+0x2502, /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+0x251C, /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+0x2524, /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+0x253C /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
new file mode 100644
@@ -0,0 +1,72 @@
+/* Procedural lookup of box drawing characters.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "text-art/box-drawing.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+
+
+/* According to
+ https://en.wikipedia.org/wiki/Box-drawing_character#Character_code
+ "DOS line- and box-drawing characters are not ordered in any programmatic
+ manner, so calculating a particular character shape needs to use a look-up
+ table. "
+ Hence this array. */
+static const cppchar_t box_drawing_chars[] = {
+#include "text-art/box-drawing-chars.inc"
+};
+
+cppchar_t
+text_art::get_box_drawing_char (directions line_dirs)
+{
+ const size_t idx = line_dirs.as_index ();
+ gcc_assert (idx < 16);
+ return box_drawing_chars[idx];
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Run all selftests in this file. */
+
+void
+text_art_box_drawing_cc_tests ()
+{
+ ASSERT_EQ (text_art::get_box_drawing_char
+ (text_art::directions (false, false, false, false)),
+ ' ');
+ ASSERT_EQ (text_art::get_box_drawing_char
+ (text_art::directions (false, false, true, true)),
+ 0x2500); /* BOX DRAWINGS LIGHT HORIZONTAL */
+ ASSERT_EQ (text_art::get_box_drawing_char
+ (text_art::directions (true, true, false, false)),
+ 0x2502); /* BOX DRAWINGS LIGHT VERTICAL */
+ ASSERT_EQ (text_art::get_box_drawing_char
+ (text_art::directions (true, false, true, false)),
+ 0x2518); /* BOX DRAWINGS LIGHT UP AND LEFT */
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,32 @@
+/* Procedural lookup of box drawing characters.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_BOX_DRAWING_H
+#define GCC_TEXT_ART_BOX_DRAWING_H
+
+#include "text-art/types.h"
+
+namespace text_art {
+
+extern cppchar_t get_box_drawing_char (directions line_dirs);
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_BOX_DRAWING_H */
new file mode 100644
@@ -0,0 +1,437 @@
+/* Canvas for random-access procedural text art.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/canvas.h"
+
+using namespace text_art;
+
+canvas::canvas (size_t size, const style_manager &style_mgr)
+: m_cells (size_t (size.w, size.h)),
+ m_style_mgr (style_mgr)
+{
+ m_cells.fill (cell_t (' '));
+}
+
+void
+canvas::paint (coord_t coord, styled_unichar ch)
+{
+ m_cells.set (coord, std::move (ch));
+}
+
+void
+canvas::paint_text (coord_t coord, const styled_string &text)
+{
+ for (auto ch : text)
+ {
+ paint (coord, ch);
+ if (ch.double_width_p ())
+ coord.x += 2;
+ else
+ coord.x++;
+ }
+}
+
+void
+canvas::fill (rect_t rect, cell_t c)
+{
+ for (int y = rect.get_min_y (); y < rect.get_next_y (); y++)
+ for (int x = rect.get_min_x (); x < rect.get_next_x (); x++)
+ paint(coord_t (x, y), c);
+}
+
+void
+canvas::debug_fill ()
+{
+ fill (rect_t (coord_t (0, 0), get_size ()), cell_t ('*'));
+}
+
+void
+canvas::print_to_pp (pretty_printer *pp,
+ const char *per_line_prefix) const
+{
+ for (int y = 0; y < m_cells.get_size ().h; y++)
+ {
+ style::id_t curr_style_id = 0;
+ if (per_line_prefix)
+ pp_string (pp, per_line_prefix);
+
+ pretty_printer line_pp;
+ line_pp.show_color = pp->show_color;
+ line_pp.url_format = pp->url_format;
+ const int final_x_in_row = get_final_x_in_row (y);
+ for (int x = 0; x <= final_x_in_row; x++)
+ {
+ if (x > 0)
+ {
+ const cell_t prev_cell = m_cells.get (coord_t (x - 1, y));
+ if (prev_cell.double_width_p ())
+ /* This cell is just a placeholder for the
+ 2nd column of a double width cell; skip it. */
+ continue;
+ }
+ const cell_t cell = m_cells.get (coord_t (x, y));
+ if (cell.get_style_id () != curr_style_id)
+ {
+ m_style_mgr.print_any_style_changes (&line_pp,
+ curr_style_id,
+ cell.get_style_id ());
+ curr_style_id = cell.get_style_id ();
+ }
+ pp_unicode_character (&line_pp, cell.get_code ());
+ if (cell.emoji_variant_p ())
+ /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
+ variation of the char. */
+ pp_unicode_character (&line_pp, 0xFE0F);
+ }
+ /* Reset the style at the end of each line. */
+ m_style_mgr.print_any_style_changes (&line_pp, curr_style_id, 0);
+
+ /* Print from line_pp to pp, stripping trailing whitespace from
+ the line. */
+ const char *line_buf = pp_formatted_text (&line_pp);
+ ::size_t len = strlen (line_buf);
+ while (len > 0)
+ {
+ if (line_buf[len - 1] == ' ')
+ len--;
+ else
+ break;
+ }
+ pp_append_text (pp, line_buf, line_buf + len);
+ pp_newline (pp);
+ }
+}
+
+DEBUG_FUNCTION void
+canvas::debug (bool styled) const
+{
+ pretty_printer pp;
+ if (styled)
+ {
+ pp_show_color (&pp) = true;
+ pp.url_format = determine_url_format (DIAGNOSTICS_URL_AUTO);
+ }
+ print_to_pp (&pp);
+ fprintf (stderr, "%s\n", pp_formatted_text (&pp));
+}
+
+/* Find right-most non-default cell in this row,
+ or -1 if all are default. */
+
+int
+canvas::get_final_x_in_row (int y) const
+{
+ for (int x = m_cells.get_size ().w - 1; x >= 0; x--)
+ {
+ cell_t cell = m_cells.get (coord_t (x, y));
+ if (cell.get_code () != ' '
+ || cell.get_style_id () != style::id_plain)
+ return x;
+ }
+ return -1;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_blank ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (5, 5), sm);
+ ASSERT_CANVAS_STREQ (c, false,
+ ("\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"));
+}
+
+static void
+test_abc ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (3, 3), sm);
+ c.paint (canvas::coord_t (0, 0), styled_unichar ('A'));
+ c.paint (canvas::coord_t (1, 1), styled_unichar ('B'));
+ c.paint (canvas::coord_t (2, 2), styled_unichar ('C'));
+
+ ASSERT_CANVAS_STREQ (c, false,
+ "A\n B\n C\n");
+}
+
+static void
+test_debug_fill ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (5, 3), sm);
+ c.debug_fill();
+ ASSERT_CANVAS_STREQ (c, false,
+ ("*****\n"
+ "*****\n"
+ "*****\n"));
+}
+
+static void
+test_text ()
+{
+ style_manager sm;
+ canvas c (canvas::size_t (6, 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), styled_string (sm, "012345"));
+ ASSERT_CANVAS_STREQ (c, false,
+ ("012345\n"));
+
+ /* Paint an emoji character that should occupy two canvas columns when
+ printed. */
+ c.paint_text (canvas::coord_t (2, 0), styled_string ((cppchar_t)0x1f642));
+ ASSERT_CANVAS_STREQ (c, false,
+ ("01🙂45\n"));
+}
+
+static void
+test_circle ()
+{
+ canvas::size_t sz (30, 30);
+ style_manager sm;
+ canvas canvas (sz, sm);
+ canvas::coord_t center (sz.w / 2, sz.h / 2);
+ const int radius = 12;
+ const int radius_squared = radius * radius;
+ for (int x = 0; x < sz.w; x++)
+ for (int y = 0; y < sz.h; y++)
+ {
+ int dx = x - center.x;
+ int dy = y - center.y;
+ char ch = "AB"[(x + y) % 2];
+ if (dx * dx + dy * dy < radius_squared)
+ canvas.paint (canvas::coord_t (x, y), styled_unichar (ch));
+ }
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("\n"
+ "\n"
+ "\n"
+ "\n"
+ " BABABABAB\n"
+ " ABABABABABABA\n"
+ " ABABABABABABABA\n"
+ " ABABABABABABABABA\n"
+ " ABABABABABABABABABA\n"
+ " ABABABABABABABABABABA\n"
+ " BABABABABABABABABABAB\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " ABABABABABABABABABABABA\n"
+ " BABABABABABABABABABABAB\n"
+ " BABABABABABABABABABAB\n"
+ " ABABABABABABABABABABA\n"
+ " ABABABABABABABABABA\n"
+ " ABABABABABABABABA\n"
+ " ABABABABABABABA\n"
+ " ABABABABABABA\n"
+ " BABABABAB\n"
+ "\n"
+ "\n"
+ "\n"));
+}
+
+static void
+test_color_circle ()
+{
+ const canvas::size_t sz (10, 10);
+ const canvas::coord_t center (sz.w / 2, sz.h / 2);
+ const int outer_r2 = 25;
+ const int inner_r2 = 10;
+ style_manager sm;
+ canvas c (sz, sm);
+ for (int x = 0; x < sz.w; x++)
+ for (int y = 0; y < sz.h; y++)
+ {
+ const int dist_from_center_squared
+ = ((x - center.x) * (x - center.x) + (y - center.y) * (y - center.y));
+ if (dist_from_center_squared < outer_r2)
+ {
+ style s;
+ if (dist_from_center_squared < inner_r2)
+ s.m_fg_color = style::named_color::RED;
+ else
+ s.m_fg_color = style::named_color::GREEN;
+ c.paint (canvas::coord_t (x, y),
+ styled_unichar ('*', false, sm.get_or_create_id (s)));
+ }
+ }
+ ASSERT_EQ (sm.get_num_styles (), 3);
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("\n"
+ " *****\n"
+ " *******\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *********\n"
+ " *******\n"
+ " *****\n"));
+ ASSERT_CANVAS_STREQ
+ (c, true,
+ ("\n"
+ " [32m[K*****[m[K\n"
+ " [32m[K***[31m[K*[32m[K***[m[K\n"
+ " [32m[K**[31m[K*****[32m[K**[m[K\n"
+ " [32m[K**[31m[K*****[32m[K**[m[K\n"
+ " [32m[K*[31m[K*******[32m[K*[m[K\n"
+ " [32m[K**[31m[K*****[32m[K**[m[K\n"
+ " [32m[K**[31m[K*****[32m[K**[m[K\n"
+ " [32m[K***[31m[K*[32m[K***[m[K\n"
+ " [32m[K*****[m[K\n"));
+}
+
+static void
+test_bold ()
+{
+ auto_fix_quotes fix_quotes;
+ style_manager sm;
+ styled_string s (styled_string::from_fmt (sm, nullptr,
+ "before %qs after", "foo"));
+ canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), s);
+ ASSERT_CANVAS_STREQ (c, false,
+ "before `foo' after\n");
+ ASSERT_CANVAS_STREQ (c, true,
+ "before `[00;01m[Kfoo[00m[K' after\n");
+}
+
+static void
+test_emoji ()
+{
+ style_manager sm;
+ styled_string s (0x26A0, /* U+26A0 WARNING SIGN. */
+ true);
+ canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), s);
+ ASSERT_CANVAS_STREQ (c, false, "⚠️\n");
+ ASSERT_CANVAS_STREQ (c, true, "⚠️\n");
+}
+
+static void
+test_emoji_2 ()
+{
+ style_manager sm;
+ styled_string s;
+ s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
+ true));
+ s.append (styled_string (sm, "test"));
+ ASSERT_EQ (s.size (), 5);
+ ASSERT_EQ (s.calc_canvas_width (), 5);
+ canvas c (canvas::size_t (s.calc_canvas_width (), 1), sm);
+ c.paint_text (canvas::coord_t (0, 0), s);
+ ASSERT_CANVAS_STREQ (c, false,
+ /* U+26A0 WARNING SIGN, as UTF-8: 0xE2 0x9A 0xA0. */
+ "\xE2\x9A\xA0"
+ /* U+FE0F VARIATION SELECTOR-16, as UTF-8: 0xEF 0xB8 0x8F. */
+ "\xEF\xB8\x8F"
+ "test\n");
+}
+
+static void
+test_canvas_urls ()
+{
+ style_manager sm;
+ canvas canvas (canvas::size_t (9, 3), sm);
+ styled_string foo_ss (sm, "foo");
+ foo_ss.set_url (sm, "https://www.example.com/foo");
+ styled_string bar_ss (sm, "bar");
+ bar_ss.set_url (sm, "https://www.example.com/bar");
+ canvas.paint_text(canvas::coord_t (1, 1), foo_ss);
+ canvas.paint_text(canvas::coord_t (5, 1), bar_ss);
+
+ ASSERT_CANVAS_STREQ (canvas, false,
+ ("\n"
+ " foo bar\n"
+ "\n"));
+ {
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_ST;
+ assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
+ (/* Line 1. */
+ "\n"
+ /* Line 2. */
+ " "
+ "\33]8;;https://www.example.com/foo\33\\foo\33]8;;\33\\"
+ " "
+ "\33]8;;https://www.example.com/bar\33\\bar\33]8;;\33\\"
+ "\n"
+ /* Line 3. */
+ "\n"));
+ }
+
+ {
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_BEL;
+ assert_canvas_streq (SELFTEST_LOCATION, canvas, &pp,
+ (/* Line 1. */
+ "\n"
+ /* Line 2. */
+ " "
+ "\33]8;;https://www.example.com/foo\afoo\33]8;;\a"
+ " "
+ "\33]8;;https://www.example.com/bar\abar\33]8;;\a"
+ "\n"
+ /* Line 3. */
+ "\n"));
+ }
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_canvas_cc_tests ()
+{
+ test_blank ();
+ test_abc ();
+ test_debug_fill ();
+ test_text ();
+ test_circle ();
+ test_color_circle ();
+ test_bold ();
+ test_emoji ();
+ test_emoji_2 ();
+ test_canvas_urls ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,74 @@
+/* Canvas for random-access procedural text art.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_CANVAS_H
+#define GCC_TEXT_ART_CANVAS_H
+
+#include "text-art/types.h"
+
+namespace text_art {
+
+class canvas;
+
+/* A 2 dimensional grid of text cells (a "canvas"), which
+ can be written to ("painted") via random access, and then
+ written out to a pretty_printer once the picture is complete.
+
+ Each text cell can be styled independently (colorization,
+ URLs, etc). */
+
+class canvas
+{
+ public:
+ typedef styled_unichar cell_t;
+ typedef size<class canvas> size_t;
+ typedef coord<class canvas> coord_t;
+ typedef range<class canvas> range_t;
+ typedef rect<class canvas> rect_t;
+
+ canvas (size_t size, const style_manager &style_mgr);
+
+ size_t get_size () const { return m_cells.get_size (); }
+
+ void paint (coord_t coord, cell_t c);
+ void paint_text (coord_t coord, const styled_string &text);
+
+ void fill (rect_t rect, cell_t c);
+ void debug_fill ();
+
+ void print_to_pp (pretty_printer *pp,
+ const char *per_line_prefix = NULL) const;
+ void debug (bool styled) const;
+
+ const cell_t &get (coord_t coord) const
+ {
+ return m_cells.get (coord);
+ }
+
+ private:
+ int get_final_x_in_row (int y) const;
+
+ array2<cell_t, size_t, coord_t> m_cells;
+ const style_manager &m_style_mgr;
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_CANVAS_H */
new file mode 100644
@@ -0,0 +1,723 @@
+/* Classes for printing labelled rulers.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_ALGORITHM
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/ruler.h"
+#include "text-art/theme.h"
+
+using namespace text_art;
+
+void
+x_ruler::add_label (const canvas::range_t &r,
+ styled_string text,
+ style::id_t style_id,
+ label_kind kind)
+{
+ m_labels.push_back (label (r, std::move (text), style_id, kind));
+ m_has_layout = false;
+}
+
+int
+x_ruler::get_canvas_y (int rel_y) const
+{
+ gcc_assert (rel_y >= 0);
+ gcc_assert (rel_y < m_size.h);
+ switch (m_label_dir)
+ {
+ default:
+ gcc_unreachable ();
+ case label_dir::ABOVE:
+ return m_size.h - (rel_y + 1);
+ case label_dir::BELOW:
+ return rel_y;
+ }
+}
+
+void
+x_ruler::paint_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const theme &theme)
+{
+ ensure_layout ();
+
+ if (0)
+ canvas.fill (canvas::rect_t (offset, m_size),
+ canvas::cell_t ('*'));
+
+ for (size_t idx = 0; idx < m_labels.size (); idx++)
+ {
+ const label &iter_label = m_labels[idx];
+
+ /* Paint the ruler itself. */
+ const int ruler_rel_y = get_canvas_y (0);
+ for (int rel_x = iter_label.m_range.start;
+ rel_x < iter_label.m_range.next;
+ rel_x++)
+ {
+ enum theme::cell_kind kind = theme::cell_kind::X_RULER_MIDDLE;
+
+ if (rel_x == iter_label.m_range.start)
+ {
+ kind = theme::cell_kind::X_RULER_LEFT_EDGE;
+ if (idx > 0)
+ {
+ const label &prev_label = m_labels[idx - 1];
+ if (prev_label.m_range.get_max () == iter_label.m_range.start)
+ kind = theme::cell_kind::X_RULER_INTERNAL_EDGE;
+ }
+ }
+ else if (rel_x == iter_label.m_range.get_max ())
+ kind = theme::cell_kind::X_RULER_RIGHT_EDGE;
+ else if (rel_x == iter_label.m_connector_x)
+ {
+ switch (m_label_dir)
+ {
+ default:
+ gcc_unreachable ();
+ case label_dir::ABOVE:
+ kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
+ break;
+ case label_dir::BELOW:
+ kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
+ break;
+ }
+ }
+ canvas.paint (canvas::coord_t (rel_x, ruler_rel_y) + offset,
+ theme.get_cell (kind, iter_label.m_style_id));
+ }
+
+ /* Paint the connector to the text. */
+ for (int connector_rel_y = 1;
+ connector_rel_y < iter_label.m_text_rect.get_min_y ();
+ connector_rel_y++)
+ {
+ canvas.paint
+ ((canvas::coord_t (iter_label.m_connector_x,
+ get_canvas_y (connector_rel_y))
+ + offset),
+ theme.get_cell (theme::cell_kind::X_RULER_VERTICAL_CONNECTOR,
+ iter_label.m_style_id));
+ }
+
+ /* Paint the text. */
+ switch (iter_label.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case x_ruler::label_kind::TEXT:
+ canvas.paint_text
+ ((canvas::coord_t (iter_label.m_text_rect.get_min_x (),
+ get_canvas_y (iter_label.m_text_rect.get_min_y ()))
+ + offset),
+ iter_label.m_text);
+ break;
+
+ case x_ruler::label_kind::TEXT_WITH_BORDER:
+ {
+ const canvas::range_t rel_x_range
+ (iter_label.m_text_rect.get_x_range ());
+
+ enum theme::cell_kind inner_left_kind;
+ enum theme::cell_kind inner_connector_kind;
+ enum theme::cell_kind inner_right_kind;
+ enum theme::cell_kind outer_left_kind;
+ enum theme::cell_kind outer_right_kind;
+
+ switch (m_label_dir)
+ {
+ default:
+ gcc_unreachable ();
+ case label_dir::ABOVE:
+ outer_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
+ outer_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
+ inner_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
+ inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW;
+ inner_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
+ break;
+ case label_dir::BELOW:
+ inner_left_kind = theme::cell_kind::TEXT_BORDER_TOP_LEFT;
+ inner_connector_kind = theme::cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE;
+ inner_right_kind = theme::cell_kind::TEXT_BORDER_TOP_RIGHT;
+ outer_left_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_LEFT;
+ outer_right_kind = theme::cell_kind::TEXT_BORDER_BOTTOM_RIGHT;
+ break;
+ }
+ /* Inner border. */
+ {
+ const int rel_canvas_y
+ = get_canvas_y (iter_label.m_text_rect.get_min_y ());
+ /* Left corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (inner_left_kind,
+ iter_label.m_style_id));
+ /* Edge. */
+ const canvas::cell_t edge_border_cell
+ = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
+ iter_label.m_style_id);
+ const canvas::cell_t connector_border_cell
+ = theme.get_cell (inner_connector_kind,
+ iter_label.m_style_id);
+ for (int rel_x = rel_x_range.get_min () + 1;
+ rel_x < rel_x_range.get_max ();
+ rel_x++)
+ if (rel_x == iter_label.m_connector_x)
+ canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
+ + offset),
+ connector_border_cell);
+ else
+ canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
+ + offset),
+ edge_border_cell);
+
+ /* Right corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (inner_right_kind,
+ iter_label.m_style_id));
+ }
+
+ {
+ const int rel_canvas_y
+ = get_canvas_y (iter_label.m_text_rect.get_min_y () + 1);
+ const canvas::cell_t border_cell
+ = theme.get_cell (theme::cell_kind::TEXT_BORDER_VERTICAL,
+ iter_label.m_style_id);
+
+ /* Left border. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
+ rel_canvas_y)
+ + offset),
+ border_cell);
+ /* Text. */
+ canvas.paint_text ((canvas::coord_t (rel_x_range.get_min () + 1,
+ rel_canvas_y)
+ + offset),
+ iter_label.m_text);
+ /* Right border. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
+ rel_canvas_y)
+ + offset),
+ border_cell);
+ }
+
+ /* Outer border. */
+ {
+ const int rel_canvas_y
+ = get_canvas_y (iter_label.m_text_rect.get_max_y ());
+ /* Left corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_min (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (outer_left_kind,
+ iter_label.m_style_id));
+ /* Edge. */
+ const canvas::cell_t border_cell
+ = theme.get_cell (theme::cell_kind::TEXT_BORDER_HORIZONTAL,
+ iter_label.m_style_id);
+ for (int rel_x = rel_x_range.get_min () + 1;
+ rel_x < rel_x_range.get_max ();
+ rel_x++)
+ canvas.paint ((canvas::coord_t (rel_x, rel_canvas_y)
+ + offset),
+ border_cell);
+
+ /* Right corner. */
+ canvas.paint ((canvas::coord_t (rel_x_range.get_max (),
+ rel_canvas_y)
+ + offset),
+ theme.get_cell (outer_right_kind,
+ iter_label.m_style_id));
+ }
+ }
+ break;
+ }
+ }
+}
+
+DEBUG_FUNCTION void
+x_ruler::debug (const style_manager &sm)
+{
+ canvas c (get_size (), sm);
+ paint_to_canvas (c, canvas::coord_t (0, 0), unicode_theme ());
+ c.debug (true);
+}
+
+x_ruler::label::label (const canvas::range_t &range,
+ styled_string text,
+ style::id_t style_id,
+ label_kind kind)
+: m_range (range),
+ m_text (std::move (text)),
+ m_style_id (style_id),
+ m_kind (kind),
+ m_text_rect (canvas::coord_t (0, 0),
+ canvas::size_t (m_text.calc_canvas_width (), 1)),
+ m_connector_x ((m_range.get_min () + m_range.get_max ()) / 2)
+{
+ if (kind == label_kind::TEXT_WITH_BORDER)
+ {
+ m_text_rect.m_size.w += 2;
+ m_text_rect.m_size.h += 2;
+ }
+}
+
+bool
+x_ruler::label::operator< (const label &other) const
+{
+ int cmp = m_range.start - other.m_range.start;
+ if (cmp)
+ return cmp < 0;
+ return m_range.next < other.m_range.next;
+}
+
+void
+x_ruler::ensure_layout ()
+{
+ if (m_has_layout)
+ return;
+ update_layout ();
+ m_has_layout = true;
+}
+
+void
+x_ruler::update_layout ()
+{
+ if (m_labels.empty ())
+ return;
+
+ std::sort (m_labels.begin (), m_labels.end ());
+
+ /* Place labels. */
+ int ruler_width = m_labels.back ().m_range.get_next ();
+ int width_with_labels = ruler_width;
+
+ /* Get x coordinates of text parts of each label
+ (m_text_rect.m_top_left.x for each label). */
+ for (size_t idx = 0; idx < m_labels.size (); idx++)
+ {
+ label &iter_label = m_labels[idx];
+ /* Attempt to center the text label. */
+ int min_x;
+ if (idx > 0)
+ {
+ /* ...but don't overlap with the connector to the left. */
+ int left_neighbor_connector_x = m_labels[idx - 1].m_connector_x;
+ min_x = left_neighbor_connector_x + 1;
+ }
+ else
+ {
+ /* ...or go beyond the leftmost column. */
+ min_x = 0;
+ }
+ int connector_x = iter_label.m_connector_x;
+ int centered_x
+ = connector_x - ((int)iter_label.m_text_rect.get_width () / 2);
+ int text_x = std::max (min_x, centered_x);
+ iter_label.m_text_rect.m_top_left.x = text_x;
+ }
+
+ /* Now walk backwards trying to place them vertically,
+ setting m_text_rect.m_top_left.y for each label,
+ consolidating the rows where possible.
+ The y cooordinates are stored with respect to label_dir::BELOW. */
+ int label_y = 2;
+ for (int idx = m_labels.size () - 1; idx >= 0; idx--)
+ {
+ label &iter_label = m_labels[idx];
+ /* Does it fit on the same row as the text label to the right? */
+ size_t text_len = iter_label.m_text_rect.get_width ();
+ /* Get the x-coord of immediately beyond iter_label's text. */
+ int next_x = iter_label.m_text_rect.get_min_x () + text_len;
+ if (idx < (int)m_labels.size () - 1)
+ {
+ if (next_x >= m_labels[idx + 1].m_text_rect.get_min_x ())
+ {
+ /* If not, start a new row. */
+ label_y += m_labels[idx + 1].m_text_rect.get_height ();
+ }
+ }
+ iter_label.m_text_rect.m_top_left.y = label_y;
+ width_with_labels = std::max (width_with_labels, next_x);
+ }
+
+ m_size = canvas::size_t (width_with_labels,
+ label_y + m_labels[0].m_text_rect.get_height ());
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+assert_x_ruler_streq (const location &loc,
+ x_ruler &ruler,
+ const theme &theme,
+ const style_manager &sm,
+ bool styled,
+ const char *expected_str)
+{
+ canvas c (ruler.get_size (), sm);
+ ruler.paint_to_canvas (c, canvas::coord_t (0, 0), theme);
+ if (0)
+ c.debug (styled);
+ assert_canvas_streq (loc, c, styled, expected_str);
+}
+
+#define ASSERT_X_RULER_STREQ(RULER, THEME, SM, STYLED, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ assert_x_ruler_streq ((SELFTEST_LOCATION), \
+ (RULER), \
+ (THEME), \
+ (SM), \
+ (STYLED), \
+ (EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+static void
+test_single ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
+ style::id_plain, x_ruler::label_kind::TEXT);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~~~~+~~~~|\n"
+ " |\n"
+ " foo\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┤\n"
+ " │\n"
+ " foo\n"));
+}
+
+static void
+test_single_above ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::ABOVE);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "hello world"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("hello world\n"
+ " |\n"
+ "|~~~~+~~~~|\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("hello world\n"
+ " │\n"
+ "├────┴────┤\n"));
+}
+
+static void
+test_multiple_contiguous ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~~~~+~~~~|~+~~|\n"
+ " | |\n"
+ " foo bar\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " foo bar\n"));
+}
+
+static void
+test_multiple_contiguous_above ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::ABOVE);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "foo"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "bar"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ (" foo bar\n"
+ " | |\n"
+ "|~~~~+~~~~|~+~~|\n"));
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ (" foo bar\n"
+ " │ │\n"
+ "├────┴────┼─┴──┤\n"));
+}
+
+static void
+test_multiple_contiguous_abutting_labels ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "12345678"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "1234678"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │ 1234678\n"
+ " 12345678\n"));
+}
+
+static void
+test_multiple_contiguous_overlapping_labels ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11), styled_string (sm, "123456789"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16), styled_string (sm, "12346789"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │ 12346789\n"
+ " 123456789\n"));
+}
+static void
+test_abutting_left_border ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 6),
+ styled_string (sm, "this is a long label"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├─┬──┤\n"
+ " │\n"
+ "this is a long label\n"));
+}
+
+static void
+test_too_long_to_consolidate_vertically ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11),
+ styled_string (sm, "long string A"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16),
+ styled_string (sm, "long string B"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │long string B\n"
+ "long string A\n"));
+}
+
+static void
+test_abutting_neighbor ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 11),
+ styled_string (sm, "very long string A"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 16),
+ styled_string (sm, "very long string B"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ ("├────┬────┼─┬──┤\n"
+ " │ │\n"
+ " │very long string B\n"
+ "very long string A\n"));
+}
+
+static void
+test_gaps ()
+{
+ style_manager sm;
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5),
+ styled_string (sm, "foo"),
+ style::id_plain);
+ r.add_label (canvas::range_t (10, 15),
+ styled_string (sm, "bar"),
+ style::id_plain);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("|~+~| |~+~|\n"
+ " | |\n"
+ " foo bar\n"));
+}
+
+static void
+test_styled ()
+{
+ style_manager sm;
+ style s1, s2;
+ s1.m_bold = true;
+ s1.m_fg_color = style::named_color::YELLOW;
+ s2.m_bold = true;
+ s2.m_fg_color = style::named_color::BLUE;
+ style::id_t sid1 = sm.get_or_create_id (s1);
+ style::id_t sid2 = sm.get_or_create_id (s2);
+
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5), styled_string (sm, "foo"), sid1);
+ r.add_label (canvas::range_t (10, 15), styled_string (sm, "bar"), sid2);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ ("[00;01;33m[K|~+~|[00m[K [00;01;34m[K|~+~|[00m[K\n"
+ " [00;01;33m[K|[00m[K [00;01;34m[K|[00m[K\n"
+ " foo bar\n"));
+}
+
+static void
+test_borders ()
+{
+ style_manager sm;
+ {
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5),
+ styled_string (sm, "label 1"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ r.add_label (canvas::range_t (10, 15),
+ styled_string (sm, "label 2"),
+ style::id_plain);
+ r.add_label (canvas::range_t (20, 25),
+ styled_string (sm, "label 3"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ "|~+~| |~+~| |~+~|\n"
+ " | | |\n"
+ " | label 2 +---+---+\n"
+ "+-+-----+ |label 3|\n"
+ "|label 1| +-------+\n"
+ "+-------+\n");
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ "├─┬─┤ ├─┬─┤ ├─┬─┤\n"
+ " │ │ │\n"
+ " │ label 2 ╭───┴───╮\n"
+ "╭─┴─────╮ │label 3│\n"
+ "│label 1│ ╰───────╯\n"
+ "╰───────╯\n");
+ }
+ {
+ x_ruler r (x_ruler::label_dir::ABOVE);
+ r.add_label (canvas::range_t (0, 5),
+ styled_string (sm, "label 1"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ r.add_label (canvas::range_t (10, 15),
+ styled_string (sm, "label 2"),
+ style::id_plain);
+ r.add_label (canvas::range_t (20, 25),
+ styled_string (sm, "label 3"),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ "+-------+\n"
+ "|label 1| +-------+\n"
+ "+-+-----+ |label 3|\n"
+ " | label 2 +---+---+\n"
+ " | | |\n"
+ "|~+~| |~+~| |~+~|\n");
+ ASSERT_X_RULER_STREQ
+ (r, unicode_theme (), sm, true,
+ "╭───────╮\n"
+ "│label 1│ ╭───────╮\n"
+ "╰─┬─────╯ │label 3│\n"
+ " │ label 2 ╰───┬───╯\n"
+ " │ │ │\n"
+ "├─┴─┤ ├─┴─┤ ├─┴─┤\n");
+ }
+}
+
+static void
+test_emoji ()
+{
+ style_manager sm;
+
+ styled_string s;
+ s.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
+ true));
+ s.append (styled_string (sm, " "));
+ s.append (styled_string (sm, "this is a warning"));
+
+ x_ruler r (x_ruler::label_dir::BELOW);
+ r.add_label (canvas::range_t (0, 5),
+ std::move (s),
+ style::id_plain,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+
+ ASSERT_X_RULER_STREQ
+ (r, ascii_theme (), sm, true,
+ "|~+~|\n"
+ " |\n"
+ "+-+------------------+\n"
+ "|⚠️ this is a warning|\n"
+ "+--------------------+\n");
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_ruler_cc_tests ()
+{
+ test_single ();
+ test_single_above ();
+ test_multiple_contiguous ();
+ test_multiple_contiguous_above ();
+ test_multiple_contiguous_abutting_labels ();
+ test_multiple_contiguous_overlapping_labels ();
+ test_abutting_left_border ();
+ test_too_long_to_consolidate_vertically ();
+ test_abutting_neighbor ();
+ test_gaps ();
+ test_styled ();
+ test_borders ();
+ test_emoji ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,125 @@
+/* Classes for printing labelled rulers.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_RULER_H
+#define GCC_TEXT_ART_RULER_H
+
+#include "text-art/canvas.h"
+
+namespace text_art {
+
+/* A way to annotate a series of ranges of canvas coordinates
+ with text labels either above or, in this example, below:
+ ├───────┬───────┼───────┬───────┼───────┬───────┤
+ │ │ │
+ label A label B label C
+ with logic to ensure that the text labels don't overlap
+ when printed. */
+
+class x_ruler
+{
+ public:
+ enum class label_dir { ABOVE, BELOW };
+ enum class label_kind
+ {
+ TEXT,
+ TEXT_WITH_BORDER
+ };
+
+ x_ruler (label_dir dir)
+ : m_label_dir (dir),
+ m_size (canvas::size_t (0, 0)),
+ m_has_layout (false)
+ {}
+
+ void add_label (const canvas::range_t &r,
+ styled_string text,
+ style::id_t style_id,
+ label_kind kind = label_kind::TEXT);
+
+ canvas::size_t get_size ()
+ {
+ ensure_layout ();
+ return m_size;
+ }
+
+ void paint_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const theme &theme);
+
+ void debug (const style_manager &sm);
+
+ private:
+ /* A particular label within an x_ruler.
+ Consider e.g.:
+
+ # x: 01234567890123456789012345678901234567890123456789
+ # y: 0: ├───────┬───────┼───────┬───────┼───────┬───────┤
+ # 1: │ │ │
+ # 2: label A label B label C
+ #
+
+ Then "label A" is:
+
+ # m_connector_x == 8
+ # V
+ # x: 0123456789012
+ # y: 0: ┬
+ # 1: │
+ # 2: label A
+ # x: 0123456789012
+ # ^
+ # m_text_coord.x == 6
+
+ and m_text_coord is (2, 6).
+ The y cooordinates are stored with respect to label_dir::BELOW;
+ for label_dir::ABOVE we flip them when painting the ruler. */
+ class label
+ {
+ friend class x_ruler;
+ public:
+ label (const canvas::range_t &range, styled_string text, style::id_t style_id,
+ label_kind kind);
+
+ bool operator< (const label &other) const;
+
+ private:
+ canvas::range_t m_range;
+ styled_string m_text;
+ style::id_t m_style_id;
+ label_kind m_kind;
+ canvas::rect_t m_text_rect; // includes any border
+ int m_connector_x;
+ };
+
+ void ensure_layout ();
+ void update_layout ();
+ int get_canvas_y (int rel_y) const;
+
+ label_dir m_label_dir;
+ std::vector<label> m_labels;
+ canvas::size_t m_size;
+ bool m_has_layout = false;
+
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_RULER_H */
new file mode 100644
@@ -0,0 +1,77 @@
+/* Selftests for text art.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "selftest.h"
+#include "pretty-print.h"
+#include "text-art/selftests.h"
+#include "text-art/canvas.h"
+
+#if CHECKING_P
+
+/* Run all tests, aborting if any fail. */
+
+void
+selftest::text_art_tests ()
+{
+ text_art_style_cc_tests ();
+ text_art_styled_string_cc_tests ();
+
+ text_art_box_drawing_cc_tests ();
+ text_art_canvas_cc_tests ();
+ text_art_ruler_cc_tests ();
+ text_art_table_cc_tests ();
+ text_art_widget_cc_tests ();
+}
+
+/* Implementation detail of ASSERT_CANVAS_STREQ. */
+
+void
+selftest::assert_canvas_streq (const location &loc,
+ const text_art::canvas &canvas,
+ pretty_printer *pp,
+ const char *expected_str)
+{
+ canvas.print_to_pp (pp);
+ if (0)
+ fprintf (stderr, "%s\n", pp_formatted_text (pp));
+ ASSERT_STREQ_AT (loc, pp_formatted_text (pp), expected_str);
+}
+
+/* Implementation detail of ASSERT_CANVAS_STREQ. */
+
+void
+selftest::assert_canvas_streq (const location &loc,
+ const text_art::canvas &canvas,
+ bool styled,
+ const char *expected_str)
+{
+ pretty_printer pp;
+ if (styled)
+ {
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_DEFAULT;
+ }
+ assert_canvas_streq (loc, canvas, &pp, expected_str);
+}
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,60 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_SELFTESTS_H
+#define GCC_TEXT_ART_SELFTESTS_H
+
+#if CHECKING_P
+
+#include "text-art/types.h"
+
+namespace selftest {
+
+extern void text_art_box_drawing_cc_tests ();
+extern void text_art_canvas_cc_tests ();
+extern void text_art_ruler_cc_tests ();
+extern void text_art_style_cc_tests ();
+extern void text_art_styled_string_cc_tests ();
+extern void text_art_table_cc_tests ();
+extern void text_art_widget_cc_tests ();
+
+extern void text_art_tests ();
+
+extern void assert_canvas_streq (const location &loc,
+ const text_art::canvas &canvas,
+ pretty_printer *pp,
+ const char *expected_str);
+extern void assert_canvas_streq (const location &loc,
+ const text_art::canvas &canvas,
+ bool styled,
+ const char *expected_str);
+
+#define ASSERT_CANVAS_STREQ(CANVAS, STYLED, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ assert_canvas_streq ((SELFTEST_LOCATION), \
+ (CANVAS), \
+ (STYLED), \
+ (EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+} /* end of namespace selftest. */
+
+#endif /* #if CHECKING_P */
+
+#endif /* GCC_TEXT_ART_SELFTESTS_H */
new file mode 100644
@@ -0,0 +1,632 @@
+/* Classes for styling text cells (color, URLs).
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "pretty-print.h"
+#include "intl.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/types.h"
+#include "color-macros.h"
+
+using namespace text_art;
+
+/* class text_art::style. */
+
+style &
+style::set_style_url (const char *url)
+{
+ m_url.clear ();
+ while (*url)
+ m_url.push_back (*(url++));
+ return *this;
+}
+
+/* class text_art::style::color. */
+
+bool
+style::color::operator== (const style::color &other) const
+{
+ if (m_kind != other.m_kind)
+ return false;
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case kind::NAMED:
+ return (u.m_named.m_name == other.u.m_named.m_name
+ && u.m_named.m_bright == other.u.m_named.m_bright);
+ case kind::BITS_8:
+ return u.m_8bit == other.u.m_8bit;
+ case kind::BITS_24:
+ return (u.m_24bit.r == other.u.m_24bit.r
+ && u.m_24bit.g == other.u.m_24bit.g
+ && u.m_24bit.b == other.u.m_24bit.b);
+ }
+}
+
+static void
+ensure_separator (pretty_printer *pp, bool &need_separator)
+{
+ if (need_separator)
+ pp_string (pp, COLOR_SEPARATOR);
+ need_separator = true;
+}
+
+void
+style::color::print_sgr (pretty_printer *pp,
+ bool fg,
+ bool &need_separator) const
+{
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case kind::NAMED:
+ {
+ static const char * const fg_normal[] = {"", // reset, for DEFAULT
+ COLOR_FG_BLACK,
+ COLOR_FG_RED,
+ COLOR_FG_GREEN,
+ COLOR_FG_YELLOW,
+ COLOR_FG_BLUE,
+ COLOR_FG_MAGENTA,
+ COLOR_FG_CYAN,
+ COLOR_FG_WHITE};
+ static const char * const fg_bright[] = {"", // reset, for DEFAULT
+ COLOR_FG_BRIGHT_BLACK,
+ COLOR_FG_BRIGHT_RED,
+ COLOR_FG_BRIGHT_GREEN,
+ COLOR_FG_BRIGHT_YELLOW,
+ COLOR_FG_BRIGHT_BLUE,
+ COLOR_FG_BRIGHT_MAGENTA,
+ COLOR_FG_BRIGHT_CYAN,
+ COLOR_FG_BRIGHT_WHITE};
+ static const char * const bg_normal[] = {"", // reset, for DEFAULT
+ COLOR_BG_BLACK,
+ COLOR_BG_RED,
+ COLOR_BG_GREEN,
+ COLOR_BG_YELLOW,
+ COLOR_BG_BLUE,
+ COLOR_BG_MAGENTA,
+ COLOR_BG_CYAN,
+ COLOR_BG_WHITE};
+ static const char * const bg_bright[] = {"", // reset, for DEFAULT
+ COLOR_BG_BRIGHT_BLACK,
+ COLOR_BG_BRIGHT_RED,
+ COLOR_BG_BRIGHT_GREEN,
+ COLOR_BG_BRIGHT_YELLOW,
+ COLOR_BG_BRIGHT_BLUE,
+ COLOR_BG_BRIGHT_MAGENTA,
+ COLOR_BG_BRIGHT_CYAN,
+ COLOR_BG_BRIGHT_WHITE};
+ STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (fg_bright));
+ STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_normal));
+ STATIC_ASSERT (ARRAY_SIZE (fg_normal) == ARRAY_SIZE (bg_bright));
+ gcc_assert ((size_t)u.m_named.m_name < ARRAY_SIZE (fg_normal));
+ const char *const *arr;
+ if (fg)
+ arr = u.m_named.m_bright ? fg_bright : fg_normal;
+ else
+ arr = u.m_named.m_bright ? bg_bright : bg_normal;
+ const char *str = arr[(size_t)u.m_named.m_name];
+ if (strlen (str) > 0)
+ {
+ ensure_separator (pp, need_separator);
+ pp_string (pp, str);
+ }
+ }
+ break;
+ case kind::BITS_8:
+ {
+ ensure_separator (pp, need_separator);
+ if (fg)
+ pp_string (pp, "38");
+ else
+ pp_string (pp, "48");
+ pp_printf (pp, ";5;%i", (int)u.m_8bit);
+ }
+ break;
+ case kind::BITS_24:
+ {
+ ensure_separator (pp, need_separator);
+ if (fg)
+ pp_string (pp, "38");
+ else
+ pp_string (pp, "48");
+ pp_printf (pp, ";2;%i;%i;%i",
+ (int)u.m_24bit.r,
+ (int)u.m_24bit.g,
+ (int)u.m_24bit.b);
+ }
+ break;
+ }
+}
+
+/* class text_art::style. */
+
+/* See https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
+ GRCM - GRAPHIC RENDITION COMBINATION MODE can be "REPLACING" or
+ "CUMULATIVE", which affects whether we need to respecify all attributes
+ at each SGR, or can accumulate them. Looks like we can't rely on the value
+ of this, so we have to emit a single SGR for all changes, with a "0" reset
+ at the front, forcing it to be effectively replacing. */
+
+void
+style::print_changes (pretty_printer *pp,
+ const style &old_style,
+ const style &new_style)
+{
+ if (pp_show_color (pp))
+ {
+ bool needs_sgr = ((old_style.m_bold != new_style.m_bold)
+ || (old_style.m_underscore != new_style.m_underscore)
+ || (old_style.m_blink != new_style.m_blink)
+ || (old_style.m_fg_color != new_style.m_fg_color)
+ || (old_style.m_bg_color != new_style.m_bg_color));
+ if (needs_sgr)
+ {
+ bool emit_reset = (old_style.m_bold
+ || new_style.m_bold
+ || old_style.m_underscore
+ || new_style.m_underscore
+ || old_style.m_blink
+ || new_style.m_blink);
+ bool need_separator = false;
+
+ pp_string (pp, SGR_START);
+ if (emit_reset)
+ {
+ pp_string (pp, COLOR_NONE);
+ need_separator = true;
+ }
+ if (new_style.m_bold)
+ {
+ gcc_assert (emit_reset);
+ ensure_separator (pp, need_separator);
+ pp_string (pp, COLOR_BOLD);
+ }
+ if (new_style.m_underscore)
+ {
+ gcc_assert (emit_reset);
+ ensure_separator (pp, need_separator);
+ pp_string (pp, COLOR_UNDERSCORE);
+ }
+ if (new_style.m_blink)
+ {
+ gcc_assert (emit_reset);
+ ensure_separator (pp, need_separator);
+ pp_string (pp, COLOR_BLINK);
+ }
+ new_style.m_fg_color.print_sgr (pp, true, need_separator);
+ new_style.m_bg_color.print_sgr (pp, false, need_separator);
+ pp_string (pp, SGR_END);
+ }
+ }
+
+ if (old_style.m_url != new_style.m_url)
+ {
+ if (!old_style.m_url.empty ())
+ pp_end_url (pp);
+ if (pp->url_format != URL_FORMAT_NONE
+ && !new_style.m_url.empty ())
+ {
+ /* Adapted from pp_begin_url, but encoding the
+ chars to UTF-8 on the fly, rather than converting
+ to a buffer. */
+ pp_string (pp, "\33]8;;");
+ for (auto ch : new_style.m_url)
+ pp_unicode_character (pp, ch);
+ switch (pp->url_format)
+ {
+ default:
+ case URL_FORMAT_NONE:
+ gcc_unreachable ();
+ case URL_FORMAT_ST:
+ pp_string (pp, "\33\\");
+ break;
+ case URL_FORMAT_BEL:
+ pp_string (pp, "\a");
+ break;
+ }
+ }
+ }
+}
+
+/* class text_art::style_manager. */
+
+style_manager::style_manager ()
+{
+ // index 0 will be the default style
+ m_styles.push_back (style ());
+}
+
+style::id_t
+style_manager::get_or_create_id (const style &s)
+{
+ // For now, linear search
+ std::vector<style>::iterator existing
+ (std::find (m_styles.begin (), m_styles.end (), s));
+
+ /* If found, return index of slot. */
+ if (existing != m_styles.end ())
+ return std::distance (m_styles.begin (), existing);
+
+ /* Not found. */
+
+ /* styled_str uses 7 bits for style information, so we can only support
+ up to 128 different style combinations.
+ Gracefully fail by turning off styling when this limit is reached. */
+ if (m_styles.size () >= 127)
+ return 0;
+
+ m_styles.push_back (s);
+ return m_styles.size () - 1;
+}
+
+void
+style_manager::print_any_style_changes (pretty_printer *pp,
+ style::id_t old_id,
+ style::id_t new_id) const
+{
+ gcc_assert (pp);
+ if (old_id == new_id)
+ return;
+
+ const style &old_style = m_styles[old_id];
+ const style &new_style = m_styles[new_id];
+ gcc_assert (!(old_style == new_style));
+ style::print_changes (pp, old_style, new_style);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+void
+assert_style_change_streq (const location &loc,
+ const style &old_style,
+ const style &new_style,
+ const char *expected_str)
+{
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ style::print_changes (&pp, old_style, new_style);
+ ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_str);
+}
+
+#define ASSERT_STYLE_CHANGE_STREQ(OLD_STYLE, NEW_STYLE, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ (OLD_STYLE), \
+ (NEW_STYLE), \
+ (EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+static void
+test_bold ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style bold;
+ bold.m_bold = true;
+
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ ASSERT_STYLE_CHANGE_STREQ (plain, bold, "\33[00;01m\33[K");
+ ASSERT_STYLE_CHANGE_STREQ (bold, plain, "\33[00m\33[K");
+}
+
+static void
+test_underscore ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style underscore;
+ underscore.m_underscore = true;
+
+ ASSERT_EQ (sm.get_or_create_id (underscore), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (underscore), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ ASSERT_STYLE_CHANGE_STREQ (plain, underscore, "\33[00;04m\33[K");
+ ASSERT_STYLE_CHANGE_STREQ (underscore, plain, "\33[00m\33[K");
+}
+
+static void
+test_blink ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style blink;
+ blink.m_blink = true;
+
+ ASSERT_EQ (sm.get_or_create_id (blink), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (blink), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ ASSERT_STYLE_CHANGE_STREQ (plain, blink, "\33[00;05m\33[K");
+ ASSERT_STYLE_CHANGE_STREQ (blink, plain, "\33[00m\33[K");
+}
+
+#define ASSERT_NAMED_COL_STREQ(NAMED_COLOR, FG, BRIGHT, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ { \
+ style plain; \
+ style s; \
+ if (FG) \
+ s.m_fg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
+ else \
+ s.m_bg_color = style::color ((NAMED_COLOR), (BRIGHT)); \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ plain, \
+ s, \
+ (EXPECTED_STR)); \
+ } \
+ SELFTEST_END_STMT
+
+static void
+test_named_colors ()
+{
+ /* Foreground colors. */
+ {
+ const bool fg = true;
+ {
+ const bool bright = false;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "[30m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "[31m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "[32m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "[33m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "[34m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "[35m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "[36m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "[37m[K");
+ }
+ {
+ const bool bright = true;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
+ "[m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "[90m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "[91m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "[92m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "[93m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "[94m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "[95m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "[96m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "[97m[K");
+ }
+ }
+
+ /* Background colors. */
+ {
+ const bool fg = false;
+ {
+ const bool bright = false;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright, "");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "[40m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "[41m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "[42m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "[43m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "[44m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "[45m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "[46m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "[47m[K");
+ }
+ {
+ const bool bright = true;
+ ASSERT_NAMED_COL_STREQ (style::named_color::DEFAULT, fg, bright,
+ "[m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLACK, fg, bright,
+ "[100m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::RED, fg, bright,
+ "[101m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::GREEN, fg, bright,
+ "[102m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::YELLOW, fg, bright,
+ "[103m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::BLUE, fg, bright,
+ "[104m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::MAGENTA, fg, bright,
+ "[105m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::CYAN, fg, bright,
+ "[106m[K");
+ ASSERT_NAMED_COL_STREQ (style::named_color::WHITE, fg, bright,
+ "[107m[K");
+ }
+ }
+}
+
+#define ASSERT_8_BIT_COL_STREQ(COL_VAL, FG, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ { \
+ style plain; \
+ style s; \
+ if (FG) \
+ s.m_fg_color = style::color (COL_VAL); \
+ else \
+ s.m_bg_color = style::color (COL_VAL); \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ plain, \
+ s, \
+ (EXPECTED_STR)); \
+ } \
+ SELFTEST_END_STMT
+
+static void
+test_8_bit_colors ()
+{
+ /* Foreground colors. */
+ {
+ const bool fg = true;
+ /* 0-15: standard and high-intensity standard colors. */
+ ASSERT_8_BIT_COL_STREQ (0, fg, "[38;5;0m[K");
+ ASSERT_8_BIT_COL_STREQ (15, fg, "[38;5;15m[K");
+ /* 16-231: 6x6x6 color cube. */
+ ASSERT_8_BIT_COL_STREQ (16, fg, "[38;5;16m[K");
+ ASSERT_8_BIT_COL_STREQ (231, fg, "[38;5;231m[K");
+ /* 232-255: grayscale. */
+ ASSERT_8_BIT_COL_STREQ (232, fg, "[38;5;232m[K");
+ ASSERT_8_BIT_COL_STREQ (255, fg, "[38;5;255m[K");
+ }
+ /* Background colors. */
+ {
+ const bool fg = false;
+ /* 0-15: standard and high-intensity standard colors. */
+ ASSERT_8_BIT_COL_STREQ (0, fg, "[48;5;0m[K");
+ ASSERT_8_BIT_COL_STREQ (15, fg, "[48;5;15m[K");
+ /* 16-231: 6x6x6 color cube. */
+ ASSERT_8_BIT_COL_STREQ (16, fg, "[48;5;16m[K");
+ ASSERT_8_BIT_COL_STREQ (231, fg, "[48;5;231m[K");
+ /* 232-255: grayscale. */
+ ASSERT_8_BIT_COL_STREQ (232, fg, "[48;5;232m[K");
+ ASSERT_8_BIT_COL_STREQ (255, fg, "[48;5;255m[K");
+ }
+}
+
+#define ASSERT_24_BIT_COL_STREQ(R, G, B, FG, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ { \
+ style plain; \
+ style s; \
+ if (FG) \
+ s.m_fg_color = style::color ((R), (G), (B)); \
+ else \
+ s.m_bg_color = style::color ((R), (G), (B)); \
+ assert_style_change_streq ((SELFTEST_LOCATION), \
+ plain, \
+ s, \
+ (EXPECTED_STR)); \
+ } \
+ SELFTEST_END_STMT
+
+static void
+test_24_bit_colors ()
+{
+ /* Foreground colors. */
+ {
+ const bool fg = true;
+ // #F3FAF2:
+ ASSERT_24_BIT_COL_STREQ (0xf3, 0xfa, 0xf2, fg,
+ "[38;2;243;250;242m[K");
+ }
+ /* Background colors. */
+ {
+ const bool fg = false;
+ // #FDF7E7
+ ASSERT_24_BIT_COL_STREQ (0xfd, 0xf7, 0xe7, fg,
+ "[48;2;253;247;231m[K");
+ }
+}
+
+static void
+test_style_combinations ()
+{
+ style_manager sm;
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style plain;
+ ASSERT_EQ (sm.get_or_create_id (plain), 0);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ style bold;
+ bold.m_bold = true;
+
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (sm.get_or_create_id (bold), 1);
+ ASSERT_EQ (sm.get_num_styles (), 2);
+
+ style magenta_on_blue;
+ magenta_on_blue.m_fg_color = style::named_color::MAGENTA;
+ magenta_on_blue.m_bg_color = style::named_color::BLUE;
+ ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
+ ASSERT_EQ (sm.get_num_styles (), 3);
+ ASSERT_EQ (sm.get_or_create_id (magenta_on_blue), 2);
+ ASSERT_EQ (sm.get_num_styles (), 3);
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_style_cc_tests ()
+{
+ test_bold ();
+ test_underscore ();
+ test_blink ();
+ test_named_colors ();
+ test_8_bit_colors ();
+ test_24_bit_colors ();
+ test_style_combinations ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,1107 @@
+/* Implementation of text_art::styled_string.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "pretty-print.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/types.h"
+#include "color-macros.h"
+
+using namespace text_art;
+
+namespace {
+
+/* Support class for parsing text containing escape codes.
+ See e.g. https://en.wikipedia.org/wiki/ANSI_escape_code
+ We only support the codes that pretty-print.cc can generate. */
+
+class escape_code_parser
+{
+public:
+ escape_code_parser (style_manager &sm,
+ std::vector<styled_unichar> &out)
+ : m_sm (sm),
+ m_out (out),
+ m_cur_style_obj (),
+ m_cur_style_id (style::id_plain),
+ m_state (state::START)
+ {
+ }
+
+ void on_char (cppchar_t ch)
+ {
+ switch (m_state)
+ {
+ default:
+ gcc_unreachable ();
+ case state::START:
+ if (ch == '\033')
+ {
+ /* The start of an escape sequence. */
+ m_state = state::AFTER_ESC;
+ return;
+ }
+ break;
+ case state::AFTER_ESC:
+ if (ch == '[')
+ {
+ /* ESC [ is a Control Sequence Introducer. */
+ m_state = state::CS_PARAMETER_BYTES;
+ return;
+ }
+ else if (ch == ']')
+ {
+ /* ESC ] is an Operating System Command. */
+ m_state = state::WITHIN_OSC;
+ return;
+ }
+ break;
+ case state::CS_PARAMETER_BYTES:
+ if (parameter_byte_p (ch))
+ {
+ m_parameter_bytes.push_back ((char)ch);
+ return;
+ }
+ else if (intermediate_byte_p (ch))
+ {
+ m_intermediate_bytes.push_back ((char)ch);
+ m_state = state::CS_INTERMEDIATE_BYTES;
+ return;
+ }
+ else if (final_byte_p (ch))
+ {
+ on_final_csi_char (ch);
+ return;
+ }
+ break;
+ case state::CS_INTERMEDIATE_BYTES:
+ /* Expect zero or more intermediate bytes. */
+ if (intermediate_byte_p (ch))
+ {
+ m_intermediate_bytes.push_back ((char)ch);
+ return;
+ }
+ else if (final_byte_p (ch))
+ {
+ on_final_csi_char (ch);
+ return;
+ }
+ break;
+ case state::WITHIN_OSC:
+ /* Accumulate chars into m_osc_string, until we see an ST or a BEL. */
+ {
+ /* Check for ESC \, the String Terminator (aka "ST"). */
+ if (ch == '\\'
+ && m_osc_string.size () > 0
+ && m_osc_string.back () == '\033')
+ {
+ m_osc_string.pop_back ();
+ on_final_osc_char ();
+ return;
+ }
+ else if (ch == '\a')
+ {
+ // BEL
+ on_final_osc_char ();
+ return;
+ }
+ m_osc_string.push_back (ch);
+ return;
+ }
+ break;
+ }
+
+ /* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
+ variation for the previous character. */
+ if (ch == 0xFE0F)
+ {
+ if (m_out.size () > 0)
+ m_out.back ().set_emoji_variant ();
+ return;
+ }
+
+ if (cpp_is_combining_char (ch))
+ {
+ if (m_out.size () > 0)
+ {
+ m_out.back ().add_combining_char (ch);
+ return;
+ }
+ }
+ /* By default, add the char. */
+ m_out.push_back (styled_unichar (ch, false, m_cur_style_id));
+ }
+
+private:
+ void on_final_csi_char (cppchar_t ch)
+ {
+ switch (ch)
+ {
+ default:
+ /* Unrecognized. */
+ break;
+ case 'm':
+ {
+ /* SGR control sequence. */
+ if (m_parameter_bytes.empty ())
+ reset_style ();
+ std::vector<int> params (params_from_decimal ());
+ for (auto iter = params.begin (); iter != params.end (); )
+ {
+ const int param = *iter;
+ switch (param)
+ {
+ default:
+ /* Unrecognized SGR parameter. */
+ break;
+ case 0:
+ reset_style ();
+ break;
+ case 1:
+ set_style_bold ();
+ break;
+ case 4:
+ set_style_underscore ();
+ break;
+ case 5:
+ set_style_blink ();
+ break;
+
+ /* Named foreground colors. */
+ case 30:
+ set_style_fg_color (style::named_color::BLACK);
+ break;
+ case 31:
+ set_style_fg_color (style::named_color::RED);
+ break;
+ case 32:
+ set_style_fg_color (style::named_color::GREEN);
+ break;
+ case 33:
+ set_style_fg_color (style::named_color::YELLOW);
+ break;
+ case 34:
+ set_style_fg_color (style::named_color::BLUE);
+ break;
+ case 35:
+ set_style_fg_color (style::named_color::MAGENTA);
+ break;
+ case 36:
+ set_style_fg_color (style::named_color::CYAN);
+ break;
+ case 37:
+ set_style_fg_color (style::named_color::WHITE);
+ break;
+
+ /* 8-bit and 24-bit color */
+ case 38:
+ case 48:
+ {
+ const bool fg = (param == 38);
+ iter++;
+ if (iter != params.end ())
+ switch (*(iter++))
+ {
+ default:
+ break;
+ case 5:
+ /* 8-bit color. */
+ if (iter != params.end ())
+ {
+ const uint8_t col = *(iter++);
+ if (fg)
+ set_style_fg_color (style::color (col));
+ else
+ set_style_bg_color (style::color (col));
+ }
+ continue;
+ case 2:
+ /* 24-bit color. */
+ if (iter != params.end ())
+ {
+ const uint8_t r = *(iter++);
+ if (iter != params.end ())
+ {
+ const uint8_t g = *(iter++);
+ if (iter != params.end ())
+ {
+ const uint8_t b = *(iter++);
+ if (fg)
+ set_style_fg_color (style::color (r,
+ g,
+ b));
+ else
+ set_style_bg_color (style::color (r,
+ g,
+ b));
+ }
+ }
+ }
+ continue;
+ }
+ continue;
+ }
+ break;
+
+ /* Named background colors. */
+ case 40:
+ set_style_bg_color (style::named_color::BLACK);
+ break;
+ case 41:
+ set_style_bg_color (style::named_color::RED);
+ break;
+ case 42:
+ set_style_bg_color (style::named_color::GREEN);
+ break;
+ case 43:
+ set_style_bg_color (style::named_color::YELLOW);
+ break;
+ case 44:
+ set_style_bg_color (style::named_color::BLUE);
+ break;
+ case 45:
+ set_style_bg_color (style::named_color::MAGENTA);
+ break;
+ case 46:
+ set_style_bg_color (style::named_color::CYAN);
+ break;
+ case 47:
+ set_style_bg_color (style::named_color::WHITE);
+ break;
+
+ /* Named foreground colors, bright. */
+ case 90:
+ set_style_fg_color (style::color (style::named_color::BLACK,
+ true));
+ break;
+ case 91:
+ set_style_fg_color (style::color (style::named_color::RED,
+ true));
+ break;
+ case 92:
+ set_style_fg_color (style::color (style::named_color::GREEN,
+ true));
+ break;
+ case 93:
+ set_style_fg_color (style::color (style::named_color::YELLOW,
+ true));
+ break;
+ case 94:
+ set_style_fg_color (style::color (style::named_color::BLUE,
+ true));
+ break;
+ case 95:
+ set_style_fg_color (style::color (style::named_color::MAGENTA,
+ true));
+ break;
+ case 96:
+ set_style_fg_color (style::color (style::named_color::CYAN,
+ true));
+ break;
+ case 97:
+ set_style_fg_color (style::color (style::named_color::WHITE,
+ true));
+ break;
+
+ /* Named foreground colors, bright. */
+ case 100:
+ set_style_bg_color (style::color (style::named_color::BLACK,
+ true));
+ break;
+ case 101:
+ set_style_bg_color (style::color (style::named_color::RED,
+ true));
+ break;
+ case 102:
+ set_style_bg_color (style::color (style::named_color::GREEN,
+ true));
+ break;
+ case 103:
+ set_style_bg_color (style::color (style::named_color::YELLOW,
+ true));
+ break;
+ case 104:
+ set_style_bg_color (style::color (style::named_color::BLUE,
+ true));
+ break;
+ case 105:
+ set_style_bg_color (style::color (style::named_color::MAGENTA,
+ true));
+ break;
+ case 106:
+ set_style_bg_color (style::color (style::named_color::CYAN,
+ true));
+ break;
+ case 107:
+ set_style_bg_color (style::color (style::named_color::WHITE,
+ true));
+ break;
+ }
+ ++iter;
+ }
+ }
+ break;
+ }
+ m_parameter_bytes.clear ();
+ m_intermediate_bytes.clear ();
+ m_state = state::START;
+ }
+
+ void on_final_osc_char ()
+ {
+ if (!m_osc_string.empty ())
+ {
+ switch (m_osc_string[0])
+ {
+ default:
+ break;
+ case '8':
+ /* Hyperlink support; see:
+ https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
+ We don't support params, so we expect either:
+ (a) "8;;URL" to begin a url (see pp_begin_url), or
+ (b) "8;;" to end a URL (see pp_end_url). */
+ if (m_osc_string.size () >= 3
+ && m_osc_string[1] == ';'
+ && m_osc_string[2] == ';')
+ {
+ set_style_url (m_osc_string.begin () + 3,
+ m_osc_string.end ());
+ }
+ break;
+ }
+ }
+ m_osc_string.clear ();
+ m_state = state::START;
+ }
+
+ std::vector<int> params_from_decimal () const
+ {
+ std::vector<int> result;
+
+ int curr_int = -1;
+ for (auto param_ch : m_parameter_bytes)
+ {
+ if (param_ch >= '0' && param_ch <= '9')
+ {
+ if (curr_int == -1)
+ curr_int = 0;
+ else
+ curr_int *= 10;
+ curr_int += param_ch - '0';
+ }
+ else
+ {
+ if (curr_int != -1)
+ {
+ result.push_back (curr_int);
+ curr_int = -1;
+ }
+ }
+ }
+ if (curr_int != -1)
+ result.push_back (curr_int);
+ return result;
+ }
+
+ void refresh_style_id ()
+ {
+ m_cur_style_id = m_sm.get_or_create_id (m_cur_style_obj);
+ }
+ void reset_style ()
+ {
+ m_cur_style_obj = style ();
+ refresh_style_id ();
+ }
+ void set_style_bold ()
+ {
+ m_cur_style_obj.m_bold = true;
+ refresh_style_id ();
+ }
+ void set_style_underscore ()
+ {
+ m_cur_style_obj.m_underscore = true;
+ refresh_style_id ();
+ }
+ void set_style_blink ()
+ {
+ m_cur_style_obj.m_blink = true;
+ refresh_style_id ();
+ }
+ void set_style_fg_color (style::color color)
+ {
+ m_cur_style_obj.m_fg_color = color;
+ refresh_style_id ();
+ }
+ void set_style_bg_color (style::color color)
+ {
+ m_cur_style_obj.m_bg_color = color;
+ refresh_style_id ();
+ }
+ void set_style_url (std::vector<cppchar_t>::iterator begin,
+ std::vector<cppchar_t>::iterator end)
+ {
+ // The empty string means "no URL"
+ m_cur_style_obj.m_url = std::vector<cppchar_t> (begin, end);
+ refresh_style_id ();
+ }
+
+ static bool parameter_byte_p (cppchar_t ch)
+ {
+ return ch >= 0x30 && ch <= 0x3F;
+ }
+
+ static bool intermediate_byte_p (cppchar_t ch)
+ {
+ return ch >= 0x20 && ch <= 0x2F;
+ }
+
+ static bool final_byte_p (cppchar_t ch)
+ {
+ return ch >= 0x40 && ch <= 0x7E;
+ }
+
+ style_manager &m_sm;
+ std::vector<styled_unichar> &m_out;
+
+ style m_cur_style_obj;
+ style::id_t m_cur_style_id;
+
+ /* Handling of control sequences. */
+ enum class state
+ {
+ START,
+
+ /* After ESC, expecting '['. */
+ AFTER_ESC,
+
+ /* Expecting zero or more parameter bytes, an
+ intermediate byte, or a final byte. */
+ CS_PARAMETER_BYTES,
+
+ /* Expecting zero or more intermediate bytes, or a final byte. */
+ CS_INTERMEDIATE_BYTES,
+
+ /* Within OSC. */
+ WITHIN_OSC
+
+ } m_state;
+ std::vector<char> m_parameter_bytes;
+ std::vector<char> m_intermediate_bytes;
+ std::vector<cppchar_t> m_osc_string;
+};
+
+} // anon namespace
+
+/* class text_art::styled_string. */
+
+/* Construct a styled_string from STR.
+ STR is assumed to be UTF-8 encoded and 0-terminated.
+
+ Parse SGR formatting chars from being in-band (within in the sequence
+ of chars) to being out-of-band, as style elements.
+ We only support parsing the subset of SGR chars that can be emitted
+ by pretty-print.cc */
+
+styled_string::styled_string (style_manager &sm, const char *str)
+: m_chars ()
+{
+ escape_code_parser parser (sm, m_chars);
+
+ /* We don't actually want the display widths here, but
+ it's an easy way to decode UTF-8. */
+ cpp_char_column_policy policy (8, cpp_wcwidth);
+ cpp_display_width_computation dw (str, strlen (str), policy);
+ while (!dw.done ())
+ {
+ cpp_decoded_char decoded_char;
+ dw.process_next_codepoint (&decoded_char);
+
+ if (!decoded_char.m_valid_ch)
+ /* Skip bytes that aren't valid UTF-8. */
+ continue;
+
+ /* Decode SGR formatting. */
+ cppchar_t ch = decoded_char.m_ch;
+ parser.on_char (ch);
+ }
+}
+
+styled_string::styled_string (cppchar_t cppchar, bool emoji)
+{
+ m_chars.push_back (styled_unichar (cppchar, emoji, style::id_plain));
+}
+
+styled_string
+styled_string::from_fmt_va (style_manager &sm,
+ printer_fn format_decoder,
+ const char *fmt,
+ va_list *args)
+{
+ text_info text;
+ text.err_no = errno;
+ text.args_ptr = args;
+ text.format_spec = fmt;
+ pretty_printer pp;
+ pp_show_color (&pp) = true;
+ pp.url_format = URL_FORMAT_DEFAULT;
+ pp_format_decoder (&pp) = format_decoder;
+ pp_format (&pp, &text);
+ pp_output_formatted_text (&pp);
+ styled_string result (sm, pp_formatted_text (&pp));
+ return result;
+}
+
+styled_string
+styled_string::from_fmt (style_manager &sm,
+ printer_fn format_decoder,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start (ap, fmt);
+ styled_string result = from_fmt_va (sm, format_decoder, fmt, &ap);
+ va_end (ap);
+ return result;
+}
+
+int
+styled_string::calc_canvas_width () const
+{
+ int result = 0;
+ for (auto ch : m_chars)
+ result += ch.get_canvas_width ();
+ return result;
+}
+
+void
+styled_string::append (const styled_string &suffix)
+{
+ m_chars.insert<std::vector<styled_unichar>::const_iterator> (m_chars.end (),
+ suffix.begin (),
+ suffix.end ());
+}
+
+void
+styled_string::set_url (style_manager &sm, const char *url)
+{
+ for (auto& ch : m_chars)
+ {
+ const style &existing_style = sm.get_style (ch.get_style_id ());
+ style with_url (existing_style);
+ with_url.set_style_url (url);
+ ch.m_style_id = sm.get_or_create_id (with_url);
+ }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_combining_chars ()
+{
+ /* This really ought to be in libcpp, but we don't have
+ selftests there. */
+ ASSERT_FALSE (cpp_is_combining_char (0));
+ ASSERT_FALSE (cpp_is_combining_char ('a'));
+
+ /* COMBINING BREVE (U+0306). */
+ ASSERT_TRUE (cpp_is_combining_char (0x0306));
+
+ /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57. */
+ ASSERT_FALSE (cpp_is_combining_char (0x5B57));
+
+ /* U+FE0F VARIATION SELECTOR-16. */
+ ASSERT_FALSE (cpp_is_combining_char (0xFE0F));
+}
+
+static void
+test_empty ()
+{
+ style_manager sm;
+ styled_string s (sm, "");
+ ASSERT_EQ (s.size (), 0);
+ ASSERT_EQ (s.calc_canvas_width (), 0);
+}
+
+/* Test of a pure ASCII string with no escape codes. */
+
+static void
+test_simple ()
+{
+ const char *c_str = "hello world!";
+ style_manager sm;
+ styled_string s (sm, c_str);
+ ASSERT_EQ (s.size (), strlen (c_str));
+ ASSERT_EQ (s.calc_canvas_width (), (int)strlen (c_str));
+ for (size_t i = 0; i < strlen (c_str); i++)
+ {
+ ASSERT_EQ (s[i].get_code (), (cppchar_t)c_str[i]);
+ ASSERT_EQ (s[i].get_style_id (), 0);
+ }
+}
+
+/* Test of decoding UTF-8. */
+
+static void
+test_pi_from_utf8 ()
+{
+ /* U+03C0 "GREEK SMALL LETTER PI". */
+ const char * const pi_utf8 = "\xCF\x80";
+
+ style_manager sm;
+ styled_string s (sm, pi_utf8);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 1);
+ ASSERT_EQ (s[0].get_code (), 0x03c0);
+ ASSERT_EQ (s[0].emoji_variant_p (), false);
+ ASSERT_EQ (s[0].double_width_p (), false);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+/* Test of double-width character. */
+
+static void
+test_emoji_from_utf8 ()
+{
+ /* U+1F642 "SLIGHTLY SMILING FACE". */
+ const char * const emoji_utf8 = "\xF0\x9F\x99\x82";
+
+ style_manager sm;
+ styled_string s (sm, emoji_utf8);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 2);
+ ASSERT_EQ (s[0].get_code (), 0x1f642);
+ ASSERT_EQ (s[0].double_width_p (), true);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+/* Test of handling U+FE0F VARIATION SELECTOR-16 to select the emoji
+ variation for the previous character. */
+
+static void
+test_emoji_variant_from_utf8 ()
+{
+ const char * const emoji_utf8
+ = (/* U+26A0 WARNING SIGN. */
+ "\xE2\x9A\xA0"
+ /* U+FE0F VARIATION SELECTOR-16 (emoji variation selector). */
+ "\xEF\xB8\x8F");
+
+ style_manager sm;
+ styled_string s (sm, emoji_utf8);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 1);
+ ASSERT_EQ (s[0].get_code (), 0x26a0);
+ ASSERT_EQ (s[0].emoji_variant_p (), true);
+ ASSERT_EQ (s[0].double_width_p (), false);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+static void
+test_emoji_from_codepoint ()
+{
+ styled_string s ((cppchar_t)0x1f642);
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s.calc_canvas_width (), 2);
+ ASSERT_EQ (s[0].get_code (), 0x1f642);
+ ASSERT_EQ (s[0].double_width_p (), true);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+static void
+test_from_mixed_width_utf8 ()
+{
+ /* This UTF-8 string literal is of the form
+ before mojibake after
+ where the Japanese word "mojibake" is written as the following
+ four unicode code points:
+ U+6587 CJK UNIFIED IDEOGRAPH-6587
+ U+5B57 CJK UNIFIED IDEOGRAPH-5B57
+ U+5316 CJK UNIFIED IDEOGRAPH-5316
+ U+3051 HIRAGANA LETTER KE.
+ Each of these is 3 bytes wide when encoded in UTF-8, whereas the
+ "before" and "after" are 1 byte per unicode character. */
+ const char * const mixed_width_utf8
+ = ("before "
+
+ /* U+6587 CJK UNIFIED IDEOGRAPH-6587
+ UTF-8: 0xE6 0x96 0x87
+ C octal escaped UTF-8: \346\226\207. */
+ "\346\226\207"
+
+ /* U+5B57 CJK UNIFIED IDEOGRAPH-5B57
+ UTF-8: 0xE5 0xAD 0x97
+ C octal escaped UTF-8: \345\255\227. */
+ "\345\255\227"
+
+ /* U+5316 CJK UNIFIED IDEOGRAPH-5316
+ UTF-8: 0xE5 0x8C 0x96
+ C octal escaped UTF-8: \345\214\226. */
+ "\345\214\226"
+
+ /* U+3051 HIRAGANA LETTER KE
+ UTF-8: 0xE3 0x81 0x91
+ C octal escaped UTF-8: \343\201\221. */
+ "\343\201\221"
+
+ " after");
+
+ style_manager sm;
+ styled_string s (sm, mixed_width_utf8);
+ ASSERT_EQ (s.size (), 6 + 1 + 4 + 1 + 5);
+ ASSERT_EQ (sm.get_num_styles (), 1);
+
+ // We expect the Japanese characters to be double width.
+ ASSERT_EQ (s.calc_canvas_width (), 6 + 1 + (2 * 4) + 1 + 5);
+
+ ASSERT_EQ (s[0].get_code (), 'b');
+ ASSERT_EQ (s[0].double_width_p (), false);
+ ASSERT_EQ (s[1].get_code (), 'e');
+ ASSERT_EQ (s[2].get_code (), 'f');
+ ASSERT_EQ (s[3].get_code (), 'o');
+ ASSERT_EQ (s[4].get_code (), 'r');
+ ASSERT_EQ (s[5].get_code (), 'e');
+ ASSERT_EQ (s[6].get_code (), ' ');
+ ASSERT_EQ (s[7].get_code (), 0x6587);
+ ASSERT_EQ (s[7].double_width_p (), true);
+ ASSERT_EQ (s[8].get_code (), 0x5B57);
+ ASSERT_EQ (s[9].get_code (), 0x5316);
+ ASSERT_EQ (s[10].get_code (), 0x3051);
+ ASSERT_EQ (s[11].get_code (), ' ');
+ ASSERT_EQ (s[12].get_code (), 'a');
+ ASSERT_EQ (s[13].get_code (), 'f');
+ ASSERT_EQ (s[14].get_code (), 't');
+ ASSERT_EQ (s[15].get_code (), 'e');
+ ASSERT_EQ (s[16].get_code (), 'r');
+
+ ASSERT_EQ (s[0].get_style_id (), 0);
+}
+
+static void
+assert_style_urleq (const location &loc,
+ const style &s,
+ const char *expected_str)
+{
+ ASSERT_EQ_AT (loc, s.m_url.size (), strlen (expected_str));
+ for (size_t i = 0; i < s.m_url.size (); i++)
+ ASSERT_EQ_AT (loc, s.m_url[i], (cppchar_t)expected_str[i]);
+}
+
+#define ASSERT_STYLE_URLEQ(STYLE, EXPECTED_STR) \
+ assert_style_urleq ((SELFTEST_LOCATION), (STYLE), (EXPECTED_STR))
+
+static void
+test_url ()
+{
+ // URL_FORMAT_ST
+ {
+ style_manager sm;
+ styled_string s
+ (sm, "\33]8;;http://example.com\33\\This is a link\33]8;;\33\\");
+ const char *expected = "This is a link";
+ ASSERT_EQ (s.size (), strlen (expected));
+ ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ for (size_t i = 0; i < strlen (expected); i++)
+ {
+ ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
+ ASSERT_EQ (s[i].get_style_id (), 1);
+ }
+ ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
+ }
+
+ // URL_FORMAT_BEL
+ {
+ style_manager sm;
+ styled_string s
+ (sm, "\33]8;;http://example.com\aThis is a link\33]8;;\a");
+ const char *expected = "This is a link";
+ ASSERT_EQ (s.size (), strlen (expected));
+ ASSERT_EQ (s.calc_canvas_width (), (int)strlen (expected));
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ for (size_t i = 0; i < strlen (expected); i++)
+ {
+ ASSERT_EQ (s[i].get_code (), (cppchar_t)expected[i]);
+ ASSERT_EQ (s[i].get_style_id (), 1);
+ }
+ ASSERT_STYLE_URLEQ (sm.get_style (1), "http://example.com");
+ }
+}
+
+static void
+test_from_fmt ()
+{
+ style_manager sm;
+ styled_string s (styled_string::from_fmt (sm, NULL, "%%i: %i", 42));
+ ASSERT_EQ (s[0].get_code (), '%');
+ ASSERT_EQ (s[1].get_code (), 'i');
+ ASSERT_EQ (s[2].get_code (), ':');
+ ASSERT_EQ (s[3].get_code (), ' ');
+ ASSERT_EQ (s[4].get_code (), '4');
+ ASSERT_EQ (s[5].get_code (), '2');
+ ASSERT_EQ (s.size (), 6);
+ ASSERT_EQ (s.calc_canvas_width (), 6);
+}
+
+static void
+test_from_fmt_qs ()
+{
+ auto_fix_quotes fix_quotes;
+ open_quote = "\xe2\x80\x98";
+ close_quote = "\xe2\x80\x99";
+
+ style_manager sm;
+ styled_string s (styled_string::from_fmt (sm, NULL, "%qs", "msg"));
+ ASSERT_EQ (sm.get_num_styles (), 2);
+ ASSERT_EQ (s[0].get_code (), 0x2018);
+ ASSERT_EQ (s[0].get_style_id (), 0);
+ ASSERT_EQ (s[1].get_code (), 'm');
+ ASSERT_EQ (s[1].get_style_id (), 1);
+ ASSERT_EQ (s[2].get_code (), 's');
+ ASSERT_EQ (s[2].get_style_id (), 1);
+ ASSERT_EQ (s[3].get_code (), 'g');
+ ASSERT_EQ (s[3].get_style_id (), 1);
+ ASSERT_EQ (s[4].get_code (), 0x2019);
+ ASSERT_EQ (s[4].get_style_id (), 0);
+ ASSERT_EQ (s.size (), 5);
+}
+
+// Test of parsing SGR codes.
+
+static void
+test_from_str_with_bold ()
+{
+ style_manager sm;
+ /* This is the result of pp_printf (pp, "%qs", "foo")
+ with auto_fix_quotes. */
+ styled_string s (sm, "`\33[01m\33[Kfoo\33[m\33[K'");
+ ASSERT_EQ (s[0].get_code (), '`');
+ ASSERT_EQ (s[0].get_style_id (), 0);
+ ASSERT_EQ (s[1].get_code (), 'f');
+ ASSERT_EQ (s[1].get_style_id (), 1);
+ ASSERT_EQ (s[2].get_code (), 'o');
+ ASSERT_EQ (s[2].get_style_id (), 1);
+ ASSERT_EQ (s[3].get_code (), 'o');
+ ASSERT_EQ (s[3].get_style_id (), 1);
+ ASSERT_EQ (s[4].get_code (), '\'');
+ ASSERT_EQ (s[4].get_style_id (), 0);
+ ASSERT_EQ (s.size (), 5);
+ ASSERT_TRUE (sm.get_style (1).m_bold);
+}
+
+static void
+test_from_str_with_underscore ()
+{
+ style_manager sm;
+ styled_string s (sm, "\33[04m\33[KA");
+ ASSERT_EQ (s[0].get_code (), 'A');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_TRUE (sm.get_style (1).m_underscore);
+}
+
+static void
+test_from_str_with_blink ()
+{
+ style_manager sm;
+ styled_string s (sm, "\33[05m\33[KA");
+ ASSERT_EQ (s[0].get_code (), 'A');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_TRUE (sm.get_style (1).m_blink);
+}
+
+// Test of parsing SGR codes.
+
+static void
+test_from_str_with_color ()
+{
+ style_manager sm;
+
+ styled_string s (sm,
+ ("0"
+ SGR_SEQ (COLOR_FG_RED)
+ "R"
+ SGR_RESET
+ "2"
+ SGR_SEQ (COLOR_FG_GREEN)
+ "G"
+ SGR_RESET
+ "4"));
+ ASSERT_EQ (s.size (), 5);
+ ASSERT_EQ (sm.get_num_styles (), 3);
+ ASSERT_EQ (s[0].get_code (), '0');
+ ASSERT_EQ (s[0].get_style_id (), 0);
+ ASSERT_EQ (s[1].get_code (), 'R');
+ ASSERT_EQ (s[1].get_style_id (), 1);
+ ASSERT_EQ (s[2].get_code (), '2');
+ ASSERT_EQ (s[2].get_style_id (), 0);
+ ASSERT_EQ (s[3].get_code (), 'G');
+ ASSERT_EQ (s[3].get_style_id (), 2);
+ ASSERT_EQ (s[4].get_code (), '4');
+ ASSERT_EQ (s[4].get_style_id (), 0);
+ ASSERT_EQ (sm.get_style (1).m_fg_color, style::named_color::RED);
+ ASSERT_EQ (sm.get_style (2).m_fg_color, style::named_color::GREEN);
+}
+
+static void
+test_from_str_with_named_color ()
+{
+ style_manager sm;
+ styled_string s (sm,
+ ("F"
+ SGR_SEQ (COLOR_FG_BLACK) "F"
+ SGR_SEQ (COLOR_FG_RED) "F"
+ SGR_SEQ (COLOR_FG_GREEN) "F"
+ SGR_SEQ (COLOR_FG_YELLOW) "F"
+ SGR_SEQ (COLOR_FG_BLUE) "F"
+ SGR_SEQ (COLOR_FG_MAGENTA) "F"
+ SGR_SEQ (COLOR_FG_CYAN) "F"
+ SGR_SEQ (COLOR_FG_WHITE) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_BLACK) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_RED) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_GREEN) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_YELLOW) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_BLUE) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_MAGENTA) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_CYAN) "F"
+ SGR_SEQ (COLOR_FG_BRIGHT_WHITE) "F"
+ SGR_SEQ (COLOR_BG_BLACK) "B"
+ SGR_SEQ (COLOR_BG_RED) "B"
+ SGR_SEQ (COLOR_BG_GREEN) "B"
+ SGR_SEQ (COLOR_BG_YELLOW) "B"
+ SGR_SEQ (COLOR_BG_BLUE) "B"
+ SGR_SEQ (COLOR_BG_MAGENTA) "B"
+ SGR_SEQ (COLOR_BG_CYAN) "B"
+ SGR_SEQ (COLOR_BG_WHITE) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_BLACK) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_RED) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_GREEN) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_YELLOW) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_BLUE) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_MAGENTA) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_CYAN) "B"
+ SGR_SEQ (COLOR_BG_BRIGHT_WHITE) "B"));
+ ASSERT_EQ (s.size (), 33);
+ for (size_t i = 0; i < s.size (); i++)
+ ASSERT_EQ (s[i].get_style_id (), i);
+ for (size_t i = 0; i < 17; i++)
+ ASSERT_EQ (s[i].get_code (), 'F');
+ for (size_t i = 17; i < 33; i++)
+ ASSERT_EQ (s[i].get_code (), 'B');
+}
+
+static void
+test_from_str_with_8_bit_color ()
+{
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("[38;5;232m[KF"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'F');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (232));
+ }
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("[48;5;231m[KB"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'B');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (231));
+ }
+}
+
+static void
+test_from_str_with_24_bit_color ()
+{
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("[38;2;243;250;242m[KF"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'F');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_fg_color, style::color (243, 250, 242));
+ }
+ {
+ style_manager sm;
+ styled_string s (sm,
+ ("[48;2;253;247;231m[KB"));
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 'B');
+ ASSERT_EQ (s[0].get_style_id (), 1);
+ ASSERT_EQ (sm.get_style (1).m_bg_color, style::color (253, 247, 231));
+ }
+}
+
+static void
+test_from_str_combining_characters ()
+{
+ style_manager sm;
+ styled_string s (sm,
+ /* CYRILLIC CAPITAL LETTER U (U+0423). */
+ "\xD0\xA3"
+ /* COMBINING BREVE (U+0306). */
+ "\xCC\x86");
+ ASSERT_EQ (s.size (), 1);
+ ASSERT_EQ (s[0].get_code (), 0x423);
+ ASSERT_EQ (s[0].get_combining_chars ().size (), 1);
+ ASSERT_EQ (s[0].get_combining_chars ()[0], 0x306);
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_styled_string_cc_tests ()
+{
+ test_combining_chars ();
+ test_empty ();
+ test_simple ();
+ test_pi_from_utf8 ();
+ test_emoji_from_utf8 ();
+ test_emoji_variant_from_utf8 ();
+ test_emoji_from_codepoint ();
+ test_from_mixed_width_utf8 ();
+ test_url ();
+ test_from_fmt ();
+ test_from_fmt_qs ();
+ test_from_str_with_bold ();
+ test_from_str_with_underscore ();
+ test_from_str_with_blink ();
+ test_from_str_with_color ();
+ test_from_str_with_named_color ();
+ test_from_str_with_8_bit_color ();
+ test_from_str_with_24_bit_color ();
+ test_from_str_combining_characters ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,1272 @@
+/* Support for tabular/grid-based content.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "make-unique.h"
+#include "pretty-print.h"
+#include "diagnostic.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/table.h"
+
+using namespace text_art;
+
+/* class text_art::table_cell_content. */
+
+table_cell_content::table_cell_content (styled_string &&s)
+: m_str (std::move (s)),
+ /* We assume here that the content occupies a single canvas row. */
+ m_size (m_str.calc_canvas_width (), 1)
+{
+}
+
+void
+table_cell_content::paint_to_canvas (canvas &canvas,
+ canvas::coord_t top_left) const
+{
+ canvas.paint_text (top_left, m_str);
+}
+
+/* struct text_art::table_dimension_sizes. */
+
+table_dimension_sizes::table_dimension_sizes (unsigned num)
+: m_requirements (num, 0)
+{
+}
+
+/* class text_art::table::cell_placement. */
+
+void
+table::cell_placement::paint_cell_contents_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg) const
+{
+ const canvas::size_t req_canvas_size = get_min_canvas_size ();
+ const canvas::size_t alloc_canvas_size = tg.get_canvas_size (m_rect);
+ gcc_assert (req_canvas_size.w <= alloc_canvas_size.w);
+ gcc_assert (req_canvas_size.h <= alloc_canvas_size.h);
+ const int x_padding = alloc_canvas_size.w - req_canvas_size.w;
+ const int y_padding = alloc_canvas_size.h - req_canvas_size.h;
+ const table::coord_t table_top_left = m_rect.m_top_left;
+ const canvas::coord_t canvas_top_left = tg.table_to_canvas (table_top_left);
+
+ gcc_assert (x_padding >= 0);
+ int x_align_offset;
+ switch (m_x_align)
+ {
+ default:
+ gcc_unreachable ();
+ case x_align::LEFT:
+ x_align_offset = 0;
+ break;
+ case x_align::CENTER:
+ x_align_offset = x_padding / 2;
+ break;
+ case x_align::RIGHT:
+ x_align_offset = x_padding;
+ break;
+ }
+
+ gcc_assert (y_padding >= 0);
+ int y_align_offset;
+ switch (m_y_align)
+ {
+ default:
+ gcc_unreachable ();
+ case y_align::TOP:
+ y_align_offset = 0;
+ break;
+ case y_align::CENTER:
+ y_align_offset = y_padding / 2;
+ break;
+ case y_align::BOTTOM:
+ y_align_offset = y_padding;
+ break;
+ }
+ const canvas::coord_t content_rel_coord
+ (canvas_top_left.x + 1 + x_align_offset,
+ canvas_top_left.y + 1 + y_align_offset);
+ m_content.paint_to_canvas (canvas, offset + content_rel_coord);
+}
+
+/* class text_art::table. */
+
+
+table::table (size_t size)
+: m_size (size),
+ m_placements (),
+ m_occupancy (size)
+{
+ m_occupancy.fill (-1);
+}
+
+void
+table::set_cell (coord_t coord,
+ table_cell_content &&content,
+ enum x_align x_align,
+ enum y_align y_align)
+{
+ set_cell_span (rect_t (coord, table::size_t (1, 1)),
+ std::move (content), x_align, y_align);
+}
+
+void
+table::set_cell_span (rect_t span,
+ table_cell_content &&content,
+ enum x_align x_align,
+ enum y_align y_align)
+{
+ gcc_assert (span.m_size.w > 0);
+ gcc_assert (span.m_size.h > 0);
+ int placement_idx = m_placements.size ();
+ m_placements.emplace_back (cell_placement (span, std::move (content),
+ x_align, y_align));
+ for (int y = span.get_min_y (); y < span.get_next_y (); y++)
+ for (int x = span.get_min_x (); x < span.get_next_x (); x++)
+ {
+ gcc_assert (m_occupancy.get (coord_t (x, y)) == -1);
+ m_occupancy.set (coord_t (x, y), placement_idx);
+ }
+}
+
+canvas
+table::to_canvas (const theme &theme, const style_manager &sm) const
+{
+ table_dimension_sizes col_widths (m_size.w);
+ table_dimension_sizes row_heights (m_size.h);
+ table_cell_sizes cell_sizes (col_widths, row_heights);
+ cell_sizes.pass_1 (*this);
+ cell_sizes.pass_2 (*this);
+ table_geometry tg (*this, cell_sizes);
+ canvas canvas (tg.get_canvas_size (), sm);
+ paint_to_canvas (canvas, canvas::coord_t (0, 0), tg, theme);
+ return canvas;
+}
+
+void
+table::paint_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg,
+ const theme &theme) const
+{
+ canvas.fill (canvas::rect_t (offset, tg.get_canvas_size ()),
+ styled_unichar (' '));
+ paint_cell_borders_to_canvas (canvas, offset, tg, theme);
+ paint_cell_contents_to_canvas (canvas, offset, tg);
+}
+
+/* Print this table to stderr. */
+
+DEBUG_FUNCTION void
+table::debug () const
+{
+ /* Use a temporary style manager.
+ Styles in the table will be meaningless, so
+ print the canvas with styling disabled. */
+ style_manager sm;
+ canvas canvas (to_canvas (unicode_theme (), sm));
+ canvas.debug (false);
+}
+
+const table::cell_placement *
+table::get_placement_at (coord_t coord) const
+{
+ const int placement_idx = m_occupancy.get (coord);
+ if (placement_idx == -1)
+ return nullptr;
+ return &m_placements[placement_idx];
+}
+
+int
+table::get_occupancy_safe (coord_t coord) const
+{
+ if (coord.x < 0)
+ return -1;
+ if (coord.x >= m_size.w)
+ return -1;
+ if (coord.y < 0)
+ return -1;
+ if (coord.y >= m_size.h)
+ return -1;
+ return m_occupancy.get (coord);
+}
+
+/* Determine if the "?" edges need borders for table cell D
+ in the following, for the directions relative to "X", based
+ on whether each of table cell boundaries AB, CD, AC, and BD
+ are boundaries between cell spans:
+
+ # up?
+ # +-----+-----+
+ # | |
+ # | ? |
+ # | A ? B |
+ # | ? |
+ # | |
+ # left?+ ??? X ??? + right?
+ # | |
+ # | ? |
+ # | C ? D |
+ # | ? |
+ # | |
+ # +-----+-----+
+ # down?
+*/
+
+directions
+table::get_connections (int table_x, int table_y) const
+{
+ int cell_a = get_occupancy_safe (coord_t (table_x - 1, table_y - 1));
+ int cell_b = get_occupancy_safe (coord_t (table_x, table_y - 1));
+ int cell_c = get_occupancy_safe (coord_t (table_x - 1, table_y));
+ int cell_d = get_occupancy_safe (coord_t (table_x, table_y));
+ const bool up = (cell_a != cell_b);
+ const bool down = (cell_c != cell_d);
+ const bool left = (cell_a != cell_c);
+ const bool right = (cell_b != cell_d);
+ return directions (up, down, left, right);
+}
+
+/* Paint the grid lines.
+
+ Consider painting
+ - a grid of cells,
+ - plus a right-hand border
+ - and a bottom border
+
+ Then we need to paint to the canvas like this:
+
+ # PER-TABLE-COLUMN R BORDER
+ # +-------------------+ +-----+
+ #
+ # TABLE CELL WIDTH (in canvas units)
+ # +-------------+
+ # . . . . . . .
+ # ...+-----+-----+.+-----+...+-----+ +
+ # | U | |.| | | U | |
+ # | U | |.| | | U | |
+ # |LL+RR|RRRRR|.|RRRRR| |LL+ | |
+ # | D | |.| | | D | |
+ # | D | |.| | | D | |
+ # ...+-----+-----+.+-----+...+-----+ |
+ # ..................... ...... +-- PER-TABLE-ROW
+ # ...+-----+-----+.+-----+...+-----+ | +
+ # | D | |.| | | D | | |
+ # | D | |.| | | D | | |
+ # | D | |.| | | D | | +---- TABLE CELL HEIGHT (in canvas units)
+ # | D | |.| | | D | | |
+ # | D | |.| | | D | | |
+ # ...+-----+-----+.+-----+...+-----+ + +
+ # . . . . . .
+ # ...+-----+-----+.+-----+...+-----+ +
+ # | D | |.| | | U | |
+ # | D | |.| | | U | |
+ # |LL+RR|RRRRR|.|RRRRR| |LL+ | | BOTTOM BORDER
+ # | | |.| | | | |
+ # | | |.| | | | |
+ # ...+-----+-----+.+-----+...+-----+ +
+
+ where each:
+
+ # +-----+
+ # | |
+ # | |
+ # | |
+ # | |
+ # | |
+ # +-----+
+
+ is a canvas cell, and the U, L, R, D express the connections
+ that are present with neighboring table cells. These affect
+ the kinds of borders that we draw for a particular table cell. */
+
+void
+table::paint_cell_borders_to_canvas (canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg,
+ const theme &theme) const
+{
+ /* The per-table-cell left and top borders are either drawn or not,
+ but if they are, they aren't affected by per-table-cell connections. */
+ const canvas::cell_t left_border
+ = theme.get_line_art (directions (true, /* up */
+ true, /* down */
+ false, /* left */
+ false /* right */));
+ const canvas::cell_t top_border
+ = theme.get_line_art (directions (false, /* up */
+ false, /* down */
+ true, /* left */
+ true)); /* right */
+ for (int table_y = 0; table_y < m_size.h; table_y++)
+ {
+ const int canvas_y = tg.table_y_to_canvas_y (table_y);
+ for (int table_x = 0; table_x < m_size.w; table_x++)
+ {
+ canvas::coord_t canvas_top_left
+ = tg.table_to_canvas(table::coord_t (table_x, table_y));
+
+ const directions c (get_connections (table_x, table_y));
+
+ /* Paint top-left corner of border, if any. */
+ canvas.paint (offset + canvas_top_left,
+ theme.get_line_art (c));
+
+ /* Paint remainder of left border of cell, if any.
+ We assume here that the content occupies a single canvas row. */
+ if (c.m_down)
+ canvas.paint (offset + canvas::coord_t (canvas_top_left.x,
+ canvas_y + 1),
+ left_border);
+
+ /* Paint remainder of top border of cell, if any. */
+ if (c.m_right)
+ {
+ const int col_width = tg.get_col_width (table_x);
+ for (int x_offset = 0; x_offset < col_width; x_offset++)
+ {
+ const int canvas_x = canvas_top_left.x + 1 + x_offset;
+ canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
+ top_border);
+ }
+ }
+ }
+
+ /* Paint right-hand border of row. */
+ const int table_x = m_size.w;
+ const int canvas_x = tg.table_x_to_canvas_x (table_x);
+ const directions c (get_connections (m_size.w, table_y));
+ canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
+ theme.get_line_art (directions (c.m_up,
+ c.m_down,
+ c.m_left,
+ false))); /* right */
+ /* We assume here that the content occupies a single canvas row. */
+ canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y + 1),
+ theme.get_line_art (directions (c.m_down, /* up */
+ c.m_down, /* down */
+ false, /* left */
+ false))); /* right */
+ }
+
+ /* Draw bottom border of table. */
+ {
+ const int canvas_y = tg.get_canvas_size ().h - 1;
+ for (int table_x = 0; table_x < m_size.w; table_x++)
+ {
+ const directions c (get_connections (table_x, m_size.h));
+ const int left_canvas_x = tg.table_x_to_canvas_x (table_x);
+ canvas.paint (offset + canvas::coord_t (left_canvas_x, canvas_y),
+ theme.get_line_art (directions (c.m_up,
+ false, /* down */
+ c.m_left,
+ c.m_right)));
+ const int col_width = tg.get_col_width (table_x);
+ for (int x_offset = 0; x_offset < col_width; x_offset++)
+ {
+ const int canvas_x = left_canvas_x + 1 + x_offset;
+ canvas.paint(offset + canvas::coord_t (canvas_x, canvas_y),
+ theme.get_line_art (directions (false, // up
+ false, // down
+ c.m_right, // left
+ c.m_right))); // right
+ }
+ }
+
+ /* Bottom-right corner of table. */
+ const int table_x = m_size.w;
+ const int canvas_x = tg.table_x_to_canvas_x (table_x);
+ const directions c (get_connections (m_size.w, m_size.h));
+ canvas.paint (offset + canvas::coord_t (canvas_x, canvas_y),
+ theme.get_line_art (directions (c.m_up, // up
+ false, // down
+ c.m_left, // left
+ false))); // right
+ }
+}
+
+void
+table::paint_cell_contents_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg) const
+{
+ for (auto &placement : m_placements)
+ placement.paint_cell_contents_to_canvas (canvas, offset, tg);
+}
+
+/* class table_cell_sizes. */
+
+/* Consider 1x1 cells. */
+
+void
+table_cell_sizes::pass_1 (const table &table)
+{
+ for (auto &placement : table.m_placements)
+ if (placement.one_by_one_p ())
+ {
+ canvas::size_t canvas_size (placement.get_min_canvas_size ());
+ table::coord_t table_coord (placement.m_rect.m_top_left);
+ m_col_widths.require (table_coord.x, canvas_size.w);
+ m_row_heights.require (table_coord.y, canvas_size.h);
+ }
+}
+
+/* Consider cells that span more than one row or column. */
+
+void
+table_cell_sizes::pass_2 (const table &table)
+{
+ for (auto &placement : table.m_placements)
+ if (!placement.one_by_one_p ())
+ {
+ const canvas::size_t req_canvas_size (placement.get_min_canvas_size ());
+ const canvas::size_t current_canvas_size
+ = get_canvas_size (placement.m_rect);
+ /* Grow columns as necessary. */
+ if (req_canvas_size.w > current_canvas_size.w)
+ {
+ /* Spread the deficit amongst the columns. */
+ int deficit = req_canvas_size.w - current_canvas_size.w;
+ const int per_col = deficit / placement.m_rect.m_size.w;
+ for (int table_x = placement.m_rect.get_min_x ();
+ table_x < placement.m_rect.get_next_x ();
+ table_x++)
+ {
+ m_col_widths.m_requirements[table_x] += per_col;
+ deficit -= per_col;
+ }
+ /* Make sure we allocate all of the deficit. */
+ if (deficit > 0)
+ {
+ const int table_x = placement.m_rect.get_max_x ();
+ m_col_widths.m_requirements[table_x] += deficit;
+ }
+ }
+ /* Grow rows as necessary. */
+ if (req_canvas_size.h > current_canvas_size.h)
+ {
+ /* Spread the deficit amongst the rows. */
+ int deficit = req_canvas_size.h - current_canvas_size.h;
+ const int per_row = deficit / placement.m_rect.m_size.h;
+ for (int table_y = placement.m_rect.get_min_y ();
+ table_y < placement.m_rect.get_next_y ();
+ table_y++)
+ {
+ m_row_heights.m_requirements[table_y] += per_row;
+ deficit -= per_row;
+ }
+ /* Make sure we allocate all of the deficit. */
+ if (deficit > 0)
+ {
+ const int table_y = placement.m_rect.get_max_y ();
+ m_row_heights.m_requirements[table_y] += deficit;
+ }
+ }
+ }
+}
+
+canvas::size_t
+table_cell_sizes::get_canvas_size (const table::rect_t &rect) const
+{
+ canvas::size_t result (0, 0);
+ for (int table_x = rect.get_min_x ();
+ table_x < rect.get_next_x ();
+ table_x ++)
+ result.w += m_col_widths.m_requirements[table_x];
+ for (int table_y = rect.get_min_y ();
+ table_y < rect.get_next_y ();
+ table_y ++)
+ result.h += m_row_heights.m_requirements[table_y];
+ /* Allow space for the borders. */
+ result.w += rect.m_size.w - 1;
+ result.h += rect.m_size.h - 1;
+ return result;
+}
+
+/* class text_art::table_geometry. */
+
+table_geometry::table_geometry (const table &table, table_cell_sizes &cell_sizes)
+: m_table (table),
+ m_cell_sizes (cell_sizes),
+ m_canvas_size (canvas::size_t (0, 0)),
+ m_col_start_x (table.get_size ().w),
+ m_row_start_y (table.get_size ().h)
+{
+ recalc_coords ();
+}
+
+void
+table_geometry::recalc_coords ()
+{
+ /* Start canvas column of table cell, including leading border. */
+ m_col_start_x.clear ();
+ int iter_canvas_x = 0;
+ for (auto w : m_cell_sizes.m_col_widths.m_requirements)
+ {
+ m_col_start_x.push_back (iter_canvas_x);
+ iter_canvas_x += w + 1;
+ }
+
+ /* Start canvas row of table cell, including leading border. */
+ m_row_start_y.clear ();
+ int iter_canvas_y = 0;
+ for (auto h : m_cell_sizes.m_row_heights.m_requirements)
+ {
+ m_row_start_y.push_back (iter_canvas_y);
+ iter_canvas_y += h + 1;
+ }
+
+ m_canvas_size = canvas::size_t (iter_canvas_x + 1,
+ iter_canvas_y + 1);
+}
+
+/* Get the TL corner of the table cell at TABLE_COORD
+ in canvas coords (including the border). */
+
+canvas::coord_t
+table_geometry::table_to_canvas (table::coord_t table_coord) const
+{
+ return canvas::coord_t (table_x_to_canvas_x (table_coord.x),
+ table_y_to_canvas_y (table_coord.y));
+}
+
+/* Get the left border of the table cell at column TABLE_X
+ in canvas coords (including the border). */
+
+int
+table_geometry::table_x_to_canvas_x (int table_x) const
+{
+ /* Allow one beyond the end, for the right-hand border of the table. */
+ if (table_x == m_col_start_x.size ())
+ return m_canvas_size.w - 1;
+ return m_col_start_x[table_x];
+}
+
+/* Get the top border of the table cell at column TABLE_Y
+ in canvas coords (including the border). */
+
+int
+table_geometry::table_y_to_canvas_y (int table_y) const
+{
+ /* Allow one beyond the end, for the right-hand border of the table. */
+ if (table_y == m_row_start_y.size ())
+ return m_canvas_size.h - 1;
+ return m_row_start_y[table_y];
+}
+
+/* class text_art::simple_table_geometry. */
+
+simple_table_geometry::simple_table_geometry (const table &table)
+: m_col_widths (table.get_size ().w),
+ m_row_heights (table.get_size ().h),
+ m_cell_sizes (m_col_widths, m_row_heights),
+ m_tg (table, m_cell_sizes)
+{
+ m_cell_sizes.pass_1 (table);
+ m_cell_sizes.pass_2 (table);
+ m_tg.recalc_coords ();
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_tic_tac_toe ()
+{
+ style_manager sm;
+ table t (table::size_t (3, 3));
+ t.set_cell (table::coord_t (0, 0), styled_string (sm, "X"));
+ t.set_cell (table::coord_t (1, 0), styled_string (sm, ""));
+ t.set_cell (table::coord_t (2, 0), styled_string (sm, ""));
+ t.set_cell (table::coord_t (0, 1), styled_string (sm, "O"));
+ t.set_cell (table::coord_t (1, 1), styled_string (sm, "O"));
+ t.set_cell (table::coord_t (2, 1), styled_string (sm, ""));
+ t.set_cell (table::coord_t (0, 2), styled_string (sm, "X"));
+ t.set_cell (table::coord_t (1, 2), styled_string (sm, ""));
+ t.set_cell (table::coord_t (2, 2), styled_string (sm, "O"));
+
+ {
+ canvas canvas (t.to_canvas (ascii_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-+-+-+\n"
+ "|X| | |\n"
+ "+-+-+-+\n"
+ "|O|O| |\n"
+ "+-+-+-+\n"
+ "|X| |O|\n"
+ "+-+-+-+\n"));
+ }
+
+ {
+ canvas canvas (t.to_canvas (unicode_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌─┬─┬─┐\n"
+ "│X│ │ │\n"
+ "├─┼─┼─┤\n"
+ "│O│O│ │\n"
+ "├─┼─┼─┤\n"
+ "│X│ │O│\n"
+ "└─┴─┴─┘\n"));
+ }
+}
+
+static table
+make_text_table ()
+{
+ style_manager sm;
+ table t (table::size_t (3, 3));
+ t.set_cell (table::coord_t (0, 0), styled_string (sm, "top left"));
+ t.set_cell (table::coord_t (1, 0), styled_string (sm, "top middle"));
+ t.set_cell (table::coord_t (2, 0), styled_string (sm, "top right"));
+ t.set_cell (table::coord_t (0, 1), styled_string (sm, "middle left"));
+ t.set_cell (table::coord_t (1, 1), styled_string (sm, "middle middle"));
+ t.set_cell (table::coord_t (2, 1), styled_string (sm, "middle right"));
+ t.set_cell (table::coord_t (0, 2), styled_string (sm, "bottom left"));
+ t.set_cell (table::coord_t (1, 2), styled_string (sm, "bottom middle"));
+ t.set_cell (table::coord_t (2, 2), styled_string (sm, "bottom right"));
+ return t;
+}
+
+static void
+test_text_table ()
+{
+ style_manager sm;
+ table table = make_text_table ();
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-----------+-------------+------------+\n"
+ "| top left | top middle | top right |\n"
+ "+-----------+-------------+------------+\n"
+ "|middle left|middle middle|middle right|\n"
+ "+-----------+-------------+------------+\n"
+ "|bottom left|bottom middle|bottom right|\n"
+ "+-----------+-------------+------------+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌───────────┬─────────────┬────────────┐\n"
+ "│ top left │ top middle │ top right │\n"
+ "├───────────┼─────────────┼────────────┤\n"
+ "│middle left│middle middle│middle right│\n"
+ "├───────────┼─────────────┼────────────┤\n"
+ "│bottom left│bottom middle│bottom right│\n"
+ "└───────────┴─────────────┴────────────┘\n"));
+ }
+}
+
+static void
+test_offset_table ()
+{
+ style_manager sm;
+ table table = make_text_table ();
+ simple_table_geometry stg (table);
+ const canvas::size_t tcs = stg.m_tg.get_canvas_size();
+ {
+ canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
+ canvas.debug_fill ();
+ table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
+ stg.m_tg,
+ ascii_theme());
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("*********************************************\n"
+ "*********************************************\n"
+ "*********************************************\n"
+ "***+-----------+-------------+------------+**\n"
+ "***| top left | top middle | top right |**\n"
+ "***+-----------+-------------+------------+**\n"
+ "***|middle left|middle middle|middle right|**\n"
+ "***+-----------+-------------+------------+**\n"
+ "***|bottom left|bottom middle|bottom right|**\n"
+ "***+-----------+-------------+------------+**\n"
+ "*********************************************\n"
+ "*********************************************\n"));
+ }
+ {
+ canvas canvas (canvas::size_t (tcs.w + 5, tcs.h + 5), sm);
+ canvas.debug_fill ();
+ table.paint_to_canvas (canvas, canvas::coord_t (3, 3),
+ stg.m_tg,
+ unicode_theme());
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("*********************************************\n"
+ "*********************************************\n"
+ "*********************************************\n"
+ "***┌───────────┬─────────────┬────────────┐**\n"
+ "***│ top left │ top middle │ top right │**\n"
+ "***├───────────┼─────────────┼────────────┤**\n"
+ "***│middle left│middle middle│middle right│**\n"
+ "***├───────────┼─────────────┼────────────┤**\n"
+ "***│bottom left│bottom middle│bottom right│**\n"
+ "***└───────────┴─────────────┴────────────┘**\n"
+ "*********************************************\n"
+ "*********************************************\n"));
+ }
+}
+
+#define ASSERT_TABLE_CELL_STREQ(TABLE, TABLE_X, TABLE_Y, EXPECTED_STR) \
+ SELFTEST_BEGIN_STMT \
+ table::coord_t coord ((TABLE_X), (TABLE_Y)); \
+ const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
+ ASSERT_NE (cp, nullptr); \
+ ASSERT_EQ (cp->get_content (), styled_string (sm, EXPECTED_STR)); \
+ SELFTEST_END_STMT
+
+#define ASSERT_TABLE_NULL_CELL(TABLE, TABLE_X, TABLE_Y) \
+ SELFTEST_BEGIN_STMT \
+ table::coord_t coord ((TABLE_X), (TABLE_Y)); \
+ const table::cell_placement *cp = (TABLE).get_placement_at (coord); \
+ ASSERT_EQ (cp, nullptr); \
+ SELFTEST_END_STMT
+
+static void
+test_spans ()
+{
+ style_manager sm;
+ table table (table::size_t (3, 3));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (3, 1)),
+ styled_string (sm, "ABC"));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 1),
+ table::size_t (2, 1)),
+ styled_string (sm, "DE"));
+ table.set_cell_span (table::rect_t (table::coord_t (2, 1),
+ table::size_t (1, 1)),
+ styled_string (sm, "F"));
+ table.set_cell (table::coord_t (0, 2), styled_string (sm, "G"));
+ table.set_cell (table::coord_t (1, 2), styled_string (sm, "H"));
+ table.set_cell (table::coord_t (2, 2), styled_string (sm, "I"));
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-----+\n"
+ "| ABC |\n"
+ "+---+-+\n"
+ "|DE |F|\n"
+ "+-+-+-+\n"
+ "|G|H|I|\n"
+ "+-+-+-+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌─────┐\n"
+ "│ ABC │\n"
+ "├───┬─┤\n"
+ "│DE │F│\n"
+ "├─┬─┼─┤\n"
+ "│G│H│I│\n"
+ "└─┴─┴─┘\n"));
+ }
+}
+
+/* Verify building this 5x5 table with spans:
+ |0|1|2|3|4|
+ +-+-+-+-+-+
+ 0|A A A|B|C|0
+ + +-+ +
+ 1|A A A|D|C|1
+ + +-+-+
+ 2|A A A|E|F|2
+ +-+-+-+-+-+
+ 3|G G|H|I I|3
+ | | +-+-+
+ 4|G G|H|J J|4
+ +-+-+-+-+-+
+ |0|1|2|3|4|
+*/
+
+static void
+test_spans_2 ()
+{
+ style_manager sm;
+ table table (table::size_t (5, 5));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "A"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 0),
+ table::size_t (1, 1)),
+ styled_string (sm, "B"));
+ table.set_cell_span (table::rect_t (table::coord_t (4, 0),
+ table::size_t (1, 2)),
+ styled_string (sm, "C"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 1),
+ table::size_t (1, 1)),
+ styled_string (sm, "D"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 2),
+ table::size_t (1, 1)),
+ styled_string (sm, "E"));
+ table.set_cell_span (table::rect_t (table::coord_t (4, 2),
+ table::size_t (1, 1)),
+ styled_string (sm, "F"));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 3),
+ table::size_t (2, 2)),
+ styled_string (sm, "G"));
+ table.set_cell_span (table::rect_t (table::coord_t (2, 3),
+ table::size_t (1, 2)),
+ styled_string (sm, "H"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 3),
+ table::size_t (2, 1)),
+ styled_string (sm, "I"));
+ table.set_cell_span (table::rect_t (table::coord_t (3, 4),
+ table::size_t (2, 1)),
+ styled_string (sm, "J"));
+
+ /* Check occupancy at each table coordinate. */
+ ASSERT_TABLE_CELL_STREQ (table, 0, 0, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 0, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 0, "B");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 0, "C");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 1, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 1, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 1, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 1, "D");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 1, "C");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 2, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 2, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 2, "A");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 2, "E");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 2, "F");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 3, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 3, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 3, "H");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 3, "I");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 3, "I");
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 4, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 4, "G");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 4, "H");
+ ASSERT_TABLE_CELL_STREQ (table, 3, 4, "J");
+ ASSERT_TABLE_CELL_STREQ (table, 4, 4, "J");
+
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+---+-+-+\n"
+ "| |B| |\n"
+ "| +-+C|\n"
+ "| A |D| |\n"
+ "| +-+-+\n"
+ "| |E|F|\n"
+ "+-+-+-+-+\n"
+ "| | | I |\n"
+ "|G|H+---+\n"
+ "| | | J |\n"
+ "+-+-+---+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌───┬─┬─┐\n"
+ "│ │B│ │\n"
+ "│ ├─┤C│\n"
+ "│ A │D│ │\n"
+ "│ ├─┼─┤\n"
+ "│ │E│F│\n"
+ "├─┬─┼─┴─┤\n"
+ "│ │ │ I │\n"
+ "│G│H├───┤\n"
+ "│ │ │ J │\n"
+ "└─┴─┴───┘\n"));
+ }
+}
+
+/* Experiment with adding a 1-table-column gap at the boundary between
+ valid vs invalid for visualizing a buffer overflow. */
+static void
+test_spans_3 ()
+{
+ const char * const str = "hello world!";
+ const size_t buf_size = 10;
+ const size_t str_size = strlen (str) + 1;
+
+ style_manager sm;
+ table table (table::size_t (str_size + 1, 3));
+
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (str_size + 1, 1)),
+ styled_string (sm, "String literal"));
+
+ for (size_t i = 0; i < str_size; i++)
+ {
+ table::coord_t c (i, 1);
+ if (i >= buf_size)
+ c.x++;
+ if (str[i] == '\0')
+ table.set_cell (c, styled_string (sm, "NUL"));
+ else
+ table.set_cell (c, styled_string ((cppchar_t)str[i]));
+ }
+
+ table.set_cell_span (table::rect_t (table::coord_t (0, 2),
+ table::size_t (buf_size, 1)),
+ styled_string::from_fmt (sm,
+ nullptr,
+ "'buf' (char[%i])",
+ (int)buf_size));
+ table.set_cell_span (table::rect_t (table::coord_t (buf_size + 1, 2),
+ table::size_t (str_size - buf_size, 1)),
+ styled_string (sm, "overflow"));
+
+ {
+ canvas canvas (table.to_canvas (ascii_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ "+-----------------------------+\n"
+ "| String literal |\n"
+ "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
+ "|h|e|l|l|o| |w|o|r|l||d|!|NUL |\n"
+ "+-+-+-+-+-+-+-+-+-+-++-+-+----+\n"
+ "| 'buf' (char[10]) ||overflow|\n"
+ "+-------------------++--------+\n");
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme (), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌─────────────────────────────┐\n"
+ "│ String literal │\n"
+ "├─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬┬─┬─┬────┤\n"
+ "│h│e│l│l│o│ │w│o│r│l││d│!│NUL │\n"
+ "├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤├─┴─┴────┤\n"
+ "│ 'buf' (char[10]) ││overflow│\n"
+ "└───────────────────┘└────────┘\n"));
+ }
+}
+
+static void
+test_double_width_chars ()
+{
+ table_cell_content tcc (styled_string ((cppchar_t)0x1f642));
+ ASSERT_EQ (tcc.get_canvas_size ().w, 2);
+ ASSERT_EQ (tcc.get_canvas_size ().h, 1);
+
+ style_manager sm;
+ table table (table::size_t (1, 1));
+ table.set_cell (table::coord_t (0,0),
+ styled_string ((cppchar_t)0x1f642));
+
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌──┐\n"
+ "│🙂│\n"
+ "└──┘\n"));
+}
+
+static void
+test_ipv4_header ()
+{
+ style_manager sm;
+ table table (table::size_t (34, 10));
+ table.set_cell (table::coord_t (0, 0), styled_string (sm, "Offsets"));
+ table.set_cell (table::coord_t (1, 0), styled_string (sm, "Octet"));
+ table.set_cell (table::coord_t (0, 1), styled_string (sm, "Octet"));
+ for (int octet = 0; octet < 4; octet++)
+ table.set_cell_span (table::rect_t (table::coord_t (2 + (octet * 8), 0),
+ table::size_t (8, 1)),
+ styled_string::from_fmt (sm, nullptr, "%i", octet));
+ table.set_cell (table::coord_t (1, 1), styled_string (sm, "Bit"));
+ for (int bit = 0; bit < 32; bit++)
+ table.set_cell (table::coord_t (bit + 2, 1),
+ styled_string::from_fmt (sm, nullptr, "%i", bit));
+ for (int word = 0; word < 6; word++)
+ {
+ table.set_cell (table::coord_t (0, word + 2),
+ styled_string::from_fmt (sm, nullptr, "%i", word * 4));
+ table.set_cell (table::coord_t (1, word + 2),
+ styled_string::from_fmt (sm, nullptr, "%i", word * 32));
+ }
+
+ table.set_cell (table::coord_t (0, 8), styled_string (sm, "..."));
+ table.set_cell (table::coord_t (1, 8), styled_string (sm, "..."));
+ table.set_cell (table::coord_t (0, 9), styled_string (sm, "56"));
+ table.set_cell (table::coord_t (1, 9), styled_string (sm, "448"));
+
+#define SET_BITS(FIRST, LAST, NAME) \
+ do { \
+ const int first = (FIRST); \
+ const int last = (LAST); \
+ const char *name = (NAME); \
+ const int row = first / 32; \
+ gcc_assert (last / 32 == row); \
+ table::rect_t rect (table::coord_t ((first % 32) + 2, row + 2), \
+ table::size_t (last + 1 - first , 1)); \
+ table.set_cell_span (rect, styled_string (sm, name)); \
+ } while (0)
+
+ SET_BITS (0, 3, "Version");
+ SET_BITS (4, 7, "IHL");
+ SET_BITS (8, 13, "DSCP");
+ SET_BITS (14, 15, "ECN");
+ SET_BITS (16, 31, "Total Length");
+
+ SET_BITS (32 + 0, 32 + 15, "Identification");
+ SET_BITS (32 + 16, 32 + 18, "Flags");
+ SET_BITS (32 + 19, 32 + 31, "Fragment Offset");
+
+ SET_BITS (64 + 0, 64 + 7, "Time To Live");
+ SET_BITS (64 + 8, 64 + 15, "Protocol");
+ SET_BITS (64 + 16, 64 + 31, "Header Checksum");
+
+ SET_BITS (96 + 0, 96 + 31, "Source IP Address");
+ SET_BITS (128 + 0, 128 + 31, "Destination IP Address");
+
+ table.set_cell_span(table::rect_t (table::coord_t (2, 7),
+ table::size_t (32, 3)),
+ styled_string (sm, "Options"));
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-------+-----+---------------+---------------------+-----------------------+-----------------------+\n"
+ "|Offsets|Octet| 0 | 1 | 2 | 3 |\n"
+ "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
+ "| Octet | Bit |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|\n"
+ "+-------+-----+-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n"
+ "| 0 | 0 |Version| IHL | DSCP | ECN | Total Length |\n"
+ "+-------+-----+-------+-------+---------------+-----+--------+--------------------------------------+\n"
+ "| 4 | 32 | Identification | Flags | Fragment Offset |\n"
+ "+-------+-----+---------------+---------------------+--------+--------------------------------------+\n"
+ "| 8 | 64 | Time To Live | Protocol | Header Checksum |\n"
+ "+-------+-----+---------------+---------------------+-----------------------------------------------+\n"
+ "| 12 | 96 | Source IP Address |\n"
+ "+-------+-----+-------------------------------------------------------------------------------------+\n"
+ "| 16 | 128 | Destination IP Address |\n"
+ "+-------+-----+-------------------------------------------------------------------------------------+\n"
+ "| 20 | 160 | |\n"
+ "+-------+-----+ |\n"
+ "| ... | ... | Options |\n"
+ "+-------+-----+ |\n"
+ "| 56 | 448 | |\n"
+ "+-------+-----+-------------------------------------------------------------------------------------+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ // FIXME: are we allowed unicode chars in string literals in our source?
+ ("┌───────┬─────┬───────────────┬─────────────────────┬───────────────────────┬───────────────────────┐\n"
+ "│Offsets│Octet│ 0 │ 1 │ 2 │ 3 │\n"
+ "├───────┼─────┼─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┼──┬──┬──┬──┬──┬──┬──┬──┤\n"
+ "│ Octet │ Bit │0│1│2│3│4│5│6│7│8│9│10│11│12│13│14│15│16│17│18│19│20│21│22│23│24│25│26│27│28│29│30│31│\n"
+ "├───────┼─────┼─┴─┴─┴─┼─┴─┴─┴─┼─┴─┴──┴──┴──┴──┼──┴──┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┤\n"
+ "│ 0 │ 0 │Version│ IHL │ DSCP │ ECN │ Total Length │\n"
+ "├───────┼─────┼───────┴───────┴───────────────┴─────┼────────┬──────────────────────────────────────┤\n"
+ "│ 4 │ 32 │ Identification │ Flags │ Fragment Offset │\n"
+ "├───────┼─────┼───────────────┬─────────────────────┼────────┴──────────────────────────────────────┤\n"
+ "│ 8 │ 64 │ Time To Live │ Protocol │ Header Checksum │\n"
+ "├───────┼─────┼───────────────┴─────────────────────┴───────────────────────────────────────────────┤\n"
+ "│ 12 │ 96 │ Source IP Address │\n"
+ "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
+ "│ 16 │ 128 │ Destination IP Address │\n"
+ "├───────┼─────┼─────────────────────────────────────────────────────────────────────────────────────┤\n"
+ "│ 20 │ 160 │ │\n"
+ "├───────┼─────┤ │\n"
+ "│ ... │ ... │ Options │\n"
+ "├───────┼─────┤ │\n"
+ "│ 56 │ 448 │ │\n"
+ "└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────────┘\n"));
+ }
+}
+
+static void
+test_missing_cells ()
+{
+ style_manager sm;
+ table table (table::size_t (3, 3));
+ table.set_cell (table::coord_t (1, 0), styled_string (sm, "A"));
+ table.set_cell (table::coord_t (0, 1), styled_string (sm, "B"));
+ table.set_cell (table::coord_t (1, 1), styled_string (sm, "C"));
+ table.set_cell (table::coord_t (2, 1), styled_string (sm, "D"));
+ table.set_cell (table::coord_t (1, 2), styled_string (sm, "E"));
+
+ ASSERT_TABLE_NULL_CELL (table, 0, 0);
+ ASSERT_TABLE_CELL_STREQ (table, 1, 0, "A");
+ ASSERT_TABLE_NULL_CELL (table, 2, 0);
+
+ ASSERT_TABLE_CELL_STREQ (table, 0, 1, "B");
+ ASSERT_TABLE_CELL_STREQ (table, 1, 1, "C");
+ ASSERT_TABLE_CELL_STREQ (table, 2, 1, "D");
+
+ ASSERT_TABLE_NULL_CELL (table, 0, 2);
+ ASSERT_TABLE_CELL_STREQ (table, 1, 2, "E");
+ ASSERT_TABLE_NULL_CELL (table, 2, 2);
+
+ {
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ (" +-+\n"
+ " |A|\n"
+ "+-+-+-+\n"
+ "|B|C|D|\n"
+ "+-+-+-+\n"
+ " |E|\n"
+ " +-+\n"));
+ }
+ {
+ canvas canvas (table.to_canvas (unicode_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ (" ┌─┐\n"
+ " │A│\n"
+ "┌─┼─┼─┐\n"
+ "│B│C│D│\n"
+ "└─┼─┼─┘\n"
+ " │E│\n"
+ " └─┘\n"));
+ }
+}
+
+static void
+test_add_row ()
+{
+ style_manager sm;
+ table table (table::size_t (3, 0));
+ for (int i = 0; i < 5; i++)
+ {
+ const int y = table.add_row ();
+ for (int x = 0; x < 3; x++)
+ table.set_cell (table::coord_t (x, y),
+ styled_string::from_fmt (sm, nullptr,
+ "%i, %i", x, y));
+ }
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+----+----+----+\n"
+ "|0, 0|1, 0|2, 0|\n"
+ "+----+----+----+\n"
+ "|0, 1|1, 1|2, 1|\n"
+ "+----+----+----+\n"
+ "|0, 2|1, 2|2, 2|\n"
+ "+----+----+----+\n"
+ "|0, 3|1, 3|2, 3|\n"
+ "+----+----+----+\n"
+ "|0, 4|1, 4|2, 4|\n"
+ "+----+----+----+\n"));
+}
+
+static void
+test_alignment ()
+{
+ style_manager sm;
+ table table (table::size_t (9, 9));
+ table.set_cell_span (table::rect_t (table::coord_t (0, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "left top"),
+ x_align::LEFT, y_align::TOP);
+ table.set_cell_span (table::rect_t (table::coord_t (3, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "center top"),
+ x_align::CENTER, y_align::TOP);
+ table.set_cell_span (table::rect_t (table::coord_t (6, 0),
+ table::size_t (3, 3)),
+ styled_string (sm, "right top"),
+ x_align::RIGHT, y_align::TOP);
+ table.set_cell_span (table::rect_t (table::coord_t (0, 3),
+ table::size_t (3, 3)),
+ styled_string (sm, "left center"),
+ x_align::LEFT, y_align::CENTER);
+ table.set_cell_span (table::rect_t (table::coord_t (3, 3),
+ table::size_t (3, 3)),
+ styled_string (sm, "center center"),
+ x_align::CENTER, y_align::CENTER);
+ table.set_cell_span (table::rect_t (table::coord_t (6, 3),
+ table::size_t (3, 3)),
+ styled_string (sm, "right center"),
+ x_align::RIGHT, y_align::CENTER);
+ table.set_cell_span (table::rect_t (table::coord_t (0, 6),
+ table::size_t (3, 3)),
+ styled_string (sm, "left bottom"),
+ x_align::LEFT, y_align::BOTTOM);
+ table.set_cell_span (table::rect_t (table::coord_t (3, 6),
+ table::size_t (3, 3)),
+ styled_string (sm, "center bottom"),
+ x_align::CENTER, y_align::BOTTOM);
+ table.set_cell_span (table::rect_t (table::coord_t (6, 6),
+ table::size_t (3, 3)),
+ styled_string (sm, "right bottom"),
+ x_align::RIGHT, y_align::BOTTOM);
+
+ canvas canvas (table.to_canvas (ascii_theme(), sm));
+ ASSERT_CANVAS_STREQ
+ (canvas, false,
+ ("+-----------+-------------+------------+\n"
+ "|left top | center top | right top|\n"
+ "| | | |\n"
+ "+-----------+-------------+------------+\n"
+ "|left center|center center|right center|\n"
+ "| | | |\n"
+ "+-----------+-------------+------------+\n"
+ "| | | |\n"
+ "|left bottom|center bottom|right bottom|\n"
+ "+-----------+-------------+------------+\n"));
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_table_cc_tests ()
+{
+ test_tic_tac_toe ();
+ test_text_table ();
+ test_offset_table ();
+ test_spans ();
+ test_spans_2 ();
+ test_spans_3 ();
+ test_double_width_chars ();
+ test_ipv4_header ();
+ test_missing_cells ();
+ test_add_row ();
+ test_alignment ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,262 @@
+/* Support for tabular/grid-based content.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_TABLE_H
+#define GCC_TEXT_ART_TABLE_H
+
+#include "text-art/canvas.h"
+#include "text-art/theme.h"
+#include <vector>
+
+namespace text_art {
+
+class table;
+class table_geometry;
+
+/* A class representing the content of a particular table cell,
+ or of a span of table cells. */
+
+class table_cell_content
+{
+ public:
+ table_cell_content () : m_str (), m_size (0, 0) {}
+ table_cell_content (styled_string &&s);
+
+ bool operator== (const table_cell_content &other) const
+ {
+ return m_str == other.m_str;
+ }
+
+ canvas::size_t get_canvas_size () const { return m_size; }
+
+ void paint_to_canvas (canvas &canvas,
+ canvas::coord_t top_left) const;
+
+ private:
+ styled_string m_str;
+ canvas::size_t m_size;
+};
+
+/* A list of required sizes of table rows or columns
+ in canvas units (row heights or column widths). */
+
+struct table_dimension_sizes
+{
+ table_dimension_sizes (unsigned num);
+
+ void require (unsigned idx, int amount)
+ {
+ m_requirements[idx] = std::max (m_requirements[idx], amount);
+ }
+
+ std::vector<int> m_requirements;
+};
+
+/* A 2D grid of cells. Instances of table_cell_content can be assigned
+ to individual table cells, and to rectangular spans of cells. Such
+ assignments do not have to fully cover the 2D grid, but they must not
+ overlap. */
+
+class table
+{
+ public:
+ typedef size<class table> size_t;
+ typedef coord<class table> coord_t;
+ typedef range<class table> range_t;
+ typedef rect<class table> rect_t;
+
+ /* A record of how a table_cell_content was placed at a table::rect_t
+ with a certain alignment. */
+ class cell_placement
+ {
+ public:
+ cell_placement (rect_t rect,
+ table_cell_content &&content,
+ x_align x_align,
+ y_align y_align)
+ : m_rect (rect),
+ m_content (std::move (content)),
+ m_x_align (x_align),
+ m_y_align (y_align)
+ {
+ }
+
+ bool one_by_one_p () const
+ {
+ return m_rect.m_size.w == 1 && m_rect.m_size.h == 1;
+ }
+
+ canvas::size_t get_min_canvas_size () const
+ {
+ // Doesn't include border
+ return m_content.get_canvas_size ();
+ }
+
+ void paint_cell_contents_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg) const;
+
+ const table_cell_content &get_content () const { return m_content; }
+
+ private:
+ friend class table_cell_sizes;
+ rect_t m_rect;
+ table_cell_content m_content;
+ x_align m_x_align;
+ y_align m_y_align;
+ };
+
+ table (size_t size);
+ ~table () = default;
+ table (table &&) = default;
+ table (const table &) = delete;
+ table &operator= (const table &) = delete;
+
+ const size_t &get_size () const { return m_size; }
+
+ int add_row ()
+ {
+ m_size.h++;
+ m_occupancy.add_row (-1);
+ return m_size.h - 1; // return the table_y of the newly-added row
+ }
+
+ void set_cell (coord_t coord,
+ table_cell_content &&content,
+ enum x_align x_align = x_align::CENTER,
+ enum y_align y_align = y_align::CENTER);
+
+ void set_cell_span (rect_t span,
+ table_cell_content &&content,
+ enum x_align x_align = x_align::CENTER,
+ enum y_align y_align = y_align::CENTER);
+
+ canvas to_canvas (const theme &theme, const style_manager &sm) const;
+
+ void paint_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg,
+ const theme &theme) const;
+
+ void debug () const;
+
+ /* Self-test support. */
+ const cell_placement *get_placement_at (coord_t coord) const;
+
+ private:
+ int get_occupancy_safe (coord_t coord) const;
+ directions get_connections (int table_x, int table_y) const;
+ void paint_cell_borders_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg,
+ const theme &theme) const;
+ void paint_cell_contents_to_canvas(canvas &canvas,
+ canvas::coord_t offset,
+ const table_geometry &tg) const;
+
+ friend class table_cell_sizes;
+
+ size_t m_size;
+ std::vector<cell_placement> m_placements;
+ array2<int, size_t, coord_t> m_occupancy; /* indices into the m_placements vec. */
+};
+
+/* A workspace for computing the row heights and column widths
+ of a table (in canvas units).
+ The col_widths and row_heights could be shared between multiple
+ instances, for aligning multiple tables vertically or horizontally. */
+
+class table_cell_sizes
+{
+ public:
+ table_cell_sizes (table_dimension_sizes &col_widths,
+ table_dimension_sizes &row_heights)
+ : m_col_widths (col_widths),
+ m_row_heights (row_heights)
+ {
+ }
+
+ void pass_1 (const table &table);
+ void pass_2 (const table &table);
+
+ canvas::size_t get_canvas_size (const table::rect_t &rect) const;
+
+ table_dimension_sizes &m_col_widths;
+ table_dimension_sizes &m_row_heights;
+};
+
+/* A class responsible for mapping from table cell coords
+ to canvas coords, handling column widths.
+ It's the result of solving "how big are all the table cells and where
+ do they go?"
+ The cell_sizes are passed in, for handling aligning multiple tables,
+ sharing column widths or row heights. */
+
+class table_geometry
+{
+ public:
+ table_geometry (const table &table, table_cell_sizes &cell_sizes);
+
+ void recalc_coords ();
+
+ const canvas::size_t get_canvas_size () const { return m_canvas_size; }
+
+ canvas::coord_t table_to_canvas (table::coord_t table_coord) const;
+ int table_x_to_canvas_x (int table_x) const;
+ int table_y_to_canvas_y (int table_y) const;
+
+ int get_col_width (int table_x) const
+ {
+ return m_cell_sizes.m_col_widths.m_requirements[table_x];
+ }
+
+ canvas::size_t get_canvas_size (const table::rect_t &rect) const
+ {
+ return m_cell_sizes.get_canvas_size (rect);
+ }
+
+ private:
+ const table &m_table;
+ table_cell_sizes &m_cell_sizes;
+ canvas::size_t m_canvas_size;
+
+ /* Start canvas column of table cell, including leading border. */
+ std::vector<int> m_col_start_x;
+
+ /* Start canvas row of table cell, including leading border. */
+ std::vector<int> m_row_start_y;
+};
+
+/* Helper class for handling the simple case of a single table
+ that doesn't need to be aligned with respect to anything else. */
+
+struct simple_table_geometry
+{
+ simple_table_geometry (const table &table);
+
+ table_dimension_sizes m_col_widths;
+ table_dimension_sizes m_row_heights;
+ table_cell_sizes m_cell_sizes;
+ table_geometry m_tg;
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_TABLE_H */
new file mode 100644
@@ -0,0 +1,183 @@
+/* Classes for abstracting ascii vs unicode output.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "text-art/selftests.h"
+#include "text-art/ruler.h"
+#include "text-art/theme.h"
+
+using namespace text_art;
+
+/* class theme. */
+
+void
+theme::paint_y_arrow (canvas &canvas,
+ int canvas_x,
+ canvas::range_t y_range,
+ y_arrow_dir dir,
+ style::id_t style_id) const
+{
+ int canvas_y;
+ int delta_y;
+ const canvas::cell_t head (get_cppchar (dir == y_arrow_dir::UP
+ ? cell_kind::Y_ARROW_UP_HEAD
+ : cell_kind::Y_ARROW_DOWN_HEAD),
+ false, style_id);
+ const canvas::cell_t tail (get_cppchar (dir == y_arrow_dir::UP
+ ? cell_kind::Y_ARROW_UP_TAIL
+ : cell_kind::Y_ARROW_DOWN_TAIL),
+ false, style_id);
+ if (dir == y_arrow_dir::UP)
+ {
+ canvas_y = y_range.get_max ();
+ delta_y = -1;
+ }
+ else
+ {
+ canvas_y = y_range.get_min ();
+ delta_y = 1;
+ }
+ for (int len = y_range.get_size (); len; len--)
+ {
+ const canvas::cell_t cell = (len > 1) ? tail : head;
+ canvas.paint (canvas::coord_t (canvas_x, canvas_y), cell);
+ canvas_y += delta_y;
+ }
+}
+
+/* class ascii_theme : public theme. */
+
+canvas::cell_t
+ascii_theme::get_line_art (directions line_dirs) const
+{
+ if (line_dirs.m_up
+ && line_dirs.m_down
+ && !(line_dirs.m_left || line_dirs.m_right))
+ return canvas::cell_t ('|');
+ if (line_dirs.m_left
+ && line_dirs.m_right
+ && !(line_dirs.m_up || line_dirs.m_down))
+ return canvas::cell_t ('-');
+ if (line_dirs.m_up
+ || line_dirs.m_down
+ || line_dirs.m_left
+ || line_dirs.m_right)
+ return canvas::cell_t ('+');
+ return canvas::cell_t (' ');
+}
+
+cppchar_t
+ascii_theme::get_cppchar (enum cell_kind kind) const
+{
+ switch (kind)
+ {
+ default:
+ gcc_unreachable ();
+ case cell_kind::X_RULER_LEFT_EDGE:
+ return '|';
+ case cell_kind::X_RULER_MIDDLE:
+ return '~';
+ case cell_kind::X_RULER_INTERNAL_EDGE:
+ return '|';
+ case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW:
+ case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE:
+ return '+';
+ case cell_kind::X_RULER_RIGHT_EDGE:
+ return '|';
+ case cell_kind::X_RULER_VERTICAL_CONNECTOR:
+ return '|';
+
+ case cell_kind::TEXT_BORDER_HORIZONTAL:
+ return '-';
+ case cell_kind::TEXT_BORDER_VERTICAL:
+ return '|';
+ case cell_kind::TEXT_BORDER_TOP_LEFT:
+ case cell_kind::TEXT_BORDER_TOP_RIGHT:
+ case cell_kind::TEXT_BORDER_BOTTOM_LEFT:
+ case cell_kind::TEXT_BORDER_BOTTOM_RIGHT:
+ return '+';
+
+ case cell_kind::Y_ARROW_UP_HEAD: return '^';
+ case cell_kind::Y_ARROW_DOWN_HEAD: return 'v';
+
+ case cell_kind::Y_ARROW_UP_TAIL:
+ case cell_kind::Y_ARROW_DOWN_TAIL:
+ return '|';
+ }
+}
+
+/* class unicode_theme : public theme. */
+
+canvas::cell_t
+unicode_theme::get_line_art (directions line_dirs) const
+{
+ return canvas::cell_t (get_box_drawing_char (line_dirs));
+}
+
+cppchar_t
+unicode_theme::get_cppchar (enum cell_kind kind) const
+{
+ switch (kind)
+ {
+ default:
+ gcc_unreachable ();
+ case cell_kind::X_RULER_LEFT_EDGE:
+ return 0x251C; /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+ case cell_kind::X_RULER_MIDDLE:
+ return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+ case cell_kind::X_RULER_INTERNAL_EDGE:
+ return 0x253C; /* "┼": U+253C: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+ case cell_kind::X_RULER_CONNECTOR_TO_LABEL_BELOW:
+ return 0x252C; /* "┬": U+252C: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+ case cell_kind::X_RULER_CONNECTOR_TO_LABEL_ABOVE:
+ return 0x2534; /* "┴": U+2534: BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+ case cell_kind::X_RULER_RIGHT_EDGE:
+ return 0x2524; /* "┤": U+2524: BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+ case cell_kind::X_RULER_VERTICAL_CONNECTOR:
+ return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+
+ case cell_kind::TEXT_BORDER_HORIZONTAL:
+ return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+ case cell_kind::TEXT_BORDER_VERTICAL:
+ return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+
+ /* Round corners. */
+ case cell_kind::TEXT_BORDER_TOP_LEFT:
+ return 0x256D; /* "╭": U+256D BOX DRAWINGS LIGHT ARC DOWN AND RIGHT. */
+ case cell_kind::TEXT_BORDER_TOP_RIGHT:
+ return 0x256E; /* "╮": U+256E BOX DRAWINGS LIGHT ARC DOWN AND LEFT. */
+ case cell_kind::TEXT_BORDER_BOTTOM_LEFT:
+ return 0x2570; /* "╰": U+2570 BOX DRAWINGS LIGHT ARC UP AND RIGHT. */
+ case cell_kind::TEXT_BORDER_BOTTOM_RIGHT:
+ return 0x256F; /* "╯": U+256F BOX DRAWINGS LIGHT ARC UP AND LEFT. */
+
+ case cell_kind::Y_ARROW_UP_HEAD:
+ return '^';
+ case cell_kind::Y_ARROW_DOWN_HEAD:
+ return 'v';
+ case cell_kind::Y_ARROW_UP_TAIL:
+ case cell_kind::Y_ARROW_DOWN_TAIL:
+ return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+ }
+}
new file mode 100644
@@ -0,0 +1,123 @@
+/* Classes for abstracting ascii vs unicode output.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_THEME_H
+#define GCC_TEXT_ART_THEME_H
+
+#include "text-art/canvas.h"
+#include "text-art/box-drawing.h"
+
+namespace text_art {
+
+class theme
+{
+ public:
+ enum class cell_kind
+ {
+ /* A left-hand edge of a range e.g. "├". */
+ X_RULER_LEFT_EDGE,
+
+ /* Within a range e.g. "─". */
+ X_RULER_MIDDLE,
+
+ /* A border between two neighboring ranges e.g. "┼". */
+ X_RULER_INTERNAL_EDGE,
+
+ /* The connector with the text label within a range e.g. "┬". */
+ X_RULER_CONNECTOR_TO_LABEL_BELOW,
+
+ /* As above, but when the text label is above the ruler. */
+ X_RULER_CONNECTOR_TO_LABEL_ABOVE,
+
+ /* The vertical connection to a text label. */
+ X_RULER_VERTICAL_CONNECTOR,
+
+ /* A right-hand edge of a range e.g. "┤". */
+ X_RULER_RIGHT_EDGE,
+
+ TEXT_BORDER_HORIZONTAL,
+ TEXT_BORDER_VERTICAL,
+ TEXT_BORDER_TOP_LEFT,
+ TEXT_BORDER_TOP_RIGHT,
+ TEXT_BORDER_BOTTOM_LEFT,
+ TEXT_BORDER_BOTTOM_RIGHT,
+
+ Y_ARROW_UP_HEAD,
+ Y_ARROW_UP_TAIL,
+ Y_ARROW_DOWN_HEAD,
+ Y_ARROW_DOWN_TAIL,
+ };
+
+ virtual ~theme () = default;
+
+ virtual bool unicode_p () const = 0;
+ virtual bool emojis_p () const = 0;
+
+ virtual canvas::cell_t
+ get_line_art (directions line_dirs) const = 0;
+
+ canvas::cell_t get_cell (enum cell_kind kind, unsigned style_idx) const
+ {
+ return canvas::cell_t (get_cppchar (kind), false, style_idx);
+ }
+
+ virtual cppchar_t get_cppchar (enum cell_kind kind) const = 0;
+
+ enum class y_arrow_dir { UP, DOWN };
+ void paint_y_arrow (canvas &canvas,
+ int x,
+ canvas::range_t y_range,
+ y_arrow_dir dir,
+ style::id_t style_id) const;
+};
+
+class ascii_theme : public theme
+{
+ public:
+ bool unicode_p () const final override { return false; }
+ bool emojis_p () const final override { return false; }
+
+ canvas::cell_t
+ get_line_art (directions line_dirs) const final override;
+
+ cppchar_t get_cppchar (enum cell_kind kind) const final override;
+};
+
+class unicode_theme : public theme
+{
+ public:
+ bool unicode_p () const final override { return true; }
+ bool emojis_p () const override { return false; }
+
+ canvas::cell_t
+ get_line_art (directions line_dirs) const final override;
+
+ cppchar_t get_cppchar (enum cell_kind kind) const final override;
+};
+
+class emoji_theme : public unicode_theme
+{
+public:
+ bool emojis_p () const final override { return true; }
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_THEME_H */
new file mode 100644
@@ -0,0 +1,504 @@
+/* Types for drawing 2d "text art".
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_TYPES_H
+#define GCC_TEXT_ART_TYPES_H
+
+#include "cpplib.h"
+#include "pretty-print.h"
+#include <vector>
+#include <string>
+
+namespace text_art {
+
+/* Forward decls. */
+
+class canvas;
+class table;
+class theme;
+
+/* Classes for geometry.
+ We use templates to avoid mixing up e.g. canvas coordinates
+ with table coordinates. */
+
+template <typename CoordinateSystem>
+struct size
+{
+ size (int w_, int h_) : w (w_), h (h_) {}
+ int w;
+ int h;
+};
+
+template <typename CoordinateSystem>
+struct coord
+{
+ coord (int x_, int y_) : x (x_), y (y_) {}
+ int x;
+ int y;
+};
+
+template <typename CoordinateSystem>
+coord<CoordinateSystem> operator+ (coord<CoordinateSystem> a,
+ coord<CoordinateSystem> b)
+{
+ return coord<CoordinateSystem> (a.x + b.x, a.y + b.y);
+}
+
+/* A half-open range [start, next) of int. */
+
+template <typename CoordinateSystem>
+struct range
+{
+ range (int start_, int next_)
+ : start (start_), next (next_)
+ {}
+
+ int get_min () const { return start; }
+ int get_max () const { return next - 1; }
+ int get_next () const { return next; }
+ int get_size () const { return next - start; }
+
+ int get_midpoint () const { return get_min () + get_size () / 2; }
+
+ int start;
+ int next;
+};
+
+/* A rectangle area within CoordinateSystem. */
+
+template <typename CoordinateSystem>
+struct rect
+{
+ rect (coord<CoordinateSystem> top_left,
+ size<CoordinateSystem> size)
+ : m_top_left (top_left),
+ m_size (size)
+ {
+ }
+
+ rect (range<CoordinateSystem> x_range,
+ range<CoordinateSystem> y_range)
+ : m_top_left (x_range.get_min (), y_range.get_min ()),
+ m_size (x_range.get_size (), y_range.get_size ())
+ {
+ }
+
+ int get_min_x () const { return m_top_left.x; }
+ int get_min_y () const { return m_top_left.y; }
+ int get_max_x () const { return m_top_left.x + m_size.w - 1; }
+ int get_max_y () const { return m_top_left.y + m_size.h - 1; }
+ int get_next_x () const { return m_top_left.x + m_size.w; }
+ int get_next_y () const { return m_top_left.y + m_size.h; }
+
+ range<CoordinateSystem> get_x_range () const
+ {
+ return range<CoordinateSystem> (get_min_x (), get_next_x ());
+ }
+ range<CoordinateSystem> get_y_range () const
+ {
+ return range<CoordinateSystem> (get_min_y (), get_next_y ());
+ }
+
+ int get_width () const { return m_size.w; }
+ int get_height () const { return m_size.h; }
+
+ coord<CoordinateSystem> m_top_left;
+ size<CoordinateSystem> m_size;
+};
+
+template <typename ElementType, typename SizeType, typename CoordType>
+class array2
+{
+ public:
+ typedef ElementType element_t;
+ typedef SizeType size_t;
+ typedef CoordType coord_t;
+
+ array2 (size_t sz)
+ : m_size (sz),
+ m_elements (sz.w * sz.h)
+ {
+ }
+ array2 (array2 &&other)
+ : m_size (other.m_size),
+ m_elements (std::move (other.m_elements))
+ {
+ }
+
+ /* Move assignment not implemented or used. */
+ array2 &operator== (array2 &&other) = delete;
+
+ /* No copy ctor or assignment op. */
+ array2 (const array2 &other) = delete;
+ array2 &operator= (const array2 &other) = delete;
+
+
+ const size_t &get_size () const { return m_size; }
+
+ void add_row (const element_t &element)
+ {
+ m_size.h++;
+ m_elements.insert (m_elements.end (), m_size.w, element);
+ }
+
+ const element_t &get (const coord_t &coord) const
+ {
+ ::size_t idx = get_idx (coord);
+ return m_elements[idx];
+ }
+
+ void set (const coord_t &coord, const element_t &element)
+ {
+ ::size_t idx = get_idx (coord);
+ m_elements[idx] = element;
+ }
+
+ void fill (element_t element)
+ {
+ for (int y = 0; y < m_size.h; y++)
+ for (int x = 0; x < m_size.w; x++)
+ set (coord_t (x, y), element);
+ }
+
+ private:
+ ::size_t get_idx (const coord_t &coord) const
+ {
+ gcc_assert (coord.x >= 0);
+ gcc_assert (coord.x < m_size.w);
+ gcc_assert (coord.y >= 0);
+ gcc_assert (coord.y < m_size.h);
+ return (coord.y * m_size.w) + coord.x;
+ }
+
+ size_t m_size;
+ std::vector<element_t> m_elements;
+};
+
+/* A combination of attributes describing how to style a text cell.
+ We only support those attributes mentioned in invoke.texi:
+ - bold,
+ - underscore,
+ - blink,
+ - inverse,
+ - colors for foreground and background:
+ - default color
+ - named colors
+ - 16-color mode colors (the "bright" variants)
+ - 88-color mode
+ - 256-color mode
+ plus URLs. */
+
+struct style
+{
+ typedef unsigned char id_t;
+ static const id_t id_plain = 0;
+
+ /* Colors. */
+ enum class named_color
+ {
+ DEFAULT,
+ // ANSI order
+ BLACK,
+ RED,
+ GREEN,
+ YELLOW,
+ BLUE,
+ MAGENTA,
+ CYAN,
+ WHITE
+ };
+
+
+ struct color
+ {
+ enum class kind
+ {
+ NAMED,
+ BITS_8,
+ BITS_24,
+ } m_kind;
+
+ union
+ {
+ struct {
+ enum named_color m_name;
+ bool m_bright;
+ } m_named;
+ uint8_t m_8bit;
+ struct {
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+ } m_24bit;
+ } u;
+
+ /* Constructor for named colors. */
+ color (enum named_color name = named_color::DEFAULT,
+ bool bright = false)
+ : m_kind (kind::NAMED)
+ {
+ u.m_named.m_name = name;
+ u.m_named.m_bright = bright;
+ }
+
+ /* Constructor for 8-bit colors. */
+ color (uint8_t col_val)
+ : m_kind (kind::BITS_8)
+ {
+ u.m_8bit = col_val;
+ }
+
+ /* Constructor for 24-bit colors. */
+ color (uint8_t r, uint8_t g, uint8_t b)
+ : m_kind (kind::BITS_24)
+ {
+ u.m_24bit.r = r;
+ u.m_24bit.g = g;
+ u.m_24bit.b = b;
+ }
+
+ bool operator== (const color &other) const;
+ bool operator!= (const color &other) const
+ {
+ return !(*this == other);
+ }
+
+ void print_sgr (pretty_printer *pp, bool fg, bool &need_separator) const;
+ };
+
+ style ()
+ : m_bold (false),
+ m_underscore (false),
+ m_blink (false),
+ m_reverse (false),
+ m_fg_color (named_color::DEFAULT),
+ m_bg_color (named_color::DEFAULT),
+ m_url ()
+ {}
+
+ bool operator== (const style &other) const
+ {
+ return (m_bold == other.m_bold
+ && m_underscore == other.m_underscore
+ && m_blink == other.m_blink
+ && m_reverse == other.m_reverse
+ && m_fg_color == other.m_fg_color
+ && m_bg_color == other.m_bg_color
+ && m_url == other.m_url);
+ }
+
+ style &set_style_url (const char *url);
+
+ static void print_changes (pretty_printer *pp,
+ const style &old_style,
+ const style &new_style);
+
+ bool m_bold;
+ bool m_underscore;
+ bool m_blink;
+ bool m_reverse;
+ color m_fg_color;
+ color m_bg_color;
+ std::vector<cppchar_t> m_url; // empty = no URL
+};
+
+/* A class to keep track of all the styles in use in a drawing, so that
+ we can refer to them via the compact style::id_t type, rather than
+ via e.g. pointers. */
+
+class style_manager
+{
+ public:
+ style_manager ();
+ style::id_t get_or_create_id (const style &style);
+ const style &get_style (style::id_t id) const
+ {
+ return m_styles[id];
+ }
+ void print_any_style_changes (pretty_printer *pp,
+ style::id_t old_id,
+ style::id_t new_id) const;
+ unsigned get_num_styles () const { return m_styles.size (); }
+
+private:
+ std::vector<style> m_styles;
+};
+
+class styled_unichar
+{
+ public:
+ friend class styled_string;
+
+ explicit styled_unichar ()
+ : m_code (0),
+ m_style_id (style::id_plain)
+ {}
+ explicit styled_unichar (cppchar_t ch)
+ : m_code (ch),
+ m_emoji_variant_p (false),
+ m_style_id (style::id_plain)
+ {}
+ explicit styled_unichar (cppchar_t ch, bool emoji, style::id_t style_id)
+ : m_code (ch),
+ m_emoji_variant_p (emoji),
+ m_style_id (style_id)
+ {
+ gcc_assert (style_id <= 0x7f);
+ }
+
+ cppchar_t get_code () const { return m_code; }
+ bool emoji_variant_p () const { return m_emoji_variant_p; }
+ style::id_t get_style_id () const { return m_style_id; }
+
+ bool double_width_p () const
+ {
+ int width = cpp_wcwidth (get_code ());
+ gcc_assert (width == 1 || width == 2);
+ return width == 2;
+ }
+
+ bool operator== (const styled_unichar &other) const
+ {
+ return (m_code == other.m_code
+ && m_emoji_variant_p == other.m_emoji_variant_p
+ && m_style_id == other.m_style_id);
+ }
+
+ void set_emoji_variant () { m_emoji_variant_p = true; }
+
+ int get_canvas_width () const
+ {
+ return cpp_wcwidth (m_code);
+ }
+
+ void add_combining_char (cppchar_t ch)
+ {
+ m_combining_chars.push_back (ch);
+ }
+
+ const std::vector<cppchar_t> get_combining_chars () const
+ {
+ return m_combining_chars;
+ }
+
+private:
+ cppchar_t m_code : 24;
+ bool m_emoji_variant_p : 1;
+ style::id_t m_style_id : 7;
+ std::vector<cppchar_t> m_combining_chars;
+};
+
+class styled_string
+{
+ public:
+ explicit styled_string () = default;
+ explicit styled_string (style_manager &sm, const char *str);
+ explicit styled_string (cppchar_t cppchar, bool emoji = false);
+
+ styled_string (styled_string &&) = default;
+ styled_string &operator= (styled_string &&) = default;
+
+ /* No copy ctor or assignment op. */
+ styled_string (const styled_string &) = delete;
+ styled_string &operator= (const styled_string &) = delete;
+
+ /* For the few cases where copying is required, spell it out explicitly. */
+ styled_string copy () const
+ {
+ styled_string result;
+ result.m_chars = m_chars;
+ return result;
+ }
+
+ bool operator== (const styled_string &other) const
+ {
+ return m_chars == other.m_chars;
+ }
+
+ static styled_string from_fmt (style_manager &sm,
+ printer_fn format_decoder,
+ const char *fmt, ...)
+ ATTRIBUTE_GCC_PPDIAG(3, 4);
+ static styled_string from_fmt_va (style_manager &sm,
+ printer_fn format_decoder,
+ const char *fmt,
+ va_list *args)
+ ATTRIBUTE_GCC_PPDIAG(3, 0);
+
+ size_t size () const { return m_chars.size (); }
+ styled_unichar operator[] (size_t idx) const { return m_chars[idx]; }
+
+ std::vector<styled_unichar>::const_iterator begin () const
+ {
+ return m_chars.begin ();
+ }
+ std::vector<styled_unichar>::const_iterator end () const
+ {
+ return m_chars.end ();
+ }
+
+ int calc_canvas_width () const;
+
+ void append (const styled_string &suffix);
+
+ void set_url (style_manager &sm, const char *url);
+
+private:
+ std::vector<styled_unichar> m_chars;
+};
+
+enum class x_align
+{
+ LEFT,
+ CENTER,
+ RIGHT
+};
+
+enum class y_align
+{
+ TOP,
+ CENTER,
+ BOTTOM
+};
+
+/* A set of cardinal directions within a canvas or table. */
+
+struct directions
+{
+public:
+ directions (bool up, bool down, bool left, bool right)
+ : m_up (up), m_down (down), m_left (left), m_right (right)
+ {
+ }
+
+ size_t as_index () const
+ {
+ return (m_up << 3) | (m_down << 2) | (m_left << 1) | m_right;
+ }
+
+ bool m_up: 1;
+ bool m_down: 1;
+ bool m_left: 1;
+ bool m_right: 1;
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_TYPES_H */
new file mode 100644
@@ -0,0 +1,275 @@
+/* Hierarchical diagram elements.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "make-unique.h"
+#include "text-art/selftests.h"
+#include "text-art/widget.h"
+
+using namespace text_art;
+
+/* class text_art::widget. */
+
+canvas
+widget::to_canvas (const style_manager &style_mgr)
+{
+ const canvas::size_t req_size = get_req_size ();
+
+ /* For now we don't constrain the allocation; we give
+ the widget the full size it requested, and widgets
+ assume they got their full size request. */
+ const canvas::size_t alloc_size = req_size;
+
+ set_alloc_rect (canvas::rect_t (canvas::coord_t (0, 0), alloc_size));
+ canvas c (alloc_size, style_mgr);
+ paint_to_canvas (c);
+ return c;
+}
+
+/* class text_art::vbox_widget : public text_art::container_widget. */
+
+const char *
+vbox_widget::get_desc () const
+{
+ return "vbox_widget";
+}
+
+canvas::size_t
+vbox_widget::calc_req_size ()
+{
+ canvas::size_t result (0, 0);
+ for (auto &child : m_children)
+ {
+ canvas::size_t child_req_size = child->get_req_size();
+ result.h += child_req_size.h;
+ result.w = std::max (result.w, child_req_size.w);
+ }
+ return result;
+}
+
+void
+vbox_widget::update_child_alloc_rects ()
+{
+ const int x = get_min_x ();
+ int y = get_min_y ();
+ for (auto &child : m_children)
+ {
+ child->set_alloc_rect
+ (canvas::rect_t (canvas::coord_t (x, y),
+ canvas::size_t (get_alloc_w (),
+ child->get_req_h ())));
+ y += child->get_req_h ();
+ }
+}
+
+/* class text_art::text_widget : public text_art::leaf_widget. */
+
+const char *
+text_widget::get_desc () const
+{
+ return "text_widget";
+}
+
+canvas::size_t
+text_widget::calc_req_size ()
+{
+ return canvas::size_t (m_str.size (), 1);
+}
+
+void
+text_widget::paint_to_canvas (canvas &canvas)
+{
+ canvas.paint_text (get_top_left (), m_str);
+}
+
+/* class text_art::canvas_widget : public text_art::leaf_widget. */
+
+const char *
+canvas_widget::get_desc () const
+{
+ return "canvas_widget";
+}
+
+canvas::size_t
+canvas_widget::calc_req_size ()
+{
+ return m_canvas.get_size ();
+}
+
+void
+canvas_widget::paint_to_canvas (canvas &canvas)
+{
+ for (int y = 0; y < m_canvas.get_size ().h; y++)
+ for (int x = 0; x < m_canvas.get_size ().w; x++)
+ {
+ canvas::coord_t rel_xy (x, y);
+ canvas.paint (get_top_left () + rel_xy,
+ m_canvas.get (rel_xy));
+ }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Concrete widget subclass for writing selftests.
+ Requests a hard-coded size, and fills its allocated rectangle
+ with a specific character. */
+
+class test_widget : public leaf_widget
+{
+public:
+ test_widget (canvas::size_t size, char ch)
+ : m_test_size (size), m_ch (ch)
+ {}
+
+ const char *get_desc () const final override
+ {
+ return "test_widget";
+ }
+ canvas::size_t calc_req_size () final override
+ {
+ return m_test_size;
+ }
+ void paint_to_canvas (canvas &canvas) final override
+ {
+ canvas.fill (get_alloc_rect (), canvas::cell_t (m_ch));
+ }
+
+private:
+ canvas::size_t m_test_size;
+ char m_ch;
+};
+
+static void
+test_test_widget ()
+{
+ style_manager sm;
+ test_widget w (canvas::size_t (3, 3), 'A');
+ canvas c (w.to_canvas (sm));
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("AAA\n"
+ "AAA\n"
+ "AAA\n"));
+}
+
+static void
+test_text_widget ()
+{
+ style_manager sm;
+ text_widget w (styled_string (sm, "hello world"));
+ canvas c (w.to_canvas (sm));
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("hello world\n"));
+}
+
+static void
+test_wrapper_widget ()
+{
+ style_manager sm;
+ wrapper_widget w (::make_unique<test_widget> (canvas::size_t (3, 3), 'B'));
+ canvas c (w.to_canvas (sm));
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("BBB\n"
+ "BBB\n"
+ "BBB\n"));
+}
+
+static void
+test_vbox_1 ()
+{
+ style_manager sm;
+ vbox_widget w;
+ for (int i = 0; i < 5; i++)
+ w.add_child
+ (::make_unique <text_widget>
+ (styled_string::from_fmt (sm, nullptr,
+ "this is line %i", i)));
+ canvas c (w.to_canvas (sm));
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("this is line 0\n"
+ "this is line 1\n"
+ "this is line 2\n"
+ "this is line 3\n"
+ "this is line 4\n"));
+}
+
+static void
+test_vbox_2 ()
+{
+ style_manager sm;
+ vbox_widget w;
+ w.add_child (::make_unique<test_widget> (canvas::size_t (1, 3), 'A'));
+ w.add_child (::make_unique<test_widget> (canvas::size_t (4, 1), 'B'));
+ w.add_child (::make_unique<test_widget> (canvas::size_t (1, 2), 'C'));
+ canvas c (w.to_canvas (sm));
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("AAAA\n"
+ "AAAA\n"
+ "AAAA\n"
+ "BBBB\n"
+ "CCCC\n"
+ "CCCC\n"));
+}
+
+static void
+test_canvas_widget ()
+{
+ style_manager sm;
+ canvas inner_canvas (canvas::size_t (5, 3), sm);
+ inner_canvas.fill (canvas::rect_t (canvas::coord_t (0, 0),
+ canvas::size_t (5, 3)),
+ canvas::cell_t ('a'));
+ canvas_widget cw (std::move (inner_canvas));
+ canvas c (cw.to_canvas (sm));
+ ASSERT_CANVAS_STREQ
+ (c, false,
+ ("aaaaa\n"
+ "aaaaa\n"
+ "aaaaa\n"));
+}
+
+/* Run all selftests in this file. */
+
+void
+text_art_widget_cc_tests ()
+{
+ test_test_widget ();
+ test_text_widget ();
+ test_wrapper_widget ();
+ test_vbox_1 ();
+ test_vbox_2 ();
+ test_canvas_widget ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
new file mode 100644
@@ -0,0 +1,246 @@
+/* Hierarchical diagram elements.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_TEXT_ART_WIDGET_H
+#define GCC_TEXT_ART_WIDGET_H
+
+#include <vector>
+#include "text-art/canvas.h"
+#include "text-art/table.h"
+
+namespace text_art {
+
+/* Abstract base class: something that knows how to size itself and
+ how to paint itself to a canvas, potentially with children, with
+ support for hierarchical sizing and positioning.
+
+ Widgets have a two-phase sizing/positioning algorithm.
+
+ Step 1: size requests: the root widget is asked for its size request i.e
+ how big it wants to be. This is handled by recursively asking child
+ widgets for their requested sizes. Each widget subclass can implement
+ their own logic for this in the "calc_req_size" vfunc, and the result
+ is cached in m_req_size.
+
+ Step 2: rect allocation: the root widget is set a canvas::rect_t as
+ its "allocated" rectangle. Each widget subclass can then place its
+ children recursively using the "update_child_alloc_rects" vfunc.
+ For simplicity, all coordinates in the hierarchy are within the same
+ coordinate system (rather than attempting to store per-child offsets).
+
+ Widget subclasses are responsible for managing their own children. */
+
+/* Subclasses in this header, with indentation indicating inheritance. */
+
+class widget; /* Abstract base class. */
+ class wrapper_widget; /* Concrete subclass: a widget with a single child. */
+ class container_widget; /* Abstract subclass: widgets with an arbitrary
+ number of children. */
+ class vbox_widget; /* Concrete widget subclass: lay out children
+ vertically. */
+ class leaf_widget; /* Abstract subclass: a widget with no children. */
+ class text_widget; /* Concrete subclass: a text string. */
+ class canvas_widget; /* Concrete subclass: a pre-rendered canvas. */
+
+class widget
+{
+ public:
+ /* This can be very useful for debugging when implementing new
+ widget subclasses. */
+ static const bool DEBUG_GEOMETRY = false;
+
+ virtual ~widget () {}
+
+ canvas to_canvas (const style_manager &style_mgr);
+
+ canvas::size_t get_req_size ()
+ {
+ m_req_size = calc_req_size();
+ if (DEBUG_GEOMETRY)
+ fprintf (stderr, "calc_req_size (%s) -> (w:%i, h:%i)\n",
+ get_desc (),
+ m_req_size.w, m_req_size.h);
+ return m_req_size;
+ }
+
+ void set_alloc_rect (const canvas::rect_t &rect)
+ {
+ if (DEBUG_GEOMETRY)
+ fprintf (stderr, "set_alloc_rect (%s): ((x:%i, y:%i), (w:%i, h:%i))\n",
+ get_desc (),
+ rect.m_top_left.x, rect.m_top_left.y,
+ rect.m_size.w, rect.m_size.h);
+ m_alloc_rect = rect;
+ update_child_alloc_rects ();
+ }
+
+ virtual const char *get_desc () const = 0;
+ virtual canvas::size_t calc_req_size () = 0;
+ virtual void update_child_alloc_rects () = 0;
+ virtual void paint_to_canvas (canvas &canvas) = 0;
+
+ /* Access to the cached size request of this widget. */
+ const canvas::size_t get_req_size () const { return m_req_size; }
+ int get_req_w () const { return m_req_size.w; }
+ int get_req_h () const { return m_req_size.h; }
+
+ /* Access to the allocated canvas coordinates of this widget. */
+ const canvas::rect_t &get_alloc_rect () const { return m_alloc_rect; }
+ int get_alloc_w () const { return m_alloc_rect.get_width (); }
+ int get_alloc_h () const { return m_alloc_rect.get_height (); }
+ int get_min_x () const { return m_alloc_rect.get_min_x (); }
+ int get_max_x () const { return m_alloc_rect.get_max_x (); }
+ int get_next_x () const { return m_alloc_rect.get_next_x (); }
+ int get_min_y () const { return m_alloc_rect.get_min_y (); }
+ int get_max_y () const { return m_alloc_rect.get_max_y (); }
+ int get_next_y () const { return m_alloc_rect.get_max_y (); }
+ canvas::range_t get_x_range () const { return m_alloc_rect.get_x_range (); }
+ canvas::range_t get_y_range () const { return m_alloc_rect.get_y_range (); }
+ const canvas::coord_t &get_top_left () const
+ {
+ return m_alloc_rect.m_top_left;
+ }
+
+ protected:
+ widget ()
+ : m_req_size (0, 0),
+ m_alloc_rect (canvas::coord_t (0, 0),
+ canvas::size_t (0, 0))
+ {}
+
+private:
+ /* How much size this widget requested. */
+ canvas::size_t m_req_size;
+ /* Where (and how big) this widget was allocated. */
+ canvas::rect_t m_alloc_rect;
+};
+
+/* Concrete subclass for a widget with a single child. */
+
+class wrapper_widget : public widget
+{
+ public:
+ wrapper_widget (std::unique_ptr<widget> child)
+ : m_child (std::move (child))
+ {}
+
+ const char *get_desc () const override
+ {
+ return "wrapper_widget";
+ }
+ canvas::size_t calc_req_size () override
+ {
+ return m_child->get_req_size ();
+ }
+ void update_child_alloc_rects ()
+ {
+ m_child->set_alloc_rect (get_alloc_rect ());
+ }
+ void paint_to_canvas (canvas &canvas) override
+ {
+ m_child->paint_to_canvas (canvas);
+ }
+ private:
+ std::unique_ptr<widget> m_child;
+};
+
+/* Abstract subclass for widgets with an arbitrary number of children. */
+
+class container_widget : public widget
+{
+ public:
+ void add_child (std::unique_ptr<widget> child)
+ {
+ m_children.push_back (std::move (child));
+ }
+
+ void paint_to_canvas (canvas &canvas) final override
+ {
+ for (auto &child : m_children)
+ child->paint_to_canvas (canvas);
+ }
+
+ protected:
+ std::vector<std::unique_ptr<widget>> m_children;
+};
+
+/* Concrete widget subclass: lay out children vertically. */
+
+class vbox_widget : public container_widget
+{
+ public:
+ const char *get_desc () const override;
+ canvas::size_t calc_req_size () override;
+ void update_child_alloc_rects () final override;
+};
+
+/* Abstract subclass for widgets with no children. */
+
+class leaf_widget : public widget
+{
+ public:
+ void update_child_alloc_rects () final override
+ {
+ /* no-op. */
+ }
+
+ protected:
+ leaf_widget () : widget () {}
+};
+
+/* Concrete widget subclass for a text string. */
+
+class text_widget : public leaf_widget
+{
+ public:
+ text_widget (styled_string str)
+ : leaf_widget (), m_str (std::move (str))
+ {
+ }
+
+ const char *get_desc () const override;
+ canvas::size_t calc_req_size () final override;
+ void paint_to_canvas (canvas &canvas) final override;
+
+private:
+ styled_string m_str;
+};
+
+/* Concrete widget subclass for a pre-rendered canvas. */
+
+class canvas_widget : public leaf_widget
+{
+ public:
+ canvas_widget (canvas &&c)
+ : leaf_widget (), m_canvas (std::move (c))
+ {
+ }
+
+ const char *get_desc () const override;
+ canvas::size_t calc_req_size () final override;
+ void paint_to_canvas (canvas &canvas) final override;
+
+private:
+ canvas m_canvas;
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_WIDGET_H */
@@ -3147,34 +3147,26 @@ cpp_display_column_to_byte_column (const char *data, int data_length,
return dw.bytes_processed () + MAX (0, display_col - avail_display);
}
-/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc,
- because that will inspect the user's locale, and in particular in an ASCII
- locale, it will not return anything useful for extended characters. But GCC
- in other respects (see e.g. _cpp_default_encoding()) behaves as if
- everything is UTF-8. We also make some tweaks that are useful for the way
- GCC needs to use this data, e.g. tabs and other control characters should be
- treated as having width 1. The lookup tables are generated from
- contrib/unicode/gen_wcwidth.py and were made by simply calling glibc
- wcwidth() on all codepoints, then applying the small tweaks. These tables
- are not highly optimized, but for the present purpose of outputting
- diagnostics, they are sufficient. */
-
-#include "generated_cpp_wcwidth.h"
-int cpp_wcwidth (cppchar_t c)
+template <typename PropertyType>
+PropertyType
+get_cppchar_property (cppchar_t c,
+ const cppchar_t *range_ends,
+ const PropertyType *range_values,
+ size_t num_ranges,
+ PropertyType default_value)
{
- if (__builtin_expect (c <= wcwidth_range_ends[0], true))
- return wcwidth_widths[0];
+ if (__builtin_expect (c <= range_ends[0], true))
+ return range_values[0];
/* Binary search the tables. */
int begin = 1;
- static const int end
- = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends);
+ static const int end = num_ranges;
int len = end - begin;
do
{
int half = len/2;
int middle = begin + half;
- if (c > wcwidth_range_ends[middle])
+ if (c > range_ends[middle])
{
begin = middle + 1;
len -= half + 1;
@@ -3184,6 +3176,61 @@ int cpp_wcwidth (cppchar_t c)
} while (len);
if (__builtin_expect (begin != end, true))
- return wcwidth_widths[begin];
- return 1;
+ return range_values[begin];
+
+ return default_value;
+}
+
+/* Our own version of wcwidth(). We don't use the actual wcwidth() in glibc,
+ because that will inspect the user's locale, and in particular in an ASCII
+ locale, it will not return anything useful for extended characters. But GCC
+ in other respects (see e.g. _cpp_default_encoding()) behaves as if
+ everything is UTF-8. We also make some tweaks that are useful for the way
+ GCC needs to use this data, e.g. tabs and other control characters should be
+ treated as having width 1. The lookup tables are generated from
+ contrib/unicode/gen_wcwidth.py and were made by simply calling glibc
+ wcwidth() on all codepoints, then applying the small tweaks. These tables
+ are not highly optimized, but for the present purpose of outputting
+ diagnostics, they are sufficient. */
+
+#include "generated_cpp_wcwidth.h"
+
+int
+cpp_wcwidth (cppchar_t c)
+{
+ const size_t num_ranges
+ = sizeof wcwidth_range_ends / sizeof (*wcwidth_range_ends);
+ return get_cppchar_property<unsigned char > (c,
+ &wcwidth_range_ends[0],
+ &wcwidth_widths[0],
+ num_ranges,
+ 1);
+}
+
+#include "combining-chars.inc"
+
+bool
+cpp_is_combining_char (cppchar_t c)
+{
+ const size_t num_ranges
+ = sizeof combining_range_ends / sizeof (*combining_range_ends);
+ return get_cppchar_property<bool> (c,
+ &combining_range_ends[0],
+ &is_combining[0],
+ num_ranges,
+ false);
+}
+
+#include "printable-chars.inc"
+
+bool
+cpp_is_printable_char (cppchar_t c)
+{
+ const size_t num_ranges
+ = sizeof printable_range_ends / sizeof (*printable_range_ends);
+ return get_cppchar_property<bool> (c,
+ &printable_range_ends[0],
+ &is_printable[0],
+ num_ranges,
+ false);
}
new file mode 100644
@@ -0,0 +1,68 @@
+/* Generated by contrib/unicode/gen-combining-chars.py
+ using version 12.1.0 of the Unicode standard. */
+
+static const cppchar_t combining_range_ends[] = {
+ 0x2ff, 0x34e, 0x34f, 0x36f, 0x482, 0x487, 0x590, 0x5bd,
+ 0x5be, 0x5bf, 0x5c0, 0x5c2, 0x5c3, 0x5c5, 0x5c6, 0x5c7,
+ 0x60f, 0x61a, 0x64a, 0x65f, 0x66f, 0x670, 0x6d5, 0x6dc,
+ 0x6de, 0x6e4, 0x6e6, 0x6e8, 0x6e9, 0x6ed, 0x710, 0x711,
+ 0x72f, 0x74a, 0x7ea, 0x7f3, 0x7fc, 0x7fd, 0x815, 0x819,
+ 0x81a, 0x823, 0x824, 0x827, 0x828, 0x82d, 0x858, 0x85b,
+ 0x8d2, 0x8e1, 0x8e2, 0x8ff, 0x93b, 0x93c, 0x94c, 0x94d,
+ 0x950, 0x954, 0x9bb, 0x9bc, 0x9cc, 0x9cd, 0x9fd, 0x9fe,
+ 0xa3b, 0xa3c, 0xa4c, 0xa4d, 0xabb, 0xabc, 0xacc, 0xacd,
+ 0xb3b, 0xb3c, 0xb4c, 0xb4d, 0xbcc, 0xbcd, 0xc4c, 0xc4d,
+ 0xc54, 0xc56, 0xcbb, 0xcbc, 0xccc, 0xccd, 0xd3a, 0xd3c,
+ 0xd4c, 0xd4d, 0xdc9, 0xdca, 0xe37, 0xe3a, 0xe47, 0xe4b,
+ 0xeb7, 0xeba, 0xec7, 0xecb, 0xf17, 0xf19, 0xf34, 0xf35,
+ 0xf36, 0xf37, 0xf38, 0xf39, 0xf70, 0xf72, 0xf73, 0xf74,
+ 0xf79, 0xf7d, 0xf7f, 0xf80, 0xf81, 0xf84, 0xf85, 0xf87,
+ 0xfc5, 0xfc6, 0x1036, 0x1037, 0x1038, 0x103a, 0x108c, 0x108d,
+ 0x135c, 0x135f, 0x1713, 0x1714, 0x1733, 0x1734, 0x17d1, 0x17d2,
+ 0x17dc, 0x17dd, 0x18a8, 0x18a9, 0x1938, 0x193b, 0x1a16, 0x1a18,
+ 0x1a5f, 0x1a60, 0x1a74, 0x1a7c, 0x1a7e, 0x1a7f, 0x1aaf, 0x1abd,
+ 0x1b33, 0x1b34, 0x1b43, 0x1b44, 0x1b6a, 0x1b73, 0x1ba9, 0x1bab,
+ 0x1be5, 0x1be6, 0x1bf1, 0x1bf3, 0x1c36, 0x1c37, 0x1ccf, 0x1cd2,
+ 0x1cd3, 0x1ce0, 0x1ce1, 0x1ce8, 0x1cec, 0x1ced, 0x1cf3, 0x1cf4,
+ 0x1cf7, 0x1cf9, 0x1dbf, 0x1df9, 0x1dfa, 0x1dff, 0x20cf, 0x20dc,
+ 0x20e0, 0x20e1, 0x20e4, 0x20f0, 0x2cee, 0x2cf1, 0x2d7e, 0x2d7f,
+ 0x2ddf, 0x2dff, 0x3029, 0x302f, 0x3098, 0x309a, 0xa66e, 0xa66f,
+ 0xa673, 0xa67d, 0xa69d, 0xa69f, 0xa6ef, 0xa6f1, 0xa805, 0xa806,
+ 0xa8c3, 0xa8c4, 0xa8df, 0xa8f1, 0xa92a, 0xa92d, 0xa952, 0xa953,
+ 0xa9b2, 0xa9b3, 0xa9bf, 0xa9c0, 0xaaaf, 0xaab0, 0xaab1, 0xaab4,
+ 0xaab6, 0xaab8, 0xaabd, 0xaabf, 0xaac0, 0xaac1, 0xaaf5, 0xaaf6,
+ 0xabec, 0xabed, 0xfb1d, 0xfb1e, 0xfe1f, 0xfe2f, 0x101fc, 0x101fd,
+ 0x102df, 0x102e0, 0x10375, 0x1037a, 0x10a0c, 0x10a0d, 0x10a0e, 0x10a0f,
+ 0x10a37, 0x10a3a, 0x10a3e, 0x10a3f, 0x10ae4, 0x10ae6, 0x10d23, 0x10d27,
+ 0x10f45, 0x10f50, 0x11045, 0x11046, 0x1107e, 0x1107f, 0x110b8, 0x110ba,
+ 0x110ff, 0x11102, 0x11132, 0x11134, 0x11172, 0x11173, 0x111bf, 0x111c0,
+ 0x111c9, 0x111ca, 0x11234, 0x11236, 0x112e8, 0x112ea, 0x1133a, 0x1133c,
+ 0x1134c, 0x1134d, 0x11365, 0x1136c, 0x1136f, 0x11374, 0x11441, 0x11442,
+ 0x11445, 0x11446, 0x1145d, 0x1145e, 0x114c1, 0x114c3, 0x115be, 0x115c0,
+ 0x1163e, 0x1163f, 0x116b5, 0x116b7, 0x1172a, 0x1172b, 0x11838, 0x1183a,
+ 0x119df, 0x119e0, 0x11a33, 0x11a34, 0x11a46, 0x11a47, 0x11a98, 0x11a99,
+ 0x11c3e, 0x11c3f, 0x11d41, 0x11d42, 0x11d43, 0x11d45, 0x11d96, 0x11d97,
+ 0x16aef, 0x16af4, 0x16b2f, 0x16b36, 0x1bc9d, 0x1bc9e, 0x1d164, 0x1d169,
+ 0x1d16c, 0x1d172, 0x1d17a, 0x1d182, 0x1d184, 0x1d18b, 0x1d1a9, 0x1d1ad,
+ 0x1d241, 0x1d244, 0x1dfff, 0x1e006, 0x1e007, 0x1e018, 0x1e01a, 0x1e021,
+ 0x1e022, 0x1e024, 0x1e025, 0x1e02a, 0x1e12f, 0x1e136, 0x1e2eb, 0x1e2ef,
+ 0x1e8cf, 0x1e8d6, 0x1e943, 0x1e94a, 0x10fffe,
+};
+
+static const bool is_combining[] = {
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+};
@@ -1602,4 +1602,7 @@ bool cpp_input_conversion_is_trivial (const char *input_charset);
int cpp_check_utf8_bom (const char *data, size_t data_length);
bool cpp_valid_utf8_p (const char *data, size_t num_bytes);
+bool cpp_is_combining_char (cppchar_t c);
+bool cpp_is_printable_char (cppchar_t c);
+
#endif /* ! LIBCPP_CPPLIB_H */
new file mode 100644
@@ -0,0 +1,231 @@
+/* Generated by contrib/unicode/gen-printable-chars.py
+ using version 12.1.0 of the Unicode standard. */
+
+static const cppchar_t printable_range_ends[] = {
+ 0x1f, 0x7e, 0x9f, 0xac, 0xad, 0x377, 0x379, 0x37f,
+ 0x383, 0x38a, 0x38b, 0x38c, 0x38d, 0x3a1, 0x3a2, 0x52f,
+ 0x530, 0x556, 0x558, 0x58a, 0x58c, 0x58f, 0x590, 0x5c7,
+ 0x5cf, 0x5ea, 0x5ee, 0x5f4, 0x605, 0x61b, 0x61d, 0x6dc,
+ 0x6dd, 0x70d, 0x70f, 0x74a, 0x74c, 0x7b1, 0x7bf, 0x7fa,
+ 0x7fc, 0x82d, 0x82f, 0x83e, 0x83f, 0x85b, 0x85d, 0x85e,
+ 0x85f, 0x86a, 0x89f, 0x8b4, 0x8b5, 0x8bd, 0x8d2, 0x8e1,
+ 0x8e2, 0x983, 0x984, 0x98c, 0x98e, 0x990, 0x992, 0x9a8,
+ 0x9a9, 0x9b0, 0x9b1, 0x9b2, 0x9b5, 0x9b9, 0x9bb, 0x9c4,
+ 0x9c6, 0x9c8, 0x9ca, 0x9ce, 0x9d6, 0x9d7, 0x9db, 0x9dd,
+ 0x9de, 0x9e3, 0x9e5, 0x9fe, 0xa00, 0xa03, 0xa04, 0xa0a,
+ 0xa0e, 0xa10, 0xa12, 0xa28, 0xa29, 0xa30, 0xa31, 0xa33,
+ 0xa34, 0xa36, 0xa37, 0xa39, 0xa3b, 0xa3c, 0xa3d, 0xa42,
+ 0xa46, 0xa48, 0xa4a, 0xa4d, 0xa50, 0xa51, 0xa58, 0xa5c,
+ 0xa5d, 0xa5e, 0xa65, 0xa76, 0xa80, 0xa83, 0xa84, 0xa8d,
+ 0xa8e, 0xa91, 0xa92, 0xaa8, 0xaa9, 0xab0, 0xab1, 0xab3,
+ 0xab4, 0xab9, 0xabb, 0xac5, 0xac6, 0xac9, 0xaca, 0xacd,
+ 0xacf, 0xad0, 0xadf, 0xae3, 0xae5, 0xaf1, 0xaf8, 0xaff,
+ 0xb00, 0xb03, 0xb04, 0xb0c, 0xb0e, 0xb10, 0xb12, 0xb28,
+ 0xb29, 0xb30, 0xb31, 0xb33, 0xb34, 0xb39, 0xb3b, 0xb44,
+ 0xb46, 0xb48, 0xb4a, 0xb4d, 0xb55, 0xb57, 0xb5b, 0xb5d,
+ 0xb5e, 0xb63, 0xb65, 0xb77, 0xb81, 0xb83, 0xb84, 0xb8a,
+ 0xb8d, 0xb90, 0xb91, 0xb95, 0xb98, 0xb9a, 0xb9b, 0xb9c,
+ 0xb9d, 0xb9f, 0xba2, 0xba4, 0xba7, 0xbaa, 0xbad, 0xbb9,
+ 0xbbd, 0xbc2, 0xbc5, 0xbc8, 0xbc9, 0xbcd, 0xbcf, 0xbd0,
+ 0xbd6, 0xbd7, 0xbe5, 0xbfa, 0xbff, 0xc0c, 0xc0d, 0xc10,
+ 0xc11, 0xc28, 0xc29, 0xc39, 0xc3c, 0xc44, 0xc45, 0xc48,
+ 0xc49, 0xc4d, 0xc54, 0xc56, 0xc57, 0xc5a, 0xc5f, 0xc63,
+ 0xc65, 0xc6f, 0xc76, 0xc8c, 0xc8d, 0xc90, 0xc91, 0xca8,
+ 0xca9, 0xcb3, 0xcb4, 0xcb9, 0xcbb, 0xcc4, 0xcc5, 0xcc8,
+ 0xcc9, 0xccd, 0xcd4, 0xcd6, 0xcdd, 0xcde, 0xcdf, 0xce3,
+ 0xce5, 0xcef, 0xcf0, 0xcf2, 0xcff, 0xd03, 0xd04, 0xd0c,
+ 0xd0d, 0xd10, 0xd11, 0xd44, 0xd45, 0xd48, 0xd49, 0xd4f,
+ 0xd53, 0xd63, 0xd65, 0xd7f, 0xd81, 0xd83, 0xd84, 0xd96,
+ 0xd99, 0xdb1, 0xdb2, 0xdbb, 0xdbc, 0xdbd, 0xdbf, 0xdc6,
+ 0xdc9, 0xdca, 0xdce, 0xdd4, 0xdd5, 0xdd6, 0xdd7, 0xddf,
+ 0xde5, 0xdef, 0xdf1, 0xdf4, 0xe00, 0xe3a, 0xe3e, 0xe5b,
+ 0xe80, 0xe82, 0xe83, 0xe84, 0xe85, 0xe8a, 0xe8b, 0xea3,
+ 0xea4, 0xea5, 0xea6, 0xebd, 0xebf, 0xec4, 0xec5, 0xec6,
+ 0xec7, 0xecd, 0xecf, 0xed9, 0xedb, 0xedf, 0xeff, 0xf47,
+ 0xf48, 0xf6c, 0xf70, 0xf97, 0xf98, 0xfbc, 0xfbd, 0xfcc,
+ 0xfcd, 0xfda, 0xfff, 0x10c5, 0x10c6, 0x10c7, 0x10cc, 0x10cd,
+ 0x10cf, 0x1248, 0x1249, 0x124d, 0x124f, 0x1256, 0x1257, 0x1258,
+ 0x1259, 0x125d, 0x125f, 0x1288, 0x1289, 0x128d, 0x128f, 0x12b0,
+ 0x12b1, 0x12b5, 0x12b7, 0x12be, 0x12bf, 0x12c0, 0x12c1, 0x12c5,
+ 0x12c7, 0x12d6, 0x12d7, 0x1310, 0x1311, 0x1315, 0x1317, 0x135a,
+ 0x135c, 0x137c, 0x137f, 0x1399, 0x139f, 0x13f5, 0x13f7, 0x13fd,
+ 0x13ff, 0x169c, 0x169f, 0x16f8, 0x16ff, 0x170c, 0x170d, 0x1714,
+ 0x171f, 0x1736, 0x173f, 0x1753, 0x175f, 0x176c, 0x176d, 0x1770,
+ 0x1771, 0x1773, 0x177f, 0x17dd, 0x17df, 0x17e9, 0x17ef, 0x17f9,
+ 0x17ff, 0x180d, 0x180f, 0x1819, 0x181f, 0x1878, 0x187f, 0x18aa,
+ 0x18af, 0x18f5, 0x18ff, 0x191e, 0x191f, 0x192b, 0x192f, 0x193b,
+ 0x193f, 0x1940, 0x1943, 0x196d, 0x196f, 0x1974, 0x197f, 0x19ab,
+ 0x19af, 0x19c9, 0x19cf, 0x19da, 0x19dd, 0x1a1b, 0x1a1d, 0x1a5e,
+ 0x1a5f, 0x1a7c, 0x1a7e, 0x1a89, 0x1a8f, 0x1a99, 0x1a9f, 0x1aad,
+ 0x1aaf, 0x1abe, 0x1aff, 0x1b4b, 0x1b4f, 0x1b7c, 0x1b7f, 0x1bf3,
+ 0x1bfb, 0x1c37, 0x1c3a, 0x1c49, 0x1c4c, 0x1c88, 0x1c8f, 0x1cba,
+ 0x1cbc, 0x1cc7, 0x1ccf, 0x1cfa, 0x1cff, 0x1df9, 0x1dfa, 0x1f15,
+ 0x1f17, 0x1f1d, 0x1f1f, 0x1f45, 0x1f47, 0x1f4d, 0x1f4f, 0x1f57,
+ 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f7d,
+ 0x1f7f, 0x1fb4, 0x1fb5, 0x1fc4, 0x1fc5, 0x1fd3, 0x1fd5, 0x1fdb,
+ 0x1fdc, 0x1fef, 0x1ff1, 0x1ff4, 0x1ff5, 0x1ffe, 0x1fff, 0x200a,
+ 0x200f, 0x2029, 0x202e, 0x205f, 0x206f, 0x2071, 0x2073, 0x208e,
+ 0x208f, 0x209c, 0x209f, 0x20bf, 0x20cf, 0x20f0, 0x20ff, 0x218b,
+ 0x218f, 0x2426, 0x243f, 0x244a, 0x245f, 0x2b73, 0x2b75, 0x2b95,
+ 0x2b97, 0x2c2e, 0x2c2f, 0x2c5e, 0x2c5f, 0x2cf3, 0x2cf8, 0x2d25,
+ 0x2d26, 0x2d27, 0x2d2c, 0x2d2d, 0x2d2f, 0x2d67, 0x2d6e, 0x2d70,
+ 0x2d7e, 0x2d96, 0x2d9f, 0x2da6, 0x2da7, 0x2dae, 0x2daf, 0x2db6,
+ 0x2db7, 0x2dbe, 0x2dbf, 0x2dc6, 0x2dc7, 0x2dce, 0x2dcf, 0x2dd6,
+ 0x2dd7, 0x2dde, 0x2ddf, 0x2e4f, 0x2e7f, 0x2e99, 0x2e9a, 0x2ef3,
+ 0x2eff, 0x2fd5, 0x2fef, 0x2ffb, 0x2fff, 0x303f, 0x3040, 0x3096,
+ 0x3098, 0x30ff, 0x3104, 0x312f, 0x3130, 0x318e, 0x318f, 0x31ba,
+ 0x31bf, 0x31e3, 0x31ef, 0x321e, 0x321f, 0x4db5, 0x4dbf, 0x9fef,
+ 0x9fff, 0xa48c, 0xa48f, 0xa4c6, 0xa4cf, 0xa62b, 0xa63f, 0xa6f7,
+ 0xa6ff, 0xa7bf, 0xa7c1, 0xa7c6, 0xa7f6, 0xa82b, 0xa82f, 0xa839,
+ 0xa83f, 0xa877, 0xa87f, 0xa8c5, 0xa8cd, 0xa8d9, 0xa8df, 0xa953,
+ 0xa95e, 0xa97c, 0xa97f, 0xa9cd, 0xa9ce, 0xa9d9, 0xa9dd, 0xa9fe,
+ 0xa9ff, 0xaa36, 0xaa3f, 0xaa4d, 0xaa4f, 0xaa59, 0xaa5b, 0xaac2,
+ 0xaada, 0xaaf6, 0xab00, 0xab06, 0xab08, 0xab0e, 0xab10, 0xab16,
+ 0xab1f, 0xab26, 0xab27, 0xab2e, 0xab2f, 0xab67, 0xab6f, 0xabed,
+ 0xabef, 0xabf9, 0xabff, 0xd7a3, 0xd7af, 0xd7c6, 0xd7ca, 0xd7fb,
+ 0xf8ff, 0xfa6d, 0xfa6f, 0xfad9, 0xfaff, 0xfb06, 0xfb12, 0xfb17,
+ 0xfb1c, 0xfb36, 0xfb37, 0xfb3c, 0xfb3d, 0xfb3e, 0xfb3f, 0xfb41,
+ 0xfb42, 0xfb44, 0xfb45, 0xfbc1, 0xfbd2, 0xfd3f, 0xfd4f, 0xfd8f,
+ 0xfd91, 0xfdc7, 0xfdef, 0xfdfd, 0xfdff, 0xfe19, 0xfe1f, 0xfe52,
+ 0xfe53, 0xfe66, 0xfe67, 0xfe6b, 0xfe6f, 0xfe74, 0xfe75, 0xfefc,
+ 0xff00, 0xffbe, 0xffc1, 0xffc7, 0xffc9, 0xffcf, 0xffd1, 0xffd7,
+ 0xffd9, 0xffdc, 0xffdf, 0xffe6, 0xffe7, 0xffee, 0xfffb, 0xfffd,
+ 0xffff, 0x1000b, 0x1000c, 0x10026, 0x10027, 0x1003a, 0x1003b, 0x1003d,
+ 0x1003e, 0x1004d, 0x1004f, 0x1005d, 0x1007f, 0x100fa, 0x100ff, 0x10102,
+ 0x10106, 0x10133, 0x10136, 0x1018e, 0x1018f, 0x1019b, 0x1019f, 0x101a0,
+ 0x101cf, 0x101fd, 0x1027f, 0x1029c, 0x1029f, 0x102d0, 0x102df, 0x102fb,
+ 0x102ff, 0x10323, 0x1032c, 0x1034a, 0x1034f, 0x1037a, 0x1037f, 0x1039d,
+ 0x1039e, 0x103c3, 0x103c7, 0x103d5, 0x103ff, 0x1049d, 0x1049f, 0x104a9,
+ 0x104af, 0x104d3, 0x104d7, 0x104fb, 0x104ff, 0x10527, 0x1052f, 0x10563,
+ 0x1056e, 0x1056f, 0x105ff, 0x10736, 0x1073f, 0x10755, 0x1075f, 0x10767,
+ 0x107ff, 0x10805, 0x10807, 0x10808, 0x10809, 0x10835, 0x10836, 0x10838,
+ 0x1083b, 0x1083c, 0x1083e, 0x10855, 0x10856, 0x1089e, 0x108a6, 0x108af,
+ 0x108df, 0x108f2, 0x108f3, 0x108f5, 0x108fa, 0x1091b, 0x1091e, 0x10939,
+ 0x1093e, 0x1093f, 0x1097f, 0x109b7, 0x109bb, 0x109cf, 0x109d1, 0x10a03,
+ 0x10a04, 0x10a06, 0x10a0b, 0x10a13, 0x10a14, 0x10a17, 0x10a18, 0x10a35,
+ 0x10a37, 0x10a3a, 0x10a3e, 0x10a48, 0x10a4f, 0x10a58, 0x10a5f, 0x10a9f,
+ 0x10abf, 0x10ae6, 0x10aea, 0x10af6, 0x10aff, 0x10b35, 0x10b38, 0x10b55,
+ 0x10b57, 0x10b72, 0x10b77, 0x10b91, 0x10b98, 0x10b9c, 0x10ba8, 0x10baf,
+ 0x10bff, 0x10c48, 0x10c7f, 0x10cb2, 0x10cbf, 0x10cf2, 0x10cf9, 0x10d27,
+ 0x10d2f, 0x10d39, 0x10e5f, 0x10e7e, 0x10eff, 0x10f27, 0x10f2f, 0x10f59,
+ 0x10fdf, 0x10ff6, 0x10fff, 0x1104d, 0x11051, 0x1106f, 0x1107e, 0x110bc,
+ 0x110bd, 0x110c1, 0x110cf, 0x110e8, 0x110ef, 0x110f9, 0x110ff, 0x11134,
+ 0x11135, 0x11146, 0x1114f, 0x11176, 0x1117f, 0x111cd, 0x111cf, 0x111df,
+ 0x111e0, 0x111f4, 0x111ff, 0x11211, 0x11212, 0x1123e, 0x1127f, 0x11286,
+ 0x11287, 0x11288, 0x11289, 0x1128d, 0x1128e, 0x1129d, 0x1129e, 0x112a9,
+ 0x112af, 0x112ea, 0x112ef, 0x112f9, 0x112ff, 0x11303, 0x11304, 0x1130c,
+ 0x1130e, 0x11310, 0x11312, 0x11328, 0x11329, 0x11330, 0x11331, 0x11333,
+ 0x11334, 0x11339, 0x1133a, 0x11344, 0x11346, 0x11348, 0x1134a, 0x1134d,
+ 0x1134f, 0x11350, 0x11356, 0x11357, 0x1135c, 0x11363, 0x11365, 0x1136c,
+ 0x1136f, 0x11374, 0x113ff, 0x11459, 0x1145a, 0x1145b, 0x1145c, 0x1145f,
+ 0x1147f, 0x114c7, 0x114cf, 0x114d9, 0x1157f, 0x115b5, 0x115b7, 0x115dd,
+ 0x115ff, 0x11644, 0x1164f, 0x11659, 0x1165f, 0x1166c, 0x1167f, 0x116b8,
+ 0x116bf, 0x116c9, 0x116ff, 0x1171a, 0x1171c, 0x1172b, 0x1172f, 0x1173f,
+ 0x117ff, 0x1183b, 0x1189f, 0x118f2, 0x118fe, 0x118ff, 0x1199f, 0x119a7,
+ 0x119a9, 0x119d7, 0x119d9, 0x119e4, 0x119ff, 0x11a47, 0x11a4f, 0x11aa2,
+ 0x11abf, 0x11af8, 0x11bff, 0x11c08, 0x11c09, 0x11c36, 0x11c37, 0x11c45,
+ 0x11c4f, 0x11c6c, 0x11c6f, 0x11c8f, 0x11c91, 0x11ca7, 0x11ca8, 0x11cb6,
+ 0x11cff, 0x11d06, 0x11d07, 0x11d09, 0x11d0a, 0x11d36, 0x11d39, 0x11d3a,
+ 0x11d3b, 0x11d3d, 0x11d3e, 0x11d47, 0x11d4f, 0x11d59, 0x11d5f, 0x11d65,
+ 0x11d66, 0x11d68, 0x11d69, 0x11d8e, 0x11d8f, 0x11d91, 0x11d92, 0x11d98,
+ 0x11d9f, 0x11da9, 0x11edf, 0x11ef8, 0x11fbf, 0x11ff1, 0x11ffe, 0x12399,
+ 0x123ff, 0x1246e, 0x1246f, 0x12474, 0x1247f, 0x12543, 0x12fff, 0x1342e,
+ 0x143ff, 0x14646, 0x167ff, 0x16a38, 0x16a3f, 0x16a5e, 0x16a5f, 0x16a69,
+ 0x16a6d, 0x16a6f, 0x16acf, 0x16aed, 0x16aef, 0x16af5, 0x16aff, 0x16b45,
+ 0x16b4f, 0x16b59, 0x16b5a, 0x16b61, 0x16b62, 0x16b77, 0x16b7c, 0x16b8f,
+ 0x16e3f, 0x16e9a, 0x16eff, 0x16f4a, 0x16f4e, 0x16f87, 0x16f8e, 0x16f9f,
+ 0x16fdf, 0x16fe3, 0x16fff, 0x187f7, 0x187ff, 0x18af2, 0x1afff, 0x1b11e,
+ 0x1b14f, 0x1b152, 0x1b163, 0x1b167, 0x1b16f, 0x1b2fb, 0x1bbff, 0x1bc6a,
+ 0x1bc6f, 0x1bc7c, 0x1bc7f, 0x1bc88, 0x1bc8f, 0x1bc99, 0x1bc9b, 0x1bc9f,
+ 0x1cfff, 0x1d0f5, 0x1d0ff, 0x1d126, 0x1d128, 0x1d172, 0x1d17a, 0x1d1e8,
+ 0x1d1ff, 0x1d245, 0x1d2df, 0x1d2f3, 0x1d2ff, 0x1d356, 0x1d35f, 0x1d378,
+ 0x1d3ff, 0x1d454, 0x1d455, 0x1d49c, 0x1d49d, 0x1d49f, 0x1d4a1, 0x1d4a2,
+ 0x1d4a4, 0x1d4a6, 0x1d4a8, 0x1d4ac, 0x1d4ad, 0x1d4b9, 0x1d4ba, 0x1d4bb,
+ 0x1d4bc, 0x1d4c3, 0x1d4c4, 0x1d505, 0x1d506, 0x1d50a, 0x1d50c, 0x1d514,
+ 0x1d515, 0x1d51c, 0x1d51d, 0x1d539, 0x1d53a, 0x1d53e, 0x1d53f, 0x1d544,
+ 0x1d545, 0x1d546, 0x1d549, 0x1d550, 0x1d551, 0x1d6a5, 0x1d6a7, 0x1d7cb,
+ 0x1d7cd, 0x1da8b, 0x1da9a, 0x1da9f, 0x1daa0, 0x1daaf, 0x1dfff, 0x1e006,
+ 0x1e007, 0x1e018, 0x1e01a, 0x1e021, 0x1e022, 0x1e024, 0x1e025, 0x1e02a,
+ 0x1e0ff, 0x1e12c, 0x1e12f, 0x1e13d, 0x1e13f, 0x1e149, 0x1e14d, 0x1e14f,
+ 0x1e2bf, 0x1e2f9, 0x1e2fe, 0x1e2ff, 0x1e7ff, 0x1e8c4, 0x1e8c6, 0x1e8d6,
+ 0x1e8ff, 0x1e94b, 0x1e94f, 0x1e959, 0x1e95d, 0x1e95f, 0x1ec70, 0x1ecb4,
+ 0x1ed00, 0x1ed3d, 0x1edff, 0x1ee03, 0x1ee04, 0x1ee1f, 0x1ee20, 0x1ee22,
+ 0x1ee23, 0x1ee24, 0x1ee26, 0x1ee27, 0x1ee28, 0x1ee32, 0x1ee33, 0x1ee37,
+ 0x1ee38, 0x1ee39, 0x1ee3a, 0x1ee3b, 0x1ee41, 0x1ee42, 0x1ee46, 0x1ee47,
+ 0x1ee48, 0x1ee49, 0x1ee4a, 0x1ee4b, 0x1ee4c, 0x1ee4f, 0x1ee50, 0x1ee52,
+ 0x1ee53, 0x1ee54, 0x1ee56, 0x1ee57, 0x1ee58, 0x1ee59, 0x1ee5a, 0x1ee5b,
+ 0x1ee5c, 0x1ee5d, 0x1ee5e, 0x1ee5f, 0x1ee60, 0x1ee62, 0x1ee63, 0x1ee64,
+ 0x1ee66, 0x1ee6a, 0x1ee6b, 0x1ee72, 0x1ee73, 0x1ee77, 0x1ee78, 0x1ee7c,
+ 0x1ee7d, 0x1ee7e, 0x1ee7f, 0x1ee89, 0x1ee8a, 0x1ee9b, 0x1eea0, 0x1eea3,
+ 0x1eea4, 0x1eea9, 0x1eeaa, 0x1eebb, 0x1eeef, 0x1eef1, 0x1efff, 0x1f02b,
+ 0x1f02f, 0x1f093, 0x1f09f, 0x1f0ae, 0x1f0b0, 0x1f0bf, 0x1f0c0, 0x1f0cf,
+ 0x1f0d0, 0x1f0f5, 0x1f0ff, 0x1f10c, 0x1f10f, 0x1f16c, 0x1f16f, 0x1f1ac,
+ 0x1f1e5, 0x1f202, 0x1f20f, 0x1f23b, 0x1f23f, 0x1f248, 0x1f24f, 0x1f251,
+ 0x1f25f, 0x1f265, 0x1f2ff, 0x1f6d5, 0x1f6df, 0x1f6ec, 0x1f6ef, 0x1f6fa,
+ 0x1f6ff, 0x1f773, 0x1f77f, 0x1f7d8, 0x1f7df, 0x1f7eb, 0x1f7ff, 0x1f80b,
+ 0x1f80f, 0x1f847, 0x1f84f, 0x1f859, 0x1f85f, 0x1f887, 0x1f88f, 0x1f8ad,
+ 0x1f8ff, 0x1f90b, 0x1f90c, 0x1f971, 0x1f972, 0x1f976, 0x1f979, 0x1f9a2,
+ 0x1f9a4, 0x1f9aa, 0x1f9ad, 0x1f9ca, 0x1f9cc, 0x1fa53, 0x1fa5f, 0x1fa6d,
+ 0x1fa6f, 0x1fa73, 0x1fa77, 0x1fa7a, 0x1fa7f, 0x1fa82, 0x1fa8f, 0x1fa95,
+ 0x1ffff, 0x2a6d6, 0x2a6ff, 0x2b734, 0x2b73f, 0x2b81d, 0x2b81f, 0x2cea1,
+ 0x2ceaf, 0x2ebe0, 0x2f7ff, 0x2fa1d, 0xe00ff, 0xe01ef, 0x10fffe,
+};
+
+static const bool is_printable[] = {
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
+};