Add QPowerPoint slide size support
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import type { Slide, SlideImage, SlideListStyle, SlideTextAlign } from "../types";
|
||||
import type { Slide, SlideImage, SlideListStyle, SlideSizeId, SlideTextAlign } from "../types";
|
||||
import { officeTitleFromFileName, PPTX_MIME_TYPE, readOfficeFileAsArrayBuffer } from "./fileHelpers";
|
||||
|
||||
export interface ImportedPowerPointDeck {
|
||||
title: string;
|
||||
slideSize?: SlideSizeId;
|
||||
slides: Slide[];
|
||||
}
|
||||
|
||||
export interface ExportablePowerPointDeck {
|
||||
title: string;
|
||||
slideSize?: SlideSizeId;
|
||||
slides: Slide[];
|
||||
}
|
||||
|
||||
@@ -47,6 +49,10 @@ const imageRelationshipType = "http://schemas.openxmlformats.org/officeDocument/
|
||||
const hyperlinkRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
||||
const emuPerInch = 914400;
|
||||
const slideTransitions = ["fade", "push", "wipe"] as const;
|
||||
const slideSizeLayouts: Record<SlideSizeId, { width: number; height: number; cx: number; cy: number; pptxLayout: string }> = {
|
||||
wide: { width: 13.333, height: 7.5, cx: 12192000, cy: 6858000, pptxLayout: "LAYOUT_WIDE" },
|
||||
standard: { width: 10, height: 7.5, cx: 9144000, cy: 6858000, pptxLayout: "LAYOUT_4x3" }
|
||||
};
|
||||
const themeColors: Record<Slide["theme"], { background: string; title: string; subtitle: string }> = {
|
||||
classic: { background: "FFFFFF", title: "202124", subtitle: "3C4043" },
|
||||
ocean: { background: "EAF6FF", title: "075985", subtitle: "0F766E" },
|
||||
@@ -65,6 +71,7 @@ export async function importPptxFile(file: File): Promise<ImportedPowerPointDeck
|
||||
const presentationXml = await readOptionalParsedXml(zip, parser, "ppt/presentation.xml");
|
||||
const presentationRelationshipsXml = await readOptionalParsedXml(zip, parser, "ppt/_rels/presentation.xml.rels");
|
||||
const slidePaths = listSlidePaths(zip, presentationXml, presentationRelationshipsXml);
|
||||
const slideSize = presentationSlideSize(presentationXml);
|
||||
|
||||
if (slidePaths.length === 0) {
|
||||
throw new Error("В файле PowerPoint не найдены слайды.");
|
||||
@@ -126,7 +133,7 @@ export async function importPptxFile(file: File): Promise<ImportedPowerPointDeck
|
||||
})
|
||||
);
|
||||
|
||||
return { title, slides };
|
||||
return { title, ...(slideSize ? { slideSize } : {}), slides };
|
||||
}
|
||||
|
||||
export async function exportPptxBlob(deck: ExportablePowerPointDeck): Promise<Blob> {
|
||||
@@ -147,8 +154,10 @@ export async function exportPptxBlob(deck: ExportablePowerPointDeck): Promise<Bl
|
||||
};
|
||||
write: (options: { outputType: "blob" | "arraybuffer" }) => Promise<Blob | ArrayBuffer>;
|
||||
};
|
||||
const slideSize = normalizedSlideSizeId(deck.slideSize);
|
||||
const slideLayout = slideSizeLayouts[slideSize];
|
||||
|
||||
pptx.layout = "LAYOUT_WIDE";
|
||||
pptx.layout = slideLayout.pptxLayout;
|
||||
pptx.author = "QOffice";
|
||||
pptx.company = "QOffice";
|
||||
pptx.subject = "Экспорт QPowerPoint";
|
||||
@@ -196,7 +205,7 @@ export async function exportPptxBlob(deck: ExportablePowerPointDeck): Promise<Bl
|
||||
slide.addText(sourceSlide.title || "Без заголовка", {
|
||||
x: 0.7,
|
||||
y: 0.65,
|
||||
w: 11.9,
|
||||
w: Math.max(1, slideLayout.width - 1.4),
|
||||
h: 0.75,
|
||||
fontFace: titleFontFamily,
|
||||
fontSize: titleFontSize,
|
||||
@@ -213,8 +222,8 @@ export async function exportPptxBlob(deck: ExportablePowerPointDeck): Promise<Bl
|
||||
slide.addText(sourceSlide.subtitle || "", {
|
||||
x: 0.75,
|
||||
y: 1.65,
|
||||
w: 11.5,
|
||||
h: 4.35,
|
||||
w: Math.max(1, slideLayout.width - 1.5),
|
||||
h: Math.max(1, slideLayout.height - 3.15),
|
||||
fontFace: subtitleFontFamily,
|
||||
fontSize: subtitleFontSize,
|
||||
...(subtitleAlign ? { align: subtitleAlign } : {}),
|
||||
@@ -411,6 +420,19 @@ export function presentationSlidePaths(presentationXml: unknown, presentationRel
|
||||
return slideIds.map((id) => relationshipTargets[id]).filter((target): target is string => Boolean(target));
|
||||
}
|
||||
|
||||
export function presentationSlideSize(presentationXml: unknown): SlideSizeId | undefined {
|
||||
const slideSize = childRecord(childRecord(presentationXml, "presentation"), "sldSz");
|
||||
const cx = numberAttribute(slideSize, "cx");
|
||||
const cy = numberAttribute(slideSize, "cy");
|
||||
if (!cx || !cy) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Object.entries(slideSizeLayouts).find(([, layout]) => Math.abs(cx - layout.cx) <= 5000 && Math.abs(cy - layout.cy) <= 5000)?.[0] as
|
||||
| SlideSizeId
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export function relationshipTargetsById(relationshipsXml: unknown, type?: string, sourcePartPath = "ppt/presentation.xml"): Record<string, string> {
|
||||
const relationships = childRecord(relationshipsXml, "Relationships");
|
||||
const targets: Record<string, string> = {};
|
||||
@@ -467,6 +489,10 @@ export function normalizeWhitespace(value: string): string {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function normalizedSlideSizeId(value?: SlideSizeId): SlideSizeId {
|
||||
return value === "standard" ? "standard" : "wide";
|
||||
}
|
||||
|
||||
function normalizeTextWithLineBreaks(value: string): string {
|
||||
return value
|
||||
.split("\n")
|
||||
|
||||
Reference in New Issue
Block a user