python-pptx: read font color

Question:

I want to read the font color from a given textbox. I can extract font name and bold but not color (or font name).

Here’s my code:

text_frame = shape.text_frame
paragraph = text_frame.paragraphs[0]

for run in paragraph.runs:
    font = run.font
    try:
        font_size = font.size.pt
        print(font_size)
        font_bold = font.bold
        print(font_bold)
        font_name = font.name
        print(font_name)
        color = font.color.rgb
        print(color)
    except:
        pass

returns:

36.0
True
None

thanks

Asked By: Boosted_d16

||

Answers:

In PowerPoint (roughly like CSS in this aspect), font attributes can be applied at a variety of levels in what’s called (by some at least) the style hierarchy. The bottom level of these, that overrides any levels above, is applying the style directly to a particular run. Only a directly-applied attribute like this can be retrieved using the properties like .bold and .color.

There could be attributes like .effective_bold and .effective_color which navigate the style hierarchy to calculate what value will be applied at rendering time, but there are not (yet).

So the None value for font.color.rgb indicates that run inherits its color settings from its style hierarchy (e.g. paragraph default, shape default, theme, or presentation default, etc.) but unfortunately does not traverse the style hierarchy to determine what its effective color setting is.

Answered By: scanny

I have had the same need for fills. The difference is not that big. It works in a similar way. Simplified it is just to change "fill.fore_color" to "font.color".

First thing is to check if the color given is specific (rgb is not None). If it is you have gotten it. If not, pursue the theme color path to get the RGB value from there.

srgb = font.color.rgb
if srgb == None:
    theme_color = font.color.theme_color
    brightness = font.color.brightness

Now what you have is a theme_color and a brightness. The theme color is actually a numeric value defining which "accent" color was used in the PowerPoint. It can be resolved by MSO_THEME_COLOR, through a private dictionary "_member_to_xml", which you can use to define the xml path needed to extract it:

from pptx.enum.dml import MSO_THEME_COLOR
accent = MSO_THEME_COLOR._member_to_xml[theme_color]
xpath = 'a:themeElements/a:clrScheme/a:{}/a:srgbClr/@val'.format(accent)

To do the extraction, you need to have the proper theme information available, which you get by extracting the theme information from the slide_master defined by the layout used by the current slide you are in. For that is needed some more imports:

from pptx.opc.constants import RELATIONSHIP_TYPE as RT
from pptx.oxml import parse_xml
slide_master_part = slide.slide_layout.slide_master.part
theme_part = slide_master_part.part_related_by(RT.THEME)
theme = parse_xml(theme_part.blob)  # theme here is an <a:theme> element

Now finally, you have what is needed to find out what PPT accent color was used, going back to the xpath we defined earlier, and translating it using the theme we just found:

hex_color = theme.xpath(xpath)[0]

If there is no brightness (brightness is zero), your hex_color not only defines the accent that was used, but also defines the true RGB color. This is extracted from the hex color value using any method you want. For example imagecolor:

from PIL import ImageColor
import numpy as np
srgb = np.array(ImageColor.getcolor('#{}'.format(hex_color), 'RGB'))

However, if brightness (which we found when finding the theme color in the start) is not zero, we have more to do. The true color is then darker or brighter than the accent color, defined by the brightness value. The additional step needed to get to the real RGB require adjusting with that brightness. Making the color brighter in a similar way as in PowerPoint require a translation to HSL. Then changing the luminance, and then convert back to RGB:

import colorsys
srgb = srgb / 255
h, luminance, s = colorsys.rgb_to_hls(*srgb)
lum_mod = 100000 * (1 - brightness)
lum_off = 100000 * brightness
luminance = luminance * (lum_mod / 100000) + (lum_off / 100000)
srgb = np.array(colorsys.hls_to_rgb(h, luminance, s))
srgb = (srgb * 255).round(0).astype(int)

I showed above the steps as needed to do one color. Finding the slide master theme is only needed once per slide, really only once per master inside a PPT presentation. So the real code is better split into an order fitting your needs.

Answered By: Mats Bengtsson

You can access the font color attributes in python-pptx once you change the color in the PowerPoint slides. Either you change to a preset color within PowerPoint: ‘standard color’ and in python-pptx: run.font.color.theme_color. Or you choose a custom color in PowerPoint: if you use the eyedropper or click on ‘more colors’ and in python-pptx: run.font.color.rgb.
If you don’t set the color attributes within PowerPoint then python-pptx will return ‘None’.

variable_dict = {"test" : "test"}
for slide in prs.slides:
        for shape in slide.shapes:
            if hasattr(shape, "text_frame"):
                for paragraph in shape.text_frame.paragraphs:
                    for key, value in variable_dict.items():
                        if key in paragraph.text: 
    
                            for run in paragraph.runs:
                                font_size = run.font.size
                                font_name = run.font.name
                                try:
                                    font_theme_color = run.font.color.theme_color
                                except AttributeError:
                                    font_theme_color = None
                                try:
                                    font_rgb_color = run.font.color.rgb
                                except AttributeError:
                                    font_rgb_color = None
                                break
                            
                            paragraph.text = paragraph.text.replace(key, str(value))
    
                            paragraph.font.size = font_size
                            paragraph.font.name = font_name
                            try:
                                if font_theme_color:
                                    paragraph.font.color.theme_color = font_theme_color
                            except AttributeError:
                                pass
                            try:
                                if font_rgb_color:
                                    paragraph.font.color.rgb = font_rgb_color
                            except AttributeError:
                                pass
Answered By: Philip
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.