Repository: wangguanquan/eec
Pull request #316 (2022-12-09 13:11:52)

Fix#312 Read cell styles


Patch:
diff --git a/src/main/java/org/ttzero/excel/entity/ICellValueAndStyle.java b/src/main/java/org/ttzero/excel/entity/ICellValueAndStyle.java
index 264f443b..654176e7 100644
--- a/src/main/java/org/ttzero/excel/entity/ICellValueAndStyle.java
+++ b/src/main/java/org/ttzero/excel/entity/ICellValueAndStyle.java
@@ -96,11 +96,13 @@ public interface ICellValueAndStyle {
             conversion(row, cell, e, hc);
             return;
         }
-
         if (e == null) {
             setNullValue(row, cell, hc);
             return;
         }
+        if (clazz == null) {
+            clazz = e.getClass();
+        }
         if (isString(clazz)) {
             cell.setSv(e.toString());
         } else if (isDate(clazz)) {
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 f2235d0a..2e8a0109 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Border.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Border.java
@@ -21,6 +21,11 @@ import org.dom4j.Element;
 
 import java.awt.Color;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.ttzero.excel.entity.style.Styles.getAttr;
 
 /**
  * Border line styles and colors
@@ -249,6 +254,78 @@ public class Border {
         return this;
     }
 
+    /**
+     * Returns top line style
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getBorderTop() {
+        return borders[2];
+    }
+
+    /**
+     * Returns right line style
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getBorderRight() {
+        return borders[1];
+    }
+
+    /**
+     * Returns bottom line style
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getBorderBottom() {
+        return borders[3];
+    }
+
+    /**
+     * Returns left line style
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getBorderLeft() {
+        return borders[0];
+    }
+
+    /**
+     * Returns Diagonal-Down line style
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getDiagonalDown() {
+        return borders[4];
+    }
+
+    /**
+     * Returns Diagonal-Up line style
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getDiagonalUp() {
+        return borders[5];
+    }
+
+    /**
+     * Returns the specify axis border, the order is left-right-top-bottom-diagonalDown-diagonalUp
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder getBorder(int axis) {
+        return axis >= 0 && axis < borders.length ? borders[axis] : null;
+    }
+
+    /**
+     * Returns all borders
+     *
+     * @return {@link SubBorder}
+     */
+    public SubBorder[] getBorders() {
+        return borders;
+    }
+
     Border delBorder(int index) {
         borders[index] = null;
         return this;
@@ -260,7 +337,7 @@ public class Border {
             , up = borders[5] != null ? 2 : 0;
         int hash = down | up;
         for (SubBorder sub : borders) {
-            hash += sub.hashCode();
+            hash += sub != null ? sub.hashCode() : 0;
         }
         return hash;
     }
@@ -339,18 +416,26 @@ public class Border {
         return border;
     }
 
-    private static class SubBorder {
-        private final BorderStyle style;
-        private final Color color;
+    public static class SubBorder {
+        public final BorderStyle style;
+        public final Color color;
 
         public SubBorder(BorderStyle style, Color color) {
             this.style = style;
             this.color = color;
         }
 
+        public BorderStyle getStyle() {
+            return style;
+        }
+
+        public Color getColor() {
+            return color;
+        }
+
         @Override
         public int hashCode() {
-            int hash = color.hashCode();
+            int hash = color != null ? color.hashCode() : 0;
             return (style.ordinal() << 24) | (hash << 8 >>> 8);
         }
 
@@ -380,6 +465,40 @@ public class Border {
         return element;
     }
 
+    public static List<Border> domToBorder(Element root) {
+        // Borders tags
+        Element ele = root.element("borders");
+        // Break if there don't contains 'borders' tag
+        if (ele == null) {
+            return new ArrayList<>();
+        }
+        return ele.elements().stream().map(Border::parseBorderTag).collect(Collectors.toList());
+    }
+
+    static Border parseBorderTag(Element tag) {
+        List<Element> sub = tag.elements();
+        // Diagonal attr
+        String diagonalDown = getAttr(tag, "diagonalDown");
+        int padding = ("1".equals(diagonalDown) || "true".equalsIgnoreCase(diagonalDown)) ? 1 : 0;
+        String diagonalUp = getAttr(tag, "diagonalUp");
+        padding |= ("1".equals(diagonalUp) || "true".equalsIgnoreCase(diagonalUp)) ? 1 << 1 : 0;
+
+        Border border = new Border();
+        for (Element e : sub) {
+            int i = StringUtil.indexOf(direction, e.getName());
+            // unknown element
+            if (i < 0) continue;
+            BorderStyle style = BorderStyle.getByName(getAttr(e, "style"));
+            if (style == null) style = BorderStyle.NONE;
+            Color color = Styles.parseColor(e.element("color"));
+            if (i < 4) border.setBorder(i, style, color);
+            else if ((padding & 1) == 1) border.setBorder(4, style, color);
+            else if ((padding & 2) == 2) border.setBorder(5, style, color);
+        }
+
+        return border;
+    }
+
     protected void writeProperties(Element element, SubBorder subBorder) {
         if (subBorder != null && subBorder.style != BorderStyle.NONE) {
             element.addAttribute("style", subBorder.style.getName());
@@ -394,4 +513,14 @@ public class Border {
         }
     }
 
+    @Override
+    public String toString() {
+        return (borders[0] != null ? borders[0].style : BorderStyle.NONE) + " "
+            + (borders[1] != null ? borders[1].style : BorderStyle.NONE) + " "
+            + (borders[2] != null ? borders[2].style : BorderStyle.NONE) + " "
+            + (borders[3] != null ? borders[3].style : BorderStyle.NONE) + " "
+            + (borders[4] != null ? borders[4].style : BorderStyle.NONE) + " "
+            + (borders[5] != null ? borders[5].style : BorderStyle.NONE);
+    }
+
 }
diff --git a/src/main/java/org/ttzero/excel/entity/style/BorderStyle.java b/src/main/java/org/ttzero/excel/entity/style/BorderStyle.java
index aa6f3ff9..94d7fd52 100644
--- a/src/main/java/org/ttzero/excel/entity/style/BorderStyle.java
+++ b/src/main/java/org/ttzero/excel/entity/style/BorderStyle.java
@@ -50,7 +50,7 @@ public enum BorderStyle {
     public static BorderStyle getByName(String name) {
         BorderStyle[] borderStyles = values();
         for (BorderStyle borderStyle : borderStyles) {
-            if (borderStyle.name.equals(name)) {
+            if (borderStyle.name.equalsIgnoreCase(name)) {
                 return borderStyle;
             }
         }
diff --git a/src/main/java/org/ttzero/excel/entity/style/BuiltInNumFmt.java b/src/main/java/org/ttzero/excel/entity/style/BuiltInNumFmt.java
index eadd5c16..d5e3443b 100644
--- a/src/main/java/org/ttzero/excel/entity/style/BuiltInNumFmt.java
+++ b/src/main/java/org/ttzero/excel/entity/style/BuiltInNumFmt.java
@@ -149,7 +149,16 @@ public final class BuiltInNumFmt {
      * @return the {@link NumFmt}
      */
     public static NumFmt get(int id) {
-        return id >=0 && id < idData.length ? idData[id] : null;
+        NumFmt fmt = null;
+        if (id >= 0) {
+            if (id < idData.length) {
+                fmt = idData[id];
+            } else if (id < 176) {
+                fmt = new NumFmt();
+                fmt.id = id;
+            }
+        }
+        return fmt;
     }
 
     public static class NumFmt extends org.ttzero.excel.entity.style.NumFmt {
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 32b38d39..9d4521c3 100644
--- a/src/main/java/org/ttzero/excel/entity/style/ColorIndex.java
+++ b/src/main/java/org/ttzero/excel/entity/style/ColorIndex.java
@@ -27,7 +27,7 @@ import java.awt.Color;
  */
 public class ColorIndex {
     /** color cache */
-    private static final int[] colors = {
+    public static final int[] colors = {
         0,         0,         0,         0,         0,         0,         0,         0,
         -16777216, -1,        -65536,    -16711936, -16776961, -256,      -65281,    -16711681,
         -8388608,  -16744448, -16777088, -8355840,  -8388480,  -16744320, -4144960,  -8355712,
@@ -38,11 +38,25 @@ public class ColorIndex {
         -16764058, -13395610, -16764160, -13421824, -6737152,  -6737050,  -13421671, -13421773
     };
 
+    /** 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),
+            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)
+    };
+
     public static int get(int index) {
         if (index < 0 || index >= colors.length) return 8;
         return colors[index];
     }
 
+    public static Color getColor(int index) {
+        return new Color(index < 0 || index >= colors.length ? colors[8] : colors[index]);
+    }
+
     public static int indexOf(Color color) {
         return indexOf(color.getRGB());
     }
@@ -84,4 +98,12 @@ public class ColorIndex {
         }
         return new String(chars);
     }
+
+    public static Color toColor(String rgb) {
+        // Remove the alpha option of the color
+        if (rgb.length() == 8 && rgb.startsWith("FF")) {
+            rgb = "#" + rgb.substring(2);
+        }
+        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 5ba3836b..07ff8553 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Fill.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Fill.java
@@ -21,8 +21,12 @@ import org.dom4j.Element;
 import org.ttzero.excel.util.StringUtil;
 
 import java.awt.Color;
-import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.ttzero.excel.entity.style.Styles.getAttr;
 
 /**
  * @author guanquan.wang at 2018-02-06 08:55
@@ -170,6 +174,35 @@ public class Fill implements Cloneable {
         return element;
     }
 
+    public static List<Fill> domToFill(Element root) {
+        // Fills tags
+        Element ele = root.element("fills");
+        // Break if there don't contains 'fills' tag
+        if (ele == null) {
+            return new ArrayList<>();
+        }
+        return ele.elements().stream().map(Fill::parseFillTag).collect(Collectors.toList());
+    }
+
+    static Fill parseFillTag(Element tag) {
+        Element e = tag.element("patternFill");
+        Fill fill = new Fill();
+        try {
+            fill.patternType = PatternType.valueOf(getAttr(e, "patternType"));
+        } catch (IllegalArgumentException ex) {
+            // Ignore
+        }
+        Element fgColor = e.element("fgColor");
+        if (fgColor != null) {
+            fill.fgColor = Styles.parseColor(fgColor);
+        }
+        Element bgColor = e.element("bgColor");
+        if (bgColor != null) {
+            fill.bgColor = Styles.parseColor(bgColor);
+        }
+        return fill;
+    }
+
     @Override public Fill clone() {
         Fill other;
         try {
@@ -186,4 +219,8 @@ public class Fill implements Cloneable {
         }
         return other;
     }
+
+    @Override public String toString() {
+        return patternType != null && patternType != PatternType.none ? fgColor + " " + patternType : PatternType.none.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 b7ee86de..744f2450 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Font.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Font.java
@@ -22,7 +22,12 @@ import org.ttzero.excel.util.StringUtil;
 
 import java.awt.Color;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.ttzero.excel.entity.style.Styles.getAttr;
 
 /**
  * @author guanquan.wang at 2018-02-02 16:51
@@ -307,7 +312,7 @@ public class Font implements Cloneable {
         hash = style << 24;
         hash += size << 16;
         hash += name.hashCode() << 8;
-        hash += color.hashCode();
+        hash += color != null ? color.hashCode() : 0;
         return hash;
     }
 
@@ -353,6 +358,37 @@ public class Font implements Cloneable {
         return element;
     }
 
+    public static List<Font> domToFont(Element root) {
+        // Fonts tags
+        Element ele = root.element("fonts");
+        // Break if there don't contains 'fonts' tag
+        if (ele == null) {
+            return new ArrayList<>();
+        }
+        return ele.elements().stream().map(Font::parseFontTag).collect(Collectors.toList());
+    }
+
+    static Font parseFontTag(Element tag) {
+        List<Element> sub = tag.elements();
+        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;
+            }
+        }
+
+        return font;
+    }
+
     @Override public Font clone() {
         Font other;
         try {
diff --git a/src/main/java/org/ttzero/excel/entity/style/HlsColor.java b/src/main/java/org/ttzero/excel/entity/style/HlsColor.java
new file mode 100644
index 00000000..40c1a676
--- /dev/null
+++ b/src/main/java/org/ttzero/excel/entity/style/HlsColor.java
@@ -0,0 +1,160 @@
+/*
+ * 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.ttzero.excel.util.StringUtil;
+
+import java.awt.Color;
+
+/**
+ * Hue-Luminance-Saturation Color Util (OpenXML theme color)
+ *
+ * @author guanquan.wang at 2023-01-02 15:59
+ */
+public class HlsColor {
+    /**
+     * Alpha
+     */
+    public float a;
+    /**
+     * Hue
+     */
+    public float h;
+    /**
+     * Luminance
+     */
+    public float l;
+    /**
+     * Saturation
+     */
+    public float s;
+
+    /**
+     * Convert rgba color to hls Color
+     *
+     * @param rgbColor rgb Color
+     * @return hlsColor
+     */
+    public static HlsColor rgbToHls(Color rgbColor) {
+        HlsColor hlsColor = new HlsColor();
+        float r = rgbColor.getRed() / 255.0F, g = rgbColor.getGreen() / 255.0F
+            , b = rgbColor.getBlue() / 255.0F, a = rgbColor.getAlpha() / 255.0F;
+        float min = Math.min(r, Math.min(g, b));
+        float max = Math.max(r, Math.max(g, b));
+        float delta = max - min;
+        if (max == min) {
+            hlsColor.h = 0F;
+            hlsColor.s = 0F;
+            hlsColor.l = max;
+            return hlsColor;
+        }
+        hlsColor.l = (min + max) / 2.0F;
+        if (hlsColor.l < 0.5F) hlsColor.s = delta / (max + min);
+        else hlsColor.s = delta / (2.0F - max - min);
+        if (r == max) hlsColor.h = (g - b) / delta;
+        if (g == max) hlsColor.h = 2.0F + (b - r) / delta;
+        if (b == max) hlsColor.h = 4.0F + (r - g) / delta;
+        hlsColor.h *= 60F;
+        if (hlsColor.h < 0F) hlsColor.h += 360F;
+        hlsColor.a = a;
+        return hlsColor;
+    }
+
+    /**
+     * Convert hls color to rgba
+     *
+     * @param hlsColor hls color
+     * @return rgbColor
+     */
+    public static Color hlsToRgb(HlsColor hlsColor) {
+        Color rgbColor;
+        if (hlsColor.s == 0) {
+            rgbColor = new Color(hlsColor.l, hlsColor.l, hlsColor.l, hlsColor.a);
+            return rgbColor;
+        }
+        float t1;
+        if (hlsColor.l < 0.5F) t1 = hlsColor.l * (1.0F + hlsColor.s);
+        else t1 = hlsColor.l + hlsColor.s - (hlsColor.l * hlsColor.s);
+        float t2 = 2.0F * hlsColor.l - t1;
+        float h = hlsColor.h / 360F;
+        float tR = h + (1.0F / 3.0F);
+        float r = hueToRGB(t1, t2, tR);
+        float tG = h;
+        float g = hueToRGB(t1, t2, tG);
+        float tB = h - (1.0F / 3.0F);
+        float b = hueToRGB(t1, t2, tB);
+//        rgbColor = new Color((int) (r * 255), (int) (g * 255), (int) (b * 255), (int) (hlsColor.a * 255));
+        rgbColor = new Color(r, g, b, hlsColor.a);
+        return rgbColor;
+    }
+
+    private static float hueToRGB(float t1, float t2, float t3) {
+        if (t3 < 0) t3 += 1.0F;
+        if (t3 > 1) t3 -= 1.0F;
+        float color;
+        if (6.0F * t3 < 1) color = t2 + (t1 - t2) * 6.0F * t3;
+        else if (2.0F * t3 < 1) color = t1;
+        else if (3.0F * t3 < 2) color = t2 + (t1 - t2) * ((2.0F / 3.0F) - t3) * 6.0F;
+        else color = t2;
+        return color;
+    }
+
+    /**
+     * If tint is supplied, then it is applied to the value of the color to determine the final color applied.
+     * <p>
+     * The tint value is stored as a double from {@code -1.0 .. 1.0}, where {@code -1.0} means {@code 100%} darken
+     * and {@code 1.0} means {@code 100%} lighten. Also, {@code 0.0} means no change.
+     * <p>
+     * In loading the value, it is converted to HLS where HLS values are (0..HLSMAX), where HLSMAX is currently 255.
+     * <p>
+     * Referer: <a href="https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.color?view=openxml-2.8.1">Tint</a>
+     *
+     * @param tint Specifies the tint value applied to the color.
+     * @param lum Luminance part
+     * @return calculate tint Luminance
+     */
+    public static float calculateFinalLumValue(Double tint, float lum) {
+        if (tint == null) return lum;
+        if (tint < 0) lum = (float) (lum * (1 + tint));
+        else lum = (float) (lum * (1 - tint) + (255 - 255 * (1 - tint)));
+        return lum;
+    }
+
+    /**
+     * Convert RGB to HSL and then adjust the luminance part
+     *
+     * @param theme rgb color
+     * @param tintV tint a double from {@code -1.0 .. 1.0}
+     * @return rgb color
+     */
+    public static Color calculateColor(Color theme, String tintV) {
+        Double tint = null;
+        if (StringUtil.isNotEmpty(tintV)) {
+            try {
+                tint = Double.parseDouble(tintV);
+            } catch (NumberFormatException ex) {
+                // Ignore
+            }
+        }
+
+        // Theme value range 0 ... 9
+        HlsColor hls = rgbToHls(theme);
+        hls.l = calculateFinalLumValue(tint, hls.l * 255) / 255;
+        return hlsToRgb(hls);
+    }
+}
diff --git a/src/main/java/org/ttzero/excel/entity/style/Horizontals.java b/src/main/java/org/ttzero/excel/entity/style/Horizontals.java
index b5c8f6a9..c0c40509 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Horizontals.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Horizontals.java
@@ -32,7 +32,7 @@ public class Horizontals {
         , DISTRIBUTED = 7 << Styles.INDEX_HORIZONTAL // Distributed Horizontal Alignment
         ;
 
-    private static final String[] _names = {
+    public static final String[] _names = {
             "general"
             ,"left"
             ,"right"
diff --git a/src/main/java/org/ttzero/excel/entity/style/NumFmt.java b/src/main/java/org/ttzero/excel/entity/style/NumFmt.java
index 349fd101..88501d81 100644
--- a/src/main/java/org/ttzero/excel/entity/style/NumFmt.java
+++ b/src/main/java/org/ttzero/excel/entity/style/NumFmt.java
@@ -19,8 +19,13 @@ package org.ttzero.excel.entity.style;
 import org.dom4j.Element;
 import org.ttzero.excel.util.StringUtil;
 
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 
+import static org.ttzero.excel.entity.style.Styles.getAttr;
+
 /**
  * To create a custom number format, you start by selecting one of the built-in number formats as a starting point.
  * You can then change any one of the code sections of that format to create your own custom number format.
@@ -222,6 +227,24 @@ public class NumFmt implements Comparable<NumFmt> {
             .addAttribute("numFmtId", String.valueOf(id));
     }
 
+    public static List<NumFmt> domToNumFmt(Element root) {
+        // Number format
+        Element ele = root.element("numFmts");
+        // Break if there don't contains 'numFmts' tag
+        if (ele == null) {
+            return new ArrayList<>();
+        }
+        List<Element> sub = ele.elements();
+        List<NumFmt> numFmts = new ArrayList<>(sub.size());
+        for (Element e : sub) {
+            String id = getAttr(e, "numFmtId"), code = getAttr(e, "formatCode");
+            numFmts.add(new NumFmt(Integer.parseInt(id), code));
+        }
+        // Sort by id
+        numFmts.sort(Comparator.comparingInt(NumFmt::getId));
+        return numFmts;
+    }
+
     @Override
     public int compareTo(NumFmt o) {
         return id - o.id;
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 b3564e8a..80d9b7f9 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Styles.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Styles.java
@@ -16,6 +16,8 @@
 
 package org.ttzero.excel.entity.style;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.ttzero.excel.annotation.TopNS;
 import org.ttzero.excel.entity.I18N;
 import org.ttzero.excel.entity.Storable;
@@ -27,6 +29,7 @@ import org.dom4j.DocumentException;
 import org.dom4j.DocumentFactory;
 import org.dom4j.Element;
 import org.dom4j.io.SAXReader;
+import org.ttzero.excel.util.StringUtil;
 
 import java.awt.Color;
 import java.io.IOException;
@@ -36,7 +39,6 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -70,7 +72,10 @@ import static org.ttzero.excel.util.StringUtil.isNotEmpty;
  */
 @TopNS(prefix = "", uri = Const.SCHEMA_MAIN, value = "styleSheet")
 public class Styles implements Storable {
-
+    /**
+     * LOGGER
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(Styles.class);
     private final Map<Integer, Integer> map;
     private final AtomicInteger counter;
     private int[] styleIndex;
@@ -152,41 +157,7 @@ public class Styles implements Storable {
     public static Styles create(I18N i18N) {
         Styles self = new Styles();
 
-        DocumentFactory factory = DocumentFactory.getInstance();
-        TopNS ns = self.getClass().getAnnotation(TopNS.class);
-        Element rootElement;
-        if (ns != null) {
-            rootElement = factory.createElement(ns.value(), ns.uri()[0]);
-        } else {
-            rootElement = factory.createElement("styleSheet", Const.SCHEMA_MAIN);
-        }
-        // number format
-        rootElement.addElement("numFmts").addAttribute("count", "0");
-        // font
-        rootElement.addElement("fonts").addAttribute("count", "0");
-        // fill
-        rootElement.addElement("fills").addAttribute("count", "0");
-        // border
-        rootElement.addElement("borders").addAttribute("count", "0");
-        // cellStyleXfs
-        Element cellStyleXfs = rootElement.addElement("cellStyleXfs").addAttribute("count", "1");
-        cellStyleXfs.addElement("xf")   // General style
-            .addAttribute("borderId", "0")
-            .addAttribute("fillId", "0")
-            .addAttribute("fontId", "0")
-            .addAttribute("numFmtId", "0")
-            .addElement("alignment")
-            .addAttribute("vertical", "center");
-        // cellXfs
-        rootElement.addElement("cellXfs").addAttribute("count", "0");
-        // cellStyles
-        Element cellStyles = rootElement.addElement("cellStyles").addAttribute("count", "1");
-        cellStyles.addElement("cellStyle")
-            .addAttribute("builtinId", "0")
-            .addAttribute("name", i18N.get("general"))
-            .addAttribute("xfId", "0");
-
-        self.document = factory.createDocument(rootElement);
+        self.document = createDocument();
 
         self.numFmts = new ArrayList<>();
 
@@ -225,7 +196,12 @@ public class Styles implements Storable {
      * @return Styles
      */
     public static Styles forReader() {
-        return new Styles();
+        Styles styles = new Styles();
+        styles.numFmts = new ArrayList<>();
+        styles.fonts = new ArrayList<>();
+        styles.fills = new ArrayList<>();
+        styles.borders = new ArrayList<>();
+        return styles;
     }
 
     /**
@@ -235,7 +211,7 @@ public class Styles implements Storable {
      * @return the {@link Styles} Object
      */
     public static Styles load(Path path) {
-        // load workbook.xml
+        // load styles.xml
         SAXReader reader = new SAXReader();
         Document document;
         try {
@@ -246,41 +222,75 @@ public class Styles implements Storable {
 
         Styles self = new Styles();
         Element root = document.getRootElement();
-        // Number format
-        Element numFmts = root.element("numFmts");
-        // Break if there don't contains 'numFmts' tag
-        if (numFmts == null) {
-            return self;
-        }
-        List<Element> sub = numFmts.elements();
-        self.numFmts = new ArrayList<>();
-        for (Element e : sub) {
-            String id = getAttr(e, "numFmtId"), code = getAttr(e, "formatCode");
-            self.numFmts.add(new NumFmt(Integer.parseInt(id), code));
-        }
-        // Sort by id
-        self.numFmts.sort(Comparator.comparingInt(NumFmt::getId));
 
-        /*
-        Ignore other styles, there only parse number format
-        It's use for Excel reader
-         */
+        // Parse Number format
+        self.numFmts = NumFmt.domToNumFmt(root);
+
+        // Parse Fonts
+        self.fonts = Font.domToFont(root);
+
+        // Parse Fills
+        self.fills = Fill.domToFill(root);
+
+        // Parse Borders
+        self.borders = Border.domToBorder(root);
 
         // Cell xf
         Element cellXfs = root.element("cellXfs");
-        sub = cellXfs.elements();
+        List<Element> sub = cellXfs.elements();
         int i = 0;
         for (Element e : sub) {
+            int style = 0;
+            // NumFmt
             String applyNumberFormat = getAttr(e, "applyNumberFormat");
-            if (isNotEmpty(applyNumberFormat) && ("1".equals(applyNumberFormat) || "true".equalsIgnoreCase(applyNumberFormat))) {
+            if ("1".equals(applyNumberFormat) || "true".equalsIgnoreCase(applyNumberFormat)) {
                 String numFmtId = getAttr(e, "numFmtId");
-                int style = Integer.parseInt(numFmtId) << INDEX_NUMBER_FORMAT;
-                self.map.put(i, style);
+                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");
+                style |= Integer.parseInt(fontId) << INDEX_FONT;
+            }
+            // Fill
+            String applyFill = getAttr(e, "applyFill");
+            if ("1".equals(applyFill) || "true".equalsIgnoreCase(applyFill)) {
+                String fillId = getAttr(e, "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");
+                style |= Integer.parseInt(borderId) << INDEX_BORDER;
+            }
+            // Alignment
+            String applyAlignment = getAttr(e, "applyAlignment");
+            if ("1".equals(applyAlignment) || "true".equalsIgnoreCase(applyAlignment)) {
+                Element alignment = e.element("alignment");
+                String horizontal = getAttr(alignment, "horizontal");
+                int index;
+                if (StringUtil.isNotEmpty(horizontal) && (index = StringUtil.indexOf(Horizontals._names, horizontal)) >= 0) {
+                    style |= index << INDEX_HORIZONTAL;
+                }
+                String vertical = getAttr(alignment, "vertical");
+                if (StringUtil.isNotEmpty(vertical) && (index = StringUtil.indexOf(Verticals._names, vertical)) >= 0) {
+                    style |= index << INDEX_VERTICAL;
+                } else style |= Verticals.BOTTOM;
+                String wrapText = getAttr(alignment, "wrapText");
+                style |= ("1".equals(wrapText) || "true".equalsIgnoreCase(wrapText) ? 1 : 0) << INDEX_WRAP_TEXT;
+            }
+            self.map.put(style, i);
+            if (i >= self.styleIndex.length) {
+                self.styleIndex = Arrays.copyOf(self.styleIndex, self.styleIndex.length << 1);
             }
+            self.styleIndex[i] = style;
             i++;
         }
+        self.counter.set(i);
         // Test number format
-        for (Integer styleIndex : self.map.keySet()) {
+        for (Integer styleIndex : self.map.values()) {
             self.isDate(styleIndex);
         }
 
@@ -337,7 +347,7 @@ public class Styles implements Storable {
             i = fonts.size();
             fonts.add(font);
         }
-        return (i + 1) << INDEX_FONT;
+        return i << INDEX_FONT;
     }
 
     /**
@@ -416,6 +426,7 @@ public class Styles implements Storable {
      */
     @Override
     public void writeTo(Path styleFile) throws IOException {
+        if (document == null) document = createDocument();
         Element root = document.getRootElement();
 
         // Number format
@@ -448,14 +459,12 @@ public class Styles implements Storable {
 
         Element cellXfs = root.element("cellXfs").addAttribute("count", String.valueOf(map.size()));
 
-        List<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
-        list.sort(Comparator.comparingInt(Map.Entry::getValue));
-        list.forEach(e -> {
-            int[] styles = unpack(e.getKey());
+        for (int i = 0, len = counter.get(); i < len; i++) {
+            int[] styles = unpack(styleIndex[i]);
 
             Element newXf = cellXfs.addElement("xf");
             newXf.addAttribute(attrNames[0], String.valueOf(styles[0]))
-                .addAttribute(attrNames[1], String.valueOf(Math.max(styles[1] - 1, 0)))
+                .addAttribute(attrNames[1], String.valueOf(styles[1]))
                 .addAttribute(attrNames[2], String.valueOf(styles[2]))
                 .addAttribute(attrNames[3], String.valueOf(styles[3]))
                 .addAttribute("xfId", "0")
@@ -484,11 +493,49 @@ public class Styles implements Storable {
             if (styles[6] > 0) {
                 subEle.addAttribute(attrNames[6], "1");
             }
-        });
+        }
 
         FileUtil.writeToDiskNoFormat(document, styleFile);
     }
 
+    public static Document createDocument() {
+        DocumentFactory factory = DocumentFactory.getInstance();
+        TopNS ns = Styles.class.getAnnotation(TopNS.class);
+        Element rootElement;
+        if (ns != null) {
+            rootElement = factory.createElement(ns.value(), ns.uri()[0]);
+        } else {
+            rootElement = factory.createElement("styleSheet", Const.SCHEMA_MAIN);
+        }
+        // number format
+        rootElement.addElement("numFmts").addAttribute("count", "0");
+        // font
+        rootElement.addElement("fonts").addAttribute("count", "0");
+        // fill
+        rootElement.addElement("fills").addAttribute("count", "0");
+        // border
+        rootElement.addElement("borders").addAttribute("count", "0");
+        // cellStyleXfs
+        Element cellStyleXfs = rootElement.addElement("cellStyleXfs").addAttribute("count", "1");
+        cellStyleXfs.addElement("xf")   // General style
+            .addAttribute("borderId", "0")
+            .addAttribute("fillId", "0")
+            .addAttribute("fontId", "0")
+            .addAttribute("numFmtId", "0")
+            .addElement("alignment")
+            .addAttribute("vertical", "center");
+        // cellXfs
+        rootElement.addElement("cellXfs").addAttribute("count", "0");
+        // cellStyles
+        Element cellStyles = rootElement.addElement("cellStyles").addAttribute("count", "1");
+        cellStyles.addElement("cellStyle")
+            .addAttribute("builtinId", "0")
+            .addAttribute("name", "常规")
+            .addAttribute("xfId", "0");
+
+        return factory.createDocument(rootElement);
+    }
+
     ////////////////////////clear style///////////////////////////////
 
     public static int clearNumFmt(int style) {
@@ -536,7 +583,7 @@ public class Styles implements Storable {
     }
 
     public static int defaultStringBorderStyle() {
-        return  (1 << INDEX_BORDER) | (2 << INDEX_FONT) | Horizontals.LEFT;
+        return  (1 << INDEX_BORDER) | (1 << INDEX_FONT) | Horizontals.LEFT;
     }
 
     public static int defaultIntBorderStyle() {
@@ -570,7 +617,7 @@ public class Styles implements Storable {
     }
 
     public static boolean hasFont(int style) {
-        return style << 8 >>> (INDEX_FONT + 8) != 0;
+        return true; // Font is required
     }
 
     public static boolean hasFill(int style) {
@@ -609,7 +656,7 @@ public class Styles implements Storable {
     }
 
     public Font getFont(int style) {
-        return fonts.get(Math.max(0, (style << 8 >>> (INDEX_FONT + 8)) - 1));
+        return fonts.get(Math.max(0, style << 8 >>> (INDEX_FONT + 8)));
     }
 
     public Border getBorder(int style) {
@@ -639,6 +686,48 @@ public class Styles implements Storable {
         return element != null ? element.attributeValue(attr) : null;
     }
 
+    /**
+     * Parse color tag
+     *
+     * @param element color tag
+     * @return awt.Color or null
+     */
+    public static Color parseColor(Element element) {
+        if (element == null) return null;
+        String rgb = getAttr(element, "rgb"), indexed = getAttr(element, "indexed")
+            , auto = getAttr(element, "auto"), theme = getAttr(element, "theme");
+        Color c = null;
+        // Standard Alpha Red Green Blue color value (ARGB).
+        if (StringUtil.isNotEmpty(rgb)) {
+            c = ColorIndex.toColor(rgb);
+        }
+        // Indexed color value. Only used for backwards compatibility.
+        // References a color in indexedColors.
+        else if (StringUtil.isNotEmpty(indexed)) {
+            c = ColorIndex.getColor(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);
+        }
+        // Theme colors
+        else if (StringUtil.isNotEmpty(theme)) {
+            int t = 0;
+            try {
+                t = Integer.parseInt(theme);
+            } catch (NumberFormatException ex) { }
+            if (t < 0 || t > 11) {
+                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);
+        }
+        return c;
+    }
+
     /**
      * Test the style is data format
      *
@@ -649,8 +738,8 @@ public class Styles implements Storable {
         // Test from cache
         if (fastTestDateFmt(styleIndex)) return true;
 
-        Integer style = map.get(styleIndex);
-        if (style == null) return false;
+        if (styleIndex > counter.get()) return false;
+        int style = this.styleIndex[styleIndex];
         int nf = style >> INDEX_NUMBER_FORMAT & 0xFF;
 
         // No number format
diff --git a/src/main/java/org/ttzero/excel/entity/style/Verticals.java b/src/main/java/org/ttzero/excel/entity/style/Verticals.java
index 6685d236..5616560f 100644
--- a/src/main/java/org/ttzero/excel/entity/style/Verticals.java
+++ b/src/main/java/org/ttzero/excel/entity/style/Verticals.java
@@ -26,7 +26,7 @@ public class Verticals {
         , BOTH   = 3 << Styles.INDEX_VERTICAL // Vertical Justification
         ;
 
-    private static final String[] _names = {"center", "bottom", "top", "both"};
+    public static final String[] _names = {"center", "bottom", "top", "both"};
 
     public static String of(int n) {
         return _names[n];
diff --git a/src/main/java/org/ttzero/excel/reader/ExcelReader.java b/src/main/java/org/ttzero/excel/reader/ExcelReader.java
index 3c2d9cb4..d84e1b5d 100644
--- a/src/main/java/org/ttzero/excel/reader/ExcelReader.java
+++ b/src/main/java/org/ttzero/excel/reader/ExcelReader.java
@@ -139,6 +139,10 @@ public class ExcelReader implements Closeable {
      * Picture or Tables
      */
     protected Drawings drawings;
+    /**
+     * A global styles
+     */
+    protected Styles styles;
 
     /**
      * Constructor Excel Reader
@@ -565,14 +569,24 @@ public class ExcelReader implements Closeable {
         // Load Styles
         Path s = tmp.resolve("xl/styles.xml");
 
-        Styles styles;
         if (exists(s)) {
-            styles = Styles.load(s);
-        } else {
-            FileUtil.rm_rf(tmp.toFile(), true);
-            throw new ExcelReadException("The file format is incorrect or corrupted. [xl/styles.xml]");
+            try {
+                styles = Styles.load(s);
+            } catch (Exception ex) {
+                LOGGER.warn("Parse style failed.", ex);
+            }
+        }
+//        else {
+//            FileUtil.rm_rf(tmp.toFile(), true);
+//            throw new ExcelReadException("The file format is incorrect or corrupted. [xl/styles.xml]");
+//        }
+        // Construct a empty Styles
+        if (styles == null) {
+            styles = Styles.forReader();
         }
 
+        // TODO Parse theme
+
         this.option = option;
         hasFormula = exists(tmp.resolve("xl/calcChain.xml"));
 
@@ -900,4 +914,13 @@ public class ExcelReader implements Closeable {
     public List<Drawings.Picture> listPictures() {
         return drawings != null ? drawings.listPictures() : null;
     }
+
+    /**
+     * Returns a global {@link Styles}
+     *
+     * @return a style entry
+     */
+    public Styles getStyles() {
+        return styles;
+    }
 }
diff --git a/src/main/java/org/ttzero/excel/reader/Row.java b/src/main/java/org/ttzero/excel/reader/Row.java
index 630ef237..4cf527c0 100644
--- a/src/main/java/org/ttzero/excel/reader/Row.java
+++ b/src/main/java/org/ttzero/excel/reader/Row.java
@@ -123,6 +123,15 @@ public abstract class Row {
         return lc;
     }
 
+    /**
+     * Returns a global {@link Styles}
+     *
+     * @return a style entry
+     */
+    public Styles getStyles() {
+        return styles;
+    }
+
     /**
      * Test unused row (not contains any filled or formatted or value)
      *
@@ -215,7 +224,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Boolean}
      */
-    protected Boolean getBoolean(Cell c) {
+    public Boolean getBoolean(Cell c) {
         boolean v;
         switch (c.t) {
             case BOOL:
@@ -270,7 +279,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Byte}
      */
-    protected Byte getByte(Cell c) {
+    public Byte getByte(Cell c) {
         byte b = 0;
         switch (c.t) {
             case NUMERIC:
@@ -323,7 +332,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Character}
      */
-    protected Character getChar(Cell c) {
+    public Character getChar(Cell c) {
         char cc = 0;
         switch (c.t) {
             case SST:
@@ -386,7 +395,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Short}
      */
-    protected Short getShort(Cell c) {
+    public Short getShort(Cell c) {
         short s = 0;
         switch (c.t) {
             case NUMERIC:
@@ -454,7 +463,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Integer}
      */
-    protected Integer getInt(Cell c) {
+    public Integer getInt(Cell c) {
         int n;
         switch (c.t) {
             case NUMERIC:
@@ -523,7 +532,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Long}
      */
-    protected Long getLong(Cell c) {
+    public Long getLong(Cell c) {
         long l;
         switch (c.t) {
             case LONG:
@@ -591,7 +600,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return string
      */
-    protected String getString(Cell c) {
+    public String getString(Cell c) {
         String s;
         switch (c.t) {
             case SST:
@@ -646,6 +655,17 @@ public abstract class Row {
         return d != null ? Float.valueOf(d.toString()) : null;
     }
 
+    /**
+     * Get {@code Float} value by cell
+     *
+     * @param c the {@link Cell}
+     * @return {@code Float}
+     */
+    public Float getFloat(Cell c) {
+        Double d = getDouble(c);
+        return d != null ? Float.valueOf(d.toString()) : null;
+    }
+
     /**
      * Get {@code Double} value by column index
      *
@@ -674,7 +694,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return {@code Double}
      */
-    protected Double getDouble(Cell c) {
+    public Double getDouble(Cell c) {
         double d;
         switch (c.t) {
             case DOUBLE:
@@ -734,7 +754,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return BigDecimal
      */
-    protected BigDecimal getDecimal(Cell c) {
+    public BigDecimal getDecimal(Cell c) {
         BigDecimal bd;
         switch (c.t) {
             case DOUBLE:
@@ -793,7 +813,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return BigDecimal
      */
-    protected Date getDate(Cell c) {
+    public Date getDate(Cell c) {
         Date date;
         switch (c.t) {
             case NUMERIC:
@@ -849,7 +869,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return java.sql.Timestamp
      */
-    protected Timestamp getTimestamp(Cell c) {
+    public Timestamp getTimestamp(Cell c) {
         Timestamp ts;
         switch (c.t) {
             case NUMERIC:
@@ -903,7 +923,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return java.sql.Time
      */
-    protected java.sql.Time getTime(Cell c) {
+    public java.sql.Time getTime(Cell c) {
         java.sql.Time t;
         switch (c.t) {
             case DOUBLE: t = toTime(c.dv); break;
@@ -953,7 +973,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return java.time.LocalDateTime
      */
-    protected LocalDateTime getLocalDateTime(Cell c) {
+    public LocalDateTime getLocalDateTime(Cell c) {
         LocalDateTime ldt;
         switch (c.t) {
             case NUMERIC:
@@ -1009,7 +1029,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return java.time.LocalDate
      */
-    protected LocalDate getLocalDate(Cell c) {
+    public LocalDate getLocalDate(Cell c) {
         LocalDate ld;
         switch (c.t) {
             case NUMERIC:
@@ -1065,7 +1085,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return java.time.LocalTime
      */
-    protected LocalTime getLocalTime(Cell c) {
+    public LocalTime getLocalTime(Cell c) {
         LocalTime lt;
         switch (c.t) {
             case NUMERIC:
@@ -1171,7 +1191,7 @@ public abstract class Row {
      * @param c the {@link Cell}
      * @return the {@link CellType}
      */
-    protected CellType getCellType(Cell c) {
+    public CellType getCellType(Cell c) {
         CellType type;
         switch (c.t) {
             case SST:
@@ -1209,6 +1229,38 @@ public abstract class Row {
         return type;
     }
 
+    /**
+     * Returns the cell styles
+     *
+     * @param columnIndex the cell index from zero
+     * @return the style value
+     */
+    public int getCellStyle(int columnIndex) {
+        Cell c = getCell(columnIndex);
+        return getCellStyle(c);
+    }
+
+    /**
+     * Returns the cell styles
+     *
+     * @param columnName the cell name
+     * @return the style value
+     */
+    public int getCellStyle(String columnName) {
+        Cell c = getCell(columnName);
+        return getCellStyle(c);
+    }
+
+    /**
+     * Returns the cell styles
+     *
+     * @param c the {@link Cell}
+     * @return the style value
+     */
+    public int getCellStyle(Cell c) {
+        return styles.getStyleByIndex(c.xf);
+    }
+
     /**
      * Returns the binding type if is bound, otherwise returns Row
      *


Test patch:
diff --git a/src/test/java/org/ttzero/excel/annotation/CustomAnnoReaderTest.java b/src/test/java/org/ttzero/excel/annotation/CustomAnnoReaderTest.java
index 2e3ce917..660967f6 100644
--- a/src/test/java/org/ttzero/excel/annotation/CustomAnnoReaderTest.java
+++ b/src/test/java/org/ttzero/excel/annotation/CustomAnnoReaderTest.java
@@ -20,11 +20,9 @@ package org.ttzero.excel.annotation;
 import org.junit.Test;
 import org.ttzero.excel.customAnno.ExcelProperty;
 import org.ttzero.excel.entity.ListSheet;
-import org.ttzero.excel.entity.style.Styles;
 import org.ttzero.excel.reader.ExcelReadException;
 import org.ttzero.excel.reader.ExcelReader;
 import org.ttzero.excel.reader.HeaderRow;
-import org.ttzero.excel.reader.SharedStrings;
 import org.ttzero.excel.reader.Sheet;
 import org.ttzero.excel.reader.XMLRow;
 import org.ttzero.excel.reader.XMLSheet;
diff --git a/src/test/java/org/ttzero/excel/entity/MultiHeaderColumnsTest.java b/src/test/java/org/ttzero/excel/entity/MultiHeaderColumnsTest.java
index 2eb98afb..fe127a3f 100644
--- a/src/test/java/org/ttzero/excel/entity/MultiHeaderColumnsTest.java
+++ b/src/test/java/org/ttzero/excel/entity/MultiHeaderColumnsTest.java
@@ -23,14 +23,19 @@ import org.ttzero.excel.annotation.HeaderComment;
 import org.ttzero.excel.entity.e7.XMLWorksheetWriter;
 import org.ttzero.excel.entity.style.Font;
 import org.ttzero.excel.entity.style.Horizontals;
+import org.ttzero.excel.entity.style.NumFmt;
 
 import java.awt.Color;
 import java.io.IOException;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * @author guanquan.wang at 2022-06-27 23:24
@@ -85,9 +90,9 @@ public class MultiHeaderColumnsTest extends SQLWorkbookTest {
             new Workbook("Multi ResultSet columns 2", author).setAutoSize(true)
                 .setConnection(con)
                 .addSheet("select id, name, age, create_date, update_date from student order by age"
-                    , new Column("通用").setHeaderStyle(794694).addSubColumn(new Column("学号", int.class))
+                    , new Column("通用").setHeaderStyle(532550).addSubColumn(new Column("学号", int.class))
                     , new Column("通用").addSubColumn(new Column("性名", String.class))
-                    , new Column("通用").addSubColumn(new Column("年龄", int.class).setHeaderStyle(794691))
+                    , new Column("通用").addSubColumn(new Column("年龄", int.class).setHeaderStyle(532550))
                     , new Column("创建时间", Timestamp.class)
                     , new Column("更新", Timestamp.class).setColIndex(1)
                 )
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 9dcc8c30..9184e05d 100644
--- a/src/test/java/org/ttzero/excel/entity/style/StylesTest.java
+++ b/src/test/java/org/ttzero/excel/entity/style/StylesTest.java
@@ -20,9 +20,11 @@ import org.junit.Before;
 import org.junit.Test;
 import org.ttzero.excel.entity.I18N;
 
+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;
@@ -156,7 +158,8 @@ public class StylesTest {
 
 
         assert !Styles.hasNumFmt(Styles.clearNumFmt(style));
-        assert !Styles.hasFont(Styles.clearFont(style));
+        // Font is required
+//        assert !Styles.hasFont(Styles.clearFont(style));
         assert !Styles.hasFill(Styles.clearFill(style));
         assert !Styles.hasBorder(Styles.clearBorder(style));
         assert !Styles.hasVertical(Styles.clearVertical(style));
@@ -164,4 +167,26 @@ public class StylesTest {
         assert !Styles.hasWrapText(Styles.clearWrapText(style));
     }
 
+    @Test public void testThemeColor() {
+        // +-1
+        Color color1 = HlsColor.calculateColor(Color.decode("#F79646"), "0.39997558519241921");
+        assert color1.getRed() <= 251 && color1.getRed() >= 249;
+        assert color1.getGreen() <= 192 && color1.getGreen() >= 190;
+        assert color1.getBlue() <= 144 && color1.getBlue() >= 142;
+
+        Color color2 = HlsColor.calculateColor(Color.decode("#4F81BD"), "0.79998168889431442");
+        assert color2.getRed() <= 221 && color2.getRed() >= 219;
+        assert color2.getGreen() <= 231 && color2.getGreen() >= 229;
+        assert color2.getBlue() <= 242 && color2.getBlue() >= 240;
+
+        Color color3 = HlsColor.calculateColor(Color.decode("#C0504D"), "0.59999389629810485");
+        assert color3.getRed() <= 231 && color3.getRed() >= 229;
+        assert color3.getGreen() <= 185 && color3.getGreen() >= 183;
+        assert color3.getBlue() <= 184 && color3.getBlue() >= 182;
+
+        Color color4 = HlsColor.calculateColor(new Color(0, 0, 0), "0.39997558519241921");
+        assert color4.getRed() <= 103 && color4.getRed() >= 101;
+        assert color4.getGreen() <= 103 && color4.getGreen() >= 101;
+        assert color4.getBlue() <= 103 && color4.getBlue() >= 101;
+    }
 }
diff --git a/src/test/java/org/ttzero/excel/reader/ReadStyleTest.java b/src/test/java/org/ttzero/excel/reader/ReadStyleTest.java
new file mode 100644
index 00000000..280e7318
--- /dev/null
+++ b/src/test/java/org/ttzero/excel/reader/ReadStyleTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.reader;
+
+import org.junit.Test;
+import org.ttzero.excel.entity.style.Border;
+import org.ttzero.excel.entity.style.BorderStyle;
+import org.ttzero.excel.entity.style.Fill;
+import org.ttzero.excel.entity.style.Font;
+import org.ttzero.excel.entity.style.Horizontals;
+import org.ttzero.excel.entity.style.NumFmt;
+import org.ttzero.excel.entity.style.PatternType;
+import org.ttzero.excel.entity.style.Styles;
+
+import java.awt.Color;
+import java.io.IOException;
+
+import static org.ttzero.excel.reader.ExcelReaderTest.testResourceRoot;
+
+/**
+ * @author guanquan.wang at 2023-01-03 20:57
+ */
+public class ReadStyleTest {
+    @Test public void testReadStyles() throws IOException {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("4.xlsx"))) {
+            reader.sheet(0).rows().forEach(row -> {
+                for (int i = row.fc; i < row.lc; i++) {
+                    String rc = new String(org.ttzero.excel.entity.Sheet.int2Col(i + 1)) + row.getRowNum();
+                    Cell cell = row.getCell(i);
+                    Styles styles = row.getStyles();
+                    int style = row.getCellStyle(cell);
+                    if ("A1".equals(rc)) {
+                        Font font = styles.getFont(style);
+                        assert font.isBold();
+                        assert font.getSize() == 11;
+                        assert font.getName().equals("微软雅黑");
+
+                        Fill fill = styles.getFill(style);
+                        assert fill.getFgColor().equals(new Color(102, 102, 153));
+
+                        assert "center".equals(Horizontals.of(styles.getHorizontal(style)));
+                    } else if ("B4".equals(rc)) {
+                        Font font = styles.getFont(style);
+                        assert font.getName().equals("Cascadia Mono");
+                        assert font.getSize() == 24;
+                        assert "right".equals(Horizontals.of(styles.getHorizontal(style)));
+
+                        Border border = styles.getBorder(style);
+                        Border.SubBorder leftBorder = border.getBorderLeft();
+                        assert leftBorder.getStyle() == BorderStyle.HAIR;
+                        assert leftBorder.getColor().equals(Color.RED);
+
+                        Border.SubBorder bottomBorder = border.getBorderBottom();
+                        assert bottomBorder.getStyle() == BorderStyle.DOUBLE;
+                        assert bottomBorder.getColor().equals(Color.BLACK);
+                    } else if ("E7".equals(rc)) {
+                        Font font = styles.getFont(style);
+                        assert font.isBold();
+                        assert font.isItalic();
+                        assert font.getSize() == 36;
+                        assert font.getName().equals("Consolas");
+
+                        assert "left".equals(Horizontals.of(styles.getHorizontal(style)));
+
+                        Fill fill = styles.getFill(style);
+                        assert fill.getPatternType() == PatternType.gray125;
+                        assert fill.getBgColor().equals(new Color(123, 193, 203));
+                    } else if ("F10".equals(rc)) {
+                        NumFmt fmt = styles.getNumFmt(style);
+                        assert fmt.getCode().equals("d-mmm-yy");
+
+                        Font font = styles.getFont(style);
+                        assert font.isDeleteLine();
+                    }
+                }
+            });
+        }
+    }
+}