313 lines
12 KiB
Python
Executable file
313 lines
12 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# coding=utf-8
|
|
#
|
|
# Copyright (C) 2008 Jonas Termeau - jonas.termeau **AT** gmail.com
|
|
#
|
|
# 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; version 2 of the License.
|
|
#
|
|
# 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.
|
|
#
|
|
# Thanks to:
|
|
#
|
|
# Bernard Gray - bernard.gray **AT** gmail.com (python helping)
|
|
# Jamie Heames (english translation issues)
|
|
# ~suv (bug report in v2.3)
|
|
# http://www.gutenberg.eu.org/publications/ (9x9 margins settings)
|
|
#
|
|
"""
|
|
This basic extension allows you to automatically draw guides in inkscape.
|
|
"""
|
|
|
|
from math import cos, sin, sqrt
|
|
import re
|
|
|
|
import inkex
|
|
|
|
from inkex.localization import inkex_gettext as _
|
|
|
|
|
|
class GuidesOpts:
|
|
"""Manager of current-page-related values for GuidesCreator"""
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
def __init__(self, svg: inkex.SvgDocumentElement) -> None:
|
|
# get page bounds
|
|
self.pages = svg.namedview.get_pages()
|
|
self.viewbox = svg.get_viewbox()
|
|
# in case the viewbox attribute is not set, fall back to viewport
|
|
self.viewbox[2:4] = svg.viewbox_width, svg.viewbox_height
|
|
self.set_page(1)
|
|
|
|
def set_page(self, pagenumber):
|
|
"""Update guide origin and width/height based on page number (1-indexed)"""
|
|
self.pagenumber = pagenumber
|
|
pagenumber = pagenumber - 1
|
|
if 0 <= pagenumber < len(self.pages):
|
|
self.page_origin = (self.pages[pagenumber].x, self.pages[pagenumber].y)
|
|
self.width = self.pages[pagenumber].width
|
|
self.height = self.pages[pagenumber].height
|
|
else:
|
|
raise ValueError("Invalid page number")
|
|
|
|
# getting edges coordinates
|
|
self.orientation = ((round(self.height, 4), 0), (0, round(self.width, 4)))
|
|
|
|
|
|
class GuidesCreator(inkex.EffectExtension):
|
|
"""Create a set of guides based on the given options"""
|
|
|
|
def add_arguments(self, pars):
|
|
pars.add_argument(
|
|
"--pages",
|
|
type=self.arg_number_ranges(),
|
|
help='On which pages the guides are created, e.g. "1, 2, 4-6, 8-". '
|
|
"Default: All pages.",
|
|
default="1-",
|
|
)
|
|
pars.add_argument(
|
|
"--tab",
|
|
type=self.arg_method("generate"),
|
|
default="regular_guides",
|
|
help="Type of guides to create.",
|
|
)
|
|
pars.add_argument("--guides_preset", default="custom", help="Preset")
|
|
pars.add_argument(
|
|
"--vertical_guides", type=int, default=2, help="Vertical guides"
|
|
)
|
|
pars.add_argument(
|
|
"--horizontal_guides", type=int, default=3, help="Horizontal guides"
|
|
)
|
|
pars.add_argument(
|
|
"--start_from_edges", type=inkex.Boolean, help="Start from edges"
|
|
)
|
|
pars.add_argument(
|
|
"--ul", type=inkex.Boolean, default=False, help="Upper left corner"
|
|
)
|
|
pars.add_argument(
|
|
"--ur", type=inkex.Boolean, default=False, help="Upper right corner"
|
|
)
|
|
pars.add_argument(
|
|
"--ll", type=inkex.Boolean, default=False, help="Lower left corner"
|
|
)
|
|
pars.add_argument(
|
|
"--lr", type=inkex.Boolean, default=False, help="Lower right corner"
|
|
)
|
|
pars.add_argument(
|
|
"--margins_preset",
|
|
default="custom",
|
|
choices=[
|
|
"custom",
|
|
"book_left",
|
|
"book_right",
|
|
"book_alternating_right",
|
|
"book_alternating_left",
|
|
],
|
|
help="Margins preset",
|
|
)
|
|
pars.add_argument("--vert", type=int, default=0, help="Vert subdivisions")
|
|
pars.add_argument("--horz", type=int, default=0, help="Horz subdivisions")
|
|
pars.add_argument("--header_margin", type=int, default=10, help="Header margin")
|
|
pars.add_argument("--footer_margin", type=int, default=10, help="Footer margin")
|
|
pars.add_argument("--left_margin", type=int, default=10, help="Left margin")
|
|
pars.add_argument("--right_margin", type=int, default=10, help="Right margin")
|
|
pars.add_argument("--delete", type=inkex.Boolean, help="Delete existing guides")
|
|
pars.add_argument(
|
|
"--nodup", type=inkex.Boolean, help="Omit duplicated guides", default=True
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.store: GuidesOpts = None
|
|
|
|
def effect(self):
|
|
if self.options.delete:
|
|
for guide in self.svg.namedview.get_guides():
|
|
guide.delete()
|
|
|
|
self.store = GuidesOpts(self.svg)
|
|
for i in self.options.pages(max(len(self.svg.namedview.get_pages()), 1)):
|
|
self.store.set_page(i)
|
|
self.options.tab()
|
|
|
|
def generate_regular_guides(self):
|
|
"""Generate a regular set of guides"""
|
|
preset = self.options.guides_preset
|
|
from_edges = self.options.start_from_edges
|
|
if preset == "custom":
|
|
h_division = self.options.horizontal_guides
|
|
v_division = self.options.vertical_guides
|
|
if from_edges:
|
|
v_division = v_division or 1
|
|
h_division = h_division or 1
|
|
|
|
self.draw_guides(v_division, from_edges, vert=True)
|
|
self.draw_guides(h_division, from_edges, vert=False)
|
|
|
|
elif preset == "golden":
|
|
gold = (1 + sqrt(5)) / 2
|
|
|
|
for fraction, index in zip([1 / gold, 1 - 1 / gold] * 2, [1, 1, 0, 0]):
|
|
position = fraction * (self.store.width, self.store.height)[index]
|
|
self.draw_guide(
|
|
(0, position) if index == 1 else (position, 0),
|
|
self.store.orientation[index],
|
|
)
|
|
|
|
if from_edges:
|
|
self.draw_guides(1, True, vert=False)
|
|
self.draw_guides(1, True, vert=True)
|
|
|
|
elif ";" in preset:
|
|
v_division = int(preset.split(";")[0])
|
|
h_division = int(preset.split(";")[1])
|
|
self.draw_guides(v_division, from_edges, vert=True)
|
|
self.draw_guides(h_division, from_edges, vert=False)
|
|
else:
|
|
raise inkex.AbortExtension(_("Unknown guide preset: {}").format(preset))
|
|
|
|
def generate_diagonal_guides(self):
|
|
"""Generate diagonal guides"""
|
|
# Dimentions
|
|
left, bottom = (0, 0)
|
|
right, top = (self.store.width, self.store.height)
|
|
|
|
# Diagonal angle
|
|
angle = 45
|
|
|
|
corner_guides = {
|
|
"ul": ((left, top), (cos(angle), cos(angle))),
|
|
"ur": ((right, top), (-sin(angle), sin(angle))),
|
|
"ll": ((left, bottom), (-cos(angle), cos(angle))),
|
|
"lr": ((right, bottom), (-sin(angle), -sin(angle))),
|
|
}
|
|
|
|
for key, (position, orientation) in corner_guides.items():
|
|
if getattr(self.options, key):
|
|
self.draw_guide(position, orientation)
|
|
|
|
def generate_margins(self):
|
|
"""Generate margin guides"""
|
|
|
|
if self.options.start_from_edges:
|
|
# horizontal borders
|
|
self.draw_guide((0, self.store.height), self.store.orientation[1])
|
|
self.draw_guide((self.store.width, 0), self.store.orientation[1])
|
|
|
|
# vertical borders
|
|
self.draw_guide((0, self.store.height), self.store.orientation[0])
|
|
self.draw_guide((self.store.width, 0), self.store.orientation[0])
|
|
|
|
if self.options.margins_preset == "custom":
|
|
margins = [
|
|
(i / j if int(j) != 0 else None)
|
|
for i, j in zip(
|
|
(
|
|
self.store.height * (self.options.header_margin - 1), # header
|
|
self.store.height, # footer
|
|
self.store.width, # left
|
|
self.store.width * (self.options.right_margin - 1), # right
|
|
),
|
|
(
|
|
self.options.header_margin,
|
|
self.options.footer_margin,
|
|
self.options.left_margin,
|
|
self.options.right_margin,
|
|
),
|
|
)
|
|
]
|
|
|
|
book_options = {
|
|
"book_left": (8 / 9, 2 / 9, 2 / 9, 8 / 9),
|
|
"book_right": (8 / 9, 2 / 9, 1 / 9, 7 / 9),
|
|
}
|
|
margins_preset = self.options.margins_preset
|
|
if margins_preset.startswith("book_alternating"):
|
|
margins_preset = (
|
|
"book_left"
|
|
if self.store.pagenumber % 2 == (1 if "left" in margins_preset else 0)
|
|
else "book_right"
|
|
)
|
|
|
|
if margins_preset in book_options:
|
|
margins = [
|
|
i * j
|
|
for i, j in zip(
|
|
book_options[margins_preset],
|
|
2 * [self.store.height] + 2 * [self.store.width],
|
|
)
|
|
]
|
|
|
|
y_header, y_footer, x_left, x_right = [
|
|
i or j for i, j in zip(margins, [self.store.height, 0, 0, self.store.width])
|
|
]
|
|
|
|
for length, position in zip(margins, [1, 1, 0, 0]):
|
|
if length is None:
|
|
continue
|
|
self.draw_guide(
|
|
(length, 0) if position == 0 else (0, length),
|
|
self.store.orientation[position],
|
|
)
|
|
|
|
# setting up properties of the rectangle created between guides
|
|
rectangle_height = y_header - y_footer
|
|
rectangle_width = x_right - x_left
|
|
|
|
for subdiv, vert, begin_from in zip(
|
|
(self.options.horz, self.options.vert), (False, True), (y_footer, x_left)
|
|
):
|
|
if subdiv != 0:
|
|
self._draw_guides(
|
|
(rectangle_width, rectangle_height),
|
|
subdiv,
|
|
edges=0,
|
|
shift=begin_from,
|
|
vert=vert,
|
|
)
|
|
|
|
def draw_guides(self, division, edges, vert=False):
|
|
"""Draw a vertical or horizontal lines"""
|
|
return self._draw_guides(
|
|
(self.store.width, self.store.height), division, edges, vert=vert
|
|
)
|
|
|
|
def _draw_guides(self, vector, division, edges, shift=0, vert=False):
|
|
if division <= 0:
|
|
return
|
|
|
|
# Vert controls both ort template and vector calculation
|
|
def ort(x):
|
|
return (x, 0) if vert else (0, x)
|
|
|
|
var = int(bool(edges))
|
|
for x in range(0, division - 1 + 2 * var):
|
|
div = vector[not bool(vert)] / division
|
|
position = round(div + (x - var) * div + shift, 4)
|
|
orientation = round(vector[bool(vert)], 4)
|
|
self.draw_guide(ort(position), ort(orientation))
|
|
|
|
def draw_guide(self, position, orientation):
|
|
"""Draw the guides"""
|
|
newpos = [
|
|
position[0] + self.store.page_origin[0],
|
|
-position[1] + self.store.height + self.store.page_origin[1],
|
|
]
|
|
# orientations are computed in the pre-1.0 coordinate system
|
|
orientation = [orientation[0], -orientation[1]]
|
|
if self.options.nodup:
|
|
self.svg.namedview.add_unique_guide(newpos, orientation)
|
|
else:
|
|
self.svg.namedview.add_guide(newpos, orientation)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
GuidesCreator().run()
|