2019-12-31 14:08:08 +01:00
|
|
|
/*
|
|
|
|
Modified by Nikolai Kudashov
|
|
|
|
Changed drawing to Bitmap instead of Picture
|
|
|
|
Added styles support
|
|
|
|
Fixed some float parsing issues
|
|
|
|
Removed gradients support
|
|
|
|
|
|
|
|
Copyright Larva Labs, LLC
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
package org.telegram.messenger;
|
2019-12-31 14:08:08 +01:00
|
|
|
|
|
|
|
import android.graphics.Bitmap;
|
2020-12-24 06:36:01 +01:00
|
|
|
import android.graphics.BitmapShader;
|
2019-12-31 14:08:08 +01:00
|
|
|
import android.graphics.Canvas;
|
|
|
|
import android.graphics.Color;
|
2020-10-30 11:26:29 +01:00
|
|
|
import android.graphics.ColorFilter;
|
2020-12-23 08:48:30 +01:00
|
|
|
import android.graphics.ComposeShader;
|
|
|
|
import android.graphics.LinearGradient;
|
2019-12-31 14:08:08 +01:00
|
|
|
import android.graphics.Matrix;
|
|
|
|
import android.graphics.Paint;
|
|
|
|
import android.graphics.Path;
|
2020-10-30 11:26:29 +01:00
|
|
|
import android.graphics.PixelFormat;
|
2020-12-23 08:48:30 +01:00
|
|
|
import android.graphics.PorterDuff;
|
2020-10-30 11:26:29 +01:00
|
|
|
import android.graphics.Rect;
|
2019-12-31 14:08:08 +01:00
|
|
|
import android.graphics.RectF;
|
2020-12-23 08:48:30 +01:00
|
|
|
import android.graphics.Shader;
|
2020-10-30 11:26:29 +01:00
|
|
|
import android.graphics.drawable.Drawable;
|
2020-12-24 06:36:01 +01:00
|
|
|
import android.os.Build;
|
2022-11-05 13:34:47 +01:00
|
|
|
import android.util.SparseArray;
|
2019-12-31 14:08:08 +01:00
|
|
|
|
2022-12-30 13:32:20 +01:00
|
|
|
import androidx.core.graphics.ColorUtils;
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
import org.telegram.ui.ActionBar.Theme;
|
2022-11-12 09:41:35 +01:00
|
|
|
import org.telegram.ui.Components.DrawingInBackgroundThreadDrawable;
|
2019-12-31 14:08:08 +01:00
|
|
|
import org.xml.sax.Attributes;
|
|
|
|
import org.xml.sax.InputSource;
|
|
|
|
import org.xml.sax.XMLReader;
|
|
|
|
import org.xml.sax.helpers.DefaultHandler;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
2021-06-25 02:43:10 +02:00
|
|
|
import java.io.InputStream;
|
2020-09-30 15:48:47 +02:00
|
|
|
import java.io.StringReader;
|
2020-12-23 08:48:30 +01:00
|
|
|
import java.lang.ref.WeakReference;
|
2019-12-31 14:08:08 +01:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
|
|
import javax.xml.parsers.SAXParser;
|
|
|
|
import javax.xml.parsers.SAXParserFactory;
|
|
|
|
|
|
|
|
public class SvgHelper {
|
|
|
|
|
2020-10-30 11:26:29 +01:00
|
|
|
private static class Line {
|
|
|
|
float x1, y1, x2, y2;
|
|
|
|
|
|
|
|
public Line(float x1, float y1, float x2, float y2) {
|
|
|
|
this.x1 = x1;
|
|
|
|
this.y1 = y1;
|
|
|
|
this.x2 = x2;
|
|
|
|
this.y2 = y2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-12 17:23:51 +02:00
|
|
|
public static class Circle {
|
2020-10-30 11:26:29 +01:00
|
|
|
float x1, y1, rad;
|
|
|
|
|
|
|
|
public Circle(float x1, float y1, float rad) {
|
|
|
|
this.x1 = x1;
|
|
|
|
this.y1 = y1;
|
|
|
|
this.rad = rad;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class Oval {
|
|
|
|
RectF rect;
|
|
|
|
|
|
|
|
public Oval(RectF rect) {
|
|
|
|
this.rect = rect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
private static class RoundRect {
|
|
|
|
RectF rect;
|
|
|
|
float rx;
|
|
|
|
public RoundRect(RectF rect, float rx) {
|
|
|
|
this.rect = rect;
|
|
|
|
this.rx = rx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-30 11:26:29 +01:00
|
|
|
public static class SvgDrawable extends Drawable {
|
|
|
|
|
2021-06-25 02:43:10 +02:00
|
|
|
protected ArrayList<Object> commands = new ArrayList<>();
|
|
|
|
protected HashMap<Object, Paint> paints = new HashMap<>();
|
2022-06-21 04:51:00 +02:00
|
|
|
private Paint overridePaint;
|
2022-08-12 17:23:51 +02:00
|
|
|
private Paint backgroundPaint;
|
2021-06-25 02:43:10 +02:00
|
|
|
protected int width;
|
|
|
|
protected int height;
|
2020-12-23 08:48:30 +01:00
|
|
|
private static int[] parentPosition = new int[2];
|
|
|
|
|
2022-11-12 09:41:35 +01:00
|
|
|
private Bitmap[] backgroundBitmap = new Bitmap[1 + DrawingInBackgroundThreadDrawable.THREAD_COUNT];
|
|
|
|
private Canvas[] backgroundCanvas = new Canvas[1 + DrawingInBackgroundThreadDrawable.THREAD_COUNT];
|
|
|
|
private LinearGradient[] placeholderGradient = new LinearGradient[1 + DrawingInBackgroundThreadDrawable.THREAD_COUNT];
|
|
|
|
private Matrix[] placeholderMatrix = new Matrix[1 + DrawingInBackgroundThreadDrawable.THREAD_COUNT];
|
2020-12-23 08:48:30 +01:00
|
|
|
private static float totalTranslation;
|
|
|
|
private static float gradientWidth;
|
|
|
|
private static long lastUpdateTime;
|
|
|
|
private static Runnable shiftRunnable;
|
|
|
|
private static WeakReference<Drawable> shiftDrawable;
|
|
|
|
private ImageReceiver parentImageReceiver;
|
2022-08-12 17:23:51 +02:00
|
|
|
private int[] currentColor = new int[2];
|
2020-12-23 08:48:30 +01:00
|
|
|
private String currentColorKey;
|
2022-11-06 02:16:18 +01:00
|
|
|
private Integer overrideColor;
|
2022-08-12 17:23:51 +02:00
|
|
|
private Theme.ResourcesProvider currentResourcesProvider;
|
2020-12-23 08:48:30 +01:00
|
|
|
private float colorAlpha;
|
2021-03-19 11:25:58 +01:00
|
|
|
private float crossfadeAlpha = 1.0f;
|
2022-11-05 13:34:47 +01:00
|
|
|
SparseArray<Paint> overridePaintByPosition = new SparseArray<>();
|
2020-10-30 11:26:29 +01:00
|
|
|
|
2021-08-31 21:06:39 +02:00
|
|
|
private boolean aspectFill = true;
|
2022-11-06 02:16:18 +01:00
|
|
|
private boolean aspectCenter = false;
|
2021-08-31 21:06:39 +02:00
|
|
|
|
2021-01-01 05:38:45 +01:00
|
|
|
@Override
|
|
|
|
public int getIntrinsicHeight() {
|
|
|
|
return width;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getIntrinsicWidth() {
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
|
2021-08-31 21:06:39 +02:00
|
|
|
public void setAspectFill(boolean value) {
|
|
|
|
aspectFill = value;
|
|
|
|
}
|
|
|
|
|
2022-11-06 02:16:18 +01:00
|
|
|
public void setAspectCenter(boolean value) {
|
|
|
|
aspectCenter = value;
|
|
|
|
}
|
|
|
|
|
2021-08-31 21:06:39 +02:00
|
|
|
public void overrideWidthAndHeight(int w, int h) {
|
|
|
|
width = w;
|
|
|
|
height = h;
|
|
|
|
}
|
|
|
|
|
2020-10-30 11:26:29 +01:00
|
|
|
@Override
|
|
|
|
public void draw(Canvas canvas) {
|
2022-11-12 09:41:35 +01:00
|
|
|
drawInternal(canvas, false, 0, System.currentTimeMillis(), getBounds().left, getBounds().top, getBounds().width(), getBounds().height());
|
2022-08-12 17:23:51 +02:00
|
|
|
}
|
|
|
|
|
2022-11-12 09:41:35 +01:00
|
|
|
public void drawInternal(Canvas canvas, boolean drawInBackground, int threadIndex, long time, float x, float y, float w, float h) {
|
2020-12-23 08:48:30 +01:00
|
|
|
if (currentColorKey != null) {
|
2022-08-12 17:23:51 +02:00
|
|
|
setupGradient(currentColorKey, currentResourcesProvider, colorAlpha, drawInBackground);
|
2020-12-23 08:48:30 +01:00
|
|
|
}
|
2022-08-12 17:23:51 +02:00
|
|
|
|
|
|
|
float scale = getScale((int) w, (int) h);
|
2022-12-30 13:32:20 +01:00
|
|
|
if (placeholderGradient[threadIndex] != null && gradientWidth > 0 && !SharedConfig.getLiteMode().enabled()) {
|
2022-08-12 17:23:51 +02:00
|
|
|
if (drawInBackground) {
|
|
|
|
long dt = time - lastUpdateTime;
|
|
|
|
if (dt > 64) {
|
|
|
|
dt = 64;
|
|
|
|
}
|
|
|
|
if (dt > 0) {
|
|
|
|
lastUpdateTime = time;
|
|
|
|
totalTranslation += dt * gradientWidth / 1800.0f;
|
|
|
|
while (totalTranslation >= gradientWidth * 2) {
|
|
|
|
totalTranslation -= gradientWidth * 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (shiftRunnable == null || shiftDrawable.get() == this) {
|
|
|
|
long dt = time - lastUpdateTime;
|
|
|
|
if (dt > 64) {
|
|
|
|
dt = 64;
|
|
|
|
}
|
|
|
|
if (dt < 0) {
|
|
|
|
dt = 0;
|
|
|
|
}
|
|
|
|
lastUpdateTime = time;
|
|
|
|
totalTranslation += dt * gradientWidth / 1800.0f;
|
|
|
|
while (totalTranslation >= gradientWidth / 2) {
|
|
|
|
totalTranslation -= gradientWidth;
|
|
|
|
}
|
|
|
|
shiftDrawable = new WeakReference<>(this);
|
|
|
|
if (shiftRunnable != null) {
|
|
|
|
AndroidUtilities.cancelRunOnUIThread(shiftRunnable);
|
|
|
|
}
|
|
|
|
AndroidUtilities.runOnUIThread(shiftRunnable = () -> shiftRunnable = null, (int) (1000 / AndroidUtilities.screenRefreshRate) - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int offset;
|
|
|
|
if (parentImageReceiver != null && !drawInBackground) {
|
|
|
|
parentImageReceiver.getParentPosition(parentPosition);
|
|
|
|
offset = parentPosition[0];
|
|
|
|
} else {
|
|
|
|
offset = 0;
|
|
|
|
}
|
|
|
|
|
2022-11-12 09:41:35 +01:00
|
|
|
int index = drawInBackground ? 1 + threadIndex : 0;
|
2022-08-12 17:23:51 +02:00
|
|
|
if (placeholderMatrix[index] != null) {
|
|
|
|
placeholderMatrix[index].reset();
|
|
|
|
if (drawInBackground) {
|
|
|
|
placeholderMatrix[index].postTranslate(-offset + totalTranslation - x, 0);
|
|
|
|
} else {
|
|
|
|
placeholderMatrix[index].postTranslate(-offset + totalTranslation - x, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
placeholderMatrix[index].postScale(1.0f / scale, 1.0f / scale);
|
|
|
|
placeholderGradient[index].setLocalMatrix(placeholderMatrix[index]);
|
|
|
|
|
|
|
|
if (parentImageReceiver != null && !drawInBackground) {
|
|
|
|
parentImageReceiver.invalidate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
canvas.save();
|
2022-08-12 17:23:51 +02:00
|
|
|
canvas.translate(x, y);
|
2022-11-06 02:16:18 +01:00
|
|
|
if (!aspectFill || aspectCenter) {
|
2022-08-12 17:23:51 +02:00
|
|
|
canvas.translate((w - width * scale) / 2, (h - height * scale) / 2);
|
2021-08-31 21:06:39 +02:00
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
canvas.scale(scale, scale);
|
|
|
|
for (int a = 0, N = commands.size(); a < N; a++) {
|
|
|
|
Object object = commands.get(a);
|
|
|
|
if (object instanceof Matrix) {
|
|
|
|
canvas.save();
|
2022-11-05 13:34:47 +01:00
|
|
|
canvas.concat((Matrix) object);
|
2020-10-30 11:26:29 +01:00
|
|
|
} else if (object == null) {
|
|
|
|
canvas.restore();
|
|
|
|
} else {
|
2022-06-21 04:51:00 +02:00
|
|
|
Paint paint;
|
2022-11-05 13:34:47 +01:00
|
|
|
Paint overridePaint = overridePaintByPosition.get(a);
|
|
|
|
if (overridePaint == null) {
|
|
|
|
overridePaint = this.overridePaint;
|
|
|
|
}
|
2022-08-12 17:23:51 +02:00
|
|
|
if (drawInBackground) {
|
|
|
|
paint = backgroundPaint;
|
|
|
|
} else if (overridePaint != null) {
|
2022-06-21 04:51:00 +02:00
|
|
|
paint = overridePaint;
|
|
|
|
} else {
|
|
|
|
paint = paints.get(object);
|
|
|
|
}
|
2020-12-23 08:48:30 +01:00
|
|
|
int originalAlpha = paint.getAlpha();
|
|
|
|
paint.setAlpha((int) (crossfadeAlpha * originalAlpha));
|
2020-10-30 11:26:29 +01:00
|
|
|
if (object instanceof Path) {
|
|
|
|
canvas.drawPath((Path) object, paint);
|
|
|
|
} else if (object instanceof Rect) {
|
|
|
|
canvas.drawRect((Rect) object, paint);
|
|
|
|
} else if (object instanceof RectF) {
|
|
|
|
canvas.drawRect((RectF) object, paint);
|
|
|
|
} else if (object instanceof Line) {
|
|
|
|
Line line = (Line) object;
|
|
|
|
canvas.drawLine(line.x1, line.y1, line.x2, line.y2, paint);
|
|
|
|
} else if (object instanceof Circle) {
|
|
|
|
Circle circle = (Circle) object;
|
|
|
|
canvas.drawCircle(circle.x1, circle.y1, circle.rad, paint);
|
|
|
|
} else if (object instanceof Oval) {
|
|
|
|
Oval oval = (Oval) object;
|
|
|
|
canvas.drawOval(oval.rect, paint);
|
2020-12-23 08:48:30 +01:00
|
|
|
} else if (object instanceof RoundRect) {
|
|
|
|
RoundRect rect = (RoundRect) object;
|
|
|
|
canvas.drawRoundRect(rect.rect, rect.rx, rect.rx, paint);
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
2020-12-23 08:48:30 +01:00
|
|
|
paint.setAlpha(originalAlpha);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
canvas.restore();
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
|
|
|
|
2022-08-12 17:23:51 +02:00
|
|
|
public float getScale(int viewWidth, int viewHeight) {
|
|
|
|
float scaleX = viewWidth / (float) width;
|
|
|
|
float scaleY = viewHeight / (float) height;
|
2022-06-21 04:51:00 +02:00
|
|
|
return aspectFill ? Math.max(scaleX, scaleY) : Math.min(scaleX, scaleY);
|
|
|
|
}
|
|
|
|
|
2020-10-30 11:26:29 +01:00
|
|
|
@Override
|
|
|
|
public void setAlpha(int alpha) {
|
2020-12-23 08:48:30 +01:00
|
|
|
crossfadeAlpha = alpha / 255.0f;
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setColorFilter(ColorFilter colorFilter) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getOpacity() {
|
|
|
|
return PixelFormat.TRANSPARENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void addCommand(Object command, Paint paint) {
|
|
|
|
commands.add(command);
|
|
|
|
paints.put(command, new Paint(paint));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void addCommand(Object command) {
|
|
|
|
commands.add(command);
|
|
|
|
}
|
2020-12-23 08:48:30 +01:00
|
|
|
|
|
|
|
public void setParent(ImageReceiver imageReceiver) {
|
|
|
|
parentImageReceiver = imageReceiver;
|
|
|
|
}
|
|
|
|
|
2022-08-12 17:23:51 +02:00
|
|
|
public void setupGradient(String colorKey, float alpha, boolean drawInBackground) {
|
|
|
|
setupGradient(colorKey, null, alpha, drawInBackground);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setupGradient(String colorKey, Theme.ResourcesProvider resourcesProvider, float alpha, boolean drawInBackground) {
|
2022-11-06 02:16:18 +01:00
|
|
|
int color = overrideColor == null ? Theme.getColor(colorKey, resourcesProvider) : overrideColor;
|
2022-08-12 17:23:51 +02:00
|
|
|
int index = drawInBackground ? 1 : 0;
|
|
|
|
currentResourcesProvider = resourcesProvider;
|
|
|
|
if (currentColor[index] != color) {
|
2020-12-23 08:48:30 +01:00
|
|
|
colorAlpha = alpha;
|
|
|
|
currentColorKey = colorKey;
|
2022-08-12 17:23:51 +02:00
|
|
|
currentColor[index] = color;
|
2020-12-23 08:48:30 +01:00
|
|
|
gradientWidth = AndroidUtilities.displaySize.x * 2;
|
2022-12-30 13:32:20 +01:00
|
|
|
if (SharedConfig.getLiteMode().enabled()) {
|
|
|
|
int color2 = ColorUtils.setAlphaComponent(currentColor[index], 70);
|
|
|
|
if (drawInBackground) {
|
|
|
|
if (backgroundPaint == null) {
|
|
|
|
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
|
|
}
|
|
|
|
backgroundPaint.setShader(null);
|
|
|
|
backgroundPaint.setColor(color2);
|
|
|
|
} else {
|
|
|
|
for (Paint paint : paints.values()) {
|
|
|
|
paint.setShader(null);
|
|
|
|
paint.setColor(color2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2020-12-23 08:48:30 +01:00
|
|
|
float w = AndroidUtilities.dp(180) / gradientWidth;
|
|
|
|
color = Color.argb((int) (Color.alpha(color) / 2 * colorAlpha), Color.red(color), Color.green(color), Color.blue(color));
|
|
|
|
float centerX = (1.0f - w) / 2;
|
2022-08-12 17:23:51 +02:00
|
|
|
placeholderGradient[index] = new LinearGradient(0, 0, gradientWidth, 0, new int[]{0x00000000, 0x00000000, color, 0x00000000, 0x00000000}, new float[]{0.0f, centerX - w / 2.0f, centerX, centerX + w / 2.0f, 1.0f}, Shader.TileMode.REPEAT);
|
2021-09-20 07:54:41 +02:00
|
|
|
Shader backgroundGradient;
|
2020-12-24 06:36:01 +01:00
|
|
|
if (Build.VERSION.SDK_INT >= 28) {
|
|
|
|
backgroundGradient = new LinearGradient(0, 0, gradientWidth, 0, new int[]{color, color}, null, Shader.TileMode.REPEAT);
|
|
|
|
} else {
|
2022-08-12 17:23:51 +02:00
|
|
|
if (backgroundBitmap[index] == null) {
|
|
|
|
backgroundBitmap[index] = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
|
|
|
|
backgroundCanvas[index] = new Canvas(backgroundBitmap[index]);
|
2020-12-24 06:36:01 +01:00
|
|
|
}
|
2022-08-12 17:23:51 +02:00
|
|
|
backgroundCanvas[index].drawColor(color);
|
|
|
|
backgroundGradient = new BitmapShader(backgroundBitmap[index], Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
|
2020-12-24 06:36:01 +01:00
|
|
|
}
|
2022-08-12 17:23:51 +02:00
|
|
|
placeholderMatrix[index] = new Matrix();
|
|
|
|
placeholderGradient[index].setLocalMatrix(placeholderMatrix[index]);
|
|
|
|
if (drawInBackground) {
|
|
|
|
if (backgroundPaint == null) {
|
|
|
|
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
|
|
}
|
2021-04-14 03:44:46 +02:00
|
|
|
if (Build.VERSION.SDK_INT <= 22) {
|
2022-08-12 17:23:51 +02:00
|
|
|
backgroundPaint.setShader(backgroundGradient);
|
2021-04-14 03:44:46 +02:00
|
|
|
} else {
|
2022-08-12 17:23:51 +02:00
|
|
|
backgroundPaint.setShader(new ComposeShader(placeholderGradient[index], backgroundGradient, PorterDuff.Mode.ADD));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (Paint paint : paints.values()) {
|
|
|
|
if (Build.VERSION.SDK_INT <= 22) {
|
|
|
|
paint.setShader(backgroundGradient);
|
|
|
|
} else {
|
|
|
|
paint.setShader(new ComposeShader(placeholderGradient[index], backgroundGradient, PorterDuff.Mode.ADD));
|
|
|
|
}
|
2021-04-14 03:44:46 +02:00
|
|
|
}
|
2020-12-23 08:48:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-21 04:51:00 +02:00
|
|
|
|
2022-11-06 02:16:18 +01:00
|
|
|
public void setColorKey(String colorKey) {
|
|
|
|
currentColorKey = colorKey;
|
|
|
|
}
|
|
|
|
|
2022-11-12 09:41:35 +01:00
|
|
|
public void setColorKey(String colorKey, Theme.ResourcesProvider resourcesProvider) {
|
|
|
|
currentColorKey = colorKey;
|
|
|
|
currentResourcesProvider = resourcesProvider;
|
|
|
|
}
|
|
|
|
|
2022-11-06 02:16:18 +01:00
|
|
|
public void setColor(int color) {
|
|
|
|
overrideColor = color;
|
|
|
|
}
|
|
|
|
|
2022-06-21 04:51:00 +02:00
|
|
|
public void setPaint(Paint paint) {
|
|
|
|
overridePaint = paint;
|
|
|
|
}
|
2022-11-05 13:34:47 +01:00
|
|
|
|
|
|
|
public void setPaint(Paint paint, int position) {
|
|
|
|
overridePaintByPosition.put(position, paint);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void copyCommandFromPosition(int position) {
|
|
|
|
commands.add(commands.get(position));
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
|
|
|
|
2021-06-25 02:43:10 +02:00
|
|
|
public static Bitmap getBitmap(int res, int width, int height, int color) {
|
2021-12-07 14:02:02 +01:00
|
|
|
return getBitmap(res, width, height, color, 1f);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Bitmap getBitmap(int res, int width, int height, int color, float scale) {
|
2021-06-25 02:43:10 +02:00
|
|
|
try (InputStream stream = ApplicationLoader.applicationContext.getResources().openRawResource(res)) {
|
|
|
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
|
|
SAXParser sp = spf.newSAXParser();
|
|
|
|
XMLReader xr = sp.getXMLReader();
|
2021-12-07 14:02:02 +01:00
|
|
|
SVGHandler handler = new SVGHandler(width, height, color, false, scale);
|
2021-06-25 02:43:10 +02:00
|
|
|
xr.setContentHandler(handler);
|
|
|
|
xr.parse(new InputSource(stream));
|
|
|
|
return handler.getBitmap();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-31 14:08:08 +01:00
|
|
|
public static Bitmap getBitmap(File file, int width, int height, boolean white) {
|
|
|
|
try (FileInputStream stream = new FileInputStream(file)) {
|
|
|
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
|
|
SAXParser sp = spf.newSAXParser();
|
|
|
|
XMLReader xr = sp.getXMLReader();
|
2021-12-07 14:02:02 +01:00
|
|
|
SVGHandler handler = new SVGHandler(width, height, white ? 0xffffffff : null, false, 1f);
|
2019-12-31 14:08:08 +01:00
|
|
|
xr.setContentHandler(handler);
|
|
|
|
xr.parse(new InputSource(stream));
|
|
|
|
return handler.getBitmap();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
2020-09-30 15:48:47 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Bitmap getBitmap(String xml, int width, int height, boolean white) {
|
|
|
|
try {
|
|
|
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
|
|
SAXParser sp = spf.newSAXParser();
|
|
|
|
XMLReader xr = sp.getXMLReader();
|
2021-12-07 14:02:02 +01:00
|
|
|
SVGHandler handler = new SVGHandler(width, height, white ? 0xffffffff : null, false, 1f);
|
2020-09-30 15:48:47 +02:00
|
|
|
xr.setContentHandler(handler);
|
|
|
|
xr.parse(new InputSource(new StringReader(xml)));
|
|
|
|
return handler.getBitmap();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-30 11:26:29 +01:00
|
|
|
public static SvgDrawable getDrawable(String xml) {
|
|
|
|
try {
|
|
|
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
|
|
SAXParser sp = spf.newSAXParser();
|
|
|
|
XMLReader xr = sp.getXMLReader();
|
2021-12-07 14:02:02 +01:00
|
|
|
SVGHandler handler = new SVGHandler(0, 0, null, true, 1f);
|
2020-10-30 11:26:29 +01:00
|
|
|
xr.setContentHandler(handler);
|
|
|
|
xr.parse(new InputSource(new StringReader(xml)));
|
|
|
|
return handler.getDrawable();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-30 13:32:20 +01:00
|
|
|
public static SvgDrawable getDrawable(int resId, Integer color) {
|
2021-06-25 02:43:10 +02:00
|
|
|
try {
|
|
|
|
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
|
|
SAXParser sp = spf.newSAXParser();
|
|
|
|
XMLReader xr = sp.getXMLReader();
|
2021-12-07 14:02:02 +01:00
|
|
|
SVGHandler handler = new SVGHandler(0, 0, color, true, 1f);
|
2021-06-25 02:43:10 +02:00
|
|
|
xr.setContentHandler(handler);
|
|
|
|
xr.parse(new InputSource(ApplicationLoader.applicationContext.getResources().openRawResource(resId)));
|
|
|
|
return handler.getDrawable();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-23 08:48:30 +01:00
|
|
|
public static SvgDrawable getDrawableByPath(String pathString, int w, int h) {
|
|
|
|
try {
|
|
|
|
Path path = doPath(pathString);
|
|
|
|
SvgDrawable drawable = new SvgDrawable();
|
|
|
|
drawable.commands.add(path);
|
|
|
|
drawable.paints.put(path, new Paint(Paint.ANTI_ALIAS_FLAG));
|
|
|
|
drawable.width = w;
|
|
|
|
drawable.height = h;
|
|
|
|
return drawable;
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-30 15:48:47 +02:00
|
|
|
public static Bitmap getBitmapByPathOnly(String pathString, int svgWidth, int svgHeight, int width, int height) {
|
|
|
|
try {
|
|
|
|
Path path = doPath(pathString);
|
|
|
|
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
|
|
Canvas canvas = new Canvas(bitmap);
|
|
|
|
canvas.scale(width / (float) svgWidth, height / (float) svgHeight);
|
|
|
|
Paint paint = new Paint();
|
|
|
|
paint.setColor(Color.WHITE);
|
|
|
|
canvas.drawPath(path,paint);
|
|
|
|
return bitmap;
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
return null;
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static NumberParse parseNumbers(String s) {
|
|
|
|
int n = s.length();
|
|
|
|
int p = 0;
|
|
|
|
ArrayList<Float> numbers = new ArrayList<>();
|
|
|
|
boolean skipChar = false;
|
|
|
|
for (int i = 1; i < n; i++) {
|
|
|
|
if (skipChar) {
|
|
|
|
skipChar = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
char c = s.charAt(i);
|
|
|
|
switch (c) {
|
|
|
|
case 'M':
|
|
|
|
case 'm':
|
|
|
|
case 'Z':
|
|
|
|
case 'z':
|
|
|
|
case 'L':
|
|
|
|
case 'l':
|
|
|
|
case 'H':
|
|
|
|
case 'h':
|
|
|
|
case 'V':
|
|
|
|
case 'v':
|
|
|
|
case 'C':
|
|
|
|
case 'c':
|
|
|
|
case 'S':
|
|
|
|
case 's':
|
|
|
|
case 'Q':
|
|
|
|
case 'q':
|
|
|
|
case 'T':
|
|
|
|
case 't':
|
|
|
|
case 'a':
|
|
|
|
case 'A':
|
|
|
|
case ')': {
|
|
|
|
String str = s.substring(p, i);
|
|
|
|
if (str.trim().length() > 0) {
|
|
|
|
Float f = Float.parseFloat(str);
|
|
|
|
numbers.add(f);
|
|
|
|
}
|
|
|
|
p = i;
|
|
|
|
return new NumberParse(numbers, p);
|
|
|
|
}
|
|
|
|
case '\n':
|
|
|
|
case '\t':
|
|
|
|
case ' ':
|
|
|
|
case ',':
|
|
|
|
case '-': {
|
|
|
|
if (c == '-' && s.charAt(i - 1) == 'e') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
String str = s.substring(p, i);
|
|
|
|
if (str.trim().length() > 0) {
|
|
|
|
Float f = Float.parseFloat(str);
|
|
|
|
numbers.add(f);
|
|
|
|
if (c == '-') {
|
|
|
|
p = i;
|
|
|
|
} else {
|
|
|
|
p = i + 1;
|
|
|
|
skipChar = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
String last = s.substring(p);
|
|
|
|
if (last.length() > 0) {
|
|
|
|
try {
|
|
|
|
numbers.add(Float.parseFloat(last));
|
|
|
|
} catch (NumberFormatException ignore) {
|
|
|
|
|
|
|
|
}
|
|
|
|
p = s.length();
|
|
|
|
}
|
|
|
|
return new NumberParse(numbers, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Matrix parseTransform(String s) {
|
|
|
|
if (s.startsWith("matrix(")) {
|
|
|
|
NumberParse np = parseNumbers(s.substring("matrix(".length()));
|
|
|
|
if (np.numbers.size() == 6) {
|
|
|
|
Matrix matrix = new Matrix();
|
|
|
|
matrix.setValues(new float[]{
|
|
|
|
np.numbers.get(0), np.numbers.get(2), np.numbers.get(4),
|
|
|
|
np.numbers.get(1), np.numbers.get(3), np.numbers.get(5),
|
|
|
|
0, 0, 1,
|
|
|
|
});
|
|
|
|
return matrix;
|
|
|
|
}
|
|
|
|
} else if (s.startsWith("translate(")) {
|
|
|
|
NumberParse np = parseNumbers(s.substring("translate(".length()));
|
|
|
|
if (np.numbers.size() > 0) {
|
|
|
|
float tx = np.numbers.get(0);
|
|
|
|
float ty = 0;
|
|
|
|
if (np.numbers.size() > 1) {
|
|
|
|
ty = np.numbers.get(1);
|
|
|
|
}
|
|
|
|
Matrix matrix = new Matrix();
|
|
|
|
matrix.postTranslate(tx, ty);
|
|
|
|
return matrix;
|
|
|
|
}
|
|
|
|
} else if (s.startsWith("scale(")) {
|
|
|
|
NumberParse np = parseNumbers(s.substring("scale(".length()));
|
|
|
|
if (np.numbers.size() > 0) {
|
|
|
|
float sx = np.numbers.get(0);
|
|
|
|
float sy = 0;
|
|
|
|
if (np.numbers.size() > 1) {
|
|
|
|
sy = np.numbers.get(1);
|
|
|
|
}
|
|
|
|
Matrix matrix = new Matrix();
|
|
|
|
matrix.postScale(sx, sy);
|
|
|
|
return matrix;
|
|
|
|
}
|
|
|
|
} else if (s.startsWith("skewX(")) {
|
|
|
|
NumberParse np = parseNumbers(s.substring("skewX(".length()));
|
|
|
|
if (np.numbers.size() > 0) {
|
|
|
|
float angle = np.numbers.get(0);
|
|
|
|
Matrix matrix = new Matrix();
|
|
|
|
matrix.postSkew((float) Math.tan(angle), 0);
|
|
|
|
return matrix;
|
|
|
|
}
|
|
|
|
} else if (s.startsWith("skewY(")) {
|
|
|
|
NumberParse np = parseNumbers(s.substring("skewY(".length()));
|
|
|
|
if (np.numbers.size() > 0) {
|
|
|
|
float angle = np.numbers.get(0);
|
|
|
|
Matrix matrix = new Matrix();
|
|
|
|
matrix.postSkew(0, (float) Math.tan(angle));
|
|
|
|
return matrix;
|
|
|
|
}
|
|
|
|
} else if (s.startsWith("rotate(")) {
|
|
|
|
NumberParse np = parseNumbers(s.substring("rotate(".length()));
|
|
|
|
if (np.numbers.size() > 0) {
|
|
|
|
float angle = np.numbers.get(0);
|
|
|
|
float cx = 0;
|
|
|
|
float cy = 0;
|
|
|
|
if (np.numbers.size() > 2) {
|
|
|
|
cx = np.numbers.get(1);
|
|
|
|
cy = np.numbers.get(2);
|
|
|
|
}
|
|
|
|
Matrix matrix = new Matrix();
|
|
|
|
matrix.postTranslate(cx, cy);
|
|
|
|
matrix.postRotate(angle);
|
|
|
|
matrix.postTranslate(-cx, -cy);
|
|
|
|
return matrix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Path doPath(String s) {
|
|
|
|
int n = s.length();
|
|
|
|
ParserHelper ph = new ParserHelper(s, 0);
|
|
|
|
ph.skipWhitespace();
|
|
|
|
Path p = new Path();
|
|
|
|
float lastX = 0;
|
|
|
|
float lastY = 0;
|
|
|
|
float lastX1 = 0;
|
|
|
|
float lastY1 = 0;
|
|
|
|
float subPathStartX = 0;
|
|
|
|
float subPathStartY = 0;
|
|
|
|
char prevCmd = 0;
|
|
|
|
while (ph.pos < n) {
|
|
|
|
char cmd = s.charAt(ph.pos);
|
|
|
|
switch (cmd) {
|
|
|
|
case '-':
|
|
|
|
case '+':
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
if (prevCmd == 'm' || prevCmd == 'M') {
|
|
|
|
cmd = (char) (((int) prevCmd) - 1);
|
|
|
|
break;
|
|
|
|
} else if (prevCmd == 'c' || prevCmd == 'C') {
|
|
|
|
cmd = prevCmd;
|
|
|
|
break;
|
|
|
|
} else if (prevCmd == 'l' || prevCmd == 'L') {
|
|
|
|
cmd = prevCmd;
|
|
|
|
break;
|
2020-12-23 08:48:30 +01:00
|
|
|
} else if (prevCmd == 's' || prevCmd == 'S') {
|
|
|
|
cmd = prevCmd;
|
|
|
|
break;
|
|
|
|
} else if (prevCmd == 'h' || prevCmd == 'H') {
|
|
|
|
cmd = prevCmd;
|
|
|
|
break;
|
|
|
|
} else if (prevCmd == 'v' || prevCmd == 'V') {
|
|
|
|
cmd = prevCmd;
|
|
|
|
break;
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
default: {
|
|
|
|
ph.advance();
|
|
|
|
prevCmd = cmd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean wasCurve = false;
|
|
|
|
switch (cmd) {
|
|
|
|
case 'M':
|
|
|
|
case 'm': {
|
|
|
|
float x = ph.nextFloat();
|
|
|
|
float y = ph.nextFloat();
|
|
|
|
if (cmd == 'm') {
|
|
|
|
subPathStartX += x;
|
|
|
|
subPathStartY += y;
|
|
|
|
p.rMoveTo(x, y);
|
|
|
|
lastX += x;
|
|
|
|
lastY += y;
|
|
|
|
} else {
|
|
|
|
subPathStartX = x;
|
|
|
|
subPathStartY = y;
|
|
|
|
p.moveTo(x, y);
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'Z':
|
|
|
|
case 'z': {
|
|
|
|
p.close();
|
|
|
|
p.moveTo(subPathStartX, subPathStartY);
|
|
|
|
lastX = subPathStartX;
|
|
|
|
lastY = subPathStartY;
|
|
|
|
lastX1 = subPathStartX;
|
|
|
|
lastY1 = subPathStartY;
|
|
|
|
wasCurve = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'L':
|
|
|
|
case 'l': {
|
|
|
|
float x = ph.nextFloat();
|
|
|
|
float y = ph.nextFloat();
|
|
|
|
if (cmd == 'l') {
|
|
|
|
p.rLineTo(x, y);
|
|
|
|
lastX += x;
|
|
|
|
lastY += y;
|
|
|
|
} else {
|
|
|
|
p.lineTo(x, y);
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'H':
|
|
|
|
case 'h': {
|
|
|
|
float x = ph.nextFloat();
|
|
|
|
if (cmd == 'h') {
|
|
|
|
p.rLineTo(x, 0);
|
|
|
|
lastX += x;
|
|
|
|
} else {
|
|
|
|
p.lineTo(x, lastY);
|
|
|
|
lastX = x;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'V':
|
|
|
|
case 'v': {
|
|
|
|
float y = ph.nextFloat();
|
|
|
|
if (cmd == 'v') {
|
|
|
|
p.rLineTo(0, y);
|
|
|
|
lastY += y;
|
|
|
|
} else {
|
|
|
|
p.lineTo(lastX, y);
|
|
|
|
lastY = y;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'C':
|
|
|
|
case 'c': {
|
|
|
|
wasCurve = true;
|
|
|
|
float x1 = ph.nextFloat();
|
|
|
|
float y1 = ph.nextFloat();
|
|
|
|
float x2 = ph.nextFloat();
|
|
|
|
float y2 = ph.nextFloat();
|
|
|
|
float x = ph.nextFloat();
|
|
|
|
float y = ph.nextFloat();
|
|
|
|
if (cmd == 'c') {
|
|
|
|
x1 += lastX;
|
|
|
|
x2 += lastX;
|
|
|
|
x += lastX;
|
|
|
|
y1 += lastY;
|
|
|
|
y2 += lastY;
|
|
|
|
y += lastY;
|
|
|
|
}
|
|
|
|
p.cubicTo(x1, y1, x2, y2, x, y);
|
|
|
|
lastX1 = x2;
|
|
|
|
lastY1 = y2;
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'S':
|
|
|
|
case 's': {
|
|
|
|
wasCurve = true;
|
|
|
|
float x2 = ph.nextFloat();
|
|
|
|
float y2 = ph.nextFloat();
|
|
|
|
float x = ph.nextFloat();
|
|
|
|
float y = ph.nextFloat();
|
|
|
|
if (cmd == 's') {
|
|
|
|
x2 += lastX;
|
|
|
|
x += lastX;
|
|
|
|
y2 += lastY;
|
|
|
|
y += lastY;
|
|
|
|
}
|
|
|
|
float x1 = 2 * lastX - lastX1;
|
|
|
|
float y1 = 2 * lastY - lastY1;
|
|
|
|
p.cubicTo(x1, y1, x2, y2, x, y);
|
|
|
|
lastX1 = x2;
|
|
|
|
lastY1 = y2;
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'A':
|
|
|
|
case 'a': {
|
|
|
|
float rx = ph.nextFloat();
|
|
|
|
float ry = ph.nextFloat();
|
|
|
|
float theta = ph.nextFloat();
|
|
|
|
int largeArc = (int) ph.nextFloat();
|
|
|
|
int sweepArc = (int) ph.nextFloat();
|
|
|
|
float x = ph.nextFloat();
|
|
|
|
float y = ph.nextFloat();
|
|
|
|
drawArc(p, lastX, lastY, x, y, rx, ry, theta, largeArc, sweepArc);
|
|
|
|
lastX = x;
|
|
|
|
lastY = y;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!wasCurve) {
|
|
|
|
lastX1 = lastX;
|
|
|
|
lastY1 = lastY;
|
|
|
|
}
|
|
|
|
ph.skipWhitespace();
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void drawArc(Path p, float lastX, float lastY, float x, float y, float rx, float ry, float theta, int largeArc, int sweepArc) {
|
|
|
|
// todo - not implemented yet, may be very hard to do using Android drawing facilities.
|
|
|
|
}
|
|
|
|
|
|
|
|
private static NumberParse getNumberParseAttr(String name, Attributes attributes) {
|
|
|
|
int n = attributes.getLength();
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
if (attributes.getLocalName(i).equals(name)) {
|
|
|
|
return parseNumbers(attributes.getValue(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String getStringAttr(String name, Attributes attributes) {
|
|
|
|
int n = attributes.getLength();
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
if (attributes.getLocalName(i).equals(name)) {
|
|
|
|
return attributes.getValue(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Float getFloatAttr(String name, Attributes attributes) {
|
|
|
|
return getFloatAttr(name, attributes, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Float getFloatAttr(String name, Attributes attributes, Float defaultValue) {
|
|
|
|
String v = getStringAttr(name, attributes);
|
|
|
|
if (v == null) {
|
|
|
|
return defaultValue;
|
|
|
|
} else {
|
|
|
|
if (v.endsWith("px")) {
|
|
|
|
v = v.substring(0, v.length() - 2);
|
|
|
|
} else if (v.endsWith("mm")) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return Float.parseFloat(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Integer getHexAttr(String name, Attributes attributes) {
|
|
|
|
String v = getStringAttr(name, attributes);
|
|
|
|
if (v == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
return Integer.parseInt(v.substring(1), 16);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
return getColorByName(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Integer getColorByName(String name) {
|
|
|
|
switch (name.toLowerCase()) {
|
|
|
|
case "black":
|
|
|
|
return Color.BLACK;
|
|
|
|
case "gray":
|
|
|
|
return Color.GRAY;
|
|
|
|
case "red":
|
|
|
|
return Color.RED;
|
|
|
|
case "green":
|
|
|
|
return Color.GREEN;
|
|
|
|
case "blue":
|
|
|
|
return Color.BLUE;
|
|
|
|
case "yellow":
|
|
|
|
return Color.YELLOW;
|
|
|
|
case "cyan":
|
|
|
|
return Color.CYAN;
|
|
|
|
case "magenta":
|
|
|
|
return Color.MAGENTA;
|
|
|
|
case "white":
|
|
|
|
return Color.WHITE;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class NumberParse {
|
|
|
|
private ArrayList<Float> numbers;
|
|
|
|
private int nextCmd;
|
|
|
|
|
|
|
|
public NumberParse(ArrayList<Float> numbers, int nextCmd) {
|
|
|
|
this.numbers = numbers;
|
|
|
|
this.nextCmd = nextCmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getNextCmd() {
|
|
|
|
return nextCmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getNumber(int index) {
|
|
|
|
return numbers.get(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class StyleSet {
|
|
|
|
HashMap<String, String> styleMap = new HashMap<>();
|
|
|
|
|
|
|
|
private StyleSet(StyleSet styleSet) {
|
|
|
|
styleMap.putAll(styleSet.styleMap);
|
|
|
|
}
|
|
|
|
|
|
|
|
private StyleSet(String string) {
|
|
|
|
String[] styles = string.split(";");
|
|
|
|
for (String s : styles) {
|
|
|
|
String[] style = s.split(":");
|
|
|
|
if (style.length == 2) {
|
|
|
|
styleMap.put(style[0].trim(), style[1].trim());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getStyle(String name) {
|
|
|
|
return styleMap.get(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class Properties {
|
|
|
|
ArrayList<StyleSet> styles;
|
|
|
|
Attributes atts;
|
|
|
|
|
|
|
|
private Properties(Attributes atts, HashMap<String, StyleSet> globalStyles) {
|
|
|
|
this.atts = atts;
|
|
|
|
String styleAttr = getStringAttr("style", atts);
|
|
|
|
if (styleAttr != null) {
|
|
|
|
styles = new ArrayList<>();
|
|
|
|
styles.add(new StyleSet(styleAttr));
|
|
|
|
} else {
|
|
|
|
String classAttr = getStringAttr("class", atts);
|
|
|
|
if (classAttr != null) {
|
|
|
|
styles = new ArrayList<>();
|
|
|
|
String[] args = classAttr.split(" ");
|
|
|
|
for (int a = 0; a < args.length; a++) {
|
|
|
|
StyleSet set = globalStyles.get(args[a].trim());
|
|
|
|
if (set != null) {
|
|
|
|
styles.add(set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getAttr(String name) {
|
|
|
|
String v = null;
|
|
|
|
if (styles != null && !styles.isEmpty()) {
|
|
|
|
for (int a = 0, N = styles.size(); a < N; a++) {
|
|
|
|
v = styles.get(a).getStyle(name);
|
|
|
|
if (v != null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (v == null) {
|
|
|
|
v = getStringAttr(name, atts);
|
|
|
|
}
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getString(String name) {
|
|
|
|
return getAttr(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Integer getHex(String name) {
|
|
|
|
String v = getAttr(name);
|
|
|
|
if (v == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
return Integer.parseInt(v.substring(1), 16);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
return getColorByName(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Float getFloat(String name, float defaultValue) {
|
|
|
|
Float v = getFloat(name);
|
|
|
|
if (v == null) {
|
|
|
|
return defaultValue;
|
|
|
|
} else {
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Float getFloat(String name) {
|
|
|
|
String v = getAttr(name);
|
|
|
|
if (v == null) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
return Float.parseFloat(v);
|
|
|
|
} catch (NumberFormatException nfe) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class SVGHandler extends DefaultHandler {
|
|
|
|
|
|
|
|
private Canvas canvas;
|
|
|
|
private Bitmap bitmap;
|
2020-10-30 11:26:29 +01:00
|
|
|
private SvgDrawable drawable;
|
2019-12-31 14:08:08 +01:00
|
|
|
private int desiredWidth;
|
|
|
|
private int desiredHeight;
|
|
|
|
private float scale = 1.0f;
|
|
|
|
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
|
|
private RectF rect = new RectF();
|
2020-12-23 08:48:30 +01:00
|
|
|
private RectF rectTmp = new RectF();
|
2021-06-25 02:43:10 +02:00
|
|
|
private Integer paintColor;
|
2021-12-07 14:02:02 +01:00
|
|
|
private float globalScale = 1f;
|
2019-12-31 14:08:08 +01:00
|
|
|
|
|
|
|
boolean pushed = false;
|
|
|
|
|
|
|
|
private HashMap<String, StyleSet> globalStyles = new HashMap<>();
|
|
|
|
|
2021-12-07 14:02:02 +01:00
|
|
|
private SVGHandler(int dw, int dh, Integer color, boolean asDrawable, float scale) {
|
|
|
|
globalScale = scale;
|
2019-12-31 14:08:08 +01:00
|
|
|
desiredWidth = dw;
|
|
|
|
desiredHeight = dh;
|
2021-06-25 02:43:10 +02:00
|
|
|
paintColor = color;
|
2020-10-30 11:26:29 +01:00
|
|
|
if (asDrawable) {
|
|
|
|
drawable = new SvgDrawable();
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void startDocument() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void endDocument() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean doFill(Properties atts) {
|
|
|
|
if ("none".equals(atts.getString("display"))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
String fillString = atts.getString("fill");
|
|
|
|
if (fillString != null && fillString.startsWith("url(#")) {
|
|
|
|
String id = fillString.substring("url(#".length(), fillString.length() - 1);
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
Integer color = atts.getHex("fill");
|
|
|
|
if (color != null) {
|
|
|
|
doColor(atts, color, true);
|
|
|
|
paint.setStyle(Paint.Style.FILL);
|
|
|
|
return true;
|
|
|
|
} else if (atts.getString("fill") == null && atts.getString("stroke") == null) {
|
|
|
|
paint.setStyle(Paint.Style.FILL);
|
2021-06-25 02:43:10 +02:00
|
|
|
if (paintColor != null) {
|
|
|
|
paint.setColor(paintColor);
|
2019-12-31 14:08:08 +01:00
|
|
|
} else {
|
2020-10-30 11:26:29 +01:00
|
|
|
paint.setColor(0xff000000);
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean doStroke(Properties atts) {
|
|
|
|
if ("none".equals(atts.getString("display"))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Integer color = atts.getHex("stroke");
|
|
|
|
if (color != null) {
|
|
|
|
doColor(atts, color, false);
|
|
|
|
Float width = atts.getFloat("stroke-width");
|
|
|
|
|
|
|
|
if (width != null) {
|
|
|
|
paint.setStrokeWidth(width);
|
|
|
|
}
|
|
|
|
String linecap = atts.getString("stroke-linecap");
|
|
|
|
if ("round".equals(linecap)) {
|
|
|
|
paint.setStrokeCap(Paint.Cap.ROUND);
|
|
|
|
} else if ("square".equals(linecap)) {
|
|
|
|
paint.setStrokeCap(Paint.Cap.SQUARE);
|
|
|
|
} else if ("butt".equals(linecap)) {
|
|
|
|
paint.setStrokeCap(Paint.Cap.BUTT);
|
|
|
|
}
|
|
|
|
String linejoin = atts.getString("stroke-linejoin");
|
|
|
|
if ("miter".equals(linejoin)) {
|
|
|
|
paint.setStrokeJoin(Paint.Join.MITER);
|
|
|
|
} else if ("round".equals(linejoin)) {
|
|
|
|
paint.setStrokeJoin(Paint.Join.ROUND);
|
|
|
|
} else if ("bevel".equals(linejoin)) {
|
|
|
|
paint.setStrokeJoin(Paint.Join.BEVEL);
|
|
|
|
}
|
|
|
|
paint.setStyle(Paint.Style.STROKE);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void doColor(Properties atts, Integer color, boolean fillMode) {
|
2021-06-25 02:43:10 +02:00
|
|
|
if (paintColor != null) {
|
|
|
|
paint.setColor(paintColor);
|
2019-12-31 14:08:08 +01:00
|
|
|
} else {
|
|
|
|
int c = (0xFFFFFF & color) | 0xFF000000;
|
|
|
|
paint.setColor(c);
|
|
|
|
}
|
|
|
|
Float opacity = atts.getFloat("opacity");
|
|
|
|
if (opacity == null) {
|
|
|
|
opacity = atts.getFloat(fillMode ? "fill-opacity" : "stroke-opacity");
|
|
|
|
}
|
|
|
|
if (opacity == null) {
|
|
|
|
paint.setAlpha(255);
|
|
|
|
} else {
|
|
|
|
paint.setAlpha((int) (255 * opacity));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean boundsMode;
|
|
|
|
private StringBuilder styles;
|
|
|
|
|
|
|
|
private void pushTransform(Attributes atts) {
|
|
|
|
final String transform = getStringAttr("transform", atts);
|
|
|
|
pushed = transform != null;
|
|
|
|
if (pushed) {
|
|
|
|
final Matrix matrix = parseTransform(transform);
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(matrix);
|
|
|
|
} else {
|
|
|
|
canvas.save();
|
|
|
|
canvas.concat(matrix);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void popTransform() {
|
|
|
|
if (pushed) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(null);
|
|
|
|
} else {
|
|
|
|
canvas.restore();
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
|
|
|
|
if (boundsMode && !localName.equals("style")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (localName) {
|
|
|
|
case "svg": {
|
|
|
|
Float w = getFloatAttr("width", atts);
|
|
|
|
Float h = getFloatAttr("height", atts);
|
|
|
|
if (w == null || h == null) {
|
|
|
|
String viewBox = getStringAttr("viewBox", atts);
|
|
|
|
if (viewBox != null) {
|
|
|
|
String[] args = viewBox.split(" ");
|
|
|
|
w = Float.parseFloat(args[2]);
|
|
|
|
h = Float.parseFloat(args[3]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (w == null || h == null) {
|
|
|
|
w = (float) desiredWidth;
|
|
|
|
h = (float) desiredHeight;
|
|
|
|
}
|
|
|
|
int width = (int) Math.ceil(w);
|
|
|
|
int height = (int) Math.ceil(h);
|
|
|
|
if (width == 0 || height == 0) {
|
|
|
|
width = desiredWidth;
|
|
|
|
height = desiredHeight;
|
2020-10-30 11:26:29 +01:00
|
|
|
} else if (desiredWidth != 0 && desiredHeight != 0) {
|
2019-12-31 14:08:08 +01:00
|
|
|
scale = Math.min(desiredWidth / (float) width, desiredHeight / (float) height);
|
|
|
|
width *= scale;
|
|
|
|
height *= scale;
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable == null) {
|
|
|
|
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
|
|
bitmap.eraseColor(0);
|
|
|
|
canvas = new Canvas(bitmap);
|
|
|
|
if (scale != 0) {
|
2021-12-07 14:02:02 +01:00
|
|
|
canvas.scale(globalScale * scale, globalScale * scale);
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
drawable.width = width;
|
|
|
|
drawable.height = height;
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "defs":
|
|
|
|
case "clipPath":
|
|
|
|
boundsMode = true;
|
|
|
|
break;
|
|
|
|
case "style":
|
|
|
|
styles = new StringBuilder();
|
|
|
|
break;
|
|
|
|
case "g":
|
|
|
|
if ("bounds".equalsIgnoreCase(getStringAttr("id", atts))) {
|
|
|
|
boundsMode = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "rect": {
|
|
|
|
Float x = getFloatAttr("x", atts);
|
|
|
|
if (x == null) {
|
|
|
|
x = 0f;
|
|
|
|
}
|
|
|
|
Float y = getFloatAttr("y", atts);
|
|
|
|
if (y == null) {
|
|
|
|
y = 0f;
|
|
|
|
}
|
|
|
|
Float width = getFloatAttr("width", atts);
|
|
|
|
Float height = getFloatAttr("height", atts);
|
2020-12-23 08:48:30 +01:00
|
|
|
Float rx = getFloatAttr("rx", atts, null);
|
2019-12-31 14:08:08 +01:00
|
|
|
pushTransform(atts);
|
|
|
|
Properties props = new Properties(atts, globalStyles);
|
|
|
|
if (doFill(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
2021-03-19 11:25:58 +01:00
|
|
|
if (rx != null) {
|
|
|
|
drawable.addCommand(new RoundRect(new RectF(x, y, x + width, y + height), rx), paint);
|
|
|
|
} else {
|
|
|
|
drawable.addCommand(new RectF(x, y, x + width, y + height), paint);
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
} else {
|
2020-12-23 08:48:30 +01:00
|
|
|
if (rx != null) {
|
|
|
|
rectTmp.set(x, y, x + width, y + height);
|
|
|
|
canvas.drawRoundRect(rectTmp, rx, rx, paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawRect(x, y, x + width, y + height, paint);
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
if (doStroke(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
2021-03-19 11:25:58 +01:00
|
|
|
if (rx != null) {
|
|
|
|
drawable.addCommand(new RoundRect(new RectF(x, y, x + width, y + height), rx), paint);
|
|
|
|
} else {
|
|
|
|
drawable.addCommand(new RectF(x, y, x + width, y + height), paint);
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
} else {
|
2020-12-23 08:48:30 +01:00
|
|
|
if (rx != null) {
|
|
|
|
rectTmp.set(x, y, x + width, y + height);
|
|
|
|
canvas.drawRoundRect(rectTmp, rx, rx, paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawRect(x, y, x + width, y + height, paint);
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
popTransform();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "line": {
|
|
|
|
Float x1 = getFloatAttr("x1", atts);
|
|
|
|
Float x2 = getFloatAttr("x2", atts);
|
|
|
|
Float y1 = getFloatAttr("y1", atts);
|
|
|
|
Float y2 = getFloatAttr("y2", atts);
|
|
|
|
Properties props = new Properties(atts, globalStyles);
|
|
|
|
if (doStroke(props)) {
|
|
|
|
pushTransform(atts);
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(new Line(x1, y1, x2, y2), paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawLine(x1, y1, x2, y2, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
popTransform();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "circle": {
|
|
|
|
Float centerX = getFloatAttr("cx", atts);
|
|
|
|
Float centerY = getFloatAttr("cy", atts);
|
|
|
|
Float radius = getFloatAttr("r", atts);
|
|
|
|
if (centerX != null && centerY != null && radius != null) {
|
|
|
|
pushTransform(atts);
|
|
|
|
Properties props = new Properties(atts, globalStyles);
|
|
|
|
if (doFill(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(new Circle(centerX, centerY, radius), paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawCircle(centerX, centerY, radius, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
if (doStroke(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(new Circle(centerX, centerY, radius), paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawCircle(centerX, centerY, radius, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
popTransform();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "ellipse": {
|
|
|
|
Float centerX = getFloatAttr("cx", atts);
|
|
|
|
Float centerY = getFloatAttr("cy", atts);
|
|
|
|
Float radiusX = getFloatAttr("rx", atts);
|
|
|
|
Float radiusY = getFloatAttr("ry", atts);
|
|
|
|
if (centerX != null && centerY != null && radiusX != null && radiusY != null) {
|
|
|
|
pushTransform(atts);
|
|
|
|
Properties props = new Properties(atts, globalStyles);
|
|
|
|
rect.set(centerX - radiusX, centerY - radiusY, centerX + radiusX, centerY + radiusY);
|
|
|
|
if (doFill(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(new Oval(rect), paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawOval(rect, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
if (doStroke(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(new Oval(rect), paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawOval(rect, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
popTransform();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "polygon":
|
|
|
|
case "polyline":
|
|
|
|
NumberParse numbers = getNumberParseAttr("points", atts);
|
|
|
|
if (numbers != null) {
|
|
|
|
Path p = new Path();
|
|
|
|
ArrayList<Float> points = numbers.numbers;
|
|
|
|
if (points.size() > 1) {
|
|
|
|
pushTransform(atts);
|
|
|
|
Properties props = new Properties(atts, globalStyles);
|
|
|
|
p.moveTo(points.get(0), points.get(1));
|
|
|
|
for (int i = 2; i < points.size(); i += 2) {
|
|
|
|
float x = points.get(i);
|
|
|
|
float y = points.get(i + 1);
|
|
|
|
p.lineTo(x, y);
|
|
|
|
}
|
|
|
|
if (localName.equals("polygon")) {
|
|
|
|
p.close();
|
|
|
|
}
|
|
|
|
if (doFill(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(p, paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawPath(p, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
if (doStroke(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(p, paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawPath(p, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
popTransform();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "path": {
|
|
|
|
Path p = doPath(getStringAttr("d", atts));
|
|
|
|
pushTransform(atts);
|
|
|
|
Properties props = new Properties(atts, globalStyles);
|
|
|
|
if (doFill(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(p, paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawPath(p, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
if (doStroke(props)) {
|
2020-10-30 11:26:29 +01:00
|
|
|
if (drawable != null) {
|
|
|
|
drawable.addCommand(p, paint);
|
|
|
|
} else {
|
|
|
|
canvas.drawPath(p, paint);
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
popTransform();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void characters(char[] ch, int start, int length) {
|
|
|
|
if (styles != null) {
|
|
|
|
styles.append(ch, start, length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void endElement(String namespaceURI, String localName, String qName) {
|
|
|
|
switch (localName) {
|
|
|
|
case "style":
|
|
|
|
if (styles != null) {
|
|
|
|
String[] args = styles.toString().split("\\}");
|
|
|
|
for (int a = 0; a < args.length; a++) {
|
|
|
|
args[a] = args[a].trim().replace("\t", "").replace("\n", "");
|
|
|
|
if (args[a].length() == 0 || args[a].charAt(0) != '.') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int idx1 = args[a].indexOf('{');
|
|
|
|
if (idx1 < 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
String name = args[a].substring(1, idx1).trim();
|
|
|
|
String style = args[a].substring(idx1 + 1);
|
|
|
|
globalStyles.put(name, new StyleSet(style));
|
|
|
|
}
|
|
|
|
styles = null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "svg":
|
|
|
|
break;
|
|
|
|
case "g":
|
|
|
|
case "defs":
|
|
|
|
case "clipPath":
|
|
|
|
boundsMode = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public Bitmap getBitmap() {
|
|
|
|
return bitmap;
|
|
|
|
}
|
2020-10-30 11:26:29 +01:00
|
|
|
|
|
|
|
public SvgDrawable getDrawable() {
|
|
|
|
return drawable;
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private static final double[] pow10 = new double[128];
|
|
|
|
|
|
|
|
static {
|
|
|
|
for (int i = 0; i < pow10.length; i++) {
|
|
|
|
pow10[i] = Math.pow(10, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class ParserHelper {
|
|
|
|
|
|
|
|
private char current;
|
|
|
|
private CharSequence s;
|
|
|
|
public int pos;
|
|
|
|
private int n;
|
|
|
|
|
|
|
|
public ParserHelper(CharSequence s, int pos) {
|
|
|
|
this.s = s;
|
|
|
|
this.pos = pos;
|
|
|
|
n = s.length();
|
|
|
|
current = s.charAt(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
private char read() {
|
|
|
|
if (pos < n) {
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
if (pos == n) {
|
|
|
|
return '\0';
|
|
|
|
} else {
|
|
|
|
return s.charAt(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void skipWhitespace() {
|
|
|
|
while (pos < n) {
|
|
|
|
if (Character.isWhitespace(s.charAt(pos))) {
|
|
|
|
advance();
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void skipNumberSeparator() {
|
|
|
|
while (pos < n) {
|
|
|
|
char c = s.charAt(pos);
|
|
|
|
switch (c) {
|
|
|
|
case ' ':
|
|
|
|
case ',':
|
|
|
|
case '\n':
|
|
|
|
case '\t':
|
|
|
|
advance();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void advance() {
|
|
|
|
current = read();
|
|
|
|
}
|
|
|
|
|
|
|
|
public float parseFloat() {
|
|
|
|
int mant = 0;
|
|
|
|
int mantDig = 0;
|
|
|
|
boolean mantPos = true;
|
|
|
|
boolean mantRead = false;
|
|
|
|
|
|
|
|
int exp = 0;
|
|
|
|
int expDig = 0;
|
|
|
|
int expAdj = 0;
|
|
|
|
boolean expPos = true;
|
|
|
|
|
|
|
|
switch (current) {
|
|
|
|
case '-':
|
|
|
|
mantPos = false;
|
|
|
|
// fallthrough
|
|
|
|
case '+':
|
|
|
|
current = read();
|
|
|
|
}
|
|
|
|
|
|
|
|
m1:
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
return Float.NaN;
|
|
|
|
|
|
|
|
case '.':
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '0':
|
|
|
|
mantRead = true;
|
|
|
|
l:
|
|
|
|
for (; ; ) {
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
break l;
|
|
|
|
case '.':
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
break m1;
|
|
|
|
default:
|
|
|
|
return 0.0f;
|
|
|
|
case '0':
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
mantRead = true;
|
|
|
|
l:
|
|
|
|
for (; ; ) {
|
|
|
|
if (mantDig < 9) {
|
|
|
|
mantDig++;
|
|
|
|
mant = mant * 10 + (current - '0');
|
|
|
|
} else {
|
|
|
|
expAdj++;
|
|
|
|
}
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
break l;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current == '.') {
|
|
|
|
current = read();
|
|
|
|
m2:
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
if (!mantRead) {
|
|
|
|
reportUnexpectedCharacterError(current);
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '0':
|
|
|
|
if (mantDig == 0) {
|
|
|
|
l:
|
|
|
|
for (; ; ) {
|
|
|
|
current = read();
|
|
|
|
expAdj--;
|
|
|
|
switch (current) {
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
break l;
|
|
|
|
default:
|
|
|
|
if (!mantRead) {
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
break m2;
|
|
|
|
case '0':
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
l:
|
|
|
|
for (; ; ) {
|
|
|
|
if (mantDig < 9) {
|
|
|
|
mantDig++;
|
|
|
|
mant = mant * 10 + (current - '0');
|
|
|
|
expAdj--;
|
|
|
|
}
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
break l;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (current) {
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
reportUnexpectedCharacterError(current);
|
|
|
|
return 0f;
|
|
|
|
case '-':
|
|
|
|
expPos = false;
|
|
|
|
case '+':
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
reportUnexpectedCharacterError(current);
|
|
|
|
return 0f;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
}
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
}
|
|
|
|
|
|
|
|
en:
|
|
|
|
switch (current) {
|
|
|
|
case '0':
|
|
|
|
l:
|
|
|
|
for (; ; ) {
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
break l;
|
|
|
|
default:
|
|
|
|
break en;
|
|
|
|
case '0':
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
l:
|
|
|
|
for (; ; ) {
|
|
|
|
if (expDig < 3) {
|
|
|
|
expDig++;
|
|
|
|
exp = exp * 10 + (current - '0');
|
|
|
|
}
|
|
|
|
current = read();
|
|
|
|
switch (current) {
|
|
|
|
default:
|
|
|
|
break l;
|
|
|
|
case '0':
|
|
|
|
case '1':
|
|
|
|
case '2':
|
|
|
|
case '3':
|
|
|
|
case '4':
|
|
|
|
case '5':
|
|
|
|
case '6':
|
|
|
|
case '7':
|
|
|
|
case '8':
|
|
|
|
case '9':
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!expPos) {
|
|
|
|
exp = -exp;
|
|
|
|
}
|
|
|
|
exp += expAdj;
|
|
|
|
if (!mantPos) {
|
|
|
|
mant = -mant;
|
|
|
|
}
|
|
|
|
|
|
|
|
return buildFloat(mant, exp);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void reportUnexpectedCharacterError(char c) {
|
|
|
|
throw new RuntimeException("Unexpected char '" + c + "'.");
|
|
|
|
}
|
|
|
|
|
|
|
|
public float buildFloat(int mant, int exp) {
|
|
|
|
if (exp < -125 || mant == 0) {
|
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exp >= 128) {
|
|
|
|
return (mant > 0) ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exp == 0) {
|
|
|
|
return mant;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mant >= (1 << 26)) {
|
|
|
|
mant++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (float) ((exp > 0) ? mant * pow10[exp] : mant / pow10[-exp]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public float nextFloat() {
|
|
|
|
skipWhitespace();
|
|
|
|
float f = parseFloat();
|
|
|
|
skipNumberSeparator();
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
}
|
2020-12-23 08:48:30 +01:00
|
|
|
|
|
|
|
public static String decompress(byte[] encoded) {
|
|
|
|
try {
|
|
|
|
StringBuilder path = new StringBuilder(encoded.length * 2);
|
|
|
|
path.append('M');
|
|
|
|
for (int i = 0; i < encoded.length; i++) {
|
|
|
|
int num = encoded[i] & 0xff;
|
|
|
|
if (num >= 128 + 64) {
|
|
|
|
int start = num - 128 - 64;
|
2021-08-31 21:06:39 +02:00
|
|
|
path.append("AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,".charAt(start));
|
2020-12-23 08:48:30 +01:00
|
|
|
} else {
|
|
|
|
if (num >= 128) {
|
|
|
|
path.append(',');
|
|
|
|
} else if (num >= 64) {
|
|
|
|
path.append('-');
|
|
|
|
}
|
|
|
|
path.append(num & 63);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path.append('z');
|
|
|
|
return path.toString();
|
|
|
|
} catch (Exception e) {
|
|
|
|
FileLog.e(e);
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
2019-12-31 14:08:08 +01:00
|
|
|
}
|