def shaping_string(fontdata, glyphOrder, text, language=None): face = hb.Face(fontdata) font = hb.Font(face) upem = face.upem font.scale = (upem, upem) hb.ot_font_set_funcs(font) buf = hb.Buffer() buf.add_str(text) buf.guess_segment_properties() if language: buf.language = language features = {"kern": True, "liga": True} hb.shape(font, buf, features) infos = buf.glyph_infos positions = buf.glyph_positions outs = [] for info, pos in zip(buf.glyph_infos, buf.glyph_positions): name = glyphOrder[info.codepoint] if name in ignorables: continue outs.append("%s=%i" % (name, info.cluster)) if pos.position[0] != 0 or pos.position[1] != 0: outs[-1] = outs[-1] + "<%i,%i>" % (pos.position[0], pos.position[1]) return "|".join(outs)
def prepare_shaper(self): face = hb.Face(self.fontdata) font = hb.Font(face) upem = face.upem font.scale = (upem, upem) hb.ot_font_set_funcs(font) self.hbfont = font
def __isEmojiSupportedByFont(self, emoji: Emoji) -> bool: # Load font (has to be done for call): face = Face(self.fontdata) font = Font(face) upem = face.upem font.scale = (upem, upem) ot_font_set_funcs(font) # Create text buffer: buf = Buffer() buf.add_str(emoji.emoji) buf.guess_segment_properties() # Shape text: features = {"kern": True, "liga": True} shape(font, buf, features) infos = buf.glyph_infos # Remove all variant selectors: while len(infos) > 0 and infos[-1].codepoint == 3: infos = infos[:-1] # Filter empty: if len(infos) <= 0: return False # Remove uncombined ending with skin tone like "👭🏿": lastCp = infos[-1].codepoint if lastCp == 1076 or lastCp == 1079 or lastCp == 1082 or lastCp == 1085 or lastCp == 1088: return False # If there is a code point 0 => Emoji not fully supported by font: return all(info.codepoint != 0 and info.codepoint != 3 for info in infos)
def shape(self, text, *, features=None, varLocation=None, direction=None, language=None, script=None): if features is None: features = {} if varLocation is None: varLocation = {} self.font.scale = (self.face.upem, self.face.upem) self.font.set_variations(varLocation) hb.ot_font_set_funcs(self.font) if self._funcs is not None: self.font.funcs = self._funcs buf = hb.Buffer.create() buf.add_str(str(text)) # add_str() does not accept str subclasses buf.guess_segment_properties() if direction is not None: buf.direction = direction if language is not None: buf.language = language if script is not None: buf.script = script hb.shape(self.font, buf, features) glyphOrder = self.glyphOrder infos = [] for info, pos in zip(buf.glyph_infos, buf.glyph_positions): infos.append(GlyphInfo(info.codepoint, glyphOrder[info.codepoint], info.cluster, *pos.position)) return infos
def ttf_font(ttf_path): with open(ttf_path, 'rb') as font_file: font_data = font_file.read() face = hb.Face(font_data) font = hb.Font(face) upem = face.upem font.scale = (upem, upem) hb.ot_font_set_funcs(font) return font
def latest_ttf(latest_ttf_path): """HB Font from latest TTF""" with open(latest_ttf_path, 'rb') as font_file: font_data = font_file.read() face = hb.Face(font_data) font = hb.Font(face) upem = face.upem font.scale = (upem, upem) hb.ot_font_set_funcs(font) return font
def opensans(): """Return a subset of OpenSans.ttf containing the following glyphs/characters: [ {gid=0, name=".notdef"}, {gid=1, name="A", code=0x41}, ] """ face = hb.Face(OPEN_SANS_TTF_PATH.read_bytes()) font = hb.Font(face) upem = face.upem font.scale = (upem, upem) hb.ot_font_set_funcs(font) return font
def emojiSupported(emoji: str, fontdata) -> bool: """ This function checks for support for a given emoji in a font file, particularly the multi-byte ZWJ sequences. Many thanks to StackOverflow user COM8 for this code. https://stackoverflow.com/a/55560968/1174966 """ # Load font (has to be done for call): face = Face(fontdata) font = UFont(face) upem = face.upem font.scale = (upem, upem) ot_font_set_funcs(font) # Create text buffer: buf = Buffer() buf.add_str(emoji) buf.guess_segment_properties() # Shape text: features = {"kern": True, "liga": True} shape(font, buf, features) infos = buf.glyph_infos # Remove all variant selectors: while len(infos) > 0 and infos[-1].codepoint == 3: infos = infos[:-1] # Filter empty: if len(infos) <= 0: return False # Remove uncombined, ending with skin tone like "ðŸ‘ðŸ�¿": lastCp = infos[-1].codepoint print(lastCp) badCp = [1076, 1079, 1082, 1085, 1088] if lastCp in badCp: return False # If there is a code point 0 or 3 => Emoji not fully supported by font: return all(info.codepoint != 0 and info.codepoint != 3 for info in infos)
def get_mhbfont( ctx, path ): import uharfbuzz as HB ### NOTE import after ctx available ### cache = _get_cache( ctx ) R = cache.fonts.get( path, None ) if R != None: # ctx.log( '^myharfbuzz/get_mhbfont@334^', "font cached: {}".format( path ) ) return R #......................................................................................................... ctx.log( '^myharfbuzz/get_mhbfont@335^', "reading font: {}".format( path ) ) with open( path, 'rb' ) as fontfile: fontdata = fontfile.read() R = ctx.AttributeDict() R.face = HB.Face( fontdata ) R.font = HB.Font( R.face ) R.upem = R.face.upem R.font.scale = ( R.upem, R.upem, ) HB.ot_font_set_funcs( R.font ) #......................................................................................................... cache.fonts[ path ] = R return R
def blankfont(): """Return a subset of AdobeBlank.ttf containing the following glyphs/characters: [ {gid=0, name=".notdef"}, {gid=1, name="a", code=0x61}, {gid=2, name="b", code=0x62}, {gid=3, name="c", code=0x63}, {gid=4, name="d", code=0x64}, {gid=5, name="e", code=0x65}, {gid=6, name="ccedilla", code=0x62}, {gid=7, name="uni0431", code=0x0431}, # CYRILLIC SMALL LETTER BE {gid=8, name="u1F4A9", code=0x1F4A9}, # PILE OF POO ] """ face = hb.Face(ADOBE_BLANK_TTF_PATH.read_bytes()) font = hb.Font(face) upem = face.upem font.scale = (upem, upem) hb.ot_font_set_funcs(font) return font
def shapeHB(text, font_name, font_size, features: Dict[str, bool] = None): font = pdfmetrics.getFont(font_name) if not isinstance(font, TTFont): # TODO make valid for all types of fonts raise RLKerningError("Not a TTF font") fontdata = font.face._ttf_data face = hb.Face(fontdata) font = hb.Font(face) # HB scales to integers in offset and advance so very big scale # will divide by SCALE_MULT to get the actual size in fractional points font.scale = (font_size * SCALE_MULT, font_size * SCALE_MULT) hb.ot_font_set_funcs(font) buf = hb.Buffer() buf.add_str(text) buf.guess_segment_properties() hb.shape(font, buf, features) return buf
def shaper(s, tf, size): face = hb.Face.create_for_tables(getSkiaFontTable, tf) font = hb.Font(face) upem = size * 64.0 font.scale = (upem, upem) hb.ot_font_set_funcs(font) buf = hb.Buffer() buf.add_utf8(s.encode('utf-8')) # buf.add_str(s) buf.guess_segment_properties() features = {"kern": True, "liga": True} # features = {} hb.shape(font, buf, features) infos = buf.glyph_infos positions = buf.glyph_positions glyphs = [info.codepoint for info in infos] positions = [(pos.x_advance / 64.0, pos.x_offset / 64.0, pos.y_offset / 64.0) for pos in positions] return (glyphs, positions)
def emojiSupported(emoji: str, fontdata) -> bool: # Load font (has to be done for call): face = Face(fontdata) font = BuzzFont(face) upem = face.upem font.scale = (upem, upem) ot_font_set_funcs(font) # Create text buffer: buf = Buffer() buf.add_str(emoji) buf.guess_segment_properties() # Shape text: features = {"kern": True, "liga": True} shape(font, buf, features) infos = buf.glyph_infos # Remove all variant selectors: while len(infos) > 0 and infos[-1].codepoint == 3: infos = infos[:-1] # Filter empty: if len(infos) <= 0: return False # Remove uncombined, ending with skin tone like "ðŸ‘ðŸ�¿": lastCp = infos[-1].codepoint # print(lastCp) badCp = [1076, 1079, 1082, 1085, 1088] if lastCp in badCp: return False # If there is a code point 0 or 3 => Emoji not fully supported by font: return all(info.codepoint != 0 and info.codepoint != 3 for info in infos)
def _to_png(self, font, font_position=None, dst=None, limit=800, size=1500, tab_width=1500, padding_characters=""): """Use HB, FreeType and Cairo to produce a png for a table. Parameters ---------- font: DFont font_position: str Label indicating which font has been used. dst: str Path to output image. If no path is given, return in-memory """ # TODO (M Foley) better packaging for pycairo, freetype-py # and uharfbuzz. # Users should be able to pip install these bindings without needing # to install the correct libs. # A special mention to the individuals who maintain these packages. Using # these dependencies has sped up the process of creating diff images # significantly. It's an incredible age we live in. y_tab = int(1500 / 25) x_tab = int(tab_width / 64) width, height = 1024, 200 cells_per_row = int((width - x_tab) / x_tab) # Compute height of image x, y, baseline = x_tab, 0, 0 for idx, row in enumerate(self._data[:limit]): x += x_tab if idx % cells_per_row == 0: y += y_tab x = x_tab height += y height += 100 # draw image Z = ImageSurface(FORMAT_ARGB32, width, height) ctx = Context(Z) ctx.rectangle(0, 0, width, height) ctx.set_source_rgb(1, 1, 1) ctx.fill() # label image ctx.set_font_size(30) ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.move_to(x_tab, 50) ctx.show_text("{}: {}".format(self.table_name, len(self._data))) ctx.move_to(x_tab, 100) if font_position: ctx.show_text("Font Set: {}".format(font_position)) if len(self._data) > limit: ctx.set_font_size(20) ctx.move_to(x_tab, 150) ctx.show_text( "Warning: {} different items. Only showing most serious {}". format(len(self._data), limit)) hb.ot_font_set_funcs(font.hbfont) # Draw glyphs x, y, baseline = x_tab, 200, 0 x_pos = x_tab y_pos = 200 for idx, row in enumerate(self._data[:limit]): string = "{}{}{}".format(padding_characters, row['string'], padding_characters) buf = self._shape_string(font, string, row['features']) char_info = buf.glyph_infos char_pos = buf.glyph_positions for info, pos in zip(char_info, char_pos): gid = info.codepoint font.ftfont.load_glyph(gid, flags=6) bitmap = font.ftslot.bitmap if bitmap.width > 0: ctx.set_source_rgb(0, 0, 0) glyph_surface = _make_image_surface( font.ftfont.glyph.bitmap, copy=False) ctx.set_source_surface( glyph_surface, x_pos + font.ftslot.bitmap_left + (pos.x_offset / 64.), y_pos - font.ftslot.bitmap_top - (pos.y_offset / 64.)) glyph_surface.flush() ctx.paint() x_pos += (pos.x_advance) / 64. y_pos += (pos.y_advance) / 64. x_pos += x_tab - (x_pos % x_tab) if idx % cells_per_row == 0: # add label if font_position: ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.set_font_size(10) ctx.move_to(width - 20, y_pos) ctx.rotate(1.5708) ctx.show_text(font_position) ctx.set_source_rgb(0, 0, 0) ctx.rotate(-1.5708) # Start a new row y_pos += y_tab x_pos = x_tab Z.flush() if dst: Z.write_to_png(dst) else: img = StringIO() Z.write_to_png(img) return Image.open(img)
def _shape(face, font, text, fontSize=None, startPos=(0, 0), startCluster=0, flippedCanvas=False, *, features=None, variations=None, direction=None, language=None, script=None): if features is None: features = {} if variations is None: variations = {} if fontSize is None: fontScaleX = fontScaleY = 1 else: fontScaleX = fontScaleY = fontSize / face.upem if flippedCanvas: fontScaleY = -fontScaleY font.scale = (face.upem, face.upem) font.set_variations(variations) hb.ot_font_set_funcs(font) buf = hb.Buffer.create() buf.add_str(text) # add_str() does not accept str subclasses buf.guess_segment_properties() buf.cluster_level = hb.BufferClusterLevel.MONOTONE_CHARACTERS if direction is not None: buf.direction = direction if language is not None: buf.language = language if script is not None: buf.script = script hb.shape(font, buf, features) gids = [info.codepoint for info in buf.glyph_infos] clusters = [info.cluster + startCluster for info in buf.glyph_infos] positions = [] startPosX, startPosY = startPos x = y = 0 for pos in buf.glyph_positions: dx, dy, ax, ay = pos.position positions.append(( startPosX + (x + dx) * fontScaleX, startPosY + (y + dy) * fontScaleY, )) x += ax y += ay endPos = startPosX + x * fontScaleX, startPosY + y * fontScaleY return SimpleNamespace( gids=gids, clusters=clusters, positions=positions, endPos=endPos, )
def __init__(self, path: Path): self.path = path face = hb.Face(path.read_bytes()) # type: ignore self.font = hb.Font(face) # type: ignore hb.ot_font_set_funcs(self.font) # type: ignore