diff --git a/lib/note/noteActions.js b/lib/note/noteActions.js
index 964f4505d6..fca032f676 100644
--- a/lib/note/noteActions.js
+++ b/lib/note/noteActions.js
@@ -2,11 +2,11 @@
 
 const fs = require('fs')
 const path = require('path')
-const markdownpdf = require('markdown-pdf')
 const shortId = require('shortid')
 const querystring = require('querystring')
 const moment = require('moment')
 const { Pandoc } = require('@hackmd/pandoc.js')
+const { convertMarkdownToPDF } = require('../utils/markdown-to-pdf')
 
 const config = require('../config')
 const logger = require('../logger')
@@ -64,7 +64,7 @@ function actionInfo (req, res, note) {
   res.send(data)
 }
 
-function actionPDF (req, res, note) {
+async function actionPDF (req, res, note) {
   const url = config.serverURL || 'http://' + req.get('host')
   const body = note.content
   const extracted = Note.extractMeta(body)
@@ -78,14 +78,17 @@ function actionPDF (req, res, note) {
   }
   const pdfPath = config.tmpPath + '/' + Date.now() + '.pdf'
   content = content.replace(/\]\(\//g, '](' + url + '/')
-  const markdownpdfOptions = {
-    highlightCssPath: highlightCssPath
-  }
-  markdownpdf(markdownpdfOptions).from.string(content).to(pdfPath, function () {
+
+  try {
+    await convertMarkdownToPDF(content, pdfPath, {
+      highlightCssPath: highlightCssPath
+    })
+
     if (!fs.existsSync(pdfPath)) {
       logger.error('PDF seems to not be generated as expected. File doesn\'t exist: ' + pdfPath)
       return errorInternalError(req, res)
     }
+
     const stream = fs.createReadStream(pdfPath)
     let filename = title
     // Be careful of special characters
@@ -95,12 +98,33 @@ function actionPDF (req, res, note) {
     res.setHeader('Cache-Control', 'private')
     res.setHeader('Content-Type', 'application/pdf; charset=UTF-8')
     res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
-    stream.on('end', () => {
-      stream.close()
-      fs.unlinkSync(pdfPath)
-    })
+
+    // Cleanup file after streaming
+    const cleanup = () => {
+      try {
+        if (fs.existsSync(pdfPath)) {
+          fs.unlinkSync(pdfPath)
+        }
+      } catch (err) {
+        logger.error('Failed to cleanup PDF file:', err)
+      }
+    }
+
+    stream.on('end', cleanup)
+    stream.on('error', cleanup)
     stream.pipe(res)
-  })
+  } catch (error) {
+    logger.error('PDF generation failed:', error)
+    // Cleanup any partially created file
+    try {
+      if (fs.existsSync(pdfPath)) {
+        fs.unlinkSync(pdfPath)
+      }
+    } catch (cleanupError) {
+      logger.error('Failed to cleanup partial PDF file:', cleanupError)
+    }
+    return errorInternalError(req, res)
+  }
 }
 
 const outputFormats = {
diff --git a/lib/utils/markdown-to-pdf.js b/lib/utils/markdown-to-pdf.js
new file mode 100644
index 0000000000..871478dafc
--- /dev/null
+++ b/lib/utils/markdown-to-pdf.js
@@ -0,0 +1,179 @@
+'use strict'
+
+const { chromium } = require('playwright-chromium')
+const markdownit = require('markdown-it')
+const path = require('path')
+const fs = require('fs')
+
+// Configure markdown-it similar to frontend
+function createMarkdownRenderer () {
+  const md = markdownit('default', {
+    html: true,
+    breaks: true,
+    linkify: true,
+    typographer: true,
+    highlight: function (str, lang) {
+      try {
+        const hljs = require('highlight.js')
+        if (lang && hljs.getLanguage(lang)) {
+          return '
' +
+                 hljs.highlight(lang, str, true).value +
+                 '
'
+        }
+      } catch (error) {
+        // Fall back to no highlighting
+      }
+      return '' + md.utils.escapeHtml(str) + '
'
+    }
+  })
+
+  // Add plugins commonly used in CodiMD
+  try {
+    md.use(require('markdown-it-abbr'))
+    md.use(require('markdown-it-footnote'))
+    md.use(require('markdown-it-deflist'))
+    md.use(require('markdown-it-mark'))
+    md.use(require('markdown-it-ins'))
+    md.use(require('markdown-it-sub'))
+    md.use(require('markdown-it-sup'))
+  } catch (error) {
+    // Some plugins may not be available, continue with basic rendering
+    console.warn('Some markdown-it plugins not available:', error.message)
+  }
+
+  return md
+}
+
+async function convertMarkdownToPDF (markdown, outputPath, options = {}) {
+  const md = createMarkdownRenderer()
+
+  // Convert markdown to HTML
+  const htmlContent = md.render(markdown)
+
+  // Read highlight.js CSS
+  const highlightCssPath = options.highlightCssPath || path.join(__dirname, '../../node_modules/highlight.js/styles/github-gist.css')
+  let highlightCss = ''
+
+  if (fs.existsSync(highlightCssPath)) {
+    highlightCss = fs.readFileSync(highlightCssPath, 'utf8')
+  }
+
+  // Create full HTML document
+  const fullHtml = `
+
+
+
+    
+    PDF Export
+    
+
+
+    ${htmlContent}
+
+`
+
+  // Launch Playwright Chromium and generate PDF
+  let browser = null
+  try {
+    browser = await chromium.launch({
+      headless: true,
+      args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
+    })
+
+    const page = await browser.newPage()
+
+    // Set a timeout for page operations
+    page.setDefaultTimeout(30000)
+
+    await page.setContent(fullHtml, {
+      waitUntil: 'networkidle'
+    })
+
+    await page.pdf({
+      path: outputPath,
+      format: 'A4',
+      margin: {
+        top: '20px',
+        right: '20px',
+        bottom: '20px',
+        left: '20px'
+      },
+      printBackground: true
+    })
+
+    return true
+  } catch (error) {
+    throw new Error(`PDF generation failed: ${error.message}`)
+  } finally {
+    if (browser) {
+      try {
+        await browser.close()
+      } catch (closeError) {
+        console.warn('Failed to close browser:', closeError.message)
+      }
+    }
+  }
+}
+
+module.exports = {
+  convertMarkdownToPDF
+}
diff --git a/package-lock.json b/package-lock.json
index 2b32910232..ff8eed5219 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,7 +44,6 @@
         "lodash": "^4.17.21",
         "lutim": "~1.0.2",
         "markdown-it": "~10.0.0",
-        "markdown-pdf": "~9.0.0",
         "method-override": "~3.0.0",
         "minimist": "^1.2.8",
         "minio": "^7.1.1",
@@ -67,6 +66,7 @@
         "passport.socketio": "~3.7.0",
         "pg": "~8.8.0",
         "pg-hstore": "~2.3.2",
+        "playwright-chromium": "^1.53.0",
         "prom-client": "^11.0.0",
         "prometheus-api-metrics": "^2.2.5",
         "randomcolor": "~0.5.4",
@@ -2620,14 +2620,6 @@
         "node": ">= 4.5.0"
       }
     },
-    "node_modules/autolinker": {
-      "version": "0.28.1",
-      "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz",
-      "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=",
-      "dependencies": {
-        "gulp-header": "^1.7.1"
-      }
-    },
     "node_modules/available-typed-arrays": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -4850,14 +4842,6 @@
         "typedarray": "^0.0.6"
       }
     },
-    "node_modules/concat-with-sourcemaps": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz",
-      "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==",
-      "dependencies": {
-        "source-map": "^0.6.1"
-      }
-    },
     "node_modules/confbox": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.1.tgz",
@@ -7180,11 +7164,6 @@
         "node": ">=0.10"
       }
     },
-    "node_modules/duplexer": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
-      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
-    },
     "node_modules/duplexer3": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@@ -8918,23 +8897,6 @@
         "extract-zip": "cli.js"
       }
     },
-    "node_modules/extract-zip/node_modules/fd-slicer": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
-      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
-      "dependencies": {
-        "pend": "~1.2.0"
-      }
-    },
-    "node_modules/extract-zip/node_modules/yauzl": {
-      "version": "2.10.0",
-      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
-      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
-      "dependencies": {
-        "buffer-crc32": "~0.2.3",
-        "fd-slicer": "~1.1.0"
-      }
-    },
     "node_modules/extsprintf": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -9011,6 +8973,15 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/fd-slicer": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+      "license": "MIT",
+      "dependencies": {
+        "pend": "~1.2.0"
+      }
+    },
     "node_modules/feature-policy": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
@@ -10445,17 +10416,6 @@
         "node": ">=4.x"
       }
     },
-    "node_modules/gulp-header": {
-      "version": "1.8.12",
-      "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz",
-      "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==",
-      "deprecated": "Removed event-stream from gulp-header",
-      "dependencies": {
-        "concat-with-sourcemaps": "*",
-        "lodash.template": "^4.4.0",
-        "through2": "^2.0.0"
-      }
-    },
     "node_modules/hachure-fill": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
@@ -10785,6 +10745,7 @@
       "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.9.tgz",
       "integrity": "sha512-M0zZvfLr5p0keDMCAhNBp03XJbKBxUx5AfyfufMdFMEP4N/Xj6dh0IqC75ys7BAzceR34NgcvXjupRVaHBPPVQ==",
       "deprecated": "Version no longer supported. Upgrade to @latest",
+      "dev": true,
       "engines": {
         "node": "*"
       }
@@ -11936,7 +11897,8 @@
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
     },
     "node_modules/isobject": {
       "version": "3.0.1",
@@ -12479,11 +12441,6 @@
         "katex": "cli.js"
       }
     },
-    "node_modules/kew": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
-      "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s="
-    },
     "node_modules/keygrip": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -12525,14 +12482,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/klaw": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
-      "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
-      "optionalDependencies": {
-        "graceful-fs": "^4.1.9"
-      }
-    },
     "node_modules/koa": {
       "version": "2.14.2",
       "resolved": "https://registry.npmjs.org/koa/-/koa-2.14.2.tgz",
@@ -12975,11 +12924,6 @@
       "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
       "dev": true
     },
-    "node_modules/lodash._reinterpolate": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
-      "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
-    },
     "node_modules/lodash.assignin": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
@@ -13062,23 +13006,6 @@
       "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
       "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0="
     },
-    "node_modules/lodash.template": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
-      "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
-      "dependencies": {
-        "lodash._reinterpolate": "^3.0.0",
-        "lodash.templatesettings": "^4.0.0"
-      }
-    },
-    "node_modules/lodash.templatesettings": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
-      "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
-      "dependencies": {
-        "lodash._reinterpolate": "^3.0.0"
-      }
-    },
     "node_modules/lodash.union": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -13329,28 +13256,6 @@
       "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
       "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
     },
-    "node_modules/markdown-pdf": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/markdown-pdf/-/markdown-pdf-9.0.0.tgz",
-      "integrity": "sha512-5Ck+LJzsxfXR4Bjmg5sLfVW9JhfkG/WEUsFUVdYN7FSHRKLEYw4r/O6esrWA8hEb+mV3RvFNUQTp+DpFKMfyYg==",
-      "dependencies": {
-        "commander": "^2.2.0",
-        "duplexer": "^0.1.1",
-        "extend": "^3.0.0",
-        "highlight.js": "^9.1.0",
-        "phantomjs-prebuilt": "^2.1.3",
-        "remarkable": "^1.7.1",
-        "stream-from-to": "^1.4.2",
-        "through2": "^2.0.0",
-        "tmp": "0.0.33"
-      },
-      "bin": {
-        "markdown-pdf": "bin/markdown-pdf"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/markdownlint": {
       "version": "0.27.0",
       "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.27.0.tgz",
@@ -15584,6 +15489,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
       "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -16154,7 +16060,8 @@
     "node_modules/pend": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
-      "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+      "license": "MIT"
     },
     "node_modules/performance-now": {
       "version": "2.1.0",
@@ -16246,65 +16153,6 @@
         "split": "^1.0.0"
       }
     },
-    "node_modules/phantomjs-prebuilt": {
-      "version": "2.1.16",
-      "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
-      "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
-      "deprecated": "this package is now deprecated",
-      "hasInstallScript": true,
-      "dependencies": {
-        "es6-promise": "^4.0.3",
-        "extract-zip": "^1.6.5",
-        "fs-extra": "^1.0.0",
-        "hasha": "^2.2.0",
-        "kew": "^0.7.0",
-        "progress": "^1.1.8",
-        "request": "^2.81.0",
-        "request-progress": "^2.0.1",
-        "which": "^1.2.10"
-      },
-      "bin": {
-        "phantomjs": "bin/phantomjs"
-      }
-    },
-    "node_modules/phantomjs-prebuilt/node_modules/fs-extra": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
-      "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
-      "dependencies": {
-        "graceful-fs": "^4.1.2",
-        "jsonfile": "^2.1.0",
-        "klaw": "^1.0.0"
-      }
-    },
-    "node_modules/phantomjs-prebuilt/node_modules/hasha": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
-      "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
-      "dependencies": {
-        "is-stream": "^1.0.1",
-        "pinkie-promise": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/phantomjs-prebuilt/node_modules/jsonfile": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
-      "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
-      "optionalDependencies": {
-        "graceful-fs": "^4.1.6"
-      }
-    },
-    "node_modules/phantomjs-prebuilt/node_modules/progress": {
-      "version": "1.1.8",
-      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
-      "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
     "node_modules/picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -16333,25 +16181,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/pinkie": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/pinkie-promise": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
-      "dependencies": {
-        "pinkie": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/pkg-conf": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz",
@@ -16483,6 +16312,32 @@
       "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz",
       "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q=="
     },
+    "node_modules/playwright-chromium": {
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.53.0.tgz",
+      "integrity": "sha512-wFIOWSc3037Ql9swJrfCQL/SfcVXbl8X944CzzQmkvh4KqCNp1QMBOGPfltu/+URTfCa5I9qc6HW1YEgY1jeNA==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "playwright-core": "1.53.0"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/playwright-core": {
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz",
+      "integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw==",
+      "bin": {
+        "playwright-core": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/points-on-curve": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
@@ -18424,21 +18279,6 @@
         "xtend": "^4.0.1"
       }
     },
-    "node_modules/remarkable": {
-      "version": "1.7.4",
-      "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz",
-      "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==",
-      "dependencies": {
-        "argparse": "^1.0.10",
-        "autolinker": "~0.28.0"
-      },
-      "bin": {
-        "remarkable": "bin/remarkable.js"
-      },
-      "engines": {
-        "node": ">= 0.10.0"
-      }
-    },
     "node_modules/remarkable-katex": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/remarkable-katex/-/remarkable-katex-1.2.1.tgz",
@@ -18669,14 +18509,6 @@
         "node": ">= 4"
       }
     },
-    "node_modules/request-progress": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
-      "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
-      "dependencies": {
-        "throttleit": "^1.0.0"
-      }
-    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -19177,11 +19009,6 @@
       "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
       "dev": true
     },
-    "node_modules/series-stream": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/series-stream/-/series-stream-1.0.1.tgz",
-      "integrity": "sha1-MRoJxcHVoJFECDLhpICkdADxAF0="
-    },
     "node_modules/serve-static": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
@@ -19622,6 +19449,7 @@
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -19916,22 +19744,6 @@
         "stream-shift": "^1.0.0"
       }
     },
-    "node_modules/stream-from-to": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/stream-from-to/-/stream-from-to-1.4.3.tgz",
-      "integrity": "sha1-snBHPrxRTnNhVyfF0vdrIplB35Q=",
-      "dependencies": {
-        "async": "^1.5.2",
-        "concat-stream": "^1.4.7",
-        "mkdirp": "^0.5.0",
-        "series-stream": "^1.0.1"
-      }
-    },
-    "node_modules/stream-from-to/node_modules/async": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
-    },
     "node_modules/stream-http": {
       "version": "2.8.3",
       "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
@@ -20609,11 +20421,6 @@
       "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
       "dev": true
     },
-    "node_modules/throttleit": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
-      "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
-    },
     "node_modules/through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -20623,6 +20430,7 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
       "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
       "dependencies": {
         "readable-stream": "~2.3.6",
         "xtend": "~4.0.1"
@@ -20665,6 +20473,7 @@
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
       "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
       "dependencies": {
         "os-tmpdir": "~1.0.2"
       },
@@ -23379,6 +23188,7 @@
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
       "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
       "dependencies": {
         "isexe": "^2.0.0"
       },
@@ -23780,6 +23590,16 @@
         "node": "6.* || 8.* || >= 10.*"
       }
     },
+    "node_modules/yauzl": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-crc32": "~0.2.3",
+        "fd-slicer": "~1.1.0"
+      }
+    },
     "node_modules/yeast": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
@@ -26012,14 +25832,6 @@
       "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
       "dev": true
     },
-    "autolinker": {
-      "version": "0.28.1",
-      "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz",
-      "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=",
-      "requires": {
-        "gulp-header": "^1.7.1"
-      }
-    },
     "available-typed-arrays": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -27970,14 +27782,6 @@
         "typedarray": "^0.0.6"
       }
     },
-    "concat-with-sourcemaps": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz",
-      "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==",
-      "requires": {
-        "source-map": "^0.6.1"
-      }
-    },
     "confbox": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.1.tgz",
@@ -29914,11 +29718,6 @@
         "nan": "^2.14.0"
       }
     },
-    "duplexer": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
-      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
-    },
     "duplexer3": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@@ -31331,25 +31130,6 @@
         "debug": "^2.6.9",
         "mkdirp": "^0.5.4",
         "yauzl": "^2.10.0"
-      },
-      "dependencies": {
-        "fd-slicer": {
-          "version": "1.1.0",
-          "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
-          "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
-          "requires": {
-            "pend": "~1.2.0"
-          }
-        },
-        "yauzl": {
-          "version": "2.10.0",
-          "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
-          "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
-          "requires": {
-            "buffer-crc32": "~0.2.3",
-            "fd-slicer": "~1.1.0"
-          }
-        }
       }
     },
     "extsprintf": {
@@ -31405,6 +31185,14 @@
         "format": "^0.2.0"
       }
     },
+    "fd-slicer": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+      "requires": {
+        "pend": "~1.2.0"
+      }
+    },
     "feature-policy": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
@@ -32538,16 +32326,6 @@
       "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
       "dev": true
     },
-    "gulp-header": {
-      "version": "1.8.12",
-      "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz",
-      "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==",
-      "requires": {
-        "concat-with-sourcemaps": "*",
-        "lodash.template": "^4.4.0",
-        "through2": "^2.0.0"
-      }
-    },
     "hachure-fill": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
@@ -32798,7 +32576,8 @@
     "highlight.js": {
       "version": "9.15.9",
       "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.9.tgz",
-      "integrity": "sha512-M0zZvfLr5p0keDMCAhNBp03XJbKBxUx5AfyfufMdFMEP4N/Xj6dh0IqC75ys7BAzceR34NgcvXjupRVaHBPPVQ=="
+      "integrity": "sha512-M0zZvfLr5p0keDMCAhNBp03XJbKBxUx5AfyfufMdFMEP4N/Xj6dh0IqC75ys7BAzceR34NgcvXjupRVaHBPPVQ==",
+      "dev": true
     },
     "hmac-drbg": {
       "version": "1.0.1",
@@ -33688,7 +33467,8 @@
     "isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
     },
     "isobject": {
       "version": "3.0.1",
@@ -34135,11 +33915,6 @@
         "commander": "^2.19.0"
       }
     },
-    "kew": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
-      "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s="
-    },
     "keygrip": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -34175,14 +33950,6 @@
       "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
       "dev": true
     },
-    "klaw": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
-      "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
-      "requires": {
-        "graceful-fs": "^4.1.9"
-      }
-    },
     "koa": {
       "version": "2.14.2",
       "resolved": "https://registry.npmjs.org/koa/-/koa-2.14.2.tgz",
@@ -34536,11 +34303,6 @@
       "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
       "dev": true
     },
-    "lodash._reinterpolate": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
-      "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
-    },
     "lodash.assignin": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
@@ -34623,23 +34385,6 @@
       "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
       "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0="
     },
-    "lodash.template": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
-      "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
-      "requires": {
-        "lodash._reinterpolate": "^3.0.0",
-        "lodash.templatesettings": "^4.0.0"
-      }
-    },
-    "lodash.templatesettings": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
-      "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
-      "requires": {
-        "lodash._reinterpolate": "^3.0.0"
-      }
-    },
     "lodash.union": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -34869,22 +34614,6 @@
       "integrity": "sha1-y5yf+RpSVawI8/09YyhuFd8KH8M=",
       "dev": true
     },
-    "markdown-pdf": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/markdown-pdf/-/markdown-pdf-9.0.0.tgz",
-      "integrity": "sha512-5Ck+LJzsxfXR4Bjmg5sLfVW9JhfkG/WEUsFUVdYN7FSHRKLEYw4r/O6esrWA8hEb+mV3RvFNUQTp+DpFKMfyYg==",
-      "requires": {
-        "commander": "^2.2.0",
-        "duplexer": "^0.1.1",
-        "extend": "^3.0.0",
-        "highlight.js": "^9.1.0",
-        "phantomjs-prebuilt": "^2.1.3",
-        "remarkable": "^1.7.1",
-        "stream-from-to": "^1.4.2",
-        "through2": "^2.0.0",
-        "tmp": "0.0.33"
-      }
-    },
     "markdownlint": {
       "version": "0.27.0",
       "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.27.0.tgz",
@@ -36697,7 +36426,8 @@
     "os-tmpdir": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true
     },
     "p-cancelable": {
       "version": "1.1.0",
@@ -37149,7 +36879,7 @@
     "pend": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
-      "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
     },
     "performance-now": {
       "version": "2.1.0",
@@ -37219,56 +36949,6 @@
         "split": "^1.0.0"
       }
     },
-    "phantomjs-prebuilt": {
-      "version": "2.1.16",
-      "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
-      "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
-      "requires": {
-        "es6-promise": "^4.0.3",
-        "extract-zip": "^1.6.5",
-        "fs-extra": "^1.0.0",
-        "hasha": "^2.2.0",
-        "kew": "^0.7.0",
-        "progress": "^1.1.8",
-        "request": "^2.81.0",
-        "request-progress": "^2.0.1",
-        "which": "^1.2.10"
-      },
-      "dependencies": {
-        "fs-extra": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
-          "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
-          "requires": {
-            "graceful-fs": "^4.1.2",
-            "jsonfile": "^2.1.0",
-            "klaw": "^1.0.0"
-          }
-        },
-        "hasha": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
-          "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
-          "requires": {
-            "is-stream": "^1.0.1",
-            "pinkie-promise": "^2.0.0"
-          }
-        },
-        "jsonfile": {
-          "version": "2.4.0",
-          "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
-          "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
-          "requires": {
-            "graceful-fs": "^4.1.6"
-          }
-        },
-        "progress": {
-          "version": "1.1.8",
-          "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
-          "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74="
-        }
-      }
-    },
     "picocolors": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -37288,19 +36968,6 @@
       "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
       "dev": true
     },
-    "pinkie": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
-    },
-    "pinkie-promise": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
-      "requires": {
-        "pinkie": "^2.0.0"
-      }
-    },
     "pkg-conf": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz",
@@ -37409,6 +37076,19 @@
       "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz",
       "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q=="
     },
+    "playwright-chromium": {
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-1.53.0.tgz",
+      "integrity": "sha512-wFIOWSc3037Ql9swJrfCQL/SfcVXbl8X944CzzQmkvh4KqCNp1QMBOGPfltu/+URTfCa5I9qc6HW1YEgY1jeNA==",
+      "requires": {
+        "playwright-core": "1.53.0"
+      }
+    },
+    "playwright-core": {
+      "version": "1.53.0",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.0.tgz",
+      "integrity": "sha512-mGLg8m0pm4+mmtB7M89Xw/GSqoNC+twivl8ITteqvAndachozYe2ZA7srU6uleV1vEdAHYqjq+SV8SNxRRFYBw=="
+    },
     "points-on-curve": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
@@ -39001,15 +38681,6 @@
         "xtend": "^4.0.1"
       }
     },
-    "remarkable": {
-      "version": "1.7.4",
-      "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz",
-      "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==",
-      "requires": {
-        "argparse": "^1.0.10",
-        "autolinker": "~0.28.0"
-      }
-    },
     "remarkable-katex": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/remarkable-katex/-/remarkable-katex-1.2.1.tgz",
@@ -39183,14 +38854,6 @@
         "uuid": "^3.3.2"
       }
     },
-    "request-progress": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
-      "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
-      "requires": {
-        "throttleit": "^1.0.0"
-      }
-    },
     "require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -39606,11 +39269,6 @@
       "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
       "dev": true
     },
-    "series-stream": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/series-stream/-/series-stream-1.0.1.tgz",
-      "integrity": "sha1-MRoJxcHVoJFECDLhpICkdADxAF0="
-    },
     "serve-static": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
@@ -40001,7 +39659,8 @@
     "source-map": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true
     },
     "source-map-resolve": {
       "version": "0.5.3",
@@ -40246,24 +39905,6 @@
         "stream-shift": "^1.0.0"
       }
     },
-    "stream-from-to": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/stream-from-to/-/stream-from-to-1.4.3.tgz",
-      "integrity": "sha1-snBHPrxRTnNhVyfF0vdrIplB35Q=",
-      "requires": {
-        "async": "^1.5.2",
-        "concat-stream": "^1.4.7",
-        "mkdirp": "^0.5.0",
-        "series-stream": "^1.0.1"
-      },
-      "dependencies": {
-        "async": {
-          "version": "1.5.2",
-          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
-          "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
-        }
-      }
-    },
     "stream-http": {
       "version": "2.8.3",
       "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
@@ -40835,11 +40476,6 @@
       "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
       "dev": true
     },
-    "throttleit": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
-      "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
-    },
     "through": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -40849,6 +40485,7 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
       "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
       "requires": {
         "readable-stream": "~2.3.6",
         "xtend": "~4.0.1"
@@ -40888,6 +40525,7 @@
       "version": "0.0.33",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
       "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
       "requires": {
         "os-tmpdir": "~1.0.2"
       }
@@ -43204,6 +42842,7 @@
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
       "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
       "requires": {
         "isexe": "^2.0.0"
       }
@@ -43531,6 +43170,15 @@
         "decamelize": "^1.2.0"
       }
     },
+    "yauzl": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+      "requires": {
+        "buffer-crc32": "~0.2.3",
+        "fd-slicer": "~1.1.0"
+      }
+    },
     "yeast": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
diff --git a/package.json b/package.json
index 4ab074735f..e6f77bbaf3 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
     "jsonlint": "find . -type f -not -ipath \"./.devcontainer/*\" -not -ipath \"./node_modules/*\" -not -ipath \"./.vscode/*\" \\( -name \"*.json\" -o -name \"*.json.*\" \\) | xargs -n 1 -I{} -- bash -c 'echo {}; jq . {} > /dev/null;'",
     "start": "sequelize db:migrate && node app.js",
     "mocha": "mocha --require intelli-espower-loader --exit ./test --recursive",
+    "mocha:cli": "mocha --require intelli-espower-loader --exit",
     "mocha:ci": "mocha --no-color -R dot --require intelli-espower-loader --exit ./test --recursive",
     "coverage": "nyc mocha --require intelli-espower-loader --exit --recursive ./test",
     "coverage:ci": "nyc mocha --no-color -R dot --require intelli-espower-loader --exit --recursive ./test",
@@ -65,7 +66,6 @@
     "lodash": "^4.17.21",
     "lutim": "~1.0.2",
     "markdown-it": "~10.0.0",
-    "markdown-pdf": "~9.0.0",
     "method-override": "~3.0.0",
     "minimist": "^1.2.8",
     "minio": "^7.1.1",
@@ -88,6 +88,7 @@
     "passport.socketio": "~3.7.0",
     "pg": "~8.8.0",
     "pg-hstore": "~2.3.2",
+    "playwright-chromium": "^1.53.0",
     "prom-client": "^11.0.0",
     "prometheus-api-metrics": "^2.2.5",
     "randomcolor": "~0.5.4",
diff --git a/test/pdf-generation.test.js b/test/pdf-generation.test.js
new file mode 100644
index 0000000000..66bfd4e594
--- /dev/null
+++ b/test/pdf-generation.test.js
@@ -0,0 +1,123 @@
+/* eslint-env node, mocha */
+'use strict'
+
+const assert = require('assert')
+const fs = require('fs')
+const path = require('path')
+const os = require('os')
+
+describe('PDF Generation', function () {
+  let convertMarkdownToPDF
+
+  before(function () {
+    // Import the PDF conversion function
+    const markdownToPdf = require('../lib/utils/markdown-to-pdf')
+    convertMarkdownToPDF = markdownToPdf.convertMarkdownToPDF
+  })
+
+  describe('Module Structure', function () {
+    it('should export convertMarkdownToPDF function', function () {
+      assert(typeof convertMarkdownToPDF === 'function', 'convertMarkdownToPDF should be a function')
+    })
+
+    it('should have required dependencies', function () {
+      const packagePath = path.join(__dirname, '../package.json')
+      const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
+
+      assert(packageJson.dependencies['playwright-chromium'], 'playwright-chromium should be in dependencies')
+      assert(!packageJson.dependencies['markdown-pdf'], 'markdown-pdf should not be in dependencies')
+    })
+  })
+
+  describe('PDF Conversion', function () {
+    const testMarkdown = `# Test Document
+
+This is a **test** document with some content.
+
+## Code Block
+\`\`\`javascript
+console.log('Hello World');
+\`\`\`
+
+- List item 1
+- List item 2
+
+> This is a blockquote
+
+| Column 1 | Column 2 |
+|----------|----------|
+| Value 1  | Value 2  |
+`
+
+    let tempDir
+    let outputPath
+
+    beforeEach(function () {
+      tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pdf-test-'))
+      outputPath = path.join(tempDir, 'test-output.pdf')
+    })
+
+    afterEach(function () {
+      // Clean up temp files
+      if (fs.existsSync(outputPath)) {
+        fs.unlinkSync(outputPath)
+      }
+      if (fs.existsSync(tempDir)) {
+        fs.rmdirSync(tempDir)
+      }
+    })
+
+    it('should convert markdown to PDF successfully', async function () {
+      this.timeout(30000) // Increase timeout for PDF generation
+
+      const result = await convertMarkdownToPDF(testMarkdown, outputPath)
+
+      assert(result === true, 'convertMarkdownToPDF should return true on success')
+      assert(fs.existsSync(outputPath), 'PDF file should be created')
+
+      const stats = fs.statSync(outputPath)
+      assert(stats.size > 0, 'PDF file should not be empty')
+    })
+
+    it('should handle empty markdown', async function () {
+      this.timeout(30000)
+
+      const result = await convertMarkdownToPDF('', outputPath)
+
+      assert(result === true, 'Should handle empty markdown')
+      assert(fs.existsSync(outputPath), 'PDF file should be created even for empty content')
+    })
+
+    it('should handle markdown with special characters', async function () {
+      this.timeout(30000)
+
+      const specialMarkdown = `# Special Characters
+
+This has **special** characters: & < > " '
+
+\`\`\`html
+Hello & Goodbye
+\`\`\`
+`
+
+      const result = await convertMarkdownToPDF(specialMarkdown, outputPath)
+
+      assert(result === true, 'Should handle special characters')
+      assert(fs.existsSync(outputPath), 'PDF file should be created')
+    })
+
+    it('should throw error for invalid output path', async function () {
+      this.timeout(30000)
+
+      const invalidPath = '/nonexistent/directory/test.pdf'
+
+      try {
+        await convertMarkdownToPDF(testMarkdown, invalidPath)
+        assert.fail('Should throw error for invalid path')
+      } catch (error) {
+        assert(error instanceof Error, 'Should throw an Error')
+        assert(error.message.includes('PDF generation failed'), 'Error should mention PDF generation failure')
+      }
+    })
+  })
+})