Source code for ged2doc.textbox

"""Module defining `TexBox` class and related methods.
"""

__all__ = ['TextBox']

import logging

from .size import Size


_log = logging.getLogger(__name__)


[docs]class TextBox: """Class representing an SVG box with a text inside. This class takes care of the text wrapping and optional resizing of the box in vertical direction to fit all text. Parameters ---------- x0 : `~ged2doc.size.Size`, optional Lowest X coordinate of corner (def: 0) y0 : `~ged2doc.size.Size`, optional Lowest Y coordinate of corner (def: 0) width : `~ged2doc.size.Size`, optional Width of a box (def: 0) maxwidth : `~ged2doc.size.Size`, optional Maximum width of a box (def: 0) height : `~ged2doc.size.Size`, optional Height of a box (def: 0) text : `str`, optional Text contained in a box (def: '') font_size : `~ged2doc.size.Size`, optional Font size (def: 10pt) rect_style : `str`, optional SVG style for rectangle text_style : `str` SVG style for text line_spacing : `~ged2doc.size.Size`, optional Space between lines (def: 1.5pt) padding : `~ged2doc.size.Size`, optional Box padding space (def: 4pt) """ def __init__(self, x0=0, y0=0, width=0, maxwidth=0, height=0, text='', font_size='10pt', padding='4pt', line_spacing='1.5pt', href=None): self._x0 = Size(x0) self._y0 = Size(y0) self._width = Size(width) self._maxwidth = Size(maxwidth) self._height = Size(height) self._text = text self._lines = self._text.split('\n') self._font_size = Size(font_size) self._padding = Size(padding) self._line_spacing = Size(line_spacing) self._href = href # calculate height if needed if self._height.value == 0: self.reflow() @property def x0(self): return self._x0 @x0.setter def x0(self, x): self._x0 = x @property def x1(self): return self._x0 + self._width @property def y0(self): return self._y0 @y0.setter def y0(self, y): self._y0 = y @property def y1(self): return self._y0 + self._height @property def midx(self): return self._x0 + self._width / 2 @property def midy(self): return self._y0 + self._height / 2 @property def width(self): return self._width @width.setter def width(self, width): self._width = width @property def height(self): return self._height @property def text(self): return self._text @property def href(self): return self._href @property def font_size(self): return self._font_size @property def lines(self): return self._lines
[docs] def lines_pos(self): """Iterate over lines and their positions. For each line of test iterator returns a tuple of two items: - text for that line - position as a tuple of two ``Size`` instances, for horizontal position it returns the center of the box (same as ``midx``), and for vertical position it returns the baseline position of that line Yields ------ line : `str` Text for a line. pos : `tuple` [ `Size` ] Text position. """ x = self.midx for i, line in enumerate(self._lines): y = self.y0 + self._padding + self._font_size * (i + 1) + \ self._line_spacing * i yield line, (x, y)
[docs] def reflow(self): """Split the text inside the box so that it fits into box width, then recalculate box height so that all text fits inside the box. """ self._lines = self._splitText(self._text) nlines = len(self._lines) self._height = nlines * self._font_size + \ (nlines - 1) * self._line_spacing + 2 * self._padding
[docs] def move(self, x0, y0): """Sets new coordinates fo x0 and y0 Parameters ---------- x0, y0 : `int` or `Size` New box coordinates. """ self._x0 = Size(x0) self._y0 = Size(y0)
[docs] def _splitText(self, text): """Tries to split a line of text into a number of lines which fit into box width. It honors embedded newlines, line will always be split at those first. Parameters ---------- text : `str` Text to split into lines. Returns ------- lines : `list` [ `str` ] """ width = self._width - 2 * self._padding # _log.debug('=========================================================') # _log.debug('_splitText: %s width=%s', text, width) lines = self._splitText1(text, width) # _log.debug('_splitText: lines=[%s]', ' | '.join(lines)) if len(lines) > 1 and self._maxwidth > Size(): # try to increase box width up to a maximum allowed width width = self._maxwidth - 2 * self._padding lines1 = self._splitText1(text, width) if len(lines1) < len(lines): self._width = max(self._textWidth(line) for line in lines1) + \ 2 * self._padding return lines1 return lines
[docs] def _splitText1(self, text, width): """Tries to split a line of text into a number of lines which fit into box width. """ lines = [] for line in text.split('\n'): words = line.split() idx = 0 while idx + 1 < len(words): twowords = ' '.join(words[idx:idx + 2]) twwidth = self._textWidth(twowords) # _log.debug('_splitText1: %s width=%s', twowords, twwidth) if twwidth <= width: words[idx:idx + 2] = [twowords] else: idx += 1 lines += words return lines
[docs] def _textWidth(self, text): """Calculates approximate width of the string of text. """ # just a wild guess for now, try to do better later return self._font_size * len(text) * 0.5
def __str__(self): return "TextBox(x0={}, x1={}, y0={}, y1={}, w={}, h={})".format( self.x0, self.x1, self.y0, self.y1, self.width, self.height )