OSDN Git Service

最初のリリース
[pynv/pylib-nicovideo.git] / setup.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3
4 #       Copyright © 2014 dyknon
5 #
6 #       This file is part of Pylib-nicovideo.
7 #
8 #       Pylib-nicovideo is free software: you can redistribute it and/or modify
9 #       it under the terms of the GNU General Public License as published by
10 #       the Free Software Foundation, either version 3 of the License, or
11 #       (at your option) any later version.
12 #
13 #       This program is distributed in the hope that it will be useful,
14 #       but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #       GNU General Public License for more details.
17 #
18 #       You should have received a copy of the GNU General Public License
19 #       along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 import distutils.core
22 import distutils.cmd
23 import distutils.command.build
24 import struct
25 import sys
26 import os
27
28 class TtfReader:
29         def __init__(self, filehandle, offset=0):
30                 self.fh = filehandle
31                 self.offset = offset
32                 self.tables = {}
33         
34         def detect_type(self):
35                 self.fh.seek(self.offset, os.SEEK_SET)
36                 mn = self.fh.read(4)
37                 if mn == b"\x00\x01\x00\x00":
38                         return "ttf"
39                 elif mn == b"ttcf":
40                         if self.fh.read(4) == b"\x00\x01\x00\x00":
41                                 return "ttc"
42                 return None
43         
44         def read_ttc_head(self):
45                 self.fh.seek(self.offset, os.SEEK_SET)
46                 if self.fh.read(8) != b"ttcf\x00\x01\x00\x00":
47                         raise "TTCファイルヘッダ読み込みエラー"
48                 self.tttable = []
49                 numfn = struct.unpack(">L", self.fh.read(4))[0]
50                 for i in range(numfn):
51                         self.tttable.append(struct.unpack(">L", self.fh.read(4))[0])
52                 return self.tttable
53
54         def read_ttf_head(self):
55                 self.fh.seek(self.offset, os.SEEK_SET)
56                 if self.fh.read(4) != b"\x00\x01\x00\x00":
57                         raise "TTFファイルヘッダ読み込みエラー"
58                 (num_tables, search_range, entry_selector, range_shift) \
59                         = struct.unpack(">HHHH", self.fh.read(8))
60                 self.table_list = {}
61                 for i in range(num_tables):
62                         tag = self.fh.read(4).decode("ascii")
63                         (cksum, offset, length) = struct.unpack(">LLL", self.fh.read(12))
64                         self.table_list[tag] = {"s": cksum, "o": offset, "l": length}
65                 return self.table_list
66
67         def check_ttf_head(self):
68                 try:
69                         self.table_list
70                 except AttributeError:
71                         self.read_ttf_head()
72
73         def get_table_head(self):
74                 if not "head" in self.tables:
75                         self.read_table_head()
76                 return self.tables["head"]
77
78         def read_table_head(self):
79                 self.check_ttf_head()
80                 self.fh.seek(self.table_list["head"]["o"], os.SEEK_SET)
81                 (version, font_revidion, check_sum_adjustment, magic_number,
82                         flags, units_per_em, created, modified,
83                         x_min, y_min, x_max, y_max, mac_style, lowest_rec_ppem,
84                         font_direction_hint, index_to_loc_format, glyph_data_format) \
85                         = struct.unpack(">llLLHHqqhhhhHHhhh", self.fh.read(54));
86                 version /= 0x10000
87                 font_revidion /= 0x10000
88                 self.tables["head"] = {
89                         "version": version,
90                         "fontRevidion": font_revidion,
91                         "checkSumAdjustment": check_sum_adjustment,
92                         "magicNumber": magic_number,
93                         "flags": flags,
94                         "unitsPerEm": units_per_em,
95                         "created": created,
96                         "modified": modified,
97                         "xMin": x_min,
98                         "yMin": y_min,
99                         "xMax": x_max,
100                         "yMax": y_max,
101                         "macStyle": mac_style,
102                         "lowestRecPPEM": lowest_rec_ppem,
103                         "fontDirectionHint": font_direction_hint,
104                         "indexToLocFormat": index_to_loc_format,
105                         "glyphDataFormat": glyph_data_format
106                 }
107                 return self.tables["head"]
108
109         def get_table_loca(self):
110                 if not "loca" in self.tables:
111                         self.read_table_loca()
112                 return self.tables["loca"]
113
114         def read_table_loca(self):
115                 self.check_ttf_head()
116                 if not "head" in self.tables:
117                         self.read_table_head()
118                 if not "maxp" in self.tables:
119                         self.read_table_maxp()
120                 self.fh.seek(self.table_list["loca"]["o"], os.SEEK_SET)
121                 if self.tables["head"]["indexToLocFormat"] == 0:
122                         table_format = struct.Struct(">H")
123                 elif self.tables["head"]["indexToLocFormat"] == 1:
124                         table_format = struct.Struct(">L")
125                 else:
126                         raise "headテーブルのindexToLocFormat値が不正。"
127                 self.tables["loca"] = []
128                 for i in range(self.tables["maxp"]["numGlyphs"]+1):
129                         offset = table_format.unpack(self.fh.read(table_format.size))
130                         self.tables["loca"].append(offset)
131                 return self.tables["loca"]
132
133         def get_glyph_from_index(self, index):
134                 self.check_ttf_head()
135                 self.fh.seek(self.table_list["glyf"]["o"]+index, os.SEEK_SET)
136                 (number_of_contours, x_min, y_min, x_max, y_max) \
137                         = struct.unpack(">hhhhh", self.fh.read(10))
138                 return {
139                         "numberOfContours": number_of_contours,
140                         "xMin": x_min,
141                         "yMin": y_min,
142                         "xMax": x_max,
143                         "yMax": y_max
144                 }
145
146         def get_glyph_from_no(self, no):
147                 if "glyf" in self.tables and no in self.tables["glyf"]:
148                         return self.tables["glyf"][no]
149                 if not "glyf" in self.tables:
150                         self.tables["glyf"] = {}
151                 if not "loca" in self.tables:
152                         self.read_table_loca()
153                 glyph = self.get_glyph_from_index(self.tables["loca"][no])
154                 self.tables["glyf"][no] = glyph
155                 return glyph
156
157         def get_glyph_from_char(self, char):
158                 if not "cmap" in self.tables:
159                         self.read_table_cmap()
160                 return self.get_glyph_from_no(self.tables["cmap"][char])
161
162         def get_table_glyf(self):
163                 if not "glyf" in self.tables:
164                         self.read_table_glyf()
165                 return self.tables["glyf"]
166
167         def read_table_glyf(self):
168                 if not "maxp" in self.tables:
169                         self.read_table_maxp()
170                 for i in range(self.tables["maxp"]["numGlyphs"]+1):
171                         self.get_glyph_from_no(i)
172                 return self.tables["glyf"]
173
174         def get_table_cmap(self):
175                 if not "cmap" in self.tables:
176                         self.read_table_cmap()
177                 return self.tables["cmap"]
178
179         def read_table_cmap(self):              #手抜きとかそういうのじゃない。無理。
180                 self.check_ttf_head()
181                 self.fh.seek(self.table_list["cmap"]["o"], os.SEEK_SET)
182                 (version, num_tables) = struct.unpack(">HH", self.fh.read(4));
183                 if version != 0:
184                         raise "cmapテーブルのバージョンがあたらしすぎまっす。"
185                 encoding_records = []
186                 maxscore = 0
187                 no = -1
188                 for i in range(num_tables):
189                         (platform_id, encoding_id, offset) \
190                                 = struct.unpack(">HHL", self.fh.read(8))
191                         score = 0
192                         if platform_id == 3:
193                                 if encoding_id == 0:
194                                         score += 1
195                                 elif encoding_id == 1:
196                                         score += 3
197                         if maxscore < score:
198                                 maxscore = score
199                                 no = i
200                         encoding_records.append({
201                                 "platformID": platform_id,
202                                 "encodingID": encoding_id,
203                                 "offset": offset
204                         })
205                 if no < 0:
206                         raise "文字マップのエンコード方式に非対応のものしかありません"
207                 encoding = None
208                 if encoding_records[no]["platformID"] == 3:
209                         if encoding_records[no]["encodingID"] == 0:
210                                 encoding = "ascii"
211                         if encoding_records[no]["encodingID"] == 1:
212                                 encoding = "utf-16be"
213                         if encoding_records[no]["encodingID"] == 2:
214                                 encoding = "sjis"
215                 if encoding == None:
216                         raise "文字マップのエンコード方式に非対応のものしかありません"
217                 self.fh.seek(self.table_list["cmap"]["o"]
218                         + encoding_records[no]["offset"], os.SEEK_SET)
219                 maptype = struct.unpack(">H", self.fh.read(2))[0]
220                 mapdict = {}
221                 if maptype == 0:                                #要修正: デコードの仕方ダメ
222                         (length, language) = struct.unpack(">HH", self.fh.read(4))
223                         for i in range(256):
224                                 code = struct.pack(">L", i).decode(encoding, "replace")
225                                 mapdict[code] = struct.unpack(">B", self.fh.read(1))[0]
226                 elif maptype == 2:
227                         (length, language) = struct.unpack(">HH", self.fh.read(4))
228                         sub_header_keys = []
229                         for i in range(256):
230                                 sub_header_keys.append(struct.unpack(">H", self.fh.read(2))[0])
231                         start_of_sub_header = self.fh.tell()
232                         for i in range(256):
233                                 self.fh.seek(start_of_sub_header + sub_header_keys[i], os.SEEK_SET)
234                                 (first_code, entry_count, id_delta, id_range_offset) \
235                                         = struct.unpack(">HHsH", self.fh.read(8))
236                                 if first_code + entry_count >= 256:
237                                         continue
238                                 self.fh.seek(id_range_offset, SEEK_CUR)
239                                 for j in range(entry_count):
240                                         glyph_index = struct.unpack(">H", self.fh.read(2))[0]
241                                         if glyph_index == 0:
242                                                 continue
243                                         glyph_index += id_delta
244                                         if i == 0:
245                                                 code = j + first_code
246                                         else:
247                                                 code = (i << 8)|((j+first_code) & 0x0f)
248                                         code = struct.pack("B", code).decode(encoding, "replace")
249                                         mapdict[code] = glyph_index
250                 elif maptype == 4:
251                         (length, language, seg_count_x2, search_range, entry_selector,
252                                 range_shift) = struct.unpack(">HHHHHH", self.fh.read(12))
253                         seg_count = int(seg_count_x2 / 2)
254                         end_count = []
255                         for i in range(seg_count):
256                                 end_count.append(struct.unpack(">H", self.fh.read(2))[0])
257                         reserved_pad = struct.unpack(">H", self.fh.read(2))[0]
258                         start_count = []
259                         for i in range(seg_count):
260                                 start_count.append(struct.unpack(">H", self.fh.read(2))[0])
261                         id_delta = []
262                         for i in range(seg_count):
263                                 id_delta.append(struct.unpack(">h", self.fh.read(2))[0])
264                         id_range_offset = []
265                         for i in range(seg_count):
266                                 id_range_offset.append(struct.unpack(">H", self.fh.read(2))[0])
267                         start_of_glyph_id_array = self.fh.tell()
268                         for i in range(seg_count):
269                                 start = start_count[i]
270                                 end = end_count[i]
271                                 delta = id_delta[i]
272                                 range_offset = id_range_offset[i]
273                                 if start == 65535 or end == 65535:
274                                         continue
275                                 for j in range(start, end+1):
276                                         if range_offset == 0:
277                                                 code = struct.pack(">H", j)
278                                                 code = code.decode(encoding, "replace")
279                                                 mapdict[code] = (j + delta) % 65536
280                                         else:
281                                                 self.fh.seek(
282                                                         start_of_glyph_id_array
283                                                                 + int((range_offset/2+j-start+i-seg_count)*2),
284                                                         os.SEEK_SET)
285                                                 glyph_index = struct.unpack(">H", self.fh.read(2))[0]
286                                                 if glyph_index == 0:
287                                                         continue
288                                                 code = struct.pack(">H", j)
289                                                 code = code.decode(encoding, "replace")
290                                                 mapdict[code] = (glyph_index + delta) % 65536
291                 else:
292                         raise "ID{}の文字マップには対応していません".format(maptype)
293                 self.tables["cmap"] = mapdict
294                 return self.tables["cmap"]
295
296         def get_table_maxp(self):
297                 if not "maxp" in self.tables:
298                         self.read_table_maxp()
299                 return self.tables["maxp"]
300
301         def read_table_maxp(self):
302                 self.check_ttf_head()
303                 self.fh.seek(self.table_list["maxp"]["o"], os.SEEK_SET)
304                 self.tables["maxp"] = {}
305                 version = struct.unpack(">L", self.fh.read(4))[0]
306                 if version == 0x00005000:
307                         self.tables["maxp"]["numGlyphs"] \
308                                 = struct.unpack(">S", self.fh.read(2))[0]
309                 elif version == 0x00010000:             #手抜き(v1.0で追加されたのを無視する)
310                         self.tables["maxp"]["numGlyphs"] \
311                                 = struct.unpack(">S", self.fh.read(2))[0]
312                 else:
313                         raise "maxpテーブルのバージョンが非対応なやつです"
314
315         def get_table_hhea(self):
316                 if not "hhea" in self.tables:
317                         self.read_table_hhea()
318                 return self.tables["hhea"]
319
320         def read_table_hhea(self):
321                 self.check_ttf_head()
322                 self.fh.seek(self.table_list["hhea"]["o"], os.SEEK_SET)
323                 (vesion, ascender, descender, line_gap, advance_width_max,
324                 min_left_side_bearing, min_right_side_bearing, x_max_extent,
325                 caret_slope_rise, caret_slope_run, caret_offset,
326                 zero0, zero1, zero2, zero3, metric_data_format, number_of_h_metrics) \
327                         = struct.unpack(">LhhhHhhhhhhhhhhhH", self.fh.read(36));
328                 self.tables["hhea"] = {
329                         "vesion": vesion,
330                         "ascender": ascender,
331                         "descender": descender,
332                         "lineGap": line_gap,
333                         "advanceWidthMax": advance_width_max,
334                         "minLeftSideBearing": min_left_side_bearing,
335                         "minRightSideBearing": min_right_side_bearing,
336                         "xMaxExtent": x_max_extent,
337                         "caretSlopeRise": caret_slope_rise,
338                         "caretSlopeRun": caret_slope_run,
339                         "caretOffset": caret_offset,
340                         "metricDataFormat": metric_data_format,
341                         "numberOfHMetrics": number_of_h_metrics
342                 }
343                 return self.tables["hhea"]
344
345         def get_table_hmtx(self):
346                 if not "hmtx" in self.tables:
347                         self.read_table_hmtx()
348                 return self.tables["hmtx"]
349
350         def read_table_hmtx(self):              #手抜き
351                 self.check_ttf_head()
352                 if not "hhea" in self.tables:
353                         self.read_table_hhea()
354                 self.fh.seek(self.table_list["hmtx"]["o"], os.SEEK_SET)
355                 long_hor_metric = []
356                 for i in range(self.tables["hhea"]["numberOfHMetrics"]):
357                         (advance_width, lsb) = struct.unpack(">Hh", self.fh.read(4))
358                         long_hor_metric.append({"advanceWidth": advance_width, "lsb": lsb})
359                 self.tables["hmtx"] = long_hor_metric
360                 return long_hor_metric
361
362         def get_charwidth_from_no(self, no):
363                 if not "hmtx" in self.tables:
364                         self.read_table_hmtx()
365                 if len(self.tables["hmtx"]) <= no:
366                         return None
367                 return self.tables["hmtx"][no]["advanceWidth"]
368
369         def get_charwidth_from_char(self, char):
370                 if not "cmap" in self.tables:
371                         self.read_table_cmap()
372                 return self.get_charwidth_from_no(self.tables["cmap"][char])
373
374         def get_table_name(self):
375                 if not "name" in self.tables:
376                         self.read_table_name()
377                 return self.tables["name"]
378
379         def read_table_name(self):              #だいぶ手抜き
380                 self.check_ttf_head()
381                 self.fh.seek(self.table_list["name"]["o"], os.SEEK_SET)
382                 (format_num, count, string_offset) \
383                         = struct.unpack(">HHH", self.fh.read(6))
384                 name_record = []
385                 for i in range(count):
386                         (platform_id, encoding_id, language_id, name_id, length, offset) \
387                                 = struct.unpack(">HHHHHH", self.fh.read(12))
388                         name_record.append({
389                                 "platformID": platform_id,
390                                 "encodingID": encoding_id,
391                                 "languageID": language_id,
392                                 "nameID": name_id,
393                                 "length": length,
394                                 "offset": offset
395                         })
396                 storagepos = self.table_list["name"]["o"] + string_offset
397                 self.tables["name"] = []
398                 for nr in name_record:                  #要修正: encID=0はAsciiだよ。雑過ぎ。
399                         encoding = None
400                         if nr["platformID"] == 3:
401                                 if nr["encodingID"] == 0:
402                                         encoding = "utf-16be"
403                                 elif nr["encodingID"] == 1:
404                                         encoding = "utf-16be"
405                                 elif nr["encodingID"] == 2:
406                                         encoding = "sjis"
407                         if not encoding:
408                                 continue
409                         self.fh.seek(storagepos + nr["offset"], os.SEEK_SET)
410                         string = self.fh.read(nr["length"]).decode(encoding, "replace")
411                         nr.update({"string": string})
412                         self.tables["name"].append(nr)
413                 return self.tables["name"]
414
415         def get_font_name(self):
416                 if not "name" in self.tables:
417                         self.read_table_name()
418                 fontname = None
419                 maxscore = -1
420                 for name in self.tables["name"]:
421                         score = 0
422                         if name["nameID"] == 4:
423                                 score += 3
424                         if name["languageID"] == 0x0411:
425                                 score += 2
426                         if name["encodingID"] == 1:
427                                 score += 1
428                         if maxscore < score:
429                                 maxscore = score
430                                 fontname = name["string"]
431                 return fontname
432
433         def has_name(self, rqname):
434                 if not "name" in self.tables:
435                         self.read_table_name()
436                 for name in self.tables["name"]:
437                         if name["nameID"] == 4:
438                                 if name["string"] == rqname:
439                                         return True
440                 return False
441
442 # ** フォント優先順位テーブルについて **
443 #適切なフォントが検出できない時のためにいくつか自動検出の候補を用意しておく。
444 #自分の手元にあったフォントからテキトーに選んだだけ。
445
446 #半角文字用フォントの優先順位。HelveticaというやつはArialと幅が同じらしい。
447 HWalphanumeric_priority = [
448         "Arial", "Helvetica", "DejaVu Sans", "VL PGothic Regular"
449 ]
450 #ゴシック体用フォントの優先順位。MS Pゴシック以外はテキトーに並べただけ。
451 gothic_priority = [
452         "MS Pゴシック", "VL PGothic Regular"
453 ]
454 gothic_replacer = ""
455 #明朝体用のフォントの優先順位。SimSun以外はテキトーに並べただけ。最後はゴシック
456 mincho_priority = [
457         "SimSun", "VL PGothic Regular"
458 ]
459 mincho_replacer = "\u02c9\u02ca\u02cb\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u311a"
460 #丸文字体用のフォント優先順位。Gulim以外はただのゴシック
461 marumoji_priority = [
462         "Gulim", "MS Pゴシック", "VL PGothic Regular"
463 ]
464 marumoji_replacer = "\u2660\u2661\u2663\u2664\u2665\u2667\u2668\u2669\u266c\uadf8"
465
466 hwfont_reader = None
467 fwfont_readers = []
468 fwfont_replacers = {}
469
470 def gen_fontwidth_py(outputfile):
471         #自動設定はWin以外は今のところ非対応。
472         fontfiles = []
473         fontdirs = []
474         if os.path.isdir("fonts"):
475                 fontdirs.append("fonts")
476         if os.name == "nt":
477                 if os.path.isdir("C:\\Windows\\Fonts\\"):
478                         fontdirs.append("C:\\Windows\\Fonts\\")
479         for fontdir in fontdirs:
480                 infontdir = os.listdir(fontdir)
481                 for filename in infontdir:
482                         filename = os.path.join(fontdir, filename)
483                         if os.path.isfile(filename) and os.access(filename, os.R_OK):
484                                 fontfiles.append(filename)
485         fonts = []
486         for fontfile in fontfiles:
487                 fh = open(fontfile, "br")
488                 headreader = TtfReader(fh)
489                 filetype = headreader.detect_type()
490                 if filetype == "ttf":
491                         fonts.append(headreader)
492                 elif filetype == "ttc":
493                         for offset in headreader.read_ttc_head():
494                                 fonts.append(TtfReader(fh, offset))
495         font_can_HW = None
496         font_can_gothic = None
497         font_can_mincho = None
498         font_can_marumoji = None
499         for rqname in reversed(HWalphanumeric_priority):
500                 for font in fonts:
501                         if font.has_name(rqname):
502                                 font_can_HW = font
503         for rqname in reversed(gothic_priority):
504                 for font in fonts:
505                         if font.has_name(rqname):
506                                 font_can_gothic = font
507         for rqname in reversed(mincho_priority):
508                 for font in fonts:
509                         if font.has_name(rqname):
510                                 font_can_mincho = font
511         for rqname in reversed(marumoji_priority):
512                 for font in fonts:
513                         if font.has_name(rqname):
514                                 font_can_marumoji = font
515         if not(font_can_HW and font_can_gothic and
516                 font_can_mincho and font_can_marumoji):
517                 print("適切なフォントをfonts/に入れてください。(README参照)")
518                 raise "必要なフォントを自動検出できんかったよ"
519         hwfont_reader = font_can_HW
520         fwfont_readers = [font_can_gothic, font_can_mincho, font_can_marumoji]
521         fwfont_replacers = {
522                 font_can_gothic.get_font_name(): gothic_replacer,
523                 font_can_mincho.get_font_name(): mincho_replacer,
524                 font_can_marumoji.get_font_name(): marumoji_replacer
525         }
526
527         outputfile.write("#Generated by nicovideo-tools")
528         outputfile.write("\n")
529         outputfile.write("font_HWalphanumeric = \"{}\"".format(hwfont_reader.get_font_name()))
530         outputfile.write("\n")
531         nametable = []
532         for tr in fwfont_readers:
533                 nametable.append("\"{}\"".format(tr.get_font_name()))
534         outputfile.write("font_priority = [{}]".format(",".join(nametable)))
535         outputfile.write("\n")
536         outputfile.write("font_change_chars = {")
537         outputfile.write("\n")
538         for key in fwfont_replacers.keys():
539                 line = "\t\"{}\": \"".format(key)
540                 for char in fwfont_replacers[key]:
541                         line += "\\U{:0>8x}".format(ord(char))
542                 line += "\","
543                 outputfile.write(line)
544                 outputfile.write("\n")
545         outputfile.write("\t\"\": \"\"")
546         outputfile.write("\n")
547         outputfile.write("}")
548         outputfile.write("\n")
549         outputfile.write("fontlist = {")
550         outputfile.write("\n")
551         for tr in fwfont_readers:
552                 outputfile.write("\t\"" + tr.get_font_name() + "\": {")
553                 outputfile.write("\n")
554                 outputfile.write("\t\t\"h\": {},".format(tr.get_table_head()["unitsPerEm"]))
555                 outputfile.write("\n")
556                 outputfile.write("\t\t\"w\": {")
557                 outputfile.write("\n")
558                 charmap = tr.get_table_cmap()
559                 for char in charmap.keys():
560                         width = tr.get_charwidth_from_char(char)
561                         if width != None:
562                                 outputfile.write("\t\t\t\"\\U{:0>8x}\": {},".format(ord(char), width))
563                                 outputfile.write("\n")
564                 outputfile.write("\t\t\t\"\": 0")
565                 outputfile.write("\n")
566                 outputfile.write("\t\t}")
567                 outputfile.write("\n")
568                 outputfile.write("\t},")
569                 outputfile.write("\n")
570
571         outputfile.write("\t\"" + hwfont_reader.get_font_name() + "\": {")
572         outputfile.write("\n")
573         outputfile.write("\t\t\"h\": {},".format(hwfont_reader.get_table_head()["unitsPerEm"]))
574         outputfile.write("\n")
575         outputfile.write("\t\t\"w\": {")
576         outputfile.write("\n")
577         charmap = hwfont_reader.get_table_cmap()
578         for char in charmap.keys():
579                 width = hwfont_reader.get_charwidth_from_char(char)
580                 if width != None:
581                         outputfile.write("\t\t\t\"\\U{:0>8x}\": {},".format(ord(char), width))
582                         outputfile.write("\n")
583         outputfile.write("\t\t\t\"\": 0")
584         outputfile.write("\n")
585         outputfile.write("\t\t}")
586         outputfile.write("\n")
587         outputfile.write("\t}")
588         outputfile.write("\n")
589         outputfile.write("}")
590         outputfile.write("\n")
591
592 class build_fontwidth_py(distutils.cmd.Command):
593         description = "\"build\" fontwidth.py"
594
595         user_options = [("build-lib=", "d", "directory to \"build\" to")]
596         boolean_options = []
597
598         def initialize_options(self):
599                 self.build_lib = None
600
601         def finalize_options(self):
602                 self.set_undefined_options("build", ('build_lib', 'build_lib'))
603
604         def run(self):
605                 self.mkpath(os.path.join(self.build_lib, "nicovideo"))
606                 outfile = os.path.join(self.build_lib, "nicovideo", "fontwidth.py")
607                 outfilehandle = open(outfile, mode="w", encoding="utf-8")
608                 outfilehandle.write("# -*- coding: utf-8 -*-\n")
609                 gen_fontwidth_py(outfilehandle)
610                 outfilehandle.close()
611
612 class my_build(distutils.command.build.build):
613         def run(self):
614                 self.run_command("build_fontwidth_py")
615                 distutils.command.build.build.run(self)
616
617 distutils.core.setup(   name="pylib-nicovideo",
618                                                 version="0.3.0.0",
619                                                 description="ニコニコにアクセスしたりできる。",
620                                                 author="dyknon",
621                                                 author_email="dyknon@users.sourceforge.jp",
622                                                 url="http://sourceforge.jp/users/dyknon/",
623                                                 packages=["nicovideo"],
624                                                 cmdclass={
625                                                         "build": my_build,
626                                                         "build_fontwidth_py": build_fontwidth_py
627                                                 }
628 )