From 622776cbfd11efb1c25d167d10c940676cc145d4 Mon Sep 17 00:00:00 2001 From: Gurkirpal Singh <12171804+gpalsingh@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:48:47 +0530 Subject: [PATCH] Add square annotation editor --- packages/react-pdf/package.json | 4 +- packages/react-pdf/src/Document.tsx | 61 ++++++++++++++++--- packages/react-pdf/src/Page.tsx | 23 +++++-- .../src/Page/AnnotationEditorLayer.css | 20 ++++-- .../src/images/cursor-editorSquare.svg | 1 + packages/react-pdf/src/shared/types.ts | 2 + 6 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 packages/react-pdf/src/images/cursor-editorSquare.svg diff --git a/packages/react-pdf/package.json b/packages/react-pdf/package.json index d2bb5431b..055d201bf 100644 --- a/packages/react-pdf/package.json +++ b/packages/react-pdf/package.json @@ -1,6 +1,6 @@ { "name": "@commutatus/react-pdf", - "version": "8.0.6", + "version": "8.0.7", "description": "Display PDFs in your React app as easily as if they were images.", "type": "module", "sideEffects": [ @@ -61,7 +61,7 @@ }, "license": "MIT", "dependencies": { - "@commutatus/pdfjs-dist": "^5.0.2", + "@commutatus/pdfjs-dist": "5.0.3", "clsx": "^2.0.0", "dequal": "^2.0.3", "make-cancellable-promise": "^1.3.1", diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index 15ce47d2d..5cc714f75 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -7,6 +7,7 @@ import React, { useImperativeHandle, useMemo, useRef, + useState, } from 'react'; import PropTypes from 'prop-types'; import makeEventProps from 'make-event-props'; @@ -123,6 +124,8 @@ export type DocumentProps = { */ highlightEditorColors?: HighlightEditorColorsType; defaultHighlightColor?: string; + defaultSquareFillColor?: string; + defaultSquareOpacity?: string; /** * The path used to prefix the src attributes of annotation SVGs. * @@ -286,6 +289,8 @@ const Document = forwardRef(function Document( file, highlightEditorColors, defaultHighlightColor, + defaultSquareFillColor, + defaultSquareOpacity, inputRef, imageResourcesPath, loading = 'Loading PDF…', @@ -315,6 +320,7 @@ const Document = forwardRef(function Document( const eventBus = useRef(new EventBus()); const defaultInputRef = useRef(null); const mainContainerRef = inputRef || defaultInputRef; + const [globalScale, setGlobalScale] = useState(null); const linkService = useRef(new LinkService()); @@ -323,6 +329,16 @@ const Document = forwardRef(function Document( const prevFile = useRef(); const prevOptions = useRef(); + useEffect(() => { + if (globalScale && (mainContainerRef as any)?.current) { + eventBus.current.dispatch('scalechanging', { + source: (mainContainerRef as any).current, + scale: globalScale, + presetValue: 'auto', + }); + } + }, [globalScale, mainContainerRef]); + useEffect(() => { if (file && file !== prevFile.current && isParameterObject(file)) { warning( @@ -513,14 +529,41 @@ const Document = forwardRef(function Document( [source], ); - useEffect(() => { - if (defaultHighlightColor && annotationEditorUiManager) { - annotationEditorUiManager.updateParams( - pdfjs.AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, - defaultHighlightColor, - ); - } - }, [defaultHighlightColor, annotationEditorUiManager]); + useEffect( + function updateDefaultHighlightColor() { + if (defaultHighlightColor && annotationEditorUiManager) { + annotationEditorUiManager.updateParams( + pdfjs.AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, + defaultHighlightColor, + ); + } + }, + [defaultHighlightColor, annotationEditorUiManager], + ); + + useEffect( + function updateDefaultSquareOpacity() { + if (defaultSquareOpacity && annotationEditorUiManager) { + annotationEditorUiManager.updateParams( + pdfjs.AnnotationEditorParamsType.SQUARE_OPACITY, + defaultSquareOpacity, + ); + } + }, + [defaultSquareOpacity, annotationEditorUiManager], + ); + + useEffect( + function updateDefaultSquareFillColor() { + if (defaultSquareFillColor && annotationEditorUiManager) { + annotationEditorUiManager.updateParams( + pdfjs.AnnotationEditorParamsType.SQUARE_COLOR, + defaultSquareFillColor, + ); + } + }, + [defaultSquareFillColor, annotationEditorUiManager], + ); function createAnnotationEditorUiManager() { const colorPickerOptions = getHighlightColorsString(highlightEditorColors); @@ -671,6 +714,7 @@ const Document = forwardRef(function Document( registerPage, renderMode, rotate, + setGlobalScale, unregisterPage, }), [ @@ -681,6 +725,7 @@ const Document = forwardRef(function Document( pdf, renderMode, rotate, + setGlobalScale, ], ); diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index 122d2d940..dc070bda8 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -361,6 +361,7 @@ const Page: React.FC = function Page(props) { renderTextLayer: renderTextLayerProps = true, rotate: rotateProps, scale: scaleProps = defaultScale, + setGlobalScale, unregisterPage, width, ...otherProps @@ -384,13 +385,15 @@ const Page: React.FC = function Page(props) { const rotate = rotateProps ?? (page ? page.rotate : null); - const scale = useMemo(() => { + const { scale, pdfjsInternalScale } = useMemo(() => { if (!page) { - return null; + // TODO: Fix this workaround and use a single scale value + return { scale: null, pdfjsInternalScale: null }; } // Be default, we'll render page at 100% * scale width. - let pageScale = 1; + let pageScale = 1, + pdfjsInternalScale = 1; // Passing scale explicitly null would cause the page not to render const scaleWithDefault = scaleProps ?? defaultScale; @@ -399,13 +402,17 @@ const Page: React.FC = function Page(props) { if (width || height) { const viewport = page.getViewport({ scale: 1, rotation: rotate as number }); if (width) { + // TODO: Fix hardcoded values + const pageBaseWidth = 816; + const sidebarWidth = 0; + pdfjsInternalScale = (width - sidebarWidth) / pageBaseWidth; pageScale = width / viewport.width; } else if (height) { pageScale = height / viewport.height; } } - return scaleWithDefault * pageScale; + return { scale: scaleWithDefault * pageScale, pdfjsInternalScale }; }, [height, page, rotate, scaleProps, width]); const drawLayer = useMemo(() => { @@ -436,6 +443,12 @@ const Page: React.FC = function Page(props) { useEffect(hook, [_enableRegisterUnregisterPage, pdf, pageIndex, unregisterPage]); + useEffect(() => { + if (pdfjsInternalScale && setGlobalScale) { + setGlobalScale(pdfjsInternalScale); + } + }, [pdfjsInternalScale, setGlobalScale]); + useEffect( function updateAnnotationEditorUiManagerPage() { if (annotationEditorUiManager) { @@ -703,7 +716,7 @@ const Page: React.FC = function Page(props) { data-page-number={pageNumber} ref={mergeRefs(inputRef, pageElement)} style={{ - ['--scale-factor' as string]: `${scale}`, + ['--scale-factor' as string]: `${(pdfjsInternalScale || 1) * pdfjs.PixelsPerInch.PDF_TO_CSS_UNITS}`, backgroundColor: canvasBackground || 'white', position: 'relative', minWidth: 'min-content', diff --git a/packages/react-pdf/src/Page/AnnotationEditorLayer.css b/packages/react-pdf/src/Page/AnnotationEditorLayer.css index 1d71395bf..d4f420690 100644 --- a/packages/react-pdf/src/Page/AnnotationEditorLayer.css +++ b/packages/react-pdf/src/Page/AnnotationEditorLayer.css @@ -37,8 +37,10 @@ --editorFreeText-editing-cursor: text; /*#if COMPONENTS*/ --editorInk-editing-cursor: pointer; + --editorSquare-editing-cursor: pointer; /*#else*/ --editorInk-editing-cursor: svg-load(../images/cursor-editorInk.svg) 0 16, pointer; + --editorSquare-editing-cursor: svg-load(../images/cursor-editorSquare.svg) 25 25, pointer; /*#endif*/ } @@ -103,7 +105,11 @@ cursor: var(--editorInk-editing-cursor); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) { +.annotationEditorLayer.squareEditing { + cursor: var(--editorSquare-editing-cursor); +} + +.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .squareEditor, .stampEditor) { position: absolute; background: transparent; z-index: 1; @@ -164,6 +170,7 @@ :is( .freeTextEditor, .inkEditor, + .squareEditor, .stampEditor, .highlightEditor, .underlineEditor, @@ -429,16 +436,19 @@ user-select: auto; } -.annotationEditorLayer .inkEditor { +.annotationEditorLayer .inkEditor, +.annotationEditorLayer .squareEditor { width: 100%; height: 100%; } -.annotationEditorLayer .inkEditor.editing { +.annotationEditorLayer .inkEditor.editing, +.annotationEditorLayer .squareEditor.editing { cursor: inherit; } -.annotationEditorLayer .inkEditor .inkEditorCanvas { +.annotationEditorLayer .inkEditor .inkEditorCanvas, +.annotationEditorLayer .squareEditor .squareEditorCanvas { position: absolute; inset: 0; width: 100%; @@ -457,7 +467,7 @@ } .annotationEditorLayer { - :is(.freeTextEditor, .inkEditor, .stampEditor) { + :is(.freeTextEditor, .inkEditor, .squareEditor, .stampEditor) { & > .resizers { position: absolute; inset: 0; diff --git a/packages/react-pdf/src/images/cursor-editorSquare.svg b/packages/react-pdf/src/images/cursor-editorSquare.svg new file mode 100644 index 000000000..5b264fc47 --- /dev/null +++ b/packages/react-pdf/src/images/cursor-editorSquare.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/react-pdf/src/shared/types.ts b/packages/react-pdf/src/shared/types.ts index c625dc7d4..b51a0f53e 100644 --- a/packages/react-pdf/src/shared/types.ts +++ b/packages/react-pdf/src/shared/types.ts @@ -15,6 +15,7 @@ import type { import type { AnnotationLayerParameters } from '@commutatus/pdfjs-dist/types/src/display/annotation_layer.js'; import type LinkService from '../LinkService.js'; import type React from 'react'; +import type { Dispatch, SetStateAction } from 'react'; type NullableObject = { [P in keyof T]: T[P] | null }; @@ -136,6 +137,7 @@ export type DocumentContextType = { registerPage: RegisterPage; renderMode?: RenderMode; rotate?: number | null; + setGlobalScale: Dispatch>; unregisterPage: UnregisterPage; } | null;