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