news 2026/1/21 9:02:12

使用 html2canvas + jsPDF 生成PDF 的简单示例(含文字下沉修复)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 html2canvas + jsPDF 生成PDF 的简单示例(含文字下沉修复)

一、为什么需要自定义封装?

自个实现全局 hooks 可控,想怎么来就怎么来(参考 html2pdf.js)。

直接使用 html2canvas 和 jsPDF 通常会遇到:

内容被截断 / 超出容器

内容生成不居中

图片跨域污染导致失败

Tailwind/UnoCSS 的样式与 html2canvas 不兼容

PDF 页面分页不正确

文字渲染模糊

文字下沉(文本 baseline 问题)

要做到尽可能无损的渲染,需要:

克隆 DOM,在单独容器中渲染(目前使用这个方法实现内容不居中问题)

处理 position/overflow/scroll 等不兼容属性

修复不支持的 CSS 颜色函数(oklab/oklch 等)

手动分页 + 图片等比缩放

覆盖一些框架默认样式(如 img display)

二、核心导出功能实现

以下代码实现了完整的导出流程:

自动加载 html2canvas / jsPDF

DOM 克隆渲染

Canvas 控制切分页

PDF 导出或预览

进度回调

代码较长,这里展示关键核心结构:

export function useHtml2Pdf() {

const exporting = ref(false);

const progress = ref(0);

const previewImages = ref<string[]>([]);

// 加载库

const loadLibraries = async () => { ... }

// DOM → Canvas

const renderToCanvas = async () => { ... }

// Canvas → PDF

const splitCanvasToPdf = () => { ... }

// 导出与预览

const exportPdf = async () => { ... }

const previewPdf = async () => { ... }

return { exporting, progress, previewImages, exportPdf, previewPdf };

}

完整代码已在文章结尾处附带(代码以及实现就不详细解读了)。

三、为什么会出现“文字下沉”?

这是使用 html2canvas 时 最常见也是最诡异的 bug 之一:

同一行文字字体不同

图片与文字混合排版

Tailwind / UnoCSS 的 baseline 设置

img 默认为 display: inline 或 inline-block

html2canvas 会根据 CSS 计算内容 baseline,高度计算错误

具体表现:

文字被向下“压”了一点点,与真实页面不一致

某些容器中的文本垂直位置偏离

根本原因

UnoCSS / Tailwind 默认对 img 设置为:

display: inline;

vertical-align: middle;

导致 html2canvas 在计算行内盒高度时:

文字基线被迫下移

图片对齐方式干扰文本

html2canvas 本身对 inline-level box 的计算就比较脆弱,因此这个默认样式会破坏它的内部排版逻辑。

四、一行 CSS 解决文字下沉问题

解决方案:强制 img 不参与 inline 排版

img {

display: inline-block !important;

}

为什么这行能解决?

inline-block 会创建自己的盒模型,不再参与行内 baseline 对齐。

html2canvas 计算布局时,不需要处理 inline-level baseline,从而避免错位。

避免 Tailwind/UnoCSS 默认的 vertical-align: middle 影响布局高度。

这是经过大量社区使用、实测最稳定的解决方案。

注意:这种 bug 仅在截屏(html2canvas)时出现,真实 DOM 渲染正常。

五、完整代码(以下不是最新版本,有许多点可以进行优化更改,可以参考各自需求)

import { ref, onMounted, type Ref } from 'vue';

/**

* html2canvas 配置选项

*/

export interface Html2CanvasOptions {

scale?: number; // 清晰度倍数,默认使用 devicePixelRatio * 1.5

useCORS?: boolean; // 是否使用 CORS

allowTaint?: boolean; // 是否允许跨域图片污染 canvas

backgroundColor?: string; // 背景色

logging?: boolean; // 是否启用日志

width?: number; // 宽度

height?: number; // 高度

windowWidth?: number; // 窗口宽度

windowHeight?: number; // 窗口高度

x?: number; // X 偏移

y?: number; // Y 偏移

scrollX?: number; // X 滚动

scrollY?: number; // Y 滚动

onclone?: (clonedDoc: Document, clonedElement: HTMLElement) => void; // 克隆回调

}

/**

* 图片质量配置

*/

export interface ImageOptions {

type?: 'png' | 'jpeg' | 'webp'; // 图片类型

quality?: number; // 图片质量 0-1,仅对 jpeg/webp 有效

}

/**

* 页面分页配置

*/

export interface PagebreakOptions {

mode?: ('avoid-all' | 'css' | 'legacy')[]; // 分页模式

before?: string | string[]; // 在此元素前分页

after?: string | string[]; // 在此元素后分页

avoid?: string | string[]; // 避免在此元素处分页

enabled?: boolean; // 是否启用自动分页,默认true

avoidSinglePage?: boolean; // 是否避免单页内容强制分页,默认true

}

/**

* PDF 页面格式类型

*/

export type PdfFormat =

| 'a0' | 'a1' | 'a2' | 'a3' | 'a4' | 'a5' | 'a6' | 'a7' | 'a8' | 'a9' | 'a10'

| 'b0' | 'b1' | 'b2' | 'b3' | 'b4' | 'b5' | 'b6' | 'b7' | 'b8' | 'b9' | 'b10'

| 'c0' | 'c1' | 'c2' | 'c3' | 'c4' | 'c5' | 'c6' | 'c7' | 'c8' | 'c9' | 'c10'

| 'dl' | 'letter' | 'government-letter' | 'legal' | 'junior-legal'

| 'ledger' | 'tabloid' | 'credit-card'

| [number, number]; // 自定义尺寸 [width, height] in mm

/**

* PDF 导出配置选项

*/

export interface Html2PdfOptions {

fileName?: string; // 文件名

scale?: number; // 清晰度倍数,默认使用 devicePixelRatio * 1.5

padding?: number; // 页面内边距 (mm)

format?: PdfFormat; // PDF格式,默认为 'a4'

orientation?: 'portrait' | 'landscape'; // 方向

align?: 'left' | 'center' | 'right'; // 内容对齐方式

image?: ImageOptions; // 图片配置

html2canvas?: Partial<Html2CanvasOptions>; // html2canvas 配置

pagebreak?: PagebreakOptions; // 分页配置

onProgress?: (progress: number) => void; // 进度回调 0-100

}

/**

* 返回值类型

*/

export interface UseHtml2PdfReturn {

exporting: Ref<boolean>;

progress: Ref<number>; // 进度 0-100

previewImages: Ref<string[]>; // 预览图片数组

exportPdf: (

element: HTMLElement | string | null,

options?: Html2PdfOptions

) => Promise<void>;

previewPdf: (

element: HTMLElement | string | null,

options?: Html2PdfOptions

) => Promise<void>;

}

/**

* 使用 html2canvas + jsPDF 生成 PDF

* 适配 Vue 3 + Nuxt.js 3

*/

export function useHtml2Pdf(): UseHtml2PdfReturn {

const exporting = ref(false);

const progress = ref(0);

const previewImages = ref<string[]>([]);

const { $message } = useNuxtApp();

// 库实例

let html2canvas: any = null;

let jsPDF: any = null;

// 库加载状态

let librariesLoading: Promise<void> | null = null;

/**

* 加载必要的库

*/

const loadLibraries = async (): Promise<void> => {

if (librariesLoading) {

return librariesLoading;

}

librariesLoading = (async () => {

try {

const [html2canvasModule, jsPDFModule] = await Promise.all([

import('html2canvas'),

import('jspdf'),

]);

html2canvas = html2canvasModule.default || html2canvasModule;

jsPDF = (jsPDFModule as any).jsPDF;

} catch (error) {

console.error('PDF 库加载失败:', error);

throw error;

}

})();

return librariesLoading;

};

// 在客户端预加载库

if (process.client) {

onMounted(() => {

loadLibraries().catch((error) => {

console.error('预加载 PDF 库失败:', error);

});

});

}

/**

* 更新进度

*/

const updateProgress = (value: number, callback?: (progress: number) => void): void => {

progress.value = Math.min(100, Math.max(0, value));

if (callback) {

callback(progress.value);

}

};

/**

* 获取目标元素

*/

const getElement = (element: HTMLElement | string | null): HTMLElement | null => {

if (!element || process.server) return null;

if (typeof element === 'string') {

return document.querySelector(element) as HTMLElement;

}

return element;

};

/**

* 保存和恢复元素样式

*/

interface StyleState {

overflow: string;

maxHeight: string;

height: string;

scrollTop: number;

scrollLeft: number;

bodyOverflow: string;

bodyScrollTop: number;

}

const saveStyles = (element: HTMLElement): StyleState => {

return {

overflow: element.style.overflow,

maxHeight: element.style.maxHeight,

height: element.style.height,

scrollTop: element.scrollTop,

scrollLeft: element.scrollLeft,

bodyOverflow: document.body.style.overflow,

bodyScrollTop: window.scrollY,

};

};

const applyCaptureStyles = (element: HTMLElement): void => {

element.style.overflow = 'visible';

element.style.maxHeight = 'none';

element.style.height = 'auto';

document.body.style.overflow = 'hidden';

element.scrollTop = 0;

element.scrollLeft = 0;

window.scrollTo(0, 0);

};

const restoreStyles = (element: HTMLElement, state: StyleState): void => {

element.style.overflow = state.overflow;

element.style.maxHeight = state.maxHeight;

element.style.height = state.height;

element.scrollTop = state.scrollTop;

element.scrollLeft = state.scrollLeft;

document.body.style.overflow = state.bodyOverflow;

window.scrollTo(0, state.bodyScrollTop);

};

/**

* 修复不支持的 CSS 颜色函数

*/

const fixUnsupportedColors = (clonedDoc: Document, originalElement: HTMLElement): void => {

clonedDoc.body.style.backgroundColor = '#ffffff';

clonedDoc.body.style.margin = '0';

clonedDoc.body.style.padding = '0';

const allElements = clonedDoc.querySelectorAll('*');

const colorProperties = [

'color',

'background-color',

'background',

'border-color',

'border-top-color',

'border-right-color',

'border-bottom-color',

'border-left-color',

'outline-color',

'box-shadow',

'text-shadow',

];

allElements.forEach((el, index) => {

if (el instanceof HTMLElement) {

// 尝试从原始文档找到对应元素

let originalEl: HTMLElement | null = null;

if (originalElement) {

const originalAll = originalElement.querySelectorAll('*');

if (originalAll[index]) {

originalEl = originalAll[index] as HTMLElement;

}

}

const targetEl = originalEl || el;

try {

const computedStyle = window.getComputedStyle(targetEl, null);

colorProperties.forEach((prop) => {

try {

const computedValue = computedStyle.getPropertyValue(prop);

const styleValue = targetEl.style.getPropertyValue(prop);

if (

(styleValue && (

styleValue.includes('oklab') ||

styleValue.includes('oklch') ||

styleValue.includes('lab(') ||

styleValue.includes('lch(')

)) ||

(computedValue && (

computedValue.includes('oklab') ||

computedValue.includes('oklch')

))

) {

if (computedValue && (computedValue.includes('rgb') || computedValue.includes('#'))) {

el.style.setProperty(prop, computedValue, 'important');

} else if (prop.includes('shadow')) {

el.style.setProperty(prop, 'none', 'important');

} else {

el.style.removeProperty(prop);

}

}

} catch (e) {

// 忽略单个属性的错误

}

});

} catch (e) {

// 如果无法获取计算样式,跳过该元素

}

}

});

if (originalElement) {

(originalElement as HTMLElement).style.position = 'relative';

(originalElement as HTMLElement).style.width = 'auto';

(originalElement as HTMLElement).style.height = 'auto';

}

};

/**

* 创建渲染容器

*/

const createRenderContainer = (sourceElement: HTMLElement): { overlay: HTMLElement; container: HTMLElement; cleanup: () => void } => {

// 创建 overlay 容器样式

const overlayCSS = {

position: 'fixed',

overflow: 'hidden',

zIndex: 1000,

left: 0,

right: 0,

bottom: 0,

top: 0,

backgroundColor: 'rgba(0,0,0,0.8)',

opacity: 0

};

// 创建内容容器样式

const containerCSS = {

position: 'absolute',

width: 'auto',

left: 0,

right: 0,

top: 0,

height: 'auto',

margin: 'auto',

backgroundColor: 'white'

};

// 创建 overlay 容器

const overlay = document.createElement('div');

overlay.className = 'html2pdf__overlay';

Object.assign(overlay.style, overlayCSS);

// 创建内容容器

const container = document.createElement('div');

container.className = 'html2pdf__container';

Object.assign(container.style, containerCSS);

// 克隆源元素并添加到容器中

const clonedElement = sourceElement.cloneNode(true) as HTMLElement;

container.appendChild(clonedElement);

overlay.appendChild(container);

document.body.appendChild(overlay);

// 清理函数

const cleanup = () => {

if (document.body.contains(overlay)) {

document.body.removeChild(overlay);

}

};

return { overlay, container, cleanup };

};

/**

* 渲染 DOM -> Canvas

*/

const renderToCanvas = async (

element: HTMLElement,

options?: {

scale?: number;

html2canvas?: Partial<Html2CanvasOptions>;

onProgress?: (progress: number) => void;

}

): Promise<HTMLCanvasElement> => {

// 确保库已加载

await loadLibraries();

if (!html2canvas) {

throw new Error('html2canvas 未加载');

}

const {

scale: customScale,

html2canvas: html2canvasOptions = {},

onProgress: progressCallback,

} = options || {};

const defaultScale = (window.devicePixelRatio || 1) * 1.5;

const finalScale = customScale ?? html2canvasOptions.scale ?? defaultScale;

const fullWidth = element.scrollWidth || html2canvasOptions.width || element.offsetWidth;

const fullHeight = element.scrollHeight || html2canvasOptions.height || element.offsetHeight;

// 保存样式

const styleState = saveStyles(element);

applyCaptureStyles(element);

// 创建渲染容器

const { container, cleanup } = createRenderContainer(element);

const clonedElement = container.firstElementChild as HTMLElement;

// 等待布局稳定

await new Promise((resolve) => {

requestAnimationFrame(() => {

requestAnimationFrame(resolve);

});

});

updateProgress(10, progressCallback);

try {

// 合并默认配置和自定义配置

const canvasOptions = {

scale: finalScale,

useCORS: true,

allowTaint: false,

logging: false,

backgroundColor: '#ffffff',

width: fullWidth,

height: fullHeight,

windowWidth: fullWidth,

windowHeight: fullHeight,

x: 0,

y: 0,

scrollX: 0,

scrollY: 0,

...html2canvasOptions,

onclone: (clonedDoc: Document, clonedElementFromCanvas: HTMLElement) => {

fixUnsupportedColors(clonedDoc, element);

// 执行用户自定义的 onclone 回调

if (html2canvasOptions.onclone) {

html2canvasOptions.onclone(clonedDoc, clonedElementFromCanvas);

}

},

};

updateProgress(20, progressCallback);

// 使用克隆的元素进行渲染

const canvas = await html2canvas(clonedElement, canvasOptions);

updateProgress(50, progressCallback);

return canvas;

} finally {

// 清理容器

cleanup();

// 恢复样式

restoreStyles(element, styleState);

}

};

/**

* 获取页面尺寸配置(单位:mm)

* 参考 jsPDF 的页面尺寸定义,使用精确的 pt 到 mm 转换

*/

const getPageSizes = (

format: PdfFormat,

orientation: 'portrait' | 'landscape' = 'portrait'

): { width: number; height: number } => {

// 如果是自定义数组格式 [width, height]

if (Array.isArray(format)) {

return { width: format[0], height: format[1] };

}

// pt 到 mm 的转换因子:1 pt = 72/25.4 mm

const k = 72 / 25.4;

// 所有页面格式的尺寸(单位:pt)

// 参考 jsPDF 的页面格式定义

const pageFormatsPt: Record<string, [number, number]> = {

// A 系列

a0: [2383.94, 3370.39],

a1: [1683.78, 2383.94],

a2: [1190.55, 1683.78],

a3: [841.89, 1190.55],

a4: [595.28, 841.89],

a5: [419.53, 595.28],

a6: [297.64, 419.53],

a7: [209.76, 297.64],

a8: [147.40, 209.76],

a9: [104.88, 147.40],

a10: [73.70, 104.88],

// B 系列

b0: [2834.65, 4008.19],

b1: [2004.09, 2834.65],

b2: [1417.32, 2004.09],

b3: [1000.63, 1417.32],

b4: [708.66, 1000.63],

b5: [498.90, 708.66],

b6: [354.33, 498.90],

b7: [249.45, 354.33],

b8: [175.75, 249.45],

b9: [124.72, 175.75],

b10: [87.87, 124.72],

// C 系列

c0: [2599.37, 3676.54],

c1: [1836.85, 2599.37],

c2: [1298.27, 1836.85],

c3: [918.43, 1298.27],

c4: [649.13, 918.43],

c5: [459.21, 649.13],

c6: [323.15, 459.21],

c7: [229.61, 323.15],

c8: [161.57, 229.61],

c9: [113.39, 161.57],

c10: [79.37, 113.39],

// 其他格式

dl: [311.81, 623.62],

letter: [612, 792],

'government-letter': [576, 756],

legal: [612, 1008],

'junior-legal': [576, 360],

ledger: [1224, 792],

tabloid: [792, 1224],

'credit-card': [153, 243],

};

const formatLower = format.toLowerCase();

let pageSize: [number, number];

if (pageFormatsPt.hasOwnProperty(formatLower)) {

pageSize = pageFormatsPt[formatLower];

} else {

// 默认使用 A4

pageSize = pageFormatsPt.a4;

console.warn(`未识别的页面格式 "${format}",使用默认格式 A4`);

}

// 转换为 mm

let width = pageSize[0] / k;

let height = pageSize[1] / k;

// 处理方向

if (orientation === 'portrait') {

// 纵向:确保宽度 < 高度

if (width > height) {

[width, height] = [height, width];

}

} else if (orientation === 'landscape') {

// 横向:确保宽度 > 高度

if (height > width) {

[width, height] = [height, width];

}

}

return { width, height };

};

/**

* 将 Canvas 切分页、生成 PDF

*/

const splitCanvasToPdf = (

canvas: HTMLCanvasElement,

options: {

format: PdfFormat;

orientation: 'portrait' | 'landscape';

padding: number;

fileName: string;

align?: 'left' | 'center' | 'right';

image?: ImageOptions;

pagebreak?: PagebreakOptions;

onProgress?: (progress: number) => void;

},

doDownload = false

): { pdf: any; images: string[] } => {

if (!jsPDF) {

throw new Error('jsPDF 未加载');

}

const {

format,

orientation,

padding,

fileName,

align = 'center',

image = { type: 'jpeg', quality: 0.95 },

pagebreak = { enabled: false, avoidSinglePage: true },

onProgress: progressCallback,

} = options;

// 获取页面尺寸

const pageSize = getPageSizes(format, orientation);

// 对于自定义尺寸(数组格式),需要特殊处理

// jsPDF 构造函数格式:new jsPDF(orientation, unit, format)

// 如果 format 是数组 [width, height],则作为自定义尺寸传递

const pdfFormat: string | [number, number] = Array.isArray(format)

? format

: format;

const pdf = new jsPDF(orientation, 'mm', pdfFormat);

const pageWidth = pageSize.width;

const pageHeight = pageSize.height;

// margin = [top, left, bottom, right]

// 这里 padding 相当于左右边距(当四边相等时)

// 支持独立设置四个方向的边距,默认只设置一个值

const marginTop = padding;

const marginLeft = padding;

const marginBottom = padding;

const marginRight = padding;

// 可用内容区域(考虑边距)

const innerWidth = pageWidth - marginLeft - marginRight;

const innerHeight = pageHeight - marginTop - marginBottom;

// 计算图片尺寸,保持宽高比

// 先计算基于可用区域的宽度和高度的比例,看哪个更限制

const widthRatio = innerWidth / canvas.width;

const heightRatio = innerHeight / canvas.height;

const scaleRatio = Math.min(widthRatio, heightRatio);

// 图片在 PDF 中的尺寸

let imgWidth: number;

let imgHeight: number;

// 图片尺寸基于可用区域和内容比例

imgWidth = canvas.width * scaleRatio;

imgHeight = canvas.height * scaleRatio;

// 确保图片不超过可用区域

if (imgWidth > innerWidth) {

imgWidth = innerWidth;

imgHeight = (canvas.height / canvas.width) * innerWidth;

}

if (imgHeight > innerHeight) {

imgHeight = innerHeight;

imgWidth = (canvas.width / canvas.height) * innerHeight;

}

// 计算PDF页面在canvas像素坐标系中的高度

// 1mm = (canvas像素 / PDF尺寸mm) 的比例

let pxPageHeight: number;

if(pagebreak.enabled) {

const pxPerMm = canvas.width / (pageSize.width - marginLeft - marginRight);

pxPageHeight = Math.floor(innerHeight * pxPerMm);

} else {

pxPageHeight = Math.floor(canvas.width * (imgHeight / imgWidth));

}

// 计算水平位置

let xPosition: number;

switch (align) {

case 'left':

// 左对齐:从左边距开始

xPosition = marginLeft;

break;

case 'right':

// 右对齐:从右边距开始计算,确保图片在右边

xPosition = pageWidth - marginRight - imgWidth;

break;

case 'center':

default:

// 居中:计算居中位置

xPosition = marginLeft + (innerWidth - imgWidth) / 2;

break;

}

// 确定图片类型和质量

const imageType = image.type || 'jpeg';

const imageQuality = image.quality ?? (imageType === 'png' ? undefined : 0.95);

const pdfImageFormat = imageType === 'png' ? 'PNG' : 'JPEG';

// 确保图片质量在有效范围内

const finalQuality = imageQuality !== undefined

? Math.max(0, Math.min(1, imageQuality))

: undefined;

const images: string[] = [];

// 根据配置决定是否分页

const pxFullHeight = canvas.height;

let nPages = 1;

if (pagebreak.enabled) {

// 计算需要的页数

const calculatedPages = Math.ceil(pxFullHeight / pxPageHeight);

// 如果避免单页强制分页,且内容不超过一页,则不分页

if (pagebreak.avoidSinglePage && calculatedPages === 1) {

nPages = 1;

} else {

nPages = calculatedPages;

}

} else {

nPages = Math.ceil(pxFullHeight / pxPageHeight);;

}

// 估算总页数用于进度计算

const estimatedTotalPages = nPages;

// 创建页面 canvas

const pageCanvas = document.createElement('canvas');

const pageCtx = pageCanvas.getContext('2d');

if (!pageCtx) {

throw new Error('无法创建 Canvas 上下文');

}

pageCanvas.width = canvas.width;

pageCanvas.height = pxPageHeight;

// 分页处理

for (let page = 0; page < nPages; page++) {

// 最后一页可能需要调整高度

let currentPxPageHeight = pxPageHeight;

let currentPageHeight = innerHeight;

if (page === nPages - 1 && pxFullHeight % pxPageHeight !== 0) {

// 最后一页:使用剩余高度

currentPxPageHeight = pxFullHeight % pxPageHeight;

currentPageHeight = (currentPxPageHeight / canvas.width) * innerWidth;

pageCanvas.height = currentPxPageHeight;

}

// 清空并绘制当前页的内容

pageCtx.fillStyle = 'white';

pageCtx.fillRect(0, 0, pageCanvas.width, currentPxPageHeight);

pageCtx.drawImage(

canvas,

0,

page * pxPageHeight,

pageCanvas.width,

currentPxPageHeight,

0,

0,

pageCanvas.width,

currentPxPageHeight

);

const sourceHeight = (currentPageHeight / imgHeight) * canvas.height;

// 根据配置生成图片数据

const mimeType = `image/${imageType}`;

const pageImgData = finalQuality !== undefined

? pageCanvas.toDataURL(mimeType, finalQuality)

: pageCanvas.toDataURL(mimeType);

// 添加新页(除了第一页)

if (page > 0) {

pdf.addPage();

}

// 添加图片到 PDF(x = marginLeft, y = marginTop)

pdf.addImage(

pageImgData,

pdfImageFormat,

xPosition,

marginTop,

imgWidth,

currentPageHeight

);

if (!doDownload) {

images.push(pageImgData);

}

// 更新进度 (50-90%)

if (progressCallback && estimatedTotalPages > 0) {

const pageProgress = 50 + ((page + 1) / estimatedTotalPages) * 40;

updateProgress(pageProgress, progressCallback);

}

}

updateProgress(95, progressCallback);

if (doDownload) {

pdf.save(fileName);

updateProgress(100, progressCallback);

}

return { pdf, images };

};

/**

* 导出 PDF

* @param element 需要导出的 DOM 元素或选择器

* @param options 配置项

*/

const exportPdf = async (

element: HTMLElement | string | null,

options?: Html2PdfOptions

): Promise<void> => {

// 服务端检查

if (process.server) {

if ($message) $

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/17 10:35:55

【Wolfram语言】20Ex 绘制洛书

引言 近日看到有关河图洛书的视频。我突发奇想,能否用目前所介绍的Wolfram语言来绘制一幅洛书呢? 起源 《周易》有云:“河出图,洛出书,圣人则之”。 传说上古有神龟从洛水出现,背负‘洛书’。 其图如下: 分析 粗看洛书是以黑点与白点排列成33矩阵。 因此可将九个数…

作者头像 李华
网站建设 2026/1/18 16:26:22

3.1 符号主义:基于逻辑的推理系统、知识表示与专家系统

3.1 符号主义&#xff1a;基于逻辑的推理系统、知识表示与专家系统 符号主义&#xff0c;亦称为逻辑主义或“老式人工智能”&#xff0c;是人工智能发展史上第一个形成完整体系并长期占据主导地位的研究范式。其核心假设在于&#xff1a;人类智能&#xff0c;特别是高阶认知功…

作者头像 李华
网站建设 2026/1/17 12:09:38

企业选对会议服务商:7个关键维度避坑指南

企业选对会议服务商&#xff1a;7个关键维度避坑指南 在数字化转型浪潮下&#xff0c;会议活动已从简单的场地与人员组织&#xff0c;演变为一个集技术集成、创意策划与精准执行于一体的复杂系统工程。企业选择会议服务商&#xff0c;实质上是选择一位能够驾驭技术变革、保障活…

作者头像 李华
网站建设 2026/1/20 17:31:14

泰克示波器 4 系列 MSO 在嵌入式开发中的实测分析

嵌入式系统广泛应用于消费电子、工业控制、汽车电子等领域。嵌入式开发人员需要面对复杂的硬件电路、复杂的软件代码以及各种通信协议。泰克 4 系列 MSO 结合了示波器、逻辑分析仪、协议分析仪等多种功能&#xff0c;可以帮助开发人员快速定位问题、验证设计并优化性能。泰克 4…

作者头像 李华
网站建设 2026/1/19 5:04:22

LCR测试仪温度漂移补偿的解决方案

LCR测试仪是电子测量中重要的仪器&#xff0c;广泛应用于元器件的参数测试&#xff0c;如电感&#xff08;L&#xff09;、电容&#xff08;C&#xff09;和电阻&#xff08;R&#xff09;。然而&#xff0c;温度变化会导致待测元件参数的漂移&#xff0c;进而影响测试结果的准…

作者头像 李华
网站建设 2026/1/19 18:55:45

C++医学图像处理经典ITK库用法详解<四>: 图像分割模块功能

1、ITK库概述ITK (Insight Segmentation and Registration Toolkit) 是一个开源的跨平台软件开发工具包&#xff0c;主要用于图像处理&#xff0c;特别是生物医学图像处理领域。该工具包提供了一套丰富的图像处理算法&#xff0c;特别是在图像分割和配准方面具有强大的功能。IT…

作者头像 李华