Module fpdf.text_region
Classes
class Bullet (bullet_fragments, text_line, bullet_r_margin)
-
Expand source code Browse git
class Bullet: def __init__( self, bullet_fragments, text_line, bullet_r_margin, ): self.fragments = bullet_fragments self.text_line = text_line self.r_margin = bullet_r_margin self.rendered_flag = False def get_fragments_width(self): fragments_width = 0 for frag in self.fragments: fragments_width += frag.get_width() return fragments_width
Methods
def get_fragments_width(self)
-
Expand source code Browse git
def get_fragments_width(self): fragments_width = 0 for frag in self.fragments: fragments_width += frag.get_width() return fragments_width
class Extents (left: float, right: float)
-
Expand source code Browse git
class Extents(NamedTuple): left: float right: float
Extents(left, right)
Ancestors
- builtins.tuple
Instance variables
var left : float
-
Expand source code Browse git
class Extents(NamedTuple): left: float right: float
Alias for field number 0
var right : float
-
Expand source code Browse git
class Extents(NamedTuple): left: float right: float
Alias for field number 1
class ImageParagraph (region,
name,
align=None,
width: float = None,
height: float = None,
fill_width: bool = False,
keep_aspect_ratio=False,
top_margin=0,
bottom_margin=0,
link=None,
title=None,
alt_text=None)-
Expand source code Browse git
class ImageParagraph: def __init__( self, region, name, align=None, width: float = None, height: float = None, fill_width: bool = False, keep_aspect_ratio=False, top_margin=0, bottom_margin=0, link=None, title=None, alt_text=None, ): self.region = region self.name = name if align: align = Align.coerce(align) if align not in (Align.L, Align.C, Align.R): raise ValueError( f"Align must be 'LEFT', 'CENTER', or 'RIGHT', not '{align.value}'." ) self.align = align self.width = width self.height = height self.fill_width = fill_width self.keep_aspect_ratio = keep_aspect_ratio self.top_margin = top_margin self.bottom_margin = bottom_margin self.link = link self.title = title self.alt_text = alt_text self.img = self.info = None def build_line(self): # We do double duty as a "text line wrapper" here, since all the necessary # information is already in the ImageParagraph object. self.name, self.img, self.info = preload_image( self.region.pdf.image_cache, self.name ) return self def render(self, col_left, col_width, max_height): if not self.img: raise RuntimeError( "ImageParagraph.build_line() must be called before render()." ) is_svg = isinstance(self.info, VectorImageInfo) # pylint: disable=possibly-used-before-assignment if self.height: h = self.height else: native_h = self.info["h"] / self.region.pdf.k if self.width: w = self.width else: native_w = self.info["w"] / self.region.pdf.k if native_w > col_width or self.fill_width: w = col_width else: w = native_w if not self.height: h = w * native_h / native_w if h > max_height: return None x = col_left if self.align: if self.align == Align.R: x += col_width - w elif self.align == Align.C: x += (col_width - w) / 2 if is_svg: return self.region.pdf._vector_image( name=self.name, svg=self.img, info=self.info, x=x, y=None, w=w, h=h, link=self.link, title=self.title, alt_text=self.alt_text, keep_aspect_ratio=self.keep_aspect_ratio, ) return self.region.pdf._raster_image( name=self.name, img=self.img, info=self.info, x=x, y=None, w=w, h=h, link=self.link, title=self.title, alt_text=self.alt_text, dims=None, keep_aspect_ratio=self.keep_aspect_ratio, )
Methods
def build_line(self)
-
Expand source code Browse git
def build_line(self): # We do double duty as a "text line wrapper" here, since all the necessary # information is already in the ImageParagraph object. self.name, self.img, self.info = preload_image( self.region.pdf.image_cache, self.name ) return self
def render(self, col_left, col_width, max_height)
-
Expand source code Browse git
def render(self, col_left, col_width, max_height): if not self.img: raise RuntimeError( "ImageParagraph.build_line() must be called before render()." ) is_svg = isinstance(self.info, VectorImageInfo) # pylint: disable=possibly-used-before-assignment if self.height: h = self.height else: native_h = self.info["h"] / self.region.pdf.k if self.width: w = self.width else: native_w = self.info["w"] / self.region.pdf.k if native_w > col_width or self.fill_width: w = col_width else: w = native_w if not self.height: h = w * native_h / native_w if h > max_height: return None x = col_left if self.align: if self.align == Align.R: x += col_width - w elif self.align == Align.C: x += (col_width - w) / 2 if is_svg: return self.region.pdf._vector_image( name=self.name, svg=self.img, info=self.info, x=x, y=None, w=w, h=h, link=self.link, title=self.title, alt_text=self.alt_text, keep_aspect_ratio=self.keep_aspect_ratio, ) return self.region.pdf._raster_image( name=self.name, img=self.img, info=self.info, x=x, y=None, w=w, h=h, link=self.link, title=self.title, alt_text=self.alt_text, dims=None, keep_aspect_ratio=self.keep_aspect_ratio, )
class LineWrapper (line: Sequence,
paragraph: Paragraph,
first_line: bool = False,
last_line: bool = False)-
Expand source code Browse git
class LineWrapper(NamedTuple): """Connects each TextLine with the Paragraph it was written to. This allows to access paragraph specific attributes like top/bottom margins when rendering the line. """ line: Sequence paragraph: Paragraph first_line: bool = False last_line: bool = False
Connects each TextLine with the Paragraph it was written to. This allows to access paragraph specific attributes like top/bottom margins when rendering the line.
Ancestors
- builtins.tuple
Instance variables
var first_line : bool
-
Expand source code Browse git
class LineWrapper(NamedTuple): """Connects each TextLine with the Paragraph it was written to. This allows to access paragraph specific attributes like top/bottom margins when rendering the line. """ line: Sequence paragraph: Paragraph first_line: bool = False last_line: bool = False
Alias for field number 2
var last_line : bool
-
Expand source code Browse git
class LineWrapper(NamedTuple): """Connects each TextLine with the Paragraph it was written to. This allows to access paragraph specific attributes like top/bottom margins when rendering the line. """ line: Sequence paragraph: Paragraph first_line: bool = False last_line: bool = False
Alias for field number 3
var line : Sequence
-
Expand source code Browse git
class LineWrapper(NamedTuple): """Connects each TextLine with the Paragraph it was written to. This allows to access paragraph specific attributes like top/bottom margins when rendering the line. """ line: Sequence paragraph: Paragraph first_line: bool = False last_line: bool = False
Alias for field number 0
var paragraph : Paragraph
-
Expand source code Browse git
class LineWrapper(NamedTuple): """Connects each TextLine with the Paragraph it was written to. This allows to access paragraph specific attributes like top/bottom margins when rendering the line. """ line: Sequence paragraph: Paragraph first_line: bool = False last_line: bool = False
Alias for field number 1
class Paragraph (region,
text_align=None,
line_height=None,
top_margin: float = 0,
bottom_margin: float = 0,
indent: float = 0,
bullet_r_margin=None,
bullet_string: str = '',
skip_leading_spaces: bool = False,
wrapmode: WrapMode = None)-
Expand source code Browse git
class Paragraph: # pylint: disable=function-redefined def __init__( self, region, text_align=None, line_height=None, top_margin: float = 0, bottom_margin: float = 0, indent: float = 0, bullet_r_margin=None, bullet_string: str = "", skip_leading_spaces: bool = False, wrapmode: WrapMode = None, ): self._region = region self.pdf = region.pdf if text_align: text_align = Align.coerce(text_align) if text_align not in (Align.L, Align.C, Align.R, Align.J): raise ValueError( f"Text_align must be 'LEFT', 'CENTER', 'RIGHT', or 'JUSTIFY', not '{text_align.value}'." ) self.text_align = text_align if line_height is None: self.line_height = region.line_height else: self.line_height = line_height self.top_margin = top_margin self.bottom_margin = bottom_margin self.indent = indent self.skip_leading_spaces = skip_leading_spaces if wrapmode is None: self.wrapmode = self._region.wrapmode else: self.wrapmode = WrapMode.coerce(wrapmode) self._text_fragments = [] if bullet_r_margin is None: # Default value of 2 to be multiplied by the conversion factor # for bullet_r_margin is given in mm bullet_r_margin = 2 * get_scale_factor("mm") / self.pdf.k if bullet_string: self.bullet = Bullet( *self.generate_bullet_frags_and_tl(bullet_string, bullet_r_margin), bullet_r_margin, ) else: self.bullet = None def __str__(self): return ( f"Paragraph(text_align={self.text_align}, line_height={self.line_height}, top_margin={self.top_margin}," f" bottom_margin={self.bottom_margin}, skip_leading_spaces={self.skip_leading_spaces}, wrapmode={self.wrapmode}," f" #text_fragments={len(self._text_fragments)})" ) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self._region.end_paragraph() def write(self, text: str, link=None): if not self.pdf.font_family: raise FPDFException("No font set, you need to call set_font() beforehand") normalized_string = self.pdf.normalize_text(text).replace("\r", "") # YYY _preload_font_styles() should accept a "link" argument. fragments = self.pdf._preload_font_styles(normalized_string, markdown=False) if link: for frag in fragments: frag.link = link self._text_fragments.extend(fragments) def generate_bullet_frags_and_tl(self, bullet_string: str, bullet_r_margin: float): if not bullet_string: return None bullet_string = self.pdf.normalize_text(bullet_string) if not self.pdf.font_family: raise FPDFException("No font set, you need to call set_font() beforehand") bullet_fragments = self.pdf._preload_font_styles(bullet_string, markdown=False) fragments_width = 0 for frag in bullet_fragments: fragments_width += frag.get_width() bullet_line_break = MultiLineBreak( bullet_fragments, max_width=self._region.get_width, margins=( self.pdf.c_margin + (self.indent - fragments_width - bullet_r_margin), self.pdf.c_margin, ), align=self.text_align or self._region.text_align or Align.L, wrapmode=self.wrapmode, line_height=self.line_height, skip_leading_spaces=self.skip_leading_spaces or self._region.skip_leading_spaces, ) bullet_text_line = bullet_line_break.get_line() return bullet_fragments, bullet_text_line def ln(self, h=None): if not self.pdf.font_family: raise FPDFException("No font set, you need to call set_font() beforehand") if h is None: h = self.pdf.font_size * self.line_height fragment = self.pdf._preload_font_styles("\n", markdown=False)[0] fragment.graphics_state["font_size_pt"] = h * fragment.k self._text_fragments.append(fragment) def build_lines(self, print_sh) -> List[LineWrapper]: text_lines = [] multi_line_break = MultiLineBreak( self._text_fragments, max_width=self._region.get_width, margins=(self.pdf.c_margin + self.indent, self.pdf.c_margin), align=self.text_align or self._region.text_align or Align.L, print_sh=print_sh, wrapmode=self.wrapmode, line_height=self.line_height, skip_leading_spaces=self.skip_leading_spaces or self._region.skip_leading_spaces, ) self._text_fragments = [] text_line = multi_line_break.get_line() first_line = True while text_line is not None: text_lines.append(LineWrapper(text_line, self, first_line=first_line)) first_line = False text_line = multi_line_break.get_line() if text_lines: last = text_lines[-1] last = LineWrapper( last.line, self, first_line=last.first_line, last_line=True ) text_lines[-1] = last return text_lines
Methods
def build_lines(self, print_sh) ‑> List[LineWrapper]
-
Expand source code Browse git
def build_lines(self, print_sh) -> List[LineWrapper]: text_lines = [] multi_line_break = MultiLineBreak( self._text_fragments, max_width=self._region.get_width, margins=(self.pdf.c_margin + self.indent, self.pdf.c_margin), align=self.text_align or self._region.text_align or Align.L, print_sh=print_sh, wrapmode=self.wrapmode, line_height=self.line_height, skip_leading_spaces=self.skip_leading_spaces or self._region.skip_leading_spaces, ) self._text_fragments = [] text_line = multi_line_break.get_line() first_line = True while text_line is not None: text_lines.append(LineWrapper(text_line, self, first_line=first_line)) first_line = False text_line = multi_line_break.get_line() if text_lines: last = text_lines[-1] last = LineWrapper( last.line, self, first_line=last.first_line, last_line=True ) text_lines[-1] = last return text_lines
def generate_bullet_frags_and_tl(self, bullet_string: str, bullet_r_margin: float)
-
Expand source code Browse git
def generate_bullet_frags_and_tl(self, bullet_string: str, bullet_r_margin: float): if not bullet_string: return None bullet_string = self.pdf.normalize_text(bullet_string) if not self.pdf.font_family: raise FPDFException("No font set, you need to call set_font() beforehand") bullet_fragments = self.pdf._preload_font_styles(bullet_string, markdown=False) fragments_width = 0 for frag in bullet_fragments: fragments_width += frag.get_width() bullet_line_break = MultiLineBreak( bullet_fragments, max_width=self._region.get_width, margins=( self.pdf.c_margin + (self.indent - fragments_width - bullet_r_margin), self.pdf.c_margin, ), align=self.text_align or self._region.text_align or Align.L, wrapmode=self.wrapmode, line_height=self.line_height, skip_leading_spaces=self.skip_leading_spaces or self._region.skip_leading_spaces, ) bullet_text_line = bullet_line_break.get_line() return bullet_fragments, bullet_text_line
def ln(self, h=None)
-
Expand source code Browse git
def ln(self, h=None): if not self.pdf.font_family: raise FPDFException("No font set, you need to call set_font() beforehand") if h is None: h = self.pdf.font_size * self.line_height fragment = self.pdf._preload_font_styles("\n", markdown=False)[0] fragment.graphics_state["font_size_pt"] = h * fragment.k self._text_fragments.append(fragment)
def write(self, text: str, link=None)
-
Expand source code Browse git
def write(self, text: str, link=None): if not self.pdf.font_family: raise FPDFException("No font set, you need to call set_font() beforehand") normalized_string = self.pdf.normalize_text(text).replace("\r", "") # YYY _preload_font_styles() should accept a "link" argument. fragments = self.pdf._preload_font_styles(normalized_string, markdown=False) if link: for frag in fragments: frag.link = link self._text_fragments.extend(fragments)
class ParagraphCollectorMixin (pdf,
*args,
text=None,
text_align='LEFT',
line_height: float = 1.0,
print_sh: bool = False,
skip_leading_spaces: bool = False,
wrapmode: WrapMode = None,
img=None,
img_fill_width=False,
**kwargs)-
Expand source code Browse git
class ParagraphCollectorMixin: def __init__( self, pdf, *args, text=None, text_align="LEFT", line_height: float = 1.0, print_sh: bool = False, skip_leading_spaces: bool = False, wrapmode: WrapMode = None, img=None, img_fill_width=False, **kwargs, ): self.pdf = pdf self.text_align = Align.coerce(text_align) # default for auto paragraphs if self.text_align not in (Align.L, Align.C, Align.R, Align.J): raise ValueError( f"Text_align must be 'LEFT', 'CENTER', 'RIGHT', or 'JUSTIFY', not '{self.text_align.value}'." ) self.line_height = line_height self.print_sh = print_sh self.wrapmode = WrapMode.coerce(wrapmode) self.skip_leading_spaces = skip_leading_spaces self._paragraphs = [] self._active_paragraph = None super().__init__(pdf, *args, **kwargs) if text: self.write(text) if img: self.image(img, fill_width=img_fill_width) def __enter__(self): if self.pdf.is_current_text_region(self): raise FPDFException( f"Unable to enter the same {self.__class__.__name__} context recursively." ) self._page = self.pdf.page self.pdf._push_local_stack() self.pdf.page = 0 self.pdf.register_text_region(self) return self def __exit__(self, exc_type, exc_value, traceback): self.pdf.clear_text_region() self.pdf.page = self._page self.pdf._pop_local_stack() self.render() def _check_paragraph(self): if self._active_paragraph == "EXPLICIT": raise FPDFException( "Conflicts with active paragraph. Either close the current paragraph or write your text inside it." ) if self._active_paragraph is None: p = Paragraph( region=self, text_align=self.text_align, skip_leading_spaces=self.skip_leading_spaces, ) self._paragraphs.append(p) self._active_paragraph = "AUTO" def write(self, text: str, link=None): # pylint: disable=unused-argument self._check_paragraph() self._paragraphs[-1].write(text) def ln(self, h=None): self._check_paragraph() self._paragraphs[-1].ln(h) def paragraph( self, text_align=None, line_height=None, skip_leading_spaces: bool = False, top_margin=0, bottom_margin=0, indent=0, bullet_string="", bullet_r_margin=None, wrapmode: WrapMode = None, ): """ Args: text_align (Align, optional): the horizontal alignment of the paragraph. line_height (float, optional): factor by which the line spacing will be different from the font height. (Default: by region) top_margin (float, optional): how much spacing is added above the paragraph. No spacing will be added at the top of the paragraph if the current y position is at (or above) the top margin of the page. (Default: 0.0) bottom_margin (float, optional): those two values determine how much spacing is added below the paragraph. No spacing will be added at the bottom if it would result in overstepping the bottom margin of the page. (Default: 0.0) indent (float, optional): determines the indentation of the paragraph. (Default: 0.0) bullet_string (str, optional): determines the fragments and text lines of the bullet. (Default: "") bullet_r_margin (float, optional): determines the spacing between the bullet and the bulleted line skip_leading_spaces (float, optional): removes all space characters at the beginning of each line. (Default: False) wrapmode (WrapMode): determines the way text wrapping is handled. (Default: None) """ if self._active_paragraph == "EXPLICIT": raise FPDFException("Unable to nest paragraphs.") p = Paragraph( region=self, text_align=text_align or self.text_align, line_height=line_height, skip_leading_spaces=skip_leading_spaces or self.skip_leading_spaces, wrapmode=wrapmode, top_margin=top_margin, bottom_margin=bottom_margin, indent=indent, bullet_string=bullet_string, bullet_r_margin=bullet_r_margin, ) self._paragraphs.append(p) self._active_paragraph = "EXPLICIT" return p def end_paragraph(self): if not self._active_paragraph: raise FPDFException("No active paragraph to end.") # self._paragraphs[-1].write("\n") self._active_paragraph = None def image( self, name, align=None, width: float = None, height: float = None, fill_width: bool = False, keep_aspect_ratio=False, top_margin=0, bottom_margin=0, link=None, title=None, alt_text=None, ): if self._active_paragraph == "EXPLICIT": raise FPDFException("Unable to nest paragraphs.") if self._active_paragraph: self.end_paragraph() p = ImageParagraph( self, name, align=align, width=width, height=height, fill_width=fill_width, keep_aspect_ratio=keep_aspect_ratio, top_margin=top_margin, bottom_margin=bottom_margin, link=link, title=title, alt_text=alt_text, ) self._paragraphs.append(p)
Subclasses
Methods
def end_paragraph(self)
-
Expand source code Browse git
def end_paragraph(self): if not self._active_paragraph: raise FPDFException("No active paragraph to end.") # self._paragraphs[-1].write("\n") self._active_paragraph = None
def image(self,
name,
align=None,
width: float = None,
height: float = None,
fill_width: bool = False,
keep_aspect_ratio=False,
top_margin=0,
bottom_margin=0,
link=None,
title=None,
alt_text=None)-
Expand source code Browse git
def image( self, name, align=None, width: float = None, height: float = None, fill_width: bool = False, keep_aspect_ratio=False, top_margin=0, bottom_margin=0, link=None, title=None, alt_text=None, ): if self._active_paragraph == "EXPLICIT": raise FPDFException("Unable to nest paragraphs.") if self._active_paragraph: self.end_paragraph() p = ImageParagraph( self, name, align=align, width=width, height=height, fill_width=fill_width, keep_aspect_ratio=keep_aspect_ratio, top_margin=top_margin, bottom_margin=bottom_margin, link=link, title=title, alt_text=alt_text, ) self._paragraphs.append(p)
def ln(self, h=None)
-
Expand source code Browse git
def ln(self, h=None): self._check_paragraph() self._paragraphs[-1].ln(h)
def paragraph(self,
text_align=None,
line_height=None,
skip_leading_spaces: bool = False,
top_margin=0,
bottom_margin=0,
indent=0,
bullet_string='',
bullet_r_margin=None,
wrapmode: WrapMode = None)-
Expand source code Browse git
def paragraph( self, text_align=None, line_height=None, skip_leading_spaces: bool = False, top_margin=0, bottom_margin=0, indent=0, bullet_string="", bullet_r_margin=None, wrapmode: WrapMode = None, ): """ Args: text_align (Align, optional): the horizontal alignment of the paragraph. line_height (float, optional): factor by which the line spacing will be different from the font height. (Default: by region) top_margin (float, optional): how much spacing is added above the paragraph. No spacing will be added at the top of the paragraph if the current y position is at (or above) the top margin of the page. (Default: 0.0) bottom_margin (float, optional): those two values determine how much spacing is added below the paragraph. No spacing will be added at the bottom if it would result in overstepping the bottom margin of the page. (Default: 0.0) indent (float, optional): determines the indentation of the paragraph. (Default: 0.0) bullet_string (str, optional): determines the fragments and text lines of the bullet. (Default: "") bullet_r_margin (float, optional): determines the spacing between the bullet and the bulleted line skip_leading_spaces (float, optional): removes all space characters at the beginning of each line. (Default: False) wrapmode (WrapMode): determines the way text wrapping is handled. (Default: None) """ if self._active_paragraph == "EXPLICIT": raise FPDFException("Unable to nest paragraphs.") p = Paragraph( region=self, text_align=text_align or self.text_align, line_height=line_height, skip_leading_spaces=skip_leading_spaces or self.skip_leading_spaces, wrapmode=wrapmode, top_margin=top_margin, bottom_margin=bottom_margin, indent=indent, bullet_string=bullet_string, bullet_r_margin=bullet_r_margin, ) self._paragraphs.append(p) self._active_paragraph = "EXPLICIT" return p
Args
text_align
:Align
, optional- the horizontal alignment of the paragraph.
line_height
:float
, optional- factor by which the line spacing will be different from the font height. (Default: by region)
top_margin
:float
, optional- how much spacing is added above the paragraph. No spacing will be added at the top of the paragraph if the current y position is at (or above) the top margin of the page. (Default: 0.0)
bottom_margin
:float
, optional- those two values determine how much spacing is added below the paragraph. No spacing will be added at the bottom if it would result in overstepping the bottom margin of the page. (Default: 0.0)
indent
:float
, optional- determines the indentation of the paragraph. (Default: 0.0)
bullet_string
:str
, optional- determines the fragments and text lines of the bullet. (Default: "")
bullet_r_margin
:float
, optional- determines the spacing between the bullet and the bulleted line
skip_leading_spaces
:float
, optional- removes all space characters at the beginning of each line. (Default: False)
wrapmode
:WrapMode
- determines the way text wrapping is handled. (Default: None)
def write(self, text: str, link=None)
-
Expand source code Browse git
def write(self, text: str, link=None): # pylint: disable=unused-argument self._check_paragraph() self._paragraphs[-1].write(text)
class TextColumnarMixin (pdf, *args, l_margin=None, r_margin=None, **kwargs)
-
Expand source code Browse git
class TextColumnarMixin: """Enable a TextRegion to perform page breaks""" def __init__(self, pdf, *args, l_margin=None, r_margin=None, **kwargs): super().__init__(*args, **kwargs) self.l_margin = pdf.l_margin if l_margin is None else l_margin left = self.l_margin self.r_margin = pdf.r_margin if r_margin is None else r_margin right = pdf.w - self.r_margin self._set_left_right(left, right) def _set_left_right(self, left, right): left = self.pdf.l_margin if left is None else left right = (self.pdf.w - self.pdf.r_margin) if right is None else right if right <= left: raise FPDFException( f"{self.__class__.__name__}(): " f"Right limit ({right}) lower than left limit ({left})." ) self.extents = Extents(left, right)
Enable a TextRegion to perform page breaks
Subclasses
class TextColumns (pdf, *args, ncols: int = 1, gutter: float = 10, balance: bool = False, **kwargs)
-
Expand source code Browse git
class TextColumns(TextRegion, TextColumnarMixin): def __init__( self, pdf, *args, ncols: int = 1, gutter: float = 10, balance: bool = False, **kwargs, ): super().__init__(pdf, *args, **kwargs) self._cur_column = 0 self._ncols = ncols self.balance = balance total_w = self.extents.right - self.extents.left col_width = (total_w - (ncols - 1) * gutter) / ncols # We calculate the column extents once in advance, and store them for lookup. c_left = self.extents.left self._cols = [Extents(c_left, c_left + col_width)] for i in range(1, ncols): # pylint: disable=unused-variable c_left += col_width + gutter self._cols.append(Extents(c_left, c_left + col_width)) self._first_page_top = max(self.pdf.t_margin, self.pdf.y) def __enter__(self): super().__enter__() self._first_page_top = max(self.pdf.t_margin, self.pdf.y) if self.balance: self._cur_column = 0 self.pdf.x = self._cols[self._cur_column].left return self def new_column(self): if self._paragraphs: self._paragraphs[-1].write(FORM_FEED) else: self.write(FORM_FEED) def _render_page_lines(self, text_lines, top, bottom): """Rendering a set of lines in one or several columns on one page.""" balancing = False next_y = self.pdf.y if self.balance: # Column balancing is currently very simplistic, and only works reliably when # line height doesn't change much within the text block. # The "correct" solution would require an exact precalculation of the hight of # each column with the specific line heights and iterative regrouping of lines, # which seems excessive at this point. # Contribution of a more reliable but still reasonably simple algorithm welcome. page_bottom = bottom if not text_lines: return tot_height = sum(l.line.height for l in text_lines) col_height = tot_height / self._ncols avail_height = bottom - top if col_height < avail_height: balancing = True # We actually have room to balance on this page. # total height divided by n bottom = top + col_height # A bit more generous: Try to keep the rightmost column the shortest. lines_per_column = math.ceil(len(text_lines) / self._ncols) + 0.5 mult_height = text_lines[0].line.height * lines_per_column if mult_height > col_height: bottom = top + mult_height if bottom > page_bottom: # Turns out we don't actually have enough room. bottom = page_bottom balancing = False for c in range(self._cur_column, self._ncols): if not text_lines: return if c != self._cur_column: self._cur_column = c col_left, col_right = self.current_x_extents(0, 0) if self.pdf.x < col_left or self.pdf.x >= col_right: self.pdf.x = col_left if balancing and c == (self._ncols - 1): # Give the last column more space in case the balancing is out of whack. bottom = self.pdf.h - self.pdf.b_margin last_line_height = self._render_column_lines(text_lines, top, bottom) if balancing: new_y = self.pdf.y + last_line_height if new_y > next_y: next_y = new_y if balancing: self.pdf.y = next_y def render(self): if not self._paragraphs: return text_lines = self.collect_lines() if not text_lines: return page_bottom = self.pdf.h - self.pdf.b_margin first_page_top = max(self.pdf.t_margin, self.pdf.y) self._render_page_lines(text_lines, first_page_top, page_bottom) # Note: text_lines is progressively emptied by ._render_column_lines() while text_lines: page_break = self.pdf._perform_page_break_if_need_be(self.pdf.h) if not page_break: # Can happen when rendering a footer in the wrong place - cf. issue #1222 break self._cur_column = 0 self._render_page_lines(text_lines, self.pdf.y, page_bottom) def current_x_extents(self, y, height): left, right = self._cols[self._cur_column] return left, right
Abstract base class for all text region subclasses.
Ancestors
Methods
def new_column(self)
-
Expand source code Browse git
def new_column(self): if self._paragraphs: self._paragraphs[-1].write(FORM_FEED) else: self.write(FORM_FEED)
def render(self)
-
Expand source code Browse git
def render(self): if not self._paragraphs: return text_lines = self.collect_lines() if not text_lines: return page_bottom = self.pdf.h - self.pdf.b_margin first_page_top = max(self.pdf.t_margin, self.pdf.y) self._render_page_lines(text_lines, first_page_top, page_bottom) # Note: text_lines is progressively emptied by ._render_column_lines() while text_lines: page_break = self.pdf._perform_page_break_if_need_be(self.pdf.h) if not page_break: # Can happen when rendering a footer in the wrong place - cf. issue #1222 break self._cur_column = 0 self._render_page_lines(text_lines, self.pdf.y, page_bottom)
Inherited members
class TextRegion (pdf,
*args,
text=None,
text_align='LEFT',
line_height: float = 1.0,
print_sh: bool = False,
skip_leading_spaces: bool = False,
wrapmode: WrapMode = None,
img=None,
img_fill_width=False,
**kwargs)-
Expand source code Browse git
class TextRegion(ParagraphCollectorMixin): """Abstract base class for all text region subclasses.""" def current_x_extents(self, y, height): """ Return the horizontal extents of the current line. Columnar regions simply return the boundaries of the column. Regions with non-vertical boundaries need to check how the largest font-height in the current line actually fits in there. For that reason we include the current y and the line height. """ raise NotImplementedError() def _render_image_paragraph(self, paragraph): if paragraph.top_margin and self.pdf.y > self.pdf.t_margin: self.pdf.y += paragraph.top_margin col_left, col_right = self.current_x_extents(self.pdf.y, 0) bottom = self.pdf.h - self.pdf.b_margin max_height = bottom - self.pdf.y rendered = paragraph.render(col_left, col_right - col_left, max_height) if rendered: margin = paragraph.bottom_margin if margin and (self.pdf.y + margin) < bottom: self.pdf.y += margin return rendered def _render_column_lines(self, text_lines, top, bottom): if not text_lines: return 0 # no rendered height self.pdf.y = top prev_line_height = 0 last_line_height = None rendered_lines = 0 for tl_wrapper in text_lines: if isinstance(tl_wrapper, ImageParagraph): if self._render_image_paragraph(tl_wrapper): rendered_lines += 1 else: # not enough room for image break else: text_line = tl_wrapper.line text_rendered = False cur_paragraph = tl_wrapper.paragraph cur_bullet = cur_paragraph.bullet for frag in text_line.fragments: if frag.characters: text_rendered = True break if ( text_rendered and tl_wrapper.first_line and not cur_bullet and cur_paragraph.top_margin and self.pdf.y > self.pdf.t_margin ): self.pdf.y += cur_paragraph.top_margin if self.pdf.y + text_line.height > bottom: # => page break last_line_height = prev_line_height break prev_line_height = last_line_height last_line_height = text_line.height col_left, col_right = self.current_x_extents(self.pdf.y, 0) if self.pdf.x < col_left or self.pdf.x >= col_right: self.pdf.x = col_left self.pdf.x += cur_paragraph.indent if cur_bullet and not cur_bullet.rendered_flag: bullet_indent_shift = ( cur_bullet.get_fragments_width() + cur_bullet.r_margin ) self.pdf.x -= bullet_indent_shift self.pdf._render_styled_text_line( cur_bullet.text_line, h=cur_bullet.text_line.height, border=0, new_x=XPos.LEFT, new_y=YPos.TOP, fill=False, ) cur_bullet.rendered_flag = True self.pdf.x += bullet_indent_shift # Don't check the return, we never render past the bottom here. self.pdf._render_styled_text_line( text_line, h=text_line.height, border=0, new_x=XPos.LEFT, new_y=YPos.NEXT, fill=False, ) self.pdf.x -= cur_paragraph.indent if tl_wrapper.last_line: margin = cur_paragraph.bottom_margin if margin and text_rendered and (self.pdf.y + margin) < bottom: self.pdf.y += cur_paragraph.bottom_margin rendered_lines += 1 if text_line.trailing_form_feed: # column break break if rendered_lines: del text_lines[:rendered_lines] return last_line_height def collect_lines(self): text_lines = [] for paragraph in self._paragraphs: if isinstance(paragraph, ImageParagraph): line = paragraph.build_line() text_lines.append(line) else: cur_lines = paragraph.build_lines(self.print_sh) if not cur_lines: continue text_lines.extend(cur_lines) return text_lines def render(self): raise NotImplementedError() def get_width(self, height): start, end = self.current_x_extents(self.pdf.y, height) if self.pdf.x > start and self.pdf.x < end: start = self.pdf.x res = end - start return res
Abstract base class for all text region subclasses.
Ancestors
Subclasses
Methods
def collect_lines(self)
-
Expand source code Browse git
def collect_lines(self): text_lines = [] for paragraph in self._paragraphs: if isinstance(paragraph, ImageParagraph): line = paragraph.build_line() text_lines.append(line) else: cur_lines = paragraph.build_lines(self.print_sh) if not cur_lines: continue text_lines.extend(cur_lines) return text_lines
def current_x_extents(self, y, height)
-
Expand source code Browse git
def current_x_extents(self, y, height): """ Return the horizontal extents of the current line. Columnar regions simply return the boundaries of the column. Regions with non-vertical boundaries need to check how the largest font-height in the current line actually fits in there. For that reason we include the current y and the line height. """ raise NotImplementedError()
Return the horizontal extents of the current line. Columnar regions simply return the boundaries of the column. Regions with non-vertical boundaries need to check how the largest font-height in the current line actually fits in there. For that reason we include the current y and the line height.
def get_width(self, height)
-
Expand source code Browse git
def get_width(self, height): start, end = self.current_x_extents(self.pdf.y, height) if self.pdf.x > start and self.pdf.x < end: start = self.pdf.x res = end - start return res
def render(self)
-
Expand source code Browse git
def render(self): raise NotImplementedError()
Inherited members
class TextRegionMixin (*args, **kwargs)
-
Expand source code Browse git
class TextRegionMixin: """Mix-in to be added to FPDF() in order to support text regions.""" def __init__(self, *args, **kwargs): self.clear_text_region() super().__init__(*args, **kwargs) def register_text_region(self, region): self.__current_text_region = region def is_current_text_region(self, region): return self.__current_text_region == region def clear_text_region(self): self.__current_text_region = None
Mix-in to be added to FPDF() in order to support text regions.
Subclasses
Methods
def clear_text_region(self)
-
Expand source code Browse git
def clear_text_region(self): self.__current_text_region = None
def is_current_text_region(self, region)
-
Expand source code Browse git
def is_current_text_region(self, region): return self.__current_text_region == region
def register_text_region(self, region)
-
Expand source code Browse git
def register_text_region(self, region): self.__current_text_region = region