diff --git a/docs/documentation.md b/docs/documentation.md index 901693d9..4c986570 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -144,7 +144,8 @@ import { ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM, - + ALIGN_COVER, + INITIAL_VALUE } from 'react-svg-pan-zoom' ``` diff --git a/src/constants.js b/src/constants.js index 7d7a9e9b..76e4f7d9 100644 --- a/src/constants.js +++ b/src/constants.js @@ -22,5 +22,6 @@ export const ALIGN_LEFT = 'left'; export const ALIGN_RIGHT = 'right'; export const ALIGN_TOP = 'top'; export const ALIGN_BOTTOM = 'bottom'; +export const ALIGN_COVER = 'cover'; export const INITIAL_VALUE = {} diff --git a/src/features/zoom.js b/src/features/zoom.js index 7ad9134c..7b828426 100644 --- a/src/features/zoom.js +++ b/src/features/zoom.js @@ -2,7 +2,7 @@ import {fromObject, scale, transform, translate} from 'transformation-matrix'; import { ACTION_ZOOM, MODE_IDLE, MODE_ZOOMING, - ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM + ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP, ALIGN_BOTTOM, ALIGN_COVER } from '../constants'; import {decompose, getSVGPoint, set} from './common'; import calculateBox from '../utils/calculateBox'; @@ -101,15 +101,16 @@ export function fitToViewer(value, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) { let scaleY = viewerHeight / SVGHeight; let scaleLevel = Math.min(scaleX, scaleY); - const scaleMatrix = scale(scaleLevel, scaleLevel); + let scaleMatrix = scale(scaleLevel, scaleLevel); let translateX = -SVGMinX * scaleX; let translateY = -SVGMinY * scaleY; - // after fitting, SVG and the viewer will match in width (1) or in height (2) - if (scaleX < scaleY) { - //(1) match in width, meaning scaled SVGHeight <= viewerHeight + // after fitting, SVG and the viewer will match in width (1) or in height (2) or SVG will cover the container with preserving aspect ratio (0) + if (scaleX < scaleY) { let remainderY = viewerHeight - scaleX * SVGHeight; + + //(1) match in width, meaning scaled SVGHeight <= viewerHeight switch(SVGAlignY) { case ALIGN_TOP: translateY = -SVGMinY * scaleLevel; @@ -123,12 +124,20 @@ export function fitToViewer(value, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) { translateY = remainderY - SVGMinY * scaleLevel; break; + case ALIGN_COVER: + scaleMatrix = scale(scaleY, scaleY); // (0) we must now match to short edge, in this case - height + let remainderX = viewerWidth - scaleY * SVGWidth; // calculate remainder in the other scale + + translateX = SVGMinX + Math.round(remainderX / 2); // center by the long edge + break; + default: //no op } } else { - //(2) match in height, meaning scaled SVGWidth <= viewerWidth let remainderX = viewerWidth - scaleY * SVGWidth; + + //(2) match in height, meaning scaled SVGWidth <= viewerWidth switch(SVGAlignX) { case ALIGN_LEFT: translateX = -SVGMinX * scaleLevel; @@ -142,12 +151,20 @@ export function fitToViewer(value, SVGAlignX=ALIGN_LEFT, SVGAlignY=ALIGN_TOP) { translateX = remainderX - SVGMinX * scaleLevel; break; + case ALIGN_COVER: + scaleMatrix = scale(scaleX, scaleX); // (0) we must now match to short edge, in this case - width + let remainderY = viewerHeight - scaleX * SVGHeight; // calculate remainder in the other scale + + translateY = SVGMinY + Math.round(remainderY / 2); // center by the long edge + break; + default: //no op } } const translationMatrix = translate(translateX, translateY); + const matrix = transform( translationMatrix, //2 scaleMatrix //1 diff --git a/storybook/stories/DifferentSizesStory.jsx b/storybook/stories/DifferentSizesStory.jsx index 9284d5b8..e88489ad 100644 --- a/storybook/stories/DifferentSizesStory.jsx +++ b/storybook/stories/DifferentSizesStory.jsx @@ -7,6 +7,7 @@ import { ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP, + ALIGN_COVER, UncontrolledReactSVGPanZoom } from '../../src/index'; @@ -34,12 +35,14 @@ export default class DifferentSizesStory extends Component { SVGAlignX: select('toolbarProps.SVGAlignX', { [ALIGN_LEFT]: ALIGN_LEFT, [ALIGN_CENTER]: ALIGN_CENTER, - [ALIGN_RIGHT]: ALIGN_RIGHT + [ALIGN_RIGHT]: ALIGN_RIGHT, + [ALIGN_COVER]: ALIGN_COVER, }, ALIGN_LEFT), SVGAlignY: select('toolbarProps.SVGAlignY', { [ALIGN_TOP]: ALIGN_TOP, [ALIGN_CENTER]: ALIGN_CENTER, - [ALIGN_BOTTOM]: ALIGN_BOTTOM + [ALIGN_BOTTOM]: ALIGN_BOTTOM, + [ALIGN_COVER]: ALIGN_COVER, }, ALIGN_TOP), } diff --git a/test/features/zoom.spec.js b/test/features/zoom.spec.js index b2d7d9c2..ae1d0783 100644 --- a/test/features/zoom.spec.js +++ b/test/features/zoom.spec.js @@ -5,6 +5,7 @@ import { ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP, + ALIGN_COVER, fitSelection, fitToViewer, MODE_IDLE, @@ -105,7 +106,29 @@ describe("fitSelection", () => { test.todo('h { +describe("fitToViewer rect > viewer", () => { + test("rect with w>h", () => { + const value = getDefaultValue( + 200, 100, //viewer 200x100 + 0, 0, 400, 400, //svg 400x400 + ) + + const value1 = fitToViewer(value, ALIGN_COVER, ALIGN_COVER) + expect(testSVGBBox(value1)).toEqual([0, -50, 200, 150]) + }) + + test("rect with w { + const value = getDefaultValue( + 100, 200, //viewer 200x100 + 0, 0, 400, 400, //svg 400x400 + ) + + const value1 = fitToViewer(value, ALIGN_COVER, ALIGN_COVER) + expect(testSVGBBox(value1)).toEqual([-50, 0, 150, 200]) + }) +}) + +describe("fitToViewer rect < viewer", () => { test("rect with w>h", () => { const value = getDefaultValue( 200, 100, //viewer 200x100 @@ -120,6 +143,9 @@ describe("fitToViewer", () => { const value3 = fitToViewer(value, ALIGN_RIGHT, ALIGN_TOP) expect(testSVGBBox(value3)).toEqual([100, 0, 200, 100]) + + const value4 = fitToViewer(value, ALIGN_COVER, ALIGN_COVER) + expect(testSVGBBox(value4)).toEqual([0, -50, 200, 150]) }) test("rect with w { @@ -136,6 +162,9 @@ describe("fitToViewer", () => { const value3 = fitToViewer(value, ALIGN_LEFT, ALIGN_BOTTOM) expect(testSVGBBox(value3)).toEqual([0, 100, 100, 200]) + + const value4 = fitToViewer(value, ALIGN_COVER, ALIGN_COVER) + expect(testSVGBBox(value4)).toEqual([-50, 0, 150, 200]) }) })