in android/src/com/android/tools/idea/rendering/VectorDrawableTransformer.java [90:309]
public static String transform(@NotNull String originalDrawable,
@NotNull Dimension targetSize,
@NotNull Gravity gravity,
double scaleFactor,
@Nullable Rectangle2D clipRectangle,
@Nullable Point2D shift,
@Nullable Color tint,
double opacity,
boolean addClipPath) {
KXmlParser parser = new KXmlParser();
try {
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(CharSequences.getReader(originalDrawable, true));
int startLine = 1;
int startColumn = 1;
int token;
while ((token = parser.nextToken()) != XmlPullParser.END_DOCUMENT && token != XmlPullParser.START_TAG) {
startLine = parser.getLineNumber();
startColumn = parser.getColumnNumber();
}
// Skip to the first tag.
if (parser.getEventType() != XmlPullParser.START_TAG || !"vector".equals(parser.getName()) || parser.getPrefix() != null) {
return originalDrawable; // Not a vector drawable.
}
String originalTintValue = parser.getAttributeValue(ANDROID_URI, "tint");
String tintValue = tint == null ? originalTintValue : ResourcesUtil.colorToString(tint);
String originalAlphaValue = parser.getAttributeValue(ANDROID_URI, "alpha");
if (originalAlphaValue != null) {
opacity *= parseDoubleValue(originalAlphaValue, "");
}
String alphaValue = formatFloatValue(opacity);
if (alphaValue.equals("1")) {
alphaValue = null; // No need to set the default opacity.
}
double targetWidth = targetSize.getWidth();
double targetHeight = targetSize.getHeight();
double width = targetWidth;
double height = targetHeight;
double originalViewportWidth = getDoubleAttributeValue(parser, ANDROID_URI, "viewportWidth", "");
double originalViewportHeight = getDoubleAttributeValue(parser, ANDROID_URI, "viewportHeight", "");
String widthValue = parser.getAttributeValue(ANDROID_URI, "width");
if (widthValue != null) {
String suffix = getSuffix(widthValue);
width = getDoubleAttributeValue(parser, ANDROID_URI, "width", suffix);
height = getDoubleAttributeValue(parser, ANDROID_URI, "height", suffix);
//noinspection FloatingPointEquality -- safe in this context since all integer values are representable as double.
if (suffix.equals("dp") && width == targetWidth && height == targetHeight &&
originalViewportWidth == targetWidth && originalViewportHeight == targetHeight &&
scaleFactor == 1 && clipRectangle == null &&
Objects.equals(tintValue, originalTintValue) && Objects.equals(alphaValue, originalAlphaValue)) {
return originalDrawable; // No transformation is needed.
}
if (Double.isNaN(width) || width == 0 || Double.isNaN(height) || height == 0) {
width = targetWidth;
height = targetHeight;
}
}
if (Double.isNaN(originalViewportWidth) || originalViewportWidth == 0 ||
Double.isNaN(originalViewportHeight) || originalViewportHeight == 0) {
originalViewportWidth = width;
originalViewportHeight = height;
}
// Components of the translation vector in viewport coordinates.
double x = 0;
double y = 0;
if (clipRectangle != null) {
// Adjust scale.
scaleFactor /= Math.max(clipRectangle.getWidth(), clipRectangle.getHeight());
// Re-center the image relative to the clip rectangle.
x += (0.5 - clipRectangle.getCenterX()) * originalViewportWidth * scaleFactor;
y += (0.5 - clipRectangle.getCenterY()) * originalViewportHeight * scaleFactor;
}
double scaleFactorX = scaleFactor;
double scaleFactorY = scaleFactor;
double originalAspectRatio = width / height;
double targetAspectRatio = targetWidth / targetHeight;
double aspectFactor = originalAspectRatio / targetAspectRatio;
if (aspectFactor < 1.0) {
// The image was stretched horizontally, modify the scale factor to restore the original shape
scaleFactorX *= aspectFactor;
} else if (aspectFactor > 1.0) {
// The image was stretched vertically, modify the scale factor to restore the original shape
scaleFactorY /= aspectFactor;
}
// Recenter after scaling
x += originalViewportWidth * ((1.0 - scaleFactorX) / 2.0);
y += originalViewportHeight * ((1.0 - scaleFactorY) / 2.0);
if (shift != null) {
x += originalViewportWidth * shift.getX();
y += originalViewportHeight * shift.getY();
}
StringBuilder result = new StringBuilder(originalDrawable.length() + originalDrawable.length() / 8);
Indenter indenter = new Indenter(originalDrawable);
// Copy contents before the first element.
indenter.copy(1, 1, startLine, startColumn, "", result);
String lineSeparator = detectLineSeparator(originalDrawable);
// Output the "vector" element with the xmlns:android attribute.
result.append(String.format("<vector %s:%s=\"%s\"", SdkConstants.XMLNS, SdkConstants.ANDROID_NS_NAME, ANDROID_URI));
// Copy remaining namespace attributes.
for (int i = 0; i < parser.getNamespaceCount(1); i++) {
String prefix = parser.getNamespacePrefix(i);
String uri = parser.getNamespaceUri(i);
if (!SdkConstants.ANDROID_NS_NAME.equals(prefix) || !ANDROID_URI.equals(uri)) {
result.append(String.format("%s%s%s:%s=\"%s\"", lineSeparator, DOUBLE_INDENT, SdkConstants.XMLNS, prefix, uri));
}
}
result.append(String.format("%s%sandroid:width=\"%sdp\"", lineSeparator, DOUBLE_INDENT, formatFloatValue(targetWidth)));
result.append(String.format("%s%sandroid:height=\"%sdp\"", lineSeparator, DOUBLE_INDENT, formatFloatValue(targetHeight)));
result.append(String.format("%s%sandroid:viewportWidth=\"%s\"", lineSeparator, DOUBLE_INDENT,
formatFloatValue(originalViewportWidth)));
result.append(String.format("%s%sandroid:viewportHeight=\"%s\"", lineSeparator, DOUBLE_INDENT,
formatFloatValue(originalViewportHeight)));
if (tintValue != null) {
result.append(String.format("%s%sandroid:tint=\"%s\"", lineSeparator, DOUBLE_INDENT, tintValue));
}
if (alphaValue != null) {
result.append(String.format("%s%sandroid:alpha=\"%s\"", lineSeparator, DOUBLE_INDENT, alphaValue));
}
// Copy remaining attributes.
for (int i = 0; i < parser.getAttributeCount(); i++) {
String prefix = parser.getAttributePrefix(i);
String name = parser.getAttributeName(i);
if (!SdkConstants.ANDROID_NS_NAME.equals(prefix) || !NAMES_OF_HANDLED_ATTRIBUTES.contains(name)) {
if (prefix != null) {
name = prefix + ':' + name;
}
result.append(String.format("%s%s%s=\"%s\"", lineSeparator, DOUBLE_INDENT, name, parser.getAttributeValue(i)));
}
}
result.append('>');
String indent = "";
int copyDepth = 2;
startLine = parser.getLineNumber();
startColumn = parser.getColumnNumber();
String translateX = isSignificantlyDifferentFromZero(x / targetWidth) ? formatFloatValue(x) : null;
String translateY = isSignificantlyDifferentFromZero(y / targetHeight) ? formatFloatValue(y) : null;
String scaleX = formatFloatValue(scaleFactorX);
String scaleY = formatFloatValue(scaleFactorY);
String clipX = formatFloatValue(originalViewportWidth);
String clipY = formatFloatValue(originalViewportHeight);
boolean adjustmentNeeded = !scaleX.equals("1") || !scaleY.equals("1") || translateX != null || translateY != null;
if (adjustmentNeeded) {
// Wrap contents of the drawable into a translation group.
result.append(lineSeparator).append(INDENT);
result.append("<group");
String delimiter = " ";
if (!scaleX.equals("1")) {
result.append(String.format("%sandroid:scaleX=\"%s\"", delimiter, scaleX));
delimiter = lineSeparator + INDENT + DOUBLE_INDENT;
}
if (!scaleY.equals("1")) {
result.append(String.format("%sandroid:scaleY=\"%s\"", delimiter, scaleY));
delimiter = lineSeparator + INDENT + DOUBLE_INDENT;
}
if (translateX != null) {
result.append(String.format("%sandroid:translateX=\"%s\"", delimiter, translateX));
delimiter = lineSeparator + INDENT + DOUBLE_INDENT;
}
if (translateY != null) {
result.append(String.format("%sandroid:translateY=\"%s\"", delimiter, translateY));
}
result.append('>');
if (addClipPath) {
// Clip to viewport since the new size may have space for more:
result.append(lineSeparator).append(DOUBLE_INDENT);
result.append(String.format("<clip-path android:pathData=\"M0,0 L0,%s L%s,%s L%s,0 z\"/>", clipY, clipX, clipY, clipX));
}
indent = INDENT;
}
// Copy contents before the </vector> tag.
while ((token = parser.nextToken()) != XmlPullParser.END_DOCUMENT && token != XmlPullParser.END_TAG ||
parser.getDepth() >= copyDepth) {
int endLineNumber = parser.getLineNumber();
int endColumnNumber = parser.getColumnNumber();
indenter.copy(startLine, startColumn, endLineNumber, endColumnNumber, token == XmlPullParser.CDSECT ? "" : indent, result);
startLine = endLineNumber;
startColumn = endColumnNumber;
}
if (startColumn > INDENT.length() + 1) {
result.append(lineSeparator);
startColumn = 1;
}
if (adjustmentNeeded) {
if (startColumn == 1) {
result.append(INDENT);
}
result.append(String.format("</group>%s", lineSeparator));
}
// Copy the closing </group> tag, the </vector> tag and the remainder of the document.
for (; token != XmlPullParser.END_DOCUMENT; token = parser.nextToken()) {
int endLineNumber = parser.getLineNumber();
int endColumnNumber = parser.getColumnNumber();
indenter.copy(startLine, startColumn, endLineNumber, endColumnNumber, "", result);
startLine = endLineNumber;
startColumn = endColumnNumber;
}
return result.toString();
}
catch (XmlPullParserException | IOException e) {
return originalDrawable; // Ignore and return the original drawable.
}
}