From 71fb5a8d4aae75700431b552a1982100ca3cc170 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 26 May 2026 16:33:52 +0200 Subject: [PATCH 1/7] Add "zoom-in" feature on images --- crates/mdbook-html/front-end/css/general.css | 32 +++++++++++++++++ crates/mdbook-html/src/html/tree.rs | 35 +++++++++++++++++-- .../basic_markdown/expected/images.html | 4 +-- .../print/relative_links/expected/print.html | 2 +- 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/crates/mdbook-html/front-end/css/general.css b/crates/mdbook-html/front-end/css/general.css index 80d0d9bf2c..7d56f36117 100644 --- a/crates/mdbook-html/front-end/css/general.css +++ b/crates/mdbook-html/front-end/css/general.css @@ -409,3 +409,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/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index 5cb97ce378..dad1662ea7 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,6 +567,19 @@ 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); @@ -566,7 +589,15 @@ where // 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/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 @@

Images

-

Image “alt” & " "text" & <stuff> url <em>html</em> — hard break

-

Image with title

\ 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 @@

inside but doesn’t exist with anchor. Link inside to html. Link inside to html with anchor.

-

Some image

+

HTML Link

raw html

Some section

From 9de27556e42bc9f2e087252cab46204ff9281ff1 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 26 May 2026 16:36:02 +0200 Subject: [PATCH 2/7] Add `title` attribute on images --- crates/mdbook-html/src/html/tree.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index dad1662ea7..99b787ae8d 100644 --- a/crates/mdbook-html/src/html/tree.rs +++ b/crates/mdbook-html/src/html/tree.rs @@ -583,12 +583,14 @@ where 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"); From 6a7ef4d1c3286b6e09bab618216a30f830264cd0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 26 May 2026 18:00:53 +0200 Subject: [PATCH 3/7] Add regression test for the "zoom-in" feature --- tests/gui/books/basic/src/chapter_1.md | 2 ++ tests/gui/image-zoom.goml | 27 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/gui/image-zoom.goml diff --git a/tests/gui/books/basic/src/chapter_1.md b/tests/gui/books/basic/src/chapter_1.md index 9aa8f00e40..eee6ba0e51 100644 --- a/tests/gui/books/basic/src/chapter_1.md +++ b/tests/gui/books/basic/src/chapter_1.md @@ -1,3 +1,5 @@ # Chapter 1 Side by side [![the logo](rust-logo.svg)](link) [![the logo](rust-logo.svg)](link). + +![rust logo](rust-logo.svg) diff --git a/tests/gui/image-zoom.goml b/tests/gui/image-zoom.goml new file mode 100644 index 0000000000..0dfe9d9e91 --- /dev/null +++ b/tests/gui/image-zoom.goml @@ -0,0 +1,27 @@ +// 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"}) + }, +) + +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" +// 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"}) + +// 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", {}) From 73d6edfcafc08beee2ca22beae24a0e28fc21a2f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 26 May 2026 18:10:36 +0200 Subject: [PATCH 4/7] Add possibility to "zoom out" using the escape key --- crates/mdbook-html/front-end/js/book.js | 9 +++++++++ tests/gui/image-zoom.goml | 27 +++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) 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/tests/gui/image-zoom.goml b/tests/gui/image-zoom.goml index 0dfe9d9e91..79df878ca8 100644 --- a/tests/gui/image-zoom.goml +++ b/tests/gui/image-zoom.goml @@ -11,17 +11,36 @@ define-function: ( }, ) +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" -// 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"}) +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", {}) From f526b18d6ad38b326ca5c8f5c6291434a6b33bfd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 8 Jun 2026 21:29:19 +0200 Subject: [PATCH 5/7] Only enable "zoom-in" feature on images not inside links --- crates/mdbook-html/src/html/tree.rs | 57 +++++++++++++++++--------- tests/gui/books/basic/src/chapter_1.md | 2 + tests/gui/image-zoom.goml | 14 +++++++ 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index 99b787ae8d..0dcaa32fb6 100644 --- a/crates/mdbook-html/src/html/tree.rs +++ b/crates/mdbook-html/src/html/tree.rs @@ -567,19 +567,6 @@ 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); @@ -591,15 +578,45 @@ where } else { img.insert_attr("title", 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)); + // If the image is not being rendered inside a link, we can enable the "zoom-in" + // feature. + if !self + .tag_stack + .iter() + .filter_map(|node_id| { + self.tree + .get(*node_id) + .and_then(|el| el.value().as_element()) + }) + .any(|el| *el.name.local == *"a") + { + let img_input_id = format!("checkbox-img-{}", self.get_current_img_label_id()); - // We exit the `label` and the `div`. - self.pop(); + 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)); + + 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(); + } else { + self.append(Node::Element(img)); + } return; } Tag::MetadataBlock(_) => { diff --git a/tests/gui/books/basic/src/chapter_1.md b/tests/gui/books/basic/src/chapter_1.md index eee6ba0e51..d42248b4fd 100644 --- a/tests/gui/books/basic/src/chapter_1.md +++ b/tests/gui/books/basic/src/chapter_1.md @@ -3,3 +3,5 @@ Side by side [![the logo](rust-logo.svg)](link) [![the logo](rust-logo.svg)](link). ![rust logo](rust-logo.svg) + +[![the logo](rust-logo.svg)](https://rust-lang.org/) diff --git a/tests/gui/image-zoom.goml b/tests/gui/image-zoom.goml index 79df878ca8..7a7e268a11 100644 --- a/tests/gui/image-zoom.goml +++ b/tests/gui/image-zoom.goml @@ -44,3 +44,17 @@ 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", {}) + +// Now we check that images in links (in `` elements) don't get the "zoom-in" feature enabled. +// There is only one zoomable image. +assert-count: ("main .checkbox-img", 1) +// We should have 3 images: 2 "normal" ones visible in the rendered book and one hidden which is +// used only for the "zoom-in" feature. +assert-count: ("main img", 3) +// We check exact paths to ensure the 3 `` are different. +assert: ("main > p > label.checkbox-label > img") +assert: ("main > p > label.checkbox-label > span.img-wrapper > img") +assert: ("main > p > a > img") + +// The cursor for the image in link should not have "zoom-in". +assert-css: ("main > p > a > img", {"cursor": "pointer"}) From 588066c3d6ec9297965ed471a1f04ee5b67c98ab Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 22 Jun 2026 18:30:51 +0200 Subject: [PATCH 6/7] Make the "zoom in" feature work with keyboard and simplify DOM --- crates/mdbook-html/front-end/css/general.css | 27 ++++++++++++-- crates/mdbook-html/front-end/js/book.js | 4 ++ crates/mdbook-html/src/html/tree.rs | 30 ++++----------- tests/gui/books/basic/src/chapter_1.md | 2 + tests/gui/image-zoom.goml | 37 ++++++++++++++++--- .../basic_markdown/expected/images.html | 4 +- .../print/relative_links/expected/print.html | 2 +- 7 files changed, 72 insertions(+), 34 deletions(-) diff --git a/crates/mdbook-html/front-end/css/general.css b/crates/mdbook-html/front-end/css/general.css index 7d56f36117..06a7f42786 100644 --- a/crates/mdbook-html/front-end/css/general.css +++ b/crates/mdbook-html/front-end/css/general.css @@ -410,7 +410,28 @@ dd > p { margin-right: 8px; } -.content .checkbox-img, .checkbox-label > .img-wrapper { +/* All this code is to handle the "zoomable" images feature. */ + +/* This one makes the checkbox invisible while keeping it selectable with the keyboard (with the + "tab" key). */ +.content .checkbox-img { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +/* When the input is focused, we make it appear to the user by adding an outline to the img + instead */ +.content .checkbox-img:focus + img { + outline: auto; +} +/* The "zoomed-in" image is not displayed until the checkbox is "ticked". */ +.content .checkbox-img:not(:checked) ~ .img-wrapper { display: none; } .content .checkbox-label { @@ -420,7 +441,7 @@ dd > p { .content .checkbox-label img { cursor: zoom-in; } -.content .checkbox-img:checked + .checkbox-label > .img-wrapper { +.content .checkbox-img:checked ~ .img-wrapper { position: fixed; top: 0; left: 0; @@ -433,7 +454,7 @@ dd > p { justify-content: center; cursor: zoom-out; } -.content .checkbox-img:checked + .checkbox-label > .img-wrapper img { +.content .checkbox-img:checked ~ .img-wrapper > img { --img-padding: 5px; padding: var(--img-padding); background: #999; diff --git a/crates/mdbook-html/front-end/js/book.js b/crates/mdbook-html/front-end/js/book.js index 3790ccd8a8..c62124cb32 100644 --- a/crates/mdbook-html/front-end/js/book.js +++ b/crates/mdbook-html/front-end/js/book.js @@ -27,6 +27,10 @@ function mdbook_something_else_has_focus(e) { // Check composedPath in case the event happened from something generated // from the shadowDOM. const target = e.composedPath()[0] || e.target; + // If this is the `checkbox-img` input which has the focus, we want to handle it here. + if (target.classList.contains('checkbox-img')) { + return false; + } return /^(?:input|select|textarea)$/i.test(target.nodeName); } diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index 0dcaa32fb6..c4abd2f266 100644 --- a/crates/mdbook-html/src/html/tree.rs +++ b/crates/mdbook-html/src/html/tree.rs @@ -199,8 +199,6 @@ 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> @@ -224,7 +222,6 @@ where table_cell_index: 0, footnote_numbers: HashMap::new(), footnote_defs: HashMap::new(), - img_label_id: 0, }; builder.process_events(); builder.add_header_links(); @@ -233,13 +230,6 @@ 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. @@ -570,14 +560,12 @@ where 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.to_tendril()); // If the image is not being rendered inside a link, we can enable the "zoom-in" // feature. @@ -591,19 +579,15 @@ where }) .any(|el| *el.name.local == *"a") { - let img_input_id = format!("checkbox-img-{}", self.get_current_img_label_id()); + let mut label = Element::new("label"); + label.insert_attr("class", "checkbox-label".to_tendril()); + self.push(Node::Element(label)); 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)); - self.append(Node::Element(img.clone())); let mut wrapper = Element::new("span"); @@ -612,7 +596,7 @@ where self.append(Node::Element(img)); - // We exit the `label` and the `div`. + // We exit the `label` and the `span` (which used push_no_stack). self.pop(); } else { self.append(Node::Element(img)); diff --git a/tests/gui/books/basic/src/chapter_1.md b/tests/gui/books/basic/src/chapter_1.md index d42248b4fd..a92701ab0e 100644 --- a/tests/gui/books/basic/src/chapter_1.md +++ b/tests/gui/books/basic/src/chapter_1.md @@ -2,6 +2,8 @@ Side by side [![the logo](rust-logo.svg)](link) [![the logo](rust-logo.svg)](link). +Some text [blob](a). + ![rust logo](rust-logo.svg) [![the logo](rust-logo.svg)](https://rust-lang.org/) diff --git a/tests/gui/image-zoom.goml b/tests/gui/image-zoom.goml index 7a7e268a11..398d7cb47a 100644 --- a/tests/gui/image-zoom.goml +++ b/tests/gui/image-zoom.goml @@ -26,6 +26,7 @@ define-function: ( ) go-to: |DOC_PATH| + "basic/index.html" +show-text: true call-function: ("check-image-not-zoomed-in", {}) // We click on the image to "zoom in". @@ -45,16 +46,42 @@ press-key: "Escape" // We check that everything is back to the previous state. call-function: ("check-image-not-zoomed-in", {}) +// We test that we can zoom in and out using only the keyboard. +// First we focus on the link just before the image we want to zoom in. +focus: "a[href='a']" +assert: "a[href='a']:focus" +press-key: "Tab" +// We check that the checkbox to "zoom in" is focused. +assert: ".checkbox-img:focus" +// We check that the image about to be zoomed in has the outline. +store-css: (".checkbox-img:focus", {"outline": outline}) +// Because we're using "auto" to match the system outline, we cannot check the whole value directly +// so instead we check if "auto" is present. +assert-variable: (outline, "auto", CONTAINS) +// We zoom in. +press-key: "Space" +call-function: ("check-image-zoomed-in", {}) +// We press the key again to zoom out. +press-key: "Space" +call-function: ("check-image-not-zoomed-in", {}) +// We now check it works with the "Escape" key as well. +press-key: "Space" +call-function: ("check-image-zoomed-in", {}) +press-key: "Escape" +call-function: ("check-image-not-zoomed-in", {}) + // Now we check that images in links (in `` elements) don't get the "zoom-in" feature enabled. // There is only one zoomable image. assert-count: ("main .checkbox-img", 1) -// We should have 3 images: 2 "normal" ones visible in the rendered book and one hidden which is +// We should have 5 images: 4 "normal" ones visible in the rendered book and one hidden which is // used only for the "zoom-in" feature. -assert-count: ("main img", 3) -// We check exact paths to ensure the 3 `` are different. -assert: ("main > p > label.checkbox-label > img") +assert-count: ("main img", 5) +// We check exact paths to ensure the 5 `` are different. +assert: ("main > p > label.checkbox-label > img:nth-of-type(1)") assert: ("main > p > label.checkbox-label > span.img-wrapper > img") -assert: ("main > p > a > img") +assert: ("main > p:nth-of-type(1) > a:nth-of-type(1) > img") +assert: ("main > p:nth-of-type(1) > a:nth-of-type(2) > img") +assert: ("main > p:not(:nth-of-type(2)) > a > img") // The cursor for the image in link should not have "zoom-in". assert-css: ("main > p > a > img", {"cursor": "pointer"}) diff --git a/tests/testsuite/markdown/basic_markdown/expected/images.html b/tests/testsuite/markdown/basic_markdown/expected/images.html index 94820cb9fb..c56a085d24 100644 --- a/tests/testsuite/markdown/basic_markdown/expected/images.html +++ b/tests/testsuite/markdown/basic_markdown/expected/images.html @@ -1,3 +1,3 @@

Images

-

-

\ 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 00dfcf8374..43a9dee373 100644 --- a/tests/testsuite/print/relative_links/expected/print.html +++ b/tests/testsuite/print/relative_links/expected/print.html @@ -13,7 +13,7 @@

inside but doesn’t exist with anchor. Link inside to html. Link inside to html with anchor.

-

+

HTML Link

raw html

Some section

From beab2fa604f07697fb6160d42e5f46fb6261b4d0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 22 Jun 2026 18:35:24 +0200 Subject: [PATCH 7/7] Mention zoom-in feature in mdbook guide --- guide/src/format/markdown.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guide/src/format/markdown.md b/guide/src/format/markdown.md index c42c8d1af6..bebc039c6d 100644 --- a/guide/src/format/markdown.md +++ b/guide/src/format/markdown.md @@ -318,3 +318,7 @@ This feature is enabled by default. To disable it, see the [`output.html.admonitions`] config option. [`output.html.admonitions`]: configuration/renderers.md#html-renderer-options + +## Zoom-in + +All images in the chapters content have a "zoom-in" feature: you can click on it to make it bigger, and click it again to zoom out. You can focus the image with the keyboard as well, and press the spacebar to zoom in and out as well.