@@ -8,7 +8,7 @@ use mdbook_core::book::{Book, BookItem};
8
8
use mdbook_core:: config:: { BookConfig , Code , Config , HtmlConfig , Playground , RustEdition } ;
9
9
use mdbook_core:: utils;
10
10
use 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 } ;
12
12
use mdbook_renderer:: { RenderContext , Renderer } ;
13
13
use regex:: { Captures , Regex } ;
14
14
use serde_json:: json;
@@ -58,16 +58,38 @@ impl HtmlHandlebars {
58
58
59
59
let content = render_markdown ( & ch. content , ctx. html_config . smart_punctuation ( ) ) ;
60
60
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
+ ) ;
63
67
if !ctx. is_index && ctx. html_config . print . page_break {
64
68
// Add page break between chapters
65
69
// 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
66
70
// Add both two CSS properties because of the compatibility issue
67
71
print_content
68
72
. push_str ( r#"<div style="break-before: page; page-break-before: always;"></div>"# ) ;
69
73
}
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
+ ) ) ;
71
93
72
94
// Update the context with data for this file
73
95
let ctx_path = path
@@ -219,7 +241,23 @@ impl HtmlHandlebars {
219
241
code_config : & Code ,
220
242
edition : Option < RustEdition > ,
221
243
) -> 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 {
223
261
let rendered = fix_code_blocks ( & rendered) ;
224
262
let rendered = add_playground_pre ( & rendered, playground_config, edition) ;
225
263
let rendered = hide_lines ( & rendered, code_config) ;
@@ -474,7 +512,7 @@ impl Renderer for HtmlHandlebars {
474
512
debug ! ( "Render template" ) ;
475
513
let rendered = handlebars. render ( "index" , & data) ?;
476
514
477
- let rendered = self . post_process (
515
+ let rendered = self . post_process_common (
478
516
rendered,
479
517
& html_config. playground ,
480
518
& html_config. code ,
@@ -667,9 +705,35 @@ fn make_data(
667
705
Ok ( data)
668
706
}
669
707
708
+ /// Go through the rendered print page HTML,
709
+ /// add path id prefix to all the elements id as well as footnote links.
710
+ fn build_print_element_id ( html : & str , print_page_id : & str ) -> String {
711
+ static ALL_ID : LazyLock < Regex > =
712
+ LazyLock :: new ( || Regex :: new ( r#"(<[^>]*?id=")([^"]+?)""# ) . unwrap ( ) ) ;
713
+ static FOOTNOTE_ID : LazyLock < Regex > = LazyLock :: new ( || {
714
+ Regex :: new (
715
+ r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""## ,
716
+ )
717
+ . unwrap ( )
718
+ } ) ;
719
+
720
+ let temp_html = ALL_ID . replace_all ( html, |caps : & Captures < ' _ > | {
721
+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
722
+ } ) ;
723
+
724
+ FOOTNOTE_ID
725
+ . replace_all ( & temp_html, |caps : & Captures < ' _ > | {
726
+ format ! ( "{}{}-{}\" " , & caps[ 1 ] , print_page_id, & caps[ 2 ] )
727
+ } )
728
+ . into_owned ( )
729
+ }
730
+
670
731
/// Goes through the rendered HTML, making sure all header tags have
671
732
/// an anchor respectively so people can link to sections directly.
672
- fn build_header_links ( html : & str ) -> String {
733
+ ///
734
+ /// `print_page_id` should be set to the print page ID prefix when adjusting the
735
+ /// print page.
736
+ fn build_header_links ( html : & str , print_page_id : Option < & str > ) -> String {
673
737
static BUILD_HEADER_LINKS : LazyLock < Regex > = LazyLock :: new ( || {
674
738
Regex :: new ( r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"# ) . unwrap ( )
675
739
} ) ;
@@ -698,21 +762,34 @@ fn build_header_links(html: &str) -> String {
698
762
caps. get ( 2 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
699
763
caps. get ( 3 ) . map ( |x| x. as_str ( ) . to_string ( ) ) ,
700
764
& mut id_counter,
765
+ print_page_id,
701
766
)
702
767
} )
703
768
. into_owned ( )
704
769
}
705
770
706
771
/// Insert a single link into a header, making sure each link gets its own
707
772
/// unique ID by appending an auto-incremented number (if necessary).
773
+ ///
774
+ /// For `print.html`, we will add a path id prefix.
708
775
fn insert_link_into_header (
709
776
level : usize ,
710
777
content : & str ,
711
778
id : Option < String > ,
712
779
classes : Option < String > ,
713
780
id_counter : & mut HashMap < String , usize > ,
781
+ print_page_id : Option < & str > ,
714
782
) -> String {
715
- let id = id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) ) ;
783
+ let id = if let Some ( print_page_id) = print_page_id {
784
+ let content_id = {
785
+ #[ allow( deprecated) ]
786
+ utils:: id_from_content ( content)
787
+ } ;
788
+ let with_prefix = format ! ( "{} {}" , print_page_id, content_id) ;
789
+ id. unwrap_or_else ( || utils:: unique_id_from_content ( & with_prefix, id_counter) )
790
+ } else {
791
+ id. unwrap_or_else ( || utils:: unique_id_from_content ( content, id_counter) )
792
+ } ;
716
793
let classes = classes
717
794
. map ( |s| format ! ( " class=\" {s}\" " ) )
718
795
. unwrap_or_default ( ) ;
@@ -1047,7 +1124,7 @@ mod tests {
1047
1124
] ;
1048
1125
1049
1126
for ( src, should_be) in inputs {
1050
- let got = build_header_links ( src) ;
1127
+ let got = build_header_links ( src, None ) ;
1051
1128
assert_eq ! ( got, should_be) ;
1052
1129
}
1053
1130
}
0 commit comments