Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BREAKING] position flavour text divider #37

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ Trademark and copyright Wizards of the Coast 2022. Templates for this project in

# Scope
* Modern style cards, normal and extended; transform and mdfc, front and back; basic lands, normal, Theros, and Unstable styles; planeswalkers, normal and extended; mutate, adventure, miracle, and snow cards; various flavours of fancy frames - stargazing, universes beyond, masterpiece, ZNE expedition, and womensday; planar cards, tokens, and basic lands.
* The flavour text divider is automatically positioned.
* Leveler and saga cards require manual intervention to position text layers, but are automated up until that point.
* Planeswalkers also require manual intervention to position text layers and the ragged textbox divider, but are automated up until that point.
* Flavour text divider is not supported, as rules text & flavour text are formatted in the same text layer, and it would be impractical to position the flavour text divider programmatically with Adobe's JavaScript library.

# Customisation
The repo includes a set of helper functions and boilerplate classes which make automating any given template straight-forward. You'll need reference layers (check out my templates on google drive for examples) for artwork to be positioned against, and for any text layers that need to be positioned vertically within a textbox. Check out the comments at the top of `templates.jsx` for more info, and you can review how I've automated my templates there for reference as well. You'll also need to adjust the function `select_template()` in `render.jsx` to point to your template class(es).
3 changes: 2 additions & 1 deletion scripts/constants.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ var LayerNames = {
RULES_TEXT_CREATURE_FLIP: "Rules Text - Creature Flip",
RULES_TEXT_ADVENTURE: "Rules Text - Adventure",
MUTATE: "Mutate",
DIVIDER: "Divider",

// planar text and icons
STATIC_ABILITY: "Static Ability",
Expand Down Expand Up @@ -185,7 +186,7 @@ var font_name_ndpmtg = "NDPMTG";
// Font spacing
var modal_indent = 5.7;
var line_break_lead = 2.4;
var flavour_text_lead = 4.4;
var flavour_text_lead = 7; // 4.4 without flavour text divider, 7 with

// Symbol colours
var rgb_c = new SolidColor();
Expand Down
10 changes: 10 additions & 0 deletions scripts/templates.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ var NormalTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = is_centred,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
pt_reference_layer = text_and_icons.layers.getByName(LayerNames.PT_REFERENCE),
pt_top_reference_layer = text_and_icons.layers.getByName(LayerNames.PT_TOP_REFERENCE),
),
Expand All @@ -301,6 +302,7 @@ var NormalTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = is_centred,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
);

Expand Down Expand Up @@ -411,6 +413,7 @@ var NormalClassicTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = is_centred,
reference_layer = reference_layer,
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
);

Expand Down Expand Up @@ -613,6 +616,7 @@ var ExpeditionTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = false,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
);
},
Expand Down Expand Up @@ -673,6 +677,7 @@ var MiracleTemplate = new Class({
flavour_text = this.layout.flavour_text,
is_centred = false,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
);
},
Expand Down Expand Up @@ -778,6 +783,7 @@ var TransformFrontTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = is_centred,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
pt_reference_layer = text_and_icons.layers.getByName(LayerNames.PT_REFERENCE),
pt_top_reference_layer = text_and_icons.layers.getByName(LayerNames.PT_TOP_REFERENCE),
),
Expand All @@ -799,6 +805,7 @@ var TransformFrontTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = is_centred,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
);

Expand Down Expand Up @@ -850,6 +857,7 @@ var IxalanTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = false,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
);
},
Expand Down Expand Up @@ -942,6 +950,7 @@ var MutateTemplate = Class({
flavour_text = this.layout.flavour_text,
is_centred = false,
reference_layer = text_and_icons.layers.getByName(LayerNames.MUTATE_REFERENCE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
)
);
}
Expand Down Expand Up @@ -987,6 +996,7 @@ var AdventureTemplate = Class({
flavour_text = "",
is_centred = false,
reference_layer = text_and_icons.layers.getByName(LayerNames.TEXTBOX_REFERENCE_ADVENTURE),
divider_layer = text_and_icons.layers.getByName(LayerNames.DIVIDER),
),
new TextField(
layer = type_line,
Expand Down
95 changes: 85 additions & 10 deletions scripts/text_layers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function vertically_align_text(layer, reference_layer) {
layer_copy.rasterize(RasterizeType.TEXTCONTENTS);
select_layer_pixels(reference_layer);
app.activeDocument.activeLayer = layer_copy;
align_vertical(layer_copy);
align_vertical();
clear_selection();
var layer_dimensions = compute_text_layer_bounds(layer);
var layer_copy_dimensions = compute_text_layer_bounds(layer_copy);
Expand All @@ -78,6 +78,7 @@ function vertically_align_text(layer, reference_layer) {
function vertically_nudge_creature_text(layer, reference_layer, top_reference_layer) {
/**
* Vertically nudge a creature's text layer if it overlaps with the power/toughness box, determined by the given reference layers.
* Returns the amount by which the text layer was nudged.
*/

// if the layer needs to be nudged
Expand Down Expand Up @@ -121,6 +122,8 @@ function vertically_nudge_creature_text(layer, reference_layer, top_reference_la
rasterised_copy.remove();
extra_bit_layer.remove();
clear_selection();

return delta;
}
}

Expand Down Expand Up @@ -232,12 +235,15 @@ var FormattedTextField = Class({
}
this.is_centred = is_centred;
},
execute: function () {
this.super();
determine_italics_text_and_flavour_index: function() {
/**
* Returns an array of italic text for the instance's text contents and the index at which flavour text begins.
* Within flavour text, any text between asterisks should not be italicised, and the asterisks should not be included
* in the rendered flavour text.
*/

// generate italic text arrays from things in (parentheses), ability words, and the given flavour text
var italic_text = generate_italics(this.text_contents);
var flavour_index = -1;
var italic_text = generate_italics(this.text_contents);
var flavour_index = -1;

if (this.flavour_text.length > 1) {
// remove things between asterisks from flavour text if necessary
Expand All @@ -256,6 +262,18 @@ var FormattedTextField = Class({
}
flavour_index = this.text_contents.length;
}
return {
flavour_index: flavour_index,
italic_text: italic_text,
}
},
execute: function () {
this.super();

// generate italic text arrays from things in (parentheses), ability words, and the given flavour text
var ret = this.determine_italics_text_and_flavour_index();
var flavour_index = ret.flavour_index;
var italic_text = ret.italic_text;

app.activeDocument.activeLayer = this.layer;
format_text(this.text_contents + "\r" + this.flavour_text, italic_text, flavour_index, this.is_centred);
Expand All @@ -270,12 +288,64 @@ var FormattedTextArea = Class({
* A FormattedTextField where the text is required to fit within a given area. An instance of this class will step down the font size
* until the text fits within the reference layer's bounds (in 0.25 pt increments), then rasterise the text layer, and centre it vertically
* with respect to the reference layer's pixels.
* Positions the flavour text divider after sizing the text to the given area.
*/

extends_: FormattedTextField,
constructor: function (layer, text_contents, text_colour, flavour_text, is_centred, reference_layer) {
constructor: function (layer, text_contents, text_colour, flavour_text, is_centred, reference_layer, divider_layer) {
this.super(layer, text_contents, text_colour, flavour_text, is_centred);
this.reference_layer = reference_layer;
this.divider_layer = divider_layer;
},
insert_divider: function() {
/**
* Inserts the flavour text divider between rules text and flavour text.
* The position of the divider is calculated by creating two copies of `this.layer`, rendering `this.text_contents` in the first copy
* and `this.flavour_text` in the second copy, then finding the midpoint between the bottom of the first copy and the top of the second copy.
* This method is slighty imperfect because of how the shape of the text layer can affect how flavour text is positioned, but should be close enough.
*/

if (this.flavour_text.length !== "") {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to test either for length if (this.flavour_text.length) or for flavour text not being empty string if (this.flavour_text !== "". As it stands now script seems to fail for cards with no flavour text.

Great work with this PR though 💪 super useful 🙏

var ret = this.determine_italics_text_and_flavour_index();
var flavour_index = ret.flavour_index;
var italic_text = ret.italic_text;

var layer_bounds = compute_text_layer_bounds(this.layer);

var layer_text_contents = this.layer.duplicate();
layer_text_contents.textItem.contents = this.text_contents;
layer_text_contents.textItem.spaceBefore = new UnitValue(line_break_lead, "px");
app.activeDocument.activeLayer = layer_text_contents;
format_text(this.text_contents, italic_text, -1, this.is_centred);
layer_text_contents.rasterize(RasterizeType.ENTIRELAYER);
text_contents_bottom = layer_text_contents.bounds[3].as("px");

var layer_flavour_text = this.layer.duplicate();
layer_flavour_text.textItem.contents = this.flavour_text;
layer_flavour_text.textItem.spaceBefore = new UnitValue(line_break_lead, "px");
app.activeDocument.activeLayer = layer_flavour_text;
format_text(this.flavour_text, italic_text, flavour_index, this.is_centred);
layer_flavour_text.rasterize(RasterizeType.ENTIRELAYER);
layer_flavour_text.translate(0, layer_bounds[3].as("px") - layer_flavour_text.bounds[3].as("px"));
var flavour_text_top = layer_flavour_text.bounds[1].as("px");

var divider_y_midpoint = (text_contents_bottom + flavour_text_top) / 2;

layer_text_contents.remove();
layer_flavour_text.remove();

app.activeDocument.activeLayer = this.divider_layer;
this.divider_layer.visible = true;

app.activeDocument.selection.select([
[0, divider_y_midpoint - 1],
[1, divider_y_midpoint - 1],
[1, divider_y_midpoint + 1],
[0, divider_y_midpoint + 1]
]);
align_vertical();
clear_selection();
}
},
execute: function () {
this.super();
Expand All @@ -286,6 +356,8 @@ var FormattedTextArea = Class({

// centre vertically
vertically_align_text(this.layer, this.reference_layer);

this.insert_divider();
}
}
});
Expand All @@ -298,15 +370,18 @@ var CreatureFormattedTextArea = Class({
*/

extends_: FormattedTextArea,
constructor: function (layer, text_contents, text_colour, flavour_text, is_centred, reference_layer, pt_reference_layer, pt_top_reference_layer) {
this.super(layer, text_contents, text_colour, flavour_text, is_centred, reference_layer);
constructor: function (layer, text_contents, text_colour, flavour_text, is_centred, reference_layer, divider_layer, pt_reference_layer, pt_top_reference_layer) {
this.super(layer, text_contents, text_colour, flavour_text, is_centred, reference_layer, divider_layer);
this.pt_reference_layer = pt_reference_layer;
this.pt_top_reference_layer = pt_top_reference_layer;
},
execute: function () {
this.super();

// shift vertically if the text overlaps the PT box
vertically_nudge_creature_text(this.layer, this.pt_reference_layer, this.pt_top_reference_layer);
var delta = vertically_nudge_creature_text(this.layer, this.pt_reference_layer, this.pt_top_reference_layer);
if (delta < 0) {
this.divider_layer.translate(0, new UnitValue(delta, "px"));
}
}
})