[go: nahoru, domu]

blob: 0b3e56f3d9549d9ce4f38f0951a22fdc67a92004 [file] [log] [blame]
Nader Jawadb1030d42019-05-07 15:33:13 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Louis Pullen-Freilich4dc4dac2020-07-22 14:39:14 +010017package androidx.compose.ui.graphics.vector.compat
Nader Jawadb1030d42019-05-07 15:33:13 -070018
19import android.content.res.Resources
20import android.util.AttributeSet
Nader Jawadd02c0972019-08-27 15:01:07 -070021import androidx.core.content.res.ComplexColorCompat
Nader Jawadb1030d42019-05-07 15:33:13 -070022import androidx.core.content.res.TypedArrayUtils
Louis Pullen-Freilich4dc4dac2020-07-22 14:39:14 +010023import androidx.compose.ui.graphics.Brush
24import androidx.compose.ui.graphics.Color
25import androidx.compose.ui.graphics.ShaderBrush
26import androidx.compose.ui.graphics.SolidColor
27import androidx.compose.ui.graphics.StrokeCap
28import androidx.compose.ui.graphics.StrokeJoin
29import androidx.compose.ui.graphics.vector.DefaultPivotX
30import androidx.compose.ui.graphics.vector.DefaultPivotY
31import androidx.compose.ui.graphics.vector.DefaultRotation
32import androidx.compose.ui.graphics.vector.DefaultScaleX
33import androidx.compose.ui.graphics.vector.DefaultScaleY
34import androidx.compose.ui.graphics.vector.DefaultTranslationX
35import androidx.compose.ui.graphics.vector.DefaultTranslationY
36import androidx.compose.ui.graphics.vector.EmptyPath
37import androidx.compose.ui.graphics.vector.PathNode
38import androidx.compose.ui.graphics.vector.VectorAssetBuilder
39import androidx.compose.ui.graphics.vector.addPathNodes
Louis Pullen-Freilicha7eeb102020-07-22 17:54:24 +010040import androidx.compose.ui.unit.dp
Nader Jawadb1030d42019-05-07 15:33:13 -070041import org.xmlpull.v1.XmlPullParser
42import org.xmlpull.v1.XmlPullParserException
43
Louis Pullen-Freilich78e7cd72020-01-09 14:54:01 +000044private const val LINECAP_BUTT = 0
45private const val LINECAP_ROUND = 1
46private const val LINECAP_SQUARE = 2
Nader Jawadb1030d42019-05-07 15:33:13 -070047
Louis Pullen-Freilich78e7cd72020-01-09 14:54:01 +000048private const val LINEJOIN_MITER = 0
49private const val LINEJOIN_ROUND = 1
50private const val LINEJOIN_BEVEL = 2
Nader Jawadb1030d42019-05-07 15:33:13 -070051
52private val FILL_TYPE_WINDING = 0
53
Louis Pullen-Freilich78e7cd72020-01-09 14:54:01 +000054private const val SHAPE_CLIP_PATH = "clip-path"
55private const val SHAPE_GROUP = "group"
56private const val SHAPE_PATH = "path"
Nader Jawadb1030d42019-05-07 15:33:13 -070057
58private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.butt): StrokeCap =
59 when (id) {
60 LINECAP_BUTT -> StrokeCap.butt
61 LINECAP_ROUND -> StrokeCap.round
62 LINECAP_SQUARE -> StrokeCap.square
63 else -> defValue
64 }
65
66private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.miter): StrokeJoin =
67 when (id) {
68 LINEJOIN_MITER -> StrokeJoin.miter
69 LINEJOIN_ROUND -> StrokeJoin.round
70 LINEJOIN_BEVEL -> StrokeJoin.bevel
71 else -> defValue
72 }
73
74internal fun XmlPullParser.isAtEnd(): Boolean =
75 eventType == XmlPullParser.END_DOCUMENT ||
76 (depth < 1 && eventType == XmlPullParser.END_TAG)
77
Yuichi Araki543845e2020-06-25 19:37:40 +090078/**
79 * @param nestedGroups The number of additionally nested VectorGroups to represent clip paths.
80 * @return The number of nested VectorGroups that are not `<group>` in XML, but represented as
81 * VectorGroup in the [builder]. These are also popped when this function sees `</group>`.
82 */
Nader Jawadb1030d42019-05-07 15:33:13 -070083internal fun XmlPullParser.parseCurrentVectorNode(
84 res: Resources,
85 attrs: AttributeSet,
86 theme: Resources.Theme? = null,
Yuichi Araki543845e2020-06-25 19:37:40 +090087 builder: VectorAssetBuilder,
88 nestedGroups: Int
89): Int {
Nader Jawadb1030d42019-05-07 15:33:13 -070090 when (eventType) {
91 XmlPullParser.START_TAG -> {
92 when (name) {
93 SHAPE_PATH -> {
94 parsePath(res, theme, attrs, builder)
95 }
96 SHAPE_CLIP_PATH -> {
Yuichi Araki543845e2020-06-25 19:37:40 +090097 parseClipPath(res, theme, attrs, builder)
98 return nestedGroups + 1
Nader Jawadb1030d42019-05-07 15:33:13 -070099 }
100 SHAPE_GROUP -> {
101 parseGroup(res, theme, attrs, builder)
102 }
103 }
104 }
105 XmlPullParser.END_TAG -> {
106 if (SHAPE_GROUP == name) {
Yuichi Araki543845e2020-06-25 19:37:40 +0900107 repeat(nestedGroups + 1) {
108 builder.popGroup()
109 }
110 return 0
Nader Jawadb1030d42019-05-07 15:33:13 -0700111 }
112 }
113 }
Yuichi Araki543845e2020-06-25 19:37:40 +0900114 return nestedGroups
Nader Jawadb1030d42019-05-07 15:33:13 -0700115}
116
117/**
118 * Helper method to seek to the first tag within the VectorDrawable xml asset
119 */
120@Throws(XmlPullParserException::class)
121internal fun XmlPullParser.seekToStartTag(): XmlPullParser {
122 var type = next()
123 while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
124 // Empty loop
125 type = next()
126 }
127 if (type != XmlPullParser.START_TAG) {
128 throw XmlPullParserException("No start tag found")
129 }
130 return this
131}
132
133@SuppressWarnings("RestrictedApi")
134internal fun XmlPullParser.createVectorImageBuilder(
135 res: Resources,
136 theme: Resources.Theme?,
137 attrs: AttributeSet
138): VectorAssetBuilder {
139 val vectorAttrs = TypedArrayUtils.obtainAttributes(
140 res,
141 theme,
142 attrs,
143 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
144 )
145
146 // TODO (njawad) handle mirroring here
147// state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
148// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED, state.mAutoMirrored)
149
150 val viewportWidth = TypedArrayUtils.getNamedFloat(
151 vectorAttrs,
152 this,
153 "viewportWidth",
154 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
155 0.0f
156 )
157
158 val viewportHeight = TypedArrayUtils.getNamedFloat(
159 vectorAttrs,
160 this,
161 "viewportHeight",
162 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
163 0.0f
164 )
165
166 if (viewportWidth <= 0) {
167 throw XmlPullParserException(
Louis Pullen-Freilich0d2d0ed2019-12-13 18:41:46 +0000168 vectorAttrs.positionDescription + "<VectorGraphic> tag requires viewportWidth > 0"
Nader Jawadb1030d42019-05-07 15:33:13 -0700169 )
170 } else if (viewportHeight <= 0) {
171 throw XmlPullParserException(
Louis Pullen-Freilich0d2d0ed2019-12-13 18:41:46 +0000172 vectorAttrs.positionDescription + "<VectorGraphic> tag requires viewportHeight > 0"
Nader Jawadb1030d42019-05-07 15:33:13 -0700173 )
174 }
175
176 val defaultWidth = vectorAttrs.getDimension(
177 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f
178 )
179 val defaultHeight = vectorAttrs.getDimension(
180 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f
181 )
182
Louis Pullen-Freilich0d2d0ed2019-12-13 18:41:46 +0000183 val defaultWidthDp = (defaultWidth / res.displayMetrics.density).dp
184 val defaultHeightDp = (defaultHeight / res.displayMetrics.density).dp
185
Nader Jawadb1030d42019-05-07 15:33:13 -0700186 vectorAttrs.recycle()
187
188 return VectorAssetBuilder(
Louis Pullen-Freilich0d2d0ed2019-12-13 18:41:46 +0000189 defaultWidth = defaultWidthDp,
190 defaultHeight = defaultHeightDp,
Nader Jawadb1030d42019-05-07 15:33:13 -0700191 viewportWidth = viewportWidth,
192 viewportHeight = viewportHeight
193 )
194}
195
196@Throws(IllegalArgumentException::class)
197@SuppressWarnings("RestrictedApi")
198internal fun XmlPullParser.parsePath(
199 res: Resources,
200 theme: Resources.Theme?,
201 attrs: AttributeSet,
202 builder: VectorAssetBuilder
203) {
204 val a = TypedArrayUtils.obtainAttributes(
205 res,
206 theme,
207 attrs,
208 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH
209 )
210
211 val hasPathData = TypedArrayUtils.hasAttribute(this, "pathData")
212 if (!hasPathData) {
213 // If there is no pathData in the VPath tag, then this is an empty VPath,
214 // nothing need to be drawn.
215 throw IllegalArgumentException("No path data available")
216 }
217
218 val name: String = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
219
220 val pathStr = a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
221
Nader Jawad4ef199b2019-10-22 18:37:01 -0700222 val pathData: List<PathNode> = addPathNodes(pathStr)
Nader Jawadb1030d42019-05-07 15:33:13 -0700223
224 val fillColor = TypedArrayUtils.getNamedComplexColor(
225 a,
226 this,
227 theme,
228 "fillColor",
229 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR, 0
230 )
231 val fillAlpha = TypedArrayUtils.getNamedFloat(
232 a,
233 this,
234 "fillAlpha",
235 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA, 1.0f
236 )
237 val lineCap = TypedArrayUtils.getNamedInt(
238 a,
239 this,
240 "strokeLineCap",
241 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP, -1
242 )
243 val strokeLineCap = getStrokeLineCap(lineCap, StrokeCap.butt)
244 val lineJoin = TypedArrayUtils.getNamedInt(
245 a,
246 this,
247 "strokeLineJoin",
248 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN, -1
249 )
Nader Jawad02a9d4e2019-08-27 10:49:47 -0700250 val strokeLineJoin =
251 getStrokeLineJoin(lineJoin, StrokeJoin.bevel)
Nader Jawadb1030d42019-05-07 15:33:13 -0700252 val strokeMiterLimit = TypedArrayUtils.getNamedFloat(
253 a,
254 this,
255 "strokeMiterLimit",
256 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
257 1.0f
258 )
259 val strokeColor = TypedArrayUtils.getNamedComplexColor(
260 a,
261 this,
262 theme,
263 "strokeColor",
264 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR, 0
265 )
266 val strokeAlpha = TypedArrayUtils.getNamedFloat(
267 a,
268 this,
269 "strokeAlpha",
270 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA, 1.0f
271 )
272 val strokeLineWidth = TypedArrayUtils.getNamedFloat(
273 a,
274 this,
275 "strokeWidth",
276 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH, 1.0f
277 )
278
279 // TODO (njawad) handle trim paths + fill rule
280// val trimPathEnd = TypedArrayUtils.getNamedFloat(
281// a, this, "trimPathEnd",
282// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END, 1.0f
283// )
284// val trimPathOffset = TypedArrayUtils.getNamedFloat(
285// a, this, "trimPathOffset",
286// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
287// 0.0f
288// )
289// val trimPathStart = TypedArrayUtils.getNamedFloat(
290// a, this, "trimPathStart",
291// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
292// 0.0f
293// )
294// val fillRule = TypedArrayUtils.getNamedInt(
295// a, this, "fillType",
296// AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
297// FILL_TYPE_WINDING
298// )
299
300 a.recycle()
301
Nader Jawadd02c0972019-08-27 15:01:07 -0700302 val fillBrush = obtainBrushFromComplexColor(fillColor)
303 val strokeBrush = obtainBrushFromComplexColor(strokeColor)
Nader Jawadb1030d42019-05-07 15:33:13 -0700304
305 builder.addPath(
306 pathData,
307 name,
308 fillBrush,
309 fillAlpha,
310 strokeBrush,
311 strokeAlpha,
312 strokeLineWidth,
313 strokeLineCap,
314 strokeLineJoin,
315 strokeMiterLimit)
316}
317
318@SuppressWarnings("RestrictedApi")
Nader Jawadd02c0972019-08-27 15:01:07 -0700319private fun obtainBrushFromComplexColor(complexColor: ComplexColorCompat): Brush? =
Aurimas Liutikas7a828d32019-10-07 17:16:05 -0700320 if (complexColor.willDraw()) {
321 val shader = complexColor.shader
322 if (shader != null) {
Nader Jawadd4dceed2020-06-26 17:18:55 -0700323 ShaderBrush(shader)
Aurimas Liutikas7a828d32019-10-07 17:16:05 -0700324 } else {
325 SolidColor(Color(complexColor.color))
326 }
327 } else {
328 null
329 }
Nader Jawadd02c0972019-08-27 15:01:07 -0700330
Yuichi Araki543845e2020-06-25 19:37:40 +0900331internal fun XmlPullParser.parseClipPath(
332 res: Resources,
333 theme: Resources.Theme?,
334 attrs: AttributeSet,
335 builder: VectorAssetBuilder
336) {
Louis Pullen-Freilich7e6ecdb2020-07-09 15:14:37 +0100337 val a = theme?.obtainStyledAttributes(
Yuichi Araki543845e2020-06-25 19:37:40 +0900338 attrs,
Louis Pullen-Freilich7e6ecdb2020-07-09 15:14:37 +0100339 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH,
340 0,
341 0
342 ) ?: res.obtainAttributes(attrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH)
343
Yuichi Araki543845e2020-06-25 19:37:40 +0900344 val name: String = a.getString(
345 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME
346 ) ?: ""
347 val pathData = addPathNodes(a.getString(
348 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA
349 ))
350 a.recycle()
351
352 // <clip-path> is parsed out as an additional VectorGroup.
353 // This allows us to replicate the behavior of VectorDrawable where <clip-path> only affects
354 // <path> that comes after it in <group>.
355 builder.pushGroup(
356 name = name,
357 clipPathData = pathData
358 )
359}
360
Nader Jawadd02c0972019-08-27 15:01:07 -0700361@SuppressWarnings("RestrictedApi")
Nader Jawadb1030d42019-05-07 15:33:13 -0700362internal fun XmlPullParser.parseGroup(
363 res: Resources,
364 theme: Resources.Theme?,
365 attrs: AttributeSet,
366 builder: VectorAssetBuilder
367) {
368 val a = TypedArrayUtils.obtainAttributes(
369 res,
370 theme,
371 attrs,
372 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP
373 )
374
375 // Account for any configuration changes.
376 // mChangingConfigurations |= Utils.getChangingConfigurations(a);
377
378 // Extract the theme attributes, if any.
379 // mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
380
381 // This is added in API 11
382 val rotate = TypedArrayUtils.getNamedFloat(
383 a,
384 this,
385 "rotation",
386 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
387 DefaultRotation
388 )
389
390 val pivotX = a.getFloat(
391 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X,
392 DefaultPivotX
393 )
394 val pivotY = a.getFloat(
395 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y,
396 DefaultPivotY
397 )
398
399 // This is added in API 11
400 val scaleX = TypedArrayUtils.getNamedFloat(
401 a,
402 this,
403 "scaleX",
404 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
405 DefaultScaleX
406 )
407
408 // This is added in API 11
409 val scaleY = TypedArrayUtils.getNamedFloat(
410 a,
411 this,
412 "scaleY",
413 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
414 DefaultScaleY
415 )
416
417 val translateX = TypedArrayUtils.getNamedFloat(
418 a,
419 this,
Yuichi Araki16a74892020-06-17 14:06:44 +0900420 "translateX",
Nader Jawadb1030d42019-05-07 15:33:13 -0700421 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
422 DefaultTranslationX
423 )
424 val translateY = TypedArrayUtils.getNamedFloat(
425 a,
426 this,
Yuichi Araki16a74892020-06-17 14:06:44 +0900427 "translateY",
Nader Jawadb1030d42019-05-07 15:33:13 -0700428 AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
429 DefaultTranslationY
430 )
431
432 val name: String =
433 a.getString(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
434
Nader Jawadb1030d42019-05-07 15:33:13 -0700435 a.recycle()
436
437 builder.pushGroup(
438 name,
439 rotate,
Louis Pullen-Freilich78e7cd72020-01-09 14:54:01 +0000440 pivotX,
441 pivotY,
Nader Jawadb1030d42019-05-07 15:33:13 -0700442 scaleX,
443 scaleY,
444 translateX,
445 translateY,
Yuichi Araki543845e2020-06-25 19:37:40 +0900446 EmptyPath
Nader Jawadb1030d42019-05-07 15:33:13 -0700447 )
448}