def linebreakString(draw: ImageDraw, text: str, lines = 2, maxWidth = 370, font = rbtv_config.fontSmall): splited = text.split(' ') result = '' test = '' twoline = False line = 1 i = 0 w, h = draw.textsize(result, font = font) while line < lines: while w < maxWidth and i < len(splited): test += str(splited[i]) + ' ' w, h = draw.textsize(test, font = font) if w < maxWidth: result += str(splited[i]) + ' ' i += 1 result += '\n' test = '' line += 1 w, h = draw.textsize(test, font = font) while i < len(splited): twoline = True result += str(splited[i]) + ' ' i += 1 return twoline, result
def _render_departure_font( self, draw: ImageDraw, y: int, departure: departure.Departure, font: FreeTypeFont, ) -> int: _, dst_height = draw.textsize( "{} {}".format(departure.line_number, departure.destination), font=font ) time_width, time_height = draw.textsize(departure.display_time, font=font) dst_y = y + 5 dst_x = 0 # set border time_x = self._d_width - time_width - 2 time_y = y + 5 draw.text( (dst_x, dst_y), "{} {}".format(departure.line_number, departure.destination), fill="white", font=font, ) draw.text((time_x, time_y), departure.display_time, fill="white", font=font) return dst_height
def renderDestinationRow(draw: ImageDraw, width, height): status = "On time" departureTime = data["departures"]["all"][0]["aimed_departure_time"] timeWidth, _ = draw.textsize(departureTime, font) statusWidth, _ = draw.textsize(status, font) draw.text((0, 0), departureTime, fill="yellow", font=font) destination = data["departures"]["all"][0]["destination_name"] draw.text((timeWidth + 5, 0), destination, fill="yellow", font=font) draw.text((width - statusWidth, 0), status, fill="yellow", font=font)
def truncateString(draw: ImageDraw, text: str, maxWidth = 420, font = rbtv_config.fontSmall): w, h = draw.textsize(text, font = font) wasModified = False while w > maxWidth: text = text[:-1] w, h = draw.textsize(text, font = font) wasModified = True if wasModified: text = text + " ..." return text
def draw_captions(self, canvas: ImageDraw, captions, settings): for setting in settings[1:]: caption = captions.pop(0) lines = wrap(caption, setting.get("wrap", settings[0].get("wrap", 20))) real_line_height = setting.get("font_size", settings[0].get("font_size", self.FONT_SIZE)) + 5 lines_count = len(lines) for line_index, line in enumerate(lines): x, y = setting.get("position") font = ImageFont.truetype("resources/fonts/lato.ttf", setting.get("font_size", settings[0].get("font_size", self.FONT_SIZE))) line_width, line_height = canvas.textsize(line, font=font) if setting.get("center", settings[0].get("center", True)): x -= line_width / 2 if setting.get("center_vertical", settings[0].get("center_vertical", False)): y -= (lines_count * real_line_height) / 2 shadow_color = setting.get("shadow_color", settings[0].get("shadow_color", None)) real_y = y + line_index * real_line_height if shadow_color is not None: shadow_size = setting.get("shadow_size", settings[0].get("shadow_size", 3)) for offset_x in range(-shadow_size, shadow_size + 1): for offset_y in range(-shadow_size, shadow_size + 1): canvas.text((x + offset_x, real_y + offset_y), line, font=font, fill=shadow_color) canvas.text((x, real_y), line, font=font, fill=setting.get("color", settings[0].get("color", self.BLACK)))
def from_text(cls, text, font, fg="black", bg=None, padding=0, max_width=None, line_spacing=0, align="left", tokenizer=whitespace_span_tokenize, hyphenator=None): """Create image from text. If max_width is set, uses the tokenizer and optional hyphenator to split text across multiple lines.""" padding = Padding(padding) if bg is None: bg = ImageColor.getrgba(fg)._replace(alpha=0) if max_width is not None: text = ImageDraw.word_wrap(text, font, max_width, tokenizer, hyphenator) w, h = ImageDraw.textsize(text, font, spacing=line_spacing) if max_width is not None and w > max_width: logger.warning("Text cropped as too wide to fit: {}".format(text)) w = max_width img = Image.new("RGBA", (w + padding.x, h + padding.y), bg) draw = ImageDraw.Draw(img) draw.text((padding.l, padding.u), text, font=font, fill=fg, spacing=line_spacing, align=align) return img
def render_text(self, _: Image, draw: ImageDraw, draw_property: Mapping, bounding_box: Tuple[Tuple[int, int], Tuple[int, int]], current_x: int, current_y: int, backwards: bool) -> Tuple[int, int]: font = ImageFont.truetype(draw_property["font"], draw_property["font_size"]) text = self.properties.get(draw_property["property"]) current_x, current_y = self.resolve_position(draw_property["position"], bounding_box, current_x, current_y) if text is not None: background = self.properties.get(draw_property["background_property"]) if background is None: background = draw_property["background_default"] text_color = self.properties.get(draw_property["text_color_property"]) if text_color is None: text_color = draw_property["text_color_default"] text_width, text_height = draw.textsize(text, font) desired_width = draw_property["size"][0] if draw_property["size"][0] != "auto" else text_width desired_height = draw_property["size"][1] if draw_property["size"][1] != "auto" else text_height if backwards: current_x -= desired_width draw.rectangle(((current_x, current_y), (current_x + desired_width, current_y + desired_height)), fill=background) # CodeReview: center between current_y and bottom of bounding box x_coord = (current_x + (desired_width - text_width) // 2) if draw_property["centered_width"] else current_x y_coord = (current_y + (desired_height - text_height) // 2) if draw_property["centered_height"] \ else current_y draw.text((x_coord, y_coord), text, fill=text_color, font=font) current_x += desired_width elif draw_property["required"]: raise Exception(f"Missing required property: {draw_property['property']} from card {self.properties}") return current_x, current_y
def _wrap_text(image_draw: ImageDraw, text, width, max_lines=0, ellipsis='...', **kwargs): lines = [] line = '' for token in iter(text): if len(lines) >= max_lines > 0: # TODO calculate the actual width lines[-1] = lines[-1][:-len(ellipsis)] + ellipsis break if token in '\r\n': lines.append(line) continue newline = line + token size = image_draw.textsize(newline, **kwargs) if size[0] < width: line = newline continue lines.append(line) line = token else: lines.append(line) return lines
def draw_text(draw: ImageDraw, image: Image, font, text="Text example", gravity="South", fill=(0, 0, 0), padding=5, margin=10): text_width, text_height = draw.textsize(text, font=font) gravity = gravity.lower() if gravity == 'south': x = (image.width - text_width) // 2 y = image.height - text_height - margin - padding elif gravity == 'north': x = (image.width - text_width) // 2 y = margin + padding elif gravity == 'center': x = (image.width - text_width) // 2 y = (image.height - text_height) // 2 elif gravity == 'southwest': x = margin + padding y = image.height - text_height - margin - padding elif gravity == 'southeast': x = image.width - margin - padding - text_width y = image.height - text_height - margin - padding elif gravity == 'northwest': x = y = margin + padding elif gravity == 'northeast': x = image.width - margin - padding - text_width y = margin + padding else: x = y = 0 draw.rectangle((x - padding, y - padding, x + text_width + padding, y + text_height + padding), fill=fill) draw.text((x, y), text=text, font=font)
def _render_alert(self, draw: ImageDraw) -> None: font = self._fonts["alert"] alert_width, alert_height = draw.textsize(self._display_alert[:30], font=font) alert_x = 0 alert_y = self._d_height - alert_height draw.text((alert_x, alert_y), self._display_alert[:30], fill="white", font=font)
def renderAdditionalRow3(draw: ImageDraw, width, height): nRow = "3rd:" draw.text((0, 0), nRow, fill="yellow", font=font) nTime = data["departures"]["all"][2]["aimed_departure_time"] nDestination = data["departures"]["all"][2]["destination_name"] nWidth, _ = draw.textsize(nRow, font) nTimeWidth, _ = draw.textsize(nTime, font) draw.text((nWidth + 5, 0), nTime, fill="yellow", font=font) draw.text((nWidth + nTimeWidth + 10, 0), nDestination, fill="yellow", font=font) status = "On time" statusWidth, _ = draw.textsize(status, font) draw.text((width - statusWidth, 0), status, fill="yellow", font=font)
def get_text_position(self, image: Image, draw: ImageDraw, font: ImageFont, text: str) -> Tuple[float, float]: center = (image.size[0] / 2, image.size[1] / 2) position_centered_x, position_centered_y = find_centered_position( center, draw.textsize(text, font=font)) if isinstance(self.position, Coordinate): return find_centered_position((self.position.x, self.position.y), draw.textsize(text, font=font)) elif self.position == AutoPosition.TOP: position_y = image.size[1] * 0.05 elif self.position == AutoPosition.CENTER: position_y = position_centered_y elif self.position == AutoPosition.BOTTOM: position_y = (image.size[1] * 0.95) - draw.textsize(text, font=font)[1] return (position_centered_x, position_y)
def mark_image(self, draw: ImageDraw, captions): HEAD_X, HEAD_Y = (327, 145) JUG_X, JUG_Y = (373, 440) lines = wrap(captions[0], 20) for line_index, line in enumerate(lines): line_width, line_height = draw.textsize(line, font=self.font) draw.text((HEAD_X - line_width / 2, HEAD_Y - line_height / 2 + line_index * 35), line, font=self.font, fill=self.BLACK) lines = wrap(captions[1], 25) for line_index, line in enumerate(lines): line_width, line_height = draw.textsize(line, font=self.small_font) draw.text((JUG_X - line_width / 2, JUG_Y - line_height / 2 + line_index * 20), line, font=self.small_font, fill=self.BLACK)
def mark_image(self, draw: ImageDraw, captions): BALL_X, BALL_Y = (250, 90) lines = wrap(captions[0], 20) for line_index, line in enumerate(lines): line_width, line_height = draw.textsize(line, font=self.font) draw.text((BALL_X - line_width / 2, BALL_Y - line_height / 2 + line_index * 35), line, font=self.font, fill=self.WHITE) ARMS_X, ARMS_Y = (550, 275) lines = wrap(captions[1] if len(captions) > 1 else "me", 20) for line_index, line in enumerate(lines): line_width, line_height = draw.textsize(line, font=self.font) draw.text((ARMS_X - line_width / 2, ARMS_Y - line_height / 2 + line_index * 35), line, font=self.font, fill=self.WHITE)
def _render_time(self, draw: ImageDraw) -> None: font = self._fonts["alert"] hour, minute, second = str(datetime.now().time()).split(".")[0].split(":") time_width, time_height = draw.textsize("{}:{}".format(hour, minute), font=font) time_x = self._d_width - time_width time_y = self._d_height - time_height draw.text( (time_x, time_y), "{}:{}".format(hour, minute), fill="white", font=font )
def renderClock(draw: ImageDraw, width, height): t = time.localtime() current_time = time.strftime("%H:%M:%S", t) textwidth, _ = draw.textsize(current_time, fontBoldTall) draw.text( ((width / 2) - (textwidth / 2), 0), current_time, fill="yellow", font=fontBoldTall, )
def draw_func(draw: ImageDraw): font = ImageFont.load_default() try: if font_file is not None and font_size > 0: font = ImageFont.truetype(font_file, font_size) except OSError: _LOGGER.warning("Unable to find font file: %s", font_file) except ImportError: _LOGGER.warning("Unable to open font: %s", font_file) finally: w, h = draw.textsize(text, font) draw.text((x - w / 2, y - h / 2), text, font=font, fill=color)
def mark_image(self, draw: ImageDraw, captions, settings): for setting in settings: caption = captions.pop(0) lines = wrap(caption, setting.get("wrap", 20)) for line_index, line in enumerate(lines): x, y = setting.get("position") font = ImageFont.truetype("resources/Lato-Regular.ttf", setting.get("font_size", self.FONT_SIZE)) if setting.get("center", True): line_width, line_height = draw.textsize(line, font=font) x -= line_width / 2 draw.text((x, y + line_index * (setting.get("font_size", self.FONT_SIZE) + 5)), line, font=font, fill=setting.get("color", self.BLACK))
def watermark_with_text(self, file_obj, text, color, fontfamily=None): image = Image.open(file_obj).convert('RGBA') draw = ImageDraw(image) width, height = image.file_size margin = 10 if fontfamily: font = ImageFont.truetype(fontfamily, int(height / 20)) else: font = None textWidth, textHeight = draw.textsize(text, font) x = (width - textWidth - margin) / 2 y = height - textHeight - margin draw.text((x, y), text, color, font) return image
def _render_centered_text(self, draw: ImageDraw, rect: Tuple[float, float, float, float], text: str, fill: str, font: ImageFont, parent_rect=None, mode="largest"): bb = BoundingBox(rect).partition_x(self.width, self.row_height) if mode == "largest": bb = [max(bb, key=lambda x: x.width())] elif mode == "first": bb = bb[:1] for b in bb: pos = rect_center(b.bb) rw, rh = rect_size(b.bb) w, h = draw.textsize(text, font=font) if rw < 1: continue if w > rw: text = "\n".join(list(text)) w, h = draw.textsize(text, font=font) draw.text((pos[0] - w/2, pos[1] - h/2), text, fill=fill, font=font)
def draw(self, draw: ImageDraw): super().draw(draw) font_w, font_h = draw.textsize(self._text, font=self._font) if font_h <= self.height and font_w <= self.width: horizontal_offset = self.abs_col if self._horizontal_align == Alignments.CENTER: horizontal_offset += (self.width - font_w) // 2 elif self._horizontal_align == Alignments.RIGHT: horizontal_offset += self.width - font_w vertical_offset = self.abs_row if self._vertical_align == Alignments.CENTER: vertical_offset += (self.height - font_h) // 2 - 1 elif self._vertical_align == Alignments.BOTTOM: vertical_offset += self.height - font_h draw.text((horizontal_offset, vertical_offset), self.text, fill=self.foreground, font=self._font)
def mark_image(self, draw: ImageDraw, captions): SIGN_X, SIGN_Y = (579, 460) """ ROTATION = -20.6 text_layer = Image.new('L', (W, H)) text_draw = ImageDraw.Draw(text_layer) lines = wrap(captions[0], 20) for line_index, line in enumerate(lines): line_width, line_height = draw.textsize(line, font=self.font) text_draw.text((SIGN_X-line_width/2, SIGN_Y-line_height/2 + line_index * 35), line, font=self.font) text_layer = text_layer.rotate(ROTATION, expand=0) """ lines = wrap(captions[0], 19) for line_index, line in enumerate(lines): line_width, line_height = draw.textsize(line, font=self.large_font) draw.text((SIGN_X - line_width / 2, SIGN_Y - line_height / 2 + line_index * 55), line, font=self.large_font, fill=self.BLACK)
def _renderSubText(offset, text_color, subText: str, draw: ImageDraw): stroke_color = (0, 0, 0) font = ImageFont.truetype(os.sep.join((os.getcwd(), "resource", "ttf", "DFKai-SB.ttf")), size=50, encoding='utf-8') draw.text(offset, subText, fill=text_color, font=font, stroke_width=2, stroke_fill=stroke_color) return draw.textsize(subText, font)
def render_list(self, result: Image, draw: ImageDraw, draw_property: Mapping, bounding_box: Tuple[Tuple[int, int], Tuple[int, int]], current_x: int, current_y: int, backwards: bool) -> Tuple[int, int]: theme_font = None if "text" in draw_property["type"]: theme_font = ImageFont.truetype(draw_property["font"], draw_property["font_size"]) items = self.properties.get(draw_property["property"]) current_x, current_y = self.resolve_position(draw_property["position"], bounding_box, current_x, current_y) size_x, size_y = draw_property["size"] if size_x == "auto": size_x = bounding_box[1][0] - current_x + bounding_box[0][0] if backwards: size_x = current_x - bounding_box[0][0] if size_y == "auto": size_y = bounding_box[1][1] - current_y + bounding_box[0][1] print(current_x, size_x, backwards) if backwards: current_x = current_x - size_x orientation = draw_property["orientation"] internal_current_y = current_y internal_current_x = current_x if items is not None: cached_current_x = current_x cached_current_y = current_y spacing = draw_property["spacing"] x_border = current_x + size_x y_border = current_y + size_y rows = draw_property["rows"] columns = draw_property["columns"] first = True # CodeReview: Specify an order that all will conform to for item in items: resource_name = f"resources/icons/{item}.png" if "icon" in draw_property["type"] and os.path.isfile(resource_name): icon = Image.open(resource_name) icon_width, icon_height = icon.size icon = icon.resize((self.format["icon_height"] * icon_width // icon_height, self.format["icon_height"]), Image.HAMMING) icon_width, icon_height = icon.size x_coord = internal_current_x y_coord = internal_current_y if orientation == "horizontal": if not first: x_coord += spacing first = False if draw_property["centered_height"]: y_coord += (size_y // (rows or 1) - icon_height) // 2 if orientation == "vertical": if not first: y_coord += spacing first = False if draw_property["centered_width"]: x_coord += (size_x // (columns or 1) - icon_width) // 2 if orientation == "horizontal" and x_coord + icon_width > x_border \ and rows is not None and internal_current_y < current_y + (rows - 1) * (size_y // rows): internal_current_y += size_y // rows internal_current_x = cached_current_x x_coord = internal_current_x y_coord += size_y // rows elif orientation == "vertical" and y_coord + icon_height > y_border \ and columns is not None and \ internal_current_x < current_x + (columns - 1) * (size_y // columns): internal_current_x += size_x // rows internal_current_y = cached_current_y result.paste(icon, (x_coord, y_coord)) if orientation == "horizontal": internal_current_x = x_coord + icon_width elif orientation == "vertical": internal_current_y = y_coord + icon_height elif "text" in draw_property["type"]: text_width, text_height = draw.textsize(item, theme_font) if draw_property["wrap"] is not None: item = textwrap.fill(item, draw_property["wrap"]) text_width, text_height = draw.multiline_textsize(item, theme_font) x_coord = internal_current_x y_coord = internal_current_y text_color = self.properties.get(draw_property["text_color_property"]) or \ draw_property["text_color_default"] if draw_property["bulleted"]: draw.ellipse((internal_current_x + spacing, internal_current_y + (text_height - draw_property["font_size"]) // 2, internal_current_x + spacing + draw_property["font_size"], internal_current_y + (text_height + draw_property["spacing"]) // 2), fill=text_color) x_coord += spacing*2 + draw_property["font_size"] if orientation == "horizontal": if not first: x_coord += spacing first = False if draw_property["centered_height"]: y_coord += (size_y // (rows or 1) - text_height) // 2 if orientation == "vertical": if not first: y_coord += spacing first = False if draw_property["centered_width"]: x_coord += (size_x // (columns or 1) - text_width) // 2 print(x_coord, text_width, x_border, cached_current_x, internal_current_y) if orientation == "horizontal" and x_coord + text_width > x_border \ and rows is not None and internal_current_y < current_y + (rows - 1) * (size_y // rows): internal_current_y += size_y // rows internal_current_x = cached_current_x x_coord = internal_current_x y_coord += size_y // rows elif orientation == "vertical" and y_coord + text_height > y_border \ and columns is not None and \ internal_current_x < current_x + (columns - 1) * (size_y // columns): internal_current_x += size_x // rows internal_current_y = cached_current_y if draw_property["wrap"] is None: draw.text((x_coord, y_coord), item, fill=text_color, font=theme_font) else: draw.multiline_text((x_coord, y_coord), item, fill=text_color, font=theme_font) if orientation == "horizontal": internal_current_x = x_coord + text_width elif orientation == "vertical": internal_current_y = y_coord + text_height else: raise Exception(f"Could not find handler for {item} for {draw_property['type']}") if not backwards: current_x += size_x if orientation == "vertical": current_y += size_y else: if draw_property["required"]: raise Exception(f"Missing required property: {draw_property['property']} from card {self.properties}") return current_x, current_y
def draw_calendar(epd_width, draw: ImageDraw, now: datetime): # Generate calendar in top-right cal_topleftx = epd_width / 2 cal_toplefty = 0 cal_x_offset = 10 cal_y_offset = 50 # Configure Calendar settings calendar.setfirstweekday(calendar.SUNDAY) cal_with_zeros = calendar.monthcalendar(now.year, now.month) leading_zeroes = cal_with_zeros[0].count(0) # Calculate header format cal_header = calendar.month_name[now.month] + ' ' + str(now.year) w, h = draw.textsize(cal_header, font=textfont32) draw.rectangle([(cal_topleftx, cal_toplefty), (epd_width, cal_toplefty + 1.5 * h)], fill=0) draw.text((cal_topleftx + (cal_topleftx - w) / 2, cal_toplefty + cal_y_offset - 40), cal_header, font=textfont32, fill=255) y = h - 3 # Draw calendar rows days = 'Su Mo Tu We Th Fr Sa' w, h = draw.textsize(days, font=textfont24) draw.text( (cal_topleftx + (cal_topleftx - w) / 2, cal_toplefty + cal_y_offset), days, font=textfont24) for row in cal_with_zeros: row = [' ' + (str(x)) if x < 10 else str(x) for x in row] # list comprehension magic row = [' ' if x == ' 0' else x for x in row] # Remove zeros from calendar line = ' '.join(row) logging.debug("Printing calendar row starting at {},{}".format( cal_topleftx + cal_x_offset, cal_toplefty + cal_y_offset + y)) draw.text((cal_topleftx + (cal_topleftx - w) / 2, cal_toplefty + cal_y_offset + y), line, font=textfont24) y += h + 3 offset_from_zero = now.day + leading_zeroes - 1 today_x_grid = offset_from_zero % 7 today_y_grid = floor(offset_from_zero // 7) logging.debug("Grid coordinates for today's date: {},{}".format( today_x_grid, today_y_grid)) true_date_w, true_date_h = draw.textsize(" 00", font=textfont24) px_buffer = 3 square_w, square_h = draw.textsize("00", font=textfont24) start_x = cal_topleftx + (cal_topleftx - w) / 2 + ( true_date_w * today_x_grid) - px_buffer start_y = cal_toplefty + cal_y_offset + (today_y_grid * 30) + true_date_h - px_buffer logging.debug("Square starting at {},{}".format(start_x, start_y)) draw.rectangle([(start_x, start_y), (start_x + square_w + 2 * px_buffer, start_y + square_h + 2 * px_buffer)], width=2) return
def fetch_weather(epd_width, epd_height, draw: ImageDraw, now: datetime, config: configparser.ConfigParser): try: owm_api_key = config['Config']['owm_api_key'] mapbox_api_key = config['Config']['mapbox_api_key'] weather_units = config['Config']['units'] weather_zip = config['Config']['city_zip_code'] weather_country = config['Config']['city_country_code'] except configparser.MissingSectionHeaderError as e: logging.error(e) sys.exit(1) # geomap address to coordinates query = weather_zip + "," + weather_country r = requests.get( "https://api.mapbox.com/geocoding/v5/mapbox.places/{}.json?access_token={}" .format(query, mapbox_api_key)) geolocation = r.json() try: logging.debug("Weather fetched for {}".format( geolocation["features"][0]["place_name"])) except TypeError: logging.error("Error occured when trying to fetch weather location") sys.exit(1) weather_long = geolocation["features"][0]["center"][0] weather_lat = geolocation["features"][0]["center"][1] # pull from weather service r = requests.get( "https://api.openweathermap.org/data/2.5/onecall?lat={}&lon={}&appid={}&units={}" .format(weather_lat, weather_long, owm_api_key, weather_units)) logging.debug("Openweathermap returned status code " + str(r.status_code)) weather = r.json() # Current Weather weather_header = "Weather" weather_topleftx = epd_width / 2 weather_toplefty = epd_height / 2 todayx = weather_topleftx + 100 forecasty = weather_toplefty + 50 tomorrowx = todayx + 130 weather_image_offset = 30 tempoffset = 100 w, h = draw.textsize(weather_header, font=textfont32) draw.rectangle([(weather_topleftx, weather_toplefty), (epd_width, weather_toplefty + 1.5 * h)], fill=0) draw.text( (weather_topleftx + (weather_topleftx - w) / 2, weather_toplefty + 5), "Weather", font=textfont32, fill=255) w, h = draw.textsize("Now", font=textfont32) draw.text( (weather_topleftx + ((todayx - weather_topleftx) - w) / 2, forecasty), "Now", font=textfont32) w, h = draw.textsize( weather_font_dict[weather["current"]["weather"][0]["icon"]], font=weatherfont48) draw.text( (weather_topleftx + ((todayx - weather_topleftx) - w) / 2, forecasty + weather_image_offset), weather_font_dict[weather["current"]["weather"][0]["icon"]], font=weatherfont48, fill=0, ) w, h = draw.textsize(str(round(weather["current"]["temp"])) + "°", font=textfont24) draw.text((weather_topleftx + ((todayx - weather_topleftx) - w) / 2, forecasty + tempoffset), str(round(weather["current"]["temp"])) + "°", font=textfont24, fill=0) w, h = draw.textsize("Feels Like:", font=textfont16) draw.text((weather_topleftx + ((todayx - weather_topleftx) - w) / 2, forecasty + 128), "Feels Like:", font=textfont16, fill=0) w, h = draw.textsize(str(round(weather["current"]["feels_like"])) + "°", font=textfont24) draw.text((weather_topleftx + ((todayx - weather_topleftx) - w) / 2, forecasty + 145), str(round(weather["current"]["feels_like"])) + "°", font=textfont24, fill=0) # Today's Forecast w, h = draw.textsize("Today", font=textfont32) draw.text((todayx + ((tomorrowx - todayx) - w) / 2, forecasty), "Today", font=textfont32) w, h = draw.textsize( weather_font_dict[weather["daily"][0]["weather"][0]["icon"]], font=weatherfont48) draw.text( (todayx + ((tomorrowx - todayx) - w) / 2, forecasty + weather_image_offset), weather_font_dict[weather["daily"][0]["weather"][0]["icon"]], font=weatherfont48, fill=0) todaymintemp = round(weather["daily"][0]["temp"]["min"]) todaymaxtemp = round(weather["daily"][0]["temp"]["max"]) w, h = draw.textsize(str(todaymaxtemp) + "°/" + str(todaymintemp) + "°", font=textfont24) draw.text( (todayx + ((tomorrowx - todayx) - w) / 2, forecasty + tempoffset), str(todaymaxtemp) + "°/" + str(todaymintemp) + "°", font=textfont24, fill=0) precipxoffset = 40 precipyoffset = 120 # today's precip chance if todaymintemp < 32: # snow image! w_image, h = draw.textsize(weather_font_dict['13d'], font=weatherfont32) w_text, h = draw.textsize(str(round(weather["daily"][0]["pop"])), font=textfont32) sum_w = w_image + w_text + precipxoffset / 2 draw.text( (todayx + ((tomorrowx - todayx) - sum_w) / 2, forecasty + precipyoffset), weather_font_dict['13d'], font=weatherfont32, fill=0) else: # lame, no snow w_image, h = draw.textsize(weather_font_dict['10d'], font=weatherfont32) w_text, h = draw.textsize(str(round(weather["daily"][0]["pop"])), font=textfont32) sum_w = w_image + w_text + precipxoffset / 2 draw.text( (todayx + ((tomorrowx - todayx) - sum_w) / 2, forecasty + precipyoffset), weather_font_dict['10d'], font=weatherfont32, fill=0) draw.text((todayx + ((tomorrowx - todayx) - sum_w) / 2 + precipxoffset, forecasty + precipyoffset + 13), str(round(weather["daily"][0]["pop"])) + "%", font=textfont32, fill=0) # Tomorrow's Forecast w, h = draw.textsize("Tomorrow", font=textfont32) draw.text((tomorrowx + ((epd_width - tomorrowx) - w) / 2, forecasty), "Tomorrow", font=textfont32) w, h = draw.textsize( weather_font_dict[weather["daily"][1]["weather"][0]["icon"]], font=weatherfont48) draw.text( (tomorrowx + ((epd_width - tomorrowx) - w) / 2, forecasty + weather_image_offset), weather_font_dict[weather["daily"][1]["weather"][0]["icon"]], font=weatherfont48, fill=0) tomorrowmaxtemp = round(weather["daily"][1]["temp"]["max"]) tomorrowmintemp = round(weather["daily"][1]["temp"]["min"]) w, h = draw.textsize(str(tomorrowmaxtemp) + "°/" + str(tomorrowmintemp) + "°", font=textfont24) draw.text((tomorrowx + ((epd_width - tomorrowx) - w) / 2, forecasty + tempoffset), str(tomorrowmaxtemp) + "°/" + str(tomorrowmintemp) + "°", font=textfont24, fill=0) # tomorrow's precip chance if tomorrowmintemp < 32: # snow image! w_image, h = draw.textsize(weather_font_dict['13d'], font=weatherfont32) w_text, h = draw.textsize(str(round(weather["daily"][1]["pop"])), font=textfont32) sum_w = w_image + w_text + precipxoffset / 2 draw.text( (tomorrowx + ((epd_width - tomorrowx) - sum_w) / 2, forecasty + precipyoffset), weather_font_dict['13d'], font=weatherfont32, fill=0) else: # lame, no snow w_image, h = draw.textsize(weather_font_dict['10d'], font=weatherfont32) w_text, h = draw.textsize(str(round(weather["daily"][1]["pop"])), font=textfont32) sum_w = w_image + w_text + precipxoffset / 2 draw.text( (tomorrowx + ((epd_width - tomorrowx) - sum_w) / 2, forecasty + precipyoffset), weather_font_dict['10d'], font=weatherfont32, fill=0) draw.text( (tomorrowx + ((epd_width - tomorrowx) - sum_w) / 2 + precipxoffset, forecasty + precipyoffset + 13), str(round(weather["daily"][1]["pop"])) + "%", font=textfont32, fill=0) # Draw division lines if draw_weather_lines: weather_bottom_divider_offset = 18 draw.line([(todayx, weather_toplefty + 1.5 * h), (todayx, epd_height - weather_bottom_divider_offset)], width=2) draw.line([(tomorrowx, weather_toplefty + 1.5 * h), (tomorrowx, epd_height - weather_bottom_divider_offset)], width=2) draw.line( [(weather_topleftx, epd_height - weather_bottom_divider_offset), (epd_width, epd_height - weather_bottom_divider_offset)], width=2) # Print weather location, update time w, h = draw.textsize("{}".format(geolocation["features"][0]["place_name"]), font=textfont12) draw.text((weather_topleftx + 8, epd_height - h - 2), "{}".format(geolocation["features"][0]["place_name"]), font=textfont12)
def display_news(epd_width, epd_height, draw: ImageDraw, config: configparser.ConfigParser): # This function could use a refactor because of all of the code reuse, but it works news_rightx = epd_width / 2 general_news_header = "Top USA Headlines" tech_news_header = "Top Tech Headlines" header_font = textfont32 headline_font = textfont16 headline_offset_from_side = 10 second_line_x_offset_from_headline = 10 first_headline_offset_from_top = 50 try: api_key = config['Config']['newsapi_key'] except configparser.MissingSectionHeaderError as e: logging.error(e) sys.exit(1) # Draw header rectangle w, h = draw.textsize(general_news_header, font=header_font) draw.text(((news_rightx - w) / 2, 10), general_news_header, font=header_font) y = first_headline_offset_from_top r = requests.get( general_news_non_tech_url.format( api_key, ",".join(included_general_news_domains))) results = r.json() general_articles = results['data'] # Process article headers titles_with_source = [] for article in general_articles: titles_with_source.append([article['source'], article['title']]) # For each headline: Calculate size, trim remainder, display for headline in titles_with_source: article_source = headline[0] article_title = headline[1] second_line = "" text_to_display = article_source + ": " + article_title text_to_display = text_to_display.encode('latin-1', 'ignore') w, h = draw.textsize(text_to_display, font=headline_font) # Split text into at most 2 lines while w + headline_offset_from_side > news_rightx: article_title, excess_word = article_title.rsplit(" ", 1) second_line = excess_word + " " + second_line text_to_display = article_source + ": " + article_title text_to_display = text_to_display.encode('latin-1', 'ignore') w, h = draw.textsize(text_to_display, font=headline_font) draw.text((headline_offset_from_side, y), text_to_display, font=headline_font) y += h if second_line: # trim excess characters and replace with "..." at the end second_line = second_line.encode('latin-1', 'ignore') encoded_space = " ".encode('latin-1') encoded_ellipse = "...".encode('latin-1') w, h = draw.textsize(second_line, font=headline_font) w_periods, h = draw.textsize("...", font=headline_font) needs_periods = False if w + headline_offset_from_side + second_line_x_offset_from_headline > news_rightx: while w + w_periods + headline_offset_from_side + second_line_x_offset_from_headline > news_rightx: needs_periods = True second_line = second_line[:-1] w, h = draw.textsize(second_line, font=headline_font) if needs_periods: if second_line.endswith(encoded_space): # Removes floating space issue second_line = second_line[:-1] second_line = second_line + encoded_ellipse draw.text((headline_offset_from_side + second_line_x_offset_from_headline, y), second_line, font=headline_font) y += 1.3 * h # Do Tech news here w, h = draw.textsize(tech_news_header, font=header_font) draw.text(((news_rightx - w) / 2, y), tech_news_header, font=header_font) y += 1.2 * h r = requests.get( tech_news_url.format(api_key, ",".join(excluded_tech_domains))) results = r.json() general_articles = results['data'] # Process article headers titles_with_source = [] for article in general_articles: titles_with_source.append([article['source'], article['title']]) # For each headline: Calculate size, trim remainder, display for headline in titles_with_source: article_source = headline[0] article_title = headline[1] second_line = "" text_to_display = article_source + ": " + article_title text_to_display = text_to_display.encode('latin-1', 'ignore') w, h = draw.textsize(text_to_display, font=headline_font) # Split text into at most 2 lines while w + headline_offset_from_side > news_rightx: article_title, excess_word = article_title.rsplit(" ", 1) second_line = excess_word + " " + second_line text_to_display = article_source + ": " + article_title text_to_display = text_to_display.encode('latin-1', 'ignore') w, h = draw.textsize(text_to_display, font=headline_font) draw.text((headline_offset_from_side, y), text_to_display, font=headline_font) y += h if second_line: # trim excess characters and replace with "..." at the end second_line = second_line.encode('latin-1', 'ignore') encoded_space = " ".encode('latin-1') encoded_ellipse = "...".encode('latin-1') w, h = draw.textsize(second_line, font=headline_font) w_periods, h = draw.textsize("...", font=headline_font) needs_periods = False if w + headline_offset_from_side + second_line_x_offset_from_headline > news_rightx: while w + w_periods + headline_offset_from_side + second_line_x_offset_from_headline > news_rightx: needs_periods = True second_line = second_line[:-1] w, h = draw.textsize(second_line, font=headline_font) if needs_periods: if second_line.endswith(encoded_space): # Removes floating space issue second_line = second_line[:-1] second_line = second_line + encoded_ellipse draw.text((headline_offset_from_side + second_line_x_offset_from_headline, y), second_line, font=headline_font) y += 1.5 * h else: y += .6 * h return
def draw_func(draw: ImageDraw): font = ImageFont.load_default() if font_file != "" and font_size > 0: font = ImageFont.truetype(font_file, font_size) w, h = draw.textsize(text, font) draw.text((x - w / 2, y - h / 2), text, font=font, fill=color)