@@ -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 ,
@@ -561,7 +595,7 @@ impl Renderer for HtmlHandlebars {
561595 let rendered = handlebars. render ( "index" , & data) ?;
562596
563597 let rendered =
564- self . post_process ( rendered, & html_config. playground , ctx. config . rust . edition ) ;
598+ self . post_process_print ( rendered, & html_config. playground , ctx. config . rust . edition ) ;
565599
566600 utils:: fs:: write_file ( destination, "print.html" , rendered. as_bytes ( ) ) ?;
567601 debug ! ( "Creating print.html ✓" ) ;
@@ -761,9 +795,43 @@ fn make_data(
761795 Ok ( data)
762796}
763797
798+ /// Goes through part of the rendered print page HTML,
799+ /// add path id prefix to all the elements id as well as footnote links.
800+ fn build_print_element_id ( html : & str , path_id : & str ) -> String {
801+ let all_id = Regex :: new ( r#"(<[^>]*?id=")([^"]+?)""# ) . unwrap ( ) ;
802+ let footnote_id = Regex :: new (
803+ r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""## ,
804+ )
805+ . unwrap ( ) ;
806+
807+ if path_id. is_empty ( ) {
808+ return html. to_string ( ) ;
809+ }
810+
811+ let temp_html = all_id
812+ . replace_all ( html, |caps : & Captures < ' _ > | {
813+ let mut fixed = String :: new ( ) ;
814+ fixed. push_str ( & path_id) ;
815+ fixed. push_str ( "-" ) ;
816+ fixed. push_str ( & caps[ 2 ] ) ;
817+ format ! ( "{}{}\" " , & caps[ 1 ] , fixed)
818+ } )
819+ . into_owned ( ) ;
820+
821+ footnote_id
822+ . replace_all ( & temp_html, |caps : & Captures < ' _ > | {
823+ let mut fixed = String :: new ( ) ;
824+ fixed. push_str ( & path_id) ;
825+ fixed. push_str ( "-" ) ;
826+ fixed. push_str ( & caps[ 2 ] ) ;
827+ format ! ( "{}{}\" " , & caps[ 1 ] , fixed)
828+ } )
829+ . into_owned ( )
830+ }
831+
764832/// Goes through the rendered HTML, making sure all header tags have
765833/// an anchor respectively so people can link to sections directly.
766- fn build_header_links ( html : & str ) -> String {
834+ fn build_header_links ( html : & str , path_id : Option < & str > ) -> String {
767835 lazy_static ! {
768836 static ref BUILD_HEADER_LINKS : Regex = Regex :: new( r"<h(\d)>(.*?)</h\d>" ) . unwrap( ) ;
769837 }
@@ -776,19 +844,26 @@ fn build_header_links(html: &str) -> String {
776844 . parse ( )
777845 . expect ( "Regex should ensure we only ever get numbers here" ) ;
778846
779- insert_link_into_header ( level, & caps[ 2 ] , & mut id_counter)
847+ insert_link_into_header ( level, & caps[ 2 ] , & mut id_counter, path_id )
780848 } )
781849 . into_owned ( )
782850}
783851
784852/// Insert a sinle link into a header, making sure each link gets its own
785853/// unique ID by appending an auto-incremented number (if necessary).
854+ ///
855+ /// For `print.html`, we will add a path id prefix.
786856fn insert_link_into_header (
787857 level : usize ,
788858 content : & str ,
789859 id_counter : & mut HashMap < String , usize > ,
860+ path_id : Option < & str > ,
790861) -> String {
791- let id = utils:: unique_id_from_content ( content, id_counter) ;
862+ let id = if let Some ( path_id) = path_id {
863+ utils:: unique_id_from_content_with_path ( content, id_counter, path_id)
864+ } else {
865+ utils:: unique_id_from_content ( content, id_counter)
866+ } ;
792867
793868 format ! (
794869 r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"## ,
@@ -996,7 +1071,7 @@ mod tests {
9961071 ] ;
9971072
9981073 for ( src, should_be) in inputs {
999- let got = build_header_links ( src) ;
1074+ let got = build_header_links ( src, None ) ;
10001075 assert_eq ! ( got, should_be) ;
10011076 }
10021077 }
0 commit comments