mightyscape-1.2/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.py

247 lines
9.2 KiB
Python

#!/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
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.')