Skip to content

default-image-extension: per-image opt-out and existence-based resolution for extensionless sources #14635

Description

@cderv

Follow-up to #6092. PR #14634 fixes the two provably-spurious cases (shortcode double-extension #14583, and empty-filename URLs like https://example.com/), but the original #6092 report — badge / API URLs such as https://bestpractices.coreinfrastructure.org/projects/1882/badge — is deliberately left untouched.

The reason: Quarto sets default-image-extension as a Pandoc reader option, and Pandoc appends it at parse time to any extensionless source that isn't a data: URI (no URL guard in Pandoc). After parsing, .../badge becomes .../badge.png, which is byte-identical to a user-typed .../badge.png pointing at a real file. There is no way to tell the two apart, and a remote URL cannot be stat-checked offline at render time. So auto-detection is not possible for these.

Two complementary opt-in mechanisms could close the remaining gap:

Remote URLs — explicit per-image opt-out

For remote sources where the user knows there is no extension (badges, image APIs, embeds), an explicit marker lets them assert it:

![OpenSSF](https://bestpractices.coreinfrastructure.org/projects/1882/badge){.no-img-ext}

A Lua filter would strip exactly one appended default-image-extension suffix when the marker is present, then consume the marker so it does not leak into output. This is additive and does not change the default, so it is safe outside a release-cycle boundary. Today the only escape is default-image-extension: "", which is document/project-wide and cannot be scoped to a single image in a doc that also relies on the multi-format behaviour.

Local files — resolve by existence (#6092 "option C")

For local sources, the extension could be resolved against the filesystem instead of appended unconditionally: if path/figure.{default} does not exist but path/figure does, use the extensionless file.

This is the long-term direction discussed in #6092. It needs care:

  • Path resolution must use the correct base (project dir, resource paths) — a false "doesn't exist" would strip a working extension and silently regress documents that render fine today.
  • Multi-format targets generated later in the pipeline will not exist when the filter runs, so the strip condition must be conservative (only when the default-extension target is provably absent and the extensionless file provably present).

Per the discussion in #6092, changing this default-adjacent behaviour should land at the start of a release cycle.

Related: #6092, #14583, #14634.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions