From 9cd46184f99be276c410c330df4791f879afe562 Mon Sep 17 00:00:00 2001 From: bookernath Date: Wed, 20 Oct 2021 20:14:13 -0500 Subject: [PATCH 1/2] Add helper for generating a 1x/2x srcset for store logo --- helpers.js | 1 + helpers/getImage.js | 4 +- helpers/getImageSrcset1x2x.js | 45 +++++++++++++++ helpers/lib/common.js | 5 +- spec/helpers.js | 1 + spec/helpers/getImageSrcset1x2x.js | 92 ++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 helpers/getImageSrcset1x2x.js create mode 100644 spec/helpers/getImageSrcset1x2x.js diff --git a/helpers.js b/helpers.js index d1f45a7..78cd1ba 100644 --- a/helpers.js +++ b/helpers.js @@ -19,6 +19,7 @@ const helpersList = [ 'getImageManagerImage', 'getImageManagerImageSrcset', 'getImageSrcset', + 'getImageSrcset1x2x', 'getVar', 'helperMissing', 'if', diff --git a/helpers/getImage.js b/helpers/getImage.js index c77015d..a60f364 100644 --- a/helpers/getImage.js +++ b/helpers/getImage.js @@ -21,8 +21,8 @@ const factory = globals => { if (utils.isObject(presets) && utils.isObject(presets[presetName])) { // If preset is one of the given presets in _images - width = parseInt(presets[presetName].width, 10) || 5120; - height = parseInt(presets[presetName].height, 10) || 5120; + width = parseInt(presets[presetName].width, 10) || common.maximumPixelSize; + height = parseInt(presets[presetName].height, 10) || common.maximumPixelSize; size = `${width}x${height}`; } else if (sizeRegex.test(settings[presetName])) { // If preset name is a setting and match the NNNxNNN format diff --git a/helpers/getImageSrcset1x2x.js b/helpers/getImageSrcset1x2x.js new file mode 100644 index 0000000..ba6244c --- /dev/null +++ b/helpers/getImageSrcset1x2x.js @@ -0,0 +1,45 @@ +'use strict'; +const utils = require('handlebars-utils'); +const SafeString = require('handlebars').SafeString; +const common = require('./lib/common'); + +const factory = () => { + return function(image, size1x) { + // Regex to test size string is of the form 123x123 + const pixelDimensionsRegex = /(^\d+w$)|(^(\d+?)x(\d+?)$)/; + + if (!utils.isObject(image) || !utils.isString(image.data) + || !common.isValidURL(image.data) || image.data.indexOf('{:size}') === -1) { + throw new Error("Invalid StencilImage passed to getImageSrcset1x2x") + } + + if (!pixelDimensionsRegex.test(size1x)) { + throw new Error("Invalid size argument passed to getImageSrcset1x2x, must be a set of pixel dimensions of the format '123x123'") + } + + if(!image.width || !image.height || !Number.isInteger(image.width) || !Number.isInteger(image.height)) { + return new SafeString(image.data.replace('{:size}', size1x)); + } + + const [width1x, height1x] = size1x.split('x').map(i => parseInt(i)); + + const [width2x, height2x] = [width1x, height1x].map(i => i * 2); + + if (width2x > image.width + || height2x > image.height + || width2x > common.maximumPixelSize + || height2x > common.maximumPixelSize) { + // Either the image is too small to make a srcset with a 2x size, + // or those sizes would be larger than the resizer supports + return new SafeString(image.data.replace('{:size}', size1x)); + } else { + const size2x = `${width2x}x${height2x}`; + return new SafeString(`${image.data.replace('{:size}', size1x)} 1x, ${image.data.replace('{:size}', size2x)} 2x`); + } + }; +}; + +module.exports = [{ + name: 'getImageSrcset1x2x', + factory: factory, +}]; diff --git a/helpers/lib/common.js b/helpers/lib/common.js index ef59ead..e120b28 100644 --- a/helpers/lib/common.js +++ b/helpers/lib/common.js @@ -16,7 +16,10 @@ function unwrapIfSafeString(handlebars, val) { return val; } +const maximumPixelSize = 5120; + module.exports = { isValidURL, - unwrapIfSafeString + unwrapIfSafeString, + maximumPixelSize }; diff --git a/spec/helpers.js b/spec/helpers.js index 3b7b962..deb2683 100644 --- a/spec/helpers.js +++ b/spec/helpers.js @@ -38,6 +38,7 @@ describe('helper registration', () => { 'getImageManagerImage', 'getImageManagerImageSrcset', 'getImageSrcset', + 'getImageSrcset1x2x', 'getVar', 'helperMissing', 'if', diff --git a/spec/helpers/getImageSrcset1x2x.js b/spec/helpers/getImageSrcset1x2x.js new file mode 100644 index 0000000..37d0608 --- /dev/null +++ b/spec/helpers/getImageSrcset1x2x.js @@ -0,0 +1,92 @@ +const Lab = require('lab'), + lab = exports.lab = Lab.script(), + describe = lab.experiment, + it = lab.it, + specHelpers = require('../spec-helpers'), + testRunner = specHelpers.testRunner, + renderString = specHelpers.renderString; + +describe('getImageSrcset1x2x helper', function() { + const urlData = 'https://cdn.example.com/path/to/{:size}/image.png?c=2'; + const context = { + image_size_small: "123x456", + image_size_large: "1000x900", + image_url: 'http://example.com/image.png', + not_an_image: null, + image_without_dimensions: { + data: urlData + }, + image_with_null_dimensions: { + data: urlData, + width: null, + height: null, + }, + image_with_dimensions: { + data: urlData, + width: 1400, + height: 950, + }, + image_with_large_dimensions: { + data: urlData, + width: 5200, + height: 5200, + }, + }; + + const runTestCases = testRunner({context}); + + it('should return a srcset if a valid image and srcset sizes are passed', function(done) { + runTestCases([ + { + input: '{{getImageSrcset1x2x image_with_dimensions "123x456"}}', + output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x', + }, + { + input: '{{getImageSrcset1x2x image_with_dimensions image_size_small}}', + output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x', + }, + ], done); + }); + + it('should return an image url alone (1x) if the 2x size is not within constraints', function(done) { + runTestCases([ + { + input: '{{getImageSrcset1x2x image_with_dimensions "1000x900"}}', + output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2', + }, + { + input: '{{getImageSrcset1x2x image_with_dimensions image_size_large}}', + output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2', + }, + { + input: '{{getImageSrcset1x2x image_with_large_dimensions "2600x2600"}}', + output: 'https://cdn.example.com/path/to/2600x2600/image.png?c=2', + }, + ], done); + }); + + it('should return an image url alone (1x) if image does not have dimesions', function(done) { + runTestCases([ + { + input: '{{getImageSrcset1x2x image_with_null_dimensions "1000x900"}}', + output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2', + }, + { + input: '{{getImageSrcset1x2x image_without_dimensions "1000x900"}}', + output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2', + }, + ], done); + }); + + it('should throw an exception if an no size argument is passed', function (done) { + renderString('{{getImageSrcset1x2x image_with_dimensions}}').catch(e => { + done(); + }); + }); + + it('should throw an exception if an invalid size argument is passed', function (done) { + renderString('{{getImageSrcset1x2x image_with_dimensions "100px"}}').catch(e => { + done(); + }); + }); +}); From 7fafb7008345986699b8a5d6ad5ca9239abeb60d Mon Sep 17 00:00:00 2001 From: bookernath Date: Wed, 20 Oct 2021 20:57:10 -0500 Subject: [PATCH 2/2] Support an arbitrary size less than 2x if necessary by image dimensions --- helpers/getImageSrcset1x2x.js | 32 +++++++++++++++++++----------- spec/helpers/getImageSrcset1x2x.js | 4 ++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/helpers/getImageSrcset1x2x.js b/helpers/getImageSrcset1x2x.js index ba6244c..6567663 100644 --- a/helpers/getImageSrcset1x2x.js +++ b/helpers/getImageSrcset1x2x.js @@ -1,12 +1,13 @@ 'use strict'; const utils = require('handlebars-utils'); -const SafeString = require('handlebars').SafeString; const common = require('./lib/common'); -const factory = () => { +const factory = globals => { return function(image, size1x) { // Regex to test size string is of the form 123x123 const pixelDimensionsRegex = /(^\d+w$)|(^(\d+?)x(\d+?)$)/; + const return1x = (image, size1x) => + new globals.handlebars.SafeString(image.data.replace('{:size}', size1x)); if (!utils.isObject(image) || !utils.isString(image.data) || !common.isValidURL(image.data) || image.data.indexOf('{:size}') === -1) { @@ -18,23 +19,30 @@ const factory = () => { } if(!image.width || !image.height || !Number.isInteger(image.width) || !Number.isInteger(image.height)) { - return new SafeString(image.data.replace('{:size}', size1x)); + return return1x(image, size1x); } const [width1x, height1x] = size1x.split('x').map(i => parseInt(i)); - const [width2x, height2x] = [width1x, height1x].map(i => i * 2); - - if (width2x > image.width - || height2x > image.height - || width2x > common.maximumPixelSize - || height2x > common.maximumPixelSize) { + if (width1x > image.width + || width1x > image.height) { // Either the image is too small to make a srcset with a 2x size, // or those sizes would be larger than the resizer supports - return new SafeString(image.data.replace('{:size}', size1x)); + return return1x(image, size1x); } else { - const size2x = `${width2x}x${height2x}`; - return new SafeString(`${image.data.replace('{:size}', size1x)} 1x, ${image.data.replace('{:size}', size2x)} 2x`); + const smallestFactor = Math.min((image.width/width1x), (image.height/height1x)); + const factor = smallestFactor < 2 ? smallestFactor : 2; + const roundedFactor = +(factor).toFixed(4) //cast to Number for clean rounding + + const [widthXx, heightXx] = [width1x, height1x].map(i => Math.round(i * factor)); + + if (widthXx > common.maximumPixelSize || heightXx > common.maximumPixelSize) { + return return1x(image, size1x); + } + + const sizeXx = `${widthXx}x${heightXx}`; + + return new globals.handlebars.SafeString(`${image.data.replace('{:size}', size1x)} 1x, ${image.data.replace('{:size}', sizeXx)} ${roundedFactor}x`); } }; }; diff --git a/spec/helpers/getImageSrcset1x2x.js b/spec/helpers/getImageSrcset1x2x.js index 37d0608..2050876 100644 --- a/spec/helpers/getImageSrcset1x2x.js +++ b/spec/helpers/getImageSrcset1x2x.js @@ -41,6 +41,10 @@ describe('getImageSrcset1x2x helper', function() { input: '{{getImageSrcset1x2x image_with_dimensions "123x456"}}', output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x', }, + { + input: '{{getImageSrcset1x2x image_with_dimensions "123x556"}}', + output: 'https://cdn.example.com/path/to/123x556/image.png?c=2 1x, https://cdn.example.com/path/to/210x950/image.png?c=2 1.7086x', + }, { input: '{{getImageSrcset1x2x image_with_dimensions image_size_small}}', output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x',