diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
index cfb59e1ee4ea..d1535703e0e9 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -277,7 +277,9 @@ function_point::print_source_line (pretty_printer *pp) const
   // TODO: monospace font
   debug_diagnostic_context tmp_dc;
   gcc_rich_location richloc (stmt->location);
-  diagnostic_show_locus (&tmp_dc, &richloc, DK_ERROR);
+  diagnostic_source_print_policy source_policy (tmp_dc);
+  gcc_assert (pp);
+  source_policy.print (*pp, richloc, DK_ERROR, nullptr);
   pp_string (pp, pp_formatted_text (tmp_dc.m_printer));
 }
 
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index cf900a9ecba2..b4c1f13ee671 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -117,7 +117,8 @@ json_from_expanded_location (diagnostic_context &context, location_t loc)
   for (int i = 0; i != ARRAY_SIZE (column_fields); ++i)
     {
       context.m_column_unit = column_fields[i].unit;
-      const int col = context.converted_column (exploc);
+      diagnostic_column_policy col_policy (context);
+      const int col = col_policy.converted_column (exploc);
       result->set_integer (column_fields[i].name, col);
       if (column_fields[i].unit == orig_unit)
 	the_column = col;
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 11b224c4c90d..ec3e7f9a2d88 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -1903,8 +1903,9 @@ sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
       rich_location my_rich_loc (m_richloc);
       my_rich_loc.set_escape_on_output (true);
 
+      diagnostic_source_print_policy source_policy (dc);
       dc.set_escape_format (m_escape_format);
-      diagnostic_show_locus (&dc, &my_rich_loc, DK_ERROR);
+      source_policy.print (*dc.m_printer, my_rich_loc, DK_ERROR, nullptr);
 
       std::unique_ptr<sarif_multiformat_message_string> result
 	= builder.make_multiformat_message_string
diff --git a/gcc/diagnostic-format-text.cc b/gcc/diagnostic-format-text.cc
index 16bf34130475..a6592fe93e6c 100644
--- a/gcc/diagnostic-format-text.cc
+++ b/gcc/diagnostic-format-text.cc
@@ -263,10 +263,12 @@ label_text
 diagnostic_text_output_format::
 get_location_text (const expanded_location &s) const
 {
-  return get_context ().get_location_text (s, pp_show_color (get_printer ()));
+  diagnostic_column_policy column_policy (get_context ());
+  return column_policy.get_location_text (s,
+					  show_column_p (),
+					  pp_show_color (get_printer ()));
 }
 
-
 /* Helpers for writing lang-specific starters/finalizers for text output.  */
 
 /* Return a formatted line and column ':%line:%column'.  Elided if
@@ -294,7 +296,6 @@ maybe_line_and_column (int line, int col)
 void
 diagnostic_text_output_format::report_current_module (location_t where)
 {
-  const diagnostic_context &context = get_context ();
   pretty_printer *pp = get_printer ();
   const line_map_ordinary *map = NULL;
 
@@ -329,7 +330,7 @@ diagnostic_text_output_format::report_current_module (location_t where)
 	      if (first && show_column_p ())
 		{
 		  s.column = SOURCE_COLUMN (map, where);
-		  col = context.converted_column (s);
+		  col = get_column_policy ().converted_column (s);
 		}
 	      const char *line_col = maybe_line_and_column (s.line, col);
 	      static const char *const msgs[] =
diff --git a/gcc/diagnostic-format-text.h b/gcc/diagnostic-format-text.h
index 5361687e7203..aacd699cd90a 100644
--- a/gcc/diagnostic-format-text.h
+++ b/gcc/diagnostic-format-text.h
@@ -34,6 +34,7 @@ class diagnostic_text_output_format : public diagnostic_output_format
 public:
   diagnostic_text_output_format (diagnostic_context &context)
   : diagnostic_output_format (context),
+    m_column_policy (context),
     m_last_module (nullptr),
     m_includes_seen (nullptr)
   {}
@@ -62,6 +63,12 @@ public:
 
   bool show_column_p () const { return get_context ().m_show_column; }
 
+  const diagnostic_column_policy &get_column_policy () const
+  {
+    return m_column_policy;
+  }
+  diagnostic_location_print_policy get_location_print_policy () const;
+
 private:
   void print_any_cwe (const diagnostic_info &diagnostic);
   void print_any_rules (const diagnostic_info &diagnostic);
@@ -73,6 +80,8 @@ private:
 
   void show_any_path (const diagnostic_info &diagnostic);
 
+  diagnostic_column_policy m_column_policy;
+
   /* Used to detect when the input file stack has changed since last
      described.  */
   const line_map_ordinary *m_last_module;
diff --git a/gcc/diagnostic-path.cc b/gcc/diagnostic-path.cc
index bef56b1d9d12..3f7d4fb50156 100644
--- a/gcc/diagnostic-path.cc
+++ b/gcc/diagnostic-path.cc
@@ -218,6 +218,29 @@ void debug (diagnostic_path *path)
 
 namespace {
 
+/* A bundle of state for printing a path.  */
+
+class path_print_policy
+{
+public:
+  path_print_policy (const diagnostic_text_output_format &text_output)
+  : m_source_policy (text_output.get_context ())
+  {
+  }
+
+  text_art::theme *
+  get_diagram_theme () const
+  {
+    return  m_source_policy.get_diagram_theme ();
+  }
+
+  const diagnostic_source_print_policy &
+  get_source_policy () const { return m_source_policy; }
+
+private:
+  diagnostic_source_print_policy m_source_policy;
+};
+
 /* Subclass of range_label for showing a particular event
    when showing a consecutive run of events within a diagnostic_path as
    labelled ranges within one gcc_rich_location.  */
@@ -557,7 +580,7 @@ struct event_range
     return result;
   }
 
-  bool maybe_add_event (const diagnostic_context &ctxt,
+  bool maybe_add_event (const path_print_policy &policy,
 			const diagnostic_event &new_ev,
 			unsigned new_ev_idx,
 			bool check_rich_locations)
@@ -590,7 +613,8 @@ struct event_range
 
     /* Potentially verify that the locations are sufficiently close.  */
     if (check_rich_locations)
-      if (!m_richloc.add_location_if_nearby (ctxt, new_ev.get_location (),
+      if (!m_richloc.add_location_if_nearby (policy.get_source_policy (),
+					     new_ev.get_location (),
 					     false, &m_path_label))
 	return false;
 
@@ -605,10 +629,10 @@ struct event_range
   }
 
   /* Print the events in this range to PP, typically as a single
-     call to the printer's diagnostic_show_locus.  */
+     call to diagnostic_show_locus.  */
 
-  void print (diagnostic_text_output_format &text_output,
-	      pretty_printer *pp,
+  void print (pretty_printer &pp,
+	      diagnostic_text_output_format &text_output,
 	      diagnostic_source_effect_info *effect_info)
   {
     location_t initial_loc = m_initial_event.get_location ();
@@ -624,7 +648,10 @@ struct event_range
 	  = linemap_client_expand_location_to_spelling_point
 	  (line_table, initial_loc, LOCATION_ASPECT_CARET);
 	if (exploc.file != LOCATION_FILE (dc.m_last_location))
-	  diagnostic_start_span (&dc) (&dc, exploc);
+	  {
+	    diagnostic_location_print_policy loc_policy (text_output);
+	    diagnostic_start_span (&dc) (loc_policy, &pp, exploc);
+	  }
       }
 
     /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the
@@ -641,14 +668,14 @@ struct event_range
 	    const diagnostic_event &iter_event = m_path.get_event (i);
 	    diagnostic_event_id_t event_id (i);
 	    label_text event_text (iter_event.get_desc (true));
-	    pp_printf (pp, " %@: %s", &event_id, event_text.get ());
-	    pp_newline (pp);
+	    pp_printf (&pp, " %@: %s", &event_id, event_text.get ());
+	    pp_newline (&pp);
 	  }
 	return;
       }
 
     /* Call diagnostic_show_locus to show the events using labels.  */
-    diagnostic_show_locus (&dc, &m_richloc, DK_DIAGNOSTIC_PATH, pp,
+    diagnostic_show_locus (&dc, &m_richloc, DK_DIAGNOSTIC_PATH, &pp,
 			   effect_info);
 
     /* If we have a macro expansion, show the expansion to the user.  */
@@ -680,9 +707,10 @@ struct event_range
 
 struct path_summary
 {
-  path_summary (diagnostic_text_output_format &text_output,
+  path_summary (const path_print_policy &policy,
 		const diagnostic_path &path,
 		bool check_rich_locations,
+		bool colorize = false,
 		bool show_event_links = true);
 
   unsigned get_num_ranges () const { return m_ranges.length (); }
@@ -742,9 +770,10 @@ per_thread_summary::interprocedural_p () const
 
 /* path_summary's ctor.  */
 
-path_summary::path_summary (diagnostic_text_output_format &text_output,
+path_summary::path_summary (const path_print_policy &policy,
 			    const diagnostic_path &path,
 			    bool check_rich_locations,
+			    bool colorize,
 			    bool show_event_links)
 {
   const unsigned num_events = path.num_events ();
@@ -760,13 +789,12 @@ path_summary::path_summary (diagnostic_text_output_format &text_output,
       pts.update_depth_limits (event.get_stack_depth ());
 
       if (cur_event_range)
-	if (cur_event_range->maybe_add_event (text_output.get_context (),
+	if (cur_event_range->maybe_add_event (policy,
 					      event,
 					      idx, check_rich_locations))
 	  continue;
 
-      const bool colorize = pp_show_color (text_output.get_printer ());
-      auto theme = text_output.get_diagram_theme ();
+      auto theme = policy.get_diagram_theme ();
       const bool allow_emojis = theme ? theme->emojis_p (): false;
       cur_event_range = new event_range (path, idx, event, pts,
 					 show_event_links,
@@ -829,6 +857,7 @@ public:
 				  event_range *range,
 				  diagnostic_source_effect_info *effect_info)
   {
+    gcc_assert (pp);
     const char *const line_color = "path";
     const char *start_line_color
       = colorize_start (pp_show_color (pp), line_color);
@@ -906,7 +935,7 @@ public:
 	}
 	pp_set_prefix (pp, prefix);
 	pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-	range->print (text_output, pp, effect_info);
+	range->print (*pp, text_output, effect_info);
 	pp_set_prefix (pp, saved_prefix);
 
 	write_indent (pp, m_cur_indent + per_frame_indent);
@@ -916,7 +945,7 @@ public:
 	pp_newline (pp);
       }
     else
-      range->print (text_output, pp, effect_info);
+      range->print (*pp, text_output, effect_info);
 
     if (const event_range *next_range = get_any_next_range ())
       {
@@ -1126,9 +1155,17 @@ diagnostic_text_output_format::print_path (const diagnostic_path &path)
     case DPF_INLINE_EVENTS:
       {
 	/* Consolidate related events.  */
-	path_summary summary (*this, path, true,
-			      get_context ().m_source_printing.show_event_links_p);
+	path_print_policy policy (*this);
 	pretty_printer *const pp = get_printer ();
+	const bool check_rich_locations = true;
+	const bool colorize = pp_show_color (pp);
+	const bool show_event_links
+	  = get_context ().m_source_printing.show_event_links_p;
+	path_summary summary (policy,
+			      path,
+			      check_rich_locations,
+			      colorize,
+			      show_event_links);
 	char *saved_prefix = pp_take_prefix (pp);
 	pp_set_prefix (pp, NULL);
 	print_path_summary_as_text (summary, *this,
@@ -1175,7 +1212,8 @@ test_empty_path (pretty_printer *event_pp)
 
   test_diagnostic_context dc;
   diagnostic_text_output_format text_output (dc);
-  path_summary summary (text_output, path, false);
+  path_print_policy policy (text_output);
+  path_summary summary (policy, path, false);
   ASSERT_EQ (summary.get_num_ranges (), 0);
 
   print_path_summary_as_text (summary, text_output, true);
@@ -1197,7 +1235,8 @@ test_intraprocedural_path (pretty_printer *event_pp)
 
   test_diagnostic_context dc;
   diagnostic_text_output_format text_output (dc);
-  path_summary summary (text_output, path, false);
+  path_print_policy policy (text_output);
+  path_summary summary (policy, path, false, false, false);
   ASSERT_EQ (summary.get_num_ranges (), 1);
 
   print_path_summary_as_text (summary, text_output, true);
@@ -1233,7 +1272,8 @@ test_interprocedural_path_1 (pretty_printer *event_pp)
   {
     test_diagnostic_context dc;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, false);
     ASSERT_EQ (summary.get_num_ranges (), 9);
 
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
@@ -1294,7 +1334,8 @@ test_interprocedural_path_1 (pretty_printer *event_pp)
     test_diagnostic_context dc;
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, false);
     print_path_summary_as_text (summary, text_output, true);
     ASSERT_STREQ
       ("  `test': events 1-2 (depth 0)\n"
@@ -1370,7 +1411,8 @@ test_interprocedural_path_2 (pretty_printer *event_pp)
   {
     test_diagnostic_context dc;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, false);
     ASSERT_EQ (summary.get_num_ranges (), 5);
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
     print_path_summary_as_text (summary, text_output, true);
@@ -1406,7 +1448,8 @@ test_interprocedural_path_2 (pretty_printer *event_pp)
     test_diagnostic_context dc;
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, false);
     print_path_summary_as_text (summary, text_output, true);
     ASSERT_STREQ
       ("  `foo': events 1-2 (depth 0)\n"
@@ -1457,7 +1500,8 @@ test_recursion (pretty_printer *event_pp)
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
 
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, false);
     ASSERT_EQ (summary.get_num_ranges (), 4);
 
     print_path_summary_as_text (summary, text_output, true);
@@ -1488,7 +1532,8 @@ test_recursion (pretty_printer *event_pp)
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
 
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, false);
     print_path_summary_as_text (summary, text_output, true);
     ASSERT_STREQ
       ("  `factorial': events 1-2 (depth 0)\n"
@@ -1609,7 +1654,8 @@ test_control_flow_1 (const line_table_case &case_,
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
     dc.m_source_printing.show_event_links_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1634,7 +1680,8 @@ test_control_flow_1 (const line_table_case &case_,
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
     dc.m_source_printing.show_event_links_p = false;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1657,7 +1704,8 @@ test_control_flow_1 (const line_table_case &case_,
     dc.m_source_printing.show_line_numbers_p = true;
     dc.m_source_printing.show_event_links_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1683,7 +1731,8 @@ test_control_flow_1 (const line_table_case &case_,
     dc.m_source_printing.show_line_numbers_p = true;
     dc.m_source_printing.show_event_links_p = false;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1705,7 +1754,8 @@ test_control_flow_1 (const line_table_case &case_,
     dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
     dc.m_source_printing.show_event_links_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1731,7 +1781,8 @@ test_control_flow_1 (const line_table_case &case_,
     dc.m_source_printing.show_event_links_p = true;
     dc.m_source_printing.show_line_numbers_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1801,7 +1852,8 @@ test_control_flow_2 (const line_table_case &case_,
     dc.m_source_printing.show_event_links_p = true;
     dc.m_source_printing.show_line_numbers_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
@@ -1887,7 +1939,8 @@ test_control_flow_3 (const line_table_case &case_,
     dc.m_source_printing.show_event_links_p = true;
     dc.m_source_printing.show_line_numbers_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-2\n"
@@ -1944,7 +1997,8 @@ assert_cfg_edge_path_streq (const location &loc,
   dc.m_source_printing.show_event_links_p = true;
   dc.m_source_printing.show_line_numbers_p = true;
   diagnostic_text_output_format text_output (dc);
-  path_summary summary (text_output, path, true);
+  path_print_policy policy (text_output);
+  path_summary summary (policy, path, true);
   print_path_summary_as_text (summary, text_output, false);
   ASSERT_STREQ_AT (loc, expected_str,
 		   pp_formatted_text (text_output.get_printer ()));
@@ -2267,7 +2321,8 @@ test_control_flow_5 (const line_table_case &case_,
     dc.m_source_printing.show_event_links_p = true;
     dc.m_source_printing.show_line_numbers_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-5\n"
@@ -2355,7 +2410,8 @@ test_control_flow_6 (const line_table_case &case_,
     dc.m_source_printing.show_event_links_p = true;
     dc.m_source_printing.show_line_numbers_p = true;
     diagnostic_text_output_format text_output (dc);
-    path_summary summary (text_output, path, true);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, path, true);
     print_path_summary_as_text (summary, text_output, false);
     ASSERT_STREQ
       ("  events 1-3\n"
diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index c7460051dbc6..a1d66cf493d6 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -82,14 +82,14 @@ struct point_state
 
    The class also has responsibility for tracking which of the above is
    active, filtering out unnecessary changes.  This allows
-   layout::print_source_line and layout::print_annotation_line
+   layout_printer::print_source_line and layout_printer::print_annotation_line
    to simply request a colorization code for *every* character they print,
    via this class, and have the filtering be done for them here.  */
 
 class colorizer
 {
  public:
-  colorizer (pretty_printer *pp,
+  colorizer (pretty_printer &pp,
 	     const rich_location &richloc,
 	     diagnostic_t diagnostic_kind);
   ~colorizer ();
@@ -97,7 +97,7 @@ class colorizer
   void set_range (int range_idx)
   {
     /* If we have a specific highlight color for the range, use it.  */
-    if (pp_show_highlight_colors (m_pp))
+    if (pp_show_highlight_colors (&m_pp))
       {
 	const location_range *const loc_range = m_richloc.get_range (range_idx);
 	if (loc_range->m_highlight_color)
@@ -134,7 +134,7 @@ class colorizer
   static const int STATE_FIXIT_DELETE  = -3;
   static const int STATE_NAMED_COLOR  = -4;
 
-  pretty_printer *m_pp;
+  pretty_printer &m_pp;
   const rich_location &m_richloc;
   diagnostic_t m_diagnostic_kind;
   int m_current_state;
@@ -383,18 +383,19 @@ struct char_display_policy : public cpp_char_column_policy
 /* A class to control the overall layout when printing a diagnostic.
 
    The layout is determined within the constructor.
-   It is then printed by repeatedly calling the "print_source_line",
-   "print_annotation_line" and "print_any_fixits" methods.
+
+   Printing the layout is handled by class layout_printer.  This separation
+   is to avoid depending on the pretty_printer in the layout.
 
    We assume we have disjoint ranges.  */
 
 class layout
 {
  public:
-  layout (const diagnostic_context &context,
+  friend class layout_printer;
+
+  layout (const diagnostic_source_print_policy &source_policy,
 	  const rich_location &richloc,
-	  diagnostic_t diagnostic_kind,
-	  pretty_printer *pp,
 	  diagnostic_source_effect_info *effect_info = nullptr);
 
   bool maybe_add_location_range (const location_range *loc_range,
@@ -407,57 +408,35 @@ class layout
   int get_linenum_width () const { return m_linenum_width; }
   int get_x_offset_display () const { return m_x_offset_display; }
 
-  void print_gap_in_line_numbering ();
   bool print_heading_for_line_span_index_p (int line_span_idx) const;
 
   expanded_location get_expanded_location (const line_span *) const;
 
-  void print_line (linenum_type row);
-
-  void print_any_right_to_left_edge_lines ();
-
   void on_bad_codepoint (const char *ptr, cppchar_t ch, size_t ch_sz);
 
-  void update_any_effects () const;
-
  private:
   bool will_show_line_p (linenum_type row) const;
-  void print_leading_fixits (linenum_type row);
-  line_bounds print_source_line (linenum_type row, const char *line,
-				 int line_bytes);
   bool should_print_annotation_line_p (linenum_type row) const;
-  void print_leftmost_column ();
-  void start_annotation_line (char margin_char = ' ');
-  void print_annotation_line (linenum_type row, const line_bounds lbounds);
-  void print_any_labels (linenum_type row);
-  void print_trailing_fixits (linenum_type row);
 
   bool annotation_line_showed_range_p (linenum_type line, int start_column,
 				       int finish_column) const;
-  void show_ruler (int max_column);
-
   bool validate_fixit_hint_p (const fixit_hint *hint);
 
   void calculate_line_spans ();
   void calculate_linenum_width ();
   void calculate_x_offset_display ();
 
-  void print_newline ();
-
   bool
   get_state_at_point (/* Inputs.  */
 		      linenum_type row, int column,
 		      int first_non_ws, int last_non_ws,
 		      enum column_unit col_unit,
 		      /* Outputs.  */
-		      point_state *out_state);
+		      point_state *out_state) const;
 
   int
   get_x_bound_for_row (linenum_type row, int caret_column,
-		       int last_non_ws);
-
-  void
-  move_to_column (int *column, int dest_column, bool add_left_margin);
+		       int last_non_ws) const;
 
  private:
   bool compatible_locations_p (location_t loc_a, location_t loc_b) const;
@@ -465,21 +444,63 @@ class layout
   const diagnostic_source_printing_options &m_options;
   const line_maps *m_line_table;
   file_cache &m_file_cache;
-  pretty_printer *m_pp;
   const text_art::ascii_theme m_fallback_theme;
   const text_art::theme &m_theme;
   diagnostic_source_effect_info *m_effect_info;
-  char_display_policy m_policy;
+  char_display_policy m_char_policy;
   location_t m_primary_loc;
   exploc_with_display_col m_exploc;
-  colorizer m_colorizer;
-  bool m_diagnostic_path_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
   int m_linenum_width;
   int m_x_offset_display;
   bool m_escape_on_output;
+};
+
+/* A bundle of state for printing a particular layout
+   to a particular pretty_printer.  */
+
+class layout_printer
+{
+public:
+  layout_printer (pretty_printer &pp,
+		  const layout &layout,
+		  const rich_location &richloc,
+		  diagnostic_t diagnostic_kind);
+
+  void print (const diagnostic_source_print_policy &source_policy);
+
+private:
+  const diagnostic_source_printing_options &
+  get_options () const { return m_layout.m_options; }
+
+  const text_art::theme &
+  get_theme () const { return m_layout.m_theme; }
+
+  void show_ruler (int max_column);
+  void print_gap_in_line_numbering ();
+  void print_leading_fixits (linenum_type row);
+  void print_line (linenum_type row);
+  line_bounds print_source_line (linenum_type row, const char *line,
+				 int line_bytes);
+  void print_leftmost_column ();
+  void start_annotation_line (char margin_char = ' ');
+  void print_annotation_line (linenum_type row, const line_bounds lbounds);
+  void print_any_labels (linenum_type row);
+  void print_trailing_fixits (linenum_type row);
+  void print_newline ();
+
+  void
+  move_to_column (int *column, int dest_column, bool add_left_margin);
+
+  void print_any_right_to_left_edge_lines ();
+
+private:
+  pretty_printer &m_pp;
+  const layout &m_layout;
+  colorizer m_colorizer;
+  bool m_is_diagnostic_path;
 
   /* Fields for handling links between labels (e.g. for showing CFG edges
      in execution paths).
@@ -536,7 +557,7 @@ class layout
 /* The constructor for "colorizer".  Lookup and store color codes for the
    different kinds of things we might need to print.  */
 
-colorizer::colorizer (pretty_printer *pp,
+colorizer::colorizer (pretty_printer &pp,
 		      const rich_location &richloc,
 		      diagnostic_t diagnostic_kind) :
   m_pp (pp),
@@ -548,7 +569,7 @@ colorizer::colorizer (pretty_printer *pp,
   m_range2 = get_color_by_name ("range2");
   m_fixit_insert = get_color_by_name ("fixit-insert");
   m_fixit_delete = get_color_by_name ("fixit-delete");
-  m_stop_color = colorize_stop (pp_show_color (m_pp));
+  m_stop_color = colorize_stop (pp_show_color (&m_pp));
 }
 
 /* The destructor for "colorize".  If colorization is on, print a code to
@@ -567,7 +588,7 @@ colorizer::set_named_color (const char *color)
 {
   finish_state (m_current_state);
   m_current_state = STATE_NAMED_COLOR;
-  pp_string (m_pp, colorize_start (pp_show_color (m_pp), color));
+  pp_string (&m_pp, colorize_start (pp_show_color (&m_pp), color));
 }
 
 /* Update state, printing color codes if necessary if there's a state
@@ -595,11 +616,11 @@ colorizer::begin_state (int state)
       break;
 
     case STATE_FIXIT_INSERT:
-      pp_string (m_pp, m_fixit_insert);
+      pp_string (&m_pp, m_fixit_insert);
       break;
 
     case STATE_FIXIT_DELETE:
-      pp_string (m_pp, m_fixit_delete);
+      pp_string (&m_pp, m_fixit_delete);
       break;
 
     case STATE_NAMED_COLOR:
@@ -610,24 +631,24 @@ colorizer::begin_state (int state)
       /* Make range 0 be the same color as the "kind" text
 	 (error vs warning vs note).  */
       pp_string
-	(m_pp,
-	 colorize_start (pp_show_color (m_pp),
+	(&m_pp,
+	 colorize_start (pp_show_color (&m_pp),
 			 diagnostic_get_color_for_kind (m_diagnostic_kind)));
       break;
 
     case 1:
-      pp_string (m_pp, m_range1);
+      pp_string (&m_pp, m_range1);
       break;
 
     case 2:
-      pp_string (m_pp, m_range2);
+      pp_string (&m_pp, m_range2);
       break;
 
     default:
       /* For ranges beyond 2, alternate between color 1 and color 2.  */
       {
 	gcc_assert (state > 2);
-	pp_string (m_pp,
+	pp_string (&m_pp,
 		   state % 2 ? m_range1 : m_range2);
       }
       break;
@@ -640,7 +661,7 @@ void
 colorizer::finish_state (int state)
 {
   if (state != STATE_NORMAL_TEXT)
-    pp_string (m_pp, m_stop_color);
+    pp_string (&m_pp, m_stop_color);
 }
 
 /* Get the color code for NAME (or the empty string if
@@ -649,7 +670,7 @@ colorizer::finish_state (int state)
 const char *
 colorizer::get_color_by_name (const char *name)
 {
-  return colorize_start (pp_show_color (m_pp), name);
+  return colorize_start (pp_show_color (&m_pp), name);
 }
 
 /* Implementation of class layout_range.  */
@@ -1275,22 +1296,24 @@ escape_as_unicode_print (pretty_printer *pp,
     }
 }
 
-/* Populate a char_display_policy based on DC and RICHLOC.  */
+/* Populate a char_display_policy based on SOURCE_POLICY and RICHLOC.  */
 
 static char_display_policy
-make_policy (const diagnostic_context &dc,
-	     const rich_location &richloc)
+make_char_policy (const diagnostic_source_print_policy &source_policy,
+		  const rich_location &richloc)
 {
   /* The default is to not escape non-ASCII bytes.  */
   char_display_policy result
-    (dc.m_tabstop, cpp_wcwidth, default_print_decoded_ch);
+    (source_policy.get_column_policy ().get_tabstop (),
+     cpp_wcwidth,
+     default_print_decoded_ch);
 
   /* If the diagnostic suggests escaping non-ASCII bytes, then
      use policy from user-supplied options.  */
   if (richloc.escape_on_output_p ())
     {
       result.m_undecoded_byte_width = width_per_escaped_byte;
-      switch (dc.get_escape_format ())
+      switch (source_policy.get_escape_format ())
 	{
 	default:
 	  gcc_unreachable ();
@@ -1319,41 +1342,29 @@ make_policy (const diagnostic_context &dc,
    Determine m_x_offset_display, to ensure that the primary caret
    will fit within the max_width provided by the diagnostic_context.  */
 
-layout::layout (const diagnostic_context &context,
+layout::layout (const diagnostic_source_print_policy &source_policy,
 		const rich_location &richloc,
-		diagnostic_t diagnostic_kind,
-		pretty_printer *pp,
 		diagnostic_source_effect_info *effect_info)
-: m_options (context.m_source_printing),
+: m_options (source_policy.get_options ()),
   m_line_table (richloc.get_line_table ()),
-  m_file_cache (context.get_file_cache ()),
-  m_pp (pp ? pp : context.m_printer),
+  m_file_cache (source_policy.get_file_cache ()),
   /* Ensure we have a non-null m_theme. */
-  m_theme (context.get_diagram_theme ()
-	   ? *context.get_diagram_theme ()
+  m_theme (source_policy.get_diagram_theme ()
+	   ? *source_policy.get_diagram_theme ()
 	   : *static_cast <const text_art::theme *> (&m_fallback_theme)),
   m_effect_info (effect_info),
-  m_policy (make_policy (context, richloc)),
+  m_char_policy (make_char_policy (source_policy, richloc)),
   m_primary_loc (richloc.get_range (0)->m_loc),
   m_exploc (m_file_cache,
-	    richloc.get_expanded_location (0), m_policy,
+	    richloc.get_expanded_location (0), m_char_policy,
 	    LOCATION_ASPECT_CARET),
-  m_colorizer (m_pp, richloc, diagnostic_kind),
-  m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
   m_layout_ranges (richloc.get_num_locations ()),
   m_fixit_hints (richloc.get_num_fixit_hints ()),
   m_line_spans (1 + richloc.get_num_locations ()),
   m_linenum_width (0),
   m_x_offset_display (0),
-  m_escape_on_output (richloc.escape_on_output_p ()),
-  m_link_lhs_state (link_lhs_state::none),
-  m_link_rhs_column (-1)
+  m_escape_on_output (richloc.escape_on_output_p ())
 {
-  if (m_options.show_event_links_p)
-    if (effect_info)
-      if (effect_info->m_leading_in_edge_column)
-	m_link_rhs_column = effect_info->m_leading_in_edge_column;
-
   for (unsigned int idx = 0; idx < richloc.get_num_locations (); idx++)
     {
       /* This diagnostic printer can only cope with "sufficiently sane" ranges.
@@ -1378,9 +1389,6 @@ layout::layout (const diagnostic_context &context,
   calculate_line_spans ();
   calculate_linenum_width ();
   calculate_x_offset_display ();
-
-  if (m_options.show_ruler_p)
-    show_ruler (m_x_offset_display + m_options.max_width);
 }
 
 
@@ -1448,14 +1456,14 @@ layout::maybe_add_location_range (const location_range *loc_range,
   /* Everything is now known to be in the correct source file,
      but it may require further sanitization.  */
   layout_range ri (exploc_with_display_col (m_file_cache,
-					    start, m_policy,
+					    start, m_char_policy,
 					    LOCATION_ASPECT_START),
 		   exploc_with_display_col (m_file_cache,
-					    finish, m_policy,
+					    finish, m_char_policy,
 					    LOCATION_ASPECT_FINISH),
 		   range_display_kind,
 		   exploc_with_display_col (m_file_cache,
-					    caret, m_policy,
+					    caret, m_char_policy,
 					    LOCATION_ASPECT_CARET),
 		   original_idx, loc_range->m_label);
 
@@ -1523,16 +1531,16 @@ layout::will_show_line_p (linenum_type row) const
    between two line spans.  */
 
 void
-layout::print_gap_in_line_numbering ()
+layout_printer::print_gap_in_line_numbering ()
 {
-  gcc_assert (m_options.show_line_numbers_p);
+  gcc_assert (m_layout.m_options.show_line_numbers_p);
 
-  pp_emit_prefix (m_pp);
+  pp_emit_prefix (&m_pp);
 
-  for (int i = 0; i < m_linenum_width + 1; i++)
-    pp_character (m_pp, '.');
+  for (int i = 0; i < m_layout.get_linenum_width () + 1; i++)
+    pp_character (&m_pp, '.');
 
-  pp_newline (m_pp);
+  pp_newline (&m_pp);
 }
 
 /* Return true iff we should print a heading when starting the
@@ -1790,7 +1798,7 @@ layout::calculate_x_offset_display ()
     = get_line_bytes_without_trailing_whitespace (line.get_buffer (),
 						  line.length ());
   int eol_display_column
-    = cpp_display_width (line.get_buffer (), line_bytes, m_policy);
+    = cpp_display_width (line.get_buffer (), line_bytes, m_char_policy);
   if (caret_display_column > eol_display_column
       || !caret_display_column)
     {
@@ -1847,17 +1855,17 @@ layout::calculate_x_offset_display ()
    both byte and display column units.  */
 
 line_bounds
-layout::print_source_line (linenum_type row, const char *line, int line_bytes)
+layout_printer::print_source_line (linenum_type row, const char *line, int line_bytes)
 {
   m_colorizer.set_normal_text ();
 
-  pp_emit_prefix (m_pp);
-  if (m_options.show_line_numbers_p)
+  pp_emit_prefix (&m_pp);
+  if (m_layout.m_options.show_line_numbers_p)
     {
       int width = num_digits (row);
-      for (int i = 0; i < m_linenum_width - width; i++)
-	pp_space (m_pp);
-      pp_printf (m_pp, "%i |", row);
+      for (int i = 0; i < m_layout.get_linenum_width () - width; i++)
+	pp_space (&m_pp);
+      pp_printf (&m_pp, "%i |", row);
     }
 
   print_leftmost_column ();
@@ -1869,7 +1877,7 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes)
   /* This object helps to keep track of which display column we are at, which is
      necessary for computing the line bounds in display units, for doing
      tab expansion, and for implementing m_x_offset_display.  */
-  cpp_display_width_computation dw (line, line_bytes, m_policy);
+  cpp_display_width_computation dw (line, line_bytes, m_layout.m_char_policy);
 
   /* Skip the first m_x_offset_display display columns.  In case the leading
      portion that will be skipped ends with a character with wcwidth > 1, then
@@ -1878,9 +1886,10 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes)
      character to be skipped over; the tab is effectively replaced by the
      correct number of trailing spaces needed to offset by the desired number of
      display columns.  */
-  for (int skipped_display_cols = dw.advance_display_cols (m_x_offset_display);
-       skipped_display_cols > m_x_offset_display; --skipped_display_cols)
-    pp_space (m_pp);
+  for (int skipped_display_cols
+	 = dw.advance_display_cols (m_layout.m_x_offset_display);
+       skipped_display_cols > m_layout.m_x_offset_display; --skipped_display_cols)
+    pp_space (&m_pp);
 
   /* Print the line and compute the line_bounds.  */
   line_bounds lbounds;
@@ -1898,15 +1907,15 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes)
 	 For frontends that only generate carets, we don't colorize the
 	 characters above them, since this would look strange (e.g.
 	 colorizing just the first character in a token).  */
-      if (m_options.colorize_source_p)
+      if (m_layout.m_options.colorize_source_p)
 	{
 	  bool in_range_p;
 	  point_state state;
 	  const int start_byte_col = dw.bytes_processed () + 1;
-	  in_range_p = get_state_at_point (row, start_byte_col,
-					   0, INT_MAX,
-					   CU_BYTES,
-					   &state);
+	  in_range_p = m_layout.get_state_at_point (row, start_byte_col,
+						    0, INT_MAX,
+						    CU_BYTES,
+						    &state);
 	  if (in_range_p)
 	    m_colorizer.set_range (state.range_idx);
 	  else
@@ -1924,7 +1933,7 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes)
 	  /* The returned display width is the number of spaces into which the
 	     tab should be expanded.  */
 	  for (int i = 0; i != this_display_width; ++i)
-	    pp_space (m_pp);
+	    pp_space (&m_pp);
 	  continue;
 	}
 
@@ -1938,7 +1947,7 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes)
 	}
 
       /* Output the character.  */
-      m_policy.m_print_cb (m_pp, cp);
+      m_layout.m_char_policy.m_print_cb (&m_pp, cp);
       c = dw.next_byte ();
     }
   print_newline ();
@@ -1967,9 +1976,9 @@ layout::should_print_annotation_line_p (linenum_type row) const
    links between labels (e.g. for CFG edges in execution paths).  */
 
 void
-layout::print_leftmost_column ()
+layout_printer::print_leftmost_column ()
 {
-  if (!m_options.show_event_links_p)
+  if (!get_options ().show_event_links_p)
     gcc_assert (m_link_lhs_state == link_lhs_state::none);
 
   switch (m_link_lhs_state)
@@ -1977,32 +1986,32 @@ layout::print_leftmost_column ()
     default:
       gcc_unreachable ();
     case link_lhs_state::none:
-      pp_space (m_pp);
+      pp_space (&m_pp);
       break;
     case link_lhs_state::rewinding_to_lhs:
       {
 	m_colorizer.set_cfg_edge ();
-	const cppchar_t ch= m_theme.get_cppchar
+	const cppchar_t ch = get_theme ().get_cppchar
 	  (text_art::theme::cell_kind::CFG_FROM_LEFT_TO_DOWN);
-	pp_unicode_character (m_pp, ch);
+	pp_unicode_character (&m_pp, ch);
 	m_colorizer.set_normal_text ();
       }
       break;
     case link_lhs_state::at_lhs:
       {
 	m_colorizer.set_cfg_edge ();
-	const cppchar_t ch= m_theme.get_cppchar
+	const cppchar_t ch = get_theme ().get_cppchar
 	  (text_art::theme::cell_kind::CFG_DOWN);
-	pp_unicode_character (m_pp, ch);
+	pp_unicode_character (&m_pp, ch);
 	m_colorizer.set_normal_text ();
       }
       break;
     case link_lhs_state::indenting_to_dest:
       {
 	m_colorizer.set_cfg_edge ();
-	const cppchar_t ch= m_theme.get_cppchar
+	const cppchar_t ch = get_theme ().get_cppchar
 	  (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_RIGHT);
-	pp_unicode_character (m_pp, ch);
+	pp_unicode_character (&m_pp, ch);
 	m_colorizer.set_normal_text ();
       }
       break;
@@ -2015,46 +2024,48 @@ layout::print_leftmost_column ()
    showing links between labels (e.g. for CFG edges in execution paths).  */
 
 void
-layout::start_annotation_line (char margin_char)
+layout_printer::start_annotation_line (char margin_char)
 {
-  pp_emit_prefix (m_pp);
-  if (m_options.show_line_numbers_p)
+  pp_emit_prefix (&m_pp);
+  if (get_options ().show_line_numbers_p)
     {
       /* Print the margin.  If MARGIN_CHAR != ' ', then print up to 3
 	 of it, right-aligned, padded with spaces.  */
       int i;
-      for (i = 0; i < m_linenum_width - 3; i++)
-	pp_space (m_pp);
-      for (; i < m_linenum_width; i++)
-	pp_character (m_pp, margin_char);
-      pp_string (m_pp, " |");
+      for (i = 0; i < m_layout.m_linenum_width - 3; i++)
+	pp_space (&m_pp);
+      for (; i < m_layout.m_linenum_width; i++)
+	pp_character (&m_pp, margin_char);
+      pp_string (&m_pp, " |");
     }
   if (margin_char == ' ')
     print_leftmost_column ();
   else
-    pp_character (m_pp, margin_char);
+    pp_character (&m_pp, margin_char);
 }
 
 /* Print a line consisting of the caret/underlines for the given
    source line.  */
 
 void
-layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
+layout_printer::print_annotation_line (linenum_type row,
+				       const line_bounds lbounds)
 {
-  int x_bound = get_x_bound_for_row (row, m_exploc.m_display_col,
-				     lbounds.m_last_non_ws_disp_col);
+  int x_bound = m_layout.get_x_bound_for_row (row,
+					      m_layout.m_exploc.m_display_col,
+					      lbounds.m_last_non_ws_disp_col);
 
   start_annotation_line ();
 
-  for (int column = 1 + m_x_offset_display; column < x_bound; column++)
+  for (int column = 1 + m_layout.m_x_offset_display; column < x_bound; column++)
     {
       bool in_range_p;
       point_state state;
-      in_range_p = get_state_at_point (row, column,
-				       lbounds.m_first_non_ws_disp_col,
-				       lbounds.m_last_non_ws_disp_col,
-				       CU_DISPLAY_COLS,
-				       &state);
+      in_range_p = m_layout.get_state_at_point (row, column,
+						lbounds.m_first_non_ws_disp_col,
+						lbounds.m_last_non_ws_disp_col,
+						CU_DISPLAY_COLS,
+						&state);
       if (in_range_p)
 	{
 	  /* Within a range.  Draw either the caret or an underline.  */
@@ -2064,19 +2075,19 @@ layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
 	      /* Draw the caret.  */
 	      char caret_char;
 	      if (state.range_idx < rich_location::STATICALLY_ALLOCATED_RANGES)
-		caret_char = m_options.caret_chars[state.range_idx];
+		caret_char = get_options ().caret_chars[state.range_idx];
 	      else
 		caret_char = '^';
-	      pp_character (m_pp, caret_char);
+	      pp_character (&m_pp, caret_char);
 	    }
 	  else
-	    pp_character (m_pp, '~');
+	    pp_character (&m_pp, '~');
 	}
       else
 	{
 	  /* Not in a range.  */
 	  m_colorizer.set_normal_text ();
-	  pp_character (m_pp, ' ');
+	  pp_character (&m_pp, ' ');
 	}
     }
   print_newline ();
@@ -2159,7 +2170,7 @@ public:
 
 /* Print any labels in this row.  */
 void
-layout::print_any_labels (linenum_type row)
+layout_printer::print_any_labels (linenum_type row)
 {
   int i;
   auto_vec<line_label> labels;
@@ -2167,7 +2178,7 @@ layout::print_any_labels (linenum_type row)
   /* Gather the labels that are to be printed into "labels".  */
   {
     layout_range *range;
-    FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+    FOR_EACH_VEC_ELT (m_layout.m_layout_ranges, i, range)
       {
 	/* Most ranges don't have labels, so reject this first.  */
 	if (range->m_label == NULL)
@@ -2180,7 +2191,7 @@ layout::print_any_labels (linenum_type row)
 	/* Reject labels that aren't fully visible due to clipping
 	   by m_x_offset_display.  */
 	const int disp_col = range->m_caret.m_columns[CU_DISPLAY_COLS];
-	if (disp_col <= m_x_offset_display)
+	if (disp_col <= m_layout.m_x_offset_display)
 	  continue;
 
 	label_text text;
@@ -2257,7 +2268,7 @@ layout::print_any_labels (linenum_type row)
 	  }
 
 	label->m_label_line = max_label_line;
-	if (m_options.show_event_links_p)
+	if (get_options ().show_event_links_p)
 	  if (label->m_has_in_edge)
 	    label_line_with_in_edge = max_label_line;
 	next_column = label->m_column;
@@ -2274,12 +2285,12 @@ layout::print_any_labels (linenum_type row)
       {
 	if (label_line == label_line_with_in_edge)
 	  {
-	    gcc_assert (m_options.show_event_links_p);
+	    gcc_assert (get_options ().show_event_links_p);
 	    m_link_lhs_state = link_lhs_state::indenting_to_dest;
 	  }
 	start_annotation_line ();
 
-	int column = 1 + m_x_offset_display;
+	int column = 1 + m_layout.m_x_offset_display;
 	line_label *label;
 	FOR_EACH_VEC_ELT (labels, i, label)
 	  {
@@ -2298,18 +2309,18 @@ layout::print_any_labels (linenum_type row)
 		       .|+----------->(10) ...to here
 		       . ^~~~~~~~~~~~~
 		       . this text.  */
-		    gcc_assert (m_options.show_event_links_p);
+		    gcc_assert (get_options ().show_event_links_p);
 		    m_colorizer.set_cfg_edge ();
-		    const cppchar_t right= m_theme.get_cppchar
+		    const cppchar_t right= get_theme ().get_cppchar
 		      (text_art::theme::cell_kind::CFG_RIGHT);
 		    while (column < label->m_column - 1)
 		      {
-			pp_unicode_character (m_pp, right);
+			pp_unicode_character (&m_pp, right);
 			column++;
 		      }
 		    if (column == label->m_column - 1)
 		      {
-			pp_character (m_pp, '>');
+			pp_character (&m_pp, '>');
 			column++;
 		      }
 		    m_colorizer.set_normal_text ();
@@ -2321,12 +2332,12 @@ layout::print_any_labels (linenum_type row)
 		gcc_assert (column == label->m_column);
 		/* Colorize the text, unless it's for events in a
 		   diagnostic_path.  */
-		if (!m_diagnostic_path_p)
+		if (!m_is_diagnostic_path)
 		  m_colorizer.set_range (label->m_state_idx);
-		pp_string (m_pp, label->m_text.m_buffer);
+		pp_string (&m_pp, label->m_text.m_buffer);
 		m_colorizer.set_normal_text ();
 		column += label->m_display_width;
-		if (m_options.show_event_links_p && label->m_has_out_edge)
+		if (get_options ().show_event_links_p && label->m_has_out_edge)
 		  {
 		    /* Print a suffix showing the start of a linkage
 		       to another label e.g. " ->-+" which will be the
@@ -2335,16 +2346,16 @@ layout::print_any_labels (linenum_type row)
 		       .                                         |
 		       .                                         |
 		       .  */
-		    const cppchar_t right= m_theme.get_cppchar
+		    const cppchar_t right= get_theme ().get_cppchar
 		      (text_art::theme::cell_kind::CFG_RIGHT);
-		    const cppchar_t from_right_to_down= m_theme.get_cppchar
+		    const cppchar_t from_right_to_down= get_theme ().get_cppchar
 		      (text_art::theme::cell_kind::CFG_FROM_RIGHT_TO_DOWN);
 		    m_colorizer.set_cfg_edge ();
-		    pp_space (m_pp);
-		    pp_unicode_character (m_pp, right);
-		    pp_unicode_character (m_pp, '>');
-		    pp_unicode_character (m_pp, right);
-		    pp_unicode_character (m_pp, from_right_to_down);
+		    pp_space (&m_pp);
+		    pp_unicode_character (&m_pp, right);
+		    pp_unicode_character (&m_pp, '>');
+		    pp_unicode_character (&m_pp, right);
+		    pp_unicode_character (&m_pp, from_right_to_down);
 		    m_colorizer.set_normal_text ();
 		    column += 5;
 		    m_link_rhs_column = column - 1;
@@ -2355,7 +2366,7 @@ layout::print_any_labels (linenum_type row)
 		gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
 		m_colorizer.set_range (label->m_state_idx);
-		pp_character (m_pp, '|');
+		pp_character (&m_pp, '|');
 		m_colorizer.set_normal_text ();
 		column++;
 	      }
@@ -2367,9 +2378,9 @@ layout::print_any_labels (linenum_type row)
 	  {
 	    move_to_column (&column, m_link_rhs_column, true);
 	    m_colorizer.set_cfg_edge ();
-	    const cppchar_t down= m_theme.get_cppchar
+	    const cppchar_t down= get_theme ().get_cppchar
 	      (text_art::theme::cell_kind::CFG_DOWN);
-	    pp_unicode_character (m_pp, down);
+	    pp_unicode_character (&m_pp, down);
 	    m_colorizer.set_normal_text ();
 	  }
 
@@ -2381,13 +2392,13 @@ layout::print_any_labels (linenum_type row)
      annotation line showing the vertical line.  */
   if (m_link_rhs_column != -1)
     {
-      int column = 1 + m_x_offset_display;
+      int column = 1 + m_layout.m_x_offset_display;
       start_annotation_line ();
       move_to_column (&column, m_link_rhs_column, true);
       m_colorizer.set_cfg_edge ();
-      const cppchar_t down= m_theme.get_cppchar
+      const cppchar_t down= get_theme ().get_cppchar
 	(text_art::theme::cell_kind::CFG_DOWN);
-      pp_unicode_character (m_pp, down);
+      pp_unicode_character (&m_pp, down);
       m_colorizer.set_normal_text ();
       print_newline ();
     }
@@ -2407,11 +2418,11 @@ layout::print_any_labels (linenum_type row)
    itself, with a leading '+'.  */
 
 void
-layout::print_leading_fixits (linenum_type row)
+layout_printer::print_leading_fixits (linenum_type row)
 {
-  for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
+  for (unsigned int i = 0; i < m_layout.m_fixit_hints.length (); i++)
     {
-      const fixit_hint *hint = m_fixit_hints[i];
+      const fixit_hint *hint = m_layout.m_fixit_hints[i];
 
       if (!hint->ends_with_newline_p ())
 	/* Not a newline fixit; print it in print_trailing_fixits.  */
@@ -2419,7 +2430,9 @@ layout::print_leading_fixits (linenum_type row)
 
       gcc_assert (hint->insertion_p ());
 
-      if (hint->affects_line_p (m_line_table, m_exploc.file, row))
+      if (hint->affects_line_p (m_layout.m_line_table,
+				m_layout.m_exploc.file,
+				row))
 	{
 	  /* Printing the '+' with normal colorization
 	     and the inserted line with "insert" colorization
@@ -2432,9 +2445,9 @@ layout::print_leading_fixits (linenum_type row)
 	     We have to print the newline separately to avoid
 	     getting additional pp prefixes printed.  */
 	  for (size_t i = 0; i < hint->get_length () - 1; i++)
-	    pp_character (m_pp, hint->get_string ()[i]);
+	    pp_character (&m_pp, hint->get_string ()[i]);
 	  m_colorizer.set_normal_text ();
-	  pp_newline (m_pp);
+	  pp_newline (&m_pp);
 	}
     }
 }
@@ -2865,27 +2878,30 @@ line_corrections::add_hint (const fixit_hint *hint)
    in layout::print_leading_fixits.  */
 
 void
-layout::print_trailing_fixits (linenum_type row)
+layout_printer::print_trailing_fixits (linenum_type row)
 {
   /* Build a list of correction instances for the line,
      potentially consolidating hints (for the sake of readability).  */
-  line_corrections corrections (m_file_cache, m_policy, m_exploc.file, row);
-  for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
+  line_corrections corrections (m_layout.m_file_cache, m_layout.m_char_policy,
+				m_layout.m_exploc.file, row);
+  for (unsigned int i = 0; i < m_layout.m_fixit_hints.length (); i++)
     {
-      const fixit_hint *hint = m_fixit_hints[i];
+      const fixit_hint *hint = m_layout.m_fixit_hints[i];
 
       /* Newline fixits are handled by layout::print_leading_fixits.  */
       if (hint->ends_with_newline_p ())
 	continue;
 
-      if (hint->affects_line_p (m_line_table, m_exploc.file, row))
+      if (hint->affects_line_p (m_layout.m_line_table,
+				m_layout.m_exploc.file,
+				row))
 	corrections.add_hint (hint);
     }
 
   /* Now print the corrections.  */
   unsigned i;
   correction *c;
-  int column = 1 + m_x_offset_display;
+  int column = 1 + m_layout.m_x_offset_display;
 
   if (!corrections.m_corrections.is_empty ())
     start_annotation_line ();
@@ -2899,7 +2915,7 @@ layout::print_trailing_fixits (linenum_type row)
 	  int start_column = c->m_printed_columns.start;
 	  move_to_column (&column, start_column, true);
 	  m_colorizer.set_fixit_insert ();
-	  pp_string (m_pp, c->m_text);
+	  pp_string (&m_pp, c->m_text);
 	  m_colorizer.set_normal_text ();
 	  column += c->m_display_cols;
 	}
@@ -2911,14 +2927,14 @@ layout::print_trailing_fixits (linenum_type row)
 	     Always show it for removals.  */
 	  int start_column = c->m_affected_columns.start;
 	  int finish_column = c->m_affected_columns.finish;
-	  if (!annotation_line_showed_range_p (row, start_column,
-					       finish_column)
+	  if (!m_layout.annotation_line_showed_range_p (row, start_column,
+							finish_column)
 	      || c->m_byte_length == 0)
 	    {
 	      move_to_column (&column, start_column, true);
 	      m_colorizer.set_fixit_delete ();
 	      for (; column <= finish_column; column++)
-		pp_character (m_pp, '-');
+		pp_character (&m_pp, '-');
 	      m_colorizer.set_normal_text ();
 	    }
 	  /* Print the replacement text.  REPLACE also covers
@@ -2928,7 +2944,7 @@ layout::print_trailing_fixits (linenum_type row)
 	    {
 	      move_to_column (&column, start_column, true);
 	      m_colorizer.set_fixit_insert ();
-	      pp_string (m_pp, c->m_text);
+	      pp_string (&m_pp, c->m_text);
 	      m_colorizer.set_normal_text ();
 	      column += c->m_display_cols;
 	    }
@@ -2936,16 +2952,16 @@ layout::print_trailing_fixits (linenum_type row)
     }
 
   /* Add a trailing newline, if necessary.  */
-  move_to_column (&column, 1 + m_x_offset_display, false);
+  move_to_column (&column, 1 + m_layout.m_x_offset_display, false);
 }
 
 /* Disable any colorization and emit a newline.  */
 
 void
-layout::print_newline ()
+layout_printer::print_newline ()
 {
   m_colorizer.set_normal_text ();
-  pp_newline (m_pp);
+  pp_newline (&m_pp);
 }
 
 /* Return true if (ROW/COLUMN) is within a range of the layout.
@@ -2960,7 +2976,7 @@ layout::get_state_at_point (/* Inputs.  */
 			    int first_non_ws, int last_non_ws,
 			    enum column_unit col_unit,
 			    /* Outputs.  */
-			    point_state *out_state)
+			    point_state *out_state) const
 {
   layout_range *range;
   int i;
@@ -3008,7 +3024,7 @@ layout::get_state_at_point (/* Inputs.  */
 
 int
 layout::get_x_bound_for_row (linenum_type row, int caret_column,
-			     int last_non_ws_column)
+			     int last_non_ws_column) const
 {
   int result = caret_column + 1;
 
@@ -3045,7 +3061,9 @@ layout::get_x_bound_for_row (linenum_type row, int caret_column,
    left margin after any newline.  */
 
 void
-layout::move_to_column (int *column, int dest_column, bool add_left_margin)
+layout_printer::move_to_column (int *column,
+				int dest_column,
+				bool add_left_margin)
 {
   /* Start a new line if we need to.  */
   if (*column > dest_column)
@@ -3053,16 +3071,16 @@ layout::move_to_column (int *column, int dest_column, bool add_left_margin)
       print_newline ();
       if (add_left_margin)
 	start_annotation_line ();
-      *column = 1 + m_x_offset_display;
+      *column = 1 + m_layout.m_x_offset_display;
     }
 
   while (*column < dest_column)
     {
       /* For debugging column issues, it can be helpful to replace this
 	 pp_space call with
-	   pp_character (m_pp, '0' + (*column % 10));
+	   pp_character (&m_pp, '0' + (*column % 10));
 	 to visualize the changing value of "*column".  */
-      pp_space (m_pp);
+      pp_space (&m_pp);
       (*column)++;
     }
 }
@@ -3071,34 +3089,40 @@ layout::move_to_column (int *column, int dest_column, bool add_left_margin)
    (after the 1-column indent).  */
 
 void
-layout::show_ruler (int max_column)
+layout_printer::show_ruler (int max_column)
 {
   /* Hundreds.  */
   if (max_column > 99)
     {
       start_annotation_line ();
-      for (int column = 1 + m_x_offset_display; column <= max_column; column++)
+      for (int column = 1 + m_layout.m_x_offset_display;
+	   column <= max_column;
+	   ++column)
 	if (column % 10 == 0)
-	  pp_character (m_pp, '0' + (column / 100) % 10);
+	  pp_character (&m_pp, '0' + (column / 100) % 10);
 	else
-	  pp_space (m_pp);
-      pp_newline (m_pp);
+	  pp_space (&m_pp);
+      pp_newline (&m_pp);
     }
 
   /* Tens.  */
   start_annotation_line ();
-  for (int column = 1 + m_x_offset_display; column <= max_column; column++)
+  for (int column = 1 + m_layout.m_x_offset_display;
+       column <= max_column;
+       ++column)
     if (column % 10 == 0)
-      pp_character (m_pp, '0' + (column / 10) % 10);
+      pp_character (&m_pp, '0' + (column / 10) % 10);
     else
-      pp_space (m_pp);
-  pp_newline (m_pp);
+      pp_space (&m_pp);
+  pp_newline (&m_pp);
 
   /* Units.  */
   start_annotation_line ();
-  for (int column = 1 + m_x_offset_display; column <= max_column; column++)
-    pp_character (m_pp, '0' + (column % 10));
-  pp_newline (m_pp);
+  for (int column = 1 + m_layout.m_x_offset_display;
+       column <= max_column;
+       ++column)
+    pp_character (&m_pp, '0' + (column % 10));
+  pp_newline (&m_pp);
 }
 
 /* Print leading fix-its (for new lines inserted before the source line)
@@ -3106,9 +3130,10 @@ layout::show_ruler (int max_column)
    consisting of any caret/underlines, then any fixits.
    If the source line can't be read, print nothing.  */
 void
-layout::print_line (linenum_type row)
+layout_printer::print_line (linenum_type row)
 {
-  char_span line = m_file_cache.get_source_line (m_exploc.file, row);
+  char_span line
+    = m_layout.m_file_cache.get_source_line (m_layout.m_exploc.file, row);
   if (!line)
     return;
 
@@ -3116,9 +3141,9 @@ layout::print_line (linenum_type row)
   print_leading_fixits (row);
   const line_bounds lbounds
     = print_source_line (row, line.get_buffer (), line.length ());
-  if (should_print_annotation_line_p (row))
+  if (m_layout.should_print_annotation_line_p (row))
     print_annotation_line (row, lbounds);
-  if (m_options.show_labels_p)
+  if (get_options ().show_labels_p)
     print_any_labels (row);
   print_trailing_fixits (row);
 }
@@ -3130,39 +3155,40 @@ layout::print_line (linenum_type row)
    at the bottom left.  */
 
 void
-layout::print_any_right_to_left_edge_lines ()
+layout_printer::print_any_right_to_left_edge_lines ()
 {
   if (m_link_rhs_column == -1)
     /* Can also happen if the out-edge had UNKNOWN_LOCATION.  */
     return;
 
-  gcc_assert (m_options.show_event_links_p);
+  gcc_assert (get_options ().show_event_links_p);
 
   /* Print the line with "|".  */
   start_annotation_line ();
-  int column = 1 + m_x_offset_display;
+  int column = 1 + m_layout.m_x_offset_display;
   move_to_column (&column, m_link_rhs_column, true);
   m_colorizer.set_cfg_edge ();
-  const cppchar_t down= m_theme.get_cppchar
+  const cppchar_t down= get_theme ().get_cppchar
     (text_art::theme::cell_kind::CFG_DOWN);
-  pp_unicode_character (m_pp, down);
+  pp_unicode_character (&m_pp, down);
   m_colorizer.set_normal_text ();
-  pp_newline (m_pp);
+  pp_newline (&m_pp);
 
   /* Print the line with "┌──────────────────────────────────────────┘".  */
   m_link_lhs_state = link_lhs_state::rewinding_to_lhs;
   start_annotation_line ();
   m_colorizer.set_cfg_edge ();
-  const cppchar_t left= m_theme.get_cppchar
+  const cppchar_t left= get_theme ().get_cppchar
     (text_art::theme::cell_kind::CFG_LEFT);
-  for (int column = 1 + m_x_offset_display; column < m_link_rhs_column;
-       column++)
-    pp_unicode_character (m_pp, left);
-  const cppchar_t from_down_to_left = m_theme.get_cppchar
+  for (int column = 1 + m_layout.m_x_offset_display;
+       column < m_link_rhs_column;
+       ++column)
+    pp_unicode_character (&m_pp, left);
+  const cppchar_t from_down_to_left = get_theme ().get_cppchar
     (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_LEFT);
-  pp_unicode_character (m_pp, from_down_to_left);
+  pp_unicode_character (&m_pp, from_down_to_left);
   m_colorizer.set_normal_text ();
-  pp_newline (m_pp);
+  pp_newline (&m_pp);
 
   /* We now have a link line on the LHS,
      and no longer have one on the RHS.  */
@@ -3170,14 +3196,21 @@ layout::print_any_right_to_left_edge_lines ()
   m_link_rhs_column = -1;
 }
 
-/* Update this layout's m_effect_info (if any) after printing this
-   layout.  */
-
-void
-layout::update_any_effects () const
+layout_printer::layout_printer (pretty_printer &pp,
+				const layout &layout,
+				const rich_location &richloc,
+				diagnostic_t diagnostic_kind)
+: m_pp (pp),
+  m_layout (layout),
+  m_colorizer (m_pp, richloc, diagnostic_kind),
+  m_is_diagnostic_path (diagnostic_kind == DK_DIAGNOSTIC_PATH),
+  m_link_lhs_state (link_lhs_state::none),
+  m_link_rhs_column (-1)
 {
-  if (m_effect_info)
-    m_effect_info->m_trailing_out_edge_column = m_link_rhs_column;
+  if (get_options ().show_event_links_p)
+    if (auto effect_info = m_layout.m_effect_info)
+      if (effect_info->m_leading_in_edge_column)
+	m_link_rhs_column = effect_info->m_leading_in_edge_column;
 }
 
 } /* End of anonymous namespace.  */
@@ -3187,18 +3220,20 @@ layout::update_any_effects () const
 
    Otherwise return false.
 
-   Use CTXT for determining how spans of lines would be printed.  */
+   Use POLICY for determining how spans of lines would be printed.  */
 
 bool
-gcc_rich_location::add_location_if_nearby (const diagnostic_context &ctxt,
-					   location_t loc,
-					   bool restrict_to_current_line_spans,
-					   const range_label *label)
+gcc_rich_location::
+add_location_if_nearby (const diagnostic_source_print_policy &policy,
+			location_t loc,
+			bool restrict_to_current_line_spans,
+			const range_label *label)
 {
   /* Use the layout location-handling logic to sanitize LOC,
      filtering it to the current line spans within a temporary
      layout instance.  */
-  layout layout (ctxt, *this, DK_ERROR, nullptr);
+
+  layout layout (policy, *this);
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
@@ -3211,7 +3246,20 @@ gcc_rich_location::add_location_if_nearby (const diagnostic_context &ctxt,
   return true;
 }
 
-/* As per diagnostic_context::show_locus, but don't print anything
+bool
+gcc_rich_location::
+add_location_if_nearby (const diagnostic_context &dc,
+			location_t loc,
+			bool restrict_to_current_line_spans,
+			const range_label *label)
+{
+  diagnostic_source_print_policy source_policy (dc);
+  return add_location_if_nearby (source_policy, loc,
+				 restrict_to_current_line_spans, label);
+}
+
+
+/* As per diagnostic_source_print_policy::print, but don't print anything
    if source printing is disabled, or if the location hasn't changed.  */
 
 void
@@ -3239,41 +3287,69 @@ diagnostic_context::maybe_show_locus (const rich_location &richloc,
 
   m_last_location = loc;
 
-  show_locus (richloc, diagnostic_kind, pp, effects);
+  if (!pp)
+    pp = m_printer;
+  gcc_assert (pp);
+
+  diagnostic_source_print_policy source_policy (*this);
+  source_policy.print (*pp, richloc, diagnostic_kind, effects);
+}
+
+diagnostic_source_print_policy::
+diagnostic_source_print_policy (const diagnostic_context &dc)
+: m_options (dc.m_source_printing),
+  m_location_policy (dc),
+  m_start_span_cb (dc.m_text_callbacks.m_start_span),
+  m_file_cache (dc.get_file_cache ()),
+  m_diagram_theme (dc.get_diagram_theme ()),
+  m_escape_format (dc.get_escape_format ())
+{
 }
 
-/* Print the physical source code corresponding to the location of
-   this diagnostic, with additional annotations.
-   If PP is non-null, then use it rather than this context's printer.
+/* Print to PP the physical source code corresponding to the location(s)
+   in RICHLOC, with additional annotations, as if for a diagnostic of the
+   given DIAGNOSTIC_KIND.
    If EFFECTS is non-null, then use and update it.  */
 
 void
-diagnostic_context::show_locus (const rich_location &richloc,
-				diagnostic_t diagnostic_kind,
-				pretty_printer *pp,
-				diagnostic_source_effect_info *effects)
+diagnostic_source_print_policy::print (pretty_printer &pp,
+				       const rich_location &richloc,
+				       diagnostic_t diagnostic_kind,
+				       diagnostic_source_effect_info *effects)
+  const
+{
+  layout layout (*this, richloc, effects);
+  layout_printer lp (pp, layout, richloc, diagnostic_kind);
+  lp.print (*this);
+}
+
+void
+layout_printer::print (const diagnostic_source_print_policy &source_policy)
 {
-  layout layout (*this, richloc, diagnostic_kind, pp, effects);
+  if (get_options ().show_ruler_p)
+    show_ruler (m_layout.m_x_offset_display + get_options ().max_width);
 
-  for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
+  for (int line_span_idx = 0; line_span_idx < m_layout.get_num_line_spans ();
        line_span_idx++)
     {
-      const line_span *line_span = layout.get_line_span (line_span_idx);
-      if (m_source_printing.show_line_numbers_p)
+      const line_span *line_span = m_layout.get_line_span (line_span_idx);
+      if (get_options ().show_line_numbers_p)
 	{
 	  /* With line numbers, we should show whenever the line-numbering
 	     "jumps".  */
 	  if (line_span_idx > 0)
-	    layout.print_gap_in_line_numbering ();
+	    print_gap_in_line_numbering ();
 	}
       else
 	{
 	  /* Without line numbers, we print headings for some line spans.  */
-	  if (layout.print_heading_for_line_span_index_p (line_span_idx))
+	  if (m_layout.print_heading_for_line_span_index_p (line_span_idx))
 	    {
 	      expanded_location exploc
-		= layout.get_expanded_location (line_span);
-	      m_text_callbacks.m_start_span (this, exploc);
+		= m_layout.get_expanded_location (line_span);
+	      const diagnostic_location_print_policy &
+		loc_policy = source_policy.get_location_policy ();
+	      source_policy.get_start_span_fn () (loc_policy, &m_pp, exploc);
 	    }
 	}
       /* Iterate over the lines within this span (using linenum_arith_t to
@@ -3281,10 +3357,11 @@ diagnostic_context::show_locus (const rich_location &richloc,
       linenum_arith_t last_line = line_span->get_last_line ();
       for (linenum_arith_t row = line_span->get_first_line ();
 	   row <= last_line; row++)
-	layout.print_line (row);
+	print_line (row);
     }
 
-  layout.update_any_effects ();
+  if (auto effect_info = m_layout.m_effect_info)
+    effect_info->m_trailing_out_edge_column = m_link_rhs_column;
 }
 
 #if CHECKING_P
@@ -3305,6 +3382,16 @@ diagnostic_show_locus_fixture (const line_table_case &case_,
 	       m_tmp_source_file.get_filename (), 1);
 }
 
+/* Populate a char_display_policy based on DC and RICHLOC.  */
+
+static char_display_policy
+make_char_policy (const diagnostic_context &dc,
+		  const rich_location &richloc)
+{
+  diagnostic_source_print_policy source_policy (dc);
+  return ::make_char_policy (source_policy, richloc);
+}
+
 /* Verify that cpp_display_width correctly handles escaping.  */
 
 static void
@@ -3324,7 +3411,7 @@ test_display_widths ()
   /* No escaping.  */
   {
     test_diagnostic_context dc;
-    char_display_policy policy (make_policy (dc, richloc));
+    char_display_policy policy (make_char_policy (dc, richloc));
     ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 1);
     ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 2);
     ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 1);
@@ -3336,7 +3423,7 @@ test_display_widths ()
   {
     test_diagnostic_context dc;
     dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
-    char_display_policy policy (make_policy (dc, richloc));
+    char_display_policy policy (make_char_policy (dc, richloc));
     ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 8);
     ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 9);
     ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 4);
@@ -3348,7 +3435,7 @@ test_display_widths ()
   {
     test_diagnostic_context dc;
     dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
-    char_display_policy policy (make_policy (dc, richloc));
+    char_display_policy policy (make_char_policy (dc, richloc));
     ASSERT_EQ (cpp_display_width (pi, strlen (pi), policy), 8);
     ASSERT_EQ (cpp_display_width (emoji, strlen (emoji), policy), 16);
     ASSERT_EQ (cpp_display_width (stray, strlen (stray), policy), 4);
@@ -3377,10 +3464,11 @@ test_offset_impl (int caret_byte_col, int max_width,
      the line number plus one space after.  */
   dc.m_source_printing.min_margin_width = left_margin - test_linenum_sep + 1;
   dc.m_source_printing.show_line_numbers_p = true;
+  diagnostic_source_print_policy source_policy (dc);
   rich_location richloc (line_table,
 			 linemap_position_for_column (line_table,
 						      caret_byte_col));
-  layout test_layout (dc, richloc, DK_ERROR, nullptr);
+  layout test_layout (source_policy, richloc, nullptr);
   ASSERT_EQ (left_margin - test_linenum_sep,
 	     test_layout.get_linenum_width ());
   ASSERT_EQ (expected_x_offset_display,
@@ -3492,11 +3580,13 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
       = test_left_margin - test_linenum_sep + 1;
     dc.m_source_printing.show_line_numbers_p = true;
     dc.m_source_printing.show_ruler_p = true;
+    diagnostic_source_print_policy policy (dc);
     rich_location richloc (line_table,
 			   linemap_position_for_column (line_table,
 							emoji_col));
-    layout test_layout (dc, richloc, DK_ERROR, nullptr);
-    test_layout.print_line (1);
+    layout test_layout (policy, richloc, nullptr);
+    layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+    lp.print (policy);
     ASSERT_STREQ ("     |         1         \n"
 		  "     |         1         \n"
 		  "     | 234567890123456789\n"
@@ -3517,11 +3607,13 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
       = test_left_margin - test_linenum_sep + 1;
     dc.m_source_printing.show_line_numbers_p = true;
     dc.m_source_printing.show_ruler_p = true;
+    diagnostic_source_print_policy policy (dc);
     rich_location richloc (line_table,
 			   linemap_position_for_column (line_table,
 							emoji_col + 2));
-    layout test_layout (dc, richloc, DK_ERROR, nullptr);
-    test_layout.print_line (1);
+    layout test_layout (dc, richloc, nullptr);
+    layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+    lp.print (policy);
     ASSERT_STREQ ("     |        1         1 \n"
 		  "     |        1         2 \n"
 		  "     | 3456789012345678901\n"
@@ -3599,8 +3691,10 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
     {
       test_diagnostic_context dc;
       dc.m_tabstop = tabstop;
-      layout test_layout (dc, richloc, DK_ERROR, nullptr);
-      test_layout.print_line (1);
+      diagnostic_source_print_policy policy (dc);
+      layout test_layout (policy, richloc, nullptr);
+      layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+      lp.print (policy);
       const char *out = pp_formatted_text (dc.m_printer);
       ASSERT_EQ (NULL, strchr (out, '\t'));
       const char *left_quote = strchr (out, '`');
@@ -3622,8 +3716,10 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
       dc.m_source_printing.min_margin_width
 	= test_left_margin - test_linenum_sep + 1;
       dc.m_source_printing.show_line_numbers_p = true;
-      layout test_layout (dc, richloc, DK_ERROR, nullptr);
-      test_layout.print_line (1);
+      diagnostic_source_print_policy policy (dc);
+      layout test_layout (policy, richloc, nullptr);
+      layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+      lp.print (policy);
 
       /* We have arranged things so that two columns will be printed before
 	 the caret.  If the tab results in more than one space, this should
@@ -3650,7 +3746,7 @@ test_diagnostic_show_locus_unknown_location ()
 {
   test_diagnostic_context dc;
   rich_location richloc (line_table, UNKNOWN_LOCATION);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ ("", pp_formatted_text (dc.m_printer));
 }
 
@@ -3672,7 +3768,7 @@ test_one_liner_simple_caret ()
   test_diagnostic_context dc;
   location_t caret = linemap_position_for_column (line_table, 10);
   rich_location richloc (line_table, caret);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		"          ^\n",
 		pp_formatted_text (dc.m_printer));
@@ -3687,7 +3783,7 @@ test_one_liner_no_column ()
   test_diagnostic_context dc;
   location_t caret = linemap_position_for_column (line_table, 0);
   rich_location richloc (line_table, caret);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n",
 		pp_formatted_text (dc.m_printer));
 }
@@ -3703,7 +3799,7 @@ test_one_liner_caret_and_range ()
   location_t finish = linemap_position_for_column (line_table, 15);
   location_t loc = make_location (caret, start, finish);
   rich_location richloc (line_table, loc);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		"       ~~~^~~~~~\n",
 		pp_formatted_text (dc.m_printer));
@@ -3736,7 +3832,7 @@ test_one_liner_multiple_carets_and_ranges ()
   rich_location richloc (line_table, foo);
   richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
   richloc.add_range (field, SHOW_RANGE_WITH_CARET);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		" ~A~   ~B~ ~~C~~\n",
 		pp_formatted_text (dc.m_printer));
@@ -3751,7 +3847,7 @@ test_one_liner_fixit_insert_before ()
   location_t caret = linemap_position_for_column (line_table, 7);
   rich_location richloc (line_table, caret);
   richloc.add_fixit_insert_before ("&");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		"       ^\n"
 		"       &\n",
@@ -3769,7 +3865,7 @@ test_one_liner_fixit_insert_after ()
   location_t foo = make_location (start, start, finish);
   rich_location richloc (line_table, foo);
   richloc.add_fixit_insert_after ("[0]");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		" ^~~\n"
 		"    [0]\n",
@@ -3792,7 +3888,7 @@ test_one_liner_fixit_remove ()
   /* Normal.  */
   {
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  "          ^~~~~~\n"
 		  "          ------\n",
@@ -3804,7 +3900,7 @@ test_one_liner_fixit_remove ()
     test_diagnostic_context dc;
     pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
     pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
 		  "TEST PREFIX:          ^~~~~~\n"
 		  "TEST PREFIX:          ------\n",
@@ -3816,7 +3912,7 @@ test_one_liner_fixit_remove ()
     test_diagnostic_context dc;
     dc.m_source_printing.show_ruler_p = true;
     dc.m_source_printing.max_width = 104;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("          0         0         0         0         0         0         0         0         0         1    \n"
 		  "          1         2         3         4         5         6         7         8         9         0    \n"
 		  " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n"
@@ -3833,7 +3929,7 @@ test_one_liner_fixit_remove ()
     dc.m_source_printing.max_width = 50;
     pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
     pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("TEST PREFIX:          1         2         3         4         5\n"
 		  "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX: foo = bar.field;\n"
@@ -3850,7 +3946,7 @@ test_one_liner_fixit_remove ()
     dc.m_source_printing.show_line_numbers_p = true;
     pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
     pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("TEST PREFIX:      |          1         2         3         4         5\n"
 		  "TEST PREFIX:      | 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX:    1 | foo = bar.field;\n"
@@ -3871,7 +3967,7 @@ test_one_liner_fixit_replace ()
   location_t field = make_location (start, start, finish);
   rich_location richloc (line_table, field);
   richloc.add_fixit_replace ("m_field");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		"           ^~~~~\n"
 		"           m_field\n",
@@ -3893,7 +3989,7 @@ test_one_liner_fixit_replace_non_equal_range ()
   range.m_start = start;
   range.m_finish = finish;
   richloc.add_fixit_replace (range, "m_field");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   /* The replacement range is not indicated in the annotation line, so
      it should be indicated via an additional underline.  */
   ASSERT_STREQ (" foo = bar.field;\n"
@@ -3918,7 +4014,7 @@ test_one_liner_fixit_replace_equal_secondary_range ()
   location_t field = make_location (start, start, finish);
   richloc.add_range (field);
   richloc.add_fixit_replace (field, "m_field");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   /* The replacement range is indicated in the annotation line,
      so it shouldn't be indicated via an additional underline.  */
   ASSERT_STREQ (" foo = bar.field;\n"
@@ -3953,7 +4049,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~~~~~~~~                               \n"
 		  "       test\n",
@@ -3969,7 +4065,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~~~~~~~~                               \n"
 		  "       -----------------------------------------\n",
@@ -3985,7 +4081,7 @@ test_one_liner_fixit_validation_adhoc_locations ()
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~~~~~~~~                               \n"
 		  "       test\n",
@@ -4004,7 +4100,7 @@ test_one_liner_many_fixits_1 ()
   for (int i = 0; i < 19; i++)
     richloc.add_fixit_insert_before ("a");
   ASSERT_EQ (1, richloc.get_num_fixit_hints ());
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		"     ^\n"
 		"     aaaaaaaaaaaaaaaaaaa\n",
@@ -4026,7 +4122,7 @@ test_one_liner_many_fixits_2 ()
       richloc.add_fixit_insert_before (loc, "a");
     }
   ASSERT_EQ (19, richloc.get_num_fixit_hints ());
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar.field;\n"
 		"     ^\n"
 		" a a a a a a a a a a a a a a a a a a a\n",
@@ -4062,7 +4158,7 @@ test_one_liner_labels ()
 
     {
       test_diagnostic_context dc;
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ (" foo = bar.field;\n"
 		    " ^~~   ~~~ ~~~~~\n"
 		    " |     |   |\n"
@@ -4074,7 +4170,7 @@ test_one_liner_labels ()
     {
       test_diagnostic_context dc;
       dc.m_source_printing.show_labels_p = false;
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ (" foo = bar.field;\n"
 		    " ^~~   ~~~ ~~~~~\n",
 		    pp_formatted_text (dc.m_printer));
@@ -4091,7 +4187,7 @@ test_one_liner_labels ()
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  " ^~~   ~~~ ~~~~~\n"
 		  " |     |   |\n"
@@ -4112,7 +4208,7 @@ test_one_liner_labels ()
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  " ^~~   ~~~ ~~~~~\n"
 		  " |     |   |\n"
@@ -4131,7 +4227,7 @@ test_one_liner_labels ()
     richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label2);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  " ~~~   ~~~ ^~~~~\n"
 		  " |     |   |\n"
@@ -4150,7 +4246,7 @@ test_one_liner_labels ()
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label2);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~\n"
 		  "       |\n"
@@ -4185,7 +4281,7 @@ test_one_liner_labels ()
     richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2c);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  " ~~~   ~~~ ^~~~~\n"
 		  " |     |   |\n"
@@ -4208,7 +4304,7 @@ test_one_liner_labels ()
     gcc_rich_location richloc (bar, &label, nullptr);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" foo = bar.field;\n"
 		  "       ^~~\n",
 		  pp_formatted_text (dc.m_printer));
@@ -4276,7 +4372,7 @@ test_one_liner_simple_caret_utf8 ()
   test_diagnostic_context dc;
   location_t caret = linemap_position_for_column (line_table, 18);
   rich_location richloc (line_table, caret);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4296,7 +4392,7 @@ test_one_liner_caret_and_range_utf8 ()
   location_t finish = linemap_position_for_column (line_table, 30);
   location_t loc = make_location (caret, start, finish);
   rich_location richloc (line_table, loc);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4332,7 +4428,7 @@ test_one_liner_multiple_carets_and_ranges_utf8 ()
   rich_location richloc (line_table, foo);
   richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
   richloc.add_range (field, SHOW_RANGE_WITH_CARET);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4351,7 +4447,7 @@ test_one_liner_fixit_insert_before_utf8 ()
   location_t caret = linemap_position_for_column (line_table, 12);
   rich_location richloc (line_table, caret);
   richloc.add_fixit_insert_before ("&");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4373,7 +4469,7 @@ test_one_liner_fixit_insert_after_utf8 ()
   location_t foo = make_location (start, start, finish);
   rich_location richloc (line_table, foo);
   richloc.add_fixit_insert_after ("[0]");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4395,7 +4491,7 @@ test_one_liner_fixit_remove_utf8 ()
   location_t dot = make_location (start, start, finish);
   rich_location richloc (line_table, dot);
   richloc.add_fixit_remove ();
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4417,7 +4513,7 @@ test_one_liner_fixit_replace_utf8 ()
   location_t field = make_location (start, start, finish);
   rich_location richloc (line_table, field);
   richloc.add_fixit_replace ("m_\xf0\x9f\x98\x82_field\xcf\x80");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4444,7 +4540,7 @@ test_one_liner_fixit_replace_non_equal_range_utf8 ()
   range.m_start = start;
   range.m_finish = finish;
   richloc.add_fixit_replace (range, "m_\xf0\x9f\x98\x82_field\xcf\x80");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   /* The replacement range is not indicated in the annotation line, so
      it should be indicated via an additional underline.  */
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
@@ -4474,7 +4570,7 @@ test_one_liner_fixit_replace_equal_secondary_range_utf8 ()
   location_t field = make_location (start, start, finish);
   richloc.add_range (field);
   richloc.add_fixit_replace (field, "m_\xf0\x9f\x98\x82_field\xcf\x80");
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   /* The replacement range is indicated in the annotation line,
      so it shouldn't be indicated via an additional underline.  */
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
@@ -4514,7 +4610,7 @@ test_one_liner_fixit_validation_adhoc_locations_utf8 ()
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		     "_foo = \xcf\x80"
 			     "_bar.\xf0\x9f\x98\x82"
@@ -4534,7 +4630,7 @@ test_one_liner_fixit_validation_adhoc_locations_utf8 ()
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		     "_foo = \xcf\x80"
 			     "_bar.\xf0\x9f\x98\x82"
@@ -4554,7 +4650,7 @@ test_one_liner_fixit_validation_adhoc_locations_utf8 ()
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		     "_foo = \xcf\x80"
 			     "_bar.\xf0\x9f\x98\x82"
@@ -4577,7 +4673,7 @@ test_one_liner_many_fixits_1_utf8 ()
   for (int i = 0; i < 19; i++)
     richloc.add_fixit_insert_before (i & 1 ? "@" : "\xcf\x80");
   ASSERT_EQ (1, richloc.get_num_fixit_hints ());
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4608,7 +4704,7 @@ test_one_liner_many_fixits_2_utf8 ()
     }
 
   ASSERT_EQ (nlocs, richloc.get_num_fixit_hints ());
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		   "_foo = \xcf\x80"
 			   "_bar.\xf0\x9f\x98\x82"
@@ -4658,7 +4754,7 @@ test_one_liner_labels_utf8 ()
 
     {
       test_diagnostic_context dc;
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		       "_foo = \xcf\x80"
 			       "_bar.\xf0\x9f\x98\x82"
@@ -4685,7 +4781,7 @@ test_one_liner_labels_utf8 ()
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
 
     ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		     "_foo = \xcf\x80"
@@ -4711,7 +4807,7 @@ test_one_liner_labels_utf8 ()
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" \xf0\x9f\x98\x82"
 		     "_foo = \xcf\x80"
 			     "_bar.\xf0\x9f\x98\x82"
@@ -4738,7 +4834,7 @@ test_one_liner_labels_utf8 ()
     {
       test_diagnostic_context dc;
       dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ (" <U+1F602>_foo = <U+03C0>_bar.<U+1F602>_field<U+03C0>;\n"
 		    " ^~~~~~~~~~~~~   ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~\n"
 		    " |               |            |\n"
@@ -4750,7 +4846,7 @@ test_one_liner_labels_utf8 ()
     {
       test_diagnostic_context dc;
       dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ
 	(" <f0><9f><98><82>_foo = <cf><80>_bar.<f0><9f><98><82>_field<cf><80>;\n"
 	 " ^~~~~~~~~~~~~~~~~~~~   ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
@@ -4773,7 +4869,7 @@ test_one_liner_colorized_utf8 ()
   diagnostic_color_init (&dc, DIAGNOSTICS_COLOR_YES);
   const location_t pi = linemap_position_for_column (line_table, 12);
   rich_location richloc (line_table, pi);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
 
   /* In order to avoid having the test depend on exactly how the colorization
      was effected, just confirm there are two pi characters in the output.  */
@@ -4883,7 +4979,7 @@ test_add_location_if_nearby (const line_table_case &case_)
 						 matching_open_brace_1_18);
     ASSERT_TRUE (added);
     ASSERT_EQ (2, richloc.get_num_locations ());
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" struct same_line { double x; double y; ;\n"
 		  "                  ~                    ^\n",
 		  pp_formatted_text (dc.m_printer));
@@ -4949,7 +5045,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     rich_location richloc (line_table, colon);
     richloc.add_fixit_insert_before (x, ".");
     richloc.add_fixit_replace (colon, "=");
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" struct point origin = {x: 0.0,\n"
 		  "                         ^\n"
 		  "                        .=\n",
@@ -4969,7 +5065,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     rich_location richloc (line_table, colon);
     richloc.add_fixit_insert_before (y, ".");
     richloc.add_fixit_replace (colon, "=");
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("FILENAME:3:24:\n"
 		  "                        y\n"
 		  "                        .\n"
@@ -4992,7 +5088,7 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
     richloc.add_fixit_replace (colon, "=");
     test_diagnostic_context dc;
     dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("    3 |                        y\n"
 		  "      |                        .\n"
 		  "......\n"
@@ -5203,7 +5299,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_replace (close_paren, "> (");
     richloc.add_fixit_insert_after (")");
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -----------------\n"
@@ -5211,7 +5307,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
 		  pp_formatted_text (dc.m_printer));
 
     /* Unit-test the line_corrections machinery.  */
-    char_display_policy policy (make_policy (dc, richloc));
+    char_display_policy policy (make_char_policy (dc, richloc));
     ASSERT_EQ (3, richloc.get_num_fixit_hints ());
     const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
     ASSERT_EQ (column_range (12, 12),
@@ -5273,7 +5369,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_replace (close_paren, ") (");
     richloc.add_fixit_insert_after (")");
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -\n"
@@ -5290,7 +5386,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     richloc.add_fixit_replace (close_paren, ") (");
     richloc.add_fixit_insert_after (")");
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -\n"
@@ -5311,7 +5407,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
        rather than by line_corrections.  */
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -------\n"
@@ -5331,7 +5427,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     ASSERT_EQ (2, richloc.get_num_fixit_hints ());
 
     /* But the corrections are.  */
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -----------------\n"
@@ -5349,7 +5445,7 @@ test_overlapped_fixit_printing (const line_table_case &case_)
     /* The first insertion is long enough that if printed naively,
        it would overlap with the second.
        Verify that they are printed as a single replacement.  */
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   foo *f = (foo *)ptr->field;\n"
 		  "                   ^~~~~~~~~~\n"
 		  "            -------\n"
@@ -5415,7 +5511,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
     richloc.add_fixit_replace (close_paren, "> (");
     richloc.add_fixit_insert_after (")");
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
 			" *f = (f\xf0\x9f\x98\x82"
 				  " *)ptr->field\xcf\x80"
@@ -5428,7 +5524,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
 		  pp_formatted_text (dc.m_printer));
 
     /* Unit-test the line_corrections machinery.  */
-    char_display_policy policy (make_policy (dc, richloc));
+    char_display_policy policy (make_char_policy (dc, richloc));
     ASSERT_EQ (3, richloc.get_num_fixit_hints ());
     const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
     ASSERT_EQ (column_range (14, 14),
@@ -5491,7 +5587,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
     richloc.add_fixit_replace (close_paren, ") (");
     richloc.add_fixit_insert_after (")");
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
 			" *f = (f\xf0\x9f\x98\x82"
 				  " *)ptr->field\xcf\x80"
@@ -5511,7 +5607,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
     richloc.add_fixit_replace (close_paren, ") (");
     richloc.add_fixit_insert_after (")");
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
 			" *f = (f\xf0\x9f\x98\x82"
 				  " *)ptr->field\xcf\x80"
@@ -5535,7 +5631,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
        rather than by line_corrections.  */
     ASSERT_EQ (1, richloc.get_num_fixit_hints ());
 
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
 			" *f = (f\xf0\x9f\x98\x82"
 				  " *)ptr->field\xcf\x80"
@@ -5559,7 +5655,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
     ASSERT_EQ (2, richloc.get_num_fixit_hints ());
 
     /* But the corrections are.  */
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
 			" *f = (f\xf0\x9f\x98\x82"
 				  " *)ptr->field\xcf\x80"
@@ -5583,7 +5679,7 @@ test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
     /* The first insertion is long enough that if printed naively,
        it would overlap with the second.
        Verify that they are printed as a single replacement.  */
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("   f\xf0\x9f\x98\x82"
 			" *f = (f\xf0\x9f\x98\x82"
 				  " *)ptr->field\xcf\x80"
@@ -5647,7 +5743,7 @@ test_overlapped_fixit_printing_2 (const line_table_case &case_)
     richloc.add_fixit_insert_before (col_21, "}");
 
     /* These fixits should be accepted; they can't be consolidated.  */
-    char_display_policy policy (make_policy (dc, richloc));
+    char_display_policy policy (make_char_policy (dc, richloc));
     ASSERT_EQ (2, richloc.get_num_fixit_hints ());
     const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
     ASSERT_EQ (column_range (23, 22),
@@ -5659,7 +5755,7 @@ test_overlapped_fixit_printing_2 (const line_table_case &case_)
     ASSERT_EQ (column_range (21, 21), get_printed_columns (fc, policy, hint_1));
 
     /* Verify that they're printed correctly.  */
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
 		  "                    ^\n"
 		  "                     } {\n",
@@ -5681,7 +5777,7 @@ test_overlapped_fixit_printing_2 (const line_table_case &case_)
     richloc.add_fixit_insert_before (col_21, "}");
     richloc.add_fixit_insert_before (col_1, "{");
     richloc.add_fixit_insert_before (col_25, "}");
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
 		  "                    ^\n"
 		  " {                  -----\n"
@@ -5724,7 +5820,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
     /* Without line numbers.  */
     {
       test_diagnostic_context dc;
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ ("       x = a;\n"
 		    "+      break;\n"
 		    "     case 'b':\n"
@@ -5736,7 +5832,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
     {
       test_diagnostic_context dc;
       dc.m_source_printing.show_line_numbers_p = true;
-      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      dc.test_show_locus (richloc);
       ASSERT_STREQ ("    2 |       x = a;\n"
 		    "  +++ |+      break;\n"
 		    "    3 |     case 'b':\n"
@@ -5752,7 +5848,7 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
     richloc.add_fixit_insert_before (case_start, "break;\n");
     ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("     case 'b':\n"
 		  "     ^~~~~~~~~\n",
 		  pp_formatted_text (dc.m_printer));
@@ -5800,7 +5896,7 @@ test_fixit_insert_containing_newline_2 (const line_table_case &case_)
 
   {
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("FILENAME:1:1:\n"
 		  "+#include <stdio.h>\n"
 		  " test (int ch)\n"
@@ -5815,7 +5911,7 @@ test_fixit_insert_containing_newline_2 (const line_table_case &case_)
   {
     test_diagnostic_context dc;
     dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("  +++ |+#include <stdio.h>\n"
 		  "    1 | test (int ch)\n"
 		  "    2 | {\n"
@@ -5858,7 +5954,7 @@ test_fixit_replace_containing_newline (const line_table_case &case_)
     return;
 
   test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar ();\n"
 		"             ^\n",
 		pp_formatted_text (dc.m_printer));
@@ -5902,7 +5998,7 @@ test_fixit_deletion_affecting_newline (const line_table_case &case_)
     return;
 
   test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" foo = bar (\n"
 		"          ~^\n"
 		"       );\n"
@@ -5947,8 +6043,7 @@ test_tab_expansion (const line_table_case &case_)
     rich_location richloc (line_table,
 			   linemap_position_for_column (line_table,
 							first_non_ws_byte_col));
-    layout test_layout (dc, richloc, DK_ERROR, nullptr);
-    test_layout.print_line (1);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("            This: `      ' is a tab.\n"
 		  "            ^\n",
 		  pp_formatted_text (dc.m_printer));
@@ -5962,8 +6057,7 @@ test_tab_expansion (const line_table_case &case_)
     rich_location richloc (line_table,
 			   linemap_position_for_column (line_table,
 							right_quote_byte_col));
-    layout test_layout (dc, richloc, DK_ERROR, nullptr);
-    test_layout.print_line (1);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("            This: `      ' is a tab.\n"
 		  "                         ^\n",
 		  pp_formatted_text (dc.m_printer));
@@ -6001,7 +6095,7 @@ test_escaping_bytes_1 (const line_table_case &case_)
 
   {
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" before \1\2\3\v\x80\xff""after\n"
 		  "       ^   ~\n",
 		  pp_formatted_text (dc.m_printer));
@@ -6010,7 +6104,7 @@ test_escaping_bytes_1 (const line_table_case &case_)
   {
     test_diagnostic_context dc;
     dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ
       (" before<U+0000><U+0001><U+0002><U+0003><U+000B><80><ff>after\n"
        "       ^~~~~~~~                        ~~~~~~~~\n",
@@ -6019,7 +6113,7 @@ test_escaping_bytes_1 (const line_table_case &case_)
   {
     test_diagnostic_context dc;
     dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" before<00><01><02><03><0b><80><ff>after\n"
 		  "       ^~~~            ~~~~\n",
 		  pp_formatted_text (dc.m_printer));
@@ -6054,7 +6148,7 @@ test_escaping_bytes_2 (const line_table_case &case_)
 
   {
     test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ ("  after\n"
 		  " ^\n",
 		  pp_formatted_text (dc.m_printer));
@@ -6063,7 +6157,7 @@ test_escaping_bytes_2 (const line_table_case &case_)
   {
     test_diagnostic_context dc;
     dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_UNICODE);
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" <U+0000>after\n"
 		  " ^~~~~~~~\n",
 		  pp_formatted_text (dc.m_printer));
@@ -6071,7 +6165,7 @@ test_escaping_bytes_2 (const line_table_case &case_)
   {
     test_diagnostic_context dc;
     dc.set_escape_format (DIAGNOSTICS_ESCAPE_FORMAT_BYTES);
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    dc.test_show_locus (richloc);
     ASSERT_STREQ (" <00>after\n"
 		  " ^~~~\n",
 		  pp_formatted_text (dc.m_printer));
@@ -6113,7 +6207,7 @@ test_line_numbers_multiline_range ()
   dc.m_source_printing.show_line_numbers_p = true;
   dc.m_source_printing.min_margin_width = 0;
   gcc_rich_location richloc (loc);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  dc.test_show_locus (richloc);
   ASSERT_STREQ (" 9 | this is line 9\n"
 		"   |         ~~~~~~\n"
 		"10 | this is line 10\n"
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 152eb9d35a8e..618837e1731d 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -533,13 +533,22 @@ convert_column_unit (file_cache &fc,
     }
 }
 
+diagnostic_column_policy::
+diagnostic_column_policy (const diagnostic_context &dc)
+: m_file_cache (dc.get_file_cache ()),
+  m_column_unit (dc.m_column_unit),
+  m_column_origin (dc.m_column_origin),
+  m_tabstop (dc.m_tabstop)
+{
+}
+
 /* Given an expanded_location, convert the column (which is in 1-based bytes)
    to the requested units and origin.  Return -1 if the column is
    invalid (<= 0).  */
 int
-diagnostic_context::converted_column (expanded_location s) const
+diagnostic_column_policy::converted_column (expanded_location s) const
 {
-  int one_based_col = convert_column_unit (get_file_cache (),
+  int one_based_col = convert_column_unit (m_file_cache,
 					   m_column_unit, m_tabstop, s);
   if (one_based_col <= 0)
     return -1;
@@ -549,8 +558,9 @@ diagnostic_context::converted_column (expanded_location s) const
 /* Return a string describing a location e.g. "foo.c:42:10".  */
 
 label_text
-diagnostic_context::get_location_text (const expanded_location &s,
-				       bool colorize) const
+diagnostic_column_policy::get_location_text (const expanded_location &s,
+					     bool show_column,
+					     bool colorize) const
 {
   const char *locus_cs = colorize_start (colorize, "locus");
   const char *locus_ce = colorize_stop (colorize);
@@ -560,8 +570,8 @@ diagnostic_context::get_location_text (const expanded_location &s,
   if (strcmp (file, special_fname_builtin ()))
     {
       line = s.line;
-      if (m_show_column)
-	col = this->converted_column (s);
+      if (show_column)
+	col = converted_column (s);
     }
 
   const char *line_col = maybe_line_and_column (line, col);
@@ -569,6 +579,21 @@ diagnostic_context::get_location_text (const expanded_location &s,
 						 line_col, locus_ce));
 }
 
+diagnostic_location_print_policy::
+diagnostic_location_print_policy (const diagnostic_context &dc)
+: m_column_policy (dc),
+  m_show_column (dc.m_show_column)
+{
+}
+
+diagnostic_location_print_policy::
+diagnostic_location_print_policy (const diagnostic_text_output_format &text_output)
+:
+  m_column_policy (text_output.get_context ()),
+  m_show_column (text_output.get_context ().m_show_column)
+{
+}
+
 static const char *const diagnostic_kind_text[] = {
 #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T),
 #include "diagnostic.def"
@@ -847,11 +872,16 @@ logical_location::function_p () const
 }
 
 void
-default_diagnostic_start_span_fn (diagnostic_context *context,
+default_diagnostic_start_span_fn (const diagnostic_location_print_policy &loc_policy,
+				  pretty_printer *pp,
 				  expanded_location exploc)
 {
-  pretty_printer *pp = context->m_printer;
-  label_text text = context->get_location_text (exploc, pp_show_color (pp));
+  const diagnostic_column_policy &column_policy
+    = loc_policy.get_column_policy ();
+  label_text text
+    = column_policy.get_location_text (exploc,
+				       loc_policy.show_column_p (),
+				       pp_show_color (pp));
   pp_string (pp, text.get ());
   pp_newline (pp);
 }
@@ -1814,7 +1844,7 @@ test_print_parseable_fixits_bytes_vs_display_columns ()
 }
 
 /* Verify that
-     diagnostic_get_location_text (..., SHOW_COLUMN)
+     diagnostic_column_policy::get_location_text (..., SHOW_COLUMN, ...)
    generates EXPECTED_LOC_TEXT, given FILENAME, LINE, COLUMN, with
    colorization disabled.  */
 
@@ -1827,7 +1857,6 @@ assert_location_text (const char *expected_loc_text,
 			= DIAGNOSTICS_COLUMN_UNIT_BYTE)
 {
   test_diagnostic_context dc;
-  dc.m_show_column = show_column;
   dc.m_column_unit = column_unit;
   dc.m_column_origin = origin;
 
@@ -1838,14 +1867,16 @@ assert_location_text (const char *expected_loc_text,
   xloc.data = NULL;
   xloc.sysp = false;
 
-  label_text actual_loc_text = dc.get_location_text (xloc, false);
+  diagnostic_column_policy column_policy (dc);
+  label_text actual_loc_text
+    = column_policy.get_location_text (xloc, show_column, false);
   ASSERT_STREQ (expected_loc_text, actual_loc_text.get ());
 }
 
-/* Verify that diagnostic_get_location_text works as expected.  */
+/* Verify that get_location_text works as expected.  */
 
 static void
-test_diagnostic_get_location_text ()
+test_get_location_text ()
 {
   const char *old_progname = progname;
   progname = "PROGNAME";
@@ -1938,7 +1969,7 @@ c_diagnostic_cc_tests ()
   test_print_parseable_fixits_remove ();
   test_print_parseable_fixits_replace ();
   test_print_parseable_fixits_bytes_vs_display_columns ();
-  test_diagnostic_get_location_text ();
+  test_get_location_text ();
   test_num_digits ();
 
 }
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 8d20316ddc79..54b7f307f849 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -171,11 +171,16 @@ struct diagnostic_info
 };
 
 /*  Forward declarations.  */
+class diagnostic_location_print_policy;
+class diagnostic_source_print_policy;
+
 typedef void (*diagnostic_text_starter_fn) (diagnostic_text_output_format &,
 					    const diagnostic_info *);
 
-typedef void (*diagnostic_start_span_fn) (diagnostic_context *,
-					  expanded_location);
+typedef void
+(*diagnostic_start_span_fn) (const diagnostic_location_print_policy &,
+			     pretty_printer *,
+			     expanded_location);
 
 typedef void (*diagnostic_text_finalizer_fn) (diagnostic_text_output_format &,
 					      const diagnostic_info *,
@@ -346,6 +351,104 @@ struct diagnostic_source_printing_options
   bool show_event_links_p;
 };
 
+/* A bundle of state for determining column numbers in diagnostics
+   (tab stops, whether to start at 0 or 1, etc).
+   Uses a file_cache to handle tabs.  */
+
+class diagnostic_column_policy
+{
+public:
+  diagnostic_column_policy (const diagnostic_context &dc);
+
+  int converted_column (expanded_location s) const;
+
+  label_text get_location_text (const expanded_location &s,
+				bool show_column,
+				bool colorize) const;
+
+  int get_tabstop () const { return m_tabstop; }
+
+private:
+  file_cache &m_file_cache;
+  enum diagnostics_column_unit m_column_unit;
+  int m_column_origin;
+  int m_tabstop;
+};
+
+/* A bundle of state for printing locations within diagnostics
+   (e.g. "FILENAME:LINE:COLUMN"), to isolate the interactions between
+   diagnostic_context and the start_span callbacks.  */
+
+class diagnostic_location_print_policy
+{
+public:
+  diagnostic_location_print_policy (const diagnostic_context &dc);
+  diagnostic_location_print_policy (const diagnostic_text_output_format &);
+
+  bool show_column_p () const { return m_show_column; }
+
+  const diagnostic_column_policy &
+  get_column_policy () const { return m_column_policy; }
+
+private:
+  diagnostic_column_policy m_column_policy;
+  bool m_show_column;
+};
+
+/* A bundle of state for printing source within a diagnostic,
+   to isolate the interactions between diagnostic_context and the
+   implementation of diagnostic_show_locus.  */
+
+class diagnostic_source_print_policy
+{
+public:
+  diagnostic_source_print_policy (const diagnostic_context &);
+
+  void
+  print (pretty_printer &pp,
+	 const rich_location &richloc,
+	 diagnostic_t diagnostic_kind,
+	 diagnostic_source_effect_info *effect_info) const;
+
+  const diagnostic_source_printing_options &
+  get_options () const { return m_options; }
+
+  diagnostic_start_span_fn
+  get_start_span_fn () const { return m_start_span_cb; }
+
+  file_cache &
+  get_file_cache () const { return m_file_cache; }
+
+  enum diagnostics_escape_format
+  get_escape_format () const
+  {
+    return m_escape_format;
+  }
+
+  text_art::theme *
+  get_diagram_theme () const { return m_diagram_theme; }
+
+  const diagnostic_column_policy &get_column_policy () const
+  {
+    return m_location_policy.get_column_policy ();
+  }
+
+  const diagnostic_location_print_policy &get_location_policy () const
+  {
+    return m_location_policy;
+  }
+
+private:
+  const diagnostic_source_printing_options &m_options;
+  class diagnostic_location_print_policy m_location_policy;
+  diagnostic_start_span_fn m_start_span_cb;
+  file_cache &m_file_cache;
+
+  /* Other data copied from diagnostic_context.  */
+  text_art::theme *m_diagram_theme;
+  enum diagnostics_escape_format m_escape_format;
+};
+
 /* This data structure bundles altogether any information relevant to
    the context of a diagnostic message.  */
 class diagnostic_context
@@ -359,6 +462,7 @@ public:
   friend diagnostic_text_finalizer_fn &
   diagnostic_text_finalizer (diagnostic_context *context);
 
+  friend class diagnostic_source_print_policy;
   friend class diagnostic_text_output_format;
 
   typedef void (*ice_handler_callback_t) (diagnostic_context *);
@@ -502,8 +606,6 @@ public:
   urlifier *get_urlifier () const { return m_urlifier; }
   text_art::theme *get_diagram_theme () const { return m_diagrams.m_theme; }
 
-  int converted_column (expanded_location s) const;
-
   int &diagnostic_count (diagnostic_t kind)
   {
     return m_diagnostic_count[kind];
@@ -544,9 +646,6 @@ public:
     return m_lang_mask;
   }
 
-  label_text get_location_text (const expanded_location &s,
-				bool colorize) const;
-
   bool diagnostic_impl (rich_location *, const diagnostic_metadata *,
 			diagnostic_option_id, const char *,
 			va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
@@ -562,11 +661,6 @@ private:
 
   void get_any_inlining_info (diagnostic_info *diagnostic);
 
-  void show_locus (const rich_location &richloc,
-		   diagnostic_t diagnostic_kind,
-		   pretty_printer *pp,
-		   diagnostic_source_effect_info *effect_info);
-
   /* Data members.
      Ideally, all of these would be private.  */
 
@@ -956,7 +1050,8 @@ extern void diagnostic_set_info_translated (diagnostic_info *, const char *,
 #endif
 void default_diagnostic_text_starter (diagnostic_text_output_format &,
 				      const diagnostic_info *);
-void default_diagnostic_start_span_fn (diagnostic_context *,
+void default_diagnostic_start_span_fn (const diagnostic_location_print_policy &,
+				       pretty_printer *,
 				       expanded_location);
 void default_diagnostic_text_finalizer (diagnostic_text_output_format &,
 					const diagnostic_info *,
diff --git a/gcc/fortran/error.cc b/gcc/fortran/error.cc
index cad0e699f0a2..2c29537a4ff0 100644
--- a/gcc/fortran/error.cc
+++ b/gcc/fortran/error.cc
@@ -1211,17 +1211,17 @@ gfc_diagnostic_build_kind_prefix (diagnostic_context *context,
 /* Return a malloc'd string describing a location.  The caller is
    responsible for freeing the memory.  */
 static char *
-gfc_diagnostic_build_locus_prefix (diagnostic_context *context,
-				   expanded_location s)
+gfc_diagnostic_build_locus_prefix (const diagnostic_location_print_policy &loc_policy,
+				   expanded_location s,
+				   bool colorize)
 {
-  pretty_printer *const pp = context->m_printer;
-  const char *locus_cs = colorize_start (pp_show_color (pp), "locus");
-  const char *locus_ce = colorize_stop (pp_show_color (pp));
+  const char *locus_cs = colorize_start (colorize, "locus");
+  const char *locus_ce = colorize_stop (colorize);
   return (s.file == NULL
 	  ? build_message_string ("%s%s:%s", locus_cs, progname, locus_ce )
 	  : !strcmp (s.file, special_fname_builtin ())
 	  ? build_message_string ("%s%s:%s", locus_cs, s.file, locus_ce)
-	  : context->m_show_column
+	  : loc_policy.show_column_p ()
 	  ? build_message_string ("%s%s:%d:%d:%s", locus_cs, s.file, s.line,
 				  s.column, locus_ce)
 	  : build_message_string ("%s%s:%d:%s", locus_cs, s.file, s.line, locus_ce));
@@ -1230,18 +1230,18 @@ gfc_diagnostic_build_locus_prefix (diagnostic_context *context,
 /* Return a malloc'd string describing two locations.  The caller is
    responsible for freeing the memory.  */
 static char *
-gfc_diagnostic_build_locus_prefix (diagnostic_context *context,
-				   expanded_location s, expanded_location s2)
+gfc_diagnostic_build_locus_prefix (const diagnostic_location_print_policy &loc_policy,
+				   expanded_location s, expanded_location s2,
+				   bool colorize)
 {
-  pretty_printer *const pp = context->m_printer;
-  const char *locus_cs = colorize_start (pp_show_color (pp), "locus");
-  const char *locus_ce = colorize_stop (pp_show_color (pp));
+  const char *locus_cs = colorize_start (colorize, "locus");
+  const char *locus_ce = colorize_stop (colorize);
 
   return (s.file == NULL
 	  ? build_message_string ("%s%s:%s", locus_cs, progname, locus_ce )
 	  : !strcmp (s.file, special_fname_builtin ())
 	  ? build_message_string ("%s%s:%s", locus_cs, s.file, locus_ce)
-	  : context->m_show_column
+	  : loc_policy.show_column_p ()
 	  ? build_message_string ("%s%s:%d:%d-%d:%s", locus_cs, s.file, s.line,
 				  MIN (s.column, s2.column),
 				  MAX (s.column, s2.column), locus_ce)
@@ -1285,9 +1285,11 @@ gfc_diagnostic_text_starter (diagnostic_text_output_format &text_output,
       same_locus = diagnostic_same_line (context, s1, s2);
     }
 
+  diagnostic_location_print_policy loc_policy (text_output);
+  const bool colorize = pp_show_color (pp);
   char * locus_prefix = (one_locus || !same_locus)
-    ? gfc_diagnostic_build_locus_prefix (context, s1)
-    : gfc_diagnostic_build_locus_prefix (context, s1, s2);
+    ? gfc_diagnostic_build_locus_prefix (loc_policy, s1, colorize)
+    : gfc_diagnostic_build_locus_prefix (loc_policy, s1, s2, colorize);
 
   if (!context->m_source_printing.enabled
       || diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION
@@ -1309,7 +1311,7 @@ gfc_diagnostic_text_starter (diagnostic_text_output_format &text_output,
 	 and we flush with a new line before setting the new prefix.  */
       pp_string (pp, "(1)");
       pp_newline (pp);
-      locus_prefix = gfc_diagnostic_build_locus_prefix (context, s2);
+      locus_prefix = gfc_diagnostic_build_locus_prefix (loc_policy, s2, colorize);
       pp_set_prefix (pp,
 		     concat (locus_prefix, " ", kind_prefix, NULL));
       free (kind_prefix);
@@ -1332,12 +1334,13 @@ gfc_diagnostic_text_starter (diagnostic_text_output_format &text_output,
 }
 
 static void
-gfc_diagnostic_start_span (diagnostic_context *context,
+gfc_diagnostic_start_span (const diagnostic_location_print_policy &loc_policy,
+			   pretty_printer *pp,
 			   expanded_location exploc)
 {
-  char *locus_prefix;
-  locus_prefix = gfc_diagnostic_build_locus_prefix (context, exploc);
-  pretty_printer * const pp = context->m_printer;
+  const bool colorize = pp_show_color (pp);
+  char *locus_prefix
+    = gfc_diagnostic_build_locus_prefix (loc_policy, exploc, colorize);
   pp_verbatim (pp, "%s", locus_prefix);
   free (locus_prefix);
   pp_newline (pp);
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index 55798847726f..54225057d87e 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -22,6 +22,8 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "rich-location.h"
 
+class diagnostic_source_print_policy;
+
 /* A gcc_rich_location is libcpp's rich_location with additional
    helper methods for working with gcc's types.  The class is not
    copyable or assignable because rich_location isn't. */
@@ -79,7 +81,12 @@ class gcc_rich_location : public rich_location
 
      Implemented in diagnostic-show-locus.cc.  */
 
-  bool add_location_if_nearby (const diagnostic_context &ctxt,
+  bool add_location_if_nearby (const diagnostic_source_print_policy &policy,
+			       location_t loc,
+			       bool restrict_to_current_line_spans = true,
+			       const range_label *label = NULL);
+
+  bool add_location_if_nearby (const diagnostic_context &dc,
 			       location_t loc,
 			       bool restrict_to_current_line_spans = true,
 			       const range_label *label = NULL);
diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc
index 3a14739c1e3d..e51210926184 100644
--- a/gcc/selftest-diagnostic.cc
+++ b/gcc/selftest-diagnostic.cc
@@ -36,6 +36,7 @@ namespace selftest {
 test_diagnostic_context::test_diagnostic_context ()
 {
   diagnostic_initialize (this, 0);
+  pp_show_color (m_printer) = false;
   m_source_printing.enabled = true;
   m_source_printing.show_labels_p = true;
   m_show_column = true;
@@ -53,11 +54,13 @@ test_diagnostic_context::~test_diagnostic_context ()
    real filename (to avoid printing the names of tempfiles).  */
 
 void
-test_diagnostic_context::start_span_cb (diagnostic_context *context,
-					expanded_location exploc)
+test_diagnostic_context::
+start_span_cb (const diagnostic_location_print_policy &loc_policy,
+	       pretty_printer *pp,
+	       expanded_location exploc)
 {
   exploc.file = "FILENAME";
-  default_diagnostic_start_span_fn (context, exploc);
+  default_diagnostic_start_span_fn (loc_policy, pp, exploc);
 }
 
 bool
@@ -76,6 +79,16 @@ test_diagnostic_context::report (diagnostic_t kind,
   return result;
 }
 
+/* Print RICHLOC's source and annotations to this context's m_printer.  */
+
+void
+test_diagnostic_context::test_show_locus (rich_location &richloc)
+{
+  gcc_assert (m_printer);
+  diagnostic_source_print_policy source_policy (*this);
+  source_policy.print (*m_printer, richloc, DK_ERROR, nullptr);
+}
+
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
diff --git a/gcc/selftest-diagnostic.h b/gcc/selftest-diagnostic.h
index f899443c4961..008db3e1617e 100644
--- a/gcc/selftest-diagnostic.h
+++ b/gcc/selftest-diagnostic.h
@@ -39,7 +39,9 @@ class test_diagnostic_context : public diagnostic_context
   /* Implementation of diagnostic_start_span_fn, hiding the
      real filename (to avoid printing the names of tempfiles).  */
   static void
-  start_span_cb (diagnostic_context *context, expanded_location exploc);
+  start_span_cb (const diagnostic_location_print_policy &,
+		 pretty_printer *,
+		 expanded_location exploc);
 
   /* Report a diagnostic to this context.  For a selftest, this
      should only be called on a context that uses a non-standard formatter
@@ -50,6 +52,8 @@ class test_diagnostic_context : public diagnostic_context
 	  const diagnostic_metadata *metadata,
 	  int option,
 	  const char * fmt, ...) ATTRIBUTE_GCC_DIAG(6,7);
+
+  void test_show_locus (rich_location &richloc);
 };
 
 } // namespace selftest
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.c
index b6def2317dfc..2f780338b6e7 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_group_plugin.c
@@ -175,11 +175,12 @@ test_diagnostic_text_starter (diagnostic_text_output_format &text_output,
    expected output.  */
 
 void
-test_diagnostic_start_span_fn (diagnostic_context *context,
-			       expanded_location exploc)
+test_diagnostic_start_span_fn (const diagnostic_location_print_policy &,
+			       pretty_printer *pp,
+			       expanded_location)
 {
-  pp_string (context->m_printer, "START_SPAN_FN: ");
-  pp_newline (context->m_printer);
+  pp_string (pp, "START_SPAN_FN: ");
+  pp_newline (pp);
 }
 
 /* Custom output format subclass.  */
