@@ -67,7 +67,25 @@ impl HtmlHandlebars {
67
67
print_content
68
68
. push_str ( r#"<div style="break-before: page; page-break-before: always;"></div>"# ) ;
69
69
}
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
+ ) ) ;
71
89
72
90
// Update the context with data for this file
73
91
let ctx_path = path
@@ -239,7 +257,23 @@ impl HtmlHandlebars {
239
257
code_config : & Code ,
240
258
edition : Option < RustEdition > ,
241
259
) -> 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 {
243
277
let rendered = fix_code_blocks ( & rendered) ;
244
278
let rendered = add_playground_pre ( & rendered, playground_config, edition) ;
245
279
let rendered = hide_lines ( & rendered, code_config) ;
@@ -497,7 +531,7 @@ impl Renderer for HtmlHandlebars {
497
531
debug ! ( "Render template" ) ;
498
532
let rendered = handlebars. render ( "index" , & data) ?;
499
533
500
- let rendered = self . post_process (
534
+ let rendered = self . post_process_common (
501
535
rendered,
502
536
& html_config. playground ,
503
537
& html_config. code ,
@@ -695,9 +729,35 @@ fn make_data(
695
729
Ok ( data)
696
730
}
697
731
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
+
698
755
/// Goes through the rendered HTML, making sure all header tags have
699
756
/// 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 {
701
761
static BUILD_HEADER_LINKS : LazyLock < Regex > = LazyLock :: new ( || {
702
762
Regex :: new ( r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"# ) . unwrap ( )
703
763
} ) ;
@@ -726,21 +786,34 @@ fn build_header_links(html: &str) -> String {
726
786
caps. get ( 2 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
727
787
caps. get ( 3 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
728
788
& mut id_counter,
789
+ print_page_id,
729
790
)
730
791
} )
731
792
. into_owned ( )
732
793
}
733
794
734
795
/// Insert a single link into a header, making sure each link gets its own
735
796
/// unique ID by appending an auto-incremented number (if necessary).
797
+ ///
798
+ /// For `print.html`, we will add a path id prefix.
736
799
fn insert_link_into_header (
737
800
level : usize ,
738
801
content : & str ,
739
802
id : Option < String > ,
740
803
classes : Option < String > ,
741
804
id_counter : & mut HashMap < String , usize > ,
805
+ print_page_id : Option < & str > ,
742
806
) -> 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
+ } ;
744
817
let classes = classes
745
818
. map ( |s| format ! ( " class=\" {s}\" " ) )
746
819
. unwrap_or_default ( ) ;
@@ -1123,7 +1196,7 @@ mod tests {
1123
1196
] ;
1124
1197
1125
1198
for ( src, should_be) in inputs {
1126
- let got = build_header_links ( src) ;
1199
+ let got = build_header_links ( src, None ) ;
1127
1200
assert_eq ! ( got, should_be) ;
1128
1201
}
1129
1202
}
0 commit comments