diff --git a/package-lock.json b/package-lock.json index 90dc2a4a..dfcb3bbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@actions/github": "^4.0.0", "@octokit/graphql": "^4.6.1", "@octokit/rest": "^18.5.3", + "@primer/css": "^16.3.0", "axios": "^0.21.1", "compression": "^1.7.4", "ejs": "^3.1.6", @@ -1811,6 +1812,28 @@ "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.7.6.tgz", "integrity": "sha1-XUGq4K61gnqbjuFcqsSnCgWBETo=" }, + "node_modules/@primer/css": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@primer/css/-/css-16.3.0.tgz", + "integrity": "sha512-nfgvRLPVGS1cdEh/Y1cak17gwEVI8HtQpbGQHvBcWrVQ78sbS3EHvdRUGpFM/Lyi/WKVZInAAwIXG9RWKukOag==", + "dependencies": { + "@primer/octicons": "13.0.0", + "@primer/primitives": "4.2.1" + } + }, + "node_modules/@primer/octicons": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-13.0.0.tgz", + "integrity": "sha512-kMNu3Ny3eocOTl2hxRC0YX0na7zJwpSIQNiZqmyqbLMKp2YwNAwk5Tan4RLGe35f30st+EbNq15dEjiMzslAcw==", + "dependencies": { + "object-assign": "^4.1.1" + } + }, + "node_modules/@primer/primitives": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-4.2.1.tgz", + "integrity": "sha512-sI0Bw/PMCZ1kfPX1MRwoNYD6RWdvU0sGk9YYD8euYASwrr4E6aNH9dutMmHTRVe/N3/coBN7QUkV79GMt0UKyQ==" + }, "node_modules/@sindresorhus/is": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", @@ -8698,7 +8721,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -13894,6 +13916,28 @@ } } }, + "@primer/css": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@primer/css/-/css-16.3.0.tgz", + "integrity": "sha512-nfgvRLPVGS1cdEh/Y1cak17gwEVI8HtQpbGQHvBcWrVQ78sbS3EHvdRUGpFM/Lyi/WKVZInAAwIXG9RWKukOag==", + "requires": { + "@primer/octicons": "13.0.0", + "@primer/primitives": "4.2.1" + } + }, + "@primer/octicons": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-13.0.0.tgz", + "integrity": "sha512-kMNu3Ny3eocOTl2hxRC0YX0na7zJwpSIQNiZqmyqbLMKp2YwNAwk5Tan4RLGe35f30st+EbNq15dEjiMzslAcw==", + "requires": { + "object-assign": "^4.1.1" + } + }, + "@primer/primitives": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-4.2.1.tgz", + "integrity": "sha512-sI0Bw/PMCZ1kfPX1MRwoNYD6RWdvU0sGk9YYD8euYASwrr4E6aNH9dutMmHTRVe/N3/coBN7QUkV79GMt0UKyQ==" + }, "@sindresorhus/is": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", @@ -19528,8 +19572,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "devOptional": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", diff --git a/package.json b/package.json index d07e37cb..d5a2fdec 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@actions/github": "^4.0.0", "@octokit/graphql": "^4.6.1", "@octokit/rest": "^18.5.3", + "@primer/css": "^16.3.0", "axios": "^0.21.1", "compression": "^1.7.4", "ejs": "^3.1.6", diff --git a/source/app/action/index.mjs b/source/app/action/index.mjs index 61eb9a8b..8d9ab644 100644 --- a/source/app/action/index.mjs +++ b/source/app/action/index.mjs @@ -213,7 +213,7 @@ info.break() info.group({metadata, name:"core", inputs:config}) info("Plugin errors", die ? "(exit with error)" : "(displayed in generated image)") - const convert = ["jpeg", "png", "json", "markdown"].includes(config["config.output"]) ? config["config.output"] : null + const convert = ["jpeg", "png", "json", "markdown", "markdown-pdf"].includes(config["config.output"]) ? config["config.output"] : null Object.assign(q, config) if (/markdown/.test(convert)) info("Markdown cache", _markdown_cache) diff --git a/source/app/metrics/index.mjs b/source/app/metrics/index.mjs index 98774ebe..966988e0 100644 --- a/source/app/metrics/index.mjs +++ b/source/app/metrics/index.mjs @@ -26,7 +26,7 @@ const pending = [] const {queries} = conf const data = {animated:true, base:{}, config:{}, errors:[], plugins:{}, computed:{}} - const imports = {plugins:Plugins, templates:Templates, metadata:conf.metadata, ...utils, ...(convert === "markdown" ? {imgb64(url, options) { + const imports = {plugins:Plugins, templates:Templates, metadata:conf.metadata, ...utils, ...(/markdown/.test(convert) ? {imgb64(url, options) { return options?.force ? utils.imgb64(...arguments) : url }} : null)} const experimental = new Set(decodeURIComponent(q["experimental.features"] ?? "").split(" ").map(x => x.trim().toLocaleLowerCase()).filter(x => x)) @@ -65,7 +65,7 @@ } //Markdown output - if (convert === "markdown") { + if (/markdown/.test(convert)) { //Retrieving template source console.debug(`metrics/compute/${login} > markdown render`) let source = image @@ -95,7 +95,10 @@ for (const delimiters of [{openDelimiter:"<", closeDelimiter:">"}, {openDelimiter:"{", closeDelimiter:"}"}]) rendered = await ejs.render(rendered, {...data, s:imports.s, f:imports.format, embed}, {views, async:true, ...delimiters}) console.debug(`metrics/compute/${login} > success`) - return {rendered, mime:"text/plain"} + //Output + if (convert === "markdown-pdf") + return imports.svg.pdf(await imports.marked(rendered), {paddings:q["config.padding"] || conf.settings.padding, style:(conf.settings.extras?.css ?? conf.settings.extras?.default ? q["extras.css"] ?? "" : "")}) + return {rendered, mime:"text/plain"} } //Rendering diff --git a/source/app/metrics/utils.mjs b/source/app/metrics/utils.mjs index a5ec1bc5..d97b504d 100644 --- a/source/app/metrics/utils.mjs +++ b/source/app/metrics/utils.mjs @@ -23,7 +23,7 @@ prism_lang() //Exports - export {fs, os, paths, url, util, processes, axios, git, opengraph, jimp, rss} + export {fs, os, paths, url, util, processes, axios, git, opengraph, jimp, rss, marked} /**Returns module __dirname */ export function __module(module) { @@ -238,6 +238,26 @@ /**SVG utils */ export const svg = { + /**Render as pdf */ + async pdf(rendered, {paddings = "", style = ""} = {}) { + //Instantiate browser if needed + if (!svg.resize.browser) { + svg.resize.browser = await puppeteer.launch() + console.debug(`metrics/svg/pdf > started ${await svg.resize.browser.version()}`) + } + //Render through browser and print pdf + console.debug("metrics/svg/pdf > loading svg") + const page = await svg.resize.browser.newPage() + page.on("console", ({_text:text}) => console.debug(`metrics/svg/pdf > puppeteer > ${text}`)) + await page.setContent(`
${rendered}
`, {waitUntil:["load", "domcontentloaded", "networkidle2"]}) + console.debug("metrics/svg/pdf > loaded svg successfully") + await page.addStyleTag({content:`main { margin: ${(Array.isArray(paddings) ? paddings : paddings.split(",")).join(" ")}; }${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "@primer/css/dist/markdown.css")).catch(_ => "")}${style}`}) + rendered = await page.pdf() + //Result + await page.close() + console.debug("metrics/svg/pdf > rendering complete") + return {rendered, mime:"application/pdf"} + }, /**Render and resize svg */ async resize(rendered, {paddings, convert}) { //Instantiate browser if needed diff --git a/source/app/web/instance.mjs b/source/app/web/instance.mjs index 6339c4a5..b549026e 100644 --- a/source/app/web/instance.mjs +++ b/source/app/web/instance.mjs @@ -261,7 +261,7 @@ graphql, rest, plugins, conf, die:q["plugins.errors.fatal"] ?? false, verify:q.verify ?? false, - convert:["jpeg", "png", "json", "markdown"].includes(q["config.output"]) ? q["config.output"] : null, + convert:["jpeg", "png", "json", "markdown", "markdown-pdf"].includes(q["config.output"]) ? q["config.output"] : null, }, {Plugins, Templates}) //Cache if ((!debug)&&(cached)) { diff --git a/source/plugins/core/metadata.yml b/source/plugins/core/metadata.yml index 7469d192..db0b463c 100644 --- a/source/plugins/core/metadata.yml +++ b/source/plugins/core/metadata.yml @@ -188,10 +188,11 @@ inputs: default: svg values: - svg - - png # Does not support animations - - jpeg # Does not support animations and transparency - - json # Outputs a JSON file instead of an image - - markdown # Outputs a Markdown file instead of an image + - png # Does not support animations + - jpeg # Does not support animations and transparency + - json # Outputs a JSON file instead of an image + - markdown # Outputs a Markdown file instead of an image + - markdown-pdf # Outputs a Markdown file as PDF instead of an image # Number of retries in case rendering fail retries: