home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 January / maximum-cd-2011-01.iso / DiscContents / calibre-0.7.26.msi / file_997 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-10-31  |  23.4 KB  |  843 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. from __future__ import with_statement
  5. __license__ = 'GPL v3'
  6. __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
  7. import os
  8. import itertools
  9. import re
  10. import logging
  11. import copy
  12. from weakref import WeakKeyDictionary
  13. from xml.dom import SyntaxErr as CSSSyntaxError
  14. import cssutils
  15. from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, CSSValueList, CSSFontFaceRule, cssproperties
  16. from cssutils import profile as cssprofiles
  17. from lxml import etree
  18. from lxml.cssselect import css_to_xpath, ExpressionError, SelectorSyntaxError
  19. from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
  20. from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize
  21. from calibre.ebooks.oeb.profile import PROFILES
  22. _html_css_stylesheet = None
  23.  
  24. def html_css_stylesheet():
  25.     global _html_css_stylesheet
  26.     if _html_css_stylesheet is None:
  27.         html_css = open(P('templates/html.css'), 'rb').read()
  28.         _html_css_stylesheet = cssutils.parseString(html_css)
  29.         _html_css_stylesheet.namespaces['h'] = XHTML_NS
  30.     
  31.     return _html_css_stylesheet
  32.  
  33. XHTML_CSS_NAMESPACE = '@namespace "%s";\n' % XHTML_NS
  34. INHERITED = set([
  35.     'azimuth',
  36.     'border-collapse',
  37.     'border-spacing',
  38.     'caption-side',
  39.     'color',
  40.     'cursor',
  41.     'direction',
  42.     'elevation',
  43.     'empty-cells',
  44.     'font-family',
  45.     'font-size',
  46.     'font-style',
  47.     'font-variant',
  48.     'font-weight',
  49.     'letter-spacing',
  50.     'line-height',
  51.     'list-style-image',
  52.     'list-style-position',
  53.     'list-style-type',
  54.     'orphans',
  55.     'page-break-inside',
  56.     'pitch-range',
  57.     'pitch',
  58.     'quotes',
  59.     'richness',
  60.     'speak-header',
  61.     'speak-numeral',
  62.     'speak-punctuation',
  63.     'speak',
  64.     'speech-rate',
  65.     'stress',
  66.     'text-align',
  67.     'text-indent',
  68.     'text-transform',
  69.     'visibility',
  70.     'voice-family',
  71.     'volume',
  72.     'white-space',
  73.     'widows',
  74.     'word-spacing'])
  75. DEFAULTS = {
  76.     'azimuth': 'center',
  77.     'background-attachment': 'scroll',
  78.     'background-color': 'transparent',
  79.     'background-image': 'none',
  80.     'background-position': '0% 0%',
  81.     'background-repeat': 'repeat',
  82.     'border-bottom-color': ':color',
  83.     'border-bottom-style': 'none',
  84.     'border-bottom-width': 'medium',
  85.     'border-collapse': 'separate',
  86.     'border-left-color': ':color',
  87.     'border-left-style': 'none',
  88.     'border-left-width': 'medium',
  89.     'border-right-color': ':color',
  90.     'border-right-style': 'none',
  91.     'border-right-width': 'medium',
  92.     'border-spacing': 0,
  93.     'border-top-color': ':color',
  94.     'border-top-style': 'none',
  95.     'border-top-width': 'medium',
  96.     'bottom': 'auto',
  97.     'caption-side': 'top',
  98.     'clear': 'none',
  99.     'clip': 'auto',
  100.     'color': 'black',
  101.     'content': 'normal',
  102.     'counter-increment': 'none',
  103.     'counter-reset': 'none',
  104.     'cue-after': 'none',
  105.     'cue-before': 'none',
  106.     'cursor': 'auto',
  107.     'direction': 'ltr',
  108.     'display': 'inline',
  109.     'elevation': 'level',
  110.     'empty-cells': 'show',
  111.     'float': 'none',
  112.     'font-family': 'serif',
  113.     'font-size': 'medium',
  114.     'font-style': 'normal',
  115.     'font-variant': 'normal',
  116.     'font-weight': 'normal',
  117.     'height': 'auto',
  118.     'left': 'auto',
  119.     'letter-spacing': 'normal',
  120.     'line-height': 'normal',
  121.     'list-style-image': 'none',
  122.     'list-style-position': 'outside',
  123.     'list-style-type': 'disc',
  124.     'margin-bottom': 0,
  125.     'margin-left': 0,
  126.     'margin-right': 0,
  127.     'margin-top': 0,
  128.     'max-height': 'none',
  129.     'max-width': 'none',
  130.     'min-height': 0,
  131.     'min-width': 0,
  132.     'orphans': '2',
  133.     'outline-color': 'invert',
  134.     'outline-style': 'none',
  135.     'outline-width': 'medium',
  136.     'overflow': 'visible',
  137.     'padding-bottom': 0,
  138.     'padding-left': 0,
  139.     'padding-right': 0,
  140.     'padding-top': 0,
  141.     'page-break-after': 'auto',
  142.     'page-break-before': 'auto',
  143.     'page-break-inside': 'auto',
  144.     'pause-after': 0,
  145.     'pause-before': 0,
  146.     'pitch': 'medium',
  147.     'pitch-range': '50',
  148.     'play-during': 'auto',
  149.     'position': 'static',
  150.     'quotes': u"'ΓÇ£' 'ΓÇ¥' 'ΓÇÿ' 'ΓÇÖ'",
  151.     'richness': '50',
  152.     'right': 'auto',
  153.     'speak': 'normal',
  154.     'speak-header': 'once',
  155.     'speak-numeral': 'continuous',
  156.     'speak-punctuation': 'none',
  157.     'speech-rate': 'medium',
  158.     'stress': '50',
  159.     'table-layout': 'auto',
  160.     'text-align': 'auto',
  161.     'text-decoration': 'none',
  162.     'text-indent': 0,
  163.     'text-transform': 'none',
  164.     'top': 'auto',
  165.     'unicode-bidi': 'normal',
  166.     'vertical-align': 'baseline',
  167.     'visibility': 'visible',
  168.     'voice-family': 'default',
  169.     'volume': 'medium',
  170.     'white-space': 'normal',
  171.     'widows': '2',
  172.     'width': 'auto',
  173.     'word-spacing': 'normal',
  174.     'z-index': 'auto' }
  175. FONT_SIZE_NAMES = set([
  176.     'xx-small',
  177.     'x-small',
  178.     'small',
  179.     'medium',
  180.     'large',
  181.     'x-large',
  182.     'xx-large'])
  183.  
  184. class CSSSelector(etree.XPath):
  185.     MIN_SPACE_RE = re.compile(' *([>~+]) *')
  186.     LOCAL_NAME_RE = re.compile("(?<!local-)name[(][)] *= *'[^:]+:")
  187.     
  188.     def __init__(self, css, namespaces = XPNSMAP):
  189.         css = self.MIN_SPACE_RE.sub('\\1', css)
  190.         
  191.         try:
  192.             path = css_to_xpath(css)
  193.         except UnicodeEncodeError:
  194.             path = '/'
  195.         except NotImplementedError:
  196.             path = '/'
  197.  
  198.         path = self.LOCAL_NAME_RE.sub("local-name() = '", path)
  199.         etree.XPath.__init__(self, path, namespaces = namespaces)
  200.         self.css = css
  201.  
  202.     
  203.     def __repr__(self):
  204.         return '<%s %s for %r>' % (self.__class__.__name__, hex(abs(id(self)))[2:], self.css)
  205.  
  206.  
  207.  
  208. class Stylizer(object):
  209.     STYLESHEETS = WeakKeyDictionary()
  210.     
  211.     def __init__(self, tree, path, oeb, opts, profile = PROFILES['PRS505'], extra_css = '', user_css = ''):
  212.         self.oeb = oeb
  213.         self.opts = opts
  214.         self.profile = profile
  215.         self.logger = oeb.logger
  216.         item = oeb.manifest.hrefs[path]
  217.         basename = os.path.basename(path)
  218.         cssname = os.path.splitext(basename)[0] + '.css'
  219.         stylesheets = [
  220.             html_css_stylesheet()]
  221.         head = xpath(tree, '/h:html/h:head')
  222.         if head:
  223.             head = head[0]
  224.         else:
  225.             head = []
  226.         for profile in self.opts.output_profile.extra_css_modules:
  227.             cssutils.profile.addProfile(profile['name'], profile['props'], profile['macros'])
  228.         
  229.         parser = cssutils.CSSParser(fetcher = self._fetch_css_file, log = logging.getLogger('calibre.css'))
  230.         self.font_face_rules = []
  231.         for elem in head:
  232.             if elem.tag == XHTML('style') and elem.text and elem.get('type', CSS_MIME) in OEB_STYLES:
  233.                 text = XHTML_CSS_NAMESPACE + elem.text
  234.                 text = oeb.css_preprocessor(text)
  235.                 stylesheet = parser.parseString(text, href = cssname)
  236.                 stylesheet.namespaces['h'] = XHTML_NS
  237.                 stylesheets.append(stylesheet)
  238.                 continue
  239.             if elem.tag == XHTML('link') and elem.get('href') and elem.get('rel', 'stylesheet').lower() == 'stylesheet' and elem.get('type', CSS_MIME).lower() in OEB_STYLES:
  240.                 href = urlnormalize(elem.attrib['href'])
  241.                 path = item.abshref(href)
  242.                 sitem = oeb.manifest.hrefs.get(path, None)
  243.                 if sitem is None:
  244.                     self.logger.warn('Stylesheet %r referenced by file %r not in manifest' % (path, item.href))
  245.                     continue
  246.                 
  247.                 if not hasattr(sitem.data, 'cssRules'):
  248.                     self.logger.warn('Stylesheet %r referenced by file %r is not CSS' % (path, item.href))
  249.                     continue
  250.                 
  251.                 stylesheets.append(sitem.data)
  252.                 continue
  253.         
  254.         csses = {
  255.             'extra_css': extra_css,
  256.             'user_css': user_css }
  257.         for w, x in csses.items():
  258.             if x:
  259.                 
  260.                 try:
  261.                     text = XHTML_CSS_NAMESPACE + x
  262.                     stylesheet = parser.parseString(text, href = cssname)
  263.                     stylesheet.namespaces['h'] = XHTML_NS
  264.                     stylesheets.append(stylesheet)
  265.                 self.logger.exception('Failed to parse %s, ignoring.' % w)
  266.                 self.logger.debug('Bad css: ')
  267.                 self.logger.debug(x)
  268.  
  269.                 continue
  270.         
  271.         rules = []
  272.         index = 0
  273.         self.stylesheets = set()
  274.         self.page_rule = { }
  275.         for stylesheet in stylesheets:
  276.             href = stylesheet.href
  277.             self.stylesheets.add(href)
  278.             for rule in stylesheet.cssRules:
  279.                 rules.extend(self.flatten_rule(rule, href, index))
  280.                 index = index + 1
  281.             
  282.         
  283.         rules.sort()
  284.         self.rules = rules
  285.         self._styles = { }
  286.         class_sel_pat = re.compile('\\.[a-z]+', re.IGNORECASE)
  287.         capital_sel_pat = re.compile('h|[A-Z]+')
  288.         for _, _, cssdict, text, _ in rules:
  289.             fl = ':first-letter' in text
  290.             if fl:
  291.                 text = text.replace(':first-letter', '')
  292.             
  293.             
  294.             try:
  295.                 selector = CSSSelector(text)
  296.             except (AssertionError, ExpressionError, etree.XPathSyntaxError, NameError, SelectorSyntaxError):
  297.                 continue
  298.  
  299.             matches = selector(tree)
  300.             if not matches:
  301.                 ntext = capital_sel_pat.sub((lambda m: m.group().lower()), text)
  302.                 if ntext != text:
  303.                     self.logger.warn('Transformed CSS selector', text, 'to', ntext)
  304.                     selector = CSSSelector(ntext)
  305.                     matches = selector(tree)
  306.                 
  307.             
  308.             if not matches and class_sel_pat.match(text) and text.lower() != text:
  309.                 found = False
  310.                 ltext = text.lower()
  311.                 for x in tree.xpath('//*[@class]'):
  312.                     if ltext.endswith('.' + x.get('class').lower()):
  313.                         matches.append(x)
  314.                         found = True
  315.                         continue
  316.                 
  317.                 if found:
  318.                     self.logger.warn('Ignoring case mismatches for CSS selector: %s in %s' % (text, item.href))
  319.                 
  320.             
  321.             if fl:
  322.                 ElementMaker = ElementMaker
  323.                 import lxml.builder
  324.                 E = ElementMaker(namespace = XHTML_NS)
  325.                 for elem in matches:
  326.                     for x in elem.iter():
  327.                         if x.text:
  328.                             span = E.span(x.text[0])
  329.                             span.tail = x.text[1:]
  330.                             x.text = None
  331.                             x.insert(0, span)
  332.                             self.style(span)._update_cssdict(cssdict)
  333.                             break
  334.                             continue
  335.                     
  336.                 
  337.             for elem in matches:
  338.                 self.style(elem)._update_cssdict(cssdict)
  339.             
  340.         
  341.         for elem in xpath(tree, '//h:img[@width or @height]'):
  342.             base = elem.get('style', '').strip()
  343.             if base:
  344.                 base += ';'
  345.             
  346.             for prop in ('width', 'height'):
  347.                 val = elem.get(prop, False)
  348.                 if val:
  349.                     base += '%s: %s;' % (prop, val)
  350.                     del elem.attrib[prop]
  351.                     continue
  352.             
  353.             elem.set('style', base)
  354.         
  355.         for elem in xpath(tree, '//h:*[@style]'):
  356.             self.style(elem)._apply_style_attr()
  357.         
  358.  
  359.     
  360.     def _fetch_css_file(self, path):
  361.         hrefs = self.oeb.manifest.hrefs
  362.         if path not in hrefs:
  363.             self.logger.warn('CSS import of missing file %r' % path)
  364.             return (None, None)
  365.         item = hrefs[path]
  366.         if item.media_type not in OEB_STYLES:
  367.             self.logger.warn('CSS import of non-CSS file %r' % path)
  368.             return (None, None)
  369.         data = item.data.cssText
  370.         return ('utf-8', data)
  371.  
  372.     
  373.     def flatten_rule(self, rule, href, index):
  374.         results = []
  375.         if isinstance(rule, CSSStyleRule):
  376.             style = self.flatten_style(rule.style)
  377.             for selector in rule.selectorList:
  378.                 specificity = selector.specificity + (index,)
  379.                 text = selector.selectorText
  380.                 selector = list(selector.seq)
  381.                 results.append((specificity, selector, style, text, href))
  382.             
  383.         elif isinstance(rule, CSSPageRule):
  384.             style = self.flatten_style(rule.style)
  385.             self.page_rule.update(style)
  386.         elif isinstance(rule, CSSFontFaceRule):
  387.             self.font_face_rules.append(rule)
  388.         
  389.         return results
  390.  
  391.     
  392.     def flatten_style(self, cssstyle):
  393.         style = { }
  394.         for prop in cssstyle:
  395.             name = prop.name
  396.             if name in ('margin', 'padding'):
  397.                 style.update(self._normalize_edge(prop.cssValue, name))
  398.                 continue
  399.             if name == 'font':
  400.                 style.update(self._normalize_font(prop.cssValue))
  401.                 continue
  402.             if name == 'list-style':
  403.                 style.update(self._normalize_list_style(prop.cssValue))
  404.                 continue
  405.             if name == 'text-align':
  406.                 style.update(self._normalize_text_align(prop.cssValue))
  407.                 continue
  408.             style[name] = prop.value
  409.         
  410.         if 'font-size' in style:
  411.             size = style['font-size']
  412.             if size == 'normal':
  413.                 size = 'medium'
  414.             
  415.             if size == 'smallest':
  416.                 size = 'xx-small'
  417.             
  418.             if size in FONT_SIZE_NAMES:
  419.                 style['font-size'] = '%dpt' % self.profile.fnames[size]
  420.             
  421.         
  422.         return style
  423.  
  424.     
  425.     def _normalize_edge(self, cssvalue, name):
  426.         style = { }
  427.         if len(primitives) == 1:
  428.             (value,) = primitives
  429.             values = [
  430.                 value,
  431.                 value,
  432.                 value,
  433.                 value]
  434.         elif len(primitives) == 2:
  435.             (vert, horiz) = primitives
  436.             values = [
  437.                 vert,
  438.                 horiz,
  439.                 vert,
  440.                 horiz]
  441.         elif len(primitives) == 3:
  442.             (top, horiz, bottom) = primitives
  443.             values = [
  444.                 top,
  445.                 horiz,
  446.                 bottom,
  447.                 horiz]
  448.         else:
  449.             values = primitives[:4]
  450.         edges = ('top', 'right', 'bottom', 'left')
  451.         for edge, value in itertools.izip(edges, values):
  452.             style['%s-%s' % (name, edge)] = value
  453.         
  454.         return style
  455.  
  456.     
  457.     def _normalize_list_style(self, cssvalue):
  458.         composition = ('list-style-type', 'list-style-position', 'list-style-image')
  459.         style = { }
  460.         if cssvalue.cssText == 'inherit':
  461.             for key in composition:
  462.                 style[key] = 'inherit'
  463.             
  464.         else:
  465.             
  466.             try:
  467.                 primitives = [ v.cssText for v in cssvalue ]
  468.             except TypeError:
  469.                 primitives = [
  470.                     cssvalue.cssText]
  471.  
  472.             primitives.reverse()
  473.             value = primitives.pop()
  474.             for key in composition:
  475.                 if cssprofiles.validate(key, value):
  476.                     style[key] = value
  477.                     if not primitives:
  478.                         break
  479.                     
  480.                     value = primitives.pop()
  481.                     continue
  482.             
  483.             for key in composition:
  484.                 if key not in style:
  485.                     style[key] = DEFAULTS[key]
  486.                     continue
  487.             
  488.         return style
  489.  
  490.     
  491.     def _normalize_text_align(self, cssvalue):
  492.         style = { }
  493.         text = cssvalue.cssText
  494.         if text == 'inherit':
  495.             style['text-align'] = 'inherit'
  496.         elif text in ('left', 'justify') and self.opts.change_justification in ('left', 'justify'):
  497.             val = self.opts.change_justification
  498.             style['text-align'] = val
  499.         else:
  500.             style['text-align'] = text
  501.         return style
  502.  
  503.     
  504.     def _normalize_font(self, cssvalue):
  505.         composition = ('font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family')
  506.         style = { }
  507.         if cssvalue.cssText == 'inherit':
  508.             for key in composition:
  509.                 style[key] = 'inherit'
  510.             
  511.         else:
  512.             
  513.             try:
  514.                 primitives = [ v.cssText for v in cssvalue ]
  515.             except TypeError:
  516.                 primitives = [
  517.                     cssvalue.cssText]
  518.  
  519.             primitives.reverse()
  520.             value = primitives.pop()
  521.             for key in composition:
  522.                 if cssprofiles.validate(key, value):
  523.                     style[key] = value
  524.                     if not primitives:
  525.                         break
  526.                     
  527.                     value = primitives.pop()
  528.                     continue
  529.             
  530.             for key in composition:
  531.                 if key not in style:
  532.                     style[key] = DEFAULTS[key]
  533.                     continue
  534.             
  535.         return style
  536.  
  537.     
  538.     def style(self, element):
  539.         
  540.         try:
  541.             return self._styles[element]
  542.         except KeyError:
  543.             return Style(element, self)
  544.  
  545.  
  546.     
  547.     def stylesheet(self, name, font_scale = None):
  548.         rules = []
  549.         for _, _, style, selector, href in self.rules:
  550.             if href != name:
  551.                 continue
  552.             
  553.             if font_scale and 'font-size' in style and style['font-size'].endswith('pt'):
  554.                 style = copy.copy(style)
  555.                 size = float(style['font-size'][:-2])
  556.                 style['font-size'] = '%.2fpt' % size * font_scale
  557.             
  558.             style = ';\n    '.join((lambda .0: for item in .0:
  559. ': '.join(item))(style.items()))
  560.             rules.append('%s {\n    %s;\n}' % (selector, style))
  561.         
  562.         return '\n'.join(rules)
  563.  
  564.  
  565.  
  566. class Style(object):
  567.     UNIT_RE = re.compile('^(-*[0-9]*[.]?[0-9]*)\\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
  568.     
  569.     def __init__(self, element, stylizer):
  570.         self._element = element
  571.         self._profile = stylizer.profile
  572.         self._stylizer = stylizer
  573.         self._style = { }
  574.         self._fontSize = None
  575.         self._width = None
  576.         self._height = None
  577.         self._lineHeight = None
  578.         stylizer._styles[element] = self
  579.  
  580.     
  581.     def set(self, prop, val):
  582.         self._style[prop] = val
  583.  
  584.     
  585.     def _update_cssdict(self, cssdict):
  586.         self._style.update(cssdict)
  587.  
  588.     
  589.     def _apply_style_attr(self):
  590.         attrib = self._element.attrib
  591.         if 'style' not in attrib:
  592.             return None
  593.         css = attrib['style'].split(';')
  594.         css = filter(None, (lambda .0: for x in .0:
  595. x.strip())(css))
  596.         
  597.         try:
  598.             style = CSSStyleDeclaration('; '.join(css))
  599.         except CSSSyntaxError:
  600.             'style' not in attrib
  601.             'style' not in attrib
  602.             return None
  603.  
  604.         self._style.update(self._stylizer.flatten_style(style))
  605.  
  606.     
  607.     def _has_parent(self):
  608.         return self._element.getparent() is not None
  609.  
  610.     
  611.     def _get_parent(self):
  612.         elem = self._element.getparent()
  613.         if elem is None:
  614.             return None
  615.         return self._stylizer.style(elem)
  616.  
  617.     
  618.     def __getitem__(self, name):
  619.         domname = cssproperties._toDOMname(name)
  620.         if hasattr(self, domname):
  621.             return getattr(self, domname)
  622.         return self._unit_convert(self._get(name))
  623.  
  624.     
  625.     def _get(self, name):
  626.         result = None
  627.         if name in self._style:
  628.             result = self._style[name]
  629.         
  630.         if (result == 'inherit' or result is None) and name in INHERITED and self._has_parent():
  631.             stylizer = self._stylizer
  632.             result = stylizer.style(self._element.getparent())._get(name)
  633.         
  634.         if result is None:
  635.             result = DEFAULTS[name]
  636.         
  637.         return result
  638.  
  639.     
  640.     def _unit_convert(self, value, base = None, font = None):
  641.         if isinstance(value, (int, long, float)):
  642.             return value
  643.         
  644.         try:
  645.             return float(value) * 72 / self._profile.dpi
  646.         except:
  647.             isinstance(value, (int, long, float))
  648.  
  649.         result = value
  650.         m = self.UNIT_RE.match(value)
  651.         if m is not None and m.group(1):
  652.             value = float(m.group(1))
  653.             unit = m.group(2)
  654.             if unit == '%':
  655.                 if base is None:
  656.                     base = self.width
  657.                 
  658.                 result = (value / 100) * base
  659.             elif unit == 'px':
  660.                 result = value * 72 / self._profile.dpi
  661.             elif unit == 'in':
  662.                 result = value * 72
  663.             elif unit == 'pt':
  664.                 result = value
  665.             elif unit == 'em':
  666.                 if not font:
  667.                     pass
  668.                 font = self.fontSize
  669.                 result = value * font
  670.             elif unit in ('ex', 'en'):
  671.                 if not font:
  672.                     pass
  673.                 font = self.fontSize
  674.                 result = value * font * 0.5
  675.             elif unit == 'pc':
  676.                 result = value * 12
  677.             elif unit == 'mm':
  678.                 result = value * 0.04
  679.             elif unit == 'cm':
  680.                 result = value * 0.4
  681.             
  682.         
  683.         return result
  684.  
  685.     
  686.     def pt_to_px(self, value):
  687.         return (self._profile.dpi / 72) * value
  688.  
  689.     
  690.     def fontSize(self):
  691.         
  692.         def normalize_fontsize(value, base):
  693.             value = value.replace('"', '').replace("'", '')
  694.             result = None
  695.             factor = None
  696.             if value == 'inherit':
  697.                 value = base
  698.             
  699.             if value in FONT_SIZE_NAMES:
  700.                 result = self._profile.fnames[value]
  701.             elif value == 'smaller':
  702.                 factor = 1 / 1.2
  703.                 for _, _, size in self._profile.fsizes:
  704.                     if base <= size:
  705.                         break
  706.                     
  707.                     factor = None
  708.                     result = size
  709.                 
  710.             elif value == 'larger':
  711.                 factor = 1.2
  712.                 for _, _, size in reversed(self._profile.fsizes):
  713.                     if base >= size:
  714.                         break
  715.                     
  716.                     factor = None
  717.                     result = size
  718.                 
  719.             else:
  720.                 result = self._unit_convert(value, base = base, font = base)
  721.                 if not isinstance(result, (int, float, long)):
  722.                     return base
  723.                 if result < 0:
  724.                     result = normalize_fontsize('smaller', base)
  725.                 
  726.             if factor:
  727.                 result = factor * base
  728.             
  729.             return result
  730.  
  731.         return self._fontSize
  732.  
  733.     fontSize = property(fontSize)
  734.     
  735.     def width(self):
  736.         if self._width is None:
  737.             width = None
  738.             base = None
  739.             parent = self._get_parent()
  740.             if parent is not None:
  741.                 base = parent.width
  742.             else:
  743.                 base = self._profile.width
  744.             if 'width' in self._element.attrib:
  745.                 width = self._element.attrib['width']
  746.             elif 'width' in self._style:
  747.                 width = self._style['width']
  748.             
  749.             if not width or width == 'auto':
  750.                 result = base
  751.             else:
  752.                 result = self._unit_convert(width, base = base)
  753.             if isinstance(result, (unicode, str, bytes)):
  754.                 result = self._profile.width
  755.             
  756.             self._width = result
  757.         
  758.         return self._width
  759.  
  760.     width = property(width)
  761.     
  762.     def height(self):
  763.         if self._height is None:
  764.             height = None
  765.             base = None
  766.             parent = self._get_parent()
  767.             if parent is not None:
  768.                 base = parent.height
  769.             else:
  770.                 base = self._profile.height
  771.             if 'height' in self._element.attrib:
  772.                 height = self._element.attrib['height']
  773.             elif 'height' in self._style:
  774.                 height = self._style['height']
  775.             
  776.             if not height or height == 'auto':
  777.                 result = base
  778.             else:
  779.                 result = self._unit_convert(height, base = base)
  780.             if isinstance(result, (unicode, str, bytes)):
  781.                 result = self._profile.height
  782.             
  783.             self._height = result
  784.         
  785.         return self._height
  786.  
  787.     height = property(height)
  788.     
  789.     def lineHeight(self):
  790.         if self._lineHeight is None:
  791.             result = None
  792.             parent = self._getparent()
  793.             if 'line-height' in self._style:
  794.                 lineh = self._style['line-height']
  795.                 
  796.                 try:
  797.                     float(lineh)
  798.                 except ValueError:
  799.                     result = self._unit_convert(lineh, base = self.fontSize)
  800.  
  801.                 result = float(lineh) * self.fontSize
  802.             elif parent is not None:
  803.                 result = parent.lineHeight
  804.             else:
  805.                 result = 1.2 * self.fontSize
  806.             self._lineHeight = result
  807.         
  808.         return self._lineHeight
  809.  
  810.     lineHeight = property(lineHeight)
  811.     
  812.     def marginTop(self):
  813.         return self._unit_convert(self._get('margin-top'), base = self.height)
  814.  
  815.     marginTop = property(marginTop)
  816.     
  817.     def marginBottom(self):
  818.         return self._unit_convert(self._get('margin-bottom'), base = self.height)
  819.  
  820.     marginBottom = property(marginBottom)
  821.     
  822.     def paddingTop(self):
  823.         return self._unit_convert(self._get('padding-top'), base = self.height)
  824.  
  825.     paddingTop = property(paddingTop)
  826.     
  827.     def paddingBottom(self):
  828.         return self._unit_convert(self._get('padding-bottom'), base = self.height)
  829.  
  830.     paddingBottom = property(paddingBottom)
  831.     
  832.     def __str__(self):
  833.         items = self._style.items()
  834.         items.sort()
  835.         return '; '.join((lambda .0: for key, val in .0:
  836. '%s: %s' % (key, val))(items))
  837.  
  838.     
  839.     def cssdict(self):
  840.         return dict(self._style)
  841.  
  842.  
  843.