classes.c22_frame_iterator
1from collections.abc import Callable 2from classes import KBox 3from lib import layout 4from icecream import ic 5from typing import Any, Literal 6 7PageSetupFunc = Callable[[int], layout.Coordinates] 8"""A callable type for functions that set up a new page. 9 10The function takes one argument: 11- `pageIndex`: The index of the page being created. 12 13Returns a tuple representing the (x, y, width, height) of the content frame. 14""" 15 16VerticalDirection = Literal["top-down", "bottom-up"] 17 18 19ItemDrawFunc = Callable[[KBox, Any, int, int], int | None] 20"""A callable type for functions used in KFrameIterator. 21 22The function takes three arguments: 23- `box`: An instance of `KBox` representing the current box. 24- `item`: The current item from the items list. 25- `itemIndex`: The index of the current item. 26- `rowIndex`: The index of the current row. 27 28Returns the height used to draw the item or 0 if there was insufficient space and a new page is needed. If the function returns None, it indicates that the item was skipped. 29""" 30 31 32class KFrameIterator: 33 PageSetup = PageSetupFunc 34 ItemDraw = ItemDrawFunc 35 36 def __init__( 37 self, 38 pageSetup: PageSetupFunc, 39 itemDraw: ItemDrawFunc, 40 items: list[Any], 41 pageBreakBefore: Callable[[Any], bool] | None = None, 42 pageBreakBeforeKey: str | None = None, 43 verticalDirection: VerticalDirection = "top-down", 44 ): 45 """ 46 Initialize a KFrameIterator instance. 47 48 Args: 49 pageSetup: A function to set up a new page and return the content frame. 50 itemDraw: A function to call for each item. 51 items: A list of items to iterate over. 52 pageBreakBefore: Optional predicate to request a page break before drawing an item. 53 pageBreakBeforeKey: Optional key checked on mapping-like items for page-break request. 54 verticalDirection: Vertical flow direction. Defaults to "top-down". 55 """ 56 self.pageSetup = pageSetup 57 self.itemDraw = itemDraw 58 self.items = items 59 self.pageBreakBefore = pageBreakBefore 60 self.pageBreakBeforeKey = pageBreakBeforeKey 61 if verticalDirection not in ("top-down", "bottom-up"): 62 raise ValueError( 63 f"Invalid verticalDirection '{verticalDirection}'. Use 'top-down' or 'bottom-up'." 64 ) 65 self.verticalDirection = verticalDirection 66 self.pageIndex = 0 67 self.itemIndex = 0 68 self.rowIndex = 0 69 self.history: list[dict] = [] 70 71 # Initialize the first page and set the content frame for the first time 72 self.box = self.createPage() 73 74 def shouldPageBreakBefore(self, item: Any) -> bool: 75 """Return True if a page break is requested before drawing the given item.""" 76 if self.pageBreakBefore is not None: 77 return bool(self.pageBreakBefore(item)) 78 79 if self.pageBreakBeforeKey and hasattr(item, "get"): 80 return bool(item.get(self.pageBreakBeforeKey, False)) 81 82 return False 83 84 def createPage(self) -> "KBox": 85 """Add a new page and update the content frame.""" 86 box = KBox(self.pageSetup(self.pageIndex)) 87 self.pageIndex += 1 88 self.rowIndex = 0 89 return box 90 91 def getAdvanceOrigin(self) -> Literal["top", "bottom"]: 92 """Get the side from which the frame is consumed after a successful draw.""" 93 return "bottom" if self.verticalDirection == "top-down" else "top" 94 95 def __iter__(self): 96 return self 97 98 def __next__(self): 99 if not self.items: 100 raise StopIteration 101 102 # Peek at the next item without removing it 103 item = self.items[0] 104 105 # Optional explicit page break before this item (ignored at top of page) 106 if self.rowIndex > 0 and self.shouldPageBreakBefore(item): 107 self.box = self.createPage() 108 109 # Try to draw the item 110 result = self.itemDraw(self.box, item, self.itemIndex, self.rowIndex) 111 112 if result == 0: 113 # Not enough space - create a new page 114 self.box = self.createPage() 115 # Try again on the new page 116 result = self.itemDraw(self.box, item, self.itemIndex, self.rowIndex) 117 118 if result == 0: 119 # Still not enough space - item too large for the frame 120 raise ValueError( 121 f"Item at index {self.itemIndex} is too large for the frame." 122 ) 123 124 # Item was drawn successfully, remove the item 125 self.items.pop(0) 126 127 # Advance the box and indices if the item was drawn 128 if result is not None: 129 self.history.append( 130 { 131 "item": item, 132 "itemIndex": self.itemIndex, 133 "rowIndex": self.rowIndex, 134 "pageIndex": self.pageIndex - 1, 135 } 136 ) 137 self.box.shrinkHeight( 138 result, origin=self.getAdvanceOrigin(), implicitUnit="pt" 139 ) 140 self.itemIndex += 1 141 self.rowIndex += 1 142 143 def iterate(self) -> "KFrameIterator": 144 """Iterate through all items, calling the function for each.""" 145 for _ in self: 146 pass # The function is already called in __next__ 147 return self
PageSetupFunc =
collections.abc.Callable[[int], tuple[float, float, float, float]]
A callable type for functions that set up a new page.
The function takes one argument:
pageIndex: The index of the page being created.
Returns a tuple representing the (x, y, width, height) of the content frame.
VerticalDirection =
typing.Literal['top-down', 'bottom-up']
ItemDrawFunc =
collections.abc.Callable[[classes.c20_box.KBox, typing.Any, int, int], int | None]
A callable type for functions used in KFrameIterator.
The function takes three arguments:
box: An instance ofKBoxrepresenting the current box.item: The current item from the items list.itemIndex: The index of the current item.rowIndex: The index of the current row.
Returns the height used to draw the item or 0 if there was insufficient space and a new page is needed. If the function returns None, it indicates that the item was skipped.
class
KFrameIterator:
33class KFrameIterator: 34 PageSetup = PageSetupFunc 35 ItemDraw = ItemDrawFunc 36 37 def __init__( 38 self, 39 pageSetup: PageSetupFunc, 40 itemDraw: ItemDrawFunc, 41 items: list[Any], 42 pageBreakBefore: Callable[[Any], bool] | None = None, 43 pageBreakBeforeKey: str | None = None, 44 verticalDirection: VerticalDirection = "top-down", 45 ): 46 """ 47 Initialize a KFrameIterator instance. 48 49 Args: 50 pageSetup: A function to set up a new page and return the content frame. 51 itemDraw: A function to call for each item. 52 items: A list of items to iterate over. 53 pageBreakBefore: Optional predicate to request a page break before drawing an item. 54 pageBreakBeforeKey: Optional key checked on mapping-like items for page-break request. 55 verticalDirection: Vertical flow direction. Defaults to "top-down". 56 """ 57 self.pageSetup = pageSetup 58 self.itemDraw = itemDraw 59 self.items = items 60 self.pageBreakBefore = pageBreakBefore 61 self.pageBreakBeforeKey = pageBreakBeforeKey 62 if verticalDirection not in ("top-down", "bottom-up"): 63 raise ValueError( 64 f"Invalid verticalDirection '{verticalDirection}'. Use 'top-down' or 'bottom-up'." 65 ) 66 self.verticalDirection = verticalDirection 67 self.pageIndex = 0 68 self.itemIndex = 0 69 self.rowIndex = 0 70 self.history: list[dict] = [] 71 72 # Initialize the first page and set the content frame for the first time 73 self.box = self.createPage() 74 75 def shouldPageBreakBefore(self, item: Any) -> bool: 76 """Return True if a page break is requested before drawing the given item.""" 77 if self.pageBreakBefore is not None: 78 return bool(self.pageBreakBefore(item)) 79 80 if self.pageBreakBeforeKey and hasattr(item, "get"): 81 return bool(item.get(self.pageBreakBeforeKey, False)) 82 83 return False 84 85 def createPage(self) -> "KBox": 86 """Add a new page and update the content frame.""" 87 box = KBox(self.pageSetup(self.pageIndex)) 88 self.pageIndex += 1 89 self.rowIndex = 0 90 return box 91 92 def getAdvanceOrigin(self) -> Literal["top", "bottom"]: 93 """Get the side from which the frame is consumed after a successful draw.""" 94 return "bottom" if self.verticalDirection == "top-down" else "top" 95 96 def __iter__(self): 97 return self 98 99 def __next__(self): 100 if not self.items: 101 raise StopIteration 102 103 # Peek at the next item without removing it 104 item = self.items[0] 105 106 # Optional explicit page break before this item (ignored at top of page) 107 if self.rowIndex > 0 and self.shouldPageBreakBefore(item): 108 self.box = self.createPage() 109 110 # Try to draw the item 111 result = self.itemDraw(self.box, item, self.itemIndex, self.rowIndex) 112 113 if result == 0: 114 # Not enough space - create a new page 115 self.box = self.createPage() 116 # Try again on the new page 117 result = self.itemDraw(self.box, item, self.itemIndex, self.rowIndex) 118 119 if result == 0: 120 # Still not enough space - item too large for the frame 121 raise ValueError( 122 f"Item at index {self.itemIndex} is too large for the frame." 123 ) 124 125 # Item was drawn successfully, remove the item 126 self.items.pop(0) 127 128 # Advance the box and indices if the item was drawn 129 if result is not None: 130 self.history.append( 131 { 132 "item": item, 133 "itemIndex": self.itemIndex, 134 "rowIndex": self.rowIndex, 135 "pageIndex": self.pageIndex - 1, 136 } 137 ) 138 self.box.shrinkHeight( 139 result, origin=self.getAdvanceOrigin(), implicitUnit="pt" 140 ) 141 self.itemIndex += 1 142 self.rowIndex += 1 143 144 def iterate(self) -> "KFrameIterator": 145 """Iterate through all items, calling the function for each.""" 146 for _ in self: 147 pass # The function is already called in __next__ 148 return self
KFrameIterator( pageSetup: Callable[[int], tuple[float, float, float, float]], itemDraw: Callable[[classes.c20_box.KBox, typing.Any, int, int], int | None], items: list[typing.Any], pageBreakBefore: Callable[[typing.Any], bool] | None = None, pageBreakBeforeKey: str | None = None, verticalDirection: Literal['top-down', 'bottom-up'] = 'top-down')
37 def __init__( 38 self, 39 pageSetup: PageSetupFunc, 40 itemDraw: ItemDrawFunc, 41 items: list[Any], 42 pageBreakBefore: Callable[[Any], bool] | None = None, 43 pageBreakBeforeKey: str | None = None, 44 verticalDirection: VerticalDirection = "top-down", 45 ): 46 """ 47 Initialize a KFrameIterator instance. 48 49 Args: 50 pageSetup: A function to set up a new page and return the content frame. 51 itemDraw: A function to call for each item. 52 items: A list of items to iterate over. 53 pageBreakBefore: Optional predicate to request a page break before drawing an item. 54 pageBreakBeforeKey: Optional key checked on mapping-like items for page-break request. 55 verticalDirection: Vertical flow direction. Defaults to "top-down". 56 """ 57 self.pageSetup = pageSetup 58 self.itemDraw = itemDraw 59 self.items = items 60 self.pageBreakBefore = pageBreakBefore 61 self.pageBreakBeforeKey = pageBreakBeforeKey 62 if verticalDirection not in ("top-down", "bottom-up"): 63 raise ValueError( 64 f"Invalid verticalDirection '{verticalDirection}'. Use 'top-down' or 'bottom-up'." 65 ) 66 self.verticalDirection = verticalDirection 67 self.pageIndex = 0 68 self.itemIndex = 0 69 self.rowIndex = 0 70 self.history: list[dict] = [] 71 72 # Initialize the first page and set the content frame for the first time 73 self.box = self.createPage()
Initialize a KFrameIterator instance.
Arguments:
- pageSetup: A function to set up a new page and return the content frame.
- itemDraw: A function to call for each item.
- items: A list of items to iterate over.
- pageBreakBefore: Optional predicate to request a page break before drawing an item.
- pageBreakBeforeKey: Optional key checked on mapping-like items for page-break request.
- verticalDirection: Vertical flow direction. Defaults to "top-down".
ItemDraw =
collections.abc.Callable[[classes.c20_box.KBox, typing.Any, int, int], int | None]
def
shouldPageBreakBefore(self, item: Any) -> bool:
75 def shouldPageBreakBefore(self, item: Any) -> bool: 76 """Return True if a page break is requested before drawing the given item.""" 77 if self.pageBreakBefore is not None: 78 return bool(self.pageBreakBefore(item)) 79 80 if self.pageBreakBeforeKey and hasattr(item, "get"): 81 return bool(item.get(self.pageBreakBeforeKey, False)) 82 83 return False
Return True if a page break is requested before drawing the given item.
85 def createPage(self) -> "KBox": 86 """Add a new page and update the content frame.""" 87 box = KBox(self.pageSetup(self.pageIndex)) 88 self.pageIndex += 1 89 self.rowIndex = 0 90 return box
Add a new page and update the content frame.
def
getAdvanceOrigin(self) -> Literal['top', 'bottom']:
92 def getAdvanceOrigin(self) -> Literal["top", "bottom"]: 93 """Get the side from which the frame is consumed after a successful draw.""" 94 return "bottom" if self.verticalDirection == "top-down" else "top"
Get the side from which the frame is consumed after a successful draw.