emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* Best way to include METAPOST in ConTeXt exporter
@ 2021-10-03 17:53 Jason Ross
  2021-10-04  8:41 ` Ihor Radchenko
  0 siblings, 1 reply; 8+ messages in thread
From: Jason Ross @ 2021-10-03 17:53 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 701 bytes --]

Hello,

I'd like to include METAPOST figures in the ConTeXt exporter backend I'm
developing. However, I don't know of an idiomatic way to add captions and
references for the figures.

Currently, I export METAPOST with `#+BEGIN_EXPORT metapost` / `#+END_EXPORT`
tags. However, this feature seems to be intended for completely "raw"
outputs
with no markup or tagging in the resulting export. I'm interested in
supporting
at least `#+NAME` and `#+CAPTION` keywords for METAPOST figures so that they
can be referred to in the Org file and also in the exported pdf.

What are some better ways of doing something like this? Source blocks? How
would
a user expect to use a feature like this?

Thanks,

Jason

[-- Attachment #2: Type: text/html, Size: 804 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-03 17:53 Best way to include METAPOST in ConTeXt exporter Jason Ross
@ 2021-10-04  8:41 ` Ihor Radchenko
  2021-10-04 15:41   ` Jason Ross
  0 siblings, 1 reply; 8+ messages in thread
From: Ihor Radchenko @ 2021-10-04  8:41 UTC (permalink / raw)
  To: Jason Ross; +Cc: emacs-orgmode

Jason Ross <jasonross1024@gmail.com> writes:

> Hello,
>
> I'd like to include METAPOST figures in the ConTeXt exporter backend I'm
> developing. However, I don't know of an idiomatic way to add captions and
> references for the figures.

You can use affiliated keywords:

(defconst org-element-affiliated-keywords
  '("CAPTION" "DATA" "HEADER" "HEADERS" "LABEL" "NAME" "PLOT" "RESNAME" "RESULT"
    "RESULTS" "SOURCE" "SRCNAME" "TBLNAME")
  "List of affiliated keywords as strings.
By default, all keywords setting attributes (e.g., \"ATTR_LATEX\")
are affiliated keywords and need not to be in this list.")

> Currently, I export METAPOST with `#+BEGIN_EXPORT metapost` / `#+END_EXPORT`
> tags. However, this feature seems to be intended for completely "raw"
> outputs
> with no markup or tagging in the resulting export. I'm interested in
> supporting
> at least `#+NAME` and `#+CAPTION` keywords for METAPOST figures so that they
> can be referred to in the Org file and also in the exported pdf.



> What are some better ways of doing something like this? Source blocks? How
> would
> a user expect to use a feature like this?


You can use a special block:
#+caption: Sample caption
#+attr_context: :field value
#+begin_metapost
...
#+end_metapost

The parsed representation of this element in exporter will be something like:

(special-block (:type "metapost" :begin 292 :end 382 :contents-begin 364 :contents-end 368 :post-blank 0 :post-affiliated 347 :caption (((#("Sample caption" 0 14 (:parent #4))))) :attr_context (":field value")))


Best,
Ihor


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-04  8:41 ` Ihor Radchenko
@ 2021-10-04 15:41   ` Jason Ross
  2021-10-04 16:46     ` Ihor Radchenko
  0 siblings, 1 reply; 8+ messages in thread
From: Jason Ross @ 2021-10-04 15:41 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

I had considered using special blocks; they match my mental model the best.
However, they don't provide any support for syntax highlighting or opening
the block in a new major mode buffer. I'm not sure if it's worth giving up
language features in order to use the block that's most intuitive to users.

I'm also considering writing an Org Babel module for METAPOST. This could
allow METAPOST figures to be included with any export backend. However,
if I go this route, there are still some challenges:
1. Getting captions and tags attached to the resulting figures is clumsy.
   As far as I know,  this could be done with the :post header argument
   and a wrapper source block, but this would require boilerplate code
   in documents that use this feature
2. I don't know if there's a reasonable way to leverage ConTeXt's native support
   for METAPOST this way; if I ask Org Babel to generate an SVG, ConTeXt then
   has to parse the SVG and (internally) convert it back into METAPOST to
   render in the document. ConTeXt's SVG support (particularly with mathematical
   symbols) is missing some features, so this will be a lossy process.
   Essentially I'd want the module to return a file most of the time, but
   return raw METAPOST (wrapped in some tags) if the ConTeXt backend is used.

Here's a minimal implementation of the source block concept:

    # Define a macro to add header arguments UNLESS ConTeXt backend is used
    #+MACRO: conditional-header (eval (when (not (eq
org-export-current-backend 'context)) (concat  "#+HEADER: :results
file :file " $1)))

    # Define a wrapper block to annotate source block outputs with
    # caption and name
    #+NAME: wrap_metapost
    #+BEGIN_SRC emacs-lisp :var data="" :var caption="" :var name=""
    (concat
     (when (org-string-nw-p name) (format "#+NAME: %s\n" name))
     (when (org-string-nw-p caption) (format "#+CAPTION: %s\n" caption))
     (if (eq org-export-current-backend 'context)
         (format "#+BEGIN_METAPOST \n%s\n#+END_METAPOST" data)
       data))
    #+END_SRC

    # Minimal Org Babel implementation for METAPOST
    #+BEGIN_SRC emacs-lisp
    (defvar org-babel-default-header-args:metapost
      '((:exports . "results"))
      "Default arguments to use when evaluating a dot source block.")
    (defun org-babel-execute:metapost (body params)
      "Execute a block of METAPOST code with org-babel.
    This function is called by `org-babel-execute-src-block'."
      (if (cdr (assq :file params))
          (let* ((out-file (cdr (assq :file params)))
                 (cmdline (or (cdr (assq :cmdline params))
                              (format "-T%s" (file-name-extension out-file))))
                 (cmd (or (cdr (assq :cmd params)) "mpost"))
                 (coding-system-for-read 'utf-8) ;use utf-8 with sub-processes
                 (coding-system-for-write 'utf-8)
                 (in-file (org-babel-temp-file "metapost-")))
            (with-temp-file in-file
              (insert (org-babel-expand-body:generic body params)))
            (org-babel-eval
             (concat cmd
                     " -s 'outputformat=\"svg\"'"
                     (format " -s 'outputtemplate=\"%s\"'"
(org-babel-process-file-name out-file))
                     " " (org-babel-process-file-name in-file)) "")
            nil)
        body))
    #+END_SRC

    # Example Usage
    {{{conditional-header(foo.svg)}}}
    #+BEGIN_SRC metapost :results drawer :post
wrap_metapost(name="my-name", caption="my-caption", data=*this*)
    beginfig(1);
    draw origin--(100,100)--(200,0)--cycle;
    endfig;
    end;
    #+END_SRC

This kind of works: It exports to HTML with a nice SVG figure and sends raw
METAPOST code to the backend, with #+CAPTION and #+NAME information
attached. Also, links to the figures work in the buffer as well as the pdf.

However, there's a lot I don't like about this.
1. No SVG preview in the buffer since the `conditional-header` macro doesn't
   get expanded until export time
2. Boilerplate; both the `conditional-header` and `wrap_metapost` definitions
   need to be included in the org file
3. Inconvenient to use since I have to add a macro call and a :post argument
   with weird syntax to every METAPOST figure, and the way captions and tags
   are specified is different.



On Mon, Oct 4, 2021 at 1:39 AM Ihor Radchenko <yantar92@gmail.com> wrote:
>
> Jason Ross <jasonross1024@gmail.com> writes:
>
> > Hello,
> >
> > I'd like to include METAPOST figures in the ConTeXt exporter backend I'm
> > developing. However, I don't know of an idiomatic way to add captions and
> > references for the figures.
>
> You can use affiliated keywords:
>
> (defconst org-element-affiliated-keywords
>   '("CAPTION" "DATA" "HEADER" "HEADERS" "LABEL" "NAME" "PLOT" "RESNAME" "RESULT"
>     "RESULTS" "SOURCE" "SRCNAME" "TBLNAME")
>   "List of affiliated keywords as strings.
> By default, all keywords setting attributes (e.g., \"ATTR_LATEX\")
> are affiliated keywords and need not to be in this list.")
>
> > Currently, I export METAPOST with `#+BEGIN_EXPORT metapost` / `#+END_EXPORT`
> > tags. However, this feature seems to be intended for completely "raw"
> > outputs
> > with no markup or tagging in the resulting export. I'm interested in
> > supporting
> > at least `#+NAME` and `#+CAPTION` keywords for METAPOST figures so that they
> > can be referred to in the Org file and also in the exported pdf.
>
>
>
> > What are some better ways of doing something like this? Source blocks? How
> > would
> > a user expect to use a feature like this?
>
>
> You can use a special block:
> #+caption: Sample caption
> #+attr_context: :field value
> #+begin_metapost
> ...
> #+end_metapost
>
> The parsed representation of this element in exporter will be something like:
>
> (special-block (:type "metapost" :begin 292 :end 382 :contents-begin 364 :contents-end 368 :post-blank 0 :post-affiliated 347 :caption (((#("Sample caption" 0 14 (:parent #4))))) :attr_context (":field value")))
>
>
> Best,
> Ihor


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-04 15:41   ` Jason Ross
@ 2021-10-04 16:46     ` Ihor Radchenko
  2021-10-05 14:40       ` Jason Ross
  0 siblings, 1 reply; 8+ messages in thread
From: Ihor Radchenko @ 2021-10-04 16:46 UTC (permalink / raw)
  To: Jason Ross; +Cc: emacs-orgmode

Jason Ross <jasonross1024@gmail.com> writes:

> I had considered using special blocks; they match my mental model the best.
> However, they don't provide any support for syntax highlighting or opening
> the block in a new major mode buffer. I'm not sure if it's worth giving up
> language features in order to use the block that's most intuitive to users.
>
> I'm also considering writing an Org Babel module for METAPOST. This could
> allow METAPOST figures to be included with any export backend. However,
> if I go this route, there are still some challenges:

If you are in control of the export backend, you can directly process
the metapost source blocks during export and ignore/filter their output
as needed.

>    #+MACRO: conditional-header (eval (when (not (eq
> org-export-current-backend 'context)) (concat  "#+HEADER: :results
> file :file " $1)))

>    #+BEGIN_SRC metapost :results drawer :post
> wrap_metapost(name="my-name", caption="my-caption", data=*this*)

Or you can use "raw" results by default and format everything as you
wish in your Org Babel module programatically.

If above is not sufficient, you can install global export filters that
can transform the metapost blocks/source blocks/their results as you
need in other export backends.

Best,
Ihor


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-04 16:46     ` Ihor Radchenko
@ 2021-10-05 14:40       ` Jason Ross
  2021-10-05 19:50         ` Jason Ross
  2021-10-13  6:54         ` Ihor Radchenko
  0 siblings, 2 replies; 8+ messages in thread
From: Jason Ross @ 2021-10-05 14:40 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

(Apologies to Ihor who I already replied to without cc'ing the list)
=======================================================
> If you are in control of the export backend, you can directly process
> the metapost source blocks during export and ignore/filter their output
> as needed.

This is definitely possible, but I don't want to commit to bypassing the
entire Org Babel system right now. I will explore this a bit.

> Or you can use "raw" results by default and format everything as you
> wish in your Org Babel module programatically.

I'm not sure I understand this yet. Would this emit METAPOST code to the
Org buffer? Ideally, I'd like to emit METAPOST code if the backend is
`context`, otherwise, emit a file.

> If above is not sufficient, you can install global export filters that
> can transform the metapost blocks/source blocks/their results as you
> need in other export backends.

This sounds like it might be a good solution. Is it possible to have an
export filter that changes the header arguments of a source block before
Org Babel sees them? If so, ox-context could change all METAPOST source
block header arguments to give raw results wrapped in appropriate tags.

Also, I'm still curious if there's a better way to add #+CAPTION and
#+NAME tags to images generated by source blocks (in any context). It
would be extremely handy to reference figures generated with
matplotlib throughout the document, not just METAPOST drawings.

Thanks,
Jason

On Mon, Oct 4, 2021 at 9:44 AM Ihor Radchenko <yantar92@gmail.com> wrote:
>
> Jason Ross <jasonross1024@gmail.com> writes:
>
> > I had considered using special blocks; they match my mental model the best.
> > However, they don't provide any support for syntax highlighting or opening
> > the block in a new major mode buffer. I'm not sure if it's worth giving up
> > language features in order to use the block that's most intuitive to users.
> >
> > I'm also considering writing an Org Babel module for METAPOST. This could
> > allow METAPOST figures to be included with any export backend. However,
> > if I go this route, there are still some challenges:
>
> If you are in control of the export backend, you can directly process
> the metapost source blocks during export and ignore/filter their output
> as needed.
>
> >    #+MACRO: conditional-header (eval (when (not (eq
> > org-export-current-backend 'context)) (concat  "#+HEADER: :results
> > file :file " $1)))
>
> >    #+BEGIN_SRC metapost :results drawer :post
> > wrap_metapost(name="my-name", caption="my-caption", data=*this*)
>
> Or you can use "raw" results by default and format everything as you
> wish in your Org Babel module programatically.
>
> If above is not sufficient, you can install global export filters that
> can transform the metapost blocks/source blocks/their results as you
> need in other export backends.
>
> Best,
> Ihor


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-05 14:40       ` Jason Ross
@ 2021-10-05 19:50         ` Jason Ross
  2021-10-13  6:51           ` Ihor Radchenko
  2021-10-13  6:54         ` Ihor Radchenko
  1 sibling, 1 reply; 8+ messages in thread
From: Jason Ross @ 2021-10-05 19:50 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1044 bytes --]

Here's a hook that modifies the source blocks to wrap their
output in #+BEGIN/END_METAPOST tags if the ConTeXt backend is used,
before Org Babel gets to them, but otherwise leaves them alone.

I wonder if anyone has any better ideas of how to do this. I'm
modifying the Org source with the hook before the document gets parsed
so that it can be more backend-agnostic but it seems like it would
be better if there was a way to modify the document parse tree
directly instead. I don't like that I'm effectively parsing and
rebuilding (hopefully) the same string in order to change the :result
type.

I also don't like that I don't really have a clean way of turning
the hook on and off with document keywords. This is kind of a nasty
thing to do to a document and users should probably have to explicitly
opt in.

I also found an old answer that describes how to add captions
to figures generated by source blocks:
https://www.mail-archive.com/emacs-orgmode@gnu.org/msg68100.html
Probably not news to many other people on this list but myself :)

[-- Attachment #2: metapost-handler.org --]
[-- Type: text/plain, Size: 5796 bytes --]

#+TITLE: Metapost Handler

This is a basic handler for METAPOST that exports as raw code
when the ConTeXt exporter is used but otherwise does whatever you
tell it to.

#+NAME: hooks
#+BEGIN_SRC emacs-lisp :exports none :results none
(defun format-src-block-arguments (arguments)
  "Returns a formatted plist of header arguments"
  (mapconcat
   (lambda (argument)
     (let ((kw (car argument))
           (vals (cdr argument)))
       (concat (format "%s" kw)
               " "
               (format "%s" vals))))
   arguments
   " "))
(defun metapost-process-hook (backend)
  "If BACKEND is `context', change metapost code blocks to output
raw code wrapped in #+BEGIN_METAPOST/#+END_METAPOST tags."
  ;; TODO This should be controlled by a flag.
  ;; TODO Check buffer info to see if we are allowed to do this.
  (when (string= backend "context")
    (goto-char (point-min))
    (let ((case-fold-search t)
          ;; Search for source code with a regex
          (regexp "^[ \t]*#\\+BEGIN_SRC"))
      (while (re-search-forward regexp nil t)
        (let* ((objectp (match-end 1))
               (tree (org-element-parse-buffer))
               ;; Get the buffer info plist (need this to export a caption)
               (info (org-combine-plists
                     (org-export--get-export-attributes)
                     (org-export-get-environment)))
               (info (progn
                      (org-export--prune-tree tree info)
                      (org-export--remove-uninterpreted-data tree info)
                      (org-combine-plists info
                                          (org-export--collect-tree-properties
                                           tree info))))
               ;; Get a code element
               (element
                (save-match-data
                  (if objectp (org-element-context) (org-element-at-point))))
               (caption (org-element-property :caption element))
               (type (org-element-type element))
               (begin (copy-marker (org-element-property :begin element)))
               (end (copy-marker
                     (save-excursion
                       (goto-char (org-element-property :end element))
                       (skip-chars-backward " \r\t\n")
                       (point))))
               (block-info (org-babel-get-src-block-info t))
               (language (nth 0 block-info))
               (body (nth 1 block-info))
               (arguments (nth 2 block-info))
               (arguments (delq (assoc :file arguments) arguments))
               (switches (nth 3 block-info))
               (name (nth 4 block-info))
               (start (nth 5 block-info))
               (coderef (nth 6 block-info)))

          (when (or t (string= (downcase language) "metapost"))
            ;; Remove "file" from `results' setting
            (setf (alist-get :results arguments)
                  (mapconcat
                   #'identity
                   (seq-filter
                    (lambda (a) (not (string= a "file")) )
                    (split-string (alist-get :results arguments)))
                   " "))
            ;; Add a wrap argument to wrap in a METAPOST special block
            (setf (alist-get :wrap arguments) "METAPOST")
            (pcase type
              (`src-block
               (progn
                 (delete-region begin end)
                 (goto-char begin)
                 (insert
                  (concat
                   ;; Captions and names got deleted; add them back
                   (when (org-string-nw-p name)
                     (format "#+NAME: %s \n" name))
                   (when caption
                     (format "#+CAPTION: %s\n"
                             (org-string-nw-p
                              (org-trim
                               (org-export-data
                                (or
                                 (org-export-get-caption element t)
                                 (org-export-get-caption element))
                                info)))))
                   ;; Add the (modified) header arguments back
                   (format "#+BEGIN_SRC metapost %s\n%s\n#+END_SRC"
                           (format-src-block-arguments arguments)
                           body)
                   "\n"))))))))
      (goto-char (point-min)))))

(remove-hook 'org-export-before-processing-hook 'metapost-process-hook)
(add-hook 'org-export-before-processing-hook 'metapost-process-hook)
#+END_SRC

#+NAME: metapost-export
#+BEGIN_SRC emacs-lisp :exports none :results none
(defun org-babel-execute:metapost (body params)
  "Execute a block of metapost code with org-babel.
This function is called by `org-babel-execute-src-block'."
  (if (cdr (assq :file params))
      (let* ((out-file (cdr (assq :file params)))
             (cmdline (or (cdr (assq :cmdline params))
                          (format "-T%s" (file-name-extension out-file))))
             (cmd (or (cdr (assq :cmd params)) "mpost"))
             (coding-system-for-read 'utf-8) ;use utf-8 with sub-processes
             (coding-system-for-write 'utf-8)
             (in-file (org-babel-temp-file "metapost-")))
        (with-temp-file in-file
          (insert (org-babel-expand-body:generic body params)))
        (org-babel-eval
         (concat cmd
                 " -s 'outputformat=\"svg\"'"
                 (format " -s 'outputtemplate=\"%s\"'" (org-babel-process-file-name out-file))
                 " " (org-babel-process-file-name in-file)) "")
        nil)
    body))
#+END_SRC


#+NAME: some-name
#+BEGIN_SRC metapost :results file :file foo.svg :exports results
beginfig(1);
draw origin--(100,100)--(200,0)--cycle;
endfig;
end;
#+END_SRC

#+CAPTION: Some caption
#+RESULTS: some-name
[[file:foo.svg]]



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-05 19:50         ` Jason Ross
@ 2021-10-13  6:51           ` Ihor Radchenko
  0 siblings, 0 replies; 8+ messages in thread
From: Ihor Radchenko @ 2021-10-13  6:51 UTC (permalink / raw)
  To: Jason Ross; +Cc: emacs-orgmode

Jason Ross <jasonross1024@gmail.com> writes:

> Here's a hook that modifies the source blocks to wrap their
> output in #+BEGIN/END_METAPOST tags if the ConTeXt backend is used,
> before Org Babel gets to them, but otherwise leaves them alone.
>
> I wonder if anyone has any better ideas of how to do this. I'm
> modifying the Org source with the hook before the document gets parsed
> so that it can be more backend-agnostic but it seems like it would
> be better if there was a way to modify the document parse tree
> directly instead. I don't like that I'm effectively parsing and
> rebuilding (hopefully) the same string in order to change the :result
> type.

I am not sure if there is an easy way. ob-exp gets run before backend
filters and you do not yet have access to the document parse tree.

You may consider a patch for ob-exp to get better control over code
execution during export.

Best,
Ihor


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: Best way to include METAPOST in ConTeXt exporter
  2021-10-05 14:40       ` Jason Ross
  2021-10-05 19:50         ` Jason Ross
@ 2021-10-13  6:54         ` Ihor Radchenko
  1 sibling, 0 replies; 8+ messages in thread
From: Ihor Radchenko @ 2021-10-13  6:54 UTC (permalink / raw)
  To: Jason Ross; +Cc: emacs-orgmode

Jason Ross <jasonross1024@gmail.com> writes:

>> Or you can use "raw" results by default and format everything as you
>> wish in your Org Babel module programatically.
>
> I'm not sure I understand this yet. Would this emit METAPOST code to the
> Org buffer? Ideally, I'd like to emit METAPOST code if the backend is
> `context`, otherwise, emit a file.

A hackish way would be detecting if you are executing code during export
and emitting different results (say, you may look at
org-export-current-backend).  However, it will do nothing when user
specifies :results file

Best,
Ihor


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-10-13  6:54 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-03 17:53 Best way to include METAPOST in ConTeXt exporter Jason Ross
2021-10-04  8:41 ` Ihor Radchenko
2021-10-04 15:41   ` Jason Ross
2021-10-04 16:46     ` Ihor Radchenko
2021-10-05 14:40       ` Jason Ross
2021-10-05 19:50         ` Jason Ross
2021-10-13  6:51           ` Ihor Radchenko
2021-10-13  6:54         ` Ihor Radchenko

Code repositories for project(s) associated with this inbox:

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).