Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/jekyll.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
python3 tests/sync-cloudflare-security-headers.test.py
bundle exec ruby tests/research-tag-collision.test.rb
bundle exec ruby tests/asset-fingerprints.test.rb
bundle exec ruby tests/responsive-images.test.rb

- name: Build site
run: bash scripts/build.sh --skip-deps
Expand Down
34 changes: 31 additions & 3 deletions _plugins/responsive_images.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,38 @@ def local_asset_path(value)

def apply_dimensions(img, info)
return unless info
return if img['width'] && img['height']

img['width'] = info['width'].to_s
img['height'] = info['height'].to_s
existing_width = img['width']
existing_height = img['height']
return if existing_width && existing_height

natural_w = info['width'].to_f
natural_h = info['height'].to_f
return if natural_w <= 0 || natural_h <= 0

# Author set neither: emit both intrinsic dims for CLS.
if existing_width.nil? && existing_height.nil?
img['width'] = info['width'].to_s
img['height'] = info['height'].to_s
return
end

# Numeric width without height: keep width, scale height.
if existing_width && existing_height.nil?
if existing_width.match?(/\A\d+(\.\d+)?\z/)
scaled = (existing_width.to_f * natural_h / natural_w).round
img['height'] = scaled.to_s if scaled.positive?
end
# Non-numeric width (e.g. 75%): leave alone; never invent height.
return
end

# Numeric height without width: keep height, scale width.
return unless existing_height && existing_width.nil?
return unless existing_height.match?(/\A\d+(\.\d+)?\z/)

scaled = (existing_height.to_f * natural_w / natural_h).round
img['width'] = scaled.to_s if scaled.positive?
end

def apply_loading(img)
Expand Down
1 change: 1 addition & 0 deletions assets/css/research-v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@
/* Static images in the paper list sit at full width, paper-style */
.research-content img:not([src*="img.shields.io"]) {
max-width: 100%;
height: auto;
border-radius: var(--r-sm);
margin: var(--s-3) 0;
}
Expand Down
69 changes: 69 additions & 0 deletions tests/responsive-images.test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'jekyll'
require 'nokogiri'
require_relative '../_plugins/responsive_images'

def assert(condition, message)
raise message unless condition
end

def img_node(html)
Nokogiri::HTML.fragment(html).at_css('img')
end

INFO = { 'width' => 2391, 'height' => 2090 }.freeze

# Neither attr: set both intrinsic dims (CLS / first-paint intent of #147).
bare = img_node('<img src="/assets/images/research/fig.png">')
CoMPhyResponsiveImages.apply_dimensions(bare, INFO)
assert(bare['width'] == '2391', 'bare img should get natural width')
assert(bare['height'] == '2090', 'bare img should get natural height')

# Author percentage width, no height: never overwrite width or invent height.
pct = img_node(
'<img src="/assets/images/research/fig.png" width="75%">'
)
CoMPhyResponsiveImages.apply_dimensions(pct, INFO)
assert(pct['width'] == '75%', 'percentage width must be preserved')
assert(pct['height'].nil?, 'percentage width must not get a pixel height')

# Numeric width without height: keep width, scale height.
num = img_node(
'<img src="/assets/images/research/fig.png" width="800">'
)
CoMPhyResponsiveImages.apply_dimensions(num, INFO)
assert(num['width'] == '800', 'numeric author width must be preserved')
expected_h = ((800.0 * 2090) / 2391).round
assert(
num['height'] == expected_h.to_s,
"expected proportional height #{expected_h}, got #{num['height']}"
)

# Both already set: leave untouched.
both = img_node(
'<img src="/assets/images/research/fig.png" width="400" height="300">'
)
CoMPhyResponsiveImages.apply_dimensions(both, INFO)
assert(both['width'] == '400', 'existing width must not change')
assert(both['height'] == '300', 'existing height must not change')

# Numeric height without width: keep height, scale width.
h_only = img_node(
'<img src="/assets/images/research/fig.png" height="1045">'
)
CoMPhyResponsiveImages.apply_dimensions(h_only, INFO)
assert(h_only['height'] == '1045', 'numeric author height must be preserved')
expected_w = ((1045.0 * 2391) / 2090).round
assert(
h_only['width'] == expected_w.to_s,
"expected proportional width #{expected_w}, got #{h_only['width']}"
)

# Nil info is a no-op.
noop = img_node('<img src="/assets/images/research/fig.png">')
CoMPhyResponsiveImages.apply_dimensions(noop, nil)
assert(noop['width'].nil? && noop['height'].nil?, 'nil info must no-op')

puts 'responsive-images.test.rb: all assertions passed'
Loading