From c47f6a37f956ed2c0b63923af124349e678bb299 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 25 Oct 2026 11:36:09 +0900 Subject: [PATCH 1/2] feat(dsl): set the theme body and heading fonts from Presentation A deck could only set a font on each Text, so its charts kept the theme's Calibri. Presentation fonts calls setPresentationFonts, which text, tables and charts without a font inherit, and keeps the theme's East Asian fonts, unlike a chart textStyle.font. --- .changeset/dsl-presentation-fonts.md | 5 +++ packages/dsl/README.md | 7 ++++- packages/dsl/src/index.ts | 4 +++ skill/references/tsx.md | 2 +- test/dsl-presentation-fonts.test.ts | 47 ++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 .changeset/dsl-presentation-fonts.md create mode 100644 test/dsl-presentation-fonts.test.ts diff --git a/.changeset/dsl-presentation-fonts.md b/.changeset/dsl-presentation-fonts.md new file mode 100644 index 00000000..76ac0e9a --- /dev/null +++ b/.changeset/dsl-presentation-fonts.md @@ -0,0 +1,5 @@ +--- +'@office-kit/pptx-dsl': minor +--- + +Added `fonts` to `Presentation`: `` 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. diff --git a/packages/dsl/README.md b/packages/dsl/README.md index b29fcf57..38c6ce29 100644 --- a/packages/dsl/README.md +++ b/packages/dsl/README.md @@ -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 | @@ -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, diff --git a/packages/dsl/src/index.ts b/packages/dsl/src/index.ts index 0dd8eb94..2da0fb28 100644 --- a/packages/dsl/src/index.ts +++ b/packages/dsl/src/index.ts @@ -23,6 +23,9 @@ export type PresentationProps = Children & | { source?: never; size?: api.PresentationSize } ) & { theme?: Parameters[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'; }; @@ -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, diff --git a/skill/references/tsx.md b/skill/references/tsx.md index a9bc0f70..cf476154 100644 --- a/skill/references/tsx.md +++ b/skill/references/tsx.md @@ -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`. | diff --git a/test/dsl-presentation-fonts.test.ts b/test/dsl-presentation-fonts.test.ts new file mode 100644 index 00000000..56aa434d --- /dev/null +++ b/test/dsl-presentation-fonts.test.ts @@ -0,0 +1,47 @@ +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 slide = api.getSlides(pres)[0]!; + const chart = api.getSlideShapes(slide).find((shape) => api.getShapeChartSpec(shape))!; + // The chart names no typeface, so it draws with the theme's. + expect(api.getShapeChartSpec(chart)?.textStyle?.font).toBeUndefined(); + }); + + 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, + ); + }); +}); From 9347b57a21373044b4e341f6191487a180dd8b2b Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 25 Oct 2026 14:35:47 +0900 Subject: [PATCH 2/2] test: satisfy the chart spec types in the new tests --- test/dsl-presentation-fonts.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/dsl-presentation-fonts.test.ts b/test/dsl-presentation-fonts.test.ts index 56aa434d..dbbeac26 100644 --- a/test/dsl-presentation-fonts.test.ts +++ b/test/dsl-presentation-fonts.test.ts @@ -1,3 +1,4 @@ +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'; @@ -32,10 +33,11 @@ describe('Presentation fonts', () => { // East Asian text keeps the theme's own East Asian and script fonts. minorEastAsian: null, }); - const slide = api.getSlides(pres)[0]!; - const chart = api.getSlideShapes(slide).find((shape) => api.getShapeChartSpec(shape))!; + 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(api.getShapeChartSpec(chart)?.textStyle?.font).toBeUndefined(); + expect(strFromU8(parts[charts[0]!]!)).not.toContain('typeface='); }); it('leaves the theme fonts alone when omitted', async () => {