added stroke font creator

This commit is contained in:
Mario Voigt 2021-10-16 01:08:58 +02:00
parent e423e1e7ea
commit a6f397a8f8
40 changed files with 2319 additions and 0 deletions

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Custom Stroke Font - Edit Stroke Font</name>
<id>fablabchemnitz.de.stroke_font_creator.edit_stroke_font</id>
<param name="tab" type="notebook">
<page name="editStrokeFont" gui-text="Edit Stroke Font">
<param name="fontName" type="optiongroup" appearance="combo" gui-text="Font:">
<option value="Custom-Script">Custom-Script</option>
<option value="Custom-Square Italic">Custom-Square Italic</option>
<option value="Custom-Square Normal">Custom-Square Normal</option>
<option value="Hershey-Astrology">Hershey-Astrology</option>
<option value="Hershey-Cyrillic">Hershey-Cyrillic</option>
<option value="Hershey-Gothic English">Hershey-Gothic English</option>
<option value="Hershey-Gothic German">Hershey-Gothic German</option>
<option value="Hershey-Gothic Italian">Hershey-Gothic Italian</option>
<option value="Hershey-Greek 1-stroke">Hershey-Greek 1-stroke</option>
<option value="Hershey-Greek medium">Hershey-Greek medium</option>
<option value="Hershey-Japanese">Hershey-Japanese</option>
<option value="Hershey-Markers">Hershey-Markers</option>
<option value="Hershey-Math (lower)">Hershey-Math (lower)</option>
<option value="Hershey-Math (upper)">Hershey-Math (upper)</option>
<option value="Hershey-Meteorology">Hershey-Meteorology</option>
<option value="Hershey-Music">Hershey-Music</option>
<option value="Hershey-Sans 1-stroke">Hershey-Sans 1-stroke</option>
<option value="Hershey-Sans bold">Hershey-Sans bold</option>
<option value="Hershey-Script 1-stroke">Hershey-Script 1-stroke</option>
<option value="Hershey-Script 1-stroke (alt)">Hershey-Script 1-stroke (alt)</option>
<option value="Hershey-Script medium">Hershey-Script medium</option>
<option value="Hershey-Serif bold">Hershey-Serif bold</option>
<option value="Hershey-Serif bold italic">Hershey-Serif bold italic</option>
<option value="Hershey-Serif medium">Hershey-Serif medium</option>
<option value="Hershey-Serif medium italic">Hershey-Serif medium italic</option>
<option value="Hershey-Symbolic">Hershey-Symbolic</option>
</param>
<param name="rowCnt" type="int" min="1" max="999999" gui-text="No of Rows:">5</param>
<param name="fontSize" type="int" min="5" max="999999" gui-text="Font Size:">1000</param>
<label>This extension overwrites the current document</label>
</page>
<page name="desc" gui-text="Help">
<label xml:space="preserve">Inkscape extension for editing a stroke font.</label>
</page>
</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Text"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">edit_stroke_font.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
'''
Inkscape extension to edit a stroke font
Dependencies: stroke_font_common.py and stroke_font_manager.py
Copyright (C) 2019 Shrinivas Kulkarni
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''
import inkex
from inkex import Effect, addNS
import sys, os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from stroke_font_common import CommonDefs, InkscapeCharDataFactory, createTempl, getAddFnTypes
from stroke_font_common import getTranslatedPath, formatStyle, getEtree, runEffect
from stroke_font_manager import FontData, xPath, xGlyphName
class EditStrokeFont(Effect):
def __init__(self):
Effect.__init__(self)
addFn, typeFloat, typeInt, typeString, typeBool = getAddFnTypes(self)
addFn( "--fontName", action = "store", type = typeString, dest = "fontName", \
default = 'Script', help = "The custom font to edit")
addFn('--rowCnt', action = 'store', type = typeInt, dest = 'rowCnt', \
default = '5', help = 'Number of rows (horizontal guides) in the template')
addFn('--fontSize', action = 'store', type = typeInt, dest = 'fontSize', \
default = '100', help = 'Size of the source glyphs to be rendered')
addFn("--tab", action = "store", type = typeString, dest = "tab", \
default = "sampling", help = "Tab")
def addElem(self, templLayer, editLayer, glyphIdx, posX, posY):
char = self.fontChars[glyphIdx]
charData = self.strokeFontData.glyphMap[char]
d = getTranslatedPath(charData.pathStr, posX, posY)
attribs = {'id':char, 'style':formatStyle(self.charStyle), \
xPath:d, xGlyphName: charData.glyphName}
getEtree().SubElement(editLayer, addNS('path','svg'), attribs)
return charData.rOffset
def effect(self):
rowCnt = self.options.rowCnt
fontName = self.options.fontName
fontSize = self.options.fontSize
lineT = CommonDefs.lineT * fontSize
strokeWidth = 0.02 * fontSize
self.charStyle = { 'stroke': '#000000', 'fill': 'none', \
'stroke-width':strokeWidth, 'stroke-linecap':'round', \
'stroke-linejoin':'round'}
vgScaleFact = CommonDefs.vgScaleFact
extPath = os.path.dirname(os.path.abspath(__file__))
self.strokeFontData = FontData(extPath, fontName, fontSize, \
InkscapeCharDataFactory())
self.fontChars = sorted(self.strokeFontData.glyphMap.keys())
glyphCnt = len(self.fontChars)
createTempl(self.addElem, self, self.strokeFontData.extraInfo, rowCnt, \
glyphCnt, vgScaleFact, True, lineT)
runEffect(EditStrokeFont())

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Custom Stroke Font - Generate Font Data</name>
<id>fablabchemnitz.de.stroke_font_creator.stroke_font_templ</id>
<param name="tab" type="notebook">
<page name="genStrokeFontData" gui-text="Generate Font Data">
<param name="fontName" type="string" gui-text="Font Name:" />
<param name="rightOffsetType" type="optiongroup" appearance="combo" gui-text="Right Offset:">
<option value="vgBbox">Vertical Guide / Bounding Box</option>
<option value="lastNode">Main Segment Last Node</option>
</param>
<param name="crInfo" type="string" gui-text="Copyright Info:" />
<param name="spaceWidth" type="float" min="0" max="999999" gui-text="Space Width (Enter 0 If Not Changed):">0</param>
</page>
<page name="desc" gui-text="Help">
<label xml:space="preserve">Inkscape extension to generate the data of the stroke font glyphs from the current SVG.
The data generated by this effect is used by the 'Render Text' extension.
Pre-requisite: This SVG should be created with the 'Create Font Design Template' extension
The glyph bottom extent is derived from the reference horizontal guide(s)
Based on the option selected, the Right offset of the glyph will be either:
i) based on the right verical guide (if it exists) or bounding box or
ii) derived from the x coordinate of the last node of the main segment
in the glyph path (useful in case of script fonts)
Enter 0 in Space Width field if you don't want to change the existing value.</label>
</page>
</param>
<effect needs-live-preview="false">
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Text"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">gen_stroke_font_data.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,268 @@
#!/usr/bin/env python3
'''
Inkscape extension to generate the data for the stroke font glyphs
designed in the current SVG. The current SVG must be generated with the
'Create Font Design Template' extension
The data generated by this effect is used by the 'Render Text' extension,
to render text with the selected stroke font.
Copyright (C) 2019 Shrinivas Kulkarni
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''
import inkex, sys, os, re, math
from bezmisc import bezierlengthSimpson
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from stroke_font_common import InkscapeCharData, CommonDefs, runEffect, getCubicSuperPath, \
InkscapeCharDataFactory, syncFontList, getAddFnTypes, getParsedPath, getTransformMat, \
applyTransform, getCubicBoundingBox, formatSuperPath, getCubicLength, getCubicSuperPath
from stroke_font_manager import FontData, xGlyphName, xAscent, \
xDescent, xCapHeight, xXHeight, xSpaceROff, xFontId, xSize, getDefaultExtraInfo
def getNearestGuide(guides, minVal, coIdx, hSeq = None):
if(guides == None or len(guides) == 0):
return None, None
if(hSeq != None):
guides = [g for g in guides if int(g[0].get(CommonDefs.idAttribName).split('_')[1]) == hSeq]
if(len(guides) == 1):
return guides[0]
for i, guide in enumerate(guides):
pp = guide[1]
#pp format [['M',[x1,y1]],['L',[x2,y2]]]
diff = abs(pp[0][1][coIdx] - minVal)
if(i > 0 and diff > minDiff):
return guides[i-1]
minDiff = diff
return guides[-1]
def getFontSizeFromGuide(pp, vgScaleFact):
#pp format [['M',[x1,y1]],['L',[x2,y2]]]
lHeight = abs(float(pp[1][1][1]) - pp[0][1][1])
return round(lHeight / vgScaleFact, 2)
#Apply transform attribute (only straight lines, no arcs etc.)
def transformedParsedPath(elem):
pp = getParsedPath(elem.get('d'))
return pp
# Following bloc takes care of the special condition where guides are transformed
# TODO: Make it working for 1.0
# ~ try:
# ~ transf = elem.get('transform')
# ~ mat = parseTransform(transf)
# ~ #pp format [['M',[x1,y1]],['L',[x2,y2]]]
# ~ for dElem in pp:
# ~ for i in range(1, len(dElem)):
# ~ param = dElem[i]
# ~ t1 = [[param[x], param[x+1]] for x in range(0, len(param), 2)]
# ~ for t1Elem in t1:
# ~ simpletransform.applyTransformToPoint(mat, t1Elem)
# ~ dElem[i] = [x for l in t1 for x in l]
# ~ elem.set('d', simplepath.formatPath(pp))
# ~ except:
# ~ #Don't break
# ~ pass
# ~ return pp
def updateFontData(strokeFontData, glyphPathElems, hGuides, lvGuides, rvGuides, rightOffsetType):
for elem in glyphPathElems:
char = elem.get(CommonDefs.idAttribName)
path = getCubicSuperPath(elem.get('d'))
glyphName = elem.get(xGlyphName)
if(glyphName == None): glyphName = char
#Just in case...
transf = elem.get('transform')
mat = getTransformMat(transf)
applyTransform(mat, path)
xmin, xmax, ymin, ymax = getCubicBoundingBox(path)
#Nearest to the bottom (ymax)
hg = getNearestGuide(hGuides, ymax, 1)
hseq = int(hg[0].get(CommonDefs.idAttribName).split('_')[1])
hgp = hg[1]
#hgp format: [['M',[x1,y1]],['H',[x2,y2]]]
hgY = hgp[0][1][1]
#Nearest to the left edge (xmin)
lvg = getNearestGuide(lvGuides, xmin, 0, hseq)
lvgp = lvg[1]
#lvgp format: [['M',[x1,y1]],['V',[x2,y2]]]
lvgX = lvgp[0][1][0]
rvgX = None
if(rvGuides != None and len(rvGuides) > 0):
#Nearest to the right edge (xmax)
rvg = getNearestGuide(rvGuides, xmax, 0, hseq)
rvgp = rvg[1]
#rvgp format: [['M',[x1,y1]],['V',[x2,y2]]]
rvgX = rvgp[0][1][0]
npath = getCubicSuperPath()
maxLenSp = None
maxSpLen = 0
for subpath in path:
nsub = []
spLen = 0
for seg in subpath:
nseg = []
for pt in seg:
x = round(pt[0] - lvgX, 2)
y = round(pt[1] - hgY, 2)
nseg.append([round(x, 4), round(y, 4)])
nsub.append(nseg)
npath.append(nsub)
#Calculate length only if needed
if(rightOffsetType == 'lastNode'):
spLen = getCubicLength(npath)
if(spLen > maxSpLen):
maxSpLen = spLen
maxLenSp = subpath
if(rightOffsetType == 'lastNode'):
lastNode = maxLenSp[-1][-1]
rOffset = lastNode[0] - lvgX
elif(rvgX != None):
rOffset = rvgX - lvgX
else:
rOffset = xmax - lvgX
rOffset = round(rOffset, 2)
pathStr = formatSuperPath(npath)
strokeFontData.updateGlyph(char, rOffset, pathStr, glyphName)
class GenStrokeFontData(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
addFn, typeFloat, typeInt, typeString, typeBool = getAddFnTypes(self)
addFn('--fontName', action = 'store', type = typeString, dest = 'fontName', \
help = 'Name of the font to be created')
addFn('--rightOffsetType', action = 'store', type = typeString, \
dest = 'rightOffsetType', help = 'Calculation of the right offset of the glyph')
addFn('--spaceWidth', action = 'store', type = typeFloat, dest = 'spaceWidth', \
help = 'Space width (enter only if changed')
addFn('--crInfo', action = 'store', type = typeString, dest = 'crInfo', \
help = 'Copyright and license details')
addFn("--tab", action = "store", type = typeString, dest = "tab", \
default = "sampling", help = "Tab")
def getGuides(self, idName, idVal):
return [(pn, transformedParsedPath(pn)) for pn in self.document.xpath('//svg:path', \
namespaces = inkex.NSS) if pn.get(idName) != None and \
pn.get(idName).startswith(idVal)]
def getFontExtraInfo(self):
info = {}
nodes = [node for node in self.document.xpath('//svg:' + CommonDefs.fontOtherInfo, \
namespaces = inkex.NSS)]
if(len(nodes) > 0):
try:
node = nodes[0]
info[xAscent] = float(node.get(xAscent))
info[xDescent] = float(node.get(xDescent))
info[xCapHeight] = float(node.get(xCapHeight))
info[xXHeight] = float(node.get(xXHeight))
info[xSpaceROff] = float(node.get(xSpaceROff))
info[xSize] = float(node.get(xSize))
info[xFontId] = node.get(xFontId)
return info
except:
pass
return None
def effect(self):
fontName = self.options.fontName
rightOffsetType = self.options.rightOffsetType
crInfo = self.options.crInfo
spaceWidth = self.options.spaceWidth
#Guide is a tuple of xml elem and parsed path
hGuides = self.getGuides(CommonDefs.idAttribName, CommonDefs.hGuideIDPrefix)
hGuides = sorted(hGuides,
key = lambda p: int(p[0].get(CommonDefs.idAttribName).split('_')[1]))
lvGuides = self.getGuides(CommonDefs.idAttribName, CommonDefs.lvGuideIDPrefix)
lvGuides = sorted(lvGuides, key = lambda p: (p[0].get(CommonDefs.idAttribName)))
rvGuides = self.getGuides(CommonDefs.idAttribName, CommonDefs.rvGuideIDPrefix)
rvGuides = sorted(rvGuides, key = lambda p: (p[0].get(CommonDefs.idAttribName)))
if(len(lvGuides) == 0 or len(hGuides) == 0):
inkex.errormsg("Missing guides. Please use the Create Font Design " + \
"Template extension to design the font.")
return
extraInfo = self.getFontExtraInfo()
if(extraInfo == None):
fontSize = getFontSizeFromGuide(lvGuides[0][1], CommonDefs.vgScaleFact)
extraInfo = getDefaultExtraInfo(fontName, fontSize)
fontSize = extraInfo[xSize]
if(round(spaceWidth, 1) == 0):
spaceWidth = extraInfo[xSpaceROff]
if(round(spaceWidth, 1) == 0): spaceWidth = fontSize / 2
extraInfo[xSpaceROff] = spaceWidth
extPath = os.path.dirname(os.path.abspath(__file__))
strokeFontData = FontData(extPath, fontName, fontSize, InkscapeCharDataFactory())
strokeFontData.setCRInfo(crInfo)
strokeFontData.setExtraInfo(extraInfo)
glyphPaths = [p for p in self.document.xpath('//svg:path', namespaces=inkex.NSS) \
if (len(p.get(CommonDefs.idAttribName)) == 1)]
updateFontData(strokeFontData, glyphPaths, hGuides, lvGuides, rvGuides, rightOffsetType)
strokeFontData.updateFontXML()
syncFontList(extPath)
try:
runEffect(GenStrokeFontData())
except:
inkex.errormsg('The data was not generated due to an error. ' + \
'If you are creating non-english glyphs then save the document, re-open and' + \
'try generating the font data once again.')

View File

@ -0,0 +1,20 @@
[
{
"name": "Custom Stroke Font - <various>",
"id": "fablabchemnitz.de.stroke_font_creator.<various>",
"path": "stroke_font_creator",
"original_name": "<various>",
"original_id": "khema.stroke.fnt.gen.<various>",
"license": "GNU GPL v2",
"license_url": "https://github.com/Shriinivas/inkscapestrokefont/blob/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/stroke_font_creator",
"fork_url": "https://github.com/Shriinivas/inkscapestrokefont",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Stroke+Font+Creator",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/Shriinivas",
"github.com/vmario89"
]
}
]

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Custom Stroke Font - Render Text</name>
<id>fablabchemnitz.de.stroke_font_creator.render_text</id>
<param name="tab" type="notebook">
<page name="splash" gui-text="Render Text">
<param name="text" type="string" gui-text="Text:" />
<param name="filePath" type="string" gui-text="Text File:" />
<param name="action" type="optiongroup" appearance="combo" gui-text="Action: ">
<option value="renderText">Render the text</option>
<option value="renderFile">Render text from file</option>
<option value="renderTable">Render font glyph table</option>
</param>
<param name="fontName" type="optiongroup" appearance="combo" gui-text="Font:">
<option value="Custom-Script">Custom-Script</option>
<option value="Custom-Square Italic">Custom-Square Italic</option>
<option value="Custom-Square Normal">Custom-Square Normal</option>
<option value="Hershey-Astrology">Hershey-Astrology</option>
<option value="Hershey-Cyrillic">Hershey-Cyrillic</option>
<option value="Hershey-Gothic English">Hershey-Gothic English</option>
<option value="Hershey-Gothic German">Hershey-Gothic German</option>
<option value="Hershey-Gothic Italian">Hershey-Gothic Italian</option>
<option value="Hershey-Greek 1-stroke">Hershey-Greek 1-stroke</option>
<option value="Hershey-Greek medium">Hershey-Greek medium</option>
<option value="Hershey-Japanese">Hershey-Japanese</option>
<option value="Hershey-Markers">Hershey-Markers</option>
<option value="Hershey-Math (lower)">Hershey-Math (lower)</option>
<option value="Hershey-Math (upper)">Hershey-Math (upper)</option>
<option value="Hershey-Meteorology">Hershey-Meteorology</option>
<option value="Hershey-Music">Hershey-Music</option>
<option value="Hershey-Sans 1-stroke">Hershey-Sans 1-stroke</option>
<option value="Hershey-Sans bold">Hershey-Sans bold</option>
<option value="Hershey-Script 1-stroke">Hershey-Script 1-stroke</option>
<option value="Hershey-Script 1-stroke (alt)">Hershey-Script 1-stroke (alt)</option>
<option value="Hershey-Script medium">Hershey-Script medium</option>
<option value="Hershey-Serif bold">Hershey-Serif bold</option>
<option value="Hershey-Serif bold italic">Hershey-Serif bold italic</option>
<option value="Hershey-Serif medium">Hershey-Serif medium</option>
<option value="Hershey-Serif medium italic">Hershey-Serif medium italic</option>
<option value="Hershey-Symbolic">Hershey-Symbolic</option>
<!-- ##! dynamically generated portion [end] -->
</param>
<param name="fontSize" type="float" min="1" max="999999" gui-text="Font Size:">20</param>
<param name="charSpacing" type="float" min="0" max="100" gui-text="Char Spacing:">1</param>
<param name="wordSpacing" type="float" min="0" max="100" gui-text="Word Spacing:">1</param>
<param name="lineSpacing" type="float" min="0" max="100" gui-text="Line Spacing:">1.5</param>
<param name="flowInBox" type="bool" gui-text="Flow Text in Selected Boxes:">true</param>
<param name="margin" type="float" min="-999999" max="999999" gui-text="Margin:">5</param>
<param name="hAlignment" type="optiongroup" appearance="combo" gui-text="Horizontal Alignment:">
<option value="left">Left</option>
<option value="right">Right</option>
<option value="center">Center</option>
<option value="justified">Justified</option>
</param>
<param name="vAlignment" type="optiongroup" appearance="combo" gui-text="Vertical Alignment:">
<option value="none">None</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
<option value="center">Center</option>
</param>
<param name="expandDir" type="optiongroup" appearance="combo" gui-text="Create Extended Rectangles:">
<option value="none">None</option>
<option value="x">Horizontal Direction</option>
<option value="y">Vertical Direction</option>
</param>
<param name="expandDist" type="float" min="0" max="100" gui-text="Extended Rectangle Offset:">1</param>
</page>
<page name="info" gui-text="Help">
<label xml:space="preserve">This extension renders given text using the selected stroke font.
Action can be one of the following:
- 'Render the text' renders the text from the Text input box
(Use \n in the input string to start a new line of text)
- 'Render text from file' renders the text from the file specified in the Text File input box
- 'Render font glyph table' displays all the available glyphs of the given font
along with the license information
If the 'Flow Text in Selected Boxes' option is selected, the text is fit into the selected rectangle objects with
specified margin and justification. The rectangles are filled in their z order. If a single word cannot fit within the
given width, it's broken into smaller components.
If there are errors, please ensure the font data files exist in the strokefontdata folder and
the font list is synchronized.</label>
</page>
</param>
<effect needs-live-preview="true" needs-document="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Text"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">render_stroke_font_text.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""
Inkscape extension to render text with the stroke fonts.
The path data used to render the text is generated by the
'Generate Font Data' extension
The original concept is from Hershey Text extension (Copyright 2011, Windell H. Oskay),
that comes bundled with Inkscape
This tool extends it with a number of rendering options like flow in boxes,
text alignment and char & line spacing
Copyright 2019 Shrinivas Kulkarni
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
from inkex import addNS, NSS, Effect, errormsg
import os, sys
from simplepath import parsePath, translatePath, formatPath
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from stroke_font_common import CommonDefs, InkscapeCharDataFactory, getEtree, getAddFnTypes
from stroke_font_common import computePtInNode, getDecodedChars
from stroke_font_common import getViewCenter, runEffect, getSelectedElements
from stroke_font_common import formatStyle, getCharStyle, getTranslatedPath, getCurrentLayer
from stroke_font_manager import DrawContext
class InkscapeFontRenderer:
def __init__(self, layer, vc, strokeWidth):
self.layer = layer
self.g = None
self.currG = None
self.vc = computePtInNode(vc, layer)
self.strokeWidth = strokeWidth
self.box = None
def renderChar(self, charData, x, y, naChar):
d = charData.pathStr
style = getCharStyle(self.strokeWidth, naChar)
d = getTranslatedPath(d, x, y)
attribs = {'style':formatStyle(style), 'd':d}
getEtree().SubElement(self.currG, addNS('path','svg'), attribs)
def beforeRender(self):
self.g = getEtree().SubElement(self.layer, 'g')
self.currG = self.g
def newBoxToBeRendered(self, box, addPlane):
boxG = getEtree().SubElement(self.layer, 'g')
if('transform' in box.attrib):
boxG.set('transform', box.get('transform'))
box.getparent().remove(box)
# order important!
self.g.append(box)
self.g.append(boxG)
self.currG = boxG
self.box = box
def moveBoxInYDir(self, moveBy):
t = 'translate(' + str(0) + ',' + str(moveBy) + ')'
self.currG.set('transform', t)
def centerInView(self, width, height):
t = 'translate(' + str(self.vc[0] - width) + ',' + str(self.vc[1] - height) + ')'
self.g.set('transform', t)
def renderPlainText(self, text, size, x = None, y = None, objName = None):
textStyle = {'fill':'#000000', 'fill-opacity':'1'}
textStyle['font-size'] = str(size)
attribs = {'style':formatStyle(textStyle)}
if(x != None and y != None):
attribs['transform'] = 'translate(' + str(x) + ', '+ str(y)+')'
textElem = getEtree().SubElement(self.g, addNS('text','svg'), attribs)
textElem.text = text
def getBoxLeftTopRightBottom(self, box):
x1 = float(box.get('x'))
y1 = float(box.get('y'))
w = float(box.get('width'))
h = float(box.get('height'))
x2 = w + x1
y2 = h + y1
return x1, y1, x2, y2
def getBoxFromCoords(self, x1, y1, x2, y2):
attribs = {'x':str(x1), 'y':str(y1), 'width':str(x2-x1), 'height':str(y2-y1)}
attribs['style'] = self.box.get('style')
self.box = getEtree().SubElement(self.layer, addNS('rect','svg'), attribs)
return self.box
def getDefaultStartLocation(self):
return 0, 0
class RenderStrokeFontText(Effect):
def __init__( self ):
Effect.__init__( self )
addFn, typeFloat, typeInt, typeString, typeBool = getAddFnTypes(self)
addFn( '--tab', action = 'store', type = typeString, dest = 'tab', \
default = 'splash', help = 'The active tab when Apply was pressed')
addFn( '--action', action = 'store', type = typeString, dest = 'action', \
default = 'render', help = 'The active option when Apply was pressed' )
addFn( '--text', action = 'store', type = typeString, dest = 'text', \
default = 'Hello World', help = 'The input text to render')
addFn( '--filePath', action = 'store', type = typeString, dest = 'filePath', \
default = '', help = 'Complete path of the text file')
addFn( '--fontName', action = 'store', type = typeString, dest = 'fontName', \
default = 'Script', help = 'The custom font to be used for rendering')
addFn('--fontSize', action = 'store', type = typeFloat, dest = 'fontSize', \
default = '100', help = 'Size of the font')
addFn('--charSpacing', action = 'store', type = typeFloat, dest = 'charSpacing', \
default = '1', help = 'Spacing between characters')
addFn('--wordSpacing', action = 'store', type = typeFloat, dest = 'wordSpacing', \
default = '1', help = 'Spacing between words')
addFn('--lineSpacing', action = 'store', type = typeFloat, dest = 'lineSpacing', \
default = '1.5', help = 'Spacing between the lines')
addFn('--flowInBox', action = 'store', type = typeBool, dest = 'flowInBox', \
default = False, help = 'Fit the text in the selected rectangle objects')
addFn('--margin', action = 'store', type = typeFloat, dest = 'margin', default = '.1', \
help = 'Inside margin of text within the box')
addFn( '--hAlignment', action='store', type = typeString, dest = 'hAlignment', \
default = 'left', help='Horizontal text alignment within the box')
addFn( '--vAlignment', action='store', type = typeString, dest = 'vAlignment', \
default = 'none', help='Vertical text alignment within the box')
addFn( '--expandDir', action='store', type = typeString, dest = 'expandDir', \
default = 'x', help='Create new rectangles if text doesn\'t fit the selected ones')
addFn('--expandDist', action = 'store', type = typeFloat, dest = 'expandDist', \
default = True, help = 'Offset distance between the newly created rectangles')
def effect(self):
fontName = self.options.fontName
fontSize = self.options.fontSize
filePath = self.options.filePath
action = self.options.action
if(action == "renderTable"):
charSpacing = 1
wordSpacing = 1
lineSpacing = 2
else:
charSpacing = self.options.charSpacing
wordSpacing = self.options.wordSpacing
lineSpacing = self.options.lineSpacing
flowInBox = self.options.flowInBox
margin = self.options.margin
hAlignment = self.options.hAlignment
vAlignment = self.options.vAlignment
expandDir = self.options.expandDir
expandDist = self.options.expandDist
if(expandDir == 'none'):
expandDir = None
expandDist = None
extPath = os.path.dirname(os.path.abspath(__file__))
strokeWidth = 0.02 * fontSize
layer = getCurrentLayer(self)
renderer = InkscapeFontRenderer(layer, getViewCenter(self), strokeWidth)
context = DrawContext(extPath, fontName, fontSize, \
charSpacing, wordSpacing, lineSpacing, InkscapeCharDataFactory(), renderer)
if(not context.fontHasGlyphs()):
errormsg('No font data; please select a font and ' + \
'ensure that the list is synchronized')
return
if(action == "renderTable"):
context.renderGlyphTable()
return
if(action == "renderText"):
text = self.options.text
text = text.replace('\\n','\n').replace('\\\n','\\n')
text = getDecodedChars(text)
elif(action == "renderFile"):
try:
readmode = 'rU' if CommonDefs.pyVer == 2 else 'r'
with open(filePath, readmode) as f:
text = f.read()
if(CommonDefs.pyVer == 2): text = unicode(text, 'utf-8')
except Exception as e:
errormsg("Error reading the file specified in Text File input box."+ str(e))
return
if(text[0] == u'\ufeff'):
text = text[1:]
if(flowInBox == True):
selElems = getSelectedElements(self)
selIds = [selElems[key].get('id') for key in selElems.keys()]
rectNodes = [r for r in self.document.xpath('//svg:rect', \
namespaces=NSS) if r.get('id') in selIds]
if(len(rectNodes) == 0):
errormsg(_("No rectangle objects selected."))
return
context.renderCharsInSelBoxes(text, rectNodes, margin, hAlignment, vAlignment, \
False, expandDir, expandDist)
else:
context.renderCharsWithoutBox(text)
runEffect(RenderStrokeFontText())

View File

@ -0,0 +1,428 @@
#!/usr/bin/env python3
'''
Defintion of Common functions and variables used by stroke font extensions
Copyright (C) 2019 Shrinivas Kulkarni
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''
import sys, os, fileinput, re, locale
from inkex import errormsg, addNS, NSS
from xml.dom.minidom import parse, Document
from math import ceil
# TODO: Find inkscape version
try:
from lxml import etree
from inkex import Style, Boolean
from inkex.paths import Path, CubicSuperPath, Transform
from inkex import bezier
ver = 1.0
except:
from inkex import etree
import simplestyle, cubicsuperpath, simplepath, simpletransform
from cubicsuperpath import CubicSuperPath
ver = 0.92
try:
from simpletransform import computePointInNode
oldVersion = False
except:
oldVersion = True # older than 0.92
# sys path already includes the module folder
from stroke_font_manager import CharData, getFontNames, xAscent, \
xDescent, xCapHeight, xXHeight, xSpaceROff, xFontId, xSize
class CommonDefs:
inkVer = ver
pyVer = sys.version_info.major
# inx filed that have the font list to be synchronized
inxFilesWithDynFont = ['render_stroke_font_text.inx', 'edit_stroke_font.inx']
vgScaleFact = 2.
lineT = .005
idAttribName = 'id'
hGuideIDPrefix = 'h_'
lvGuideIDPrefix = 'lv_'
rvGuideIDPrefix = 'rv_'
fontOtherInfo = 'otherInfo'
encoding = sys.stdin.encoding
if(encoding == 'cp0' or encoding is None):
encoding = locale.getpreferredencoding()
######### Function variants for 1.0 and 0.92 - Start ##########
# Used only in 0.92
def getPartsFromCubicSuper(csp):
parts = []
for subpath in csp:
part = []
prevBezPt = None
for i, bezierPt in enumerate(subpath):
if(prevBezPt != None):
seg = [prevBezPt[1], prevBezPt[2], bezierPt[0], bezierPt[1]]
part.append(seg)
prevBezPt = bezierPt
parts.append(part)
return parts
def formatStyle(styleStr):
if(CommonDefs.inkVer == 1.0):
return str(Style(styleStr))
else:
return simplestyle.formatStyle(styleStr)
def getCubicSuperPath(d = None):
if(CommonDefs.inkVer == 1.0):
if(d == None): return CubicSuperPath([])
return CubicSuperPath(Path(d).to_superpath())
else:
if(d == None): return []
return CubicSuperPath(simplepath.parsePath(d))
def getCubicLength(csp):
if(CommonDefs.inkVer == 1.0):
return bezier.csplength(csp)[1]
else:
parts = getPartsFromCubicSuper(cspath)
curveLen = 0
for i, part in enumerate(parts):
for j, seg in enumerate(part):
curveLen += bezmisc.bezierlengthSimpson((seg[0], seg[1], seg[2], seg[3]), \
tolerance = tolerance)
return curveLen
def getCubicBoundingBox(csp):
if(CommonDefs.inkVer == 1.0):
bbox = csp.to_path().bounding_box()
return bbox.left, bbox.right, bbox.top, bbox.bottom
else:
return simpletransform.refinedBBox(csp)
def formatSuperPath(csp):
if(CommonDefs.inkVer == 1.0):
return csp.__str__()
else:
return cubicsuperpath.formatPath(csp)
def getParsedPath(d):
if(CommonDefs.inkVer == 1.0):
# Copied from Path.to_arrays for compatibility
return [[seg.letter, list(seg.args)] for seg in Path(d).to_absolute()]
else:
return simplepath.parsePath(d)
def applyTransform(mat, csp):
if(CommonDefs.inkVer == 1.0):
csp.transform(mat)
else:
simpletransform.applyTransformToPath(mat, csp)
def getTranslatedPath(d, posX, posY):
if(CommonDefs.inkVer == 1.0):
path = Path(d)
path.translate(posX, posY, inplace = True)
return path.to_superpath().__str__()
else:
path = simplepath.parsePath(d)
simplepath.translatePath(path, posX, posY)
return simplepath.formatPath(path)
def getTransformMat(matAttr):
if(CommonDefs.inkVer == 1.0):
return Transform(matAttr)
else:
return simpletransform.parseTransform(matAttr)
def getCurrentLayer(effect):
if(CommonDefs.inkVer == 1.0):
return effect.svg.get_current_layer()
else:
return effect.current_layer
def getViewCenter(effect):
if(CommonDefs.inkVer == 1.0):
return effect.svg.namedview.center
else:
return effect.view_center
def computePtInNode(vc, layer):
if(CommonDefs.inkVer == 1.0):
# ~ return (-Transform(layer.transform * mat)).apply_to_point(vc)
return (-layer.transform).apply_to_point(vc)
else:
if(oldVersion):
return list(vc)
else:
return computePointInNode(list(vc), layer)
def getSelectedElements(effect):
if(CommonDefs.inkVer == 1.0):
return effect.svg.selected
else:
return effect.selected
def getEtree():
return etree
def getAddFnTypes(effect):
if(CommonDefs.inkVer == 1.0):
addFn = effect.arg_parser.add_argument
typeFloat = float
typeInt = int
typeString = str
typeBool = Boolean
else:
addFn = effect.OptionParser.add_option
typeFloat = 'float'
typeInt = 'int'
typeString = 'string'
typeBool = 'inkbool'
return addFn, typeFloat, typeInt, typeString, typeBool
def runEffect(effect):
if(CommonDefs.inkVer == 1.0): effect.run()
else: effect.affect()
######### Function variants for 1.0 and 0.92 - End ##########
def getDecodedChars(chars):
if(CommonDefs.pyVer == 2):
return chars.decode(CommonDefs.encoding)
else: #if?
return chars
def indentStr(cnt):
ostr = ''
for i in range(0, cnt):
ostr += ' '
return ostr
def getXMLItemsStr(sectMarkerLine, sectMarker, fontNames):
lSpaces = sectMarkerLine.find(sectMarker)
outStr = indentStr(lSpaces) + sectMarker + ' [start] -->\n'
for fName in fontNames:
outStr += indentStr(lSpaces + 4) + '<item value="' + fName + '">' + fName + '</item>\n'
outStr += indentStr(lSpaces) + sectMarker + ' [end] -->\n'
return outStr
def syncFontList(extPath):
sectMarker = '<!-- ##! dynamically generated portion'
sectMarkerLine = None
xmlFilePaths = [extPath + "/" + f for f in CommonDefs.inxFilesWithDynFont]
try:
fontNames = getFontNames(extPath)
for xf in xmlFilePaths:
for line in fileinput.input(xf, inplace = True):
if sectMarker in line:
if(sectMarkerLine != None):
if(CommonDefs.pyVer == 3):
# For some reasons python2 giving syntax error without eval
eval("print(getXMLItemsStr(sectMarkerLine, sectMarker, fontNames), end = '')")
else:
print(getXMLItemsStr(sectMarkerLine, sectMarker, fontNames)),
sectMarkerLine = None
else:
sectMarkerLine = line
else:
if(sectMarkerLine == None):
if(CommonDefs.pyVer == 3):
eval("print(line, end = '')")
else:
print(line),
except Exception as e:
errormsg('Error updating font list...\n' + str(e))
def addGridLine(layer, posX, posY, length, lType, style, attribs):
line = etree.Element(addNS('path','svg'))
d = 'M '+str(posX) + ' ' + str(posY) +' '+ lType +' '
if(lType == 'H'):
d += str(posX + length)
if(lType == 'V'):
d += str(posY + length)
line.set('style', formatStyle(style))
line.set('d', d)
for key in attribs:
line.set(key, attribs[key])
layer.append(line)
def addText(layer, textStr, posX, posY, style):
text = etree.Element(addNS('text','svg'))
text.text = textStr
text.set('x', str(posX))
text.set('y', str(posY))
text.set('style', formatStyle(style))
layer.append(text)
def createTempl(callback, effect, extraInfo, rowCnt, glyphCnt, \
vgScaleFact, createRvGuides, lineT, newCallBackLayerName = None):
hgStyle = {'stroke-width':str(lineT), 'opacity':'1', 'stroke':'#ff0066'}
lvgStyle = {'stroke-width':str(lineT), 'opacity':'1', 'stroke':'#00aa88'}
rvgStyle = {'stroke-width':str(lineT), 'opacity':'1', 'stroke':'#1b46ff'}
fontSize = extraInfo[xSize]
spcY = fontSize * 3
spcX = fontSize * 3
fontSize = extraInfo[xSize]
vLineH = fontSize * vgScaleFact
colCnt = int(ceil(float(glyphCnt) / float(rowCnt)))
docW = (colCnt + 1) * spcX
docH = (rowCnt + 1) * spcY
svg = effect.document.getroot()
svg.set('width', str(docW))
svg.set('height', str(docH))
#Remove viewbox
if('viewBox' in svg.attrib):
svg.attrib.pop('viewBox')
currLayers = svg.xpath('//svg:g', namespaces = NSS)
for layer in currLayers:
# Note: getparent()
parentLayer = layer.getparent() if(CommonDefs.inkVer == 1.0) \
else effect.getParentNode(layer)
if(parentLayer != None):
parentLayer.remove(layer)
currExtraElems = svg.xpath('//svg:' + CommonDefs.fontOtherInfo, namespaces = NSS)
for elem in currExtraElems:
parentElem = elem.getparent() if(CommonDefs.inkVer == 1.0) \
else effect.getParentNode(elem)
parentElem.remove(elem)
extraInfoElem = etree.SubElement(svg, CommonDefs.fontOtherInfo)
extraInfoElem.set(xAscent, str(extraInfo[xAscent]))
extraInfoElem.set(xDescent, str(extraInfo[xDescent]))
extraInfoElem.set(xCapHeight, str(extraInfo[xCapHeight]))
extraInfoElem.set(xXHeight, str(extraInfo[xXHeight]))
extraInfoElem.set(xSpaceROff, str(extraInfo[xSpaceROff]))
extraInfoElem.set(xFontId, str(extraInfo[xFontId]))
extraInfoElem.set(xSize, str(extraInfo[xSize]))
templLayer = etree.SubElement(svg, 'g')
templLayer.set(addNS('label', 'inkscape'), 'Guides')
templLayer.set(addNS('groupmode', 'inkscape'), 'layer')
if(newCallBackLayerName != None):
callbackLayer = etree.SubElement(svg, 'g')
callbackLayer.set(addNS('label', 'inkscape'), newCallBackLayerName)
callbackLayer.set(addNS('groupmode', 'inkscape'), 'layer')
else:
callbackLayer = templLayer
editLayer = etree.SubElement(svg, 'g')
editLayer.set(addNS('label', 'inkscape'), 'Glyphs')
editLayer.set(addNS('groupmode', 'inkscape'), 'layer')
editLayer.set('id', 'glyph')#TODO: How to make this dynamic?
view = svg.namedview if CommonDefs.inkVer == 1.0 else effect.getNamedView()
view.set(addNS('current-layer', 'inkscape'), editLayer.get('id'))
for row in range(0, rowCnt):
hAttribs = {CommonDefs.idAttribName : CommonDefs.hGuideIDPrefix + str(row)}
addGridLine(templLayer, 0, \
(row + 1) * spcY, docW, 'H', hgStyle, hAttribs)
for col in range(0, colCnt):
glyphIdx = row * colCnt + col
if(glyphIdx >= glyphCnt):
break
posX = (col + 1) * spcX
posY = (row + 1) * spcY# + lineT / 2
#Caller can create whatever it wants at this position
rOffset = callback(callbackLayer, editLayer, glyphIdx, posX, posY)
if(rOffset == None):
rOffset = fontSize
lvAttribs = {CommonDefs.idAttribName : CommonDefs.lvGuideIDPrefix + \
str(row).zfill(4) + '_' + str(col).zfill(4)}
addGridLine(templLayer, \
posX, posY + fontSize / 1.5, -vLineH, 'V', \
lvgStyle, lvAttribs)
if(createRvGuides):
rvAttribs = {CommonDefs.idAttribName : CommonDefs.rvGuideIDPrefix + \
str(row).zfill(4) + '_' + str(col).zfill(4)}
addGridLine(templLayer, \
posX + rOffset, posY + fontSize / 1.5, -vLineH, 'V', \
rvgStyle, rvAttribs)
def getCharStyle(strokeWidth, naChar):
#na character is a filled box
naStyle = { 'stroke': '#000000', 'fill': '#000000', 'stroke-width': strokeWidth}
charStyle = { 'stroke': '#000000', 'fill': 'none', 'stroke-width': strokeWidth,
'stroke-linecap':'round', 'stroke-linejoin':'round'}
if(naChar):
return naStyle
else:
return charStyle
class InkscapeCharData(CharData):
def __init__(self, char, rOffset, pathStr, glyphName):
self.pathStr = pathStr
super(InkscapeCharData, self).__init__(char, rOffset, glyphName)
def getBBox(self):
return getCubicBoundingBox(getCubicSuperPath(self.pathStr))
def scaleGlyph(self, scaleX, scaleY):
self.rOffset *= scaleX
cspath = getCubicSuperPath(self.pathStr)
for subpath in cspath:
for bezierPts in subpath:
for i in range(0, len(bezierPts)):
#No worries about origin...
bezierPts[i] = [bezierPts[i][0] * scaleX, bezierPts[i][1] * scaleY]
self.pathStr = formatSuperPath(cspath)
self.bbox = getCubicBoundingBox(cspath)
class InkscapeCharDataFactory:
def __init__(self):
pass
def getCharData(self, char, rOffset, pathStr, glyphName):
return InkscapeCharData(char, rOffset, pathStr, glyphName)

View File

@ -0,0 +1,711 @@
#
#
# This module is used commonly by the Inkscape extension and the Blender add-on
# that render the stroke font text
#
# Copyright (C) 2019 Shrinivas Kulkarni
#
# License: MIT
#
# Not yet pep8 compliant
import os, re, sys
from xml.dom.minidom import parse, Document, getDOMImplementation
dataFileSubdir = 'strokefontdata'
##### XML Data Constants ########
xDefs = 'defs'
xFont = 'font'
xFontId = 'id'
xFontFace = 'font-face'
xFontFamily = 'font-family'
xCRInfo = 'metadata'
xSize = 'units-per-em'
xSpaceROff = 'horiz-adv-x'
xChar = 'unicode'
xROff = 'horiz-adv-x'
xGlyph = 'glyph'
xPath = 'd'
xMissingGlyph = 'missing-glyph'
xGlyphName = 'glyph-name'
xAscent='ascent'
xDescent = 'descent'
xCapHeight = 'cap-height'
xXHeight = 'x-height'
def getFontNames(parentPath):
dataFileDirPath = parentPath + '/' + dataFileSubdir
return sorted([fname[:-4] for fname in os.listdir(dataFileDirPath)
if fname.endswith('.svg')], key=lambda s: s.lower())
def getDefaultExtraInfo(fontName, fontSize):
info = {}
info[xAscent] = 0.8 * fontSize
info[xDescent] = -0.2 * fontSize
info[xCapHeight] = 0.5 * fontSize
info[xXHeight] = 0.3 * fontSize
info[xSpaceROff] = 0.5 * fontSize
info[xFontId] = ''.join(fontName.split(' '))
info[xSize] = fontSize
return info
class CharData(object):
def __init__(self, char, rOffset, glyphName = ''):
self.char = char
self.rOffset = rOffset
self.bbox = self.getBBox() # Abstract
self.glyphName = glyphName if glyphName != '' else char
class FontData:
def __init__(self, parentPath, fontName, fontSize, charDataFactory):
dataFileDirPath = parentPath + '/' + dataFileSubdir
self.dataFilePath = dataFileDirPath + '/' + fontName + '.svg'
self.fontName = fontName
self.glyphMap = {}
self.fontSize = fontSize
self.spaceWidth = fontSize / 2
self.crInfo = ''
fontDefs = None
self.charDataFactory = charDataFactory
if(not os.path.isdir(dataFileDirPath)):
os.makedirs(dataFileDirPath)
try:
with open(self.dataFilePath, encoding="UTF-8") as xml:
dataDoc = parse(xml)
fontDefs = dataDoc.getElementsByTagName(xDefs)[0]
except Exception as e:
pass
if(fontDefs is not None):
fontFaceElem = fontDefs.getElementsByTagName(xFontFace)[0]
fontElem = fontDefs.getElementsByTagName(xFont)[0]
crElem = dataDoc.getElementsByTagName(xCRInfo)[0]
if(len(crElem.childNodes) > 0):
self.crInfo = crElem.childNodes[0].nodeValue
self.fontName = fontFaceElem.getAttribute(xFontFamily)
oldFontSize = float(fontFaceElem.getAttribute(xSize))
info = {}
try:
info[xSize] = oldFontSize
info[xFontId] = fontElem.getAttribute(xFontId)
info[xAscent] = float(fontFaceElem.getAttribute(xAscent))
info[xDescent] = float(fontFaceElem.getAttribute(xDescent))
info[xCapHeight] = float(fontFaceElem.getAttribute(xCapHeight))
info[xXHeight] = float(fontFaceElem.getAttribute(xXHeight))
info[xSpaceROff] = float(fontElem.getAttribute(xSpaceROff))
except Exception as e:
# ~ inkex.errormsg(str(e))
info = getDefaultExtraInfo(self.fontName, oldFontSize)
glyphElems = fontDefs.getElementsByTagName(xGlyph)
for e in glyphElems:
char = e.getAttribute(xChar)
rOffset = float(e.getAttribute(xROff))
glyphName = e.getAttribute(xGlyphName)
if(glyphName == 'space'):
info[xSpaceROff] = rOffset
else:
pathStr = e.getAttribute(xPath)
if(pathStr != None and pathStr.strip() != ''):
charData = charDataFactory.getCharData(char, rOffset, pathStr, glyphName)
scaleFact = fontSize / oldFontSize
charData.scaleGlyph(scaleFact, -scaleFact)
self.glyphMap[char] = charData
self.extraInfo = {}
for key in info:
if(isinstance(info[key], float) or isinstance(info[key], int)):
self.extraInfo[key] = fontSize * info[key] / oldFontSize
else:
self.extraInfo[key] = info[key]
self.spaceWidth = self.extraInfo[xSpaceROff]
else:
self.extraInfo = getDefaultExtraInfo(self.fontName, self.fontSize)
def updateGlyph(self, char, rOffset, pathStr, glyphName):
charData = self.charDataFactory.getCharData(char, rOffset, pathStr, glyphName)
self.glyphMap[char] = charData
def setCRInfo(self, crInfo):
if(crInfo is not None and crInfo != ''):
self.crInfo = crInfo
def setExtraInfo(self, extraInfo):
self.extraInfo = extraInfo
def hasGlyphs(self):
return len(self.glyphMap) > 0
# invertY = True because glyph was inverted in FontData constructor
def updateFontXML(self, invertY = True):
imp = getDOMImplementation()
doctype = imp.createDocumentType('svg', "-//W3C//DTD SVG 1.1//EN", \
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd")
doc = imp.createDocument(None, 'svg', doctype)
docElem = doc.documentElement
docElem.setAttribute("xmlns", "http://www.w3.org/2000/svg")
docElem.setAttributeNS("xmls", "xmlns:xlink", "http://www.w3.org/1999/xlink")