1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import base64
6import os
7import re
8
9
10class Image(object):
11
12  def __init__(self, resource):
13    self.resource = resource
14    self.aliases = []
15
16  @property
17  def relative_path(self):
18    return self.resource.relative_path
19
20  @property
21  def absolute_path(self):
22    return self.resource.absolute_path
23
24  @property
25  def contents(self):
26    return self.resource.contents
27
28
29class ParsedStyleSheet(object):
30  def __init__(self, loader, containing_dirname, contents):
31    self.loader = loader
32    self.contents = contents
33    self._images = None
34    self._Load(containing_dirname)
35
36  @property
37  def images(self):
38    return self._images
39
40  def AppendDirectlyDependentFilenamesTo(self, dependent_filenames):
41    for i in self.images:
42      dependent_filenames.append(i.resource.absolute_path)
43
44  @property
45  def contents_with_inlined_images(self):
46    images_by_url = {}
47    for i in self.images:
48      for a in i.aliases:
49        images_by_url[a] = i
50
51    def InlineUrl(m):
52      url = m.group('url')
53      image = images_by_url[url]
54
55      ext = os.path.splitext(image.absolute_path)[1]
56      data = base64.standard_b64encode(image.contents)
57
58      return "url(data:image/%s;base64,%s)" % (ext[1:], data)
59
60    # I'm assuming we only have url()'s associated with images
61    return re.sub('url\((?P<quote>"|\'|)(?P<url>[^"\'()]*)(?P=quote)\)',
62                  InlineUrl, self.contents)
63
64  def AppendDirectlyDependentFilenamesTo(self, dependent_filenames):
65    for i in self.images:
66      dependent_filenames.append(i.resource.absolute_path)
67
68  def _Load(self, containing_dirname):
69    if self.contents.find('@import') != -1:
70      raise Exception('@imports are not supported')
71
72    matches = re.findall(
73        'url\((?:["|\']?)([^"\'()]*)(?:["|\']?)\)',
74        self.contents)
75
76    def resolve_url(url):
77      if os.path.isabs(url):
78        # FIXME: module is used here, but tvcm.module is never imported.
79        # However, tvcm.module cannot be imported since tvcm.module may import
80        # style_sheet, leading to an import loop.
81        raise module.DepsException('URL references must be relative')
82      # URLS are relative to this module's directory
83      abs_path = os.path.abspath(os.path.join(containing_dirname, url))
84      image = self.loader.LoadImage(abs_path)
85      image.aliases.append(url)
86      return image
87
88    self._images = [resolve_url(x) for x in matches]
89
90
91class StyleSheet(object):
92  """Represents a stylesheet resource referenced by a module via the
93  base.requireStylesheet(xxx) directive."""
94  def __init__(self, loader, name, resource):
95    self.loader = loader
96    self.name = name
97    self.resource = resource
98    self._parsed_style_sheet = None
99
100  @property
101  def filename(self):
102    return self.resource.absolute_path
103
104  @property
105  def contents(self):
106    return self.resource.contents
107
108  def __repr__(self):
109    return "StyleSheet(%s)" % self.name
110
111  @property
112  def images(self):
113    self._InitParsedStyleSheetIfNeeded()
114    return self._parsed_style_sheet.images
115
116  def AppendDirectlyDependentFilenamesTo(self, dependent_filenames):
117    self._InitParsedStyleSheetIfNeeded()
118
119    dependent_filenames.append(self.resource.absolute_path)
120    self._parsed_style_sheet.AppendDirectlyDependentFilenamesTo(
121        dependent_filenames)
122
123  @property
124  def contents_with_inlined_images(self):
125    self._InitParsedStyleSheetIfNeeded()
126    return self._parsed_style_sheet.contents_with_inlined_images
127
128  def load(self):
129    self._InitParsedStyleSheetIfNeeded()
130
131  def _InitParsedStyleSheetIfNeeded(self):
132    if self._parsed_style_sheet:
133      return
134    module_dirname = os.path.dirname(self.resource.absolute_path)
135    self._parsed_style_sheet = ParsedStyleSheet(
136        self.loader, module_dirname, self.contents)
137