Insert images to powerpoint slide using python (win32com.client) - python

I have been tasked with inserting and re-sizing several hundred images to a powerpoint.
I need to use a particular source format that is similar to other power points used by our company.
I have been playing around with activepython's win32com API's and I have figured out how to open up a file and create a blank slide.
My question is how would I go about inserting an image and resizing it to whatever size I need(the images will be the only thing on each page). Also I am trying to use my company's theme for the background and title page but this is not as important as getting the images on page and re-sized.
any help would be greatly appreciated, thanks!

I got this from the page Xavier referred to:
Pict1 = Slide1.Shapes.AddPicture(FileName=pictName, LinkToFile=False, SaveWithDocument=True, Left=100, Top=100, Width=200, Height=200)
That will work if your original images are square; otherwise, it will distort them.
Better to specify -1 for the width and height. Then PPT will insert them at their "natural" size (whatever PPT might decide that is ... not important for present purposes). Then you can read the shape's size to determine its aspect ratio and make sure that stays constant if you change the size or you can set the shape's .LockAspectRatio property to true and adjust either height or width and the other will auto adjust to maintain the aspect ratio.

You should use Shapes.AddPicture as described here (archived version of this broken link):
from tkinter import *
import tkinter.filedialog as tkFileDialog
import win32com.client # middleman/translator/messanger between windows and python
import win32com.gen_py.MSO as MSO # contains constants refering to Microsoft Office Objects
import win32com.gen_py.MSPPT as MSPPT # contains constants refering to Microsoft Office Power Point Objects
g = globals() # a dictonary of global vlaues, that will be the constants of the two previous imports
for c in dir(MSO.constants): g[c] = getattr(MSO.constants, c) # globally define these
for c in dir(MSPPT.constants): g[c] = getattr(MSPPT.constants, c)
Application = win32com.client.Dispatch("PowerPoint.Application")
Application.Visible = True # shows what's happening, not required, but helpful for now
Presentation = Application.Presentations.Add() # adds a new presentation
Slide1 = Presentation.Slides.Add(1, ppLayoutBlank) # new slide, at beginning
TenptStr = Slide1.Shapes.AddShape(msoShape10pointStar, 100, 100, 200, 200)
pictName = tkFileDialog.askopenfilename(title="Please Select the Image you wish to load")
print(pictName)
Pict1 = Slide1.Shapes.AddPicture(FileName=pictName, LinkToFile=False, SaveWithDocument=True, Left=100, Top=100, Width=200, Height=200)
Note that the "choose file" option produces a file path with '/" rather than '' and you may need to replace the former by the latter.

Firstly I would resize them first before insertion using PIL python imaging library. In general mucking about with any office image processing has been uniformly awful for me so I would not recommend it.
secondly the object model examples in VBA found in PowerPoint help or online are usually all you need to get a long way
from memory the insert was pretty easy -
ppt.slide.ImageFromFile("path")
however exact positioning I do not remember and I am afraid I don't have ppt here to try out. If I get achance later I will post some references
Good luck with the docs

I agree with the accepted answer. I would like to point out that I needed to prefix the FileName string with an 'r' to treat the backslashes as literal characters. Escaping the backslashes by replacing single backslashes with double also works. #vestland this may be helpful

Related

Vertical alignment in SVG via svgwrite

I'm having issues with vertical alignment of texts when using the svgwrite python library.
For example, here's a simple code that generates a red-filled circle with a black text saying "Text" on top of the circle:
import svgwrite
d = svgwrite.Drawing(filename='alignment_test.svg',
size=(60,60))
circle = d.circle((30,30), 30, fill='red')
text = d.text('Test', (30,30),
style='text-anchor:middle',
font_size='17px')
d.add(circle)
d.add(text)
d.save()
The result is this expected image:
However, I want the text to also be vertically aligned.
I tried using the alignment-baseline attribute of SVG, i.e.
style='text-anchor:middle;\
alignment-baseline:middle'
However, it doesn't work - neither in FireFox (where the vertical alignment just doesn't work), not in Inkscape, which complains Unimplemented style property 363.
I would appreciate any suggestions on how to solve the matter.
...so while writing this question I searched around a bit more, and actually read the second answer here (by toutankh):
vertical alignment of text element in SVG,
which suggests using the attribute dominant-baseline instead of alignment-baseline for text objects. And it works perfectly.
Lesson learned: never read only the first answer to a SO question.

How to Invert SVG text with appropriate kerning?

How can I invert (rotate 180 degrees) a text object so that the text is kerned appropriately?
My example uses Python and the svgwrite package, but my question seems about any SVG.
Suppose I use the following code:
dwg = svgwrite.Drawing()
dwg.add(dwg.text(fullName, (int(width/2.),gnameHeight),
font_size=gnameFontSize, text_anchor="middle"))
The above code generates text looking like this:
dwg.text() objects accept a rotate parameter that is applied to all characters in a text string, so I've used the following code to reverse the string first:
pcRotate = [180]
ngap = 1
revFullName = fullName
rcl = []
for c in revFullName:
rcl.append(c)
for i in range(ngap):
rcl.append(' ')
rcl.reverse()
revFullName = ''.join(rcl)
dwg.add(dwg.text(revFullName, (int(width/2.),pcnameHeight),
font_size=gnameFontSize, text_anchor="middle", rotate=pcRotate))
But, this produces the very ugly version below:
and this is using an artificial space gap between characters to make it slightly less unreadable.
What's the best way to tap into whatever kerning is being used by standard text in this inverted situation?
The rotate attribute of a <text> element is intended for situations where you want to rotate individual characters. If you want to rotate the whole text object then you should be using a transform instead.
http://pythonhosted.org/svgwrite/classes/mixins.html#transform-mixin
I'm posting this as a self-answer, only to make formatting more clear. Two useful hints from #paul-lebeau happily acknowledged.
While the svgwrite package seems solid, its documentation is a bit thin. The two things I wish it had said:
The rotate attribute of a <text> element is intended for situations where you want to rotate individual characters. If you want to rotate the whole text object, then you should be using a transform mixin instead.
If you need to center the transformed text with respect to some center (other that the default current user coordinate system), add two additional parameters xctr,yctr. This differs from the doc which calls for a single center argument that is a (2-tuple).
The correct code is:
pcRotate = 'rotate(180,%s,%s)' % (int(width/2.),pcnameHeight)
textGroup = svgwrite.container.Group(transform=pcRotate)
textGroup.add(dwg.text(fullName, (int(width/2.),pcnameHeight),
font_size=gnameFontSize, text_anchor="middle"))
dwg.add(textGroup)

Python, how to insert value in Powerpoint template?

I want to use an existing powerpoint presentation to generate a series of reports:
In my imagination the powerpoint slides will have content in such or similar form:
Date of report: {{report_date}}
Number of Sales: {{no_sales}}
...
Then my python app opens the powerpoint, fills in the values for this report and saves the report with a new name.
I googled, but could not find a solution for this.
There is python-pptx out there, but this is all about creating a new presentation and not inserting values in a template.
Can anybody advice?
Ultimately, barring some other library which has additional functionality, you need some sort of brute force approach to iterate the Slides collection and each Slide's respective Shapes collection in order to identify the matching shape (unless there is some other library which has additional "Find" functionality in PPT). Here is brute force using only win32com:
from win32com import client
find_date = r'{{report_date}}'
find_sales = r'{{no_sales}}'
report_date = '01/01/2016' # Modify as needed
no_sales = '604' # Modify as needed
path = 'c:/path/to/file.pptx'
outpath = 'c:/path/to/output.pptx'
ppt = client.Dispatch("PowerPoint.Application")
pres = ppt.Presentations.Open(path, WithWindow=False)
for sld in pres.Slides:
for shp in sld.Shapes:
with shp.TextFrame.TextRange as tr:
if find_date in tr.Text
tr.Replace(find_date, report_date)
elif find_sales in shp.TextFrame.Characters.Text
tr.Replace(find_sales, no_sales)
pres.SaveAs(outpath)
pres.Close()
ppt.Quit()
If these strings are inside other strings with mixed text formatting, it gets trickier to preserve existing formatting, but it should still be possible.
If the template file is still in design and subject to your control, I would consider giving the shape a unique identifier like a CustomXMLPart or you could assign something to the shapes' AlternativeText property. The latter is easier to work with because it doesn't require well-formed XML, and also because it's able to be seen & manipulated via the native UI, whereas the CustomXMLPart is only accessible programmatically, and even that is kind of counterintuitive. You'll still need to do shape-by-shape iteration, but you can avoid the string comparisons just by checking the relevant property value.
I tried this on a ".ppx" file I had hanging around.
A microsoft office power point ".pptx" file is in ".zip" format.
When I unzipped my file, I got an ".xml" file and three directories.
My ".pptx" file has 116 slides comprised of 3,477 files and 22 directories/subdirectories.
Normally, I would say it is not workable, but since you have only two short changes you probably could figure out what to change and zip the files to make a new ".ppx" file.
A warning: there are some xml blobs of binary data in one or more of the ".xml" files.
You can definitely do what you want with python-pptx, just perhaps not as straightforwardly as you imagine.
You can read the objects in a presentation, including the slides and the shapes on the slides. So if you wanted to change the text of the second shape on the second slide, you could do it like this:
slide = prs.slides[1]
shape = slide.shapes[1]
shape.text = 'foobar'
The only real question is how you find the shape you're interested in. If you can make non-visual changes to the presentation (template), you can determine the shape id or shape name and use that. Or you could fetch the text for each shape and use regular expressions to find your keyword/replacement bits.
It's not without its challenges, and python-pptx doesn't have features specifically designed for this role, but based on the parameters of your question, this is definitely a doable thing.

Finding The Current Size Of A tkinter.Text Widget

I am trying to create a roguelike using the Text widget.
I have figured out a few things, namely that I can set the size of the widget using width and height options and that I can find the pixel height or width of said widget. However, what I want to do is have the widget resizable (pack(expand="yes", fill="both")) but be able to refresh the displayed text on a resize. Is there a way to get the character dimensions when the widget is running without resorting to winfo_width() and math based on pixel dimensions of characters?
I've run into that exact same problem a couple times jaccarmac, and to my knowledge there is no way to find the width of a string of characters. Really the only way is to use the winfo_ commands: width, height, geometry. However, it kind of sounds like you just want to make sure that all of the text is displayed if you change the label and add more text. If that is the case, you don't have to worry about it. That should all be taken care of by the widgets themselves. If you don't see them expanding to show all of your label, that usually means one of the widgets containing that label is not set to expand (either using expand=YES with .pack, or columnconfigure(i, weight=1) for .grid).
A final thought; in the pack arguments make sure it's YES, and not "yes". That uppercase YES is not a string, but a variable name defined by Tkinter.
There is no way to automatically get the width in characters, but it's easy to calculate, assuming you're using a fixed width font. One way to do this is to use the font_measure method of a font object. Use font_measure to get the width of a '0' (or any other character for that matter; I think tk users zero internally, not that it matters with a fixed width font), then use this in your calculations.
This is an old question, but after doing some research I've found that there actually is a way to get height/width info directly without maths or playing with font widths using the Text widget's cget() method:
text_widget = tk.Text()
width_in_char = text_widget.cget('width')
height_in_char = text_wdiget.cget('height')
Since the Text widget stores its height and width configuration in characters, simply querying for those parameters will give you what you're looking for.

Suggestions on manipulating an SVG map

I'm working on a map of the native languages of California for Wikipedia. The map contains areas that each correspond to a language. The original looks like this (click it to see the SVG):
I want to make "locator maps" for each of those individual languages by hand (in Inkscape), like this one, for a language called Cahuilla (which has the language code cah):
Needless to say, doing this would be a pain in the neck if I generated all 60-some by hand in Inkscape.
Worse, whenever I find a mistake in the original, I'd have to redo the whole set of locator maps. (And in fact I recently realized that my original is missing one language entirely. Sorry Cupeño.)
So my goal is to automate this process. I don't have much experience processing SVG or even XML, so I'm looking for recommendations as to which libraries would be most helpful. I'd prefer solutions in Python, sincely I'm not hopeless in that language.
To summarize, I need to:
Get a list of references to all the language shapes in the original SVG file.
For each language in this list:
change the background color of the shape for the current language
put a box around the language
duplicate the box and scale it to a given size (I recognize that in this step it might be difficult to actually "crop" the surrounding shapes as I did in my example -- just creating a box with the language shape on a white background would be sufficient.)
place the duplicate in the upper right corner
save all this stuff in code.svg
The final product will then be 60 SVG files named cah.svg, etc. Better yet, it would be possible to re-generate the whole shebang if it became necessary to edit the original map (which is quite likely).
I would recommend using Python and specifically creating extensions for Inkscape. I don't think you really need 60 SVG unless you really want to because the source map will have everything you need.
What I would do is use Inkscape to rename the various regions to the same language code you will be using. For example, Cahuilla or cah is currently path13882 in your SVG file. I would rename it to cah or Cahuilla and repeat the process for each of the language regions. Use the Edit\XML Editor to help make sure you update all of the paths.
After you have updated the names/IDs, then you might look into SVG scripting. I would just create a javascript/ecmascript map or dictionary that has the relevant information for the language:
var langaugeMap = {};
languageMap["cah"] = { name: "Cahuilla", color: "rgb(255, 0, 0)" };
languageMap["cup"] = { name: "Cupeño", color: "rgb(255, 64, 0)" };
// and so on -- this could even be generated from a CSV file or Excel, etc.
// if the highlighted color is always the same, then you don't need it in the map
// or use style sheets for an activeshape and inactiveshape
// Put any information that is specific to a language in the map
Then you just need to add a mouseover function that would add and position the bounding box and change the path color. Here is one example of events and scripting although it is quite dated. Carto.net has an interactive map example as well.
The SVG would look something like:
<path
style="fill:#800000;fill-opacity:1;display:inline"
d="m 422.43078,517.40746 c 0.52151,0.006 1.10755,0.0374 1.75925,0.0825 3.82011,0.26462 5.01088,0.75501 5.75001,2.37491 0.51312,1.12355 2.4121,3.0097 4.22213,4.1946 3.906,2.55656 7.38824,2.07964 9.61517,-1.3194 2.12996,-3.25075 9.13451,-3.19196 13.61739,0.11545 1.77185,1.30707 4.04994,2.38037 5.06319,2.38041 1.01325,0 3.34593,0.92548 5.18421,2.06155 2.52816,1.56236 4.9918,2.09869 10.09889,2.19902 3.71359,0.0729 7.68145,0.64349 8.82374,1.26442 2.81717,1.53202 5.67633,1.42382 10.7693,-0.40133 4.97461,-1.78261 6.31161,-1.36525 17.10267,5.31063 3.39862,2.10239 6.90491,4.08094 7.7956,4.39801 2.46593,0.8776 4.55428,4.66976 3.95259,7.17971 -0.29359,1.22605 -0.75898,3.51121 -1.03349,5.07968 -0.27411,1.56855 -0.88382,3.33952 -1.35761,3.93621 -1.50842,1.89871 -20.98501,7.77151 -27.8945,8.41122 -3.66014,0.33879 -8.3091,1.04337 -10.32987,1.56676 -3.50666,0.90799 -3.81743,0.79746 -6.78388,-2.44089 -3.3486,-3.65594 -6.11308,-4.2716 -8.48815,-1.89661 -2.14408,2.14401 -1.85126,3.96434 1.0667,6.66846 1.40725,1.30409 1.85699,2.10446 1.00027,1.77571 -0.85672,-0.32883 -6.3937,-0.12213 -12.3033,0.46176 -5.9096,0.58386 -12.56062,1.27336 -14.78297,1.53381 -4.17058,0.4888 -5.09869,-0.37014 -2.61673,-2.42989 2.1563,-1.78956 1.74245,-2.63318 -1.65999,-3.36449 -1.69931,-0.36525 -4.94789,-1.90738 -7.213,-3.42496 -2.26473,-1.51754 -5.89662,-3.66823 -8.07583,-4.77731 -2.17921,-1.10923 -6.21922,-3.94186 -8.97721,-6.29463 -4.75318,-4.05478 -4.93682,-4.36681 -3.43604,-6.02527 0.96935,-1.07117 2.36209,-1.56397 3.5899,-1.26992 1.62639,0.38937 2.49494,-0.41237 4.59588,-4.24958 1.42481,-2.60257 2.23686,-4.95457 1.80316,-5.22266 -0.4337,-0.26805 -1.06784,-3.14557 -1.40725,-6.39358 -0.33978,-3.24797 -1.19001,-6.79064 -1.89134,-7.87242 -1.74322,-2.68957 -1.2114,-3.65437 2.44111,-3.61188 l 0,0 z"
id="cah"
inkscape:label="#cah"
onmouseover="highlightRegion(evt);"
onmouseout="restoreRegion(evt);" />
For any automation with the SVG file, I would use Inkscape extensions. Look at the Inkscape wiki under the Developer Tutorials / Extensions. Otherwise, SVG is still XML so if you have named your language regions in the source file with a distinguishing name, you could use Python to parse the XML and save each path/language region to a separate file.
Here's an example using your map. You can click any element to get the boundingbox, this can be used for the miniview viewBox (with some tweaking). As you see it's not adding much code to the map, just a couple of elements. When you have all the viewBoxes you can update the miniview 'viewBox' attribute on the fly using javascript.
For the coloring of the selected shape, I'd suggest adding a <use> element inside the miniview <svg> that has a fill that overrides any fill used in the main map (you may have to use a stylesheet rule for this to make sure it's got high specificity, e.g adding #miniview .activeshape { fill: red !important }. Let the <use> point to the selected shape, and make sure you add class="activeshape" on the shape you point to.
This should be a start at least, hope it helps :)

Categories

Resources