Source code for pycif.plugins.datastreams.fluxes.dummy_txt.make.fromtxt

import numpy as np
import xarray as xr
from PIL import Image, ImageDraw, ImageFont


[docs] def makefromtext(flx_txt, dom_xsize, dom_ysize, file_font="/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf"): """Render a text string onto a domain-sized grid as a synthetic flux field. Enumerates possible line-splits of ``flx_txt`` (see :func:`split_text`) and, for each, the largest font size that fits within ``dom_xsize``/``dom_ysize``; picks the split/font-size combination that maximizes the rendered text area (with fewest lines and most balanced line lengths as tie-breakers), draws it onto a blank canvas, and returns the result as a grayscale-normalized array usable as a flux field spelling out the text. Args: flx_txt (str): the text to render; an empty/whitespace-only string produces an all-zero field. dom_xsize (int): width of the target grid (number of longitude cells). dom_ysize (int): height of the target grid (number of latitude cells). file_font (str): path to a TrueType font file used to render the text. Returns: np.ndarray: a 2D array of shape ``(dom_ysize, dom_xsize)`` with values in ``[0, 1]`` (1 where the text is drawn, 0 on the background). """ # Split the text splits = split_text(flx_txt) # If no text return zeros if flx_txt.strip() == "": return np.zeros((dom_ysize, dom_xsize)) # Loop over all possible combinations of newlines areas = [] text_widths = [] text_heights = [] valid_splits = [] for split in splits: width = max(list(map(len, split))) size = min(dom_xsize // width, dom_ysize // (1 + len(split))) size += 3 * size // 4 pil_font = ImageFont.truetype(file_font, size=size, encoding="unic") area = 0 widths = [] heights = [] for s in split: w, h = pil_font.getsize(s) widths.append(w) heights.append(h) area += w * h valid_splits.append(split) areas.append(area) text_widths.append(widths) text_heights.append(heights) # create a blank canvas with extra space between lines canvas = Image.new("RGB", [dom_xsize, dom_ysize], (255, 255, 255)) draw = ImageDraw.Draw(canvas) # Get the one with maximum occupation of space with minimum number of lines imax = np.where(np.array(areas) >= 0.9 * np.max(areas))[0] stds = [np.std(list(map(len, valid_splits[i]))) for i in imax] iimax = np.where(np.array(stds) == np.min(stds))[0] imax = [imax[i] for i in iimax] lens = [len(valid_splits[i]) for i in iimax] iimax = np.where(np.array(lens) == np.min(lens))[0] imax = [imax[i] for i in iimax] imax = imax[0] text_width = text_widths[imax] text_height = text_heights[imax] split = valid_splits[imax] # draw the text onto the canvas offset = ( (dom_xsize - max(text_width)) // 2, (dom_ysize - sum(text_height)) // 2, ) white = "#000000" width = offset[0] + max(text_width) // 2 y_text = offset[1] for s, w, h in zip(split, text_width, text_height): draw.text((width - (w // 2), y_text), s, font=pil_font, fill=white) y_text += h # Convert the canvas into an array with values in [0, 1] flx = (255 - np.asarray(canvas)) / 255.0 return flx.mean(axis=2)
[docs] def split_text(txt): """Return all possible splits for a given text Args: txt (str): the text to split Returns: list(str) Notes: This function is not optimized and can take a long time for string of more than a few words """ # If empty text if txt == "": return [txt] split = txt.split() # Recursively loop over substring of the text if len(split) == 1: return [[txt]] elif len(split) == 2: return [[txt], split] else: prev = [[split[0]] + b for b in split_text(" ".join(txt.split()[1:]))] post = [ b + [split[-1]] for b in split_text(" ".join(txt.split()[:-1])) ] return [[txt]] + prev + post