@@ -67,7 +67,25 @@ impl HtmlHandlebars {
6767 print_content
6868 . push_str ( r#"<div style="break-before: page; page-break-before: always;"></div>"# ) ;
6969 }
70- print_content. push_str ( & fixed_content) ;
70+ let print_page_id = {
71+ let mut base = path. display ( ) . to_string ( ) ;
72+ if base. ends_with ( ".md" ) {
73+ base. truncate ( base. len ( ) - 3 ) ;
74+ }
75+ & base
76+ . replace ( "/" , "-" )
77+ . replace ( "\\ " , "-" )
78+ . to_ascii_lowercase ( )
79+ } ;
80+
81+ // We have to build header links in advance so that we can know the ranges
82+ // for the headers in one page.
83+ // Insert a dummy div to make sure that we can locate the specific page.
84+ print_content. push_str ( & ( format ! ( r#"<div id="{print_page_id}"></div>"# ) ) ) ;
85+ print_content. push_str ( & build_header_links (
86+ & build_print_element_id ( & fixed_content, & print_page_id) ,
87+ Some ( print_page_id) ,
88+ ) ) ;
7189
7290 // Update the context with data for this file
7391 let ctx_path = path
@@ -239,7 +257,23 @@ impl HtmlHandlebars {
239257 code_config : & Code ,
240258 edition : Option < RustEdition > ,
241259 ) -> String {
242- let rendered = build_header_links ( & rendered) ;
260+ let rendered = build_header_links ( & rendered, None ) ;
261+ let rendered = self . post_process_common ( rendered, & playground_config, code_config, edition) ;
262+
263+ rendered
264+ }
265+
266+ /// Applies some post-processing to the HTML to apply some adjustments.
267+ ///
268+ /// This common function is used for both normal chapters (via
269+ /// `post_process`) and the combined print page.
270+ fn post_process_common (
271+ & self ,
272+ rendered : String ,
273+ playground_config : & Playground ,
274+ code_config : & Code ,
275+ edition : Option < RustEdition > ,
276+ ) -> String {
243277 let rendered = fix_code_blocks ( & rendered) ;
244278 let rendered = add_playground_pre ( & rendered, playground_config, edition) ;
245279 let rendered = hide_lines ( & rendered, code_config) ;
@@ -497,7 +531,7 @@ impl Renderer for HtmlHandlebars {
497531 debug ! ( "Render template" ) ;
498532 let rendered = handlebars. render ( "index" , & data) ?;
499533
500- let rendered = self . post_process (
534+ let rendered = self . post_process_common (
501535 rendered,
502536 & html_config. playground ,
503537 & html_config. code ,
@@ -695,9 +729,35 @@ fn make_data(
695729 Ok ( data)
696730}
697731
732+ /// Go through the rendered print page HTML,
733+ /// add path id prefix to all the elements id as well as footnote links.
734+ fn build_print_element_id ( html : & str , print_page_id : & str ) -> String {
735+ static ALL_ID : LazyLock < Regex > =
736+ LazyLock :: new ( || Regex :: new ( r#"(<[^>]*?id=")([^"]+?)""# ) . unwrap ( ) ) ;
737+ static FOOTNOTE_ID : LazyLock < Regex > = LazyLock :: new ( || {
738+ Regex :: new (
739+ r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""## ,
740+ )
741+ . unwrap ( )
742+ } ) ;
743+
744+ let temp_html = ALL_ID . replace_all ( html, |caps : & Captures < ' _ > | {
745+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
746+ } ) ;
747+
748+ FOOTNOTE_ID
749+ . replace_all ( & temp_html, |caps : & Captures < ' _ > | {
750+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
751+ } )
752+ . into_owned ( )
753+ }
754+
698755/// Goes through the rendered HTML, making sure all header tags have
699756/// an anchor respectively so people can link to sections directly.
700- fn build_header_links ( html : & str ) -> String {
757+ ///
758+ /// `print_page_id` should be set to the print page ID prefix when adjusting the
759+ /// print page.
760+ fn build_header_links ( html : & str , print_page_id : Option < & str > ) -> String {
701761 static BUILD_HEADER_LINKS : LazyLock < Regex > = LazyLock :: new ( || {
702762 Regex :: new ( r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"# ) . unwrap ( )
703763 } ) ;
@@ -726,21 +786,34 @@ fn build_header_links(html: &str) -> String {
726786 caps. get ( 2 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
727787 caps. get ( 3 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
728788 & mut id_counter,
789+ print_page_id,
729790 )
730791 } )
731792 . into_owned ( )
732793}
733794
734795/// Insert a single link into a header, making sure each link gets its own
735796/// unique ID by appending an auto-incremented number (if necessary).
797+ ///
798+ /// For `print.html`, we will add a path id prefix.
736799fn insert_link_into_header (
737800 level : usize ,
738801 content : & str ,
739802 id : Option < String > ,
740803 classes : Option < String > ,
741804 id_counter : & mut HashMap < String , usize > ,
805+ print_page_id : Option < & str > ,
742806) -> String {
743- let id = id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) ) ;
807+ let id = if let Some ( print_page_id) = print_page_id {
808+ let content_id = {
809+ #[ allow( deprecated) ]
810+ utils:: id_from_content ( content)
811+ } ;
812+ let with_prefix = format ! ( "{} {}" , print_page_id, content_id) ;
813+ id. unwrap_or_else ( || utils:: unique_id_from_content ( & with_prefix, id_counter) )
814+ } else {
815+ id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) )
816+ } ;
744817 let classes = classes
745818 . map ( |s| format ! ( " class=\" {s}\" " ) )
746819 . unwrap_or_default ( ) ;
@@ -1123,7 +1196,7 @@ mod tests {
11231196 ] ;
11241197
11251198 for ( src, should_be) in inputs {
1126- let got = build_header_links ( src) ;
1199+ let got = build_header_links ( src, None ) ;
11271200 assert_eq ! ( got, should_be) ;
11281201 }
11291202 }
0 commit comments