diff --git a/website/package.json b/website/package.json index f5a1d992ac..dc69571af6 100644 --- a/website/package.json +++ b/website/package.json @@ -36,6 +36,10 @@ "react": "^17.0.1", "react-cookie-consent": "^6.4.1", "react-dom": "^17.0.1", + "sha1": "^1.1.1", + "sharp": "^0.29.3", + "superstruct": "^0.15.3", + "text-to-svg": "^3.1.5", "url-loader": "^4.1.1" }, "browserslist": { diff --git a/website/plugins/docusaurus-plugin-open-graph-image/Readme.md b/website/plugins/docusaurus-plugin-open-graph-image/Readme.md new file mode 100644 index 0000000000..c0d5d1664a --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/Readme.md @@ -0,0 +1,86 @@ +# Docusaurus OpenGraph image generator plugin +Как это работает? +Для манипуляций с изображениями используется [sharp](https://sharp.pixelplumbing.com/) работающий через `libvips`. На этапе postBuild, когда у нас всё собрано, получаем инфу из doc плагина и на её основе генерируем изображение с необходимыми нам дополнительными слоями. Сами изображения и слои описываем в наших шаблонах. Если нам нужно применить конкретный шаблон для конкретного документа - используем правила. + +## Usage +Шаблоны помещаются в папку `open-graph-templates`. Для настройки плагина используется `config.json`. +Шаблонов может быть сколько угодно много, но при этом (Важно!) `basic` обязательный для работы плагина. + + +### Templates folder files listing. +```sh +└── website/ + └── open-graph-tempaltes/ + # required + ├── basic + | ├── font.ttf + | ├── preview.png + | └── template.json + | + └── config.json +``` + +### Templates configuration file example: +**config.json** +```json +{ + "outputDir": "assets/og", + "textWidthLimit": 1100, + "quality": 70, + "rules": [ + { + "name": "basic", + "priority": 0, + "pattern": "." + }, + { + "name": "gray", + "priority": 1, + "pattern": "^concepts*" + }, + { + "name": "gray", + "priority": 2, + "pattern": "^about*" + } + ] +} + +``` +`outputDir` - выходная директория в билде для наших картинок. +`textWidthLimit` - ограничение по длине текстовой строки, при превышении которого шрифт будет скейлиться. +`quality` - качество(компрессия JPEG Quality) картинки на выходе. +`rules` - правила(их может быть сколько угодно много), по которым будет применяться тот или иной шаблон в зависимости от пути до документа(позволяет нам для разных эндпоинтов док, создавать свои превьюшки): +- `rules.name` - имя шаблона (название папки в open-graph-templates) +- `rules.priority` - приоритет, правило с более высоким приоритетом замещает собой правила с более низким. +- `rules.pattern` - RegExp шаблон, по которому сравнивается путь документа для применения того или иного правила. + + +### Template configuration example: +**template.json** +```json +{ + "image": "preview.png", + "font": "arial.ttf", + "layout": [ + { + "type": "text", + "name": "title", + "fontSize": 80, + "fill": "white", + "stroke": "white", + "top": 400, + "left": 200 + } + ] +} +``` +`image` - путь до изображения на основе которого шаблон будет делать preview. +`font` - используемый файл шрифта. +`layout` - описывает накладываемые слои и их расположение: +- `layout.type` - задел на будущее пока только "text", в дальнейшем планируется image, postEffect и тд. +- `layout.name` - на данный момент для text типа получает поле из плагина doc, полезные варианты: title, description, formattedLastUpdatedAt остальные поля очень спорны для применения. +- `layout.fontSize` - размер шрифта для слоя с типом text. +- `layout.fill` - цвет заливки букв для слоя с типом text. +- `layout.stroke` - цвет контура букв для слоя с типом text. +- `layout.top`, `layout.left` - отступ нашего слоя от края изображения. diff --git a/website/plugins/docusaurus-plugin-open-graph-image/config.js b/website/plugins/docusaurus-plugin-open-graph-image/config.js new file mode 100644 index 0000000000..97684353e6 --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/config.js @@ -0,0 +1,37 @@ +const fs = require("fs"); +const { object, string, number, array, is } = require("superstruct"); +const { objectFromBuffer } = require("./utils"); + +function getConfig(path, encode = "utf-8") { + const config = objectFromBuffer(fs.readFileSync(`${path}\\config.json`, encode)); + if (!validateConfig(config)) { + console.error("Config validation error"); + return; + } + return config; +} + +const Rule = object({ + name: string(), + priority: number(), + pattern: string(), +}); + +const Config = object({ + outputDir: string(), + textWidthLimit: number(), + quality: number(), + rules: array(Rule), +}); + +function validateConfig(config) { + if (is(config, Config)) { + return config.rules.reduce((validationResult, rule) => { + if (!is(rule, Rule)) return false; + return validationResult; + }, true); + } + return false; +} + +module.exports = { getConfig }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/font.js b/website/plugins/docusaurus-plugin-open-graph-image/font.js new file mode 100644 index 0000000000..20130cdca7 --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/font.js @@ -0,0 +1,34 @@ +const textToSVG = require("text-to-svg"); + +function createFontsMapFromTemplates(templates) { + const fonts = new Map(); + templates.forEach((template) => { + if (!fonts.has(template.params.font)) { + fonts.set( + template.params.font, + textToSVG.loadSync(`${template.path}\\${template.name}\\${template.params.font}`), + ); + } + }); + return fonts; +} + +function createSVGText( + font, + text, + { fontSize = 72, fill = "white", stroke = "white" }, + widthLimit = 1000, +) { + const attributes = { fill, stroke }; + const options = { fontSize, anchor: "top", attributes }; + + /* If font width more than widthLimit => scale font width to ~90% of widthLimit */ + if (widthLimit) { + const { width } = font.getMetrics(text, options); + if (width > widthLimit) + options.fontSize = Math.trunc((fontSize * 0.9) / (width / widthLimit)); + } + + return font.getSVG(text, options); +} +module.exports = { createSVGText, createFontsMapFromTemplates }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/image.js b/website/plugins/docusaurus-plugin-open-graph-image/image.js new file mode 100644 index 0000000000..4a020cdffe --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/image.js @@ -0,0 +1,29 @@ +const sharp = require("sharp"); + +function getTemplateImageId(template) { + return `${template.name}_${template.params.image}`; +} + +function createImagePipeline(file) { + // TODO: Apply effects, compression and etc. + // TODO: File validation? + return sharp(file); +} + +function createImageFromTemplate({ path, name, params }) { + return createImagePipeline(`${path}\\${name}\\${params.image}`); +} + +function createImagesMapFromTemplates(templates) { + const images = new Map(); + templates.forEach((template) => { + const imageId = getTemplateImageId(template); + + if (!images.has(imageId)) { + images.set(imageId, createImageFromTemplate(template)); + } + }); + return images; +} + +module.exports = { createImagesMapFromTemplates, getTemplateImageId }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/index.js b/website/plugins/docusaurus-plugin-open-graph-image/index.js new file mode 100644 index 0000000000..392bf75364 --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/index.js @@ -0,0 +1,100 @@ +const fs = require("fs"); +const sha1 = require("sha1"); +const { getTemplates } = require("./template"); +const { createLayoutLayers } = require("./layout"); +const { createFontsMapFromTemplates } = require("./font"); +const { createImagesMapFromTemplates, getTemplateImageId } = require("./image"); +const { getConfig } = require("./config"); +const { getTemplateNameByRules } = require("./rules"); + +module.exports = function ({ templatesDir }) { + const initData = bootstrap(templatesDir); + if (!initData) { + console.error("OpenGraph plugin exit with error."); + return; + } + + const { config } = initData; + + return { + name: "docusaurus-plugin-open-graph-image", + async postBuild({ plugins, outDir, i18n }) { + const docsPlugin = plugins.find( + (plugin) => plugin.name === "docusaurus-plugin-content-docs", + ); + + if (!docsPlugin) throw new Error("Docusaurus Doc plugin not found."); + + const previewOutputDir = `${outDir}\\${config.outputDir}`; + fs.mkdir(previewOutputDir, { recursive: true }, (error) => { + if (error) throw error; + }); + + const docsContent = docsPlugin.content; + const docsVersions = docsContent.loadedVersions; + docsVersions.forEach((version) => { + const { docs } = version; + + docs.forEach((document) => { + generateImageFromDoc(initData, document, i18n.currentLocale, previewOutputDir); + }); + }); + }, + }; +}; + +function bootstrap(templatesDir) { + const isProd = process.env.NODE_ENV === "production"; + if (!isProd) return; + + if (!templatesDir) { + console.error("Wrong templatesDir option."); + return; + } + + const templates = getTemplates(templatesDir); + if (!templates) return; + + const config = getConfig(templatesDir); + if (!config) return; + + // TODO: File not found exception? + const fonts = createFontsMapFromTemplates(templates); + const images = createImagesMapFromTemplates(templates); + + return { templates, config, fonts, images }; +} + +async function generateImageFromDoc(initData, doc, locale, outputDir) { + const { templates, config, images, fonts } = initData; + const { id, title } = doc; + + const hashFileName = sha1(id + locale); + + const templateName = getTemplateNameByRules(id, config.rules); + + const template = templates.find((template) => template.name === templateName); + + const previewImage = await images.get(getTemplateImageId(template)).clone(); + + const previewFont = fonts.get(template.params.font); + + const textLayers = createLayoutLayers( + doc, + template.params.layout, + previewFont, + config.textWidthLimit, + ); + + try { + await previewImage.composite(textLayers); + await previewImage + .jpeg({ + quality: config.quality, + chromaSubsampling: "4:4:4", + }) + .toFile(`${outputDir}\\${hashFileName}.jpg`); + } catch (error) { + console.error(error, id, title, hashFileName); + } +} diff --git a/website/plugins/docusaurus-plugin-open-graph-image/layout.js b/website/plugins/docusaurus-plugin-open-graph-image/layout.js new file mode 100644 index 0000000000..6bcb384672 --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/layout.js @@ -0,0 +1,27 @@ +const { createSVGText } = require("./font"); + +function createLayoutLayers(doc, layout, previewFont, textWidthLimit) { + /* Check for all layers names exist in doc fields */ + if (layout.some((layer) => !doc[layer.name])) { + console.error(`Wrong template config.`); + return; + } + + return layout.map((layer) => { + const layoutOptions = { + fontSize: layer.fontSize, + fill: layer.fill, + stroke: layer.stroke, + }; + + return { + input: Buffer.from( + createSVGText(previewFont, doc[layer.name], layoutOptions, textWidthLimit), + ), + top: layer.top, + left: layer.left, + }; + }); +} + +module.exports = { createLayoutLayers }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/rules.js b/website/plugins/docusaurus-plugin-open-graph-image/rules.js new file mode 100644 index 0000000000..896fb61913 --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/rules.js @@ -0,0 +1,7 @@ +function getTemplateNameByRules(path, rules) { + const filteredRules = rules.filter((rule) => new RegExp(rule.pattern).test(path)); + const sortedRules = filteredRules.sort((a, b) => b.priority - a.priority); + return sortedRules[0]?.name || "basic"; +} + +module.exports = { getTemplateNameByRules }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/template.js b/website/plugins/docusaurus-plugin-open-graph-image/template.js new file mode 100644 index 0000000000..2d6ec095f9 --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/template.js @@ -0,0 +1,59 @@ +const fs = require("fs"); +const { object, string, number, array, is } = require("superstruct"); +const { objectFromBuffer } = require("./utils"); + +const dirIgnore = ["config.json"]; + +function getTemplates(templatesDir, encode = "utf8") { + const templatesDirNames = fs + .readdirSync(templatesDir) + .filter((fileName) => !dirIgnore.includes(fileName)); + + // TODO: check file exist + const templates = templatesDirNames.map((templateName) => ({ + name: templateName, + path: templatesDir, + params: objectFromBuffer( + fs.readFileSync(`${templatesDir}\\${templateName}\\template.json`, encode), + ), + })); + + if (!templates.some(validateTemplate)) { + console.error("Templates validation error."); + return; + } + + return templates; +} + +// TODO: May be with postEffects, images and etc? (optional fontSize, fill and etc) +const Layout = object({ + type: string(), + name: string(), + fontSize: number(), + fill: string(), + stroke: string(), + top: number(), + left: number(), +}); + +const Template = object({ + image: string(), + font: string(), + layout: array(Layout), +}); + +function validateTemplate({ params }) { + if (is(params, Template)) { + if (params.layout.length === 0) return false; + + return params.layout.reduce((validationResult, layout) => { + if (!is(layout, Layout)) return false; + return validationResult; + }, true); + } + + return false; +} + +module.exports = { getTemplates }; diff --git a/website/plugins/docusaurus-plugin-open-graph-image/utils.js b/website/plugins/docusaurus-plugin-open-graph-image/utils.js new file mode 100644 index 0000000000..b824a4e88c --- /dev/null +++ b/website/plugins/docusaurus-plugin-open-graph-image/utils.js @@ -0,0 +1,5 @@ +function objectFromBuffer(buffer) { + return JSON.parse(buffer.toString()); +} + +module.exports = { objectFromBuffer }; diff --git a/website/yarn.lock b/website/yarn.lock index fd55b93cc4..69c7169d54 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -4274,6 +4274,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +"charenc@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + cheerio@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -4553,7 +4558,7 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== -commander@^2.20.0: +commander@^2.11.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== @@ -4779,6 +4784,11 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +"crypt@>= 0.0.1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -5043,6 +5053,13 @@ decompress-response@^4.2.0: dependencies: mimic-response "^2.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -8221,6 +8238,11 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -8421,6 +8443,13 @@ node-abi@^2.21.0: dependencies: semver "^5.4.1" +node-abi@^3.3.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.5.0.tgz#26e8b7b251c3260a5ac5ba5aef3b4345a0229248" + integrity sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw== + dependencies: + semver "^7.3.5" + node-addon-api@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" @@ -8735,6 +8764,14 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +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" + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -9575,6 +9612,25 @@ prebuild-install@^6.1.4: tar-fs "^2.0.0" tunnel-agent "^0.6.0" +prebuild-install@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" + integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -10756,6 +10812,14 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +sha1@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848" + integrity sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg= + dependencies: + charenc ">= 0.0.1" + crypt ">= 0.0.1" + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -10782,6 +10846,20 @@ sharp@^0.29.1: tar-fs "^2.1.1" tunnel-agent "^0.6.0" +sharp@^0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== + dependencies: + color "^4.0.1" + detect-libc "^1.0.3" + node-addon-api "^4.2.0" + prebuild-install "^7.0.0" + semver "^7.3.5" + simple-get "^4.0.0" + tar-fs "^2.1.1" + tunnel-agent "^0.6.0" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -10848,6 +10926,15 @@ simple-get@^3.0.3, simple-get@^3.1.0: once "^1.3.1" simple-concat "^1.0.0" +simple-get@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" + integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -11156,6 +11243,11 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2 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.5" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz#59370644e1db7e4c0c045277690cf7b01203c4da" @@ -11383,6 +11475,11 @@ sugarss@^2.0.0: dependencies: postcss "^7.0.2" +superstruct@^0.15.3: + version "0.15.3" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.3.tgz#07edfc715259ebfe3b4b2e4cb53e8e45b51674a4" + integrity sha512-wilec1Rg3FtKuRjRyCt70g5W29YUEuaLnybdVQUI+VQ7m0bw8k7TzrRv5iYmo6IpjLVrwxP5t3RgjAVqhYh4Fg== + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -11553,6 +11650,14 @@ text-table@0.2.0, 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" + through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -11578,6 +11683,11 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== +tiny-inflate@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + tiny-invariant@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"