diff --git a/packages/core/image-asset/image-asset-common.ts b/packages/core/image-asset/image-asset-common.ts index 798727dd3..029fc8807 100644 --- a/packages/core/image-asset/image-asset-common.ts +++ b/packages/core/image-asset/image-asset-common.ts @@ -19,7 +19,7 @@ export class ImageAssetBase extends Observable implements ImageAssetDefinition { } set options(value: ImageAssetOptions) { - this._options = value; + this._options = normalizeImageAssetOptions(value); } get nativeImage(): any { @@ -35,6 +35,35 @@ export class ImageAssetBase extends Observable implements ImageAssetDefinition { } } +function toPositiveInt(value: any): number { + if (value == null) { + return 0; + } + if (typeof value === 'number') { + return value > 0 ? Math.floor(value) : 0; + } + if (typeof value === 'string') { + const parsed = parseInt(value, 10); + return isNaN(parsed) || parsed <= 0 ? 0 : parsed; + } + return 0; +} + +function normalizeImageAssetOptions(options: ImageAssetOptions): ImageAssetOptions { + const normalized = options ? { ...options } : ({} as ImageAssetOptions); + // Coerce potential string values to positive integers; fallback to 0 + // to trigger default sizing downstream + (normalized as any).width = toPositiveInt((options as any)?.width); + (normalized as any).height = toPositiveInt((options as any)?.height); + if (typeof normalized.keepAspectRatio !== 'boolean') { + normalized.keepAspectRatio = true; + } + if (typeof normalized.autoScaleFactor !== 'boolean') { + normalized.autoScaleFactor = true; + } + return normalized; +} + export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, reqHeight) { const widthCoef = sourceWidth / reqWidth; const heightCoef = sourceHeight / reqHeight; @@ -47,8 +76,9 @@ export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, req } export function getRequestedImageSize(src: { width: number; height: number }, options: ImageAssetOptions): { width: number; height: number } { - let reqWidth = options.width || Math.min(src.width, Screen.mainScreen.widthPixels); - let reqHeight = options.height || Math.min(src.height, Screen.mainScreen.heightPixels); + const normalized = normalizeImageAssetOptions(options); + let reqWidth = normalized.width || Math.min(src.width, Screen.mainScreen.widthPixels); + let reqHeight = normalized.height || Math.min(src.height, Screen.mainScreen.heightPixels); if (options && options.keepAspectRatio) { const safeAspectSize = getAspectSafeDimensions(src.width, src.height, reqWidth, reqHeight); diff --git a/packages/core/image-asset/image-asset-options.spec.ts b/packages/core/image-asset/image-asset-options.spec.ts new file mode 100644 index 000000000..d7a10649f --- /dev/null +++ b/packages/core/image-asset/image-asset-options.spec.ts @@ -0,0 +1,27 @@ +import { getRequestedImageSize } from './image-asset-common'; + +describe('ImageAssetOptions normalization', () => { + it('coerces string width/height to numbers', () => { + const src = { width: 2000, height: 1500 }; + const result = getRequestedImageSize(src as any, { width: '300' as any, height: '200' as any, keepAspectRatio: false, autoScaleFactor: true } as any); + expect(result.width).toBe(300); + expect(result.height).toBe(200); + }); + + it('falls back to defaults when invalid strings provided', () => { + const src = { width: 800, height: 600 }; + const result = getRequestedImageSize(src as any, { width: 'abc' as any, height: '' as any, keepAspectRatio: false } as any); + // should fall back to screen pixel defaults via getRequestedImageSize, but since + // we cannot easily control Screen.mainScreen here, we at least assert they are > 0 + expect(result.width).toBeGreaterThan(0); + expect(result.height).toBeGreaterThan(0); + }); + + it('respects keepAspectRatio by adjusting to safe dimensions', () => { + const src = { width: 2000, height: 1000 }; + const result = getRequestedImageSize(src as any, { width: '500' as any, height: '500' as any, keepAspectRatio: true } as any); + // current implementation scales using the smaller coefficient (min), so expect 1000x500 + expect(result.width).toBe(1000); + expect(result.height).toBe(500); + }); +}); diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar index c9fc4e1f5..b0cb2cd98 100644 Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java index d32fed9f4..29a3da916 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java @@ -170,6 +170,36 @@ public class Utils { boolean autoScaleFactor; } + private static int parsePositiveInt(JSONObject object, String key) { + if (object == null || key == null) { + return 0; + } + try { + if (!object.has(key) || object.isNull(key)) { + return 0; + } + Object value = object.get(key); + if (value instanceof Number) { + int parsed = (int) Math.floor(((Number) value).doubleValue()); + return parsed > 0 ? parsed : 0; + } + if (value instanceof String) { + String s = ((String) value).trim(); + if (s.length() == 0) { + return 0; + } + try { + int parsed = Integer.parseInt(s); + return parsed > 0 ? parsed : 0; + } catch (NumberFormatException ignored) { + return 0; + } + } + } catch (JSONException ignored) { + } + return 0; + } + private static final Executor executors = Executors.newCachedThreadPool(); @@ -297,8 +327,9 @@ public class Utils { try { JSONObject object = new JSONObject(options); - opts.width = object.optInt("width", 0); - opts.height = object.optInt("height", 0); + // Coerce numeric strings or numbers; fallback to 0 for invalid values + opts.width = parsePositiveInt(object, "width"); + opts.height = parsePositiveInt(object, "height"); opts.keepAspectRatio = object.optBoolean("keepAspectRatio", true); opts.autoScaleFactor = object.optBoolean("autoScaleFactor", true); } catch (JSONException ignored) {