ASML-Labs / PPTX.jl

Generate PowerPoint PPTX files from Julia
https://asml-labs.github.io/PPTX.jl/
MIT License
86 stars 7 forks source link

Support SVG figures #32

Closed hellemo closed 1 year ago

hellemo commented 1 year ago

Thanks for this great package, so nice to not have to rely on wrapping a python library for this :-)

Any plans for supporting SVG images for nice vector graphics from the plotting packages such as CairoMakie?

matthijscox commented 1 year ago

Thanks for the compliment!

Can SVG images not be used currently? I expected that to Just Work™ since all we do is wrap around a filepath with Picture("file.svg"). But we did recently switch to ImageIO instead of Images to reduce precompile times in v0.6.

I would have to look.

hellemo commented 1 year ago

I get an error ERROR: No applicable_loaders found for SVG from FileIO, it seems.

matthijscox commented 1 year ago

This is unfortunate. I don't know when I will have time to look into a decent fix.

jaakkor2 commented 1 year ago

This will be a killer feature for PPTX since python-pptx does not support this.

v0.6 removed support for inserting wmf-files, but those got rasterized in the process which kind of defeats the purpose. I do not think SVGs ever worked with Images.jl.

matthijscox-asml commented 1 year ago

The File IO is only used to get the image_aspect_ratio of Pictures. Other than that, PPTX.jl just copy-pastes the figure file into the .pptx file.

I guess we just need to add another convenient constructor where the user manually provides the x and y sizes individually.

Then it's a matter of documentation, or removing the automatic aspect ratio functionality...

matthijscox-asml commented 1 year ago

Seems to work now in v0.6.2. Use Picture("image.svg"; size_x=40, size_y=30) to avoid the automatic ratio calculation which tries to load the file.

hellemo commented 1 year ago

Thanks! Just tested with v0.6.2 and can confirm that it works nicely.

jaakkor2 commented 1 year ago

PowerPoint (version 2208) claims file produced with PPTX.jl v0.6.2 is corrupted image

using PPTX
pres = Presentation()
logo = Picture(joinpath(pwd(), "julia-logo-color.svg"), size_x=100, size_y=100*200/320) # https://raw.githubusercontent.com/JuliaLang/julia-logo-graphics/master/images/julia-logo-color.svg
s1 = Slide()
push!(s1, logo)
push!(pres, s1)
write("foo.pptx", pres, open_ppt=false, overwrite=true)

Likely similar problem as in https://github.com/ASML-Labs/PPTX.jl/issues/28 .

PowerPoints can repair the file. One thing it does is it puts a png-version of the file for backward compatibility. If this is the culprit, we could use https://github.com/lobingera/Rsvg.jl or maybe just put a dummy png file with

using FileIO, ImageIO, ColorTypes
pixel = ColorTypes.RGB{ColorTypes.N0f8}(0.5,0.0,0.0)
width, height = 10, 100
d = fill(pixel, height, width)
save("foo.png", d)
matthijscox-asml commented 1 year ago

Okay, re-opening. And I see a PR already from you: https://github.com/ASML-Labs/PPTX.jl/pull/38. Thanks!

matthijscox-asml commented 1 year ago

PR https://github.com/ASML-Labs/PPTX.jl/pull/38 only addresses the aspect ratio I see.

Any idea how to solve the repair need, which automatically converts .svg to .png. I guess we need to reverse engineer how .pptx handles .svg files internally.

jaakkor2 commented 1 year ago

During JuliaCon, I tried to have a look at the diff between the pptx generated by PPTX.jl and the pptx repaired by PowerPoint. PowerPoint touched many files, and I could not figure out the fix.

matthijscox-asml commented 1 year ago

I made two .pptx with each one slide, one with a .png and one with a .svg.

ppt/slides/slide1.xml differences

On the slide1.xml file, the main difference is in the extension list there is now a reference to an SVG schema, and it mentions a new relationship id rId3, while the picture itself has rId2.

<a:ext uri="{96DAC541-7B7A-43D3-8B79-37D633B846F1}">
    <asvg:svgBlip xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main" r:embed="rId3"/>
</a:ext>

image

ppt/slides/_rels/slide1.xml.rels

The SVG pptx now contains two images: a png and an svg:

<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
    <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image2.svg"/>
    <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/>
    <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout6.xml"/>
</Relationships>

So rId2 is the png, and rId3 is the svg.

I guess we'll have to create a .png from the .svg and add both. (I wonder if PowerPoint regenerates the .png is constantly when the user scales the picture?)

repaired SVG

I created a .pptx with PPTX v0.6.2 with a .svg, then I allowed it to be repaired. I unzipped it.

The slide.xml now contains only a reference to the SVG

<a:blip r:embed="rId2">
  <a:extLst>
    <a:ext uri="{96DAC541-7B7A-43D3-8B79-37D633B846F1}">
      <asvg:svgBlip xmlns:asvg="http://schemas.microsoft.com/office/drawing/2016/SVG/main" r:embed="rId3"/>
    </a:ext>
  </a:extLst>
</a:blip>

Apparently that's also allowed.

matthijscox-asml commented 1 year ago

To summarize: Repairing of the pptx seems to conserve the SVG, this is good.

To avoid the repair actions, I now assume we need to

jaakkor2 commented 1 year ago

I found out that adding to [Content_Types].xml these definitions, PowerPoint is happy.

<Default
        Extension="emf"
        ContentType="image/x-emf"/>
<Default
        Extension="gif"
        ContentType="image/gif"/>
<Default
        Extension="svg"
        ContentType="image/svg+xml"/>
<Default
        Extension="tif"
        ContentType="application/octet-stream"/>
<Default
        Extension="wmf"
        ContentType="image/x-wmf"/>

Adding these to https://github.com/ASML-Labs/PPTX.jl/blob/main/templates/no-slides/%5BContent_Types%5D.xml and https://github.com/ASML-Labs/PPTX.jl/blob/main/templates/no-slides-dark/%5BContent_Types%5D.xml is easy, but to be figured out how to add to user-supplied template.

jaakkor2 commented 1 year ago

Something like this

using EzXML

path = "[Content_Types].xml"
doc = readxml(path)
r = root(doc)
addelement!(r, """Default Extension="emf" ContentType="image/x-emf" """)
addelement!(r, """Default Extension="gif" ContentType="image/gif" """)
addelement!(r, """Default Extension="svg" ContentType="image/svg+xml" """)
addelement!(r, """Default Extension="tif" ContentType="application/octet-stream" """)
addelement!(r, """Default Extension="wmf" ContentType="image/x-wmf" """)
open(path, "w") do io
    prettyprint(io, doc)
end

One thing to check is that if the template contains already some of the extension definitions, is it ok to have duplicate lines.

matthijscox-asml commented 1 year ago

Interesting. If we can avoid the image conversion that would be splendid. I found Rsvg.jl for SVG, but we'd have to figure this out for every vector format...

jaakkor2 commented 1 year ago

No image conversions needed for any vector formats!

Even adding a gif-file without <Default Extension="gif" ContentType="image/gif"/> leads to PowerPoint complaining.

matthijscox-asml commented 1 year ago

Okay, this is the way to go then. Great that you found out about this!

Did you already try adding a duplicate line?

jaakkor2 commented 1 year ago

Duplicated lines results in complaints from PowerPoint. Existing [Content_Types].xml has to be checked before adding new entries.

matthijscox-asml commented 1 year ago

Should be fixed by @jaakkor2 in v0.6.4