Repository navigation
v3.5.0
This release makes a text edit land where the text is actually read. Paragraph.text and
DiagramPoint.text are settable, and the SmartArt setter writes the fallback drawing cache
that every renderer without a layout engine paints from, not just the data model PowerPoint
reads. The diagram data model gains its tree (Diagram.nodes, Diagram.point()) and a link
from a node to the shape drawn for it. importSlide stops copying chrome the destination
already holds, which takes a duplicate layout and master off every import between decks
templated from one file, and with it off the carried slides ts-pptx/script emits. And two
read surfaces stop losing graphic frames: inspectPptx reports them instead of skipping
them, and a SmartArt slide is copied rather than transcribed into a script with a hole in it.
Added
-
Paragraph.textis settable.Run.text,TextFrame.text,Shape.textand
TableCell.textcould all be assigned; the paragraph in between could not, so replacing
one bullet of a list meant either collapsing the whole frame or rewriting runs by hand.
Setting it replaces that paragraph with a single run, keeping the first run'sa:rPrand
the paragraph's owna:pPr(level, alignment, bullet), and leaves sibling paragraphs
untouched — the same rule the SmartArt drawing-cache mirror already ran on internally. -
DiagramPoint.texthas a setter, and it keeps the drawing cache honest. A SmartArt
diagram stores every string twice: as authored nodes inppt/diagrams/data{N}.xml, which
is what PowerPoint reads, and as a copy of every drawn string in
ppt/diagrams/drawing{N}.xml, which is what every renderer without a SmartArt layout
engine paints. Editing a node throughtextFramewrote only the first, so the change was
invisible in LibreOffice, Google Slides, thumbnailers and web previews until someone opened
the deck in PowerPoint and saved it. The new setter writes both, replacing the one paragraph
of the drawn shape that belongs to that node and leaving its siblings byte-identical (one
shape commonly draws three nodes at once). Geometry is not recomputed, which is inherent
rather than a defect: the drawn shape keeps its cached size, so a much longer string
overflows its box in those renderers until PowerPoint re-lays the diagram out.textFramestill edits the data model alone and now says so; it is the escape hatch for
run-level formatting. Seedocs/reference/pptx-read.md. -
Diagram.nodesgives the data model its tree, andDiagram.point()resolves an id.
connectionshands back the rawparOfedges, so every consumer that wanted "the top-level
nodes, in order" wrote the same index-and-walk, and a hierarchy read as a flat list with no
indication of depth. ADiagramNodecarries itspoint, itschildren(in@srcOrd
order), itsparentand itslevel. Assistant (asst) points are in the tree and keep
their own type; transition points are not, because they label an edge rather than a node and
are reached throughDiagramConnection.parentTransitionId/siblingTransitionId. A
parOfcycle raisesdiagram/parent-edge-cyclerather than hanging or silently dropping
the points it swallows. -
DiagramPoint.drawnShapeandDiagramPoint.presentationIdlink a node to what is drawn
for it.drawnShaperesolves thedsp:spof the fallback drawing part that carries the
point's text, plus the paragraph index inside it, as aTextFramebound to the drawing
part. The mapping is not the obvious one and is many-to-one in both directions: a
dsp:sp/@modelIdis always a generatedprespoint's id rather than the authored point's,
one shape draws several points at paragraph indices that can contradict document order, and
one point has several presentations of which at most one holds text.nullis a defined
outcome, covering a package with no drawing part, a point with nopresOfedge (every
unlabelled transition point), aprespoint that draws nothing, and a drawn shape with no
text body. Measured over five layout families and 90 authored points.
Changed
-
importSlidebinds to chrome the destination already holds instead of copying it in
again.copyPartwalked the source slide'sslideLayout -> slideMaster -> theme
subgraph and copied every part of it under a fresh partname, with no notion of what the
destination arrived with. For the deck templated from its own source (fromTemplatekeeps
a package's chrome byte-identical and strips only its slides, which is exactly what
ts-pptx/script's template-anchored tier emits) every one of those parts was already there
under its own partname, so each imported slide grew the deck a duplicate layout and master:
one extra entry in PowerPoint's layout picker per slide, and a later
appendSlides({ layout: <name> })that threwlayout/ambiguous-namebecause two layouts
answered to the name.A part is now reused when the destination holds it at the same partname with the same
content type, the same bytes as they would be written, the same relationships, and the same
test passing recursively; a master must also be registered inp:sldMasterIdLstand a
layout must sit in a registered master's gallery. The decision is made at the page boundary
and never below it, so a part reached from something this import copied is copied too and
copied chrome stays self-contained. Anything short of identical still copies.Migration. Importing between two decks that share byte-identical chrome (the same file
opened twice, or two decks made from one template) now adds fewer parts: no new layout,
master or theme, and the imported page resolves to the destination's own. Rendering is
unchanged, since reuse only happens where the bytes were the same. A consumer asserting on
part counts after such an import sees the smaller numbers. -
A converted script's carried slide no longer costs a layout, and batches bind by name
again.ts-pptx/script's template-anchored tier copies a slide the write API cannot
author (slide.carried) withimportSlide, out of the very file it templated the deck
from. That import used to duplicate the slide's layout and master, and because the duplicate
repeated a layout name, it demoted everyappendSlidesbinding in the emitted script from
the name to a gallery position. WithimportSlidereusing chrome the destination already
holds, layout names stay unambiguous and bindings print as{ layout: 'Titelfolie' }. A
multi-master deck that genuinely repeats a layout name still falls back to a position, which
is the case the fallback was always for. -
inspectPptxreports graphic frames. Ap:graphicFrame(table, chart, SmartArt, or a
payload this library does not model) was skipped outright and consumed nozIndex, so a
deck whose slides are SmartArt or tables inspected aselements: []withwordCount: 0,
indistinguishable from a deck of blank slides. It also put the two read surfaces in flat
disagreement:Slide.texton the deep model has always flattened table cells and SmartArt
node text.A frame now comes back as one element with
kind: 'graphicFrame', its own box, azIndex
in the shape-tree walk, and a newgraphicKindfield ('table' | 'chart' | 'chartEx' | 'diagram' | 'other'). Itstextis what a reader sees on the slide, so it counts toward
the slide'stextandwordCount; a chart contributes none, matchingSlide.text, which
treats data labels as chart data rather than slide body text. The structure is still not
flattened:textRunsandparagraphsstay empty, and cells, series, and nodes are reached
throughts-pptx/read.Migration.
PptxSlideElementKindgains'graphicFrame', so an exhaustiveswitchover
it must handle the new member;PptxSlideElementgainsgraphicKind,nullon every other
kind. A consumer that counted elements, summedwordCount, or readzIndexvalues sees
different numbers on decks containing graphic frames. Filter onkind !== 'graphicFrame'to
get the old element set back (the oldzIndexnumbering is not recoverable, and was itself
a walk with holes in it).
Fixed
-
A slide holding SmartArt is no longer emitted as a script with a hole in it.
ts-pptx/scriptdecides per slide whether the emitted script can describe it or whether the
printer must copy the source slide verbatim, and that test named extended charts and nothing
else. But three graphic-frame payloads produce no call, not one: an extended chart, a
SmartArt diagram, and a frame the reader does not decode at all (an OLE object, ink, a 3-D
model). So a chartEx slide round-tripped correctly by being copied while a SmartArt slide
was transcribed and silently lost its diagram, with only a per-shape fidelity note recording
the loss. Both sites now share one predicate, so they cannot drift apart again.The copy costs nothing in the layout gallery, because
importSlidenow binds to chrome the
destination already holds (see above) and the template-anchored tier copies out of the very
file it templated the deck from. Layout handles are still resolved up front rather than
where first used, since animportSlidebetween twoappendSlidescalls moves the gallery
underneath them.Migration: a deck whose SmartArt, OLE or ink slides previously came out
authorednow comes
outcarried. The emitted script imports those slides from the template instead of
rebuilding them, which needs the source deck present (it already did for the template
itself). The standalone tier is unchanged: it has no source package to copy from, so it
still transcribes such a slide and still reports the per-shape note.