home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_999 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  23.2 KB  |  834 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.         for x in (extra_css, user_css):
  255.             if x:
  256.                 text = XHTML_CSS_NAMESPACE + x
  257.                 stylesheet = parser.parseString(text, href = cssname)
  258.                 stylesheet.namespaces['h'] = XHTML_NS
  259.                 stylesheets.append(stylesheet)
  260.                 continue
  261.         
  262.         rules = []
  263.         index = 0
  264.         self.stylesheets = set()
  265.         self.page_rule = { }
  266.         for stylesheet in stylesheets:
  267.             href = stylesheet.href
  268.             self.stylesheets.add(href)
  269.             for rule in stylesheet.cssRules:
  270.                 rules.extend(self.flatten_rule(rule, href, index))
  271.                 index = index + 1
  272.             
  273.         
  274.         rules.sort()
  275.         self.rules = rules
  276.         self._styles = { }
  277.         class_sel_pat = re.compile('\\.[a-z]+', re.IGNORECASE)
  278.         capital_sel_pat = re.compile('h|[A-Z]+')
  279.         for _, _, cssdict, text, _ in rules:
  280.             fl = ':first-letter' in text
  281.             if fl:
  282.                 text = text.replace(':first-letter', '')
  283.             
  284.             
  285.             try:
  286.                 selector = CSSSelector(text)
  287.             except (AssertionError, ExpressionError, etree.XPathSyntaxError, NameError, SelectorSyntaxError):
  288.                 continue
  289.  
  290.             matches = selector(tree)
  291.             if not matches:
  292.                 ntext = capital_sel_pat.sub((lambda m: m.group().lower()), text)
  293.                 if ntext != text:
  294.                     self.logger.warn('Transformed CSS selector', text, 'to', ntext)
  295.                     selector = CSSSelector(ntext)
  296.                     matches = selector(tree)
  297.                 
  298.             
  299.             if not matches and class_sel_pat.match(text) and text.lower() != text:
  300.                 found = False
  301.                 ltext = text.lower()
  302.                 for x in tree.xpath('//*[@class]'):
  303.                     if ltext.endswith('.' + x.get('class').lower()):
  304.                         matches.append(x)
  305.                         found = True
  306.                         continue
  307.                 
  308.                 if found:
  309.                     self.logger.warn('Ignoring case mismatches for CSS selector: %s in %s' % (text, item.href))
  310.                 
  311.             
  312.             if fl:
  313.                 ElementMaker = ElementMaker
  314.                 import lxml.builder
  315.                 E = ElementMaker(namespace = XHTML_NS)
  316.                 for elem in matches:
  317.                     for x in elem.iter():
  318.                         if x.text:
  319.                             span = E.span(x.text[0])
  320.                             span.tail = x.text[1:]
  321.                             x.text = None
  322.                             x.insert(0, span)
  323.                             self.style(span)._update_cssdict(cssdict)
  324.                             break
  325.                             continue
  326.                     
  327.                 
  328.             for elem in matches:
  329.                 self.style(elem)._update_cssdict(cssdict)
  330.             
  331.         
  332.         for elem in xpath(tree, '//h:img[@width or @height]'):
  333.             base = elem.get('style', '').strip()
  334.             if base:
  335.                 base += ';'
  336.             
  337.             for prop in ('width', 'height'):
  338.                 val = elem.get(prop, False)
  339.                 if val:
  340.                     base += '%s: %s;' % (prop, val)
  341.                     del elem.attrib[prop]
  342.                     continue
  343.             
  344.             elem.set('style', base)
  345.         
  346.         for elem in xpath(tree, '//h:*[@style]'):
  347.             self.style(elem)._apply_style_attr()
  348.         
  349.  
  350.     
  351.     def _fetch_css_file(self, path):
  352.         hrefs = self.oeb.manifest.hrefs
  353.         if path not in hrefs:
  354.             self.logger.warn('CSS import of missing file %r' % path)
  355.             return (None, None)
  356.         item = hrefs[path]
  357.         if item.media_type not in OEB_STYLES:
  358.             self.logger.warn('CSS import of non-CSS file %r' % path)
  359.             return (None, None)
  360.         data = item.data.cssText
  361.         return ('utf-8', data)
  362.  
  363.     
  364.     def flatten_rule(self, rule, href, index):
  365.         results = []
  366.         if isinstance(rule, CSSStyleRule):
  367.             style = self.flatten_style(rule.style)
  368.             for selector in rule.selectorList:
  369.                 specificity = selector.specificity + (index,)
  370.                 text = selector.selectorText
  371.                 selector = list(selector.seq)
  372.                 results.append((specificity, selector, style, text, href))
  373.             
  374.         elif isinstance(rule, CSSPageRule):
  375.             style = self.flatten_style(rule.style)
  376.             self.page_rule.update(style)
  377.         elif isinstance(rule, CSSFontFaceRule):
  378.             self.font_face_rules.append(rule)
  379.         
  380.         return results
  381.  
  382.     
  383.     def flatten_style(self, cssstyle):
  384.         style = { }
  385.         for prop in cssstyle:
  386.             name = prop.name
  387.             if name in ('margin', 'padding'):
  388.                 style.update(self._normalize_edge(prop.cssValue, name))
  389.                 continue
  390.             if name == 'font':
  391.                 style.update(self._normalize_font(prop.cssValue))
  392.                 continue
  393.             if name == 'list-style':
  394.                 style.update(self._normalize_list_style(prop.cssValue))
  395.                 continue
  396.             if name == 'text-align':
  397.                 style.update(self._normalize_text_align(prop.cssValue))
  398.                 continue
  399.             style[name] = prop.value
  400.         
  401.         if 'font-size' in style:
  402.             size = style['font-size']
  403.             if size == 'normal':
  404.                 size = 'medium'
  405.             
  406.             if size == 'smallest':
  407.                 size = 'xx-small'
  408.             
  409.             if size in FONT_SIZE_NAMES:
  410.                 style['font-size'] = '%dpt' % self.profile.fnames[size]
  411.             
  412.         
  413.         return style
  414.  
  415.     
  416.     def _normalize_edge(self, cssvalue, name):
  417.         style = { }
  418.         if len(primitives) == 1:
  419.             (value,) = primitives
  420.             values = [
  421.                 value,
  422.                 value,
  423.                 value,
  424.                 value]
  425.         elif len(primitives) == 2:
  426.             (vert, horiz) = primitives
  427.             values = [
  428.                 vert,
  429.                 horiz,
  430.                 vert,
  431.                 horiz]
  432.         elif len(primitives) == 3:
  433.             (top, horiz, bottom) = primitives
  434.             values = [
  435.                 top,
  436.                 horiz,
  437.                 bottom,
  438.                 horiz]
  439.         else:
  440.             values = primitives[:4]
  441.         edges = ('top', 'right', 'bottom', 'left')
  442.         for edge, value in itertools.izip(edges, values):
  443.             style['%s-%s' % (name, edge)] = value
  444.         
  445.         return style
  446.  
  447.     
  448.     def _normalize_list_style(self, cssvalue):
  449.         composition = ('list-style-type', 'list-style-position', 'list-style-image')
  450.         style = { }
  451.         if cssvalue.cssText == 'inherit':
  452.             for key in composition:
  453.                 style[key] = 'inherit'
  454.             
  455.         else:
  456.             
  457.             try:
  458.                 primitives = [ v.cssText for v in cssvalue ]
  459.             except TypeError:
  460.                 primitives = [
  461.                     cssvalue.cssText]
  462.  
  463.             primitives.reverse()
  464.             value = primitives.pop()
  465.             for key in composition:
  466.                 if cssprofiles.validate(key, value):
  467.                     style[key] = value
  468.                     if not primitives:
  469.                         break
  470.                     
  471.                     value = primitives.pop()
  472.                     continue
  473.             
  474.             for key in composition:
  475.                 if key not in style:
  476.                     style[key] = DEFAULTS[key]
  477.                     continue
  478.             
  479.         return style
  480.  
  481.     
  482.     def _normalize_text_align(self, cssvalue):
  483.         style = { }
  484.         text = cssvalue.cssText
  485.         if text == 'inherit':
  486.             style['text-align'] = 'inherit'
  487.         elif text in ('left', 'justify') and self.opts.change_justification in ('left', 'justify'):
  488.             val = self.opts.change_justification
  489.             style['text-align'] = val
  490.         else:
  491.             style['text-align'] = text
  492.         return style
  493.  
  494.     
  495.     def _normalize_font(self, cssvalue):
  496.         composition = ('font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family')
  497.         style = { }
  498.         if cssvalue.cssText == 'inherit':
  499.             for key in composition:
  500.                 style[key] = 'inherit'
  501.             
  502.         else:
  503.             
  504.             try:
  505.                 primitives = [ v.cssText for v in cssvalue ]
  506.             except TypeError:
  507.                 primitives = [
  508.                     cssvalue.cssText]
  509.  
  510.             primitives.reverse()
  511.             value = primitives.pop()
  512.             for key in composition:
  513.                 if cssprofiles.validate(key, value):
  514.                     style[key] = value
  515.                     if not primitives:
  516.                         break
  517.                     
  518.                     value = primitives.pop()
  519.                     continue
  520.             
  521.             for key in composition:
  522.                 if key not in style:
  523.                     style[key] = DEFAULTS[key]
  524.                     continue
  525.             
  526.         return style
  527.  
  528.     
  529.     def style(self, element):
  530.         
  531.         try:
  532.             return self._styles[element]
  533.         except KeyError:
  534.             return Style(element, self)
  535.  
  536.  
  537.     
  538.     def stylesheet(self, name, font_scale = None):
  539.         rules = []
  540.         for _, _, style, selector, href in self.rules:
  541.             if href != name:
  542.                 continue
  543.             
  544.             if font_scale and 'font-size' in style and style['font-size'].endswith('pt'):
  545.                 style = copy.copy(style)
  546.                 size = float(style['font-size'][:-2])
  547.                 style['font-size'] = '%.2fpt' % size * font_scale
  548.             
  549.             style = ';\n    '.join((lambda .0: for item in .0:
  550. ': '.join(item))(style.items()))
  551.             rules.append('%s {\n    %s;\n}' % (selector, style))
  552.         
  553.         return '\n'.join(rules)
  554.  
  555.  
  556.  
  557. class Style(object):
  558.     UNIT_RE = re.compile('^(-*[0-9]*[.]?[0-9]*)\\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
  559.     
  560.     def __init__(self, element, stylizer):
  561.         self._element = element
  562.         self._profile = stylizer.profile
  563.         self._stylizer = stylizer
  564.         self._style = { }
  565.         self._fontSize = None
  566.         self._width = None
  567.         self._height = None
  568.         self._lineHeight = None
  569.         stylizer._styles[element] = self
  570.  
  571.     
  572.     def set(self, prop, val):
  573.         self._style[prop] = val
  574.  
  575.     
  576.     def _update_cssdict(self, cssdict):
  577.         self._style.update(cssdict)
  578.  
  579.     
  580.     def _apply_style_attr(self):
  581.         attrib = self._element.attrib
  582.         if 'style' not in attrib:
  583.             return None
  584.         css = attrib['style'].split(';')
  585.         css = filter(None, (lambda .0: for x in .0:
  586. x.strip())(css))
  587.         
  588.         try:
  589.             style = CSSStyleDeclaration('; '.join(css))
  590.         except CSSSyntaxError:
  591.             'style' not in attrib
  592.             'style' not in attrib
  593.             return None
  594.  
  595.         self._style.update(self._stylizer.flatten_style(style))
  596.  
  597.     
  598.     def _has_parent(self):
  599.         return self._element.getparent() is not None
  600.  
  601.     
  602.     def _get_parent(self):
  603.         elem = self._element.getparent()
  604.         if elem is None:
  605.             return None
  606.         return self._stylizer.style(elem)
  607.  
  608.     
  609.     def __getitem__(self, name):
  610.         domname = cssproperties._toDOMname(name)
  611.         if hasattr(self, domname):
  612.             return getattr(self, domname)
  613.         return self._unit_convert(self._get(name))
  614.  
  615.     
  616.     def _get(self, name):
  617.         result = None
  618.         if name in self._style:
  619.             result = self._style[name]
  620.         
  621.         if (result == 'inherit' or result is None) and name in INHERITED and self._has_parent():
  622.             stylizer = self._stylizer
  623.             result = stylizer.style(self._element.getparent())._get(name)
  624.         
  625.         if result is None:
  626.             result = DEFAULTS[name]
  627.         
  628.         return result
  629.  
  630.     
  631.     def _unit_convert(self, value, base = None, font = None):
  632.         if isinstance(value, (int, long, float)):
  633.             return value
  634.         
  635.         try:
  636.             return float(value) * 72 / self._profile.dpi
  637.         except:
  638.             isinstance(value, (int, long, float))
  639.  
  640.         result = value
  641.         m = self.UNIT_RE.match(value)
  642.         if m is not None and m.group(1):
  643.             value = float(m.group(1))
  644.             unit = m.group(2)
  645.             if unit == '%':
  646.                 if base is None:
  647.                     base = self.width
  648.                 
  649.                 result = (value / 100) * base
  650.             elif unit == 'px':
  651.                 result = value * 72 / self._profile.dpi
  652.             elif unit == 'in':
  653.                 result = value * 72
  654.             elif unit == 'pt':
  655.                 result = value
  656.             elif unit == 'em':
  657.                 if not font:
  658.                     pass
  659.                 font = self.fontSize
  660.                 result = value * font
  661.             elif unit in ('ex', 'en'):
  662.                 if not font:
  663.                     pass
  664.                 font = self.fontSize
  665.                 result = value * font * 0.5
  666.             elif unit == 'pc':
  667.                 result = value * 12
  668.             elif unit == 'mm':
  669.                 result = value * 0.04
  670.             elif unit == 'cm':
  671.                 result = value * 0.4
  672.             
  673.         
  674.         return result
  675.  
  676.     
  677.     def pt_to_px(self, value):
  678.         return (self._profile.dpi / 72) * value
  679.  
  680.     
  681.     def fontSize(self):
  682.         
  683.         def normalize_fontsize(value, base):
  684.             value = value.replace('"', '').replace("'", '')
  685.             result = None
  686.             factor = None
  687.             if value == 'inherit':
  688.                 value = base
  689.             
  690.             if value in FONT_SIZE_NAMES:
  691.                 result = self._profile.fnames[value]
  692.             elif value == 'smaller':
  693.                 factor = 1 / 1.2
  694.                 for _, _, size in self._profile.fsizes:
  695.                     if base <= size:
  696.                         break
  697.                     
  698.                     factor = None
  699.                     result = size
  700.                 
  701.             elif value == 'larger':
  702.                 factor = 1.2
  703.                 for _, _, size in reversed(self._profile.fsizes):
  704.                     if base >= size:
  705.                         break
  706.                     
  707.                     factor = None
  708.                     result = size
  709.                 
  710.             else:
  711.                 result = self._unit_convert(value, base = base, font = base)
  712.                 if not isinstance(result, (int, float, long)):
  713.                     return base
  714.                 if result < 0:
  715.                     result = normalize_fontsize('smaller', base)
  716.                 
  717.             if factor:
  718.                 result = factor * base
  719.             
  720.             return result
  721.  
  722.         return self._fontSize
  723.  
  724.     fontSize = property(fontSize)
  725.     
  726.     def width(self):
  727.         if self._width is None:
  728.             width = None
  729.             base = None
  730.             parent = self._get_parent()
  731.             if parent is not None:
  732.                 base = parent.width
  733.             else:
  734.                 base = self._profile.width
  735.             if 'width' in self._element.attrib:
  736.                 width = self._element.attrib['width']
  737.             elif 'width' in self._style:
  738.                 width = self._style['width']
  739.             
  740.             if not width or width == 'auto':
  741.                 result = base
  742.             else:
  743.                 result = self._unit_convert(width, base = base)
  744.             if isinstance(result, (unicode, str, bytes)):
  745.                 result = self._profile.width
  746.             
  747.             self._width = result
  748.         
  749.         return self._width
  750.  
  751.     width = property(width)
  752.     
  753.     def height(self):
  754.         if self._height is None:
  755.             height = None
  756.             base = None
  757.             parent = self._get_parent()
  758.             if parent is not None:
  759.                 base = parent.height
  760.             else:
  761.                 base = self._profile.height
  762.             if 'height' in self._element.attrib:
  763.                 height = self._element.attrib['height']
  764.             elif 'height' in self._style:
  765.                 height = self._style['height']
  766.             
  767.             if not height or height == 'auto':
  768.                 result = base
  769.             else:
  770.                 result = self._unit_convert(height, base = base)
  771.             if isinstance(result, (unicode, str, bytes)):
  772.                 result = self._profile.height
  773.             
  774.             self._height = result
  775.         
  776.         return self._height
  777.  
  778.     height = property(height)
  779.     
  780.     def lineHeight(self):
  781.         if self._lineHeight is None:
  782.             result = None
  783.             parent = self._getparent()
  784.             if 'line-height' in self._style:
  785.                 lineh = self._style['line-height']
  786.                 
  787.                 try:
  788.                     float(lineh)
  789.                 except ValueError:
  790.                     result = self._unit_convert(lineh, base = self.fontSize)
  791.  
  792.                 result = float(lineh) * self.fontSize
  793.             elif parent is not None:
  794.                 result = parent.lineHeight
  795.             else:
  796.                 result = 1.2 * self.fontSize
  797.             self._lineHeight = result
  798.         
  799.         return self._lineHeight
  800.  
  801.     lineHeight = property(lineHeight)
  802.     
  803.     def marginTop(self):
  804.         return self._unit_convert(self._get('margin-top'), base = self.height)
  805.  
  806.     marginTop = property(marginTop)
  807.     
  808.     def marginBottom(self):
  809.         return self._unit_convert(self._get('margin-bottom'), base = self.height)
  810.  
  811.     marginBottom = property(marginBottom)
  812.     
  813.     def paddingTop(self):
  814.         return self._unit_convert(self._get('padding-top'), base = self.height)
  815.  
  816.     paddingTop = property(paddingTop)
  817.     
  818.     def paddingBottom(self):
  819.         return self._unit_convert(self._get('padding-bottom'), base = self.height)
  820.  
  821.     paddingBottom = property(paddingBottom)
  822.     
  823.     def __str__(self):
  824.         items = self._style.items()
  825.         items.sort()
  826.         return '; '.join((lambda .0: for key, val in .0:
  827. '%s: %s' % (key, val))(items))
  828.  
  829.     
  830.     def cssdict(self):
  831.         return dict(self._style)
  832.  
  833.  
  834.