Feat:重构pdf的导出逻辑,导出的pdf尺寸不再是固定的a4,而是思维导图的尺寸,同时取出分页导出的配置
This commit is contained in:
parent
5bea2606f6
commit
29c5075fa5
@ -332,12 +332,6 @@ export const ERROR_TYPES = {
|
|||||||
EXPORT_LOAD_IMAGE_ERROR: 'export_load_image_error'
|
EXPORT_LOAD_IMAGE_ERROR: 'export_load_image_error'
|
||||||
}
|
}
|
||||||
|
|
||||||
// a4纸的宽高
|
|
||||||
export const a4Size = {
|
|
||||||
width: 592.28,
|
|
||||||
height: 841.89
|
|
||||||
}
|
|
||||||
|
|
||||||
// css
|
// css
|
||||||
export const cssContent = `
|
export const cssContent = `
|
||||||
/* 鼠标hover和激活时渲染的矩形 */
|
/* 鼠标hover和激活时渲染的矩形 */
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
import { SVG } from '@svgdotjs/svg.js'
|
import { SVG } from '@svgdotjs/svg.js'
|
||||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||||
import { transformToMarkdown } from '../parse/toMarkdown'
|
import { transformToMarkdown } from '../parse/toMarkdown'
|
||||||
import { a4Size, ERROR_TYPES } from '../constants/constant'
|
import { ERROR_TYPES } from '../constants/constant'
|
||||||
|
|
||||||
// 导出插件
|
// 导出插件
|
||||||
class Export {
|
class Export {
|
||||||
@ -92,14 +92,7 @@ class Export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// svg转png
|
// svg转png
|
||||||
svgToPng(
|
svgToPng(svgSrc, transparent, ignoreDpr = false) {
|
||||||
svgSrc,
|
|
||||||
transparent,
|
|
||||||
checkRotate = () => {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
compress
|
|
||||||
) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
// 跨域图片需要添加这个属性,否则画布被污染了无法导出图片
|
||||||
@ -107,42 +100,20 @@ class Export {
|
|||||||
img.onload = async () => {
|
img.onload = async () => {
|
||||||
try {
|
try {
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
const dpr = Math.max(
|
const dpr = ignoreDpr
|
||||||
window.devicePixelRatio,
|
? 1
|
||||||
this.mindMap.opt.minExportImgCanvasScale
|
: Math.max(
|
||||||
)
|
window.devicePixelRatio,
|
||||||
|
this.mindMap.opt.minExportImgCanvasScale
|
||||||
|
)
|
||||||
let imgWidth = img.width
|
let imgWidth = img.width
|
||||||
let imgHeight = img.height
|
let imgHeight = img.height
|
||||||
// 压缩图片
|
canvas.width = imgWidth * dpr
|
||||||
if (compress) {
|
canvas.height = imgHeight * dpr
|
||||||
const compressedSize = resizeImgSize(
|
canvas.style.width = imgWidth + 'px'
|
||||||
imgWidth,
|
canvas.style.height = imgHeight + 'px'
|
||||||
imgHeight,
|
|
||||||
compress.width,
|
|
||||||
compress.height
|
|
||||||
)
|
|
||||||
imgWidth = compressedSize[0]
|
|
||||||
imgHeight = compressedSize[1]
|
|
||||||
}
|
|
||||||
// 如果宽比高长,那么旋转90度
|
|
||||||
const needRotate = checkRotate(imgWidth, imgHeight)
|
|
||||||
if (needRotate) {
|
|
||||||
canvas.width = imgHeight * dpr
|
|
||||||
canvas.height = imgWidth * dpr
|
|
||||||
canvas.style.width = imgHeight + 'px'
|
|
||||||
canvas.style.height = imgWidth + 'px'
|
|
||||||
} else {
|
|
||||||
canvas.width = imgWidth * dpr
|
|
||||||
canvas.height = imgHeight * dpr
|
|
||||||
canvas.style.width = imgWidth + 'px'
|
|
||||||
canvas.style.height = imgHeight + 'px'
|
|
||||||
}
|
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
ctx.scale(dpr, dpr)
|
ctx.scale(dpr, dpr)
|
||||||
if (needRotate) {
|
|
||||||
ctx.rotate(0.5 * Math.PI)
|
|
||||||
ctx.translate(0, -imgHeight)
|
|
||||||
}
|
|
||||||
// 绘制背景
|
// 绘制背景
|
||||||
if (!transparent) {
|
if (!transparent) {
|
||||||
await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight)
|
await this.drawBackgroundToCanvas(ctx, imgWidth, imgHeight)
|
||||||
@ -232,31 +203,22 @@ class Export {
|
|||||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||||
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
* 方法2.把svg的图片提取出来再挨个绘制到canvas里,最后一起转换
|
||||||
*/
|
*/
|
||||||
async png(name, transparent = false, checkRotate, compress) {
|
async png(name, transparent = false) {
|
||||||
const { str } = await this.getSvgData()
|
const { str } = await this.getSvgData()
|
||||||
const svgUrl = await this.fixSvgStrAndToBlob(str)
|
const svgUrl = await this.fixSvgStrAndToBlob(str)
|
||||||
// 绘制到canvas上
|
const res = await this.svgToPng(svgUrl, transparent)
|
||||||
const res = await this.svgToPng(svgUrl, transparent, checkRotate, compress)
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出为pdf
|
// 导出为pdf
|
||||||
async pdf(name, useMultiPageExport, maxImageWidth) {
|
async pdf(name, transparent = false) {
|
||||||
if (!this.mindMap.doExportPDF) {
|
if (!this.mindMap.doExportPDF) {
|
||||||
throw new Error('请注册ExportPDF插件')
|
throw new Error('请注册ExportPDF插件')
|
||||||
}
|
}
|
||||||
const img = await this.png(
|
const { str } = await this.getSvgData()
|
||||||
'',
|
const svgUrl = await this.fixSvgStrAndToBlob(str)
|
||||||
false,
|
const img = await this.svgToPng(svgUrl, transparent, true)
|
||||||
(width, height) => {
|
await this.mindMap.doExportPDF.pdf(name, img)
|
||||||
if (width <= a4Size.width && height && a4Size.height) return false
|
|
||||||
return width / height > 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
width: maxImageWidth || a4Size.width * 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await this.mindMap.doExportPDF.pdf(name, img, useMultiPageExport)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出为xmind
|
// 导出为xmind
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import JsPDF from '../utils/jspdf'
|
import JsPDF from '../utils/jspdf'
|
||||||
import { a4Size } from '../constants/constant'
|
|
||||||
|
|
||||||
// 导出PDF插件,需要通过Export插件使用
|
// 导出PDF插件,需要通过Export插件使用
|
||||||
class ExportPDF {
|
class ExportPDF {
|
||||||
@ -9,104 +8,20 @@ class ExportPDF {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 导出为pdf
|
// 导出为pdf
|
||||||
async pdf(name, img, useMultiPageExport = false) {
|
async pdf(name, img) {
|
||||||
if (useMultiPageExport) {
|
|
||||||
await this.multiPageExport(name, img)
|
|
||||||
} else {
|
|
||||||
await this.onePageExport(name, img)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单页导出
|
|
||||||
onePageExport(name, img) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let pdf = new JsPDF({
|
const image = new Image()
|
||||||
unit: 'pt',
|
|
||||||
format: 'a4',
|
|
||||||
compress: true
|
|
||||||
})
|
|
||||||
let a4Ratio = a4Size.width / a4Size.height
|
|
||||||
let image = new Image()
|
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
let imageWidth = image.width
|
const imageWidth = image.width
|
||||||
let imageHeight = image.height
|
const imageHeight = image.height
|
||||||
let imageRatio = imageWidth / imageHeight
|
const pdf = new JsPDF({
|
||||||
let w, h
|
unit: 'px',
|
||||||
if (imageWidth <= a4Size.width && imageHeight <= a4Size.height) {
|
format: [imageWidth, imageHeight],
|
||||||
// 使用图片原始宽高
|
compress: true,
|
||||||
w = imageWidth
|
hotfixes: ['px_scaling'],
|
||||||
h = imageHeight
|
orientation: imageWidth > imageHeight ? 'landscape' : 'portrait'
|
||||||
} else if (a4Ratio > imageRatio) {
|
|
||||||
// 以a4Height为高度,缩放图片宽度
|
|
||||||
w = imageRatio * a4Size.height
|
|
||||||
h = a4Size.height
|
|
||||||
} else {
|
|
||||||
// 以a4Width为宽度,缩放图片高度
|
|
||||||
w = a4Size.width
|
|
||||||
h = a4Size.width / imageRatio
|
|
||||||
}
|
|
||||||
pdf.addImage(
|
|
||||||
img,
|
|
||||||
'PNG',
|
|
||||||
(a4Size.width - w) / 2,
|
|
||||||
(a4Size.height - h) / 2,
|
|
||||||
w,
|
|
||||||
h
|
|
||||||
)
|
|
||||||
pdf.save(name)
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
image.onerror = e => {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
image.src = img
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多页导出
|
|
||||||
multiPageExport(name, img) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let image = new Image()
|
|
||||||
image.onload = () => {
|
|
||||||
let imageWidth = image.width
|
|
||||||
let imageHeight = image.height
|
|
||||||
// 一页pdf显示高度
|
|
||||||
let pageHeight = (imageWidth / a4Size.width) * a4Size.height
|
|
||||||
// 未生成pdf的高度
|
|
||||||
let leftHeight = imageHeight
|
|
||||||
// 偏移
|
|
||||||
let position = 0
|
|
||||||
// a4纸的尺寸[595.28,841.89],图片在pdf中图片的宽高
|
|
||||||
let imgWidth = a4Size.width
|
|
||||||
let imgHeight = (a4Size.width / imageWidth) * imageHeight
|
|
||||||
let pdf = new JsPDF({
|
|
||||||
unit: 'pt',
|
|
||||||
format: 'a4',
|
|
||||||
compress: true
|
|
||||||
})
|
})
|
||||||
// 有两个高度需要区分,一个是图片的实际高度,和生成pdf的页面高度(841.89)
|
pdf.addImage(img, 'PNG', 0, 0, imageWidth, imageHeight)
|
||||||
// 当内容未超过pdf一页显示的范围,无需分页
|
|
||||||
if (leftHeight < pageHeight) {
|
|
||||||
pdf.addImage(
|
|
||||||
img,
|
|
||||||
'PNG',
|
|
||||||
(a4Size.width - imgWidth) / 2,
|
|
||||||
(a4Size.height - imgHeight) / 2,
|
|
||||||
imgWidth,
|
|
||||||
imgHeight
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 分页
|
|
||||||
while (leftHeight > 0) {
|
|
||||||
pdf.addImage(img, 'PNG', 0, position, imgWidth, imgHeight)
|
|
||||||
leftHeight -= pageHeight
|
|
||||||
position -= a4Size.height
|
|
||||||
// 避免添加空白页
|
|
||||||
if (leftHeight > 0) {
|
|
||||||
pdf.addPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pdf.save(name)
|
pdf.save(name)
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,17 +48,11 @@
|
|||||||
@keydown.native.stop
|
@keydown.native.stop
|
||||||
></el-input>
|
></el-input>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-show="['png'].includes(exportType)"
|
v-show="['png', 'pdf'].includes(exportType)"
|
||||||
v-model="isTransparent"
|
v-model="isTransparent"
|
||||||
style="margin-left: 12px"
|
style="margin-left: 12px"
|
||||||
>{{ $t('export.isTransparent') }}</el-checkbox
|
>{{ $t('export.isTransparent') }}</el-checkbox
|
||||||
>
|
>
|
||||||
<el-checkbox
|
|
||||||
v-show="['pdf'].includes(exportType)"
|
|
||||||
v-model="useMultiPageExport"
|
|
||||||
style="margin-left: 12px"
|
|
||||||
>{{ $t('export.useMultiPageExport') }}</el-checkbox
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="downloadTypeList">
|
<div class="downloadTypeList">
|
||||||
<div
|
<div
|
||||||
@ -107,8 +101,7 @@ export default {
|
|||||||
loading: false,
|
loading: false,
|
||||||
loadingText: '',
|
loadingText: '',
|
||||||
paddingX: 10,
|
paddingX: 10,
|
||||||
paddingY: 10,
|
paddingY: 10
|
||||||
useMultiPageExport: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -183,7 +176,7 @@ export default {
|
|||||||
this.isTransparent
|
this.isTransparent
|
||||||
)
|
)
|
||||||
} else if (this.exportType === 'pdf') {
|
} else if (this.exportType === 'pdf') {
|
||||||
this.$bus.$emit('export', this.exportType, true, this.fileName, this.useMultiPageExport)
|
this.$bus.$emit('export', this.exportType, true, this.fileName, this.isTransparent)
|
||||||
} else {
|
} else {
|
||||||
this.$bus.$emit('export', this.exportType, true, this.fileName)
|
this.$bus.$emit('export', this.exportType, true, this.fileName)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user