Commit 653f0e85 authored by nanahira's avatar nanahira

shrink fonts

parent 371083d2
Pipeline #41248 passed with stages
in 3 minutes
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -84,6 +84,7 @@ export class AppService extends ConsoleLogger {
const copyFirstPageToBottom = async () => {
const [newPage] = await doc.copyPages(doc, [0]);
doc.addPage(newPage);
this.drawTextService.duplicateOperations(doc, 0, doc.getPageCount() - 1);
};
const cards = [...new Set([...ydk.main, ...ydk.extra, ...ydk.side])];
......@@ -140,7 +141,6 @@ export class AppService extends ConsoleLogger {
return grouped.length > 20;
})
) {
await copyFirstPageToBottom();
this.log(
`Added extra page to PDF for deck export due to card type overflow.`,
);
......@@ -198,7 +198,7 @@ export class AppService extends ConsoleLogger {
const usePages = _.chunk(groupedCards, spaces);
await ensurePages(usePages.length - 1);
for (let i = 0; i < usePages.length; ++i) {
await drawCardsOnPage(usePages[i], i + 1);
await drawCardsOnPage(usePages[i], i);
}
} else {
await drawCardsOnPage(groupedCards, 0);
......@@ -215,9 +215,7 @@ export class AppService extends ConsoleLogger {
await drawCards(ydk.side, Coordinates.side, 15);
this.log(`Filled side deck cards.`);
if (needExtraPage) {
doc.removePage(0);
}
await this.drawTextService.runBatch(doc);
const resBuf = Buffer.from(await doc.save());
this.log(`Generated PDF buffer for deck export.`);
......
import { Injectable } from '@nestjs/common';
import * as fs from 'node:fs';
import { PDFDocument, PDFFont, rgb } from 'pdf-lib';
import { DrawTextOptions } from './draw-text-options';
import fontkit from '@pdf-lib/fontkit';
import Fontmin from 'fontmin';
type Operation = {
kind: 'drawTextInBox';
text: string;
options: DrawTextOptions;
pageIndex: number; // 对应你之前的 pageCount 参数
};
@Injectable()
export class DrawTextService {
private fontCache = new WeakMap<PDFDocument, PDFFont>();
private operations = new WeakMap<PDFDocument, Operation[]>();
/*
async getFont(doc: PDFDocument): Promise<PDFFont> {
const cached = this.fontCache.get(doc);
if (cached) return cached;
const fontBuffer = await fs.promises.readFile('./resources/textFont.ttf');
doc.registerFontkit(fontkit);
const font = await doc.embedFont(fontBuffer);
const font = await doc.embedFont(fontBuffer); // 注意:这是不裁剪的全量嵌入
this.fontCache.set(doc, font);
return font;
}
*/
/** 内部:确保 doc 对应的操作队列存在 */
private ensureOps(doc: PDFDocument): Operation[] {
let ops = this.operations.get(doc);
if (!ops) {
ops = [];
this.operations.set(doc, ops);
}
return ops;
}
/**
* 在指定矩形内绘制文本(自动居中、缩放、截断
* 在指定矩形内绘制文本(改为仅记录步骤;仍保持 async 签名以兼容调用端
*/
async drawTextInBox(
doc: PDFDocument,
text: string,
options: DrawTextOptions,
pageCount = 0,
) {
): Promise<void> {
if (!text) return;
const ops = this.ensureOps(doc);
ops.push({
kind: 'drawTextInBox',
text,
options,
pageIndex: pageCount,
});
}
/**
* 批量执行所有已记录的绘制步骤:
* 1) 统计字符集合并子集化字体
* 2) 一次性 embed 子集字体
* 3) 回放所有操作并绘制
*/
async runBatch(doc: PDFDocument): Promise<void> {
const ops = this.operations.get(doc) ?? [];
if (ops.length === 0) return;
// 1) 收集字符(为安全起见,用原始文本全集合来子集化)
const allText = ops
.filter((op) => op.kind === 'drawTextInBox')
.map((op) => op.text)
.join('');
const uniqueChars = Array.from(new Set([...allText])).join('');
// 2) 子集化字体到 Buffer(内存中完成,不落盘)
const subsetBuffer = await this.subsetFontToBuffer(
'./resources/textFont.ttf',
uniqueChars,
);
// 3) embed 子集字体并缓存
doc.registerFontkit(fontkit);
const subsetFont = await doc.embedFont(subsetBuffer);
this.fontCache.set(doc, subsetFont);
// 4) 回放并实际绘制
for (const op of ops) {
if (op.kind === 'drawTextInBox') {
await this.drawTextInBoxWithFont(
doc,
subsetFont,
op.text,
op.options,
op.pageIndex,
);
}
}
// 5) 清空本次操作队列(避免重复执行)
this.operations.delete(doc);
}
/**
* 实际绘制(把你原本的测量/缩放/截断逻辑搬到这里,并显式使用传入的 PDFFont)
*/
private async drawTextInBoxWithFont(
doc: PDFDocument,
font: PDFFont,
text: string,
options: DrawTextOptions,
pageIndex: number,
): Promise<void> {
if (!text) return;
const { x, y, width, height, fontSize } = options;
const page = doc.getPage(pageCount);
const page = doc.getPage(pageIndex);
const font = await this.getFont(doc);
let size = fontSize;
// 测量宽度并调整大小防止越界
......@@ -78,4 +163,48 @@ export class DrawTextService {
lineHeight: size * 1.1,
});
}
/**
* 使用 fontmin 在内存中对子集化字体并返回 Buffer
* - 若使用 OTF,请先转 TTF 或改用其它子集工具
*/
private async subsetFontToBuffer(
fontPath: string,
text: string,
): Promise<Buffer> {
const uniqText = Array.from(new Set([...text])).join('');
return new Promise<Buffer>((resolve, reject) => {
const fm = new Fontmin().src(fontPath).use(
Fontmin.glyph({
text: uniqText,
hinting: true, // 尽量保留 hinting,视字体而定
}),
);
// 不调用 .dest(),只走内存
// @ts-ignore
fm.run((err, files: { contents: Buffer }[]) => {
if (err) return reject(err);
const out = files.find((f) => Buffer.isBuffer(f.contents));
// console.log(files);
if (!out) return reject(new Error('Fontmin produced no buffer output'));
resolve(out.contents);
});
});
}
duplicateOperations(doc: PDFDocument, fromPage: number, toPage: number) {
const ops = this.operations.get(doc);
if (!ops) return;
const newOps: Operation[] = [];
for (const op of ops) {
if (op.pageIndex === fromPage) {
newOps.push({
...op,
pageIndex: toPage,
});
}
}
ops.push(...newOps);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment