[go: nahoru, domu]

blob: 0b3e56f3d9549d9ce4f38f0951a22fdc67a92004 [file] [log] [blame]
/*
* Copyright 2019 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 androidx.compose.ui.graphics.vector.compat
import android.content.res.Resources
import android.util.AttributeSet
import androidx.core.content.res.ComplexColorCompat
import androidx.core.content.res.TypedArrayUtils
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.DefaultPivotX
import androidx.compose.ui.graphics.vector.DefaultPivotY
import androidx.compose.ui.graphics.vector.DefaultRotation
import androidx.compose.ui.graphics.vector.DefaultScaleX
import androidx.compose.ui.graphics.vector.DefaultScaleY
import androidx.compose.ui.graphics.vector.DefaultTranslationX
import androidx.compose.ui.graphics.vector.DefaultTranslationY
import androidx.compose.ui.graphics.vector.EmptyPath
import androidx.compose.ui.graphics.vector.PathNode
import androidx.compose.ui.graphics.vector.VectorAssetBuilder
import androidx.compose.ui.graphics.vector.addPathNodes
import androidx.compose.ui.unit.dp
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
private const val LINECAP_BUTT = 0
private const val LINECAP_ROUND = 1
private const val LINECAP_SQUARE = 2
private const val LINEJOIN_MITER = 0
private const val LINEJOIN_ROUND = 1
private const val LINEJOIN_BEVEL = 2
private val FILL_TYPE_WINDING = 0
private const val SHAPE_CLIP_PATH = "clip-path"
private const val SHAPE_GROUP = "group"
private const val SHAPE_PATH = "path"
private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.butt): StrokeCap =
when (id) {
LINECAP_BUTT -> StrokeCap.butt
LINECAP_ROUND -> StrokeCap.round
LINECAP_SQUARE -> StrokeCap.square
else -> defValue
}
private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.miter): StrokeJoin =
when (id) {
LINEJOIN_MITER -> StrokeJoin.miter
LINEJOIN_ROUND -> StrokeJoin.round
LINEJOIN_BEVEL -> StrokeJoin.bevel
else -> defValue
}
internal fun XmlPullParser.isAtEnd(): Boolean =
eventType == XmlPullParser.END_DOCUMENT ||
(depth < 1 && eventType == XmlPullParser.END_TAG)
/**
* @param nestedGroups The number of additionally nested VectorGroups to represent clip paths.
* @return The number of nested VectorGroups that are not `<group>` in XML, but represented as
* VectorGroup in the [builder]. These are also popped when this function sees `</group>`.
*/
internal fun XmlPullParser.parseCurrentVectorNode(
res: Resources,
attrs: AttributeSet,
theme: Resources.Theme? = null,
builder: VectorAssetBuilder,
nestedGroups: Int
): Int {
when (eventType) {
XmlPullParser.START_TAG -> {
when (name) {
SHAPE_PATH -> {
parsePath(res, theme, attrs, builder)
}
SHAPE_CLIP_PATH -> {
parseClipPath(res, theme, attrs, builder)
return nestedGroups + 1
}
SHAPE_GROUP -> {
parseGroup(res, theme, attrs, builder)
}
}
}
XmlPullParser.END_TAG -> {
if (SHAPE_GROUP == name) {
repeat(nestedGroups + 1) {
builder.popGroup()
}
return 0
}
}
}
return nestedGroups
}
/**
* Helper method to seek to the first tag within the VectorDrawable xml asset
*/
@Throws(XmlPullParserException::class)
internal fun XmlPullParser.seekToStartTag(): XmlPullParser {
var type = next()
while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
// Empty loop
type = next()
}
if (type != XmlPullParser.START_TAG) {
throw XmlPullParserException("No start tag found")
}
return this
}
@SuppressWarnings("RestrictedApi")
internal fun XmlPullParser.createVectorImageBuilder(
res: Resources,
theme: Resources.Theme?,
attrs: AttributeSet
): VectorAssetBuilder {
val vectorAttrs = TypedArrayUtils.obtainAttributes(
res,
theme,
attrs,
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
)
// TODO (njawad) handle mirroring here
// state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored)
val viewportWidth = TypedArrayUtils.getNamedFloat(
vectorAttrs,
this,
"viewportWidth",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
0.0f
)
val viewportHeight = TypedArrayUtils.getNamedFloat(
vectorAttrs,
this,
"viewportHeight",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
0.0f
)
if (viewportWidth <= 0) {
throw XmlPullParserException(
vectorAttrs.positionDescription + "<VectorGraphic> tag requires viewportWidth > 0"
)
} else if (viewportHeight <= 0) {
throw XmlPullParserException(
vectorAttrs.positionDescription + "<VectorGraphic> tag requires viewportHeight > 0"
)
}
val defaultWidth = vectorAttrs.getDimension(
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f
)
val defaultHeight = vectorAttrs.getDimension(
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f
)
val defaultWidthDp = (defaultWidth / res.displayMetrics.density).dp
val defaultHeightDp = (defaultHeight / res.displayMetrics.density).dp
vectorAttrs.recycle()
return VectorAssetBuilder(
defaultWidth = defaultWidthDp,
defaultHeight = defaultHeightDp,
viewportWidth = viewportWidth,
viewportHeight = viewportHeight
)
}
@Throws(IllegalArgumentException::class)
@SuppressWarnings("RestrictedApi")
internal fun XmlPullParser.parsePath(
res: Resources,
theme: Resources.Theme?,
attrs: AttributeSet,
builder: VectorAssetBuilder
) {
val a = TypedArrayUtils.obtainAttributes(
res,
theme,
attrs,
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH
)
val hasPathData = TypedArrayUtils.hasAttribute(this, "pathData")
if (!hasPathData) {
// If there is no pathData in the VPath tag, then this is an empty VPath,
// nothing need to be drawn.
throw IllegalArgumentException("No path data available")
}
val name: String = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
val pathStr = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
val pathData: List<PathNode> = addPathNodes(pathStr)
val fillColor = TypedArrayUtils.getNamedComplexColor(
a,
this,
theme,
"fillColor",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, 0
)
val fillAlpha = TypedArrayUtils.getNamedFloat(
a,
this,
"fillAlpha",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, 1.0f
)
val lineCap = TypedArrayUtils.getNamedInt(
a,
this,
"strokeLineCap",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1
)
val strokeLineCap = getStrokeLineCap(lineCap, StrokeCap.butt)
val lineJoin = TypedArrayUtils.getNamedInt(
a,
this,
"strokeLineJoin",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1
)
val strokeLineJoin =
getStrokeLineJoin(lineJoin, StrokeJoin.bevel)
val strokeMiterLimit = TypedArrayUtils.getNamedFloat(
a,
this,
"strokeMiterLimit",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
1.0f
)
val strokeColor = TypedArrayUtils.getNamedComplexColor(
a,
this,
theme,
"strokeColor",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, 0
)
val strokeAlpha = TypedArrayUtils.getNamedFloat(
a,
this,
"strokeAlpha",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, 1.0f
)
val strokeLineWidth = TypedArrayUtils.getNamedFloat(
a,
this,
"strokeWidth",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, 1.0f
)
// TODO (njawad) handle trim paths + fill rule
// val trimPathEnd = TypedArrayUtils.getNamedFloat(
// a, this, "trimPathEnd",
// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, 1.0f
// )
// val trimPathOffset = TypedArrayUtils.getNamedFloat(
// a, this, "trimPathOffset",
// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
// 0.0f
// )
// val trimPathStart = TypedArrayUtils.getNamedFloat(
// a, this, "trimPathStart",
// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
// 0.0f
// )
// val fillRule = TypedArrayUtils.getNamedInt(
// a, this, "fillType",
// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
// FILL_TYPE_WINDING
// )
a.recycle()
val fillBrush = obtainBrushFromComplexColor(fillColor)
val strokeBrush = obtainBrushFromComplexColor(strokeColor)
builder.addPath(
pathData,
name,
fillBrush,
fillAlpha,
strokeBrush,
strokeAlpha,
strokeLineWidth,
strokeLineCap,
strokeLineJoin,
strokeMiterLimit)
}
@SuppressWarnings("RestrictedApi")
private fun obtainBrushFromComplexColor(complexColor: ComplexColorCompat): Brush? =
if (complexColor.willDraw()) {
val shader = complexColor.shader
if (shader != null) {
ShaderBrush(shader)
} else {
SolidColor(Color(complexColor.color))
}
} else {
null
}
internal fun XmlPullParser.parseClipPath(
res: Resources,
theme: Resources.Theme?,
attrs: AttributeSet,
builder: VectorAssetBuilder
) {
val a = theme?.obtainStyledAttributes(
attrs,
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH,
0,
0
) ?: res.obtainAttributes(attrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH)
val name: String = a.getString(
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME
) ?: ""
val pathData = addPathNodes(a.getString(
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA
))
a.recycle()
// <clip-path> is parsed out as an additional VectorGroup.
// This allows us to replicate the behavior of VectorDrawable where <clip-path> only affects
// <path> that comes after it in <group>.
builder.pushGroup(
name = name,
clipPathData = pathData
)
}
@SuppressWarnings("RestrictedApi")
internal fun XmlPullParser.parseGroup(
res: Resources,
theme: Resources.Theme?,
attrs: AttributeSet,
builder: VectorAssetBuilder
) {
val a = TypedArrayUtils.obtainAttributes(
res,
theme,
attrs,
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP
)
// Account for any configuration changes.
// mChangingConfigurations |= Utils.getChangingConfigurations(a);
// Extract the theme attributes, if any.
// mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
// This is added in API 11
val rotate = TypedArrayUtils.getNamedFloat(
a,
this,
"rotation",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
DefaultRotation
)
val pivotX = a.getFloat(
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X,
DefaultPivotX
)
val pivotY = a.getFloat(
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y,
DefaultPivotY
)
// This is added in API 11
val scaleX = TypedArrayUtils.getNamedFloat(
a,
this,
"scaleX",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
DefaultScaleX
)
// This is added in API 11
val scaleY = TypedArrayUtils.getNamedFloat(
a,
this,
"scaleY",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
DefaultScaleY
)
val translateX = TypedArrayUtils.getNamedFloat(
a,
this,
"translateX",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
DefaultTranslationX
)
val translateY = TypedArrayUtils.getNamedFloat(
a,
this,
"translateY",
AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
DefaultTranslationY
)
val name: String =
a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
a.recycle()
builder.pushGroup(
name,
rotate,
pivotX,
pivotY,
scaleX,
scaleY,
translateX,
translateY,
EmptyPath
)
}