def flip_path(upside_down_path): path = [] _, _, min_y, max_y = upside_down_path.bbox() offset = max_y + min_y for segment in upside_down_path._segments: if type(segment) is Line: path.append( Line(complex(segment.start.real, -segment.start.imag + offset), complex(segment.end.real, -segment.end.imag + offset))) elif type(segment) is Arc: path.append( Arc(complex(segment.start.real, -segment.start.imag + offset), segment.radius, abs(180 - segment.rotation), segment.large_arc, not segment.sweep, complex(segment.end.real, -segment.end.imag + offset))) elif type(segment) is QuadraticBezier: path.append( QuadraticBezier( complex(segment.start.real, -segment.start.imag + offset), complex(segment.control.real, -segment.control.imag + offset), complex(segment.end.real, -segment.end.imag + offset))) else: raise ValueError(f"Unknown type: {type(segment)}") return Path(*path)
def save_points_as_svg_handler(points, size, image_number): # filename like file:///home/gdshen/Pictures/00000.jpg filename = os.path.join(project_path, svg_dir, image_number + '.svg') paths = [] for i in range(size): start_point_x = points.property(i).property('startPoint').property( 'X').toInt() start_point_y = points.property(i).property('startPoint').property( 'Y').toInt() control_point_x = points.property(i).property( 'controlPoint').property('X').toInt() control_point_y = points.property(i).property( 'controlPoint').property('Y').toInt() target_point_x = points.property(i).property( 'targetPoint').property('X').toInt() target_point_y = points.property(i).property( 'targetPoint').property('Y').toInt() print(start_point_x, start_point_y, control_point_x, control_point_y, target_point_x, target_point_y) paths.append( Path( QuadraticBezier(complex(start_point_x, start_point_y), complex(control_point_x, control_point_y), complex(target_point_x, target_point_y)))) wsvg(paths=paths, filename=filename)
def qCurveTo(self, *points): cpoints = [self.convertPoint(self._lastOnCurve)] cpoints.extend([self.convertPoint(p) for p in points]) if len(cpoints) <= 3: self._contour.append(QuadraticBezier(*cpoints)) else: # a starting on-curve point, two or more off-curve points, and a final on-curve point startPoint = cpoints[0] for i in range(1, len(cpoints) - 2): impliedPoint = ((cpoints[i] + cpoints[i + 1]) / 2) self._contour.append( QuadraticBezier(startPoint, cpoints[i], impliedPoint)) startPoint = impliedPoint self._contour.append( QuadraticBezier(startPoint, cpoints[-2], cpoints[-1])) self.logger.debug(f"qCurveTo({points})") self._lastOnCurve = points[-1]
def sample_points_from_curves(curves, template): curves = apply_templates(curves).squeeze() curves = curves.reshape([-1, 6])[:sum(templates.topology[:template])] segments = [] points = [] for curve in curves: segment = [] for point in curve.reshape([-1, 2]): segment.append(complex(*point).conjugate()) segments.append(QuadraticBezier(*segment)) for seg in segments: length = seg.length() for a in np.linspace(0, 1, num=length * n_points_per_unit_length): points.append(seg.point(seg.ilength(a * length))) return [(p.real, -p.imag) for p in points]
def draw_glyph(font, char, ctx, offset=(0, 0), color=(0.6, 0.6, 0.6)): try: face = Face('data/ttfs/{}.ttf'.format(font)) except: face = Face('data/ttfs/{}.otf'.format(font)) face.set_char_size(48*64) face.load_char(char) outline = face.glyph.outline contours = [-1] + outline.contours segment = [] segments = [] paths = [] for i in range(len(outline.points)): segment.append(complex(*outline.points[i]).conjugate()) tag = int(bin(outline.tags[i])[2]) try: j = contours.index(i) if tag == 0: segment.append(complex(*outline.points[contours[j-1]+1]).conjugate()) tag = 2 else: tag = 3 except ValueError: pass if tag > 0: if len(segment) == 1: pass elif len(segment) == 2: segments.append(Line(*segment)) elif len(segment) == 3: segments.append(QuadraticBezier(*segment)) elif len(segment) == 4: segments.append(CubicBezier(*segment)) else: for k in range(len(segment)-1): A, C = segment[k:k+2] B = (A+C) / 2 segments.append(QuadraticBezier(A, B, C)) if tag == 1: segment = [complex(*outline.points[i]).conjugate()] elif tag == 2: paths.append(Path(*segments)) segments = [] segment = [] else: segments.append(Line(segment[-1], complex(*outline.points[contours[j-1]+1]).conjugate())) paths.append(Path(*segments)) segments = [] segment = [] xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) factor = 0.8 / max(xmax-xmin, ymax-ymin) for i, path in enumerate(paths): paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor) xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) xmargin = (1 - (xmax-xmin)) / 2 ymargin = (1 - (ymax-ymin)) / 2 for i, path in enumerate(paths): paths[i] = path.translated(complex(xmargin, ymargin)) ctx.set_source_rgb(*color) ctx.new_path() ctx.set_line_width(0.02) x, y = offset for path in paths: ctx.move_to(path[0].bpoints()[0].real + x, path[0].bpoints()[0].imag + y) for seg in path: bpoints = seg.bpoints() if len(bpoints) == 2: ctx.line_to(bpoints[1].real + x, bpoints[1].imag + y) elif len(bpoints) == 3: ctx.curve_to(bpoints[0].real * 1/3 + bpoints[1].real * 2/3 + x, bpoints[0].imag * 1/3 + bpoints[1].imag * 2/3 + y, bpoints[1].real * 2/3 + bpoints[2].real * 1/3 + x, bpoints[1].imag * 2/3 + bpoints[2].imag * 1/3 + y, bpoints[2].real + x, bpoints[2].imag + y) elif len(bpoints) == 4: ctx.curve_to(bpoints[1].real + x, bpoints[1].imag + y, bpoints[2].real + x, bpoints[2].imag + y, bpoints[3].real + x, bpoints[3].imag + y) ctx.fill()
def process_letter(font_char): try: font, char = font_char name = "{}_{}".format(char, os.path.splitext(font)[0]) face = Face('data/fonts/ttfs/{}'.format(font)) face.set_char_size(48 * 64) face.load_char(char) outline = face.glyph.outline contours = [-1] + outline.contours segment = [] segments = [] paths = [] for i in range(len(outline.points)): segment.append(complex(*outline.points[i]).conjugate()) tag = int(bin(outline.tags[i])[2]) try: j = contours.index(i) if tag == 0: segment.append( complex(*outline.points[contours[j - 1] + 1]).conjugate()) tag = 2 else: tag = 3 except ValueError: pass if tag > 0: if len(segment) == 1: pass elif len(segment) == 2: segments.append(Line(*segment)) elif len(segment) == 3: segments.append(QuadraticBezier(*segment)) elif len(segment) == 4: segments.append(CubicBezier(*segment)) else: for k in range(len(segment) - 1): A, C = segment[k:k + 2] B = (A + C) / 2 segments.append(QuadraticBezier(A, B, C)) if tag == 1: segment = [complex(*outline.points[i]).conjugate()] elif tag == 2: paths.append(Path(*segments)) segments = [] segment = [] else: segments.append( Line( segment[-1], complex(*outline.points[contours[j - 1] + 1]).conjugate())) paths.append(Path(*segments)) segments = [] segment = [] xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) factor = 0.8 / max(xmax - xmin, ymax - ymin) for i, path in enumerate(paths): paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor) xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) xmargin = (1 - (xmax - xmin)) / 2 ymargin = (1 - (ymax - ymin)) / 2 for i, path in enumerate(paths): paths[i] = path.translated(complex(xmargin, ymargin)) points = [] surface = cairo.ImageSurface(cairo.Format.RGB24, opt.img_size, opt.img_size) ctx = cairo.Context(surface) ctx.scale(opt.img_size, opt.img_size) ctx.set_source_rgba(1, 1, 1) ctx.rectangle(0, 0, 1, 1) ctx.fill() ctx.set_source_rgb(0, 0, 0) ctx.new_path() ctx.set_line_width(0.02) for path in paths: ctx.move_to(path[0].bpoints()[0].real, path[0].bpoints()[0].imag) for seg in path: bpoints = seg.bpoints() if len(bpoints) == 2: ctx.line_to(bpoints[1].real, bpoints[1].imag) elif len(bpoints) == 3: ctx.curve_to( bpoints[0].real * 1 / 3 + bpoints[1].real * 2 / 3, bpoints[0].imag * 1 / 3 + bpoints[1].imag * 2 / 3, bpoints[1].real * 2 / 3 + bpoints[2].real * 1 / 3, bpoints[1].imag * 2 / 3 + bpoints[2].imag * 1 / 3, bpoints[2].real, bpoints[2].imag) elif len(bpoints) == 4: ctx.curve_to(bpoints[1].real, bpoints[1].imag, bpoints[2].real, bpoints[2].imag, bpoints[3].real, bpoints[3].imag) for t in np.linspace(0, 1, num=opt.n_points_sampled // len(paths) + 1): points.append(path.point(t)) ctx.fill() n_points = len(points) points = np.array(points, dtype=np.complex64).view(np.float32).reshape([-1, 2]) np.random.shuffle(points) np.save('data/fonts/points/{}.npy'.format(name), points) grid = np.mgrid[-0.25:1.25:opt.img_size * 1.5j, -0.25:1.25:opt.img_size * 1.5j].T[:, :, None, :] distances = np.empty((grid.shape[0], grid.shape[1])) for i in range(grid.shape[0]): for j in range(grid.shape[0]): distances[i, j] = np.amin( np.linalg.norm(grid[i, j] - points, axis=1)) if not np.isnan(distances).any(): np.save('data/fonts/distances/{}.npy'.format(name), distances) surface.write_to_png('data/fonts/pngs/{}.png'.format(name)) else: return e except Exception as e: return e return None
def parse_path(pathdef, current_pos=0j, tree_element=None): # In the SVG specs, initial movetos are absolute, even if # specified as 'm'. This is the default behavior here as well. # But if you pass in a current_pos variable, the initial moveto # will be relative to that current_pos. This is useful. elements = list(_tokenize_path(pathdef)) # Reverse for easy use of .pop() elements.reverse() if tree_element is None: segments = Path() else: segments = Path(tree_element=tree_element) start_pos = None command = None while elements: if elements[-1] in COMMANDS: # New command. last_command = command # Used by S and T command = elements.pop() absolute = command in UPPERCASE command = command.upper() else: # If this element starts with numbers, it is an implicit command # and we don't change the command. Check that it's allowed: if command is None: raise ValueError( "Unallowed implicit command in %s, position %s" % (pathdef, len(pathdef.split()) - len(elements))) if command == 'M': # Moveto command. x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if absolute: current_pos = pos else: current_pos += pos # when M is called, reset start_pos # This behavior of Z is defined in svg spec: # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand start_pos = current_pos # Implicit moveto commands are treated as lineto commands. # So we set command to lineto here, in case there are # further implicit commands after this moveto. command = 'L' elif command == 'Z': # Close path if not (current_pos == start_pos): segments.append(Line(current_pos, start_pos)) segments.closed = True current_pos = start_pos command = None elif command == 'L': x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if not absolute: pos += current_pos segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'H': x = elements.pop() pos = float(x) + current_pos.imag * 1j if not absolute: pos += current_pos.real segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'V': y = elements.pop() pos = current_pos.real + float(y) * 1j if not absolute: pos += current_pos.imag * 1j segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'C': control1 = float(elements.pop()) + float(elements.pop()) * 1j control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control1 += current_pos control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'S': # Smooth curve. First control point is the "reflection" of # the second control point in the previous path. if last_command not in 'CS': # If there is no previous command or if the previous command # was not an C, c, S or s, assume the first control point is # coincident with the current point. control1 = current_pos else: # The first control point is assumed to be the reflection of # the second control point on the previous command relative # to the current point. control1 = current_pos + current_pos - segments[-1].control2 control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'Q': control = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control += current_pos end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'T': # Smooth curve. Control point is the "reflection" of # the second control point in the previous path. if last_command not in 'QT': # If there is no previous command or if the previous command # was not an Q, q, T or t, assume the first control point is # coincident with the current point. control = current_pos else: # The control point is assumed to be the reflection of # the control point on the previous command relative # to the current point. control = current_pos + current_pos - segments[-1].control end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'A': radius = float(elements.pop()) + float(elements.pop()) * 1j rotation = float(elements.pop()) arc = float(elements.pop()) sweep = float(elements.pop()) end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(Arc(current_pos, radius, rotation, arc, sweep, end)) current_pos = end return segments
def renderLabel(self, inString): dwg = svgwrite.Drawing() # SVG drawing in memory strIdx = 0 # Used to iterate over inString xOffset = 100 # Cumulative character placement offset yOffset = 0 # Cumulative character placement offset charSizeX = 8 # Character size constant charSizeY = 8 # Character size constant baseline = 170 # Y value of text baseline glyphBounds = [ ] # List of boundingBox objects to track rendered character size finalSegments = [] # List of output paths escaped = False # Track whether the current character was preceded by a '\' lineover = False # Track whether the current character needs to be lined over lineoverList = [] # If we can't find the typeface that the user requested, we have to quit try: face = Face( os.path.dirname(os.path.abspath(__file__)) + '/typeface/' + self.fontName + '.ttf') face.set_char_size(charSizeX, charSizeY, 200, 200) except Exception as e: print(e) print("WARN: No Typeface found with the name " + self.fontName + ".ttf") sys.exit(0) # quit Python # If the typeface that the user requested exists, but there's no position table for it, we'll continue with a warning try: table = __import__( 'KiBuzzard.KiBuzzard.buzzard.typeface.' + self.fontName, globals(), locals(), ['glyphPos']) glyphPos = table.glyphPos spaceDistance = table.spaceDistance except: glyphPos = 0 spaceDistance = 60 print( "WARN: No Position Table found for this typeface. Composition will be haphazard at best." ) # If there's lineover text, drop the text down to make room for the line dropBaseline = False a = False x = 0 while x < len(inString): if x > 0 and inString[x] == '\\': a = True if x != len(inString) - 1: x += 1 if inString[x] == '!' and not a: dropBaseline = True a = False x += 1 if dropBaseline: baseline = 190 # Draw and compose the glyph portion of the tag for charIdx in range(len(inString)): # Check whether this character is a space if inString[charIdx] == ' ': glyphBounds.append(boundingBox(0, 0, 0, 0)) xOffset += spaceDistance continue # Check whether this character is a backslash that isn't escaped # and isn't the first character (denoting a backslash-shaped tag) if inString[charIdx] == '\\' and charIdx > 0 and not escaped: glyphBounds.append(boundingBox(0, 0, 0, 0)) escaped = True continue # If this is a non-escaped '!' mark the beginning of lineover if inString[charIdx] == '!' and not escaped: glyphBounds.append(boundingBox(0, 0, 0, 0)) lineover = True # If we've hit the end of the string but not the end of the lineover # go ahead and finish it out if charIdx == len(inString) - 1 and len(lineoverList) > 0: linePaths = [] linePaths.append( Line(start=complex(lineoverList[0], 10), end=complex(xOffset, 10))) linePaths.append( Line(start=complex(xOffset, 10), end=complex(xOffset, 30))) linePaths.append( Line(start=complex(xOffset, 30), end=complex(lineoverList[0], 30))) linePaths.append( Line(start=complex(lineoverList[0], 30), end=complex(lineoverList[0], 10))) linepath = Path(*linePaths) linepath = elPath(linepath.d()) finalSegments.append(linepath) lineover = False lineoverList.clear() continue # All special cases end in 'continue' so if we've gotten here we can clear our flags if escaped: escaped = False face.load_char( inString[charIdx]) # Load character curves from font outline = face.glyph.outline # Save character curves to var y = [t[1] for t in outline.points] # flip the points outline_points = [(p[0], max(y) - p[1]) for p in outline.points] start, end = 0, 0 paths = [] box = 0 yOffset = 0 for i in range(len(outline.contours)): end = outline.contours[i] points = outline_points[start:end + 1] points.append(points[0]) tags = outline.tags[start:end + 1] tags.append(tags[0]) segments = [ [ points[0], ], ] box = boundingBox(points[0][0], points[0][1], points[0][0], points[0][1]) for j in range(1, len(points)): if not tags[j]: # if this point is off-path if tags[j - 1]: # and the last point was on-path segments[-1].append( points[j]) # toss this point onto the segment elif not tags[j - 1]: # and the last point was off-path # get center point of two newPoint = ((points[j][0] + points[j - 1][0]) / 2.0, (points[j][1] + points[j - 1][1]) / 2.0) segments[-1].append( newPoint ) # toss this new point onto the segment segments.append( [ newPoint, points[j], ] ) # and start a new segment with the new point and this one elif tags[j]: # if this point is on-path segments[-1].append( points[j]) # toss this point onto the segment if j < (len(points) - 1): segments.append( [ points[j], ] ) # and start a new segment with this point if we're not at the end for segment in segments: if len(segment) == 2: paths.append( Line(start=tuple_to_imag(segment[0]), end=tuple_to_imag(segment[1]))) elif len(segment) == 3: paths.append( QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(segment[2]))) start = end + 1 # Derive bounding box of character for segment in paths: i = 0 while i < 10: point = segment.point(0.1 * i) if point.real > box.xMax: box.xMax = point.real if point.imag > box.yMax: box.yMax = point.imag if point.real < box.xMin: box.xMin = point.real if point.imag < box.yMin: box.yMin = point.imag i += 1 glyphBounds.append(box) path = Path(*paths) if glyphPos != 0: try: xOffset += glyphPos[inString[charIdx]].real yOffset = glyphPos[inString[charIdx]].imag except: pass if lineover and len(lineoverList) == 0: lineoverList.append(xOffset) lineover = False if (lineover and len(lineoverList) > 0): linePaths = [] linePaths.append( Line(start=complex(lineoverList[0], 10), end=complex(xOffset, 10))) linePaths.append( Line(start=complex(xOffset, 10), end=complex(xOffset, 30))) linePaths.append( Line(start=complex(xOffset, 30), end=complex(lineoverList[0], 30))) linePaths.append( Line(start=complex(lineoverList[0], 30), end=complex(lineoverList[0], 10))) linepath = Path(*linePaths) linepath = elPath(linepath.d()) finalSegments.append(linepath) lineover = False lineoverList.clear() pathTransform = Matrix.translate(xOffset, baseline + yOffset - box.yMax) path = elPath(path.d()) * pathTransform path = elPath(path.d()) finalSegments.append(path) xOffset += 30 if glyphPos != 0: try: xOffset -= glyphPos[inString[charIdx]].real except: pass xOffset += (glyphBounds[charIdx].xMax - glyphBounds[charIdx].xMin) strIdx += 1 if self.leftCap == '' and self.rightCap == '': for i in range(len(finalSegments)): svgObj = dwg.add(dwg.path(finalSegments[i].d())) svgObj['fill'] = "#000000" else: #draw the outline of the label as a filled shape and #subtract each latter from it tagPaths = [] if self.rightCap == 'round': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Arc(start=complex(xOffset, 0), radius=complex(100, 100), rotation=180, large_arc=1, sweep=1, end=complex(xOffset, 200))) elif self.rightCap == 'square': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Line(start=complex(xOffset, 0), end=complex(xOffset + 50, 0))) tagPaths.append( Line(start=complex(xOffset + 50, 0), end=complex(xOffset + 50, 200))) tagPaths.append( Line(start=complex(xOffset + 50, 200), end=complex(xOffset, 200))) elif self.rightCap == 'pointer': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Line(start=complex(xOffset, 0), end=complex(xOffset + 50, 0))) tagPaths.append( Line(start=complex(xOffset + 50, 0), end=complex(xOffset + 100, 100))) tagPaths.append( Line(start=complex(xOffset + 100, 100), end=complex(xOffset + 50, 200))) tagPaths.append( Line(start=complex(xOffset + 50, 200), end=complex(xOffset, 200))) elif self.rightCap == 'flagtail': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Line(start=complex(xOffset, 0), end=complex(xOffset + 100, 0))) tagPaths.append( Line(start=complex(xOffset + 100, 0), end=complex(xOffset + 50, 100))) tagPaths.append( Line(start=complex(xOffset + 50, 100), end=complex(xOffset + 100, 200))) tagPaths.append( Line(start=complex(xOffset + 100, 200), end=complex(xOffset, 200))) elif self.rightCap == 'fslash': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Line(start=complex(xOffset, 0), end=complex(xOffset + 50, 0))) tagPaths.append( Line(start=complex(xOffset + 50, 0), end=complex(xOffset, 200))) elif self.rightCap == 'bslash': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Line(start=complex(xOffset, 0), end=complex(xOffset + 50, 200))) tagPaths.append( Line(start=complex(xOffset + 50, 200), end=complex(xOffset, 200))) elif self.rightCap == '' and self.leftCap != '': tagPaths.append( Line(start=complex(100, 0), end=complex(xOffset, 0))) tagPaths.append( Line(start=complex(xOffset, 0), end=complex(xOffset, 200))) if self.leftCap == 'round': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Arc(start=complex(100, 200), radius=complex(100, 100), rotation=180, large_arc=0, sweep=1, end=complex(100, 0))) elif self.leftCap == 'square': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Line(start=complex(100, 200), end=complex(50, 200))) tagPaths.append( Line(start=complex(50, 200), end=complex(50, 0))) tagPaths.append(Line(start=complex(50, 0), end=complex(100, 0))) elif self.leftCap == 'pointer': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Line(start=complex(100, 200), end=complex(50, 200))) tagPaths.append( Line(start=complex(50, 200), end=complex(0, 100))) tagPaths.append(Line(start=complex(0, 100), end=complex(50, 0))) tagPaths.append(Line(start=complex(50, 0), end=complex(100, 0))) elif self.leftCap == 'flagtail': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Line(start=complex(100, 200), end=complex(0, 200))) tagPaths.append( Line(start=complex(0, 200), end=complex(50, 100))) tagPaths.append(Line(start=complex(50, 100), end=complex(0, 0))) tagPaths.append(Line(start=complex(0, 0), end=complex(100, 0))) elif self.leftCap == 'fslash': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Line(start=complex(100, 200), end=complex(50, 200))) tagPaths.append( Line(start=complex(50, 200), end=complex(100, 0))) elif self.leftCap == 'bslash': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Line(start=complex(100, 200), end=complex(50, 0))) tagPaths.append(Line(start=complex(50, 0), end=complex(100, 0))) elif self.leftCap == '' and self.rightCap != '': tagPaths.append( Line(start=complex(xOffset, 200), end=complex(100, 200))) tagPaths.append( Line(start=complex(100, 200), end=complex(100, 0))) path = Path(*tagPaths) for i in range(len(finalSegments)): path = elPath(path.d() + " " + finalSegments[i].reverse()) tagObj = dwg.add(dwg.path(path.d())) tagObj['fill'] = "#000000" dwg['width'] = xOffset + 100 dwg['height'] = 250 #dwg.saveas('out.svg') print('create svg') return dwg
def text2pathd(text, group_transform=(1, 0, 0, 1, 0, 0)): attributes = dom2dict(text) if "font-size" in attributes: font_size = float(attributes["font-size"]) elif "style" in attributes: if attributes["style"].find("font-size") >= 0: font_size = attributes["style"].split("font-size:")[1].split( ";")[0] font_size = float(font_size.replace("px", "")) else: font_size = 12 else: font_size = 12 if "x" in attributes: x_global_offset = float(attributes["x"]) else: x_global_offset = 0 if "y" in attributes: y_global_offset = float(attributes["y"]) else: y_global_offset = 0 if hasattr(text.childNodes[0], "data"): text_string = text.childNodes[0].data else: flow_para = text.getElementsByTagName('flowPara') if flow_para: text_string = flow_para[0].childNodes[0].data # strip newline characters from the string, they aren't rendered in svg text_string = text_string.replace("\n", "").replace("\r", "") def tuple_to_imag(t): return t[0] + t[1] * 1j # keep fonts with repository, as dealing with importing fonts across platforms is a # nightmare foldername = os_path.dirname(os_path.abspath(__file__)) face = Face(os_path.join(foldername, 'Vera.ttf')) face.set_char_size(48 * 64) scale = font_size / face.size.height outlines = [] current_x = 0 transform = get_transform(text) transform = combine_transforms(transform, group_transform) x_global_offset, y_global_offset = transform_point( [x_global_offset, y_global_offset], transform) for i, letter in enumerate(text_string): face.load_char(letter) outline = face.glyph.outline if i != 0: kerning = face.get_kerning(text_string[i - 1], text_string[i]) kerning_x = kerning.x else: kerning_x = 0 if text_string[i] == ' ': # a space is usually 30% of the widest character, capital W char_width = face.size.max_advance * 0.3 char_height = 0 char_offset = 0 else: char_width = outline.get_bbox().xMax char_offset = face.size.height - outline.get_bbox().yMax char_height = outline.get_bbox().yMax outline_dict = {} current_x += kerning_x outline_dict["points"] = [ (scale * (p[0] + current_x) + x_global_offset, scale * (char_offset + char_height - p[1]) + y_global_offset) for p in outline.points ] outline_dict["contours"] = outline.contours outline_dict["tags"] = outline.tags outlines.append(outline_dict) current_x += char_width paths = [] for outline in outlines: start, end = 0, 0 for i in range(len(outline["contours"])): end = outline["contours"][i] points = outline["points"][start:end + 1] points.append(points[0]) tags = outline["tags"][start:end + 1] tags.append(tags[0]) segments = [ [ points[0], ], ] for j in range(1, len(points)): segments[-1].append(points[j]) if tags[j] and j < (len(points) - 1): segments.append([ points[j], ]) for segment in segments: if len(segment) == 2: paths.append( Line(start=tuple_to_imag(segment[0]), end=tuple_to_imag(segment[1]))) elif len(segment) == 3: paths.append( QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(segment[2]))) elif len(segment) == 4: C = ((segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0) paths.append( QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(C))) paths.append( QuadraticBezier(start=tuple_to_imag(C), control=tuple_to_imag(segment[2]), end=tuple_to_imag(segment[3]))) start = end + 1 path = Path(*paths) return path.d()
def char(ch): def tuple_to_imag(t): return t[0] + t[1] * 1j from freetype import Face #face = Face('/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf') face = Face(ddd.DATA_DIR + '/fonts/OpenSansEmoji.ttf') face.set_char_size(48 * 64) face.load_char(ch) #kerning = face.get_kerning(ch, 'x') # or from previous, actually? #print(kerning) outline = face.glyph.outline y = [t[1] for t in outline.points] # flip the points outline_points = [(p[0], max(y) - p[1]) for p in outline.points] start, end = 0, 0 paths = [] for i in range(len(outline.contours)): end = outline.contours[i] points = outline_points[start:end + 1] points.append(points[0]) tags = outline.tags[start:end + 1] tags.append(tags[0]) segments = [ [ points[0], ], ] for j in range(1, len(points)): segments[-1].append(points[j]) if tags[j] and j < (len(points) - 1): segments.append([ points[j], ]) for segment in segments: if len(segment) == 2: paths.append( Line(start=tuple_to_imag(segment[0]), end=tuple_to_imag(segment[1]))) elif len(segment) == 3: paths.append( QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(segment[2]))) elif len(segment) == 4: C = ((segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0) paths.append( QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(C))) paths.append( QuadraticBezier(start=tuple_to_imag(C), control=tuple_to_imag(segment[2]), end=tuple_to_imag(segment[3]))) start = end + 1 path = Path(*paths) #wsvg(path, filename="/tmp/test.svg") path_d = path.d() # https://gis.stackexchange.com/questions/301605/how-to-create-shape-in-shapely-from-an-svg-path-element # This page also has info about SVG reading! from svgpath2mpl import parse_path #svgpath = 'M10 10 C 20 20, 40 20, 50 10Z' mpl_path = parse_path(path_d) coords = mpl_path.to_polygons() # Add or subtract char_2d = ddd.polygon(coords[0]) for c in coords[1:]: ng = ddd.polygon(c) #print (ng.geom.is_valid) if not ng.geom.is_valid: continue if char_2d.contains(ng): char_2d = char_2d.subtract(ng) else: char_2d = char_2d.union(ng) #result = ddd.group([ddd.polygon(c) for c in coords], empty=2) result = char_2d result = result.scale([1.0 / (48 * 64), -1.0 / (48 * 64)]) result = result.simplify(0.005) # return result
def char_to_path(char: str) -> Tuple[Path, freetype.GlyphMetrics]: if len(char) != 1: raise ValueError(f'1 character at a time; got {len(char)}\n"{char}"') if re.search(r"\s", char): # Can't handle white-space characters yet raise ValueError(f'char cannot be whitespace: "{char}"') face = get_font_face() # Shamelessly stolen from <http://chatherineh.github.io/programming/2018/02/01/text-to-svg-paths> # Request nominal size (in points) # The height is set to the width if the height is left as 0 face.set_char_size(width=char_size) # Load character into glyph slot so all methods apply to that glyph face.load_char(char) # Grab the outline of the glyph, and select only the y coordinate of the points outline = face.glyph.outline y = [t[1] for t in outline.points] # Flip the points outline_points = [(p[0], max(y) - p[1]) for p in outline.points] start, end = 0, 0 paths = [] for i in range(len(outline.contours)): end = outline.contours[i] points = outline_points[start:end + 1] points.append(points[0]) tags = outline.tags[start:end + 1] tags.append(tags[0]) segments = [[points[0]]] for j in range(1, len(points)): segments[-1].append(points[j]) if tags[j] and j < (len(points) - 1): segments.append([points[j]]) for segment in segments: if len(segment) == 2: paths.append( Line(start=tuple_to_imag(segment[0]), end=tuple_to_imag(segment[1]))) elif len(segment) == 3: paths.append( QuadraticBezier( start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(segment[2]), )) elif len(semgent) == 4: C = ( (segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0, ) paths.append( QuadraticBezier( start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(C), )) paths.append( QuadraticBezier( start=tuple_to_imag(C), control=tuple_to_imag(segment[2]), end=tuple_to_imag(segment[3]), )) start = end + 1 path = Path(*paths) return (path, face.glyph.metrics)
points.append(points[0]) tags = outline.tags[start:end + 1] tags.append(tags[0]) segments = [[points[0], ], ] for j in range(1, len(points)): segments[-1].append(points[j]) if tags[j] and j < (len(points) - 1): segments.append([points[j], ]) for segment in segments: if len(segment) == 2: paths.append(Line(start=tuple_to_imag(segment[0]), end=tuple_to_imag(segment[1]))) elif len(segment) == 3: paths.append(QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(segment[2]))) elif len(segment) == 4: C = ((segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0) paths.append(QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(C))) paths.append(QuadraticBezier(start=tuple_to_imag(C), control=tuple_to_imag(segment[2]), end=tuple_to_imag(segment[3]))) start = end + 1 path = Path(*paths) wsvg(path, filename="text.svg")
def sample_points_from_font(font, char): try: face = Face('data/ttfs/{}.ttf'.format(font)) except: face = Face('data/ttfs/{}.otf'.format(font)) face.set_char_size(48 * 64) face.load_char(char) outline = face.glyph.outline contours = [-1] + outline.contours segment = [] segments = [] paths = [] for i in range(len(outline.points)): segment.append(complex(*outline.points[i]).conjugate()) tag = int(bin(outline.tags[i])[2]) try: j = contours.index(i) if tag == 0: segment.append( complex(*outline.points[contours[j - 1] + 1]).conjugate()) tag = 2 else: tag = 3 except ValueError: pass if tag > 0: if len(segment) == 1: pass elif len(segment) == 2: segments.append(Line(*segment)) elif len(segment) == 3: segments.append(QuadraticBezier(*segment)) elif len(segment) == 4: segments.append(CubicBezier(*segment)) else: for k in range(len(segment) - 1): A, C = segment[k:k + 2] B = (A + C) / 2 segments.append(QuadraticBezier(A, B, C)) if tag == 1: segment = [complex(*outline.points[i]).conjugate()] elif tag == 2: paths.append(Path(*segments)) segments = [] segment = [] else: segments.append( Line( segment[-1], complex(*outline.points[contours[j - 1] + 1]).conjugate())) paths.append(Path(*segments)) segments = [] segment = [] xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) factor = 0.8 / max(xmax - xmin, ymax - ymin) for i, path in enumerate(paths): paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor) xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) xmargin = (1 - (xmax - xmin)) / 2 ymargin = (1 - (ymax - ymin)) / 2 for i, path in enumerate(paths): paths[i] = path.translated(complex(xmargin, ymargin)) points = [] for path in paths: for seg in path: length = seg.length() for a in np.linspace(0, 1, num=length * n_points_per_unit_length): points.append(seg.point(seg.ilength(a * length))) return [(p.real, p.imag) for p in points]
def calc_path(self, fontsize: float) -> Path: ''' fontsize: svg font-size in px ''' def tuple2complex(t): return t[0] + t[1] * 1j yflip = self.yflip_value if yflip == None: yflip = self.bbox.yMax xshift = self.xshift_value if xshift == None: xshift = self.bbox.xMin # extra scaling scaling = fontsize / self.CHAR_SIZE ''' You'll need to flip the y values of the points in order to render the characters right-side-up: ''' outline: freetype.Outline = self.face.glyph.outline # shift and flip the points outline_points = [((pt[0] - xshift) * scaling, (yflip - pt[1]) * scaling) for pt in outline.points] ''' The face has three lists of interest: the points, the tags, and the contours. The points are the x/y coordinates of the start and end points of lines and control points. The tags indicate what type of point it is, where tag values of 0 are control points. Finally, the contours are the end point list index for each shape. Characters like i or ! have two shapes, most others have only one contour. So, for each contour, we want to pick out only the tags and points for that contour. ''' start, end = 0, 0 paths = [] for i in range(len(outline.contours)): end = outline.contours[i] points = outline_points[start:end + 1] points.append(points[0]) tags = outline.tags[start:end + 1] tags.append(tags[0]) ''' Next, we want to split the points up into path segments, using the tags. If the tags are 0, add the point to the current segment, else create a new segment, so that control points stay with their path segments: ''' segments = [ [ points[0], ], ] for j in range(1, len(points)): segments[-1].append(points[j]) if tags[j] and j < (len(points) - 1): segments.append([ points[j], ]) ''' Then convert the segments to lines. ''' for segment in segments: #print("segment (len=%d)" % len(segment)) if len(segment) == 2: paths.append( Line(start=tuple2complex(segment[0]), end=tuple2complex(segment[1]))) elif len(segment) == 3: C12 = segment[1] P1 = segment[0] P2 = segment[2] paths.append( QuadraticBezier(start=tuple2complex(P1), control=tuple2complex(C12), end=tuple2complex(P2))) elif len(segment) == 4: C12 = segment[1] C23 = segment[2] P1 = segment[0] P2 = ((segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0) P3 = segment[3] paths.append( QuadraticBezier(start=tuple2complex(P1), control=tuple2complex(C12), end=tuple2complex(P2))) paths.append( QuadraticBezier(start=tuple2complex(P2), control=tuple2complex(C23), end=tuple2complex(P3))) elif len(segment) == 5: C12 = segment[1] C23 = segment[2] C34 = segment[3] P1 = segment[0] P2 = ((segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0) P3 = ((segment[2][0] + segment[3][0]) / 2.0, (segment[2][1] + segment[3][1]) / 2.0) P4 = segment[4] paths.append( QuadraticBezier(start=tuple2complex(P1), control=tuple2complex(C12), end=tuple2complex(P2))) paths.append( QuadraticBezier(start=tuple2complex(P2), control=tuple2complex(C23), end=tuple2complex(P3))) paths.append( QuadraticBezier(start=tuple2complex(P3), control=tuple2complex(C34), end=tuple2complex(P4))) else: # with algo N = len(segment) - 1 # first Ps = segment[0] Ctrl = segment[1] Pe = ((segment[1][0] + segment[2][0]) / 2.0, (segment[1][1] + segment[2][1]) / 2.0) paths.append( QuadraticBezier(start=tuple2complex(Ps), control=tuple2complex(Ctrl), end=tuple2complex(Pe))) # second - ... for k in range(2, len(segment) - 2): Ps = ((segment[k - 1][0] + segment[k][0]) / 2.0, (segment[k - 1][1] + segment[k][1]) / 2.0) Ctrl = segment[k] Pe = ((segment[k][0] + segment[k + 1][0]) / 2.0, (segment[k][1] + segment[k + 1][1]) / 2.0) paths.append( QuadraticBezier(start=tuple2complex(Ps), control=tuple2complex(Ctrl), end=tuple2complex(Pe))) # last Ps = ((segment[N - 2][0] + segment[N - 1][0]) / 2.0, (segment[N - 2][1] + segment[N - 1][1]) / 2.0) Ctrl = segment[N - 1] Pe = segment[N] paths.append( QuadraticBezier(start=tuple2complex(Ps), control=tuple2complex(Ctrl), end=tuple2complex(Pe))) ''' Set the start location to the end location and continue. You can use the svgpathtools Path to merge the paths: ''' start = end + 1 self.path = Path(*paths) return self.path
def char(self, ch): def tuple_to_imag(t): return t[0] + t[1] * 1j #face = Face('/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf') face = Face(ddd.DATA_DIR + '/fonts/OpenSansEmoji.ttf') face.set_char_size(self.char_size) face.load_char(ch) #kerning = face.get_kerning(ch, 'x') # or from previous, actually? #print(kerning) outline = face.glyph.outline y = [t[1] for t in outline.points] # flip the points outline_points = [(p[0], max(y) - p[1]) for p in outline.points] start, end = 0, 0 paths = [] for i in range(len(outline.contours)): end = outline.contours[i] points = outline_points[start:end + 1] points.append(points[0]) tags = outline.tags[start:end + 1] tags.append(tags[0]) segments = [ [ points[0], ], ] for j in range(1, len(points)): segments[-1].append(points[j]) if tags[j] and j < (len(points) - 1): segments.append([ points[j], ]) for segment in segments: if len(segment) == 2: paths.append( Line(start=tuple_to_imag(segment[0]), end=tuple_to_imag(segment[1]))) elif len(segment) == 3: paths.append( QuadraticBezier(start=tuple_to_imag(segment[0]), control=tuple_to_imag(segment[1]), end=tuple_to_imag(segment[2]))) elif len(segment) == 4: paths.append( CubicBezier(start=tuple_to_imag(segment[0]), control1=tuple_to_imag(segment[1]), control2=tuple_to_imag(segment[2]), end=tuple_to_imag(segment[3]))) #C = ((segment[1][0] + segment[2][0]) / 2.0, # (segment[1][1] + segment[2][1]) / 2.0) #paths.append(QuadraticBezier(start=tuple_to_imag(segment[0]), # control=tuple_to_imag(segment[1]), # end=tuple_to_imag(C))) #paths.append(QuadraticBezier(start=tuple_to_imag(C), # control=tuple_to_imag(segment[2]), # end=tuple_to_imag(segment[3]))) start = end + 1 path = Path(*paths) #wsvg(path, filename="/tmp/test.svg") path_d = path.d() # https://gis.stackexchange.com/questions/301605/how-to-create-shape-in-shapely-from-an-svg-path-element # This page also has info about SVG reading! #svgpath = 'M10 10 C 20 20, 40 20, 50 10Z' mpl_path = parse_path(path_d) coords = mpl_path.to_polygons(closed_only=True) item = None for c in coords: # coords[1:]: if len(c) < 3: continue ng = ddd.polygon(c) #.clean(eps=char_size / 100) #.convex_hull() #ng.show() if item is None: item = ng elif item.contains(ng): item = item.subtract(ng) else: item = item.union(ng) item = item.clean( eps=self.char_size / 200) # Note that this is effectively limiting resolution #result = ddd.group([ddd.polygon(c) for c in coords], empty=2) result = item result = result.scale([1.0 / self.char_size, -1.0 / self.char_size]) result = result.simplify( 0.005) # Note that this is effectively limiting resolution return (result, face)