diff --git a/app/package.json b/app/package.json index 50fe8bda..023e4aba 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "name": "Pencil", "productName": "Pencil", "description": "An open-source GUI prototyping tool that is available for ALL platforms.", - "version": "3.0.3", + "version": "3.0.4", "author": { "name": "Evolus", "url": "http://evolus.vn", diff --git a/app/pencil-core/common/EpgzHandler.js b/app/pencil-core/common/EpgzHandler.js index 92691188..328e4e77 100644 --- a/app/pencil-core/common/EpgzHandler.js +++ b/app/pencil-core/common/EpgzHandler.js @@ -15,7 +15,11 @@ EpgzHandler.prototype.loadDocument = function(filePath) { var thiz = this; return new Promise(function (resolve, reject) { + var wrappedRejectCalled = false; var wrappedReject = function (error) { + if (wrappedRejectCalled) return; + wrappedRejectCalled = true; + console.log(error); var recoverable = fs.existsSync(path.join(Pencil.documentHandler.tempDir.name, "content.xml")); if (!recoverable) { reject(error); @@ -38,7 +42,7 @@ EpgzHandler.prototype.loadDocument = function(filePath) { fs.createReadStream(filePath) .pipe(zlib.Gunzip()) .on("error", wrappedReject) - .pipe(tarfs.extract(Pencil.documentHandler.tempDir.name) + .pipe(tarfs.extract(Pencil.documentHandler.tempDir.name, {readable: true, writable: true}) .on("error", wrappedReject) .on("finish", function() { console.log("Successfully extracted."); diff --git a/app/pencil-core/common/FileHandler.js b/app/pencil-core/common/FileHandler.js index ca63409a..2598627d 100644 --- a/app/pencil-core/common/FileHandler.js +++ b/app/pencil-core/common/FileHandler.js @@ -25,20 +25,21 @@ FileHandler.prototype.parseDocument = function (filePath, callback) { thiz.controller.doc.properties[propNode.getAttribute("name")] = value; }); Dom.workOn("./p:Pages/p:Page", dom.documentElement, function (pageNode) { - var page = new Page(thiz.controller.doc); + if (!pageNode) return; var pageFileName = pageNode.getAttribute("href"); + if (pageFileName == null) return; + var pageFile = path.join(targetDir, pageFileName); + if (!fs.existsSync(pageFile)) { + return; + } + var page = new Page(thiz.controller.doc); page.pageFileName = pageFileName; - page.tempFilePath = path.join(targetDir, pageFileName); + page.tempFilePath = pageFile; thiz.controller.doc.pages.push(page); }); thiz.controller.doc.pages.forEach(function (page) { - var pageFile = path.join(targetDir, page.pageFileName); - if (!fs.existsSync(pageFile)) { - if (callback) callback(new Error(Util.getMessage("page.specification.is.not.found.in.the.archive"))); - return; - } - + var pageFile = page.tempFilePath; var dom = Controller.parser.parseFromString(fs.readFileSync(pageFile, "utf8"), "text/xml"); Dom.workOn("./p:Properties/p:Property", dom.documentElement, function (propNode) { var propName = propNode.getAttribute("name"); @@ -77,6 +78,8 @@ FileHandler.prototype.parseDocument = function (filePath, callback) { if (fs.existsSync(thumbPath)) { page.thumbPath = thumbPath; page.thumbCreated = new Date(); + } else { + thiz.controller.invalidateBitmapFilePath(page); } page.canvas = null; }, thiz); @@ -86,11 +89,15 @@ FileHandler.prototype.parseDocument = function (filePath, callback) { page.backgroundPage = this.controller.findPageById(page.backgroundPageId); thiz.controller.invalidateBitmapFilePath(page); } - if (page.parentPageId) { var parentPage = this.controller.findPageById(page.parentPageId); - page.parentPage = parentPage; - parentPage.children.push(page); + if (parentPage){ + page.parentPage = parentPage; + parentPage.children.push(page); + } else { + console.log("Remove parent page due to does not exist --> " + page.parentPageId); + delete page.parentPageId; + } } }, thiz); @@ -102,10 +109,15 @@ FileHandler.prototype.parseDocument = function (filePath, callback) { FontLoader.instance.setDocumentRepoDir(path.join(targetDir, "fonts")); FontLoader.instance.loadFonts(function () { thiz.controller.sayControllerStatusChanged(); + var activePage = null; if (thiz.controller.doc.properties.activeId) { - thiz.controller.activatePage(thiz.controller.findPageById(thiz.controller.doc.properties.activeId)); - } else { - if (thiz.controller.doc.pages.length > 0) thiz.controller.activatePage(thiz.controller.doc.pages[0]); + activePage = thiz.controller.findPageById(thiz.controller.doc.properties.activeId); + } + if (activePage == null && thiz.controller.doc.pages.length > 0) { + activePage = thiz.controller.doc.pages[0]; + } + if (activePage != null) { + thiz.controller.activatePage(activePage); } thiz.controller.applicationPane.onDocumentChanged(); thiz.modified = false; @@ -114,7 +126,7 @@ FileHandler.prototype.parseDocument = function (filePath, callback) { Pencil.documentHandler.preDocument = filePath; } catch (e) { // Pencil.documentHandler.newDocument() - console.error(e); + console.log(e); Dialog.alert("Unexpected error while accessing file: " + path.basename(filePath), null, function() { (oldPencilDocument != null) ? Pencil.documentHandler.loadDocument(oldPencilDocument) : function() { Pencil.controller.confirmAndclose(function () { diff --git a/app/pencil-core/common/FontLoader.js b/app/pencil-core/common/FontLoader.js index c4dee6eb..55560822 100644 --- a/app/pencil-core/common/FontLoader.js +++ b/app/pencil-core/common/FontLoader.js @@ -262,6 +262,15 @@ FontRepository.SUPPORTED_VARIANTS["italic"] = { displayName: "Regular Italic" }; +FontRepository.findVariantName = function (weight, style) { + for (var variantName in FontRepository.SUPPORTED_VARIANTS) { + var spec = FontRepository.SUPPORTED_VARIANTS[variantName]; + if (spec.weight == weight && spec.style == style) return variantName; + } + + return null; +}; + FontRepository.prototype.addFont = function (data) { if (!this.loaded) this.load(); var font = { diff --git a/app/pencil-core/definition/collectionManager.js b/app/pencil-core/definition/collectionManager.js index 03703a68..af607a84 100644 --- a/app/pencil-core/definition/collectionManager.js +++ b/app/pencil-core/definition/collectionManager.js @@ -288,23 +288,37 @@ CollectionManager.extractCollection = function(file, callback) { var targetDir = path.join(CollectionManager.getUserStencilDirectory(), fileName); console.log("extracting to", targetDir); - var extractor = unzip.Extract({ path: targetDir }); - extractor.on("close", function () { - if (callback) { - callback(err); + var admZip = require('adm-zip'); + + var zip = new admZip(filePath); + zip.extractAllToAsync(targetDir, true, function (err) { + if (err) { + error(err); + setTimeout(function() { + CollectionManager.removeCollectionDir(targetDir); + }, 10); + } else { + resolve(targetDir); } - resolve(targetDir); }); - extractor.on("error", (err) => { - console.log("extract error", err); - error(err); - setTimeout(function() { - CollectionManager.removeCollectionDir(targetDir); - }, 10); - }); - - fs.createReadStream(filePath).pipe(extractor); + // var extractor = unzip.Extract({ path: targetDir }); + // extractor.on("close", function () { + // if (callback) { + // callback(err); + // } + // resolve(targetDir); + // }); + // extractor.on("error", (err) => { + // console.log("extract error", err); + // error(err); + + // setTimeout(function() { + // CollectionManager.removeCollectionDir(targetDir); + // }, 10); + // }); + + // fs.createReadStream(filePath).pipe(extractor); }); }; CollectionManager.installCollectionFonts = function (collection) { @@ -319,6 +333,7 @@ CollectionManager.installCollectionFonts = function (collection) { if (existingFont.source == collection.id) { FontLoader.instance.userRepo.removeFont(existingFont); } else { + console.log("Skip installing: " + font.name); continue; //skip installing this font } } @@ -344,6 +359,8 @@ CollectionManager.installCollectionFonts = function (collection) { fontData[variantName + "FilePath"] = filePath; } + console.log("Fontdata to install", fontData); + FontLoader.instance.installNewFont(fontData); installedFonts.push(fontData.fontName); } diff --git a/app/pencil-core/definition/shapeDefCollectionParser.js b/app/pencil-core/definition/shapeDefCollectionParser.js index b0b546db..30725add 100644 --- a/app/pencil-core/definition/shapeDefCollectionParser.js +++ b/app/pencil-core/definition/shapeDefCollectionParser.js @@ -210,12 +210,15 @@ ShapeDefCollectionParser.prototype.loadCustomLayout = function (installDirPath) }); Dom.workOn("./p:Fonts/p:Font", shapeDefsNode, function (fontNode) { var font = { - name: fontNode.getAttribute("name"), - regular: fontNode.getAttribute("regular"), - bold: fontNode.getAttribute("bold"), - italic: fontNode.getAttribute("italic"), - boldItalic: fontNode.getAttribute("boldItalic") + name: fontNode.getAttribute("name") }; + + for (var variantName in FontRepository.SUPPORTED_VARIANTS) { + var filePath = fontNode.getAttribute(variantName); + if (filePath) font[variantName] = filePath; + } + + console.log("Font:", font); collection.fonts.push(font); }); diff --git a/app/pencil-core/privateCollection/privateCollectionManager.js b/app/pencil-core/privateCollection/privateCollectionManager.js index d238771e..18862e05 100644 --- a/app/pencil-core/privateCollection/privateCollectionManager.js +++ b/app/pencil-core/privateCollection/privateCollectionManager.js @@ -221,63 +221,122 @@ PrivateCollectionManager.installCollectionFromFile = function (file) { var targetDir = path.join(tempDir.name, fileName); console.log("targetPath:", targetDir); - var extractor = unzip.Extract({ path: targetDir }); - extractor.on("close", function () { + var admZip = require('adm-zip'); - //try loading the collection - try { - var definitionFile = path.join(targetDir, "Definition.xml"); - if (!fs.existsSync(definitionFile)) throw Util.getMessage("collection.specification.is.not.found.in.the.archive"); + var zip = new admZip(filePath); + zip.extractAllToAsync(targetDir, true, function (err) { + if (err) { + ApplicationPane._instance.unbusy(); + Dialog.error("Error installing collection."); + tempDir.removeCallback(); + } else { + //try loading the collection + try { + var definitionFile = path.join(targetDir, "Definition.xml"); + if (!fs.existsSync(definitionFile)) throw Util.getMessage("collection.specification.is.not.found.in.the.archive"); - var fileContents = fs.readFileSync(definitionFile, ShapeDefCollectionParser.CHARSET); + var fileContents = fs.readFileSync(definitionFile, ShapeDefCollectionParser.CHARSET); - var domParser = new DOMParser(); + var domParser = new DOMParser(); - var collection = null; - var dom = domParser.parseFromString(fileContents, "text/xml"); - if (dom != null) { - var dom = dom.documentElement; - var parser = new PrivateShapeDefParser(); - Dom.workOn("./p:Collection", dom, function (node) { - collection = parser.parseNode(node); - }); - }; - - if (collection && collection.id) { - //check for duplicate of name - for (i in PrivateCollectionManager.privateShapeDef.collections) { - var existingCollection = PrivateCollectionManager.privateShapeDef.collections[i]; - if (existingCollection.id == collection.id) { - throw Util.getMessage("collection.named.already.installed", collection.id); - } - } + var collection = null; + var dom = domParser.parseFromString(fileContents, "text/xml"); + if (dom != null) { + var dom = dom.documentElement; + var parser = new PrivateShapeDefParser(); + Dom.workOn("./p:Collection", dom, function (node) { + collection = parser.parseNode(node); + }); + }; - Dialog.confirm("Are you sure you want to install the unsigned collection: " + collection.displayName + "?", - "Since a collection may contain execution code that could harm your machine. It is hightly recommanded that you should only install collections from authors whom you trust.", - "Install", function () { - // CollectionManager.setCollectionCollapsed(collection, false); - PrivateCollectionManager.addShapeCollection(collection); - tempDir.removeCallback(); - }, "Cancel", function () { - tempDir.removeCallback(); + if (collection && collection.id) { + //check for duplicate of name + for (i in PrivateCollectionManager.privateShapeDef.collections) { + var existingCollection = PrivateCollectionManager.privateShapeDef.collections[i]; + if (existingCollection.id == collection.id) { + throw Util.getMessage("collection.named.already.installed", collection.id); + } } - ); - } else { - throw Util.getMessage("collection.specification.is.not.found.in.the.archive"); + + Dialog.confirm("Are you sure you want to install the unsigned collection: " + collection.displayName + "?", + "Since a collection may contain execution code that could harm your machine. It is hightly recommanded that you should only install collections from authors whom you trust.", + "Install", function () { + // CollectionManager.setCollectionCollapsed(collection, false); + PrivateCollectionManager.addShapeCollection(collection); + tempDir.removeCallback(); + }, "Cancel", function () { + tempDir.removeCallback(); + } + ); + } else { + throw Util.getMessage("collection.specification.is.not.found.in.the.archive"); + } + } catch (e) { + Dialog.error("Error installing collection."); + } finally { + ApplicationPane._instance.unbusy(); + tempDir.removeCallback(); } - } catch (e) { - Dialog.error("Error installing collection."); - } finally { - ApplicationPane._instance.unbusy(); - tempDir.removeCallback(); } - }).on("error", function (error) { - ApplicationPane._instance.unbusy(); - Dialog.error("Error installing collection."); - tempDir.removeCallback(); }); - fs.createReadStream(filePath).pipe(extractor); + // var extractor = unzip.Extract({ path: targetDir }); + // extractor.on("close", function () { + + // //try loading the collection + // try { + // var definitionFile = path.join(targetDir, "Definition.xml"); + // if (!fs.existsSync(definitionFile)) throw Util.getMessage("collection.specification.is.not.found.in.the.archive"); + + // var fileContents = fs.readFileSync(definitionFile, ShapeDefCollectionParser.CHARSET); + + // var domParser = new DOMParser(); + + // var collection = null; + // var dom = domParser.parseFromString(fileContents, "text/xml"); + // if (dom != null) { + // var dom = dom.documentElement; + // var parser = new PrivateShapeDefParser(); + // Dom.workOn("./p:Collection", dom, function (node) { + // collection = parser.parseNode(node); + // }); + // }; + + // if (collection && collection.id) { + // //check for duplicate of name + // for (i in PrivateCollectionManager.privateShapeDef.collections) { + // var existingCollection = PrivateCollectionManager.privateShapeDef.collections[i]; + // if (existingCollection.id == collection.id) { + // throw Util.getMessage("collection.named.already.installed", collection.id); + // } + // } + + // Dialog.confirm("Are you sure you want to install the unsigned collection: " + collection.displayName + "?", + // "Since a collection may contain execution code that could harm your machine. It is hightly recommanded that you should only install collections from authors whom you trust.", + // "Install", function () { + // // CollectionManager.setCollectionCollapsed(collection, false); + // PrivateCollectionManager.addShapeCollection(collection); + // tempDir.removeCallback(); + // }, "Cancel", function () { + // tempDir.removeCallback(); + // } + // ); + // } else { + // throw Util.getMessage("collection.specification.is.not.found.in.the.archive"); + // } + // } catch (e) { + // Dialog.error("Error installing collection."); + // } finally { + // ApplicationPane._instance.unbusy(); + // tempDir.removeCallback(); + // } + // }).on("error", function (error) { + // ApplicationPane._instance.unbusy(); + // Dialog.error("Error installing collection."); + // tempDir.removeCallback(); + // }); + + // fs.createReadStream(filePath).pipe(extractor); }; PrivateCollectionManager.setLastUsedCollection = function (collection) { Config.set("PrivateCollection.lastUsedCollection.id", collection.id); diff --git a/app/views/ApplicationPane.js b/app/views/ApplicationPane.js index 5f69d9e0..b2e1e7f4 100644 --- a/app/views/ApplicationPane.js +++ b/app/views/ApplicationPane.js @@ -175,7 +175,9 @@ ApplicationPane.prototype.createCanvas = function () { }; ApplicationPane.prototype.onDocumentChanged = function () { this.pageListView.currentPage = this.controller.activePage; - this.controller.activePage.canvas._sayTargetChanged(); + if (this.pageListView.currentPage != null && this.pageListView.currentPage.canvas != null) { + this.pageListView.currentPage.canvas._sayTargetChanged(); + } this.pageListView.renderPages(); this.onDocumentOptionsChanged(); diff --git a/app/views/common/PageListView.js b/app/views/common/PageListView.js index 038a7907..180c94d7 100644 --- a/app/views/common/PageListView.js +++ b/app/views/common/PageListView.js @@ -496,7 +496,7 @@ PageListView.prototype.renderPages = function() { for (var i = 0; i < thiz.childPageContainer.childNodes.length; i++) { var item = thiz.childPageContainer.childNodes[i]; - if (item._page.id == thiz.currentPage.id) { + if (thiz.currentPage != null && item._page.id == thiz.currentPage.id) { childListTo = childListFrom + item.offsetWidth; break; } @@ -506,7 +506,7 @@ PageListView.prototype.renderPages = function() { for (var i = 0; i < thiz.pageListContainer.childNodes.length; i++) { var item = thiz.pageListContainer.childNodes[i]; - if (item.__widget.page.id == thiz.currentPage.id) { + if (thiz.currentPage != null && item.__widget.page.id == thiz.currentPage.id) { thumbnailTo = thumbnailFrom + item.offsetWidth + Util.em(); break; } diff --git a/app/views/tools/StencilCollectionBuilder.js b/app/views/tools/StencilCollectionBuilder.js index de51d408..4b269416 100644 --- a/app/views/tools/StencilCollectionBuilder.js +++ b/app/views/tools/StencilCollectionBuilder.js @@ -24,7 +24,8 @@ collection.BOUND_CALCULATOR = { }, calculate: function (box, spec, h0, h1) { - if (spec.match(/^(([a-zA-Z0-9]+)\.)?([A-Z0-9]*[A-Z])([0-9\-]+)$/)) { + var matchResult = null; + if (matchResult = spec.match(/^(([a-zA-Z0-9]+)\\.)?([A-Z0-9]*[A-Z])([0-9\\-]+)$/)) { var bounding = box; var targetName = RegExp.$2; var func = RegExp.$3; @@ -41,12 +42,14 @@ collection.BOUND_CALCULATOR = { var node = Dom.getSingle(".//svg:*[@p:name='" + name + "']", shapeNode); if (node) { var bbox = node.getBBox(); - bounding = { - x: bbox.x, - y: bbox.y, - w: bbox.width, - h: bbox.height - }; + if (bbox.width > 0 && bbox.height > 0) { + bounding = { + x: bbox.x, + y: bbox.y, + w: bbox.width, + h: bbox.height + }; + } } } } @@ -506,12 +509,13 @@ StencilCollectionBuilder.prototype.getPageMargin = function () { var pageMargin = Pencil.controller.getDocumentPageMargin(); return pageMargin || 0; }; -StencilCollectionBuilder.prototype.toCollectionReadyImageData = function (imageData, name) { +StencilCollectionBuilder.prototype.toCollectionReadyImageData = function (imageData, name, isVectorHint, source) { var value = ImageData.fromString(imageData.toString()); if (value.data && value.data.match(/^ref:\/\//)) { var id = ImageData.refStringToId(value.data); if (id) { - var vector = value.data.match(/svg$/); + var vector = (typeof(isVectorHint) == "boolean") ? isVectorHint : value.data.match(/svg$/); + console.log("value.data", value.data, vector ? true: false, "hint: " + isVectorHint, source); var resourceType = vector ? StencilCollectionBuilder.SUBDIR_VECTORS : StencilCollectionBuilder.SUBDIR_BITMAPS; var filePath = Pencil.controller.refIdToFilePath(id); @@ -722,6 +726,8 @@ StencilCollectionBuilder.prototype.buildImpl = function (options, onBuildDoneCal ApplicationPane._instance.setContentVisible(false); + var embeddableFontFaces = []; + var finalize = function () { var resourceList = []; @@ -789,6 +795,58 @@ StencilCollectionBuilder.prototype.buildImpl = function (options, onBuildDoneCal })); } + //add fonts + if (options.embedReferencedFonts && embeddableFontFaces.length > 0) { + var fontsSpec = { + _name: "Fonts", + _uri: PencilNamespaces.p, + _children: [] + }; + + var fontsDirName = "fonts"; + var fontsDir = null; + + embeddableFontFaces.forEach(function (f) { + var font = FontLoader.instance.userRepo.getFont(f); + if (!font || !font.variants || font.variants.length <= 0) return; + + if (!fontsDir) { + fontsDir = path.join(dir, fontsDirName); + if (!fsExistSync(fontsDir)) { + fs.mkdirSync(fontsDir); + } + } + + var fontSpec = { + _name: "Font", + _uri: PencilNamespaces.p, + name: f + }; + + var key = f.trim().replace(/[^a-z0-9]+/i, "-"); + + var fontDir = path.join(fontsDir, key); + if (!fsExistSync(fontDir)) { + fs.mkdirSync(fontDir); + } + + font.variants.forEach(function (variant) { + var variantName = FontRepository.findVariantName(variant.weight, variant.style); + + var fileName = path.basename(variant.filePath); + var filePath = path.join(fontDir, fileName); + var fileRelativePath = fontsDirName + "/" + key + "/" + fileName; + copyFileSync(variant.filePath, filePath); + + fontSpec[variantName] = fileRelativePath; + }); + + fontsSpec._children.push(fontSpec); + }); + + shapes.appendChild(Dom.newDOMElement(fontsSpec)); + } + this.saveResultDom(dom, dir, options, function () { var showDone = function () { Pencil.controller.doc._lastUsedStencilOutputPath = options.outputPath; @@ -836,7 +894,13 @@ StencilCollectionBuilder.prototype.buildImpl = function (options, onBuildDoneCal for (var spec of globalPropertySpecs) { var prop = spec._prop; - globalPropertyMap[prop.name] = prop.type.fromString(prop.value); + var propValueObject = prop.type.fromString(prop.value); + globalPropertyMap[prop.name] = propValueObject; + + if (options.embedReferencedFonts && prop.type === Font) { + var face = propValueObject.family; + if (embeddableFontFaces.indexOf(face) < 0) embeddableFontFaces.push(face); + } } } @@ -1195,9 +1259,9 @@ StencilCollectionBuilder.prototype.buildImpl = function (options, onBuildDoneCal shape.appendChild(contentNode); shapes.appendChild(shape); - if (fs.existsSync(page.thumbPath)) { + if (fs.existsSync(page.thumbPath) && fs.statSync(page.thumbPath).size > 0) { var thumPath = path.join(thiz.iconDir, shapeId + ".png"); - fs.createReadStream(page.thumbPath).pipe(fs.createWriteStream(thumPath)); + copyFileSync(page.thumbPath, thumPath); } shape._propertyFragmentSpec = properties; @@ -1320,7 +1384,7 @@ StencilCollectionBuilder.prototype.processShortcuts = function (pages, dom, dir, var ref = ImageData.idToRefString(thiz.controller.generateCollectionResourceRefId(effectiveCollection, declaredPath)); if (ref == value.data) continue; } - value = thiz.toCollectionReadyImageData(value, spec.displayName + "-" + prop.name); + value = thiz.toCollectionReadyImageData(value, spec.displayName + "-" + prop.name, prop.name.indexOf("vector") >= 0 ? true : undefined, symbolName + "." + name); } } @@ -1376,6 +1440,10 @@ StencilCollectionBuilder.prototype.processShortcuts = function (pages, dom, dir, spec.icon = "icons/" + fileName; } + if (fs.statSync(targetPath).size <= 0) { + fs.unlinkSync(targetPath); + } + shortcutSpecs.push(spec); __callback(); }); diff --git a/appveyor.yml b/appveyor.yml index 31d85186..e5881da5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.0.3+{build} +version: 3.0.4+{build} platform: - x64 diff --git a/package.json b/package.json index 1d03370c..6986f22e 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "install-app-deps": "node ./node_modules/electron-builder/out/install-app-deps.js", "start": "./node_modules/.bin/electron ./app", "start:dev": "./node_modules/.bin/electron ./app --enable-dev --disable-gpu", + "start:mac": "./node_modules/.bin/electron ./app --enable-dev", "clean": "rimraf dist", "pack": "build", "dist": "build",