From 6ccb82047269f06804f18862d7ebe68119aea4e0 Mon Sep 17 00:00:00 2001 From: Steve Hannah Date: Sun, 14 Apr 2024 08:51:00 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20fixed=20but=20with=20drawString=20render?= =?UTF-8?q?ing=20long=20RTL=20strings=20incorrectly=E2=80=A6=20(#3803)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: fixed but with drawString rendering long RTL strings incorrectly on iOS * Removed swing import * Wrapped isRTLString in autorelease pool --------- Co-authored-by: Steve Hannah --- Ports/iOSPort/nativeSources/IOSNative.m | 51 +++++++++++++++++-- .../codename1/impl/ios/IOSImplementation.java | 23 +++++++-- .../src/com/codename1/impl/ios/IOSNative.java | 1 + 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index b6cd5214d8..2b1dfdee05 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -9680,9 +9680,7 @@ JAVA_INT drawLabelStringValign(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisO return drawLabelString(threadStateData, __cn1ThisObject, nativeGraphics, nativeFont, str, x, y + iconStringHGap, textSpaceW, isTickerRunning, tickerShiftText, textDecoration, rtl, endsWith3Points, textWidth, fontHeight); } } - - - + JAVA_VOID com_codename1_impl_ios_IOSImplementation_drawLabelComponent___java_lang_Object_int_int_int_int_com_codename1_ui_plaf_Style_java_lang_String_java_lang_Object_java_lang_Object_int_int_boolean_boolean_int_int_boolean_int_boolean_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT nativeGraphics, JAVA_INT cmpX, JAVA_INT cmpY, JAVA_INT cmpHeight, JAVA_INT cmpWidth, JAVA_OBJECT style, JAVA_OBJECT text, JAVA_OBJECT icon, JAVA_OBJECT stateIcon, JAVA_INT preserveSpaceForState, JAVA_INT gap, JAVA_BOOLEAN rtl, JAVA_BOOLEAN isOppositeSide, JAVA_INT textPosition, JAVA_INT stringWidth, JAVA_BOOLEAN isTickerRunning, JAVA_INT tickerShiftText, JAVA_BOOLEAN endsWith3Points, JAVA_INT valign) { JAVA_OBJECT font = com_codename1_ui_plaf_Style_getFont___R_com_codename1_ui_Font(threadStateData, style); JAVA_OBJECT nativeFont = com_codename1_ui_Font_getNativeFont___R_java_lang_Object(threadStateData, font); @@ -9930,3 +9928,50 @@ JAVA_VOID com_codename1_impl_ios_IOSNative_endBackgroundTask___long(CN1_THREAD_S { [[UIApplication sharedApplication] endBackgroundTask:(UIBackgroundTaskIdentifier)bgTask]; } + +JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isRTLString___java_lang_String_R_boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_OBJECT javaString) +{ + POOL_BEGIN(); + NSString *string = toNSString(CN1_THREAD_STATE_PASS_ARG javaString); + // Define Unicode ranges for Hebrew and Arabic + NSRange hebrewRange = NSMakeRange(0x0590, 0x05FF - 0x0590 + 1); + NSRange arabicRange = NSMakeRange(0x0600, 0x06FF - 0x0600 + 1); + // Range for common neutral characters (basic Latin, common punctuation, and digits) + NSRange neutralRange = NSMakeRange(0x0020, 0x007E - 0x0020 + 1); + // Emoji ranges (covering most common emoji blocks) + NSArray *emojiRanges = @[ + [NSValue valueWithRange:NSMakeRange(0x1F600, 0x1F64F - 0x1F600 + 1)], // Emoticons + [NSValue valueWithRange:NSMakeRange(0x1F300, 0x1F5FF - 0x1F300 + 1)], // Miscellaneous Symbols and Pictographs + [NSValue valueWithRange:NSMakeRange(0x1F900, 0x1F9FF - 0x1F900 + 1)], // Supplemental Symbols and Pictographs + [NSValue valueWithRange:NSMakeRange(0x2600, 0x26FF - 0x2600 + 1)] // Miscellaneous Symbols + ]; + + NSUInteger length = [string length]; + for (NSUInteger i = 0; i < length; i++) { + unichar c = [string characterAtIndex:i]; + // Continue if the character is within the neutral or emoji ranges + BOOL isNeutralOrEmoji = (c >= neutralRange.location && c <= NSMaxRange(neutralRange)); + for (NSValue *value in emojiRanges) { + NSRange range = [value rangeValue]; + if (c >= range.location && c <= NSMaxRange(range)) { + isNeutralOrEmoji = YES; + break; + } + } + if (isNeutralOrEmoji) { + continue; + } + // Return true if the character is within the Hebrew or Arabic Unicode ranges + if ((c >= hebrewRange.location && c <= NSMaxRange(hebrewRange)) || + (c >= arabicRange.location && c <= NSMaxRange(arabicRange))) { + POOL_END(); + return YES; + } + // If the first significant character is not Hebrew or Arabic, return false + POOL_END(); + return NO; + } + + POOL_END(); + return NO; +} diff --git a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java index e9e504a9af..adf5b82866 100644 --- a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java +++ b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java @@ -109,6 +109,7 @@ import com.codename1.util.Callback; import com.codename1.util.StringUtil; import com.codename1.util.SuccessCallback; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Arrays; @@ -150,6 +151,8 @@ public class IOSImplementation extends CodenameOneImplementation { private boolean isActive=false; private final ArrayList onActiveListeners = new ArrayList(); private static BackgroundFetch backgroundFetchCallback; + + private boolean useContentBasedRTLStringDetection = false; /** @@ -342,7 +345,13 @@ public Vector getCookiesForURL(String url) { return v; } return super.getCookiesForURL(url); - } + } + + public void setPlatformHint(String key, String value) { + if ("platformHint.ios.useContentBasedRTLStringDetection".equals(key)) { + useContentBasedRTLStringDetection = Boolean.parseBoolean(value); + } + } private boolean textEditorHidden; @@ -1854,17 +1863,25 @@ public void drawString(Object graphics, String str, int x, int y) { int l = str.length(); int max = fnt.getMaxStringLength(); if(l > max) { + boolean rtl = useContentBasedRTLStringDetection + ? nativeInstance.isRTLString(str) + : UIManager.getInstance().getLookAndFeel().isRTL(); // really long string split it and draw multiple strings to avoid texture overload int one = 1; if(l % max == 0) { one = 0; } + if (rtl) { + x += stringWidth(fnt, str); + } int stringCount = l / max + one; for(int iter = 0 ; iter < stringCount ; iter++) { int pos = iter * max; String s = str.substring(pos, Math.min(pos + max, str.length())); - ng.nativeDrawString(ng.color, ng.alpha, fnt.peer, s, x, y); - x += stringWidth(fnt, s); + int substrWidth = stringWidth(fnt, s); + int rtlOffset = rtl ? -substrWidth : 0; + ng.nativeDrawString(ng.color, ng.alpha, fnt.peer, s, x + rtlOffset, y); + x += (rtl ? -substrWidth : substrWidth); } } else { ng.nativeDrawString(ng.color, ng.alpha, fnt.peer, str, x, y); diff --git a/Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java b/Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java index 737ac3cc48..010e59d315 100644 --- a/Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java +++ b/Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java @@ -719,5 +719,6 @@ native void nativeSetTransformMutable( native int getDisplaySafeInsetBottom(); + native boolean isRTLString(String javaString); } \ No newline at end of file