DEV Community

sunshey
sunshey

Posted on

How to Convert Word to PDF in the Browser with Vue 3

Converting Word to PDF seems simple, but the reality is more complex. Word uses flow layout while PDF uses fixed layout. Bridging this gap requires careful rendering and pagination.

Here's how to build a browser-based Word to PDF converter with Vue 3.

The challenge: Flow vs Fixed layout

Word documents flow content across pages dynamically. PDFs have fixed page sizes and positions. Converting between them means:

  1. Parsing DOCX structure
  2. Rendering to HTML/CSS
  3. Capturing as images
  4. Assembling into PDF pages

The stack

  • Vue 3 with Composition API
  • docx-parser for DOCX reading
  • html2canvas for rendering
  • pdf-lib for PDF generation
  • Vite for bundling

The core implementation

<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument } from 'pdf-lib'
import html2canvas from 'html2canvas'

const file = ref<File | null>(null)
const converting = ref(false)
const result = ref<Blob | null>(null)

async function convertWordToPdf() {
  if (!file.value) return
  converting.value = true

  // 1. Parse DOCX
  const arrayBuffer = await file.value.arrayBuffer()
  const doc = await parseDocx(arrayBuffer)

  // 2. Render to HTML
  const html = renderToHtml(doc)

  // 3. Split into pages
  const pages = splitIntoPages(html)

  // 4. Convert each page to PDF
  const pdfDoc = await PDFDocument.create()

  for (const pageHtml of pages) {
    const canvas = await renderPage(pageHtml)
    const imgData = await canvasToPdfImage(canvas)

    const page = pdfDoc.addPage([canvas.width, canvas.height])
    page.drawImage(imgData, {
      x: 0, y: 0,
      width: canvas.width,
      height: canvas.height
    })
  }

  result.value = await pdfDoc.save()
  converting.value = false
}
</script>
Enter fullscreen mode Exit fullscreen mode

Key implementation details

1. DOCX parsing

Extract text and structure from DOCX:

async function parseDocx(arrayBuffer: ArrayBuffer) {
  const zip = await JSZip.loadAsync(arrayBuffer)
  const xml = await zip.file('word/document.xml').async('text')

  const parser = new DOMParser()
  const doc = parser.parseFromString(xml, 'text/xml')

  return {
    paragraphs: doc.querySelectorAll('w:p'),
    tables: doc.querySelectorAll('w:tbl')
  }
}
Enter fullscreen mode Exit fullscreen mode

2. HTML rendering

Convert parsed content to HTML:

function renderToHtml(doc: ParsedDocx): string {
  let html = '<div class="document">'

  doc.paragraphs.forEach(p => {
    const text = p.textContent || ''
    html += `<p>${escapeHtml(text)}</p>`
  })

  html += '</div>'
  return html
}
Enter fullscreen mode Exit fullscreen mode

3. Page splitting

Detect page breaks and split content:

function splitIntoPages(html: string): string[] {
  const pages = html.split(/<div[^>]*class="[^"]*page-break[^"]*"[^>]*><\/div>/)
  return pages.filter(p => p.trim())
}
Enter fullscreen mode Exit fullscreen mode

4. Canvas rendering

Convert HTML to canvas image:

async function renderPage(html: string): Promise<HTMLCanvasElement> {
  const container = document.createElement('div')
  container.innerHTML = html
  document.body.appendChild(container)

  const canvas = await html2canvas(container, {
    scale: 2,
    useCORS: true,
    logging: false
  })

  document.body.removeChild(container)
  return canvas
}
Enter fullscreen mode Exit fullscreen mode

Limitations

Complex layouts

Tables, floating images, and multi-column layouts may not render correctly.

Solution: Simplify document structure or use professional tools.

Custom fonts

Fonts may not match between Word and PDF.

Solution: Use standard web-safe fonts.

Large files

Very large documents may cause memory issues.

Solution: Process in chunks or use Web Workers.

Summary

Building a browser-based Word to PDF converter involves:

  1. Parsing DOCX structure
  2. Rendering to HTML
  3. Splitting into pages
  4. Capturing as images
  5. Assembling into PDF

Try it at en.sotool.top/word-to-pdf.

Top comments (0)