diff --git a/extensions/fablabchemnitz/stroke_font_creator/edit_stroke_font.inx b/extensions/fablabchemnitz/stroke_font_creator/edit_stroke_font.inx
new file mode 100644
index 00000000..787e54a4
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/edit_stroke_font.inx
@@ -0,0 +1,54 @@
+
+
+ Custom Stroke Font - Edit Stroke Font
+ fablabchemnitz.de.stroke_font_creator.edit_stroke_font
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+ 1000
+
+
+
+
+
+
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/stroke_font_creator/edit_stroke_font.py b/extensions/fablabchemnitz/stroke_font_creator/edit_stroke_font.py
new file mode 100644
index 00000000..a920da4e
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/edit_stroke_font.py
@@ -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())
diff --git a/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.inx b/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.inx
new file mode 100644
index 00000000..9bfa6adb
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.inx
@@ -0,0 +1,40 @@
+
+
+ Custom Stroke Font - Generate Font Data
+ fablabchemnitz.de.stroke_font_creator.stroke_font_templ
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.py b/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.py
new file mode 100644
index 00000000..1fbaf0a8
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/gen_stroke_font_data.py
@@ -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.')
diff --git a/extensions/fablabchemnitz/stroke_font_creator/meta.json b/extensions/fablabchemnitz/stroke_font_creator/meta.json
new file mode 100644
index 00000000..0cd9df26
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/meta.json
@@ -0,0 +1,20 @@
+[
+ {
+ "name": "Custom Stroke Font - ",
+ "id": "fablabchemnitz.de.stroke_font_creator.",
+ "path": "stroke_font_creator",
+ "original_name": "",
+ "original_id": "khema.stroke.fnt.gen.",
+ "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"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/stroke_font_creator/render_stroke_font_text.inx b/extensions/fablabchemnitz/stroke_font_creator/render_stroke_font_text.inx
new file mode 100644
index 00000000..8940d0d8
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/render_stroke_font_text.inx
@@ -0,0 +1,97 @@
+
+
+ Custom Stroke Font - Render Text
+ fablabchemnitz.de.stroke_font_creator.render_text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 20
+ 1
+ 1
+ 1.5
+ true
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/stroke_font_creator/render_stroke_font_text.py b/extensions/fablabchemnitz/stroke_font_creator/render_stroke_font_text.py
new file mode 100644
index 00000000..4c6dca50
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/render_stroke_font_text.py
@@ -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())
diff --git a/extensions/fablabchemnitz/stroke_font_creator/stroke_font_common.py b/extensions/fablabchemnitz/stroke_font_creator/stroke_font_common.py
new file mode 100644
index 00000000..9cd251f9
--- /dev/null
+++ b/extensions/fablabchemnitz/stroke_font_creator/stroke_font_common.py
@@ -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) + '- ' + fName + '
\n'
+ outStr += indentStr(lSpaces) + sectMarker + ' [end] -->\n'
+ return outStr
+
+def syncFontList(extPath):
+ sectMarker = '