| Server IP : 93.86.61.54 / Your IP : 216.73.216.206 Web Server : Apache/2.4.62 (Ubuntu) System : Linux rasin.ddns.net 6.8.0-124-generic #124~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 21:05:19 UTC x86_64 User : www-data ( 33) PHP Version : 8.4.22 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/inkscape/extensions/ |
Upload File : |
#!/usr/bin/env python
# coding=utf-8
#
# Copyright (C) 2012 Jabiertxo Arraiza, jabier.arraiza@marker.es
# Copyright (C) 2016 su_v, <suv-sf@users.sf.net>
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Version 0.6 - DPI Switcher
This extension scales a document to fit different SVG DPI -90/96-
Changes since v0.5:
- transform all top-level containers and graphics elements
- support scientific notation in SVG lengths
- fix scaling with existing matrix()
- support different units for document width, height attributes
- improve viewBox support (syntax, offset)
- support common cases of text-put-on-path in SVG root
- support common cases of <use> references in SVG root
- examples from http://tavmjong.free.fr/INKSCAPE/UNITS/ tested
TODO:
- check grids/guides created with 0.91:
http://tavmjong.free.fr/INKSCAPE/UNITS/units_mm_nv_90dpi.svg
- check <symbol> instances
- check more <use> and text-on-path cases (reverse scaling needed?)
- scale perspective of 3dboxes
"""
import re
import math
import inkex
from inkex import Use, TextElement, transforms
# globals
SKIP_CONTAINERS = [
'defs',
'glyph',
'marker',
'mask',
'missing-glyph',
'pattern',
'symbol',
]
CONTAINER_ELEMENTS = [
'a',
'g',
'switch',
]
GRAPHICS_ELEMENTS = [
'circle',
'ellipse',
'image',
'line',
'path',
'polygon',
'polyline',
'rect',
'text',
'use',
]
def is_3dbox(element):
"""Check whether element is an Inkscape 3dbox type."""
return element.get('sodipodi:type') == 'inkscape:box3d'
def is_text_on_path(element):
"""Check whether text element is put on a path."""
if isinstance(element, TextElement):
text_path = element.find('svg:textPath')
if text_path is not None and len(text_path):
return True
return False
def is_sibling(element1, element2):
"""Check whether element1 and element2 are siblings of same parent."""
return element2 in element1.getparent()
def is_in_defs(doc, element):
"""Check whether element is in defs."""
if element is not None:
defs = doc.find('defs')
if defs is not None:
return element in defs.iterdescendants()
return False
def check_3dbox(svg, element, scale_x, scale_y):
"""Check transformation for 3dbox element."""
skip = False
if skip:
# 3dbox elements ignore preserved transforms
# FIXME: manually update geometry of 3dbox?
pass
return skip
def check_text_on_path(svg, element, scale_x, scale_y):
"""Check whether to skip scaling a text put on a path."""
skip = False
path = element.find('textPath').href
if not is_in_defs(svg, path):
if is_sibling(element, path):
# skip common element scaling if both text and path are siblings
skip = True
# scale offset
if 'transform' in element.attrib:
element.transform.add_scale(scale_x, scale_y)
# scale font size
mat = inkex.Transform('scale({},{})'.format(scale_x, scale_y)).matrix
det = abs(mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0])
descrim = math.sqrt(abs(det))
prop = 'font-size'
# outer text
sdict = dict(inkex.Style.parse_str(element.get('style')))
if prop in sdict:
sdict[prop] = float(sdict[prop]) * descrim
element.set('style', str(inkex.Style(sdict)))
# inner tspans
for child in element.iterdescendants():
if isinstance(element, inkex.Tspan):
sdict = dict(inkex.Style.parse_str(child.get('style')))
if prop in sdict:
sdict[prop] = float(sdict[prop]) * descrim
child.set('style', str(inkex.Style(sdict)))
return skip
def check_use(svg, element, scale_x, scale_y):
"""Check whether to skip scaling an instantiated element (<use>)."""
skip = False
path = element.href
if not is_in_defs(svg, path):
if is_sibling(element, path):
skip = True
# scale offset
if 'transform' in element.attrib:
element.transform.add_scale(scale_x, scale_y)
return skip
class DPISwitcher(inkex.EffectExtension):
multi_inx = True
factor_a = 90.0 / 96.0
factor_b = 96.0 / 90.0
units = "px"
def add_arguments(self, pars):
pars.add_argument("--switcher", type=str, default="0",
help="Select the DPI switch you want")
# dictionaries of unit to user unit conversion factors
__uuconvLegacy = {
'in': 90.0,
'pt': 1.25,
'px': 1.0,
'mm': 3.5433070866,
'cm': 35.433070866,
'm': 3543.3070866,
'km': 3543307.0866,
'pc': 15.0,
'yd': 3240.0,
'ft': 1080.0,
}
__uuconv = {
'in': 96.0,
'pt': 1.33333333333,
'px': 1.0,
'mm': 3.77952755913,
'cm': 37.7952755913,
'm': 3779.52755913,
'km': 3779527.55913,
'pc': 16.0,
'yd': 3456.0,
'ft': 1152.0,
}
def parse_length(self, length, percent=False):
"""Parse SVG length."""
if self.options.switcher == "0": # dpi90to96
known_units = list(self.__uuconvLegacy)
else: # dpi96to90
known_units = list(self.__uuconv)
if percent:
unitmatch = re.compile('(%s)$' % '|'.join(known_units + ['%']))
else:
unitmatch = re.compile('(%s)$' % '|'.join(known_units))
param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
p = param.match(length)
u = unitmatch.search(length)
val = 100 # fallback: assume default length of 100
unit = 'px' # fallback: assume 'px' unit
if p:
val = float(p.string[p.start():p.end()])
if u:
unit = u.string[u.start():u.end()]
return val, unit
def convert_length(self, val, unit):
"""Convert length to self.units if unit differs."""
doc_unit = self.units or 'px'
if unit != doc_unit:
if self.options.switcher == "0": # dpi90to96
val_px = val * self.__uuconvLegacy[unit]
val = val_px / (self.__uuconvLegacy[doc_unit] / self.__uuconvLegacy['px'])
unit = doc_unit
else: # dpi96to90
val_px = val * self.__uuconv[unit]
val = val_px / (self.__uuconv[doc_unit] / self.__uuconv['px'])
unit = doc_unit
return val, unit
def check_attr_unit(self, element, attr, unit_list):
"""Check unit of attribute value, match to units in *unit_list*."""
if attr in element.attrib:
unit = self.parse_length(element.get(attr), percent=True)[1]
return unit in unit_list
def scale_attr_val(self, element, attr, unit_list, factor):
"""Scale attribute value if unit matches one in *unit_list*."""
if attr in element.attrib:
val, unit = self.parse_length(element.get(attr), percent=True)
if unit in unit_list:
element.set(attr, '{}{}'.format(val * factor, unit))
def scale_root(self, unit_exponent=1.0):
"""Scale all top-level elements in SVG root."""
# update viewport
width_num = self.parse_length(self.svg.get('width'))[0]
height_num = self.convert_length(*self.parse_length(self.svg.get('height')))[0]
width_doc = width_num * self.factor_a * unit_exponent
height_doc = height_num * self.factor_a * unit_exponent
svg = self.svg
if svg.get('height'):
svg.set('height', str(height_doc))
if svg.get('width'):
svg.set('width', str(width_doc))
# update viewBox
if svg.get('viewBox'):
viewboxstring = re.sub(' +|, +|,', ' ', svg.get('viewBox'))
viewboxlist = [float(i) for i in viewboxstring.strip().split(' ', 4)]
svg.set('viewBox', '{} {} {} {}'.format(*[(val * self.factor_a) for val in viewboxlist]))
# update guides, grids
if self.options.switcher == "1":
# FIXME: dpi96to90 only?
self.scale_guides()
self.scale_grid()
for element in svg: # iterate all top-level elements of SVGRoot
# init variables
tag = element.TAG
width_scale = self.factor_a
height_scale = self.factor_a
if tag in GRAPHICS_ELEMENTS or tag in CONTAINER_ELEMENTS:
# test for specific elements to skip from scaling
if is_3dbox(element):
if check_3dbox(svg, element, width_scale, height_scale):
continue
if is_text_on_path(element):
if check_text_on_path(svg, element, width_scale, height_scale):
continue
if isinstance(element, Use):
if check_use(svg, element, width_scale, height_scale):
continue
# relative units ('%') in presentation attributes
for attr in ['width', 'height']:
self.scale_attr_val(element, attr, ['%'], 1.0 / self.factor_a)
for attr in ['x', 'y']:
self.scale_attr_val(element, attr, ['%'], 1.0 / self.factor_a)
# set preserved transforms on top-level elements
if width_scale != 1.0 and height_scale != 1.0:
scale_mat = transforms.Transform(scale=(width_scale, height_scale))
element.transform = scale_mat * element.transform
def scale_element(self, elem):
pass # TODO: optionally scale graphics elements only?
def scale_guides(self):
"""Scale the guidelines"""
for guide in self.svg.namedview.get_guides():
point = guide.get("position").split(",")
guide.set("position", str(float(point[0].strip()) * self.factor_a) + "," + str(float(point[1].strip()) * self.factor_a))
def scale_grid(self):
"""Scale the inkscape grid"""
grids = self.svg.xpath('//inkscape:grid')
for grid in grids:
grid.set("units", "px")
if grid.get("spacingx"):
spacingx = str(float(re.sub("[a-zA-Z]", "", grid.get("spacingx"))) * self.factor_a) + "px"
grid.set("spacingx", str(spacingx))
if grid.get("spacingy"):
spacingy = str(float(re.sub("[a-zA-Z]", "", grid.get("spacingy"))) * self.factor_a) + "px"
grid.set("spacingy", str(spacingy))
if grid.get("originx"):
originx = str(float(re.sub("[a-zA-Z]", "", grid.get("originx"))) * self.factor_a) + "px"
grid.set("originx", str(originx))
if grid.get("originy"):
originy = str(float(re.sub("[a-zA-Z]", "", grid.get("originy"))) * self.factor_a) + "px"
grid.set("originy", str(originy))
def effect(self):
svg = self.svg
if self.options.switcher == "0":
self.factor_a = 96.0 / 90.0
self.factor_b = 90.0 / 96.0
svg.namedview.set('inkscape:document-units', "px")
self.units = self.parse_length(svg.get('width'))[1]
unit_exponent = 1.0
if self.units and self.units != "px" and self.units != "" and self.units != "%":
if self.options.switcher == "0":
unit_exponent = 1.0 / (self.factor_a / self.__uuconv[self.units])
else:
unit_exponent = 1.0 / (self.factor_a / self.__uuconvLegacy[self.units])
self.scale_root(unit_exponent)
if __name__ == '__main__':
DPISwitcher().run()