def request(method, url, **kwargs): """ Central place to make any v1 api request. :param (str) method: The request method ('get', 'put', 'delete', ...). :param (str) url: The full api url to make the request to. :param kwargs: These are passed along to requests. :return: (requests.Response) The response directly from requests. """ if kwargs.get('json', None) is not None: # See chart_studio.api.v2.utils.request for examples on how to do this. raise _plotly_utils.exceptions.PlotlyError( 'V1 API does not handle arbitrary json.') kwargs['headers'] = dict(kwargs.get('headers', {}), **get_headers()) kwargs['verify'] = config.get_config()['plotly_ssl_verification'] try: response = requests.request(method, url, **kwargs) except RequestException as e: # The message can be an exception. E.g., MaxRetryError. message = str(getattr(e, 'message', 'No message')) response = getattr(e, 'response', None) status_code = response.status_code if response else None content = response.content if response else 'No content' raise exceptions.PlotlyRequestError(message, status_code, content) validate_response(response) return response
def get_headers(): """ Using session credentials/config, get headers for a V2 API request. Users may have their own proxy layer and so we free up the `authorization` header for this purpose (instead adding the user authorization in a new `plotly-authorization` header). See pull #239. :returns: (dict) Headers to add to a requests.request call. """ from plotly import version creds = config.get_credentials() headers = { "plotly-client-platform": "python {}".format(version.stable_semver()), "content-type": "application/json", } plotly_auth = basic_auth(creds["username"], creds["api_key"]) proxy_auth = basic_auth(creds["proxy_username"], creds["proxy_password"]) if config.get_config()["plotly_proxy_authorization"]: headers["authorization"] = proxy_auth if creds["username"] and creds["api_key"]: headers["plotly-authorization"] = plotly_auth else: if creds["username"] and creds["api_key"]: headers["authorization"] = plotly_auth return headers
def build_url(resource, id="", route=""): """ Create a url for a request on a V2 resource. :param (str) resource: E.g., 'files', 'plots', 'grids', etc. :param (str) id: The unique identifier for the resource. :param (str) route: Detail/list route. E.g., 'restore', 'lookup', etc. :return: (str) The url. """ base = config.get_config()["plotly_api_domain"] formatter = {"base": base, "resource": resource, "id": id, "route": route} # Add path to base url depending on the input params. Note that `route` # can refer to a 'list' or a 'detail' route. Since it cannot refer to # both at the same time, it's overloaded in this function. if id: if route: url = "{base}/v2/{resource}/{id}/{route}".format(**formatter) else: url = "{base}/v2/{resource}/{id}".format(**formatter) else: if route: url = "{base}/v2/{resource}/{route}".format(**formatter) else: url = "{base}/v2/{resource}".format(**formatter) return url
def clientresp(data, **kwargs): """ Deprecated endpoint, still used because it can parse data out of a plot. When we get around to forcing users to create grids and then create plots, we can finally get rid of this. :param (list) data: The data array from a figure. """ from plotly import version creds = config.get_credentials() cfg = config.get_config() dumps_kwargs = {'sort_keys': True, 'cls': PlotlyJSONEncoder} payload = { 'platform': 'python', 'version': version.stable_semver(), 'args': _json.dumps(data, **dumps_kwargs), 'un': creds['username'], 'key': creds['api_key'], 'origin': 'plot', 'kwargs': _json.dumps(kwargs, **dumps_kwargs) } url = '{plotly_domain}/clientresp'.format(**cfg) response = request('post', url, data=payload) # Old functionality, just keeping it around. parsed_content = response.json() if parsed_content.get('warning'): warnings.warn(parsed_content['warning']) if parsed_content.get('message'): print(parsed_content['message']) return response
def get_headers(): """ Using session credentials/config, get headers for a V2 API request. Users may have their own proxy layer and so we free up the `authorization` header for this purpose (instead adding the user authorization in a new `plotly-authorization` header). See pull #239. :returns: (dict) Headers to add to a requests.request call. """ from plotly import version creds = config.get_credentials() headers = { 'plotly-client-platform': 'python {}'.format(version.stable_semver()), 'content-type': 'application/json' } plotly_auth = basic_auth(creds['username'], creds['api_key']) proxy_auth = basic_auth(creds['proxy_username'], creds['proxy_password']) if config.get_config()['plotly_proxy_authorization']: headers['authorization'] = proxy_auth if creds['username'] and creds['api_key']: headers['plotly-authorization'] = plotly_auth else: if creds['username'] and creds['api_key']: headers['authorization'] = plotly_auth return headers
def build_url(resource, id='', route=''): """ Create a url for a request on a V2 resource. :param (str) resource: E.g., 'files', 'plots', 'grids', etc. :param (str) id: The unique identifier for the resource. :param (str) route: Detail/list route. E.g., 'restore', 'lookup', etc. :return: (str) The url. """ base = config.get_config()['plotly_api_domain'] formatter = {'base': base, 'resource': resource, 'id': id, 'route': route} # Add path to base url depending on the input params. Note that `route` # can refer to a 'list' or a 'detail' route. Since it cannot refer to # both at the same time, it's overloaded in this function. if id: if route: url = '{base}/v2/{resource}/{id}/{route}'.format(**formatter) else: url = '{base}/v2/{resource}/{id}'.format(**formatter) else: if route: url = '{base}/v2/{resource}/{route}'.format(**formatter) else: url = '{base}/v2/{resource}'.format(**formatter) return url
def request(method, url, **kwargs): """ Central place to make any api v2 api request. :param (str) method: The request method ('get', 'put', 'delete', ...). :param (str) url: The full api url to make the request to. :param kwargs: These are passed along (but possibly mutated) to requests. :return: (requests.Response) The response directly from requests. """ kwargs["headers"] = dict(kwargs.get("headers", {}), **get_headers()) # Change boolean params to lowercase strings. E.g., `True` --> `'true'`. # Just change the value so that requests handles query string creation. if isinstance(kwargs.get("params"), dict): kwargs["params"] = kwargs["params"].copy() for key in kwargs["params"]: if isinstance(kwargs["params"][key], bool): kwargs["params"][key] = _json.dumps(kwargs["params"][key]) # We have a special json encoding class for non-native objects. if kwargs.get("json") is not None: if kwargs.get("data"): raise _plotly_utils.exceptions.PlotlyError( "Cannot supply data and json kwargs." ) kwargs["data"] = _json.dumps( kwargs.pop("json"), sort_keys=True, cls=PlotlyJSONEncoder ) # The config file determines whether reuqests should *verify*. kwargs["verify"] = config.get_config()["plotly_ssl_verification"] try: response = requests.request(method, url, **kwargs) except RequestException as e: # The message can be an exception. E.g., MaxRetryError. message = str(getattr(e, "message", "No message")) response = getattr(e, "response", None) status_code = response.status_code if response else None content = response.content if response else "No content" raise exceptions.PlotlyRequestError(message, status_code, content) validate_response(response) return response
def get_headers(): """ Using session credentials/config, get headers for a v1 API request. Users may have their own proxy layer and so we free up the `authorization` header for this purpose (instead adding the user authorization in a new `plotly-authorization` header). See pull #239. :returns: (dict) Headers to add to a requests.request call. """ headers = {} creds = config.get_credentials() proxy_auth = basic_auth(creds['proxy_username'], creds['proxy_password']) if config.get_config()['plotly_proxy_authorization']: headers['authorization'] = proxy_auth return headers
def request(method, url, **kwargs): """ Central place to make any api v2 api request. :param (str) method: The request method ('get', 'put', 'delete', ...). :param (str) url: The full api url to make the request to. :param kwargs: These are passed along (but possibly mutated) to requests. :return: (requests.Response) The response directly from requests. """ kwargs['headers'] = dict(kwargs.get('headers', {}), **get_headers()) # Change boolean params to lowercase strings. E.g., `True` --> `'true'`. # Just change the value so that requests handles query string creation. if isinstance(kwargs.get('params'), dict): kwargs['params'] = kwargs['params'].copy() for key in kwargs['params']: if isinstance(kwargs['params'][key], bool): kwargs['params'][key] = _json.dumps(kwargs['params'][key]) # We have a special json encoding class for non-native objects. if kwargs.get('json') is not None: if kwargs.get('data'): raise _plotly_utils.exceptions.PlotlyError( 'Cannot supply data and json kwargs.') kwargs['data'] = _json.dumps(kwargs.pop('json'), sort_keys=True, cls=PlotlyJSONEncoder) # The config file determines whether reuqests should *verify*. kwargs['verify'] = config.get_config()['plotly_ssl_verification'] try: response = requests.request(method, url, **kwargs) except RequestException as e: # The message can be an exception. E.g., MaxRetryError. message = str(getattr(e, 'message', 'No message')) response = getattr(e, 'response', None) status_code = response.status_code if response else None content = response.content if response else 'No content' raise exceptions.PlotlyRequestError(message, status_code, content) validate_response(response) return response
def _return_layout_specs(num_of_boxes, url_lines, title_lines, text_block, code_blocks, slide_num, style): # returns specs of the form (left, top, height, width) code_theme = "tomorrowNight" if style == "martik": specs_for_boxes = [] margin = 18 # in pxs # set Headings styles paragraph_styles["Heading 1"].update({ "color": "#0D0A1E", "fontFamily": "Raleway", "fontSize": 55, "fontWeight": fontWeight_dict["Bold"]["fontWeight"], }) paragraph_styles["Heading 2"] = copy.deepcopy( paragraph_styles["Heading 1"]) paragraph_styles["Heading 2"].update({"fontSize": 36}) paragraph_styles["Heading 3"] = copy.deepcopy( paragraph_styles["Heading 1"]) paragraph_styles["Heading 3"].update({"fontSize": 30}) # set Body style paragraph_styles["Body"].update({ "color": "#96969C", "fontFamily": "Roboto", "fontSize": 16, "fontWeight": fontWeight_dict["Regular"]["fontWeight"], }) bkgd_color = "#F4FAFB" title_font_color = "#0D0A1E" text_font_color = "#96969C" if num_of_boxes == 0 and slide_num == 0: text_textAlign = "center" else: text_textAlign = "left" if num_of_boxes == 0: specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) bkgd_color = "#0D0A1E" title_font_color = "#F4FAFB" text_font_color = "#F4FAFB" elif num_of_boxes == 1: if code_blocks != [] or (url_lines != [] and get_config()["plotly_domain"] in url_lines[0]): if code_blocks != []: w_range = 40 else: w_range = 60 text_top = _top_spec_for_text_at_bottom( text_block, 80, per_from_bottom=(margin / HEIGHT) * 100) specs_for_title = (0, 3, 20, 100) specs_for_text = (10, text_top, 30, 80) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=w_range, height_range=60, margin=margin, betw_boxes=4, ) bkgd_color = "#0D0A1E" title_font_color = "#F4FAFB" text_font_color = "#F4FAFB" code_theme = "tomorrow" elif title_lines == [] and text_block == "": specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=50, height_range=80, margin=0, betw_boxes=0, ) else: title_text_width = 40 - (margin / WIDTH) * 100 text_top = _top_spec_for_text_at_bottom( text_block, title_text_width, per_from_bottom=(margin / HEIGHT) * 100, ) specs_for_title = (60, 3, 20, 40) specs_for_text = (60, text_top, 1, title_text_width) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="leftgroup_v", width_range=60, margin=margin, betw_boxes=4, ) bkgd_color = "#0D0A1E" title_font_color = "#F4FAFB" text_font_color = "#F4FAFB" elif num_of_boxes == 2 and url_lines != []: text_top = _top_spec_for_text_at_bottom( text_block, 46, per_from_bottom=(margin / HEIGHT) * 100, min_top=50) specs_for_title = (0, 3, 20, 50) specs_for_text = (52, text_top, 40, 46) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype="checkerboard_topright") elif num_of_boxes >= 2 and url_lines == []: text_top = _top_spec_for_text_at_bottom( text_block, 92, per_from_bottom=(margin / HEIGHT) * 100, min_top=15) if num_of_boxes == 2: betw_boxes = 90 else: betw_boxes = 10 specs_for_title = (0, 3, 20, 100) specs_for_text = (4, text_top, 1, 92) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=92, height_range=60, margin=margin, betw_boxes=betw_boxes, ) code_theme = "tomorrow" else: text_top = _top_spec_for_text_at_bottom( text_block, 40 - (margin / WIDTH) * 100, per_from_bottom=(margin / HEIGHT) * 100, ) specs_for_title = (0, 3, 20, 40 - (margin / WIDTH) * 100) specs_for_text = ( (margin / WIDTH) * 100, text_top, 50, 40 - (margin / WIDTH) * 100, ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="rightgroup_v", width_range=60, margin=margin, betw_boxes=4, ) elif style == "moods": specs_for_boxes = [] margin = 18 code_theme = "tomorrowNight" # set Headings styles paragraph_styles["Heading 1"].update({ "color": "#000016", "fontFamily": "Roboto", "fontSize": 55, "fontWeight": fontWeight_dict["Black"]["fontWeight"], }) paragraph_styles["Heading 2"] = copy.deepcopy( paragraph_styles["Heading 1"]) paragraph_styles["Heading 2"].update({"fontSize": 36}) paragraph_styles["Heading 3"] = copy.deepcopy( paragraph_styles["Heading 1"]) paragraph_styles["Heading 3"].update({"fontSize": 30}) # set Body style paragraph_styles["Body"].update({ "color": "#000016", "fontFamily": "Roboto", "fontSize": 16, "fontWeight": fontWeight_dict["Thin"]["fontWeight"], }) bkgd_color = "#FFFFFF" title_font_color = None text_font_color = None if num_of_boxes == 0 and slide_num == 0: text_textAlign = "center" else: text_textAlign = "left" if num_of_boxes == 0: if slide_num == 0 or text_block == "": bkgd_color = "#F7F7F7" specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) else: bkgd_color = "#F7F7F7" text_top = _top_spec_for_text_at_bottom( text_block, width_per=90, per_from_bottom=(margin / HEIGHT) * 100, min_top=20, ) specs_for_title = (0, 2, 20, 100) specs_for_text = (5, text_top, 50, 90) elif num_of_boxes == 1: if code_blocks != []: # code if text_block == "": margin = 5 specs_for_title = (0, 3, 20, 100) specs_for_text = (0, 0, 0, 0) top = 12 specs_for_boxes = [(margin, top, 100 - top - margin, 100 - 2 * margin)] elif slide_num % 2 == 0: # middle center width_per = 90 height_range = 60 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=100 - height_range / 2.0, ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=50, height_range=60, margin=margin, ) specs_for_title = (0, 3, 20, 100) specs_for_text = (5, text_top, 2, width_per) else: # right width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30, ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="rightgroup_v", width_range=50, margin=40, ) specs_for_title = (0, 3, 20, 50) specs_for_text = (2, text_top, 2, width_per - 2) elif url_lines != [] and get_config( )["plotly_domain"] in url_lines[0]: # url if slide_num % 2 == 0: # top half width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=60, ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=100, height_range=60, middle_center=30, ) specs_for_title = (0, 60, 20, 100) specs_for_text = (2.5, text_top, 2, width_per) else: # middle across width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=60, ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=100, height_range=60, ) specs_for_title = (0, 3, 20, 100) specs_for_text = (2.5, text_top, 2, width_per) else: # image if slide_num % 2 == 0: # right width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30, ) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype="rightgroup_v", width_range=50, margin=0) specs_for_title = (0, 3, 20, 50) specs_for_text = (2, text_top, 2, width_per - 2) else: # left width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30, ) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype="leftgroup_v", width_range=50, margin=0) specs_for_title = (50, 3, 20, 50) specs_for_text = (52, text_top, 2, width_per - 2) elif num_of_boxes == 2: # right stack width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30, ) specs_for_boxes = [(50, 0, 50, 50), (50, 50, 50, 50)] specs_for_title = (0, 3, 20, 50) specs_for_text = (2, text_top, 2, width_per - 2) elif num_of_boxes == 3: # middle top width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=40, ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype="middle", width_range=100, height_range=40, middle_center=30, ) specs_for_title = (0, 0, 20, 100) specs_for_text = (2.5, text_top, 2, width_per) else: # right stack width_per = 40 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30, ) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype="rightgroup_v", width_range=60, margin=0) specs_for_title = (0, 3, 20, 40) specs_for_text = (2, text_top, 2, width_per - 2) # set text style attributes title_style_attr = {} text_style_attr = {"textAlign": text_textAlign} if text_font_color: text_style_attr["color"] = text_font_color if title_font_color: title_style_attr["color"] = title_font_color return ( specs_for_boxes, specs_for_title, specs_for_text, bkgd_color, title_style_attr, text_style_attr, code_theme, )
def _markdown_to_presentation(self, markdown_string, style, imgStretch): list_of_slides = _list_of_slides(markdown_string) for slide_num, slide in enumerate(list_of_slides): lines_in_slide = slide.split("\n") title_lines = [] # validate blocks of code if slide.count("```") % 2 != 0: raise _plotly_utils.exceptions.PlotlyError(CODE_ENV_ERROR) # find code blocks code_indices = [] code_blocks = [] wdw_size = len("```") for j in range(len(slide)): if slide[j:j + wdw_size] == "```": code_indices.append(j) for k in range(int(len(code_indices) / 2)): code_blocks.append( slide[code_indices[2 * k]:code_indices[(2 * k) + 1]]) lang_and_code_tuples = [] for code_block in code_blocks: # validate code blocks code_by_lines = code_block.split("\n") language = _remove_extra_whitespace_from_line( code_by_lines[0][3:]).lower() if language == "" or language not in VALID_LANGUAGES: raise _plotly_utils.exceptions.PlotlyError( "The language of your code block should be " "clearly indicated after the first ``` that " "begins the code block. The valid languages to " "choose from are" + list_of_options(VALID_LANGUAGES)) lang_and_code_tuples.append( (language, "\n".join(code_by_lines[1:]))) # collect text, code and urls title_lines = [] url_lines = [] text_lines = [] inCode = False for line in lines_in_slide: # inCode handling if line[:3] == "```" and len(line) > 3: inCode = True if line == "```": inCode = False if not inCode and line != "```": if len(line) > 0 and line[0] == "#": title_lines.append(line) elif _url_parens_contained("Plotly", line) or _url_parens_contained( "Image", line): if (line.startswith("Plotly(") and get_config()["plotly_domain"] not in line): raise _plotly_utils.exceptions.PlotlyError( "You are attempting to insert a Plotly Chart " "in your slide but your url does not have " "your plotly domain '{}' in it.".format( get_config()["plotly_domain"])) url_lines.append(line) else: # find and set transition properties trans = "transition:" if line.startswith(trans) and title_lines == []: slide_trans = line[len(trans):] slide_trans = _remove_extra_whitespace_from_line( slide_trans) slide_transition_list = [] for key in VALID_TRANSITIONS: if key in slide_trans: slide_transition_list.append(key) if slide_transition_list == []: slide_transition_list.append("slide") self._set_transition(slide_transition_list, slide_num) else: text_lines.append(line) # make text block for i in range(2): try: while text_lines[-i] == "": text_lines.pop(-i) except IndexError: pass text_block = "\n".join(text_lines) num_of_boxes = len(url_lines) + len(lang_and_code_tuples) ( specs_for_boxes, specs_for_title, specs_for_text, bkgd_color, title_style_attr, text_style_attr, code_theme, ) = _return_layout_specs( num_of_boxes, url_lines, title_lines, text_block, code_blocks, slide_num, style, ) # background color self._color_background(bkgd_color, slide_num) # insert title, text, code, and images if len(title_lines) > 0: # clean titles title = title_lines[0] num_hashes = 0 while title[0] == "#": title = title[1:] num_hashes += 1 title = _remove_extra_whitespace_from_line(title) self._insert( box="Text", text_or_url=title, left=specs_for_title[0], top=specs_for_title[1], height=specs_for_title[2], width=specs_for_title[3], slide=slide_num, style_attr=title_style_attr, paragraphStyle="Heading 1".format(min(num_hashes, 3)), ) # text if len(text_lines) > 0: self._insert( box="Text", text_or_url=text_block, left=specs_for_text[0], top=specs_for_text[1], height=specs_for_text[2], width=specs_for_text[3], slide=slide_num, style_attr=text_style_attr, paragraphStyle="Body", ) url_and_code_blocks = list(url_lines + lang_and_code_tuples) for k, specs in enumerate(specs_for_boxes): url_or_code = url_and_code_blocks[k] if isinstance(url_or_code, tuple): # code language = url_or_code[0] code = url_or_code[1] box_name = "CodePane" # code style props_attr = {} props_attr["language"] = language props_attr["theme"] = code_theme self._insert( box=box_name, text_or_url=code, left=specs[0], top=specs[1], height=specs[2], width=specs[3], slide=slide_num, props_attr=props_attr, ) else: # url if get_config()["plotly_domain"] in url_or_code: box_name = "Plotly" else: box_name = "Image" url = url_or_code[len(box_name) + 1:-1] self._insert( box=box_name, text_or_url=url, left=specs[0], top=specs[1], height=specs[2], width=specs[3], slide=slide_num, ) if not imgStretch: for s, slide in enumerate(self["presentation"]["slides"]): for c, child in enumerate(slide["children"]): if child["type"] in ["Image", "Plotly"]: deep_child = child["props"]["style"] width = deep_child["width"] height = deep_child["height"] if width >= height: deep_child["max-width"] = deep_child.pop("width") else: deep_child["max-height"] = deep_child.pop("height")
def _markdown_to_presentation(self, markdown_string, style, imgStretch): list_of_slides = _list_of_slides(markdown_string) for slide_num, slide in enumerate(list_of_slides): lines_in_slide = slide.split('\n') title_lines = [] # validate blocks of code if slide.count('```') % 2 != 0: raise _plotly_utils.exceptions.PlotlyError(CODE_ENV_ERROR) # find code blocks code_indices = [] code_blocks = [] wdw_size = len('```') for j in range(len(slide)): if slide[j:j+wdw_size] == '```': code_indices.append(j) for k in range(int(len(code_indices) / 2)): code_blocks.append( slide[code_indices[2 * k]:code_indices[(2 * k) + 1]] ) lang_and_code_tuples = [] for code_block in code_blocks: # validate code blocks code_by_lines = code_block.split('\n') language = _remove_extra_whitespace_from_line( code_by_lines[0][3:] ).lower() if language == '' or language not in VALID_LANGUAGES: raise _plotly_utils.exceptions.PlotlyError( "The language of your code block should be " "clearly indicated after the first ``` that " "begins the code block. The valid languages to " "choose from are" + list_of_options( VALID_LANGUAGES ) ) lang_and_code_tuples.append( (language, '\n'.join(code_by_lines[1:])) ) # collect text, code and urls title_lines = [] url_lines = [] text_lines = [] inCode = False for line in lines_in_slide: # inCode handling if line[:3] == '```' and len(line) > 3: inCode = True if line == '```': inCode = False if not inCode and line != '```': if len(line) > 0 and line[0] == '#': title_lines.append(line) elif (_url_parens_contained('Plotly', line) or _url_parens_contained('Image', line)): if (line.startswith('Plotly(') and get_config()['plotly_domain'] not in line): raise _plotly_utils.exceptions.PlotlyError( "You are attempting to insert a Plotly Chart " "in your slide but your url does not have " "your plotly domain '{}' in it.".format( get_config()['plotly_domain'] ) ) url_lines.append(line) else: # find and set transition properties trans = 'transition:' if line.startswith(trans) and title_lines == []: slide_trans = line[len(trans):] slide_trans = _remove_extra_whitespace_from_line( slide_trans ) slide_transition_list = [] for key in VALID_TRANSITIONS: if key in slide_trans: slide_transition_list.append(key) if slide_transition_list == []: slide_transition_list.append('slide') self._set_transition( slide_transition_list, slide_num ) else: text_lines.append(line) # make text block for i in range(2): try: while text_lines[-i] == '': text_lines.pop(-i) except IndexError: pass text_block = '\n'.join(text_lines) num_of_boxes = len(url_lines) + len(lang_and_code_tuples) (specs_for_boxes, specs_for_title, specs_for_text, bkgd_color, title_style_attr, text_style_attr, code_theme) = _return_layout_specs( num_of_boxes, url_lines, title_lines, text_block, code_blocks, slide_num, style ) # background color self._color_background(bkgd_color, slide_num) # insert title, text, code, and images if len(title_lines) > 0: # clean titles title = title_lines[0] num_hashes = 0 while title[0] == '#': title = title[1:] num_hashes += 1 title = _remove_extra_whitespace_from_line(title) self._insert( box='Text', text_or_url=title, left=specs_for_title[0], top=specs_for_title[1], height=specs_for_title[2], width=specs_for_title[3], slide=slide_num, style_attr=title_style_attr, paragraphStyle='Heading 1'.format( min(num_hashes, 3) ) ) # text if len(text_lines) > 0: self._insert( box='Text', text_or_url=text_block, left=specs_for_text[0], top=specs_for_text[1], height=specs_for_text[2], width=specs_for_text[3], slide=slide_num, style_attr=text_style_attr, paragraphStyle='Body' ) url_and_code_blocks = list(url_lines + lang_and_code_tuples) for k, specs in enumerate(specs_for_boxes): url_or_code = url_and_code_blocks[k] if isinstance(url_or_code, tuple): # code language = url_or_code[0] code = url_or_code[1] box_name = 'CodePane' # code style props_attr = {} props_attr['language'] = language props_attr['theme'] = code_theme self._insert(box=box_name, text_or_url=code, left=specs[0], top=specs[1], height=specs[2], width=specs[3], slide=slide_num, props_attr=props_attr) else: # url if get_config()['plotly_domain'] in url_or_code: box_name = 'Plotly' else: box_name = 'Image' url = url_or_code[len(box_name) + 1: -1] self._insert(box=box_name, text_or_url=url, left=specs[0], top=specs[1], height=specs[2], width=specs[3], slide=slide_num) if not imgStretch: for s, slide in enumerate(self['presentation']['slides']): for c, child in enumerate(slide['children']): if child['type'] in ['Image', 'Plotly']: deep_child = child['props']['style'] width = deep_child['width'] height = deep_child['height'] if width >= height: deep_child['max-width'] = deep_child.pop('width') else: deep_child['max-height'] = deep_child.pop('height')
def _return_layout_specs(num_of_boxes, url_lines, title_lines, text_block, code_blocks, slide_num, style): # returns specs of the form (left, top, height, width) code_theme = 'tomorrowNight' if style == 'martik': specs_for_boxes = [] margin = 18 # in pxs # set Headings styles paragraph_styles['Heading 1'].update( {'color': '#0D0A1E', 'fontFamily': 'Raleway', 'fontSize': 55, 'fontWeight': fontWeight_dict['Bold']['fontWeight']} ) paragraph_styles['Heading 2'] = copy.deepcopy( paragraph_styles['Heading 1'] ) paragraph_styles['Heading 2'].update({'fontSize': 36}) paragraph_styles['Heading 3'] = copy.deepcopy( paragraph_styles['Heading 1'] ) paragraph_styles['Heading 3'].update({'fontSize': 30}) # set Body style paragraph_styles['Body'].update( {'color': '#96969C', 'fontFamily': 'Roboto', 'fontSize': 16, 'fontWeight': fontWeight_dict['Regular']['fontWeight']} ) bkgd_color = '#F4FAFB' title_font_color = '#0D0A1E' text_font_color = '#96969C' if num_of_boxes == 0 and slide_num == 0: text_textAlign = 'center' else: text_textAlign = 'left' if num_of_boxes == 0: specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) bkgd_color = '#0D0A1E' title_font_color = '#F4FAFB' text_font_color = '#F4FAFB' elif num_of_boxes == 1: if code_blocks != [] or (url_lines != [] and get_config()['plotly_domain'] in url_lines[0]): if code_blocks != []: w_range = 40 else: w_range = 60 text_top = _top_spec_for_text_at_bottom( text_block, 80, per_from_bottom=(margin / HEIGHT) * 100 ) specs_for_title = (0, 3, 20, 100) specs_for_text = (10, text_top, 30, 80) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=w_range, height_range=60, margin=margin, betw_boxes=4 ) bkgd_color = '#0D0A1E' title_font_color = '#F4FAFB' text_font_color = '#F4FAFB' code_theme = 'tomorrow' elif title_lines == [] and text_block == '': specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=50, height_range=80, margin=0, betw_boxes=0 ) else: title_text_width = 40 - (margin / WIDTH) * 100 text_top = _top_spec_for_text_at_bottom( text_block, title_text_width, per_from_bottom=(margin / HEIGHT) * 100 ) specs_for_title = (60, 3, 20, 40) specs_for_text = (60, text_top, 1, title_text_width) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='leftgroup_v', width_range=60, margin=margin, betw_boxes=4 ) bkgd_color = '#0D0A1E' title_font_color = '#F4FAFB' text_font_color = '#F4FAFB' elif num_of_boxes == 2 and url_lines != []: text_top = _top_spec_for_text_at_bottom( text_block, 46, per_from_bottom=(margin / HEIGHT) * 100, min_top=50 ) specs_for_title = (0, 3, 20, 50) specs_for_text = (52, text_top, 40, 46) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='checkerboard_topright' ) elif num_of_boxes >= 2 and url_lines == []: text_top = _top_spec_for_text_at_bottom( text_block, 92, per_from_bottom=(margin / HEIGHT) * 100, min_top=15 ) if num_of_boxes == 2: betw_boxes = 90 else: betw_boxes = 10 specs_for_title = (0, 3, 20, 100) specs_for_text = (4, text_top, 1, 92) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=92, height_range=60, margin=margin, betw_boxes=betw_boxes ) code_theme = 'tomorrow' else: text_top = _top_spec_for_text_at_bottom( text_block, 40 - (margin / WIDTH) * 100, per_from_bottom=(margin / HEIGHT) * 100 ) specs_for_title = (0, 3, 20, 40 - (margin / WIDTH) * 100) specs_for_text = ( (margin / WIDTH) * 100, text_top, 50, 40 - (margin / WIDTH) * 100 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=60, margin=margin, betw_boxes=4 ) elif style == 'moods': specs_for_boxes = [] margin = 18 code_theme = 'tomorrowNight' # set Headings styles paragraph_styles['Heading 1'].update( {'color': '#000016', 'fontFamily': 'Roboto', 'fontSize': 55, 'fontWeight': fontWeight_dict['Black']['fontWeight']} ) paragraph_styles['Heading 2'] = copy.deepcopy( paragraph_styles['Heading 1'] ) paragraph_styles['Heading 2'].update({'fontSize': 36}) paragraph_styles['Heading 3'] = copy.deepcopy( paragraph_styles['Heading 1'] ) paragraph_styles['Heading 3'].update({'fontSize': 30}) # set Body style paragraph_styles['Body'].update( {'color': '#000016', 'fontFamily': 'Roboto', 'fontSize': 16, 'fontWeight': fontWeight_dict['Thin']['fontWeight']} ) bkgd_color = '#FFFFFF' title_font_color = None text_font_color = None if num_of_boxes == 0 and slide_num == 0: text_textAlign = 'center' else: text_textAlign = 'left' if num_of_boxes == 0: if slide_num == 0 or text_block == '': bkgd_color = '#F7F7F7' specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) else: bkgd_color = '#F7F7F7' text_top = _top_spec_for_text_at_bottom( text_block, width_per=90, per_from_bottom=(margin / HEIGHT) * 100, min_top=20 ) specs_for_title = (0, 2, 20, 100) specs_for_text = (5, text_top, 50, 90) elif num_of_boxes == 1: if code_blocks != []: # code if text_block == '': margin = 5 specs_for_title = (0, 3, 20, 100) specs_for_text = (0, 0, 0, 0) top = 12 specs_for_boxes = [ (margin, top, 100 - top - margin, 100 - 2 * margin) ] elif slide_num % 2 == 0: # middle center width_per = 90 height_range = 60 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=100 - height_range / 2. ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=50, height_range=60, margin=margin, ) specs_for_title = (0, 3, 20, 100) specs_for_text = ( 5, text_top, 2, width_per ) else: # right width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=50, margin=40, ) specs_for_title = (0, 3, 20, 50) specs_for_text = ( 2, text_top, 2, width_per - 2 ) elif (url_lines != [] and get_config()['plotly_domain'] in url_lines[0]): # url if slide_num % 2 == 0: # top half width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=60 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=100, height_range=60, middle_center=30 ) specs_for_title = (0, 60, 20, 100) specs_for_text = ( 2.5, text_top, 2, width_per ) else: # middle across width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=60 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=100, height_range=60 ) specs_for_title = (0, 3, 20, 100) specs_for_text = ( 2.5, text_top, 2, width_per ) else: # image if slide_num % 2 == 0: # right width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=50, margin=0, ) specs_for_title = (0, 3, 20, 50) specs_for_text = ( 2, text_top, 2, width_per - 2 ) else: # left width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='leftgroup_v', width_range=50, margin=0, ) specs_for_title = (50, 3, 20, 50) specs_for_text = ( 52, text_top, 2, width_per - 2 ) elif num_of_boxes == 2: # right stack width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30 ) specs_for_boxes = [(50, 0, 50, 50), (50, 50, 50, 50)] specs_for_title = (0, 3, 20, 50) specs_for_text = ( 2, text_top, 2, width_per - 2 ) elif num_of_boxes == 3: # middle top width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=40 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=100, height_range=40, middle_center=30 ) specs_for_title = (0, 0, 20, 100) specs_for_text = ( 2.5, text_top, 2, width_per ) else: # right stack width_per = 40 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30 ) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=60, margin=0, ) specs_for_title = (0, 3, 20, 40) specs_for_text = ( 2, text_top, 2, width_per - 2 ) # set text style attributes title_style_attr = {} text_style_attr = {'textAlign': text_textAlign} if text_font_color: text_style_attr['color'] = text_font_color if title_font_color: title_style_attr['color'] = title_font_color return (specs_for_boxes, specs_for_title, specs_for_text, bkgd_color, title_style_attr, text_style_attr, code_theme)
def _return_layout_specs(num_of_boxes, url_lines, title_lines, text_block, code_blocks, slide_num, style): # returns specs of the form (left, top, height, width) code_theme = 'tomorrowNight' if style == 'martik': specs_for_boxes = [] margin = 18 # in pxs # set Headings styles paragraph_styles['Heading 1'].update({ 'color': '#0D0A1E', 'fontFamily': 'Raleway', 'fontSize': 55, 'fontWeight': fontWeight_dict['Bold']['fontWeight'] }) paragraph_styles['Heading 2'] = copy.deepcopy( paragraph_styles['Heading 1']) paragraph_styles['Heading 2'].update({'fontSize': 36}) paragraph_styles['Heading 3'] = copy.deepcopy( paragraph_styles['Heading 1']) paragraph_styles['Heading 3'].update({'fontSize': 30}) # set Body style paragraph_styles['Body'].update({ 'color': '#96969C', 'fontFamily': 'Roboto', 'fontSize': 16, 'fontWeight': fontWeight_dict['Regular']['fontWeight'] }) bkgd_color = '#F4FAFB' title_font_color = '#0D0A1E' text_font_color = '#96969C' if num_of_boxes == 0 and slide_num == 0: text_textAlign = 'center' else: text_textAlign = 'left' if num_of_boxes == 0: specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) bkgd_color = '#0D0A1E' title_font_color = '#F4FAFB' text_font_color = '#F4FAFB' elif num_of_boxes == 1: if code_blocks != [] or (url_lines != [] and get_config()['plotly_domain'] in url_lines[0]): if code_blocks != []: w_range = 40 else: w_range = 60 text_top = _top_spec_for_text_at_bottom( text_block, 80, per_from_bottom=(margin / HEIGHT) * 100) specs_for_title = (0, 3, 20, 100) specs_for_text = (10, text_top, 30, 80) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='middle', width_range=w_range, height_range=60, margin=margin, betw_boxes=4) bkgd_color = '#0D0A1E' title_font_color = '#F4FAFB' text_font_color = '#F4FAFB' code_theme = 'tomorrow' elif title_lines == [] and text_block == '': specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='middle', width_range=50, height_range=80, margin=0, betw_boxes=0) else: title_text_width = 40 - (margin / WIDTH) * 100 text_top = _top_spec_for_text_at_bottom( text_block, title_text_width, per_from_bottom=(margin / HEIGHT) * 100) specs_for_title = (60, 3, 20, 40) specs_for_text = (60, text_top, 1, title_text_width) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='leftgroup_v', width_range=60, margin=margin, betw_boxes=4) bkgd_color = '#0D0A1E' title_font_color = '#F4FAFB' text_font_color = '#F4FAFB' elif num_of_boxes == 2 and url_lines != []: text_top = _top_spec_for_text_at_bottom( text_block, 46, per_from_bottom=(margin / HEIGHT) * 100, min_top=50) specs_for_title = (0, 3, 20, 50) specs_for_text = (52, text_top, 40, 46) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='checkerboard_topright') elif num_of_boxes >= 2 and url_lines == []: text_top = _top_spec_for_text_at_bottom( text_block, 92, per_from_bottom=(margin / HEIGHT) * 100, min_top=15) if num_of_boxes == 2: betw_boxes = 90 else: betw_boxes = 10 specs_for_title = (0, 3, 20, 100) specs_for_text = (4, text_top, 1, 92) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='middle', width_range=92, height_range=60, margin=margin, betw_boxes=betw_boxes) code_theme = 'tomorrow' else: text_top = _top_spec_for_text_at_bottom( text_block, 40 - (margin / WIDTH) * 100, per_from_bottom=(margin / HEIGHT) * 100) specs_for_title = (0, 3, 20, 40 - (margin / WIDTH) * 100) specs_for_text = ((margin / WIDTH) * 100, text_top, 50, 40 - (margin / WIDTH) * 100) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='rightgroup_v', width_range=60, margin=margin, betw_boxes=4) elif style == 'moods': specs_for_boxes = [] margin = 18 code_theme = 'tomorrowNight' # set Headings styles paragraph_styles['Heading 1'].update({ 'color': '#000016', 'fontFamily': 'Roboto', 'fontSize': 55, 'fontWeight': fontWeight_dict['Black']['fontWeight'] }) paragraph_styles['Heading 2'] = copy.deepcopy( paragraph_styles['Heading 1']) paragraph_styles['Heading 2'].update({'fontSize': 36}) paragraph_styles['Heading 3'] = copy.deepcopy( paragraph_styles['Heading 1']) paragraph_styles['Heading 3'].update({'fontSize': 30}) # set Body style paragraph_styles['Body'].update({ 'color': '#000016', 'fontFamily': 'Roboto', 'fontSize': 16, 'fontWeight': fontWeight_dict['Thin']['fontWeight'] }) bkgd_color = '#FFFFFF' title_font_color = None text_font_color = None if num_of_boxes == 0 and slide_num == 0: text_textAlign = 'center' else: text_textAlign = 'left' if num_of_boxes == 0: if slide_num == 0 or text_block == '': bkgd_color = '#F7F7F7' specs_for_title = (0, 50, 20, 100) specs_for_text = (15, 60, 50, 70) else: bkgd_color = '#F7F7F7' text_top = _top_spec_for_text_at_bottom( text_block, width_per=90, per_from_bottom=(margin / HEIGHT) * 100, min_top=20) specs_for_title = (0, 2, 20, 100) specs_for_text = (5, text_top, 50, 90) elif num_of_boxes == 1: if code_blocks != []: # code if text_block == '': margin = 5 specs_for_title = (0, 3, 20, 100) specs_for_text = (0, 0, 0, 0) top = 12 specs_for_boxes = [(margin, top, 100 - top - margin, 100 - 2 * margin)] elif slide_num % 2 == 0: # middle center width_per = 90 height_range = 60 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=100 - height_range / 2.) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='middle', width_range=50, height_range=60, margin=margin, ) specs_for_title = (0, 3, 20, 100) specs_for_text = (5, text_top, 2, width_per) else: # right width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=50, margin=40, ) specs_for_title = (0, 3, 20, 50) specs_for_text = (2, text_top, 2, width_per - 2) elif (url_lines != [] and get_config()['plotly_domain'] in url_lines[0]): # url if slide_num % 2 == 0: # top half width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=60) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='middle', width_range=100, height_range=60, middle_center=30) specs_for_title = (0, 60, 20, 100) specs_for_text = (2.5, text_top, 2, width_per) else: # middle across width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=60) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='middle', width_range=100, height_range=60) specs_for_title = (0, 3, 20, 100) specs_for_text = (2.5, text_top, 2, width_per) else: # image if slide_num % 2 == 0: # right width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=50, margin=0, ) specs_for_title = (0, 3, 20, 50) specs_for_text = (2, text_top, 2, width_per - 2) else: # left width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='leftgroup_v', width_range=50, margin=0, ) specs_for_title = (50, 3, 20, 50) specs_for_text = (52, text_top, 2, width_per - 2) elif num_of_boxes == 2: # right stack width_per = 50 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30) specs_for_boxes = [(50, 0, 50, 50), (50, 50, 50, 50)] specs_for_title = (0, 3, 20, 50) specs_for_text = (2, text_top, 2, width_per - 2) elif num_of_boxes == 3: # middle top width_per = 95 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=40) specs_for_boxes = _box_specs_gen(num_of_boxes, grouptype='middle', width_range=100, height_range=40, middle_center=30) specs_for_title = (0, 0, 20, 100) specs_for_text = (2.5, text_top, 2, width_per) else: # right stack width_per = 40 text_top = _top_spec_for_text_at_bottom( text_block, width_per=width_per, per_from_bottom=(margin / HEIGHT) * 100, min_top=30) specs_for_boxes = _box_specs_gen( num_of_boxes, grouptype='rightgroup_v', width_range=60, margin=0, ) specs_for_title = (0, 3, 20, 40) specs_for_text = (2, text_top, 2, width_per - 2) # set text style attributes title_style_attr = {} text_style_attr = {'textAlign': text_textAlign} if text_font_color: text_style_attr['color'] = text_font_color if title_font_color: title_style_attr['color'] = title_font_color return (specs_for_boxes, specs_for_title, specs_for_text, bkgd_color, title_style_attr, text_style_attr, code_theme)