3030 PlaneswalkerLayout ,
3131 PrototypeLayout ,
3232 SplitLayout ,
33+ StationLayout ,
3334)
3435from src .schema .adobe import EffectGradientOverlay , EffectStroke , LayerEffects
3536from src .schema .colors import ColorObject , GradientColor
3839from src .templates .planeswalker import PlaneswalkerMod
3940from src .templates .saga import SagaMod
4041from src .templates .split import SplitMod
42+ from src .templates .station import StationMod
4143from src .templates .transform import TransformMod
4244from src .text_layers import FormattedTextArea , FormattedTextField , TextField
4345from src .utils .adobe import LayerObjectTypes , ReferenceLayer
@@ -74,7 +76,13 @@ class TextboxSizingArgs(TypedDict):
7476
7577
7678class BorderlessShowcase (
77- SplitMod , VerticalMod , PlaneswalkerMod , AdventureMod , LevelerMod , BackupAndRestore
79+ SplitMod ,
80+ VerticalMod ,
81+ PlaneswalkerMod ,
82+ AdventureMod ,
83+ LevelerMod ,
84+ StationMod ,
85+ BackupAndRestore ,
7886):
7987 # region Constants
8088
@@ -292,10 +300,24 @@ def supports_dynamic_textbox_height(self) -> bool:
292300 and not self .is_leveler
293301 )
294302
303+ @cached_property
304+ def is_centered (self ) -> bool :
305+ if self .is_station :
306+ return False
307+ return super ().is_centered
308+
295309 # endregion Checks
296310
297311 # region Frame Details
298312
313+ @cached_property
314+ def doc_height (self ) -> int | float :
315+ return self .docref .height
316+
317+ @cached_property
318+ def doc_width (self ) -> int | float :
319+ return self .docref .width
320+
299321 @cached_property
300322 def art_frame_vertical (self ) -> str :
301323 if self .bottom_border_type == "Full" :
@@ -304,15 +326,12 @@ def art_frame_vertical(self) -> str:
304326
305327 @cached_property
306328 def size (self ) -> str :
307- if self .is_leveler :
308- return BorderlessTextbox .Tall
309-
310329 if self .supports_dynamic_textbox_height and (
311330 self .textbox_height or self .rules_text_font_size
312331 ):
313332 # Return something else than Tall to trigger textbox_positioning
314333 return BorderlessTextbox .Automatic
315- elif self .is_split :
334+ elif self .is_split or self . is_leveler or self . is_station :
316335 # Disables textbox_positioning
317336 return BorderlessTextbox .Tall
318337
@@ -598,52 +617,142 @@ def textbox_reference(self) -> ReferenceLayer | None:
598617 and self .textbox_reference_base
599618 and self .text_wrap_reference_base
600619 ):
601- textboxes_to_adjust : list [TextboxSizingArgs ] = [
602- {
603- "base_text_layer" : self .text_layer_rules_base ,
604- "base_textbox_reference" : self .textbox_reference_base ,
605- "base_text_wrap_reference" : self .text_wrap_reference_base ,
606- "divider_layer" : self .divider_layer ,
607- "oracle_text" : self .layout .oracle_text ,
608- "flavor_text" : self .layout .flavor_text ,
609- }
610- ]
620+ if isinstance (self .layout , StationLayout ):
621+ if self .station_group :
622+ self .station_group .visible = True
623+
624+ textbox_ref = self .station_levels_base_text_references [0 ]
625+ stations_len = len (self .layout .stations )
626+
627+ adjusted_text_layers : list [ArtLayer ] = []
628+
629+ # Adjust Station levels
630+ for i in reversed (range (stations_len )):
631+ details = self .layout .stations [i ]
632+ level_group = self .station_level_groups [i ]
633+ level_text = self .station_level_text_layers [i ]
634+ level_text_ref = self .station_levels_base_text_references [i ]
635+ requirement_group = self .station_requirement_groups [i ]
636+ pt_group = self .station_pt_groups [i ]
637+
638+ sized = self .adjust_textbox_for_font_size (
639+ base_text_layer = level_text ,
640+ base_textbox_reference = textbox_ref ,
641+ base_text_wrap_reference = level_text_ref ,
642+ divider_layer = None ,
643+ oracle_text = details ["ability" ],
644+ flavor_text = None ,
645+ )
611646
612- if (
613- isinstance (self .layout , AdventureLayout )
614- and self .text_layer_rules_adventure
615- and self .textbox_reference_adventure_base
616- and self .text_wrap_reference_adventure_base
617- ):
618- # With Adventure cards we need to make sure that
619- # both left and right rules texts fit
620- height_delta = self .textbox_reference_adventure_base .dims ["height" ]
621- textboxes_to_adjust .append (
647+ if sized :
648+ text_layer = sized [0 ]
649+
650+ self .align_center_ys (requirement_group , text_layer )
651+ if "pt" in details :
652+ self .align_center_ys (pt_group , text_layer )
653+
654+ adjusted_text_layers .insert (0 , text_layer )
655+
656+ text_ref = sized [1 ]
657+ next_ref = (
658+ self .textbox_reference_base .duplicate (
659+ self .textbox_reference_base ,
660+ ElementPlacement .PlaceBefore ,
661+ )
662+ if i == 0
663+ else self .station_levels_base_text_references [i - 1 ]
664+ )
665+ temp_shape = create_shape_layer (
666+ (
667+ {"x" : 0 , "y" : text_ref .dims ["top" ]},
668+ {"x" : self .doc_width , "y" : text_ref .dims ["top" ]},
669+ {"x" : self .doc_width , "y" : self .doc_height },
670+ {"x" : 0 , "y" : self .doc_height },
671+ ),
672+ relative_layer = next_ref ,
673+ placement = ElementPlacement .PlaceBefore ,
674+ )
675+
676+ # Next rules text section needs to be placed on top of this one,
677+ # so we have to adjust it's text reference.
678+ textbox_ref = ReferenceLayer (
679+ merge_shapes (
680+ temp_shape ,
681+ next_ref ,
682+ operation = ShapeOperation .SubtractFront ,
683+ )
684+ )
685+ textbox_ref .visible = False
686+ else :
687+ raise ValueError (
688+ f"Station textbox sizing failed for { level_group .name } "
689+ )
690+
691+ self .station_level_text_layers = adjusted_text_layers
692+
693+ # Adjust normal rules text
694+ if sized := self .adjust_textbox_for_font_size (
695+ base_text_layer = self .text_layer_rules_base ,
696+ base_textbox_reference = textbox_ref ,
697+ base_text_wrap_reference = textbox_ref ,
698+ divider_layer = None ,
699+ oracle_text = self .layout .oracle_text ,
700+ flavor_text = None ,
701+ ):
702+ rules_text , textbox_ref , _ = sized
703+ self .text_layer_rules = rules_text
704+ ref = textbox_ref
705+ else :
706+ raise ValueError (
707+ "Station textbox sizing failed for normal rules text"
708+ )
709+ else :
710+ textboxes_to_adjust : list [TextboxSizingArgs ] = [
622711 {
623- "base_text_layer" : self .text_layer_rules_adventure ,
624- "base_textbox_reference" : self .textbox_reference_adventure_base ,
625- "base_text_wrap_reference" : self .text_wrap_reference_adventure_base ,
712+ "base_text_layer" : self .text_layer_rules_base ,
713+ "base_textbox_reference" : self .textbox_reference_base ,
714+ "base_text_wrap_reference" : self .text_wrap_reference_base ,
626715 "divider_layer" : self .divider_layer ,
627- "oracle_text" : self .layout .oracle_text_adventure ,
628- "flavor_text" : self .layout .flavor_text_adventure ,
629- "height_padding" : height_delta ,
716+ "oracle_text" : self .layout .oracle_text ,
717+ "flavor_text" : self .layout .flavor_text ,
630718 }
631- )
719+ ]
632720
633- # Adjust textboxes to fit rules text of fixed font size
634- sized_boxes = self .adjust_textboxes_for_font_size (
635- self .rules_text_font_size , textboxes_to_adjust
636- )
721+ if (
722+ isinstance (self .layout , AdventureLayout )
723+ and self .text_layer_rules_adventure
724+ and self .textbox_reference_adventure_base
725+ and self .text_wrap_reference_adventure_base
726+ ):
727+ # With Adventure cards we need to make sure that
728+ # both left and right rules texts fit
729+ height_delta = self .textbox_reference_adventure_base .dims ["height" ]
730+ textboxes_to_adjust .append (
731+ {
732+ "base_text_layer" : self .text_layer_rules_adventure ,
733+ "base_textbox_reference" : self .textbox_reference_adventure_base ,
734+ "base_text_wrap_reference" : self .text_wrap_reference_adventure_base ,
735+ "divider_layer" : self .divider_layer ,
736+ "oracle_text" : self .layout .oracle_text_adventure ,
737+ "flavor_text" : self .layout .flavor_text_adventure ,
738+ "height_padding" : height_delta ,
739+ }
740+ )
741+
742+ # Adjust textboxes to fit rules text of fixed font size
743+ sized_boxes = self .adjust_textboxes_for_font_size (
744+ self .rules_text_font_size , textboxes_to_adjust
745+ )
637746
638- rules_text , textbox_ref , _ = sized_boxes [0 ]
639- self .text_layer_rules = rules_text
640- ref = textbox_ref
747+ rules_text , textbox_ref , _ = sized_boxes [0 ]
748+ self .text_layer_rules = rules_text
749+ ref = textbox_ref
641750
642- if self .is_adventure :
643- rules_text_left , textbox_ref_left , _ = sized_boxes [1 ]
751+ if self .is_adventure :
752+ rules_text_left , textbox_ref_left , _ = sized_boxes [1 ]
644753
645- self .text_layer_rules_adventure = rules_text_left
646- self .textbox_reference_adventure = textbox_ref_left
754+ self .text_layer_rules_adventure = rules_text_left
755+ self .textbox_reference_adventure = textbox_ref_left
647756 elif (
648757 not self .is_textless
649758 and self .supports_dynamic_textbox_height
@@ -1332,7 +1441,6 @@ def adjust_textboxes_for_font_size(
13321441 Adjusts multiple textboxes, whose bottom edges are aligned horizontally,
13331442 to font size so that all the textboxes will end up with the same height.
13341443 """
1335- doc_height : float | int = APP .activeDocument .height
13361444
13371445 # Size the textboxes starting from the one with most characters in its text
13381446 # in the hope that we avoid resizing textboxes that way.
@@ -1358,7 +1466,7 @@ def adjust_textboxes_for_font_size(
13581466 oracle_text = arg ["oracle_text" ],
13591467 flavor_text = arg ["flavor_text" ],
13601468 min_top = min (
1361- arg .get ("min_top" , doc_height ) or doc_height ,
1469+ arg .get ("min_top" , self . doc_height ) or self . doc_height ,
13621470 tallest_top + height_padding ,
13631471 )
13641472 or None ,
@@ -1520,7 +1628,7 @@ def pw_enable_loyalty_graphics(self) -> None:
15201628 self .loyalty_group .visible = True
15211629
15221630 @cached_property
1523- def text_layer_methods (self ) -> list [Callable [[None ], None ]]:
1631+ def text_layer_methods (self ) -> list [Callable [[], None ]]:
15241632 methods = super ().text_layer_methods
15251633 if not self .is_planeswalker :
15261634 methods .remove (self .pw_text_layers )
@@ -1530,7 +1638,7 @@ def text_layer_methods(self) -> list[Callable[[None], None]]:
15301638 return methods
15311639
15321640 @property
1533- def post_text_methods (self ) -> list [Callable [[None ], None ]]:
1641+ def post_text_methods (self ) -> list [Callable [[], None ]]:
15341642 methods = super ().post_text_methods
15351643 methods .remove (self .pw_ability_mask )
15361644 if self .is_textless :
@@ -2023,7 +2131,7 @@ def text_layers_prototype(self) -> None:
20232131
20242132 def post_text_layers_prototype (self ) -> None :
20252133 if isinstance (self .layout , PrototypeLayout ):
2026- if self .text_layer_rules_prototype :
2134+ if self .text_layer_rules_prototype and self . text_layer_rules :
20272135 self .text_layer_rules_prototype .textItem .size = (
20282136 self .text_layer_rules .textItem .size
20292137 )
@@ -2050,3 +2158,35 @@ def post_text_layers_prototype(self) -> None:
20502158 self .prototype_group .translate (0 , delta )
20512159
20522160 # endregion Prototype
2161+
2162+ # region Station
2163+
2164+ @cached_property
2165+ def station_levels_base_text_references (self ) -> list [ReferenceLayer ]:
2166+ layers : list [ReferenceLayer ] = []
2167+ if isinstance (self .layout , StationLayout ):
2168+ for details , level_group in zip (
2169+ self .layout .stations , self .station_level_groups
2170+ ):
2171+ if layer := get_reference_layer (
2172+ LAYER_NAMES .TEXT_REFERENCE_CREATURE
2173+ if "pt" in details
2174+ else LAYER_NAMES .TEXT_REFERENCE ,
2175+ level_group ,
2176+ ):
2177+ layers .append (layer )
2178+ return layers
2179+
2180+ def frame_layers_station (self ) -> None :
2181+ super ().frame_layers_station ()
2182+
2183+ for group_list in (self .station_requirement_groups , self .station_pt_groups ):
2184+ for group in group_list :
2185+ if layer := getLayer (LAYERS .PINLINES , group ):
2186+ self .generate_layer (group = layer , colors = self .pinlines_colors )
2187+
2188+ def layer_positioning_station (self ) -> None :
2189+ if not self .rules_text_font_size :
2190+ return super ().layer_positioning_station ()
2191+
2192+ # endregion Station
0 commit comments