From c062ac07e6241cf32a4a5655775aa037bdb1e76e Mon Sep 17 00:00:00 2001 From: Jon Bergli Heier Date: Mon, 1 Oct 2007 00:04:47 +0200 Subject: Moved pykfx.py to pykfx/__init__.py Added self.names in Styles along with int support in __getitem__. Added setup.py. Added fscalc, an extension that calculates text sizes for a given font. Using boost.python-wrapper for fscalc. --- fscalc.cpp | 102 ++++++++++++++++++++++++ fscalc.h | 24 ++++++ pykfx.py | 229 ----------------------------------------------------- pykfx/__init__.py | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 17 ++++ wrapper.cpp | 12 +++ 6 files changed, 387 insertions(+), 229 deletions(-) create mode 100644 fscalc.cpp create mode 100644 fscalc.h delete mode 100644 pykfx.py create mode 100644 pykfx/__init__.py create mode 100644 setup.py create mode 100644 wrapper.cpp diff --git a/fscalc.cpp b/fscalc.cpp new file mode 100644 index 0000000..6842a65 --- /dev/null +++ b/fscalc.cpp @@ -0,0 +1,102 @@ +#include +#include "fscalc.h" + +Font::Font(std::string s, int slant, int weight) { + FcPattern *tmp1, *tmp2, *final; + FcResult res; + FcChar8 *filename; + int fontindex; + + if(!(fc = FcInitLoadConfigAndFonts())) + throw(std::runtime_error("failed to initialize fontconfig")); + + if(FT_Init_FreeType(&library)) + throw(std::runtime_error("failed to initialize freetype")); + + aux = FcPatternCreate(); + + tmp1 = FcPatternBuild(NULL, + FC_FAMILY, FcTypeString, s.c_str(), + FC_SLANT, FcTypeInteger, slant, + FC_WEIGHT, FcTypeInteger, weight ? 300 : 100, + NULL); + if(!tmp1) + throw(std::runtime_error("failed to load font")); + + tmp2 = FcFontRenderPrepare(fc, tmp1, aux); + FcPatternDestroy(tmp1); + final = FcFontMatch(fc, tmp2, &res); + FcPatternDestroy(tmp2); + if(!final) + throw(std::runtime_error("failed to load font")); + + if(FcPatternGetString(final, FC_FILE, 0, &filename) != FcResultMatch || FcPatternGetInteger(final, FC_INDEX, 0, &fontindex) != FcResultMatch) { + FcPatternDestroy(final); + throw(std::runtime_error("failed to locate font")); + } + + if(FT_New_Face(library, (char*)filename, fontindex, &face)) { + FcPatternDestroy(final); + throw(std::runtime_error("failed to create new face")); + } + + FcPatternDestroy(final); + FcPatternDestroy(aux); +// FcFini(); +} + +Font::~Font() { + FT_Done_Face(face); + FT_Done_FreeType(library); +} + +void Font::set_font_size(int size) { + FT_Size_RequestRec req; + TT_OS2 *os2; + TT_HoriHeader *hori; + float scale = 1.0; + + FT_Size_Metrics *m = &face->size->metrics; + + os2 = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2); + hori = (TT_HoriHeader*)FT_Get_Sfnt_Table(face, ft_sfnt_hhea); + if(os2 && hori) { + int horisum = hori->Ascender - hori->Descender; + unsigned int winsum = os2->usWinAscent + os2->usWinDescent; + scale = (float)horisum / (float)winsum; + } + + req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM; + req.width = 0; + req.height = (FT_F26Dot6)(size * 64 * scale); + req.horiResolution = 0; + req.vertResolution = 0; + FT_Request_Size(face, &req); +} + +double Font::get_kerning(FT_UInt left, FT_UInt right) { + if(FT_HAS_KERNING(face)) { + FT_Vector d; + FT_Get_Kerning(face, left, right, 0, &d); + return (double)d.x / 64.0; + } else return 0; +} + +unsigned int Font::get_text_width(std::string s) { + FT_UInt prev = 0; + bool kerning = FT_HAS_KERNING(face); + double width = 0; + for(std::string::iterator c = s.begin(); c < s.end(); c++) { + int i = FT_Get_Char_Index(face, *c); + if(kerning && prev && i) + width += get_kerning(prev, i); + if(FT_Load_Char(face, *c, 0)) + throw(std::runtime_error("failed to load char")); + width += (double)face->glyph->advance.x / 64.0; + } + return int(width); +} + +unsigned int Font::get_line_height() { + return face->bbox.yMax - face->bbox.yMin; +} diff --git a/fscalc.h b/fscalc.h new file mode 100644 index 0000000..5046b8f --- /dev/null +++ b/fscalc.h @@ -0,0 +1,24 @@ +#ifndef _FSCALC_H_ +#define _FSCALC_H_ + +#include +#include +#include +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H + +class Font { + FcConfig *fc; + FcPattern *aux; + FT_Library library; + FT_Face face; + public: + Font(std::string, int, int); + ~Font(); + void set_font_size(int); + double get_kerning(FT_UInt, FT_UInt); + unsigned int get_text_width(std::string); + unsigned int get_line_height(); +}; + +#endif diff --git a/pykfx.py b/pykfx.py deleted file mode 100644 index b2cbff6..0000000 --- a/pykfx.py +++ /dev/null @@ -1,229 +0,0 @@ -import re - -class StyleException(Exception): pass -class EventException(Exception): pass -class ScriptException(Exception): pass - -class ScriptInfo: - def __init__(self, f = None): - # original case keys used for writing - self.ock = {} - self.info = {} - if f: - self.read(f) - - def __getitem__(self, key): - try: - return self.info[key.lower()] - except ValueError: - raise KeyError - - def read(self, f): - s = f.readline().strip() - while s: - if not s.startswith(';'): - k, v = s.split(': ', 1) - self.ock[k.lower()] = k - self.info[k.lower()] = v - s = f.readline().strip() - - def write(self, f): - f.write('[Script Info]\n') - f.writelines(['%s: %s\n' % (self.ock[k], self.info[k]) for k in self.info.keys()]) - -class Style: - def __init__(self, format, s = None): - self.format = format - if s: - self.parse(s) - - def __str__(self): - return 'Style: %s' % ','.join([getattr(self, x.lower()) for x in self.format]) - - def parse(self, s): - s = s.split(': ', 1) - if s[0] != 'Style': - raise StyleException('invalid style line') - s = s[1].split(',') - for k, v in zip(self.format, s): - setattr(self, k.lower(), v) - -class Styles: - def __init__(self, f = None): - self.format, self.styles = [], {} - if f: - self.read(f) - - def __getitem__(self, key): - if self.styles.has_key(key.lower()): - return self.styles[key.lower()] - else: - raise KeyError('style "%s" not found' % key) - - def read(self, f): - s = f.readline().strip() - s = s.split(': ', 1) - if s[0] != 'Format': - raise StyleException('format line not found') - self.format = s[1].split(', ') - - s = f.readline().strip() - while s: - s = Style(self.format, s) - self.styles[s.name.lower()] = s - s = f.readline().split() - - def write(self, f): - f.write('[V4+ Styles]\n') - f.write('Format: %s\n' % ', '.join(self.format)) - f.writelines(['%s\n' % str(s) for s in self.styles.values()]) - -class Syllable: - def __init__(self, start, end, text): - self.start, self.end, self.dur, self.text = start, end, end - start, text - - def __str__(self): - return '{\\k%d}%s' % (self.dur / 10, self.text) - -class Event: - def __init__(self, format, s = None): - self.karaoke = [] - self.format = format - if s: - self.parse(s) - - def __getitem__(self, key): - if type(key) is int: - return self.karaoke[key] - else: - raise TypeError('key must be int') - - def __str__(self): - return '%s: %s' % (self.kind.capitalize(), ','.join([getattr(self, x.lower()) for x in self.format])) - - def parse(self, s): - s = s.split(': ', 1) - self.kind = s[0].lower() - s = s[1].split(',') - for k, v in zip(self.format, s): - setattr(self, k.lower(), v) - if self.kind == 'dialogue' and self.text.find('\\k') > -1: - self.parse_karaoke() - - def parse_karaoke(self): - s = self.text - i = 0 - tot = 0 - while i < len(s): - i = s.find('\\k', i) - # break if no more \k's (end-of-line-text is added at the end of the loop) - if i == -1: - break - i += 2 - ms = '' - while s[i].isdigit(): - ms += s[i] - i += 1 - ms = int(ms) * 10 - tot += ms - # unless next is another override, skip one char (past '}') - if s[i] != '\\': - i += 1 - n = s.find('\\k', i) - if n > -1: - part = s[i:n-1] # get text up to next \k - else: - part = s[i:] # end of line - - # go to next if part is empty - if not part.strip(): - continue - - self.karaoke.append(Syllable(tot - ms, tot, part)) - - def add_lead_in(self, s): - t = self.start.split(':') - ts = float(t[-1]) + 60 * int(t[-2]) + 3600 * int(t[-3]) - s - th, ts = int(ts / 3600), ts % 3600 - tm, ts = int(ts / 60), ts % 60 - self.start = '%01d:%02d:%05.2f' % (th, tm, ts) - - def add_lead_out(self, s): - t = self.snd.split(':') - ts = float(t[-1]) + 60 * int(t[-2]) + 3600 * int(t[-3]) + s - th, ts = int(ts / 3600), ts % 3600 - tm, ts = int(ts / 60), ts % 60 - self.end = '%01d:%02d:%05.2f' % (th, tm, ts) - -class Events: - def __init__(self, f = None): - self.format, self.events = [], [] - if f: - self.read(f) - - def __getitem__(self, key): - if type(key) is int: - return self.events[key] - else: - raise TypeError('key must be int') - - def add(self, s = None, e = None): - if not e and s: - e = Event(self.format, s) - if e: - self.events.append(e) - return e - - def read(self, f): - s = f.readline().strip() - s = s.split(': ', 1) - if s[0] != 'Format': - raise EventException('format line not found') - self.format = s[1].split(', ') - - s = f.readline().strip() - while s: - self.add(s) - s = f.readline().strip() - - def write(self, f): - f.write('[Events]\n') - f.write('Format: %s\n' % ', '.join(self.format)) - f.writelines(['%s\n' % str(e) for e in self.events]) - -class Script: - def __init__(self, f = None): - if f: - self.read(f) - - def read(self, f): - f = open(f, 'r') - - s = f.readline().strip() - if s.startswith('\xef\xbb\xbf'): - s = s[3:] - if s == '[Script Info]': - self.info = ScriptInfo(f) - else: - raise ScriptException('script info not found') - - s = f.readline().strip() - if re.match('\[V4\+? Styles\]', s): - self.styles = Styles(f) - else: - raise ScriptException('script styles not found') - - s = f.readline().strip() - if s == '[Events]': - self.events = Events(f) - else: - raise ScriptException('script events not found') - - def write(self, f): - f = open(f, 'w') - self.info.write(f) - f.write('\n') - self.styles.write(f) - f.write('\n') - self.events.write(f) - f.write('\n') diff --git a/pykfx/__init__.py b/pykfx/__init__.py new file mode 100644 index 0000000..4ef55f1 --- /dev/null +++ b/pykfx/__init__.py @@ -0,0 +1,232 @@ +import re + +class StyleException(Exception): pass +class EventException(Exception): pass +class ScriptException(Exception): pass + +class ScriptInfo: + def __init__(self, f = None): + # original case keys used for writing + self.ock = {} + self.info = {} + if f: + self.read(f) + + def __getitem__(self, key): + try: + return self.info[key.lower()] + except ValueError: + raise KeyError + + def read(self, f): + s = f.readline().strip() + while s: + if not s.startswith(';'): + k, v = s.split(': ', 1) + self.ock[k.lower()] = k + self.info[k.lower()] = v + s = f.readline().strip() + + def write(self, f): + f.write('[Script Info]\n') + f.writelines(['%s: %s\n' % (self.ock[k], self.info[k]) for k in self.info.keys()]) + +class Style: + def __init__(self, format, s = None): + self.format = format + if s: + self.parse(s) + + def __str__(self): + return 'Style: %s' % ','.join([getattr(self, x.lower()) for x in self.format]) + + def parse(self, s): + s = s.split(': ', 1) + if s[0] != 'Style': + raise StyleException('invalid style line') + s = s[1].split(',') + for k, v in zip(self.format, s): + setattr(self, k.lower(), v) + +class Styles: + def __init__(self, f = None): + self.format, self.styles, self.names = [], {}, [] + if f: + self.read(f) + + def __getitem__(self, key): + if type(key) == int: + return self.styles[self.names[key]] + elif self.styles.has_key(key.lower()): + return self.styles[key.lower()] + else: + raise KeyError('style "%s" not found' % key) + + def read(self, f): + s = f.readline().strip() + s = s.split(': ', 1) + if s[0] != 'Format': + raise StyleException('format line not found') + self.format = s[1].split(', ') + + s = f.readline().strip() + while s: + s = Style(self.format, s) + self.styles[s.name.lower()] = s + self.names.append(s.name.lower()) + s = f.readline().split() + + def write(self, f): + f.write('[V4+ Styles]\n') + f.write('Format: %s\n' % ', '.join(self.format)) + f.writelines(['%s\n' % str(s) for s in self.styles.values()]) + +class Syllable: + def __init__(self, start, end, text): + self.start, self.end, self.dur, self.text = start, end, end - start, text + + def __str__(self): + return '{\\k%d}%s' % (self.dur / 10, self.text) + +class Event: + def __init__(self, format, s = None): + self.karaoke = [] + self.format = format + if s: + self.parse(s) + + def __getitem__(self, key): + if type(key) is int: + return self.karaoke[key] + else: + raise TypeError('key must be int') + + def __str__(self): + return '%s: %s' % (self.kind.capitalize(), ','.join([getattr(self, x.lower()) for x in self.format])) + + def parse(self, s): + s = s.split(': ', 1) + self.kind = s[0].lower() + s = s[1].split(',') + for k, v in zip(self.format, s): + setattr(self, k.lower(), v) + if self.kind == 'dialogue' and self.text.find('\\k') > -1: + self.parse_karaoke() + + def parse_karaoke(self): + s = self.text + i = 0 + tot = 0 + while i < len(s): + i = s.find('\\k', i) + # break if no more \k's (end-of-line-text is added at the end of the loop) + if i == -1: + break + i += 2 + ms = '' + while s[i].isdigit(): + ms += s[i] + i += 1 + ms = int(ms) * 10 + tot += ms + # unless next is another override, skip one char (past '}') + if s[i] != '\\': + i += 1 + n = s.find('\\k', i) + if n > -1: + part = s[i:n-1] # get text up to next \k + else: + part = s[i:] # end of line + + # go to next if part is empty + if not part.strip(): + continue + + self.karaoke.append(Syllable(tot - ms, tot, part)) + + def add_lead_in(self, s): + t = self.start.split(':') + ts = float(t[-1]) + 60 * int(t[-2]) + 3600 * int(t[-3]) - s + th, ts = int(ts / 3600), ts % 3600 + tm, ts = int(ts / 60), ts % 60 + self.start = '%01d:%02d:%05.2f' % (th, tm, ts) + + def add_lead_out(self, s): + t = self.snd.split(':') + ts = float(t[-1]) + 60 * int(t[-2]) + 3600 * int(t[-3]) + s + th, ts = int(ts / 3600), ts % 3600 + tm, ts = int(ts / 60), ts % 60 + self.end = '%01d:%02d:%05.2f' % (th, tm, ts) + +class Events: + def __init__(self, f = None): + self.format, self.events = [], [] + if f: + self.read(f) + + def __getitem__(self, key): + if type(key) is int: + return self.events[key] + else: + raise TypeError('key must be int') + + def add(self, s = None, e = None): + if not e and s: + e = Event(self.format, s) + if e: + self.events.append(e) + return e + + def read(self, f): + s = f.readline().strip() + s = s.split(': ', 1) + if s[0] != 'Format': + raise EventException('format line not found') + self.format = s[1].split(', ') + + s = f.readline().strip() + while s: + self.add(s) + s = f.readline().strip() + + def write(self, f): + f.write('[Events]\n') + f.write('Format: %s\n' % ', '.join(self.format)) + f.writelines(['%s\n' % str(e) for e in self.events]) + +class Script: + def __init__(self, f = None): + if f: + self.read(f) + + def read(self, f): + f = open(f, 'r') + + s = f.readline().strip() + if s.startswith('\xef\xbb\xbf'): + s = s[3:] + if s == '[Script Info]': + self.info = ScriptInfo(f) + else: + raise ScriptException('script info not found') + + s = f.readline().strip() + if re.match('\[V4\+? Styles\]', s): + self.styles = Styles(f) + else: + raise ScriptException('script styles not found') + + s = f.readline().strip() + if s == '[Events]': + self.events = Events(f) + else: + raise ScriptException('script events not found') + + def write(self, f): + f = open(f, 'w') + self.info.write(f) + f.write('\n') + self.styles.write(f) + f.write('\n') + self.events.write(f) + f.write('\n') diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c131572 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +import subprocess, re +from distutils.core import setup, Extension + +freetype_config = subprocess.Popen('freetype-config --cflags --libs', shell = True, stdout = subprocess.PIPE).stdout.read() + +setup( + name = 'pykfx', + version = '0.1', + description = 'pykfx is a collection of classes for parsing ASS-scripts.', + author = 'Jon Bergli Heier', + author_email = 'jonheier@start.no', + packages = ['pykfx'], + ext_modules = [Extension('pykfx.fscalc', ['fscalc.cpp', 'wrapper.cpp'], + include_dirs = re.findall('-I[^\/\w]*([\w\/]+)', freetype_config), + libraries = ['boost_python', 'fontconfig'] + re.findall('-l\W*(\w+)', freetype_config), + )], +) diff --git a/wrapper.cpp b/wrapper.cpp new file mode 100644 index 0000000..9991b24 --- /dev/null +++ b/wrapper.cpp @@ -0,0 +1,12 @@ +#include +#include "fscalc.h" + +using namespace boost::python; + +BOOST_PYTHON_MODULE(fscalc) { + class_("Font", init()) + .def("set_font_size", &Font::set_font_size) + .def("get_kerning", &Font::get_kerning) + .def("get_text_width", &Font::get_text_width) + .def("get_line_height", &Font::get_line_height); +} -- cgit v1.2.3