pdf、markdown、docx文件预览

夏飞鹏
2023-12-01

记录一下实现 .md \ .pdf \ .docx文件的预览。

markdown 文件预览

安装 markdown-it

$> npm install --save markdown-it

解析.md文件转换为 html,然后添加到 dom 节点中。

import MarkdownIt from "markdown-it";

function parseFile(fileUrl) {
  // 创建实例
  const md = new MarkdownIt({
    html: true, // Enable HTML tags in source
    xhtmlOut: false, // Use '/' to close single tags (<br />).
    // This is only for full CommonMark compatibility.
    breaks: false, // Convert '\n' in paragraphs into <br>
    langPrefix: "language-", // CSS language prefix for fenced blocks. Can be
    // useful for external highlighters.
    linkify: false, // Autoconvert URL-like text to links

    // Enable some language-neutral replacement + quotes beautification
    // For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js
    typographer: false,
    // Double + single quotes replacement pairs, when typographer enabled,
    // and smartquotes on. Could be either a String or an Array.
    //
    // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
    // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
    quotes: "“”‘’",
  });

  // 解析文件
  const html = md.render(fileUrl);

  return html;
}

拿到转换后的 html,插入到 dom 节点中,这里给一个 vue 的示例

<template>
  <div class="md-preview">
    <div class="content">
      <div className="markdown-content" v-html="content" />
    </div>
  </div>
</template>
<script>
import { onMounted, ref, computed } from "vue";
// md
import ReadME from "@/README.md?raw";

// 解析后的内容变量
const content = ref("");

onMounted(() => {
  //
  content = parseFile(ReadME);
});
</script>

highlight.js 代码高亮

解析出来的内容没有任何样式,通常我们的代码使用高亮,方便阅读。

安装

$> npm install highlight.js

通过属性highlight定义需要处理的代码片段

const md = new MarkdownIt({
  // ...
  highlight: (str, lang) => {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return (
          '<pre class="hljs"><code>' +
          hljs.highlight(str, {
            language: lang,
            ignoreIllegals: true,
          }).value +
          "</code></pre>"
        );
      } catch (__) {}
    }

    return (
      '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + "</code></pre>"
    );
  },
});

此时只加了基本的样式,可以通过导入不同主题的.css文件,来应用样式。主题文件目录highlight.js/styles/**

在顶部导入样式文件,

import "highlight.js/styles/atom-one-dark.css";

markdown-it-anchor 增加导航标识

通过标题导航到指定的文字,通常# 会被转换为h . 通过配置增加永久性导航设置

安装

@sindresorhus/slugify 用来处理获取到的文本内容,有时候我们的标题中会含有一些特殊字符。能很好的处理

$> npm install markdown-it-anchor @sindresorhus/slugify --save

MarkdownIt 通过 use 方法安装插件,

import MarkdownItAnchor from "markdown-it-anchor";
import slugify from "@sindresorhus/slugify";

const md = new MarkdownIt({
  // ...
});
md.use(MarkdownItAnchor, {
  level: 1,
  slugify: (s) => {
    return slugify(s);
  },
  getTokensText(tokens) {
    return tokens
      .filter((t) => ["text", "code_inline"].includes(t.type))
      .map((t) => t.content)
      .join("");
  },
});

可以看到转换后的 html 内容中,所有的标题都包含有 id 属性,通过permalink 生成永久性导航链接,

md.use(MarkdownItAnchor, {
  // ...
  permalink: MarkdownItAnchor.permalink.headerLink(),
});

可以在生成的 html 中看到h标题中有<a class="header-anchor" href="#title"></a>

node-html-parser 生成简单 dom 结构,支持 query 节点

在转换后的 html 中已经包含了可导航的标题信息;再从中提取出标题数据,即可自定义渲染导航菜单。

$> npm install node-html-parser --save

以 vue 代码示例

import { parse } from "node-html-parser";

// 通过计算属性,等待md文件解析完后生成对应的导航数据
const navData = computed(() => {
  const root = parse(content.value);
  const match = [];
  for (const h of root.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
    const slug = h.getAttribute("id") || slugify(h.textContent);
    h.setAttribute("id", slug);
    // h.innerHTML = `<a href="#${slug}>${h.innerHTML}</a>`
    match.push(`<a href="#${slug}" class="${h.rawTagName}">${h.innerHTML}</a>`);
  }
  return match.join("");
});

pdf 文件预览

加载 pdf 文件,进行预览。

安装

$> npm install pdfjs-dist --save

vue3 中没有 require,所以安装其他依赖

# 支持es module
$> npm install @bundled-es-modules/pdfjs-dist

通过读取.pdf文件内容,绘制到 canvas 中,在家动态生成的 canvas 节点添加到指定区域。

import pdfjs from "@bundled-es-modules/pdfjs-dist/build/pdf";
// import viewer from '@bundled-es-modules/pdfjs-dist/web/pdf_viewer';
import worker from "@bundled-es-modules/pdfjs-dist/build/pdf.worker.js?url";

pdfjs.GlobalWorkerOptions.workerSrc = worker;

function parsePdf(fileUrl) {
  const self = this;
  // 创建实例,加载文件
  const loadingTask = pdfjs.getDocument(fileUrl);

  // container box ref 引用
  const container = this.$refs.pdf;
  // 通过promise读取
  loadingTask.promise.then(function (doc) {
    const numPages = doc.numPages;

    // meta 数据信息
    doc.getMetadata().then(function (data) {
      // 元数据信息
    });
    const loadPage = function (pageNum) {
      doc.getPage(pageNum).then(function (page) {
        const scale = 1;
        const viewport = page.getViewport({ scale });
        const outputScale = window.devicePixelRatio || 1;

        const canvas = document.createElement("canvas");
        const context = canvas.getContext("2d");
        // contianer 为预览所在的dom区域
        container.appendChild(canvas);

        canvas.width = Math.floor(viewport.width * outputScale);
        canvas.height = Math.floor(viewport.height * outputScale);
        canvas.style.width = Math.floor(viewport.width) + "px";
        canvas.style.height = Math.floor(viewport.height) + "px";

        const transform =
          outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;

        const renderContext = {
          canvasContext: context,
          transform,
          viewport,
        };
        page.render(renderContext);
      });
    };
    for (let i = 1; i <= numPages; i++) {
      // 加载读取每一页数据
      loadPage(i);
    }
  });
}

那对应的 vue 示例,

<template>
  <div class="pdf-preview">
    <div ref="pdf" />
  </div>
</template>

docx文件预览

加载 docx 文件数据流转换为 html,然后插入 dom 节点中

安装

$> npm install mammoth --save

浏览器使用引用的是mammoth/mammoth.browser,需要的数据格式必须是ArrayBuffer

示例是加载本地项目总的资源,通过网络请求加在资源为数据流blob,然后通过FileReader转换为 ArrayBuffer

此处以 vue 示例

<template>
  <div class="docx-preview">
    <div ref="docx" v-html="content" />
  </div>
</template>
<script setup>
import mammoth from 'mammoth/mammoth.browser'
import { ref } from 'vue'

const conent = ref('')

function parseDocs(fileUrl){
    let $this  = this
    // raadfile
    const blob = await this.$http.request({
        type: 'get',
        url: fileUrl,
        responseType: 'blob',
    })
    // blob 转 arrayBuffer
    const fileReader = new FileReader()

    fileReader.onload = function (res) {
    // 解析转换
    mammoth
        .convertToHtml({ arrayBuffer: fileReader.result })
        .then(function (result) {
            const html = result.value // The generated HTML

            $this.content = html
        })
        .done()
    }
    fileReader.readAsArrayBuffer(blob)
}
<script>

.docx只解析为 html,没有任何样式,需要自己去设置样式。所以说如果就只是预览,可以让后端转为 pdf 后前端在展示预览。

具体实例代码可参考示例项目:

vite+vue3 模板代码仓库(vite-vue3)[https://gitee.com/ngd_b/vue3-vite]

 类似资料: