public Array<String> writeFile(FileHandle fileHandle) {
        Array<String> warnings = new Array<>();
        
        StringWriter stringWriter = new StringWriter();
        JsonWriter jsonWriter = new JsonWriter(stringWriter);
        jsonWriter.setOutputType(projectData.getExportFormat().getOutputType());
        
        Json json = new Json(projectData.getExportFormat().getOutputType());
        json.setWriter(jsonWriter);
        json.writeObjectStart();

        //fonts
        if (fonts.size > 0) {
            String className = projectData.isUsingSimpleNames() ? BitmapFont.class.getSimpleName() : BitmapFont.class.getName();
            json.writeObjectStart(className);
            for (FontData font : fonts) {
                json.writeObjectStart(font.getName());
                json.writeValue("file", font.file.name());
                json.writeValue("scaledSize", font.getScaling());
                json.writeValue("markupEnabled", font.isMarkupEnabled());
                json.writeValue("flip", font.isFlip());
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }

        //colors
        if (colors.size > 0) {
            String className = projectData.isUsingSimpleNames() ? Color.class.getSimpleName() : Color.class.getName();
            json.writeObjectStart(className);
            for (ColorData color : colors) {
                json.writeObjectStart(color.getName());
                if (projectData.isExportingHex()) {
                    json.writeValue("hex", color.color.toString());
                    json.writeObjectEnd();
                } else {
                    json.writeValue("r", color.color.r);
                    json.writeValue("g", color.color.g);
                    json.writeValue("b", color.color.b);
                    json.writeValue("a", color.color.a);
                    json.writeObjectEnd();
                }
            }
            json.writeObjectEnd();
        }
        
        //FreeType fonts
        boolean exportFreeType = false;
        for (FreeTypeFontData font : freeTypeFonts) {
            if (font.useCustomSerializer) {
                exportFreeType = true;
                break;
            }
        }

        if (exportFreeType) {
            json.writeObjectStart(FreeTypeFontGenerator.class.getName());
            for (FreeTypeFontData font : freeTypeFonts) {
                if (font.useCustomSerializer) {
                    json.writeObjectStart(font.name);
                    json.writeValue("font", font.file.name());
                    json.writeValue("size", font.size);
                    json.writeValue("mono", font.mono);
                    if (font.color != null) json.writeValue("color", font.color);
                    json.writeValue("gamma", font.gamma);
                    json.writeValue("renderCount", font.renderCount);
                    json.writeValue("borderWidth", font.borderWidth);
                    if (font.borderColor != null) json.writeValue("borderColor", font.borderColor);
                    json.writeValue("borderStraight", font.borderStraight);
                    json.writeValue("borderGamma", font.borderGamma);
                    json.writeValue("shadowOffsetX", font.shadowOffsetX);
                    json.writeValue("shadowOffsetY", font.shadowOffsetY);
                    if (font.shadowColor != null) json.writeValue("shadowColor", font.shadowColor);
                    json.writeValue("spaceX", font.spaceX);
                    json.writeValue("spaceY", font.spaceY);
                    json.writeValue("kerning", font.kerning);
                    json.writeValue("flip", font.flip);
                    json.writeValue("genMipMaps", font.genMipMaps);
                    json.writeValue("incremental", font.incremental);
                    json.writeValue("hinting", font.hinting);
                    json.writeValue("minFilter", font.minFilter);
                    json.writeValue("magFilter", font.magFilter);
                    json.writeValue("characters", font.characters.equals("") ? FreeTypeFontData.DEFAULT_CHARS : font.characters);
                    json.writeObjectEnd();
                }
            }
            json.writeObjectEnd();
        }
        
        var tintedDrawables = new Array<DrawableData>();
        var tiledDrawables = new Array<DrawableData>();
        var pixelDrawables = new Array<DrawableData>();
        var textureRegionDrawables = new Array<DrawableData>();
        var ninePatchDrawables = new Array<DrawableData>();
        var tenPatchDrawables = new Array<DrawableData>();
        var tvgDrawables = new Array<DrawableData>();
        for (DrawableData drawable : projectData.getAtlasData().getDrawables()) {
            switch (drawable.type) {
                case TILED:
                    tiledDrawables.add(drawable);
                    break;
                case TINTED:
                case TINTED_FROM_COLOR_DATA:
                    tintedDrawables.add(drawable);
                    break;
                case TENPATCH:
                    tenPatchDrawables.add(drawable);
                    break;
                case TVG:
                    tvgDrawables.add(drawable);
                    break;
                case PIXEL:
                    pixelDrawables.add(drawable);
                    break;
                case TEXTURE:
                    //only include if minSize has been modified
                    if (!MathUtils.isEqual(drawable.minWidth, -1f) || !MathUtils.isEqual(drawable.minHeight, -1f)) {
                        textureRegionDrawables.add(drawable);
                    }
                    break;
                case NINE_PATCH:
                    //only include if minSize has been modified
                    if (!MathUtils.isEqual(drawable.minWidth, -1f) || !MathUtils.isEqual(drawable.minHeight, -1f)) {
                        ninePatchDrawables.add(drawable);
                    }
                    break;
            }
        }
        
        //texture region drawables for minWidth and minHeight definition
        if (textureRegionDrawables.size > 0) {
            var className = TextureRegionDrawable.class.getName();
            json.writeObjectStart(className);
            for (var drawable : textureRegionDrawables) {
                json.writeObjectStart(drawable.file.nameWithoutExtension());
                json.writeValue("region", drawable.file.nameWithoutExtension());
                if (!MathUtils.isEqual(drawable.minWidth, -1)) json.writeValue("minWidth", drawable.minWidth);
                else json.writeValue("minWidth", Utils.imageDimensions(drawable.file).x);
                if (!MathUtils.isEqual(drawable.minHeight, -1)) json.writeValue("minHeight", drawable.minHeight);
                else json.writeValue("minHeight", Utils.imageDimensions(drawable.file).y);
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }
        
        //ninepatch drawables for minWidth and minHeight definition
        if (ninePatchDrawables.size > 0) {
            var className = NinePatchDrawable.class.getName();
            json.writeObjectStart(className);
            for (var drawable : ninePatchDrawables) {
                var name = drawable.name;
                json.writeObjectStart(name);
                json.writeValue("patch", drawable.file.name().replaceAll("\\.9.*$", ""));
                if (!MathUtils.isEqual(drawable.minWidth, -1)) json.writeValue("minWidth", drawable.minWidth);
                else json.writeValue("minWidth", Utils.imageDimensions(drawable.file).x);
                if (!MathUtils.isEqual(drawable.minHeight, -1)) json.writeValue("minHeight", drawable.minHeight);
                else json.writeValue("minHeight", Utils.imageDimensions(drawable.file).y);
                if (drawable.tintName != null) json.writeValue("color", drawable.tintName);
                if (drawable.tint != null) {
                    json.writeObjectStart("color");
                    if (projectData.isExportingHex()) {
                        json.writeValue("hex", drawable.tint.toString());
                    } else {
                        json.writeValue("r", drawable.tint.r);
                        json.writeValue("g", drawable.tint.g);
                        json.writeValue("b", drawable.tint.b);
                        json.writeValue("a", drawable.tint.a);
                    }
                    json.writeObjectEnd();
                }
                
                var atlasDrawable = atlasData.getDrawablePairs().get(drawable);
                json.writeValue("leftWidth", atlasDrawable.getLeftWidth());
                json.writeValue("rightWidth", atlasDrawable.getRightWidth());
                json.writeValue("topHeight", atlasDrawable.getTopHeight());
                json.writeValue("bottomHeight", atlasDrawable.getBottomHeight());
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }
        
        //tinted drawables
        if (tintedDrawables.size > 0 || pixelDrawables.size > 0) {
            String className = projectData.isUsingSimpleNames() ? TintedDrawable.class.getSimpleName() : TintedDrawable.class.getName();
            json.writeObjectStart(className);
            var drawables = new Array<>(tintedDrawables);
            drawables.addAll(pixelDrawables);
            for (DrawableData drawable : drawables) {
                json.writeObjectStart(drawable.name);
    
                json.writeValue("name", drawable.type == PIXEL ? "white-pixel" : DrawableData.proper(drawable.file.name()));
                if (drawable.type == TINTED) {
                    json.writeObjectStart("color");
                    if (projectData.isExportingHex()) {
                        json.writeValue("hex", drawable.tint.toString());
                    } else {
                        json.writeValue("r", drawable.tint.r);
                        json.writeValue("g", drawable.tint.g);
                        json.writeValue("b", drawable.tint.b);
                        json.writeValue("a", drawable.tint.a);
                    }
                    json.writeObjectEnd();
                } else if (drawable.type == TINTED_FROM_COLOR_DATA || drawable.type == PIXEL) {
                    json.writeValue("color", drawable.tintName);
                }
                
                if (drawable.minWidth >= 0) {
                    json.writeValue("minWidth", drawable.minWidth);
                }
                if (drawable.minHeight >= 0) {
                    json.writeValue("minHeight", drawable.minHeight);
                }
                
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }
        
        //tiled drawables
        if (tiledDrawables.size > 0) {
            String className = projectData.isUsingSimpleNames() ? TiledDrawable.class.getSimpleName() : TiledDrawable.class.getName();
            json.writeObjectStart(className);
            for (DrawableData drawable : tiledDrawables) {
                json.writeObjectStart(drawable.name);
                json.writeValue("region", DrawableData.proper(drawable.file.name()));
                json.writeValue("color", drawable.tintName);
                json.writeValue("minWidth", drawable.minWidth);
                json.writeValue("minHeight", drawable.minHeight);
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }
        
        //ten patch drawables
        if (tenPatchDrawables.size > 0) {
            String className = TenPatchDrawable.class.getName();
            json.writeObjectStart(className);
            for (DrawableData drawable : tenPatchDrawables) {
                json.writeObjectStart(drawable.name);
                json.writeValue("region", DrawableData.proper(drawable.file.name()));
                if (drawable.tenPatchData.colorName != null) {
                    json.writeValue("color", drawable.tenPatchData.colorName);
                }
                
                if (drawable.tenPatchData.horizontalStretchAreas.size > 0) {
                    json.writeValue("horizontalStretchAreas", drawable.tenPatchData.horizontalStretchAreas.toArray());
                } else {
                    var region = atlasData.getAtlas().findRegion(drawable.file.nameWithoutExtension());
                    json.writeValue("horizontalStretchAreas", new int[]{0, region.getRegionWidth() - 1});
                }
                
                if (drawable.tenPatchData.verticalStretchAreas.size > 0) {
                    json.writeValue("verticalStretchAreas", drawable.tenPatchData.verticalStretchAreas.toArray());
                } else {
                    var region = atlasData.getAtlas().findRegion(drawable.file.nameWithoutExtension());
                    json.writeValue("verticalStretchAreas", new int[]{0, region.getRegionHeight() - 1});
                }
                
                json.writeValue("tiling", drawable.tenPatchData.tile);
                if (!MathUtils.isEqual(drawable.minWidth, -1)) json.writeValue("minWidth", drawable.minWidth);
                else json.writeValue("minWidth", Utils.imageDimensions(drawable.file).x);
                if (!MathUtils.isEqual(drawable.minHeight, -1)) json.writeValue("minHeight", drawable.minHeight);
                else json.writeValue("minHeight", Utils.imageDimensions(drawable.file).y);
                json.writeValue("rightWidth", drawable.tenPatchData.contentRight);
                json.writeValue("leftWidth", drawable.tenPatchData.contentLeft);
                json.writeValue("bottomHeight", drawable.tenPatchData.contentBottom);
                json.writeValue("topHeight", drawable.tenPatchData.contentTop);
                if (drawable.tenPatchData.color1Name != null) {
                    json.writeValue("color1", drawable.tenPatchData.color1Name);
                }
                if (drawable.tenPatchData.color2Name != null) {
                    json.writeValue("color2", drawable.tenPatchData.color2Name);
                }
                if (drawable.tenPatchData.color3Name != null) {
                    json.writeValue("color3", drawable.tenPatchData.color3Name);
                }
                if (drawable.tenPatchData.color4Name != null) {
                    json.writeValue("color4", drawable.tenPatchData.color4Name);
                }
                json.writeValue("offsetX", drawable.tenPatchData.offsetX);
                json.writeValue("offsetY", drawable.tenPatchData.offsetY);
                json.writeValue("offsetXspeed", drawable.tenPatchData.offsetXspeed);
                json.writeValue("offsetYspeed", drawable.tenPatchData.offsetYspeed);
                json.writeValue("frameDuration", drawable.tenPatchData.frameDuration);
                json.writeValue("regions", drawable.tenPatchData.regionNames, Array.class, String.class);
                json.writeValue("playMode", drawable.tenPatchData.playMode);
                json.writeValue("crushMode", drawable.tenPatchData.crushMode);
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }
        
        //tvg drawables
        if (tvgDrawables.size > 0) {
            String className = TinyVGDrawable.class.getName();
            json.writeObjectStart(className);
            for (DrawableData drawable : tvgDrawables) {
                json.writeObjectStart(drawable.name);
                json.writeValue("file", drawable.file.name());
                json.writeValue("clipBasedOnTVGsize", drawable.tvgData.clipBasedOnTVGsize);
                json.writeObjectEnd();
            }
            json.writeObjectEnd();
        }
        
        //custom classes declared before UI classes
        for (CustomClass customClass : customClasses) {
            if (!customClass.isDeclareAfterUIclasses()) {
                if (customClassHasFields(customClass)) {
                    json.writeObjectStart(customClass.getFullyQualifiedName());
                    for (CustomStyle customStyle : customClass.getStyles()) {
                        if (customStyleHasFields(customStyle)) {
                            json.writeObjectStart(customStyle.getName());

                            for (CustomProperty customProperty : customStyle.getProperties()) {
                                //only write value if it is valid
                                if (customPropertyIsNotNull(customProperty)) {
                                    if (customProperty.getType().equals(CustomProperty.PropertyType.RAW_TEXT)) {
                                        try {
                                            json.getWriter().json(customProperty.getName(), (String)customProperty.getValue());
                                        } catch (Exception e) {
                                            DialogFactory.showDialogErrorStatic("Error writing custom property.", "Error writing custom property " + customProperty.getName() + " for custom class " + customClass.getDisplayName() + ".");
                                        }
                                    } else {
                                        json.writeValue(customProperty.getName(), customProperty.getValue());
                                    }
                                }
                            }
                            json.writeObjectEnd();
                        } else {
                            warnings.add("Did not export custom style [BLACK]" + customStyle.getName() + "[] for class [BLACK]" + customClass.getDisplayName() + "[] (All fields null)");
                        }
                    }
                    json.writeObjectEnd();
                } else {
                    warnings.add("Did not export custom class [BLACK]" + customClass.getDisplayName() + "[] (No valid styles)");
                }
            }
        }

        //styles
        for (int i = 0; i < Main.STYLE_CLASSES.length; i++) {
            Class clazz = Main.STYLE_CLASSES[i];
            Array<StyleData> styles = classStyleMap.get(Main.styleToBasicClass(clazz));

            //check if any style has the mandatory fields necessary to write
            boolean hasMandatoryStyles = false;
            for (StyleData style : styles) {
                if (style.hasMandatoryFields() && ! style.hasAllNullFields()) {
                    hasMandatoryStyles = true;
                    break;
                }
            }

            if (hasMandatoryStyles) {
                String className = projectData.isUsingSimpleNames() ? clazz.getSimpleName() : clazz.getName();
                json.writeObjectStart(className);
                for (StyleData style : styles) {
                    if (style.hasMandatoryFields() && !style.hasAllNullFields()) {
                        json.writeObjectStart(style.name);
                        if (style.parent != null) {
                            json.writeValue("parent", style.parent);
                        }
                        for (StyleProperty property : style.properties.values()) {

                            //if not optional, null, or zero
                            if (!property.optional || property.value != null
                                    && !(property.value instanceof Number
                                    && MathUtils.isZero((float) (double) property.value))) {
                                if (property.value != null) {
                                    json.writeValue(property.name, property.value);
                                }
                            }
                        }
                        json.writeObjectEnd();
                    } else {
                        if (style.hasAllNullFields()) {
                            warnings.add("Did not export style [BLACK]" + style.name + "[] for class [BLACK]" + clazz.getSimpleName() + " (All fields null)");
                        } else if (!style.hasMandatoryFields()) {
                            warnings.add("Did not export style [BLACK]" + style.name + "[] for class [BLACK]" + clazz.getSimpleName() + " (All fields null)");
                        }
                    }
                }
                json.writeObjectEnd();
            } else {
                warnings.add("Did not export class [BLACK]" + clazz.getSimpleName() + "[] (No valid styles)");
            }
        }
        
        //custom classes declared after UI classes
        for (CustomClass customClass : customClasses) {
            if (customClass.isDeclareAfterUIclasses()) {
                if (customClassHasFields(customClass)) {
                    json.writeObjectStart(customClass.getFullyQualifiedName());
                    for (CustomStyle customStyle : customClass.getStyles()) {
                        if (customStyleHasFields(customStyle)) {
                            json.writeObjectStart(customStyle.getName());

                            for (CustomProperty customProperty : customStyle.getProperties()) {
                                //only write value if it is valid
                                if (customPropertyIsNotNull(customProperty)) {
                                    if (customProperty.getType().equals(CustomProperty.PropertyType.RAW_TEXT)) {
                                        try {
                                            json.getWriter().json(customProperty.getName(), (String)customProperty.getValue());
                                        } catch (Exception e) {
                                            DialogFactory.showDialogErrorStatic("Error writing custom property.", "Error writing custom property " + customProperty.getName() + " for custom class " + customClass.getDisplayName() + ".");
                                        }
                                    } else {
                                        json.writeValue(customProperty.getName(), customProperty.getValue());
                                    }
                                }
                            }
                            json.writeObjectEnd();
                        } else {
                            warnings.add("Did not export custom style [BLACK]" + customStyle.getName() + "[] for class [BLACK]" + customClass.getDisplayName() + "[] (All fields null)");
                        }
                    }
                    json.writeObjectEnd();
                } else {
                    warnings.add("Did not export custom class [BLACK]" + customClass.getDisplayName() + "[] (No valid styles)");
                }
            }
        }

        json.writeObjectEnd();
        fileHandle.writeString(json.prettyPrint(stringWriter.toString()), false, "UTF-8");
        
        return warnings;
    }
Exports skin data to a JSON file to be loaded by libGDX.
@param fileHandle
@return
core/src/com/ray3k/skincomposer/data/JsonData.java