Repository: wangguanquan/eec
Pull request #329 (2023-01-29 07:59:44)

Fix#312 read theme color
Optimize build-in color and theme colors


Patch:
diff --git a/src/main/java/org/ttzero/excel/entity/style/Border.java b/src/main/java/org/ttzero/excel/entity/style/Border.java
index 56b51bd6..be1219de 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Border.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Border.java
@@ -38,7 +38,7 @@ import static org.ttzero.excel.entity.style.Styles.getAttr;
  * @see BorderStyle
  * @author guanquan.wang on 2018-02-06 08:55
  */
-public class Border {
+public class Border implements Cloneable {
 
     private static final Color defaultColor = new Color(51, 51, 51); // #333333
 
@@ -505,10 +505,14 @@ public class Border {
             element.addAttribute("style", subBorder.style.getName());
             Element colorEle = element.element("color");
             if (colorEle == null) colorEle = element.addElement("color");
-            int colorIndex;
-            if ((colorIndex = ColorIndex.indexOf(subBorder.color)) > -1) {
-                colorEle.addAttribute("indexed", String.valueOf(colorIndex));
-            } else {
+            int index;
+            if (subBorder.color instanceof BuildInColor) {
+                colorEle.addAttribute("indexed", String.valueOf(((BuildInColor) subBorder.color).getIndexed()));
+            }
+            else if ((index = ColorIndex.indexOf(subBorder.color)) > -1) {
+                element.addAttribute("indexed", String.valueOf(index));
+            }
+            else {
                 colorEle.addAttribute("rgb", ColorIndex.toARGB(subBorder.color));
             }
         }
@@ -524,4 +528,13 @@ public class Border {
             + (borders[5] != null ? borders[5].style : BorderStyle.NONE);
     }
 
+    @Override
+    public Border clone() {
+        Border newBorder = new Border();
+        for (int i = 0; i < borders.length; i++) {
+            if (borders[i] == null) continue;
+            newBorder.borders[i] = new SubBorder(borders[i].style, borders[i].color);
+        }
+        return newBorder;
+    }
 }
diff --git a/src/main/java/org/ttzero/excel/entity/style/BuildInColor.java b/src/main/java/org/ttzero/excel/entity/style/BuildInColor.java
new file mode 100644
index 00000000..5c8fd7d3
--- /dev/null
+++ b/src/main/java/org/ttzero/excel/entity/style/BuildInColor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2017-2023, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed 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.ttzero.excel.entity.style;
+
+import java.awt.Color;
+
+/**
+ * OpenXML build in color
+ *
+ * @author wangguanquan3 at 2023-01-18 16:51
+ */
+public class BuildInColor extends Color {
+    // OpenXML color index
+    private final int indexed;
+
+    public BuildInColor(int indexed) {
+        // Approximate processing, when indexed greater than 64 means automatic
+        super(indexed >=0 && indexed < 64 ? ColorIndex.colors[indexed] : 0);
+        this.indexed = indexed;
+    }
+
+    public int getIndexed() {
+        return indexed;
+    }
+
+//    @Override
+//    public int hashCode() {
+//        return indexed;
+//    }
+//
+//    @Override
+//    public boolean equals(Object o) {
+//        return o instanceof BuildInColor && ((BuildInColor) o).indexed == indexed;
+//    }
+
+    @Override
+    public String toString() {
+        return "Color indexed=\"" + indexed + "\"";
+    }
+}
diff --git a/src/main/java/org/ttzero/excel/entity/style/ColorIndex.java b/src/main/java/org/ttzero/excel/entity/style/ColorIndex.java
index 9d4521c3..4c1e9525 100644
--- a/src/main/java/org/ttzero/excel/entity/style/ColorIndex.java
+++ b/src/main/java/org/ttzero/excel/entity/style/ColorIndex.java
@@ -28,7 +28,7 @@ import java.awt.Color;
 public class ColorIndex {
     /** color cache */
     public static final int[] colors = {
-        0,         0,         0,         0,         0,         0,         0,         0,
+        -16777216, -1,        -65536,    -16711936, -16776961, -256,      -65281,    -16711681,
         -16777216, -1,        -65536,    -16711936, -16776961, -256,      -65281,    -16711681,
         -8388608,  -16744448, -16777088, -8355840,  -8388480,  -16744320, -4144960,  -8355712,
         -6710785,  -6737050,  -52,       -3342337,  -10092442, -32640,    -16750900, -3355393,
@@ -40,19 +40,29 @@ public class ColorIndex {
 
     /** Defined 12 base theme color */
     public static final Color[] themeColors = {
-            new Color(  0,   0,   0), new Color(255, 255, 255),
-            new Color( 31,  73, 125), new Color(238, 236, 225),
+            // lt1, dk1
+            new Color(255, 255, 255), new Color(  0,   0,   0),
+            // lt2, dk2
+            new Color(238, 236, 225), new Color( 31,  73, 125),
             new Color( 79, 129, 189), new Color(192,  80,  77),
             new Color(155, 187,  89), new Color(128, 100, 162),
             new Color( 75, 172, 198), new Color(247, 150,  70),
             new Color(  0,   0, 255), new Color(128,   0, 128)
     };
 
+    /**
+     * {@link BuildInColor}
+     */
+    @Deprecated
     public static int get(int index) {
         if (index < 0 || index >= colors.length) return 8;
         return colors[index];
     }
 
+    /**
+     * {@link BuildInColor}
+     */
+    @Deprecated
     public static Color getColor(int index) {
         return new Color(index < 0 || index >= colors.length ? colors[8] : colors[index]);
     }
@@ -62,8 +72,8 @@ public class ColorIndex {
     }
 
     public static int indexOf(int rgb) {
+        if (rgb >= 0) return -1; // alpha=0
         int i = 8;
-        if (rgb >= 0) return i;
         for ( ; i < colors.length; i++) {
             if (colors[i] == rgb) break;
         }
@@ -103,6 +113,8 @@ public class ColorIndex {
         // Remove the alpha option of the color
         if (rgb.length() == 8 && rgb.startsWith("FF")) {
             rgb = "#" + rgb.substring(2);
+        } else if (rgb.length() == 6 && rgb.charAt(0) != '#') {
+            rgb = "#" + rgb;
         }
         return Styles.toColor(rgb);
     }
diff --git a/src/main/java/org/ttzero/excel/entity/style/Fill.java b/src/main/java/org/ttzero/excel/entity/style/Fill.java
index 07ff8553..78e65f54 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Fill.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Fill.java
@@ -154,19 +154,26 @@ public class Fill implements Cloneable {
         Element element = root.addElement(StringUtil.lowFirstKey(getClass().getSimpleName()));
         Element patternFill = element.addElement("patternFill").addAttribute("patternType", patternType.name());
 
+        int index;
         if (fgColor != null) {
-            int colorIndex = ColorIndex.indexOf(fgColor);
-            if (colorIndex > -1) {
-                patternFill.addElement("fgColor").addAttribute("indexed", String.valueOf(colorIndex));
-            } else {
+            if (fgColor instanceof BuildInColor) {
+                patternFill.addElement("fgColor").addAttribute("indexed", String.valueOf(((BuildInColor) fgColor).getIndexed()));
+            }
+            else if ((index = ColorIndex.indexOf(fgColor)) > -1) {
+                patternFill.addElement("fgColor").addAttribute("indexed", String.valueOf(index));
+            }
+            else {
                 patternFill.addElement("fgColor").addAttribute("rgb", ColorIndex.toARGB(fgColor));
             }
         }
         if (bgColor != null) {
-            int colorIndex = ColorIndex.indexOf(bgColor);
-            if (colorIndex > -1) {
-                patternFill.addElement("bgColor").addAttribute("indexed", String.valueOf(colorIndex));
-            } else {
+            if (bgColor instanceof BuildInColor) {
+                patternFill.addElement("bgColor").addAttribute("indexed", String.valueOf(((BuildInColor) bgColor).getIndexed()));
+            }
+            else if ((index = ColorIndex.indexOf(bgColor)) > -1) {
+                patternFill.addElement("bgColor").addAttribute("indexed", String.valueOf(index));
+            }
+            else {
                 patternFill.addElement("bgColor").addAttribute("rgb", ColorIndex.toARGB(bgColor));
             }
         }
@@ -221,6 +228,24 @@ public class Fill implements Cloneable {
     }
 
     @Override public String toString() {
-        return patternType != null && patternType != PatternType.none ? fgColor + " " + patternType : PatternType.none.toString();
+        if (patternType == null || patternType == PatternType.none) return "<fill><patternFill patternType=\"none\"/></fill>";
+        StringBuilder buf = new StringBuilder("<fill><patternFill patternType=\"").append(patternType).append("\">");
+        int index;
+        if (fgColor != null) {
+            if (fgColor instanceof BuildInColor)
+                buf.append("<fgColor indexed=\"").append(((BuildInColor) fgColor).getIndexed()).append("\"/>");
+            else if ((index = ColorIndex.indexOf(fgColor)) > -1)
+                buf.append("<fgColor indexed=\"").append(index).append("\"/>");
+            else buf.append("<fgColor rgb=\"").append(ColorIndex.toARGB(fgColor)).append("\"/>");
+        }
+        if (bgColor != null) {
+            if (bgColor instanceof BuildInColor)
+                buf.append("<bgColor indexed=\"").append(((BuildInColor) bgColor).getIndexed()).append("\"/>");
+            else if ((index = ColorIndex.indexOf(bgColor)) > -1)
+                buf.append("<bgColor indexed=\"").append(index).append("\"/>");
+            else buf.append("<bgColor rgb=\"").append(ColorIndex.toARGB(bgColor)).append("\"/>");
+        }
+        buf.append("</patternFill></fill>");
+        return buf.toString();
     }
 }
diff --git a/src/main/java/org/ttzero/excel/entity/style/Font.java b/src/main/java/org/ttzero/excel/entity/style/Font.java
index 744f2450..a93f9e8e 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Font.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Font.java
@@ -16,7 +16,6 @@
 
 package org.ttzero.excel.entity.style;
 
-import org.ttzero.excel.manager.Const;
 import org.dom4j.Element;
 import org.ttzero.excel.util.StringUtil;
 
@@ -34,6 +33,7 @@ import static org.ttzero.excel.entity.style.Styles.getAttr;
  */
 public class Font implements Cloneable {
     private int style;
+    // this value is actually the result of the actual font size*10, eg: font-size:11 save as 110
     private int size;
     private String name;
     private Color color;
@@ -53,7 +53,22 @@ public class Font implements Cloneable {
 
     public Font(String name, int size, int style, Color color) {
         this.style = style;
-        this.size = size;
+        this.size = checkAndCrop(size * 10);
+        this.name = name;
+        this.color = color;
+    }
+
+    public Font(String name, double size) {
+        this(name, size, Style.NORMAL, null);
+    }
+
+    public Font(String name, double size, Color color) {
+        this(name, size, Style.NORMAL, color);
+    }
+
+    public Font(String name, double size, int style, Color color) {
+        this.style = style;
+        this.size = checkAndCrop(round10(size));
         this.name = name;
         this.color = color;
     }
@@ -96,10 +111,10 @@ public class Font implements Cloneable {
         boolean beforeSize = true;
         for (int i = 0; i < values.length; i++) {
             String temp = values[i].trim(), v;
-            Integer size = null;
+            Double size = null;
             if (beforeSize) {
                 try {
-                    size = Integer.valueOf(temp);
+                    size = Double.valueOf(temp);
                 } catch (NumberFormatException e) {
                     //
                 }
@@ -122,7 +137,7 @@ public class Font implements Cloneable {
                         throw new FontParseException("Property " + v + " not support.");
                     }
                 } else if (size > 0) {
-                    font.size = size;
+                    font.size = checkAndCrop(round10(size));
                     if (i + 1 < values.length) {
                         font.name = values[++i].trim().replace('+', ' ');
                     } else {
@@ -150,11 +165,20 @@ public class Font implements Cloneable {
     }
 
     public int getSize() {
-        return size;
+        return size / 10;
+    }
+
+    public double getSize2() {
+        return size / 10.0D;
     }
 
     public Font setSize(int size) {
-        this.size = size;
+        this.size = checkAndCrop(size * 10);
+        return this;
+    }
+
+    public Font setSize(double size) {
+        this.size = checkAndCrop(round10(size));
         return this;
     }
 
@@ -269,24 +293,27 @@ public class Font implements Cloneable {
     private static final String[] NODE_NAME = {"u", "b", "i", "strike"};
     @Override
     public String toString() {
-        StringBuilder buf = new StringBuilder("<font>").append(Const.lineSeparator);
+        StringBuilder buf = new StringBuilder("<font>");
         // Font style
         for (int n = style, i = 0; n > 0; n >>= 1, i++) {
-            if ((n & 1) == 1) buf.append("    <").append(NODE_NAME[i]).append("/>").append(Const.lineSeparator);
+            if ((n & 1) == 1) buf.append("<").append(NODE_NAME[i]).append("/>");
         }
         // size
-        buf.append("    <sz val=\"").append(size).append("\"/>").append(Const.lineSeparator);
+        buf.append("<sz val=\"");
+        if ((size & 1) == 0) buf.append(size / 10);
+        else buf.append(size / 10.0D);
+        buf.append("\"/>");
         // color
         if (color != null) {
             int index;
             if ((index = ColorIndex.indexOf(color.getRGB())) == -1) {
-                buf.append("    <color rgb=\"").append(ColorIndex.toARGB(color.getRGB())).append("\"/>").append(Const.lineSeparator);
+                buf.append("<color rgb=\"").append(ColorIndex.toARGB(color.getRGB())).append("\"/>");
             } else {
-                buf.append("    <color indexed=\"").append(index).append("\"/>").append(Const.lineSeparator);
+                buf.append("<color indexed=\"").append(index).append("\"/>");
             }
         }
         // name
-        buf.append("    <name val=\"").append(name).append("\"/>").append(Const.lineSeparator);
+        buf.append("<name val=\"").append(name).append("\"/>");
         // family
 //        DECORATIVE
 //        MODERN
@@ -297,10 +324,10 @@ public class Font implements Cloneable {
 
         // charset
         if (charset > 0) {
-            buf.append("    <charset val=\"").append(charset).append("\"/>").append(Const.lineSeparator);
+            buf.append("<charset val=\"").append(charset).append("\"/>");
         }
         if (StringUtil.isNotEmpty(scheme)) {
-            buf.append("    <scheme val=\"").append(scheme).append("\"/>").append(Const.lineSeparator);
+            buf.append("<scheme val=\"").append(scheme).append("\"/>");
         }
 
         return buf.append("</font>").toString();
@@ -332,13 +359,17 @@ public class Font implements Cloneable {
 
     public Element toDom4j(Element root) {
         Element element = root.addElement(StringUtil.lowFirstKey(getClass().getSimpleName()));
-        element.addElement("sz").addAttribute("val", String.valueOf(size));
+        element.addElement("sz").addAttribute("val", ((size & 1) == 0) ? String.valueOf(size / 10) : String.valueOf(size / 10.0D));
         element.addElement("name").addAttribute("val", name);
         if (color != null) {
             int index;
-            if ((index = ColorIndex.indexOf(color)) > -1) {
+            if (color instanceof BuildInColor) {
+                element.addElement("color").addAttribute("indexed", String.valueOf(((BuildInColor) color).getIndexed()));
+            }
+            else if ((index = ColorIndex.indexOf(color)) > -1) {
                 element.addElement("color").addAttribute("indexed", String.valueOf(index));
-            } else {
+            }
+            else {
                 element.addElement("color").addAttribute("rgb", ColorIndex.toARGB(color));
             }
         }
@@ -373,16 +404,16 @@ public class Font implements Cloneable {
         Font font = new Font();
         for (Element e : sub) {
             switch (e.getName()) {
-                case "sz"     : font.size = Integer.parseInt(getAttr(e, "val"));    break;
-                case "color"  : font.color = Styles.parseColor(e);                  break;
-                case "name"   : font.name = getAttr(e, "val");                      break;
-                case "charset": font.charset = Integer.parseInt(getAttr(e, "val")); break;
-                case "scheme" : font.scheme = getAttr(e, "val");                    break;
-                case "family" : font.family = Integer.parseInt(getAttr(e, "val"));  break;
-                case "u"      : font.style |= Style.UNDERLINE;                      break;
-                case "b"      : font.style |= Style.BOLD;                           break;
-                case "i"      : font.style |= Style.ITALIC;                         break;
-                case "strike" : font.style |= Style.DELETE;                         break;
+                case "sz"     : font.size = round10(Double.parseDouble(getAttr(e, "val"))); break;
+                case "color"  : font.color = Styles.parseColor(e);                          break;
+                case "name"   : font.name = getAttr(e, "val");                              break;
+                case "charset": font.charset = Integer.parseInt(getAttr(e, "val"));         break;
+                case "scheme" : font.scheme = getAttr(e, "val");                            break;
+                case "family" : font.family = Integer.parseInt(getAttr(e, "val"));          break;
+                case "u"      : font.style |= Style.UNDERLINE;                              break;
+                case "b"      : font.style |= Style.BOLD;                                   break;
+                case "i"      : font.style |= Style.ITALIC;                                 break;
+                case "strike" : font.style |= Style.DELETE;                                 break;
             }
         }
 
@@ -406,6 +437,22 @@ public class Font implements Cloneable {
         return other;
     }
 
+    public static int round10(double v) {
+        int i = (int) v;
+        double l = v - i;
+        if (l < 0.23D) i = i * 10;
+        else if (l < 0.73) i = i * 10 + 5;
+        else i = i * 10 + 10;
+        return i;
+    }
+
+    // Check and crop the font size
+    static int checkAndCrop(int size) {
+        if (size < 10) size = 10;
+        else if (size > 4090) size = 4090;
+        return size;
+    }
+
     // ######################################Static inner class######################################
 
     public static class Style {
diff --git a/src/main/java/org/ttzero/excel/entity/style/Styles.java b/src/main/java/org/ttzero/excel/entity/style/Styles.java
index 3bee52b1..e25375e5 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Styles.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Styles.java
@@ -22,7 +22,6 @@ import org.ttzero.excel.annotation.TopNS;
 import org.ttzero.excel.entity.I18N;
 import org.ttzero.excel.entity.Storable;
 import org.ttzero.excel.manager.Const;
-import org.ttzero.excel.reader.ExcelReadException;
 import org.ttzero.excel.util.FileUtil;
 import org.dom4j.Document;
 import org.dom4j.DocumentException;
@@ -217,10 +216,16 @@ public class Styles implements Storable {
         try {
             document = reader.read(Files.newInputStream(path));
         } catch (DocumentException | IOException e) {
-            throw new ExcelReadException(e);
+            LOGGER.warn("Read the style failed and ignore the style to continue.", e);
+            Styles self = forReader();
+            // Add a default font
+            self.addFont(new Font("Arial", 11, Color.black));
+            return self;
         }
 
         Styles self = new Styles();
+        Path themePath = path.getParent().resolve("theme/theme1.xml");
+        if (Files.exists(themePath) && !Files.isDirectory(themePath)) Theme.load(themePath);
         Element root = document.getRootElement();
 
         // Parse Number format
@@ -242,33 +247,28 @@ public class Styles implements Storable {
         for (Element e : sub) {
             int style = 0;
             // NumFmt
-            String applyNumberFormat = getAttr(e, "applyNumberFormat");
-            if ("1".equals(applyNumberFormat) || "true".equalsIgnoreCase(applyNumberFormat)) {
-                String numFmtId = getAttr(e, "numFmtId");
+            String numFmtId = getAttr(e, "numFmtId"); // applyNumberFormat = getAttr(e, "applyNumberFormat");
+            if (StringUtil.isNotEmpty(numFmtId) && !"0".equals(numFmtId)) {
                 style |= Integer.parseInt(numFmtId) << INDEX_NUMBER_FORMAT;
             }
             // Font
-            String applyFont = getAttr(e, "applyFont");
-            if ("1".equals(applyFont) || "true".equalsIgnoreCase(applyFont)) {
-                String fontId = getAttr(e, "fontId");
+            String fontId = getAttr(e, "fontId"); // applyFont = getAttr(e, "applyFont");
+            if (StringUtil.isNotEmpty(fontId) && !"0".equals(fontId)) {
                 style |= Integer.parseInt(fontId) << INDEX_FONT;
             }
             // Fill
-            String applyFill = getAttr(e, "applyFill");
-            if ("1".equals(applyFill) || "true".equalsIgnoreCase(applyFill)) {
-                String fillId = getAttr(e, "fillId");
+            String fillId = getAttr(e, "fillId"); // applyFill = getAttr(e, "applyFill");
+            if (StringUtil.isNotEmpty(fillId) && !"0".equals(fillId)) {
                 style |= Integer.parseInt(fillId) << INDEX_FILL;
             }
             // Border
-            String applyBorder = getAttr(e, "applyBorder");
-            if ("1".equals(applyBorder) || "true".equalsIgnoreCase(applyBorder)) {
-                String borderId = getAttr(e, "borderId");
+            String borderId = getAttr(e, "borderId"); // applyBorder = getAttr(e, "applyBorder");
+            if (StringUtil.isNotEmpty(borderId) && !"0".equals(borderId)) {
                 style |= Integer.parseInt(borderId) << INDEX_BORDER;
             }
             // Alignment
-            String applyAlignment = getAttr(e, "applyAlignment");
-            if ("1".equals(applyAlignment) || "true".equalsIgnoreCase(applyAlignment)) {
-                Element alignment = e.element("alignment");
+            Element alignment = e.element("alignment");
+            if (alignment != null) {
                 String horizontal = getAttr(alignment, "horizontal");
                 int index;
                 if (StringUtil.isNotEmpty(horizontal) && (index = StringUtil.indexOf(Horizontals._names, horizontal)) >= 0) {
@@ -704,11 +704,12 @@ public class Styles implements Storable {
         // Indexed color value. Only used for backwards compatibility.
         // References a color in indexedColors.
         else if (StringUtil.isNotEmpty(indexed)) {
-            c = ColorIndex.getColor(Integer.parseInt(indexed));
+            // if indexed greater than 64 means auto.
+            c = new BuildInColor(Integer.parseInt(indexed));
         }
         // A boolean value indicating the color is automatic and system color dependent.
         else if ("1".equals(auto) || "true".equalsIgnoreCase(auto)) {
-            c = ColorIndex.getColor(8);
+            c = new BuildInColor(64);
         }
         // Theme colors
         else if (StringUtil.isNotEmpty(theme)) {
@@ -720,7 +721,6 @@ public class Styles implements Storable {
                 LOGGER.warn("Unknown theme color index {}", t);
                 t = 0;
             }
-            // FIXME read theme.xml
             Color themeColor = ColorIndex.themeColors[t];
             String tint = getAttr(element, "tint");
             c = HlsColor.calculateColor(themeColor, tint);
diff --git a/src/main/java/org/ttzero/excel/entity/style/Theme.java b/src/main/java/org/ttzero/excel/entity/style/Theme.java
new file mode 100644
index 00000000..50b83130
--- /dev/null
+++ b/src/main/java/org/ttzero/excel/entity/style/Theme.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2017-2023, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed 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.ttzero.excel.entity.style;
+
+import org.dom4j.Document;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.ttzero.excel.annotation.TopNS;
+
+import java.awt.Color;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.ttzero.excel.entity.style.Styles.getAttr;
+import static org.ttzero.excel.util.ExtBufferedWriter.digits_uppercase;
+
+/**
+ * Theme style
+ * NOTE: Only read the theme color current
+ *
+ * @author wangguanquan3 at 2023-01-18 10:29
+ */
+@TopNS(prefix = "", uri = "http://schemas.openxmlformats.org/drawingml/2006/main", value = "a")
+public class Theme {
+    /**
+     * LOGGER
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Theme.class);
+    // theme colors
+    private ClrScheme[] clrs;
+
+    public static Theme load(Path path) {
+        Theme self = new Theme();
+        // load theme1.xml
+        SAXReader reader = new SAXReader();
+        Document document;
+        try {
+            document = reader.read(Files.newInputStream(path));
+            Element root = document.getRootElement();
+            Element clrScheme = root.element("themeElements").element("clrScheme");
+            List<Element> clrSchemes;
+            if (clrScheme != null && (clrSchemes = clrScheme.elements()) != null && !clrSchemes.isEmpty()) {
+                ClrScheme[] clrs = new ClrScheme[clrSchemes.size()];
+                self.clrs = clrs;
+                int i = 0;
+                for (Element e : clrSchemes) self.clrs[i++] = toClrScheme(e);
+
+                // Adjust color index (lt1 > dk1 > lt2 > dk2)
+                if (clrs.length >= 2 && "dk1".equals(clrs[0].tag) && "lt1".equals(clrs[1].tag)) {
+                    ClrScheme tmp = clrs[1];
+                    clrs[1] = clrs[0];
+                    clrs[0] = tmp;
+                }
+                if (clrs.length >= 4 && "dk2".equals(clrs[2].tag) && "lt2".equals(clrs[3].tag)) {
+                    ClrScheme tmp = clrs[3];
+                    clrs[3] = clrs[2];
+                    clrs[2] = tmp;
+                }
+
+                // FIXME Temporary processing
+                int len = Math.min(clrs.length, ColorIndex.themeColors.length);
+                i = 0;
+                for (; i < len; i++) ColorIndex.themeColors[i] = clrs[i].color;
+            }
+
+            // TODO others
+
+        } catch (Exception e) {
+            LOGGER.warn("Read the theme failed and ignore the style to continue.", e);
+            // Ignore
+        }
+        return self;
+    }
+
+    static ClrScheme toClrScheme(Element e) {
+        ClrScheme c = new ClrScheme();
+        c.tag = e.getName();
+        List<Element> subs = e.elements();
+        Color color = null;
+        if (subs != null && !subs.isEmpty()) {
+            Element sub = subs.get(0);
+            String v = getAttr(sub, "lastClr");
+            if (v == null) v = getAttr(sub, "val");
+            else {
+                try {
+                    color = ColorIndex.toColor(v);
+                } catch (Exception ex) {
+                    v = getAttr(sub, "val");
+                }
+            }
+            if (color == null) {
+                try {
+                    color = ColorIndex.toColor(v);
+                } catch (Exception ex) {
+                    color = new BuildInColor(64); // auto if exception
+                }
+            }
+        } else color = new BuildInColor(64); // auto if unknown tag
+        c.color = color;
+        return c;
+    }
+
+    public Color[] getClrSchemes() {
+        if (clrs != null && clrs.length > 0) {
+            Color[] colors = new Color[clrs.length];
+            for (int i = 0; i < clrs.length; i++) colors[i] = clrs[i].color;
+            return colors;
+        } else return ColorIndex.themeColors;
+    }
+
+    public static class ClrScheme {
+        public String tag;
+        public Color color;
+
+        public ClrScheme() { }
+
+        public ClrScheme(String tag, Color color) {
+            this.tag = tag;
+            this.color = color;
+        }
+
+        public String toString() {
+            if (color == null) return tag;
+            int r = color.getRed(), g = color.getGreen(), b = color.getBlue();
+            return tag + ": " + new String(new char[] {
+                digits_uppercase[r >> 4], digits_uppercase[r & 0xF],
+                digits_uppercase[g >> 4], digits_uppercase[g & 0xF],
+                digits_uppercase[b >> 4], digits_uppercase[b & 0xF]
+            });
+        }
+    }
+}
diff --git a/src/main/java/org/ttzero/excel/util/ExtBufferedWriter.java b/src/main/java/org/ttzero/excel/util/ExtBufferedWriter.java
index d07af29a..aea940e4 100644
--- a/src/main/java/org/ttzero/excel/util/ExtBufferedWriter.java
+++ b/src/main/java/org/ttzero/excel/util/ExtBufferedWriter.java
@@ -153,7 +153,7 @@ public class ExtBufferedWriter extends BufferedWriter {
         write(Double.toString(d));
     }
 
-    private char[] toChars(int i) {
+    public char[] toChars(int i) {
         if (i == Integer.MIN_VALUE)
             return MIN_INTEGER_CHARS;
         int size = stringSize(i);
@@ -212,7 +212,7 @@ public class ExtBufferedWriter extends BufferedWriter {
         }
     }
 
-    private char[] toChars(long i) {
+    public char[] toChars(long i) {
         if (i == Long.MIN_VALUE)
             return MIN_LONG_CHARS;
         int size = stringSize(i);
@@ -293,7 +293,7 @@ public class ExtBufferedWriter extends BufferedWriter {
         }
     }
 
-    private final static char[] digitTens = {
+    public final static char[] digitTens = {
         '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
         '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
         '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
@@ -306,7 +306,7 @@ public class ExtBufferedWriter extends BufferedWriter {
         '9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
     };
 
-    private final static char[] digitOnes = {
+    public final static char[] digitOnes = {
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
@@ -319,7 +319,7 @@ public class ExtBufferedWriter extends BufferedWriter {
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
     };
 
-    private final static char[] digits = {
+    public final static char[] digits = {
         '0', '1', '2', '3', '4', '5',
         '6', '7', '8', '9', 'a', 'b',
         'c', 'd', 'e', 'f', 'g', 'h',
@@ -328,4 +328,13 @@ public class ExtBufferedWriter extends BufferedWriter {
         'u', 'v', 'w', 'x', 'y', 'z'
     };
 
+    public final static char[] digits_uppercase = {
+        '0', '1', '2', '3', '4', '5',
+        '6', '7', '8', '9', 'A', 'B',
+        'C', 'D', 'E', 'F', 'G', 'H',
+        'I', 'J', 'K', 'L', 'M', 'N',
+        'O', 'P', 'Q', 'R', 'S', 'T',
+        'U', 'V', 'W', 'X', 'Y', 'Z'
+    };
+
 }


Test patch:
diff --git a/src/test/java/org/ttzero/excel/entity/style/StylesTest.java b/src/test/java/org/ttzero/excel/entity/style/StylesTest.java
index 9184e05d..424b0832 100644
--- a/src/test/java/org/ttzero/excel/entity/style/StylesTest.java
+++ b/src/test/java/org/ttzero/excel/entity/style/StylesTest.java
@@ -24,7 +24,6 @@ import java.awt.Color;
 import java.io.IOException;
 import java.nio.file.Path;
 
-import static org.ttzero.excel.Print.println;
 import static org.ttzero.excel.entity.WorkbookTest.getOutputTestPath;
 import static org.ttzero.excel.entity.style.Styles.INDEX_BORDER;
 import static org.ttzero.excel.entity.style.Styles.INDEX_FILL;
@@ -189,4 +188,24 @@ public class StylesTest {
         assert color4.getGreen() <= 103 && color4.getGreen() >= 101;
         assert color4.getBlue() <= 103 && color4.getBlue() >= 101;
     }
+
+    @Test public void testRound2() {
+        assert Font.round10(11) == 110;
+        assert Font.round10(11.1) == 110;
+        assert Font.round10(11.2) == 110;
+        assert Font.round10(11.22) == 110;
+        assert Font.round10(11.23) == 115;
+        assert Font.round10(11.3) == 115;
+        assert Font.round10(11.5) == 115;
+        assert Font.round10(11.7) == 115;
+        assert Font.round10(11.72) == 115;
+        assert Font.round10(11.73) == 120;
+        assert Font.round10(11.8) == 120;
+
+        Font font = Font.parse("italic_bold_12.24_宋体");
+        assert font.isItalic();
+        assert font.isBold();
+        assert font.getSize2() == 12.5D;
+        assert font.getName().equals("宋体");
+    }
 }