@@ -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 ,
@@ -698,9 +732,35 @@ fn make_data(
698732 Ok ( data)
699733}
700734
735+ /// Go through the rendered print page HTML,
736+ /// add path id prefix to all the elements id as well as footnote links.
737+ fn build_print_element_id ( html : & str , print_page_id : & str ) -> String {
738+ static ALL_ID : LazyLock < Regex > =
739+ LazyLock :: new ( || Regex :: new ( r#"(<[^>]*?id=")([^"]+?)""# ) . unwrap ( ) ) ;
740+ static FOOTNOTE_ID : LazyLock < Regex > = LazyLock :: new ( || {
741+ Regex :: new (
742+ r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""## ,
743+ )
744+ . unwrap ( )
745+ } ) ;
746+
747+ let temp_html = ALL_ID . replace_all ( html, |caps : & Captures < ' _ > | {
748+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
749+ } ) ;
750+
751+ FOOTNOTE_ID
752+ . replace_all ( & temp_html, |caps : & Captures < ' _ > | {
753+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
754+ } )
755+ . into_owned ( )
756+ }
757+
701758/// Goes through the rendered HTML, making sure all header tags have
702759/// an anchor respectively so people can link to sections directly.
703- fn build_header_links ( html : & str ) -> String {
760+ ///
761+ /// `print_page_id` should be set to the print page ID prefix when adjusting the
762+ /// print page.
763+ fn build_header_links ( html : & str , print_page_id : Option < & str > ) -> String {
704764 static_regex ! (
705765 BUILD_HEADER_LINKS ,
706766 r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#
@@ -730,21 +790,34 @@ fn build_header_links(html: &str) -> String {
730790 caps. get ( 2 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
731791 caps. get ( 3 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
732792 & mut id_counter,
793+ print_page_id,
733794 )
734795 } )
735796 . into_owned ( )
736797}
737798
738799/// Insert a single link into a header, making sure each link gets its own
739800/// unique ID by appending an auto-incremented number (if necessary).
801+ ///
802+ /// For `print.html`, we will add a path id prefix.
740803fn insert_link_into_header (
741804 level : usize ,
742805 content : & str ,
743806 id : Option < String > ,
744807 classes : Option < String > ,
745808 id_counter : & mut HashMap < String , usize > ,
809+ print_page_id : Option < & str > ,
746810) -> String {
747- let id = id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) ) ;
811+ let id = if let Some ( print_page_id) = print_page_id {
812+ let content_id = {
813+ #[ allow( deprecated) ]
814+ utils:: id_from_content ( content)
815+ } ;
816+ let with_prefix = format ! ( "{} {}" , print_page_id, content_id) ;
817+ id. unwrap_or_else ( || utils:: unique_id_from_content ( & with_prefix, id_counter) )
818+ } else {
819+ id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) )
820+ } ;
748821 let classes = classes
749822 . map ( |s| format ! ( " class=\" {s}\" " ) )
750823 . unwrap_or_default ( ) ;
@@ -1125,7 +1198,7 @@ mod tests {
11251198 ] ;
11261199
11271200 for ( src, should_be) in inputs {
1128- let got = build_header_links ( src) ;
1201+ let got = build_header_links ( src, None ) ;
11291202 assert_eq ! ( got, should_be) ;
11301203 }
11311204 }
0 commit comments