Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dsl-presentation-fonts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@office-kit/pptx-dsl': minor
---

Added `fonts` to `Presentation`: `<Presentation fonts={{ minorLatin: 'Arial', majorLatin: 'Arial' }}>` sets the theme's body and heading typefaces (the same fields as `setPresentationFonts`). Text, tables and charts that name no font inherit them. Before, a deck could only set a font on each `Text`, so its charts kept the theme's Calibri while the slides used another font. Setting `textStyle.font` on each chart instead did not help Japanese or Chinese decks, because a chart's `textStyle.font` names one typeface for Latin and East Asian text alike, while the theme keeps its own East Asian and per-script fonts.
7 changes: 6 additions & 1 deletion packages/dsl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Layout is explicit: CSS and automatic UI layout are not implemented.

| Element | Input |
| -------------- | ------------------------------------------------------------------------- |
| `Presentation` | Optional source PPTX bytes or `size`, theme, `mode` |
| `Presentation` | Optional source PPTX bytes or `size`, theme, fonts, `mode` |
| `Slide` | Source `from` or edit `target`, layout, background, notes |
| `Text` | Literal children or core `ParagraphSpec[]` via `paragraphs`, text format |
| `Shape` | Core preset geometry, fill, stroke, effects, optional text |
Expand Down Expand Up @@ -185,6 +185,11 @@ retains existing parts. Editing text intentionally replaces that shape's text.
longer than the placeholder); omitted, the shape keeps the template's setting.
`size` applies only to new decks; source decks retain their original dimensions.
`theme` accepts the core theme overrides and follows its first-theme semantics.
`fonts` sets the theme typefaces the same way (`setPresentationFonts`): text,
tables and charts that name no font inherit `minorLatin` (body) and `majorLatin`
(headings). Set the deck's body font there rather than on every `Text`, so chart
labels use it too; a chart's `textStyle.font` names one typeface for Latin and
East Asian text alike.

Slide references accept a zero-based index, exact title, or part name. Layout
references accept exact name, type, or part name. Shape targets accept exact name,
Expand Down
4 changes: 4 additions & 0 deletions packages/dsl/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export type PresentationProps = Children &
| { source?: never; size?: api.PresentationSize }
) & {
theme?: Parameters<typeof api.setPresentationTheme>[1];
/** Theme typefaces (`minorLatin` is the body font, `majorLatin` the heading font) that
* text, tables and charts without a font of their own inherit. */
fonts?: api.PresentationFontsInput;
/** Compose replaces the slide sequence; edit retains the original sequence. */
mode?: 'compose' | 'edit';
};
Expand All @@ -40,6 +43,7 @@ export function Presentation(props: PresentationProps): PresentationNode {
? api.createPresentation(props.size === undefined ? {} : { size: props.size })
: await api.loadPresentation(props.source);
if (props.theme) api.setPresentationTheme(presentation, props.theme);
if (props.fonts) api.setPresentationFonts(presentation, props.fonts);
const originals = [...api.getSlides(presentation)];
const context: Context = {
presentation,
Expand Down
2 changes: 1 addition & 1 deletion skill/references/tsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Details that trip up type checking and layout:

| Element | Inputs |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Presentation` | Optional `source` bytes, new-deck `size`, `theme`, `mode`. |
| `Presentation` | Optional `source` bytes, new-deck `size`, `theme`, `fonts` (theme body / heading typefaces that charts and tables inherit), `mode`. |
| `Slide` | `background`, `notes`, `layout`, source `from` or edit `target`. |
| `Text` | Literal children or rich core `ParagraphSpec[]` in `paragraphs`; core `TextFormat` props such as `size`, `font`, `bold`, `color`; `bullets`, `paragraphSpacing`. |
| `Shape` | `preset`, solid/gradient `fill`, `stroke`, rotation, effects, optional text with `align` and `anchor`. |
Expand Down
49 changes: 49 additions & 0 deletions test/dsl-presentation-fonts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { strFromU8, unzipSync } from 'fflate';
import { describe, expect, it } from 'vitest';
import * as api from '../src/api/index.ts';
import { Chart, Presentation, Slide, Text, compile } from '../packages/dsl/src/index.ts';

describe('Presentation fonts', () => {
it('sets the theme typefaces that charts and unformatted text inherit', async () => {
const pres = await compile(
Presentation({
fonts: { minorLatin: 'Arial', majorLatin: 'Arial' },
children: Slide({
children: [
Text({ x: 1, y: 1, width: 4, height: 1, children: '本文' }),
Chart({
x: 1,
y: 2,
width: 6,
height: 4,
spec: {
kind: 'column',
categories: ['4 月', '5 月'],
series: [{ name: '件数', values: [1, 2] }],
},
}),
],
}),
}),
);

expect(api.getPresentationFonts(pres)).toMatchObject({
minorLatin: 'Arial',
majorLatin: 'Arial',
// East Asian text keeps the theme's own East Asian and script fonts.
minorEastAsian: null,
});
const parts = unzipSync(await api.savePresentation(pres));
const charts = Object.keys(parts).filter((path) => /^ppt\/charts\/chart\d+\.xml$/.test(path));
expect(charts).toHaveLength(1);
// The chart names no typeface, so it draws with the theme's.
expect(strFromU8(parts[charts[0]!]!)).not.toContain('typeface=');
});

it('leaves the theme fonts alone when omitted', async () => {
const plain = await compile(Presentation({ children: Slide({}) }));
expect(api.getPresentationFonts(plain)?.minorLatin).toBe(
api.getPresentationFonts(api.createPresentation())?.minorLatin,
);
});
});
Loading