返回 oh-my-ppt
font-collect.test.ts
根目录 / tests / unit / html-pptx / font-collect.test.ts
1 import { afterEach, describe, expect, it } from 'vitest'
2 import { copyFile, mkdtemp, mkdir, rm, writeFile } from 'fs/promises'
3 import os from 'os'
4 import path from 'path'
5 import type { HtmlToPptxSlide } from '@arcsin1/html2pptx'
6 import { collectEmbeddedFonts } from '../../../src/main/io/html-pptx/font-collect'
7
8 const tempDirectories: string[] = []
9
10 const readEotHeaderNames = (data: Uint8Array): { family: string; style: string } => {
11 const view = new DataView(data.buffer, data.byteOffset, data.byteLength)
12 let offset = 82
13 const readName = (): string => {
14 const byteLength = view.getUint16(offset, true)
15 offset += 2
16 const value = new TextDecoder('utf-16le').decode(data.slice(offset, offset + byteLength))
17 offset += byteLength + 2
18 return value
19 }
20 return { family: readName(), style: readName() }
21 }
22
23 afterEach(async () => {
24 await Promise.all(tempDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })))
25 })
26
27 describe('PPTX font embedding', () => {
28 it('embeds only the user font faces actually used by exported text', async () => {
29 const projectDir = await mkdtemp(path.join(os.tmpdir(), 'oh-my-ppt-font-'))
30 tempDirectories.push(projectDir)
31 const userFontsDir = path.join(projectDir, 'assets', 'fonts', 'user-fonts')
32 const titleDir = path.join(userFontsDir, 'title-font')
33 const bodyDir = path.join(userFontsDir, 'body-font')
34 await Promise.all([mkdir(titleDir, { recursive: true }), mkdir(bodyDir, { recursive: true })])
35
36 const regularSource = path.resolve('resources/google-fonts/Poppins/400-normal-0.woff2')
37 const boldSource = path.resolve('resources/google-fonts/Poppins/700-normal-0.woff2')
38 await Promise.all([
39 copyFile(boldSource, path.join(titleDir, 'title.woff2')),
40 copyFile(regularSource, path.join(bodyDir, 'body.woff2'))
41 ])
42
43 const htmlPath = path.join(projectDir, 'page-1.html')
44 await writeFile(
45 htmlPath,
46 `<style>
47 @font-face { font-family: "User Title"; src: url("./assets/fonts/user-fonts/title-font/title.woff2") format("woff2"); font-weight: 700; font-style: normal; }
48 @font-face { font-family: "User Body"; src: url("./assets/fonts/user-fonts/body-font/body.woff2") format("woff2"); font-weight: 400; font-style: normal; }
49 </style>`,
50 'utf-8'
51 )
52
53 const slides: HtmlToPptxSlide[] = [
54 {
55 texts: [
56 { text: 'Title', x: 0, y: 0, w: 2, h: 0.4, fontSize: 20, fontFace: 'User Title', bold: true },
57 { text: 'Body', x: 0, y: 1, w: 2, h: 0.3, fontSize: 12, fontFace: 'User Body' }
58 ]
59 }
60 ]
61
62 const embeddedFonts = await collectEmbeddedFonts(projectDir, slides, {
63 mode: 'always',
64 pageHtmlPaths: [htmlPath]
65 })
66
67 expect(embeddedFonts).toHaveLength(2)
68 expect(embeddedFonts.map((font) => [font.fontFace, font.style]).sort()).toEqual([
69 ['User Body', 'regular'],
70 ['User Title', 'bold']
71 ])
72 expect(embeddedFonts.map((font) => readEotHeaderNames(font.ttfBuffer)).sort((a, b) => a.family.localeCompare(b.family))).toEqual([
73 { family: 'User Body', style: 'Regular' },
74 { family: 'User Title', style: 'Bold' }
75 ])
76 })
77 })
78
78 lines TYPESCRIPT