Ejemplo n.º 1
0
    def handle(self, node: nodes.image) -> None:
        try:
            basename = os.path.basename(node['uri'])
            if '?' in basename:
                basename = basename.split('?')[0]
            if basename == '' or len(basename) > MAX_FILENAME_LEN:
                filename, ext = os.path.splitext(node['uri'])
                basename = sha1(filename.encode()).hexdigest() + ext
            basename = re.sub(CRITICAL_PATH_CHAR_RE, "_", basename)

            dirname = node['uri'].replace('://', '/').translate({
                ord("?"): "/",
                ord("&"): "/"
            })
            if len(dirname) > MAX_FILENAME_LEN:
                dirname = sha1(dirname.encode()).hexdigest()
            ensuredir(os.path.join(self.imagedir, dirname))
            path = os.path.join(self.imagedir, dirname, basename)

            headers = {}
            if os.path.exists(path):
                timestamp = ceil(os.stat(path).st_mtime)  # type: float
                headers['If-Modified-Since'] = epoch_to_rfc1123(timestamp)

            r = requests.get(node['uri'], headers=headers)
            if r.status_code >= 400:
                logger.warning(
                    __('Could not fetch remote image: %s [%d]') %
                    (node['uri'], r.status_code))
            else:
                self.app.env.original_image_uri[path] = node['uri']

                if r.status_code == 200:
                    with open(path, 'wb') as f:
                        f.write(r.content)

                last_modified = r.headers.get('last-modified')
                if last_modified:
                    timestamp = rfc1123_to_epoch(last_modified)
                    os.utime(path, (timestamp, timestamp))

                mimetype = guess_mimetype(path, default='*')
                if mimetype != '*' and os.path.splitext(basename)[1] == '':
                    # append a suffix if URI does not contain suffix
                    ext = get_image_extension(mimetype)
                    newpath = os.path.join(self.imagedir, dirname,
                                           basename + ext)
                    os.replace(path, newpath)
                    self.app.env.original_image_uri.pop(path)
                    self.app.env.original_image_uri[newpath] = node['uri']
                    path = newpath
                node['candidates'].pop('?')
                node['candidates'][mimetype] = path
                node['uri'] = path
                self.app.env.images.add_file(self.env.docname, path)
        except Exception as exc:
            logger.warning(
                __('Could not fetch remote image: %s [%s]') %
                (node['uri'], exc))
Ejemplo n.º 2
0
def render_math(self: HTMLTranslator, math: str) -> Tuple[str, int]:
    """Render the LaTeX math expression *math* using latex and dvipng or
    dvisvgm.

    Return the filename relative to the built document and the "depth",
    that is, the distance of image bottom and baseline in pixels, if the
    option to use preview_latex is switched on.

    Error handling may seem strange, but follows a pattern: if LaTeX or dvipng
    (dvisvgm) aren't available, only a warning is generated (since that enables
    people on machines without these programs to at least build the rest of the
    docs successfully).  If the programs are there, however, they may not fail
    since that indicates a problem in the math source.
    """
    image_format = self.builder.config.imgmath_image_format.lower()
    if image_format not in SUPPORT_FORMAT:
        raise MathExtError(
            'imgmath_image_format must be either "png" or "svg"')

    latex = generate_latex_macro(image_format, math, self.builder.config,
                                 self.builder.confdir)

    filename = "%s.%s" % (sha1(latex.encode()).hexdigest(), image_format)
    relfn = posixpath.join(self.builder.imgpath, 'math', filename)
    outfn = path.join(self.builder.outdir, self.builder.imagedir, 'math',
                      filename)
    if path.isfile(outfn):
        if image_format == 'png':
            depth = read_png_depth(outfn)
        elif image_format == 'svg':
            depth = read_svg_depth(outfn)
        return relfn, depth

    # if latex or dvipng (dvisvgm) has failed once, don't bother to try again
    if hasattr(self.builder, '_imgmath_warned_latex') or \
       hasattr(self.builder, '_imgmath_warned_image_translator'):
        return None, None

    # .tex -> .dvi
    try:
        dvipath = compile_math(latex, self.builder)
    except InvokeError:
        self.builder._imgmath_warned_latex = True  # type: ignore
        return None, None

    # .dvi -> .png/.svg
    try:
        if image_format == 'png':
            imgpath, depth = convert_dvi_to_png(dvipath, self.builder)
        elif image_format == 'svg':
            imgpath, depth = convert_dvi_to_svg(dvipath, self.builder)
    except InvokeError:
        self.builder._imgmath_warned_image_translator = True  # type: ignore
        return None, None

    # Move generated image on tempdir to build dir
    ensuredir(path.dirname(outfn))
    shutil.move(imgpath, outfn)

    return relfn, depth
Ejemplo n.º 3
0
def render_dot(self: SphinxTranslator,
               code: str,
               options: Dict,
               format: str,
               prefix: str = 'graphviz') -> Tuple[str, str]:
    """Render graphviz code into a PNG or PDF output file."""
    graphviz_dot = options.get('graphviz_dot',
                               self.builder.config.graphviz_dot)
    hashkey = (code + str(options) + str(graphviz_dot) +
               str(self.builder.config.graphviz_dot_args)).encode()

    fname = '%s-%s.%s' % (prefix, sha1(hashkey).hexdigest(), format)
    relfn = posixpath.join(self.builder.imgpath, fname)
    outfn = path.join(self.builder.outdir, self.builder.imagedir, fname)

    if path.isfile(outfn):
        return relfn, outfn

    if (hasattr(self.builder, '_graphviz_warned_dot')
            and self.builder._graphviz_warned_dot.get(graphviz_dot)
        ):  # type: ignore  # NOQA
        return None, None

    ensuredir(path.dirname(outfn))

    dot_args = [graphviz_dot]
    dot_args.extend(self.builder.config.graphviz_dot_args)
    dot_args.extend(['-T' + format, '-o' + outfn])

    docname = options.get('docname', 'index')
    cwd = path.dirname(path.join(self.builder.srcdir, docname))

    if format == 'png':
        dot_args.extend(['-Tcmapx', '-o%s.map' % outfn])

    try:
        ret = subprocess.run(dot_args,
                             input=code.encode(),
                             stdout=PIPE,
                             stderr=PIPE,
                             cwd=cwd,
                             check=True)
        if not path.isfile(outfn):
            raise GraphvizError(
                __('dot did not produce an output file:\n[stderr]\n%r\n'
                   '[stdout]\n%r') % (ret.stderr, ret.stdout))
        return relfn, outfn
    except OSError:
        logger.warning(
            __('dot command %r cannot be run (needed for graphviz '
               'output), check the graphviz_dot setting'), graphviz_dot)
        if not hasattr(self.builder, '_graphviz_warned_dot'):
            self.builder._graphviz_warned_dot = {}  # type: ignore
        self.builder._graphviz_warned_dot[graphviz_dot] = True  # type: ignore
        return None, None
    except CalledProcessError as exc:
        raise GraphvizError(
            __('dot exited with error:\n[stderr]\n%r\n'
               '[stdout]\n%r') % (exc.stderr, exc.stdout))
    def run(self) -> List[Node]:
        document = self.state.document
        if self.arguments:
            if self.content:
                return [
                    document.reporter.warning(
                        __("Only explicit path supported"), line=self.lineno)
                ]
            argument = search_image_for_language(self.arguments[0], self.env)
            rel_filename, filename = self.env.relfn2path(argument)
            self.env.note_dependency(rel_filename)
            try:
                with open(filename, encoding="utf-8") as fp:
                    diagram_code = fp.read()
            except OSError:
                return [
                    document.reporter.warning(
                        __("External Diagrams file %r not found or reading "
                           "it failed") % filename,
                        line=self.lineno,
                    )
                ]
        else:
            diagram_code = "\n".join(self.content)
            filename = self.env.docname + sha1(
                diagram_code.encode("utf-8")).hexdigest()
            if not diagram_code.strip():
                return [
                    self.state_machine.reporter.warning(
                        __('Ignoring "diagrams" directive without content.'),
                        line=self.lineno,
                    )
                ]
        node = diagrams()
        node["code"] = diagram_code
        node["options"] = {"docname": self.env.docname}
        if OPTION_FILENAME not in self.options:
            inferred_filename = f"{Path(filename).stem}.png"
            node["options"][OPTION_FILENAME] = inferred_filename
            document.reporter.info(
                __(":filename: argument not pass assuming "
                   f"the output file is named.'{inferred_filename}'"),
                line=self.lineno,
            )
        else:
            node["options"][OPTION_FILENAME] = self.options[OPTION_FILENAME]

        return [node]
Ejemplo n.º 5
0
    def parse(self, dot: str = None) -> None:
        matched = self.maptag_re.match(self.content[0])
        if not matched:
            raise GraphvizError('Invalid clickable map file found: %s' %
                                self.filename)

        self.id = matched.group(1)
        if self.id == '%3':
            # graphviz generates wrong ID if graph name not specified
            # https://gitlab.com/graphviz/graphviz/issues/1327
            hashed = sha1(dot.encode()).hexdigest()
            self.id = 'grapviz%s' % hashed[-10:]
            self.content[0] = self.content[0].replace('%3', self.id)

        for line in self.content:
            if self.href_re.search(line):
                self.clickable.append(line)
Ejemplo n.º 6
0
    def handle(self, node: nodes.image) -> None:
        image = parse_data_uri(node['uri'])
        ext = get_image_extension(image.mimetype)
        if ext is None:
            logger.warning(__('Unknown image format: %s...'), node['uri'][:32],
                           location=node)
            return

        ensuredir(os.path.join(self.imagedir, 'embeded'))
        digest = sha1(image.data).hexdigest()
        path = os.path.join(self.imagedir, 'embeded', digest + ext)
        self.app.env.original_image_uri[path] = node['uri']

        with open(path, 'wb') as f:
            f.write(image.data)

        node['candidates'].pop('?')
        node['candidates'][image.mimetype] = path
        node['uri'] = path
        self.app.env.images.add_file(self.env.docname, path)