/* * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA) * * This file is part of Open-Sankoré. * * Open-Sankoré is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, * with a specific linking exception for the OpenSSL project's * "OpenSSL" library (or with modified versions of it that use the * same license as the "OpenSSL" library). * * Open-Sankoré is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Open-Sankoré. If not, see . */ #include "UBSvgSubsetAdaptor.h" #include #include #include "domain/UBGraphicsSvgItem.h" #include "domain/UBGraphicsPixmapItem.h" #include "domain/UBGraphicsProxyWidget.h" #include "domain/UBGraphicsPolygonItem.h" #include "domain/UBGraphicsMediaItem.h" #include "domain/UBGraphicsWidgetItem.h" #include "domain/UBGraphicsPDFItem.h" #include "domain/UBGraphicsTextItem.h" #include "domain/UBGraphicsTextItemDelegate.h" #include "domain/UBGraphicsStroke.h" #include "domain/UBGraphicsStrokesGroup.h" #include "domain/UBGraphicsGroupContainerItem.h" #include "domain/UBItem.h" #include "tools/UBGraphicsRuler.h" #include "tools/UBGraphicsCompass.h" #include "tools/UBGraphicsProtractor.h" #include "tools/UBGraphicsCurtainItem.h" #include "tools/UBGraphicsTriangle.h" #include "tools/UBGraphicsCache.h" #include "document/UBDocumentProxy.h" #include "board/UBBoardView.h" #include "board/UBBoardController.h" #include "board/UBDrawingController.h" #include "board/UBBoardPaletteManager.h" #include "frameworks/UBFileSystemUtils.h" #include "frameworks/UBStringUtils.h" #include "frameworks/UBFileSystemUtils.h" #include "core/UBSettings.h" #include "core/UBSetting.h" #include "core/UBPersistenceManager.h" #include "core/UBApplication.h" #include "gui/UBTeacherGuideWidget.h" #include "gui/UBDockTeacherGuideWidget.h" #include "interfaces/IDataStorage.h" #include "document/UBDocumentContainer.h" #include "pdf/PDFRenderer.h" #include "core/memcheck.h" //#include "qtlogger.h" const QString UBSvgSubsetAdaptor::nsSvg = "http://www.w3.org/2000/svg"; const QString UBSvgSubsetAdaptor::nsXHtml = "http://www.w3.org/1999/xhtml"; const QString UBSvgSubsetAdaptor::nsXLink = "http://www.w3.org/1999/xlink"; const QString UBSvgSubsetAdaptor::xmlTrue = "true"; const QString UBSvgSubsetAdaptor::xmlFalse = "false"; const QString UBSvgSubsetAdaptor::sFontSizePrefix = "font-size:"; const QString UBSvgSubsetAdaptor::sPixelUnit = "px"; const QString UBSvgSubsetAdaptor::sFontWeightPrefix = "font-weight:"; const QString UBSvgSubsetAdaptor::sFontStylePrefix = "font-style:"; const QString UBSvgSubsetAdaptor::sFormerUniboardDocumentNamespaceUri = "http://www.mnemis.com/uniboard"; const QString tElement = "element"; const QString tGroup = "group"; const QString tStrokeGroup = "strokeGroup"; const QString tGroups = "groups"; const QString aId = "id"; QMap UBSvgSubsetAdaptor::additionalElementToStore; QString UBSvgSubsetAdaptor::toSvgTransform(const QMatrix& matrix) { return QString("matrix(%1, %2, %3, %4, %5, %6)") .arg(matrix.m11(), 0 , 'g') .arg(matrix.m12(), 0 , 'g') .arg(matrix.m21(), 0 , 'g') .arg(matrix.m22(), 0 , 'g') .arg(matrix.dx(), 0 , 'g') .arg(matrix.dy(), 0 , 'g'); } QMatrix UBSvgSubsetAdaptor::fromSvgTransform(const QString& transform) { QMatrix matrix; QString ts = transform; ts.replace("matrix(", ""); ts.replace(")", ""); QStringList sl = ts.split(","); if (sl.size() >= 6) { matrix.setMatrix( sl.at(0).toFloat(), sl.at(1).toFloat(), sl.at(2).toFloat(), sl.at(3).toFloat(), sl.at(4).toFloat(), sl.at(5).toFloat()); } return matrix; } static bool itemZIndexComp(const QGraphicsItem* item1, const QGraphicsItem* item2) { return item1->data(UBGraphicsItemData::ItemOwnZValue).toReal() < item2->data(UBGraphicsItemData::ItemOwnZValue).toReal(); } void UBSvgSubsetAdaptor::upgradeScene(UBDocumentProxy* proxy, const int pageIndex) { //4.2 QDomDocument doc = loadSceneDocument(proxy, pageIndex); QDomElement elSvg = doc.documentElement(); // SVG tag QString ubVersion = elSvg.attributeNS(UBSettings::uniboardDocumentNamespaceUri, "version", "4.1"); // default to 4.1 if (ubVersion.startsWith("4.1") || ubVersion.startsWith("4.2") || ubVersion.startsWith("4.3")) { // migrate to 4.2.1 (or latter) UBGraphicsScene *scene = loadScene(proxy, pageIndex); scene->setModified(true); persistScene(proxy, scene, pageIndex); } } QDomDocument UBSvgSubsetAdaptor::loadSceneDocument(UBDocumentProxy* proxy, const int pPageIndex) { QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg",pPageIndex); QFile file(fileName); QDomDocument doc("page"); if (file.exists()) { if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Cannot open file " << fileName << " for reading ..."; return doc; } doc.setContent(&file, true); file.close(); } return doc; } void UBSvgSubsetAdaptor::setSceneUuid(UBDocumentProxy* proxy, const int pageIndex, QUuid pUuid) { QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg",pageIndex); QFile file(fileName); if (!file.exists() || !file.open(QIODevice::ReadOnly)) return; QTextStream textReadStream(&file); QString xmlContent = textReadStream.readAll(); int uuidIndex = xmlContent.indexOf("uuid"); if (-1 == uuidIndex) { qWarning() << "Cannot read UUID from file" << fileName << "to set new UUID"; file.close(); return; } int quoteStartIndex = xmlContent.indexOf('"', uuidIndex); if (-1 == quoteStartIndex) { qWarning() << "Cannot read UUID from file" << fileName << "to set new UUID"; file.close(); return; } int quoteEndIndex = xmlContent.indexOf('"', quoteStartIndex + 1); if (-1 == quoteEndIndex) { qWarning() << "Cannot read UUID from file" << fileName << "to set new UUID"; file.close(); return; } file.close(); QString newXmlContent = xmlContent.left(quoteStartIndex + 1); newXmlContent.append(UBStringUtils::toCanonicalUuid(pUuid)); newXmlContent.append(xmlContent.right(xmlContent.length() - quoteEndIndex)); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QTextStream textWriteStream(&file); textWriteStream << newXmlContent; file.close(); } else { qWarning() << "Cannot open file" << fileName << "to write UUID"; } } bool UBSvgSubsetAdaptor::addElementToBeStored(QString domName, IDataStorage *dataStorageClass) { if(domName.isEmpty() || additionalElementToStore.contains(domName)){ qWarning() << "Error adding the element that should persist"; return false; } additionalElementToStore.insert(domName,dataStorageClass); return true; } QString UBSvgSubsetAdaptor::uniboardDocumentNamespaceUriFromVersion(int mFileVersion) { return mFileVersion >= 40200 ? UBSettings::uniboardDocumentNamespaceUri : sFormerUniboardDocumentNamespaceUri; } UBGraphicsScene* UBSvgSubsetAdaptor::loadScene(UBDocumentProxy* proxy, const int pageIndex) { QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); qDebug() << fileName; QFile file(fileName); if (file.exists()) { if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Cannot open file " << fileName << " for reading ..."; return 0; } UBGraphicsScene* scene = loadScene(proxy, file.readAll()); file.close(); return scene; } return 0; } QUuid UBSvgSubsetAdaptor::sceneUuid(UBDocumentProxy* proxy, const int pageIndex) { QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", pageIndex); QFile file(fileName); QUuid uuid; if (file.exists()) { if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Cannot open file " << fileName << " for reading ..."; return 0; } QXmlStreamReader xml(file.readAll()); bool foundSvg = false; while (!xml.atEnd() && !foundSvg) { xml.readNext(); if (xml.isStartElement()) { if (xml.name() == "svg") { QStringRef svgSceneUuid = xml.attributes().value(UBSettings::uniboardDocumentNamespaceUri, "uuid"); if (svgSceneUuid.isNull()) svgSceneUuid = xml.attributes().value("http://www.mnemis.com/uniboard", "uuid"); if (!svgSceneUuid.isNull()) uuid = QUuid(svgSceneUuid.toString()); foundSvg = true; } } } file.close(); } return uuid; } UBGraphicsScene* UBSvgSubsetAdaptor::loadScene(UBDocumentProxy* proxy, const QByteArray& pArray) { UBSvgSubsetReader reader(proxy, pArray); return reader.loadScene(); } QString UBSvgSubsetAdaptor::readTeacherGuideNode(int sceneIndex) { QString result; QString fileName = UBApplication::boardController->selectedDocument()->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.svg", sceneIndex); QFile file(fileName); file.open(QIODevice::ReadOnly); QByteArray fileByteArray=file.readAll(); file.close(); QXmlStreamReader mXmlReader(fileByteArray); while (!mXmlReader.atEnd()) { mXmlReader.readNext(); if (mXmlReader.isStartElement()) { if (mXmlReader.name() == "teacherBar" || mXmlReader.name() == "teacherGuide"){ result.clear(); result += ""; result += "\n"; } else if (mXmlReader.name() == "media" || mXmlReader.name() == "link" || mXmlReader.name() == "title" || mXmlReader.name() == "comment" || mXmlReader.name() == "action") { result += "<" + mXmlReader.name().toString() + " "; foreach(QXmlStreamAttribute attribute, mXmlReader.attributes()) result += attribute.name().toString() + "=\"" + attribute.value().toString() + "\" "; result += " />\n"; } else { // NOOP } } else if (mXmlReader.isEndElement() && (mXmlReader.name() == "teacherBar" || mXmlReader.name() == "teacherGuide")){ result += ""; } } if (mXmlReader.hasError()) { qWarning() << "error parsing Sankore file " << mXmlReader.errorString(); } return result; } UBSvgSubsetAdaptor::UBSvgSubsetReader::UBSvgSubsetReader(UBDocumentProxy* pProxy, const QByteArray& pXmlData) : mXmlReader(pXmlData) , mProxy(pProxy) , mDocumentPath(pProxy->persistencePath()) , mGroupHasInfo(false) { // NOOP } UBGraphicsScene* UBSvgSubsetAdaptor::UBSvgSubsetReader::loadScene() { qDebug() << "loadScene() : starting reading..."; QTime time; time.start(); mScene = 0; UBGraphicsWidgetItem *currentWidget = 0; mFileVersion = 40100; // default to 4.1.0 UBGraphicsStrokesGroup* strokesGroup = 0; UBGraphicsStroke* currentStroke = 0; while (!mXmlReader.atEnd()) { mXmlReader.readNext(); if (mXmlReader.isStartElement()) { qreal zFromSvg = getZValueFromSvg(); QUuid uuidFromSvg = getUuidFromSvg(); if (mXmlReader.name() == "svg") { if (!mScene) { mScene = new UBGraphicsScene(mProxy, false); } // introduced in UB 4.2 QStringRef svgUbVersion = mXmlReader.attributes().value(UBSettings::uniboardDocumentNamespaceUri, "version"); if (!svgUbVersion.isNull()) { QString ubVersion = svgUbVersion.toString(); //may look like : 4 or 4.1 or 4.2 or 4.2.1, etc QStringList parts = ubVersion.split("."); if (parts.length() > 0) { mFileVersion = parts.at(0).toInt() * 10000; } if (parts.length() > 1) { mFileVersion += parts.at(1).toInt() * 100; } if (parts.length() > 2) { mFileVersion += parts.at(2).toInt(); } } mNamespaceUri = uniboardDocumentNamespaceUriFromVersion(mFileVersion); QStringRef svgSceneUuid = mXmlReader.attributes().value(mNamespaceUri, "uuid"); if (!svgSceneUuid.isNull()) mScene->setUuid(QUuid(svgSceneUuid.toString())); // introduced in UB 4.0 QStringRef svgViewBox = mXmlReader.attributes().value("viewBox"); if (!svgViewBox.isNull()) { QStringList ts = svgViewBox.toString().split(QLatin1Char(' '), QString::SkipEmptyParts); QRectF sceneRect; if (ts.size() >= 4) { sceneRect.setX(ts.at(0).toFloat()); sceneRect.setY(ts.at(1).toFloat()); sceneRect.setWidth(ts.at(2).toFloat()); sceneRect.setHeight(ts.at(3).toFloat()); mScene->setSceneRect(sceneRect); } else { qWarning() << "cannot make sense of 'viewBox' value " << svgViewBox.toString(); } } QStringRef pageDpi = mXmlReader.attributes().value("pageDpi"); if (!pageDpi.isNull()) UBSettings::settings()->pageDpi->set(pageDpi.toString()); bool darkBackground = false; bool crossedBackground = false; QStringRef ubDarkBackground = mXmlReader.attributes().value(mNamespaceUri, "dark-background"); if (!ubDarkBackground.isNull()) darkBackground = (ubDarkBackground.toString() == xmlTrue); QStringRef ubCrossedBackground = mXmlReader.attributes().value(mNamespaceUri, "crossed-background"); if (!ubDarkBackground.isNull()) crossedBackground = (ubCrossedBackground.toString() == xmlTrue); mScene->setBackground(darkBackground, crossedBackground); QStringRef pageNominalSize = mXmlReader.attributes().value(mNamespaceUri, "nominal-size"); if (!pageNominalSize.isNull()) { QStringList ts = pageNominalSize.toString().split(QLatin1Char('x'), QString::SkipEmptyParts); QSize sceneSize; if (ts.size() >= 2) { sceneSize.setWidth(ts.at(0).toInt()); sceneSize.setHeight(ts.at(1).toInt()); mScene->setNominalSize(sceneSize); } else { qWarning() << "cannot make sense of 'nominal-size' value " << pageNominalSize.toString(); } } } else if (mXmlReader.name() == "g") { strokesGroup = new UBGraphicsStrokesGroup(); graphicsItemFromSvg(strokesGroup); QStringRef ubZValue = mXmlReader.attributes().value(mNamespaceUri, "z-value"); if (!ubZValue.isNull()) { mGroupZIndex = ubZValue.toString().toFloat(); mGroupHasInfo = true; } QStringRef ubFillOnDarkBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-dark-background"); if (!ubFillOnDarkBackground.isNull()) { mGroupDarkBackgroundColor.setNamedColor(ubFillOnDarkBackground.toString()); } QStringRef ubFillOnLightBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-light-background"); if (!ubFillOnLightBackground.isNull()) { mGroupLightBackgroundColor.setNamedColor(ubFillOnLightBackground.toString()); } } else if (mXmlReader.name() == "polygon" || mXmlReader.name() == "line") { UBGraphicsPolygonItem* polygonItem = 0; QString parentId = mXmlReader.attributes().value(mNamespaceUri, "parent").toString(); if (mXmlReader.name() == "polygon") polygonItem = polygonItemFromPolygonSvg(mScene->isDarkBackground() ? Qt::white : Qt::black); else if (mXmlReader.name() == "line") polygonItem = polygonItemFromLineSvg(mScene->isDarkBackground() ? Qt::white : Qt::black); if(parentId.isEmpty() && strokesGroup) parentId = strokesGroup->uuid().toString(); if(parentId.isEmpty()) parentId = QUuid::createUuid().toString(); if (polygonItem) { polygonItem->setUuid(uuidFromSvg); polygonItem->setData(UBGraphicsItemData::ItemLayerType, QVariant(UBItemLayerType::Graphic)); UBGraphicsStrokesGroup* group; if(!mStrokesList.contains(parentId)){ group = new UBGraphicsStrokesGroup(); mStrokesList.insert(parentId,group); currentStroke = new UBGraphicsStroke(); group->setTransform(polygonItem->transform()); UBGraphicsItem::assignZValue(group, zFromSvg); } else group = mStrokesList.value(parentId); if(polygonItem->transform().isIdentity()) polygonItem->setTransform(group->transform()); group->addToGroup(polygonItem); polygonItem->setStrokesGroup(group); polygonItem->setStroke(currentStroke); polygonItem->show(); group->addToGroup(polygonItem); } } else if (mXmlReader.name() == "polyline") { QList polygonItems = polygonItemsFromPolylineSvg(mScene->isDarkBackground() ? Qt::white : Qt::black); QString parentId = QUuid::createUuid().toString(); foreach(UBGraphicsPolygonItem* polygonItem, polygonItems) { polygonItem->setData(UBGraphicsItemData::ItemLayerType, QVariant(UBItemLayerType::Graphic)); UBGraphicsStrokesGroup* group; if(!mStrokesList.contains(parentId)){ group = new UBGraphicsStrokesGroup(); mStrokesList.insert(parentId,group); currentStroke = new UBGraphicsStroke(); group->setTransform(polygonItem->transform()); UBGraphicsItem::assignZValue(group, zFromSvg); } else group = mStrokesList.value(parentId); if(polygonItem->transform().isIdentity()) polygonItem->setTransform(group->transform()); group->addToGroup(polygonItem); polygonItem->setStrokesGroup(group); polygonItem->setStroke(currentStroke); polygonItem->show(); group->addToGroup(polygonItem); } } else if (mXmlReader.name() == "image") { QStringRef imageHref = mXmlReader.attributes().value(nsXLink, "href"); if (!imageHref.isNull()) { QString href = imageHref.toString(); QStringRef ubBackground = mXmlReader.attributes().value(mNamespaceUri, "background"); bool isBackground = (!ubBackground.isNull() && ubBackground.toString() == xmlTrue); if (href.contains("png")) { UBGraphicsPixmapItem* pixmapItem = pixmapItemFromSvg(); if (pixmapItem) { UBGraphicsItem::assignZValue(pixmapItem, zFromSvg); pixmapItem->setUuid(uuidFromSvg); pixmapItem->setFlag(QGraphicsItem::ItemIsMovable, true); pixmapItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(pixmapItem); if (isBackground) mScene->setAsBackgroundObject(pixmapItem); pixmapItem->show(); } } else if (href.contains("svg")) { UBGraphicsSvgItem* svgItem = svgItemFromSvg(); if (svgItem) { UBGraphicsItem::assignZValue(svgItem, zFromSvg); svgItem->setUuid(uuidFromSvg); svgItem->setFlag(QGraphicsItem::ItemIsMovable, true); svgItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(svgItem); if (isBackground) mScene->setAsBackgroundObject(svgItem); svgItem->show(); } } else { qWarning() << "don't know what to do with href value " << href; } } } else if (mXmlReader.name() == "audio") { UBGraphicsMediaItem* audioItem = audioItemFromSvg(); if (audioItem) { UBGraphicsItem::assignZValue(audioItem, zFromSvg); audioItem->setUuid(uuidFromSvg); audioItem->setFlag(QGraphicsItem::ItemIsMovable, true); audioItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(audioItem); audioItem->show(); //force start to load the video and display the first frame audioItem->mediaObject()->play(); audioItem->mediaObject()->pause(); } } else if (mXmlReader.name() == "video") { UBGraphicsMediaItem* videoItem = videoItemFromSvg(); if (videoItem) { UBGraphicsItem::assignZValue(videoItem, zFromSvg); videoItem->setUuid(uuidFromSvg); videoItem->setFlag(QGraphicsItem::ItemIsMovable, true); videoItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(videoItem); videoItem->show(); //force start to load the video and display the first frame videoItem->mediaObject()->play(); videoItem->mediaObject()->pause(); } } else if (mXmlReader.name() == "text")//This is for backward compatibility with proto text field prior to version 4.3 { UBGraphicsTextItem* textItem = textItemFromSvg(); if (textItem) { UBGraphicsItem::assignZValue(textItem, zFromSvg); textItem->setUuid(uuidFromSvg); textItem->setFlag(QGraphicsItem::ItemIsMovable, true); textItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(textItem); textItem->show(); } } else if (mXmlReader.name() == "curtain") { UBGraphicsCurtainItem* mask = curtainItemFromSvg(); if (mask) { UBGraphicsItem::assignZValue(mask, zFromSvg); mask->setUuid(uuidFromSvg); mScene->addItem(mask); mScene->registerTool(mask); } } else if (mXmlReader.name() == "ruler") { UBGraphicsRuler *ruler = rulerFromSvg(); if (ruler) { UBGraphicsItem::assignZValue(ruler, zFromSvg); ruler->setUuid(uuidFromSvg); mScene->addItem(ruler); mScene->registerTool(ruler); } } else if (mXmlReader.name() == "compass") { UBGraphicsCompass *compass = compassFromSvg(); if (compass) { UBGraphicsItem::assignZValue(compass, zFromSvg); compass->setUuid(uuidFromSvg); mScene->addItem(compass); mScene->registerTool(compass); } } else if (mXmlReader.name() == "protractor") { UBGraphicsProtractor *protractor = protractorFromSvg(); if (protractor) { UBGraphicsItem::assignZValue(protractor, zFromSvg); protractor->setUuid(uuidFromSvg); mScene->addItem(protractor); mScene->registerTool(protractor); } } else if (mXmlReader.name() == "triangle") { UBGraphicsTriangle *triangle = triangleFromSvg(); if (triangle) { UBGraphicsItem::assignZValue(triangle, zFromSvg); triangle->setUuid(uuidFromSvg); mScene->addItem(triangle); mScene->registerTool(triangle); } } else if (mXmlReader.name() == "cache") { UBGraphicsCache* cache = cacheFromSvg(); if(cache) { UBGraphicsItem::assignZValue(cache, zFromSvg); cache->setUuid(uuidFromSvg); mScene->addItem(cache); mScene->registerTool(cache); UBApplication::boardController->notifyCache(true); } } else if (mXmlReader.name() == "foreignObject") { QString href = mXmlReader.attributes().value(nsXLink, "href").toString(); QString src = mXmlReader.attributes().value(mNamespaceUri, "src").toString(); QString type = mXmlReader.attributes().value(mNamespaceUri, "type").toString(); bool isBackground = mXmlReader.attributes().value(mNamespaceUri, "background").toString() == xmlTrue; qreal foreignObjectWidth = mXmlReader.attributes().value("width").toString().toFloat(); qreal foreignObjectHeight = mXmlReader.attributes().value("height").toString().toFloat(); if (href.contains(".pdf")) { UBGraphicsPDFItem* pdfItem = pdfItemFromPDF(); if (pdfItem) { UBGraphicsItem::assignZValue(pdfItem, zFromSvg); pdfItem->setUuid(uuidFromSvg); QDesktopWidget* desktop = UBApplication::desktop(); qreal currentDpi = (desktop->physicalDpiX() + desktop->physicalDpiY()) / 2; qreal pdfScale = UBSettings::settings()->pageDpi->get().toReal()/currentDpi; pdfItem->setScale(pdfScale); pdfItem->setFlag(QGraphicsItem::ItemIsMovable, true); pdfItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(pdfItem); if (isBackground) mScene->setAsBackgroundObject(pdfItem); pdfItem->show(); currentWidget = 0; } } else if (src.contains(".wdgt")) { UBGraphicsAppleWidgetItem* appleWidgetItem = graphicsAppleWidgetFromSvg(); if (appleWidgetItem) { UBGraphicsItem::assignZValue(appleWidgetItem, zFromSvg); appleWidgetItem->setUuid(uuidFromSvg); appleWidgetItem->setFlag(QGraphicsItem::ItemIsSelectable, true); appleWidgetItem->resize(foreignObjectWidth, foreignObjectHeight); mScene->addItem(appleWidgetItem); appleWidgetItem->show(); currentWidget = appleWidgetItem; } } else if (src.contains(".wgt")) { UBGraphicsW3CWidgetItem* w3cWidgetItem = graphicsW3CWidgetFromSvg(); if (w3cWidgetItem) { UBGraphicsItem::assignZValue(w3cWidgetItem, zFromSvg); w3cWidgetItem->setUuid(uuidFromSvg); w3cWidgetItem->setFlag(QGraphicsItem::ItemIsSelectable, true); w3cWidgetItem->resize(foreignObjectWidth, foreignObjectHeight); mScene->addItem(w3cWidgetItem); w3cWidgetItem->show(); currentWidget = w3cWidgetItem; } } else if (type == "text") { UBGraphicsTextItem* textItem = textItemFromSvg(); UBGraphicsTextItemDelegate *textDelegate = 0; if (textItem) textDelegate = dynamic_cast(textItem->Delegate()); if (textDelegate) { QDesktopWidget* desktop = UBApplication::desktop(); qreal currentDpi = (desktop->physicalDpiX() + desktop->physicalDpiY()) / 2; qreal textSizeMultiplier = UBSettings::settings()->pageDpi->get().toReal()/currentDpi; textDelegate->scaleTextSize(textSizeMultiplier); } if (textItem) { UBGraphicsItem::assignZValue(textItem, zFromSvg); textItem->setUuid(uuidFromSvg); textItem->setFlag(QGraphicsItem::ItemIsMovable, true); textItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(textItem); textItem->show(); } } else { qWarning() << "Ignoring unknown foreignObject:" << href; } } else if (currentWidget && (mXmlReader.name() == "preference")) { QString key = mXmlReader.attributes().value("key").toString(); QString value = mXmlReader.attributes().value("value").toString(); currentWidget->setPreference(key, value); } else if (currentWidget && (mXmlReader.name() == "datastoreEntry")) { QString key = mXmlReader.attributes().value("key").toString(); QString value = mXmlReader.attributes().value("value").toString(); currentWidget->setDatastoreEntry(key, value); } else if (mXmlReader.name() == tGroups) { //considering groups section at the end of the document readGroupRoot(); } else { // NOOP } } else if (mXmlReader.isEndElement()) { if (mXmlReader.name() == "g") { mGroupHasInfo = false; mGroupDarkBackgroundColor = QColor(); mGroupLightBackgroundColor = QColor(); } } } if (mXmlReader.hasError()) { qWarning() << "error parsing Sankore file " << mXmlReader.errorString(); } qDebug() << "Number of detected strokes: " << mStrokesList.count(); QHashIterator iterator(mStrokesList); while (iterator.hasNext()) { iterator.next(); mScene->addItem(iterator.value()); } if (mScene) mScene->setModified(false); mScene->enableUndoRedoStack(); qDebug() << "loadScene() : created scene and read file"; qDebug() << "spent milliseconds: " << time.elapsed(); return mScene; } UBGraphicsGroupContainerItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::readGroup() { UBGraphicsGroupContainerItem *group = new UBGraphicsGroupContainerItem(); QList groupContainer; QString id = mXmlReader.attributes().value(aId).toString(); id = id.mid(1,id.length()-2); bool shouldSkipSubElements = false; if(mStrokesList.contains(id)) shouldSkipSubElements = true; mXmlReader.readNext(); while (!mXmlReader.atEnd()) { if (mXmlReader.isEndElement()) { mXmlReader.readNext(); break; } else if (mXmlReader.isStartElement()) { if (mXmlReader.name() == tGroup) { UBGraphicsGroupContainerItem *curGroup = readGroup(); if (curGroup) groupContainer.append(curGroup); } else if (mXmlReader.name() == tElement && !shouldSkipSubElements) { QString id = mXmlReader.attributes().value(aId).toString(); QGraphicsItem *curItem = readElementFromGroup(); // Explanation: the second condition discriminate the old storage version that should // not be interpreted anymore if(curItem && id.count("{") < 2) groupContainer.append(curItem); } else { mXmlReader.skipCurrentElement(); } } else { mXmlReader.readNext(); } } foreach(QGraphicsItem* item, groupContainer) group->addToGroup(item); if (group->childItems().count()) { mScene->addItem(group); if (1 == group->childItems().count()) { group->destroy(false); } } return group; } void UBSvgSubsetAdaptor::UBSvgSubsetReader::readGroupRoot() { mXmlReader.readNext(); while (!mXmlReader.atEnd()) { if (mXmlReader.isEndElement()) { mXmlReader.readNext(); break; } else if (mXmlReader.isStartElement()) { if (mXmlReader.name() == tGroup) { UBGraphicsGroupContainerItem *curGroup = readGroup(); if (curGroup) { mScene->addGroup(curGroup); } } else { mXmlReader.skipCurrentElement(); } } else { mXmlReader.readNext(); } } } QGraphicsItem *UBSvgSubsetAdaptor::UBSvgSubsetReader::readElementFromGroup() { QGraphicsItem *result = 0; QString id = mXmlReader.attributes().value(aId).toString(); QString uuid = id.right(QUuid().toString().size()); result = mScene->itemForUuid(QUuid(uuid)); if(!result) result = mStrokesList.take(uuid.replace("}","").replace("{","")); mXmlReader.skipCurrentElement(); mXmlReader.readNext(); return result; } void UBSvgSubsetAdaptor::persistScene(UBDocumentProxy* proxy, UBGraphicsScene* pScene, const int pageIndex) { UBSvgSubsetWriter writer(proxy, pScene, pageIndex); writer.persistScene(pageIndex); } UBSvgSubsetAdaptor::UBSvgSubsetWriter::UBSvgSubsetWriter(UBDocumentProxy* proxy, UBGraphicsScene* pScene, const int pageIndex) : mScene(pScene) , mDocumentPath(proxy->persistencePath()) , mPageIndex(pageIndex) { // NOOP } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::writeSvgElement() { mXmlWriter.writeStartElement("svg"); mXmlWriter.writeAttribute("version", "1.1"); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "version", UBSettings::currentFileVersion); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "uuid", UBStringUtils::toCanonicalUuid(mScene->uuid())); int margin = UBSettings::settings()->svgViewBoxMargin->get().toInt(); QRect normalized = mScene->normalizedSceneRect().toRect(); normalized.translate(margin * -1, margin * -1); normalized.setWidth(normalized.width() + (margin * 2)); normalized.setHeight(normalized.height() + (margin * 2)); mXmlWriter.writeAttribute("viewBox", QString("%1 %2 %3 %4").arg(normalized.x()).arg(normalized.y()).arg(normalized.width()).arg(normalized.height())); QSize pageNominalSize = mScene->nominalSize(); if (pageNominalSize.isValid()) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "nominal-size", QString("%1x%2").arg(pageNominalSize.width()).arg(pageNominalSize.height())); } mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "dark-background", mScene->isDarkBackground() ? xmlTrue : xmlFalse); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "crossed-background", mScene->isCrossedBackground() ? xmlTrue : xmlFalse); QDesktopWidget* desktop = UBApplication::desktop(); mXmlWriter.writeAttribute("pageDpi", QString("%1").arg((desktop->physicalDpiX() + desktop->physicalDpiY()) / 2)); mXmlWriter.writeStartElement("rect"); mXmlWriter.writeAttribute("fill", mScene->isDarkBackground() ? "black" : "white"); mXmlWriter.writeAttribute("x", QString::number(normalized.x())); mXmlWriter.writeAttribute("y", QString::number(normalized.y())); mXmlWriter.writeAttribute("width", QString::number(normalized.width())); mXmlWriter.writeAttribute("height", QString::number(normalized.height())); mXmlWriter.writeEndElement(); } bool UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistScene(int pageIndex) { if (mScene->isModified() || (UBApplication::boardController->paletteManager()->teacherGuideDockWidget() && UBApplication::boardController->paletteManager()->teacherGuideDockWidget()->teacherGuideWidget()->isModified())) { //Creating dom structure to store information QDomDocument groupDomDocument; QDomElement groupRoot = groupDomDocument.createElement(tGroups); groupDomDocument.appendChild(groupRoot); QBuffer buffer; buffer.open(QBuffer::WriteOnly); mXmlWriter.setDevice(&buffer); mXmlWriter.setAutoFormatting(true); mXmlWriter.writeStartDocument(); mXmlWriter.writeDefaultNamespace(nsSvg); mXmlWriter.writeNamespace(nsXLink, "xlink"); mXmlWriter.writeNamespace(UBSettings::uniboardDocumentNamespaceUri, "ub"); mXmlWriter.writeNamespace(nsXHtml, "xhtml"); writeSvgElement(); // Get the items from the scene QList items = mScene->items(); int strokes = 0; int polygons = 0; foreach(QGraphicsItem *item, items) { if (item->type() == UBGraphicsPolygonItem::Type) { polygons++; } else if (item->type() == UBGraphicsStrokesGroup::Type) { strokes++; } } qDebug() << "---Strokes count" << strokes << "Polygons count" << polygons; qSort(items.begin(), items.end(), itemZIndexComp); UBGraphicsStroke *openStroke = 0; int nextStroke = 0; bool groupHoldsInfo = false; while (!items.empty()) { QGraphicsItem *item = items.takeFirst(); // Is the item a strokes group? UBGraphicsStrokesGroup* strokesGroupItem = qgraphicsitem_cast(item); if(strokesGroupItem && strokesGroupItem->isVisible()){ // Add the polygons //parsing number of polygons into one polygon qDebug() << "parsing stroke number" << nextStroke++; UBGraphicsPolygonItem *resultPoly = 0; foreach(QGraphicsItem* item, strokesGroupItem->childItems()) { UBGraphicsPolygonItem* poly = qgraphicsitem_cast(item); if (!poly) continue; if (!resultPoly) { resultPoly = poly; continue; } QPolygonF unitedPolygon = resultPoly->polygon().united(poly->polygon()); resultPoly->setPolygon(unitedPolygon); items.removeOne(poly); } if (resultPoly) { resultPoly->setZValue(strokesGroupItem->zValue()); //Claudio: the painter path simplification remove all the polygons overlap QPainterPath painterPath; painterPath.addPolygon(resultPoly->polygon()); painterPath = painterPath.simplified(); resultPoly->setPolygon(painterPath.toFillPolygon()); polygonItemToSvgPolygon(resultPoly, false); items.removeOne(resultPoly); } } // Is the item a polygon? UBGraphicsPolygonItem *polygonItem = qgraphicsitem_cast (item); if (polygonItem && polygonItem->isVisible()) { UBGraphicsStroke* currentStroke = polygonItem->stroke(); if (openStroke && (currentStroke != openStroke)) { mXmlWriter.writeEndElement(); //g openStroke = 0; groupHoldsInfo = false; } bool firstPolygonInStroke = currentStroke && !openStroke; if (firstPolygonInStroke) { mXmlWriter.writeStartElement("g"); openStroke = currentStroke; QMatrix matrix = item->sceneMatrix(); if (!matrix.isIdentity()) mXmlWriter.writeAttribute("transform", toSvgTransform(matrix)); UBGraphicsStroke* stroke = dynamic_cast(currentStroke); if (stroke) { QColor colorOnDarkBackground = polygonItem->colorOnDarkBackground(); QColor colorOnLightBackground = polygonItem->colorOnLightBackground(); if (colorOnDarkBackground.isValid() && colorOnLightBackground.isValid()) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "z-value" , QString("%1").arg(polygonItem->zValue())); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-dark-background", colorOnDarkBackground.name()); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-light-background", colorOnLightBackground.name()); groupHoldsInfo = true; } } if (stroke && !stroke->hasPressure()) { strokeToSvgPolyline(stroke, groupHoldsInfo); //we can dequeue all polygons belonging to that stroke foreach(UBGraphicsPolygonItem* gi, stroke->polygons()) { items.removeOne(gi); } continue; } } if (polygonItem->isNominalLine()) polygonItemToSvgLine(polygonItem, groupHoldsInfo); else polygonItemToSvgPolygon(polygonItem, groupHoldsInfo); continue; } if (openStroke) { mXmlWriter.writeEndElement(); //g groupHoldsInfo = false; openStroke = 0; } // Is the item a picture? UBGraphicsPixmapItem *pixmapItem = qgraphicsitem_cast (item); if (pixmapItem && pixmapItem->isVisible()) { pixmapItemToLinkedImage(pixmapItem); continue; } // Is the item a shape? UBGraphicsSvgItem *svgItem = qgraphicsitem_cast (item); if (svgItem && svgItem->isVisible()) { svgItemToLinkedSvg(svgItem); continue; } UBGraphicsMediaItem *mediaItem = qgraphicsitem_cast (item); if (mediaItem && mediaItem->isVisible()) { if (UBGraphicsMediaItem::mediaType_Video == mediaItem->getMediaType()) videoItemToLinkedVideo(mediaItem); else audioItemToLinkedAudio(mediaItem); continue; } // Is the item an app? UBGraphicsAppleWidgetItem *appleWidgetItem = qgraphicsitem_cast (item); if (appleWidgetItem && appleWidgetItem->isVisible()) { graphicsAppleWidgetToSvg(appleWidgetItem); continue; } // Is the item a W3C? UBGraphicsW3CWidgetItem *w3cWidgetItem = qgraphicsitem_cast (item); if (w3cWidgetItem && w3cWidgetItem->isVisible()) { graphicsW3CWidgetToSvg(w3cWidgetItem); continue; } // Is the item a PDF? UBGraphicsPDFItem *pdfItem = qgraphicsitem_cast (item); if (pdfItem && pdfItem->isVisible()) { pdfItemToLinkedPDF(pdfItem); continue; } // Is the item a text? UBGraphicsTextItem *textItem = qgraphicsitem_cast (item); if (textItem && textItem->isVisible()) { textItemToSvg(textItem); continue; } // Is the item a curtain? UBGraphicsCurtainItem *curtainItem = qgraphicsitem_cast (item); if (curtainItem && curtainItem->isVisible()) { curtainItemToSvg(curtainItem); continue; } // Is the item a ruler? UBGraphicsRuler *ruler = qgraphicsitem_cast (item); if (ruler && ruler->isVisible()) { rulerToSvg(ruler); continue; } // Is the item a cache? UBGraphicsCache* cache = qgraphicsitem_cast(item); if(cache && cache->isVisible()) { cacheToSvg(cache); continue; } // Is the item a compass UBGraphicsCompass *compass = qgraphicsitem_cast (item); if (compass && compass->isVisible()) { compassToSvg(compass); continue; } // Is the item a protractor? UBGraphicsProtractor *protractor = qgraphicsitem_cast (item); if (protractor && protractor->isVisible()) { protractorToSvg(protractor); continue; } // Is the item a triangle? UBGraphicsTriangle *triangle = qgraphicsitem_cast (item); if (triangle && triangle->isVisible()) { triangleToSvg(triangle); continue; } // Is the item a group? UBGraphicsGroupContainerItem *groupItem = qgraphicsitem_cast(item); if (groupItem && groupItem->isVisible()) { persistGroupToDom(groupItem, &groupRoot, &groupDomDocument); continue; } } if (openStroke) { mXmlWriter.writeEndElement(); groupHoldsInfo = false; openStroke = 0; } QMap elements = getAdditionalElementToStore(); QVector dataStorageItems; if(elements.value("teacherGuide")) dataStorageItems = elements.value("teacherGuide")->save(pageIndex); foreach(tIDataStorage* eachItem, dataStorageItems){ if(eachItem->type == eElementType_START){ mXmlWriter.writeStartElement(eachItem->name); foreach(QString key,eachItem->attributes.keys()) mXmlWriter.writeAttribute(key,eachItem->attributes.value(key)); } else if (eachItem->type == eElementType_END) mXmlWriter.writeEndElement(); else if (eachItem->type == eElementType_UNIQUE){ mXmlWriter.writeStartElement(eachItem->name); foreach(QString key,eachItem->attributes.keys()) mXmlWriter.writeAttribute(key,eachItem->attributes.value(key)); mXmlWriter.writeEndElement(); } else qWarning() << "unknown type"; } //writing group data if (groupRoot.hasChildNodes()) { mXmlWriter.writeStartElement(tGroups); QDomElement curElement = groupRoot.firstChildElement(); while (!curElement.isNull()) { if (curElement.hasAttribute(aId)) { mXmlWriter.writeStartElement(curElement.tagName()); mXmlWriter.writeAttribute(aId, curElement.attribute(aId)); QDomElement curSubElement = curElement.firstChildElement(); while (!curSubElement.isNull()) { if (curSubElement.hasAttribute(aId)) { mXmlWriter.writeStartElement(curSubElement.tagName()); mXmlWriter.writeAttribute(aId, curSubElement.attribute(aId)); mXmlWriter.writeEndElement(); curSubElement = curSubElement.nextSiblingElement(); } } mXmlWriter.writeEndElement(); } curElement = curElement.nextSiblingElement(); } mXmlWriter.writeEndElement(); } mXmlWriter.writeEndDocument(); QString fileName = mDocumentPath + UBFileSystemUtils::digitFileFormat("/page%1.svg", mPageIndex); QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCritical() << "cannot open " << fileName << " for writing ..."; return false; } file.write(buffer.data()); file.flush(); file.close(); } else { qDebug() << "ignoring unmodified page" << UBApplication::boardController->pageFromSceneIndex(mPageIndex); } return true; } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::persistGroupToDom(QGraphicsItem *groupItem, QDomElement *curParent, QDomDocument *groupDomDocument) { QUuid uuid = UBGraphicsScene::getPersonalUuid(groupItem); if (!uuid.isNull()) { QDomElement curGroupElement = groupDomDocument->createElement(tGroup); curGroupElement.setAttribute(aId, uuid); curParent->appendChild(curGroupElement); foreach (QGraphicsItem *item, groupItem->childItems()) { QUuid tmpUuid = UBGraphicsScene::getPersonalUuid(item); if (!tmpUuid.isNull()) { if (item->type() == UBGraphicsGroupContainerItem::Type && item->childItems().count()) persistGroupToDom(item, curParent, groupDomDocument); } else { QDomElement curSubElement = groupDomDocument->createElement(tElement); curSubElement.setAttribute(aId, tmpUuid); curGroupElement.appendChild(curSubElement); } } } } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::polygonItemToSvgLine(UBGraphicsPolygonItem* polygonItem, bool groupHoldsInfo) { mXmlWriter.writeStartElement("line"); QLineF line = polygonItem->originalLine(); mXmlWriter.writeAttribute("x1", QString::number(line.p1().x(), 'f', 2)); mXmlWriter.writeAttribute("y1", QString::number(line.p1().y(), 'f', 2)); // SVG renderers (Chrome) do not like line where (x1, y1) == (x2, y2) qreal x2 = line.p2().x(); if (line.p1() == line.p2()) x2 += 0.01; mXmlWriter.writeAttribute("x2", QString::number(x2, 'f', 2)); mXmlWriter.writeAttribute("y2", QString::number(line.p2().y(), 'f', 2)); mXmlWriter.writeAttribute("stroke-width", QString::number(polygonItem->originalWidth(), 'f', -1)); mXmlWriter.writeAttribute("stroke", polygonItem->brush().color().name()); qreal alpha = polygonItem->brush().color().alphaF(); if (alpha < 1.0) mXmlWriter.writeAttribute("stroke-opacity", QString::number(alpha, 'f', 2)); mXmlWriter.writeAttribute("stroke-linecap", "round"); if (!groupHoldsInfo) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "z-value", QString("%1").arg(polygonItem->zValue())); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "fill-on-dark-background", polygonItem->colorOnDarkBackground().name()); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "fill-on-light-background", polygonItem->colorOnLightBackground().name()); } mXmlWriter.writeEndElement(); } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::strokeToSvgPolyline(UBGraphicsStroke* stroke, bool groupHoldsInfo) { QList pols = stroke->polygons(); if (pols.length() > 0) { mXmlWriter.writeStartElement("polyline"); QVector points; foreach(UBGraphicsPolygonItem* polygon, pols) { points << polygon->originalLine().p1(); } points << pols.last()->originalLine().p2(); // SVG renderers (Chrome) do not like line withe where x1/y1 == x2/y2 if (points.size() == 2 && (points.at(0) == points.at(1))) { points[1] = QPointF(points[1].x() + 0.01, points[1].y()); } QString svgPoints = pointsToSvgPointsAttribute(points); mXmlWriter.writeAttribute("points", svgPoints); UBGraphicsPolygonItem* firstPolygonItem = pols.at(0); mXmlWriter.writeAttribute("fill", "none"); mXmlWriter.writeAttribute("stroke-width", QString::number(firstPolygonItem->originalWidth(), 'f', 2)); mXmlWriter.writeAttribute("stroke", firstPolygonItem->brush().color().name()); mXmlWriter.writeAttribute("stroke-opacity", QString("%1").arg(firstPolygonItem->brush().color().alphaF())); mXmlWriter.writeAttribute("stroke-linecap", "round"); if (!groupHoldsInfo) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "z-value", QString("%1").arg(firstPolygonItem->zValue())); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-dark-background", firstPolygonItem->colorOnDarkBackground().name()); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-light-background", firstPolygonItem->colorOnLightBackground().name()); } mXmlWriter.writeEndElement(); } } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::strokeToSvgPolygon(UBGraphicsStroke* stroke, bool groupHoldsInfo) { QList pis = stroke->polygons(); if (pis.length() > 0) { QPolygonF united; foreach(UBGraphicsPolygonItem* pi, pis) { united = united.united(pi->polygon()); } UBGraphicsPolygonItem *clone = static_cast(pis.at(0)->deepCopy()); clone->setPolygon(united); polygonItemToSvgPolygon(clone, groupHoldsInfo); } } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::polygonItemToSvgPolygon(UBGraphicsPolygonItem* polygonItem, bool groupHoldsInfo) { QPolygonF polygon = polygonItem->polygon(); int pointsCount = polygon.size(); if (polygonItem && pointsCount > 0) { mXmlWriter.writeStartElement("polygon"); QString points = pointsToSvgPointsAttribute(polygon); mXmlWriter.writeAttribute("points", points); mXmlWriter.writeAttribute("transform",toSvgTransform(polygonItem->sceneMatrix())); mXmlWriter.writeAttribute("fill", polygonItem->brush().color().name()); qreal alpha = polygonItem->brush().color().alphaF(); mXmlWriter.writeAttribute("fill-opacity", QString::number(alpha, 'f', 2)); // we trick SVG antialiasing, to avoid seeing light gaps between polygons if (alpha < 1.0) { qreal trickedAlpha = trickAlpha(alpha); mXmlWriter.writeAttribute("stroke", polygonItem->brush().color().name()); mXmlWriter.writeAttribute("stroke-width", "1"); mXmlWriter.writeAttribute("stroke-opacity", QString::number(trickedAlpha, 'f', 2)); } // svg default fill rule is nonzero, but Qt is evenodd // //http://www.w3.org/TR/SVG11/painting.html //http://doc.trolltech.com/4.5/qgraphicspolygonitem.html#fillRule // if (polygonItem->fillRule() == Qt::OddEvenFill) mXmlWriter.writeAttribute("fill-rule", "evenodd"); if (!groupHoldsInfo) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "z-value", QString("%1").arg(polygonItem->zValue())); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-dark-background", polygonItem->colorOnDarkBackground().name()); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-light-background", polygonItem->colorOnLightBackground().name()); } mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "uuid", UBStringUtils::toCanonicalUuid(polygonItem->uuid())); if (polygonItem->parentItem()) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "parent", UBStringUtils::toCanonicalUuid(UBGraphicsItem::getOwnUuid(polygonItem->parentItem()))); } mXmlWriter.writeEndElement(); } } UBGraphicsPolygonItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::polygonItemFromPolygonSvg(const QColor& pDefaultColor) { UBGraphicsPolygonItem* polygonItem = new UBGraphicsPolygonItem(); QStringRef svgPoints = mXmlReader.attributes().value("points"); QPolygonF polygon; if (!svgPoints.isNull()) { QStringList ts = svgPoints.toString().split(QLatin1Char(' '), QString::SkipEmptyParts); foreach(const QString sPoint, ts) { QStringList sCoord = sPoint.split(QLatin1Char(','), QString::SkipEmptyParts); if (sCoord.size() == 2) { QPointF point; point.setX(sCoord.at(0).toFloat()); point.setY(sCoord.at(1).toFloat()); polygon << point; } else if (sCoord.size() == 4){ //This is the case on system were the "," is used to seperate decimal QPointF point; QString x = sCoord.at(0) + "." + sCoord.at(1); QString y = sCoord.at(2) + "." + sCoord.at(3); point.setX(x.toFloat()); point.setY(y.toFloat()); polygon << point; } else { qWarning() << "cannot make sense of a 'point' value" << sCoord; } } } else { qWarning() << "cannot make sense of 'points' value " << svgPoints.toString(); } polygonItem->setPolygon(polygon); QStringRef svgTransform = mXmlReader.attributes().value("transform"); QMatrix itemMatrix; if (!svgTransform.isNull()) { itemMatrix = fromSvgTransform(svgTransform.toString()); polygonItem->setMatrix(itemMatrix); } QStringRef svgFill = mXmlReader.attributes().value("fill"); QColor brushColor = pDefaultColor; if (!svgFill.isNull()) { brushColor.setNamedColor(svgFill.toString()); } QStringRef svgFillOpacity = mXmlReader.attributes().value("fill-opacity"); qreal opacity = 1.0; if (!svgFillOpacity.isNull()) { opacity = svgFillOpacity.toString().toFloat(); brushColor.setAlphaF(opacity); } polygonItem->setColor(brushColor); QStringRef ubZValue = mXmlReader.attributes().value(mNamespaceUri, "z-value"); if (!ubZValue.isNull()) { UBGraphicsItem::assignZValue(polygonItem, ubZValue.toString().toFloat()); } else { UBGraphicsItem::assignZValue(polygonItem, mGroupZIndex); } QStringRef ubFillOnDarkBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-dark-background"); if (!ubFillOnDarkBackground.isNull()) { QColor color; color.setNamedColor(ubFillOnDarkBackground.toString()); if (!color.isValid()) color = Qt::white; color.setAlphaF(opacity); polygonItem->setColorOnDarkBackground(color); } else { QColor color = mGroupDarkBackgroundColor; color.setAlphaF(opacity); polygonItem->setColorOnDarkBackground(color); } QStringRef ubFillOnLightBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-light-background"); if (!ubFillOnLightBackground.isNull()) { QColor color; color.setNamedColor(ubFillOnLightBackground.toString()); if (!color.isValid()) color = Qt::black; color.setAlphaF(opacity); polygonItem->setColorOnLightBackground(color); } else { QColor color = mGroupLightBackgroundColor; color.setAlphaF(opacity); polygonItem->setColorOnLightBackground(color); } return polygonItem; } UBGraphicsPolygonItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::polygonItemFromLineSvg(const QColor& pDefaultColor) { QStringRef svgX1 = mXmlReader.attributes().value("x1"); QStringRef svgY1 = mXmlReader.attributes().value("y1"); QStringRef svgX2 = mXmlReader.attributes().value("x2"); QStringRef svgY2 = mXmlReader.attributes().value("y2"); QLineF line; if (!svgX1.isNull() && !svgY1.isNull() && !svgX2.isNull() && !svgY2.isNull()) { qreal x1 = svgX1.toString().toFloat(); qreal y1 = svgY1.toString().toFloat(); qreal x2 = svgX2.toString().toFloat(); qreal y2 = svgY2.toString().toFloat(); line.setLine(x1, y1, x2, y2); } else { qWarning() << "cannot make sense of 'line' value"; return 0; } QStringRef strokeWidth = mXmlReader.attributes().value("stroke-width"); qreal lineWidth = 1.; if (!strokeWidth.isNull()) { lineWidth = strokeWidth.toString().toFloat(); } UBGraphicsPolygonItem* polygonItem = new UBGraphicsPolygonItem(line, lineWidth); QStringRef svgStroke = mXmlReader.attributes().value("stroke"); QColor brushColor = pDefaultColor; if (!svgStroke.isNull()) { brushColor.setNamedColor(svgStroke.toString()); } QStringRef svgStrokeOpacity = mXmlReader.attributes().value("stroke-opacity"); qreal opacity = 1.0; if (!svgStrokeOpacity.isNull()) { opacity = svgStrokeOpacity.toString().toFloat(); brushColor.setAlphaF(opacity); } polygonItem->setColor(brushColor); QStringRef ubZValue = mXmlReader.attributes().value(mNamespaceUri, "z-value"); if (!ubZValue.isNull()) { UBGraphicsItem::assignZValue(polygonItem, ubZValue.toString().toFloat()); } else { UBGraphicsItem::assignZValue(polygonItem, mGroupZIndex); } QStringRef ubFillOnDarkBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-dark-background"); if (!ubFillOnDarkBackground.isNull()) { QColor color; color.setNamedColor(ubFillOnDarkBackground.toString()); if (!color.isValid()) color = Qt::white; color.setAlphaF(opacity); polygonItem->setColorOnDarkBackground(color); } else { QColor color = mGroupDarkBackgroundColor; color.setAlphaF(opacity); polygonItem->setColorOnDarkBackground(color); } QStringRef ubFillOnLightBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-light-background"); if (!ubFillOnLightBackground.isNull()) { QColor color; color.setNamedColor(ubFillOnLightBackground.toString()); if (!color.isValid()) color = Qt::black; color.setAlphaF(opacity); polygonItem->setColorOnLightBackground(color); } else { QColor color = mGroupLightBackgroundColor; color.setAlphaF(opacity); polygonItem->setColorOnLightBackground(color); } return polygonItem; } QList UBSvgSubsetAdaptor::UBSvgSubsetReader::polygonItemsFromPolylineSvg(const QColor& pDefaultColor) { QStringRef strokeWidth = mXmlReader.attributes().value("stroke-width"); qreal lineWidth = 1.; if (!strokeWidth.isNull()) { lineWidth = strokeWidth.toString().toFloat(); } QColor brushColor = pDefaultColor; QStringRef svgStroke = mXmlReader.attributes().value("stroke"); if (!svgStroke.isNull()) { brushColor.setNamedColor(svgStroke.toString()); } qreal opacity = 1.0; QStringRef svgStrokeOpacity = mXmlReader.attributes().value("stroke-opacity"); if (!svgStrokeOpacity.isNull()) { opacity = svgStrokeOpacity.toString().toFloat(); brushColor.setAlphaF(opacity); } QStringRef ubZValue = mXmlReader.attributes().value(mNamespaceUri, "z-value"); qreal zValue = mGroupZIndex; if (!ubZValue.isNull()) { zValue = ubZValue.toString().toFloat(); } QColor colorOnDarkBackground = mGroupDarkBackgroundColor; QStringRef ubFillOnDarkBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-dark-background"); if (!ubFillOnDarkBackground.isNull()) { colorOnDarkBackground.setNamedColor(ubFillOnDarkBackground.toString()); } if (!colorOnDarkBackground.isValid()) colorOnDarkBackground = Qt::white; colorOnDarkBackground.setAlphaF(opacity); QColor colorOnLightBackground = mGroupLightBackgroundColor; QStringRef ubFillOnLightBackground = mXmlReader.attributes().value(mNamespaceUri, "fill-on-light-background"); if (!ubFillOnLightBackground.isNull()) { QColor colorOnLightBackground; colorOnLightBackground.setNamedColor(ubFillOnLightBackground.toString()); } if (!colorOnLightBackground.isValid()) colorOnLightBackground = Qt::black; colorOnLightBackground.setAlphaF(opacity); QStringRef svgPoints = mXmlReader.attributes().value("points"); QList polygonItems; if (!svgPoints.isNull()) { QStringList ts = svgPoints.toString().split(QLatin1Char(' '), QString::SkipEmptyParts); QList points; foreach(const QString sPoint, ts) { QStringList sCoord = sPoint.split(QLatin1Char(','), QString::SkipEmptyParts); if (sCoord.size() == 2) { QPointF point; point.setX(sCoord.at(0).toFloat()); point.setY(sCoord.at(1).toFloat()); points << point; } else if (sCoord.size() == 4){ //This is the case on system were the "," is used to seperate decimal QPointF point; QString x = sCoord.at(0) + "." + sCoord.at(1); QString y = sCoord.at(2) + "." + sCoord.at(3); point.setX(x.toFloat()); point.setY(y.toFloat()); points << point; } else { qWarning() << "cannot make sense of a 'point' value" << sCoord; } } for (int i = 0; i < points.size() - 1; i++) { UBGraphicsPolygonItem* polygonItem = new UBGraphicsPolygonItem(QLineF(points.at(i), points.at(i + 1)), lineWidth); polygonItem->setColor(brushColor); UBGraphicsItem::assignZValue(polygonItem, zValue); polygonItem->setColorOnDarkBackground(colorOnDarkBackground); polygonItem->setColorOnLightBackground(colorOnLightBackground); polygonItems <uuid().toString() + ".png"; QString path = mDocumentPath + "/" + fileName; if (!QFile::exists(path)) { QDir dir; dir.mkdir(mDocumentPath + "/" + UBPersistenceManager::imageDirectory); pixmapItem->pixmap().toImage().save(path, "PNG"); } mXmlWriter.writeAttribute(nsXLink, "href", fileName); graphicsItemToSvg(pixmapItem); mXmlWriter.writeEndElement(); } UBGraphicsPixmapItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::pixmapItemFromSvg() { UBGraphicsPixmapItem* pixmapItem = new UBGraphicsPixmapItem(); QStringRef imageHref = mXmlReader.attributes().value(nsXLink, "href"); if (!imageHref.isNull()) { QString href = imageHref.toString(); QPixmap pix(mDocumentPath + "/" + UBFileSystemUtils::normalizeFilePath(href)); pixmapItem->setPixmap(pix); } else { qWarning() << "cannot make sens of image href value"; return 0; } graphicsItemFromSvg(pixmapItem); return pixmapItem; } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::svgItemToLinkedSvg(UBGraphicsSvgItem* svgItem) { mXmlWriter.writeStartElement("image"); QString fileName = UBPersistenceManager::imageDirectory + "/" + svgItem->uuid().toString() + ".svg"; QString path = mDocumentPath + "/" + fileName; if (!QFile::exists(path)) { QDir dir; dir.mkdir(mDocumentPath + "/" + UBPersistenceManager::imageDirectory); QFile file(path); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "cannot open file for writing embeded svg content " << path; return; } file.write(svgItem->fileData()); } mXmlWriter.writeAttribute(nsXLink, "href", fileName); graphicsItemToSvg(svgItem); mXmlWriter.writeEndElement(); } UBGraphicsSvgItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::svgItemFromSvg() { UBGraphicsSvgItem* svgItem = 0; QStringRef imageHref = mXmlReader.attributes().value(nsXLink, "href"); if (!imageHref.isNull()) { QString href = imageHref.toString(); svgItem = new UBGraphicsSvgItem(mDocumentPath + "/" + UBFileSystemUtils::normalizeFilePath(href)); } else { qWarning() << "cannot make sens of image href value"; return 0; } graphicsItemFromSvg(svgItem); return svgItem; } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::pdfItemToLinkedPDF(UBGraphicsPDFItem* pdfItem) { mXmlWriter.writeStartElement("foreignObject"); mXmlWriter.writeAttribute("requiredExtensions", "http://ns.adobe.com/pdf/1.3/"); QString fileName = UBPersistenceManager::objectDirectory + "/" + pdfItem->fileUuid().toString() + ".pdf"; QString path = mDocumentPath + "/" + fileName; if (!QFile::exists(path)) { QDir dir; dir.mkdir(mDocumentPath + "/" + UBPersistenceManager::objectDirectory); QFile file(path); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "cannot open file for writing embeded pdf content " << path; return; } file.write(pdfItem->fileData()); } mXmlWriter.writeAttribute(nsXLink, "href", fileName + "#page=" + QString::number(pdfItem->pageNumber())); graphicsItemToSvg(pdfItem); mXmlWriter.writeEndElement(); } UBGraphicsPDFItem* UBSvgSubsetAdaptor::UBSvgSubsetReader::pdfItemFromPDF() { UBGraphicsPDFItem* pdfItem = 0; QString href = mXmlReader.attributes().value(nsXLink, "href").toString(); QStringList parts = href.split("#page="); if (parts.count() != 2) { qWarning() << "invalid pdf href value" << href; return 0; } QString pdfPath = parts[0]; QUuid uuid(QFileInfo(pdfPath).baseName()); int pageNumber = parts[1].toInt(); pdfItem = new UBGraphicsPDFItem(PDFRenderer::rendererForUuid(uuid, mDocumentPath + "/" + UBFileSystemUtils::normalizeFilePath(pdfPath)), pageNumber); graphicsItemFromSvg(pdfItem); return pdfItem; } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::audioItemToLinkedAudio(UBGraphicsMediaItem* audioItem) { mXmlWriter.writeStartElement("audio"); graphicsItemToSvg(audioItem); if (audioItem->mediaObject()->state() == Phonon::PausedState && audioItem->mediaObject()->remainingTime() > 0) { qint64 pos = audioItem->mediaObject()->currentTime(); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "position", QString("%1").arg(pos)); } QString audioFileHref = audioItem->mediaFileUrl().toString(); audioFileHref = UBFileSystemUtils::removeLocalFilePrefix(audioFileHref); if(audioFileHref.startsWith(mDocumentPath)) audioFileHref = audioFileHref.replace(mDocumentPath + "/",""); mXmlWriter.writeAttribute(nsXLink, "href", audioFileHref); mXmlWriter.writeEndElement(); } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::videoItemToLinkedVideo(UBGraphicsMediaItem* videoItem) { /* w3c sample * *