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))
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))
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)
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
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
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' )
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)