/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "SvgImportUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SVG_IMAGE_DEF "image" #define SVG_BASE64_DEF OD_T("base64") struct SvgImportProperties : OdRxDispatchImpl<> { OdString m_Block; OdString m_Path; OdDbDatabasePtr m_pDb; OdString m_ImagePath; OdIntPtr m_Palette = 0; double m_LineWeightScale = 25.0; double m_Tolerance = 0.0; OdStreamBufPtr m_InputStream; ODRX_DECLARE_DYNAMIC_PROPERTY_MAP(SvgImportProperties); OdRxObjectPtr get_Database() const { return m_pDb.get(); } void put_Database(OdRxObject* obj) { m_pDb = obj; } OdString get_SvgPath() const { return m_Path; } void put_SvgPath(const OdString& path) { m_Path = path; } OdString get_ImagePath() const { return m_ImagePath; } void put_ImagePath(const OdString& path) { m_ImagePath = path; } OdString get_BlockName() const { return m_Block; } void put_BlockName(const OdString& path) { m_Block = path; } OdIntPtr get_Palette() const { return m_Palette; } void put_Palette(OdIntPtr p) { m_Palette = p; } double get_LineWeightScale() const { return m_LineWeightScale; } void put_LineWeightScale(double d) { m_LineWeightScale = d; } double get_Tolerance() const { return m_Tolerance; } void put_Tolerance(double d) { m_Tolerance = d; } OdRxObjectPtr get_InputStream() const { return OdRxObject::cast(m_InputStream); } void put_InputStream(OdRxObject* obj) { m_InputStream = obj; } }; ODRX_DECLARE_PROPERTY(Database) ODRX_DECLARE_PROPERTY(SvgPath) ODRX_DECLARE_PROPERTY(ImagePath) ODRX_DECLARE_PROPERTY(BlockName) ODRX_DECLARE_PROPERTY(Palette) ODRX_DECLARE_PROPERTY(LineWeightScale) ODRX_DECLARE_PROPERTY(Tolerance) ODRX_DECLARE_PROPERTY(InputStream) ODRX_DEFINE_PROPERTY(SvgPath, SvgImportProperties, getString) ODRX_DEFINE_PROPERTY(ImagePath, SvgImportProperties, getString) ODRX_DEFINE_PROPERTY(BlockName, SvgImportProperties, getString) ODRX_DEFINE_PROPERTY_OBJECT(Database, SvgImportProperties, get_Database, put_Database, OdDbDatabase) ODRX_DEFINE_PROPERTY(Palette, SvgImportProperties, getIntPtr) ODRX_DEFINE_PROPERTY(LineWeightScale, SvgImportProperties, getDouble) ODRX_DEFINE_PROPERTY(Tolerance, SvgImportProperties, getDouble) ODRX_DEFINE_PROPERTY_OBJECT(InputStream, SvgImportProperties, get_InputStream, put_InputStream, OdStreamBuf) ODRX_BEGIN_DYNAMIC_PROPERTY_MAP(SvgImportProperties); ODRX_GENERATE_PROPERTY(Database) ODRX_GENERATE_PROPERTY(SvgPath) ODRX_GENERATE_PROPERTY(ImagePath) ODRX_GENERATE_PROPERTY(BlockName) ODRX_GENERATE_PROPERTY(Palette) ODRX_GENERATE_PROPERTY(LineWeightScale) ODRX_GENERATE_PROPERTY(Tolerance) ODRX_GENERATE_PROPERTY(InputStream) ODRX_END_DYNAMIC_PROPERTY_MAP(SvgImportProperties); static OdString getCurrentDir(OdString fileName) { int i = fileName.reverseFind('\\'); int i2 = fileName.reverseFind('/'); if (i2 > i) i = i2; return fileName.mid(0, i + 1); } class CSVGImportTextData; typedef OdArray> SVGTextDataArray; class SvgImporter : public OdSvgImport { OdDbBlockTableRecordPtr m_Block; OdSmartPtr m_pProperties; std::unordered_map m_NamedColors; struct Gradient { enum eGradientUnits { eUserSpaceOnUse , eObjectBoundingBox }; double angle = 0.0; double shift = 0.0; OdGePoint3d center; OdString name; OdCmColor color1; OdCmColor color2; eGradientUnits eGradUnits = eObjectBoundingBox; }; std::unordered_map m_Gradients; std::unordered_map m_References; std::unordered_map m_ClipPaths; std::unordered_map m_Classes; struct Traits { OdGeMatrix3d transform; // actually always 2d, but since OdDbEntity::transformBy accepts 3d it is more efficient to store it as 3d OdCmColor color = OdCmEntityColor::kByLayer; OdCmColor fill = OdCmEntityColor::kNone; OdCmTransparency strokeTransparency; OdCmTransparency fillTransparency; OdDb::LineWeight lineWeight = OdDb::kLnWtByLayer; Gradient* gradient = nullptr; OdString href; OdGePoint2dArray clip; }; std::stack m_Traits; std::deque m_svgStack; std::unordered_map m_Styles; double m_Tolerance = 0.0; unsigned short int m_nImageCounter = 1; public: SvgImporter() { m_pProperties = OdRxObjectImpl::createObject(); loadNamedColors(); } ImportResult import() override; OdRxDictionaryPtr properties() override { return m_pProperties; } private: void loadExternalClasses(const OdAnsiString& fileContent); void processDefinitions(const TiXmlElement* elemNode); void processGroupsOrGeometry(const TiXmlElement* elemNode); void addSvg(const TiXmlElement* svgNode); void addSvgTransform(const TiXmlElement* svgNode); void addGeometry(const TiXmlElement* node); void addImage(const TiXmlElement* node); void addClasses(const char* textClasses); OdString addInlineImage(OdString strImgData); void addText(const TiXmlElement* node); bool addTextData(const TiXmlElement* node, const CSVGImportTextData* pParentTData, SVGTextDataArray& arrTextChain, int& nLastRow); CSVGImportTextData* addNewTextToChain(const TiXmlElement* node, const CSVGImportTextData* pParentTData, SVGTextDataArray& arrTextChain); void appendTextChain(const SVGTextDataArray& textChain); OdDbEntityPtrArray parsePath(const TiXmlElement* node); void startNewPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void closeCurentPath(int startPathIndex, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, bool updateLastPoint = false); void addLinePath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addHorizontalPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addVerticalPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addEllipticPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addCubicBezier(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addCubicBezierShort(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addQuadraticBezier(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addQuadraticBezierShort(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args); void addRect(const TiXmlElement* node); void addLine(const TiXmlElement* node); void addEllipse(const TiXmlElement* node); void addCircle(const TiXmlElement* node); void addPolyline(const TiXmlElement* node, bool closed); void addLinearGradient(const TiXmlElement* elemNode); void addRadialGradient(const TiXmlElement* elemNode); void getGradientSettings(double& dShift, double& dAngle, const OdDbHatch* pHatch, const Gradient* pG) const; void appendEntity(OdDbEntity* e, const TiXmlElement* node, bool applyTransform = true) { if (m_Traits.top().color.colorMethod() != OdCmEntityColor::kNone || m_Traits.top().fill != OdCmEntityColor::kNone || m_Traits.top().gradient != nullptr) m_Block->appendOdDbEntity(e); applyTraits({ e }, node, applyTransform); } void appendEntities(const OdDbEntityPtrArray& ents, const TiXmlElement* node, bool applyTransform = true) { if (m_Traits.top().color.colorMethod() != OdCmEntityColor::kNone || m_Traits.top().fill != OdCmEntityColor::kNone || m_Traits.top().gradient != nullptr) for (auto e : ents) { if (!e.isNull()) m_Block->appendOdDbEntity(e); } applyTraits(ents, node, applyTransform); } void pushTraits(const TiXmlElement* node); void popTraits() { m_Traits.pop(); } void applyTraits(const OdDbEntityPtrArray& ents, const TiXmlElement* node, bool applyTransform); void nodePreprocessing(const TiXmlElement* nodeC); void setClasses(const TiXmlElement* nodeC); void setClassData(const TiXmlElement* nodeC, const std::string& strClassName); void groupCurvesIntoLoops(const OdDbEntityPtrArray& ents, OdArray& arrLoops); void addHatches(const OdDbEntityPtrArray& ents, const TiXmlElement* node); void addSimpleHatch(const OdArray& arrLoops); void addHatchProps(OdDbHatch* h); void addHatches(const OdArray& arrLoops); void addHatch(const LoopNode* node); void addHatchLoops(OdDbHatch* h, const LoopNode* node); void addHyperlink(OdDbObject* e); void applyPalette(OdCmColor& c); OdCmColor parseColor(const char* s, Gradient** gradient, bool bApplyPalette = true); OdDb::LineWeight parseLineweight(const char* strokeWidth); void parseStyleAttrs(const std::string strStyle, TiXmlElement* elm); void loadNamedColors(); OdDbObjectId getTextStyle(const CSVGImportTextData* pTData); OdDbDatabase* database() const { return m_Block->database(); } OdString getNewImagePath(OdString strSvgPath, OdString strImgExt); bool isCurrentElmFilled() const; double getDbl(const char* val, const char* attrName) const; const char* attributeBySvgStack(const char* name) const; public: static OdUInt32 decodeBase64(const OdString& stringToDecode, OdBinaryData& out); }; class SvgImportModule : public OdSvgImportModule { public: virtual void initApp() override {} virtual void uninitApp() override {} virtual OdSvgImportPtr create() override { return OdRxObjectImpl::createObject(); } }; ODRX_DEFINE_DYNAMIC_MODULE(SvgImportModule) static OdDbBlockTableRecordPtr getTargetBlock(OdDbDatabase* db, const OdString& name) { if (name.isEmpty()) return db->getModelSpaceId().safeOpenObject(OdDb::kForWrite); OdDbBlockTablePtr bt = db->getBlockTableId().safeOpenObject(); auto id = bt->getAt(name); if (id.isNull()) { bt->upgradeOpen(); auto b = OdDbBlockTableRecord::createObject(); b->setName(name); bt->add(b); return b; } else return id.safeOpenObject(OdDb::kForWrite); } static std::vector parseDoubleArray(std::string::const_iterator begin, std::string::const_iterator end, bool bCatchFlags = false) { static std::regex wscomma("(\\s+)|(\\s*\\,\\s*)", std::regex::nosubs | std::regex::optimize); std::vector dd; auto endToken = std::sregex_token_iterator(); for (auto it = std::sregex_token_iterator(begin, end, wscomma, -1); it != endToken; ++it) { std::string token = *it; size_t dotCount = std::count(token.begin(), token.end(), '.'); bool bFlags(false); if (bCatchFlags && (token.find("01") == 0 || token.find("00") == 0)) bFlags = true; //Doubles can be written as "5.1-8.7,12-15.6,20.6-20.8" with "-" separators between the numbers //They can also be written without a leading zero as -14.86.166 (-14.86 and 0.166) and 20.376.319 (20.376 and 0.319) (dotCount) if (token.find('-') != std::string::npos || dotCount > 1 || bFlags) { if (bFlags) { dd.push_back(0); if (token.find("01") == 0) dd.push_back(1); else dd.push_back(0); token = token.substr(2, token.length()); } static std::regex digits("-?(?:\\d*\\.\\d+|\\d+)(?:[eE][+-]?\\d+)?", std::regex::nosubs | std::regex::optimize); std::vector ss{ std::sregex_token_iterator(token.begin(), token.end(), digits), std::sregex_token_iterator() }; for (const auto& subSS : ss) dd.push_back(odStrToD(subSS.c_str())); } else dd.push_back(odStrToD(token.c_str())); } return dd; } static double caclulateTolerance(const char* s) { if (s == nullptr) return OdGeContext::gTol.equalPoint(); std::string viewBox(s); auto pts = parseDoubleArray(viewBox.begin(), viewBox.end()); if (pts.size() < 4) return OdGeContext::gTol.equalPoint(); return std::max(pts[2], pts[3]) / 10000; } static void zoomExtents(OdDbDatabase* db, OdDbBlockTableRecord* block) { if (block->isLayout()) db->setCurrentLayout(block->getLayoutId()); else return; OdDbObjectPtr pVp; if (block->objectId() == db->getModelSpaceId()) { pVp = db->activeViewportId().safeOpenObject(OdDb::kForWrite); } else { OdDbLayoutPtr pLayout = block->getLayoutId().safeOpenObject(); OdDbObjectId overallVpId = pLayout->overallVportId(); pVp = overallVpId.safeOpenObject(OdDb::kForWrite); } OdAbstractViewPEPtr(pVp)->zoomExtents(pVp); } class CSVGImportTextData { public: enum eRelPosType { eNone = 0, eRelPosX, eRelPosY, eRelPosBoth }; public: CSVGImportTextData() : m_strContent(OD_T("")), m_origin(OdGePoint3d::kOrigin), m_relPosType(eNone), m_bAsNewTextEnt(false), m_node(NULL), m_dFontSize(16.0) {} CSVGImportTextData(const TiXmlElement* node, const CSVGImportTextData* pParentTData) : m_strContent(OD_T("")), m_origin(OdGePoint3d::kOrigin), m_relPosType(eNone), m_bAsNewTextEnt(false), m_node(node), m_dFontSize(16.0) { setSize(node, pParentTData); setPosition(node); fillStyleData(node, pParentTData); } ~CSVGImportTextData() {} double getSize() const { return m_dFontSize; } double setDimVal(const char* val) { double dbl(.0); auto l = strlen(val); if (l > 1 && val[l - 2] == 'e' && val[l - 1] == 'm') { std::string s(val); double dEmFactor = odStrToD(s.substr(0, s.length() - 2).c_str()); dbl = dEmFactor * m_dFontSize; } else dbl = odStrToD(val); return dbl; } void setSize(const TiXmlElement* node, const CSVGImportTextData* pParentTData) { if (!node) return; auto attrSize = node->Attribute("font-size"); if (!attrSize && pParentTData) { auto attrSizeParent = pParentTData->getStyleValue("font-size"); if (attrSizeParent) attrSize = attrSizeParent->c_str(); } if (!attrSize) return; std::string h(attrSize); double dSize = odStrToD(h.c_str()); auto pos = h.find("rem"); if (pos != std::string::npos) { double dRemFactor = odStrToD(h.substr(0, pos).c_str()); dSize = m_dFontSize * dRemFactor; } m_dFontSize = dSize; } void setPosition(const TiXmlElement* node) { if (!node) return; auto x = node->Attribute("x"); auto y = node->Attribute("y"); auto dx = node->Attribute("dx"); auto dy = node->Attribute("dy"); auto transform = node->Attribute("transform"); if (x) m_origin.x = setDimVal(x); if (y) m_origin.y = -setDimVal(y); if (dx) m_origin.x += setDimVal(dx); if (dy) m_origin.y += -setDimVal(dy); if (x || y || dx || dy || transform) m_bAsNewTextEnt = true; if (!x && !y) m_relPosType = eRelPosBoth; else if (!x) m_relPosType = eRelPosX; else if (!y) m_relPosType = eRelPosY; } bool asNewText() const { return m_bAsNewTextEnt; } void setPosition(const OdGePoint3d& pos, eRelPosType relPosType) { m_origin = pos; m_relPosType = relPosType; } void setContent(const char* srcBuf) { m_strContent = srcBuf; } void appendContent(const char* srcBuf) { m_strContent += srcBuf; } const TiXmlElement* getNode() const { return m_node; } void fillStyleData(const TiXmlElement* node, const CSVGImportTextData* pParentTData) { const std::vector m_styleNamesArr = { "font-family" , "font-style", "font-weight", "font-size", "fill", "text-anchor"}; for (const auto& strStyle : m_styleNamesArr) { auto attr = node->Attribute(strStyle.c_str()); m_styleMap[strStyle] = (attr) ? attr : ""; if (pParentTData) { auto styleVal = pParentTData->getStyleValue(strStyle); if (!styleVal || styleVal->empty()) { if (!m_styleMap[strStyle].empty()) m_bAsNewTextEnt = true; continue; } if (m_styleMap[strStyle].empty()) { m_styleMap[strStyle] = *styleVal; continue; } if ((*styleVal) != m_styleMap[strStyle]) m_bAsNewTextEnt = true; } } } const std::string* getStyleValue(const std::string& strStyle) const { auto it = m_styleMap.find(strStyle); if (it == m_styleMap.end()) return nullptr; return &it->second; } OdDbTextPtr createTextObject(OdDbDatabase* pDb, const OdGePoint3d& basePnt) const { OdDbTextPtr dbText = OdDbText::createObject(); dbText->setDatabaseDefaults(pDb); OdCharArray dstBuf; OdCharMapper::utf8ToUnicode(m_strContent, (unsigned)m_strContent.getLength(), dstBuf); dbText->setTextString(dstBuf.getPtr()); OdGePoint3d origin = m_origin; switch (m_relPosType) { case eRelPosX: origin.x = basePnt.x + origin.x; break; case eRelPosY: origin.y = basePnt.y + origin.y; break; case eRelPosBoth: origin.x = basePnt.x + origin.x; origin.y = basePnt.y + origin.y; break; } dbText->setPosition(origin); if (auto fa = getStyleValue("text-anchor")) { if (strcmp(fa->c_str(), "end") == 0) { dbText->setHorizontalMode(OdDb::kTextRight); } else if (strcmp(fa->c_str(), "middle") == 0) { dbText->setHorizontalMode(OdDb::kTextMid); } dbText->setAlignmentPoint(origin); } return dbText; } private: OdString m_strContent; OdGePoint3d m_origin; eRelPosType m_relPosType; bool m_bAsNewTextEnt; std::map m_styleMap; const TiXmlElement* m_node; double m_dFontSize; }; OdSvgImport::ImportResult SvgImporter::import() { auto db = OdDbDatabase::cast(m_pProperties->get_Database()); if (db.isNull()) return ImportResult::bad_database; auto pRVars = OdDbRasterVariables::openRasterVariables(db, OdDb::kForWrite); pRVars->setImageFrame(OdDbRasterVariables::kImageFrameOff); db->setFRAME(3); try { m_Block = getTargetBlock(db, m_pProperties->get_BlockName()); } catch (const OdError&) { return ImportResult::bad_block; } OdStreamBufPtr pFileStream = m_pProperties->get_InputStream(); if (pFileStream.isNull()) pFileStream = odrxSystemServices()->createFile(m_pProperties->get_SvgPath()); if (pFileStream.isNull() || pFileStream->isEof()) return ImportResult::bad_file; const OdInt32 nlen = static_cast(pFileStream->length()); OdAnsiString strFileContent; pFileStream->getBytes(strFileContent.getBufferSetLength(nlen), nlen); TiXmlDocument _xml; _xml.Parse(strFileContent); loadExternalClasses(strFileContent); const TiXmlElement* root = _xml.RootElement(); if (root->Value() != OdAnsiString("svg")) return ImportResult::bad_file; m_Traits.push(Traits()); m_Traits.top().transform = OdGeMatrix3d::mirroring(OdGePlane(OdGePoint3d::kOrigin, OdGeVector3d::kYAxis)); // SVG coordinates are upside down m_Tolerance = m_pProperties->get_Tolerance(); if (m_Tolerance <= 0) m_Tolerance = caclulateTolerance(root->Attribute("viewBox")); { OdDbTransactionWrapper trans(db); addSvg(root); trans.endTransaction(); } zoomExtents(db, m_Block); if (m_Block->newIterator()->done()) return ImportResult::no_objects_imported; return ImportResult::success; } void SvgImporter::loadExternalClasses(const OdAnsiString& fileContent) { if (fileContent.isEmpty()) return; int start = 0; int end; while ((end = fileContent.find('\n', start)) != -1) { OdString line = fileContent.mid(start, end - start); if (line.find(L"get_SvgPath()) + path; try { OdStreamBufPtr pCssStream = odrxSystemServices()->createFile(strCssPath); OdAnsiString strCssContent; pCssStream->getBytes(strCssContent.getBuffer(static_cast(pCssStream->length())), static_cast(pCssStream->length())); addClasses(strCssContent); } catch (const OdError&) { ODA_FAIL_M_ONCE("CSS file not found"); } } } start = end + 1; } } // TODO: read definitions (gradients, text) // https://opendesign.atlassian.net/browse/CORE-25362 void SvgImporter::processDefinitions(const TiXmlElement* elemNode) { auto id = elemNode->Attribute("id"); if (id == nullptr && strcmp(elemNode->Value(), "style") != 0) return; if (strcmp(elemNode->Value(), "linearGradient") == 0) addLinearGradient(elemNode); else if (strcmp(elemNode->Value(), "radialGradient") == 0) addRadialGradient(elemNode); else if (strcmp(elemNode->Value(), "filter") == 0) { } else if (strcmp(elemNode->Value(), "clipPath") == 0) { if (auto id = elemNode->Attribute("id")) { for (const TiXmlElement* child = elemNode->FirstChildElement(); child; child = child->NextSiblingElement()) { if (strcmp(child->Value(), "path") == 0) { OdGePoint2dArray clip; for (auto e : parsePath(child)) { if (auto c = OdDbCurve::cast(e)) { OdGeCurve3d* pGeCurve = nullptr; if (eOk == c->getOdGeCurve(pGeCurve)) { OdGeInterval interval; pGeCurve->getInterval(interval); OdGePoint3dArray pts; pGeCurve->getSamplePoints(&interval, m_Tolerance, pts); for (const auto& p : pts) { OdGePoint2d newPoint(p.x, p.y); if (clip.empty() || clip.last() != newPoint) clip.push_back(newPoint); } delete pGeCurve; } } } if (clip.size() > 0) { m_ClipPaths[id] = clip; break; } } } } } else if (strcmp(elemNode->Value(), "style") == 0) { addClasses(elemNode->GetText()); } else // everything else for { m_References[id] = elemNode; } } void SvgImporter::processGroupsOrGeometry(const TiXmlElement* elemNode) { nodePreprocessing(elemNode); pushTraits(elemNode); if (strcmp(elemNode->Value(), "g") == 0) // group { for (const TiXmlElement* child = elemNode->FirstChildElement(); child; child = child->NextSiblingElement()) processGroupsOrGeometry(child); } else if (strcmp(elemNode->Value(), "a") == 0) // link { m_Traits.top().href = OdAnsiString(elemNode->Attribute("xlink:href"), CP_UTF_8); for (const TiXmlElement* child = elemNode->FirstChildElement(); child; child = child->NextSiblingElement()) processGroupsOrGeometry(child); } else if (strcmp(elemNode->Value(), "use") == 0) // reference { auto id = elemNode->Attribute("xlink:href"); if (id && *id == '#') { auto r = m_References.find(id + 1); if (r != m_References.end()) processGroupsOrGeometry(r->second); } else // global IRI ? { ODA_FAIL_M_ONCE("Unknown SVG reference type skipped"); } } else if (strcmp(elemNode->Value(), "title") == 0 || strcmp(elemNode->Value(), "desc") == 0) { } else { addGeometry(elemNode); } popTraits(); } void SvgImporter::addSvg(const TiXmlElement* svgNode) { for (const TiXmlElement* elemNode = svgNode->FirstChildElement("defs"); elemNode; elemNode = elemNode->NextSiblingElement("defs")) { for (const TiXmlElement* def = elemNode->FirstChildElement(); def; def = def->NextSiblingElement()) processDefinitions(def); } for (const TiXmlElement* elemNode = svgNode->FirstChildElement(); elemNode; elemNode = elemNode->NextSiblingElement()) { if (strcmp(elemNode->Value(), "defs") != 0) { processGroupsOrGeometry(elemNode); } } } void SvgImporter::addSvgTransform(const TiXmlElement* svgNode) { auto h = attributeBySvgStack("height"); auto w = attributeBySvgStack("width"); auto vb = svgNode->Attribute("viewBox"); if (h && w && vb) { double dh = odStrToD(h); double dw = odStrToD(w); std::string viewBox(vb); auto pts = parseDoubleArray(viewBox.begin(), viewBox.end()); if (pts.size() >= 4) { double dSclX = dw / pts[2]; double dSclY = dh / pts[3]; auto& t = m_Traits.top(); t.transform.postMultBy(OdGeMatrix3d::scaling(odmin(dSclX, dSclY))); } } auto x = svgNode->Attribute("x"); auto y = svgNode->Attribute("y"); if (x && y) { double dx = odStrToD(x); double dy = odStrToD(y); auto& t = m_Traits.top(); t.transform.postMultBy(OdGeMatrix3d::translation(OdGeVector3d(dx, dy, 0.0))); } } void SvgImporter::addGeometry(const TiXmlElement* node) { if (strcmp(node->Value(), "polyline") == 0) { addPolyline(node, false); } else if (strcmp(node->Value(), "line") == 0) { addLine(node); } else if (strcmp(node->Value(), "circle") == 0) { addCircle(node); } else if (strcmp(node->Value(), "ellipse") == 0) { addEllipse(node); } else if (strcmp(node->Value(), "rect") == 0) { addRect(node); } else if (strcmp(node->Value(), "polygon") == 0) { addPolyline(node, true); } else if (strcmp(node->Value(), "path") == 0) { appendEntities(parsePath(node), node); } else if (strcmp(node->Value(), "text") == 0) { addText(node); } else if (strcmp(node->Value(), SVG_IMAGE_DEF) == 0) { addImage(node); } else if (strcmp(node->Value(), "style") == 0) { addClasses(node->GetText()); } else if (strcmp(node->Value(), "linearGradient") == 0) { addLinearGradient(node); } else if (strcmp(node->Value(), "defs") == 0) { for (const TiXmlElement* def = node->FirstChildElement(); def; def = def->NextSiblingElement()) processDefinitions(def); } else if (strcmp(node->Value(), "radialGradient") == 0) { addRadialGradient(node); } else if (strcmp(node->Value(), "svg") == 0) { size_t size = m_Traits.size(); m_svgStack.push_back(node); m_Traits.push(m_Traits.top()); addSvgTransform(node); addSvg(node); m_svgStack.pop_back(); while (m_Traits.size() != size) popTraits(); } else { ODA_FAIL_M_ONCE("Unknown SVG element skipped"); } } static OdGeMatrix3d parseTransform(const char* s) { OdGeMatrix3d m; std::string text(s); static std::regex re("(\\w+)\\s*\\(\\s*([^\\)]*?)\\s*\\)", std::regex::optimize); // match "func(args)" pieces for (auto it = std::sregex_iterator(text.begin(), text.end(), re); it != std::sregex_iterator(); ++it) { std::string func = (*it)[1].str(); auto args = parseDoubleArray((*it)[2].first, (*it)[2].second); if (args.empty()) { ODA_FAIL_M_ONCE("Invalid transform format"); continue; } if (func == "scale") { m.postMultBy(OdGeMatrix3d::scaling(OdGeScale3d(args[0], args.size() == 1 ? args[0] : args[1], 1.0*std::abs(args[0])))); } else if (func == "rotate") { OdGePoint3d center; if (args.size() > 2) { center.x = args[1]; center.y = args[2]; } m.postMultBy(OdGeMatrix3d::rotation(OdaToRadian(args[0]), OdGeVector3d::kZAxis, center)); } else if (func == "translate") { m.postMultBy(OdGeMatrix3d::translation(OdGeVector3d(args[0], args.size() > 1 ? args[1] : 0.0, 0.0))); } else if (func == "skewX") { OdGeMatrix3d m1; m1(0, 1) = tan(OdaToRadian(args[0])); m.postMultBy(m1); } else if (func == "skewY") { OdGeMatrix3d m1; m1(1, 0) = tan(OdaToRadian(args[0])); m.postMultBy(m1); } else if (func == "matrix") { if (args.size() == 6) { OdGeMatrix3d m1; m1(0, 0) = args[0]; m1(1, 0) = args[1]; m1(0, 1) = args[2]; m1(1, 1) = args[3]; m1(0, 3) = args[4]; m1(1, 3) = args[5]; //To ensure uniform orthogonal scaling { OdGeVector3d x(args[0], args[2], 0); m1(2, 2) *= x.length(); } m.postMultBy(m1); } else { ODA_FAIL_M_ONCE("Invalid matrix format"); } } else { ODA_FAIL_M_ONCE("Unknown transform?"); } } return m; } static void transformClip(const OdGeMatrix3d& m, OdGePoint2dArray& clip) { if (clip.empty()) return; OdGeVector3d normal{ OdGeVector3d::kZAxis }; double elevation{ 0 }; OdGeMatrix2d m2d = m.convertToLocal(normal, elevation); for (auto& p : clip) p.transformBy(m2d); } void SvgImporter::addImage(const TiXmlElement* node) { auto pathAttribute = node->Attribute("xlink:href"); if (!pathAttribute) return; OdCharArray dstBuf; OdCharMapper::utf8ToUnicode(pathAttribute, (unsigned)strlen(pathAttribute), dstBuf); OdString path(dstBuf.getPtr()); if (path.find(SVG_BASE64_DEF) != -1) { path = addInlineImage(path); } else if (!odrxSystemServices()->accessFile(path, 0)) { OdString newPath = database()->appServices()->findFile(path); if (!newPath.isEmpty() && odrxSystemServices()->accessFile(newPath, 0)) path = newPath; else { newPath = database()->appServices()->findFile(getCurrentDir(m_pProperties->get_SvgPath()) + path); if (!newPath.isEmpty() && odrxSystemServices()->accessFile(newPath, 0)) path = newPath; } } OdDbObjectId imageDictId = OdDbRasterImageDef::createImageDictionary(database()); OdDbDictionaryPtr pImageDict = imageDictId.safeOpenObject(OdDb::kForWrite); OdDbRasterImageDefPtr pImageDef = OdDbRasterImageDef::createObject(); OdDbObjectId imageDefId = pImageDict->setAt(OdDbRasterImageDef::suggestName(pImageDict, path), pImageDef); pImageDef->setSourceFileName(path); pImageDef->load(); OdGePoint3d origin; if (auto x = node->Attribute("x")) origin.x = odStrToD(x); if (auto y = node->Attribute("y")) origin.y = odStrToD(y); double h = 1.0, w = 1.0; if (auto height = node->Attribute("height")) h = odStrToD(height); if (auto width = node->Attribute("width")) w = odStrToD(width); // clipping boundary in SVG is in local image coordinates (usually 0 -> 1) transformClip(m_Traits.top().transform.inverse(), m_Traits.top().clip); auto transform = node->Attribute("transform"); if (transform) { auto m = parseTransform(transform); m_Traits.top().transform.postMultBy(m.inverse()); OdGePoint3d center; OdGeVector3d xAxis, yAxis, zAxis; m.getCoordSystem(center, xAxis, yAxis, zAxis); w *= xAxis.normalizeGetLength(); h *= yAxis.normalizeGetLength(); m.setCoordSystem(center + yAxis * h, xAxis, -yAxis, zAxis); // image is upside down and origin is in the top left m_Traits.top().transform.postMultBy(m); } // in DWG clipping boundary is also in OCS but is scaled up to image size auto size = pImageDef->size(); transformClip(OdGeMatrix3d::scaling(OdGeScale3d(size.x, size.y, 1)), m_Traits.top().clip); auto pImage = OdDbRasterImage::createObject(); pImage->setDatabaseDefaults(database()); pImage->setImageDefId(imageDefId); pImage->setOrientation(origin, OdGeVector3d(w, 0, 0), OdGeVector3d(0, h, 0)); pImage->setDisplayOpt(OdDbRasterImage::kShow, true); pImage->setDisplayOpt(OdDbRasterImage::kShowUnAligned, true); if (!transform) pImage->transformBy(OdGeMatrix3d::mirroring(OdGePlane(origin + OdGeVector3d(1, 0, 0)*w/2.0 + OdGeVector3d(0, 1, 0)*h/2.0, OdGeVector3d::kYAxis))); if (!m_Traits.top().clip.isEmpty()) { OdGeExtents3d ext; pImage->getGeomExtents(ext); OdGePoint2dArray extPts { ext.minPoint().convert2d(), {ext.minPoint().x, ext.maxPoint().y}, ext.maxPoint().convert2d(), {ext.maxPoint().x, ext.minPoint().y}, ext.minPoint().convert2d() }; transformClip(m_Traits.top().transform, extPts); OdGePoint2dArray tmp; OdGe::ClipCondition cc; // ignore overall viewport clip if (OdGe::eOk == OdGeClipBoundary2d(m_Traits.top().clip).clipPolygon(extPts, tmp, cc) && cc != OdGe::kAllSegmentsInside && cc != OdGe::kInvalid) { pImage->setClipBoundary(m_Traits.top().clip); } } appendEntity(pImage, node); } void SvgImporter::addClasses(const char* textClasses) { if (!textClasses) return; std::string strClasses(textClasses); std::regex classRegex(R"(([.#]?[a-zA-Z0-9_-]+)\s*\{([^}]+)\})", std::regex::optimize); std::sregex_iterator it(strClasses.begin(), strClasses.end(), classRegex); std::sregex_iterator end; while (it != end) { std::string className = (*it)[1].str(); std::string attributes = (*it)[2].str(); auto mapIt = m_Classes.find(className); if (mapIt == m_Classes.end()) mapIt = m_Classes.emplace(className, TiXmlElement(className.c_str())).first; parseStyleAttrs(attributes, &mapIt->second); ++it; } } OdString SvgImporter::addInlineImage(OdString strImgData) { const OdString strBase64Mark(SVG_BASE64_DEF); const OdString strImageExtMark(SVG_IMAGE_DEF); strImgData.remove('\n'); int nBinDataPos = strImgData.find(strBase64Mark); int nExtPos = strImgData.find(strImageExtMark); if (nBinDataPos == -1 || nExtPos == -1) return OdString::kEmpty; OdString strExt = strImgData.mid(nExtPos + strImageExtMark.getLength() + 1, nBinDataPos - nExtPos - strImageExtMark.getLength() - 2); strImgData = strImgData.mid(nBinDataPos + strBase64Mark.getLength() + 1); OdBinaryData imageBin; decodeBase64(strImgData, imageBin); OdString strPath = getNewImagePath(m_pProperties->get_SvgPath(), strExt); OdStreamBufPtr stream = odrxSystemServices()->createFile(strPath, Oda::kFileWrite, Oda::kShareDenyNo, Oda::kCreateAlways); stream->putBytes(imageBin.asArrayPtr(), imageBin.size()); return strPath; } static double textHeightScale(OdDbObjectId textStyleId, double svgFontSize) { if (auto f = giTextStyleFromDb(textStyleId).getFont()) return svgFontSize * f->fontAbove() / (f->getHeight() - f->getInternalLeading()); else return 1.0; // (for our export correct height is always 1.0) } void SvgImporter::addText(const TiXmlElement* node) { SVGTextDataArray arrTextChain; int nLastRow(0); addTextData(node, nullptr, arrTextChain, nLastRow); appendTextChain(arrTextChain); } bool SvgImporter::addTextData(const TiXmlElement* node, const CSVGImportTextData* pParentTData, SVGTextDataArray& arrTextChain, int& nLastRow) { if (pParentTData) nodePreprocessing(node); CSVGImportTextData* pTextData(nullptr); int idx = -1; std::unique_ptr textData(new CSVGImportTextData(node, pParentTData)); if (textData->asNewText()) { idx = arrTextChain.append(std::move(textData)); pTextData = arrTextChain[idx].get(); } else pTextData = textData.get(); bool bNewText(false); int nElmRow(0); std::string text; for (const TiXmlNode* child = node->FirstChild(); child; child = child->NextSibling()) { if (!nElmRow) nElmRow = child->Row(); if (const TiXmlText* textNode = child->ToText()) { if (textNode->Row() > nElmRow) text += " "; nElmRow = textNode->Row(); text += textNode->Value(); if (bNewText) { pTextData = addNewTextToChain(node, pParentTData, arrTextChain); bNewText = false; } } else if (const TiXmlElement* elementNode = child->ToElement()) { if (strcmp(elementNode->Value(), "tspan") == 0) { if (elementNode->Row() > nElmRow) { text += " "; bNewText = false; } if (text == " ") pTextData = addNewTextToChain(node, pParentTData, arrTextChain); nElmRow = elementNode->Row(); int nLastRow(0); if (!addTextData(elementNode, pTextData, arrTextChain, nLastRow)) { text += elementNode->GetText(); } else if (!bNewText) { pTextData->setContent(text.c_str()); text.clear(); bNewText = true; auto next = elementNode->NextSibling(); if (next && (next->ToText() || (next->ToElement() && strcmp(next->Value(), "tspan") == 0)) && (next->Row() - nLastRow) > 1) { arrTextChain.last()->appendContent(" "); nElmRow = next->Row(); } } } if (strcmp(elementNode->Value(), "tref") == 0) { //auto href = elementNode->Attribute("xlink:href"); //TODO: ?? } } } nLastRow = nElmRow; if (!bNewText) pTextData->setContent(text.c_str()); return (idx >= 0); } CSVGImportTextData* SvgImporter::addNewTextToChain(const TiXmlElement* node, const CSVGImportTextData* pParentTData, SVGTextDataArray& arrTextChain) { std::unique_ptr newTextData(new CSVGImportTextData(node, pParentTData)); newTextData->setPosition(OdGePoint3d::kOrigin, CSVGImportTextData::eRelPosBoth); int idx = arrTextChain.append(std::move(newTextData)); return arrTextChain[idx].get(); } void SvgImporter::appendTextChain(const SVGTextDataArray& textChain) { OdGePoint3d basePnt; //First element of textChain already has traits bool bHasTraits(true); for (const auto& textData : textChain) { auto textObj = textData->createTextObject(database(), basePnt); auto textStyleId = getTextStyle(textData.get()); double height = 1.0; height *= textHeightScale(textStyleId, textData->getSize()); if (!bHasTraits) pushTraits(textData->getNode()); m_Traits.top().color = m_Traits.top().fill; if (m_Traits.top().color == OdCmEntityColor::kNone) m_Traits.top().color = OdCmEntityColor::kByLayer; m_Traits.top().fill = OdCmEntityColor::kNone; // in SVG text cannot be a hatch border, and fill color is actually a text color if (bHasTraits) m_Traits.top().transform.postMultBy(OdGeMatrix3d::mirroring(OdGePlane(OdGePoint3d::kOrigin, OdGeVector3d::kYAxis))); textObj->setTextStyle(textStyleId); textObj->setHeight(height); basePnt = textObj->position(); OdGePoint3dArray curBoundPnts; textObj->getBoundingPoints(curBoundPnts); if ((curBoundPnts.length() > 3)) basePnt.x = curBoundPnts[3].x; appendEntity(textObj, textData->getNode()); if (!bHasTraits) popTraits(); bHasTraits = false; } } static OdString generateTestStyleName(OdDbTextStyleTable* tst) { OdString name; for (int i = 0;;++i) { name.format(L"SVG%d", i); if (!tst->has(name)) break; } return name; } OdDbObjectId SvgImporter::getTextStyle(const CSVGImportTextData* pTData) { if (auto ff = pTData->getStyleValue("font-family")) { std::string styleName = *ff; bool italic = false; if (auto fs = pTData->getStyleValue("font-style")) { if (strcmp(fs->c_str(), "italic") == 0) { styleName += "$i"; italic = true; } } bool bold = false; if (auto fw = pTData->getStyleValue("font-weight")) { if (strcmp(fw->c_str(), "bold") == 0) { styleName += "$b"; bold = true; } } auto ps = m_Styles.find(styleName); if (ps != m_Styles.end()) return ps->second; OdDbTextStyleTablePtr tst = database()->getTextStyleTableId().safeOpenObject(OdDb::kForWrite); auto ts = OdDbTextStyleTableRecord::createObject(); OdString typeface = ff->c_str(); int comma = typeface.find(','); if (comma != -1) typeface = typeface.left(comma); ts->setFont(typeface, bold, italic, 0, 0); ts->setName(generateTestStyleName(tst)); auto id = tst->add(ts); m_Styles[styleName] = id; return id; } else return database()->getTextStyleStandardId(); } OdString SvgImporter::getNewImagePath(OdString strSvgPath, OdString strImgExt) { int nPos = strSvgPath.reverseFind('.'); OdString strPath; strPath.format(OD_T("%s_Image%i.%s"), strSvgPath.mid(0, nPos).c_str(), m_nImageCounter++, strImgExt.c_str()); return strPath; } bool SvgImporter::isCurrentElmFilled() const { const Traits& t = m_Traits.top(); if ((t.fill == OdCmEntityColor::kNone || t.fill == OdCmEntityColor::kByBlock) && t.gradient == nullptr) return false; return true; } double SvgImporter::getDbl(const char* val, const char* attrName) const { double dbl(.0); auto l = strlen(val); if (l > 0 && val[l - 1] == '%') { std::string s(val); double percent = odStrToD(s.substr(0, s.length() - 1).c_str()); if (auto attr = attributeBySvgStack(attrName)) { double dAttr = odStrToD(attr); dbl = dAttr * (percent / 100.0); } else dbl = odStrToD(val); } else dbl = odStrToD(val); return dbl; } const char* SvgImporter::attributeBySvgStack(const char* name) const { for (auto it = m_svgStack.rbegin(); it != m_svgStack.rend(); ++it) if (auto attr = (*it)->Attribute(name)) return attr; return nullptr; } OdUInt32 SvgImporter::decodeBase64(const OdString& stringToDecode, OdBinaryData& out) { static unsigned char* decode64Dict = NULL; if (!decode64Dict) { static const char* base64Dict = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static OdBinaryData dataDict; dataDict.resize(256); decode64Dict = dataDict.asArrayPtr(); for (OdUInt32 i = 0; i < 64; i++) decode64Dict[base64Dict[i]] = (unsigned char)i; } OdUInt32 length = stringToDecode.getLength(); if (length % 4 != 0) return OdUInt32(-1); const OdChar* pToDecode = stringToDecode.c_str(); OdUInt32 outLength = length * 3 / 4; outLength -= (pToDecode[length - 2] == '=') ? 2 : ((pToDecode[length - 1] == '=') ? 1 : 0); out.resize(outLength); unsigned char* data = (unsigned char*)out.asArrayPtr(); for (OdUInt32 i = 0, j = 0; i < length;) { OdUInt32 sextet_a = (pToDecode[i] == '=') ? (0 & i++) : decode64Dict[pToDecode[i++]]; OdUInt32 sextet_b = (pToDecode[i] == '=') ? (0 & i++) : decode64Dict[pToDecode[i++]]; OdUInt32 sextet_c = (pToDecode[i] == '=') ? (0 & i++) : decode64Dict[pToDecode[i++]]; OdUInt32 sextet_d = (pToDecode[i] == '=') ? (0 & i++) : decode64Dict[pToDecode[i++]]; OdUInt32 triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); if (j < outLength) data[j++] = (triple >> 2 * 8) & 0xFF; if (j < outLength) data[j++] = (triple >> 1 * 8) & 0xFF; if (j < outLength) data[j++] = (triple >> 0 * 8) & 0xFF; } return outLength; } OdDbEntityPtrArray SvgImporter::parsePath(const TiXmlElement* node) { OdDbEntityPtrArray ents; auto pathAttribute = node->Attribute("d"); if (!pathAttribute) return ents; std::string path(pathAttribute); OdGePoint2d lastPoint; int startPathIndex = -1; static std::regex re("([mMZzLlHhVvAacCsSqQtT])\\s*([^mMZzLlHhVvAacCsSqQtT]*)", std::regex::optimize); for (auto it = std::sregex_iterator(path.begin(), path.end(), re); it != std::sregex_iterator(); ++it) { char func = *(*it)[1].first; auto args = parseDoubleArray((*it)[2].first, (*it)[2].second, func == 'a' || func == 'A'); switch (func) { case 'M': // start a new path with absolute coordinates case 'm': // start a new path with relative coordinates if (ents.size() && isCurrentElmFilled()) { closeCurentPath(startPathIndex, ents, lastPoint); ents.append(nullptr); } startNewPath(func == 'm', ents, lastPoint, args); startPathIndex = ents.size() - 1; break; case 'z': // close the current path case 'Z': closeCurentPath(startPathIndex, ents, lastPoint, true); startPathIndex = -1; break; case 'L': // continue the current path with lines (absolute coordinates) case 'l': // continue the current path with lines (relative coordinates) addLinePath(func == 'l', ents, lastPoint, args); break; case 'H': // continue the current path with horizontal lines (absolute coordinates) case 'h': // continue the current path with horizontal lines (relative coordinates) addHorizontalPath(func == 'h', ents, lastPoint, args); break; case 'V': // continue the current path with vertical lines (absolute coordinates) case 'v': // continue the current path with vertical lines (relative coordinates) addVerticalPath(func == 'v', ents, lastPoint, args); break; case 'A': case 'a': addEllipticPath(func == 'a', ents, lastPoint, args); break; case 'c': case 'C': addCubicBezier(func == 'c', ents, lastPoint, args); break; case 's': case 'S': addCubicBezierShort(func == 's', ents, lastPoint, args); break; case 'q': case 'Q': addQuadraticBezier(func == 'q', ents, lastPoint, args); break; case 't': case 'T': addQuadraticBezierShort(func == 't', ents, lastPoint, args); break; } } if (ents.size() && isCurrentElmFilled()) closeCurentPath(startPathIndex, ents, lastPoint); return ents; } void SvgImporter::startNewPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { auto pl = OdDbPolyline::createObject(); pl->setDatabaseDefaults(database()); ents.append(pl); for (unsigned i = 0; i < args.size() / 2; ++i) { if (relative) { lastPoint.x += args[i * 2]; lastPoint.y += args[i * 2 + 1]; } else { lastPoint.x = args[i * 2]; lastPoint.y = args[i * 2 + 1]; } pl->addVertexAt(i, lastPoint); } } void SvgImporter::closeCurentPath(int startPathIndex, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, bool updateLastPoint) { if (ents.isEmpty() || startPathIndex == -1) return; if (auto pl = OdDbCurve::cast(ents[startPathIndex])) { OdGePoint3d p1; if (pl->getStartPoint(p1) == eOk) { if (pl->isKindOf(OdDbPolyline::desc()) && (ents.size() - startPathIndex) == 1) { OdDbPolylinePtr(pl)->setClosed(true); } else { OdGePoint3d curPnt(lastPoint.x, lastPoint.y, 0.0); if (!p1.isEqualTo(curPnt)) { auto l = OdDbLine::createObject(); l->setDatabaseDefaults(database()); l->setStartPoint(curPnt); l->setEndPoint(p1); ents.append(l); } } // Update lastPoint to the start point of the closed path only for SVG "z" command if (updateLastPoint) { lastPoint.x = p1.x; lastPoint.y = p1.y; } } } } void SvgImporter::addLinePath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (ents.isEmpty()) return; auto pl = OdDbPolyline::cast(ents.last()); if (!pl) { pl = OdDbPolyline::createObject(); pl->setDatabaseDefaults(database()); ents.append(pl); pl->addVertexAt(0, lastPoint); } for (unsigned i = 0; i < args.size() / 2; ++i) { if (relative) { lastPoint.x += args[i * 2]; lastPoint.y += args[i * 2 + 1]; } else { lastPoint.x = args[i * 2]; lastPoint.y = args[i * 2 + 1]; } pl->addVertexAt(pl->numVerts(), lastPoint); } } void SvgImporter::addHorizontalPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (ents.isEmpty()) return; auto pl = OdDbPolyline::cast(ents.last()); if (!pl) { pl = OdDbPolyline::createObject(); pl->setDatabaseDefaults(database()); ents.append(pl); pl->addVertexAt(0, lastPoint); } for (unsigned i = 0; i < args.size(); ++i) { if (relative) lastPoint.x += args[i]; else lastPoint.x = args[i]; pl->addVertexAt(pl->numVerts(), lastPoint); } } void SvgImporter::addVerticalPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (ents.isEmpty()) return; auto pl = OdDbPolyline::cast(ents.last()); if (!pl) { pl = OdDbPolyline::createObject(); pl->setDatabaseDefaults(database()); ents.append(pl); pl->addVertexAt(0, lastPoint); } for (unsigned i = 0; i < args.size(); ++i) { if (relative) lastPoint.y += args[i]; else lastPoint.y = args[i]; pl->addVertexAt(pl->numVerts(), lastPoint); } } static void fixEllipticRadii(double& rx, double& ry, const OdGeVector2d& mid) { double L = mid.x * mid.x / (rx * rx) + mid.y * mid.y / (ry * ry); if (L > 1) { rx *= L; ry *= L; } } static OdDbSplinePtr makeSplineFromNurbs(const OdGeNurbCurve3d& nurbs, OdDbDatabase* db) { auto spline = OdDbSpline::createObject(); spline->setDatabaseDefaults(db); { int degree; bool rational, periodic; OdGePoint3dArray controlPoints; OdGeKnotVector knots; OdGeDoubleArray weights; nurbs.getDefinitionData(degree, rational, periodic, knots, controlPoints, weights); spline->setNurbsData(degree, false, false, false, controlPoints, knots, weights, OdGeContext::gTol.equalPoint()); } return spline; } static OdGePoint3d getPreviousControlPoint(const OdDbEntityPtrArray& ents, const OdGePoint2d& lastPoint) { OdGePoint3d prevCtlPoint = { lastPoint.x, lastPoint.y, 0 }; if (!ents.isEmpty() && ents.last()->isKindOf(OdDbSpline::desc())) { OdDbSplinePtr s = ents.last(); int N = s->numControlPoints(); if (N > 2) OdDbSplinePtr(ents.last())->getControlPointAt(N - 2, prevCtlPoint); } return prevCtlPoint; } void SvgImporter::addQuadraticBezier(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (args.size() % 4 != 0) return; int N = (int)args.size() / 4; OdGeDoubleArray knots = { 0,0,0,1,1,1 }; OdGePoint3dArray ctrls(3); OdGeNurbCurve3d nurbs; for (int i = 0; i < N; ++i) { ctrls.push_back({ lastPoint.x, lastPoint.y, 0 }); for (int k = 0; k < 2; ++k) ctrls.push_back(relative ? OdGePoint3d(lastPoint.x + args[i * 4 + k * 2], lastPoint.y + args[i * 4 + k * 2 + 1], 0) : OdGePoint3d(args[i * 4 + k * 2], args[i * 4 + k * 2 + 1], 0)); lastPoint = { ctrls.last().x, ctrls.last().y }; if (i == 0) nurbs.set(2, knots, ctrls, {}); else nurbs.joinWith(OdGeNurbCurve3d(2, knots, ctrls, {})); ctrls.clear(); } ents.append(makeSplineFromNurbs(nurbs, database())); } void SvgImporter::addQuadraticBezierShort(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (args.size() % 2 != 0) return; int N = (int)args.size() / 2; auto prevCtlPoint = getPreviousControlPoint(ents, lastPoint); OdGePoint3dArray ctrls(3); OdGeDoubleArray knots = { 0,0,0,1,1,1 }; OdGeNurbCurve3d nurbs; for (int i = 0; i < N; ++i) { ctrls.push_back({ lastPoint.x, lastPoint.y, 0 }); ctrls.push_back(ctrls.last() + (ctrls.last() - prevCtlPoint)); ctrls.push_back(relative ? OdGePoint3d(ctrls.last().x + args[i * 2], ctrls.last().y + args[i * 2 + 1], 0) : OdGePoint3d(args[i * 2], args[i * 2 + 1], 0) ); prevCtlPoint = ctrls[ctrls.size() - 2]; lastPoint = { ctrls.last().x, ctrls.last().y }; if (i == 0) nurbs.set(2, knots, ctrls, {}); else nurbs.joinWith(OdGeNurbCurve3d(2, knots, ctrls, {})); ctrls.clear(); } ents.append(makeSplineFromNurbs(nurbs, database())); } void SvgImporter::addCubicBezier(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (args.size() % 6 != 0) return; int N = (int)args.size() / 6; OdGeDoubleArray knots = { 0,0,0,0,1,1,1,1 }; OdGePoint3dArray ctrls(4); OdGeNurbCurve3d nurbs; for (int i = 0; i < N; ++i) { ctrls.push_back({ lastPoint.x, lastPoint.y, 0 }); for (int k = 0; k < 3; ++k) ctrls.push_back(relative ? OdGePoint3d(lastPoint.x + args[i * 6 + k * 2], lastPoint.y + args[i * 6 + k * 2 + 1], 0) : OdGePoint3d(args[i * 6 + k * 2], args[i * 6 + k * 2 + 1], 0)); lastPoint = { ctrls.last().x, ctrls.last().y }; if (i == 0) nurbs.set(3, knots, ctrls, {}); else nurbs.joinWith(OdGeNurbCurve3d(3, knots, ctrls, {})); ctrls.clear(); } ents.append(makeSplineFromNurbs(nurbs, database())); } void SvgImporter::addCubicBezierShort(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { if (args.size() % 4 != 0) return; auto prevCtlPoint = getPreviousControlPoint(ents, lastPoint); int N = (int)args.size() / 4; OdGePoint3dArray ctrls(4); OdGeDoubleArray knots = { 0,0,0,0,1,1,1,1 }; OdGeNurbCurve3d nurbs; for (int i = 0; i < N; ++i) { ctrls.push_back({ lastPoint.x, lastPoint.y, 0 }); ctrls.push_back(ctrls.last() + (ctrls.last() - prevCtlPoint)); for (int k = 0; k < 2; ++k) ctrls.push_back(relative ? OdGePoint3d(lastPoint.x + args[i * 4 + k * 2], lastPoint.y + args[i * 4 + k * 2 + 1], 0) : OdGePoint3d(args[i * 4 + k * 2], args[i * 4 + k * 2 + 1], 0) ); prevCtlPoint = ctrls[ctrls.size() - 2]; lastPoint = { ctrls.last().x, ctrls.last().y }; if (i == 0) nurbs.set(3, knots, ctrls, {}); else nurbs.joinWith(OdGeNurbCurve3d(3, knots, ctrls, {})); ctrls.clear(); } ents.append(makeSplineFromNurbs(nurbs, database())); } void SvgImporter::addEllipticPath(bool relative, OdDbEntityPtrArray& ents, OdGePoint2d& lastPoint, std::vector& args) { const int nEllArgsCount = 7; if (args.size() % nEllArgsCount != 0) return; int N = (int)args.size() / nEllArgsCount; for (int i = 0; i < N; ++i) { int nStep = i * nEllArgsCount; double rx = fabs(args[nStep]); double ry = fabs(args[nStep + 1]); const OdGePoint2d endPoint = relative ? lastPoint + OdGeVector2d(args[nStep + 5], args[nStep + 6]) : OdGePoint2d(args[nStep + 5], args[nStep + 6]); if (OdZero(rx) || OdZero(ry) || endPoint == lastPoint) { auto l = OdDbLine::createObject(); l->setDatabaseDefaults(database()); l->setStartPoint(OdGePoint3d(lastPoint.x, lastPoint.y, 0.0)); l->setEndPoint(OdGePoint3d(endPoint.x, endPoint.y, 0.0)); ents.append(l); } else { const double angle = OdaToRadian(args[nStep + 2]); // SVG coordinates are upside down const bool largeArcFlag = args[nStep + 3] != 0.0; const bool sweepFlag = args[nStep + 4] != 0.0; OdGeVector2d mid = (endPoint - lastPoint) * 0.5; // see https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes for the detailed explanation mid.transformBy(OdGeMatrix2d::rotation(-angle)); fixEllipticRadii(rx, ry, mid); const double k1 = rx * rx * mid.y * mid.y + ry * ry * mid.x * mid.x; const double k = (largeArcFlag == sweepFlag ? 1.0 : -1.0) * sqrt((rx * rx * ry * ry - k1) / k1); OdGeVector2d c1(k * rx * mid.y / ry, -k * ry * mid.x / rx); OdGeVector2d center(c1); center.transformBy(OdGeMatrix2d::rotation(angle)); center += (endPoint.asVector() + lastPoint.asVector()) * 0.5; const double startAngle = OdGeVector2d::kXAxis.angleToCCW(lastPoint.asVector() - center); const double endAngle = OdGeVector2d::kXAxis.angleToCCW(endPoint.asVector() - center); if (OdEqual(rx, ry)) { auto a = OdDbArc::createObject(); a->setDatabaseDefaults(database()); a->setCenter(OdGePoint3d(center.x, center.y, 0.0)); a->setRadius(rx); a->setStartAngle(sweepFlag ? startAngle : endAngle); a->setEndAngle(sweepFlag ? endAngle : startAngle); if (sweepFlag) a->reverseCurve(); ents.append(a); } else { auto e = OdDbEllipse::createObject(); e->setDatabaseDefaults(database()); OdGeVector3d major = OdGeVector3d::kXAxis; double angleShift = rx > ry ? 0.0 : OdaPI2; major.rotateBy(angle + angleShift, OdGeVector3d::kZAxis); major *= rx > ry ? rx : ry; e->set(OdGePoint3d(center.x, center.y, 0.0), OdGeVector3d::kZAxis, major, rx > ry ? ry / rx : rx / ry, (sweepFlag ? startAngle : endAngle) - angleShift - angle, (sweepFlag ? endAngle : startAngle) - angleShift - angle); ents.append(e); } } lastPoint = endPoint; } } void SvgImporter::addRect(const TiXmlElement* node) { auto pl = OdDbPolyline::createObject(); pl->setDatabaseDefaults(database()); OdGePoint2d origin; double w = 0, h = 0; if (auto x = node->Attribute("x")) origin.x = getDbl(x, "width"); if (auto y = node->Attribute("y")) origin.y = getDbl(y, "height"); if (auto width = node->Attribute("width")) w = getDbl(width, "width"); if (auto height = node->Attribute("height")) h = getDbl(height, "height"); pl->addVertexAt(0, origin); pl->addVertexAt(1, origin + OdGeVector2d::kXAxis * w); pl->addVertexAt(2, origin + OdGeVector2d::kXAxis * w + OdGeVector2d::kYAxis * h); pl->addVertexAt(3, origin + OdGeVector2d::kYAxis * h); pl->setClosed(true); appendEntity(pl, node); } void SvgImporter::addLine(const TiXmlElement* node) { auto l = OdDbLine::createObject(); l->setDatabaseDefaults(database()); OdGePoint3d p1, p2; if (auto sp1x = node->Attribute("x1")) p1.x = odStrToD(sp1x); if (auto sp1y = node->Attribute("y1")) p1.y = odStrToD(sp1y); if (auto sp2x = node->Attribute("x2")) p2.x = odStrToD(sp2x); if (auto sp2y = node->Attribute("y2")) p2.y = odStrToD(sp2y); l->setStartPoint(p1); l->setEndPoint(p2); appendEntity(l, node); } void SvgImporter::addEllipse(const TiXmlElement* node) { auto e = OdDbEllipse::createObject(); e->setDatabaseDefaults(database()); double rx = 0.0, ry = 0.0; if (auto srx = node->Attribute("rx")) rx = odStrToD(srx); if (auto sry = node->Attribute("ry")) ry = odStrToD(sry); if (!OdPositive(rx) || !OdPositive(ry)) return; OdGeVector3d majorAxis = (rx >= ry) ? OdGeVector3d::kXAxis * rx : OdGeVector3d::kYAxis * ry; double ratio = (rx >= ry) ? ry / rx : rx / ry; OdGePoint3d center; if (auto cx = node->Attribute("cx")) center.x = odStrToD(cx); if (auto cy = node->Attribute("cy")) center.y = odStrToD(cy); e->set(center, OdGeVector3d::kZAxis, majorAxis, ratio); appendEntity(e, node); } void SvgImporter::addCircle(const TiXmlElement* node) { auto c = OdDbCircle::createObject(); c->setDatabaseDefaults(database()); if (auto sr = node->Attribute("r")) c->setRadius(odStrToD(sr)); OdGePoint3d center; if (auto cx = node->Attribute("cx")) center.x = odStrToD(cx); if (auto cy = node->Attribute("cy")) center.y = odStrToD(cy); c->setCenter(center); appendEntity(c, node); } static OdGePoint2dArray parsePoints(const char* points, const OdGeMatrix3d& m) { OdGePoint2dArray pp; std::string text(points); static std::regex wscomma("(\\s+)|(\\s*\\,\\s*)", std::regex::nosubs | std::regex::optimize); // points look like "293.378,519.554 445.339,367.594 " auto end = std::sregex_token_iterator(); for (auto it = std::sregex_token_iterator(text.begin(), text.end(), wscomma, -1); it != end; ++it) { std::string s = *it++; if (it == end) break; OdGePoint3d pt; pt.x = odStrToD(s.data()); s = *it; pt.y = odStrToD(s.data()); pt.transformBy(m); pp.append(pt.convert2d()); } return pp; } void SvgImporter::addPolyline(const TiXmlElement* node, bool closed) { if (auto points = node->Attribute("points")) { auto pp = parsePoints(points, m_Traits.top().transform); if (pp.size() == 2) { auto l = OdDbLine::createObject(); l->setDatabaseDefaults(database()); l->setStartPoint(OdGePoint3d(pp[0].x, pp[0].y, 0)); l->setEndPoint(OdGePoint3d(pp[1].x, pp[1].y, 0)); appendEntity(l, node, false); } else { auto p = OdDbPolyline::createObject(); p->setDatabaseDefaults(database()); for (unsigned int i = 0; i < pp.size(); ++i) p->addVertexAt(i, pp[i]); if (closed) p->setClosed(true); appendEntity(p, node, false); } } } void SvgImporter::addLinearGradient(const TiXmlElement* elemNode) { auto id = elemNode->Attribute("id"); if (id == nullptr) return; bool firstColor = true; Gradient g; g.name = L"LINEAR"; auto x1 = elemNode->Attribute("x1"); auto y1 = elemNode->Attribute("y1"); auto x2 = elemNode->Attribute("x2"); auto y2 = elemNode->Attribute("y2"); if (x1 && x2 && y1 && y2) g.angle = (OdGePoint2d(odStrToD(x2), odStrToD(y2)) - OdGePoint2d(odStrToD(x1), odStrToD(y1))).angle(); for (const TiXmlElement* child = elemNode->FirstChildElement(); child; child = child->NextSiblingElement()) { if (strcmp(child->Value(), "stop") != 0) continue; if (auto c = child->Attribute("stop-color")) { if (firstColor) g.color1 = parseColor(c, nullptr, false); else g.color2 = parseColor(c, nullptr, false); // only 2 colors are supported in DWG firstColor = false; } } m_Gradients[id] = g; } void SvgImporter::addRadialGradient(const TiXmlElement* elemNode) { auto id = elemNode->Attribute("id"); if (id == nullptr) return; bool firstColor = true; Gradient g; g.name = L"INVSPHERICAL"; auto cxAttr = elemNode->Attribute("cx"); auto cyAttr = elemNode->Attribute("cy"); auto rAttr = elemNode->Attribute("r"); auto fxAttr = elemNode->Attribute("fx"); auto fyAttr = elemNode->Attribute("fy"); auto unitsAtts = elemNode->Attribute("gradientUnits"); if (unitsAtts && strcmp(unitsAtts, "userSpaceOnUse") == 0) g.eGradUnits = Gradient::eUserSpaceOnUse; double cx = (cxAttr) ? odStrToD(cxAttr) : .0; double cy = (cyAttr) ? odStrToD(cyAttr) : .0; double fx = (fxAttr) ? odStrToD(fxAttr) : .0; double fy = (fyAttr) ? odStrToD(fyAttr) : .0; cx += fx; cy += fy; g.center = OdGePoint3d(cx, cy, 0); auto gradTransf = elemNode->Attribute("gradientTransform"); if (gradTransf) { OdGeMatrix3d mat = parseTransform(gradTransf); g.center.transformBy(mat); } g.center.y = -g.center.y; for (const TiXmlElement* child = elemNode->FirstChildElement(); child; child = child->NextSiblingElement()) { if (strcmp(child->Value(), "stop") != 0) continue; if (auto c = child->Attribute("stop-color")) { if (firstColor) g.color1 = parseColor(c, nullptr, false); else g.color2 = parseColor(c, nullptr, false); // only 2 colors are supported in DWG firstColor = false; } } m_Gradients[id] = g; } void SvgImporter::getGradientSettings(double& dShift, double& dAngle, const OdDbHatch* pHatch, const Gradient* pG) const { OdGeExtents3d hatchExtents; pHatch->getGeomExtents(hatchExtents); double radius = odmax(hatchExtents.maxPoint().x - hatchExtents.minPoint().x, hatchExtents.maxPoint().y - hatchExtents.minPoint().y) / 2.0; OdGeVector3d gradVec; if (pG->eGradUnits == Gradient::eUserSpaceOnUse) { gradVec = pG->center - hatchExtents.center(); } else { OdGePoint3d startPnt(hatchExtents.minPoint().x, hatchExtents.maxPoint().y, 0); OdGePoint3d center(startPnt.x + pG->center.x * hatchExtents.diagonal().x, startPnt.y + pG->center.y * hatchExtents.diagonal().y, 0); gradVec = center - hatchExtents.center(); } dAngle = gradVec.angleTo(OdGeVector3d(-1.0, 1.0, 0.0), OdGeVector3d(.0, .0, -1.0)); dShift = gradVec.length() / radius; } // convert 100.0% -> 255 static OdUInt8 parseColorPart(std::string s) { if (s[s.length() - 1] == '%') { double percent = odStrToD(s.substr(0, s.length() - 1).c_str()); return OdUInt8(255.0 * percent / 100.0); } else return (OdUInt8)atoi(s.c_str()); } OdCmColor SvgImporter::parseColor(const char* s, Gradient** gradient, bool bApplyPalette) { OdCmColor c; if (strcmp(s, "none") == 0) c = OdCmEntityColor::kNone; else if (strncmp(s, "rgb(", 4) == 0) // may look like rgb(255,0,0) or rgb(100%, 0.0%, 0%) { static std::regex r("rgb\\(\\s*(\\d+(?:\\.\\d+)?%?)\\s*,\\s*(\\d+(?:\\.\\d+)?%?)\\s*,\\s*(\\d+(?:\\.\\d+)?%?)\\s*\\)", std::regex::optimize); std::match_results ms; if (std::regex_match(s, ms, r) && ms.size() == 4) c.setRGB(parseColorPart(ms[1]), parseColorPart(ms[2]), parseColorPart(ms[3])); } else if (s[0] == '#') { auto l = strlen(s); if (l == 4) // #RGB { c.setRGB(OdUInt8(OdCharConverter::hexValue(s[1]) << 4 | OdCharConverter::hexValue(s[1])), OdUInt8(OdCharConverter::hexValue(s[2]) << 4 | OdCharConverter::hexValue(s[2])), OdUInt8(OdCharConverter::hexValue(s[3]) << 4 | OdCharConverter::hexValue(s[3]))); } else if (l == 7) // #RRGGBB { c.setRGB(OdUInt8(OdCharConverter::hexValue(s[1]) << 4 | OdCharConverter::hexValue(s[2])), OdUInt8(OdCharConverter::hexValue(s[3]) << 4 | OdCharConverter::hexValue(s[4])), OdUInt8(OdCharConverter::hexValue(s[5]) << 4 | OdCharConverter::hexValue(s[6]))); } else // ?? { ODA_FAIL_M_ONCE("Unknown color format"); } } else if (strncmp(s, "url(", 4) == 0) // gradient ref { c = OdCmEntityColor::kNone; static std::regex r("url\\(\\#([^\\)]+)\\)", std::regex::optimize); std::match_results ms; if (std::regex_match(s, ms, r) && ms.size() == 2) { auto pg = m_Gradients.find(ms[1].str()); if (gradient && pg != m_Gradients.end()) *gradient = &pg->second; } } else { auto pc = m_NamedColors.find(s); if (pc != m_NamedColors.end()) c.setRGB(ODGETRED(pc->second), ODGETGREEN(pc->second), ODGETBLUE(pc->second)); else { ODA_FAIL_M_ONCE("Unknown color name or format"); } } if (bApplyPalette) applyPalette(c); return c; } void SvgImporter::applyPalette(OdCmColor& c) { if (c.colorMethod() != OdCmEntityColor::kByColor) return; if (const ODCOLORREF* p = (const ODCOLORREF*)m_pProperties->get_Palette()) { ODCOLORREF rgb = ODRGB(c.red(), c.green(), c.blue()); auto pc = std::find(p, p + 256, rgb); if (pc != (p + 256)) { OdUInt16 index = OdUInt16(pc - p); c.setColorIndex(index); } } } OdDb::LineWeight SvgImporter::parseLineweight(const char* strokeWidth) { double w = odStrToD(strokeWidth) * m_pProperties->get_LineWeightScale(); if (w < 0) return OdDb::kLnWtByLayer; else if (w > OdDb::kLnWt211) return OdDb::kLnWt211; else return (OdDb::LineWeight)(int)w; } void SvgImporter::parseStyleAttrs(const std::string strStyle, TiXmlElement* elm) { if (!elm) return; std::regex style_regex(R"((\w[\w-]*)\s*:\s*([^;\r\n]+)\s*;?)"); auto words_begin = std::sregex_iterator(strStyle.begin(), strStyle.end(), style_regex); auto words_end = std::sregex_iterator(); for (auto it = words_begin; it != words_end; ++it) { std::string property = (*it)[1].str(); std::string value = (*it)[2].str(); elm->SetAttribute(property.c_str(), value.c_str()); } } void SvgImporter::pushTraits(const TiXmlElement* node) { m_Traits.push(m_Traits.top()); auto& t = m_Traits.top(); if (auto transform = node->Attribute("transform")) t.transform.postMultBy(parseTransform(transform)); if (auto stroke = node->Attribute("stroke")) t.color = parseColor(stroke, nullptr); if (auto strokeWidth = node->Attribute("stroke-width")) t.lineWeight = parseLineweight(strokeWidth); if (auto fill = node->Attribute("fill")) t.fill = parseColor(fill, &t.gradient); if (auto fop = node->Attribute("fill-opacity")) t.fillTransparency = odStrToD(fop); if (auto sop = node->Attribute("stroke-opacity")) t.strokeTransparency = odStrToD(sop); if (auto clip = node->Attribute("clip-path")) { static std::regex r("url\\(\\#([^\\)]+)\\)", std::regex::optimize); std::match_results ms; if (std::regex_match(clip, ms, r) && ms.size() == 2) { auto pc = m_ClipPaths.find(ms[1].str()); if (pc != m_ClipPaths.end()) { auto newClip = pc->second; transformClip(t.transform, newClip); if (t.clip.isEmpty()) t.clip = newClip; else { OdGePoint2dArray tmp; OdGe::ClipCondition cc; if (OdGe::eOk == OdGeClipBoundary2d(t.clip).clipPolygon(newClip, tmp, cc) && cc != OdGe::kInvalid && !tmp.isEmpty()) t.clip = tmp; } } } } } void SvgImporter::applyTraits(const OdDbEntityPtrArray& ents, const TiXmlElement* node, bool applyTransform) { if (m_Traits.empty()) { ODA_FAIL_M("applyTraits called before import is set up"); return; } for (auto e : ents) { if (e.isNull()) continue; if (applyTransform && m_Traits.top().transform != OdGeMatrix3d::kIdentity) { if (e->transformBy(m_Traits.top().transform) != eOk) { OdDbEntityPtr te; if (e->getTransformedCopy(m_Traits.top().transform, te) == eOk) { e->handOverTo(te); } } } e->setColor(m_Traits.top().color); if (m_Traits.top().strokeTransparency != OdCmTransparency()) e->setTransparency(m_Traits.top().strokeTransparency); e->setLineWeight(m_Traits.top().lineWeight); addHyperlink(e); } addHatches(ents, node); } void SvgImporter::nodePreprocessing(const TiXmlElement * nodeC) { setClasses(nodeC); if (auto style = nodeC->Attribute("style")) { std::string strStyle(style); parseStyleAttrs(style, const_cast(nodeC)); } } void SvgImporter::setClasses(const TiXmlElement* nodeC) { std::string strName(nodeC->Value()); setClassData(nodeC, strName); if (auto classes = nodeC->Attribute("class")) { std::string strClasses(classes); size_t start = 0, end; while ((end = strClasses.find(' ', start)) != std::string::npos) { std::string strClassName("."); strClassName += strClasses.substr(start, end - start); setClassData(nodeC, strClassName); start = end + 1; } std::string strClassName("."); strClassName += strClasses.substr(start, end - start); setClassData(nodeC, strClassName); } } void SvgImporter::setClassData(const TiXmlElement* nodeC, const std::string& strClassName) { auto mapIt = m_Classes.find(strClassName); if (mapIt != m_Classes.end()) { TiXmlElement* node = const_cast(nodeC); const TiXmlAttribute* pAttr = mapIt->second.FirstAttribute(); while (pAttr) { node->SetAttribute(pAttr->Name(), pAttr->Value()); pAttr = pAttr->Next(); } } } void SvgImporter::groupCurvesIntoLoops(const OdDbEntityPtrArray& ents, OdArray& arrLoops) { if (ents.isEmpty()) return; auto it = arrLoops.append(); for (auto e : ents) { if (e.isNull()) { if (!it->isEmpty()) it = arrLoops.append(); continue; } if (!e->isKindOf(OdDbCurve::desc())) continue; auto pLine = OdDbPolyline::cast(e); if (pLine && pLine->numVerts() == 1) continue; it->append(e->objectId()); } } void SvgImporter::addHatches(const OdDbEntityPtrArray& ents, const TiXmlElement* node) { if (!node || !isCurrentElmFilled()) return; bool bZeroMode = true; if (auto fillRuleAttr = node->Attribute("fill-rule")) { if (strcmp(fillRuleAttr, "nonzero") != 0) bZeroMode = false; } //We need to group the curves into loops for future hatch OdArray arrLoops; groupCurvesIntoLoops(ents, arrLoops); if (!bZeroMode) addSimpleHatch(arrLoops); else addHatches(arrLoops); } void SvgImporter::addSimpleHatch(const OdArray& arrLoops) { auto h = OdDbHatch::createObject(); h->setDatabaseDefaults(database()); try { for (const auto& loop : arrLoops) if (!loop.isEmpty()) h->appendLoop(OdDbHatch::kExternal, loop, true); } catch (const OdError&) { ODA_FAIL_M_ONCE("Bad hatch boundary?"); return; } addHatchProps(h); } void SvgImporter::addHatchProps(OdDbHatch* h) { const Traits& t = m_Traits.top(); if (t.gradient) { double dAngle = t.gradient->angle; double dShift = t.gradient->shift; if (t.gradient->name.compare(OD_T("INVSPHERICAL")) == 0) getGradientSettings(dShift, dAngle, h, t.gradient); h->setHatchObjectType(OdDbHatch::kGradientObject); h->setGradient(OdDbHatch::kPreDefinedGradient, t.gradient->name); OdCmColor colors[] = { t.gradient->color1, t.gradient->color2 }; double values[] = { 0.0, 1.0 }; h->setGradientColors(2, colors, values); h->setGradientAngle(dAngle); h->setGradientShift(dShift); } else { h->setPattern(OdDbHatch::kPreDefined, L"SOLID"); h->setColor(t.fill); } if (t.fillTransparency != OdCmTransparency()) h->setTransparency(t.fillTransparency); m_Block->appendOdDbEntity(h); addHyperlink(h); } void SvgImporter::addHatches(const OdArray& arrLoops) { OdArray pathLoops; for (const auto& loop : arrLoops) { if (loop.isEmpty()) continue; auto itLoops = pathLoops.append(); itLoops->init(loop); } std::vector> hierarchy; LoopNode::buildLoopHierarchy(pathLoops, hierarchy); for (const auto& node : hierarchy) addHatch(node.get()); } void SvgImporter::addHatch(const LoopNode* node) { auto h = OdDbHatch::createObject(); h->setDatabaseDefaults(database()); h->setHatchStyle(OdDbHatch::HatchStyle::kIgnore); try { addHatchLoops(h, node); } catch (const OdError&) { ODA_FAIL_M_ONCE("Bad hatch boundary?"); return; } addHatchProps(h); } void SvgImporter::addHatchLoops(OdDbHatch* h, const LoopNode* node) { if (node->isNewHatch()) addSimpleHatch({ node->m_loop->m_entIds }); else h->appendLoop(node->isExternal() ? OdDbHatch::kExternal : OdDbHatch::kOutermost, node->m_loop->m_entIds, true); for (auto& child : node->m_children) addHatchLoops(h, child.get()); } void SvgImporter::addHyperlink(OdDbObject* e) { if (!m_Traits.top().href.isEmpty()) { database()->newRegApp(L"PE_URL"); OdDbEntityHyperlinkPEPtr hpe = e->queryX(OdDbEntityHyperlinkPE::desc()); OdDbHyperlinkCollectionPtr hcoll = hpe->getHyperlinkCollection(e); hcoll->addTail(m_Traits.top().href, m_Traits.top().href); hpe->setHyperlinkCollection(e, hcoll); } } void SvgImporter::loadNamedColors() { #include "SvgNamedColors.h" for (const auto& c : namedColors) m_NamedColors[c.name] = c.color; }