100 Commits
Author SHA1 Message Date
Mike Kaganski a36c7459d1 Fix unit test testPieTextArea
CppunitTest_oox_shape failed in the gerrit_linux_gcc_main and
gerrit_linux_clang_dbgutil_main builds like

    oox/qa/unit/shape.cxx:1170:testPieTextArea::TestBody
    double equality assertion failed
    - Expected: 6673
    - Actual  : 6691
    - Delta   : 10

The test from 2ac8858b10699c897dd530c8b1434bd9604a4aff (tdf#149918 fix
pie in presetShapeDefinitions.xml, 2026-10-06) checked the BoundRect of
the shape, which includes the ink of its text; text layout changes merged
meanwhile probably moved that ink. Check the text frame that the preset
defines instead; it does not depend on fonts or line metrics.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ica16e4457cc150e9115dfc9994abd3f801778621
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/14447
Reviewed-by: Andras Timar <andras.timar@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 0bcc06cdc592bf7c9828b694b00acb7fed07eccf)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211889
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-10-06 21:18:29 +02:00
Mike Kaganski 4f8889f928 vcl: anchor the object-stream test on /Type/ObjStm
testEncryptedTaggedUsesObjectStreams searched the whole exported file
for /ObjStm, which binary data can hold, and every later search starts
from that match. The writer emits /Type/ObjStm, with /Length and the
stream data after it in the same dictionary.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ibb1417ee9bd399ea183883d2849f02a4e3d945c4
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13491
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 8171259354f2e36fddcdb815d393d7d60c303e01)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211702
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-10-02 11:05:31 +02:00
Mike Kaganski c5592ec7db tdf#163240 sw: build a link's rectangles from its line pieces
Exporting a hyperlink laid out down the page, over more than one line,
produced link annotations a PDF/UA validator rejects: on a vertical page
whose link wraps, two of the three annotations carried no structure
element, and veraPDF reported both under ISO 14289-1 7.18.5:

 A Link annotation is an Artifact or is nested within null tag
 (standard type = null) instead of Link

Export a document whose hyperlink ends in a ruby and runs over several
lines down the page to PDF with tagged PDF enabled, then run veraPDF
over the result in PDF/UA-1 mode to see it.

An annotation is tied to a structure element by the text painted inside
it, so one holding no text of any line is tied to nothing. That is what
the region CalcFrameRects returns can hand out: it covers the selection
in whatever rectangles fall out of inverting the pieces it was built
from, and one of those can span several lines while another holds a ruby
and none of the text beside it.

Let CalcFrameRects return those pieces as well, and the holes it cuts
afterwards, and take the shape from the pieces and the area from the
region: the pieces follow the lines and, once the overlapping ones are
joined, no longer overlap each other, so every part of the region falls
in exactly one of them. What tdf#157816 requires still decides each
part, and the parts a piece has left are joined unless the join would
reach over a hole - a fly, a drop cap - that the selection excludes on
purpose.

Every annotation now holds text of the lines it covers, so every one of
them is tagged, and a ruby sits inside the rectangle of the line that
carries it. The narrowing to a portion's band, the growing over a ruby
portion and the joining of tiling rectangles that
Ide896d00c7619b772802762e85abfb6e2cf7ef0a ("tdf#163240 sw: let a link's
rectangle cover the ruby it ends in") added are all gone. On the
horizontal document that change was measured against, the exported
rectangles are the same to the last digit.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I9faf2bc10af491576cc2f4ba5f3e3433eb03d16c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13767
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit e77b1d25d3601315f80169e4e811d74ef2ff0374)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211704
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-10-02 09:52:04 +02:00
Mike Kaganski 8c112eb826 tdf#163240 sw: let a link's rectangle cover the ruby it ends in
A ruby makes its line taller than the line's own text, so for a
hyperlink that ends inside one the selection region for that line was
split into two rectangles: the band holding the line's text, which runs
on under the ruby's base, and above it a band reaching only as far as
the ruby begins. Neither covered the phonetic annotation standing above
the base. Each became a link annotation in the exported PDF, so clicking
the phonetic annotation did nothing, while a 6.9pt annotation stood over
blank space carrying no structure element, which veraPDF reported under
ISO 14289-1 7.18.5:

 A Link annotation is an Artifact or is nested within null tag
 (standard type = null) instead of Link

Export a document whose hyperlink ends in a ruby to PDF with tagged PDF
enabled, then run veraPDF over the result in PDF/UA-1 mode to see it.
The same document with its text running down the page draws the clause
twice.

The rectangle a hit test returns for a character is as tall as the whole
line, ruby and all, so GetCursorRectsContainingText, which
6593d680e2 (tdf#157816 sw: PDF export:
filter out links on empty space, fields, 2023-11-02) added to drop a
rectangle holding no text, kept the empty band. Ask for the real height
and the comparison narrows to the band of the portion the character is
in. Dropping the empty band would leave the phonetic annotation
uncovered, so grow the surviving rectangle over the ruby as well, using
the extent GetCharRect reports for a position inside a ruby portion.

Where the text runs down the page a band's height is measured along X,
which the real height carries as a negative height, so narrow across the
line there instead. The region split still hands such a line back in
pieces, and the piece holding the ruby holds none of the line's text, so
nothing paints in it and nothing tags it. Pieces that tile one strip of
a line are therefore joined into one annotation. Where the text wraps
onto a further line the pieces no longer tile, and such a page keeps an
annotation a validator rejects.

veraPDF no longer reports 7.18.5, in either writing direction, for a
line that does not wrap, and the phonetic annotation is inside the
link's rectangle. An annotation is a plain box, so a ruby over part of a
link raises that line's whole rectangle, and a link ending partway
through a ruby's base widens it to the end of that base.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ide896d00c7619b772802762e85abfb6e2cf7ef0a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13704
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 3c9aede98b24d272e6dfcae0f3b898777fa89d44)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211703
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-10-02 09:51:58 +02:00
Mike Kaganski 4998ea8631 sw: emit a Ruby's RB before its RT
ISO 32000-2 Table 369 makes the order normative: a Ruby shall contain
one RB followed by an RT, or by RP, RT and RP. A ruby's two sub-lines
paint in visual order, though, and each opened its structure element as
it painted, so an exported ruby held its RT before its RB. PAC 2026
reports that as two errors per ruby; veraPDF does not report it.

An element is ordered among its siblings where its type is set, not
where it is painted, so both kids are created and typed before either
sub-line paints, and each sub-line then enters the kid already created
for it.

Which sub-line carries the annotation follows OnTop, while bRubyTop only
reflects where a squared grid puts the annotation and is true everywhere
else. Keying the role off bRubyTop tagged the base of a ruby positioned
below outside a squared grid, or to the right, as the RT and its
annotation as the RB, so both the role and the element follow OnTop now.
A squared grid with the ruby text below was already right, bRubyTop and
OnTop agreeing there.

The kids are created only where BeginInlineStructureElements would enter
them: a ruby in the repeated headline row of a follow table would
otherwise leave an empty RB and RT on the table, once per following
page.

testLinkWithRuby asserted only that both kids are present, the order
being wrong when it was written; it compares them in order now.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I8e38a3544008f79939d7d0795c4808a1a72caad7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13402
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 65c62bc31aff793465f31ab6172c118921fe9d51)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211690
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-10-01 22:41:28 +02:00
Mike Kaganski eec52071ef tdf#157028 vcl: use object streams in an encrypted file too
Object streams and a cross-reference stream shrink a tagged PDF, but
I1e27dc3a8f43aa8b6c75ae930615c8d4b4645627 ("tdf#157028 vcl: write the
structure tree into object streams") left an encrypted export out of
both, so a password kept the whole structure tree uncompressed. The
spreadsheet reported in tdf#166233, attachment 171182, exported tagged
at 2368642 bytes, and at 13465120 with a password. The protected export
is 2389620 bytes now, and what it has over the plain tagged one is what
encryption itself costs.

This writer encrypts a string as it builds the object. ISO 32000-2 7.5.7
forbids encrypting a string in an object stream separately: the stream
that carries it is encrypted instead. So the strings of a stream-bound
object go in plain now, and the object stream is encrypted as a whole,
with the cipher set up for the stream's own object number rather than
for the objects inside. The stream's /Length counts what the cipher
adds. Only the structure elements and the ID tree put strings in a
stream, so only those two builders ask getStringEncryptor for one, and
it returns nothing while the file uses object streams.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ia6a063244baf456d7c7368a6bba7a8cbb96e31b0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13370
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 59193295323917bd065ea3a235f7653ad7f9b04b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211655
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-10-01 15:43:44 +02:00
Mike Kaganski 24f40c4b11 tdf#163272 sw,drawinglayer,vcl: give anchored content its own /Lang
With Tagged PDF on, a paragraph in a language other than the document's
gets /Lang on its structure element. What is anchored in the paragraph
is tagged inside that same element and inherits the attribute, so a
frame's text and its description would be declared the paragraph's
language.

0d2d9e656d87647f904f1a463ed9b879c2bfb10c (tdf#163272 sw: give a
paragraph its own /Lang, 2026-09-21) avoided that by putting no /Lang on
such a paragraph, on a check of one node of a text frame that can cover
several. Its text still carried the language, on a Span, while what was
anchored in it specified none and inherited the document's. In a German
document that declared a shape's English text German. See
https://gerrit.collaboraoffice.com/c/online/+/13117/comment/89a7866d_907a2957/

The paragraph specifies its own language now, and a frame anchored in it
no longer inherits it. A Figure, a Formula and a frame's Division
specify the document's where the paragraph's differs, which is what the
frame's own paragraphs are measured against. A drawing shape's text is
tagged by drawinglayer, which takes the locale of the drawn portions and
specifies it where they agree; such a shape is nested in its anchor
paragraph in Writer and in no such paragraph elsewhere, so there is
nothing to compare against.

A drawing shape's own element specifies the document's language too. svx
anchors it under the anchor paragraph, drawinglayer opens it there, and
it carries the shape's title and description. Without the attribute, a
described shape in a paragraph that specifies one would have its
description declared the paragraph's language, and its text likewise
where the portions disagree.

testParagraphLanguage compared a structure type against a language and
quoted no text, so nothing identified the words affected. It lists every
element holding text or a description now, against the language it
specifies, an entry reading "P de-DE: Ein deutscher Absatz mit einem
Bild."; one specifying none is listed without a language, so correct
inheritance is asserted too. PDFiumPageObject gained getMarkedContentID
to join a page object's text to its element.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I1eecf0b56f09fc6d144894910a9037d89884dd3b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13239
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 898a2e1b3a6fc07d7b43315b2f4781b32f1a20d3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211609
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-10-01 10:25:19 +02:00
Mike Kaganski d00d192770 tdf#157028 vcl: write the structure tree into object streams
Turning Tagged PDF on added 2.4 MB to the reporter's document, 2.0 MB of that
its structure tree: 12562 elements, each its own top-level object,
uncompressed, and each costing a further 20 bytes in the cross-reference
table.

The elements go into /Type/ObjStm streams of 200 now, with the parent and ID
trees. The cross-reference has to become a /Type/XRef stream with them,
because a table cannot address an object inside another object. PDF 1.5
introduced both, so a PDF/A-1b file keeps the plain form. So does an encrypted
file, since a string in an object stream shall not be separately encrypted,
and this writer encrypts strings as it builds the object. So does a hybrid
file, because sdext reads the embedded original document out of a trailer.

The reporter's document exported 3157118 bytes tagged before the change and
1086230 after, against 769448 untagged either way. An 8300-row Writer table
exported 4017140 bytes tagged before and 500967 after, against 73183 untagged.

testTdf159066 and testTdf173162 grep the exported bytes for something the
structure tree carries, /Alt< and the /Namespaces array. They inflate the
object streams first now. testListLabelPDFExport counted its List element in
the token stream, and asks GetObjects() for it.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I1e27dc3a8f43aa8b6c75ae930615c8d4b4645627
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13209
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit ad4bb8ae080aba98080dfaabd688dfdb48cf7ade)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211556
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-30 13:25:11 +02:00
Mike Kaganski e4efd9faf2 sw: compare positions, not pointers, when looking for a format redline
When a paragraph had a tracked format change before the selection,
formatting another author's tracked format change and rejecting the new
change kept the other author's formatting.

Since commit ee3171adb2 (tdf#143918 sw:
fix reject of multiple changes, 2021-08-19), lcl_SetRedline looked for
an existing redline over the range, and continued while
"pRStt <= rRg.Start()", which compared the addresses of the two
positions, not the positions themselves.

Compare the positions, and go on while the redline does not start after
the end of the range, because since commit
b5d0868ef42135beb0329ecb9302ce48b08fa2a8 (cool#15941 sw interdependent
redlines: fix creating a fmt on top of ins-then-fmt, 2026-06-17) a
redline inside the range counts as well.

For some reason, the added unit test fails without the fix only on
Windows; on Linux on my system, it passes without the fix.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I396567d8507e800fefcdc643a63ee6ad91b8b8ee
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13802
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 2da0c4fa2e5284b25f0dca85243a1f8ef5e9506b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211602
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-30 12:20:44 +02:00
Mike Kaganski 984efa1ff7 Simplify ChartExport::exportHatch
Make it fill the Hatch struct in all three cases, and only write it in
one place, for consistency.

Change-Id: Ibf4eb97e1c7046a5fe7397509fff9560140b5eef
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13683
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit f14a9a4d3d45b08da075aa5a34b8d7daa5cae6a4)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211597
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-30 12:19:32 +02:00
Mike Kaganski 67346d08e9 tools: keep the input deflate could not take in one pass
ZCodec::Compress read a fresh chunk over whatever deflate had left unconsumed.
deflate stops as soon as its output buffer is full, and the write-back that
frees that buffer only ran at the top of the next iteration, by which time the
tail of the previous chunk was gone.

Nothing said so. EndCompression answers with the number of bytes taken and no
caller compared it, so the result was a shorter document than the one handed
in, compressed perfectly well. Passing 100000 bytes through a 64 byte output
buffer took 49152 of them.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I1044a46aa1d1f424e7d699a927e01a8427768919
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13208
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 23e2e9c2136d8b5c9fd6d9604bccd35e9b657571)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211555
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-29 15:58:29 +02:00
Mike Kaganski a0c08a780e vcl: let PDFDocument list the objects an object stream holds
GetElements() hands out the tokens the file itself carries, so a caller
hunting structure elements finds none of those a /Type/ObjStm holds - every
one of them, in a file from a producer that compresses its objects. They are
there already: Tokenize unpacks each object stream it passes and registers
what it finds, so only an accessor was missing.

The 64 places that scanned the token stream for an object ask GetObjects()
instead.

testTdf57423 and testStyleNamedStructureTypes compared the order as well. The
map is keyed by object number, which for a structure tree is document order,
where the token stream carried the emission order that writes a child before
its parent - the type sequence reads "Document Table TR TD" now instead of
ending "TR Table". Both hold the same 22 elements; testTdf57423 stopped
indexing its figures and compares one string per kind.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I3e46e600e0aed3802f5ee2f8c6dc25a50002763d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13207
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 50bf4f7acb344520ce2055bddfaf844c23239a8e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211450
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-29 12:32:49 +02:00
Mike Kaganski 0b3a0fae8c Related: tdf#166401 fix writing properties for empty placeholder
Regression after commit b41a780d139ec6ca5a5514db972ccf0e50058dfc (tdf#166401:
support custom placeholder prompt text, 2026-08-17), where a placeholder that
authored no prompt stopped writing text and got a bare <a:p/> instead.

On a slide an empty presentation object reports no text through UNO anyway, so
the paragraph the export wrote for such a placeholder never had a run. It
carried the properties, and those are the formatting. In PowerPoint 2016 the
original title showed white at 22pt, indented 56.5pt; the round-tripped one
showed bold black at 17pt, indented 18pt.

Write the paragraph the way a shape with no text already does: the paragraph
properties, the run properties in endParaRPr, and no run. The prompt still
stays out of the file. That branch of DrawingML::WriteText is now a method of
its own, called from both places.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I034a4e14f388c90872069d32b303c6c143c1814c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10768
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit e2859f5b23f53281d80cbad4c56db1764aaf1406)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211538
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-29 08:45:18 +02:00
Mike Kaganski 8343c6e702 vcl: size an appended cross-reference stream's offsets by the file
WriteXRef wrote /W[1 3 1] and three offset bytes whatever the document.
Signing a PDF larger than 16 MB recorded every offset in the appended update
modulo 16777216, so the updated catalog and the signature itself pointed into
the middle of the original body.

The field is as wide as the largest offset needs now. /DecodeParms/Columns
counts the bytes of a row apart from its predictor byte, so it follows the
field rather than staying at 5, and each byte is shifted out of the offset
rather than masked with an int too narrow to hold the mask.

Reachable only from a file whose cross-reference is already a stream, which is
why our own exports never met it. It has no test: the only path that appends a
cross-reference section is signing, and that needs a certificate.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I08279fcb43d0c538f66dc029890a58a76f6dab7e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13206
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 7e52e9eabbda9de67fedab5bf96a71902e53bc28)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211445
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-25 12:02:23 +02:00
Mike Kaganski 010611712f vcl: read a cross-reference stream that carries no predictor
PDFDocument sized every row of a /Type/XRef stream as one predictor byte plus
the /W fields and read the fields from offset one, and the no-prediction arm
of the filter never copied the row it had read. /Predictor defaults to 1, so
a stream written without /DecodeParms decoded as nothing but free entries. It
gave up entirely when /Filter was absent, which is as legal.

An indirect /Length is resolved through those offsets. With none of them the
tokenizer cannot skip a stream body, so it reads the compressed bytes as
tokens and Read() fails on the file.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I4929e879e02ba91dce8280421f129f91dba9074d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13205
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 3adab88af78016314f210f2be66668190b1f3493)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211444
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-25 12:02:18 +02:00
Mike Kaganski 59453af50d tdf#171555 sw: draw a non-breaking hyphen as a plain hyphen
Regression after commit 367ef7c1dca300b1966093de5bbfca8ea97d403a
(tdf#167946: reimplement the fix for tdf#120677, 2026-02-11). A
non-breaking hyphen was shown as an empty box on macOS, and on Windows
in a font other than the rest of the text. Inside a field it wasn't
painted at all, and the brackets around double-lined text came out as
the text they enclose.

SwTextFormatter::NewPortion builds the portion for U+2011 with a plain
'-'. The SwTextSlot in SwExpandPortion carried that character to the
font; dropping the base class sent U+2011 instead. Carlito, Caladea,
Arial and Courier New have no glyph for it, nor for the U+2010
HarfBuzz falls back to.

So declare the substitution in SwBlankPortion. A blank stands in for
one character; it doesn't expand to computed text. Format, measure and
paint all use that character where it differs from the model one; a
no-break space is shown as itself, so tdf#120677 and tdf#167946 stay
fixed. SwTextPortion::Paint splits in two for it, since the
decorations read the index that the swap resets, and a blank with no
length still has a width to paint.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I93af27f8647e7e04014734af7100372fa66018cb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13203
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit e9ce5e4424deb3a28db630fdb540ccfac1f4c0a2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211440
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-25 08:58:35 +02:00
Mike Kaganski f0600a8d88 tdf#163272 sw: give a paragraph its own /Lang
Write a paragraph in a language other than the document's, then File -
Export As - Export as PDF with Tagged PDF on. The paragraph's element
said nothing about the language, and the Span holding its text said it
instead:

 /StructElem /S/Standard /K[6 0 R]
 /StructElem /S/Span /Lang (en-GB) /K[1 2 3 4 5 6 7 8]

A run of text was compared against the language of the document, so
every run of a paragraph that declares one of its own differed from it
and opened a Span. The comparison is against the paragraph now, and a
paragraph unlike the document names its language itself. A run unlike
its paragraph still opens a Span and names its own, which it did not
where it was the document's language the paragraph had departed from.
A Span that carries anything else - a strikeout, a script, a character
style - still stands, without the language it was repeating, and so
does a Link. A paragraph with something anchored in it says nothing,
as before: what hangs there is tagged underneath it and would take the
paragraph's language for its own.

testSpans reads four list items, three of them en-GB in a de-DE
document. It asserted the shape the old rule made - for one of them it
looked for a single Span and took the language off that, and for the
rest it counted the Spans that rule opened. It reads the language off
the paragraph now, asserts that the item at the document's own
language names none, and where an item holds a hyperlink and a
footnote it finds the two Links alone.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I4082891666598793f59d1bf62c33e883ebdebb8f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13117
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 0d2d9e656d87647f904f1a463ed9b879c2bfb10c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211375
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-23 19:56:06 +02:00
Mike Kaganski 5d6c85d78b vcl: a tagged PDF's lengths are in user space units
Indent a paragraph by half an inch, then File - Export As - Export as
PDF with Universal Accessibility (PDF/UA) on. Its element read

 /A <</O/Layout/Placement/Block
 /StartIndent 0.72

where half an inch is 36 points. Every length was a fiftieth of what
it names: a 64.8pt inline formula carried /Width 1.296 and a 481.9pt
table /Width 9.638. Writer passes twips, and the PDF writer prints
thousandths of a point. For an inline Formula the standard makes
Width the sole source of information about the element's extent
(ISO 32000-2 14.8.5.4.6), so the fiftieth is all a reader reflowing
the file has.

A bounding box was right all along, because SetStructureBoundingBox
converts one out of the current map mode. The nine lengths this
writer can set take the same conversion now. The counts and the
identifiers pass through untouched - RowSpan, ColSpan, Language and
the annotation ids.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I4da4d085de9da7e3773294756ff3205b9b1b38aa
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12997
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit ec0a83ea9cacd67b7842a4899e9996b2d0048ce0)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211350
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-23 12:22:03 +02:00
Mike Kaganski f7bfdc2d03 tdf#164049 sw: a formula in a paragraph is not a block
Insert - OLE Object - Formula Object, write E=mc^2 inside a sentence,
then File - Export As - Export as PDF with Universal Accessibility
(PDF/UA) on. PAC 2026 puts 1 warning on the "Formula" structure
elements row: Possibly inappropriate use of a "Formula" structure
element. The element read

 /A<</O/Layout/Placement/Block/BBox[212.05 713.7 276.85 735.3]...>>

Placement was Block for every frame but a table cell and a figure
anchored in the body. A formula is tagged inside its anchor paragraph
though - as character it sits in the text, and CheckReopenTag reopens
the paragraph's tag for the other content anchorings - and ISO
32000-2 14.8.5.4.2, Table 378 reads "When applied to an ILSE, any
value except Inline shall cause the element to be treated as a BLSE
instead". Neither the description, the bounding box nor the size has
a part in it, each ruled out one at a time in an exported file.

The element reads /Placement/Inline now for a formula anchored as
character, to the paragraph or to the character. One anchored to the
page or to a frame is tagged under the document, where a block is
what it is.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I57e5b52c5dd4e34a8588bc1091b13864e6d4497e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12988
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit bae420ab335afaff185046486a3efbad59d6b150)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211349
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-23 12:21:58 +02:00
Mike Kaganski c3bcf7d5f8 tdf#168720 sw: an undescribed formula's /Alt is its source
Insert - OLE Object - Formula Object, write E=mc^2, save and reopen,
then export with File - Export As - Export as PDF and Universal
Accessibility (PDF/UA) on. PAC 2026 fails it under Alternative
Descriptions, the element saying nothing about what it holds:

 <</Type/StructElem/S/Formula /P 5 0 R /Pg 1 0 R ... /K[1 ]>>

Writer takes a frame's Alt from the title and description a user
typed; a formula has neither unless asked. A tagged export falls back
to the formula's own source now, so the element says E=mc^2 and PAC
passes that checkpoint:

 /Alt <FEFF0045003D006D0063005E0032>

A title or description still wins. The report asks for an option; a
formula with neither said nothing at all, so the source goes in with
none to switch it off. Reading the source needs the object running,
so the export runs each undescribed formula and puts it back. PAC's
separate Structure tree warning still stands.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I09a68eff3d15981a7e112f79409b3448bceb4bc0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12987
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 5b84c1873533bcdd98d917e8b6fb6d149a238047)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211348
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-23 12:21:47 +02:00
Mike Kaganski d663a07822 sw,vcl: name the heading a TOC item lists
Insert - Table of Contents and Index, then export with File - Export
as PDF, Universal Accessibility (PDF/UA) on and PDF version 2.0. The
file fails ISO 14289-2 clause 8.2.5.8 once per entry, which veraPDF
reports as:

 Each TOCI in the table of contents shall identify the target of the
 reference using the Ref entry, either directly on the TOCI structure
 element itself or on one of its child structure elements

An item named no target at all:

 <</Type/StructElem/NS 2 0 R/S/TOCI /P 8 0 R /Pg 3 0 R /K[10 0 R ]>>

It names the heading now, in the /Ref entry PDF 2.0 added:

 <</Type/StructElem/NS 2 0 R/S/TOCI /P 8 0 R /Pg 3 0 R /Ref[ 15 0 R ] /K[10 0 R ]>>

Object 15 is the /S/H1 the entry links to, found from the mark its
URL names. An item that links to a table, a frame or an object, or
that has no link at all, still names nothing.

The document of testTdf173162 linked its entries to
bookmark alone now, as Writer writes them, and the document passes
the profile, so the test asks for PDF/UA instead of plain tagging
and the harness validates it.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I76e73684d2ab09f187f56cab093d13980150d737
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12750
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit ee49bf58e7d3eb3ff94677e9a51c73b76eb51a99)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211338
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-23 00:04:53 +02:00
Mike Kaganski 07ccc05cf7 sw,vcl: name a footnote FENote at PDF 2.0
Put a footnote in a document and export with File - Export as PDF,
Tagged PDF on and PDF version 2.0. The file fails ISO 14289-2 clause
8.2.5.14 once per footnote. veraPDF, on the PDF/UA-2 profile, reports:

 The Note standard structure type shall not be present in conforming
 documents unless role mapped to a structure element in the PDF 2.0
 namespace

The element carried the type PDF 2.0 replaced, in the 1.7 namespace
ISO 14289-2 8.2.4 accepts alongside the 2.0 one:

 <</Type/StructElem/NS 2 0 R/S/Note /ID (id29) /P 28 0 R /Pg 3 0 R ...>>

Writer asks for FENote now, so the element lands in the 2.0 namespace
and the check stops failing:

 <</Type/StructElem/NS 1 0 R/S/FENote /ID (id29) /P 28 0 R /Pg 3 0 R ...>>

Below PDF 2.0 the file still says Note, the only footnote type
ISO 32000-1 has.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I4674f4f902621ee8765121782799f42cc59c5e6e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12749
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 42433cf89b23328991e7a64daea4f771b60f728a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211337
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-23 00:04:49 +02:00
Mike Kaganski 3e517fbfe9 vcl: give each style a structure name of its own
Write a paragraph in Writer's built-in Index paragraph style and
export with Tagged PDF switched on. The element comes out as
/S/Index, over a role map that reads

 /Index/P

so the standard Index type means a paragraph, document-wide. veraPDF,
on the PDF/UA-1 profile:

 Standard tags defined in ISO 32000-1:2008, 14.8.4, shall not be
 remapped

A style called Artifact went further. Its paragraph reached the
content stream beside the marker for content that is not part of the
structure at all, the two being one name:

 /Artifact BMC
 /Artifact<</MCID 10>>BDC

Writer names a structure element after the style that produced it,
and the role map records what that name means, one entry per name.
Styles collide there with the standard types, with each other, being
separate families for paragraphs and characters, and with the names
Writer asks for itself, Source Text writing Code literally.

A name a standard type has, or another alias asked for, is now passed
over for the first indexed name beside it that is free, so the Index
style writes /S/Index-1 over /Index-1/P. PDF 2.0 has standard names
1.7 lacks: a style called H7 gives way there, while a heading at
outline level 7, which Writer asks for by that same name, keeps it
and needs no entry at all. The synthetic Div containers ask for no
name now, and carry the Div tag as before.

The test document remapped four standard tags at PDF 1.7 and six at
2.0; now none at either.

Found while reproducing tdf#165454.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I63e9116e5db17bdb554b508a864e313a77f40bc2
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12361
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 54e0207ff6bcf937187a395912f1ed57b60d90e3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211314
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-22 20:49:36 +02:00
Mike Kaganski cfbbd638ab tdf#168638 sc: tag a database range's labels as TH
Mark a table with Data - Define Range and the option "Contains
column labels", and export with File - Export as PDF and Tagged PDF
on. Every cell of the range comes out the same:

 <</Type/StructElem/S/TD /P 7 0 R /Pg 1 0 R /K[9 0 R ]>>

so nothing in the file marks the row that names the columns.

The label cells are TH now:

 <</Type/StructElem/S/TH /P 7 0 R /Pg 1 0 R /A <</O/Table/Scope/Column>> /K[9 0 R ]>>

A range sorted left to right holds its labels in the first column and
gets /Scope/Row there. A range Calc defines itself for a sort or an
AutoFilter counts the same, on the header row it guessed.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I5f4d3ec709f330604d518e1bf0c89c1655b18816
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12657
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 8a524317421379ba257fcfba04f63f06da7cdcc1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211174
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-21 00:21:15 +02:00
Mike Kaganski fe55fbaa27 sc: tag a sheet the page range starts inside
Export a two-sheet spreadsheet with File - Export as PDF and a page
range that leaves out the second sheet's first page - "1;3" where
that sheet spans pages 2 and 3. The file gets one bookmark where it
should get two, the second sheet having none. Switch Tagged PDF on as
well and it also carries one Worksheet element, holding both sheets:

 5 0 obj<</Type/StructElem/S/Worksheet/P 4 0 R/Pg 1 0 R/K[6 0 R 22 0 R]>>

where 6 is the first sheet's table and 22 the second sheet's.

The per-sheet work ran on

 nRenderer == nTabStart || bIsFirstPage

but the renderer index is the page's own index in the document and a
page outside the range is never rendered at all, so a sheet whose
first page is left out never reaches it. It asks whether the sheet
changed since the last rendered page now.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I158feb5ca4a6f4501e6dfdf5234c8fdf75b8ee40
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12571
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 49e441a059f98d703ce2bddd5b0eabf80b7144aa)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211173
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-21 00:21:10 +02:00
Mike Kaganski 5c7062bde2 tdf#171022 sc: name the element a cell's hyperlink points at
Give a cell a link to another sheet with Insert - Hyperlink. Export
it with File - Export as PDF, Tagged PDF on. veraPDF, on the PDF/UA-2
profile, fails clause 8.8 once per such link:

 Destination in Outline item, OpenAction or Link annotation is not a
 structure destination

The link annotation named a page and an area on it, and that is all:

 /Dest[28 0 R/XYZ 56.693 445.607 0]

I56c2f517977d4b876e463f4bcadc53f8ae444b08 ("tdf#171022 sc: name the
element an outline item points at") gave the export a list of
destinations to name an element for, resolved once the last page is
drawn. A hyperlink goes in that list too. The export now keeps each
cell's element as the cell is drawn, so a hyperlink onto a cell names
that:

 /A<</Type/Action/S/GoTo/D[28 0 R/XYZ 56.693 445.607 0]/SD[33 0 R/XYZ 56.693 445.607 0]>>

/SD names an element where /D names a page. Object 33 is that cell's
/S/TD, and its /Pg is that page. A sheet's /S/Worksheet has the
sheet's first page for its /Pg, so it can stand in only for a
destination there. A link onto a cell nothing was drawn for names the
sheet then, and keeps the plain destination on a later page. So does
a link to a sheet that prints nothing. The test document goes from
four failures of the clause to none.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I3b1dc51e9962ac19061934d447da9542ee56b0fd
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12393
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 51a84acd2fccf7120959ff7ff16a0549ddb34f58)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211172
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-21 00:21:06 +02:00
Mike Kaganski 9ec63cdce0 tdf#171022 sc: name the element an outline item points at
With Tagged PDF and Export outlines both on, export a two-sheet
spreadsheet with File - Export as PDF. veraPDF, on the PDF/UA-2
profile, gives two failures of clause 8.8, one per sheet:

 Destination in Outline item, OpenAction or Link annotation is not a
 structure destination

Each sheet's outline item named a page and an area on it, and that is
all:

 /Dest[1 0 R/XYZ 0 792 0]

Calc creates the outline item before drawing the sheet, and opens the
Worksheet element only while drawing. So the destination can't name
the element where it is made. The export state now outlives one
sheet: it keeps each sheet's element, and each destination with the
sheet it points at. The last page joins the two:

 /A<</Type/Action/S/GoTo/D[1 0 R/XYZ 0 792 0]/SD[5 0 R/XYZ 0 792 0]>>

Object 5 is the first sheet's /S/Worksheet, and the clause passes.
The Writer half is Ic76d80d46f130d549ebaacf175393a3d6ec77998
("tdf#171022 sw,vcl: name the element a link points at"), which added
the vcl call used here. A hyperlink to a cell still gets a plain page
destination, and needs its own change.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I56c2f517977d4b876e463f4bcadc53f8ae444b08
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12366
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 3571fc6be8f8e33ce25a1e83ac4d5dcd8da6494a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211171
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-21 00:20:59 +02:00
Mike Kaganski c3cae7d482 drawinglayer,svx: ask for an artifact instead of faking a tag
I9431c460cade3d432cb7abf76930e23f8106a7f1 ("tdf#168462 svx: mark the
slide background as an artifact") left page decoration reaching its
/Artifact by a lie: VclMetafileProcessor2D emitted one only for a
background image, and dropped a tag naming NonStructElement, so the
tag claimed to be a Division and an image. Decoration now says so
itself: the artifact decision reads the decorative flag, and the
caller passes NonStructElement and no image and means them. A
background image keeps its own path to an artifact.
isTaggedSdrObject() becomes opensStructureElement(); its comment
still claimed every one of these primitives comes from an SdrObject.

The exported file is unchanged. The one call that goes away with the
old predicate, alternative text on a tag that opens an artifact,
could not reach the file either: setAlternateText writes nothing
without m_bEmitStructure, and checkEmitStructure() is false inside a
NonStructElement. operator== now compares the element and the
decorative flag, which decide what comes out and were not compared.

Letting a master slide's own shapes out as artifacts does not follow
from treating every background as decoration. A background object can
hold tagged children, and a Paragraph begun inside an artifact opens
no marked content at all; tried on testTdf135192, three pieces of its
content came out in no marked-content section. Something narrower has
to tell the two apart.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Id0fc0d9bee36e8182702d77e9ac9647709a63f68
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12321
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 9353a1cac3e8056d175acc29dbeecc79ff1cdcd4)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211170
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-21 00:20:39 +02:00
Mike Kaganski f1e4a0f0ab tdf#168462 svx: mark the slide background as an artifact
A slide with a background, its own or its master's, exported to
tagged PDF with that background as untagged content. ISO 14289-1 7.1
leaves no third state, and veraPDF says so once per background:

 Content is neither marked as Artifact nor tagged as real content

Page decoration is wrapped as an artifact in
ViewObjectContact::getPrimitive2DSequence, which the background never
reached: the master page descriptor builds its own hierarchy and
emitted the fill straight into the visitor. It now takes the same
wrapper as the page decoration beside it - the machinery of commit
81ef846485 ("(related: tdf#135192) svx:
PDF/UA export: tag background as Artifact", 2022-12-01), whose author
hoped there that "anything that's not SdrObject can be tagged as
Artifact".

The wrapper has to lie to be heard: VclMetafileProcessor2D drops a
tag that is a NonStructElement, and drops a background tag that is
not an image, so page decoration goes out as a Division claiming to
be an image. That second refusal is also why a shape on a master
slide, unless it is a graphic, is untagged to this day. LATER: let the
processor emit an artifact for a plain background and stop dropping
one, and both go.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I9431c460cade3d432cb7abf76930e23f8106a7f1
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12311
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit b11283cd97e1170688a75f4229fd43bd8298e276)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211166
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-18 22:42:53 +02:00
Mike Kaganski 32dec34df6 tdf#171022 sw,vcl: name the element a link points at
Exporting a document to PDF/UA-2 produced a file that fails
ISO 14289-2 clause 8.8, once per outline item and internal link.
veraPDF reports

 Destination in Outline item, OpenAction or Link annotation is not
 a structure destination

The destination named the page and an area on it, and that is all:
what was linked to could only be worked out by looking at what the
layout had put at that position.

A destination for a link or an outline item is now written as a GoTo
action carrying a structure destination beside the page one
(ISO 32000-2 12.3.2.3):

 /A<</Type/Action/S/GoTo/D[<page> 0 R/XYZ ...]/SD[<elem> 0 R/XYZ ...]>>

sw creates a destination before its target is tagged, so the node is
remembered while the cursor is on it, and matched against the tags in
the pass that already runs after the last page. Creating one and
remembering its node is a single call now, which every destination a
link or an outline item points at goes through: bibliography entries
and links in the text of a shape are named as well.

The cursor is not always on the target: a jump that selects a frame
or a drawing object, and one that finds nothing, move it nowhere, and
where it was left standing on the link, that link's own element must
not become the target. The pass over a shape's links can instead
start with the cursor on the target already, where a jump that did
reach it shows no move either, so there the mark names its own node.
Those keep writing /Dest, as do a target that was never tagged and
one whose element was never emitted.

The element carries its role and its content, not a name of its own:
a heading becomes /S/H1 with no /Alt or /T. veraPDF does not check
what a structure destination points at, so the test does.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ic76d80d46f130d549ebaacf175393a3d6ec77998
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12300
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit a6502c6e96d13425bcb4a3bf5731a998982d5957)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211150
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-18 19:07:01 +02:00
Mike Kaganski 6e785d7b0b tdf#163240 sw: subdivide a Link by Spans
The commit "tdf#163240 vcl,sw: keep one Link tag for a wrapped link"
made language, baseline shift and decoration come from the link's first
portion for the whole link, and noted subdividing by Spans as the fix:
those three are attributes of the Link tag, and there is now one tag per
hyperlink, so a change inside it had nowhere to go.

Open a Span in the Link instead. A span waits inside its link, so two
tags can be open at once, and an annotation goes to the link's own tag -
vcl takes it on a Link only, and a styled run crossing a line break
would otherwise leave that line's annotation without a StructParent.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I302d32f66fcb4e3b1eaf63b136bdcf96aaea17b7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/11543
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 8f52d63f897aaf710dfe4cd9771f39689471abfd)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211101
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-18 14:21:04 +02:00
Mike Kaganski 6e70cb2e00 tdf#163240 sw: track which tags to close, not how many
The commit "tdf#163240 vcl,sw: keep one Link tag for a wrapped link"
kept the tag across a line break. A ruby still split it, and a character
style on a ruby base pulled the following text into the Ruby element.

Tags were closed by a count, which callers decremented to leave one open
for the next portion; a count does not record which tag. Keep two stacks
instead: the tags this helper opened, and the tags left open for a
following portion. Close a deferred tag only while it is the current
element, so neither a ruby nor a fly closes a tag of another.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Icdf793f149076db2b5dfac2b7668d0c414467427
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/11542
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 1936ac2268b9e603c64a03eff6eb8b7ecb8b56de)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211090
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-18 11:29:55 +02:00
Mike Kaganski 79b06ca748 tdf#163240 vcl,sw: keep one Link tag for a wrapped link
Regression after commit a71da3b7a8
(tdf#156565 sw: PDF/UA export: split Link SE at line break,
2023-11-01), which closed the tag at each line break so that every
annotation had a tag to nest in.

A structure element could reference one annotation only, the
LinkAnnotation attribute being a value in a std::map; give it a vector
and emit an OBJR kid and a parent tree entry per annotation, so each
StructParent resolves to that tag. A page or column break still ends
the tag.

Language, baseline shift and decoration now come from the link's first
portion, at a line break as much as within a line - splitting the tag
at a break would only make the result depend on where the line ends.
LATER: subdivide the Link by Spans, which covers both.

testTdf157816Link and testTdf142806 expected a tag per annotation.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I493439a33b2cbc0eecf1af6b300859cd78ee87ba
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/11541
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit ade287e6a4edf7476e667f48082261deeddd96d5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211077
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-18 08:45:00 +02:00
Mike Kaganski 71a7e22201 tdf#173162 vcl: put dropped types in the PDF 1.7 namespace
Exporting a document with a table of contents to PDF/UA-2 produced a
file that fails ISO 14289-2 clause 8.2.4, once for the TOC element
and once for each entry in it. veraPDF reports

 Non-standard structure type http://iso.org/pdf2/ssn:/TOC is not
 mapped to a standard type
 Non-standard structure type http://iso.org/pdf2/ssn:/TOCI is not
 mapped to a standard type

Commit d23c5560c1 (tdf#160196 add
support for PDF/UA-2 and NS for struct. elem., 2025-01-22) declared
one namespace, PDF 2.0, and pointed every element at it. ISO 32000-2
14.8.6 leaves TOC and TOCI out of that namespace, and also Art,
BlockQuote, Index, Quote, Note, Reference, BibEntry and Code. Writer
emits all but Art and Reference, so one footnote is enough to reach
this.

The bug asks for L and LI with Lbl and LBody instead, or for a
RoleMapNS entry to a standard type. Role mapping is the smaller of
the two and does clear 8.2.4 for the TOC, but the element is then
validated as an L, so veraPDF reports ISO 32005 Table 5 per entry

 <LI> contains <P>

Neither remedy reaches Note, Index, Quote, Code or BibEntry.

ISO 14289-2 8.2.4 offers a third way out: it accepts the PDF 1.7
namespace alongside the 2.0 one, and TOC and TOCI are standard types
there, so nothing needs remapping and the tree keeps its shape.
Declare both namespaces, list both on the structure tree root, and
point all ten types at the 1.7 one. The root's RoleMap is the
fallback for elements there, so RoleMapNS stays on the 2.0 namespace.
Every PDF 2.0 export now declares both, PDF/A-4 included.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ibdf0f27afe2c7b5b5801dbe1b813afb6de95bb0f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12200
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 92a2bfff6f47e055dd49d767a5013cbfcdafcf11)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211067
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-17 13:52:15 +02:00
Mike Kaganski fd583f101a tdf#166963 vcl: reparent the children whose kids a split moves
A document with a table of more than 8191 rows, exported to tagged
PDF, failed PAC with 1393 of

 Structure element refers to a different parent than the actual parent

The kid list is split into generated Div containers. But m_aChildren
and m_aKids aren't parallel: a non-structure element gets a child
entry and no kid, and marked content adds a kid with no child.
Advancing both by the same count moved the wrong children. Cut the
child list where the moved kids end instead.

addInternalStructureContainer also held a reference into m_aStructure
across an emplace_back on it, so a split that reallocated left it
dangling. It takes an index now.

The test needs a document past that threshold. It takes about two
minutes locally, most of it in the PDF parser it reads the result back
with. Disable the test if that costs CI too much.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I37cb4f3e187b8a836b26e7c3da802bd12147904e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12187
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 901e5afec874f0b16959038425365198dfbac7e7)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211066
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-17 13:51:51 +02:00
Mike Kaganski dfdee8ff8a tdf#173194 sw: export a derived Table Heading as TH
A table cell whose first paragraph uses a style inherited from Table
Heading was tagged TD, so a screen reader announces it as data rather
than as a header; only the built-in style itself became TH.

lcl_IsHeadlineCell compared the paragraph's own style name and stopped
there. Walk the DerivedFrom() chain, the way the HTML table writer
already does.

Scope stays Column on every TH, so a row header still claims its
column; that is a defect of its own.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I9ccbd1b3837d3edc5087162a143560e8412a846d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12168
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 270d087cad2c8327775ce204371830c34eecc0d7)
(cherry picked from commit d86b837d439355e62afcdd6b771603c260bc5949)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211049
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-17 11:26:54 +02:00
Mike Kaganski 7f398acee4 tdf#173217 editeng,xmloff,oox: keep a hyperlink's name
A hyperlink in Impress or Draw can carry a name, the Hyperlink
dialog's Name field: the description a screen reader announces, and
what PDF export writes as the link annotation's Contents. Saving the
document and loading it again lost it, and a tooltip carried by a
PPTX never arrived at all, so the link fell back to describing itself
by its own visible text.

Commit 8bf8873f30 (tdf#161583
drawinglayer,editeng,svx,sd: add "Name" to SvxURLField ...,
2025-03-06) gave SvxURLField the name and wired the export path to
it, but no filter. It is not reachable over UNO either, and both
filters go through UNO, so neither had anywhere to put it.

Give the URL field a Name property, and read and write it in both
filters: office:name in ODF, and the tooltip of a:hlinkClick in
OOXML. On import that tooltip becomes the name rather than
Representation, which TextRun overwrites with the link's text; a
shape's own hyperlink goes through the same import context and is
unchanged. A Draw or Impress hyperlink now survives both round trips,
as a Writer one already did.

Inserting a field into shape text does not reach
SvxUnoTextField::CreateFieldData: SvxUnoTextBase builds the stored
field with SvxFieldData::Create, which needs the name too, or it is
dropped again on the way in.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I43d44d55ab460547bbed8acd04d8938a4ee9a615
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12270
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit f6218048f057a229eea7f15c198d850ceedd95dc)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211048
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-17 08:58:43 +02:00
Mike Kaganski ab5bdc9cdd Use IsTextNode instead of comparison of GetNodeType result
Looks more consistent to me.

Change-Id: Id61cb07ece99766b31713006ad3cc4d46c362791
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12304
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 1c7834da4d12de8458d57eee25e2fe97b5c6ace5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211047
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 20:20:59 +02:00
Mike Kaganski 1eca0c353a Simplify SwFillCursorPos ctor using default initializers
Simplifies code, no functional changes.

Change-Id: I433a3aa697d8bf3056d51894137f783821915970
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12051
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit ce897ee2f106bf890b8e3d431e0e34f48c9d9c65)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211046
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-16 20:20:53 +02:00
Mike Kaganski 121c5f332f Drop SwSpecialPos ctor, and use default initializers instead
Simplifies the code a bit, no functional changes.

Change-Id: I72f71db5e12945d904b8922987bf7d5a4d231481
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12050
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 72bfe9ed3e36faf927a31e235fef3fb91ae52851)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211045
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 20:20:45 +02:00
Mike Kaganski 7c2c72d189 tdf#167121 sw: tag the link an index mark covers
A hyperlink whose text an alphabetical index entry covers exported a
link annotation with no tag around it, so PDF/UA checkers reject the
file:

 Link annotation is not nested inside a Link structure element

that is ISO 14289-1 7.18.5. The same happens under a reference mark or
an RDF metadata field.

Each of the three lays its run out as its own portion type, a
SwTextPortion whose only addition is a shading that never reaches an
export. BeginInlineStructureElements listed Text, Lay and Para, so the
run matched no case and got no element at all - not even the Span
carrying its language.

Handle the three with the other text portions.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ie38193f64923cd6a0a3afa509691fb29a007f272
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12104
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 409e0cc615e191a0392ee080717bb84eb61e4724)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211027
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-16 15:56:48 +02:00
Mike Kaganski 32ab4a61cc Simplify SwPosition comparison operators
Drop inequality, that is automatically deduced; and use less-than
to define others.

Change-Id: Ib979cbe74ec28c1fde0337276d4f602a1fe907b4
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10674
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 9f8156beb5eb02ef7d7b00a3effbd6631fc58dfb)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210507
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-05 12:45:59 +02:00
Mike Kaganski 2141c9e689 Simplify some SwNodeIndex methods
Delegate where possible, and drop operator !=.

Change-Id: I64efc13539dc78c4858775c10d3287dc416467fe
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10733
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit c4a06b011473941dfa0b258f3ba655e7f1a9cc3e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210489
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-04 16:31:34 +02:00
Mike Kaganski f56b27b9a0 cool#16080: give the PPTX slide master the shapes its layouts share
If6c23baae18f1d4bd18421a9e48da74956e4a47a ("cool#16080: keep a layout's shapes
off the slide master") made the master part write only placeholders, so a
master that drew anything of its own lost it and every layout kept a copy.
Each page of a group draws the master's shapes first and its own after, so the
leading run they all have alike is the master's: write those on the master and
leave them off the layouts. A shape only one layout draws is not in that run,
so it stays where that commit put it.

Comparing what the pages have in common, rather than where they came from,
works for a deck authored in ODF too. Two shapes are the same when what the
export writes for them is the same; what a page recalculates - a frame's
laid-out rectangle, a cached ordinal number - is left out of it.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: If0660def98c52bbbade6b747c926806d7c4064b8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10138
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit d27410878eba5dc16d601e97a2e0d5e2bed1399c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210365
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-02 01:03:13 +02:00
Mike Kaganski b51d976d0a cool#16080: keep a PPTX layout's shapes off the slide master
An imported master and layout collapse onto one Impress master page, so
the page standing for a group of layouts holds that layout's own artwork;
writing its whole shape tree into the master part painted a full-slide
rectangle from the title layout under every slide. Write only the
placeholders there - a placeholder reaches a slide only through a layout
that references it, a drawn shape paints unconditionally - and let each
layout write its page's shapes. A master of its own keeps writing them,
since its layouts are written with no content.

A master carrying artwork of its own now loses it from the master part
while every layout keeps it; telling those apart needs the shapes common
to the whole group.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: If6c23baae18f1d4bd18421a9e48da74956e4a47a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10086
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 9928c7a8badb4b20fded7a18ba4a59fc0aba44f7)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210364
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-02 01:03:09 +02:00
Mike Kaganski cd1536dfd8 Related: tdf#167713 sw: keep a replayed shape out of the row buffer
Converting a document whose header holds a table with a shape in a cell
aborted the RTF import in a Debug CODA build:

    C:\PROGRA~1\MICROS~1\18\COMMUN~1\VC\Tools\MSVC\1451~1.362\Include\deque(1128) : Assertion failed: back() called on empty deque

reached, as crashtesting found it, with

    COConvert.exe rtf fdo35334-3.odt
    COConvert.exe pdf fdo35334-3.rtf

on https://bugs.documentfoundation.org/attachment.cgi?id=57398.

The row buffer is replayed while the states still point at it, so the shape
insertion was buffered again, behind the cell being replayed, and dropped
with the buffer; the EndShape next to it then closed a shape that was never
opened, taking the table manager that the next substream needed.

Regression from 6c7c171d1468765f7e2a1872d7243f6bf809e37e (tdf#167713 sw:
keep an RTF pict shape in its cell, 2026-08-06).

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ib4fe247910cf67a8b60d448da75510bcf2251885
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10084
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit b2a7dd57076573ddac7141b99f9c33a68fbe799d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210363
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-02 01:02:54 +02:00
Mike Kaganski 26d693d85e sd: extend Undo to cover the placeholder mechanics
Deleting all the text of a placeholder was not undoable: sd puts the prompt
back before svx captures the text change, so the action it creates sees the
prompt as both the old and the new text and drops itself. The text is now
recorded before the prompt goes in, in one group with the change of what
represents a picture placeholder, so one step takes the whole deletion back.

Two things that only surface through that step: a kind with no style sheet of
its own kept none and painted itself in the pool default fill colour, and
the placeholder built to stand in for the text recorded an insert that redo
performed a second time, on top of the swap.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I7bd581216b9111197144d4691c2a12c996cf0e3f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9442
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 33b95acb2e26a22d3242605f7d609108a678620e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210330
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-01 19:52:11 +02:00
Mike Kaganski 4c9116ee9d tdf#166401: keep text in a picture placeholder
A slide's picture placeholder can hold text the author typed instead of an
image, which PowerPoint displays. Impress dropped that text on import and
wrote nothing at all for such a shape on save. To reproduce, type into a
picture placeholder in PowerPoint, save, open the file and save it again:
the text is gone from the shape, and the shape from the file.

What a placeholder is now lives in the page's presentation object list, and
what represents it follows what it holds: a graphic object for a picture, an
SdrObjKind::OutlineText object while it holds text, the placeholder itself
while it holds nothing. It changes over when a file states such text, when
that text is deleted again and the prompt comes back, and when an image is
put in. Text arriving another way, through the API, is caught by the new
SdrPage::onEmptyPresObjFilled and switched at the next idle, so that route
needs a spinning scheduler; what a save writes is right either way. An object
in text edit is left to that edit, which is where it changes over: the idle
pass walks every page, so it asks each object rather than trusting the one
that asked for the pass - in a shared document another view holds it. That
switch is the model's own and covered by no undo step - it answers for a
document the API changed, not for anything the user did.

What the file says about the shape travels with it both ways, the graphic
items included, so an outline a picture placeholder is clipped to is still
there when a picture arrives. It is not written while the placeholder holds
text: the file says text box, and a text box states no geometry to clip a
picture to.

PPTX writes it as the placeholder it is, with the text it holds. ODF states
the class of what represents it, so the placeholder's own class travels
beside it as coext:placeholder-class, and a reader that ignores the
extension sees an outline placeholder with the text.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I0f3353115e56d63a4158ebaeb5689689499894bb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9441
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 7921401ae48188568e1016db00bd90256d666a6a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210329
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-01 19:52:06 +02:00
Mike Kaganski 3d01685a4f sd: fix crash setting IsEmptyPresentationObject twice
Making a presentation object empty took the style for its prompt from the first
paragraph of the text it holds, without checking that it holds any: a graphic or
an OLE one never does, and making an object non-empty removes what text it had.

There is no message to quote - SdXShape::SetEmptyPresObj dereferences a null
OutlinerParaObject and the process goes down where it stands. To reach it in a
build without this fix, open a document whose slide holds an empty picture
placeholder, such as sd/qa/unit/data/pptx/tdfpictureplaceholder.pptx, and run in
Basic:

    oShape = ThisComponent.DrawPages(0).getByIndex(0)
    oShape.IsEmptyPresentationObject = False
    oShape.IsEmptyPresentationObject = True

Which way the prompt runs was taken from the text as well, so an object without
any got a horizontal one; it answers for that itself now.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ie5ba7ee52e1e9a2131dbd09e1df4c773ea52431c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9304
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit a8df64888cad38c406929cd5a63960cdb8fdc545)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210325
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-01 19:52:01 +02:00
Mike Kaganski 84c8ca6fd5 Address review comments
See https://gerrit.collaboraoffice.com/c/online/+/9927

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I6139405c1285914323a4c2e0b3ef6ace0034195b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9953
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 609c2a17a654452409ec090c47351b49c4990349)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210335
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-01 19:51:49 +02:00
Mike Kaganski ecf0efa073 tdf#165609: save the form control label font and alignment
The XLSX export wrote the label as a bare <font> element, so a checkbox
came back with the control default font at 10pt and adjacent labels
overlapped. The label's font name, size and colour, its bold, italic,
underline and strikeout, and its horizontal and vertical alignment are
now written out.

The underline stopped reaching the export altogether when the control
property list started asking for "FontLineStyle", which no control model
has: commit 3187193a61 (tools: rename
FontUnderline to FontLineStyle, 2016-01-29) renamed the VCL enum and
took the UNO property name string along. The same rename hit
sw/source/filter/html/htmlforw.cxx, which is not handled here and needs
a separate fix. A double underline also needs the "fontN" class on the
underline element, which the import compared with a literal "font4",
introduced with no explanation given in commit
04c272eb83 (dr77: #i114128# import legacy
drawing controls, 2010-08-26). N is the index into the workbook font
table, and is treated as one now.

Excel keeps the label font in the VML shape, in the DrawingML run
properties and in a workbook font record, and builds its model from the
DrawingML, while we read the VML and the record it names. Both are
written here so that the two applications show the same document. TODO:
read the label font from the DrawingML run properties, as Excel does.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I87259d25a68829f6c1a78179f8e3aa7811a5688f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9927
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit ca86699205743f2709f3cd5d412a8a3f0d3fba38)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210334
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-01 19:51:44 +02:00
Mike Kaganski 64fe896867 tdf#166401: keep a placeholder's prompt in ODF
Commit 67ba6ccd5a (tdf#140912, tdf#159219:
fix import of graphic placeholder with custom prompt, 2024-01-17) left
the prompt out of ODF for want of a representation, so a reload replaced
an authored one with our own localized default. It now travels as a
coext:placeholder-prompt attribute beside presentation:placeholder, on
draw:frame and draw:page-thumbnail, written when saving with extensions
and added to the extended schema so the round trip validates. It is
also listed in the Collabora ODF extensions notes.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I12b51ff2bc103e5c26e94558802064919c7289dd
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9070
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 6b1b6331ada0cc948e880eb83db5506f8bf1aa9c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210297
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 23:49:35 +02:00
Mike Kaganski 72b866f367 cool#16083: an empty placeholder stays empty
Formatting an empty presentation object wrote the outliner's emptiness back over
the prompt the object holds, leaving a blank frame with nothing to insert into.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I7c168196dfccfb5d0df9b01cc9d4c52c62db5555
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9287
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit d4aa2b9e28adbe7bb241bdee33c5af91e85258b4)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210236
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-29 13:48:20 +02:00
Mike Kaganski 5f26da385c cool#16083: do not take a prompt as slide content
A slide placeholder took its layout placeholder's paragraphs as its own text, so
it arrived filled and stopped offering to insert a table, a chart or a picture.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I879dc64e0129101623fda42361afb13b6be3e4e3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9286
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit e00c44aa81d7d695a34eddf7e672f926aa7fc955)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210246
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-29 13:48:13 +02:00
Mike Kaganski 95cbf113ed cool#16082: keep the subtitle a slide layout holds
A subtitle placeholder was written only for a page that is not a master,
since commit 238cfa9efa (tdf#112557:
Subtitle placeholder shape leads to corrupted PPTX file, 2017-09-22) -
which in the same change made the slide master part's shape tree pass
PageType::MASTER, at the time the only page written with it.

Layouts started taking that page type in commit
bd5d39b307 (sd: de-duplicate masters on
save to pptx follow-up, 2025-01-07), where a master page standing for a
group of equivalent masters writes its p:sldLayout through
ImplWritePPTXLayoutWithContent. So every layout of a master holding more
than one lost its subtitle, and a slide made from that layout afterwards
had nowhere to put one. A deck with a single layout was never affected,
taking the path that passes PageType::LAYOUT.

The master part needs no page-type test of its own: WritePlaceholder
already returns early for a type a slide master may not hold, subtitles
among them.

testCustomPromptTexts asserted the workaround's own damage, with a TODO
naming it and the two values it expected instead. Both hold now, so the
assertions carry them and the TODO comments are gone. It passed its
arguments the other way round, which is why the listing below reads
inverted; they are in the (expected, actual) order now.

 testCustomPromptTexts::TestBody
 equality assertion failed
 - Expected: Click to edit customized Master Subtitle style
 - Actual  : Click to add Text
 - Wrong placeholder text!

testTdf106867 moved its expected spid from 66 to 67, a layout writing
one shape more and shape ids being document-wide. It still names the
p:pic that holds the video.

 testTdf106867::TestBody
 equality assertion failed
 - Expected: 66
 - Actual  : 67
 - In <>, attribute 'spid' of '/p:sld/p:timing/ ... /p:spTgt' incorrect
   value.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I65aecb44f6df67ef7bb6d0ffd4be9da471f7e950
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9226
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 5ffd346c12e0844efbd2681b75065d4c37d30996)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210235
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-29 13:48:09 +02:00
Mike Kaganski 9b3b1373b2 cool#16080: write the body the slide master holds
The master part was written from the page standing for the group of
equivalent masters, so it carried that layout's placeholders and
nothing else - for a deck whose first layout is a title slide, no body
at all, and PowerPoint offered no Master text styles to edit. Take a
type a master may hold that this page has none of from the first page
of the group that has it.

A layout placeholder was imported with what it inherits from the
master, so the first layout with a body carries the master's own
geometry, which is where PowerPoint puts the box. A deck whose first
body-bearing layout states a geometry of its own hands that over
instead.

testTdf106867 moved its expected spid from 65 to 66: the master part
writes one shape more, and shape ids are document-wide. It still names
the p:pic that holds the video.

Underneath is a document model difference: PowerPoint has masters that carry
layouts, Impress has masters alone, so the import maps each layout to a master
of its own and the export reconstructs both levels from them.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I1e6d311e5f20cd3377ce4c0c3f2dfa59ff4dfefb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9196
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 73dfd24f01a6fdf5fbd7c0d48f17c027442fb870)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210234
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-29 13:48:05 +02:00
Mike Kaganski 605ce703de cool#16080: import the layouts in the master's order
The layouts came from the slide master's relations, which carry no order:
a relationship id is opaque, and a producer may assign any id to any part.
The map is keyed by that id as a string, which is what made it visible -
a master with ten or more layouts handed over rId1, rId10, rId11, rId2 ...
and the master pages kept that order. Read the master's
<p:sldLayoutIdLst> instead, which is the order it lists them in.

Beside the layout order itself, this decides which master page stands
for the group of equivalent masters: before the fix, a second save
picked the page of a layout with no title, and wrote a slide master
without one.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I6ff62fd190742246a8317f9efae907eeb4a9d8a3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9194
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 5652d7d68358aae8819ade918c5f5c3cf35f5456)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210143
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-28 12:08:53 +02:00
Mike Kaganski 2391b3dec0 cool#16080: keep the slide master's own shapes
A master standing for a group of equivalent masters wrote a shape tree with
nothing in it and left the content to its layouts, so PowerPoint showed an
empty slide master: nothing for a layout to inherit, nowhere to make an edit
meant for all of them, and master level header and footer state that
PowerPoint refuses to set while the placeholder is missing. Write its shapes
into the master part as well, the way PowerPoint authors one.

A placeholder index now belongs to the shape rather than to whichever part
writes it first, because the master part and the layout write the same shapes.
Without that a slide named the master's indexes while its layout carried fresh
ones, and a slide pairs with its layout by type and index - PowerPoint's own
files pair those two and number the master separately.

testTdf106867 had to move its expected spid from 61 to 65: the master part
writes four more shapes and shape ids are document-wide, so the animation's
target moved with them. It still names the <p:pic> that holds the video.

Underneath is a document model difference: PowerPoint has masters that carry
layouts, Impress has masters alone, so the import maps each layout to a master
of its own and the export reconstructs both levels from them.

Two limits stay, both from an imported master and layout collapsing onto one
Impress master page: the master part is written from the page that represents
the group, so it carries that layout's title geometry rather than the master's
own, and there is no body placeholder left to write.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I4c1465718918886ecfda16baafcd803e4cb095fb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9193
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 270d83101f69bdfe8980a14414d08f88b64232ca)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210142
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-28 12:08:48 +02:00
Mike Kaganski 4bfb5aaa80 cool#16079: keep the date field in a placeholder
A date/time placeholder saved to PPTX held our own display string
"<date/time>" as literal text with no <a:fld> at all, so PowerPoint showed
that string where the original showed a date. DrawingML::GetFieldValue has
no case for the field kind a header/footer date carries, "DateTime", and
with no type name nothing writes the field.

Name the type that leaves the format to the reading application, datetime:
the field carries no format of its own, the page holds it. PowerPoint writes
datetimeFigureOut in such a placeholder, an alias of datetime1 - MS-OI29500
part 1, 21.1.2.2.4 (fld) - which would pin a formatless field to MM/DD/YYYY;
that alias is read on import as the datetime1 it stands for, so a slide's own
date field keeps its format across a round-trip. Every other datetime* type
was understood; the one PowerPoint itself writes was being dropped.

The page's own DateTimeFormat is out of reach where the type is chosen, so
a footer set to show a time is written as a date. PowerPoint has no
per-page format to fall back on either way, and a date beats the literal
string this replaces.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Icbd9ac57ed7b2311d5f6570fffad1cd78e4d6bf7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9192
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 145a37d84dd3b22023c46b6be8577008f36a48b9)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210141
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-28 12:08:43 +02:00
Mike Kaganski 1dc3b23162 cool#16078: OOXML round-trip of the clip polygon
A placeholder clipped to an outline carries it as custom geometry, which an
image shape has nowhere to keep, so the outline was dropped on load and the save
wrote a plain rectangle over it. Read it into GraphicClipPolyPolygon, and write
it back from there.

The two directions are not equally capable. Import reads paths of literal
coordinates: one driven by guides or formulas, and an arc, both need the
custom-shape machinery. A quadratic curve is read, converting exactly to the
cubic the model keeps. A path that states no w and h is not read either - such
coordinates are absolute EMU rather than a space to scale from, and no file
authored that way turned up to check the reading against. Export writes whatever
polygon the model holds, so that direction loses nothing - a geometry the import
gave up on was never stored. A curve that closes a contour states no end point
of its own, and the point the contour started from answers for it.

A path that states fill="none" states that it paints nothing, and PowerPoint
leaves it out of the clip: a picture clipped by two paths shows through the
plain one and not through the nofill one, while the same file with that one
attribute removed - or with stroke="0" in its place - shows through both. So
such a path is read past, while the shading variants and stroke="0" are not:
those say how an area is painted, not whether there is one.

Several paths become one polygon by area, not by concatenation: the contours of
one path are filled by the even-odd rule, so two of them overlapping leave a
hole, while paths are filled one after another and the shape covers what they
cover together. Concatenating them instead cut a hole wherever two paths
overlap, which the file does not describe and PowerPoint does not draw.

A shape's own custom geometry now replaces the one it inherits, the way a preset
geometry already did. Saving states the outline on the slide as well as on its
layout, so reading that back gave the slide placeholder its own geometry beside
the inherited one, and two geometries at once are not one this reads - the
placeholder came back unclipped while PowerPoint showed the same file correctly.

The outline reaches the drawing the commit before this one added, so a
placeholder read from a file shows what sits behind the parts it does not cover.
It survives a round-trip through ODF as well, where the property already travels
as coext:graphic-clip-poly, with nothing to carry until now.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I87bd21e68bcea648c8c9151b6af5439578f1c2cf
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9303
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 04bbb74a24bad4ae54f257b6b028f3fd48955272)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210123
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-27 21:14:55 +02:00
Mike Kaganski be205bb214 cool#16078: ODF round-trip of the clip polygon
Carry the GraphicClipPolyPolygon UNO property on graphic-object
shapes through ODF save/reload as a new <coext:graphic-clip-poly>
child of <draw:frame> with the polygon serialised as svg:d (the
same SVG d-path encoding draw:path uses). A new XML_GRAPHIC_CLIP_POLY
token is added in lockstep across xmltoken.hxx, xmltoken.cxx, and
tokens.txt so the XMLTokenEnum and FastParser tables stay aligned.

The element is taken before the frame decides what its shape child is, and
applied when the frame ends: an empty presentation object has no draw:image for
the polygon to follow, so the polygon comes first and the shape it belongs on
does not exist until then. A placeholder saved to ODF came back unclipped
otherwise, which is the shape of file our own save writes.

It goes on the image the frame keeps, which is not always the last one it saw:
a frame with a replacement image holds several, and the ones that lose are
taken off the page and disposed.

Export goes through a new ImpExportGraphicClipPoly helper called
from ImpExportGraphicObjectShape alongside ImpExportSignatureLine
and ImpExportQRCode (under ODFSVER_EXTENDED). Import is handled
in SdXMLFrameShapeContext::createFastChildContext, which is where
<draw:frame> children land - the GraphicObjectShape sub-context is
nested inside that and never sees this sibling directly.

The element is added to the extended ODF schema next to
loext-signatureline and loext-qrcode, so a document carrying it
validates, and is listed in the Collabora ODF extensions notes.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ib9ae65c1b6f2d8fdb28ae086fd7e2be2f8a8ee24
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9191
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit b7a56041afda6b658b6b16bb51a5939295c40d75)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210122
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-27 17:00:20 +02:00
Mike Kaganski e26a668059 Avoid useless call to CreateColorBitmap
It is potentially expensive, and isn't needed: the ctor of Bitmap
taking a bitmap and an apha mask only uses colors from source
bitmap anyway, not its aplha.

Change-Id: I086fec94659495020108dc88a75b5479aff53b66
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9533
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit a16c260b2729bfc64a8d2cc9ae74c99f978bb506)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210124
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-27 15:46:34 +02:00
Mike Kaganski dec2f2b9ef cool#16078: draw a graphic object's clip polygon
The clip a graphic object carries was kept in the model and carried through
save and load, but nothing ever drew it, so a shape a file describes as an
outline still covered its whole frame and hid what sat behind the parts the
outline leaves out. Wrap the shape's content in a mask.

The polygon states the shape's own coordinates, so it is normalised and then
put through the same object matrix as the content, and follows the shape's
rotation and shear along with it. A flip is expressed on the graphic itself
rather than in that matrix, so the clip is mirrored here to follow it - the
picture would otherwise turn round inside an outline that stayed where it
was. The mask covers the fill as well as the graphic, which is what uncovers
the background. The invisible outline stays outside it, leaving bounds and
hit-testing as they were.

A mask clips with an even-odd fill, which is what makes a contour overlapping
another one a hole.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I69c8c6c31f43e87399fb582665904dc81221b32b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9302
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 120b9ef0b7c50a0e025e7be87e005aa39ab7db8a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210037
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-27 09:53:25 +02:00
Mike Kaganski 9d36b250d9 cool#16078: add SdrGrafClipPolyPolygonItem
Introduce an SfxPoolItem in the SDR graphic attribute range that
carries a basegfx::B2DPolyPolygon clip outline on SdrGrafObj.
Allocate SDRATTR_GRAFCLIPPOLYPOLYGON between SDRATTR_GRAFCROP and
SDRATTR_GRAF_LAST (which shifts SDRATTR_3D_FIRST and the downstream
ids by one), register the pool default, and surface the value as the
"GraphicClipPolyPolygon" UNO property on the GraphicObjectShape
property map (covering both drawing.GraphicObjectShape and
presentation.GraphicObjectShape via SVXMAP_GRAPHICOBJECT) using the
css::drawing::PolyPolygonBezierCoords type. QueryValue/PutValue
do the round-trip through the existing basegfx helpers.

The new which-id is 1175. The trailing comments in svddef.hxx have
run 2 high since two XATTR items were dropped upstream, so the ones
added here say 1177 to stay consistent with the file.

No reader, writer or render path consumes the property yet; this
is infrastructure for upcoming custGeom round-trip work on empty
picture placeholders.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I17d98cabeb230c25b3de0da4eadd8338ed672ce1
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9190
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit be7f21fc7187bb87129eed4e8ed2f794faf6e757)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210036
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-26 21:09:05 +02:00
Mike Kaganski 9fac5940ce cool#16078: write a schema-valid empty draw:image
A graphic-object shape with no embedded graphic data exported as
<draw:image><text:p/></draw:image>. Per ODF's draw-image content
model draw:image has a choice between common-draw-data-attlist (the
xlink:href and friends attributes) and office-binary-data, followed
by draw-text (text:p etc.). Without either alternative of the choice
the element is incomplete, and the validator rejects it - the
diagnostic mentions text:p because the parser stops at the missing
choice content before reaching the draw-text part where text:p would
be valid.

Emit an empty <office:binary-data/> placeholder when there is no
xlink:href and no graphic data, satisfying the choice.

Groundwork for the clip polygon that follows, which is written on
exactly such a shape: a picture placeholder that never got an image.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I5bf792c60affd35b33779c91be0a381eb074f00f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9189
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 6d1cbfc9fb8e9a705b05f6cdddd4a87a630e973d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210035
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-26 21:08:51 +02:00
Mike Kaganski c4256a2a76 cool#15754: round-trip empty media placeholders
Commit 73f1e76b1760d4e936aa22627623c922dcda964b (cool#15754: Fix PPTX
round-trip of empty picture placeholders, 2026-05-21) covered <p:ph
type="pic"> only. A layout's media placeholder still imported as an
outliner shape, and an empty slide-side one exported as a <p:pic> whose
<a:videoFile> named an empty external relationship - a package the Open
XML SDK validator refuses to open at all.

A media placeholder needs no copy of its own onto the layout, the way a
picture one does: it reaches the file through the page's own shape tree.
Whether it is still waiting for its content is asked of the media, since
nothing marks such a placeholder empty.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I85707ff863a63874c6f493a0f284f25070f26e49
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9188
Tested-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 44a05619224ab206dae4ef076269cbb8ce56f43b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210009
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-26 21:08:44 +02:00
Mike Kaganski 47aa325014 tdf#166401: support custom placeholder prompt text
A slide layout is imported as a master page, and the shown text was only set
off a master, so an authored prompt was stored but never displayed or written
back; the export emitted Impress's own localized resource instead. Set it on
a master too, and accept the graphic kind in RestoreDefaultText, which keeps
the object an empty presentation object. Now that the two are
distinguishable, a placeholder that authored no prompt writes no text - its
text body keeps the properties a reader needs, and holds an empty paragraph.

A slide's own placeholder asks its master for that prompt before falling back
to the resource string, so the two no longer disagree about what is really
one placeholder.

This is what degraded in commit 73f1e76b1760d4e936aa22627623c922dcda964b
(cool#15754: Fix PPTX round-trip of empty picture placeholders, 2026-05-21):
mapping a layout <p:ph type="pic"/> to a real presentation service is what
made it reach that text reset, where the previous OutlinerShape mapping had
kept the authored prompt.

Implemented a TODO from commit 67ba6ccd5a
(tdf#140912, tdf#159219: fix import of graphic placeholder with custom
prompt, 2024-01-17). That commit deferred this because marking the object
empty after setting the text crashed the PPTX export; going through
RestoreDefaultText, which does exactly that, no longer does. Its other
objection stands: ODF stores no placeholder prompt, so a save to ODP still
drops it.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I1b6f3e38cb24ac737d3de6258559090596817bad
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9046
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit b41a780d139ec6ca5a5514db972ccf0e50058dfc)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210008
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-26 12:20:37 +02:00
Mike Kaganski 898d74f652 cool#15754: do not paint a master's picture placeholder on slides
A slide carrying its own instance of a layout's picture placeholder showed
two of them: its own movable one, and the layout's immovable one behind it.
Hide a graphic placeholder on a master page the way the title, outline and
notes ones already are. Everything else stays visible from the master, the
footer and slide number above all.

Regression from commit 73f1e76b1760d4e936aa22627623c922dcda964b (cool#15754:
Fix PPTX round-trip of empty picture placeholders, 2026-05-21): a layout's
<p:ph type="pic"/> used to become an outline shape, which was already hidden
this way, so 25.04 shows one placeholder where we show two.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I0af73c8d2de8bc1e2686217bcb085d2ea8c7cc21
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9057
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 15681ac99c23aa79ec90c0df833d6b091aaa56cf)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209952
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-26 09:00:54 +02:00
Mike Kaganski b931a886ee cool#15754: keep content placeholders off the master
PowerPoint takes only title, body, dt, ftr and sldNum placeholders on a slide
master and refuses to open a file whose master carries any other type, so a
deck whose only layout held a picture placeholder saved into a file that could
not be reopened at all. Skip such a placeholder in the master part and write
the master page's own copy into the layout instead, which is where a slide
resolves what it inherits. A master representing an equivalence group already
writes its whole content into the layouts, so only the single-master case
needed the copy.

Write the slide placeholder's own geometry too, rather than leaving an empty
<p:spPr/> for the layout to fill in. That is what PowerPoint itself authors,
and our import does not always resolve the layout placeholder, which left the
shape 0x0.

Regression from commit 73f1e76b1760d4e936aa22627623c922dcda964b (cool#15754:
Fix PPTX round-trip of empty picture placeholders, 2026-05-21), which mapped a
pic placeholder to a real presentation service everywhere, the master
included.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ie773152f0784de5104b5ab3ddcd0869621646f13
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9045
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Aron Budea <aron.budea@collabora.com>
(cherry picked from commit 44f8c6457c797a9eae7e7612d377d227e1a354c3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209951
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-26 09:00:49 +02:00
Mike Kaganski 45225d31c4 sw: keep the drag start point in a Point
The two coordinate statics were always written and read together, and
every reader outside the edit window built a Point out of them anyway.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I7333728d5c51a15d435bd9ced2f1d8770f1e10c7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8999
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit f7f785e565b208649065441ccbd0f0d056f3e29d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209829
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-25 08:52:33 +02:00
Mike Kaganski db23488a24 sw: extract a helper for arming a frame drag
The same four lines stood at five press-handling sites; the last of them
came with 3efae0655f0d26f0686560ae00ed57ca45f654a5 (sw: drag after text
edit used a stale start point, 2026-08-02).

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I8f64d5855818f0d1e2166906033e3fb3eba0f104
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8995
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 16501dab9627e77dd3bcf640e091141b7aa7508a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209828
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-24 23:02:35 +02:00
Mike Kaganski df813b5afc CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I4fd3eedda81455f082cdbcd07c323e8c4aab77ff
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9522
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 90fca744e7cd75173ecd694bb326b7706bce9a6b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209523
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:29:02 +02:00
Mike Kaganski 71afdd2a6a CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I1a541a9cb977cde63b89b5e838fa0e77e3116c45
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9513
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 349f60f1c3f425bf3ec4a7b85e053c372a166e4a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209522
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:50 +02:00
Mike Kaganski 6c4d9d9e0c Avoid useless call to CreateColorBitmap
It is potentially expensive, and isn't needed: the ctor of Bitmap
taking a bitmap and an apha mask only uses colors from source
bitmap anyway, not its aplha.

Change-Id: Icedae8c1e0c84e99654e223a36b52e9fbfdc6439
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9532
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 985f05348e44f8144e9f085f50ea372e31a916b5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209521
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:45 +02:00
Mike Kaganski 1058d1f18d CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

And avoid useless call to CreateColorBitmap: it is expensive, and isn't
needed: the ctor of Bitmap taking a bitmap and an apha mask only uses
colors from source bitmap anyway, not its aplha.

Change-Id: I08a9cc5087d5059ca250daed5bdfdd5039bd2836
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9534
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 051a4de4d2af6fe143364fc367b070805865c285)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209520
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:37 +02:00
Mike Kaganski 164816bbd3 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I85badd996a26fe2f451da07fbd5dac4889a8e77d
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9531
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 4970121192f268115122b271b7b286d8691a82be)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209519
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:33 +02:00
Mike Kaganski e035015902 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: Ia6284fbc0c36950ad40e3abdb9ea7095b4db894a
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9529
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit d9058f14a0d050adb9b594c0c3864af3c1a7583a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209518
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:25 +02:00
Mike Kaganski f1e01466f0 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: If2caeff175ed120dde336c40f5e96ccd8157d5ad
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9528
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 86514450b4655f8e5623362c54d0153f5887d753)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209517
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:19 +02:00
Mike Kaganski f03760a4b4 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: Id33122c14f009d43fff668c05efb662aa197a133
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9527
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit f8089f18c50639527f8657169c98b1f8c43c9787)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209516
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:14 +02:00
Mike Kaganski 1e10dbb206 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I4cd2e0d36e0b9fc68976b673d070578ca8ef628a
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9526
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 86c34245fb35609dba0054d96021f796a423236c)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209515
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:08 +02:00
Mike Kaganski a244cac0cb CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I6ce4e568f0f3dd2baf79c4de45194822ace02268
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9524
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit e5ad1af72afd1975d2a75c4e995644bda7b02bf7)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209514
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:28:02 +02:00
Mike Kaganski 9bfa8c6843 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: Ieef19e0725dcdf0c9b17444b8f0e8b9eefaf74ee
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9521
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 0c72a1c0c7fd9da268a80038fad3ccb46a515834)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209513
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:27:57 +02:00
Mike Kaganski c47c991c85 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I9323b8d5f5142657f402783438d67c388cf8d5e6
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9512
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit b6d5ade8b30112ff2b7bde1eb41008b43b713302)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209512
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:27:51 +02:00
Mike Kaganski 5df7faddb9 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I3fb2873a5e7b1e1e73266efdca1dc671a687277d
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9519
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Tested-by: Noel Grandin <noel.grandin@collabora.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 74272c95e9ae155ec61c300e82a6bc1fd06939f4)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209511
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:27:45 +02:00
Mike Kaganski 7161748dcd CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

Change-Id: I899590dca6d00f7d5bf850d3ee0329215caaf2f6
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9517
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 235b590828ebcc3167bb9b56f7acbf5c8300d4db)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209510
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:27:40 +02:00
Mike Kaganski 60c92f9752 CreateAlphaMask + CreateColorBitmap -> SplitIntoColorAndAlpha
See commit f7b67e6c8c5981790fb9ac162dcc68e470b53112 (pdfexport: reduce
the number of times we split images, 2026-08-19) for rationale.

While at that: drop the check added in commit
e06456180a (crashtesting: crash on export
of forum-en-41439.odp to pdf, 2023-07-27), which was obsoleted by commit
4942aa1533 (tdf#156808 force an alpha mask
to be created even if it has no alpha, 2023-09-14).

Change-Id: I318c24629676b115c34cc42333bdcb136c420bd9
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9510
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 69c9ed699924b40cd05f08bca30cce115bb629ff)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209509
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:27:34 +02:00
Mike Kaganski d385b81da3 Avoid second call to CreateAlphaMask
This is an expensive operation.

Change-Id: I4ffd17de84d324dcb091905927d9ef7423fdb1c9
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9505
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 9e2d6727f27caa7663a18564f46bb4a90b3b87a3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209508
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:27:29 +02:00
Mike Kaganski 6214c82bd0 Fix signed/unsigned mismatch with MSVC 2026
After commit db92d0be756305ec0b1e122a1d00333477c17ba5 (dbase: apply
one consistent key-per-page limit in the .ndx index, 2026-08-19) a
local CODA-W build failed with:

 C:/lo/online/engine/connectivity/source/drivers/dbase/dindexnode.cxx(518): error C2220: the following warning is treated as an error
 C:/lo/online/engine/connectivity/source/drivers/dbase/dindexnode.cxx(518): warning C4018: '<': signed/unsigned mismatch
 C:/lo/online/engine/connectivity/source/drivers/dbase/dindexnode.cxx(599): warning C4018: '<': signed/unsigned mismatch

The problem was the change of type of nNewCount from sal_uInt16 to
sal_uInt32, which made the following comparison warn:

    if (nNewCount < (nMaxNodes_2 * 2))

nMaxNodes_2 is sal_uInt16, but multiplication gives int; before the
change, nNewCount was promoted from sal_uInt16 to int as well; but
now it isn't promoted, hence the problem.

Fix it by making sure that multiplication produces an unsigned value.

Change-Id: I056fa7517834b64706be27b4318bb21c42ecfebb
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9581
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit d68d84af021b67ceaf9f94294722c2e97ee9e963)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209455
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-21 18:46:35 +02:00
Mike Kaganski 45d479de0b cool#16072 sw: do not let a footnote force a row to split
lcl_GetMaxRowHeight returned the current height of the page body. That
height is smaller when the page has footnotes. SwTabFrame::Split
compares a row that must not split with it. When the row does not fit,
Split treats the row as not fitting the whole page, and splits it so
that no text is hidden. So a row of 4150 twips was compared with 3950,
not with the 4535 that the body has when the page has no footnotes. It
was split, 25 twips short of fitting. SetForceRowSplitAllowed is never
reset, so the row stayed splittable and keep-together was ignored from
then on.

Measure the body as if the page had no footnotes: the print height of
the page, without the header and the footer. The footnotes give that
space back when the row needs it. A footnote of an earlier row stays on
the page that the row leaves. A footnote of the row itself moves with
the row.

This is not enough on its own, because the row can now stay on a page
that is too small for it. Split does not give the first row of a table
to a follow: that would leave the table with no rows, and MakeAll uses
Lower() right after it without a check. So the row stayed, and the text
that did not fit was drawn below the table border, or was not drawn at
all. Split now says what to do about it, and MakeAll does it. Which of
the two ways it is depends on the footnote that took the space.

A footnote of content on an earlier page cannot move, so the table has
to: SwFootnoteRescue::MoveTable. The move needs bMoveAlways, because a
table at the top of a page has no frame before it, and that normally
stops the move. The page still has that footnote, so it is not left
blank; and the next page has no such footnote, so the table does not
move again.

A footnote of the row itself can move, and then the row can stay:
SwFootnoteRescue::MoveOwnFootnote. MakeAll asks the page to put the
footnotes below the table. What has no room left there goes to the next
page, and the body gets that space back. So the row keeps the page, like
in Word. MakeAll asks only once, so a footnote that cannot move does not
make a loop.

This improves on commit 229964ac93
(tdf#163285: consider page body frame height to allow the split,
2024-10-05), which made the rule of commit
b271cc4685 (tdf#123116 sw layout: allow
rows larger than page to split anyway, 2020-03-12) use the body height.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Id3177c20df7ba1d9e10d607d98980849f32a843f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8954
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 99ad6a1867dc06b9acc386c85373db746e531829)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209429
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-21 09:44:21 +02:00
Mike Kaganski e55cb4a7b8 tdf#167714 sw: mark Word horizontal rules in the model
Nothing told a horizontal rule apart from an ordinary rectangle, so it could
not be laid out the way Word does; the marker is that distinction, and its
absence is what keeps documents written before it rendering unchanged.

The UNO property reports DEFAULT_VALUE while false, since ODF export only
writes direct values - else every shape and graphic style would carry it.

Also let the RTF import build a rectangle for a rule, as the VML import
already does, so both filters produce one model object.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Id2facd7bdc272197273462f0ad581537551234bc
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8657
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit dbe7dca2951640990d3febaf12a9c3118e15ec22)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209328
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-19 17:18:11 +02:00
Mike Kaganski bf23464799 sw: drag after text edit used a stale start point
testSnapTextBoxToGrid in CppunitTest_sw_snap_to_grid failed in a CODA-W
build with

 C:/lo/online/engine/sw/qa/extras/snaptogrid/snaptogrid.cxx(200) : error : Assertion
 Test name: `anonymous namespace'::testSnapTextBoxToGrid::TestBody
 equality assertion failed
 - Expected: 2032
 - Actual  : 4064

The press that ends a text edit also arms a frame drag, but left the
drag start at the click that selected the object, so the drag jumped
the object to that old point instead of following the press. Only a Kit
build keeps the handles during a text edit, which is what made the jump
a lost resize here.

In Classic:
- insert a rectangle with some text in it, and click outside it
- double-click near its top left corner, to start editing its text
- press on its bottom right corner, inside the shape but outside its
  text, and drag: the shape jumps, keeping its size, until the point
  of the double-click is under the pointer

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I9bce49be53969414995049c0d9e3aa8c7135413c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8485
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 3efae0655f0d26f0686560ae00ed57ca45f654a5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209262
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-19 08:39:17 +02:00
Mike Kaganski dc498d5a13 tdf#167713 sw: keep an RTF pict shape in its cell
A picture carrying shape properties is imported as a shape, and its insertion
was not buffered like the rest of the row, so it landed in the row's first cell
whichever cell it was written in.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ia5e51c49698d764561839e6361e6517648ca81fb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8656
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 6c7c171d1468765f7e2a1872d7243f6bf809e37e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209261
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-19 08:37:19 +02:00
Mike Kaganski e59e33f064 tdf#171370: a char must stringify to itself
Regression after commit 2143c0a694
(Simplify getter/setter implementations, 2025-06-03): sal_Unicode is
std::integral, and took the generic numeric path.

Co-authored-by: Andreas Heinisch <andreas.heinisch@yahoo.de>
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I0c12590c06536f4d2efefcbe09657fff454ed2a4
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8798
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit e725c25d6cb8c9cd1af08f70f6fea541df121b1d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209264
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-19 08:36:23 +02:00
Mike Kaganski 44c92622d2 cool#16071 sw: do not lose footnote lines cut away to fit
RearrangeFootnotes may cause SetRstHeight to set the rest height to 0 for
footnote lines no more fitting the footnote area; but 0 was used as a
special value in IsInside, meaning "no rest height was set", which then
incorrectly measured the upper instead - still as tall as the lines the
frame had before the truncation. Then the frame was considered as having
enough space, marked valid, and the lines cut away from it got no follow to
move to, so they never appeared anywhere.

Give the rest height its own "not given" state instead of spelling that as
a number, and honour a height of zero for what it says. A magic negative
would not do: SwContentFrame::WouldFit_ passes a negative height when the
frames before it overfill the new upper, and that has to keep meaning that
no line fits. The footnote computation in the constructor still falls back
to the upper when it ends up at or below zero, so only the SetRstHeight
path changes behaviour.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: If692577f3ee692ad5c0dbe87f9a11a2670f8cf16
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8906
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 77634c741bb538853a757c2a41e4c9b919f22815)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209271
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-19 00:40:59 +02:00
Mike Kaganski 50ce488d18 cool#16067 sw: take table rows back when the page regains space
A row moved to the follow because a footnote anchored in a row below it
was taking space on the page; that row moved too and took the footnote
along, so the page can now hold the row that was moved off it. The
follow's rows now get one more try to join once the page has grown.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Id4120e3d35e13c8f2eb3eb7c2bb1deff3e13d0cb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8868
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit c808931b72d1576b30be0c8ac813a4e48bc0e06e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209270
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-19 00:40:55 +02:00
Mike Kaganski 2f194066ca Related: tdf#171708 sw: keep a no-wrap footer object clear of the text
To see the problem, open that bug's document,
https://bugs.documentfoundation.org/attachment.cgi?id=206628 - its
footer holds a picture that wraps top and bottom, anchored to the footer
paragraph and lifted by 81pt, which is less than its own height of
88.5pt. Writer applied that offset as given, so the picture covered the
footer text and sat 7.3pt lower on the page than Word puts it.

What Word does, measured over hand-authored probes:

 - an object that wraps top and bottom cannot share a line with its
   anchor paragraph, and a footer paragraph has nowhere to be pushed -
   the footer holds it at the page's footer distance;
 - so the object goes up instead, until its bottom reaches the top of
   that paragraph, and it does so for any offset of zero or less, no
   matter what the offset says;
 - an offset above zero puts the object below the paragraph and is left
   alone, and square or through wrapping is not touched at all.

In the body the two already agree, because there the anchor paragraph
can move and both push it below the object.

So pull such an object up until it clears its anchor paragraph, for
MSO-formatted documents only; ODF documents without compatibility flags
keep their pre-existing layout. This reuses the compat flags as commit
409835939a3f1fbaeb1fdf705e60ce508a2be09e (tdf#162211 tdf#137590
layoutInCell: identify bMSOLayout better, 2026-04-14) did, identifying
MSO layout by TAB_OVER_MARGIN and TAB_OVER_SPACING; no new compat flag.

Headers are left alone: there Word clips the object at the page edge
instead, which is a separate question, as is whether a footer object may
hang off the bottom of the page at all (tdf#143933).

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ifabfe5ee28ba48f3bce615ff490eef52e2ab26f0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8829
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 7926e7a5852178e17d682cf1c54e5b74d34e1abf)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209267
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-18 17:47:05 +02:00
Mike Kaganski 0b151e25c8 Related: tdf#171708 sw: keep floattable clear of a header/footer object
To see the problem, open that bug's document,
https://bugs.documentfoundation.org/attachment.cgi?id=206628 - its
body starts with a floating table spanning three pages (w:tblpPr,
w:tblOverlap val="never"), and its footer holds a picture with
<wp:wrapTopAndBottom/> and a negative positionV offset, so the picture
reaches up into the body. Writer put every page's part of the table at
the top of the body and painted its last rows over the picture, and it
left the table lower on the page than Word leaves it.

What Word does, measured from its print output over hand-authored probes
with exact row heights:

 - how much of the table goes on a page comes from the full body height,
   with nothing in the way taken into account;
 - the part is then shifted up as a whole until its bottom clears the
   object below it, which may take it into the top margin area;
 - the page edge is the limit - where clearing the object would take the
   part off the page, it stays put and keeps the overlap.

The low placement and the overlap are therefore one behaviour: the shift
is what avoids the object. CalcOverlap already avoids overlaps for split
flys, but a header/footer object is anchored outside the body and so was
dismissed as a fly from another upper, and the one conflict it knows how
to resolve is by moving down - which here would push the fly off the
page.

So consider such an object too, and resolve a conflict with one that
starts below the fly by shifting the fly up until its bottom clears it,
as far as the page frame allows. Where the page cannot hold the shifted
fly, keep the overlap, and in no case give up a row to fit.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I00eed5401b5981b0f3eb9b40e7be920bf71b15b3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8826
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit b356ac63ed2824e20906b26f337d7852994c1a6b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209266
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-18 17:46:49 +02:00
Mike Kaganski f0e78f28ee cool#16066 sw: omit the repeated heading where a row needs the full page
A row that may not break across pages, and is taller than what a page has
left below the repeated heading, had nowhere to go: it stayed under the
heading, reached past the bottom of the page, and everything below that was
not shown. The setup never worked - StarOffice 5.2 drops the same text.

Two strategies fix it and both are viable: split the row anyway, or leave
the heading off the page the row needs and repeat it again afterwards. This
implements the second, because Word does that. Either would serve our own
documents, but differing from Word turns every such DOCX into an
interoperability report, and adopting Word's layout later would then need a
compatibility flag to keep the ODF documents written in the meantime laying
out as their authors designed them.

A heading is not repeated where the following row could not fit under it, in
neither of the two places that create the repeats: splitting the table, and
rebuilding its frames after a row is added. Otherwise the row is only pushed
on again, and the repeat is left holding a page of its own.

Because the change creates layouts with a lone heading row and no non-heading
row on a page, GetFirstNonHeadlineRow() now returns nullptr where it
previously could not. The two places that dereferenced it unchecked,
MakeSelUnions and SwContentFrame::Cut, are guarded; the first of them crashed
on inserting a row into such a table.

A row taller than the page itself is still split regardless of the setting,
as since commit b271cc4685 (tdf#123116 sw
layout: allow rows larger than page to split anyway, 2020-03-04).

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I5d7a6c555c0b978a9d48d5266574f90e3c41ae3d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8834
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit db3f6d7fe6b368b5e8989f389f68f73dc3c163b4)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209269
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-18 16:55:39 +02:00
Mike Kaganski 9b3f4c90ec tdf#171708: improve empty master detection
A split fly's intermediate anchor has no draw objects of its own - they
all sit in the chain's master - so it was taken for a master that had
already failed, and the last anchor was denied the split it needed.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: Ic7cf989fcdbf013fa640e94877b7805358c3be8f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8821
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit e8331d56cc14ddef0f243c9cc0091c5179457027)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209265
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-18 16:36:40 +02:00
Mike Kaganski 7152b64569 sw: fix null deref in InsertCnt_ when a section has no frame
A customer reported recurring crashes (no reproducer available) while a Writer
document was being saved by AutoRecovery.  The minidump shows a read of address
0 on the GUI thread with this stack:

    InsertCnt_                                   sw/source/core/layout/frmtool.cxx
    MakeFrames                                   sw/source/core/layout/frmtool.cxx
    UnHideRedlines                               sw/source/core/layout/wsfrm.cxx
    UnHide                                       sw/source/core/layout/wsfrm.cxx
    SwRootFrame::SetHideRedlines                 sw/source/core/layout/wsfrm.cxx
    sw::DocumentRedlineManager::SetRedlineFlags  sw/source/core/doc/DocumentRedlineManager.cxx
    SwXMLWriter::WriteMedium                     sw/source/filter/xml/wrtxml.cxx
    SwWriter::Write                              sw/source/filter/basflt/shellio.cxx
    SwDocShell::SaveAs                           sw/source/uibase/app/docsh.cxx
    SfxBaseModel::storeToRecoveryFile

Serializing to ODF temporarily shows the tracked changes, which rebuilds the
layout (SetHideRedlines -> UnHideRedlines -> MakeFrames -> InsertCnt_).  While
closing a section, InsertCnt_ does

    pLay = pLay->FindSctFrame();

and then dereferences pLay unconditionally (pLay->IsHiddenNow(), and in the other
branch pLay->ContainsContent()).  FindSctFrame() returns nullptr when pLay is not
inside a section frame, which happens for a section whose content was not laid out
- seen here with tracked changes inside a table - so the dereference crashes.
Guard the result: when there is no section frame there is nothing to close, and
pLay must be left untouched because the following nodes reuse it.

The unguarded pLay->IsHiddenNow() was added in
0c6ec63c81 ("sw: fix hiding of flys in hidden
sections, part 3", 2024-09-06).  This is the same kind of fix as
4d48d43a68 ("null deref of pActualSection seen",
2025-08-11), which guarded pActualSection in this same function.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I52d1fb1933498d7d6cb246dab4618807e106afcb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/7321
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Noel Grandin <noel.grandin@collabora.com>
(cherry picked from commit 340b7273e3401da077402b3dcf158ca12e2d7055)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209255
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-18 15:56:31 +02:00