/* * Copyright (C) 2015-2016 Département de l'Instruction Publique (DIP-SEM) * * Copyright (C) 2013 Open Education Foundation * * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour * l'Education Numérique en Afrique (GIP ENA) * * This file is part of OpenBoard. * * OpenBoard 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). * * OpenBoard 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 OpenBoard. If not, see . */ #include "UBSvgSubsetAdaptor.h" #include #include #include #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/UBGraphicsGroupContainerItemDelegate.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 "core/UBTextTools.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"; 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"; } } 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; } QByteArray UBSvgSubsetAdaptor::loadSceneAsText(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 ""; } return file.readAll(); file.close(); } return ""; } 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, UBTextTools::cleanHtmlCData(QString(pArray)).toUtf8()); return reader.loadScene(proxy); } UBSvgSubsetAdaptor::UBSvgSubsetReader::UBSvgSubsetReader(UBDocumentProxy* pProxy, const QByteArray& pXmlData) : mXmlReader(pXmlData) , mProxy(pProxy) , mDocumentPath(pProxy->persistencePath()) , mGroupHasInfo(false) { // NOOP } UBGraphicsScene* UBSvgSubsetAdaptor::UBSvgSubsetReader::loadScene(UBDocumentProxy* proxy) { qDebug() << "loadScene() : starting reading..."; QTime time; time.start(); mScene = 0; UBGraphicsWidgetItem *currentWidget = 0; bool pageDpiSpecified = true; mFileVersion = 40100; // default to 4.1.0 UBGraphicsStrokesGroup* strokesGroup = 0; UBGraphicsStroke* currentStroke = 0; while (!mXmlReader.atEnd()) { mXmlReader.readNext(); if (mXmlReader.isStartElement()) { 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()) proxy->setPageDpi(pageDpi.toInt()); else if (proxy->pageDpi() == 0) { proxy->setPageDpi((UBApplication::desktop()->physicalDpiX() + UBApplication::desktop()->physicalDpiY())/2); pageDpiSpecified = false; } bool darkBackground = false; bool crossedBackground = false; bool ruledBackground = 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 (!ubCrossedBackground.isNull()) crossedBackground = (ubCrossedBackground.toString() == xmlTrue); if (crossedBackground) { QStringRef ubGridSize = mXmlReader.attributes().value(mNamespaceUri, "grid-size"); if (!ubGridSize.isNull()) { int gridSize = ubGridSize.toInt(); UBSettings::settings()->crossSize = gridSize; mScene->setBackgroundGridSize(gridSize); } } QStringRef ubRuledBackground = mXmlReader.attributes().value(mNamespaceUri, "ruled-background"); if (!ubRuledBackground.isNull()) ruledBackground = (ubRuledBackground.toString() == xmlTrue); if (ruledBackground && !crossedBackground) { // if for some reason both are true, the background will be a grid QStringRef ubGridSize = mXmlReader.attributes().value(mNamespaceUri, "grid-size"); if (!ubGridSize.isNull()) { int gridSize = ubGridSize.toInt(); UBSettings::settings()->crossSize = gridSize; mScene->setBackgroundGridSize(gridSize); } } UBPageBackground bg; if (crossedBackground) bg = UBPageBackground::crossed; else if (ruledBackground) bg = UBPageBackground::ruled; else bg = UBPageBackground::plain; mScene->setBackground(darkBackground, bg); 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()); } QStringRef ubUuid = mXmlReader.attributes().value(mNamespaceUri, "uuid"); if (!ubUuid.isNull()) strokesGroup->setUuid(ubUuid.toString()); else strokesGroup->setUuid(QUuid::createUuid()); QString uuid_stripped = strokesGroup->uuid().toString().replace("}","").replace("{",""); if (!mStrokesList.contains(uuid_stripped)) mStrokesList.insert(uuid_stripped, strokesGroup); } 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->setData(UBGraphicsItemData::ItemLayerType, QVariant(UBItemLayerType::Graphic)); UBGraphicsStrokesGroup* group; if(!mStrokesList.contains(parentId)){ group = new UBGraphicsStrokesGroup(); mStrokesList.insert(parentId,group); group->setTransform(polygonItem->transform()); UBGraphicsItem::assignZValue(group, polygonItem->zValue()); } else group = mStrokesList.value(parentId); if (!currentStroke) currentStroke = new UBGraphicsStroke(); 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 = mXmlReader.attributes().value(mNamespaceUri, "parent").toString(); if(parentId.isEmpty() && strokesGroup) parentId = strokesGroup->uuid().toString(); if(parentId.isEmpty()) 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); group->setTransform(polygonItem->transform()); UBGraphicsItem::assignZValue(group, polygonItem->zValue()); } else group = mStrokesList.value(parentId); if (!currentStroke) currentStroke = new UBGraphicsStroke(); 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) { 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) { 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) { audioItem->setFlag(QGraphicsItem::ItemIsMovable, true); audioItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(audioItem); audioItem->show(); audioItem->play(); audioItem->pause(); } } else if (mXmlReader.name() == "video") { UBGraphicsMediaItem* videoItem = videoItemFromSvg(); if (videoItem) { videoItem->setFlag(QGraphicsItem::ItemIsMovable, true); videoItem->setFlag(QGraphicsItem::ItemIsSelectable, true); mScene->addItem(videoItem); videoItem->show(); } } else if (mXmlReader.name() == "text")//This is for backward compatibility with proto text field prior to version 4.3 { UBGraphicsTextItem* textItem = textItemFromSvg(); if (textItem) { 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) { mScene->addItem(mask); mScene->registerTool(mask); } } else if (mXmlReader.name() == "ruler") { UBGraphicsRuler *ruler = rulerFromSvg(); if (ruler) { mScene->addItem(ruler); mScene->registerTool(ruler); } } else if (mXmlReader.name() == "compass") { UBGraphicsCompass *compass = compassFromSvg(); if (compass) { mScene->addItem(compass); mScene->registerTool(compass); } } else if (mXmlReader.name() == "protractor") { UBGraphicsProtractor *protractor = protractorFromSvg(); if (protractor) { mScene->addItem(protractor); mScene->registerTool(protractor); } } else if (mXmlReader.name() == "triangle") { UBGraphicsTriangle *triangle = triangleFromSvg(); if (triangle) { mScene->addItem(triangle); mScene->registerTool(triangle); } } else if (mXmlReader.name() == "cache") { UBGraphicsCache* cache = cacheFromSvg(); if(cache) { 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) { QDesktopWidget* desktop = UBApplication::desktop(); qreal currentDpi = (desktop->physicalDpiX() + desktop->physicalDpiY()) / 2; // qDebug() << "currentDpi (" << desktop->physicalDpiX() << " + " << desktop->physicalDpiY() << ")/2 = " << currentDpi; qreal pdfScale = qreal(proxy->pageDpi())/currentDpi; // qDebug() << "pdfScale " << pdfScale; // If the PDF is in the background, it occupies the whole page; so we can simply // use that information to calculate its scale. if (isBackground) { qreal pageWidth = mScene->nominalSize().width(); qreal pageHeight = mScene->nominalSize().height(); qreal scaleX = pageWidth / pdfItem->sceneBoundingRect().width(); qreal scaleY = pageHeight / pdfItem->sceneBoundingRect().height(); pdfScale = (scaleX+scaleY)/2.; } 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) { 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) { 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 = qreal(proxy->pageDpi())/currentDpi; //textDelegate->scaleTextSize(textSizeMultiplier); } if (textItem) { textItem->setFlag(QGraphicsItem::ItemIsMovable, true); textItem->setFlag(QGraphicsItem::ItemIsSelectable, true); textItem->activateTextEditor(false); 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(); strokesGroup = NULL; currentStroke = NULL; } } } if (mXmlReader.hasError()) { qWarning() << "error parsing 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) { QString ubLocked = mXmlReader.attributes().value(UBSettings::uniboardDocumentNamespaceUri, "locked").toString(); UBGraphicsGroupContainerItem *curGroup = readGroup(); if (!ubLocked.isEmpty()) { bool isLocked = ubLocked.contains(xmlTrue); curGroup->Delegate()->setLocked(isLocked); } 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(proxy, pageIndex); } UBSvgSubsetAdaptor::UBSvgSubsetWriter::UBSvgSubsetWriter(UBDocumentProxy* proxy, UBGraphicsScene* pScene, const int pageIndex) : mScene(pScene) , mDocumentPath(proxy->persistencePath()) , mPageIndex(pageIndex) { // NOOP } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::writeSvgElement(UBDocumentProxy* proxy) { 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); bool crossedBackground = mScene->pageBackground() == UBPageBackground::crossed; bool ruledBackground = mScene->pageBackground() == UBPageBackground::ruled; mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "crossed-background", crossedBackground ? xmlTrue : xmlFalse); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "ruled-background", ruledBackground ? xmlTrue : xmlFalse); if (crossedBackground || ruledBackground) { int gridSize = mScene->backgroundGridSize(); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "grid-size", QString::number(gridSize)); } QDesktopWidget* desktop = UBApplication::desktop(); if (proxy->pageDpi() == 0) proxy->setPageDpi((desktop->physicalDpiX() + desktop->physicalDpiY()) / 2); mXmlWriter.writeAttribute("pageDpi", QString::number(proxy->pageDpi())); 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(UBDocumentProxy* proxy, int pageIndex) { Q_UNUSED(pageIndex); //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(proxy); // Get the items from the scene QList items = mScene->items(); qSort(items.begin(), items.end(), itemZIndexComp); UBGraphicsStroke *openStroke = 0; bool groupHoldsInfo = false; while (!items.empty()) { QGraphicsItem *item = items.takeFirst(); // 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(); UBGraphicsStrokesGroup * sg = polygonItem->strokesGroup(); if (colorOnDarkBackground.isValid() && colorOnLightBackground.isValid() && sg) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "z-value" , QString("%1").arg(polygonItem->strokesGroup()->zValue())); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-dark-background", colorOnDarkBackground.name()); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri , "fill-on-light-background", colorOnLightBackground.name()); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "uuid", UBStringUtils::toCanonicalUuid(sg->uuid())); qDebug() << "Attributes written"; 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; } } UBGraphicsStroke* stroke = dynamic_cast(currentStroke); if (stroke && stroke->hasPressure()) polygonItemToSvgPolygon(polygonItem, groupHoldsInfo); else if (polygonItem->isNominalLine()) polygonItemToSvgLine(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; } UBGraphicsVideoItem * videoItem = qgraphicsitem_cast (item); if (videoItem && videoItem->isVisible()) { videoItemToLinkedVideo(videoItem); continue; } UBGraphicsAudioItem * audioItem = qgraphicsitem_cast (item); if (audioItem && audioItem->isVisible()) { audioItemToLinkedAudio(audioItem); 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; } //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)); if(curElement.hasAttribute("locked")){ mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri,"locked",curElement.attribute("locked")); } 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(); 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.toString()); UBGraphicsGroupContainerItem* group = dynamic_cast(groupItem); if(group && group->Delegate()){ if(group->Delegate()->isLocked()) curGroupElement.setAttribute("locked", xmlTrue); else curGroupElement.setAttribute("locked", xmlFalse); } 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.toString()); 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.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "uuid", UBStringUtils::toCanonicalUuid(firstPolygonItem->uuid())); if (firstPolygonItem->parentItem()) { mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "parent", UBStringUtils::toCanonicalUuid(UBGraphicsItem::getOwnUuid(firstPolygonItem->strokesGroup()))); } 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 (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"); else mXmlWriter.writeAttribute("fill-rule", "winding"); 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(); graphicsItemFromSvg(polygonItem); 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 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 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); } /* Unfortunately the fill rule was never saved correctly until OpenBoard v1.4, before then, it was always saved as even-odd. So we can't load it safely here. Saving is now fixed, but any old documents would be loaded incorrectly if the code below is used. It should be activated at some point in the future though. QStringRef fillRule = mXmlReader.attributes().value("fill-rule"); if (!fillRule.isNull()) { QString value = fillRule.toString(); if (value == "evenodd") polygonItem->setFillRule(Qt::OddEvenFill); else polygonItem->setFillRule(Qt::WindingFill); } */ polygonItem->setFillRule(Qt::WindingFill); 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); graphicsItemFromSvg(polygonItem); 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 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; 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"; 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()); file.close(); } 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(UBGraphicsAudioItem *audioItem) { mXmlWriter.writeStartElement("audio"); graphicsItemToSvg(audioItem); if (audioItem->playerState() == QMediaPlayer::PausedState && (audioItem->mediaDuration() - audioItem->mediaPosition()) > 0) { qint64 pos = audioItem->mediaPosition(); mXmlWriter.writeAttribute(UBSettings::uniboardDocumentNamespaceUri, "position", QString("%1").arg(pos)); } QString audioFileHref = "audios/" + audioItem->mediaFileUrl().fileName(); mXmlWriter.writeAttribute(nsXLink, "href", audioFileHref); mXmlWriter.writeEndElement(); } void UBSvgSubsetAdaptor::UBSvgSubsetWriter::videoItemToLinkedVideo(UBGraphicsVideoItem* videoItem) { /* w3c sample * *