Skip to content

Commit d2b66b5

Browse files
committed
Fix text formatting and positioning issues in Borderless Showcase template
1 parent ad7ece2 commit d2b66b5

5 files changed

Lines changed: 142 additions & 41 deletions

File tree

py/borderless_showcase.py

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88
from photoshop.api._layerSet import LayerSet
99
from photoshop.api.enumerations import ElementPlacement
1010

11-
from plugins.proxy_stuff.py.utils.colors import (
12-
create_gradient_config,
13-
create_gradient_location_map,
14-
)
1511
from src import CFG
1612
from src.cards import strip_reminder_text
1713
from src.enums.layers import LAYERS
@@ -71,6 +67,10 @@
7167
is_color_identity,
7268
parse_hex_color_list,
7369
)
70+
from .utils.colors import (
71+
create_gradient_config,
72+
create_gradient_location_map,
73+
)
7474
from .utils.layer import get_layer_dimensions_via_rasterization
7575
from .utils.layer_fx import get_stroke_details
7676
from .utils.path import check_layer_overlap_with_shape, create_shape_layer
@@ -600,6 +600,51 @@ def pt_reference(self) -> ReferenceLayer | None:
600600
):
601601
refs.append(layer)
602602

603+
# Reserve space for flipide PT text
604+
if self.text_layer_flipside_pt:
605+
# Format flipside PT text
606+
for idx, txt in enumerate(reversed(self.text)):
607+
if txt.layer is self.text_layer_flipside_pt:
608+
self.text.pop(idx)
609+
if txt.validate():
610+
txt.execute()
611+
612+
# Create a shape that encompasses flipside PT text
613+
flipside_ref_dims = get_layer_dimensions(layer)
614+
flipside_pt_dims = get_layer_dimensions_via_rasterization(
615+
self.text_layer_flipside_pt
616+
)
617+
rules_stroke_size = (
618+
(get_stroke_details(self.text_layer_rules_base) or {}).get(
619+
"size", 0
620+
)
621+
if self.text_layer_rules_base
622+
else 0
623+
)
624+
padded_left = flipside_pt_dims["left"] - rules_stroke_size
625+
padded_top = flipside_pt_dims["top"] - rules_stroke_size
626+
refs.append(
627+
create_shape_layer(
628+
(
629+
{
630+
"x": padded_left,
631+
"y": padded_top,
632+
},
633+
{"x": flipside_ref_dims["right"], "y": padded_top},
634+
{
635+
"x": flipside_ref_dims["right"],
636+
"y": flipside_ref_dims["bottom"],
637+
},
638+
{
639+
"x": padded_left,
640+
"y": flipside_ref_dims["bottom"],
641+
},
642+
),
643+
relative_layer=layer,
644+
placement=ElementPlacement.PlaceAfter,
645+
)
646+
)
647+
603648
if refs:
604649
if len(refs) > 1:
605650
merged = merge_shapes(*refs, operation=ShapeOperation.Unite)
@@ -881,6 +926,10 @@ def textbox_reference_adventure(self) -> ArtLayer | None:
881926
)
882927
)
883928

929+
@cached_property
930+
def textless_bottom_reference_layer(self) -> ReferenceLayer | None:
931+
return get_reference_layer(LAYERS.TEXTLESS, self.textbox_reference_group)
932+
884933
# endregion Reference Layers
885934

886935
# region Shapes
@@ -1268,7 +1317,7 @@ def format_temp_rules_text(
12681317
text_field = FormattedTextField(
12691318
layer=layer,
12701319
contents=oracle_text,
1271-
flavor=flavor_text or "",
1320+
flavor=flavor_text,
12721321
divider=divider_layer,
12731322
)
12741323
if not text_field.validate():
@@ -1316,6 +1365,12 @@ def adjust_textbox_for_font_size(
13161365
vertical_padding: tuple[float | int, float | int] | None = None,
13171366
) -> tuple[ArtLayer, ReferenceLayer] | None:
13181367
"""Calculates the required size for rules textbox when the rules text has a fixed font size."""
1368+
if not oracle_text + (flavor_text or ""):
1369+
return (
1370+
base_text_layer,
1371+
self.textless_bottom_reference_layer or base_textbox_reference,
1372+
)
1373+
13191374
min_top = min_top if min_top is not None else self.doc_height
13201375
vertical_padding = (
13211376
vertical_padding
@@ -1485,7 +1540,7 @@ def adjust_textbox_for_font_size(
14851540
shaped_text
14861541
)
14871542

1488-
top = dims_text_ref_shape["top"] - delta - vertical_padding[1]
1543+
top = dims_text_ref_shape["top"] + delta - vertical_padding[1]
14891544

14901545
shaped_text.remove()
14911546
shaped_text = self.create_offset_text_shape(
@@ -1601,8 +1656,12 @@ def adjust_textboxes_for_font_size(
16011656
# First pass of sizing
16021657
for idx, arg in enumerate(textbox_args_sorted):
16031658
height_padding = arg.get("height_padding", 0) or 0
1659+
arg["base_text_layer"].visible = False
1660+
base_text_layer_copy = arg["base_text_layer"].duplicate(
1661+
arg["base_text_layer"], ElementPlacement.PlaceBefore
1662+
)
16041663
sized = self.adjust_textbox_for_font_size(
1605-
base_text_layer=arg["base_text_layer"],
1664+
base_text_layer=base_text_layer_copy,
16061665
base_textbox_reference=arg["base_textbox_reference"],
16071666
base_text_wrap_reference=arg["base_text_wrap_reference"],
16081667
divider_layer=arg["divider_layer"],
@@ -1619,6 +1678,7 @@ def adjust_textboxes_for_font_size(
16191678
# other layer seems to fix it without causing side effects.
16201679
if self.art_layer:
16211680
select_layer(self.art_layer)
1681+
base_text_layer_copy.remove()
16221682
if sized:
16231683
sizes.append(sized)
16241684
if (
@@ -1646,8 +1706,11 @@ def adjust_textboxes_for_font_size(
16461706
layer.remove()
16471707
ref.remove()
16481708
min_top = tallest_top + height_padding
1709+
base_text_layer_copy = arg["base_text_layer"].duplicate(
1710+
arg["base_text_layer"], ElementPlacement.PlaceBefore
1711+
)
16491712
sized = self.adjust_textbox_for_font_size(
1650-
base_text_layer=arg["base_text_layer"],
1713+
base_text_layer=base_text_layer_copy,
16511714
base_textbox_reference=arg["base_textbox_reference"],
16521715
base_text_wrap_reference=arg["base_text_wrap_reference"],
16531716
divider_layer=arg["divider_layer"],
@@ -1656,6 +1719,10 @@ def adjust_textboxes_for_font_size(
16561719
min_top=min_top,
16571720
align_to=min_top + (tallest_height - height_padding) / 2,
16581721
)
1722+
# Same reference layer turning visible bug as above.
1723+
if self.art_layer:
1724+
select_layer(self.art_layer)
1725+
base_text_layer_copy.remove()
16591726
if sized:
16601727
sizes[orig_idx] = sized
16611728
else:
@@ -1672,15 +1739,20 @@ def disable_text_area_scaling(self, text_area: FormattedTextArea) -> None:
16721739
text_area.fix_overflow_width = False
16731740

16741741
def rules_text_and_pt_layers(self) -> None:
1675-
if self.is_split and self.rules_text_font_size:
1676-
# Split cards don't have PT text and rules text is already adjusted elsewhere
1677-
return
1742+
if self.is_split:
1743+
if self.rules_text_font_size:
1744+
# Split cards don't have PT text and rules text is already adjusted elsewhere
1745+
return
1746+
return super().rules_text_and_pt_layers()
16781747

16791748
if self.is_planeswalker:
16801749
if self.text_layer_rules_base:
16811750
self.text_layer_rules_base.visible = False
16821751
return
16831752

1753+
# Ensure that sizing logic associated with fixed font size runs before
1754+
# accessing the rules text layer
1755+
self.textbox_reference
16841756
super(BorderlessVectorTemplate, self).rules_text_and_pt_layers()
16851757

16861758
if self.supports_dynamic_textbox_height:
@@ -1741,7 +1813,7 @@ def textbox_positioning(self) -> None:
17411813
ref.dims["top"] -= self.rules_text_padding / 2
17421814
else:
17431815
ref = (
1744-
get_reference_layer(LAYERS.TEXTLESS, self.textbox_reference_group)
1816+
self.textless_bottom_reference_layer
17451817
if self.is_vertical_layout
17461818
else self.textbox_reference
17471819
)
@@ -1923,7 +1995,9 @@ def match_adventure_font_sizes(self) -> None:
19231995
},
19241996
)
19251997
if text_area.validate():
1926-
text_area.execute()
1998+
# It's enough to reposition the text since it's already formatted.
1999+
# Reformatting actually breaks the styling of the text.
2000+
text_area.position_within_reference()
19272001
break
19282002

19292003
# endregion Adventure

py/utils/layer.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from contextlib import AbstractContextManager
2+
from types import TracebackType
3+
14
from photoshop.api import ElementPlacement, RasterizeType
25
from photoshop.api._artlayer import ArtLayer
36
from photoshop.api._layerSet import LayerSet
@@ -24,3 +27,21 @@ def get_layer_dimensions_via_rasterization(
2427
dims = get_layer_dimensions(layer_copy)
2528
layer_copy.remove()
2629
return dims
30+
31+
32+
class LayerVisibleContext(AbstractContextManager[None]):
33+
def __init__(self, layer: ArtLayer | LayerSet) -> None:
34+
self._layer = layer
35+
self._initial_visibility: bool
36+
37+
def __enter__(self) -> None:
38+
self._initial_visibility = self._layer.visible
39+
self._layer.visible = False
40+
41+
def __exit__(
42+
self,
43+
exc_type: type[BaseException] | None,
44+
exc_val: BaseException | None,
45+
exc_tb: TracebackType | None,
46+
) -> None:
47+
self._layer.visible = self._initial_visibility

py/utils/layer_fx.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,33 @@
66

77
from src import APP
88

9+
from .layer import LayerVisibleContext
10+
911

1012
class StrokeDetails(TypedDict):
1113
size: int
1214

1315

1416
def get_stroke_details(layer: ArtLayer | LayerSet) -> StrokeDetails | None:
15-
APP.instance.activeDocument.activeLayer = layer
17+
with LayerVisibleContext(layer):
18+
APP.instance.activeDocument.activeLayer = layer
1619

17-
ref = ActionReference()
18-
ref.putEnumerated(
19-
APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt")
20-
)
21-
desc: ActionDescriptor = APP.instance.executeActionGet(ref)
20+
ref = ActionReference()
21+
ref.putEnumerated(
22+
APP.instance.cID("Lyr "), APP.instance.cID("Ordn"), APP.instance.cID("Trgt")
23+
)
24+
desc: ActionDescriptor = APP.instance.executeActionGet(ref)
2225

23-
layer_effects_id = APP.instance.sID("layerEffects")
24-
if not desc.hasKey(layer_effects_id):
25-
return
26+
layer_effects_id = APP.instance.sID("layerEffects")
27+
if not desc.hasKey(layer_effects_id):
28+
return
2629

27-
layer_effects: ActionDescriptor = desc.getObjectValue(layer_effects_id)
30+
layer_effects: ActionDescriptor = desc.getObjectValue(layer_effects_id)
2831

29-
frame_fx_id = APP.instance.sID("frameFX")
30-
if not layer_effects.hasKey(frame_fx_id):
31-
return
32+
frame_fx_id = APP.instance.sID("frameFX")
33+
if not layer_effects.hasKey(frame_fx_id):
34+
return
3235

33-
frame_fx: ActionDescriptor = layer_effects.getObjectValue(frame_fx_id)
36+
frame_fx: ActionDescriptor = layer_effects.getObjectValue(frame_fx_id)
3437

35-
return {"size": frame_fx.getInteger(APP.instance.sID("size"))}
38+
return {"size": frame_fx.getInteger(APP.instance.sID("size"))}

py/utils/path.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@
1212
from src.helpers.layers import select_layer, select_layers
1313
from src.helpers.selection import (
1414
check_selection_bounds,
15-
select_bounds,
15+
select_layer_pixels,
1616
select_overlapping,
1717
)
1818
from src.schema.colors import ColorObject
1919

2020
from ..uxp.path import PathPointConf, create_path
21-
from .layer_fx import get_stroke_details
2221

2322

2423
def get_layer_path(layer: ArtLayer) -> tuple[Any, bool]:
@@ -156,17 +155,11 @@ def subtract_front_shape(shape_1: ArtLayer, shape_2: ArtLayer) -> ArtLayer:
156155
return active_layer
157156

158157

159-
def check_layer_overlap_with_shape(layer: ArtLayer, ref: ArtLayer):
158+
def check_layer_overlap_with_shape(layer: ArtLayer, ref: ArtLayer) -> float:
160159
selection = APP.instance.activeDocument.selection
161-
dims = get_shape_dimensions(ref)
162-
ref_bounds = (dims["left"], dims["top"], dims["right"], dims["bottom"])
163-
select_bounds(ref_bounds, selection=selection)
164-
165-
if details := get_stroke_details(layer):
166-
selection.expand(details["size"])
167-
160+
select_layer_pixels(ref)
168161
select_overlapping(layer)
169162
if bounds := check_selection_bounds(selection):
170163
selection.deselect()
171-
return ref_bounds[1] - bounds[3]
164+
return get_shape_dimensions(ref)["top"] - bounds[3]
172165
return 0

py/uxp/text.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ def create_text_layer_with_path(
219219
color = kwargs.get("color", ref_text.color)
220220
size = kwargs.get("size", ref_text.size)
221221
leading = kwargs.get("leading", ref_text.leading)
222+
try:
223+
space_after = ref_text.spaceAfter
224+
space_before = ref_text.spaceBefore
225+
except COMError:
226+
# Hacky workaround to a value retrieval issue with an unknown cause.
227+
# This was observed on a duplicated text layer when accessing spaceAfter.
228+
# https://community.adobe.com/t5/photoshop-ecosystem-discussions/textitem-color-returns-quot-general-photoshop-error-quot/m-p/10900372#M303432
229+
ref_text.contents = ref_text.contents
230+
space_after = ref_text.spaceAfter
231+
space_before = ref_text.spaceBefore
222232
desc: MakeTextLayerActionDescriptor = {
223233
"_obj": "make",
224234
"_target": [{"_ref": "textLayer"}],
@@ -271,11 +281,11 @@ def create_text_layer_with_path(
271281
"_obj": "paragraphStyle",
272282
"spaceBefore": {
273283
"_unit": "pointsUnit",
274-
"_value": float(ref_text.spaceBefore),
284+
"_value": float(space_before),
275285
},
276286
"spaceAfter": {
277287
"_unit": "pointsUnit",
278-
"_value": float(ref_text.spaceAfter),
288+
"_value": float(space_after),
279289
},
280290
},
281291
}

0 commit comments

Comments
 (0)