https://bugs.documentfoundation.org/show_bug.cgi?id=173238

            Bug ID: 173238
           Summary: Impress: PPTX with an SVG shape fill never finishes
                    loading, because XFillBitmapItem::isPattern()
                    rasterises the whole SVG just to test for an 8x8
                    pattern bitmap
           Product: LibreOffice
           Version: 26.2.5.2 release
          Hardware: All
                OS: All
            Status: UNCONFIRMED
          Severity: normal
          Priority: medium
         Component: graphics stack
          Assignee: [email protected]
          Reporter: [email protected]

Description:
svx/source/xoutdev/xattrbmp.cxx:162

    bool XFillBitmapItem::isPattern() const
    {
        Color aBack, aFront;
        return vcl::bitmap::isHistorical8x8(
            GetGraphicObject().GetGraphic().GetBitmap(), aBack, aFront);
    }

GetBitmap() forces a full rasterisation of the graphic. But isHistorical8x8()
(vcl/source/bitmap/BitmapTools.cxx:882) returns false immediately unless the
bitmap is exactly 8x8 pixels.

So LibreOffice renders a 1.8 MB SVG only to find out it is not 8 pixels wide.

These "historical 8x8" patterns only come from old binary formats. A vector
graphic can never be one. Checking the graphic type before calling GetBitmap()
would skip the render entirely and cost nothing.

This is not the same code path as tdf#168699. That bug is an SVG inserted as an
image, which becomes an SdrGrafObj and reaches basegfx through
Graphic::GetPrefSize(). This one is an SVG used as a shape fill, which becomes
an XFillBitmapItem and reaches basegfx through isPattern(). Fixing the SVG
renderer would fix both. Fixing isPattern() fixes only this one, but it is a
small and safe change.


Steps to Reproduce:
1. Open the attached svg-fill-repro.pptx in Impress.
2. Wait.

The attachment is one slide with one rectangle. The rectangle's area fill is an
SVG. Nothing else is in the file.

To confirm the SVG fill is what does it, the second attachment
control-png-only.pptx is byte-for-byte the same file with only the
asvg:svgBlip extension removed, so the shape falls back to its PNG. It opens in
0.9 seconds.

Actual Results:
The file never opens. soffice.bin sits at 100% CPU. It did not finish in 180
seconds and I killed it. Memory stays flat at about 220 MB, so it is CPU only,
not a leak.

Stack, taken by interrupting the hang:

  log10
  rtl_math_approxEqual
  basegfx::utils::isPointOnLine
  basegfx::utils::isPointOnPolygon
  basegfx::utils::isInside
  basegfx::utils::isInside
  basegfx::utils::createNonzeroConform
  (libsvgio)
  VectorGraphicData::ensureSequenceAndRange
  VectorGraphicData::ensureReplacement
  VectorGraphicData::getReplacement
  ImpGraphic::updateBitmapFromVectorGraphic
  ImpGraphic::getBitmap
  Graphic::GetBitmap
  XFillBitmapItem::isPattern                  <-- the avoidable call
  XFillBitmapItem::checkForUniqueItem
  sdr::properties::BaseProperties::SetMergedItemSetAndBroadcast
  SdrObject::SetMergedItemSetAndBroadcast
  SvxShape::SetFillAttribute
  SvxShape::setPropertyValueImpl
  SvxShape::setPropertyValues
  oox::PropertySet::setProperties

The original file I hit this with produces the same stack.

Expected Results:
The file opens. isPattern() should answer "no" without rendering the graphic,
because a vector graphic is never an 8x8 pattern bitmap.


Reproducible: Always


User Profile Reset: Yes

Additional Info:
Version: 26.2.5.2 (X86_64) / LibreOffice Community
Build ID: 30643f3b95f81f04449dc87ba649cc313a42270e
CPU threads: 8; OS: Linux 7.1; UI render: default; VCL: kf6
Locale: en-US (en_US.UTF-8); UI: en-US
Calc: threaded

Also reproduces on a locally built 26.2.4.2 with a Skia/Vulkan Qt backend, so
it is not specific to the packaged build or to any rendering backend. Verified
with --headless --convert-to pdf, which uses no GPU at all.

The SVG in the attachment is generated, not taken from anywhere. It is one
<path> with 300 concentric closed subpaths of 60 cubic beziers each, 18,000
segments total, and no fill-rule attribute, so nonzero applies. Concentric
matters: it makes each subpath's bounding box contain the ones inside it, which
is what stops createNonzeroConform rejecting the pairs on the cheap range check
before it reaches isInside().

I first hit this on a real presentation template whose worst SVG is one <path>
with a 1,362,526-character "d" attribute, about 36,600 beziers over 19
subpaths. Same stack. I have not attached that file because I do not think its
licence allows redistribution. Happy to provide it privately if it helps.

Related: tdf#168699 (the SVG renderer itself). Same basegfx stack, different
entry point.

Note on the existing guard in createNonzeroConform
(basegfx/source/polygon/b2dpolypolygoncutter.cxx:701):

    if (rCandidate.count() > 1000)

count() is the number of subpaths, not points. This path has 19 subpaths, so
the guard never fires no matter how many bezier segments they contain. That may
be worth a look on tdf#168699 rather than here.

Workaround for anyone hitting this: each SVG in a PPTX sits in an asvg:svgBlip
extension inside an a:blip that already points at a PNG fallback. Removing the
extension blocks makes the file load in about 5 seconds with no visible
difference.

-- 
You are receiving this mail because:
You are the assignee for the bug.

Reply via email to