Ejemplo n.º 1
0
 def append_properties(self, props):
     if props:
         self.changed = True
         for prop in props:
             self.css_declaration.setProperty(
                 Property(prop.name, prop.value, prop.literalpriority,
                          self.css_declaration))
Ejemplo n.º 2
0
 def set_property(self, name, value, priority='', replace=True):
     # Note that this does not handle shorthand properties, so you must
     # call remove_property() yourself in that case
     self.changed = True
     if replace:
         self.css_declaration.removeProperty(name)
     self.css_declaration.setProperty(Property(name, value, priority, parent=self.css_declaration))
Ejemplo n.º 3
0
def iterdeclaration(decl):
    for p in all_properties(decl):
        n = normalizers.get(p.name)
        if n is None:
            yield p
        else:
            for k, v in n(p.name, p.propertyValue).iteritems():
                yield Property(k, v, p.literalpriority)
Ejemplo n.º 4
0
def defvals():
    global _defvals
    if _defvals is None:
        u = type('')
        _defvals = {
            k: Values(Property(k, u(val)).propertyValue)
            for k, val in DEFAULTS.iteritems()
        }
    return _defvals
Ejemplo n.º 5
0
 def __iter__(self):
     dec = self.css_declaration
     for p in all_properties(dec):
         n = normalizers.get(p.name)
         if n is None:
             yield p, None
         else:
             if p not in self.expanded_properties:
                 self.expanded_properties[p] = [Property(k, v, p.literalpriority) for k, v in n(p.name, p.propertyValue).iteritems()]
             for ep in self.expanded_properties[p]:
                 yield ep, p
Ejemplo n.º 6
0
    def build_media_query(self):
        """ Returns CSS media queries that scales pixel / em values in response to screen size changes.

        **Generated CSS for ``font-size-24-s`` minus the inline comments & line breaks**::

            // Default size above medium
            .font-size-24-s { font-size: 24px; }

            // medium screen font size reduction
            @media only screen and (max-width: 64.0em) {
                .font-size-24-s { font-size: 23.0px; }
            }

            // medium screen font size reduction
            @media only screen and (max-width: 45.0em) {
                .font-size-24-s { font-size: 21.3px; }
            }

            // small screen font size reduction
            @media only screen and (max-width: 30.0em) {
                .font-size-24-s { font-size: 19.2px; }
            }


        **Priority !important -- Generated CSS for ``font-size-24-s-i`` minus the inline comments & line breaks**::

            // Default size above the maximum 'medium' width breakpoint.
            .font-size-24-s-i { font-size: 24px !important; }

            // medium screen font size reduction
            @media only screen and (max-width: 64.0em) {
                .font-size-24-s-i { font-size: 23.0px !important; }
            }

            // Apply 'medium' screen font size reduction.
            @media only screen and (max-width: 45.0em) {
                .font-size-24-s-i { font-size: 21.3px !important; }
            }

            // Apply 'small' screen font size reduction.
            @media only screen and (max-width: 30.0em) {
                .font-size-24-s-i { font-size: 19.2px !important; }
            }

        :return: (*str*) -- Returns CSS media queries that scales pixel / em values in response to screen size changes.

        """
        if not self.is_scaling:
            return ''

        name = self.css_property.name
        value = self.css_property.value
        units = ''.join(filter(lambda x: x.isalpha(), value))                   # Only keep letters.
        priority = self.css_property.priority
        deny_empty_or_whitespace(str(value), variable_name='value')
        float_value = float(value.replace(units, ''))                           # Remove units.

        _max = 1

        large_property = Property(name=name, value=value, priority=priority)
        medium_property = Property(name=name, value=value, priority=priority)
        small_property = Property(name=name, value=value, priority=priority)

        large_value = round(float_value / self.scale_dict['large'], 4)          # Scale to large screen
        large_property.value = str(large_value) + units                         # Add units

        medium_value = round(float_value / self.scale_dict['medium'], 4)        # Scale to medium screen
        medium_property.value = str(medium_value) + units                       # Add units

        small_value = round(float_value / self.scale_dict['small'], 4)          # Scale to small screen
        small_property.value = str(small_value) + units                         # Add units

        return (
            '.' + self.css_class + ' { ' + self.css_property.cssText + '; }\n\n' +
            '@media only screen and (max-width: ' + large[_max] + ') {\n' +
            '\t.' + self.css_class + ' { ' + large_property.cssText + '; }\n' +
            '}\n\n' +
            '@media only screen and (max-width: ' + medium[_max] + ') {\n' +
            '\t.' + self.css_class + ' { ' + medium_property.cssText + '; }\n' +
            '}\n\n' +
            '@media only screen and (max-width: ' + small[_max] + ') {\n' +
            '\t.' + self.css_class + ' { ' + small_property.cssText + '; }\n' +
            '}\n\n'
        )
Ejemplo n.º 7
0
    def flatten_node(self, node, stylizer, names, styles, pseudo_styles, psize,
                     item_id):
        if not isinstance(node.tag, basestring) \
           or namespace(node.tag) != XHTML_NS:
            return
        tag = barename(node.tag)
        style = stylizer.style(node)
        cssdict = style.cssdict()
        try:
            font_size = style['font-size']
        except:
            font_size = self.sbase if self.sbase is not None else \
                self.context.source.fbase
        if tag == 'body' and isinstance(font_size, (int, float)):
            stylizer.body_font_size = font_size
        if 'align' in node.attrib:
            if tag != 'img':
                cssdict['text-align'] = node.attrib['align']
            else:
                val = node.attrib['align']
                if val in ('middle', 'bottom', 'top'):
                    cssdict['vertical-align'] = val
                elif val in ('left', 'right'):
                    cssdict['float'] = val
            del node.attrib['align']
        if node.tag == XHTML('font'):
            tags = [
                'descendant::h:%s' % x
                for x in ('p', 'div', 'table', 'h1', 'h2', 'h3', 'h4', 'h5',
                          'h6', 'ol', 'ul', 'dl', 'blockquote')
            ]
            tag = 'div' if XPath('|'.join(tags))(node) else 'span'
            node.tag = XHTML(tag)
            if 'size' in node.attrib:

                def force_int(raw):
                    return int(re.search(r'([0-9+-]+)', raw).group(1))

                size = node.attrib['size'].strip()
                if size:
                    fnums = self.context.source.fnums
                    if size[0] in ('+', '-'):
                        # Oh, the warcrimes
                        try:
                            esize = 3 + force_int(size)
                        except:
                            esize = 3
                        if esize < 1:
                            esize = 1
                        if esize > 7:
                            esize = 7
                        font_size = fnums[esize]
                    else:
                        try:
                            font_size = fnums[force_int(size)]
                        except:
                            font_size = fnums[3]
                    cssdict['font-size'] = '%.1fpt' % font_size
                del node.attrib['size']
            if 'face' in node.attrib:
                cssdict['font-family'] = node.attrib['face']
                del node.attrib['face']
        if 'color' in node.attrib:
            try:
                cssdict['color'] = Property('color',
                                            node.attrib['color']).value
            except (ValueError, SyntaxErr):
                pass
            del node.attrib['color']
        if 'bgcolor' in node.attrib:
            try:
                cssdict['background-color'] = Property(
                    'background-color', node.attrib['bgcolor']).value
            except (ValueError, SyntaxErr):
                pass
            del node.attrib['bgcolor']
        if cssdict.get('font-weight', '').lower() == 'medium':
            cssdict[
                'font-weight'] = 'normal'  # ADE chokes on font-weight medium

        fsize = font_size
        is_drop_cap = (cssdict.get('float', None) == 'left'
                       and 'font-size' in cssdict and len(node) == 0
                       and node.text and len(node.text) == 1)
        # Detect drop caps generated by the docx input plugin
        if (node.tag and node.tag.endswith('}p') and len(node) == 0
                and node.text and len(node.text.strip()) == 1 and not node.tail
                and 'line-height' in cssdict and 'font-size' in cssdict):
            dp = node.getparent()
            if dp.tag and dp.tag.endswith('}div') and len(
                    dp) == 1 and not dp.text:
                if stylizer.style(dp).cssdict().get('float', None) == 'left':
                    is_drop_cap = True
        if not self.context.disable_font_rescaling and not is_drop_cap:
            _sbase = self.sbase if self.sbase is not None else \
                self.context.source.fbase
            dyn_rescale = dynamic_rescale_factor(node)
            if dyn_rescale is not None:
                fsize = self.fmap[_sbase]
                fsize *= dyn_rescale
                cssdict['font-size'] = '%0.5fem' % (fsize / psize)
                psize = fsize
            elif 'font-size' in cssdict or tag == 'body':
                fsize = self.fmap[font_size]
                try:
                    cssdict['font-size'] = "%0.5fem" % (fsize / psize)
                except ZeroDivisionError:
                    cssdict['font-size'] = '%.1fpt' % fsize
                psize = fsize

        try:
            minlh = self.context.minimum_line_height / 100.
            if not is_drop_cap and style['line-height'] < minlh * fsize:
                cssdict['line-height'] = str(minlh)
        except:
            self.oeb.logger.exception('Failed to set minimum line-height')

        if cssdict:
            for x in self.filter_css:
                popval = cssdict.pop(x, None)
                if (self.body_font_family and popval and x == 'font-family'
                        and popval.partition(',')[0][1:-1]
                        == self.body_font_family.partition(',')[0][1:-1]):
                    cssdict[x] = popval

        if cssdict:
            if self.lineh and self.fbase and tag != 'body':
                self.clean_edges(cssdict, style, psize)
            if 'display' in cssdict and cssdict['display'] == 'in-line':
                cssdict['display'] = 'inline'
            if self.unfloat and 'float' in cssdict \
               and cssdict.get('display', 'none') != 'none':
                del cssdict['display']
            if self.untable and 'display' in cssdict \
               and cssdict['display'].startswith('table'):
                display = cssdict['display']
                if display == 'table-cell':
                    cssdict['display'] = 'inline'
                else:
                    cssdict['display'] = 'block'
            if 'vertical-align' in cssdict \
               and cssdict['vertical-align'] == 'sup':
                cssdict['vertical-align'] = 'super'
        if self.lineh and 'line-height' not in cssdict:
            lineh = self.lineh / psize
            cssdict['line-height'] = "%0.5fem" % lineh

        if (self.context.remove_paragraph_spacing
                or self.context.insert_blank_line) and tag in ('p', 'div'):
            if item_id != 'calibre_jacket' or self.context.output_profile.name == 'Kindle':
                for prop in ('margin', 'padding', 'border'):
                    for edge in ('top', 'bottom'):
                        cssdict['%s-%s' % (prop, edge)] = '0pt'
            if self.context.insert_blank_line:
                cssdict['margin-top'] = cssdict['margin-bottom'] = \
                    '%fem'%self.context.insert_blank_line_size
            indent_size = self.context.remove_paragraph_spacing_indent_size
            keep_indents = indent_size < 0.0
            if (self.context.remove_paragraph_spacing and not keep_indents
                    and cssdict.get('text-align',
                                    None) not in ('center', 'right')):
                cssdict['text-indent'] = "%1.1fem" % indent_size

        pseudo_classes = style.pseudo_classes(self.filter_css)
        if cssdict or pseudo_classes:
            keep_classes = set()

            if cssdict:
                items = sorted(cssdict.items())
                css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
                classes = node.get('class', '').strip() or 'calibre'
                klass = ascii_text(
                    STRIPNUM.sub('',
                                 classes.split()[0].replace('_', '')))
                if css in styles:
                    match = styles[css]
                else:
                    match = klass + str(names[klass] or '')
                    styles[css] = match
                    names[klass] += 1
                node.attrib['class'] = match
                keep_classes.add(match)

            for psel, cssdict in pseudo_classes.iteritems():
                items = sorted(cssdict.iteritems())
                css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
                pstyles = pseudo_styles[psel]
                if css in pstyles:
                    match = pstyles[css]
                else:
                    # We have to use a different class for each psel as
                    # otherwise you can have incorrect styles for a situation
                    # like: a:hover { color: red } a:link { color: blue } a.x:hover { color: green }
                    # If the pcalibre class for a:hover and a:link is the same,
                    # then the class attribute for a.x tags will contain both
                    # that class and the class for a.x:hover, which is wrong.
                    klass = 'pcalibre'
                    match = klass + str(names[klass] or '')
                    pstyles[css] = match
                    names[klass] += 1
                keep_classes.add(match)
                node.attrib['class'] = ' '.join(keep_classes)

        elif 'class' in node.attrib:
            del node.attrib['class']
        if 'style' in node.attrib:
            del node.attrib['style']
        for child in node:
            self.flatten_node(child, stylizer, names, styles, pseudo_styles,
                              psize, item_id)