@@ -147,10 +147,52 @@ Phoenix.libs = {
147147// global API is only usable/stable after App init
148148Phoenix . globalAPI = { } ;
149149
150- async function _capturePageBinary ( rect ) {
150+ function _resolveRect ( rectOrNodeOrSelector ) {
151+ if ( rectOrNodeOrSelector === undefined || rectOrNodeOrSelector === null ) {
152+ return undefined ; // full page capture
153+ }
154+ let element ;
155+ // Case 1: jQuery selector string
156+ if ( typeof rectOrNodeOrSelector === 'string' ) {
157+ const $el = $ ( rectOrNodeOrSelector ) ;
158+ if ( $el . length === 0 ) {
159+ throw new Error ( "No element found for selector: " +
160+ rectOrNodeOrSelector ) ;
161+ }
162+ if ( $el . length > 1 ) {
163+ throw new Error ( "Selector must match exactly one element, but matched " +
164+ $el . length + ": " + rectOrNodeOrSelector ) ;
165+ }
166+ element = $el [ 0 ] ;
167+ } else if ( rectOrNodeOrSelector instanceof HTMLElement ) {
168+ // Case 2: DOM node (Element instance)
169+ element = rectOrNodeOrSelector ;
170+ } else if ( typeof rectOrNodeOrSelector === 'object' ) {
171+ // Case 3: Plain rect object {x, y, width, height}
172+ return rectOrNodeOrSelector ; // pass through for validation in _capturePageBinary
173+ } else {
174+ throw new Error ( "Expected a rect object, DOM node, or jQuery selector string" ) ;
175+ }
176+ // Convert DOM element to rect via getBoundingClientRect().
177+ // getBoundingClientRect() returns values in the zoomed CSS coordinate space, but
178+ // the native capture APIs (Electron capturePage, Tauri capture_page) expect
179+ // coordinates in the unzoomed viewport space. Divide by the webview zoom factor
180+ // to convert.
181+ const zoomFactor = ( window . PhStore && window . PhStore . getItem ( "desktopZoomScale" ) ) || 1 ;
182+ const domRect = element . getBoundingClientRect ( ) ;
183+ return {
184+ x : Math . round ( domRect . x * zoomFactor ) ,
185+ y : Math . round ( domRect . y * zoomFactor ) ,
186+ width : Math . round ( domRect . width * zoomFactor ) ,
187+ height : Math . round ( domRect . height * zoomFactor )
188+ } ;
189+ }
190+
191+ async function _capturePageBinary ( rectOrNodeOrSelector ) {
151192 if ( ! Phoenix . isNativeApp ) {
152193 throw new Error ( "Screenshot capture is not supported in browsers" ) ;
153194 }
195+ const rect = _resolveRect ( rectOrNodeOrSelector ) ;
154196 if ( rect !== undefined ) {
155197 if ( rect . x === undefined || rect . y === undefined ||
156198 rect . width === undefined || rect . height === undefined ) {
@@ -166,10 +208,11 @@ async function _capturePageBinary(rect) {
166208 if ( rect . width <= 0 || rect . height <= 0 ) {
167209 throw new Error ( "rect width and height must be greater than 0" ) ;
168210 }
169- if ( rect . x + rect . width > window . innerWidth ) {
211+ const zoomFactor = ( window . PhStore && window . PhStore . getItem ( "desktopZoomScale" ) ) || 1 ;
212+ if ( rect . x + rect . width > window . innerWidth * zoomFactor ) {
170213 throw new Error ( "rect x + width exceeds window innerWidth" ) ;
171214 }
172- if ( rect . y + rect . height > window . innerHeight ) {
215+ if ( rect . y + rect . height > window . innerHeight * zoomFactor ) {
173216 throw new Error ( "rect y + height exceeds window innerHeight" ) ;
174217 }
175218 }
@@ -830,18 +873,46 @@ Phoenix.app = {
830873 }
831874 return ( ) => { } ; // No-op for unsupported platforms
832875 } ,
833- screenShotBinary : function ( rect ) {
834- return _capturePageBinary ( rect ) ;
876+ /**
877+ * Captures a screenshot and returns the raw PNG bytes.
878+ * @param {Object|HTMLElement|string } [rectOrNodeOrSelector] - Area to capture. Can be:
879+ * - A rect object `{x, y, width, height}` specifying pixel coordinates
880+ * - A DOM element whose bounding rect will be captured
881+ * - A jQuery selector string (must match exactly one element)
882+ * - Omit to capture the full page
883+ * @returns {Promise<Uint8Array> } PNG image data
884+ */
885+ screenShotBinary : function ( rectOrNodeOrSelector ) {
886+ return _capturePageBinary ( rectOrNodeOrSelector ) ;
835887 } ,
836- screenShotToBlob : async function ( rect ) {
837- const bytes = await _capturePageBinary ( rect ) ;
888+ /**
889+ * Captures a screenshot and returns it as a PNG Blob.
890+ * @param {Object|HTMLElement|string } [rectOrNodeOrSelector] - Area to capture. Can be:
891+ * - A rect object `{x, y, width, height}` specifying pixel coordinates
892+ * - A DOM element whose bounding rect will be captured
893+ * - A jQuery selector string (must match exactly one element)
894+ * - Omit to capture the full page
895+ * @returns {Promise<Blob> } PNG Blob with type "image/png"
896+ */
897+ screenShotToBlob : async function ( rectOrNodeOrSelector ) {
898+ const bytes = await _capturePageBinary ( rectOrNodeOrSelector ) ;
838899 return new Blob ( [ bytes ] , { type : "image/png" } ) ;
839900 } ,
840- screenShotToPNGFile : async function ( filePathToSave , rect ) {
901+ /**
902+ * Captures a screenshot and writes it to a PNG file.
903+ * @param {string } filePathToSave - VFS path to save the PNG file to
904+ * @param {Object|HTMLElement|string } [rectOrNodeOrSelector] - Area to capture. Can be:
905+ * - A rect object `{x, y, width, height}` specifying pixel coordinates
906+ * - A DOM element whose bounding rect will be captured
907+ * - A jQuery selector string (must match exactly one element)
908+ * - Omit to capture the full page
909+ * @returns {Promise<void> }
910+ */
911+ screenShotToPNGFile : async function ( filePathToSave , rectOrNodeOrSelector ) {
841912 if ( ! filePathToSave || typeof filePathToSave !== 'string' ) {
842913 throw new Error ( "filePathToSave must be a non-empty string" ) ;
843914 }
844- const bytes = await _capturePageBinary ( rect ) ;
915+ const bytes = await _capturePageBinary ( rectOrNodeOrSelector ) ;
845916 return new Promise ( ( resolve , reject ) => {
846917 fs . writeFile ( filePathToSave , bytes . buffer , 'binary' , ( err ) => {
847918 if ( err ) {
0 commit comments