Example #1
0
 def populate_root_frame(self, frame, pres_style, draw_style):
     frame_style = self.style_src.find("office:automatic-styles")\
         .find({"style:style"}, {"style:name": pres_style})
     parent_frame_style = self.pres.styles.find("office:styles")\
         .find({"style:style"}, {"style:name": frame_style["style:parent-style-name"]})
     parent_text_props = parent_frame_style.find({"style:text-properties"}, recursive=False)
     parent_para_props = parent_frame_style.find({"style:paragraph-properties"}, recursive=False)
     frame_text_style = self.style_src.find("office:automatic-styles")\
         .find({"style:style"}, {"style:name": draw_style})
     frame_text_props = frame_text_style.find({"style:text-properties"}, recursive=False)
     frame_para_props = frame_text_style.find({"style:paragraph-properties"}, recursive=False)
     for prop in TEXT_PROPS:
         if frame_text_props and prop in frame_text_props.attrs:
             frame[prop] = frame_text_props[prop]
         elif parent_text_props and prop in parent_text_props.attrs:
             frame[prop] = parent_text_props[prop]
     for prop in PARA_PROPS:
         if frame_para_props and prop in frame_para_props.attrs:
             frame[prop] = frame_para_props[prop]
         elif parent_para_props and prop in parent_para_props.attrs:
             frame[prop] = parent_para_props[prop]
     for prop in PARA_PROPS_U:
         if frame_para_props and prop in frame_para_props.attrs:
             frame[prop] = units_to_float(frame_para_props[prop])
         elif parent_para_props and prop in parent_para_props.attrs:
             frame[prop] = units_to_float(parent_para_props[prop])
Example #2
0
 def parse_page_animations(self, timing_root, json_data, page_json_data):
     main_seq = timing_root.find("anim:seq")
     click_anims = main_seq.findChildren({"anim:par"}, recursive=False)
     # Keep track of initially hidden and visible elements that have associated animations
     init_visible, init_hidden = [], []
     for click_anim in click_anims:
         timed_anims = click_anim.findChildren({"anim:par"}, recursive=False)
         first_timed_anim = None
         begin_at = "indefinite" # First node is initiated with a click
         anim_json_data = {}
         anim_order = {}
         for timed_anim in timed_anims:
             parallel_anims = timed_anim.findChildren({"anim:par"}, recursive=False)
             begin_delay = units_to_float(timed_anim["smil:begin"])
             for anim_data in parallel_anims:
                 # Find animation target
                 anim_subnode = anim_data.findChild()
                 anim_target = "obj_" + anim_subnode["smil:targetelement"]
                 anim_preset = anim_data["presentation:preset-id"]
                 anim_delay = begin_delay + units_to_float(anim_data["smil:begin"])
                 if anim_target in self.xml_ids:
                     if first_timed_anim:
                         begin_at = first_timed_anim + ".begin+" + str(anim_delay) + "s"
                     anim_id = self.animator.add_animation(\
                         self, self.xml_ids[anim_target], anim_data, begin_at)
                     if anim_id:
                         if not first_timed_anim:
                             first_timed_anim = anim_id
                             anim_json_data["id"] = anim_id
                         if anim_preset.find("ooo-entrance") == -1\
                             and anim_target not in init_hidden:
                             init_visible.append(anim_target)
                         elif anim_target not in init_visible and anim_target not in init_hidden:
                             init_hidden.append(anim_target)
                         # Track order of sub-animations
                         if anim_delay in anim_order:
                             anim_order[anim_delay].append(anim_id)
                         else:
                             anim_order[anim_delay] = [anim_id]
         anim_json_data["anim_order"] = []
         for time_index in sorted(anim_order.keys()):
             anim_json_data["anim_order"] += anim_order[time_index]
         if "id" in anim_json_data:
             # Condition prevents "empty" animations begin added (those that refer to
             # un-implemented types of animation)
             page_json_data["animations"].append(anim_json_data)
     page_json_data["init_hidden"] = init_hidden
     page_json_data["init_visible"] = init_visible
Example #3
0
 def fill_hatch(cls, dwg, elt, pres, attrs, e_width, e_height, style_tag):
     hatch_node = pres.styles.find("office:styles")\
         .find({"draw:hatch"}, {"draw:name": attrs["draw:fill-hatch-name"]})
     hatch_angle = int(hatch_node["draw:rotation"]) / 10
     hatch_dist = units_to_float(str(hatch_node["draw:distance"]))
     # Print background color first
     pattern = dwg.pattern(insert=(0, 0), size=(e_width, e_height), \
             patternUnits="userSpaceOnUse", patternContentUnits="userSpaceOnUse")
     if style_tag.find(
             "style:graphic-properties")["draw:fill-hatch-solid"] == "true":
         pattern.add(dwg.rect((0, 0), (e_width, e_height),\
             fill=attrs["draw:fill-color"]))
     else:
         pattern.add(dwg.rect((0, 0), (e_width, e_height), fill='#ffffff'))
     # Single line hatch
     FillFactory.draw_hatching(dwg, pattern, hatch_angle, hatch_dist,\
         hatch_node["draw:color"], 1/DPCM, e_width, e_height)
     # Double line hatch
     if hatch_node["draw:style"] in ["double", "triple"]:
         FillFactory.draw_hatching(dwg, pattern, hatch_angle + 90, hatch_dist,\
             hatch_node["draw:color"], 1/DPCM, e_width, e_height)
     # Triple line hatch
     if hatch_node["draw:style"] == "triple":
         FillFactory.draw_hatching(dwg, pattern, hatch_angle + 135, hatch_dist,\
             hatch_node["draw:color"], 1/DPCM, e_width, e_height)
     dwg.defs.add(pattern)
     elt.fill(pattern.get_paint_server())
Example #4
0
 def parse_line(self, item, layer_g, style_src):
     line_x1 = units_to_float(item.attrs["svg:x1"])
     line_y1 = units_to_float(item.attrs["svg:y1"])
     line_x2 = units_to_float(item.attrs["svg:x2"])
     line_y2 = units_to_float(item.attrs["svg:y2"])
     line = self.dwg.line(start=(line_x1, line_y1), end=(line_x2, line_y2))
     StrokeFactory.stroke(self, self.dwg, item, line, 1, style_src)
     if "xml:id" in item.attrs:
         self.xml_ids["obj_"+item["xml:id"]] = {
             "item": line,
             "x": min(line_x1, line_x2),
             "y": min(line_y1, line_y2),
             "width": abs(line_x2 - line_x1),
             "height": abs(line_y2 - line_y1)
         }
         line.__setitem__("id", "obj_"+item["xml:id"])
     layer_g.add(line)
Example #5
0
    def parse_polygon(self, item, layer_g, style_src):
        polygon_w = units_to_float(item.attrs["svg:width"])
        polygon_h = units_to_float(item.attrs["svg:height"])
        polygon_vb = [int(x) for x in item.attrs["svg:viewbox"].split()]
        # TODO: Check assumption - this is always scaled equally on both axes
        polygon_pts = item.attrs["draw:points"].replace(',', ' ').split()
        polygon_d = "M " + ' '.join(polygon_pts[0:2]) + " L " + \
            ' '.join(polygon_pts[2:]) + ' Z'
        # TODO: Add in fill
        polygon = self.dwg.path(d=polygon_d, fill='none')
        if "svg:x" in item.attrs and "svg:y" in item.attrs:
            polygon.translate(units_to_float(item.attrs["svg:x"]),\
                units_to_float(item.attrs["svg:y"]))
        elif "draw:transform" in item.attrs:
            ShapeParser.transform_shape(item, polygon)

        polygon_scale = polygon_w / (polygon_vb[2]-polygon_vb[0])
        polygon.scale(polygon_scale)
        style_tag = style_src.find("office:automatic-styles")\
            .find({"style:style"}, {"style:name": item["draw:style-name"]})
        attrs = style_tag.find("style:graphic-properties").attrs
        FillFactory.fill(self.dwg, polygon, self, attrs, \
            polygon_w/polygon_scale, polygon_h/polygon_scale, style_tag)
        StrokeFactory.stroke(self, self.dwg, item, polygon, 1/polygon_scale, style_src)
        if "xml:id" in item.attrs:
            self.xml_ids["obj_"+item["xml:id"]] = {
                "item": polygon,
                "x": units_to_float(item.attrs["svg:x"]),
                "y": units_to_float(item.attrs["svg:y"]),
                "width": polygon_w,
                "height": polygon_h
            }
            polygon.__setitem__("id", "obj_"+item["xml:id"])
        layer_g.add(polygon)
Example #6
0
    def parse_path(self, item, layer_g, style_src):
        path_w = units_to_float(item.attrs["svg:width"])
        path_vb = [int(x) for x in item.attrs["svg:viewbox"].split()]
        # TODO: Check assumption - this is always scaled equally on both axes
        path = self.dwg.path(d=item.attrs["svg:d"], fill='none')
        if "svg:x" in item.attrs and "svg:y" in item.attrs:
            path.translate(units_to_float(item.attrs["svg:x"]),\
                units_to_float(item.attrs["svg:y"]))
        elif "draw:transform" in item.attrs:
            ShapeParser.transform_shape(item, path)

        path_scale = path_w / (path_vb[2]-path_vb[0])
        path.scale(path_scale)
        # TODO: See how dashed paths and end markers for paths work
        StrokeFactory.stroke(self, self.dwg, item, path, 1/path_scale, style_src)
        if "xml:id" in item.attrs:
            # TODO: Configure this to work with draw:transforms
            self.xml_ids["obj_"+item["xml:id"]] = {
                "item": path,
                "x": units_to_float(item.attrs["svg:x"]),
                "y": units_to_float(item.attrs["svg:y"]),
                "width": path_w,
                "height": units_to_float(item.attrs["svg:width"])
            }
            path.__setitem__("id", "obj_"+item["xml:id"])
        layer_g.add(path)
Example #7
0
    def __init__(self, url, data_store):
        self.url = url
        self.data_store = data_store
        pres_archive = zipfile.ZipFile(url, 'r')
        self.styles = BeautifulSoup(pres_archive.read('styles.xml'), \
            "lxml", from_encoding='UTF-8')
        self.content = BeautifulSoup(pres_archive.read('content.xml'), \
            "lxml", from_encoding='UTF-8')
        self.font_mgr = font_manager.FontManager()
        self.animator = AnimationFactory()
        self.xml_ids = {}

        # Create SVG drawing of correct size
        drawing_size = self.get_document_size()
        self.d_width = units_to_float(str(drawing_size[0]))
        self.d_height = units_to_float(str(drawing_size[1]))
        self.view_box = '0 0 ' + str(self.d_width) + ' ' + str(self.d_height)
        self.dwg = svgwrite.Drawing(size=drawing_size, viewBox=(self.view_box))

        # Setup iterative variables
        self.clip_id = 0
        self.sub_g = 0
Example #8
0
    def parse_polyline(self, item, layer_g, style_src):
        polyline_w = units_to_float(item.attrs["svg:width"])
        polyline_vb = [int(x) for x in item.attrs["svg:viewbox"].split()]
        # TODO: Check assumption - this is always scaled equally on both axes
        polyline_pts = item.attrs["draw:points"].replace(',', ' ').split()
        polyline_d = "M " + ' '.join(polyline_pts[0:2]) + " L " + ' '.join(polyline_pts[2:])
        polyline = self.dwg.path(d=polyline_d, fill='none')
        if "draw:transform" in item.attrs:
            ShapeParser.transform_shape(item, polyline)

        polyline_scale = polyline_w / (polyline_vb[2]-polyline_vb[0])
        polyline.scale(polyline_scale)
        # TODO: See how dashed polylines and end markers for polylines work
        StrokeFactory.stroke(self, self.dwg, item, polyline, 1/polyline_scale, style_src)
        if "xml:id" in item.attrs:
            self.xml_ids["obj_"+item["xml:id"]] = {
                "item": polyline,
                "x": units_to_float(item.attrs["svg:x"]),
                "y": units_to_float(item.attrs["svg:y"]),
                "width": polyline_w,
                "height": units_to_float(item.attrs["svg:height"])
            }
            polyline.__setitem__("id", "obj_"+item["xml:id"])
        layer_g.add(polyline)
Example #9
0
 def populate_stack_frame(self, frame, style_name):
     style_tag = self.style_src.find("office:automatic-styles")\
         .find({"style:style"}, {"style:name": style_name})
     style_text_props = style_tag.find({"style:text-properties"}, recursive=False)
     if style_text_props:
         for prop in TEXT_PROPS:
             if prop in style_text_props.attrs:
                 frame[prop] = style_text_props[prop]
     style_para_props = style_tag.find({"style:paragraph-properties"}, recursive=False)
     if style_para_props:
         for prop in PARA_PROPS:
             if prop in style_para_props.attrs:
                 frame[prop] = style_para_props[prop]
         for prop in PARA_PROPS_U:
             if prop in style_para_props.attrs:
                 frame[prop] = units_to_float(style_para_props[prop])
Example #10
0
    def fill_bitmap(cls, dwg, elt, pres, attrs, e_width, e_height):
        image_node = pres.styles.find("office:styles")\
            .find({"draw:fill-image"}, {"draw:name": attrs["draw:fill-image-name"]})
        # Extract image to data store
        pres_archive = zipfile.ZipFile(pres.url, 'r')
        pres_archive.extract(image_node["xlink:href"], pres.data_store)
        if "draw:fill-image-width" in attrs:
            if attrs["draw:fill-image-width"][-1] == "%":
                bitmap_width = e_width * (
                    int(attrs["draw:fill-image-width"][:-1]) / 100)
                bitmap_height = e_height * (
                    int(attrs["draw:fill-image-height"][:-1]) / 100)
            else:
                bitmap_width = units_to_float(
                    str(attrs["draw:fill-image-width"]))
                bitmap_height = units_to_float(
                    str(attrs["draw:fill-image-height"]))
            if bitmap_width > 0 and bitmap_height > 0:
                pattern_size = (bitmap_width, bitmap_height)
            else:
                with Image.open(pres.data_store +
                                image_node["xlink:href"]) as img:
                    im_width, im_height = img.size
                pattern_size = (im_width / DPCM, im_height / DPCM)

        if attrs["style:repeat"] == "stretch":
            pattern = dwg.pattern(insert=(0, 0), size=(e_width, e_height), \
                patternUnits="userSpaceOnUse", patternContentUnits="userSpaceOnUse")
            pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                    insert=(0, 0), size=(e_width, e_height), preserveAspectRatio="none"))
            dwg.defs.add(pattern)
            elt.fill(pattern.get_paint_server())

        elif attrs["style:repeat"] == "no-repeat":
            if attrs["draw:fill-image-ref-point"] in [
                    "top-left", "left", "bottom-left"
            ]:
                image_x = 0
            elif attrs["draw:fill-image-ref-point"] in [
                    "top", "center", "bottom"
            ]:
                image_x = (e_width - bitmap_width) / 2
            else:
                image_x = e_width - bitmap_width
            if attrs["draw:fill-image-ref-point"] in [
                    "top-left", "top", "top-right"
            ]:
                image_y = 0
            elif attrs["draw:fill-image-ref-point"] in [
                    "left", "center", "right"
            ]:
                image_y = (e_height - bitmap_height) / 2
            else:
                image_y = e_height - bitmap_height

            pattern = dwg.pattern(insert=(0, 0), size=(e_width, e_height), \
                patternUnits="userSpaceOnUse", patternContentUnits="userSpaceOnUse")
            pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                insert=(image_x, image_y), size=(bitmap_width, bitmap_height), \
                preserveAspectRatio="none"))
            dwg.defs.add(pattern)
            elt.fill(pattern.get_paint_server())

        else:  # Tiled background
            # Adjust pattern start point based on reference point
            if attrs["draw:fill-image-ref-point"] in [
                    "top", "center", "bottom"
            ]:
                x_base = (((e_width - pattern_size[0]) / 2) %
                          pattern_size[0]) / pattern_size[0]
                col_count_parity = (((e_width - pattern_size[0]) / 2) //
                                    pattern_size[0] % 2)
                if col_count_parity == 0:
                    tile_col_offset = [1, 0, 1]
                else:
                    tile_col_offset = [0, 1, 0]
            elif attrs["draw:fill-image-ref-point"] in [
                    "top-right", "right", "bottom-right"
            ]:
                x_base = (e_width % pattern_size[0]) / pattern_size[0]
                col_count_parity = (e_width // pattern_size[0]) % 2
                if col_count_parity == 0:
                    tile_col_offset = [0, 1, 0]
                else:
                    tile_col_offset = [1, 0, 1]
            else:  # top-left, center-left, bottom-left
                x_base = 0.0
                tile_col_offset = [
                    1, 0, 1
                ]  # First full column (index 1) is not offset
            if attrs["draw:fill-image-ref-point"] in [
                    "left", "center", "right"
            ]:
                y_base = (((e_height - pattern_size[1]) / 2) %
                          pattern_size[1]) / pattern_size[1]
                row_count_parity = (((e_height - pattern_size[1]) / 2) //
                                    pattern_size[1] % 2)
                if row_count_parity == 0:
                    tile_row_offset = [1, 0, 1]
                else:
                    tile_row_offset = [0, 1, 0]
            elif attrs["draw:fill-image-ref-point"] in [
                    "bottom-left", "bottom", "bottom-right"
            ]:
                y_base = (e_height % pattern_size[1]) / pattern_size[1]
                row_count_parity = (e_height // pattern_size[1]) % 2
                if row_count_parity == 0:
                    tile_row_offset = [0, 1, 0]
                else:
                    tile_row_offset = [1, 0, 1]
            else:
                y_base = 0.0
                tile_row_offset = [1, 0,
                                   1]  # First full row (index 1) is not offset
            # Adjust pattern offset
            x_offset = x_base + (
                (int(attrs["draw:fill-image-ref-point-x"][:-1]) % 100)) / 100
            if x_offset >= 1:
                x_offset -= 1
                if tile_col_offset == [0, 1, 0]:
                    tile_col_offset = [1, 0, 1]
                else:
                    tile_col_offset = [0, 1, 0]
            y_offset = y_base + (
                (int(attrs["draw:fill-image-ref-point-y"][:-1]) % 100)) / 100
            if y_offset >= 1:
                y_offset -= 1
                if tile_row_offset == [0, 1, 0]:
                    tile_row_offset = [1, 0, 1]
                else:
                    tile_row_offset = [0, 1, 0]

            # Determine if image has row/col offset or not
            offset_type = attrs["draw:tile-repeat-offset"].split(" ")
            if offset_type[0] == "0%":
                pattern = dwg.pattern(insert=(0, 0), size=pattern_size, \
                    patternUnits="userSpaceOnUse", patternContentUnits="userSpaceOnUse")
                pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                    insert=(x_offset*pattern_size[0], y_offset*pattern_size[1]),\
                    size=pattern_size, preserveAspectRatio="none"))
                pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                    insert=((x_offset-1)*pattern_size[0], y_offset*pattern_size[1]),\
                    size=pattern_size, preserveAspectRatio="none"))
                pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                    insert=(x_offset*pattern_size[0], (y_offset-1)*pattern_size[1]),\
                    size=pattern_size, preserveAspectRatio="none"))
                pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                    insert=((x_offset-1)*pattern_size[0], (y_offset-1)*pattern_size[1]),\
                    size=pattern_size, preserveAspectRatio="none"))
            else:
                tiled_offset = (int(offset_type[0][:-1]) % 100) / 100
                if offset_type[1] == "horizontal":
                    # Horizontal tiling - make fill 1 wide x 2 high
                    pattern = dwg.pattern(insert=(0, 0), size=(pattern_size[0], 2*pattern_size[1]),\
                        patternUnits="userSpaceOnUse", patternContentUnits="userSpaceOnUse")
                    if tiled_offset + x_offset >= 1:
                        tiled_offset -= 1
                    # Top row
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset + tiled_offset*tile_row_offset[0]) * pattern_size[0],\
                            (y_offset-1)*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset-1 + tiled_offset*tile_row_offset[0]) * pattern_size[0],\
                            (y_offset-1)*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    # Centre row
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset + tiled_offset*tile_row_offset[1]) * pattern_size[0],\
                            y_offset*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset-1 + tiled_offset*tile_row_offset[1]) * pattern_size[0],\
                            y_offset*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    # Bottom row
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset + tiled_offset*tile_row_offset[2]) * pattern_size[0],\
                            (y_offset+1)*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset-1 + tiled_offset*tile_row_offset[2]) * pattern_size[0],\
                            (y_offset+1)*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                else:
                    # Vertical tiling - make fill 2 wide x 1 high
                    pattern = dwg.pattern(insert=(0, 0), size=(2*pattern_size[0], pattern_size[1]),\
                        patternUnits="userSpaceOnUse", patternContentUnits="userSpaceOnUse")
                    if tiled_offset + y_offset >= 1:
                        tiled_offset -= 1
                    # Left column
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset-1) * pattern_size[0],\
                            (y_offset-1 + tiled_offset*tile_col_offset[0]) * pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset-1) * pattern_size[0],\
                            (y_offset + tiled_offset*tile_col_offset[0])*pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    # Centre column
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=(x_offset * pattern_size[0],\
                            (y_offset-1 + tiled_offset*tile_col_offset[1]) * pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=(x_offset * pattern_size[0],\
                            (y_offset + tiled_offset*tile_col_offset[1]) * pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    # Right column
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset+1) * pattern_size[0],\
                            (y_offset-1 + tiled_offset*tile_col_offset[2]) * pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
                    pattern.add(dwg.image(pres.data_store + image_node["xlink:href"],\
                        insert=((x_offset+1) * pattern_size[0],\
                            (y_offset + tiled_offset*tile_col_offset[2]) * pattern_size[1]),\
                        size=pattern_size, preserveAspectRatio="none"))
            dwg.defs.add(pattern)
            elt.fill(pattern.get_paint_server())
Example #11
0
    def parse_frame(self, item, layer_g, style_src):
        # TODO: Work out how to store xml:ids for images with and without borders and text areas
        # with and without borders...
        print("parse frame")
        frame_attrs = item.attrs

        if "presentation:placeholder" in frame_attrs \
            and frame_attrs["presentation:placeholder"] == "true":
            print("Frame is a placeholder - skip!")
            return

        if "draw:transform" in frame_attrs:
            frame_x, frame_y = 0, 0
        else:
            frame_x = units_to_float(frame_attrs["svg:x"])
            frame_y = units_to_float(frame_attrs["svg:y"])

        frame_w = units_to_float(frame_attrs["svg:width"])
        frame_h = units_to_float(frame_attrs["svg:height"])
        # TODO: draw:frame might contain something other than an image...
        clip_area = None
        if "draw:style-name" in frame_attrs:
            style_tag = style_src.find("office:automatic-styles")\
                .find({"style:style"}, {"style:name": frame_attrs["draw:style-name"]})\
                .find("style:graphic-properties")
            if style_tag and "fo:clip" in style_tag.attrs:
                clip_area = style_tag["fo:clip"]
        if item.find("draw:image"):
            print("add image")
            image_href = item.find("draw:image").attrs["xlink:href"]
            # Extract image to data store
            pres_archive = zipfile.ZipFile(self.url, 'r')
            pres_archive.extract(image_href, self.data_store)
            if clip_area and clip_area[0:4] == "rect":
                clip = [units_to_float(x) for x in clip_area[5:-1].split(", ")]
                clip_path = self.dwg.defs.add(\
                    self.dwg.clipPath(id="clip" + str(self.clip_id)))
                clip_path.add(self.dwg.rect(
                    insert=(frame_x, frame_y), size=(frame_w, frame_h)))
                img_px = Image.open(self.data_store + image_href).size
                img_w = frame_w * img_px[0] / (img_px[0] - DPCM * (clip[1] + clip[3]))
                img_h = frame_h * img_px[1] / (img_px[1] - DPCM * (clip[0] + clip[2]))
                img_x = frame_x - \
                    (frame_w * DPCM * clip[3] / (img_px[0] - DPCM * (clip[1] + clip[3])))
                img_y = frame_y - \
                    (frame_h * DPCM * clip[0] / (img_px[1] - DPCM * (clip[0] + clip[2])))
                clip_img = self.dwg.image(self.data_store + image_href,\
                    insert=(img_x, img_y), size=(img_w, img_h),\
                    preserveAspectRatio="none",\
                    clip_path="url(#clip" + str(self.clip_id) + ")")
                layer_g.add(clip_img)
                self.clip_id += 1
            else:
                clip_img = self.dwg.image(self.data_store + image_href,\
                    insert=(frame_x, frame_y), size=(frame_w, frame_h),\
                    preserveAspectRatio="none")
                layer_g.add(clip_img)
            frame = self.dwg.rect(insert=(frame_x, frame_y), \
                size=(frame_w, frame_h), fill='none')
            StrokeFactory.stroke(self, self.dwg, item, frame, 1, style_src)
            if "draw:transform" in frame_attrs:
                ShapeParser.transform_shape(item, clip_img)
                ShapeParser.transform_shape(item, frame)
            layer_g.add(frame)
        elif item.find("draw:text-box"):
            tb_rect = self.dwg.rect(insert=(frame_x, frame_y), size=(frame_w, frame_h))
            vert_align = "middle"
            if "presentation:style-name" in frame_attrs:
                tb_style_name = frame_attrs["presentation:style-name"]
            elif "draw:style-name" in frame_attrs:
                tb_style_name = frame_attrs["draw:style-name"]
            else:
                tb_style_name = None
            if tb_style_name:
                frame_style = style_src.find("office:automatic-styles")\
                    .find({"style:style"}, {"style:name": tb_style_name})
                frame_graphics = frame_style.find({"style:graphic-properties"})
                FillFactory.fill(self.dwg, tb_rect, self, frame_attrs, frame_w, frame_h, \
                    frame_style)
                layer_g.add(tb_rect)
                if "draw:textarea-vertical-align" in frame_graphics.attrs:
                    vert_align = frame_graphics["draw:textarea-vertical-align"]
            tb_parser = TextBoxParser(self.dwg, self, item, self.font_mgr, vert_align, style_src)
            tb_parser.visit_textbox(layer_g, "textbox")
Example #12
0
    def visit_p(self, item_p, is_in_list):
        # TODO: Underline, overline - final few variations
        p_stack_frame = self.style_stack[len(self.style_stack) - 1].copy() # shallow copy
        if "text:style-name" in item_p.attrs:
            self.populate_stack_frame(p_stack_frame, item_p["text:style-name"])
        self.style_stack.append(p_stack_frame)

        if is_in_list:
            self.style_stack[-1]["fo:margin-left"] = self.style_stack[-2]["fo:margin-left"]

        # Add on before spacing to cur_y
        self.cur_y += p_stack_frame["fo:margin-top"]

        p_height = 0
        first_span = None
        line_size, line_indent = 0, 0
        queued_spans = []
        on_first_line = True
        first_descent = 0

        highlights = []
        decor_lines = []

        for item_span in item_p.find_all({"text:span"}, recursive=False):
            span_start = 0
            span_pos = 0
            span_stack_frame = self.style_stack[len(self.style_stack) - 1].copy() # shallow copy
            if "text:style-name" in item_span.attrs:
                self.populate_stack_frame(span_stack_frame, item_span["text:style-name"])
            self.style_stack.append(span_stack_frame)

            span_font = self.font_mgr.findfont(FontProperties(\
                family=span_stack_frame["style:font-name"],
                style=span_stack_frame["fo:font-style"],
                weight=span_stack_frame["fo:font-weight"]))

            # Text position is either "normal" or a baseline adjustment e.g. "33%" or
            # a baseline adjustment and font size adjustment e.g. "-33% 58%"
            # Font size only changes in the final of the three cases
            if len(span_stack_frame["style:text-position"].split()) == 1:
                base_font_size = units_to_float(span_stack_frame["fo:font-size"])
            else:
                bf_scale = units_to_float(span_stack_frame["style:text-position"].split()[1]) / 100
                base_font_size = units_to_float(span_stack_frame["fo:font-size"]) * bf_scale

            scaled_font_size = str(base_font_size/DPCM)

            i_font = ImageFont.truetype(span_font, math.ceil(base_font_size*96/72))
            i_font_height = i_font.font.ascent + i_font.font.descent

            # Replace all <text:s></text:s> tags with spaces
            for spacer in item_span.find_all({"text:s"}):
                spacer.replace_with(" ")
            # Replace all <text:tab></text:tab> tags with literal tabs
            for tab in item_span.find_all({"text:tab"}):
                tab.replace_with("\t")
            for line_break in item_span.find_all({"text:line-break"}):
                line_break.replace_with("\n")
            for fields in item_span.find_all({"presentation:date-time", "presentation:footer",\
                "text:page-number"}):
                # TODO: Decide whether/how to implement these properly...
                fields.replace_with("")

            print("++" + str(item_span.contents) + "++")
            span_text = ''.join(item_span.contents)
            # Cope with line break span
            if span_text == "\n":
                line_h, line_d, tspan, h_lights, d_lines = self.process_line(queued_spans, \
                    [line_indent, span_stack_frame["fo:margin-right"]], \
                    span_stack_frame["fo:line-height"])
                queued_spans.clear()
                line_size = 0
                p_height += line_h
                if on_first_line:
                    on_first_line = False
                    first_span = tspan
                    first_descent = line_d
            # Cope with empty or whitespace-only spans (tabs and/or spaces)
            elif span_text.strip() == "":
                if span_text == "":
                    span_w = 0
                else:
                    span_w = i_font.getsize(span_text)[0]
                queued_spans.append({\
                    "style": self.output_tspan_style(span_stack_frame, scaled_font_size),
                    "height": i_font_height/DPCM,
                    "descent": i_font.font.descent/DPCM,
                    "ascent": i_font.font.ascent/DPCM,
                    "width": span_w,
                    "font-size": base_font_size,
                    "text": span_text,
                    "stack-frame": span_stack_frame.copy()})
                span_text = "" # Ensure that while loop doesn't run

            while span_pos != -1:
                h_lights, d_lines = [], []
                # Recalculate available width to take indents into account
                if on_first_line:
                    line_indent = span_stack_frame["fo:margin-left"] + \
                        span_stack_frame["fo:text-indent"]
                else:
                    line_indent = span_stack_frame["fo:margin-left"]
                line_width = (self.svg_w - line_indent - span_stack_frame["fo:margin-right"])*DPCM
                # Find next word break
                next_pos = span_text.find(" ", span_pos+1)
                if next_pos != -1 and span_text[span_start:next_pos].strip() != "":
                    size = i_font.getsize(span_text[span_start:next_pos])
                    if size[0] + line_size > line_width:
                        # Write out span up to span_pos then start new span on next line
                        queued_spans.append({\
                            "style": self.output_tspan_style(span_stack_frame, scaled_font_size),
                            "height": i_font_height/DPCM,
                            "descent": i_font.font.descent/DPCM,
                            "ascent": i_font.font.ascent/DPCM,
                            "width": i_font.getsize(span_text[span_start:span_pos].rstrip())[0],
                            "font-size": base_font_size,
                            "text": span_text[span_start:span_pos].rstrip(),
                            "stack-frame": span_stack_frame.copy()})
                        line_h, line_d, tspan, h_lights, d_lines = self.process_line(queued_spans, \
                            [line_indent, span_stack_frame["fo:margin-right"]], \
                            span_stack_frame["fo:line-height"])
                        queued_spans.clear()
                        span_start = span_pos
                        line_size = 0
                        p_height += line_h
                        if on_first_line:
                            on_first_line = False
                            first_span = tspan
                            first_descent = line_d
                elif next_pos == -1 and span_text[span_start:].strip() != "":
                    # End of span_text has been reached
                    size = i_font.getsize(span_text[span_start:])
                    if size[0] + line_size > line_width:
                        # Write out span up to span_pos then start new span on next line
                        if span_text[span_start:span_pos] != "":
                            queued_spans.append({\
                                "style": self.output_tspan_style(span_stack_frame, \
                                    scaled_font_size),
                                "height": i_font_height/DPCM,
                                "descent": i_font.font.descent/DPCM,
                                "ascent": i_font.font.ascent/DPCM,
                                "width": i_font.getsize(span_text[span_start:span_pos]\
                                    .rstrip())[0],
                                "font-size": base_font_size,
                                "text": span_text[span_start:span_pos].rstrip(),
                                "stack-frame": span_stack_frame.copy()})
                        line_h, line_d, tspan, h_lights, d_lines = self.process_line(queued_spans, \
                            [line_indent, span_stack_frame["fo:margin-right"]], \
                            span_stack_frame["fo:line-height"])
                        queued_spans.clear()
                        # Queue remainder of span
                        queued_spans.append({\
                            "style": self.output_tspan_style(span_stack_frame, scaled_font_size),
                            "height": i_font_height/DPCM,
                            "descent": i_font.font.descent/DPCM,
                            "ascent": i_font.font.ascent/DPCM,
                            "width": i_font.getsize(span_text[span_pos:])[0],
                            "font-size": base_font_size,
                            "text": span_text[span_pos:],
                            "stack-frame": span_stack_frame.copy()})
                        line_size = i_font.getsize(span_text[span_pos:])[0]
                        span_start = span_pos
                        p_height += line_h
                        if on_first_line:
                            first_span = tspan
                            on_first_line = False
                            first_descent = line_d
                    else:
                        # Just queue rest of current span
                        queued_spans.append({\
                            "style": self.output_tspan_style(span_stack_frame, scaled_font_size),
                            "height": i_font_height/DPCM,
                            "descent": i_font.font.descent/DPCM,
                            "ascent": i_font.font.ascent/DPCM,
                            "width": i_font.getsize(span_text[span_start:])[0],
                            "font-size": base_font_size,
                            "text": span_text[span_start:],
                            "stack-frame": span_stack_frame.copy()})
                        line_size += size[0]
                span_pos = next_pos
                # Process highlights and decor_lines
                for hl in h_lights:
                    highlights.append(hl)
                for dl in d_lines:
                    decor_lines.append(dl)

            # Pop stack frame (span)
            self.style_stack.pop()

        # Write out any queued spans before going on to next p
        h_lights, d_lines = [], []
        if queued_spans:
            line_h, line_d, tspan, h_lights, d_lines = self.process_line(queued_spans, \
                [line_indent, span_stack_frame["fo:margin-right"]], \
                span_stack_frame["fo:line-height"])
            queued_spans.clear()
            p_height += line_h
            if on_first_line:
                first_span = tspan
                first_descent = line_d
        # Process highlights and decor_lines
            for hl in h_lights:
                highlights.append(hl)
            for dl in d_lines:
                decor_lines.append(dl)

        # Add before spacing to first span of paragraph, increase p_height accordingly
        if first_span:
            first_dy = first_span.__getitem__("dy") + p_stack_frame["fo:margin-top"]
            first_span.__setitem__("dy", first_dy)
            p_height += p_stack_frame["fo:margin-top"]

        # Add after spacing to cur_y
        self.cur_y += p_stack_frame["fo:margin-bottom"]

        # Pop stack frame (p)
        self.style_stack.pop()
        return p_height, first_descent, first_span, p_stack_frame["fo:margin-bottom"], \
            highlights, decor_lines
Example #13
0
    def render_shape(cls, dwg, pres, shape, layer, style_src):
        # TODO: cope with draw:transform for text boxes
        geom = shape.find("draw:enhanced-geometry")
        if "svg:viewbox" in geom.attrs:
            vb_bounds = [int(x) for x in geom["svg:viewbox"].split()]
        else:
            vb_bounds = [0, 0, 21600, 21600]
        if "svg:x" in shape.attrs:
            base_x = units_to_float(str(shape["svg:x"]))
        else:
            base_x = 0.0
        if "svg:y" in shape.attrs:
            base_y = units_to_float(str(shape["svg:y"]))
        else:
            base_y = 0.0
        bases = [base_x, base_y]
        scale_x = units_to_float(str(shape["svg:width"])) / \
            (vb_bounds[2] - vb_bounds[0])
        scale_y = units_to_float(str(shape["svg:height"])) / \
            (vb_bounds[3] - vb_bounds[1])
        scales = [scale_x, scale_y]
        if "draw:mirror-horizontal" in geom.attrs and \
            geom.attrs["draw:mirror-horizontal"] == "true":
            x_adj = vb_bounds[2] - vb_bounds[0]
        else:
            x_adj = 0
        if "draw:mirror-vertical" in geom.attrs and geom.attrs[
                "draw:mirror-vertical"] == "true":
            y_adj = vb_bounds[3] - vb_bounds[1]
        else:
            y_adj = 0
        adjs = [x_adj, y_adj]

        # Step 0 - perform substitutions
        modifiers, eq_results = ShapeParser.evaluate_equations(geom, vb_bounds)

        # Step 1 - split string into command sections
        path_sections = re.findall(r'[a-zA-Z][?\$f0-9 -.]*',
                                   geom["draw:enhanced-path"])

        # Step 2 - translate sections from ODP grammar to SVG equivalent
        shape_path = dwg.path()

        path_start_x, path_start_y = 0, 0
        cur_x, cur_y = 0, 0

        for section in path_sections:
            # Replace any variables in section
            section_groups = section.split(" ")
            for idx, group in enumerate(section_groups):
                if group and group[0] == "$":
                    section_groups[idx] = str(
                        eval("modifiers[" + group[1:] + "]"))
                elif group and group[0] == "?":
                    section_groups[idx] = str(
                        eval("eq_results[" + group[2:] + "]"))
            section = ' '.join(section_groups)
            # print(section)

            # Process section
            if section[0] in ["Z"]:
                cur_x, cur_y = ShapeParser.closepath\
                    (section, shape_path, path_start_x, path_start_y)
            elif section[0] in ["L", "M"]:
                cur_x, cur_y, path_start_x, path_start_y = ShapeParser.draw_linemove\
                    (section, shape_path, path_start_x, path_start_y, scales, bases, adjs)
            elif section[0] in ["C"]:
                cur_x, cur_y = ShapeParser.draw_curve(section, shape_path,
                                                      scales, bases, adjs)
            elif section[0] in ["N"]:
                #  N = endpath
                print("N not supported")
            elif section[0] in ["F", "S"]:
                #  F = nofill, S = nostroke
                print("FS not supported")
            elif section[0] in ["Q"]:
                # TODO: Q
                #  Q = quadratic-curveto    (x1 y1 x y)+                   Q (x1 y1 x y)+
                print("Q not yet supported")
            elif section[0] in ["A", "B", "V", "W"]:
                cur_x, cur_y, path_start_x, path_start_y = ShapeParser.draw_arc\
                    (section, shape_path, path_start_x, path_start_y, scales, bases, adjs)
            elif section[0] in ["T", "U"]:
                cur_x, cur_y, path_start_x, path_start_y = ShapeParser.draw_ellipse_seg\
                    (section, shape_path, path_start_x, path_start_y, scales, bases, adjs)
            elif section[0] in ["X", "Y"]:
                cur_x, cur_y = ShapeParser.draw_quadrant\
                    (section, shape_path, cur_x, cur_y, scales, bases, adjs)
            else:
                print("Unrecognised command: " + section)

        # Apply stroke and fill
        StrokeFactory.stroke(pres, dwg, shape, shape_path, 1, style_src)
        style_tag = style_src.find("office:automatic-styles")\
            .find({"style:style"}, {"style:name": shape["draw:style-name"]})
        attrs = style_tag.find("style:graphic-properties").attrs
        FillFactory.fill(dwg, shape_path, pres, attrs, \
            units_to_float(str(shape["svg:width"])), \
            units_to_float(str(shape["svg:height"])), style_tag)

        # Apply transformation to shape if needed
        if "draw:transform" in shape.attrs:
            ShapeParser.transform_shape(shape, shape_path)

        # Store xml:id, if it exists
        # TODO: Do we need to group the overlaid text with this...?
        if "xml:id" in shape.attrs:
            pres.xml_ids["obj_" + shape["xml:id"]] = {
                "item": shape_path,
                "x": base_x,
                "y": base_y,
                "width": units_to_float(shape["svg:width"]),
                "height": units_to_float(shape["svg:height"])
            }
            shape_path.__setitem__("id", "obj_" + shape["xml:id"])

        # Add custom shape to main drawing
        layer.add(shape_path)

        # Overlay any text
        vert_align = "middle"
        if "draw:style-name" in shape.attrs:
            tb_style_name = shape["draw:style-name"]
            tb_style = style_src.find("office:automatic-styles")\
                .find({"style:style"}, {"style:name": tb_style_name})
            tb_graphics = tb_style.find({"style:graphic-properties"})
            if "draw:textarea-vertical-align" in tb_graphics.attrs:
                vert_align = tb_graphics["draw:textarea-vertical-align"]
        tb_parser = TextBoxParser(dwg, pres, shape, pres.font_mgr, vert_align,
                                  style_src)
        tb_parser.visit_textbox(layer, "shape")
Example #14
0
    def visit_textbox(self, layer, mode):
        if mode == "textbox":
            item_tb = self.item.find("draw:text-box")
        elif mode == "shape":
            item_tb = self.item

        self.svg_w = units_to_float(self.item["svg:width"])
        self.svg_h = units_to_float(self.item["svg:height"])
        if "svg:x" in self.item.attrs:
            self.svg_x = units_to_float(self.item["svg:x"])
        else:
            self.svg_x = 0.0
        if "svg:y" in self.item.attrs:
            self.svg_y = units_to_float(self.item["svg:y"])
        else:
            self.svg_y = 0.0
        
        # TODO: Now need to transform based on draw:transform if no svg:x,y

        self.svg_w_px = self.svg_w * DPCM
        self.cur_y = self.svg_y
        self.textbox = self.dwg.text('', insert=(self.svg_x, self.svg_y))

        # Populate root layer of style stack, keeping track of font family, size and styles
        #  through the text box tree.  Each stack frame stores the styles on each layer of
        #  the tree, with missing attributes filled in from parent elements.  When going back
        #  up the tree stack frames are popped.
        self.style_stack = []
        root_stack_frame = {"style:font-name": "", "fo:font-size": "",\
            "fo:font-style": "normal", "fo:font-weight": "normal", \
            "fo:color": "#000000", "fo:text-align": "start",\
            "fo:margin-left": 0, "fo:margin-right": 0, "fo:text-indent": 0,\
            "fo:margin-top": 0, "fo:margin-bottom": 0, "fo:line-height": 100,\
            "style:text-outline": "false", "fo:text-shadow": "none",\
            "style:text-position": "normal", "fo:background-color": "transparent", \
            "style:text-underline-style": "none", \
            "style:text-underline-color": "font-color", \
            "style:text-underline-type": "single", \
            "style:text-underline-width": "auto", \
            "style:text-overline-style": "none",\
            "style:text-overline-color": "font-color", \
            "style:text-overline-type": "single", \
            "style:text-overline-width": "auto", \
            "style:text-line-through-style": "none", \
            "style:text-line-through-color": "font-color", \
            "style:text-line-through-type": "single", \
            "style:text-line-through-width": "auto", \
            "style:font-relief": "none"}
        if "presentation:style-name" in self.item.attrs:
            self.populate_root_frame(root_stack_frame, \
                self.item["presentation:style-name"], self.item["draw:text-style-name"])
        elif "draw:style-name" in self.item.attrs:
            self.populate_root_frame(root_stack_frame, \
                self.item["draw:style-name"], self.item["draw:text-style-name"])
        else:
            self.populate_stack_frame(root_stack_frame, self.item["draw:text-style-name"])
        self.style_stack.append(root_stack_frame)

        textbox_h = 0
        on_first_item = True
        prev_after = 0
        self.highlights = []
        self.decor_lines = []

        first_span = None
        first_descent = 0
        for p_item in item_tb.find_all({"text:p", "text:list"}, recursive=False):
            if p_item.name == "text:p":
                item_h, item_d, tspan, after_i, h_lights, d_lines = self.visit_p(p_item, False)
            elif p_item.name == "text:list":
                item_h, item_d, tspan, after_i, h_lights, d_lines = self.visit_list(p_item, 0, None)

            textbox_h += item_h + after_i
            if on_first_item:
                first_span = tspan
                on_first_item = False
                first_descent = item_d
            else:
                if tspan:
                    item_dy = tspan.__getitem__("dy") + prev_after
                    tspan.__setitem__("dy", item_dy)
                prev_after = after_i
            # Process highlights and decor_lines
            for hl in h_lights:
                self.highlights.append(hl)
            for dl in d_lines:
                self.decor_lines.append(dl)

        # Set textbox vertical align
        if self.v_align == "top":
            v_adj = 0
        elif self.v_align == "middle":
            v_adj = (self.svg_h - textbox_h) / 2
        else:
            v_adj = self.svg_h - textbox_h
        v_adj -= first_descent

        # TODO: Get this to work properly - use horizontal lines to check calcs
        # Doesn't yet appear to work with textboxes that have before-spacing in first paragraph
        if first_span:
            textbox_dy = first_span.__getitem__("dy") + v_adj
            first_span.__setitem__("dy", textbox_dy)

        for hl in self.highlights:
            hl["baseline"] += v_adj
            layer.add(self.dwg.rect(
                insert=(hl["x"], hl["baseline"] - hl["height"] + hl["descent"]),
                size=(hl["width"], hl["height"]),
                fill=hl["color"]))

        for dl in self.decor_lines:
            dl["y"] += v_adj
            stroke_w = dl["font-size"]/(DPCM*12)
            if dl["line-width"] == "bold":
                stroke_w *= 2
            if dl["decor-type"][3:] == "single":
                dl_ys = [dl["y"]]
            else:
                stroke_w *= 2/3
                dl_ys = [dl["y"] - stroke_w, dl["y"] + stroke_w]
            for y in dl_ys:
                d_line = self.dwg.line(
                    start=(dl["x"], y), end=(dl["x"] + dl["width"], y),
                    stroke=dl["color"], stroke_width=stroke_w)
                if dl["style"] in DASH_ARRAYS:
                    d_array = DASH_ARRAYS[dl["style"]]
                    for idx, item in enumerate(d_array):
                        d_array[idx] = item * dl["font-size"] / DPCM
                    d_line.dasharray(d_array)
                if dl["shadow-color"] != "none":
                    delta_xy = dl["font-size"] / (12*DPCM)
                    s_line = self.dwg.line(
                        start=(dl["x"] + delta_xy, y + delta_xy), 
                        end=(dl["x"] + dl["width"] + delta_xy, y + delta_xy),
                        stroke=dl["shadow-color"], stroke_width=stroke_w)
                    if dl["style"] in DASH_ARRAYS:
                        d_array = DASH_ARRAYS[dl["style"]]
                        for idx, item in enumerate(d_array):
                            d_array[idx] = item * dl["font-size"] / DPCM
                        s_line.dasharray(d_array)
                    layer.add(s_line)
                layer.add(d_line)

        layer.add(self.textbox)
Example #15
0
    def visit_list(self, item_l, level, parent_style):
        # TODO: Bullet image
        # TODO: Relative indents

        # Load in list styles
        if "text:style-name" in item_l.attrs:
            list_style = item_l["text:style-name"]
        else:
            list_style = parent_style
        l_styles = self.style_src.find("office:automatic-styles")\
            .find({"text:list-style"}, {"style:name": list_style}).findChildren(recursive=False)

        l_height = 0
        first_span = None
        first_descent = 0
        on_first_item = True
        prev_after = 0

        highlights = []
        decor_lines = []

        bullet_font, bullet_color = None, None
        list_para_style = l_styles[level].find({"style:list-level-properties"})
        if "fo:font-family" in l_styles[level].find("style:text-properties").attrs:
            bullet_font = l_styles[level].find("style:text-properties")["fo:font-family"]
            if bullet_font == "StarSymbol":
                bullet_font = "OpenSymbol" # LibreOffice bug 112948
        if "fo:color" in l_styles[level].find("style:text-properties").attrs:
            bullet_color = l_styles[level].find("style:text-properties")["fo:color"]
        bullet_scale = units_to_float(l_styles[level].find("style:text-properties")\
            ["fo:font-size"])/100

        list_stack_frame = self.style_stack[len(self.style_stack) - 1].copy() # shallow copy
        if "text:space-before" in list_para_style.attrs:
            space_before = units_to_float(list_para_style["text:space-before"])
        else:
            space_before = 0
        if "text:min-label-width" in list_para_style.attrs:
            list_stack_frame["fo:margin-left"] = space_before\
                + units_to_float(list_para_style["text:min-label-width"])
        else:
            list_stack_frame["fo:margin-left"] = space_before
        self.style_stack.append(list_stack_frame)

        list_header = item_l.find({"text:list-header"})
        if level == 0 and list_header:
            for subheader_item in list_header.find_all({"text:p"}, recursive=False):
                # TODO: Later add in other children of list-header
                if subheader_item.name == "text:p":
                    item_h, item_d, tspan, after_i, h_lights, d_lines = self.visit_p(subheader_item, True)
                    l_height += item_h + after_i
                    if on_first_item:
                        first_span = tspan
                        on_first_item = False
                        first_descent = item_d
                    else:
                        if tspan:
                            item_dy = tspan.__getitem__("dy") + prev_after
                            tspan.__setitem__("dy", item_dy)
                        prev_after = after_i
                    # Process highlights and decor_lines
                    for hl in h_lights:
                        highlights.append(hl)
                    for dl in d_lines:
                        decor_lines.append(dl)

        bullet_count = 0
        for list_item in item_l.find_all({"text:list-item"}, recursive=False):
            for sublist_item in list_item.find_all({"text:p", "text:list"}, recursive=False):
                if sublist_item.name == "text:list":
                    item_h, item_d, tspan, after_i, h_lights, d_lines = \
                        self.visit_list(sublist_item, (level+1), list_style)
                    l_height += item_h + after_i
                else:
                    # Add in bullet point
                    bullet_count += 1
                    if "text:bullet-char" in l_styles[level].attrs:
                        list_bullet = l_styles[level]["text:bullet-char"]
                    else:
                        # Create numbered bullet based on format specified
                        list_bullet = int_to_format(bullet_count, \
                            l_styles[level]["style:num-format"])
                        if "style:num-prefix" in l_styles[level].attrs:
                            list_bullet = l_styles[level]["style:num-prefix"] + list_bullet
                        if "style:num-suffix" in l_styles[level].attrs:
                            list_bullet = list_bullet + l_styles[level]["style:num-suffix"]
                    bullet_span = self.dwg.tspan(list_bullet, x=[self.svg_x+space_before], dy=[0])
                    self.textbox.add(bullet_span)
                    item_h, item_d, tspan, after_i, h_lights, d_lines = self.visit_p(sublist_item, True)
                    l_height += item_h + after_i

                    if tspan.text.strip() == "":
                        # Remove empty bullet points by deleting bullet text
                        bullet_span.text = ""
                    else:

                        if on_first_item:
                            first_span = bullet_span
                            on_first_item = False
                            first_descent = item_d
                        else:
                            if tspan:
                                item_dy = tspan.__getitem__("dy") + prev_after
                                tspan.__setitem__("dy", item_dy)
                            prev_after = after_i

                        # Copy attributes to bullet_span from tspan
                        bullet_span.__setitem__("dy", tspan.__getitem__("dy"))
                        tspan.__setitem__("dy", 0)
                        bullet_style = tspan.__getitem__("style")
                        if bullet_font:
                            font_start = bullet_style.find("font-family:")
                            font_end = bullet_style.find(";", font_start + 1)
                            bullet_style = bullet_style[:font_start+12] + bullet_font + \
                                bullet_style[font_end:]
                        size_start = bullet_style.find("font-size:")
                        size_end = bullet_style.find("pt", size_start + 1)
                        scaled_size = float(bullet_style[size_start+10:size_end]) * bullet_scale
                        bullet_style = bullet_style[:size_start+10] + \
                            str(scaled_size) + bullet_style[size_end:]
                        if bullet_color:
                            color_start = bullet_style.find("fill:")
                            color_end = bullet_style.find(";", color_start + 1)
                            bullet_style = bullet_style[:color_start+5] + bullet_color + \
                                bullet_style[color_end:]
                        bullet_span.__setitem__("style", bullet_style)
                # Process highlights and decor_lines
                for hl in h_lights:
                    highlights.append(hl)
                for dl in d_lines:
                    decor_lines.append(dl)
        self.style_stack.pop()

        # TODO: Get after spacing and bubble up instead of "0"
        return l_height, first_descent, first_span, 0, highlights, decor_lines
Example #16
0
    def stroke(cls, pres, dwg, odp_node, svg_elt, scale_factor, style_src):
        # Get stroke parameters from the style tree of odp_node, using the first encountered
        # instance of each parameter (i.e. prefer child style over parent style)
        style_tag = style_src.find("office:automatic-styles")\
            .find({"style:style"}, {"style:name": odp_node["draw:style-name"]})
        stroke_params = {}
        continue_descent = True
        while continue_descent:
            style_attrs = style_tag.find({"style:graphic-properties"}).attrs
            for x in ["draw:stroke", "draw:stroke-dash", "svg:stroke-color",\
                "svg:stroke-width", "svg:stroke-opacity", \
                "draw:marker-start", "draw:marker-start-width", "draw:marker-start-center",\
                "draw:marker-end", "draw:marker-end-width", "draw:marker-end-center"]:
                if x in style_attrs and not x in stroke_params:
                    stroke_params[x] = style_attrs[x]
            if "style:parent-style-name" in style_tag.attrs:
                style_tag = pres.styles.find("office:styles")\
                    .find({"style:style"}, {"style:name": style_tag["style:parent-style-name"]})
            else:
                continue_descent = False
        if stroke_params["draw:stroke"] in ["solid", "dash"]:
            stroke_width = units_to_float(
                str(stroke_params["svg:stroke-width"]))
            if stroke_width == 0.0:
                stroke_width = 0.01
            stroke_width = stroke_width * scale_factor
            svg_elt.stroke(stroke_params["svg:stroke-color"])
            svg_elt.stroke(width=stroke_width)
            if "svg:stroke-opacity" in stroke_params:
                svg_elt.stroke(opacity=int(re.sub(r'[^0-9.]', '', \
                    str(stroke_params["svg:stroke-opacity"])))/100)
        else:
            svg_elt.stroke(width=0)
        if stroke_params["draw:stroke"] == "dash":
            dash_node = pres.styles.find("office:styles")\
                .find({"draw:stroke-dash"}, {"draw:name": stroke_params["draw:stroke-dash"]})
            dash_array = []
            gap_length = dash_node["draw:distance"]
            if gap_length[-1] == "%":
                gap_length = stroke_width * int(gap_length[:-1]) / 100
            else:
                gap_length = units_to_float(str(gap_length)) * scale_factor
            if "draw:dots1-length" in dash_node.attrs:
                dots1_length = dash_node["draw:dots1-length"]
            else:
                dots1_length = "100%"
            if dots1_length[-1] == "%":
                dots1_length = stroke_width * int(dots1_length[:-1]) / 100
            else:
                dots1_length = units_to_float(str(dots1_length)) * scale_factor
            for i in range(int(dash_node["draw:dots1"])):
                dash_array.append(dots1_length)
                dash_array.append(gap_length)
            if "draw:dots2" in dash_node.attrs:
                if "draw:dots2-length" in dash_node.attrs:
                    dots2_length = dash_node["draw:dots2-length"]
                else:
                    dots2_length = "100%"
                if dots2_length[-1] == "%":
                    dots2_length = stroke_width * int(dots2_length[:-1]) / 100
                else:
                    dots2_length = units_to_float(
                        str(dots2_length)) * scale_factor
                for j in range(int(dash_node["draw:dots2"])):
                    dash_array.append(dots2_length)
                    dash_array.append(gap_length)
            # Apply dash array to stroke
            svg_elt.dasharray(dash_array)

        # Add start and end markers to lines
        if odp_node.name in ["draw:line"]:
            line_angle = math.degrees(math.atan2(\
                units_to_float(odp_node.attrs["svg:y2"]) - \
                units_to_float(odp_node.attrs["svg:y1"]),\
                units_to_float(odp_node.attrs["svg:x2"]) - \
                units_to_float(odp_node.attrs["svg:x1"])))
            markers = [None, None, None]
            if "draw:marker-start" in stroke_params:
                s_marker_style = pres.styles.find("office:styles")\
                    .find({"draw:marker"}, {"draw:name": stroke_params["draw:marker-start"]})
                s_marker_vb = s_marker_style["svg:viewbox"].split()
                s_marker_w = units_to_float(
                    stroke_params["draw:marker-start-width"])
                s_marker_vb_w = int(s_marker_vb[2]) - int(s_marker_vb[0])
                s_marker_vb_h = int(s_marker_vb[3]) - int(s_marker_vb[1])
                s_marker_d = s_marker_style["svg:d"]
                s_ref_y = 0.0
                if "draw:marker-start-center" in stroke_params:
                    if stroke_params["draw:marker-start-center"] == 'true':
                        s_ref_y = 0.5
                s_marker = dwg.marker(insert=(0.5 * s_marker_vb_w, s_ref_y * s_marker_vb_h),\
                    size=(s_marker_w, s_marker_w * s_marker_vb_h / s_marker_vb_w), \
                    viewBox=(' '.join(s_marker_vb)), markerUnits="userSpaceOnUse",\
                    fill=stroke_params["svg:stroke-color"], orient=line_angle-90)
                s_marker_path = dwg.path(d=s_marker_d)
                s_marker.add(s_marker_path)
                dwg.defs.add(s_marker)
                markers[0] = s_marker

            if "draw:marker-end" in stroke_params:
                e_marker_style = pres.styles.find("office:styles")\
                    .find({"draw:marker"}, {"draw:name": stroke_params["draw:marker-end"]})
                e_marker_vb = e_marker_style["svg:viewbox"].split()
                e_marker_w = units_to_float(
                    stroke_params["draw:marker-end-width"])
                e_marker_vb_w = int(e_marker_vb[2]) - int(e_marker_vb[0])
                e_marker_vb_h = int(e_marker_vb[3]) - int(e_marker_vb[1])
                e_marker_d = e_marker_style["svg:d"]
                e_ref_y = 0.0
                if "draw:marker-end-center" in stroke_params:
                    if stroke_params["draw:marker-end-center"] == 'true':
                        e_ref_y = 0.5
                e_marker = dwg.marker(insert=(0.5 * e_marker_vb_w, e_ref_y * e_marker_vb_h),\
                    size=(e_marker_w, e_marker_w * e_marker_vb_h / e_marker_vb_w), \
                    viewBox=(' '.join(e_marker_vb)), markerUnits="userSpaceOnUse",\
                    fill=stroke_params["svg:stroke-color"], orient=line_angle+90)
                e_marker_path = dwg.path(d=e_marker_d)
                e_marker.add(e_marker_path)
                dwg.defs.add(e_marker)
                markers[2] = e_marker
            svg_elt.set_markers(tuple(markers))
Example #17
0
    def process_line(self, queue, margins, line_h):
        # Create tspan for each span in queue, including any line spacing
        # Return line_height, first_span (ref to first tspan in line)
        highlights = []
        decor_lines = []

        line_height = max(x["height"] for x in queue) * line_h / 100
        line_descent = max(x["descent"] for x in queue)
        line_ascent = max(x["ascent"] for x in queue)
        self.cur_y += line_height
        sum_widths = math.fsum(x["width"] for x in queue)
        available_width = self.svg_w_px - (margins[0] + margins[1]) * DPCM
        t_align = queue[0]["stack-frame"]["fo:text-align"]

        if t_align == 'start':
            first_x = self.svg_x + margins[0]
        elif t_align == 'center':
            first_x = (available_width - sum_widths)/(DPCM*2) + self.svg_x + margins[0]
        elif t_align == 'justify':
            first_x = self.svg_x + margins[0]
        else:
            first_x = (available_width - sum_widths)/DPCM + self.svg_x + margins[0]
        span_xs = [first_x]

        # Trim leading and trailing spaces for line
        queue[0]["text"] = queue[0]["text"].lstrip()
        queue[-1]["text"] = queue[-1]["text"].rstrip()

        # Cope with completely blank line by inserting unicode non-breaking space
        if len(queue) == 1 and queue[0]["text"] == "":
            queue[0]["text"] = "\xa0"
            word_lens, space_lens = [0], [0]
        else:
            # Split spans into words to allow highlights and decor lines to line up
            queue_copy = deepcopy(queue)
            queue.clear()
            total_words = 0
            total_spaces = 0
            word_lens = []
            space_lens = []
            prev_type = "space"
            for span in queue_copy:
                span_parts = re.split(r'([ ]+)', span["text"])
                span_font = self.font_mgr.findfont(FontProperties(\
                    family=span["stack-frame"]["style:font-name"], \
                    style=span["stack-frame"]["fo:font-style"], \
                    weight=span["stack-frame"]["fo:font-weight"]))
                i_font = ImageFont.truetype(span_font, math.ceil(float(span["font-size"])*96/72))
                for part in span_parts:
                    # Get width of part
                    if part != "":
                        size = i_font.getsize(part)
                        # If word then add to queue and add current value of total width to span_x
                        if part.strip() != "":
                            if prev_type == "word":
                                # Add zero size space
                                space_lens.append(0)
                            queue.append({"text": part, "style": span["style"], \
                                "stack-frame": span["stack-frame"].copy(), "width": size[0], \
                                "height": span["height"], "font-size": span["font-size"]})
                            word_lens.append(size[0])
                            total_words += size[0]
                            prev_type = "word"
                        else:
                            if prev_type == "space":
                                # Add zero size word
                                queue.append({"text": "\xa0", "style": span["style"], \
                                    "stack-frame": span["stack-frame"].copy(), "width": 0, \
                                    "height": span["height"], "font-size": span["font-size"]})
                            space_lens.append(size[0])
                            total_spaces += size[0]
                            prev_type = "space"
            # Divide available space among spaces
            if t_align == "justify" and total_spaces > 0:
                space_scale = (available_width - total_words) / total_spaces
            else:
                space_scale = 1
            cur_val = 0
            if word_lens:
                for idx, space in enumerate(space_lens):
                    cur_val += (space*space_scale) + word_lens[idx]
                    span_xs.append(cur_val/DPCM + first_x)

        # Write out queued spans
        hl_x = first_x
        prev_highlight = "transparent"
        prev_ul_str, prev_ul = "none", None
        prev_ol_str, prev_ol = "none", None
        prev_lt_str, prev_lt = "none", None
        for idx, item in enumerate(queue):
            tspan = self.dwg.tspan(item["text"], style=item["style"])
            if idx == 0:
                first_span = tspan
                tspan.__setitem__('dy', line_height)
            tspan.__setitem__('x', span_xs[idx])
            bs_adj = 0
            if item["stack-frame"]["style:text-position"] != "normal":
                # Adjust baseline for superscript or subscript text
                if item["stack-frame"]["style:text-position"].split()[0] == "super":
                    bs_adj = item["height"] * -0.6
                elif item["stack-frame"]["style:text-position"].split()[0] == "sub":
                    bs_adj = item["height"] * 0.33
                else:
                    bs_adj = item["height"] * \
                        -units_to_float(item["stack-frame"]["style:text-position"].split()[0])/100
                if bs_adj != 0:
                    tspan.__setitem__('dy', bs_adj)
            self.textbox.add(tspan)
            if bs_adj != 0:
                # Reset baseline after superscript or subscript
                tspan = self.dwg.tspan('\u200B', style=item["style"])
                tspan.__setitem__('dy', -float(bs_adj))
                self.textbox.add(tspan)
            # Record highlight
            if item["stack-frame"]["fo:background-color"] != "transparent":
                if item["stack-frame"]["fo:background-color"] != prev_highlight:
                    highlights.append({
                        "baseline": self.cur_y,
                        "x": hl_x,
                        "descent": line_descent,
                        "width": item["width"]/DPCM,
                        "height": line_height,
                        "color": item["stack-frame"]["fo:background-color"]})
                    prev_highlight = item["stack-frame"]["fo:background-color"]
                else:
                    highlights[-1]["width"] = (item["width"] /DPCM) + hl_x - highlights[-1]["x"]
            else:
                prev_highlight = "transparent"

            if item["stack-frame"]["fo:text-shadow"] == "none":
                shadow_color = "none"
            elif item["stack-frame"]["fo:color"] == "#000000":
                shadow_color = "#cccccc"
            else:
                shadow_color = "#000000"

            # Record underline
            if item["stack-frame"]["style:text-underline-style"] != "none":
                if item["stack-frame"]["style:text-underline-color"] == "font-color":
                    line_color = item["stack-frame"]["fo:color"]
                else:
                    line_color = item["stack-frame"]["style:text-underline-color"]
                ul_string = item["stack-frame"]["style:text-underline-style"] + "_" + \
                    item["stack-frame"]["style:text-underline-width"] + "_" + \
                    item["stack-frame"]["style:text-underline-type"] + "_" + \
                    line_color + "_" + shadow_color
                if ul_string != prev_ul_str:
                    decor_lines.append({
                        "x": hl_x,
                        "y": self.cur_y + line_descent/2,
                        "width": item["width"]/DPCM,
                        "font-size": item["font-size"],
                        "decor-type": "ul-" + item["stack-frame"]["style:text-underline-type"],
                        "color": line_color,
                        "style": item["stack-frame"]["style:text-underline-style"],
                        "line-width": item["stack-frame"]["style:text-underline-width"],
                        "shadow-color": shadow_color
                    })
                    prev_ul_str = ul_string
                    prev_ul = decor_lines[-1]
                else:
                    prev_ul["width"] = (item["width"] /DPCM) + hl_x - prev_ul["x"]
            else:
                prev_ul_str = "none"

            # Record overline
            if item["stack-frame"]["style:text-overline-style"] != "none":
                if item["stack-frame"]["style:text-overline-color"] == "font-color":
                    line_color = item["stack-frame"]["fo:color"]
                else:
                    line_color = item["stack-frame"]["style:text-overline-color"]
                ol_string = item["stack-frame"]["style:text-overline-style"] + "_" + \
                    item["stack-frame"]["style:text-overline-width"] + "_" + \
                    item["stack-frame"]["style:text-overline-type"] + "_" + \
                    line_color + "_" + shadow_color
                if ol_string != prev_ol_str:
                    decor_lines.append({
                        "x": hl_x,
                        "y": self.cur_y - line_ascent + line_descent/2,
                        "width": item["width"]/DPCM,
                        "font-size": item["font-size"],
                        "decor-type": "ol-" + item["stack-frame"]["style:text-overline-type"],
                        "color": line_color,
                        "style": item["stack-frame"]["style:text-overline-style"],
                        "line-width": item["stack-frame"]["style:text-overline-width"],
                        "shadow-color": shadow_color
                    })
                    prev_ol_str = ol_string
                    prev_ol = decor_lines[-1]
                else:
                    prev_ol["width"] = (item["width"] /DPCM) + hl_x - prev_ol["x"]
            else:
                prev_ol_str = "none"

            # Record line through
            if item["stack-frame"]["style:text-line-through-style"] != "none":
                if item["stack-frame"]["style:text-line-through-color"] == "font-color":
                    line_color = item["stack-frame"]["fo:color"]
                else:
                    line_color = item["stack-frame"]["style:text-line-through-color"]
                lt_string = item["stack-frame"]["style:text-line-through-style"] + "_" + \
                    item["stack-frame"]["style:text-line-through-width"] + "_" + \
                    item["stack-frame"]["style:text-line-through-type"] + "_" + \
                    line_color + "_" + shadow_color
                if lt_string != prev_lt_str:
                    decor_lines.append({
                        "x": hl_x,
                        "y": self.cur_y + line_descent - (line_height * 0.4),
                        "width": item["width"]/DPCM,
                        "font-size": item["font-size"],
                        "decor-type": "lt-" + item["stack-frame"]["style:text-line-through-type"],
                        "color": line_color,
                        "style": item["stack-frame"]["style:text-line-through-style"],
                        "line-width": item["stack-frame"]["style:text-line-through-width"],
                        "shadow-color": shadow_color
                    })
                    prev_lt_str = lt_string
                    prev_lt = decor_lines[-1]
                else:
                    prev_lt["width"] = (item["width"] /DPCM) + hl_x - prev_lt["x"]
            else:
                prev_lt_str = "none"

            if idx < len(space_lens):
                hl_x += ((item["width"] + space_lens[idx]) / DPCM)
        return line_height, line_descent, first_span, highlights, decor_lines