This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new c55ae88f4d TIKA-4902: fix locale issue (#3227)
c55ae88f4d is described below

commit c55ae88f4d69aa2dd8ad9c89f4280b8c6a1b0c0c
Author: Tim Allison <[email protected]>
AuthorDate: Thu Sep 24 06:30:12 2026 -0400

    TIKA-4902: fix locale issue (#3227)
---
 .../tika/parser/image/ImageMetadataExtractor.java  | 56 +++++++++++++++++++---
 .../image/ImageMetadataExtractorLocaleTest.java    | 41 ++++++++++++++++
 .../parser/image/ImageMetadataExtractorTest.java   | 23 ++++++++-
 3 files changed, 112 insertions(+), 8 deletions(-)

diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
index 6fa3299e3d..a23684d12b 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
@@ -384,12 +384,15 @@ public class ImageMetadataExtractor {
                     if (MetadataFields.isMetadataField(name)) {
                         continue;
                     }
+                    int iccType = directory instanceof IccDirectory ?
+                            iccTagDataType(directory, tag.getTagType()) : 0;
                     // decided before getDescription(): formatting a curve is 
the whole cost
-                    if (!includeIccCurvesAndLuts && directory instanceof 
IccDirectory &&
-                            isIccCurveOrLut(directory, tag.getTagType())) {
+                    if (!includeIccCurvesAndLuts && isIccCurveOrLut(iccType)) {
                         continue;
                     }
-                    String description = tag.getDescription();
+                    String description = iccType == ICC_TYPE_CURV ?
+                            
formatIccCurve(directory.getByteArray(tag.getTagType())) :
+                            tag.getDescription();
                     if (description != null) {
                         String value = description.trim();
                         if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
@@ -409,17 +412,20 @@ public class ImageMetadataExtractor {
             }
         }
 
-        private static boolean isIccCurveOrLut(Directory directory, int 
tagType) {
+        private static int iccTagDataType(Directory directory, int tagType) {
             // header fields sit below the printable-ASCII range of tag-table 
signatures
             if (tagType <= 0x20202020 || tagType >= 0x7a7a7a7a) {
-                return false;
+                return 0;
             }
             byte[] data = directory.getByteArray(tagType);
             if (data == null || data.length < 4) {
-                return false;
+                return 0;
             }
-            int type = ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
+            return ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
                     ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+        }
+
+        private static boolean isIccCurveOrLut(int type) {
             switch (type) {
                 case ICC_TYPE_CURV:
                 case ICC_TYPE_PARA:
@@ -432,6 +438,42 @@ public class ImageMetadataExtractor {
                     return false;
             }
         }
+
+        // metadata-extractor 2.21.0 formats curv in the default locale: "0," 
under de_DE
+        static String formatIccCurve(byte[] data) {
+            if (data.length < 12) {
+                return null;
+            }
+            ByteBuffer b = ByteBuffer.wrap(data);
+            long count = Integer.toUnsignedLong(b.getInt(8));
+            if (count > (data.length - 12) / 2) {
+                return null;
+            }
+            // per the ICC curveType: no entries is identity, one entry is a 
u8Fixed8 gamma
+            if (count == 0) {
+                return "1.0";
+            }
+            if (count == 1) {
+                return formatIccNumber((b.getShort(12) & 0xffff) / 256.0);
+            }
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < count; i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append(formatIccNumber((b.getShort(12 + i * 2) & 0xffff) / 
65535.0));
+            }
+            return sb.toString();
+        }
+
+        private static String formatIccNumber(double value) {
+            String v = String.format(Locale.ROOT, "%.7f", value);
+            int end = v.length();
+            while (end > v.indexOf('.') + 2 && v.charAt(end - 1) == '0') {
+                end--;
+            }
+            return v.substring(0, end);
+        }
     }
 
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorLocaleTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorLocaleTest.java
new file mode 100644
index 0000000000..a4e015cb25
--- /dev/null
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorLocaleTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.parser.image;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Locale;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Isolated;
+
+// sets the JVM-wide default locale, so it must not overlap other test classes
+@Isolated
+public class ImageMetadataExtractorLocaleTest {
+
+    @Test
+    public void testIccCurveFormattingIgnoresDefaultLocale() {
+        Locale defaultLocale = Locale.getDefault();
+        try {
+            Locale.setDefault(Locale.GERMANY);
+            assertEquals("0.0, 0.4999924, 1.0", 
ImageMetadataExtractor.CopyUnknownFieldsHandler
+                    .formatIccCurve(ImageMetadataExtractorTest.iccCurve(3)));
+        } finally {
+            Locale.setDefault(defaultLocale);
+        }
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
index 7bc751c9b5..9f8b0a3ec8 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
@@ -193,6 +193,18 @@ public class ImageMetadataExtractorTest {
         assertEquals("0.0, 1.0", metadata.get(ImageMetadataExtractor.ICC_NS + 
"Red TRC"));
     }
 
+    @Test
+    public void testIccCurveSpecialCounts() {
+        assertEquals("1.0", 
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(
+                iccCurveRaw(0)));
+        assertEquals("1.0", 
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(
+                iccCurveRaw(1, (short) 0x0100)));
+        assertEquals("2.1992188", 
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(
+                iccCurveRaw(1, (short) 0x0233)));
+        assertEquals("0.0, 0.4999924, 1.0",
+                
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(iccCurve(3)));
+    }
+
     //ICC 'desc' tag: type, reserved, byte count including the NUL, ASCII, NUL
     private static byte[] iccDesc(String text) {
         byte[] ascii = text.getBytes(StandardCharsets.US_ASCII);
@@ -203,7 +215,16 @@ public class ImageMetadataExtractorTest {
     }
 
     //ICC 'curv' tag: type, reserved, count, count uint16 samples spread over 
0..1
-    private static byte[] iccCurve(int count) {
+    static byte[] iccCurveRaw(int count, short... entries) {
+        ByteBuffer b = ByteBuffer.allocate(12 + 2 * entries.length);
+        
b.put("curv".getBytes(StandardCharsets.US_ASCII)).putInt(0).putInt(count);
+        for (short e : entries) {
+            b.putShort(e);
+        }
+        return b.array();
+    }
+
+    static byte[] iccCurve(int count) {
         ByteBuffer b = ByteBuffer.allocate(12 + 2 * count);
         
b.put("curv".getBytes(StandardCharsets.US_ASCII)).putInt(0).putInt(count);
         for (int i = 0; i < count; i++) {

Reply via email to