export.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065
  1. """
  2. This module defines exporters for the SWF fileformat.
  3. """
  4. from .consts import *
  5. from .geom import *
  6. from .utils import *
  7. from .data import *
  8. from .tag import *
  9. from .filters import *
  10. from lxml import objectify
  11. from lxml import etree
  12. import base64
  13. try:
  14. import Image
  15. except ImportError:
  16. from PIL import Image
  17. try:
  18. from cBytesIO import BytesIO
  19. except ImportError:
  20. from io import BytesIO
  21. import math
  22. import re
  23. import copy
  24. import cgi
  25. SVG_VERSION = "1.1"
  26. SVG_NS = "http://www.w3.org/2000/svg"
  27. XLINK_NS = "http://www.w3.org/1999/xlink"
  28. XLINK_HREF = "{%s}href" % XLINK_NS
  29. NS = {"svg" : SVG_NS, "xlink" : XLINK_NS}
  30. PIXELS_PER_TWIP = 20
  31. EM_SQUARE_LENGTH = 1024
  32. MINIMUM_STROKE_WIDTH = 0.5
  33. CAPS_STYLE = {
  34. 0 : 'round',
  35. 1 : 'butt',
  36. 2 : 'square'
  37. }
  38. JOIN_STYLE = {
  39. 0 : 'round',
  40. 1 : 'bevel',
  41. 2 : 'miter'
  42. }
  43. class DefaultShapeExporter(object):
  44. """
  45. The default (abstract) Shape exporter class.
  46. All shape exporters should extend this class.
  47. """
  48. def __init__(self, swf=None, debug=False, force_stroke=False):
  49. self.swf = None
  50. self.debug = debug
  51. self.force_stroke = force_stroke
  52. def begin_bitmap_fill(self, bitmap_id, matrix=None, repeat=False, smooth=False):
  53. pass
  54. def begin_fill(self, color, alpha=1.0):
  55. pass
  56. def begin_gradient_fill(self, type, colors, alphas, ratios,
  57. matrix=None,
  58. spreadMethod=SpreadMethod.PAD,
  59. interpolationMethod=InterpolationMethod.RGB,
  60. focalPointRatio=0.0):
  61. pass
  62. def line_style(self,
  63. thickness=float('nan'), color=0, alpha=1.0,
  64. pixelHinting=False,
  65. scaleMode=LineScaleMode.NORMAL,
  66. startCaps=None, endCaps=None,
  67. joints=None, miterLimit=3.0):
  68. pass
  69. def line_gradient_style(self,
  70. thickness=float('nan'), color=0, alpha=1.0,
  71. pixelHinting=False,
  72. scaleMode=LineScaleMode.NORMAL,
  73. startCaps=None, endCaps=None,
  74. joints=None, miterLimit=3.0,
  75. type = 1, colors = [], alphas = [], ratios = [],
  76. matrix=None,
  77. spreadMethod=SpreadMethod.PAD,
  78. interpolationMethod=InterpolationMethod.RGB,
  79. focalPointRatio=0.0):
  80. pass
  81. def line_bitmap_style(self,
  82. thickness=float('nan'),
  83. pixelHinting=False,
  84. scaleMode=LineScaleMode.NORMAL,
  85. startCaps=None, endCaps=None,
  86. joints=None, miterLimit = 3.0,
  87. bitmap_id=None, matrix=None, repeat=False, smooth=False):
  88. pass
  89. def end_fill(self):
  90. pass
  91. def begin_fills(self):
  92. pass
  93. def end_fills(self):
  94. pass
  95. def begin_lines(self):
  96. pass
  97. def end_lines(self):
  98. pass
  99. def begin_shape(self):
  100. pass
  101. def end_shape(self):
  102. pass
  103. def move_to(self, x, y):
  104. #print "move_to", x, y
  105. pass
  106. def line_to(self, x, y):
  107. #print "line_to", x, y
  108. pass
  109. def curve_to(self, cx, cy, ax, ay):
  110. #print "curve_to", cx, cy, ax, ay
  111. pass
  112. class DefaultSVGShapeExporter(DefaultShapeExporter):
  113. def __init__(self, defs=None):
  114. self.defs = defs
  115. self.current_draw_command = ""
  116. self.path_data = ""
  117. self._e = objectify.ElementMaker(annotate=False,
  118. namespace=SVG_NS, nsmap={None : SVG_NS, "xlink" : XLINK_NS})
  119. super(DefaultSVGShapeExporter, self).__init__()
  120. def move_to(self, x, y):
  121. self.current_draw_command = ""
  122. self.path_data += "M" + \
  123. str(NumberUtils.round_pixels_20(x)) + " " + \
  124. str(NumberUtils.round_pixels_20(y)) + " "
  125. def line_to(self, x, y):
  126. if self.current_draw_command != "L":
  127. self.current_draw_command = "L"
  128. self.path_data += "L"
  129. self.path_data += "" + \
  130. str(NumberUtils.round_pixels_20(x)) + " " + \
  131. str(NumberUtils.round_pixels_20(y)) + " "
  132. def curve_to(self, cx, cy, ax, ay):
  133. if self.current_draw_command != "Q":
  134. self.current_draw_command = "Q"
  135. self.path_data += "Q"
  136. self.path_data += "" + \
  137. str(NumberUtils.round_pixels_20(cx)) + " " + \
  138. str(NumberUtils.round_pixels_20(cy)) + " " + \
  139. str(NumberUtils.round_pixels_20(ax)) + " " + \
  140. str(NumberUtils.round_pixels_20(ay)) + " "
  141. def begin_bitmap_fill(self, bitmap_id, matrix=None, repeat=False, smooth=False):
  142. self.finalize_path()
  143. def begin_fill(self, color, alpha=1.0):
  144. self.finalize_path()
  145. def end_fill(self):
  146. pass
  147. #self.finalize_path()
  148. def begin_fills(self):
  149. pass
  150. def end_fills(self):
  151. self.finalize_path()
  152. def begin_gradient_fill(self, type, colors, alphas, ratios,
  153. matrix=None,
  154. spreadMethod=SpreadMethod.PAD,
  155. interpolationMethod=InterpolationMethod.RGB,
  156. focalPointRatio=0.0):
  157. self.finalize_path()
  158. def line_style(self,
  159. thickness=float('nan'), color=0, alpha=1.0,
  160. pixelHinting=False,
  161. scaleMode=LineScaleMode.NORMAL,
  162. startCaps=None, endCaps=None,
  163. joints=None, miterLimit=3.0):
  164. self.finalize_path()
  165. def end_lines(self):
  166. self.finalize_path()
  167. def end_shape(self):
  168. self.finalize_path()
  169. def finalize_path(self):
  170. self.current_draw_command = ""
  171. self.path_data = ""
  172. class SVGShapeExporter(DefaultSVGShapeExporter):
  173. def __init__(self):
  174. self.path = None
  175. self.num_patterns = 0
  176. self.num_gradients = 0
  177. self._gradients = {}
  178. self._gradient_ids = {}
  179. self.paths = {}
  180. self.fills_ended = False
  181. super(SVGShapeExporter, self).__init__()
  182. def begin_shape(self):
  183. self.g = self._e.g()
  184. def begin_fill(self, color, alpha=1.0):
  185. self.finalize_path()
  186. self.path.set("fill", ColorUtils.to_rgb_string(color))
  187. if alpha < 1.0:
  188. self.path.set("fill-opacity", str(alpha))
  189. elif self.force_stroke:
  190. self.path.set("stroke", ColorUtils.to_rgb_string(color))
  191. self.path.set("stroke-width", "1")
  192. else:
  193. self.path.set("stroke", "none")
  194. def begin_gradient_fill(self, type, colors, alphas, ratios,
  195. matrix=None,
  196. spreadMethod=SpreadMethod.PAD,
  197. interpolationMethod=InterpolationMethod.RGB,
  198. focalPointRatio=0.0):
  199. self.finalize_path()
  200. gradient_id = self.export_gradient(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio)
  201. self.path.set("stroke", "none")
  202. self.path.set("fill", "url(#%s)" % gradient_id)
  203. def export_gradient(self, type, colors, alphas, ratios,
  204. matrix=None,
  205. spreadMethod=SpreadMethod.PAD,
  206. interpolationMethod=InterpolationMethod.RGB,
  207. focalPointRatio=0.0):
  208. self.num_gradients += 1
  209. gradient_id = "gradient%d" % self.num_gradients
  210. gradient = self._e.linearGradient() if type == GradientType.LINEAR \
  211. else self._e.radialGradient()
  212. gradient.set("gradientUnits", "userSpaceOnUse")
  213. if type == GradientType.LINEAR:
  214. gradient.set("x1", "-819.2")
  215. gradient.set("x2", "819.2")
  216. else:
  217. gradient.set("r", "819.2")
  218. gradient.set("cx", "0")
  219. gradient.set("cy", "0")
  220. if focalPointRatio < 0.0 or focalPointRatio > 0.0:
  221. gradient.set("fx", str(819.2 * focalPointRatio))
  222. gradient.set("fy", "0")
  223. if spreadMethod == SpreadMethod.PAD:
  224. gradient.set("spreadMethod", "pad")
  225. elif spreadMethod == SpreadMethod.REFLECT:
  226. gradient.set("spreadMethod", "reflect")
  227. elif spreadMethod == SpreadMethod.REPEAT:
  228. gradient.set("spreadMethod", "repeat")
  229. if interpolationMethod == InterpolationMethod.LINEAR_RGB:
  230. gradient.set("color-interpolation", "linearRGB")
  231. if matrix is not None:
  232. sm = _swf_matrix_to_svg_matrix(matrix)
  233. gradient.set("gradientTransform", sm);
  234. for i in range(0, len(colors)):
  235. entry = self._e.stop()
  236. offset = ratios[i] / 255.0
  237. entry.set("offset", str(offset))
  238. if colors[i] != 0.0:
  239. entry.set("stop-color", ColorUtils.to_rgb_string(colors[i]))
  240. if alphas[i] != 1.0:
  241. entry.set("stop-opacity", str(alphas[i]))
  242. gradient.append(entry)
  243. # prevent same gradient in <defs />
  244. key = etree.tostring(gradient)
  245. if key in self._gradients:
  246. gradient_id = self._gradient_ids[key]
  247. else:
  248. self._gradients[key] = copy.copy(gradient)
  249. self._gradient_ids[key] = gradient_id
  250. gradient.set("id", gradient_id)
  251. self.defs.append(gradient)
  252. return gradient_id
  253. def export_pattern(self, bitmap_id, matrix, repeat=False, smooth=False):
  254. self.num_patterns += 1
  255. bitmap_id = "c%d" % bitmap_id
  256. e = self.defs.xpath("./svg:image[@id='%s']" % bitmap_id, namespaces=NS)
  257. if len(e) < 1:
  258. raise Exception("SVGShapeExporter::begin_bitmap_fill Could not find bitmap!")
  259. image = e[0]
  260. pattern_id = "pat%d" % (self.num_patterns)
  261. pattern = self._e.pattern()
  262. pattern.set("id", pattern_id)
  263. pattern.set("width", image.get("width"))
  264. pattern.set("height", image.get("height"))
  265. pattern.set("patternUnits", "userSpaceOnUse")
  266. #pattern.set("patternContentUnits", "objectBoundingBox")
  267. if matrix is not None:
  268. pattern.set("patternTransform", _swf_matrix_to_svg_matrix(matrix, True, True, True))
  269. pass
  270. use = self._e.use()
  271. use.set(XLINK_HREF, "#%s" % bitmap_id)
  272. pattern.append(use)
  273. self.defs.append(pattern)
  274. return pattern_id
  275. def begin_bitmap_fill(self, bitmap_id, matrix=None, repeat=False, smooth=False):
  276. self.finalize_path()
  277. pattern_id = self.export_pattern(bitmap_id, matrix, repeat, smooth)
  278. self.path.set("stroke", "none")
  279. self.path.set("fill", "url(#%s)" % pattern_id)
  280. def line_style(self,
  281. thickness=float('nan'), color=0, alpha=1.0,
  282. pixelHinting=False,
  283. scaleMode=LineScaleMode.NORMAL,
  284. startCaps=None, endCaps=None,
  285. joints=None, miterLimit=3.0):
  286. self.finalize_path()
  287. self.path.set("fill", "none")
  288. self.path.set("stroke", ColorUtils.to_rgb_string(color))
  289. thickness = 1 if math.isnan(thickness) else thickness
  290. thickness = MINIMUM_STROKE_WIDTH if thickness < MINIMUM_STROKE_WIDTH else thickness
  291. self.path.set("stroke-width", str(thickness))
  292. if alpha < 1.0:
  293. self.path.set("stroke-opacity", str(alpha))
  294. def line_gradient_style(self,
  295. thickness=float('nan'),
  296. pixelHinting = False,
  297. scaleMode=LineScaleMode.NORMAL,
  298. startCaps=0, endCaps=0,
  299. joints=0, miterLimit=3.0,
  300. type = 1,
  301. colors = [],
  302. alphas = [],
  303. ratios = [],
  304. matrix=None,
  305. spreadMethod=SpreadMethod.PAD,
  306. interpolationMethod=InterpolationMethod.RGB,
  307. focalPointRatio=0.0):
  308. self.finalize_path()
  309. gradient_id = self.export_gradient(type, colors, alphas, ratios, matrix, spreadMethod, interpolationMethod, focalPointRatio)
  310. self.path.set("fill", "none")
  311. self.path.set("stroke-linejoin", JOIN_STYLE[joints])
  312. self.path.set("stroke-linecap", CAPS_STYLE[startCaps])
  313. self.path.set("stroke", "url(#%s)" % gradient_id)
  314. thickness = 1 if math.isnan(thickness) else thickness
  315. thickness = MINIMUM_STROKE_WIDTH if thickness < MINIMUM_STROKE_WIDTH else thickness
  316. self.path.set("stroke-width", str(thickness))
  317. def line_bitmap_style(self,
  318. thickness=float('nan'),
  319. pixelHinting=False,
  320. scaleMode=LineScaleMode.NORMAL,
  321. startCaps=None, endCaps=None,
  322. joints=None, miterLimit = 3.0,
  323. bitmap_id=None, matrix=None, repeat=False, smooth=False):
  324. self.finalize_path()
  325. pattern_id = self.export_pattern(bitmap_id, matrix, repeat, smooth)
  326. self.path.set("fill", "none")
  327. self.path.set("stroke", "url(#%s)" % pattern_id)
  328. self.path.set("stroke-linejoin", JOIN_STYLE[joints])
  329. self.path.set("stroke-linecap", CAPS_STYLE[startCaps])
  330. thickness = 1 if math.isnan(thickness) else thickness
  331. thickness = MINIMUM_STROKE_WIDTH if thickness < MINIMUM_STROKE_WIDTH else thickness
  332. self.path.set("stroke-width", str(thickness))
  333. def begin_fills(self):
  334. self.fills_ended = False
  335. def end_fills(self):
  336. self.finalize_path()
  337. self.fills_ended = True
  338. def finalize_path(self):
  339. if self.path is not None and len(self.path_data) > 0:
  340. self.path_data = self.path_data.rstrip()
  341. self.path.set("d", self.path_data)
  342. self.g.append(self.path)
  343. self.path = self._e.path()
  344. super(SVGShapeExporter, self).finalize_path()
  345. class BaseExporter(object):
  346. def __init__(self, swf=None, shape_exporter=None, force_stroke=False):
  347. self.shape_exporter = SVGShapeExporter() if shape_exporter is None else shape_exporter
  348. self.clip_depth = 0
  349. self.mask_id = None
  350. self.jpegTables = None
  351. self.force_stroke = force_stroke
  352. if swf is not None:
  353. self.export(swf)
  354. def export(self, swf, force_stroke=False):
  355. self.force_stroke = force_stroke
  356. self.export_define_shapes(swf.tags)
  357. self.export_display_list(self.get_display_tags(swf.tags))
  358. def export_define_bits(self, tag):
  359. png_buffer = BytesIO()
  360. image = None
  361. if isinstance(tag, TagDefineBitsJPEG3):
  362. tag.bitmapData.seek(0)
  363. tag.bitmapAlphaData.seek(0, 2)
  364. num_alpha = tag.bitmapAlphaData.tell()
  365. tag.bitmapAlphaData.seek(0)
  366. image = Image.open(tag.bitmapData)
  367. if num_alpha > 0:
  368. image_width = image.size[0]
  369. image_height = image.size[1]
  370. image_data = image.getdata()
  371. image_data_len = len(image_data)
  372. if num_alpha == image_data_len:
  373. buff = b""
  374. for i in range(0, num_alpha):
  375. alpha = ord(tag.bitmapAlphaData.read(1))
  376. rgb = list(image_data[i])
  377. buff += struct.pack("BBBB", rgb[0], rgb[1], rgb[2], alpha)
  378. image = Image.frombytes("RGBA", (image_width, image_height), buff)
  379. elif isinstance(tag, TagDefineBitsJPEG2):
  380. tag.bitmapData.seek(0)
  381. image = Image.open(tag.bitmapData)
  382. else:
  383. tag.bitmapData.seek(0)
  384. if self.jpegTables is not None:
  385. buff = BytesIO()
  386. self.jpegTables.seek(0)
  387. buff.write(self.jpegTables.read())
  388. buff.write(tag.bitmapData.read())
  389. buff.seek(0)
  390. image = Image.open(buff)
  391. else:
  392. image = Image.open(tag.bitmapData)
  393. self.export_image(tag, image)
  394. def export_define_bits_lossless(self, tag):
  395. tag.bitmapData.seek(0)
  396. image = Image.open(tag.bitmapData)
  397. self.export_image(tag, image)
  398. def export_define_sprite(self, tag, parent=None):
  399. display_tags = self.get_display_tags(tag.tags)
  400. self.export_display_list(display_tags, parent)
  401. def export_define_shape(self, tag):
  402. self.shape_exporter.debug = isinstance(tag, TagDefineShape4)
  403. tag.shapes.export(self.shape_exporter)
  404. def export_define_shapes(self, tags):
  405. for tag in tags:
  406. if isinstance(tag, SWFTimelineContainer):
  407. self.export_define_sprite(tag)
  408. self.export_define_shapes(tag.tags)
  409. elif isinstance(tag, TagDefineShape):
  410. self.export_define_shape(tag)
  411. elif isinstance(tag, TagJPEGTables):
  412. if tag.length > 0:
  413. self.jpegTables = tag.jpegTables
  414. elif isinstance(tag, TagDefineBits):
  415. self.export_define_bits(tag)
  416. elif isinstance(tag, TagDefineBitsLossless):
  417. self.export_define_bits_lossless(tag)
  418. elif isinstance(tag, TagDefineFont):
  419. self.export_define_font(tag)
  420. elif isinstance(tag, TagDefineText):
  421. self.export_define_text(tag)
  422. def export_display_list(self, tags, parent=None):
  423. self.clip_depth = 0
  424. for tag in tags:
  425. self.export_display_list_item(tag, parent)
  426. def export_display_list_item(self, tag, parent=None):
  427. pass
  428. def export_image(self, tag, image=None):
  429. pass
  430. def get_display_tags(self, tags, z_sorted=True):
  431. dp_tuples = []
  432. for tag in tags:
  433. if isinstance(tag, TagPlaceObject):
  434. dp_tuples.append((tag, tag.depth))
  435. elif isinstance(tag, TagShowFrame):
  436. break
  437. if z_sorted:
  438. dp_tuples = sorted(dp_tuples, key=lambda tag_info: tag_info[1])
  439. display_tags = []
  440. for item in dp_tuples:
  441. display_tags.append(item[0])
  442. return display_tags
  443. def serialize(self):
  444. return None
  445. class SVGExporter(BaseExporter):
  446. def __init__(self, swf=None, margin=0):
  447. self._e = objectify.ElementMaker(annotate=False,
  448. namespace=SVG_NS, nsmap={None : SVG_NS, "xlink" : XLINK_NS})
  449. self._margin = margin
  450. super(SVGExporter, self).__init__(swf)
  451. def export(self, swf, force_stroke=False):
  452. """ Exports the specified SWF to SVG.
  453. @param swf The SWF.
  454. @param force_stroke Whether to force strokes on non-stroked fills.
  455. """
  456. self.svg = self._e.svg(version=SVG_VERSION)
  457. self.force_stroke = force_stroke
  458. self.defs = self._e.defs()
  459. self.root = self._e.g()
  460. self.svg.append(self.defs)
  461. self.svg.append(self.root)
  462. self.shape_exporter.defs = self.defs
  463. self._num_filters = 0
  464. self.fonts = dict([(x.characterId,x) for x in swf.all_tags_of_type(TagDefineFont)])
  465. self.fontInfos = dict([(x.characterId,x) for x in swf.all_tags_of_type(TagDefineFontInfo)])
  466. # GO!
  467. super(SVGExporter, self).export(swf, force_stroke)
  468. # Setup svg @width, @height and @viewBox
  469. # and add the optional margin
  470. self.bounds = SVGBounds(self.svg)
  471. self.svg.set("width", "%dpx" % round(self.bounds.width))
  472. self.svg.set("height", "%dpx" % round(self.bounds.height))
  473. if self._margin > 0:
  474. self.bounds.grow(self._margin)
  475. vb = [self.bounds.minx, self.bounds.miny,
  476. self.bounds.width, self.bounds.height]
  477. self.svg.set("viewBox", "%s" % " ".join(map(str,vb)))
  478. # Return the SVG as BytesIO
  479. return self._serialize()
  480. def _serialize(self):
  481. return BytesIO(etree.tostring(self.svg,
  482. encoding="UTF-8", xml_declaration=True))
  483. def export_define_sprite(self, tag, parent=None):
  484. id = "c%d"%tag.characterId
  485. g = self._e.g(id=id)
  486. self.defs.append(g)
  487. self.clip_depth = 0
  488. super(SVGExporter, self).export_define_sprite(tag, g)
  489. def export_define_font(self, tag):
  490. if not tag.characterId in self.fontInfos:
  491. return
  492. fontInfo = self.fontInfos[tag.characterId]
  493. if not fontInfo.useGlyphText:
  494. return
  495. defs = self._e.defs(id="font_{0}".format(tag.characterId))
  496. for index, glyph in enumerate(tag.glyphShapeTable):
  497. # Export the glyph as a shape and add the path to the "defs"
  498. # element to be referenced later when exporting text.
  499. code_point = fontInfo.codeTable[index]
  500. pathGroup = glyph.export().g.getchildren()
  501. if len(pathGroup):
  502. path = pathGroup[0]
  503. path.set("id", "font_{0}_{1}".format(tag.characterId, code_point))
  504. # SWF glyphs are always defined on an EM square of 1024 by 1024 units.
  505. path.set("transform", "scale({0})".format(float(1)/EM_SQUARE_LENGTH))
  506. # We'll be setting the color on the USE element that
  507. # references this element.
  508. del path.attrib["stroke"]
  509. del path.attrib["fill"]
  510. defs.append(path)
  511. self.defs.append(defs)
  512. def export_define_text(self, tag):
  513. g = self._e.g(id="c{0}".format(int(tag.characterId)))
  514. g.set("class", "text_content")
  515. x = 0
  516. y = 0
  517. for rec in tag.records:
  518. if rec.hasXOffset:
  519. x = rec.xOffset/PIXELS_PER_TWIP
  520. if rec.hasYOffset:
  521. y = rec.yOffset/PIXELS_PER_TWIP
  522. size = rec.textHeight/PIXELS_PER_TWIP
  523. if rec.fontId not in self.fontInfos:
  524. continue
  525. fontInfo = self.fontInfos[rec.fontId]
  526. if not fontInfo.useGlyphText:
  527. inner_text = ""
  528. xValues = []
  529. for glyph in rec.glyphEntries:
  530. code_point = fontInfo.codeTable[glyph.index]
  531. # Ignore control characters
  532. if code_point in range(32):
  533. continue
  534. if fontInfo.useGlyphText:
  535. use = self._e.use()
  536. use.set(XLINK_HREF, "#font_{0}_{1}".format(rec.fontId, code_point))
  537. use.set(
  538. 'transform',
  539. "scale({0}) translate({1} {2})".format(
  540. size, float(x)/size, float(y)/size
  541. )
  542. )
  543. color = ColorUtils.to_rgb_string(ColorUtils.rgb(rec.textColor))
  544. use.set("style", "fill: {0}; stroke: {0}".format(color))
  545. g.append(use)
  546. else:
  547. inner_text += chr(code_point)
  548. xValues.append(str(x))
  549. x = x + float(glyph.advance)/PIXELS_PER_TWIP
  550. if not fontInfo.useGlyphText:
  551. text = self._e.text(inner_text)
  552. text.set("font-family", fontInfo.fontName)
  553. text.set("font-size", str(size))
  554. text.set("fill", ColorUtils.to_rgb_string(ColorUtils.rgb(rec.textColor)))
  555. text.set("y", str(y))
  556. text.set("x", " ".join(xValues))
  557. if fontInfo.bold:
  558. text.set("font-weight", "bold")
  559. if fontInfo.italic:
  560. text.set("font-style", "italic")
  561. g.append(text)
  562. self.defs.append(g)
  563. def export_define_shape(self, tag):
  564. self.shape_exporter.force_stroke = self.force_stroke
  565. super(SVGExporter, self).export_define_shape(tag)
  566. shape = self.shape_exporter.g
  567. shape.set("id", "c%d" % tag.characterId)
  568. self.defs.append(shape)
  569. def export_display_list_item(self, tag, parent=None):
  570. g = self._e.g()
  571. use = self._e.use()
  572. is_mask = False
  573. if tag.hasMatrix:
  574. use.set("transform", _swf_matrix_to_svg_matrix(tag.matrix))
  575. if tag.hasClipDepth:
  576. self.mask_id = "mask%d" % tag.characterId
  577. self.clip_depth = tag.clipDepth
  578. g = self._e.mask(id=self.mask_id)
  579. # make sure the mask is completely filled white
  580. paths = self.defs.xpath("./svg:g[@id='c%d']/svg:path" % tag.characterId, namespaces=NS)
  581. for path in paths:
  582. path.set("fill", "#ffffff")
  583. elif tag.depth <= self.clip_depth and self.mask_id is not None:
  584. g.set("mask", "url(#%s)" % self.mask_id)
  585. filters = []
  586. filter_cxform = None
  587. self._num_filters += 1
  588. filter_id = "filter%d" % self._num_filters
  589. svg_filter = self._e.filter(id=filter_id)
  590. if tag.hasColorTransform:
  591. filter_cxform = self.export_color_transform(tag.colorTransform, svg_filter)
  592. filters.append(filter_cxform)
  593. if tag.hasFilterList and len(tag.filters) > 0:
  594. cxform = "color-xform" if tag.hasColorTransform else None
  595. f = self.export_filters(tag, svg_filter, cxform)
  596. if len(f) > 0:
  597. filters.extend(f)
  598. if tag.hasColorTransform or (tag.hasFilterList and len(filters) > 0):
  599. self.defs.append(svg_filter)
  600. use.set("filter", "url(#%s)" % filter_id)
  601. use.set(XLINK_HREF, "#c%s" % tag.characterId)
  602. g.append(use)
  603. if is_mask:
  604. self.defs.append(g)
  605. else:
  606. if parent is not None:
  607. parent.append(g)
  608. else:
  609. self.root.append(g)
  610. return use
  611. def export_color_transform(self, cxform, svg_filter, result='color-xform'):
  612. fe_cxform = self._e.feColorMatrix()
  613. fe_cxform.set("in", "SourceGraphic")
  614. fe_cxform.set("type", "matrix")
  615. fe_cxform.set("values", " ".join(map(str, cxform.matrix)))
  616. fe_cxform.set("result", "cxform")
  617. fe_composite = self._e.feComposite(operator="in")
  618. fe_composite.set("in2", "SourceGraphic")
  619. fe_composite.set("result", result)
  620. svg_filter.append(fe_cxform)
  621. svg_filter.append(fe_composite)
  622. return result
  623. def export_filters(self, tag, svg_filter, cxform=None):
  624. num_filters = len(tag.filters)
  625. elements = []
  626. attr_in = None
  627. for i in range(0, num_filters):
  628. swf_filter = tag.filters[i]
  629. #print swf_filter
  630. if isinstance(swf_filter, FilterDropShadow):
  631. elements.append(self.export_filter_dropshadow(swf_filter, svg_filter, cxform))
  632. #print swf_filter.strength
  633. pass
  634. elif isinstance(swf_filter, FilterBlur):
  635. pass
  636. elif isinstance(swf_filter, FilterGlow):
  637. #attr_in = SVGFilterFactory.export_glow_filter(self._e, svg_filter, attr_in=attr_in)
  638. #elements.append(attr_in)
  639. pass
  640. elif isinstance(swf_filter, FilterBevel):
  641. pass
  642. elif isinstance(swf_filter, FilterGradientGlow):
  643. pass
  644. elif isinstance(swf_filter, FilterConvolution):
  645. pass
  646. elif isinstance(swf_filter, FilterColorMatrix):
  647. attr_in = SVGFilterFactory.export_color_matrix_filter(self._e, svg_filter, swf_filter.colorMatrix, svg_filter, attr_in=attr_in)
  648. elements.append(attr_in)
  649. pass
  650. elif isinstance(swf_filter, FilterGradientBevel):
  651. pass
  652. else:
  653. raise Exception("unknown filter: ", swf_filter)
  654. return elements
  655. # <filter id="test-filter" x="-50%" y="-50%" width="200%" height="200%">
  656. # <feGaussianBlur in="SourceAlpha" stdDeviation="6" result="blur"/>
  657. # <feOffset dy="0" dx="0"/>
  658. # <feComposite in2="SourceAlpha" operator="arithmetic"
  659. # k2="-1" k3="1" result="shadowDiff"/>
  660. # <feFlood flood-color="black" flood-opacity="1"/>
  661. # <feComposite in2="shadowDiff" operator="in"/>
  662. # </filter>;
  663. def export_filter_dropshadow(self, swf_filter, svg_filter, blend_in=None, result="offsetBlur"):
  664. gauss = self._e.feGaussianBlur()
  665. gauss.set("in", "SourceAlpha")
  666. gauss.set("stdDeviation", "6")
  667. gauss.set("result", "blur")
  668. if swf_filter.knockout:
  669. composite0 = self._e.feComposite(
  670. in2="SourceAlpha", operator="arithmetic",
  671. k2="-1", k3="1", result="shadowDiff")
  672. flood = self._e.feFlood()
  673. flood.set("flood-color", "black")
  674. flood.set("flood-opacity", "1")
  675. composite1 = self._e.feComposite(
  676. in2="shadowDiff", operator="in", result=result)
  677. svg_filter.append(gauss)
  678. svg_filter.append(composite0)
  679. svg_filter.append(flood)
  680. svg_filter.append(composite1)
  681. else:
  682. SVGFilterFactory.create_drop_shadow_filter(self._e, svg_filter,
  683. None,
  684. swf_filter.blurX/20.0,
  685. swf_filter.blurY/20.0,
  686. blend_in,
  687. result)
  688. #print etree.tostring(svg_filter, pretty_print=True)
  689. return result
  690. def export_image(self, tag, image=None):
  691. if image is not None:
  692. buff = BytesIO()
  693. image.save(buff, "PNG")
  694. buff.seek(0)
  695. with open("C:\\Users\\Administrator\\Desktop\\a.png","wb") as f:
  696. f.write(buff.getvalue())
  697. data_url = _encode_png(buff.read())
  698. img = self._e.image()
  699. img.set("id", "c%s" % tag.characterId)
  700. img.set("x", "0")
  701. img.set("y", "0 ")
  702. img.set("width", "%s" % str(image.size[0]))
  703. img.set("height", "%s" % str(image.size[1]))
  704. img.set(XLINK_HREF, "%s" % data_url)
  705. self.defs.append(img)
  706. class SingleShapeSVGExporter(SVGExporter):
  707. """
  708. An SVG exporter which knows how to export a single shape.
  709. """
  710. def __init__(self, margin=0):
  711. super(SingleShapeSVGExporter, self).__init__(margin = margin)
  712. def export_single_shape(self, shape_tag, swf):
  713. from swf.movie import SWF
  714. # find a typical use of this shape
  715. example_place_objects = [x for x in swf.all_tags_of_type(TagPlaceObject) if x.hasCharacter and x.characterId == shape_tag.characterId]
  716. if len(example_place_objects):
  717. place_object = example_place_objects[0]
  718. characters = swf.build_dictionary()
  719. ids_to_export = place_object.get_dependencies()
  720. ids_exported = set()
  721. tags_to_export = []
  722. # this had better form a dag!
  723. while len(ids_to_export):
  724. id = ids_to_export.pop()
  725. if id in ids_exported or id not in characters:
  726. continue
  727. tag = characters[id]
  728. ids_to_export.update(tag.get_dependencies())
  729. tags_to_export.append(tag)
  730. ids_exported.add(id)
  731. tags_to_export.reverse()
  732. tags_to_export.append(place_object)
  733. else:
  734. place_object = TagPlaceObject()
  735. place_object.hasCharacter = True
  736. place_object.characterId = shape_tag.characterId
  737. tags_to_export = [ shape_tag, place_object ]
  738. stunt_swf = SWF()
  739. stunt_swf.tags = tags_to_export
  740. return super(SingleShapeSVGExporter, self).export(stunt_swf)
  741. class SVGFilterFactory(object):
  742. # http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Filters
  743. # http://dev.opera.com/articles/view/svg-evolution-3-applying-polish/
  744. @classmethod
  745. def create_drop_shadow_filter(cls, e, filter, attr_in=None, blurX=0, blurY=0, blend_in=None, result=None):
  746. gaussianBlur = SVGFilterFactory.create_gaussian_blur(e, attr_deviaton="1", result="blur-out")
  747. offset = SVGFilterFactory.create_offset(e, "blur-out", blurX, blurY, "the-shadow")
  748. blend = SVGFilterFactory.create_blend(e, blend_in, attr_in2="the-shadow", result=result)
  749. filter.append(gaussianBlur)
  750. filter.append(offset)
  751. filter.append(blend)
  752. return result
  753. @classmethod
  754. def export_color_matrix_filter(cls, e, filter, matrix, svg_filter, attr_in=None, result='color-matrix'):
  755. attr_in = "SourceGraphic" if attr_in is None else attr_in
  756. fe_cxform = e.feColorMatrix()
  757. fe_cxform.set("in", attr_in)
  758. fe_cxform.set("type", "matrix")
  759. fe_cxform.set("values", " ".join(map(str, matrix)))
  760. fe_cxform.set("result", result)
  761. filter.append(fe_cxform)
  762. #print etree.tostring(filter, pretty_print=True)
  763. return result
  764. @classmethod
  765. def export_glow_filter(cls, e, filter, attr_in=None, result="glow-out"):
  766. attr_in = "SourceGraphic" if attr_in is None else attr_in
  767. gaussianBlur = SVGFilterFactory.create_gaussian_blur(e, attr_in=attr_in, attr_deviaton="1", result=result)
  768. filter.append(gaussianBlur)
  769. return result
  770. @classmethod
  771. def create_blend(cls, e, attr_in=None, attr_in2="BackgroundImage", mode="normal", result=None):
  772. blend = e.feBlend()
  773. attr_in = "SourceGraphic" if attr_in is None else attr_in
  774. blend.set("in", attr_in)
  775. blend.set("in2", attr_in2)
  776. blend.set("mode", mode)
  777. if result is not None:
  778. blend.set("result", result)
  779. return blend
  780. @classmethod
  781. def create_gaussian_blur(cls, e, attr_in="SourceAlpha", attr_deviaton="3", result=None):
  782. gaussianBlur = e.feGaussianBlur()
  783. gaussianBlur.set("in", attr_in)
  784. gaussianBlur.set("stdDeviation", attr_deviaton)
  785. if result is not None:
  786. gaussianBlur.set("result", result)
  787. return gaussianBlur
  788. @classmethod
  789. def create_offset(cls, e, attr_in=None, dx=0, dy=0, result=None):
  790. offset = e.feOffset()
  791. if attr_in is not None:
  792. offset.set("in", attr_in)
  793. offset.set("dx", "%d" % round(dx))
  794. offset.set("dy", "%d" % round(dy))
  795. if result is not None:
  796. offset.set("result", result)
  797. return offset
  798. class SVGBounds(object):
  799. def __init__(self, svg=None):
  800. self.minx = 1000000.0
  801. self.miny = 1000000.0
  802. self.maxx = -self.minx
  803. self.maxy = -self.miny
  804. self._stack = []
  805. self._matrix = self._calc_combined_matrix()
  806. if svg is not None:
  807. self._svg = svg;
  808. self._parse(svg)
  809. def add_point(self, x, y):
  810. self.minx = x if x < self.minx else self.minx
  811. self.miny = y if y < self.miny else self.miny
  812. self.maxx = x if x > self.maxx else self.maxx
  813. self.maxy = y if y > self.maxy else self.maxy
  814. def set(self, minx, miny, maxx, maxy):
  815. self.minx = minx
  816. self.miny = miny
  817. self.maxx = maxx
  818. self.maxy = maxy
  819. def grow(self, margin):
  820. self.minx -= margin
  821. self.miny -= margin
  822. self.maxx += margin
  823. self.maxy += margin
  824. @property
  825. def height(self):
  826. return self.maxy - self.miny
  827. def merge(self, other):
  828. self.minx = other.minx if other.minx < self.minx else self.minx
  829. self.miny = other.miny if other.miny < self.miny else self.miny
  830. self.maxx = other.maxx if other.maxx > self.maxx else self.maxx
  831. self.maxy = other.maxy if other.maxy > self.maxy else self.maxy
  832. def shrink(self, margin):
  833. self.minx += margin
  834. self.miny += margin
  835. self.maxx -= margin
  836. self.maxy -= margin
  837. @property
  838. def width(self):
  839. return self.maxx - self.minx
  840. def _parse(self, element):
  841. if element.get("transform") and element.get("transform").find("matrix") < 0:
  842. pass
  843. if element.get("transform") and element.get("transform").find("matrix") >= 0:
  844. self._push_transform(element.get("transform"))
  845. if element.tag == "{%s}path" % SVG_NS:
  846. self._handle_path_data(str(element.get("d")))
  847. elif element.tag == "{%s}use" % SVG_NS:
  848. href = element.get(XLINK_HREF)
  849. if href:
  850. href = href.replace("#", "")
  851. els = self._svg.xpath("./svg:defs//svg:g[@id='%s']" % href,
  852. namespaces=NS)
  853. if len(els) > 0:
  854. self._parse(els[0])
  855. for child in element.getchildren():
  856. if child.tag == "{%s}defs" % SVG_NS: continue
  857. self._parse(child)
  858. if element.get("transform") and element.get("transform").find("matrix") >= 0:
  859. self._pop_transform()
  860. def _build_matrix(self, transform):
  861. if transform.find("matrix") >= 0:
  862. raw = str(transform).replace("matrix(", "").replace(")", "")
  863. f = map(float, re.split("\s+|,", raw))
  864. return Matrix2(f[0], f[1], f[2], f[3], f[4], f[5])
  865. def _calc_combined_matrix(self):
  866. m = Matrix2()
  867. for mat in self._stack:
  868. m.append_matrix(mat)
  869. return m
  870. def _handle_path_data(self, d):
  871. parts = re.split("[\s]+", d)
  872. for i in range(0, len(parts), 2):
  873. try:
  874. p0 = parts[i]
  875. p1 = parts[i+1]
  876. p0 = p0.replace("M", "").replace("L", "").replace("Q", "")
  877. p1 = p1.replace("M", "").replace("L", "").replace("Q", "")
  878. v = [float(p0), float(p1)]
  879. w = self._matrix.multiply_point(v)
  880. self.minx = w[0] if w[0] < self.minx else self.minx
  881. self.miny = w[1] if w[1] < self.miny else self.miny
  882. self.maxx = w[0] if w[0] > self.maxx else self.maxx
  883. self.maxy = w[1] if w[1] > self.maxy else self.maxy
  884. except:
  885. continue
  886. def _pop_transform(self):
  887. m = self._stack.pop()
  888. self._matrix = self._calc_combined_matrix()
  889. return m
  890. def _push_transform(self, transform):
  891. self._stack.append(self._build_matrix(transform))
  892. self._matrix = self._calc_combined_matrix()
  893. def _encode_jpeg(data):
  894. return "data:image/jpeg;base64," + str(base64.encodestring(data)[:-1])
  895. def _encode_png(data):
  896. return "data:image/png;base64," + str(base64.encodestring(data)[:-1])
  897. def _swf_matrix_to_matrix(swf_matrix=None, need_scale=False, need_translate=True, need_rotation=False, unit_div=20.0):
  898. if swf_matrix is None:
  899. values = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]
  900. else:
  901. values = swf_matrix.to_array()
  902. if need_rotation:
  903. values[1] /= unit_div
  904. values[2] /= unit_div
  905. if need_scale:
  906. values[0] /= unit_div
  907. values[3] /= unit_div
  908. if need_translate:
  909. values[4] /= unit_div
  910. values[5] /= unit_div
  911. return values
  912. def _swf_matrix_to_svg_matrix(swf_matrix=None, need_scale=False, need_translate=True, need_rotation=False, unit_div=20.0):
  913. values = _swf_matrix_to_matrix(swf_matrix, need_scale, need_translate, need_rotation, unit_div)
  914. str_values = ",".join(map(str, values))
  915. return "matrix(%s)" % str_values