def normalize_edge(name, cssvalue): style = {} if isinstance(cssvalue, PropertyValue): primitives = [css_text(v) for v in cssvalue] else: primitives = [css_text(cssvalue)] if len(primitives) == 1: value, = primitives values = (value, value, value, value) elif len(primitives) == 2: vert, horiz = primitives values = (vert, horiz, vert, horiz) elif len(primitives) == 3: top, horiz, bottom = primitives values = (top, horiz, bottom, horiz) else: values = primitives[:4] if '-' in name: l, _, r = name.partition('-') for edge, value in zip(EDGES, values): style['%s-%s-%s' % (l, edge, r)] = value else: for edge, value in zip(EDGES, values): style['%s-%s' % (name, edge)] = value return style
def remove_links_to(container, predicate): ''' predicate must be a function that takes the arguments (name, href, fragment=None) and returns True iff the link should be removed ''' from ebook_converter.ebooks.oeb.base import iterlinks, OEB_DOCS, OEB_STYLES, XPath, XHTML stylepath = XPath('//h:style') styleattrpath = XPath('//*[@style]') changed = set() for name, mt in container.mime_map.items(): removed = False if mt in OEB_DOCS: root = container.parsed(name) for el, attr, href, pos in iterlinks(root, find_links_in_css=False): hname = container.href_to_name(href, name) frag = href.partition('#')[-1] if predicate(hname, href, frag): if attr is None: el.text = None else: if el.tag == XHTML('link') or el.tag == XHTML('img'): extract(el) else: del el.attrib[attr] removed = True for tag in stylepath(root): if tag.text and (tag.get('type') or 'text/css').lower() == 'text/css': sheet = container.parse_css(tag.text) if remove_links_in_sheet( partial(container.href_to_name, base=name), sheet, predicate): tag.text = css_text(sheet) removed = True for tag in styleattrpath(root): style = tag.get('style') if style: style = container.parse_css(style, is_declaration=True) if remove_links_in_declaration( partial(container.href_to_name, base=name), style, predicate): removed = True tag.set('style', css_text(style)) elif mt in OEB_STYLES: removed = remove_links_in_sheet( partial(container.href_to_name, base=name), container.parsed(name), predicate) if removed: changed.add(name) tuple(map(container.dirty, changed)) return changed
def remove_property_value(prop, predicate): ''' Remove the Values that match the predicate from this property. If all values of the property would be removed, the property is removed from its parent instead. Note that this means the property must have a parent (a CSSStyleDeclaration). ''' removed_vals = list(filter(predicate, prop.propertyValue)) if len(removed_vals) == len(prop.propertyValue): prop.parent.removeProperty(prop.name) else: x = base.css_text(prop.propertyValue) for v in removed_vals: x = x.replace(base.css_text(v), '').strip() prop.propertyValue.cssText = x return bool(removed_vals)
def test_border_condensation(self): vals = 'red solid 5px' css = '; '.join('border-%s-%s: %s' % (edge, p, v) for edge in EDGES for p, v in zip(BORDER_PROPS, vals.split())) style = parseStyle(css) condense_rule(style) for e, p in product(EDGES, BORDER_PROPS): self.assertFalse(style.getProperty('border-%s-%s' % (e, p))) self.assertFalse(style.getProperty('border-%s' % e)) self.assertFalse(style.getProperty('border-%s' % p)) self.assertEqual(style.getProperty('border').value, vals) css = '; '.join('border-%s-%s: %s' % (edge, p, v) for edge in ('top', ) for p, v in zip(BORDER_PROPS, vals.split())) style = parseStyle(css) condense_rule(style) self.assertEqual(css_text(style), 'border-top: %s' % vals) css += ';' + '; '.join( 'border-%s-%s: %s' % (edge, p, v) for edge in ('right', 'left', 'bottom') for p, v in zip(BORDER_PROPS, vals.replace('red', 'green').split())) style = parseStyle(css) condense_rule(style) self.assertEqual(len(style.getProperties()), 4) self.assertEqual(style.getProperty('border-top').value, vals) self.assertEqual( style.getProperty('border-left').value, vals.replace('red', 'green'))
def normalize_simple_composition(name, cssvalue, composition, check_inherit=True): if check_inherit and css_text(cssvalue) == 'inherit': style = {k: 'inherit' for k in composition} else: style = {k: DEFAULTS[k] for k in composition} try: primitives = [css_text(v) for v in cssvalue] except TypeError: primitives = [css_text(cssvalue)] while primitives: value = primitives.pop() for key in composition: if cssprofiles.validate(key, value): style[key] = value break return style
def __call__(self, oeb): if not self.body_font_family: return None if not self.href: iid, href = oeb.manifest.generate('page_styles', 'page_styles.css') rules = [base.css_text(x) for x in self.rules] rules = '\n\n'.join(rules) sheet = css_parser.parseString(rules, validate=False) self.href = oeb.manifest.add(iid, href, mimetypes.guess_type(href)[0], data=sheet).href return self.href
def get_font_properties(rule, default=None): ''' Given a CSS rule, extract normalized font properties from it. Note that shorthand font property should already have been expanded by the CSS flattening code. ''' props = {} s = rule.style for q in ('font-family', 'src', 'font-weight', 'font-stretch', 'font-style'): g = 'uri' if q == 'src' else 'value' try: val = s.getProperty(q).propertyValue[0] val = getattr(val, g) if q == 'font-family': val = parse_font_family(css_text(s.getProperty(q).propertyValue)) if val and val[0] == 'inherit': val = None except (IndexError, KeyError, AttributeError, TypeError, ValueError): val = None if q in {'src', 'font-family'} else default if q in {'font-weight', 'font-stretch', 'font-style'}: val = str(val).lower() if (val or val == 0) else val if val == 'inherit': val = default if q == 'font-weight': val = {'normal':'400', 'bold':'700'}.get(val, val) if val not in {'100', '200', '300', '400', '500', '600', '700', '800', '900', 'bolder', 'lighter'}: val = default if val == 'normal': val = '400' elif q == 'font-style': if val not in {'normal', 'italic', 'oblique'}: val = default elif q == 'font-stretch': if val not in {'normal', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'}: val = default props[q] = val return props
def collect_global_css(self): global_css = collections.defaultdict(list) for item in self.items: stylizer = self.stylizers[item] if float(self.context.margin_top) >= 0: stylizer.page_rule['margin-top'] = '%gpt'%\ float(self.context.margin_top) if float(self.context.margin_bottom) >= 0: stylizer.page_rule['margin-bottom'] = '%gpt'%\ float(self.context.margin_bottom) items = sorted(stylizer.page_rule.items()) css = ';\n'.join("%s: %s" % (key, val) for key, val in items) css = ('@page {\n%s\n}\n' % css) if items else '' rules = [ base.css_text(r) for r in stylizer.font_face_rules + self.embed_font_rules ] raw = '\n\n'.join(rules) css += '\n\n' + raw global_css[css].append(item) gc_map = {} manifest = self.oeb.manifest for css in global_css: href = None if css.strip(): id_, href = manifest.generate('page_css', 'page_styles.css') sheet = css_parser.parseString(css, validate=False) if self.transform_css_rules: from ebook_converter.ebooks.css_transform_rules import transform_sheet transform_sheet(self.transform_css_rules, sheet) manifest.add(id_, href, base.CSS_MIME, data=sheet) gc_map[css] = href ans = {} for css, items in global_css.items(): for item in items: ans[item] = gc_map[css] return ans
def normalize_font(cssvalue, font_family_as_list=False): # See https://developer.mozilla.org/en-US/docs/Web/CSS/font composition = font_composition val = css_text(cssvalue) if val == 'inherit': ans = {k: 'inherit' for k in composition} elif val in { 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar' }: ans = {k: DEFAULTS[k] for k in composition} else: ans = {k: DEFAULTS[k] for k in composition} ans.update(parse_font(val)) if font_family_as_list: if isinstance(ans['font-family'], (str, bytes)): ans['font-family'] = [ x.strip() for x in ans['font-family'].split(',') ] else: if not isinstance(ans['font-family'], (str, bytes)): ans['font-family'] = serialize_font_family(ans['font-family']) return ans