Skip to content

Support JPEG image sizes for feed and open-graph metadata - #15050

Merged
cderv merged 3 commits into
mainfrom
fix/image-size-non-png
Oct 14, 2026
Merged

cderv merged 3 commits into
mainfrom
fix/image-size-non-png

Conversation

@cderv

@cderv cderv commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

imageSize() only measured PNG files, and only when the path ended in a lowercase .png. A JPEG feed image, listing item image or og:image therefore never got a height or width: no <height>/<width> in the feed <image>, no size on item media:content, no og:image:width/og:image:height. JPEG is the common format for photo thumbnails, and #12016 showed users do need the open-graph sizes for them.

imageSize() now picks the parser from the file's header bytes instead of its extension. This also fixes feed.PNG, .jpeg and .jfif paths, which the extension check skipped. JPEG sizes come from a new src/core/jpeg.ts that scans markers up to the first start-of-frame segment, with no new dependency. Baseline and progressive JPEGs are both read. Callers are unchanged.

Only PNG and JPEG are measured. SVG often has no pixel size, and Facebook and X reject SVG og:image anyway. GIF, WebP and AVIF can be added the same way if there is demand. Unknown formats still return undefined, as before.

png.ts now exports isPng(), which checks the 8-byte signature from the PNG spec. It replaces the old check that compared the bytes joined as decimal digits against the string "13780787113102610".

Related to #15040, which fixes the same open-graph size behaviour for declared sizes with site-url.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR
AI-assisted PR
  • AI tool used: Claude Code
  • Codebase grounding: local clone
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.

@posit-snyk-bot

posit-snyk-bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

cderv added 3 commits October 14, 2026 11:47
imageSize only measured files whose name ended in ".png", so a JPEG
(the usual format for blog thumbnails and the preview images Quarto
auto-discovers) gave listing feeds and og:image/twitter:image no
height or width. The extension check was also case-sensitive, so
"feed.PNG" was skipped.

imageSize now picks the reader from the file's header bytes: PNG as
before, JPEG via a small marker scan to the first start-of-frame
segment (baseline, progressive and lossless alike), no dependency.
Other formats (SVG, GIF, WebP) still return no size; SVG often has no
pixel size and social platforms reject it as og:image anyway. Sniffing
content also means a WebP saved as ".jpg" is no longer misread, and a
non-PNG file named ".png" now gets no size instead of an error.

Checked against Pillow on every PNG and JPEG tracked in the repo
(298 files): identical sizes.
@cderv
cderv force-pushed the fix/image-size-non-png branch from b53fec0 to 16cf14c Compare October 14, 2026 09:48
@cderv
cderv merged commit f4cc173 into main Oct 14, 2026
51 checks passed
@cderv
cderv deleted the fix/image-size-non-png branch October 14, 2026 10:37
cderv added a commit to quarto-dev/quarto-web that referenced this pull request Oct 14, 2026
The image row described only PNG measurement and suggested JPEG as a
case for declared sizes. Since quarto-dev/quarto-cli#15050, Quarto also
measures JPEG, so declared sizes only matter for remote images and
formats like WebP or GIF.

quarto-dev/quarto-cli#15040 also stops a page image from inheriting the
sizes declared in website: open-graph. The check is whether the page
supplies an image, not whether it differs from the site image, and only
the page's open-graph block is read, not top-level image-width/height.
Sites without site-url that used the site sizes as defaults lose those
tags, so document the migration through page or _metadata.yml
open-graph metadata, and note that directory sizes also apply to the
site image on pages without their own image.

All statements checked by rendering test sites with a 1.11 dev build
and with 1.10.19.
cderv added a commit that referenced this pull request Oct 14, 2026
Since #15050, Quarto measures JPEG images for open-graph metadata, and a
measured size wins over a declared one. The site's mark.jpg is 34x52, so
the fixtures that use it as the site image now emit 34 and 52 instead of
the declared 1200 and 892. The declared-size cases that Quarto cannot
measure keep their expectations in post-svg-declared.qmd.
cderv added a commit that referenced this pull request Oct 14, 2026
Since #15050 measures JPEG files, the fixtures using the local mark.jpg
site image no longer exercise the declared-size fallback at the site
level. A remote image is never measured, so with site-url its declared
image-width and image-height must still reach og:image:width and
og:image:height. This fails on main, where the size is replaced by the
undefined measurement.
cderv added a commit that referenced this pull request Oct 14, 2026
#15050 now measures JPEG images, so the original JPEG case from #12016
no longer depends on this change. What remains is keeping a declared size
for images Quarto cannot measure, such as remote ones, and limiting a
site-declared size to the site image. The second part changes output for
sites without site-url that declared a global size because all their page
images share it: a page that sets its own image, even the site image
again, no longer gets the site size. The entry names where to move the
declaration.
cderv added a commit that referenced this pull request Oct 14, 2026
…15040)

With `site-url` set, `og:image:width` and `og:image:height` were
dropped for an open-graph image Quarto cannot measure, even when
`image-width` and `image-height` were declared. The measured size
replaced the declared one with undefined, because imageSize only reads
PNG. #15050 now measures JPEG; this change covers the remaining cases,
such as remote images, SVG, GIF and WebP.

The measured size is still used when Quarto can read the file, so the
tags describe the actual pixels. Otherwise the declared size is kept.

A size declared in the site open-graph options describes the site
image. It no longer applies when a page supplies its own image (an
`image`, a page open-graph image, or a preview image found in the page
content). That image only gets the dimensions the page declares under
open-graph, each dimension separately. `image: false` and the
website.image fallback keep the site image and its size.

This changes output for sites without site-url: a site-declared size is
no longer emitted on pages that set their own image. To keep the tags,
declare image-width and image-height under open-graph in the page front
matter or in shared metadata such as _metadata.yml. Top-level
image-width and image-height are not read for open-graph.

- Use the measured size when Quarto can read the image, else the declared one
- Scope a site-declared size to the site image only
- Treat an empty page open-graph entry as no page block

Fixes #12016
cderv added a commit to quarto-dev/quarto-web that referenced this pull request Oct 14, 2026
* Document how open-graph image-width and image-height apply

The image rows of both social metadata tables said a declared
image-width/image-height improves "your Twitter Card". Twitter Card
metadata never reads a declared size; only Open Graph does.

With site-url set, Quarto measures PNG images and uses that size over a
declared one. From Quarto 1.11 (quarto-dev/quarto-cli#15040), a declared
size is kept for images Quarto can't measure (JPEG, SVG, ...), and a
page that brings its own image no longer inherits the size declared for
the site image. Explain both in the Open Graph table, and point the
Twitter Card row there instead.

* Explain open-graph image size behaviour in its own section

The image row described only PNG measurement and suggested JPEG as a
case for declared sizes. Since quarto-dev/quarto-cli#15050, Quarto also
measures JPEG, so declared sizes only matter for remote images and
formats like WebP or GIF.

quarto-dev/quarto-cli#15040 also stops a page image from inheriting the
sizes declared in website: open-graph. The check is whether the page
supplies an image, not whether it differs from the site image, and only
the page's open-graph block is read, not top-level image-width/height.
Sites without site-url that used the site sizes as defaults lose those
tags, so document the migration through page or _metadata.yml
open-graph metadata, and note that directory sizes also apply to the
site image on pages without their own image.

All statements checked by rendering test sites with a 1.11 dev build
and with 1.10.19.
cderv added a commit that referenced this pull request Oct 15, 2026
Pages without an `image` could pick a data URI as their preview image,
such as the ORCID badge, and join it to `site-url`. Since #15050 the image
size is read from any path, so the render then failed with a `stat`
error. Crawlers and feed readers fetch images by URL, so a data URI is
never usable there, so social metadata and listing feeds skip it and use
the next candidate.

- Preview image discovery skips data URIs and tries every marked image
  before the site image.
- A data URI set as a page, site, listing item or feed image is ignored
  with a warning for social metadata and listing feeds, and the next
  candidate is used.
- `projectRelativeHref` leaves data URIs unchanged, so listing cards
  keep displaying them instead of a broken project path.

Fixes #15055
cderv added a commit that referenced this pull request Oct 16, 2026
The #15050 entry no longer claims twitter:image gets a height and width,
as the measured size now goes to og:image only.
@cderv cderv mentioned this pull request Oct 16, 2026
3 of 5 tasks
cderv added a commit that referenced this pull request Oct 16, 2026
)

With site-url set, the size measured from a local PNG or JPEG image
was written to every social metadata provider. The twitter provider
passed it through as twitter:image-height and twitter:image-width,
which Twitter Cards do not define: they only have twitter:image and
twitter:image:alt for images. Crawlers ignore unknown meta names, so
the effect is cosmetic, but the tags are wrong. The measured size
now goes to og:image:height and og:image:width only.

The same mix-up reached the options: twitter-card shared its option
set with open-graph, so image-width and image-height showed in its
reference although nothing read them (#12014). Removing them from
twitter-card outright would fail the render of any config that sets
them, since the object is closed, so they stay accepted there as
hidden options that warn they are ignored and point to open-graph.

- Send the measured size to Open Graph tags only
- Move the image-width and image-height options to open-graph
- Keep them accepted on twitter-card as hidden, ignored options

The PNG case dates back to 2021; #15050 extended it to JPEG.
This follows up on #15083.

Fixes #12014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants