From 9cb9e37c33173c16cbafd345f43e43b5a550537d Mon Sep 17 00:00:00 2001
From: Titus Wormer ");
+ }
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`Resource`][Name::Resource].
+fn on_enter_resource(context: &mut CompileContext) {
+ context.buffer(); // We can have line endings in the resource, ignore them.
+ context.media_stack.last_mut().unwrap().destination = Some("".to_string());
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`ResourceDestinationString`][Name::ResourceDestinationString].
+fn on_enter_resource_destination_string(context: &mut CompileContext) {
+ context.buffer();
+ // Ignore encoding the result, as we’ll first percent encode the url and
+ // encode manually after.
+ context.encode_html = false;
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`Strong`][Name::Strong].
+fn on_enter_strong(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`AutolinkEmail`][Name::AutolinkEmail].
+fn on_exit_autolink_email(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ Some("mailto:"),
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ false,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`AutolinkProtocol`][Name::AutolinkProtocol].
+fn on_exit_autolink_protocol(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ None,
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ false,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:{[`HardBreakEscape`][Name::HardBreakEscape],[`HardBreakTrailing`][Name::HardBreakTrailing]}.
+fn on_exit_break(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("");
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`CodeIndented`][Name::CodeIndented].
+fn on_enter_code_indented(context: &mut CompileContext) {
+ context.raw_flow_seen_data = Some(false);
+ context.line_ending_if_needed();
+ context.push("
");
+}
+
+/// Handle [`Enter`][Kind::Enter]:{[`CodeFenced`][Name::CodeFenced],[`MathFlow`][Name::MathFlow]}.
+fn on_enter_raw_flow(context: &mut CompileContext) {
+ context.raw_flow_seen_data = Some(false);
+ context.line_ending_if_needed();
+ // Note that no `>` is used, which is added later (due to info)
+ context.push("
");
+ }
+ context.buffer();
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`Definition`][Name::Definition].
+fn on_enter_definition(context: &mut CompileContext) {
+ context.buffer();
+ context.media_stack.push(Media {
+ image: false,
+ label: None,
+ label_id: None,
+ reference_id: None,
+ destination: None,
+ title: None,
+ });
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`DefinitionDestinationString`][Name::DefinitionDestinationString].
+fn on_enter_definition_destination_string(context: &mut CompileContext) {
+ context.buffer();
+ context.encode_html = false;
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`Emphasis`][Name::Emphasis].
+fn on_enter_emphasis(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("");
+ }
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`Frontmatter`][Name::Frontmatter].
+fn on_enter_frontmatter(context: &mut CompileContext) {
+ context.buffer();
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmFootnoteDefinition`][Name::GfmFootnoteDefinition].
+fn on_enter_gfm_footnote_definition(context: &mut CompileContext) {
+ context.tight_stack.push(false);
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmFootnoteCall`][Name::GfmFootnoteCall].
+fn on_enter_gfm_footnote_call(context: &mut CompileContext) {
+ context.media_stack.push(Media {
+ image: false,
+ label_id: None,
+ label: None,
+ reference_id: None,
+ destination: None,
+ title: None,
+ });
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmStrikethrough`][Name::GfmStrikethrough].
+fn on_enter_gfm_strikethrough(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("
");
+ }
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmTable`][Name::GfmTable].
+fn on_enter_gfm_table(context: &mut CompileContext) {
+ // Find the alignment.
+ let mut index = context.index;
+ let mut in_delimiter_row = false;
+ let mut align = vec![];
+
+ while index < context.events.len() {
+ let event = &context.events[index];
+
+ if in_delimiter_row {
+ if event.kind == Kind::Enter {
+ // Start of alignment value: set a new column.
+ if event.name == Name::GfmTableDelimiterCellValue {
+ align.push(
+ if context.events[index + 1].name == Name::GfmTableDelimiterMarker {
+ GfmTableAlign::Left
+ } else {
+ GfmTableAlign::None
+ },
+ );
+ }
+ } else {
+ // End of alignment value: change the column.
+ if event.name == Name::GfmTableDelimiterCellValue {
+ if context.events[index - 1].name == Name::GfmTableDelimiterMarker {
+ let align_index = align.len() - 1;
+ align[align_index] = if align[align_index] == GfmTableAlign::Left {
+ GfmTableAlign::Center
+ } else {
+ GfmTableAlign::Right
+ }
+ }
+ }
+ // Done!
+ else if event.name == Name::GfmTableDelimiterRow {
+ break;
+ }
+ }
+ } else if event.kind == Kind::Enter && event.name == Name::GfmTableDelimiterRow {
+ in_delimiter_row = true;
+ }
+
+ index += 1;
+ }
+
+ // Generate.
+ context.gfm_table_align = Some(align);
+ context.line_ending_if_needed();
+ context.push("");
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmTableBody`][Name::GfmTableBody].
+fn on_enter_gfm_table_body(context: &mut CompileContext) {
+ context.push("");
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmTableCell`][Name::GfmTableCell].
+fn on_enter_gfm_table_cell(context: &mut CompileContext) {
+ let column = context.gfm_table_column;
+ let align = context.gfm_table_align.as_ref().unwrap();
+
+ if column >= align.len() {
+ // Capture cell to ignore it.
+ context.buffer();
+ } else {
+ let value = align[column];
+ context.line_ending_if_needed();
+
+ if context.gfm_table_in_head {
+ context.push("
");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTableBody`][Name::GfmTableBody].
+fn on_exit_gfm_table_body(context: &mut CompileContext) {
+ context.line_ending_if_needed();
+ context.push("");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTableCell`][Name::GfmTableCell].
+fn on_exit_gfm_table_cell(context: &mut CompileContext) {
+ let align = context.gfm_table_align.as_ref().unwrap();
+
+ if context.gfm_table_column < align.len() {
+ if context.gfm_table_in_head {
+ context.push("");
+ } else {
+ context.push("");
+ }
+ } else {
+ // Stop capturing.
+ context.resume();
+ }
+
+ context.gfm_table_column += 1;
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTableHead`][Name::GfmTableHead].
+fn on_exit_gfm_table_head(context: &mut CompileContext) {
+ context.gfm_table_in_head = false;
+ context.line_ending_if_needed();
+ context.push("");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTableRow`][Name::GfmTableRow].
+fn on_exit_gfm_table_row(context: &mut CompileContext) {
+ let mut column = context.gfm_table_column;
+ let len = context.gfm_table_align.as_ref().unwrap().len();
+
+ // Add “phantom” cells, for body rows that are shorter than the delimiter
+ // row (which is equal to the head row).
+ while column < len {
+ on_enter_gfm_table_cell(context);
+ on_exit_gfm_table_cell(context);
+ column += 1;
+ }
+
+ context.gfm_table_column = 0;
+ context.line_ending_if_needed();
+ context.push("");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTaskListItemCheck`][Name::GfmTaskListItemCheck].
+fn on_exit_gfm_task_list_item_check(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("/>");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTaskListItemValueChecked`][Name::GfmTaskListItemValueChecked].
+fn on_exit_gfm_task_list_item_value_checked(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("checked=\"\" ");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`HeadingAtx`][Name::HeadingAtx].
+fn on_exit_heading_atx(context: &mut CompileContext) {
+ let rank = context
+ .heading_atx_rank
+ .take()
+ .expect("`heading_atx_rank` must be set in headings");
+
+ context.push("");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`HeadingAtxSequence`][Name::HeadingAtxSequence].
+fn on_exit_heading_atx_sequence(context: &mut CompileContext) {
+ // First fence we see.
+ if context.heading_atx_rank.is_none() {
+ let rank = Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .len();
+ context.line_ending_if_needed();
+ context.heading_atx_rank = Some(rank);
+ context.push(" context.push(" align=\"left\""),
+ GfmTableAlign::Right => context.push(" align=\"right\""),
+ GfmTableAlign::Center => context.push(" align=\"center\""),
+ GfmTableAlign::None => {}
+ }
+
+ context.push(">");
+ }
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmTableHead`][Name::GfmTableHead].
+fn on_enter_gfm_table_head(context: &mut CompileContext) {
+ context.line_ending_if_needed();
+ context.push("");
+ context.gfm_table_in_head = true;
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmTableRow`][Name::GfmTableRow].
+fn on_enter_gfm_table_row(context: &mut CompileContext) {
+ context.line_ending_if_needed();
+ context.push(" ");
+}
+
+/// Handle [`Enter`][Kind::Enter]:[`GfmTaskListItemCheck`][Name::GfmTaskListItemCheck].
+fn on_enter_gfm_task_list_item_check(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push(" | -␊
+ // ^
+ // | a
+ // ```
+ let mut at_prefix = false;
+ // Blank line directly after item, which is just a prefix.
+ //
+ // ```markdown
+ // > | -␊
+ // ^
+ // | - a
+ // ```
+ let mut at_empty_list_item = false;
+ // Blank line at block quote prefix:
+ //
+ // ```markdown
+ // > | * >␊
+ // ^
+ // | * a
+ // ```
+ let mut at_empty_block_quote = false;
+
+ if balance == 1 {
+ let mut before = index - 2;
+
+ if events[before].name == Name::ListItem {
+ before -= 1;
+
+ if events[before].name == Name::SpaceOrTab {
+ before -= 2;
+ }
+
+ if events[before].name == Name::BlockQuote
+ && events[before - 1].name == Name::BlockQuotePrefix
+ {
+ at_empty_block_quote = true;
+ } else if events[before].name == Name::ListItemPrefix {
+ at_empty_list_item = true;
+ }
+ }
+ } else {
+ let mut before = index - 2;
+
+ if events[before].name == Name::SpaceOrTab {
+ before -= 2;
+ }
+
+ if events[before].name == Name::ListItemPrefix {
+ at_prefix = true;
+ }
+ }
+
+ if !at_prefix && !at_empty_list_item && !at_empty_block_quote {
+ loose = true;
+ break;
+ }
+ }
+
+ // Done.
+ if balance == 0 && event.name == *name {
+ break;
+ }
+ }
+
+ index += 1;
+ }
+
+ context.tight_stack.push(!loose);
+ context.line_ending_if_needed();
+ // Note: no `>`.
+ context.push(if *name == Name::ListOrdered {
+ " ");
+ }
+
+ context.line_ending_if_needed();
+
+ context.push("
");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`BlankLineEnding`][Name::BlankLineEnding].
+fn on_exit_blank_line_ending(context: &mut CompileContext) {
+ if context.index == context.events.len() - 1 {
+ context.line_ending_if_needed();
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`BlockQuote`][Name::BlockQuote].
+fn on_exit_block_quote(context: &mut CompileContext) {
+ context.tight_stack.pop();
+ context.line_ending_if_needed();
+ context.slurp_one_line_ending = false;
+ context.push("");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarker`][Name::CharacterReferenceMarker].
+fn on_exit_character_reference_marker(context: &mut CompileContext) {
+ context.character_reference_marker = Some(b'&');
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarkerHexadecimal`][Name::CharacterReferenceMarkerHexadecimal].
+fn on_exit_character_reference_marker_hexadecimal(context: &mut CompileContext) {
+ context.character_reference_marker = Some(b'x');
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarkerNumeric`][Name::CharacterReferenceMarkerNumeric].
+fn on_exit_character_reference_marker_numeric(context: &mut CompileContext) {
+ context.character_reference_marker = Some(b'#');
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceValue`][Name::CharacterReferenceValue].
+fn on_exit_character_reference_value(context: &mut CompileContext) {
+ let marker = context
+ .character_reference_marker
+ .take()
+ .expect("expected `character_reference_kind` to be set");
+ let slice = Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ );
+ let value = slice.as_str();
+
+ let value = match marker {
+ b'#' => decode_numeric(value, 10),
+ b'x' => decode_numeric(value, 16),
+ b'&' => decode_named(value),
+ _ => panic!("impossible"),
+ };
+
+ context.push(&encode(&value, context.encode_html));
+}
+
+/// Handle [`Exit`][Kind::Exit]:{[`CodeFlowChunk`][Name::CodeFlowChunk],[`MathFlowChunk`][Name::MathFlowChunk]}.
+fn on_exit_raw_flow_chunk(context: &mut CompileContext) {
+ context.raw_flow_seen_data = Some(true);
+ context.push(&encode(
+ &Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ // Must serialize to get virtual spaces.
+ .serialize(),
+ context.encode_html,
+ ));
+}
+
+/// Handle [`Exit`][Kind::Exit]:{[`CodeFencedFence`][Name::CodeFencedFence],[`MathFlowFence`][Name::MathFlowFence]}.
+fn on_exit_raw_flow_fence(context: &mut CompileContext) {
+ let count = if let Some(count) = context.raw_flow_fences_count {
+ count
+ } else {
+ 0
+ };
+
+ if count == 0 {
+ context.push(">");
+ context.slurp_one_line_ending = true;
+ }
+
+ context.raw_flow_fences_count = Some(count + 1);
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`CodeFencedFenceInfo`][Name::CodeFencedFenceInfo].
+///
+/// Note: math (flow) does not support `info`.
+fn on_exit_raw_flow_fence_info(context: &mut CompileContext) {
+ let value = context.resume();
+ context.push(" class=\"language-");
+ context.push(&value);
+ context.push("\"");
+}
+
+/// Handle [`Exit`][Kind::Exit]:{[`CodeFenced`][Name::CodeFenced],[`CodeIndented`][Name::CodeIndented],[`MathFlow`][Name::MathFlow]}.
+fn on_exit_raw_flow(context: &mut CompileContext) {
+ // One special case is if we are inside a container, and the raw (flow) was
+ // not closed (meaning it runs to the end).
+ // In that case, the following line ending, is considered *outside* the
+ // fenced code and block quote by micromark, but CM wants to treat that
+ // ending as part of the code.
+ if let Some(count) = context.raw_flow_fences_count {
+ // No closing fence.
+ if count == 1
+ // In a container.
+ && !context.tight_stack.is_empty()
+ // Empty (as the closing is right at the opening fence)
+ && !matches!(context.events[context.index - 1].name, Name::CodeFencedFence | Name::MathFlowFence)
+ {
+ context.line_ending();
+ }
+ }
+
+ // But in most cases, it’s simpler: when we’ve seen some data, emit an extra
+ // line ending when needed.
+ if context
+ .raw_flow_seen_data
+ .take()
+ .expect("`raw_flow_seen_data` must be defined")
+ {
+ context.line_ending_if_needed();
+ }
+
+ context.push("");
+
+ if let Some(count) = context.raw_flow_fences_count.take() {
+ if count < 2 {
+ context.line_ending_if_needed();
+ }
+ }
+
+ context.slurp_one_line_ending = false;
+}
+
+/// Handle [`Exit`][Kind::Exit]:{[`CodeText`][Name::CodeText],[`MathText`][Name::MathText]}.
+fn on_exit_raw_text(context: &mut CompileContext) {
+ let result = context.resume();
+ let mut bytes = result.as_bytes().to_vec();
+
+ // If we are in a GFM table, we need to decode escaped pipes.
+ // This is a rather weird GFM feature.
+ if context.gfm_table_align.is_some() {
+ let mut index = 0;
+ let mut len = bytes.len();
+
+ while index < len {
+ if index + 1 < len && bytes[index] == b'\\' && bytes[index + 1] == b'|' {
+ bytes.remove(index);
+ len -= 1;
+ }
+
+ index += 1;
+ }
+ }
+
+ let mut trim = false;
+ let mut index = 0;
+ let mut end = bytes.len();
+
+ if end > 2 && bytes[index] == b' ' && bytes[end - 1] == b' ' {
+ index += 1;
+ end -= 1;
+ while index < end && !trim {
+ if bytes[index] != b' ' {
+ trim = true;
+ break;
+ }
+ index += 1;
+ }
+ }
+
+ if trim {
+ bytes.remove(0);
+ bytes.pop();
+ }
+
+ context.raw_text_inside = false;
+ context.push(str::from_utf8(&bytes).unwrap());
+
+ if !context.image_alt_inside {
+ context.push("");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:*.
+///
+/// Resumes, and ignores what was resumed.
+fn on_exit_drop(context: &mut CompileContext) {
+ context.resume();
+}
+
+/// Handle [`Exit`][Kind::Exit]:*.
+///
+/// Resumes, ignores what was resumed, and slurps the following line ending.
+fn on_exit_drop_slurp(context: &mut CompileContext) {
+ context.resume();
+ context.slurp_one_line_ending = true;
+}
+
+/// Handle [`Exit`][Kind::Exit]:{[`CodeTextData`][Name::CodeTextData],[`Data`][Name::Data],[`CharacterEscapeValue`][Name::CharacterEscapeValue]}.
+fn on_exit_data(context: &mut CompileContext) {
+ context.push(&encode(
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ context.encode_html,
+ ));
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`Definition`][Name::Definition].
+fn on_exit_definition(context: &mut CompileContext) {
+ context.resume();
+ let media = context.media_stack.pop().unwrap();
+ let indices = media.reference_id.unwrap();
+ let id =
+ normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str());
+
+ context.definitions.push(Definition {
+ id,
+ destination: media.destination,
+ title: media.title,
+ });
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`DefinitionDestinationString`][Name::DefinitionDestinationString].
+fn on_exit_definition_destination_string(context: &mut CompileContext) {
+ let buf = context.resume();
+ context.media_stack.last_mut().unwrap().destination = Some(buf);
+ context.encode_html = true;
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`DefinitionLabelString`][Name::DefinitionLabelString].
+fn on_exit_definition_label_string(context: &mut CompileContext) {
+ // Discard label, use the source content instead.
+ context.resume();
+ context.media_stack.last_mut().unwrap().reference_id =
+ Some(Position::from_exit_event(context.events, context.index).to_indices());
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`DefinitionTitleString`][Name::DefinitionTitleString].
+fn on_exit_definition_title_string(context: &mut CompileContext) {
+ let buf = context.resume();
+ context.media_stack.last_mut().unwrap().title = Some(buf);
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`Emphasis`][Name::Emphasis].
+fn on_exit_emphasis(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`Frontmatter`][Name::Frontmatter].
+fn on_exit_frontmatter(context: &mut CompileContext) {
+ context.resume();
+ context.slurp_one_line_ending = true;
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralEmail`][Name::GfmAutolinkLiteralEmail].
+fn on_exit_gfm_autolink_literal_email(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ Some("mailto:"),
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ true,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralMailto`][Name::GfmAutolinkLiteralMailto].
+fn on_exit_gfm_autolink_literal_mailto(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ None,
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ true,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralProtocol`][Name::GfmAutolinkLiteralProtocol].
+fn on_exit_gfm_autolink_literal_protocol(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ None,
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ true,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralWww`][Name::GfmAutolinkLiteralWww].
+fn on_exit_gfm_autolink_literal_www(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ Some("http://"),
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ true,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralXmpp`][Name::GfmAutolinkLiteralXmpp].
+fn on_exit_gfm_autolink_literal_xmpp(context: &mut CompileContext) {
+ generate_autolink(
+ context,
+ None,
+ Slice::from_position(
+ context.bytes,
+ &Position::from_exit_event(context.events, context.index),
+ )
+ .as_str(),
+ true,
+ );
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteCall`][Name::GfmFootnoteCall].
+fn on_exit_gfm_footnote_call(context: &mut CompileContext) {
+ let indices = context.media_stack.pop().unwrap().label_id.unwrap();
+ let id =
+ normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str());
+ let safe_id = sanitize(&id.to_lowercase());
+ let mut call_index = 0;
+
+ // See if this has been called before.
+ while call_index < context.gfm_footnote_definition_calls.len() {
+ if context.gfm_footnote_definition_calls[call_index].0 == id {
+ break;
+ }
+ call_index += 1;
+ }
+
+ // New.
+ if call_index == context.gfm_footnote_definition_calls.len() {
+ context.gfm_footnote_definition_calls.push((id, 0));
+ }
+
+ // Increment.
+ context.gfm_footnote_definition_calls[call_index].1 += 1;
+
+ // No call is output in an image alt, though the definition and
+ // backreferences are generated as if it was the case.
+ if context.image_alt_inside {
+ return;
+ }
+
+ context.push(" 1 {
+ context.push("-");
+ context.push(
+ &context.gfm_footnote_definition_calls[call_index]
+ .1
+ .to_string(),
+ );
+ }
+ context.push("\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">");
+
+ context.push(&(call_index + 1).to_string());
+ context.push("");
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteDefinitionLabelString`][Name::GfmFootnoteDefinitionLabelString].
+fn on_exit_gfm_footnote_definition_label_string(context: &mut CompileContext) {
+ context
+ .gfm_footnote_definition_stack
+ .push(Position::from_exit_event(context.events, context.index).to_indices());
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteDefinitionPrefix`][Name::GfmFootnoteDefinitionPrefix].
+fn on_exit_gfm_footnote_definition_prefix(context: &mut CompileContext) {
+ // Drop the prefix.
+ context.resume();
+ // Capture everything until end of definition.
+ context.buffer();
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteDefinition`][Name::GfmFootnoteDefinition].
+fn on_exit_gfm_footnote_definition(context: &mut CompileContext) {
+ let value = context.resume();
+ let indices = context.gfm_footnote_definition_stack.pop().unwrap();
+ context.tight_stack.pop();
+ context.gfm_footnote_definitions.push((
+ normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str()),
+ value,
+ ));
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmStrikethrough`][Name::GfmStrikethrough].
+fn on_exit_gfm_strikethrough(context: &mut CompileContext) {
+ if !context.image_alt_inside {
+ context.push("");
+ }
+}
+
+/// Handle [`Exit`][Kind::Exit]:[`GfmTable`][Name::GfmTable].
+fn on_exit_gfm_table(context: &mut CompileContext) {
+ context.gfm_table_align = None;
+ context.line_ending_if_needed();
+ context.push("