def action(elem, doc): if isinstance(elem, panflute.CodeBlock) and 'graphviz' in elem.classes: svg = check_output(GRAPHVIZ_COMMAND, input=elem.text.encode('utf8')) b64data = b64encode(svg).decode('utf8') url = 'data:image/svg+xml;base64,{}'.format(b64data) return panflute.Para(panflute.Image(url=url)) elif isinstance(elem, panflute.CodeBlock) and 'uml' in elem.classes: svg = check_output(PLANTUML_COMMAND, input=elem.text.encode('utf8')) b64data = b64encode(svg).decode('utf8') url = 'data:image/svg+xml;base64,{}'.format(b64data) return panflute.Para(panflute.Image(url=url))
def sage(elem, doc): elemtype = type(elem) if elemtype in [pf.Math, pf.RawInline]: contents = replace_sagecommand(elem.text) if elemtype == pf.Math: return pf.Math(contents, format=elem.format) else: return pf.RawInline(contents, format=elem.format) if elemtype == pf.CodeBlock: isSageSilent = 'sagesilent' in elem.classes isSageBlock = 'sageblock' in elem.classes isSagePlot = 'sageplot' in elem.classes code = elem.text if isSageBlock or isSagePlot or isSageSilent: img_file = get_image_output_filename(code) sage_file = get_sage_filename(code) if isSagePlot: code = code.strip("\n") codelist = code.split("\n") plot_cmd = codelist.pop() code = "\n".join(codelist) m = re.search(r"sageplot\[(?P<first_name>.*)\]\((.*)\)", plot_cmd) if m == None: para, cmd = "", plot_cmd else: para, cmd = m.group(1), m.group(2) if len(para) > 0: para = ',' + para code += "\n(%s).save(\"%s\"%s)" % (cmd, img_file, para) out, err = run_sage(code) if isSageSilent: return pf.Plain(pf.RawInline("", "tex")) elif isSageBlock: sys.stderr.write('\n convert markdown \n') return pf.convert_text(out) else: return pf.Para( pf.Image(url=img_file, attributes=elem.attributes)) if 'latex' in elem.classes: out, err, img_file = run_tex(code) return pf.Para(pf.Image(url=img_file, attributes=elem.attributes))
def figure(self, options, data, element, doc): # pf.debug(doc.get_metadata("include", "no hoge")) # pf.debug(element.attributes) # pf.debug(element.parent) # pf.debug("prev", element.prev) # pf.debug(element) # pf.debug("next", element.next) # Para( # Image( # Strong(Str(caption)); # url="../images/front-image.png", # title="fig:", # attributes=OrderedDict([("width", "50%")]) # ) # ) # Get options fn = options.get("source") pf.debug("rotate image of", fn) fn = os.path.abspath(fn).replace("\\", "/") title = options.get("title", "fig:") caption = options.get("caption") label = options.get("label", os.path.splitext(os.path.basename(fn))[0]) angle = options.get("angle", 0) attr = options.get("attr", {}) # pf.debug(attr) fn = self.rotate(fn, angle) title = pf.convert_text(title) if not attr: attr = OrderedDict({}) # assert len(title) == 1, title title = title[0] title_text = pf.stringify(title).strip() # pf.debug(caption) if caption: caption = pf.convert_text(caption) caption = caption[0].content img = pf.Image(*caption, url=fn, title=title_text, attributes=attr) else: img = pf.Image(url=fn, attributes=attr) ans = pf.Para(img) # pf.debug("ans", ans) return ans
def do_image(self, elem): old_url = elem.url basename = os.path.basename(old_url) dirname = os.path.dirname(old_url) parts = os.path.normpath(dirname).split(os.sep) first_seg = parts[0] if "http" in first_seg: return elem image_key = "" # for ik in ["image", "images"]: # if ik not in parts: # continue # image_key = ik # break for part in parts: if part in ["image", "images"]: image_key = part break if image_key == "": return elem sep_idx = parts.index(image_key) + 1 find_parts = parts[:sep_idx] rest_parts = parts[sep_idx:] new_dir = self.find_image_dir(find_parts) if new_dir is None: return elem new_parts = [new_dir] + rest_parts + [basename] new_url = os.path.join(*new_parts) return pf.Image(pf.Str(elem.title), url=new_url)
def graphviz_filter(elem, doc): if isinstance(elem, pf.CodeBlock) and 'graphviz' in elem.classes: code = elem.text graph = pygraphviz.AGraph(string=code) title = graph.graph_attr.pop('label', '') graph.layout() path = gen_randpath() graph.draw(path, prog='dot') para = pf.Para(pf.Image(pf.Str(title), title='fig:', url=path)) return para
def action(self, elem, doc): if isinstance(elem, pf.Link) and (("wavedrom" in elem.classes) or ("bitfield" in elem.classes)): fn = elem.url options = elem.attributes idn = elem.identifier caption = elem.content with open(fn, "r", encoding="utf-8") as f: data = f.read() data = self.validatejson(data) self.get_options(options, data, elem, doc) assert self.source is not None, "mandatory option input is not set" assert os.path.exists( self.source) == 1, "input file does not exist" assert isinstance(self.convert_to_png, bool), "option png is boolean" assert isinstance(self.convert_to_pdf, bool), "option pdf is boolean" assert isinstance(self.convert_to_eps, bool), "option eps is boolean" output = wavedrom.render(data, self.svg_filename) output.saveas(self.svg_filename) # pf.debug(output.tostring()) self.render_images() pf.debug("[inline] generate wavedrom from", self.linkto) # pf.debug(elem) try: elem.classes.remove("wavedrom") except: elem.classes.remove("bitfield") elem = pf.Image(*caption, classes=elem.classes, url=self.linkto, identifier=idn, title="fig:", attributes=elem.attributes) # pf.debug(elem) return elem if isinstance(elem, pf.Image) and "wavedrom" in elem.classes: pf.debug("#") pf.debug( "# Inline wavedrom in image link syntax, which is *obsolete*, is detected." ) pf.debug( "# Use hyperlink syntax from now - Just remove ! in front.") pf.debug("# Removing link for safety.") pf.debug("#") return []
def replace_mermaid_blocks_with_images(doc): """Replaces all mermaid code blocks with image blocks. Then saves the markdown content as a new file. Args: doc (panflute.Doc): Pandoc document container, has a mermaid attribute, where the code block \ index and image path are stored. """ logger.info("Replacing mermaid code blocks with image blocks.") for mermaid_block_index, image_path in doc.mermaid.items(): logger.debug(f"Replacing mermaid block {doc.content.list[mermaid_block_index]}.") image_element = panflute.Para(panflute.Image(panflute.Str("Image"), url=image_path)) doc.content.list[mermaid_block_index] = image_element
def test_remember(self): """It should remember and forget.""" doc = pf.Doc() self.assertIsNone(get_remembered(doc, "somekey")) header = pf.Header() remember(doc, "header", header) rememembered_el = get_remembered(doc, "header") self.assertEqual(rememembered_el, header) self.assertIsNone(get_remembered(doc, "header")) img = pf.Image() remember(doc, "img", img) rememembered_img = get_remembered(doc, "img") self.assertEqual(rememembered_img, img) self.assertIsNone(get_remembered(doc, "img"))
def generate(self, options, data, element, doc): # pf.debug("generate()") self.get_options(options, data, element, doc) assert self.source is not None, "mandatory option input is not set" assert os.path.exists(self.source) == 1, "input file does not exist" assert isinstance(self.convert_to_png, bool), "option png is boolean" assert isinstance(self.convert_to_pdf, bool), "option pdf is boolean" assert isinstance(self.convert_to_eps, bool), "option eps is boolean" self.json2svg() self.render_images() if not self.attr: attr = OrderedDict({}) caption = pf.convert_text(self.caption) title = pf.convert_text(self.title) title = title[0] title_text = pf.stringify(title).strip() caption = caption[0] caption = caption.content # pf.debug(caption) # pf.debug(linkto) # pf.debug(title_text) # pf.debug(label) # pf.debug(attr) pf.debug("generate bitfield from", self.linkto) # img = pf.Image(*caption, url=self.linkto, identifier=elem.identifier, title="fig:", attributes=attr) element.classes.remove("bitfield") img = pf.Image(*caption, classes=element.classes, url=self.linkto, identifier=element.identifier, title="fig:", attributes=element.attributes) # pf.debug(img) ans = pf.Para(img) # pf.debug(ans) return ans
def proc_image(elm, doc): if type(elm) == pf.CodeBlock and 'image' in elm.classes: attr = elm.attributes altstr = attr.get('alt', 'an image') if 'alt' in attr: del attr['alt'] title = attr.get('title', 'an image') if 'title' in attr: del attr['title'] url = elm.text.strip() sys.stderr.write('image #' + elm.identifier + 'url=' + url + '\n') return pf.Para( pf.Image(pf.Str(altstr), url=url, title=title, identifier=elm.identifier, classes=elm.classes, attributes=attr))
def graphviz(elem, doc): if isinstance(elem, pf.CodeBlock) and 'graphviz' in elem.classes: code = elem.text G = pygraphviz.AGraph(string=code) G.layout(prog='dot') filename = sha1(code) filetype = {'html': 'png', 'latex': 'pdf'}.get(doc.format, 'png') caption = elem.attributes.get('caption', '') imagedir = f'{MD_DIR}/graphviz-images' src = f'{imagedir}/{filename}.{filetype}' if not os.path.isfile(src): try: os.mkdir(imagedir) sys.stderr.write(f'Created directory {imagedir}\n') except OSError: pass G.draw(src) sys.stderr.write(f'Created image {src}\n') return pf.Para(pf.Image(pf.Str(caption), url=src, title=caption))
def figure(options, data, element, doc): # Get options fn = os.path.abspath(options['source']).replace('\\', '/') title = options.get('title', 'Untitled') notes = data label = options.get('label', os.path.splitext(os.path.basename(fn))[0]) if doc.format == 'latex': subs = {'fn': fn, 'label': label} subs['title'] = pf.convert_markdown(title, format='latex') subs['notes'] = pf.convert_markdown(notes, format='latex') backmatter = doc.get_metadata('format.backmatter', False) pagebreak = doc.get_metadata('format.media-pagebreak', False) w = options.get('width', 1.0) subs['width'] = w subs['innerwidth'] = options.get('innerwidth', w) / w subs['notesize'] = options.get('notesize', 'small') subs['pagebreak'] = '\\clearpage\n' if pagebreak else '' text = LATEX_TEMPLATE.safe_substitute(subs) ans = pf.RawBlock(text=text, format='latex') if backmatter: doc.backmatter.append(ans) msg = '\hyperref[fig:{}]{{[\Cref{{fig:{}}} Goes Here]}}' msg = msg.format(label, label) return pf.Plain(pf.Str(msg)) else: return ans else: title = pf.convert_markdown(title) assert len(title)==1, title title = (title[0]).items notes = pf.Div(*pf.convert_markdown(notes), classes=['note']) title_text = pf.stringify(title) img = pf.Image(*title, url=fn, title=title_text, identifier=label) ans = pf.Div(pf.Plain(img), pf.Plain(pf.LineBreak), notes, classes=['figure']) return ans
def create_image(filename, descr, elem, add_descr=True, block=True): """Create an image element.""" img = pf.Image(url=filename, classes=ELEMENT_CLASSES["IMAGE"]) if add_descr: descr = parse_fragment(descr, elem.doc.metadata["lang"].text, as_doc=True) img.title = shorten( pf.stringify(*descr.content).strip(), width=125, placeholder="..." ) else: img.title = descr if block: ret = pf.Div(pf.Plain(img), classes=ELEMENT_CLASSES["FIGURE"]) remember(elem.doc, "label", ret) if add_descr: ret.content.append(descr.content[0]) else: remember(elem.doc, "label", img) ret = img return ret
def mermaid(elem, doc): if isinstance(elem, pf.CodeBlock) and 'mermaid' in elem.classes: code = elem.text caption = elem.attributes.get('caption', '') filename = sha1(code) mermaid_dir = f'{MD_DIR}/mermaid-images' src_file = f'{mermaid_dir}/{filename}.mmd' dst_img = f'{mermaid_dir}/{filename}.svg' if not os.path.isfile(src_file): try: os.mkdir(mermaid_dir) sys.stderr.write(f'Created directory {mermaid_dir}\n') except OSError: pass with open(src_file, 'w') as f: f.write(code) subprocess.check_call(['mmdc', '-i', src_file, '-o', dst_img], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) return pf.Para(pf.Image(pf.Str(caption), url=dst_img, title=caption))
def render(self, options, data, element, doc): self.doc = doc self.source = options.get("input") self.toPNG = bool(options.get("png", True)) self.toSVG = bool(options.get("svg", False)) self.toPDF = True if doc.format in ["latex"] else bool( options.get("pdf", False)) self.caption = options.get("caption", "Untitled") self.dir_to = options.get("directory", self.defaultdir_to) if os.path.exists(self.dir_to) != 1: os.mkdir(self.dir_to) self.counter = hashlib.sha1(data.encode("utf-8")).hexdigest()[:8] self.basename = "/".join([self.dir_to, str(self.counter)]) if not self.source and data is not None: # pf.debug("not source and data is not None") self.source = ".".join([self.basename, "txt"]) code = data open(self.source, "w", encoding="utf-8").write(data) else: # source and data is "dont care" code = open(self.source, "r", encoding="utf-8").read() assert self.source is not None, "option input is mandatory" assert isinstance(self.toPNG, bool), "option png is boolean" assert isinstance(self.toPDF, bool), "option pdf is boolean" self.svg = ".".join([self.basename, "svg"]) self.png = ".".join([self.basename, "png"]) self.pdf = ".".join([self.basename, "pdf"]) aafigure.render(code, self.svg, {"format": "svg"}) if (self.toPDF): aafigure.render(code, self.pdf, {"format": "pdf"}) if (self.toPNG): aafigure.render(code, self.png, {"format": "png"}) # options = { # "format": filetype, # } if self.doc.format in ["latex"]: linkto = self.pdf elif self.doc.format in ["html", "html5"]: linkto = self.svg else: if not (self.toPNG): aafigure.render(code, self.png, {"format": "png"}) linkto = self.png caption = pf.convert_text(self.caption) caption = caption[0] caption = caption.content render_message = " ".join(["generate aafigure from", linkto]) self.render_message = render_message if not self.render_message else " ".join( [self.render_message, linkto]) pf.debug(self.render_message) element.classes.remove("aafigure") linkto = os.path.abspath(linkto).replace("\\", "/") img = pf.Image(*caption, classes=element.classes, url=linkto, identifier=element.identifier, title="fig:", attributes=element.attributes) # pf.debug(img) ans = pf.Para(img) # pf.debug(ans) return ans
def convert(self): doc = panflute.Doc( api_version=(1, 17, 5), metadata={ 'pagetitle': self.title, }, ) doc.content.append(panflute.Header(panflute.Str(self.title))) lists = {} tables = {} table_rows = {} table_cells = {} for chunk in self._attr_chunks(): self.logger.debug(chunk) container = panflute.Para() cdiv = panflute.Div(container) # Handle lists if 'list_class' in chunk[0]['attrs']: lc = chunk[0]['attrs']['list_class'] check_state = None if lc in ['checked', 'unchecked']: check_state = lc lc = 'checklist' ld = chunk[0]['attrs']['list_depth'] # prune any lists that are lower than us, they're finished for i in list(lists.keys()): if i > ld: lists.pop(i) # non-homogenous list types can be immediately adjacent without # ending up merged if ld in lists and lists[ld]['class'] != lc: lists.pop(ld) # checklists are a special case, they can't contain other lists if lc != 'checklist' and lists and lists[1][ 'class'] == 'checklist': lists = {} # make sure any intermediate lists were created, including # the top level because boxnotes for i in range(1, ld + 1): if i not in lists: lists[i] = self._list(lc, i) if i != ld: lists[i]['pf'].content.append(panflute.ListItem()) lp = lists[i]['pf'] if lc == 'checklist': lp = panflute.Div(lp, classes=['checklist']) if i == 1: doc.content.append(lp) else: lists[i - 1]['pf'].content[-1].content.append(lp) # set the container for the other subchunks container = panflute.Plain() cdiv.content = [container] cdiv.classes.append(lc) if check_state: cdiv.classes.append(check_state) lists[ld]['pf'].content.append(panflute.ListItem(cdiv)) if check_state == 'checked': container.content.append(panflute.Str(CHECKED)) elif check_state == 'unchecked': container.content.append(panflute.Str(UNCHECKED)) elif 'table_id' in chunk[-1]['attrs']: table_id = chunk[-1]['attrs']['table_id'] row_id = chunk[-1]['attrs']['table_row'] cell_id = row_id + chunk[-1]['attrs']['table_col'] if table_id not in tables: # There's some magic in the constructor for panflute tables # that isn't exposed in any other way, so we can't create # the table until we've finished populating the rows. # Instead, use a placeholder div to locate it within the # document. tables[table_id] = { 'div': panflute.Div(), 'rows': [], } doc.content.append(tables[table_id]['div']) if row_id not in table_rows: table_rows[row_id] = panflute.TableRow() tables[table_id]['rows'].append(table_rows[row_id]) if cell_id not in table_cells: cdiv = panflute.Div(panflute.Plain()) table_cells[cell_id] = panflute.TableCell(cdiv) table_rows[row_id].content.append(table_cells[cell_id]) container = table_cells[cell_id].content[0].content[0] else: lists = {} doc.content.append(cdiv) if 'align' in chunk[0]['attrs']: cdiv.attributes['style'] = 'text-align: ' + chunk[0]['attrs'][ 'align'] + ';' for subchunk in chunk: if subchunk['newlines'] > 1: # we've had an extra linebreak, no more adding on to lists lists = {} # don't do anything with markers if subchunk['text'] == '*' and 'lmkr' in subchunk['attrs']: continue scont = container if 'href' in subchunk['attrs']: scont = panflute.Link(url=subchunk['attrs']['href']) container.content.append(scont) if 'image' in subchunk['attrs']: scont.content.append( panflute.Image( url=self._image(subchunk['attrs']['author'], subchunk['attrs']['image']))) continue span = panflute.Span() lines = subchunk['text'].splitlines() while lines: subtext = lines.pop(0) span.content.append(panflute.Str(subtext)) if lines: span.content.append(panflute.LineBreak()) if 'font' in subchunk['attrs']: color = subchunk['attrs']['font'].get('color', '000000') size = subchunk['attrs']['font'].get('size', 'medium') span.classes.append('font-size-' + size) span.classes.append('font-color-' + color) # I don't actually know what the possible colors are and I # don't feel like finding out, so just inject it as an # inline style. if color != '000000': span.attributes['style'] = 'color: #' + color + ';' if subchunk['attrs'].get('underline'): span.classes.append('underline') if subchunk['attrs'].get('bold'): span = panflute.Strong(span) if subchunk['attrs'].get('italic'): span = panflute.Emph(span) if subchunk['attrs'].get('strikethrough'): span = panflute.Strikeout(span) scont.content.append(span) # Actually create the tables for x in tables: tables[x]['div'].content.append(panflute.Table(*tables[x]['rows'])) with io.StringIO() as f: panflute.dump(doc, f) return f.getvalue()
def run(self): url = self.renderer(self.input, self.cmd_renderer) pfcaption = (pf.convert_text(self.caption))[0].content return pf.Para( pf.Image(*pfcaption, url=url, title=u'fig:' + self.caption))
def action(self, elem, doc): if isinstance(elem, pf.Link) and "svgbob" in elem.classes: fn = elem.url options = elem.attributes caption = elem.content meta_font_family = doc.get_metadata("svgbob.font-family", "Arial") meta_font_size = doc.get_metadata("svgbob.font-size", 14) meta_scale = doc.get_metadata("svgbob.scale", 1) meta_stroke_width = doc.get_metadata("svgbob.stroke-width", 2) font_family = options.get("font-family", meta_font_family) font_size = options.get("font-size", meta_font_size) scale = options.get("scale", meta_scale) stroke_width = options.get("stroke-width", meta_stroke_width) svgbob_option = " ".join([ '--font-family "{}"'.format(font_family) if font_family is not None else "", "--font-size {}".format(font_size) if font_size is not None else "", "--scale {}".format(scale) if scale is not None else "", "--stroke-width {}".format(stroke_width) if stroke_width is not None else "", ]) if not os.path.exists(self.dir_to): os.mkdir(self.dir_to) data = open(fn, "r", encoding="utf-8").read() counter = hashlib.sha1(data.encode("utf-8")).hexdigest()[:8] self.basename = "/".join([self.dir_to, str(counter)]) _format = "svg" # if doc.format in ["latex"]: # format = "pdf" # elif doc.format in ["html", "html5"]: # format = "svg" # else: # format = "png" fn = os.path.abspath(fn) linkto = os.path.abspath(".".join([self.basename, _format])).replace("\\", "/") command = "svgbob {} {} -o {}".format(fn, svgbob_option, linkto) pf.debug(command) sp.Popen(command, shell=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) pf.debug("[inline] generate svgbob from {} to {}".format( fn, linkto)) elem.classes.remove("svgbob") elem = pf.Image(*caption, classes=elem.classes, url=linkto, identifier=elem.identifier, title="fig:", attributes=elem.attributes) # pf.debug(elem) return elem