@@ -8,7 +8,7 @@ use mdbook_core::book::{Book, BookItem};
88use mdbook_core:: config:: { BookConfig , Code , Config , HtmlConfig , Playground , RustEdition } ;
99use mdbook_core:: utils;
1010use mdbook_core:: utils:: fs:: get_404_output_file;
11- use mdbook_markdown:: { render_markdown, render_markdown_with_path } ;
11+ use mdbook_markdown:: { render_markdown, render_markdown_with_path_and_redirects } ;
1212use mdbook_renderer:: { RenderContext , Renderer } ;
1313use regex:: { Captures , Regex } ;
1414use serde_json:: json;
@@ -58,16 +58,38 @@ impl HtmlHandlebars {
5858
5959 let content = render_markdown ( & ch. content , ctx. html_config . smart_punctuation ( ) ) ;
6060
61- let fixed_content =
62- render_markdown_with_path ( & ch. content , ctx. html_config . smart_punctuation ( ) , Some ( path) ) ;
61+ let printed_item = render_markdown_with_path_and_redirects (
62+ & ch. content ,
63+ ctx. html_config . smart_punctuation ( ) ,
64+ Some ( path) ,
65+ & ctx. html_config . redirect ,
66+ ) ;
6367 if !ctx. is_index && ctx. html_config . print . page_break {
6468 // Add page break between chapters
6569 // See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
6670 // Add both two CSS properties because of the compatibility issue
6771 print_content
6872 . push_str ( r#"<div style="break-before: page; page-break-before: always;"></div>"# ) ;
6973 }
70- print_content. push_str ( & fixed_content) ;
74+ let print_page_id = {
75+ let mut base = path. display ( ) . to_string ( ) ;
76+ if base. ends_with ( ".md" ) {
77+ base. truncate ( base. len ( ) - 3 ) ;
78+ }
79+ & base
80+ . replace ( "/" , "-" )
81+ . replace ( "\\ " , "-" )
82+ . to_ascii_lowercase ( )
83+ } ;
84+
85+ // We have to build header links in advance so that we can know the ranges
86+ // for the headers in one page.
87+ // Insert a dummy div to make sure that we can locate the specific page.
88+ print_content. push_str ( & ( format ! ( r#"<div id="{print_page_id}"></div>"# ) ) ) ;
89+ print_content. push_str ( & build_header_links (
90+ & build_print_element_id ( & printed_item, & print_page_id) ,
91+ Some ( print_page_id) ,
92+ ) ) ;
7193
7294 // Update the context with data for this file
7395 let ctx_path = path
@@ -219,7 +241,23 @@ impl HtmlHandlebars {
219241 code_config : & Code ,
220242 edition : Option < RustEdition > ,
221243 ) -> String {
222- let rendered = build_header_links ( & rendered) ;
244+ let rendered = build_header_links ( & rendered, None ) ;
245+ let rendered = self . post_process_common ( rendered, & playground_config, code_config, edition) ;
246+
247+ rendered
248+ }
249+
250+ /// Applies some post-processing to the HTML to apply some adjustments.
251+ ///
252+ /// This common function is used for both normal chapters (via
253+ /// `post_process`) and the combined print page.
254+ fn post_process_common (
255+ & self ,
256+ rendered : String ,
257+ playground_config : & Playground ,
258+ code_config : & Code ,
259+ edition : Option < RustEdition > ,
260+ ) -> String {
223261 let rendered = fix_code_blocks ( & rendered) ;
224262 let rendered = add_playground_pre ( & rendered, playground_config, edition) ;
225263 let rendered = hide_lines ( & rendered, code_config) ;
@@ -474,7 +512,7 @@ impl Renderer for HtmlHandlebars {
474512 debug ! ( "Render template" ) ;
475513 let rendered = handlebars. render ( "index" , & data) ?;
476514
477- let rendered = self . post_process (
515+ let rendered = self . post_process_common (
478516 rendered,
479517 & html_config. playground ,
480518 & html_config. code ,
@@ -672,9 +710,35 @@ fn make_data(
672710 Ok ( data)
673711}
674712
713+ /// Go through the rendered print page HTML,
714+ /// add path id prefix to all the elements id as well as footnote links.
715+ fn build_print_element_id ( html : & str , print_page_id : & str ) -> String {
716+ static ALL_ID : LazyLock < Regex > =
717+ LazyLock :: new ( || Regex :: new ( r#"(<[^>]*?id=")([^"]+?)""# ) . unwrap ( ) ) ;
718+ static FOOTNOTE_ID : LazyLock < Regex > = LazyLock :: new ( || {
719+ Regex :: new (
720+ r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""## ,
721+ )
722+ . unwrap ( )
723+ } ) ;
724+
725+ let temp_html = ALL_ID . replace_all ( html, |caps : & Captures < ' _ > | {
726+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
727+ } ) ;
728+
729+ FOOTNOTE_ID
730+ . replace_all ( & temp_html, |caps : & Captures < ' _ > | {
731+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
732+ } )
733+ . into_owned ( )
734+ }
735+
675736/// Goes through the rendered HTML, making sure all header tags have
676737/// an anchor respectively so people can link to sections directly.
677- fn build_header_links ( html : & str ) -> String {
738+ ///
739+ /// `print_page_id` should be set to the print page ID prefix when adjusting the
740+ /// print page.
741+ fn build_header_links ( html : & str , print_page_id : Option < & str > ) -> String {
678742 static BUILD_HEADER_LINKS : LazyLock < Regex > = LazyLock :: new ( || {
679743 Regex :: new ( r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"# ) . unwrap ( )
680744 } ) ;
@@ -703,21 +767,34 @@ fn build_header_links(html: &str) -> String {
703767 caps. get ( 2 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
704768 caps. get ( 3 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
705769 & mut id_counter,
770+ print_page_id,
706771 )
707772 } )
708773 . into_owned ( )
709774}
710775
711776/// Insert a single link into a header, making sure each link gets its own
712777/// unique ID by appending an auto-incremented number (if necessary).
778+ ///
779+ /// For `print.html`, we will add a path id prefix.
713780fn insert_link_into_header (
714781 level : usize ,
715782 content : & str ,
716783 id : Option < String > ,
717784 classes : Option < String > ,
718785 id_counter : & mut HashMap < String , usize > ,
786+ print_page_id : Option < & str > ,
719787) -> String {
720- let id = id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) ) ;
788+ let id = if let Some ( print_page_id) = print_page_id {
789+ let content_id = {
790+ #[ allow( deprecated) ]
791+ utils:: id_from_content ( content)
792+ } ;
793+ let with_prefix = format ! ( "{} {}" , print_page_id, content_id) ;
794+ id. unwrap_or_else ( || utils:: unique_id_from_content ( & with_prefix, id_counter) )
795+ } else {
796+ id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) )
797+ } ;
721798 let classes = classes
722799 . map ( |s| format ! ( " class=\" {s}\" " ) )
723800 . unwrap_or_default ( ) ;
@@ -1052,7 +1129,7 @@ mod tests {
10521129 ] ;
10531130
10541131 for ( src, should_be) in inputs {
1055- let got = build_header_links ( src) ;
1132+ let got = build_header_links ( src, None ) ;
10561133 assert_eq ! ( got, should_be) ;
10571134 }
10581135 }
0 commit comments