Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F85140433
matrix.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Sep 27, 01:32
Size
13 KB
Mime Type
text/x-python
Expires
Sun, Sep 29, 01:32 (2 d)
Engine
blob
Format
Raw Data
Handle
21133999
Attached To
rNIETZSCHEPYTHON nietzsche-python
matrix.py
View Options
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" This class can be used to transform a svg/text[@transform] matrix-string into a matrix representation.
"""
# Copyright (C) University of Basel 2019 {{{1
#
# 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 3 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, see <https://www.gnu.org/licenses/> 1}}}
__author__
=
"Christian Steiner"
__maintainer__
=
__author__
__copyright__
=
'University of Basel'
__email__
=
"christian.steiner@unibas.ch"
__status__
=
"Development"
__license__
=
"GPL v3"
__version__
=
"0.0.1"
import
re
import
math
class
Matrix
:
"""
This class transforms a svg @transform matrix-string into a matrix representation.
Args:
transform_matrix_string (str) string of the form 'matrix(1.0 0.0 0.0 1.0 0.0 0.0)' or 'rotate(10)'
"""
A
=
0
B
=
1
C
=
2
D
=
3
E
=
4
F
=
5
XINDEX
=
4
YINDEX
=
5
MATRIX_LENGTH
=
6
DOWN
=
1
STRAIGHT
=
0
UP
=
-
1
def
__init__
(
self
,
transform_matrix_string
=
None
,
transkription_field
=
None
,
matrix_list
=
[]):
self
.
matrix
=
[
0.0
for
i
in
range
(
Matrix
.
MATRIX_LENGTH
)
]
if
len
(
matrix_list
)
<
6
else
matrix_list
if
transform_matrix_string
is
not
None
:
m
=
re
.
search
(
'(?<=rotate\()[-]*[0-9]+'
,
transform_matrix_string
)
if
m
is
not
None
:
# transform='rotate(a)' to transform='matrix(cos(a), sin(a), -sin(a), cos(a), 0, 0)'
angle
=
float
(
m
.
group
(
0
))
self
.
matrix
[
Matrix
.
A
]
=
round
(
math
.
cos
(
math
.
radians
(
angle
)),
3
)
self
.
matrix
[
Matrix
.
B
]
=
round
(
math
.
sin
(
math
.
radians
(
angle
)),
3
)
self
.
matrix
[
Matrix
.
C
]
=
round
(
math
.
sin
(
math
.
radians
(
angle
))
*-
1
,
3
)
self
.
matrix
[
Matrix
.
D
]
=
round
(
math
.
cos
(
math
.
radians
(
angle
)),
3
)
self
.
matrix
[
Matrix
.
E
]
=
0
self
.
matrix
[
Matrix
.
F
]
=
0
elif
re
.
search
(
r'matrix\(\s*([-]*\d+(\.\d+(e-\d+)*)*[,\s][\s]*){5}[-]*\d+(\.\d+)*.*\s*\)'
,
transform_matrix_string
):
#elif re.search(r'matrix\(\s*([-]*[0-9].*\s){5}[-]*[0-9].*\s*\)', transform_matrix_string):
# old-> does not include comma separated matrix string
self
.
matrix
=
[
float
(
i
)
for
i
in
transform_matrix_string
.
replace
(
'matrix('
,
''
)
.
\
replace
(
', '
,
','
)
.
replace
(
','
,
' '
)
.
replace
(
')'
,
''
)
.
split
(
' '
)
]
else
:
raise
Exception
(
'Error: string "{}" is not a valid transform matrix string!'
.
format
(
transform_matrix_string
))
if
transkription_field
is
not
None
:
self
.
matrix
[
Matrix
.
XINDEX
]
-=
transkription_field
.
xmin
self
.
matrix
[
Matrix
.
YINDEX
]
-=
transkription_field
.
ymin
if
(
len
(
self
.
matrix
)
<
Matrix
.
MATRIX_LENGTH
):
raise
Exception
(
'Error: string "{}" is not a valid matrix string!'
.
format
(
transform_matrix_string
))
def
add2X
(
self
,
add_to_x
=
0
):
"""Return x-value of matrix (float) + add_to_x.
"""
return
self
.
matrix
[
Matrix
.
XINDEX
]
+
float
(
add_to_x
)
def
add2Y
(
self
,
add_to_y
=
0
):
"""Return y-value of matrix (float) + add_to_y.
"""
return
self
.
matrix
[
Matrix
.
YINDEX
]
+
float
(
add_to_y
)
def
getX
(
self
):
"""Return x-value of matrix (float).
"""
return
self
.
matrix
[
Matrix
.
XINDEX
]
def
getY
(
self
):
"""Return y-value of matrix (float).
"""
return
self
.
matrix
[
Matrix
.
YINDEX
]
def
is_matrix_horizontal
(
self
):
"""Returns whether matrix is horizontal.
[:return:] True/False
"""
return
self
.
matrix
[
Matrix
.
A
]
==
1
and
self
.
matrix
[
Matrix
.
B
]
==
0
and
self
.
matrix
[
Matrix
.
C
]
==
0
and
self
.
matrix
[
Matrix
.
D
]
==
1
def
get_new_x
(
self
,
x
=
0.0
,
y
=
0.0
):
"""Returns new position of x.
:return: (float) x
"""
top_left_x
=
x
-
self
.
matrix
[
self
.
E
]
if
x
!=
0.0
else
0.0
top_left_y
=
y
-
self
.
matrix
[
self
.
F
]
if
y
!=
0.0
else
0.0
return
self
.
matrix
[
Matrix
.
A
]
*
top_left_x
+
self
.
matrix
[
Matrix
.
C
]
*
top_left_y
+
self
.
matrix
[
self
.
E
]
def
get_new_y
(
self
,
x
=
0.0
,
y
=
0.0
):
"""Returns new position of y.
:return: (float) y
"""
top_left_x
=
x
-
self
.
matrix
[
self
.
E
]
if
x
!=
0.0
else
0.0
top_left_y
=
y
-
self
.
matrix
[
self
.
F
]
if
y
!=
0.0
else
0.0
return
self
.
matrix
[
Matrix
.
B
]
*
top_left_x
+
self
.
matrix
[
Matrix
.
D
]
*
top_left_y
+
self
.
matrix
[
self
.
F
]
def
get_old_x
(
self
,
x
=
0.0
,
y
=
0.0
):
"""Returns old position of x.
:return: (float) x
"""
old_x
=
(
self
.
matrix
[
self
.
D
]
*
x
-
self
.
matrix
[
Matrix
.
D
]
*
self
.
matrix
[
Matrix
.
E
]
-
self
.
matrix
[
Matrix
.
C
]
*
y
+
self
.
matrix
[
Matrix
.
C
]
*
self
.
matrix
[
Matrix
.
F
])
\
/
(
self
.
matrix
[
Matrix
.
A
]
*
self
.
matrix
[
Matrix
.
D
]
-
self
.
matrix
[
Matrix
.
B
]
*
self
.
matrix
[
Matrix
.
C
])
return
self
.
add2X
(
old_x
)
def
get_transformed_positions
(
self
,
x
=
0.0
,
y
=
0.0
,
width
=
0.0
,
height
=
0.0
):
"""Returns transformed x, y, width and height.
"""
top_left_x
=
x
top_left_y
=
y
top_right_x
=
x
+
width
top_right_y
=
y
bottom_left_x
=
x
bottom_left_y
=
y
+
height
bottom_right_x
=
x
+
width
bottom_right_y
=
y
+
height
new_x
=
self
.
matrix
[
Matrix
.
A
]
*
top_left_x
+
self
.
matrix
[
Matrix
.
C
]
*
top_left_y
+
self
.
matrix
[
self
.
E
]
new_y
=
self
.
matrix
[
Matrix
.
B
]
*
top_left_x
+
self
.
matrix
[
Matrix
.
D
]
*
top_left_y
+
self
.
matrix
[
self
.
F
]
new_top_right_x
=
self
.
matrix
[
Matrix
.
A
]
*
top_right_x
+
self
.
matrix
[
Matrix
.
C
]
*
top_right_y
+
self
.
matrix
[
self
.
E
]
new_top_right_y
=
self
.
matrix
[
Matrix
.
B
]
*
top_right_x
+
self
.
matrix
[
Matrix
.
D
]
*
top_right_y
+
self
.
matrix
[
self
.
F
]
new_bottom_left_x
=
self
.
matrix
[
Matrix
.
A
]
*
bottom_left_x
+
self
.
matrix
[
Matrix
.
C
]
*
bottom_left_y
+
self
.
matrix
[
self
.
E
]
new_bottom_left_y
=
self
.
matrix
[
Matrix
.
B
]
*
bottom_left_x
+
self
.
matrix
[
Matrix
.
D
]
*
bottom_left_y
+
self
.
matrix
[
self
.
F
]
new_bottom_right_x
=
self
.
matrix
[
Matrix
.
A
]
*
bottom_right_x
+
self
.
matrix
[
Matrix
.
C
]
*
bottom_right_y
+
self
.
matrix
[
self
.
E
]
new_bottom_right_y
=
self
.
matrix
[
Matrix
.
B
]
*
bottom_right_x
+
self
.
matrix
[
Matrix
.
D
]
*
bottom_right_y
+
self
.
matrix
[
self
.
F
]
new_width
=
abs
(
new_top_right_x
-
new_x
)
\
if
abs
(
new_top_right_x
-
new_x
)
>=
abs
(
new_bottom_right_x
-
new_bottom_left_x
)
\
else
abs
(
new_bottom_right_x
-
new_bottom_left_x
)
new_height
=
abs
(
new_bottom_left_y
-
new_y
)
\
if
abs
(
new_bottom_left_y
-
new_y
)
>=
abs
(
new_top_right_y
-
new_bottom_right_y
)
\
else
abs
(
new_top_right_y
-
new_bottom_right_y
)
return
new_x
,
new_y
,
new_width
,
new_height
def
clone_transformation_matrix
(
self
):
"""Returns a matrix that contains only the transformation part.
[:return:] (Matrix) a clone of this matrix
"""
return
Matrix
(
matrix_list
=
self
.
matrix
[
0
:
4
]
+
[
0
,
0
])
def
isRotationMatrix
(
self
):
"""Return whether matrix is a rotation matrix.
"""
return
self
.
matrix
[
Matrix
.
A
]
<
1
or
self
.
matrix
[
Matrix
.
B
]
!=
0
def
toCSSTransformString
(
self
):
"""Returns the CSS3 transform string: 'rotate(Xdeg)' where X is the angle.
"""
angle
=
0
if
self
.
isRotationMatrix
():
angle
=
int
(
round
(
math
.
degrees
(
math
.
asin
(
self
.
matrix
[
Matrix
.
B
])),
0
))
if
angle
==
0
:
angle
=
int
(
round
(
math
.
degrees
(
math
.
acos
(
self
.
matrix
[
Matrix
.
A
])),
0
))
return
'rotate({}deg)'
.
format
(
angle
)
def
toString
(
self
):
"""Returns a transform_matrix_string representation of the matrix.
[:returns:] (str) 'matrix(X X X X X X)'
"""
return
'matrix('
+
' '
.
join
([
str
(
round
(
x
,
5
))
for
x
in
self
.
matrix
])
+
')'
def
get_rotation_direction
(
self
):
"""Get rotation direction of rotation matrix.
[:return:] (int) direction code Matrix.UP, Matrix.STRAIGHT, Matrix.DOWN
"""
if
not
self
.
isRotationMatrix
():
return
self
.
STRAIGHT
else
:
angle
=
int
(
round
(
math
.
degrees
(
math
.
asin
(
self
.
matrix
[
Matrix
.
B
])),
0
))
return
self
.
UP
if
angle
<
0
else
self
.
DOWN
@staticmethod
def
IS_IN_FOOTNOTE_AREA
(
transform_matrix_string
,
transkription_field
):
"""Returns true if matrix specifies a position that is part of the footnote area.
text_node (lxml.etree.Element)
transkription_field (datatypes.transkription_field.TranskriptionField)
"""
matrix
=
Matrix
(
transform_matrix_string
=
transform_matrix_string
)
if
matrix
.
getY
()
<
transkription_field
.
ymax
:
return
False
is_part
=
matrix
.
getX
()
>
transkription_field
.
xmin
\
if
transkription_field
.
is_page_verso
()
\
else
matrix
.
getX
()
>
transkription_field
.
documentWidth
/
4
return
is_part
@staticmethod
def
IS_IN_MARGIN_FIELD
(
transform_matrix_string
,
transkription_field
):
"""Returns true if matrix specifies a position that is part of the margin field.
text_node (lxml.etree.Element)
transkription_field (datatypes.transkription_field.TranskriptionField)
"""
line_number_area_width
=
15
\
if
transkription_field
.
line_number_area_width
==
0.0
\
else
transkription_field
.
line_number_area_width
matrix
=
Matrix
(
transform_matrix_string
=
transform_matrix_string
)
if
matrix
.
getY
()
<
transkription_field
.
ymin
or
matrix
.
getY
()
>
transkription_field
.
ymax
:
return
False
is_part
=
matrix
.
getX
()
<
transkription_field
.
xmin
-
line_number_area_width
\
if
transkription_field
.
is_page_verso
()
\
else
matrix
.
getX
()
>
transkription_field
.
xmax
+
line_number_area_width
return
is_part
@staticmethod
def
IS_IN_PLACE_OF_PRINTING_AREA
(
transform_matrix_string
,
transkription_field
):
"""Returns true if matrix specifies a position that is part of the area where the places of printing ('Druckorte') are printed.
text_node (lxml.etree.Element)
transkription_field (datatypes.transkription_field.TranskriptionField)
"""
matrix
=
Matrix
(
transform_matrix_string
=
transform_matrix_string
)
if
matrix
.
getY
()
<
transkription_field
.
ymax
:
return
False
is_part
=
matrix
.
getX
()
<
transkription_field
.
xmin
\
if
transkription_field
.
is_page_verso
()
\
else
matrix
.
getX
()
<
transkription_field
.
documentWidth
/
4
return
is_part
@staticmethod
def
IS_PART_OF_TRANSKRIPTION_FIELD
(
transkription_field
,
text_node
=
None
,
matrix
=
None
):
"""Returns true if matrix specifies a position that is part of transkription field.
text_node (lxml.etree.Element)
transkription_field (datatypes.transkription_field.TranskriptionField)
"""
if
matrix
is
None
and
not
bool
(
text_node
.
get
(
'transform'
)):
return
False
if
matrix
is
None
:
matrix
=
Matrix
(
transform_matrix_string
=
text_node
.
get
(
'transform'
))
is_part
=
matrix
.
getX
()
>
transkription_field
.
xmin
and
matrix
.
getX
()
<
transkription_field
.
xmax
\
and
matrix
.
getY
()
>
transkription_field
.
ymin
and
matrix
.
getY
()
<
transkription_field
.
ymax
if
not
is_part
and
matrix
.
isRotationMatrix
()
and
len
([
child
.
text
for
child
in
text_node
.
getchildren
()
if
not
re
.
match
(
r'^\s*$'
,
child
.
text
)])
>
0
:
first_tspan_node
=
[
child
for
child
in
text_node
.
getchildren
()
if
not
re
.
match
(
r'^\s*$'
,
child
.
text
)][
0
]
x
=
matrix
.
add2X
(
float
(
first_tspan_node
.
get
(
'x'
)))
y
=
matrix
.
add2Y
(
float
(
first_tspan_node
.
get
(
'y'
)))
new_x
=
matrix
.
get_new_x
(
x
=
x
,
y
=
y
)
new_y
=
matrix
.
get_new_y
(
x
=
x
,
y
=
y
)
return
new_x
>
transkription_field
.
xmin
and
new_x
<
transkription_field
.
xmax
\
and
new_y
>
transkription_field
.
ymin
and
new_y
<
transkription_field
.
ymax
return
is_part
@staticmethod
def
IS_NEARX_TRANSKRIPTION_FIELD
(
transform_matrix_string
,
transkription_field
,
diffx
=
20.0
):
"""Returns true if matrix specifies a position that is on its x axis near the transkription_field.
transform_matrix_string (str): string from which to init Matrix.
transkription_field (svgscripts.TranskriptionField)
diffx (float): defines threshold for positions that count as near.
"""
matrix
=
Matrix
(
transform_matrix_string
=
transform_matrix_string
)
MINLEFT
=
transkription_field
.
xmin
-
diffx
MAXRIGHT
=
transkription_field
.
xmax
+
diffx
return
matrix
.
getY
()
>
transkription_field
.
ymin
and
matrix
.
getY
()
<
transkription_field
.
ymax
\
and
((
matrix
.
getX
()
>
MINLEFT
and
matrix
.
getX
()
<
transkription_field
.
xmin
)
\
or
(
matrix
.
getX
()
>
transkription_field
.
xmax
and
matrix
.
getX
()
<
MAXRIGHT
))
@staticmethod
def
DO_CONVERSION_FACTORS_DIFFER
(
matrix_a
,
matrix_b
,
diff_threshold
=
0.001
):
"""Returns whether the conversion factors (a-d) differ more than diff_threshold.
"""
if
matrix_a
is
None
or
matrix_b
is
None
:
return
not
(
matrix_a
is
None
and
matrix_b
is
None
)
return
abs
(
matrix_a
.
matrix
[
Matrix
.
A
]
-
matrix_b
.
matrix
[
Matrix
.
A
])
>
diff_threshold
\
or
abs
(
matrix_a
.
matrix
[
Matrix
.
B
]
-
matrix_b
.
matrix
[
Matrix
.
B
])
>
diff_threshold
\
or
abs
(
matrix_a
.
matrix
[
Matrix
.
C
]
-
matrix_b
.
matrix
[
Matrix
.
C
])
>
diff_threshold
\
or
abs
(
matrix_a
.
matrix
[
Matrix
.
D
]
-
matrix_b
.
matrix
[
Matrix
.
D
])
>
diff_threshold
Event Timeline
Log In to Comment