@@ -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 ,
@@ -698,9 +732,35 @@ fn make_data(
698
732
Ok ( data)
699
733
}
700
734
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
+
701
758
/// Goes through the rendered HTML, making sure all header tags have
702
759
/// 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 {
704
764
static_regex ! (
705
765
BUILD_HEADER_LINKS ,
706
766
r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#
@@ -730,21 +790,34 @@ fn build_header_links(html: &str) -> String {
730
790
caps. get ( 2 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
731
791
caps. get ( 3 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
732
792
& mut id_counter,
793
+ print_page_id,
733
794
)
734
795
} )
735
796
. into_owned ( )
736
797
}
737
798
738
799
/// Insert a single link into a header, making sure each link gets its own
739
800
/// unique ID by appending an auto-incremented number (if necessary).
801
+ ///
802
+ /// For `print.html`, we will add a path id prefix.
740
803
fn insert_link_into_header (
741
804
level : usize ,
742
805
content : & str ,
743
806
id : Option < String > ,
744
807
classes : Option < String > ,
745
808
id_counter : & mut HashMap < String , usize > ,
809
+ print_page_id : Option < & str > ,
746
810
) -> 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
+ } ;
748
821
let classes = classes
749
822
. map ( |s| format ! ( " class=\" {s}\" " ) )
750
823
. unwrap_or_default ( ) ;
@@ -1125,7 +1198,7 @@ mod tests {
1125
1198
] ;
1126
1199
1127
1200
for ( src, should_be) in inputs {
1128
- let got = build_header_links ( src) ;
1201
+ let got = build_header_links ( src, None ) ;
1129
1202
assert_eq ! ( got, should_be) ;
1130
1203
}
1131
1204
}
0 commit comments