diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index bac1e325e9..c4dd082c64 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -79,6 +79,7 @@ All changes included in 1.11: ### Websites - ([#10756](https://github.com/quarto-dev/quarto-cli/issues/10756)): Fix the notebook preview of an embedded `.qmd` file going missing from the output directory when a project is rendered again, which broke its source notebook link. +- ([#12016](https://github.com/quarto-dev/quarto-cli/issues/12016)): Fix `open-graph` `image-width` and `image-height` being dropped when `site-url` is set and Quarto cannot measure the image, such as a remote image. A size declared in the site `open-graph` options no longer applies when a page sets its own `image`, even the same file: declare `image-width` and `image-height` in the page `open-graph` options or in shared metadata such as `_metadata.yml`. - ([#14972](https://github.com/quarto-dev/quarto-cli/issues/14972)): Fix the website navbar, sidebar toggle bar, or announcement covering the header of notebook preview pages, which hid the back link, notebook title, and download link. The notebook preview header now sits below the site header. - ([#14974](https://github.com/quarto-dev/quarto-cli/issues/14974)): Fix math in `.llms.md` files from `llms-txt` coming out garbled with the default `mathjax` method, and with `katex` and `webtex`. Math is now written as `$...$` and `$$...$$` with the `mathjax`, `katex`, `webtex` and `mathml` methods. - ([#15004](https://github.com/quarto-dev/quarto-cli/issues/15004)): Fix notebook preview pages and notebook sources missing from the output directory when a website with embedded notebooks is rendered from a directory other than the project directory. diff --git a/src/project/types/website/website-meta.ts b/src/project/types/website/website-meta.ts index b70ac230d6..9a49aa5c80 100644 --- a/src/project/types/website/website-meta.ts +++ b/src/project/types/website/website-meta.ts @@ -180,9 +180,13 @@ export function metadataHtmlPostProcessor( // find a preview image if one is not provided if (metadata[kImage] === undefined && format.metadata[kImage] !== false) { - const previewImg = findPreviewImg(doc) || websiteImage(project.config); + const pagePreviewImg = findPreviewImg(doc); + const previewImg = pagePreviewImg || websiteImage(project.config); metadata[kImage] = previewImg ? previewImg.src : undefined; metadata[kImageAlt] = previewImg ? previewImg.alt : undefined; + if (pagePreviewImg && provider.key === kOpenGraph) { + usePageDeclaredImageSize(format, metadata); + } } // cook up a description if one is not provided @@ -266,10 +270,30 @@ function opengraphMetadata( metadata[key] = openGraph[key]; } }); + + // Same predicate as mergedSiteAndDocumentData uses to replace the site image + const docOpenGraph = format.metadata[kOpenGraph]; + const pageSetsImage = !!format.metadata[kImage] || + (typeof docOpenGraph === "object" && docOpenGraph !== null && + !!(docOpenGraph as Metadata)[kImage]); + if (pageSetsImage) { + usePageDeclaredImageSize(format, metadata); + } } return metadata; } +// A size declared on the site describes the site image, so an image the page +// supplies only gets the dimensions the page itself declares +function usePageDeclaredImageSize(format: Format, metadata: Metadata) { + const docOpenGraph = format.metadata[kOpenGraph]; + const docData = typeof docOpenGraph === "object" && docOpenGraph !== null + ? docOpenGraph as Metadata + : {}; + metadata[kImageHeight] = docData[kImageHeight]; + metadata[kImageWidth] = docData[kImageWidth]; +} + function twitterMetadata(format: Format) { const metadata: Record = {}; @@ -331,10 +355,11 @@ function resolveImageMetadata( project, ); - // Update the image information + // Update the image information, preferring the size read from the file + // over a declared one metadata[kImage] = imgMeta.href; - metadata[kImageHeight] = imgMeta.height; - metadata[kImageWidth] = imgMeta.width; + metadata[kImageHeight] = imgMeta.height ?? metadata[kImageHeight]; + metadata[kImageWidth] = imgMeta.width ?? metadata[kImageWidth]; const altText = format.metadata[kImageAlt]; if (altText && !metadata[kImageAlt]) { diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/.gitignore b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/.gitignore new file mode 100644 index 0000000000..43c566bbdf --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/.gitignore @@ -0,0 +1,3 @@ +/.quarto/ +_site/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/_quarto.yml b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/_quarto.yml new file mode 100644 index 0000000000..4574d732d3 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/_quarto.yml @@ -0,0 +1,9 @@ +project: + type: website + +website: + title: "issue-12016-no-site-url" + open-graph: + image: mark.jpg + image-width: 1200 + image-height: 892 diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/index.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/index.qmd new file mode 100644 index 0000000000..694a921666 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/index.qmd @@ -0,0 +1,15 @@ +--- +title: "Without site-url, the site image keeps its declared size" +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +The declared size describes the site image used by this page. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/mark.jpg b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/mark.jpg new file mode 100644 index 0000000000..fffe01e8e8 Binary files /dev/null and b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/mark.jpg differ diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/post-image-false.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/post-image-false.qmd new file mode 100644 index 0000000000..e4fad1aba2 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/post-image-false.qmd @@ -0,0 +1,16 @@ +--- +title: "A page with image false keeps the site image and its size" +image: false +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +`image: false` does not replace the site image, so the size declared for it still applies. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/post-svg.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/post-svg.qmd new file mode 100644 index 0000000000..52d8d5f376 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-no-site-url/post-svg.qmd @@ -0,0 +1,16 @@ +--- +title: "Without site-url, a page image does not inherit the site's size" +image: square.svg +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - + - ' diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/.gitignore b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/.gitignore new file mode 100644 index 0000000000..43c566bbdf --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/.gitignore @@ -0,0 +1,3 @@ +/.quarto/ +_site/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/_quarto.yml b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/_quarto.yml new file mode 100644 index 0000000000..0a4b2be128 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/_quarto.yml @@ -0,0 +1,10 @@ +project: + type: website + +website: + title: "issue-12016-preview-image" + site-url: https://example.com/ + image: mark.jpg + open-graph: + image-width: 1200 + image-height: 892 diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/index.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/index.qmd new file mode 100644 index 0000000000..ff871eee3c --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/index.qmd @@ -0,0 +1,15 @@ +--- +title: "The website image gets its measured size, not the size declared on the site" +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +The page has no image of its own, so the site's image is used with its measured size, not the declared one. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/mark.jpg b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/mark.jpg new file mode 100644 index 0000000000..fffe01e8e8 Binary files /dev/null and b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/mark.jpg differ diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/post-preview-empty-open-graph.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/post-preview-empty-open-graph.qmd new file mode 100644 index 0000000000..74d6e47622 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-preview-image/post-preview-empty-open-graph.qmd @@ -0,0 +1,18 @@ +--- +title: "An empty page open-graph entry with a preview image gets no size" +open-graph: +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - + - '' + - + - ' diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/.gitignore b/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/.gitignore new file mode 100644 index 0000000000..43c566bbdf --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/.gitignore @@ -0,0 +1,3 @@ +/.quarto/ +_site/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/_quarto.yml b/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/_quarto.yml new file mode 100644 index 0000000000..bb7e189af4 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/_quarto.yml @@ -0,0 +1,10 @@ +project: + type: website + +website: + title: "issue-12016-remote-site-image" + site-url: https://example.com/ + open-graph: + image: https://images.example.com/social.jpg + image-width: 1200 + image-height: 630 diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/index.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/index.qmd new file mode 100644 index 0000000000..aa5ebd8278 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016-remote-site-image/index.qmd @@ -0,0 +1,15 @@ +--- +title: "A remote site image keeps the size declared on the site" +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +With `site-url` set, Quarto cannot measure a remote image, so the `image-width` and `image-height` declared next to the site image are used. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/.gitignore b/tests/docs/smoke-all/2026/10/13/issue-12016/.gitignore new file mode 100644 index 0000000000..43c566bbdf --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/.gitignore @@ -0,0 +1,3 @@ +/.quarto/ +_site/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/_quarto.yml b/tests/docs/smoke-all/2026/10/13/issue-12016/_quarto.yml new file mode 100644 index 0000000000..4f92ae41c7 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/_quarto.yml @@ -0,0 +1,10 @@ +project: + type: website + +website: + title: "issue-12016" + site-url: https://example.com/ + open-graph: + image: mark.jpg + image-width: 1200 + image-height: 892 diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/index.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016/index.qmd new file mode 100644 index 0000000000..6d279a1c7d --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/index.qmd @@ -0,0 +1,15 @@ +--- +title: "JPEG open-graph image gets its measured size, not the site's declared size" +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +With `site-url` set, Quarto measures the JPEG and the measured size wins over the site's declared `image-width` and `image-height`. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/mark.jpg b/tests/docs/smoke-all/2026/10/13/issue-12016/mark.jpg new file mode 100644 index 0000000000..fffe01e8e8 Binary files /dev/null and b/tests/docs/smoke-all/2026/10/13/issue-12016/mark.jpg differ diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/page.png b/tests/docs/smoke-all/2026/10/13/issue-12016/page.png new file mode 100644 index 0000000000..820adae294 Binary files /dev/null and b/tests/docs/smoke-all/2026/10/13/issue-12016/page.png differ diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/post-empty-open-graph.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016/post-empty-open-graph.qmd new file mode 100644 index 0000000000..d23840397f --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/post-empty-open-graph.qmd @@ -0,0 +1,16 @@ +--- +title: "An empty page open-graph entry keeps the site's image and its measured size" +open-graph: +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +The page declares `open-graph:` with no value, so the site's open-graph image is used and its size is measured, not declared. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/post-png.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016/post-png.qmd new file mode 100644 index 0000000000..9de2b46d85 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/post-png.qmd @@ -0,0 +1,16 @@ +--- +title: "A page image gets its own measured size, not the site's" +image: page.png +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +The site declares a size for its own image; this page's PNG is measured instead. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/post-svg-declared.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016/post-svg-declared.qmd new file mode 100644 index 0000000000..c34a8342f9 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/post-svg-declared.qmd @@ -0,0 +1,19 @@ +--- +title: "A page image keeps the size the page declares for it" +open-graph: + image: square.svg + image-width: 10 + image-height: 10 +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - '' + - [] +--- + +Quarto cannot measure an SVG, so the size declared next to the page image is used. diff --git a/tests/docs/smoke-all/2026/10/13/issue-12016/post-svg-width-only.qmd b/tests/docs/smoke-all/2026/10/13/issue-12016/post-svg-width-only.qmd new file mode 100644 index 0000000000..3b66fd4e62 --- /dev/null +++ b/tests/docs/smoke-all/2026/10/13/issue-12016/post-svg-width-only.qmd @@ -0,0 +1,18 @@ +--- +title: "A page image does not inherit a dimension the page leaves out" +open-graph: + image: square.svg + image-width: 10 +_quarto: + render-project: true + tests: + html: + ensureFileRegexMatches: + - + - '' + - '' + - + - '' + - + - '