def layout_table_cell(self, lc): """Lay out a table cell element and its descendants.""" # we're basically doing block layout here, but within a fake context # tailored to our place in the table align = self.get_style("text-align", None, Value('IDENT', 'left'), inherit=True) # fake dimensions for this table cell tlc = lc.get_table_context() d = lc.containing_block_dim fake_dim = Dimensions() fake_dim.content.x = d.content.x + tlc.line_width fake_dim.content.y = d.content.y + lc.height w = tlc.table_colw[tlc.table_coli] * tlc.table_colf fake_dim.content.width = w fake_dim.content.height = 0 #print "layout_table_cell: fake_dim.content: %s" % fake_dim.content fake_lc = LayoutContext(lc, fake_dim, align.to_str()) self.layout_block(fake_lc) tlc.line_width += w tlc.table_coli += 1
def calculate_block_position(self, lc): """Finish calculating the block's edge sizes, and position it within its containing block.""" """http://www.w3.org/TR/CSS2/visudet.html#normal-block""" """Sets the vertical margin/padding/border dimensions, and the `x`, `y` values.""" #print "calculate_block_position: %s" % self d = self.dimensions # margin, border, and padding have initial value 0. zero = Value('DIMENSION', 0.0, 'px') # If margin-top or margin-bottom is `auto`, the used value is zero. d.margin.top = self.get_style("margin-top", "margin", zero).to_px() d.margin.bottom = self.get_style("margin-bottom", "margin", zero).to_px() d.border.top = self.get_style("border-top-width", "border-width", zero).to_px() d.border.bottom = self.get_style("border-bottom-width", "border-width", zero).to_px() d.padding.top = self.get_style("padding-top", "padding", zero).to_px() d.padding.bottom = self.get_style("padding-bottom", "padding", zero).to_px() d.content.x = lc.containing_block_dim.content.x + d.margin.left + d.border.left + d.padding.left # Position the box below all the previous boxes in the container. d.content.y = lc.height + lc.containing_block_dim.content.y + d.margin.top + d.border.top + d.padding.top
def layout_block_children(self, lc): align = self.get_style("text-align", None, Value('IDENT', 'left'), inherit=True) clc = LayoutContext(lc, self.dimensions, align.to_str()) for child in self.children: child.layout(clc) # finish + align last line clc.line_wrap() return clc
def layout_inline_children(self, lc): align = self.get_style("text-align", None, Value('IDENT', 'left'), inherit=True) clc = LayoutContext(lc, self.dimensions, align.to_str()) for child in self.children: child.layout(clc) # finish + align last line clc.line_wrap() if clc.height > self.dimensions.content.height: self.dimensions.content.height = clc.height
def layout_table(self, lc): """ Very simple table layout support at this point. """ d = self.dimensions align = self.get_style("text-align", None, Value('IDENT', 'left'), inherit=True) # # ask children about their widths to determine column widths # lc.table_colw = [] for tpart in self.children: #print "layout_table: tpart=%s" % tpart for tr in tpart.children: #print "layout_table: tr=%s" % tr col_i = 0 for td in tr.children: #print "layout_table: td=%s" % td if len(lc.table_colw) <= col_i: lc.table_colw.append(0.0) td.calculate_inline_width_height() mb = td.dimensions.margin_box() if mb.width > lc.table_colw[col_i]: lc.table_colw[col_i] = mb.width col_i += 1 lc.table_colw_sum = reduce(lambda x, y: x + y, lc.table_colw, 0.0) #print "layout_table: colw=%s sum=%f" % (repr(lc.table_colw), lc.table_colw_sum) # # continue with regular block layout for now # (tds will use lc table info further down in the tree) # self.layout_block(lc)
def calculate_block_width(self, lc): """Calculate the width of a block-level non-replaced element in normal flow.""" """http://www.w3.org/TR/CSS2/visudet.html#blockwidth""" """Sets the horizontal margin/padding/border dimensions, and the `width`.""" #print "calculate_block_width: %s" % (self) # `width` has initial value `auto`. auto = Value('IDENT', 'auto') width = self.get_style('width', None, auto) # margin, border, and padding have initial value 0. zero = Value('DIMENSION', 0.0, 'px') margin_left = self.get_style("margin-left", "margin", zero) margin_right = self.get_style("margin-right", "margin", zero) border_left = self.get_style("border-left-width", "border-width", zero) border_right = self.get_style("border-right-width", "border-width", zero) padding_left = self.get_style("padding-left", "padding", zero) padding_right = self.get_style("padding-right", "padding", zero) total = reduce( lambda x, y: x + y, map(lambda x: 0.0 if x.is_auto() else x.to_px(), [ margin_left, margin_right, border_left, border_right, padding_left, padding_right, width ])) #print " margin: %s, %s;\n border: %s, %s;\n padding: %s, %s;\n total: %s" % (margin_left, margin_right, border_left, border_right, padding_left, padding_right, total) # If width is not auto and the total is wider than the container, treat auto margins as 0. if not width.is_auto( ) and total > lc.containing_block_dim.content.width: if margin_left.is_auto(): margin_left = zero if margin_right.is_auto(): margin_right = zero # Adjust used values so that the above sum equals `lc.containing_block_dim.width`. # Each arm of the `match` should increase the total width by exactly `underflow`, # and afterward all values should be absolute lengths in px. underflow = lc.containing_block_dim.content.width - total #print " underflow: %f" % underflow # If the values are overconstrained, calculate margin_right. if not width.is_auto() and not margin_left.is_auto( ) and not margin_right.is_auto(): margin_right = Value.length(margin_right.to_px() + underflow, 'px') # If exactly one size is auto, its used value follows from the equality. elif not width.is_auto() and not margin_left.is_auto( ) and margin_right.is_auto(): margin_right = Value.length(underflow, 'px') elif not width.is_auto() and margin_left.is_auto( ) and not margin_right.is_auto(): margin_right = Value.length(underflow, 'px') # If width is set to auto, any other auto values become 0. elif width.is_auto(): if margin_left.is_auto(): margin_left = Value.length(0.0, 'px') if margin_right.is_auto(): margin_right = Value.length(0.0, 'px') if underflow >= 0.0: # Expand width to fill the underflow. width = Value.length(underflow, 'px') else: # Width can't be negative. Adjust the right margin instead. width = Value.length(0.0, 'px') margin_right = Value.length(margin_right.to_px() + underflow, 'px') # If margin-left and margin-right are both auto, their used values are equal. elif not width.is_auto() and margin_left.is_auto( ) and margin_right.is_auto(): margin_left = Value.length(underflow / 2.0, 'px') margin_right = Value.length(underflow / 2.0, 'px') d = self.dimensions d.content.width = width.to_px() d.padding.left = padding_left.to_px() d.padding.right = padding_right.to_px() d.border.left = border_left.to_px() d.border.right = border_right.to_px() d.margin.left = margin_left.to_px() d.margin.right = margin_right.to_px()
def calculate_inline_width_height(self): #print "calculate_inline_width_height: %s" % (self) # margin, border, and padding have initial value 0. margin_left = self.get_style("margin-left", "margin", zero) margin_right = self.get_style("margin-right", "margin", zero) margin_top = self.get_style("margin-top", "margin", zero) margin_bottom = self.get_style("margin-bottom", "margin", zero) border_left = self.get_style("border-left-width", "border-width", zero) border_right = self.get_style("border-right-width", "border-width", zero) border_top = self.get_style("border-top-width", "border-width", zero) border_bottom = self.get_style("border-bottom-width", "border-width", zero) padding_left = self.get_style("padding-left", "padding", zero) padding_right = self.get_style("padding-right", "padding", zero) padding_top = self.get_style("padding-top", "padding", zero) padding_bottom = self.get_style("padding-bottom", "padding", zero) # calculate text size font_family = self.get_style("font-family", None, "Monospace", inherit=True) font_size = self.get_style("font-size", None, 16, inherit=True) if self.text is not None: xtents = self.html.text_extents(self.html.user_data, font_family.to_str(), font_size.to_px(), self.text) width = xtents[4] xtents = self.html.font_extents(self.html.user_data, font_family.to_str(), font_size.to_px()) height = xtents[2] else: width = 0 height = 0 width = self.get_style("width", "width", Value('DIMENSION', width, 'px')).to_px() # make room for children, if any for child in self.children: if child.box_type == 'img': child.calculate_image_width_height() else: child.calculate_inline_width_height() cb = child.dimensions.margin_box() if cb.width > width: width = cb.width if cb.height > height: height = cb.height d = self.dimensions d.content.width = width d.content.height = height d.padding.left = padding_left.to_px() d.padding.right = padding_right.to_px() d.padding.top = padding_top.to_px() d.padding.bottom = padding_bottom.to_px() d.border.left = border_left.to_px() d.border.right = border_right.to_px() d.border.top = border_top.to_px() d.border.bottom = border_bottom.to_px() d.margin.left = margin_left.to_px() d.margin.right = margin_right.to_px() d.margin.top = margin_top.to_px() d.margin.bottom = margin_bottom.to_px()
def __init__(self, html, css, width, load_resourcefn, text_extents, font_extents, user_data): self.text_extents = text_extents self.font_extents = font_extents self.load_resourcefn = load_resourcefn self.user_data = user_data if VERBOSE: start = time.clock() end = time.clock() print "robinson: %8.3fs lxml parsing..." % (end-start) pr = cProfile.Profile() root = etree.fromstring(html) document = etree.ElementTree(root) if VERBOSE: end = time.clock() print repr(root), root.__class__ print document, repr(document), document.__class__ print etree.tostring(document.getroot()) print "robinson: %8.3fs tinycss.css21.CSS21Parser()..." % (end-start) cssparser = tinycss.css21.CSS21Parser() stylesheet = cssparser.parse_stylesheet(css) if VERBOSE: end = time.clock() print "robinson: %8.3fs style mapping..." % (end-start) style_map = {} sel_to_xpath = cssselect.xpath.HTMLTranslator().selector_to_xpath for rule in stylesheet.rules: if not isinstance (rule, tinycss.css21.RuleSet): continue sel_css = rule.selector.as_css() sels = cssselect.parse (sel_css) #print "CSS Ruleset: %s" % (rule.selector.as_css()) for sel in sels: speci = sel.specificity() prio = speci2prio (speci) #print " selector: %s, specificity: %s (%06d)" % (repr(sel), sel.specificity(), prio) xpath = sel_to_xpath (sel) #print " xpath: %s" % repr(xpath) for item in document.xpath(xpath): #print " matched item: %s" % repr(item.tag) if not item in style_map: style_map[item] = {} for decl in rule.declarations: #print " declaration: %s: %s" % (decl.name, decl.value) if not decl.name in style_map[item]: style_map[item][decl.name] = (prio, Value.from_token(decl.value)) else: if prio > style_map[item][decl.name][0]: style_map[item][decl.name] = (prio, Value.from_token(decl.value)) #print "Style map done." #print repr(style_map) if VERBOSE: end = time.clock() print "robinson: %8.3fs building layout tree..." % (end-start) pr.enable() viewport = Dimensions () viewport.content.width = width self.ltree = self._layout_tree (document.getroot(), style_map, viewport) if VERBOSE: end = time.clock() print "robinson: %8.3fs __init__ done." % (end-start)
def calculate_block_width(self, lc): """Calculate the width of a block-level non-replaced element in normal flow.""" """http://www.w3.org/TR/CSS2/visudet.html#blockwidth""" """Sets the horizontal margin/padding/border dimensions, and the `width`.""" #print "calculate_block_width: %s" % (self) # `width` has initial value `auto`. auto = Value('IDENT', 'auto') width = self.get_style ('width', None, auto) # margin, border, and padding have initial value 0. zero = Value ('DIMENSION', 0.0, 'px') margin_left = self.get_style("margin-left", "margin", zero) margin_right = self.get_style("margin-right", "margin", zero) border_left = self.get_style("border-left-width", "border-width", zero) border_right = self.get_style("border-right-width", "border-width", zero) padding_left = self.get_style("padding-left", "padding", zero) padding_right = self.get_style("padding-right", "padding", zero) total = reduce(lambda x, y: x+y, map(lambda x: 0.0 if x.is_auto() else x.to_px(), [margin_left, margin_right, border_left, border_right, padding_left, padding_right, width])) #print " margin: %s, %s;\n border: %s, %s;\n padding: %s, %s;\n total: %s" % (margin_left, margin_right, border_left, border_right, padding_left, padding_right, total) # If width is not auto and the total is wider than the container, treat auto margins as 0. if not width.is_auto() and total > lc.containing_block_dim.content.width: if margin_left.is_auto(): margin_left = zero if margin_right.is_auto(): margin_right = zero # Adjust used values so that the above sum equals `lc.containing_block_dim.width`. # Each arm of the `match` should increase the total width by exactly `underflow`, # and afterward all values should be absolute lengths in px. underflow = lc.containing_block_dim.content.width - total #print " underflow: %f" % underflow # If the values are overconstrained, calculate margin_right. if not width.is_auto() and not margin_left.is_auto() and not margin_right.is_auto(): margin_right = Value.length(margin_right.to_px() + underflow, 'px') # If exactly one size is auto, its used value follows from the equality. elif not width.is_auto() and not margin_left.is_auto() and margin_right.is_auto(): margin_right = Value.length(underflow, 'px') elif not width.is_auto() and margin_left.is_auto() and not margin_right.is_auto(): margin_right = Value.length(underflow, 'px') # If width is set to auto, any other auto values become 0. elif width.is_auto() : if margin_left.is_auto(): margin_left = Value.length(0.0, 'px') if margin_right.is_auto(): margin_right = Value.length(0.0, 'px') if underflow >= 0.0: # Expand width to fill the underflow. width = Value.length(underflow, 'px') else: # Width can't be negative. Adjust the right margin instead. width = Value.length(0.0, 'px') margin_right = Value.length(margin_right.to_px() + underflow, 'px') # If margin-left and margin-right are both auto, their used values are equal. elif not width.is_auto() and margin_left.is_auto() and margin_right.is_auto(): margin_left = Value.length(underflow / 2.0, 'px'); margin_right = Value.length(underflow / 2.0, 'px'); d = self.dimensions d.content.width = width.to_px() d.padding.left = padding_left.to_px() d.padding.right = padding_right.to_px() d.border.left = border_left.to_px() d.border.right = border_right.to_px() d.margin.left = margin_left.to_px() d.margin.right = margin_right.to_px()
def __init__(self, html, css, width, load_resourcefn, text_extents, font_extents, user_data): self.text_extents = text_extents self.font_extents = font_extents self.load_resourcefn = load_resourcefn self.user_data = user_data if VERBOSE: start = time.clock() end = time.clock() print("robinson: %8.3fs lxml parsing..." % (end - start)) pr = cProfile.Profile() root = etree.fromstring(html) document = etree.ElementTree(root) if VERBOSE: end = time.clock() print(repr(root), root.__class__) print(document, repr(document), document.__class__) print(etree.tostring(document.getroot())) print("robinson: %8.3fs tinycss.css21.CSS21Parser()..." % (end - start)) cssparser = tinycss.css21.CSS21Parser() stylesheet = cssparser.parse_stylesheet(css) if VERBOSE: end = time.clock() print("robinson: %8.3fs style mapping..." % (end - start)) style_map = {} sel_to_xpath = cssselect.xpath.HTMLTranslator().selector_to_xpath for rule in stylesheet.rules: if not isinstance(rule, tinycss.css21.RuleSet): continue sel_css = rule.selector.as_css() sels = cssselect.parse(sel_css) #print "CSS Ruleset: %s" % (rule.selector.as_css()) for sel in sels: speci = sel.specificity() prio = speci2prio(speci) #print " selector: %s, specificity: %s (%06d)" % (repr(sel), sel.specificity(), prio) xpath = sel_to_xpath(sel) #print " xpath: %s" % repr(xpath) for item in document.xpath(xpath): #print " matched item: %s" % repr(item.tag) if not item in style_map: style_map[item] = {} for decl in rule.declarations: #print " declaration: %s: %s" % (decl.name, decl.value) if not decl.name in style_map[item]: style_map[item][decl.name] = (prio, Value.from_token( decl.value)) else: if prio > style_map[item][decl.name][0]: style_map[item][decl.name] = (prio, Value.from_token( decl.value)) #print "Style map done." #print repr(style_map) if VERBOSE: end = time.clock() print("robinson: %8.3fs building layout tree..." % (end - start)) pr.enable() viewport = Dimensions() viewport.content.width = width self.ltree = self._layout_tree(document.getroot(), style_map, viewport) if VERBOSE: end = time.clock() print("robinson: %8.3fs __init__ done." % (end - start))