@@ -54,16 +54,38 @@ impl HtmlHandlebars {
5454 let content = ch. content . clone ( ) ;
5555 let content = utils:: render_markdown ( & content, ctx. html_config . curly_quotes ) ;
5656
57- let fixed_content =
58- utils:: render_markdown_with_path ( & ch. content , ctx. html_config . curly_quotes , Some ( path) ) ;
57+ let fixed_content = utils:: render_markdown_with_path (
58+ & ch. content ,
59+ ctx. html_config . curly_quotes ,
60+ Some ( path) ,
61+ ctx. html_config . redirect ,
62+ ) ;
5963 if !ctx. is_index && ctx. html_config . print . page_break {
6064 // Add page break between chapters
6165 // 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
6266 // Add both two CSS properties because of the compatibility issue
6367 print_content
6468 . push_str ( r#"<div style="break-before: page; page-break-before: always;"></div>"# ) ;
6569 }
66- print_content. push_str ( & fixed_content) ;
70+ let path_id = {
71+ let mut base = path. display ( ) . to_string ( ) ;
72+ if base. ends_with ( ".md" ) {
73+ base. replace_range ( 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="{}"></div>"# , & path_id) ) ) ;
85+ print_content. push_str ( & build_header_links (
86+ & build_print_element_id ( & fixed_content, & path_id) ,
87+ Some ( path_id) ,
88+ ) ) ;
6789
6890 // Update the context with data for this file
6991 let ctx_path = path
@@ -188,19 +210,31 @@ impl HtmlHandlebars {
188210 }
189211
190212 #[ cfg_attr( feature = "cargo-clippy" , allow( clippy:: let_and_return) ) ]
191- fn post_process (
213+ fn post_process_print (
192214 & self ,
193215 rendered : String ,
194216 playground_config : & Playground ,
195217 edition : Option < RustEdition > ,
196218 ) -> String {
197- let rendered = build_header_links ( & rendered) ;
198219 let rendered = fix_code_blocks ( & rendered) ;
199220 let rendered = add_playground_pre ( & rendered, playground_config, edition) ;
200221
201222 rendered
202223 }
203224
225+ #[ cfg_attr( feature = "cargo-clippy" , allow( clippy:: let_and_return) ) ]
226+ fn post_process (
227+ & self ,
228+ rendered : String ,
229+ playground_config : & Playground ,
230+ edition : Option < RustEdition > ,
231+ ) -> String {
232+ let rendered = build_header_links ( & rendered, None ) ;
233+ let rendered = self . post_process_print ( rendered, & playground_config, edition) ;
234+
235+ rendered
236+ }
237+
204238 fn copy_static_files (
205239 & self ,
206240 destination : & Path ,
@@ -560,7 +594,7 @@ impl Renderer for HtmlHandlebars {
560594 let rendered = handlebars. render ( "index" , & data) ?;
561595
562596 let rendered =
563- self . post_process ( rendered, & html_config. playground , ctx. config . rust . edition ) ;
597+ self . post_process_print ( rendered, & html_config. playground , ctx. config . rust . edition ) ;
564598
565599 utils:: fs:: write_file ( destination, "print.html" , rendered. as_bytes ( ) ) ?;
566600 debug ! ( "Creating print.html ✓" ) ;
@@ -760,9 +794,43 @@ fn make_data(
760794 Ok ( data)
761795}
762796
797+ /// Goes through part of the rendered print page HTML,
798+ /// add path id prefix to all the elements id as well as footnote links.
799+ fn build_print_element_id ( html : & str , path_id : & str ) -> String {
800+ let all_id = Regex :: new ( r#"(<[^>]*?id=")([^"]+?)""# ) . unwrap ( ) ;
801+ let footnote_id = Regex :: new (
802+ r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""## ,
803+ )
804+ . unwrap ( ) ;
805+
806+ if path_id. is_empty ( ) {
807+ return html. to_string ( ) ;
808+ }
809+
810+ let temp_html = all_id
811+ . replace_all ( html, |caps : & Captures < ' _ > | {
812+ let mut fixed = String :: new ( ) ;
813+ fixed. push_str ( & path_id) ;
814+ fixed. push_str ( "-" ) ;
815+ fixed. push_str ( & caps[ 2 ] ) ;
816+ format ! ( "{}{}\" " , & caps[ 1 ] , fixed)
817+ } )
818+ . into_owned ( ) ;
819+
820+ footnote_id
821+ . replace_all ( & temp_html, |caps : & Captures < ' _ > | {
822+ let mut fixed = String :: new ( ) ;
823+ fixed. push_str ( & path_id) ;
824+ fixed. push_str ( "-" ) ;
825+ fixed. push_str ( & caps[ 2 ] ) ;
826+ format ! ( "{}{}\" " , & caps[ 1 ] , fixed)
827+ } )
828+ . into_owned ( )
829+ }
830+
763831/// Goes through the rendered HTML, making sure all header tags have
764832/// an anchor respectively so people can link to sections directly.
765- fn build_header_links ( html : & str ) -> String {
833+ fn build_header_links ( html : & str , path_id : Option < & str > ) -> String {
766834 lazy_static ! {
767835 static ref BUILD_HEADER_LINKS : Regex = Regex :: new( r"<h(\d)>(.*?)</h\d>" ) . unwrap( ) ;
768836 }
@@ -775,19 +843,26 @@ fn build_header_links(html: &str) -> String {
775843 . parse ( )
776844 . expect ( "Regex should ensure we only ever get numbers here" ) ;
777845
778- insert_link_into_header ( level, & caps[ 2 ] , & mut id_counter)
846+ insert_link_into_header ( level, & caps[ 2 ] , & mut id_counter, path_id )
779847 } )
780848 . into_owned ( )
781849}
782850
783851/// Insert a sinle link into a header, making sure each link gets its own
784852/// unique ID by appending an auto-incremented number (if necessary).
853+ ///
854+ /// For `print.html`, we will add a path id prefix.
785855fn insert_link_into_header (
786856 level : usize ,
787857 content : & str ,
788858 id_counter : & mut HashMap < String , usize > ,
859+ path_id : Option < & str > ,
789860) -> String {
790- let id = utils:: unique_id_from_content ( content, id_counter) ;
861+ let id = if let Some ( path_id) = path_id {
862+ utils:: unique_id_from_content_with_path ( content, id_counter, path_id)
863+ } else {
864+ utils:: unique_id_from_content ( content, id_counter)
865+ } ;
791866
792867 format ! (
793868 r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"## ,
@@ -998,7 +1073,7 @@ mod tests {
9981073 ] ;
9991074
10001075 for ( src, should_be) in inputs {
1001- let got = build_header_links ( src) ;
1076+ let got = build_header_links ( src, None ) ;
10021077 assert_eq ! ( got, should_be) ;
10031078 }
10041079 }
0 commit comments