def plotTextString(stringToPlot, kerning=False, startXY=(0,0)): fontPath = "/home/meawoppl/Dropbox/repos/babyfood/cmr10.pfb" typeFace = Face(fontPath) typeFace.attach_file("/home/meawoppl/Dropbox/repos/babyfood/cmr10.pfm") typeFace.set_char_size(48 * 64) figure() startX, startY = startXY for n, char in enumerate(stringToPlot): typeFace.load_char(char) loopz = unpackCharacter(typeFace.glyph) loopz = shiftLoopSet(loopz, startX, startY) startX += typeFace.glyph.advance.x startY += typeFace.glyph.advance.y if kerning and (n != 0): kv = typeFace.get_kerning(char, stringToPlot[n-1]) print(char, stringToPlot[n-1]) print(kv.x, kv.y) print(dir(kv)) startX += kv.x plotLoopSets(loopz) axis("equal") show() close()
def __get_path(self, c, fname=expanduser('~/Library/Fonts/keifont.ttf')): """ This function is presented originally in freetype-py GitHub repository: we can refer the source 'glyph-vector.py' in the examples directory. """ face = Face(fname) face.set_char_size(24 * 64) face.load_char(c) slot = face.glyph outline = slot.outline start, end = 0, 0 VERTS, CODES = [], [] for i in xrange(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] & (1 << 0) and j < (len(points) - 1): segments.append([ points[j], ]) verts = [ points[0], ] codes = [ Path.MOVETO, ] for segment in segments: if len(segment) == 2: verts.extend(segment[1:]) codes.extend([Path.LINETO]) elif len(segment) == 3: verts.extend(segment[1:]) codes.extend([Path.CURVE3, Path.CURVE3]) else: verts.append(segment[1]) codes.append(Path.CURVE3) for i in range(1, len(segment) - 2): A, B = segment[i], segment[i + 1] C = ((A[0] + B[0]) / 2.0, (A[1] + B[1]) / 2.0) verts.extend([C, B]) codes.extend([Path.CURVE3, Path.CURVE3]) verts.append(segment[-1]) codes.append(Path.CURVE3) VERTS.extend(verts) CODES.extend(codes) start = end + 1 return Path(VERTS, CODES)
def get_bounding_box(self): face = Face('UbuntuMono-R.ttf') face.set_char_size(self.size) face.load_char('g') total_width = 0 total_height = 0 for char in self.text: total_width += face.glyph.metrics.width total_height = max(total_height, face.glyph.metrics.width) # For the moment return return Box(self.position, [total_width, total_height])
def __init__(self, face: freetype.Face, char: str) -> None: if face.load_char(char, freetype.FT_LOAD_RENDER): raise RuntimeError('failed to load char \'%s\'' % char) glyph = face.glyph bitmap = glyph.bitmap assert bitmap.pixel_mode == freetype.FT_PIXEL_MODE_GRAY, \ "We haven't implemented support for other pixel modes" glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT) glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE) glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) glPixelStorei(GL_UNPACK_ALIGNMENT, 1) self._texture_id = glGenTextures(1) self._width = bitmap.width self._height = bitmap.rows self._descender = glyph.bitmap_top - self._height self._bearing_x = glyph.bitmap_left self._advance = numpy.array( [face.glyph.advance.x / 64.0, face.glyph.advance.y / 64.0]) glBindTexture(GL_TEXTURE_2D, self._texture_id) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) data = numpy.array(bitmap.buffer, numpy.ubyte).reshape(self._height, self._width) glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, self._width, self._height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, numpy.flipud(data)) glPopClientAttrib()
def fn_SortGlyphs(o_FreetypeFace: freetype.Face, s_Chars: str, b_Invert: bool = False) -> list: """ : : Sorts glyphs based on pixel density. : : : Args: : freetype.Face o_FreetypeFace : Font face used to calculate pixel densities : str s_Chars : Character set over which pixel densities will be taken : bool b_Invert : Whether to invert pixel density mapping order (default False) : : Returns : : """ l_Output = [] # Profile pixel density over character set # ... for c in s_Chars: o_FreetypeFace.load_char(c) b_Buffer = o_FreetypeFace.glyph.bitmap.buffer l_Output.append((sum(b_Buffer), c)) # Sort output list by pixel density # ... l_Output = [y[1] for y in sorted(l_Output, key=lambda x: x[0])] # Optionally invert order # ... if b_Invert: l_Output = l_Output[::-1] # Return it # ... return l_Output
class TextRenderer(object): _VERT_SHADER_SRC_PATH = os.path.join(_here, 'shaders', 'text_renderer_vert.glsl') _FRAG_SHADER_SRC_PATH = os.path.join(_here, 'shaders', 'text_renderer_frag.glsl') _VERT_SHADER_SRC = None _FRAG_SHADER_SRC = None def __init__(self, font_file=os.path.join(_here, 'fonts', 'VeraMono.ttf'), size=72 * 16): _logger.debug('loading %s...', font_file) self._font_file = font_file self._face = Face(font_file) self._face.set_char_size(size) width, max_asc, max_desc = 0, 0, 0 widths = [] for c in range(32, 128): self._face.load_char(chr(c), FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) bitmap = self._face.glyph.bitmap width = max(width, bitmap.width) max_asc = max(max_asc, self._face.glyph.bitmap_top) max_desc = max(max_desc, bitmap.rows - self._face.glyph.bitmap_top) widths.append(bitmap.width) self._max_asc = max_asc self._widths = np.array(widths) self._width = width self._height = max_asc + max_desc self._read_shader_src() self._screen_size = (800.0, 600.0) self._texcoords = np.zeros((32, 2), dtype=np.float32) self._gl_initialized = False def set_screen_size(self, screen_size): self._screen_size = screen_size def init_gl(self): if self._gl_initialized: return import OpenGL.GL as gl self._texture_unit = 4 vs_id = gl.glCreateShader(gl.GL_VERTEX_SHADER) gl.glShaderSource(vs_id, self._VERT_SHADER_SRC) gl.glCompileShader(vs_id) if not gl.glGetShaderiv(vs_id, gl.GL_COMPILE_STATUS): raise Exception('failed to compile %s vertex shader:\n%s' % (self.__class__.__name__, gl.glGetShaderInfoLog(vs_id).decode())) fs_id = gl.glCreateShader(gl.GL_FRAGMENT_SHADER) gl.glShaderSource(fs_id, self._FRAG_SHADER_SRC) gl.glCompileShader(fs_id) if not gl.glGetShaderiv(fs_id, gl.GL_COMPILE_STATUS): raise Exception('failed to compile %s fragment shader:\n%s' % (self.__class__.__name__, gl.glGetShaderInfoLog(fs_id).decode())) self._program_id = gl.glCreateProgram() gl.glAttachShader(self._program_id, vs_id) gl.glAttachShader(self._program_id, fs_id) gl.glLinkProgram(self._program_id) gl.glDetachShader(self._program_id, vs_id) gl.glDetachShader(self._program_id, fs_id) if not gl.glGetProgramiv(self._program_id, gl.GL_LINK_STATUS): raise Exception('failed to link program for %s' % self.__class__.__name__) self._attribute_locations = { attribute: gl.glGetAttribLocation(self._program_id, attribute) for attribute in self._ATTRIBUTES } self._uniform_locations = { uniform: gl.glGetUniformLocation(self._program_id, uniform) for uniform in self._UNIFORMS } width, height = self._width, self._height self._image_width, self._image_height = image_width, image_height = width * 16, height * 6 bitmap_buffer = np.zeros((image_height, image_width), dtype=np.ubyte) self._char_to_texcoords = {} for j in range(6): for i in range(16): i_char = j * 16 + i char = chr(32 + i_char) self._char_to_texcoords[char] = (i / 16.0, j / 6.0) self._face.load_char(char, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) glyph = self._face.glyph bitmap = glyph.bitmap x = i * width + glyph.bitmap_left y = j * height + self._max_asc - glyph.bitmap_top bitmap_buffer[y:y + bitmap.rows, x:x + bitmap.width].flat = bitmap.buffer self._texture_id = gl.glGenTextures(1) gl.glBindTexture(gl.GL_TEXTURE_2D, self._texture_id) self._sampler_id = gl.glGenSamplers(1) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) gl.glBindSampler(self._texture_unit, self._sampler_id) gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RED, image_width, image_height, 0, gl.GL_RED, gl.GL_UNSIGNED_BYTE, bitmap_buffer) gl.glGenerateMipmap(gl.GL_TEXTURE_2D) gl.glBindTexture(gl.GL_TEXTURE_2D, 0) if gl.glGetError() != gl.GL_NO_ERROR: raise Exception('failed to create font texture') self._gl_initialized = True _logger.debug('%s.init_gl: OK', self.__class__.__name__) def draw_text(self, text, color=(1.0, 1.0, 0.0, 0.0), screen_position=(0.0, 0.0)): import OpenGL.GL as gl gl.glUseProgram(self._program_id) gl.glActiveTexture(gl.GL_TEXTURE0 + self._texture_unit) gl.glBindTexture(gl.GL_TEXTURE_2D, self._texture_id) #gl.glBindSampler(tex_unit, self._sampler_id) gl.glUniform1i(self._uniform_locations['u_fonttex'], self._texture_unit) gl.glUniform4f(self._uniform_locations['u_color'], *color) gl.glUniform2f(self._uniform_locations['u_screen_size'], *self._screen_size) gl.glUniform2f(self._uniform_locations['u_char_size'], self._width, self._height) gl.glUniform2f(self._uniform_locations['u_screen_position'], *screen_position) gl.glUniform2f(self._uniform_locations['u_fonttex_size'], self._image_width, self._image_height) nchars = len(text) gl.glUniform1ui(self._uniform_locations['u_nchars'], nchars) self._texcoords[:nchars] = [self._char_to_texcoords[c] for c in text] gl.glUniform2fv(self._uniform_locations['u_texcoords'], nchars, self._texcoords) gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glDisable(gl.GL_BLEND) def save_image(self, filename=None): from PIL import Image if filename is None: filename = os.path.split_ext(os.path.basename( self._font_file))[0] + '.png' _logger.debug('filename = %s', filename) width, height = self._width, self._height image_width, image_height = width * 16, height * 6 bitmap_buffer = np.zeros((image_height, image_width), dtype=np.ubyte) for j in range(6): for i in range(16): i_char = j * 16 + i char = chr(32 + i_char) self._face.load_char(char, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) glyph = self._face.glyph bitmap = glyph.bitmap x = i * width + glyph.bitmap_left y = j * height + self._max_asc - glyph.bitmap_top bitmap_buffer[y:y + bitmap.rows, x:x + bitmap.width].flat = bitmap.buffer image = Image.new('L', (bitmap_buffer.shape[1], bitmap_buffer.shape[0])) image.putdata(list(bitmap_buffer.ravel())) image.save(filename) _logger.info('...saved to %s', filename) @classmethod def _read_shader_src(cls): if cls._VERT_SHADER_SRC is None: with open(cls._VERT_SHADER_SRC_PATH) as f: cls._VERT_SHADER_SRC = f.read() attr_matches = _ATTRIBUTE_DECL_RE.finditer(cls._VERT_SHADER_SRC) attributes = [] for m in attr_matches: attributes.append(m['attribute_name']) cls._ATTRIBUTES = attributes if cls._FRAG_SHADER_SRC is None: with open(cls._FRAG_SHADER_SRC_PATH) as f: cls._FRAG_SHADER_SRC = f.read() unif_matches = _UNIFORM_DECL_RE.finditer(cls._FRAG_SHADER_SRC) uniforms = [] for m in unif_matches: uniforms.append(m['uniform_name']) cls._UNIFORMS = uniforms
def setup(self): ''' Construct the texture atlas for the font ''' face = Face(self.filename) face.set_pixel_sizes(0, self.size) rowh, roww = 0,0 # Determine image size for i in xrange(32,128): face.load_char( chr(i), FT_LOAD_RENDER) bitmap = face.glyph.bitmap if roww + bitmap.width + 1 >= 1024: # max texture width self.w = max(self.w, roww) self.h += rowh roww = 0 rowh = 0 roww += bitmap.width + 1 rowh = max(rowh, bitmap.rows) self.w = max(self.w, roww) self.h += rowh ## Create texture to hold ASCII glyphs # Ensure no texture is currently selected glActiveTexture(GL_TEXTURE0) self.texid = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.texid) glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, self.w, self.h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0) # We require 1 byte alignment when uploading texture data glPixelStorei(GL_UNPACK_ALIGNMENT, 1) # Clamping to edges is important to prevent artifacts when scaling glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) # Linear filtering looks better for text glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) # Add glyphs to texture ox = 0 oy = 0 rowh = 0 # class to hold data class CharInfo: pass for i in xrange(32,128): face.load_char( chr(i), FT_LOAD_RENDER) g = face.glyph bitmap = g.bitmap if ox + bitmap.width + 1 >= 1024: # max texture width oy += rowh rowh = 0 ox = 0 glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, bitmap.width, bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.buffer) ci = CharInfo() ci.ax = float(g.advance.x >> 6) ci.ay = float(g.advance.y >> 6) ci.bw = float(bitmap.width) ci.bh = float(bitmap.rows) ci.bl = float(g.bitmap_left) ci.bt = float(g.bitmap_top) ci.tx = float(ox) / float(self.w) ci.ty = float(oy) / float(self.h) self.c[chr(i)] = ci rowh = max(rowh, bitmap.rows) ox += bitmap.width + 1
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
from svgpathtools import wsvg, Line, QuadraticBezier, Path from freetype import Face def tuple_to_imag(t): return t[0] + t[1] * 1j face = Face('./Vera.ttf') face.set_char_size(48 * 64) face.load_char('a') 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], ])
class TextRenderer(object): _VERT_SHADER_SRC_PATH = os.path.join(_here, 'shaders', 'text_renderer_vert.glsl') _FRAG_SHADER_SRC_PATH = os.path.join(_here, 'shaders', 'text_renderer_frag.glsl') _VERT_SHADER_SRC = None _FRAG_SHADER_SRC = None def __init__(self, font_file=os.path.join(_here, 'fonts', 'VeraMono.ttf'), size=72*16): _logger.debug('loading %s...', font_file) self._font_file = font_file self._face = Face(font_file) self._face.set_char_size(size) width, max_asc, max_desc = 0, 0, 0 widths = [] for c in range(32, 128): self._face.load_char(chr(c), FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) bitmap = self._face.glyph.bitmap width = max(width, bitmap.width) max_asc = max(max_asc, self._face.glyph.bitmap_top) max_desc = max(max_desc, bitmap.rows-self._face.glyph.bitmap_top) widths.append(bitmap.width) self._max_asc = max_asc self._widths = np.array(widths) self._width = width self._height = max_asc + max_desc self._read_shader_src() self._screen_size = (800.0, 600.0) self._texcoords = np.zeros((32, 2), dtype=np.float32) self._gl_initialized = False def set_screen_size(self, screen_size): self._screen_size = screen_size def init_gl(self): if self._gl_initialized: return import OpenGL.GL as gl self._texture_unit = 4 vs_id = gl.glCreateShader(gl.GL_VERTEX_SHADER) gl.glShaderSource(vs_id, self._VERT_SHADER_SRC) gl.glCompileShader(vs_id) if not gl.glGetShaderiv(vs_id, gl.GL_COMPILE_STATUS): raise Exception('failed to compile %s vertex shader:\n%s' % (self.__class__.__name__, gl.glGetShaderInfoLog(vs_id).decode())) fs_id = gl.glCreateShader(gl.GL_FRAGMENT_SHADER) gl.glShaderSource(fs_id, self._FRAG_SHADER_SRC) gl.glCompileShader(fs_id) if not gl.glGetShaderiv(fs_id, gl.GL_COMPILE_STATUS): raise Exception('failed to compile %s fragment shader:\n%s' % (self.__class__.__name__, gl.glGetShaderInfoLog(fs_id).decode())) self._program_id = gl.glCreateProgram() gl.glAttachShader(self._program_id, vs_id) gl.glAttachShader(self._program_id, fs_id) gl.glLinkProgram(self._program_id) gl.glDetachShader(self._program_id, vs_id) gl.glDetachShader(self._program_id, fs_id) if not gl.glGetProgramiv(self._program_id, gl.GL_LINK_STATUS): raise Exception('failed to link program for %s' % self.__class__.__name__) self._attribute_locations = {attribute: gl.glGetAttribLocation(self._program_id, attribute) for attribute in self._ATTRIBUTES} self._uniform_locations = {uniform: gl.glGetUniformLocation(self._program_id, uniform) for uniform in self._UNIFORMS} width, height = self._width, self._height self._image_width, self._image_height = image_width, image_height = width * 16, height * 6 bitmap_buffer = np.zeros((image_height, image_width), dtype=np.ubyte) self._char_to_texcoords = {} for j in range(6): for i in range(16): i_char = j * 16 + i char = chr(32 + i_char) self._char_to_texcoords[char] = (i / 16.0, j / 6.0) self._face.load_char(char, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) glyph = self._face.glyph bitmap = glyph.bitmap x = i*width + glyph.bitmap_left y = j*height + self._max_asc - glyph.bitmap_top bitmap_buffer[y:y+bitmap.rows,x:x+bitmap.width].flat = bitmap.buffer self._texture_id = gl.glGenTextures(1) gl.glBindTexture(gl.GL_TEXTURE_2D, self._texture_id) self._sampler_id = gl.glGenSamplers(1) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) gl.glSamplerParameteri(self._sampler_id, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) gl.glBindSampler(self._texture_unit, self._sampler_id) gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RED, image_width, image_height, 0, gl.GL_RED, gl.GL_UNSIGNED_BYTE, bitmap_buffer) gl.glGenerateMipmap(gl.GL_TEXTURE_2D) gl.glBindTexture(gl.GL_TEXTURE_2D, 0) if gl.glGetError() != gl.GL_NO_ERROR: raise Exception('failed to create font texture') self._gl_initialized = True _logger.debug('%s.init_gl: OK', self.__class__.__name__) def draw_text(self, text, color=(1.0, 1.0, 0.0, 0.0), screen_position=(0.0, 0.0)): import OpenGL.GL as gl gl.glUseProgram(self._program_id) gl.glActiveTexture(gl.GL_TEXTURE0+self._texture_unit) gl.glBindTexture(gl.GL_TEXTURE_2D, self._texture_id) #gl.glBindSampler(tex_unit, self._sampler_id) gl.glUniform1i(self._uniform_locations['u_fonttex'], self._texture_unit) gl.glUniform4f(self._uniform_locations['u_color'], *color) gl.glUniform2f(self._uniform_locations['u_screen_size'], *self._screen_size) gl.glUniform2f(self._uniform_locations['u_char_size'], self._width, self._height) gl.glUniform2f(self._uniform_locations['u_screen_position'], *screen_position) gl.glUniform2f(self._uniform_locations['u_fonttex_size'], self._image_width, self._image_height) nchars = len(text) gl.glUniform1ui(self._uniform_locations['u_nchars'], nchars) self._texcoords[:nchars] = [self._char_to_texcoords[c] for c in text] gl.glUniform2fv(self._uniform_locations['u_texcoords'], nchars, self._texcoords) gl.glEnable(gl.GL_BLEND) gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) gl.glDrawArrays(gl.GL_TRIANGLE_STRIP, 0, 4) gl.glDisable(gl.GL_BLEND) def save_image(self, filename=None): from PIL import Image if filename is None: filename = os.path.split_ext(os.path.basename(self._font_file))[0] + '.png' _logger.debug('filename = %s', filename) width, height = self._width, self._height image_width, image_height = width * 16, height * 6 bitmap_buffer = np.zeros((image_height, image_width), dtype=np.ubyte) for j in range(6): for i in range(16): i_char = j * 16 + i char = chr(32 + i_char) self._face.load_char(char, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) glyph = self._face.glyph bitmap = glyph.bitmap x = i*width + glyph.bitmap_left y = j*height + self._max_asc - glyph.bitmap_top bitmap_buffer[y:y+bitmap.rows,x:x+bitmap.width].flat = bitmap.buffer image = Image.new('L', (bitmap_buffer.shape[1], bitmap_buffer.shape[0])) image.putdata(list(bitmap_buffer.ravel())) image.save(filename) _logger.info('...saved to %s', filename) @classmethod def _read_shader_src(cls): if cls._VERT_SHADER_SRC is None: with open(cls._VERT_SHADER_SRC_PATH) as f: cls._VERT_SHADER_SRC = f.read() attr_matches = _ATTRIBUTE_DECL_RE.finditer(cls._VERT_SHADER_SRC) attributes = [] for m in attr_matches: attributes.append(m['attribute_name']) cls._ATTRIBUTES = attributes if cls._FRAG_SHADER_SRC is None: with open(cls._FRAG_SHADER_SRC_PATH) as f: cls._FRAG_SHADER_SRC = f.read() unif_matches = _UNIFORM_DECL_RE.finditer(cls._FRAG_SHADER_SRC) uniforms = [] for m in unif_matches: uniforms.append(m['uniform_name']) cls._UNIFORMS = uniforms
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)