Code

Extensions. XAML export improvements.
[inkscape.git] / share / extensions / replace_font.py
1 #!/usr/bin/env python
2 '''
3 replace_font.py
5 Copyright (C) 2010 Craig Marshall, craig9 [at] gmail.com
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21 -----------------------
23 This script finds all fonts in the current drawing that match the
24 specified find font, and replaces them with the specified replacement
25 font.
27 It can also replace all fonts indiscriminately, and list all fonts
28 currently being used.
29 '''
31 import os
32 import sys
33 import inkex
34 import simplestyle
35 import gettext
36 _ = gettext.gettext
38 text_tags = ['{http://www.w3.org/2000/svg}tspan',
39                             '{http://www.w3.org/2000/svg}text',
40                             '{http://www.w3.org/2000/svg}flowRoot',
41                             '{http://www.w3.org/2000/svg}flowPara',
42                             '{http://www.w3.org/2000/svg}flowSpan']
43 font_attributes = ['font-family', '-inkscape-font-specification']
45 def set_font(node, new_font, style=None):
46     '''
47     Sets the font attribute in the style attribute of node, using the
48     font name stored in new_font. If the style dict is open already,
49     it can be passed in, otherwise it will be optned anyway.
51     Returns a dirty boolean flag
52     '''
53     dirty = False
54     if not style:
55         style = get_style(node)
56     if style:
57         for att in font_attributes:
58             if att in style:
59                 style[att] = new_font
60                 set_style(node, style)
61                 dirty = True
62     return dirty
64 def find_replace_font(node, find, replace):
65     '''
66     Searches the relevant font attributes/styles of node for find, and
67     replaces them with replace.
69     Returns a dirty boolean flag
70     '''
71     dirty = False
72     style = get_style(node)
73     if style:
74         for att in font_attributes:
75             if att in style and style[att].strip().lower() == find:
76                 set_font(node, replace, style)
77                 dirty = True
78     return dirty
80 def is_styled_text(node):
81     '''
82     Returns true if the tag in question is a "styled" element that
83     can hold text.
84     '''
85     return node.tag in text_tags and 'style' in node.attrib
87 def is_text(node):
88     '''
89     Returns true if the tag in question is an element that
90     can hold text.
91     '''
92     return node.tag in text_tags
95 def get_style(node):
96     '''
97     Sugar coated way to get style dict from a node
98     '''
99     if 'style' in node.attrib:
100         return simplestyle.parseStyle(node.attrib['style'])
102 def set_style(node, style):
103     '''
104     Sugar coated way to set the style dict, for node
105     '''
106     node.attrib['style'] = simplestyle.formatStyle(style)
108 def get_fonts(node):
109     '''
110     Given a node, returns a list containing all the fonts that
111     the node is using.
112     '''
113     fonts = []
114     s = get_style(node)
115     if not s:
116         return fonts
117     for a in font_attributes:
118         if a in s:
119             fonts.append(s[a])
120     return fonts
122 def die(msg = "Dying!"):
123     inkex.errormsg(msg)
124     sys.exit(0)
126 def report_replacements(num):
127     '''
128     Sends a message to the end user showing success of failure
129     of the font replacement
130     '''
131     if num == 0:
132         die(_('Couldn\'t find anything using that font, please ensure the spelling and spacing is correct.'))
134 def report_findings(findings):
135     '''
136     Tells the user which fonts were found, if any
137     '''
138     if len(findings) == 0:
139         inkex.errormsg(_("Didn't find any fonts in this document/selection."))
140     else:
141         if len(findings) == 1:
142             inkex.errormsg(_("Found the following font only: %s") % findings[0])
143         else:
144             inkex.errormsg(_("Found the following fonts:\n%s") % '\n'.join(findings))
146 class ReplaceFont(inkex.Effect):
147     '''
148     Replaces all instances of one font with another
149     '''
150     def __init__(self):
151         inkex.Effect.__init__(self)
152         self.OptionParser.add_option("--fr_find", action="store",
153                                         type="string", dest="fr_find",
154                                         default=None, help="")
156         self.OptionParser.add_option("--fr_replace", action="store",
157                                         type="string", dest="fr_replace",
158                                         default=None, help="")
160         self.OptionParser.add_option("--r_replace", action="store",
161                                         type="string", dest="r_replace",
162                                         default=None, help="")
164         self.OptionParser.add_option("--action", action="store",
165                                         type="string", dest="action",
166                                         default=None, help="")
168         self.OptionParser.add_option("--scope", action="store",
169                                         type="string", dest="scope",
170                                         default=None, help="")
172     def find_child_text_items(self, node):
173         '''
174         Recursive method for appending all text-type elements
175         to self.selected_items
176         '''
177         if is_text(node):
178             self.selected_items.append(node)
179             for child in node:
180                 self.find_child_text_items(child)
182     def relevant_items(self, scope):
183         '''
184         Depending on the scope, returns all text elements, or all
185         selected text elements including nested children
186         '''
187         items = []
188         to_return = []
189         if scope == "selection_only":
190             self.selected_items = []
191             for item in self.selected.iteritems():
192                 self.find_child_text_items(item[1])
193             items = self.selected_items
194             if len(items) == 0:
195                 die(_("There was nothing selected"))
196         else:
197             items = self.document.getroot().getiterator()
198         to_return.extend(filter(is_text, items))
199         return to_return
201     def find_replace(self, nodes, find, replace):
202         '''
203         Walks through nodes, replacing fonts as it goes according
204         to find and replace
205         '''
206         replacements = 0
207         for node in nodes:
208             if find_replace_font(node, find, replace):
209                 replacements += 1
210         report_replacements(replacements)
212     def replace_all(self, nodes, replace):
213         '''
214         Walks through nodes, setting fonts indiscriminately.
215         '''
216         replacements = 0
217         for node in nodes:
218             if set_font(node, replace):
219                 replacements += 1
220         report_replacements(replacements)
222     def list_all(self, nodes):
223         '''
224         Walks through nodes, building a list of all fonts found, then
225         reports to the user with that list
226         '''
227         fonts_found = []
228         for node in nodes:
229             for f in get_fonts(node):
230                 if not f in fonts_found:
231                     fonts_found.append(f)
232         report_findings(sorted(fonts_found))
234     def effect(self):
235         action = self.options.action.strip("\"") # TODO Is this a bug? (Extra " characters)
236         scope = self.options.scope
238         relevant_items = self.relevant_items(scope)
240         if action == "find_replace":
241             find = self.options.fr_find
242             if find is None or find == "":
243                 die(_("Please enter a search string in the find box."));
244             find = find.strip().lower()
245             replace = self.options.fr_replace
246             if replace is None or replace == "":
247                 die(_("Please enter a replacement font in the replace with box."));
248             self.find_replace(relevant_items, find, replace)
249         elif action == "replace_all":
250             replace = self.options.r_replace
251             if replace is None or replace == "":
252                 die(_("Please enter a replacement font in the replace all box."));
253             self.replace_all(relevant_items, replace)
254         elif action == "list_only":
255             self.list_all(relevant_items)
256             sys.exit(0)
258 if __name__ == "__main__":
259     e = ReplaceFont()
260     e.affect()