blob: afb89b38085bdb411b86f9174715798fb2b4915f [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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 com.android.ide.common.vectordrawable;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.google.common.io.Files;
import java.awt.Component;
import java.awt.Dimension;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Calendar;
import javax.swing.DefaultListSelectionModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
/**
* Support a command line tool to convert SVG files to VectorDrawables and display them.
*/
public class VdCommandLineTool {
// Show Vector Drawables in a table, below are the parameters for the table.
private static final int COLUMN_NUMBER = 8;
private static final int ROW_HEIGHT = 80;
private static final Dimension MAX_WINDOW_SIZE = new Dimension(600, 600);
public static final String BROKEN_FILE_EXTENSION = ".broken";
private static final boolean DBG_COPY_BROKEN_SVG = false;
private static void exitWithErrorMessage(String message) {
System.err.println(message);
System.exit(-1);
}
public static void main(String[] args) {
VdCommandLineOptions options = new VdCommandLineOptions();
String criticalError = options.parse(args);
if (criticalError != null) {
exitWithErrorMessage(criticalError + "\n\n" + VdCommandLineOptions.COMMAND_LINE_OPTION);
}
boolean needsConvertSvg = options.getConvertSvg();
boolean needsDisplayXml = options.getDisplayXml();
File[] filesToDisplay = options.getInputFiles();
if (needsConvertSvg) {
filesToDisplay = convertSVGToXml(options);
}
if (needsDisplayXml) {
displayXmlAsync(filesToDisplay);
}
}
private static void displayXmlAsync(final File[] displayFiles) {
SwingUtilities.invokeLater(() -> displayXml(displayFiles));
}
private static class MyTableModel extends AbstractTableModel {
private VdIcon[] mIconList;
public MyTableModel(VdIcon[] iconList) {
mIconList = iconList;
}
@Override
public String getColumnName(int column) {
return null;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
int index = rowIndex * COLUMN_NUMBER + columnIndex;
if (index < 0) {
return null;
}
return mIconList.length > index ? mIconList[index] : null;
}
@Override
public int getRowCount() {
return mIconList.length / COLUMN_NUMBER +
((mIconList.length % COLUMN_NUMBER == 0) ? 0 : 1);
}
@Override
public int getColumnCount() {
return Math.min(COLUMN_NUMBER, mIconList.length);
}
@Override
public Class<?> getColumnClass(int column) {
return Icon.class;
}
}
private static void displayXml(File[] inputFiles) {
ArrayList<VdIcon> iconList = new ArrayList<>();
int validXmlFileCounter = 0;
for (File xmlFile : inputFiles) {
if (!xmlFile.isFile() || xmlFile.length() == 0) {
continue;
}
String xmlFilename = xmlFile.getName();
if (xmlFilename.isEmpty()) {
continue;
}
if (!xmlFilename.endsWith(SdkConstants.DOT_XML)) {
continue;
}
try {
VdIcon icon = new VdIcon(xmlFile.toURI().toURL());
icon.enableCheckerBoardBackground(true);
iconList.add(icon);
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
}
validXmlFileCounter++;
}
System.out.println("Showing " + validXmlFileCounter + " valid icons");
MyTableModel model = new MyTableModel(iconList.toArray(new VdIcon[0]));
JTable table = new JTable(model) {
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
JComponent c = (JComponent)super.prepareRenderer(renderer, row, column);
VdIcon icon = (VdIcon)getValueAt(row, column);
c.setToolTipText(icon != null ? icon.getName() : null);
return c;
}
};
table.setOpaque(false);
table.setPreferredScrollableViewportSize(MAX_WINDOW_SIZE);
table.setRowHeight(ROW_HEIGHT);
table.setSelectionModel(new DefaultListSelectionModel() {
@Override
public void setSelectionInterval(int index0, int index1) {
// do nothing
}
});
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
if (table.getPreferredSize().getHeight() > MAX_WINDOW_SIZE.getHeight()) {
JScrollPane pane = new JScrollPane(table);
frame.getContentPane().add(pane);
} else {
frame.getContentPane().add(table);
}
frame.pack();
frame.setVisible(true);
}
private static final String AOSP_HEADER = "<!--\n" +
"Copyright (C) " + Calendar.getInstance().get(Calendar.YEAR) +
" The Android Open Source Project\n\n" +
" Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
" you may not use this file except in compliance with the License.\n" +
" You may obtain a copy of the License at\n\n" +
" http://www.apache.org/licenses/LICENSE-2.0\n\n" +
" Unless required by applicable law or agreed to in writing, software\n" +
" distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
" See the License for the specific language governing permissions and\n" +
" limitations under the License.\n" +
"-->\n";
private static File[] convertSVGToXml(VdCommandLineOptions options) {
File[] inputSVGFiles = options.getInputFiles();
File outputDir = options.getOutputDir();
int totalSvgFileCounter = 0;
int errorSvgFileCounter = 0;
ArrayList<File> allOutputFiles = new ArrayList<>();
for (File inputSVGFile : inputSVGFiles) {
String svgFilename = inputSVGFile.getName();
if (!svgFilename.endsWith(SdkConstants.DOT_SVG)) {
continue;
}
String svgFilenameWithoutExtension = svgFilename.substring(0,
svgFilename.lastIndexOf('.'));
File outputFile = new File(outputDir,
svgFilenameWithoutExtension + SdkConstants.DOT_XML);
allOutputFiles.add(outputFile);
try {
ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream();
String error = Svg2Vector.parseSvgToXml(inputSVGFile, byteArrayOutStream);
if (!error.isEmpty()) {
errorSvgFileCounter++;
System.err.println("error is " + error);
if (DBG_COPY_BROKEN_SVG) {
// Copy the broken svg file in the same directory but with a new extension.
String brokenFileName = svgFilename + BROKEN_FILE_EXTENSION;
File brokenSvgFile = new File(outputDir, brokenFileName);
Files.copy(inputSVGFile, brokenSvgFile);
}
}
// Override the size info if needed. Negative value will be ignored.
String vectorXmlContent = byteArrayOutStream.toString();
if (options.getForceHeight() > 0 || options.getForceWidth() > 0) {
Document vdDocument = parseVdStringIntoDocument(vectorXmlContent, null);
if (vdDocument != null) {
VdOverrideInfo info = new VdOverrideInfo(options.getForceWidth(),
options.getForceHeight(), null, 1,
false /*auto mirrored*/);
vectorXmlContent = VdPreview.overrideXmlContent(vdDocument, info, null);
}
}
if (options.isAddHeader()) {
vectorXmlContent = AOSP_HEADER + vectorXmlContent;
}
// Write the final result into the output XML file.
try (PrintWriter writer = new PrintWriter(outputFile)) {
writer.print(vectorXmlContent);
}
} catch (Exception e) {
System.err.println("exception" + e.getMessage());
e.printStackTrace();
}
totalSvgFileCounter++;
}
System.out.println("Convert " + totalSvgFileCounter + " SVG files in total, errors found in "
+ errorSvgFileCounter + " files");
return allOutputFiles.toArray(new File[0]);
}
/**
* Parses a vector drawable XML file into a {@link Document} object.
*
* @param xmlFileContent the content of the VectorDrawable's XML file.
* @param errorLog when errors were found, log them in this builder if it is not null.
* @return parsed document or null if errors happened.
*/
@Nullable
private static Document parseVdStringIntoDocument(
@NonNull String xmlFileContent, @Nullable StringBuilder errorLog) {
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
return db.parse(new InputSource(new StringReader(xmlFileContent)));
} catch (Exception e) {
if (errorLog != null) {
errorLog.append("Exception while parsing XML file:\n").append(e.getMessage());
}
return null;
}
}
}