From b725cc272488f8c7ef8d55d933b851c16e375835 Mon Sep 17 00:00:00 2001
From: arch1t3cht <arch1t3cht@gmail.com>
Date: Fri, 24 Nov 2023 01:52:17 +0100
Subject: [PATCH] Perspective, Resample: Move more logic to Perspective module

---
 DependencyControl.json        |  14 ++++-
 macros/arch.Resample.moon     | 100 ++++++++++------------------------
 modules/arch/Perspective.moon |  85 ++++++++++++++++++++++++++++-
 3 files changed, 127 insertions(+), 72 deletions(-)

diff --git a/DependencyControl.json b/DependencyControl.json
index 54d3eb8..a6e162c 100644
--- a/DependencyControl.json
+++ b/DependencyControl.json
@@ -230,7 +230,7 @@
               "moduleName": "arch.Perspective",
               "name": "Perspective",
               "url": "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts",
-              "version": "0.2.5"
+              "version": "1.0.0"
             }
           ]
         }
@@ -268,6 +268,12 @@
           "Add proper handling for drawings",
           "Bump Perspective dependency to fix resampling in cases involving 180 degree rotations",
           "Add warning messages for cases where the script is likely to break"
+        ],
+        "2.0.0": [
+          "Add support for nofax \\org mode"
+        ],
+        "2.1.0": [
+          "Move some more logic over to the Perspective module"
         ]
       }
     },
@@ -553,6 +559,12 @@
         ],
         "0.2.4": [
           "Fix the tagsFromQuad computation when 180 degree rotations are involved"
+        ],
+        "0.2.5": [
+          "Add support for a nofax org mode"
+        ],
+        "1.0.0": [
+          "Add prepareForPerspective function that wraps the remaining logic needed for general perspective computations with ASSFoundation"
         ]
       }
     }
diff --git a/macros/arch.Resample.moon b/macros/arch.Resample.moon
index a113c20..b31caaa 100644
--- a/macros/arch.Resample.moon
+++ b/macros/arch.Resample.moon
@@ -2,7 +2,7 @@ export script_name = "Resample Perspective"
 export script_description = "Apply after resampling a script in Aegisub to fix any lines with 3D rotations."
 export script_author = "arch1t3cht"
 export script_namespace = "arch.Resample"
-export script_version = "2.0.0"
+export script_version = "2.1.0"
 
 DependencyControl = require "l0.DependencyControl"
 dep = DependencyControl{
@@ -14,20 +14,16 @@ dep = DependencyControl{
          feed: "https://raw.githubusercontent.com/TypesettingTools/ASSFoundation/master/DependencyControl.json"},
         {"arch.Math", version: "0.1.8", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts",
          feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"},
-        {"arch.Perspective", version: "0.2.5", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts",
+        {"arch.Perspective", version: "1.0.0", url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts",
          feed: "https://raw.githubusercontent.com/TypesettingTools/arch1t3cht-Aegisub-Scripts/main/DependencyControl.json"},
     }
 }
 LineCollection, ASS, AMath, APersp = dep\requireModules!
 {:Matrix} = AMath
-{:transformPoints, :tagsFromQuad} = APersp
+{:relevantTags, :usedTags, :transformPoints, :tagsFromQuad, :prepareForPerspective} = APersp
 
 logger = dep\getLogger!
 
-alltags = {"shear_x", "shear_y", "scale_x", "scale_y", "angle", "angle_x", "angle_y", "origin", "position", "outline", "outline_x", "outline_y", "shadow", "shadow_x", "shadow_y"}
-usedtags = {"shear_x", "shear_y", "scale_x", "scale_y", "angle", "angle_x", "angle_y", "origin", "position", "outline_x", "outline_y", "shadow_x", "shadow_y"}
-
-
 resample = (ratiox, ratioy, orgmode, subs, sel) ->
     anamorphic = math.max(ratiox, ratioy) / math.min(ratiox, ratioy) > 1.01
 
@@ -38,73 +34,37 @@ resample = (ratiox, ratioy, orgmode, subs, sel) ->
         -- No perspective tags, we don't need to do anything
         return if not anamorphic and #data\getTags({"angle_x", "angle_y"}) == 0
 
-        tagvals = data\getEffectiveTags(-1, true, true, true).tags
+        tagvals, width, height, warnings = prepareForPerspective(ASS, data)
+
+        for warn in *warnings
+            switch warn[1]
+                when "multiple_tags"
+                    aegisub.log("Warning: Line #{line.humanizedNumber} has more than one #{warn[2]} tag! This might break resampling.\n") if warn[2] == "\\frx" or warn[2] == "\\fry"
+                when "transform"
+                    aegisub.log("Warning: Line #{line.humanizedNumber} contains a #{warn[2]} tag in a transform tag! This might break resampling.\n") if warn[2] == "\\frx" or warn[2] == "\\fry"
+
         return if not anamorphic and tagvals.angle_x.value == 0 and tagvals.angle_y.value == 0
 
-        width, height = 0, 0
-        has_text, has_drawing = false, false
-
-        data\callback (section) ->
-            if section.class == ASS.Section.Text
-                has_text = true
-                width, height = data\getTextExtents!
-            if section.class == ASS.Section.Drawing
-                has_drawing = true
-                bounds = section\getBounds!
-                width, height = bounds.w, bounds.h
-
-        if has_text and has_drawing
-            aegisub.log("Line #{line.humanizedNumber} has both text and drawings! Skipping.\n")
-            return
-
-        if has_text and (width == 0 or height == 0)
-            aegisub.log("Warning: Line #{line.humanizedNumber} has zero width or height!\n")
-
-        -- Width and height can be 0 for drawings
-        width = math.max(width, 0.01)
-        height = math.max(height, 0.01)
-
-        width /= (tagvals.scale_x.value / 100)
-        height /= (tagvals.scale_y.value / 100)
-
-        if data\getPosition().class == ASS.Tag.Move
-            aegisub.log("Line #{line.humanizedNumber} has \\move! Skipping.\n")
-            return
-
-        -- Do some checks for cases that break this script
-        -- These are a bit more aggressive than necessary (e.g. two tags of the same type in the same section will trigger this detection but not break resampling)
-        -- but I can't be bothered to be more exact. Users can run ASSWipe before resampling or something.
-        for tname in *alltags
-            if #data\getTags({tname}) >= 2
-                aegisub.log("Warning: Line #{line.humanizedNumber} has more than one #{ASS.tagMap[tname].overrideName} tag! This might break resampling.")
-
-        -- Assf doesn't support nested transforms so this code could be much simpler, but a) I only found that out after writing this and b) I guess I can
-        -- keep this code around in case it ever starts supporting them
-        checkTransformTags = (section, initial) ->
-            if not initial
-                for tname in *alltags
-                    if #section\getTags({tname}) >= 1
-                        aegisub.log("Warning: Line #{line.humanizedNumber} contains a #{ASS.tagMap[tname].overrideName} tag in a transform tag! This might break resampling.")
-
-            section\modTags {"transform"}, (tag) ->
-                checkTransformTags tag.tags, false
-                tag
-
-        checkTransformTags data, true
-
-        -- Manually enforce the relations between tags
-        if #data\getTags({"origin"}) == 0
-            tagvals.origin.x = tagvals.position.x
-            tagvals.origin.y = tagvals.position.y
-        for name in *{"outline", "shadow"}
-            for coord in *{"x", "y"}
-                cname = "#{name}_#{coord}"
-                if #data\getTags({cname}) == 0
-                    tagvals[cname].value = tagvals[name].value
+        for warn in *warnings
+            switch warn[1]
+                when "multiple_tags"
+                    aegisub.log("Warning: Line #{line.humanizedNumber} has more than one #{warn[2]} tag! This might break resampling.\n")
+                when "transform"
+                    aegisub.log("Warning: Line #{line.humanizedNumber} contains a #{warn[2]} tag in a transform tag! This might break resampling.\n")
+                when "zero_size"
+                    aegisub.log("Warning: Line #{line.humanizedNumber} has zero width or height!\n")
+                when "move"
+                    aegisub.log("Line #{line.humanizedNumber} has \\move! Skipping.\n")
+                    return
+                when "text_and_drawings"
+                    aegisub.log("Line #{line.humanizedNumber} has both text and drawings! Skipping.\n")
+                    return
+                else
+                    aegisub.log("Unknown warning on line #{line.humanizedNumber}: #{warn[1]}\n")
 
         -- Set up the tags
-        data\removeTags alltags
-        data\insertTags [ tagvals[k] for k in *usedtags ]
+        data\removeTags relevantTags
+        data\insertTags [ tagvals[k] for k in *usedTags ]
 
         -- Revert Aegisub's resampling.
         for tag in *{"position", "origin"}
diff --git a/modules/arch/Perspective.moon b/modules/arch/Perspective.moon
index 154e964..9042eb8 100644
--- a/modules/arch/Perspective.moon
+++ b/modules/arch/Perspective.moon
@@ -5,7 +5,7 @@ local amath
 if haveDepCtrl
     depctrl = DependencyControl {
         name: "Perspective",
-        version: "0.2.5",
+        version: "1.0.0",
         description: [[Math functions for dealing with perspective transformations.]],
         author: "arch1t3cht",
         url: "https://github.com/TypesettingTools/arch1t3cht-Aegisub-Scripts",
@@ -118,6 +118,86 @@ screen_z = 312.5
 an_xshift = { 0, 0.5, 1, 0, 0.5, 1, 0, 0.5, 1 }
 an_yshift = { 1, 1, 1, 0.5, 0.5, 0.5, 0, 0, 0 }
 
+
+-- List of tags that affect perspective
+relevantTags = {"fontsize", "shear_x", "shear_y", "scale_x", "scale_y", "angle", "angle_x", "angle_y", "origin", "position", "outline", "outline_x", "outline_y", "shadow", "shadow_x", "shadow_y"}
+
+-- List of tags that are used in perspective. This is the same list as relevant_tags except for outline and shadow, since the single-coordinate versions of those tags are used instead
+usedTags = {"fontsize", "shear_x", "shear_y", "scale_x", "scale_y", "angle", "angle_x", "angle_y", "origin", "position", "outline_x", "outline_y", "shadow_x", "shadow_y"}
+
+-- Takes an ASSFoundation LineContents object and returns its effective tags, but preprocessed
+-- for perspective handling. This includes inforcing the relations between \org and \pos as
+-- well as those between the various transform tags, as well as warning when the line has tags
+-- that would break perspective calulations (\move, certain \t transformations, multiple tag sections, etc).
+-- Also needs the ASSFoundation library to be given in its first parameter. This is to prevent this library
+-- from depending on ASSFoundation.
+-- Returns:
+--   - the effective tags table
+--   - the line's width (not scaled with \fscx)
+--   - the line's height (not scaled with \fscy)
+--   - a table of warnings about possibly problematic tags
+--        -> each warning is of the form {warning, details} where details may be nil depending on the warning.
+prepareForPerspective = (ASS, data) ->
+    tagvals = data\getEffectiveTags(-1, true, true, true).tags
+    return if not anamorphic and tagvals.angle_x.value == 0 and tagvals.angle_y.value == 0
+
+    width, height = 0, 0
+    has_text, has_drawing = false, false
+
+    data\callback (section) ->
+        if section.class == ASS.Section.Text
+            has_text = true
+            width, height = data\getTextExtents!
+        if section.class == ASS.Section.Drawing
+            has_drawing = true
+            bounds = section\getBounds!
+            width, height = bounds.w, bounds.h
+
+    warnings = {}
+
+    table.insert(warnings, {"text_and_drawings"}) if has_text and has_drawing
+    table.insert(warnings, {"zero_size"}) if has_text and (width == 0 or height == 0)
+    table.insert(warnings, {"move"}) if data\getPosition().class == ASS.Tag.Move
+
+    -- Width and height can be 0 for drawings
+    width = math.max(width, 0.01)
+    height = math.max(height, 0.01)
+
+    width /= (tagvals.scale_x.value / 100)
+    height /= (tagvals.scale_y.value / 100)
+
+    -- Do some checks for cases that break this script
+    -- These are a bit more aggressive than necessary (e.g. two tags of the same type in the same section will trigger this detection but not break resampling)
+    -- but I can't be bothered to be more exact. Users can run ASSWipe before resampling or something.
+    for tname in *relevantTags
+        table.insert(warnings, {"multiple_tags", ASS.tagMap[tname].overrideName}) if #data\getTags({tname}) >= 2
+
+    -- Assf doesn't support nested transforms so this code could be much simpler, but a) I only found that out after writing this and b) I guess I can
+    -- keep this code around in case it ever starts supporting them
+    checkTransformTags = (section, initial) ->
+        if not initial
+            for tname in *relevantTags
+                table.insert(warnings, {"transform", ASS.tagMap[tname].overrideName}) if #section\getTags({tname}) >= 1
+
+        section\modTags {"transform"}, (tag) ->
+            checkTransformTags tag.tags, false
+            tag
+
+    checkTransformTags data, true
+
+    -- Manually enforce the relations between tags
+    if #data\getTags({"origin"}) == 0
+        tagvals.origin.x = tagvals.position.x
+        tagvals.origin.y = tagvals.position.y
+    for name in *{"outline", "shadow"}
+        for coord in *{"x", "y"}
+            cname = "#{name}_#{coord}"
+            if #data\getTags({cname}) == 0
+                tagvals[cname].value = tagvals[name].value
+
+    return tagvals, width, height, warnings
+
+
 -- Transforms the given list of points in a relative coordinate system according to the given .ass tags.
 -- If no list of points is given, a rectangle with the given dimensions is used.
 -- The width and height parameters should contain the raw dimensions of the line to be transformed. These are used for alignment.
@@ -293,6 +373,9 @@ tagsFromQuad = (t, quad, width, height, orgMode=0) ->
 
 lib = {
     :Quad,
+    :relevantTags,
+    :usedTags,
+    :prepareForPerspective
     :transformPoints,
     :tagsFromQuad,
 }