summaryrefslogtreecommitdiffstats
path: root/share/extensions/replace_font.py
blob: 37b5bde63fbf335a1d51adc188482f49fdb7b00b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#!/usr/bin/env python
# coding=utf-8
#
# Copyright (C) 2010 Craig Marshall, craig9 [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; 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
#
"""
This script finds all fonts in the current drawing that match the
specified find font, and replaces them with the specified replacement
font.

It can also replace all fonts indiscriminately, and list all fonts
currently being used.
"""
import inkex
from inkex.localization import inkex_gettext as _

text_tags = [
    "{http://www.w3.org/2000/svg}tspan",
    "{http://www.w3.org/2000/svg}text",
    "{http://www.w3.org/2000/svg}flowRoot",
    "{http://www.w3.org/2000/svg}flowPara",
    "{http://www.w3.org/2000/svg}flowSpan",
]
font_attributes = ["font-family", "-inkscape-font-specification"]


def set_font(node, new_font, style=None):
    """
    Sets the font attribute in the style attribute of node, using the
    font name stored in new_font. If the style dict is open already,
    it can be passed in, otherwise it will be optned anyway.

    Returns a dirty boolean flag
    """
    dirty = False
    if not style:
        style = node.style
    if style:
        for att in font_attributes:
            if att in style:
                style[att] = new_font
                node.style = style
                dirty = True
    return dirty


def find_replace_font(node, find, replace):
    """
    Searches the relevant font attributes/styles of node for find, and
    replaces them with replace.

    Returns a dirty boolean flag
    """
    dirty = False
    style = node.style
    if style:
        for att in font_attributes:
            if style(att).strip().lower() == find:
                set_font(node, replace, style)
                dirty = True
    return dirty


def is_styled_text(node):
    """
    Returns true if the tag in question is a "styled" element that
    can hold text.
    """
    return node.tag in text_tags and "style" in node.attrib


def is_text(node):
    """
    Returns true if the tag in question is an element that
    can hold text.
    """
    return node.tag in text_tags


def get_fonts(node):
    """
    Given a node, returns a list containing all the fonts that
    the node is using.
    """
    fonts = []
    s = node.style
    if not s:
        return fonts
    for a in font_attributes:
        if a in s:
            fonts.append(s[a])
    return fonts


def report_replacements(num):
    """
    Sends a message to the end user showing success of failure
    of the font replacement
    """
    if num == 0:
        inkex.errormsg(
            _(
                "Couldn't find anything using that font, please ensure the spelling "
                "and spacing is correct."
            )
        )


def report_findings(findings):
    """
    Tells the user which fonts were found, if any
    """
    if len(findings) == 0:
        inkex.errormsg(_("Didn't find any fonts in this document/selection."))
    else:
        if len(findings) == 1:
            inkex.errormsg(_("Found the following font only: %s") % findings[0])
        else:
            inkex.errormsg(_("Found the following fonts:\n%s") % "\n".join(findings))


class ReplaceFont(inkex.EffectExtension):
    """
    Replaces all instances of one font with another
    """

    def add_arguments(self, pars):
        pars.add_argument("--fr_find")
        pars.add_argument("--fr_replace")
        pars.add_argument("--r_replace")
        pars.add_argument("--action")
        pars.add_argument("--scope")

    def find_child_text_items(self, node):
        """
        Recursive method for appending all text-type elements
        to self.selected_items
        """
        if is_text(node):
            yield node

        for child in node:
            for textchild in self.find_child_text_items(child):
                yield textchild

    def relevant_items(self, scope):
        """
        Depending on the scope, returns all text elements, or all
        selected text elements including nested children
        """
        items = []
        to_return = []

        selected = self.svg
        if scope == "selection_only":
            selected = self.svg.selection.values()

        for item in selected:
            items.extend(self.find_child_text_items(item))

        if not items:
            return inkex.errormsg(_("There was nothing selected"))

        return items

    def find_replace(self, nodes, find, replace):
        """
        Walks through nodes, replacing fonts as it goes according
        to find and replace
        """
        replacements = 0
        for node in nodes:
            if find_replace_font(node, find, replace):
                replacements += 1
        report_replacements(replacements)

    def replace_all(self, nodes, replace):
        """
        Walks through nodes, setting fonts indiscriminately.
        """
        replacements = 0
        for node in nodes:
            if set_font(node, replace):
                replacements += 1
        report_replacements(replacements)

    def list_all(self, nodes):
        """
        Walks through nodes, building a list of all fonts found, then
        reports to the user with that list
        """
        fonts_found = []
        for node in nodes:
            for f in get_fonts(node):
                if not f in fonts_found:
                    fonts_found.append(f)
        report_findings(sorted(fonts_found))

    def effect(self):
        if not self.options.action:
            return inkex.errormsg(_("Nothing to do, no action specified."))
        action = self.options.action.strip(
            '"'
        )  # TODO Is this a bug? (Extra " characters)
        scope = self.options.scope

        relevant_items = self.relevant_items(scope)

        if action == "find_replace":
            find = self.options.fr_find
            if find is None or find == "":
                return inkex.errormsg(
                    _("Please enter a search string in the find box.")
                )
            find = find.strip().lower()
            replace = self.options.fr_replace
            if replace is None or replace == "":
                return inkex.errormsg(
                    _("Please enter a replacement font in the replace with box.")
                )
            self.find_replace(relevant_items, find, replace)
        elif action == "replace_all":
            replace = self.options.r_replace
            if replace is None or replace == "":
                return inkex.errormsg(
                    _("Please enter a replacement font in the replace all box.")
                )
            self.replace_all(relevant_items, replace)
        elif action == "list_only":
            self.list_all(relevant_items)


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