Two defects on fills, met while building the umbrella's deck fixture (#158).
1. The PDF export drops solid slide backgrounds; the PNG export draws them
A slide background given as a fill (p:bg/p:bgPr), on the slide, its layout or
its master, is drawn in render_slide_to_png and rpptx convert --to png, and missing from to_pdf() and
rpptx convert --to pdf: the PDF page is white. White text on a coloured title slide then disappears from
the PDF. The umbrella's deck fixture shows it on its first slide, and a background set from Python through
Slide.background.fill (#142) is missing from the PDF the same way.
The render lowers the fill into PageFrame.background (rpptx-render/src/lib.rs:546). The rasteriser paints
it (oxml-pdf/src/raster.rs:294); the PDF writer (oxml-pdf/src/writer.rs) never reads the field. Picture
backgrounds are lowered as page elements instead, so they reach the PDF.
import io, subprocess
import rpptx
from pptx import Presentation
from pptx.dml.color import RGBColor
from PIL import Image
def build(path, where):
prs = Presentation()
layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(layout)
owner = {"slide": slide, "layout": layout, "master": prs.slide_master}[where]
owner.background.fill.solid()
owner.background.fill.fore_color.rgb = RGBColor(0x7B, 0x1E, 0x3A)
prs.save(path)
def corner(png):
return Image.open(io.BytesIO(png)).convert("RGB").getpixel((5, 5))
for where in ("slide", "layout", "master"):
build(f"bg_{where}.pptx", where)
prs = rpptx.Presentation(f"bg_{where}.pptx")
png = prs.render_slide_to_png(0, 20)
open(f"bg_{where}.pdf", "wb").write(prs.to_pdf())
pdf_png = subprocess.run(["pdftoppm", "-r", "20", "-png", "-singlefile", f"bg_{where}.pdf"], capture_output=True).stdout
print(f"background on the {where:<6} PNG corner {corner(png)} PDF corner {corner(pdf_png)}")
background on the slide PNG corner (123, 30, 58) PDF corner (255, 255, 255)
background on the layout PNG corner (123, 30, 58) PDF corner (255, 255, 255)
background on the master PNG corner (123, 30, 58) PDF corner (255, 255, 255)
(pdftoppm from poppler rasterises the PDF.) Acceptance: the PDF writer paints PageFrame.background
under the page content, for every paint the field can hold, and a test asserts that PDF and PNG agree on
it.
The writer is shared, so any other producer that sets the field gets the same fix.
2. A gradient whose a:lin has no ang, or whose a:path has no path, refuses the whole file
In ECMA-376 both attributes are optional (CT_LinearShadeProperties/@ang and CT_PathShadeProperties/@path,
use="optional"), and python-pptx writes <a:lin scaled="0"/> for every fill.gradient(). rpptx parses
them as required (oxml-drawing/src/fill.rs:1039 and 1055), so opening the file fails, not only the
rendering of that fill. Of the required_* calls of oxml-drawing I checked against the schema, these two
are the only ones on an optional attribute.
import zipfile
import rpptx
from pptx import Presentation
from pptx.dml.color import RGBColor
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
slide.background.fill.gradient() # python-pptx writes <a:lin scaled="0"/>, without ang
slide.background.fill.gradient_stops[0].color.rgb = RGBColor(0x7B, 0x1E, 0x3A)
slide.background.fill.gradient_stops[1].color.rgb = RGBColor(0x3E, 0x4A, 0x59)
prs.save("grad.pptx")
def variant(name, old, new):
src = zipfile.ZipFile("grad.pptx")
with zipfile.ZipFile(name, "w", zipfile.ZIP_DEFLATED) as z:
for info in src.infolist():
data = src.read(info.filename)
if info.filename == "ppt/slides/slide1.xml":
data = data.replace(old.encode(), new.encode())
z.writestr(info, data)
try:
rpptx.Presentation(name)
return "opens"
except Exception as e:
return f"{type(e).__name__}: {e}"
LIN = '<a:lin scaled="0"/>'
print("a:lin without ang ", variant("g1.pptx", LIN, LIN))
print("a:lin with ang ", variant("g2.pptx", LIN, '<a:lin ang="5400000" scaled="0"/>'))
print("a:path without path ", variant("g3.pptx", LIN, '<a:path><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>'))
print("a:path with path ", variant("g4.pptx", LIN, '<a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>'))
a:lin without ang XmlError: malformed PresentationML part /ppt/slides/slide1.xml: invalid value: DrawingML lin requires @ang
a:lin with ang opens
a:path without path XmlError: malformed PresentationML part /ppt/slides/slide1.xml: invalid value: DrawingML path requires @path
a:path with path opens
Acceptance: both files open and render, and a round trip does not add the missing attribute.
Environment: main at 9a7ed714 (S75), release build, linux x86_64, Python 3.11, python-pptx 1.0.2 for the
fixtures.
Two defects on fills, met while building the umbrella's deck fixture (#158).
1. The PDF export drops solid slide backgrounds; the PNG export draws them
A slide background given as a fill (
p:bg/p:bgPr), on the slide, its layout orits master, is drawn in
render_slide_to_pngandrpptx convert --to png, and missing fromto_pdf()andrpptx convert --to pdf: the PDF page is white. White text on a coloured title slide then disappears fromthe PDF. The umbrella's deck fixture shows it on its first slide, and a background set from Python through
Slide.background.fill(#142) is missing from the PDF the same way.The render lowers the fill into
PageFrame.background(rpptx-render/src/lib.rs:546). The rasteriser paintsit (
oxml-pdf/src/raster.rs:294); the PDF writer (oxml-pdf/src/writer.rs) never reads the field. Picturebackgrounds are lowered as page elements instead, so they reach the PDF.
(
pdftoppmfrom poppler rasterises the PDF.) Acceptance: the PDF writer paintsPageFrame.backgroundunder the page content, for every paint the field can hold, and a test asserts that PDF and PNG agree on
it.
The writer is shared, so any other producer that sets the field gets the same fix.
2. A gradient whose
a:linhas noang, or whosea:pathhas nopath, refuses the whole fileIn ECMA-376 both attributes are optional (
CT_LinearShadeProperties/@angandCT_PathShadeProperties/@path,use="optional"), and python-pptx writes<a:lin scaled="0"/>for everyfill.gradient(). rpptx parsesthem as required (
oxml-drawing/src/fill.rs:1039and1055), so opening the file fails, not only therendering of that fill. Of the
required_*calls ofoxml-drawingI checked against the schema, these twoare the only ones on an optional attribute.
Acceptance: both files open and render, and a round trip does not add the missing attribute.
Environment:
mainat9a7ed714(S75), release build, linux x86_64, Python 3.11, python-pptx 1.0.2 for thefixtures.