diff --git a/stroke_font_manager.py b/stroke_font_manager.py index b6d826b..f0aec4d 100644 --- a/stroke_font_manager.py +++ b/stroke_font_manager.py @@ -22,6 +22,7 @@ xFontFamily = 'font-family' xCRInfo = 'metadata' xSize = 'units-per-em' +xFontROff = 'horiz-adv-x' xSpaceROff = 'horiz-adv-x' xChar = 'unicode' xROff = 'horiz-adv-x' @@ -29,6 +30,11 @@ xPath = 'd' xMissingGlyph = 'missing-glyph' xGlyphName = 'glyph-name' +xHKern = 'hkern' +xKernG1 = 'g1' +xKernG2 = 'g2' +xKernDist = 'k' + xAscent='ascent' xDescent = 'descent' @@ -67,6 +73,7 @@ def __init__(self, parentPath, fontName, fontSize, charDataFactory): self.dataFilePath = dataFileDirPath + '/' + fontName + '.svg' self.fontName = fontName self.glyphMap = {} + self.kernMap = {} self.fontSize = fontSize self.spaceWidth = fontSize / 2 self.crInfo = '' @@ -93,9 +100,9 @@ def __init__(self, parentPath, fontName, fontSize, charDataFactory): self.fontName = fontFaceElem.getAttribute(xFontFamily) oldFontSize = float(fontFaceElem.getAttribute(xSize)) + scaleFact = fontSize / oldFontSize info = {} - try: info[xSize] = oldFontSize info[xFontId] = fontElem.getAttribute(xFontId) @@ -108,22 +115,42 @@ def __init__(self, parentPath, fontName, fontSize, charDataFactory): # ~ inkex.errormsg(str(e)) info = getDefaultExtraInfo(self.fontName, oldFontSize) + defaultHorizOffset = info[xSpaceROff] + 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 + char = None + glyphName = None + rOffset = None + try: + glyphName = e.getAttribute(xGlyphName) + char = e.getAttribute(xChar) + if e.hasAttribute(xROff): + rOffset = float(e.getAttribute(xROff)) + else: + rOffset = defaultHorizOffset + if(glyphName == 'space'): + info[xSpaceROff] = rOffset + else: + pathStr = e.getAttribute(xPath) + if(pathStr != None and pathStr.strip() != ''): + charData = charDataFactory.getCharData(char, rOffset, pathStr, glyphName) + charData.scaleGlyph(scaleFact, -scaleFact) + self.glyphMap[char] = charData + + except ValueError: + print(f"Couldn't parse glyph {char} {glyphName} {rOffset}") + + kernElems = fontDefs.getElementsByTagName(xHKern) + for e in kernElems: + first = e.getAttribute(xKernG1).split(",") + second = e.getAttribute(xKernG2).split(",") + distance = float(e.getAttribute(xKernDist)) + for fc in first: + if not fc in self.kernMap: + self.kernMap[fc]={} + for sc in second: + self.kernMap[fc][sc] = distance*scaleFact self.extraInfo = {} for key in info: @@ -335,11 +362,21 @@ def drawWordWithLenCalc(self, wordData, render=False, x=0, y=0): return 0 nextX = x - + last_char=None for i, charData in enumerate(wordData): # Always start from bbox minX of the first letter if(i == 0): nextX -= charData.bbox[0] + else: + # adjust for kerning + if charData.char in self.strokeFontData.glyphMap: + this_char = self.strokeFontData.glyphMap[charData.char].glyphName + if last_char is not None and last_char in self.strokeFontData.kernMap and this_char in self.strokeFontData.kernMap[last_char]: + print(f"Kerning: {last_char} {this_char} {self.strokeFontData.kernMap[last_char][this_char]}") + nextX -= self.strokeFontData.kernMap[last_char][this_char]*self.charSpacing + last_char=this_char + else: + last_char = None if(render): naChar = self.strokeFontData.glyphMap.get(charData.char) is None @@ -578,6 +615,8 @@ def renderCharsWithoutBox(self, chars): y += self.yCoeff * self.lineHeight self.renderer.centerInView(wmax / 2, y / 2) + self.renderer.afterRender() + #Remove newline chars if alignment is not none def preprocess(self, chars, vAlignment, isFirstLine): @@ -630,6 +669,7 @@ def renderCharsInSelBoxes(self, chars, rectangles, margin, hAlignment, vAlignmen break if(len(chars) == 0): + self.renderer.afterRender() return self.renderer.newBoxToBeRendered(box, addPlane) @@ -657,9 +697,11 @@ def renderCharsInSelBoxes(self, chars, rectangles, margin, hAlignment, vAlignmen if(chars is None or \ (lenCharsBeforeProc == len(chars) and (len(rectangles)-1) == i)): + self.renderer.afterRender() return i += 1 + self.renderer.afterRender() def renderGlyphTable(self): self.renderer.beforeRender() @@ -709,3 +751,4 @@ def renderGlyphTable(self): width = letterSpace * hCnt / 2 height = y / 2 self.renderer.centerInView(width, height) + self.renderer.afterRender() diff --git a/strokefontdata/ReliefSingleLine-Regular.svg b/strokefontdata/ReliefSingleLine-Regular.svg new file mode 100644 index 0000000..5a760ce --- /dev/null +++ b/strokefontdata/ReliefSingleLine-Regular.svg @@ -0,0 +1,3097 @@ + + + + +Created by FontForge 20201107 at Sat Dec 11 11:58:47 2021 + By Tanguy Vanlaeys +Copyright 2021 The Relief SingleLine Project Authors (https://github.com/isdat-type/Relief-SingleLine) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/strokefontmain.py b/strokefontmain.py index a691307..2c730df 100644 --- a/strokefontmain.py +++ b/strokefontmain.py @@ -102,7 +102,7 @@ def main(context, rectangles = None): except: params.copyPropertiesCurve = None - + joinGlyphs = params.joinGlyphs cloneGlyphs = params.cloneGlyphs confined = params.confined width = params.width @@ -115,12 +115,12 @@ def main(context, rectangles = None): addPlane = params.addPlane return addText(fontName, fontSize, charSpacing, wordSpacing, lineSpacing, copyPropObj, \ - rgba, text, cloneGlyphs, action, filePath, confined, width, height, \ + rgba, text, cloneGlyphs, joinGlyphs,action, filePath, confined, width, height, \ margin, hAlignment, vAlignment, expandDir, expandDist, rectangles, addPlane) #Default options if called from writing animation def addText(fontName, fontSize, charSpacing, wordSpacing, lineSpacing, copyPropObj, rgba, \ - text, cloneGlyphs, action = 'addInputText', filePath = None, confined = False, \ + text, cloneGlyphs, joinGlyphs,action = 'addInputText', filePath = None, confined = False, \ width = None, height = None, margin = None, hAlignment = None, \ vAlignment = None, expandDir = None, expandDist = None, \ rectangles = None, addPlane = None, bevelDepth = None): @@ -129,7 +129,7 @@ def addText(fontName, fontSize, charSpacing, wordSpacing, lineSpacing, copyPropO if(bevelDepth == None): bevelDepth = 0.01 * fontSize - renderer = BlenderFontRenderer(copyPropObj, bevelDepth, cloneGlyphs, rgba) + renderer = BlenderFontRenderer(copyPropObj, bevelDepth, cloneGlyphs, joinGlyphs,rgba) if(action == "addGlyphTable"): charSpacing = 1 @@ -202,7 +202,7 @@ def getCharData(self, char, rOffset, pathStr, glyphName): return BlenderCharData(char, rOffset, segs, glyphName) class BlenderFontRenderer: - def __init__(self, copyPropObj, bevelDepth, cloneGlyphs, rgba): + def __init__(self, copyPropObj, bevelDepth, cloneGlyphs, joinGlyphs,rgba): matName = 'Flat Text Material' self.copyPropObj = copyPropObj self.collection = None @@ -216,9 +216,14 @@ def __init__(self, copyPropObj, bevelDepth, cloneGlyphs, rgba): else: self.objMat = None self.cloneGlyphs = cloneGlyphs + self.joinGlyphs = joinGlyphs self.charObjDataCache = {} + + self.first_chars_rendered="" def renderChar(self, charData, x, y, naChar): + if len(self.first_chars_rendered)<20: + self.first_chars_rendered+=charData.char curveData = self.charObjDataCache.get(charData.char) isFlat = self.objMat != None curve = CPath().addCurve(charData, self.copyPropObj, \ @@ -250,10 +255,53 @@ def beforeRender(self): if(self.objMat != None): self.bevelObj = createCircle(self.bevelDepth, self.collection) self.currCollection = self.collection + + def afterRender(self): + print("join glyphs:",self.joinGlyphs) + if self.joinGlyphs: + parentcollection = bpy.context.scene.collection + collection=self.currCollection + outerCollection = None + if self.collection != collection: + outerCollection=self.collection + + print("Joining") + curves = [] + for x in collection.all_objects: + if x.type=="CURVE": + curves.append(x) + else: + print(x) + print(curves) + if len(curves)>0: + bpy.ops.object.select_all(action='DESELECT') + bpy.context.view_layer.objects.active = curves[0] + for x in curves: + x.select_set(True) + bpy.ops.object.join() + join_obj=bpy.context.selected_objects[0] + top_level_objs=[] + for x in collection.all_objects: + if x.parent is None: + top_level_objs.append(x) + if len(top_level_objs)==1: + for obj in collection.all_objects: + parentcollection.objects.link(obj) + collection.objects.unlink(obj) + bpy.data.collections.remove(collection) + if self.currPlane: + self.currPlane.name = self.first_chars_rendered + self.currPlane.data.name = self.first_chars_rendered + join_obj.name = self.first_chars_rendered+"_text" + join_obj.data.name = self.first_chars_rendered+"_text" + else: + join_obj.name = self.first_chars_rendered + join_obj.data.name = self.first_chars_rendered + def newBoxToBeRendered(self, box, addPlane): - self.currCollection = bpy.data.collections.new('StrokeFontTextBox') - self.collection.children.link(self.currCollection) +# self.currCollection = bpy.data.collections.new('StrokeFontTextBox') + # self.collection.children.link(self.currCollection) self.z = box[0][2] if(addPlane): self.currPlane = createPlane(box[0], box[1], self.currCollection) diff --git a/strokefontui.py b/strokefontui.py index 6f05890..b7d1d11 100644 --- a/strokefontui.py +++ b/strokefontui.py @@ -83,6 +83,10 @@ class AddStrokeFontTextParams(PropertyGroup): cloneGlyphs : BoolProperty(name="Clone Glyphs", default = True, \ description='Common data for the same glyphs') + joinGlyphs : BoolProperty(name="Join Glyphs", default = True, \ + description='Make text into a single curve') + + confined : BoolProperty(name="Confine Area", default = False, \ description='Render text in confined 2d area') @@ -155,6 +159,7 @@ def draw(self, context): if(params.action != 'addGlyphTable'): col.prop(params, "cloneGlyphs") + col.prop(params, "joinGlyphs") col.prop(params, "confined") if(params.confined):