#!/usr/pkg/bin/python3.12
# coding=utf-8
#
# Authors:
#   Nicolas Dufour - Association Inkscape-fr
#   Aurelio A. Heckert <aurium(a)gmail.com>
#
# Copyright (C) 2008 Authors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
"""
This extension allows you to draw crop, registration and other
printing marks in Inkscape.
"""

import math
import inkex
from inkex import Circle, Rectangle, TextElement


class PrintingMarks(inkex.EffectExtension):
    # Default parameters
    stroke_width = 0.25

    def add_arguments(self, pars):
        pars.add_argument("--where", help="Apply crop marks to...", default="canvas")
        pars.add_argument(
            "--crop_marks", type=inkex.Boolean, default=True, help="Draw crop Marks"
        )
        pars.add_argument("--bleed_marks", type=inkex.Boolean, help="Draw Bleed Marks")
        pars.add_argument(
            "--registration_marks",
            type=inkex.Boolean,
            dest="reg_marks",
            default=True,
            help="Draw Registration Marks?",
        )
        pars.add_argument("--star_target", type=inkex.Boolean, help="Draw Star Target?")
        pars.add_argument(
            "--colour_bars", type=inkex.Boolean, help="Draw Colour Bars?", default=True
        )
        pars.add_argument(
            "--page_info", type=inkex.Boolean, help="Draw Page Information?"
        )
        pars.add_argument("--unit", default="px", help="Draw measurement")
        pars.add_argument("--crop_offset", type=float, default=5.0, help="Offset")
        pars.add_argument("--bleed_top", type=float, default=5.0, help="Bleed Top Size")
        pars.add_argument(
            "--bleed_bottom", type=float, default=5.0, help="Bleed Bottom Size"
        )
        pars.add_argument(
            "--bleed_left", type=float, default=5.0, help="Bleed Left Size"
        )
        pars.add_argument(
            "--bleed_right", type=float, default=5.0, help="Bleed Right Size"
        )
        pars.add_argument("--tab", help="The selected UI-tab when OK was pressed")

    def draw_crop_line(self, x1, y1, x2, y2, name, parent):
        style = {
            "stroke": "#000000",
            "stroke-width": str(self.stroke_width),
            "fill": "none",
        }
        line_attribs = {
            "style": str(inkex.Style(style)),
            "id": name,
            "d": "M " + str(x1) + "," + str(y1) + " L " + str(x2) + "," + str(y2),
        }
        parent.add(inkex.PathElement(**line_attribs))

    def draw_bleed_line(self, x1, y1, x2, y2, name, parent):
        style = {
            "stroke": "#000000",
            "stroke-width": str(self.stroke_width),
            "fill": "none",
            "stroke-miterlimit": "4",
            "stroke-dasharray": "4, 2, 1, 2",
            "stroke-dashoffset": "0",
        }
        line_attribs = {
            "style": str(inkex.Style(style)),
            "id": name,
            "d": "M " + str(x1) + "," + str(y1) + " L " + str(x2) + "," + str(y2),
        }
        parent.add(inkex.PathElement(**line_attribs))

    def draw_reg_circles(self, cx, cy, r, name, colours, parent):
        for i in range(len(colours)):
            style = {
                "stroke": colours[i],
                "stroke-width": str(r / len(colours)),
                "fill": "none",
            }
            circle_attribs = {
                "style": str(inkex.Style(style)),
                "inkscape:label": name,
                "cx": str(cx),
                "cy": str(cy),
                "r": str((r / len(colours)) * (i + 0.5)),
            }
            parent.add(Circle(**circle_attribs))

    def draw_reg_marks(self, cx, cy, rotate, name, parent):
        colours = ["#000000", "#00ffff", "#ff00ff", "#ffff00", "#000000"]
        g = parent.add(inkex.Group(id=name))
        for i in range(len(colours)):
            style = {"fill": colours[i], "fill-opacity": "1", "stroke": "none"}
            r = self.mark_size / 2
            step = r
            stroke = r / len(colours)
            regoffset = stroke * i
            regmark_attribs = {
                "style": str(inkex.Style(style)),
                "d": "m"
                + " "
                + str(-regoffset)
                + ","
                + str(r)
                + " "
                + str(-stroke)
                + ",0"
                + " "
                + str(step)
                + ","
                + str(-r)
                + " "
                + str(-step)
                + ","
                + str(-r)
                + " "
                + str(stroke)
                + ",0"
                + " "
                + str(step)
                + ","
                + str(r)
                + " "
                + str(-step)
                + ","
                + str(r)
                + " z",
                "transform": "translate("
                + str(cx)
                + ","
                + str(cy)
                + ") rotate("
                + str(rotate)
                + ")",
            }
            g.add(inkex.PathElement(**regmark_attribs))

    def draw_star_target(self, cx, cy, name, parent):
        r = self.mark_size / 2
        style = {
            "fill": "#000 device-cmyk(1,1,1,1)",
            "fill-opacity": "1",
            "stroke": "none",
        }
        d = " M 0,0"
        i = 0
        while i < (2 * math.pi):
            i += math.pi / 16
            d += (
                " L 0,0 "
                + " L "
                + str(math.sin(i) * r)
                + ","
                + str(math.cos(i) * r)
                + " L "
                + str(math.sin(i + 0.09) * r)
                + ","
                + str(math.cos(i + 0.09) * r)
            )
        elem = parent.add(inkex.PathElement())
        elem.label = name
        elem.transform.add_translate(cx, cy)
        elem.path = d
        elem.style = inkex.Style(style)

    def draw_coluor_bars(self, cx, cy, rotate, name, parent, bbox):
        group = parent.add(inkex.Group(id=name))
        group.transform = inkex.Transform(translate=(cx, cy)) @ inkex.Transform(
            rotate=rotate
        )
        loc = 0
        if bbox:
            loc = min(self.mark_size / 3, max(bbox.width, bbox.height) / 45)
        for bar in [
            {"c": "*", "stroke": "#000", "x": 0, "y": -(loc + 1)},
            {"c": "r", "stroke": "#0FF", "x": 0, "y": 0},
            {"c": "g", "stroke": "#F0F", "x": (loc * 11) + 1, "y": -(loc + 1)},
            {"c": "b", "stroke": "#FF0", "x": (loc * 11) + 1, "y": 0},
        ]:
            i = 0
            while i <= 1:
                color = inkex.Color("white")
                if bar["c"] == "r" or bar["c"] == "*":
                    color.red = 255 * i
                if bar["c"] == "g" or bar["c"] == "*":
                    color.green = 255 * i
                if bar["c"] == "b" or bar["c"] == "*":
                    color.blue = 255 * i
                r_att = {
                    "fill": str(color),
                    "stroke": bar["stroke"],
                    "stroke-width": loc / 8,
                    "x": str((loc * i * 10) + bar["x"]),
                    "y": str(bar["y"]),
                    "width": loc,
                    "height": loc,
                }
                rect = Rectangle()
                for att, value in r_att.items():
                    rect.set(att, value)
                group.add(rect)
                i += 0.1

    def draw_effect(self, bbox, name=""):
        # Get SVG document dimensions
        # self.width must be replaced by bbox.right. same to others.
        svg = self.document.getroot()

        # Convert parameters to user unit
        offset = self.svg.viewport_to_unit(
            str(self.options.crop_offset) + self.options.unit
        )
        bt = self.svg.viewport_to_unit(str(self.options.bleed_top) + self.options.unit)
        bb = self.svg.viewport_to_unit(
            str(self.options.bleed_bottom) + self.options.unit
        )
        bl = self.svg.viewport_to_unit(str(self.options.bleed_left) + self.options.unit)
        br = self.svg.viewport_to_unit(
            str(self.options.bleed_right) + self.options.unit
        )
        # Bleed margin
        if bt < offset:
            bmt = 0
        else:
            bmt = bt - offset
        if bb < offset:
            bmb = 0
        else:
            bmb = bb - offset
        if bl < offset:
            bml = 0
        else:
            bml = bl - offset
        if br < offset:
            bmr = 0
        else:
            bmr = br - offset

        # Define the new document limits
        offset_left = bbox.left - offset
        offset_right = bbox.right + offset
        offset_top = bbox.top - offset
        offset_bottom = bbox.bottom + offset

        # Get middle positions
        middle_vertical = bbox.top + (bbox.height / 2)
        middle_horizontal = bbox.left + (bbox.width / 2)

        # Construct layer id and layer name
        layer_id = "printing-marks"
        layer_name = "Printing Marks"
        if name != "":
            layer_id += "-" + name
            layer_name += " " + name

        # Create a new layer
        layer = svg.add(inkex.Layer.new(layer_name))
        layer.set("id", layer_id)
        layer.set("sodipodi:insensitive", "true")

        # Crop Mark
        if self.options.crop_marks:
            # Create a group for Crop Mark
            g_crops = layer.add(inkex.Group())
            g_crops.label = "CropMarks"
            g_crops.set("id", "CropMarks" + name)

            # Top left Mark
            self.draw_crop_line(
                bbox.left,
                offset_top,
                bbox.left,
                offset_top - self.mark_size,
                "cropTL1",
                g_crops,
            )
            self.draw_crop_line(
                offset_left,
                bbox.top,
                offset_left - self.mark_size,
                bbox.top,
                "cropTL2",
                g_crops,
            )

            # Top right Mark
            self.draw_crop_line(
                bbox.right,
                offset_top,
                bbox.right,
                offset_top - self.mark_size,
                "cropTR1",
                g_crops,
            )
            self.draw_crop_line(
                offset_right,
                bbox.top,
                offset_right + self.mark_size,
                bbox.top,
                "cropTR2",
                g_crops,
            )

            # Bottom left Mark
            self.draw_crop_line(
                bbox.left,
                offset_bottom,
                bbox.left,
                offset_bottom + self.mark_size,
                "cropBL1",
                g_crops,
            )
            self.draw_crop_line(
                offset_left,
                bbox.bottom,
                offset_left - self.mark_size,
                bbox.bottom,
                "cropBL2",
                g_crops,
            )

            # Bottom right Mark
            self.draw_crop_line(
                bbox.right,
                offset_bottom,
                bbox.right,
                offset_bottom + self.mark_size,
                "cropBR1",
                g_crops,
            )
            self.draw_crop_line(
                offset_right,
                bbox.bottom,
                offset_right + self.mark_size,
                bbox.bottom,
                "cropBR2",
                g_crops,
            )

        # Bleed Mark
        if self.options.bleed_marks:
            # Create a group for Bleed Mark
            g_bleed = layer.add(inkex.Group())
            g_bleed.label = "BleedMarks"
            g_bleed.set("id", "BleedMarks" + name)

            # Top left Mark
            self.draw_bleed_line(
                bbox.left - bl,
                offset_top - bmt,
                bbox.left - bl,
                offset_top - bmt - self.mark_size,
                "bleedTL1",
                g_bleed,
            )
            self.draw_bleed_line(
                offset_left - bml,
                bbox.top - bt,
                offset_left - bml - self.mark_size,
                bbox.top - bt,
                "bleedTL2",
                g_bleed,
            )

            # Top right Mark
            self.draw_bleed_line(
                bbox.right + br,
                offset_top - bmt,
                bbox.right + br,
                offset_top - bmt - self.mark_size,
                "bleedTR1",
                g_bleed,
            )
            self.draw_bleed_line(
                offset_right + bmr,
                bbox.top - bt,
                offset_right + bmr + self.mark_size,
                bbox.top - bt,
                "bleedTR2",
                g_bleed,
            )

            # Bottom left Mark
            self.draw_bleed_line(
                bbox.left - bl,
                offset_bottom + bmb,
                bbox.left - bl,
                offset_bottom + bmb + self.mark_size,
                "bleedBL1",
                g_bleed,
            )
            self.draw_bleed_line(
                offset_left - bml,
                bbox.bottom + bb,
                offset_left - bml - self.mark_size,
                bbox.bottom + bb,
                "bleedBL2",
                g_bleed,
            )

            # Bottom right Mark
            self.draw_bleed_line(
                bbox.right + br,
                offset_bottom + bmb,
                bbox.right + br,
                offset_bottom + bmb + self.mark_size,
                "bleedBR1",
                g_bleed,
            )
            self.draw_bleed_line(
                offset_right + bmr,
                bbox.bottom + bb,
                offset_right + bmr + self.mark_size,
                bbox.bottom + bb,
                "bleedBR2",
                g_bleed,
            )

        # Registration Mark
        if self.options.reg_marks:
            # Create a group for Registration Mark
            g_center = layer.add(inkex.Group())
            g_center.label = "RegistrationMarks"
            g_center.set("id", "RegistrationMarks" + name)
            # Left Mark
            cx = max(bml + offset, self.min_mark_margin)
            self.draw_reg_marks(
                bbox.left - cx - (self.mark_size / 2),
                middle_vertical - self.mark_size * 1.5,
                "0",
                "regMarkL",
                g_center,
            )

            # Right Mark
            cx = max(bmr + offset, self.min_mark_margin)
            self.draw_reg_marks(
                bbox.right + cx + (self.mark_size / 2),
                middle_vertical - self.mark_size * 1.5,
                "180",
                "regMarkR",
                g_center,
            )

            # Top Mark
            cy = max(bmt + offset, self.min_mark_margin)
            self.draw_reg_marks(
                middle_horizontal,
                bbox.top - cy - (self.mark_size / 2),
                "90",
                "regMarkT",
                g_center,
            )

            # Bottom Mark
            cy = max(bmb + offset, self.min_mark_margin)
            self.draw_reg_marks(
                middle_horizontal,
                bbox.bottom + cy + (self.mark_size / 2),
                "-90",
                "regMarkB",
                g_center,
            )

        # Star Target
        if self.options.star_target:
            # Create a group for Star Target
            g_center = layer.add(inkex.Group())
            g_center.label = "StarTarget"
            g_center.set("id", "StarTarget" + name)

            if bbox.height < bbox.width:
                # Left Star
                cx = max(bml + offset, self.min_mark_margin)
                self.draw_star_target(
                    bbox.left - cx - (self.mark_size / 2),
                    middle_vertical,
                    "starTargetL",
                    g_center,
                )
                # Right Star
                cx = max(bmr + offset, self.min_mark_margin)
                self.draw_star_target(
                    bbox.right + cx + (self.mark_size / 2),
                    middle_vertical,
                    "starTargetR",
                    g_center,
                )
            else:
                # Top Star
                cy = max(bmt + offset, self.min_mark_margin)
                self.draw_star_target(
                    middle_horizontal - self.mark_size * 1.5,
                    bbox.top - cy - (self.mark_size / 2),
                    "starTargetT",
                    g_center,
                )
                # Bottom Star
                cy = max(bmb + offset, self.min_mark_margin)
                self.draw_star_target(
                    middle_horizontal - self.mark_size * 1.5,
                    bbox.bottom + cy + (self.mark_size / 2),
                    "starTargetB",
                    g_center,
                )

        # Colour Bars
        if self.options.colour_bars:
            # Create a group for Colour Bars
            g_center = layer.add(inkex.Group())
            g_center.label = "ColourBars"
            g_center.set("id", "PrintingColourBars" + name)

            if bbox.height > bbox.width:
                # Left Bars
                cx = max(bml + offset, self.min_mark_margin)
                self.draw_coluor_bars(
                    bbox.left - cx - (self.mark_size / 2),
                    middle_vertical + self.mark_size,
                    90,
                    "PrintingColourBarsL",
                    g_center,
                    bbox,
                )
                # Right Bars
                cx = max(bmr + offset, self.min_mark_margin)
                self.draw_coluor_bars(
                    bbox.right + cx + (self.mark_size / 2),
                    middle_vertical + self.mark_size,
                    90,
                    "PrintingColourBarsR",
                    g_center,
                    bbox,
                )
            else:
                # Top Bars
                cy = max(bmt + offset, self.min_mark_margin)
                self.draw_coluor_bars(
                    middle_horizontal + self.mark_size,
                    bbox.top - cy - (self.mark_size / 2),
                    0,
                    "PrintingColourBarsT",
                    g_center,
                    bbox,
                )
                # Bottom Bars
                cy = max(bmb + offset, self.min_mark_margin)
                self.draw_coluor_bars(
                    middle_horizontal + self.mark_size,
                    bbox.bottom + cy + (self.mark_size / 2),
                    0,
                    "PrintingColourBarsB",
                    g_center,
                    bbox,
                )

        # Page Information
        if self.options.page_info:
            # Create a group for Page Information
            g_pag_info = layer.add(inkex.Group())
            g_pag_info.label = "PageInformation"
            g_pag_info.set("id", "PageInformation" + name)
            y_margin = max(bmb + offset, self.min_mark_margin)
            font_size = self.svg.viewport_to_unit("9pt")
            txt_attribs = {
                "style": f"font-size:{font_size}px;font-style:normal;font-weight:normal;fill:#000000;font-family:Bitstream Vera Sans,sans-serif;text-anchor:middle;text-align:center",
                "x": str(middle_horizontal),
                "y": str(bbox.bottom + y_margin + self.mark_size + 20),
            }
            txt = g_pag_info.add(TextElement(**txt_attribs))
            txt.text = (
                "Page size: "
                + str(
                    round(self.svg.unit_to_viewport(bbox.width, self.options.unit), 2)
                )
                + "x"
                + str(
                    round(self.svg.unit_to_viewport(bbox.height, self.options.unit), 2)
                )
                + " "
                + self.options.unit
            )

    def remove_layers(self):
        # Find layers with id starting with "printing-marks" and remove them
        layers = self.svg.xpath(
            '//*[starts-with(@id, "printing-marks") and @inkscape:groupmode="layer"]'
        )
        for layer in layers:
            self.document.getroot().remove(layer)

    def effect(self):
        self.mark_size = self.svg.viewport_to_unit("1cm")
        self.min_mark_margin = self.svg.viewport_to_unit("3mm")

        if self.options.where == "selection":
            bbox = self.svg.selection.bounding_box()
            if bbox is None:
                raise inkex.AbortExtension(_("Selection is empty"))

            self.remove_layers()
            self.draw_effect(bbox)
        else:
            pages = self.svg.namedview.get_pages()

            self.remove_layers()

            # Handle single-page document
            if len(pages) < 2:
                self.draw_effect(self.svg.get_page_bbox(), "0")
            else:
                for p, page in enumerate(pages):
                    self.draw_effect(page.bounding_box, str(p))


if __name__ == "__main__":
    PrintingMarks().run()
