public static String transform()

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.
    }
  }