3232from typing import Optional , List , Dict , Union
3333
3434from ttconv .model import Region , ContentDocument , P , Br , Span , Text
35- from ttconv .scc .content import SccCaptionText , SccCaptionLine
36- from ttconv .scc .style import SccCaptionStyle
35+ from ttconv .scc .caption_line import SccCaptionLine
36+ from ttconv .scc .caption_style import SccCaptionStyle
37+ from ttconv .scc .caption_text import SccCaptionText
3738from ttconv .scc .utils import get_position_from_offsets , get_extent_from_dimensions , convert_cells_to_percentages
3839from ttconv .style_properties import CoordinateType , ExtentType , StyleProperties , LengthType , DisplayAlignType , ShowBackgroundType , \
3940 TextAlignType , NamedColors
5152class SccCaptionParagraph :
5253 """Caption paragraph"""
5354
55+ @staticmethod
56+ def default (caption_style : SccCaptionStyle = SccCaptionStyle .Unknown ):
57+ """Initializes a default caption paragraph"""
58+ return SccCaptionParagraph (caption_style = caption_style )
59+
5460 def __init__ (self , safe_area_x_offset : int = 0 , safe_area_y_offset : int = 0 ,
5561 caption_style : SccCaptionStyle = SccCaptionStyle .Unknown ):
5662 self ._caption_id : str = ""
@@ -69,6 +75,8 @@ def __init__(self, safe_area_x_offset: int = 0, safe_area_y_offset: int = 0,
6975 self ._current_line : Optional [SccCaptionLine ] = None
7076 # Lines per row in the active area (will be separated by line-breaks)
7177 self ._caption_lines : Dict [int , SccCaptionLine ] = {}
78+ # Initialize first default line
79+ self .new_caption_line ()
7280
7381 self ._caption_style : SccCaptionStyle = caption_style
7482 self ._style_properties = {}
@@ -85,15 +93,15 @@ def set_begin(self, time_code):
8593 """Sets caption begin time code"""
8694 self ._begin = copy .copy (time_code )
8795
88- def get_begin (self ) -> SmpteTimeCode :
96+ def get_begin (self ) -> Optional [ SmpteTimeCode ] :
8997 """Returns the caption begin time code"""
9098 return self ._begin
9199
92100 def set_end (self , time_code ):
93101 """Sets caption end time code"""
94102 self ._end = copy .copy (time_code )
95103
96- def get_end (self ) -> SmpteTimeCode :
104+ def get_end (self ) -> Optional [ SmpteTimeCode ] :
97105 """Returns the caption end time code"""
98106 return self ._end
99107
@@ -105,18 +113,20 @@ def get_safe_area_y_offset(self):
105113 """Returns the safe area y offset"""
106114 return self ._safe_area_y_offset
107115
116+ def set_caption_style (self , caption_style : SccCaptionStyle ):
117+ """Sets the caption style"""
118+ self ._caption_style = caption_style
119+
108120 def get_caption_style (self ) -> SccCaptionStyle :
109121 """Returns the caption style"""
110122 return self ._caption_style
111123
112- def get_current_line (self ) -> Optional [ SccCaptionLine ] :
124+ def get_current_line (self ) -> SccCaptionLine :
113125 """Returns the current caption line"""
114126 return self ._current_line
115127
116- def get_current_text (self ) -> Optional [ SccCaptionText ] :
128+ def get_current_text (self ) -> SccCaptionText :
117129 """Returns the current caption text"""
118- if self ._current_line is None :
119- return None
120130 return self ._current_line .get_current_text ()
121131
122132 def append_text (self , text : str ):
@@ -150,9 +160,14 @@ def get_style_property(self, style_property) -> Optional:
150160 def set_cursor_at (self , row : int , indent : Optional [int ] = None ):
151161 """Set cursor position and initialize a new line if necessary"""
152162
153- # Remove current line if empty (useless)
154- if self ._current_line is not None and self ._current_line .is_empty ():
155- del self ._caption_lines [self ._current_line .get_row ()]
163+ if self ._caption_lines .get (self ._current_line .get_row ()) is not None :
164+ # Set current line if necessary
165+ if self ._caption_lines .get (self ._current_line .get_row ()) is not self ._current_line :
166+ self ._current_line = self ._caption_lines .get (self ._current_line .get_row ())
167+
168+ # Remove current line if empty (i.e. useless)
169+ if self ._current_line .is_empty ():
170+ del self ._caption_lines [self ._current_line .get_row ()]
156171
157172 self ._cursor = (row , indent if indent is not None else 0 )
158173
@@ -162,7 +177,7 @@ def set_cursor_at(self, row: int, indent: Optional[int] = None):
162177 self ._current_line = self ._caption_lines .get (row )
163178
164179 if indent is not None :
165- self ._current_line . set_cursor ( self . _cursor [ 1 ] - self . _current_line . get_indent () )
180+ self ._update_current_line_cursor ( )
166181
167182 def get_cursor (self ) -> (int , int ):
168183 """Returns cursor coordinates"""
@@ -176,12 +191,29 @@ def indent_cursor(self, indent: int):
176191 # If the current line is empty, set cursor indent as a line tabulation
177192 self ._current_line .indent (indent )
178193 else :
179- self ._current_line .set_cursor (self ._cursor [1 ] - self ._current_line .get_indent ())
194+ self ._update_current_line_cursor ()
195+
196+ def _update_current_line_cursor (self ):
197+ """Updates cursor position on current line"""
198+ new_cursor_position = self ._cursor [1 ] - self ._current_line .get_indent ()
199+
200+ if new_cursor_position < 0 :
201+ self ._current_line .indent (new_cursor_position )
202+
203+ self ._current_line .set_cursor (new_cursor_position )
180204
181205 def get_lines (self ) -> Dict [int , SccCaptionLine ]:
182206 """Returns the paragraph lines per row"""
183207 return self ._caption_lines
184208
209+ def is_empty (self ) -> bool :
210+ """Returns whether the paragraph has no content"""
211+ return self ._get_length () == 0
212+
213+ def _get_length (self ) -> int :
214+ """Returns the total length of contained text"""
215+ return sum ([line .get_length () for line in self ._caption_lines .values ()])
216+
185217 def copy_lines (self ) -> Dict [int , SccCaptionLine ]:
186218 """Copy paragraph lines (without time attributes)"""
187219 lines_copy = {}
@@ -199,10 +231,6 @@ def copy_lines(self) -> Dict[int, SccCaptionLine]:
199231
200232 def new_caption_text (self ):
201233 """Appends a new caption text content, and keeps reference on it"""
202- if self ._current_line is None :
203- LOGGER .warning ("Add a new caption line to add new caption text" )
204- self .new_caption_line ()
205-
206234 self ._current_line .add_text (SccCaptionText ())
207235
208236 def new_caption_line (self ):
@@ -227,7 +255,7 @@ def roll_up(self):
227255
228256 def get_origin (self ) -> CoordinateType :
229257 """Computes and returns the current paragraph origin, based on its content"""
230- if len ( self ._caption_lines ) > 0 :
258+ if not self .is_empty () :
231259 x_offsets = [text .get_indent () for text in self ._caption_lines .values ()]
232260 y_offsets = [text .get_row () - 1 for text in self ._caption_lines .values ()]
233261
@@ -237,7 +265,7 @@ def get_origin(self) -> CoordinateType:
237265
238266 def get_extent (self ) -> ExtentType :
239267 """Computes and returns the current paragraph extent, based on its content"""
240- if len ( self ._caption_lines ) == 0 :
268+ if self .is_empty () :
241269 return get_extent_from_dimensions (0 , 0 )
242270
243271 paragraph_rows = self ._caption_lines .keys ()
@@ -260,6 +288,9 @@ def guess_text_alignment(self) -> TextAlignType:
260288 def get_line_right_offset (line : SccCaptionLine ) -> int :
261289 return SCC_ROOT_CELL_RESOLUTION_COLUMNS - (line .get_indent () + line .get_length ())
262290
291+ if self .is_empty ():
292+ return TextAlignType .start
293+
263294 # look for longest line
264295 longest_line = max (self ._caption_lines .values (), key = lambda line : line .get_length ())
265296
0 commit comments