michael@0: michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkSVGPaintState.h" michael@0: #include "SkSVGElements.h" michael@0: #include "SkSVGParser.h" michael@0: #include "SkParse.h" michael@0: michael@0: SkSVGAttribute SkSVGPaint::gAttributes[] = { michael@0: SVG_LITERAL_ATTRIBUTE(clip-path, f_clipPath), michael@0: SVG_LITERAL_ATTRIBUTE(clip-rule, f_clipRule), michael@0: SVG_LITERAL_ATTRIBUTE(enable-background, f_enableBackground), michael@0: SVG_ATTRIBUTE(fill), michael@0: SVG_LITERAL_ATTRIBUTE(fill-rule, f_fillRule), michael@0: SVG_ATTRIBUTE(filter), michael@0: SVG_LITERAL_ATTRIBUTE(font-family, f_fontFamily), michael@0: SVG_LITERAL_ATTRIBUTE(font-size, f_fontSize), michael@0: SVG_LITERAL_ATTRIBUTE(letter-spacing, f_letterSpacing), michael@0: SVG_ATTRIBUTE(mask), michael@0: SVG_ATTRIBUTE(opacity), michael@0: SVG_LITERAL_ATTRIBUTE(stop-color, f_stopColor), michael@0: SVG_LITERAL_ATTRIBUTE(stop-opacity, f_stopOpacity), michael@0: SVG_ATTRIBUTE(stroke), michael@0: SVG_LITERAL_ATTRIBUTE(stroke-dasharray, f_strokeDasharray), michael@0: SVG_LITERAL_ATTRIBUTE(stroke-linecap, f_strokeLinecap), michael@0: SVG_LITERAL_ATTRIBUTE(stroke-linejoin, f_strokeLinejoin), michael@0: SVG_LITERAL_ATTRIBUTE(stroke-miterlimit, f_strokeMiterlimit), michael@0: SVG_LITERAL_ATTRIBUTE(stroke-width, f_strokeWidth), michael@0: SVG_ATTRIBUTE(style), michael@0: SVG_ATTRIBUTE(transform) michael@0: }; michael@0: michael@0: const int SkSVGPaint::kAttributesSize = SK_ARRAY_COUNT(SkSVGPaint::gAttributes); michael@0: michael@0: SkSVGPaint::SkSVGPaint() : fNext(NULL) { michael@0: } michael@0: michael@0: SkString* SkSVGPaint::operator[](int index) { michael@0: SkASSERT(index >= 0); michael@0: SkASSERT(index < &fTerminal - &fInitial); michael@0: SkASSERT(&fTerminal - &fInitial == kTerminal - kInitial); michael@0: SkString* result = &fInitial + index + 1; michael@0: return result; michael@0: } michael@0: michael@0: void SkSVGPaint::addAttribute(SkSVGParser& parser, int attrIndex, michael@0: const char* attrValue, size_t attrLength) { michael@0: SkString* attr = (*this)[attrIndex]; michael@0: switch(attrIndex) { michael@0: case kClipPath: michael@0: case kClipRule: michael@0: case kEnableBackground: michael@0: case kFill: michael@0: case kFillRule: michael@0: case kFilter: michael@0: case kFontFamily: michael@0: case kFontSize: michael@0: case kLetterSpacing: michael@0: case kMask: michael@0: case kOpacity: michael@0: case kStopColor: michael@0: case kStopOpacity: michael@0: case kStroke: michael@0: case kStroke_Dasharray: michael@0: case kStroke_Linecap: michael@0: case kStroke_Linejoin: michael@0: case kStroke_Miterlimit: michael@0: case kStroke_Width: michael@0: case kTransform: michael@0: attr->set(attrValue, attrLength); michael@0: return; michael@0: case kStyle: { michael@0: // iterate through colon / semi-colon delimited pairs michael@0: int pairs = SkParse::Count(attrValue, ';'); michael@0: const char* attrEnd = attrValue + attrLength; michael@0: do { michael@0: const char* end = strchr(attrValue, ';'); michael@0: if (end == NULL) michael@0: end = attrEnd; michael@0: const char* delimiter = strchr(attrValue, ':'); michael@0: SkASSERT(delimiter != 0 && delimiter < end); michael@0: int index = parser.findAttribute(this, attrValue, (int) (delimiter - attrValue), true); michael@0: SkASSERT(index >= 0); michael@0: delimiter++; michael@0: addAttribute(parser, index, delimiter, (int) (end - delimiter)); michael@0: attrValue = end + 1; michael@0: } while (--pairs); michael@0: return; michael@0: } michael@0: default: michael@0: SkASSERT(0); michael@0: } michael@0: } michael@0: michael@0: bool SkSVGPaint::flush(SkSVGParser& parser, bool isFlushable, bool isDef) { michael@0: SkSVGPaint current; michael@0: SkSVGPaint* walking = parser.fHead; michael@0: int index; michael@0: while (walking != NULL) { michael@0: for (index = kInitial + 1; index < kTerminal; index++) { michael@0: SkString* lastAttr = (*walking)[index]; michael@0: if (lastAttr->size() == 0) michael@0: continue; michael@0: if (current[index]->size() > 0) michael@0: continue; michael@0: current[index]->set(*lastAttr); michael@0: } michael@0: walking = walking->fNext; michael@0: } michael@0: bool paintChanged = false; michael@0: SkSVGPaint& lastState = parser.fLastFlush; michael@0: if (isFlushable == false) { michael@0: if (isDef == true) { michael@0: if (current.f_mask.size() > 0 && current.f_mask.equals(lastState.f_mask) == false) { michael@0: SkSVGElement* found; michael@0: const char* idStart = strchr(current.f_mask.c_str(), '#'); michael@0: SkASSERT(idStart); michael@0: SkString id(idStart + 1, strlen(idStart) - 2); michael@0: bool itsFound = parser.fIDs.find(id.c_str(), &found); michael@0: SkASSERT(itsFound); michael@0: SkSVGElement* gradient = found->getGradient(); michael@0: if (gradient) { michael@0: gradient->write(parser, current.f_fill); michael@0: gradient->write(parser, current.f_stroke); michael@0: } michael@0: } michael@0: } michael@0: goto setLast; michael@0: } michael@0: { michael@0: bool changed[kTerminal]; michael@0: memset(changed, 0, sizeof(changed)); michael@0: for (index = kInitial + 1; index < kTerminal; index++) { michael@0: if (index == kTransform || index == kClipPath || index == kStopColor || index == kStopOpacity || michael@0: index == kClipRule || index == kFillRule) michael@0: continue; michael@0: SkString* lastAttr = lastState[index]; michael@0: SkString* currentAttr = current[index]; michael@0: paintChanged |= changed[index] = lastAttr->equals(*currentAttr) == false; michael@0: } michael@0: if (paintChanged) { michael@0: if (current.f_mask.size() > 0) { michael@0: if (current.f_fill.equals("none") == false && strncmp(current.f_fill.c_str(), "url(#", 5) != 0) { michael@0: SkASSERT(current.f_fill.c_str()[0] == '#'); michael@0: SkString replacement("url(#mask"); michael@0: replacement.append(current.f_fill.c_str() + 1); michael@0: replacement.appendUnichar(')'); michael@0: current.f_fill.set(replacement); michael@0: } michael@0: if (current.f_stroke.equals("none") == false && strncmp(current.f_stroke.c_str(), "url(#", 5) != 0) { michael@0: SkASSERT(current.f_stroke.c_str()[0] == '#'); michael@0: SkString replacement("url(#mask"); michael@0: replacement.append(current.f_stroke.c_str() + 1); michael@0: replacement.appendUnichar(')'); michael@0: current.f_stroke.set(replacement); michael@0: } michael@0: } michael@0: if (current.f_fill.equals("none") && current.f_stroke.equals("none")) michael@0: current.f_opacity.set("0"); michael@0: if (parser.fSuppressPaint == false) { michael@0: parser._startElement("paint"); michael@0: bool success = writeChangedAttributes(parser, current, changed); michael@0: if (success == false) michael@0: return paintChanged; michael@0: success = writeChangedElements(parser, current, changed); michael@0: if (success == false) michael@0: return paintChanged; michael@0: parser._endElement(); // paint michael@0: } michael@0: } michael@0: } michael@0: setLast: michael@0: for (index = kInitial + 1; index < kTerminal; index++) { michael@0: SkString* lastAttr = lastState[index]; michael@0: SkString* currentAttr = current[index]; michael@0: lastAttr->set(*currentAttr); michael@0: } michael@0: return paintChanged; michael@0: } michael@0: michael@0: int SkSVGPaint::getAttributes(const SkSVGAttribute** attrPtr) { michael@0: *attrPtr = gAttributes; michael@0: return kAttributesSize; michael@0: } michael@0: michael@0: void SkSVGPaint::setSave(SkSVGParser& parser) { michael@0: SkTDArray clips; michael@0: SkSVGPaint* walking = parser.fHead; michael@0: int index; michael@0: SkMatrix sum; michael@0: sum.reset(); michael@0: while (walking != NULL) { michael@0: for (index = kInitial + 1; index < kTerminal; index++) { michael@0: SkString* lastAttr = (*walking)[index]; michael@0: if (lastAttr->size() == 0) michael@0: continue; michael@0: if (index == kTransform) { michael@0: const char* str = lastAttr->c_str(); michael@0: SkASSERT(strncmp(str, "matrix(", 7) == 0); michael@0: str += 6; michael@0: const char* strEnd = strrchr(str, ')'); michael@0: SkASSERT(strEnd != NULL); michael@0: SkString mat(str, strEnd - str); michael@0: SkSVGParser::ConvertToArray(mat); michael@0: SkScalar values[6]; michael@0: SkParse::FindScalars(mat.c_str() + 1, values, 6); michael@0: SkMatrix matrix; michael@0: matrix.reset(); michael@0: matrix.setScaleX(values[0]); michael@0: matrix.setSkewY(values[1]); michael@0: matrix.setSkewX(values[2]); michael@0: matrix.setScaleY(values[3]); michael@0: matrix.setTranslateX(values[4]); michael@0: matrix.setTranslateY(values[5]); michael@0: sum.setConcat(matrix, sum); michael@0: continue; michael@0: } michael@0: if ( index == kClipPath) michael@0: *clips.insert(0) = lastAttr; michael@0: } michael@0: walking = walking->fNext; michael@0: } michael@0: if ((sum == parser.fLastTransform) == false) { michael@0: SkMatrix inverse; michael@0: bool success = parser.fLastTransform.invert(&inverse); michael@0: SkASSERT(success == true); michael@0: SkMatrix output; michael@0: output.setConcat(inverse, sum); michael@0: parser.fLastTransform = sum; michael@0: SkString outputStr; michael@0: outputStr.appendUnichar('['); michael@0: outputStr.appendScalar(output.getScaleX()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getSkewX()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getTranslateX()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getSkewY()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getScaleY()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getTranslateY()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getPerspX()); michael@0: outputStr.appendUnichar(','); michael@0: outputStr.appendScalar(output.getPerspY()); michael@0: outputStr.append(",1]"); michael@0: parser._startElement("matrix"); michael@0: parser._addAttributeLen("matrix", outputStr.c_str(), outputStr.size()); michael@0: parser._endElement(); michael@0: } michael@0: #if 0 // incomplete michael@0: if (parser.fTransformClips.size() > 0) { michael@0: // need to reset the clip when the 'g' scope is ended michael@0: parser._startElement("add"); michael@0: const char* start = strchr(current->f_clipPath.c_str(), '#') + 1; michael@0: SkASSERT(start); michael@0: parser._addAttributeLen("use", start, strlen(start) - 1); michael@0: parser._endElement(); // clip michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: bool SkSVGPaint::writeChangedAttributes(SkSVGParser& parser, michael@0: SkSVGPaint& current, bool* changed) { michael@0: SkSVGPaint& lastState = parser.fLastFlush; michael@0: for (int index = kInitial + 1; index < kTerminal; index++) { michael@0: if (changed[index] == false) michael@0: continue; michael@0: SkString* topAttr = current[index]; michael@0: size_t attrLength = topAttr->size(); michael@0: if (attrLength == 0) michael@0: continue; michael@0: const char* attrValue = topAttr->c_str(); michael@0: SkString* lastAttr = lastState[index]; michael@0: switch(index) { michael@0: case kClipPath: michael@0: case kClipRule: michael@0: case kEnableBackground: michael@0: break; michael@0: case kFill: michael@0: if (topAttr->equals("none") == false && lastAttr->equals("none") == true) michael@0: parser._addAttribute("stroke", "false"); michael@0: goto fillStrokeAttrCommon; michael@0: case kFillRule: michael@0: case kFilter: michael@0: case kFontFamily: michael@0: break; michael@0: case kFontSize: michael@0: parser._addAttributeLen("textSize", attrValue, attrLength); michael@0: break; michael@0: case kLetterSpacing: michael@0: parser._addAttributeLen("textTracking", attrValue, attrLength); michael@0: break; michael@0: case kMask: michael@0: break; michael@0: case kOpacity: michael@0: break; michael@0: case kStopColor: michael@0: break; michael@0: case kStopOpacity: michael@0: break; michael@0: case kStroke: michael@0: if (topAttr->equals("none") == false && lastAttr->equals("none") == true) michael@0: parser._addAttribute("stroke", "true"); michael@0: fillStrokeAttrCommon: michael@0: if (strncmp(attrValue, "url(", 4) == 0) { michael@0: SkASSERT(attrValue[4] == '#'); michael@0: const char* idStart = attrValue + 5; michael@0: const char* idEnd = strrchr(attrValue, ')'); michael@0: SkASSERT(idStart < idEnd); michael@0: SkString id(idStart, idEnd - idStart); michael@0: SkSVGElement* found; michael@0: if (strncmp(id.c_str(), "mask", 4) != 0) { michael@0: bool itsFound = parser.fIDs.find(id.c_str(), &found); michael@0: SkASSERT(itsFound); michael@0: SkASSERT(found->getType() == SkSVGType_LinearGradient || michael@0: found->getType() == SkSVGType_RadialGradient); michael@0: } michael@0: parser._addAttribute("shader", id.c_str()); michael@0: } michael@0: break; michael@0: case kStroke_Dasharray: michael@0: break; michael@0: case kStroke_Linecap: michael@0: parser._addAttributeLen("strokeCap", attrValue, attrLength); michael@0: break; michael@0: case kStroke_Linejoin: michael@0: parser._addAttributeLen("strokeJoin", attrValue, attrLength); michael@0: break; michael@0: case kStroke_Miterlimit: michael@0: parser._addAttributeLen("strokeMiter", attrValue, attrLength); michael@0: break; michael@0: case kStroke_Width: michael@0: parser._addAttributeLen("strokeWidth", attrValue, attrLength); michael@0: case kStyle: michael@0: case kTransform: michael@0: break; michael@0: default: michael@0: SkASSERT(0); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool SkSVGPaint::writeChangedElements(SkSVGParser& parser, michael@0: SkSVGPaint& current, bool* changed) { michael@0: SkSVGPaint& lastState = parser.fLastFlush; michael@0: for (int index = kInitial + 1; index < kTerminal; index++) { michael@0: SkString* topAttr = current[index]; michael@0: size_t attrLength = topAttr->size(); michael@0: if (attrLength == 0) michael@0: continue; michael@0: const char* attrValue = topAttr->c_str(); michael@0: SkString* lastAttr = lastState[index]; michael@0: switch(index) { michael@0: case kClipPath: michael@0: case kClipRule: michael@0: // !!! need to add this outside of paint michael@0: break; michael@0: case kEnableBackground: michael@0: // !!! don't know what to do with this michael@0: break; michael@0: case kFill: michael@0: goto addColor; michael@0: case kFillRule: michael@0: case kFilter: michael@0: break; michael@0: case kFontFamily: michael@0: parser._startElement("typeface"); michael@0: parser._addAttributeLen("fontName", attrValue, attrLength); michael@0: parser._endElement(); // typeface michael@0: break; michael@0: case kFontSize: michael@0: case kLetterSpacing: michael@0: break; michael@0: case kMask: michael@0: case kOpacity: michael@0: if (changed[kStroke] == false && changed[kFill] == false) { michael@0: parser._startElement("color"); michael@0: SkString& opacity = current.f_opacity; michael@0: parser._addAttributeLen("color", parser.fLastColor.c_str(), parser.fLastColor.size()); michael@0: parser._addAttributeLen("alpha", opacity.c_str(), opacity.size()); michael@0: parser._endElement(); // color michael@0: } michael@0: break; michael@0: case kStopColor: michael@0: break; michael@0: case kStopOpacity: michael@0: break; michael@0: case kStroke: michael@0: addColor: michael@0: if (strncmp(lastAttr->c_str(), "url(", 4) == 0 && strncmp(attrValue, "url(", 4) != 0) { michael@0: parser._startElement("shader"); michael@0: parser._endElement(); michael@0: } michael@0: if (topAttr->equals(*lastAttr)) michael@0: continue; michael@0: { michael@0: bool urlRef = strncmp(attrValue, "url(", 4) == 0; michael@0: bool colorNone = strcmp(attrValue, "none") == 0; michael@0: bool lastEqual = parser.fLastColor.equals(attrValue, attrLength); michael@0: bool newColor = urlRef == false && colorNone == false && lastEqual == false; michael@0: if (newColor || changed[kOpacity]) { michael@0: parser._startElement("color"); michael@0: if (newColor || changed[kOpacity]) { michael@0: parser._addAttributeLen("color", attrValue, attrLength); michael@0: parser.fLastColor.set(attrValue, attrLength); michael@0: } michael@0: if (changed[kOpacity]) { michael@0: SkString& opacity = current.f_opacity; michael@0: parser._addAttributeLen("alpha", opacity.c_str(), opacity.size()); michael@0: } michael@0: parser._endElement(); // color michael@0: } michael@0: } michael@0: break; michael@0: case kStroke_Dasharray: michael@0: parser._startElement("dash"); michael@0: SkSVGParser::ConvertToArray(*topAttr); michael@0: parser._addAttribute("intervals", topAttr->c_str()); michael@0: parser._endElement(); // dash michael@0: break; michael@0: case kStroke_Linecap: michael@0: case kStroke_Linejoin: michael@0: case kStroke_Miterlimit: michael@0: case kStroke_Width: michael@0: case kStyle: michael@0: case kTransform: michael@0: break; michael@0: default: michael@0: SkASSERT(0); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void SkSVGPaint::Push(SkSVGPaint** head, SkSVGPaint* newRecord) { michael@0: newRecord->fNext = *head; michael@0: *head = newRecord; michael@0: } michael@0: michael@0: void SkSVGPaint::Pop(SkSVGPaint** head) { michael@0: SkSVGPaint* next = (*head)->fNext; michael@0: *head = next; michael@0: }