// @Override setEditableValue(model: any, propertyName: string, value: string) { if (!value) { model[propertyName] = undefined; return; } let processedValue = ColorUtil.parseAndroidColor(value); if (!processedValue) { processedValue = ColorUtil.parseAndroidColor(ColorUtil.svgToAndroidColor(value)); } model[propertyName] = ColorUtil.toAndroidString(processedValue); }
// @Override interpolateValue(start: string, end: string, f: number) { if (!start || !end) { return undefined; } const s = ColorUtil.parseAndroidColor(start); const e = ColorUtil.parseAndroidColor(end); return ColorUtil.toAndroidString({ r: _.clamp(Math.round(MathUtil.lerp(s.r, e.r, f)), 0, 0xff), g: _.clamp(Math.round(MathUtil.lerp(s.g, e.g, f)), 0, 0xff), b: _.clamp(Math.round(MathUtil.lerp(s.b, e.b, f)), 0, 0xff), a: _.clamp(Math.round(MathUtil.lerp(s.a, e.a, f)), 0, 0xff), }); }
function getColor(obj: HTMLElement, attr: string, def = '') { const androidAttr = `android:${attr}`; const color = obj.hasAttribute(androidAttr) ? obj.getAttribute(androidAttr) : def; return !!ColorUtil.parseAndroidColor(color) ? color : def; }
const nodeToLayerFn = (node: Element, transforms: ReadonlyArray<Matrix>): Layer => { if ( !node || node.nodeType === Node.TEXT_NODE || node.nodeType === Node.COMMENT_NODE || node instanceof SVGDefsElement || node instanceof SVGUseElement ) { return undefined; } const nodeTransforms = getNodeTransforms(node as SVGGraphicsElement); transforms = [...transforms, ...nodeTransforms]; const flattenedTransforms = Matrix.flatten(transforms); // Get the referenced clip-path ID, if one exists. const refClipPathId = getReferencedClipPathId(node); const maybeWrapClipPathInGroupFn = (layer: Layer) => { if (!refClipPathId) { return layer; } const paths = (clipPathMap[refClipPathId] || []).map(p => { return new Path( p .mutate() .transform(flattenedTransforms) .build() .getPathString(), ); }); if (!paths.length) { // If the clipPath has no children, then clip the entire layer. paths.push(new Path('M 0 0 Z')); } const groupChildren: Layer[] = paths.map(p => { return new ClipPathLayer({ name: makeFinalNodeIdFn(refClipPathId, 'mask'), pathData: p, children: [], }); }); groupChildren.push(layer); return new GroupLayer({ name: makeFinalNodeIdFn('wrapper', 'group'), children: groupChildren, }); }; if (node instanceof SVGPathElement && node.getAttribute('d')) { const path = node.getAttribute('d'); const attrMap: Dictionary<any> = {}; const simpleAttrFn = (nodeAttr: string, contextAttr: string) => { if (node.hasAttribute(nodeAttr)) { attrMap[contextAttr] = node.getAttribute(nodeAttr); } }; simpleAttrFn('stroke', 'strokeColor'); simpleAttrFn('stroke-width', 'strokeWidth'); simpleAttrFn('stroke-linecap', 'strokeLinecap'); simpleAttrFn('stroke-linejoin', 'strokeLinejoin'); simpleAttrFn('stroke-miterlimit', 'strokeMiterLimit'); simpleAttrFn('stroke-opacity', 'strokeAlpha'); simpleAttrFn('fill', 'fillColor'); simpleAttrFn('fill-opacity', 'fillAlpha'); simpleAttrFn('fill-rule', 'fillType'); // Set the default values as specified by the SVG spec. Note that some of these default // values are different than the default values used by VectorDrawables. const fillColor = 'fillColor' in attrMap ? ColorUtil.svgToAndroidColor(attrMap['fillColor']) : '#000'; const strokeColor = 'strokeColor' in attrMap ? ColorUtil.svgToAndroidColor(attrMap['strokeColor']) : undefined; const fillAlpha = 'fillAlpha' in attrMap ? Number(attrMap['fillAlpha']) : 1; let strokeWidth = 'strokeWidth' in attrMap ? Number(attrMap['strokeWidth']) : 1; const strokeAlpha = 'strokeAlpha' in attrMap ? Number(attrMap['strokeAlpha']) : 1; const strokeLinecap: StrokeLineCap = 'strokeLinecap' in attrMap ? attrMap['strokeLinecap'] : 'butt'; const strokeLinejoin: StrokeLineJoin = 'strokeLinejoin' in attrMap ? attrMap['strokeLinecap'] : 'miter'; const strokeMiterLimit = 'strokeMiterLimit' in attrMap ? Number(attrMap['strokeMiterLimit']) : 4; const fillRuleToFillTypeFn = (fillRule: string) => { return fillRule === 'evenodd' ? 'evenOdd' : 'nonZero'; }; const fillType: FillType = 'fillType' in attrMap ? fillRuleToFillTypeFn(attrMap['fillType']) : 'nonZero'; let pathData = new Path(path); if (transforms.length) { pathData = new Path( pathData .mutate() .transform(flattenedTransforms) .build() .getPathString(), ); strokeWidth = MathUtil.round(strokeWidth * flattenedTransforms.getScaleFactor()); } // TODO: make best effort attempt to restore trimPath{Start,End,Offset} return maybeWrapClipPathInGroupFn( new PathLayer({ id: _.uniqueId(), name: makeFinalNodeIdFn(node.getAttribute('id'), 'path'), children: [], pathData, fillColor, fillAlpha, strokeColor, strokeAlpha, strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, fillType, }), ); } if (node.childNodes) { const children: Layer[] = []; for (let i = 0; i < node.childNodes.length; i++) { const child = node.childNodes.item(i) as Element; const layer = nodeToLayerFn(child, transforms); if (layer) { children.push(layer); } } return maybeWrapClipPathInGroupFn( new GroupLayer({ id: _.uniqueId(), name: makeFinalNodeIdFn(node.getAttribute('id'), 'group'), children, }), ); } return undefined; };
(layer: VectorLayer | GroupLayer | PathLayer, parentNode: Node) => { if (layer instanceof VectorLayer) { if (withIds) { conditionalAttr(destinationNode, 'id', vl.name, ''); } conditionalAttr(destinationNode, 'opacity', vl.alpha, 1); return parentNode; } if (layer instanceof PathLayer) { const { pathData } = layer; if (!pathData.getPathString()) { return undefined; } const node = xmlDoc.createElement('path'); if (withIds) { conditionalAttr(node, 'id', layer.name); } maybeSetClipPathForLayerFn(node, layer.id); conditionalAttr(node, 'd', pathData.getPathString()); if (layer.fillColor) { conditionalAttr(node, 'fill', ColorUtil.androidToCssHexColor(layer.fillColor), ''); } else { conditionalAttr(node, 'fill', 'none'); } conditionalAttr(node, 'fill-opacity', layer.fillAlpha, 1); if (layer.strokeColor) { conditionalAttr(node, 'stroke', ColorUtil.androidToCssHexColor(layer.strokeColor), ''); } conditionalAttr(node, 'stroke-opacity', layer.strokeAlpha, 1); conditionalAttr(node, 'stroke-width', layer.strokeWidth, 0); if (layer.trimPathStart !== 0 || layer.trimPathEnd !== 1 || layer.trimPathOffset !== 0) { const flattenedTransform = LayerUtil.getCanvasTransformForLayer(vl, layer.id); const { a, d } = flattenedTransform; // Note that we only return the length of the first sub path due to // https://code.google.com/p/android/issues/detail?id=172547 let pathLength: number; if (Math.abs(a) !== 1 || Math.abs(d) !== 1) { // Then recompute the scaled path length. pathLength = pathData .mutate() .transform(flattenedTransform) .build() .getSubPathLength(0); } else { pathLength = pathData.getSubPathLength(0); } const strokeDashArray = PathUtil.toStrokeDashArray( layer.trimPathStart, layer.trimPathEnd, layer.trimPathOffset, pathLength, ).join(','); const strokeDashOffset = PathUtil.toStrokeDashOffset( layer.trimPathStart, layer.trimPathEnd, layer.trimPathOffset, pathLength, ).toString(); conditionalAttr(node, 'stroke-dasharray', strokeDashArray); conditionalAttr(node, 'stroke-dashoffset', strokeDashOffset); } conditionalAttr(node, 'stroke-linecap', layer.strokeLinecap, 'butt'); conditionalAttr(node, 'stroke-linejoin', layer.strokeLinejoin, 'miter'); conditionalAttr(node, 'stroke-miterlimit', layer.strokeMiterLimit, 4); const fillRule = !layer.fillType || layer.fillType === 'nonZero' ? 'nonzero' : 'evenodd'; conditionalAttr(node, 'fill-rule', fillRule, 'nonzero'); parentNode.appendChild(node); return parentNode; } if (layer instanceof GroupLayer) { const node = xmlDoc.createElement('g'); if (withIds) { conditionalAttr(node, 'id', layer.name); } const transformValues: string[] = []; if (layer.translateX || layer.translateY) { transformValues.push(`translate(${layer.translateX} ${layer.translateY})`); } if (layer.rotation) { transformValues.push(`rotate(${layer.rotation} ${layer.pivotX} ${layer.pivotY})`); } if (layer.scaleX !== 1 || layer.scaleY !== 1) { if (layer.pivotX || layer.pivotY) { transformValues.push(`translate(${layer.pivotX} ${layer.pivotY})`); } transformValues.push(`scale(${layer.scaleX} ${layer.scaleY})`); if (layer.pivotX || layer.pivotY) { transformValues.push(`translate(${-layer.pivotX} ${-layer.pivotY})`); } } let nodeToAttachToParent = node; if (transformValues.length) { node.setAttributeNS(undefined, 'transform', transformValues.join(' ')); if (isLayerBeingClippedFn(layer.id)) { // Create a wrapper node so that the clip-path is applied before the transformations. const wrapperNode = xmlDoc.createElement('g'); wrapperNode.appendChild(node); nodeToAttachToParent = wrapperNode; } } maybeSetClipPathForLayerFn(nodeToAttachToParent, layer.id); parentNode.appendChild(nodeToAttachToParent); return node; } return undefined; },