diff --git a/crates/mdbook-html/front-end/css/general.css b/crates/mdbook-html/front-end/css/general.css
index 91401e3bb2..cf931876b2 100644
--- a/crates/mdbook-html/front-end/css/general.css
+++ b/crates/mdbook-html/front-end/css/general.css
@@ -406,3 +406,35 @@ dd > p {
/* Add some space between the icon and the text. */
margin-right: 8px;
}
+
+.content .checkbox-img, .checkbox-label > .img-wrapper {
+ display: none;
+}
+.content .checkbox-label {
+ display: block;
+ max-width: 100%;
+}
+.content .checkbox-label img {
+ cursor: zoom-in;
+}
+.content .checkbox-img:checked + .checkbox-label > .img-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.4);
+ z-index: 1001;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: zoom-out;
+}
+.content .checkbox-img:checked + .checkbox-label > .img-wrapper img {
+ --img-padding: 5px;
+ padding: var(--img-padding);
+ background: #999;
+ cursor: zoom-out;
+ max-width: calc(100% - (var(--img-padding) * 2));
+ max-height: calc(100% - (var(--img-padding) * 2));
+}
diff --git a/crates/mdbook-html/front-end/js/book.js b/crates/mdbook-html/front-end/js/book.js
index fe74b7106f..3790ccd8a8 100644
--- a/crates/mdbook-html/front-end/js/book.js
+++ b/crates/mdbook-html/front-end/js/book.js
@@ -658,6 +658,12 @@ aria-label="Show hidden lines">';
})();
(function chapterNavigation() {
+ function zoomOutImages() {
+ for (const elem of Array.from(document.querySelectorAll('input.checkbox-img'))) {
+ elem.checked = false;
+ }
+ }
+
document.addEventListener('keydown', function(e) {
if (e.altKey ||
e.ctrlKey ||
@@ -724,6 +730,9 @@ aria-label="Show hidden lines">';
e.preventDefault();
showHelp();
break;
+ case 'Escape':
+ zoomOutImages();
+ break;
}
// Rest of the keys are only active when the Shift key is not pressed
diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs
index 5cb97ce378..99b787ae8d 100644
--- a/crates/mdbook-html/src/html/tree.rs
+++ b/crates/mdbook-html/src/html/tree.rs
@@ -9,7 +9,7 @@ use super::tokenizer::parse_html;
use super::{HtmlRenderOptions, hide_lines, wrap_rust_main};
use crate::utils::{id_from_content, unique_id};
use ego_tree::{NodeId, NodeRef, Tree};
-use html5ever::tendril::StrTendril;
+use html5ever::tendril::{SliceExt, StrTendril};
use html5ever::tokenizer::{TagKind, Token};
use html5ever::{LocalName, QualName};
use indexmap::IndexMap;
@@ -69,7 +69,7 @@ impl Node {
}
/// An HTML element.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub(crate) struct Element {
/// The tag name.
pub(crate) name: QualName,
@@ -199,6 +199,8 @@ pub(crate) struct MarkdownTreeBuilder<'opts, 'event, EventIter> {
/// tag. After the document has been parsed, all the definitions are moved
/// to the end of the document.
footnote_defs: HashMap, NodeId>,
+ /// Current ID to be used to generate the images label and checkbox.
+ img_label_id: usize,
}
impl<'opts, 'event, EventIter> MarkdownTreeBuilder<'opts, 'event, EventIter>
@@ -222,6 +224,7 @@ where
table_cell_index: 0,
footnote_numbers: HashMap::new(),
footnote_defs: HashMap::new(),
+ img_label_id: 0,
};
builder.process_events();
builder.add_header_links();
@@ -230,6 +233,13 @@ where
builder.tree
}
+ /// Returns the current `img_label_id` and increase its value.
+ fn get_current_img_label_id(&mut self) -> usize {
+ let ret = self.img_label_id;
+ self.img_label_id += 1;
+ ret
+ }
+
/// Append a new child to the current node.
///
/// Returns the [`NodeId`] of the new node.
@@ -557,16 +567,39 @@ where
title,
id: _,
} => {
+ let img_input_id = format!("checkbox-img-{}", self.get_current_img_label_id());
+
+ let mut input = Element::new("input");
+ input.insert_attr("id", img_input_id.to_tendril());
+ input.insert_attr("class", "checkbox-img".to_tendril());
+ input.insert_attr("type", "checkbox".to_tendril());
+ self.append(Node::Element(input));
+
+ let mut label = Element::new("label");
+ label.insert_attr("class", "checkbox-label".to_tendril());
+ label.insert_attr("for", img_input_id.to_tendril());
+ self.push(Node::Element(label));
+
let mut img = Element::new("img");
let src = fix_link(dest_url).into_tendril();
img.insert_attr("src", src);
+ // This will eat TagEnd::Image
+ let alt = self.text_for_img_alt();
+ img.insert_attr("alt", alt.to_tendril());
if !title.is_empty() {
img.insert_attr("title", title.into_tendril());
+ } else {
+ img.insert_attr("title", alt.into());
}
- // This will eat TagEnd::Image
- let alt = self.text_for_img_alt();
- img.insert_attr("alt", alt.into());
+ self.append(Node::Element(img.clone()));
+
+ let mut wrapper = Element::new("span");
+ wrapper.insert_attr("class", "img-wrapper".to_tendril());
+ self.push_no_stack(Node::Element(wrapper));
self.append(Node::Element(img));
+
+ // We exit the `label` and the `div`.
+ self.pop();
return;
}
Tag::MetadataBlock(_) => {
diff --git a/tests/gui/books/basic/src/chapter_1.md b/tests/gui/books/basic/src/chapter_1.md
index b743fda354..b19dc20616 100644
--- a/tests/gui/books/basic/src/chapter_1.md
+++ b/tests/gui/books/basic/src/chapter_1.md
@@ -1 +1,3 @@
# Chapter 1
+
+
diff --git a/tests/gui/books/basic/src/rust-logo.svg b/tests/gui/books/basic/src/rust-logo.svg
new file mode 100644
index 0000000000..1a6c762d4e
--- /dev/null
+++ b/tests/gui/books/basic/src/rust-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/gui/image-zoom.goml b/tests/gui/image-zoom.goml
new file mode 100644
index 0000000000..79df878ca8
--- /dev/null
+++ b/tests/gui/image-zoom.goml
@@ -0,0 +1,46 @@
+// This test ensures that the image zoom-in/zoom-out works as expected.
+
+define-function: (
+ "check-image-not-zoomed-in",
+ [],
+ block {
+ // The "zoomed in" image should not be displayed.
+ assert-css: (".checkbox-label > .img-wrapper", {"display": "none"})
+ // The cursor for the image should be "zoom-in".
+ assert-css: (".checkbox-label > img", {"cursor": "zoom-in"})
+ },
+)
+
+define-function: (
+ "check-image-zoomed-in",
+ [],
+ block {
+ // The image wrapper should now be displayed and have a "zoom-out" cursor.
+ assert-css: (".checkbox-label > .img-wrapper", {"display": "flex", "cursor": "zoom-out"})
+ // Same for the image it contains.
+ assert-css: (
+ ".checkbox-label > .img-wrapper img",
+ {"display": "block", "cursor": "zoom-out"},
+ )
+ },
+)
+
+go-to: |DOC_PATH| + "basic/index.html"
+call-function: ("check-image-not-zoomed-in", {})
+
+// We click on the image to "zoom in".
+click: ".checkbox-label > img"
+call-function: ("check-image-zoomed-in", {})
+
+// We click on the wrapper to "zoom out".
+click: ".checkbox-label > img"
+// We check that everything is back to the previous state.
+call-function: ("check-image-not-zoomed-in", {})
+
+// We test that the "escape" key also hides the "zoomed in" image.
+// We click on the image to "zoom in".
+click: ".checkbox-label > img"
+call-function: ("check-image-zoomed-in", {})
+press-key: "Escape"
+// We check that everything is back to the previous state.
+call-function: ("check-image-not-zoomed-in", {})
diff --git a/tests/testsuite/markdown/basic_markdown/expected/images.html b/tests/testsuite/markdown/basic_markdown/expected/images.html
index 4dfdf1657a..94820cb9fb 100644
--- a/tests/testsuite/markdown/basic_markdown/expected/images.html
+++ b/tests/testsuite/markdown/basic_markdown/expected/images.html
@@ -1,3 +1,3 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/tests/testsuite/print/relative_links/expected/print.html b/tests/testsuite/print/relative_links/expected/print.html
index bff4ce1c65..00dfcf8374 100644
--- a/tests/testsuite/print/relative_links/expected/print.html
+++ b/tests/testsuite/print/relative_links/expected/print.html
@@ -13,7 +13,7 @@
-
+
HTML Link