diff --git a/electron-main.js b/electron-main.js index c63bddb8d..617ba0332 100644 --- a/electron-main.js +++ b/electron-main.js @@ -2,23 +2,20 @@ const { app, ipcMain, BrowserWindow, dialog } = require('electron'); app.commandLine.appendSwitch('ignore-gpu-blacklist'); app.allowRendererProcessReuse = false; -const FontScanner = require('font-scanner'); -const TextToSVG = require('text-to-svg'); const path = require('path'); const url = require('url'); const fs = require('fs'); const os = require('os'); -const exec = require('child_process').exec; const Store = require('electron-store'); const Sentry = require('@sentry/electron'); const BackendManager = require('./src/node/backend-manager.js'); +const fontHelper = require('./src/node/font-helper'); const MonitorManager = require('./src/node/monitor-manager.js'); const MenuManager = require('./src/node/menu-manager.js'); const UpdateManager = require('./src/node/update-manager.js'); const UglyNotify = require('./src/node/ugly-notify.js'); const events = require('./src/node/ipc-events'); -const TTC2TTF = require('./src/node/ttc2ttf.js'); const { getDeeplinkUrl, handleDeepLinkUrl } = require('./src/node/deep-link-helper'); Sentry.init({ dsn: 'https://bbd96134db9147658677dcf024ae5a83@o28957.ingest.sentry.io/5617300' }); @@ -352,12 +349,6 @@ ipcMain.on(events.CHECK_BACKEND_STATUS, () => { console.error('Recv async-status request but main window not exist'); } }); -var fontsListCache = []; -ipcMain.on(events.GET_AVAILABLE_FONTS, (event, arg) => { - const fonts = FontScanner.getAvailableFontsSync(); - fontsListCache = fonts; - event.returnValue = fonts; -}); ipcMain.on(events.SVG_URL_TO_IMG_URL, (e, data) => { const { svgUrl: url, imgWidth: width, imgHeight: height, bb, imageRatio, id, strokeWidth } = data; @@ -371,51 +362,7 @@ ipcMain.on(events.SVG_URL_TO_IMG_URL_DONE, (e, data) => { mainWindow.send(`${events.SVG_URL_TO_IMG_URL_DONE}_${id}`, imageUrl); }); -function findFontsSync(arg) { - const availableFonts = FontScanner.getAvailableFontsSync(); - const matchFamily = availableFonts.filter(font => font.family === arg.family); - const match = matchFamily.filter(font => { - result = true - Object.getOwnPropertyNames(arg).forEach(a => { - if (arg[a] !== font[a]) { - result = false; - } - }); - return result; - }); - return match; -} - -function findFontSync(arg) { - arg.style = arg.style || 'Regular'; - const availableFonts = FontScanner.getAvailableFontsSync(); - let font = availableFonts[0]; - let match = availableFonts.filter(font => font.family === arg.family); - font = match[0] || font; - if (arg.italic != null) { - match = match.filter(font => font.italic === arg.italic); - font = match[0] || font; - } - match = match.filter(font => font.style === arg.style); - font = match[0] || font; - if (arg.weight != null) { - match = match.filter(font => font.weight === arg.weight); - } - font = match[0] || font; - return font; -}; - -ipcMain.on(events.FIND_FONTS, (event, arg) => { - // FontScanner.findFontsSync({ family: 'Arial' }); - const fonts = findFontsSync(arg); - event.returnValue = fonts; -}); - -ipcMain.on(events.FIND_FONT, (event, arg) => { - // FontScanner.findFontSync({ family: 'Arial', weight: 700 }) - const font = findFontSync(arg); - event.returnValue = font; -}); +fontHelper.registerEvents(); ipcMain.on('save-dialog', function (event, title, allFiles, extensionName, extensions, filename, file) { const isMac = process.platform === 'darwin'; @@ -461,123 +408,6 @@ ipcMain.on('save-dialog', function (event, title, allFiles, extensionName, exten event.returnValue = filePath; }) -ipcMain.on(events.REQUEST_PATH_D_OF_TEXT, async (event, { text, x, y, fontFamily, fontSize, fontStyle, letterSpacing, key }) => { - const substitutedFamily = (function () { - - // Escape for Whitelists - const whiteList = ['標楷體']; - const whiteKeyWords = ['華康', 'Adobe', '文鼎']; - if (whiteList.indexOf(fontFamily) >= 0) { - return fontFamily; - } - for (let i = 0; i < whiteKeyWords.length; i++) { - let keyword = whiteKeyWords[i]; - if (fontFamily.indexOf(keyword) >= 0) { - return fontFamily; - } - } - //if only contain basic character (123abc!@#$...), don't substitute. - //because my Mac cannot substituteFont properly handing font like 'Windings' - //but we have to subsittue text if text contain both English and Chinese - const textOnlyContainBasicLatin = Array.from(text).every(char => { - return char.charCodeAt(0) <= 0x007F; - }); - if (textOnlyContainBasicLatin) { - return fontFamily; - } - - const originFont = findFontSync({ - family: fontFamily, - style: fontStyle - }); - - // array of used family which are in the text - - const originPostscriptName = originFont.postscriptName; - const fontList = Array.from(text).map(char => - FontScanner.substituteFontSync(originPostscriptName, char) - ); - let familyList = fontList.map(font => font.family); - let postscriptList = fontList.map(font => font.postscriptName) - // make unique - familyList = [...new Set(familyList)]; - postscriptList = [...new Set(postscriptList)]; - - if (familyList.length === 1) { - return familyList[0]; - } else { - // Test all found fonts if they contain all - - let fontIndex; - for (let i = 0; i < postscriptList.length; ++i) { - let allFit = true; - for (let j = 0; j < text.length; ++j) { - if (fontList[j].postscriptName === postscriptList[i]) { - continue; - } - const foundfont = FontScanner.substituteFontSync(postscriptList[i], text[j]).family; - if (familyList[i] !== foundfont) { - allFit = false; - break; - } - } - if (allFit) { - console.log(`Find ${familyList[i]} fit for all char`); - return familyList[i]; - } - } - console.log('Cannot find a font fit for all') - return (familyList.filter(family => family !== fontFamily))[0]; - } - })(); - - // Font Manager won't return PingFang Semibold if input PingFang Bold - if (substitutedFamily && substitutedFamily.indexOf('PingFang') > -1) { - switch (fontStyle) { - case 'Bold': - fontStyle = 'Semibold'; - break; - case 'Italic': - fontStyle = 'Regular'; - break; - case 'Bold Italic': - fontStyle = 'Semibold'; - break; - default: - break; - } - } - console.log('fontstyle', fontStyle); - let font = findFontSync({ - family: substitutedFamily, - style: fontStyle - }); - let fontPath = font.path; - if (fontFamily.indexOf("華康") >= 0 && (fontPath.toLowerCase().indexOf("arial") > 0 || fontPath.toLowerCase().indexOf("meiryo") > 0)) { - // This is a hotfix for 華康系列 fonts, because fontScanner does not support - for (var i in fontsListCache) { - const fontInfo = fontsListCache[i]; - if (fontInfo.family == fontFamily) { - fontPath = fontInfo.path; - font = fontInfo; - } - } - } - console.log("New Font path ", fontPath); - if (fontPath.toLowerCase().endsWith('.ttc') || fontPath.toLowerCase().endsWith('.ttcf')) { - fontPath = await TTC2TTF(fontPath, font.postscriptName); - } - const pathD = TextToSVG.loadSync(fontPath).getD(text, { - fontSize: Number(fontSize), - anchor: 'left baseline', - x: x, - y: y, - letterSpacing: letterSpacing - }); - - event.sender.send(events.RESOLVE_PATH_D_OF_TEXT + key, pathD); -}); - console.log('Running Beam Studio on ', os.arch()); app.setAsDefaultProtocolClient('beam-studio'); diff --git a/package.json b/package.json index f5033a445..a06206b87 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "sprintf-js": "^1.1.2", "squirejs": "^0.2.1", "sudo-prompt": "^9.0.0", - "text-to-svg": "^3.1.5", "typescript": "^4.2.4", "typings": "^2.1.1", "ws": "^7.1.0" diff --git a/src/implementations/fontHelper.ts b/src/implementations/fontHelper.ts index 16ed2595a..fcbacf798 100644 --- a/src/implementations/fontHelper.ts +++ b/src/implementations/fontHelper.ts @@ -1,19 +1,20 @@ import fontkit from 'fontkit'; -import fontScanner from 'font-scanner'; + +import communicator from 'implementations/communicator'; import { FontDescriptor, FontHelper } from 'interfaces/IFont'; export default { findFont(fontDescriptor: FontDescriptor): FontDescriptor { - return fontScanner.findFontSync(fontDescriptor); + return communicator.sendSync('FIND_FONT', fontDescriptor); }, findFonts(fontDescriptor: FontDescriptor): FontDescriptor[] { - return fontScanner.findFontsSync(fontDescriptor); + return communicator.sendSync('FIND_FONTS', fontDescriptor); }, getAvailableFonts() { - return fontScanner.getAvailableFontsSync(); + return communicator.sendSync('GET_AVAILABLE_FONTS'); }, substituteFont(postscriptName: string, text: string) { - return fontScanner.substituteFontSync(postscriptName, text); + return communicator.sendSync('SUBSTITUTE_FONT', postscriptName, text); }, getFontName(font: FontDescriptor): string { let fontName = font.family; diff --git a/src/node/font-helper.js b/src/node/font-helper.js new file mode 100644 index 000000000..df94b979e --- /dev/null +++ b/src/node/font-helper.js @@ -0,0 +1,73 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable @typescript-eslint/no-var-requires */ +const fontScanner = require('font-scanner'); +const { ipcMain } = require('electron'); + +const events = require('./ipc-events'); + +let fontsListCache = []; + +const findFontsSync = (arg) => { + const availableFonts = fontsListCache || fontScanner.getAvailableFontsSync(); + const matchFamily = availableFonts.filter((font) => font.family === arg.family); + const match = matchFamily.filter((font) => { + let result = true; + Object.getOwnPropertyNames(arg).forEach((a) => { + if (arg[a] !== font[a]) { + result = false; + } + }); + return result; + }); + return match; +}; + +const findFontSync = (arg) => { + if (arg.postscriptName) { + return fontScanner.findFontSync(arg); + } + + arg.style = arg.style || 'Regular'; + const availableFonts = fontsListCache || fontScanner.getAvailableFontsSync(); + let font = availableFonts[0]; + let match = availableFonts.filter((f) => f.family === arg.family); + font = match[0] || font; + if (arg.italic != null) { + match = match.filter((f) => f.italic === arg.italic); + font = match[0] || font; + } + match = match.filter((f) => f.style === arg.style); + font = match[0] || font; + if (arg.weight != null) { + match = match.filter((f) => f.weight === arg.weight); + } + font = match[0] || font; + return font; +}; + +const registerEvents = () => { + ipcMain.on(events.GET_AVAILABLE_FONTS, (event) => { + const fonts = fontScanner.getAvailableFontsSync(); + fontsListCache = fonts; + event.returnValue = fonts; + }); + + ipcMain.on(events.FIND_FONTS, (event, arg) => { + const fonts = findFontsSync(arg); + event.returnValue = fonts; + }); + + ipcMain.on(events.FIND_FONT, (event, arg) => { + const font = findFontSync(arg); + event.returnValue = font; + }); + + ipcMain.on(events.SUBSTITUTE_FONT, (event, postscriptName, text) => { + const font = fontScanner.substituteFontSync(postscriptName, text); + event.returnValue = font; + }); +}; + +module.exports = { + registerEvents, +}; diff --git a/src/node/ipc-events.js b/src/node/ipc-events.js index 19f591fb2..c090e64da 100644 --- a/src/node/ipc-events.js +++ b/src/node/ipc-events.js @@ -1,37 +1,36 @@ module.exports = { - NOTIFY_LANGUAGE : 'NOTIFY_LANGUAGE', - NOTIFY_MACHINE_STATUS : 'NOTIFY_MACHINE_STATUS', + NOTIFY_LANGUAGE: 'NOTIFY_LANGUAGE', + NOTIFY_MACHINE_STATUS: 'NOTIFY_MACHINE_STATUS', - FRONTEND_READY : 'FRONTEND_READY', - BACKEND_UP : 'BACKEND_UP', - BACKEND_DOWN : 'BACKEND_DOWN', - CHECK_BACKEND_STATUS : 'CHECK_BACKEND_STATUS', - NOTIFY_BACKEND_STATUS : 'NOTIFY_BACKEND_STATUS', + FRONTEND_READY: 'FRONTEND_READY', + BACKEND_UP: 'BACKEND_UP', + BACKEND_DOWN: 'BACKEND_DOWN', + CHECK_BACKEND_STATUS: 'CHECK_BACKEND_STATUS', + NOTIFY_BACKEND_STATUS: 'NOTIFY_BACKEND_STATUS', - MENU_CLICK : 'MENU_CLICK', - UPDATE_CUSTOM_TITLEBAR : 'UPDATE_CUSTOM_TITLEBAR', - DISABLE_MENU_ITEM : 'DISABLE_MENU_ITEM', - ENABLE_MENU_ITEM : 'ENABLE_MENU_ITEM', + MENU_CLICK: 'MENU_CLICK', + UPDATE_CUSTOM_TITLEBAR: 'UPDATE_CUSTOM_TITLEBAR', + DISABLE_MENU_ITEM: 'DISABLE_MENU_ITEM', + ENABLE_MENU_ITEM: 'ENABLE_MENU_ITEM', - POPUP_MENU : 'POPUP_MENU_ITEM', + POPUP_MENU: 'POPUP_MENU_ITEM', - UPDATE_ACCOUNT : 'UPDATE_ACCOUNT', + UPDATE_ACCOUNT: 'UPDATE_ACCOUNT', - GET_AVAILABLE_FONTS : 'GET_AVAILABLE_FONTS', - FIND_FONTS : 'FIND_FONTS', - FIND_FONT : 'FIND_FONT', - REQUEST_PATH_D_OF_TEXT : 'REQUEST_PATH_D_OF_TEXT', - RESOLVE_PATH_D_OF_TEXT : 'RESOLVE_PATH_D_OF_TEXT', + GET_AVAILABLE_FONTS: 'GET_AVAILABLE_FONTS', + FIND_FONTS: 'FIND_FONTS', + FIND_FONT: 'FIND_FONT', + SUBSTITUTE_FONT: 'SUBSTITUTE_FONT', - SET_AS_DEFAULT : 'SET_AS_DEFAULT', + SET_AS_DEFAULT: 'SET_AS_DEFAULT', - CHECK_FOR_UPDATE : 'CHECK_FOR_UPDATE', - UPDATE_AVAILABLE : 'UPDATE_AVAILABLE', - DOWNLOAD_UPDATE : 'DOWNLOAD_UPDATE', - DOWNLOAD_PROGRESS : 'DOWNLOAD_PROGRESS', - UPDATE_DOWNLOADED : 'UPDATE_DOWNLOADED', - QUIT_AND_INSTALL : 'QUIT_AND_INSTALL', + CHECK_FOR_UPDATE: 'CHECK_FOR_UPDATE', + UPDATE_AVAILABLE: 'UPDATE_AVAILABLE', + DOWNLOAD_UPDATE: 'DOWNLOAD_UPDATE', + DOWNLOAD_PROGRESS: 'DOWNLOAD_PROGRESS', + UPDATE_DOWNLOADED: 'UPDATE_DOWNLOADED', + QUIT_AND_INSTALL: 'QUIT_AND_INSTALL', - SVG_URL_TO_IMG_URL : 'SVG_URL_TO_IMG_URL', - SVG_URL_TO_IMG_URL_DONE : 'SVG_URL_TO_IMG_URL_DONE' + SVG_URL_TO_IMG_URL: 'SVG_URL_TO_IMG_URL', + SVG_URL_TO_IMG_URL_DONE: 'SVG_URL_TO_IMG_URL_DONE', }; diff --git a/src/node/ttc2ttf.js b/src/node/ttc2ttf.js deleted file mode 100644 index 30e140008..000000000 --- a/src/node/ttc2ttf.js +++ /dev/null @@ -1,100 +0,0 @@ -const fs = require('fs'); -const os = require('os'); -const path = require('path'); -const bufferpack = require('bufferpack'); -const fontkit = require('fontkit'); - -const unpack = bufferpack.unpack.bind(bufferpack); -const packTo = bufferpack.packTo.bind(bufferpack); - -const tmpFontCachePath = os.tmpdir(); -console.log('tmpFontCachePath: ', tmpFontCachePath); - -const ttc2ttf = async (_ttcFontPath, _postscriptName = '') => { - if (!isTTC(_ttcFontPath)) { - return false; - } - const hashPath = path.join(tmpFontCachePath, getHashCode(_ttcFontPath, _postscriptName) + '.ttf'); - try { - fs.accessSync(hashPath); - } catch (error) { - console.log(`catch ${error.name}: ${error.message}`); - await devideTTC(_ttcFontPath); - } - return hashPath; - -}; - -const isTTC = (_fontPath) => { - const buf = fs.readFileSync(_fontPath); - const fileType = unpack('4c', buf, 0x00).join(''); - if (fileType !== 'ttcf') { - console.log('This is not ttcf format. %s is %s', _fontPath, fileType); - return false; - } - return true; -}; - -const getHashCode = (_ttcPath, _postscriptName) => { - const str = [path.normalize(_ttcPath), _postscriptName].join('+'); - let hash = 0; - if (str.length == 0) { return hash; } - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer - } - return Math.abs(hash).toString(); -}; - -const devideTTC = async (_ttcFontPath) => { - const buf = fs.readFileSync(_ttcFontPath); - const ttf_count = unpack('!L', buf, 0x08)[0]; - const ttf_offset_array = unpack('!' + ttf_count + 'L', buf, 0x0C); - for (let i = 0; i < ttf_count; i++) { - - const table_header_offset = ttf_offset_array[i]; - - const table_count = unpack('!H', buf, table_header_offset + 0x04)[0]; - const header_length = 0x0C + table_count * 0x10; - - let table_length = 0; - for (let j = 0; j < table_count; j++) { - const length = unpack('!L', buf, table_header_offset + 0x0C + 0x0C + j * 0x10)[0]; - table_length += ceil4(length); - } - const total_length = header_length + table_length; - - const new_buf = new Buffer(total_length); - const header = unpack(header_length + 'c', buf, table_header_offset); - packTo(header_length + 'c', new_buf, 0, header); - let current_offset = header_length; - - for (let j = 0; j < table_count; j++) { - const offset = unpack('!L', buf, table_header_offset + 0x0C + 0x08 + j * 0x10)[0]; - const length = unpack('!L', buf, table_header_offset + 0x0C + 0x0C + j * 0x10)[0]; - packTo('!L', new_buf, 0x0C + 0x08 + j * 0x10, [current_offset]); - const current_table = unpack(length + 'c', buf, offset); - packTo(length + 'c', new_buf, current_offset, current_table); - - current_offset += ceil4(length); - } - - const ttfFont = fontkit.create(new_buf); - const postscriptName = ttfFont.postscriptName.toString(); - const ttfPath = path.join(tmpFontCachePath, `${getHashCode(_ttcFontPath, postscriptName)}.ttf`); - - console.log('fullName: ', ttfFont.fullName.toString()); - console.log('\tpostscriptName: ', postscriptName); - console.log('\tttfPath: ', ttfPath); - - fs.writeFileSync(ttfPath, new_buf); - } -}; - - -function ceil4(n) { - return (n + 3) & ~3; -} - -module.exports = ttc2ttf; diff --git a/yarn.lock b/yarn.lock index a136919aa..f2cf83ae5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4170,7 +4170,7 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.11.0, commander@^2.12.1, commander@^2.19.0, commander@^2.20.0: +commander@^2.12.1, commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -9216,14 +9216,6 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -opentype.js@0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-0.11.0.tgz#310f3fb85f09ca6cf22ac8cf540df67b418c3351" - integrity sha512-Z9NkAyQi/iEKQYzCSa7/VJSqVIs33wknw8Z8po+DzuRUAqivJ+hJZ94mveg3xIeKwLreJdWTMyEO7x1K13l41Q== - dependencies: - string.prototype.codepointat "^0.2.1" - tiny-inflate "^1.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -11199,11 +11191,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.codepointat@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc" - integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg== - string.prototype.matchall@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz#608f255e93e072107f5de066f81a2dfb78cf6b29" @@ -11492,14 +11479,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -text-to-svg@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/text-to-svg/-/text-to-svg-3.1.5.tgz#493913d70eae1b12240b309547d64d787ec11e57" - integrity sha512-mbeGhMz9MAFaGaZGE8n4Mh/iQV/UkVnYJXhXFrv0eWkcNTgflhpHR2a8nr2ci3NU4FekIHJYKh0N0qdc6yUpfA== - dependencies: - commander "^2.11.0" - opentype.js "0.11.0" - textextensions@^5.11.0: version "5.12.0" resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-5.12.0.tgz#b908120b5c1bd4bb9eba41423d75b176011ab68a"