From 7176f43282749c06daf2756360633cc47d79b63c Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Mon, 18 Dec 2023 12:56:33 -0800 Subject: [PATCH] org: Add image alignment * lisp/org.el (org-image--align, org-image-align, org-toggle-inline-images): Add the ability to left-align, center or right-align inline image previews in the Emacs window. This is controlled globally using the new user option `org-image-align'. Alignment can be specified per image using the `#+ATTR.*' affiliated keywords. The function `org-image--align' determines the kind of alignment for its argument link. * lisp/org-lint.el (org-lint-invalid-image-alignment): Add an org-lint checker to catch invalid ":align" and ":center" attributes in `#+attr_org' keywords. * doc/org-manual.org: Document the new feature under the Images section. --- doc/org-manual.org | 33 ++++++++++++++++ lisp/org-lint.el | 35 +++++++++++++++++ lisp/org.el | 94 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 158 insertions(+), 4 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index 5217e647d..186f08c51 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -11501,6 +11501,39 @@ command: and fall back on the original width if none is found. + #+vindex: org-image-align + Org mode can left-align, center or right-align the display of inline + images. This setting is controlled (globally) by ~org-image-align~. + Only standalone images are affected, corresponding to links with no + surrounding text in their paragraph except for whitespace. Its + value can be the following: + - (default) The symbol ~left~, which inserts the image where the + link appears in the buffer. + - The symbol ~center~, which will preview links centered in the + Emacs window. + - The symbol ~right~, which will preview links right-aligned in the + Emacs window. + + Inline image alignment can be specified for each link using the + =#+ATTR.*= keyword if it matches an alignment specification like: + #+begin_example + ,#+ATTR_HTML: :align center + #+end_example + Org will use the alignment specification from any =#+ATTR.*= + keyword, such as =#+ATTR_HTML= or =#+ATTR_LATEX=, but =#+ATTR_ORG= + (if present) will override the others. For instance, this link + #+begin_example + ,#+ATTR_HTML: :align right + ,#+ATTR_ORG: :align center + [[/path/to/image/file.png]] + #+end_example + will be displayed centered in Emacs but exported right-aligned to + HTML. + + When =#+ATTR_ORG= is not set, inline image alignment is also read + from the =:center= attribute supported by some export backends (like + HTML, LaTeX and Beamer.) + #+vindex: org-cycle-inline-images-display Inline images can also be displayed when cycling the folding state. When custom option ~org-cycle-inline-images-display~ is set, the diff --git a/lisp/org-lint.el b/lisp/org-lint.el index 0e2967b6c..3442d3571 100644 --- a/lisp/org-lint.el +++ b/lisp/org-lint.el @@ -964,6 +964,36 @@ Use \"export %s\" instead" reports)))) reports)) +(defun org-lint-invalid-image-alignment (ast) + (apply + #'nconc + (org-element-map ast 'paragraph + (lambda (p) + (let ((center-re ":center[[:space:]]+\\(\\S-+\\)") + (align-re ":align[[:space:]]+\\(\\S-+\\)") + (keyword-string + (car-safe (org-element-property :attr_org p))) + reports) + (when keyword-string + (when (and (string-match align-re keyword-string) + (not (member (match-string 1 keyword-string) + '("left" "center" "right")))) + (push + (list (org-element-begin p) + (format + "\"%s\" not a supported value for #+ATTR_ORG keyword attribute \":align\"." + (match-string 1 keyword-string))) + reports)) + (when (and (string-match center-re keyword-string) + (not (equal (match-string 1 keyword-string) "t"))) + (push + (list (org-element-begin p) + (format + "\"%s\" not a supported value for #+ATTR_ORG keyword attribute \":center\"." + (match-string 1 keyword-string))) + reports))) + reports))))) + (defun org-lint-extraneous-element-in-footnote-section (ast) (org-element-map ast 'headline (lambda (h) @@ -1390,6 +1420,11 @@ Use \"export %s\" instead" #'org-lint-invalid-keyword-syntax :trust 'low) +(org-lint-add-checker 'invalid-image-alignment + "Report unsupported align attribute for keyword" + #'org-lint-invalid-image-alignment + :trust 'low) + (org-lint-add-checker 'invalid-block "Report invalid blocks" #'org-lint-invalid-block diff --git a/lisp/org.el b/lisp/org.el index 59fe3d2d3..24e80970f 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -16175,6 +16175,25 @@ cache Display remote images, and open them in separate buffers (const :tag "Display and silently update remote images" cache)) :safe #'symbolp) +(defcustom org-image-align 'left + "How to align images previewed using `org-display-inline-images'. + +Only stand-alone image links are affected by this setting. These +are links without surrounding text. + +Possible values of this option are: + +left Insert image at specified position. +center Center image previews. +right Right-align image previews." + :group 'org-appearance + :package-version '(Org . "9.7") + :type '(choice + (const :tag "Left align (or don\\='t align) image previews" left) + (const :tag "Center image previews" center) + (const :tag "Right align image previews" right)) + :safe #'symbolp) + (defun org--create-inline-image (file width) "Create image located at FILE, or return nil. WIDTH is the width of the image. The image may not be created @@ -16293,8 +16312,9 @@ buffer boundaries with possible narrowing." (expand-file-name path)))) (when (and file (file-exists-p file)) (let ((width (org-display-inline-image--width link)) - (old (get-char-property-and-overlay - (org-element-property :begin link) + (align (org-image--align link)) + (old (get-char-property-and-overlay + (org-element-begin link) 'org-image-overlay))) (if (and (car-safe old) refresh) (image-flush (overlay-get (cdr old) 'display)) @@ -16304,8 +16324,8 @@ buffer boundaries with possible narrowing." (org-element-property :begin link) (progn (goto-char - (org-element-property :end link)) - (skip-chars-backward " \t") + (org-element-end link)) + (unless (eolp) (skip-chars-backward " \t")) (point))))) ;; FIXME: See bug#59902. We cannot rely ;; on Emacs to update image if the file @@ -16319,6 +16339,15 @@ buffer boundaries with possible narrowing." (list 'org-display-inline-remove-overlay)) (when (boundp 'image-map) (overlay-put ov 'keymap image-map)) + (when align + (overlay-put + ov 'before-string + (propertize + " " 'face 'default + 'display + (pcase align + ("center" `(space :align-to (- center (0.5 . ,image)))) + ("right" `(space :align-to (- right ,image))))))) (push ov org-inline-image-overlays)))))))))))))))) (defvar visual-fill-column-width) ; Silence compiler warning @@ -16380,6 +16409,63 @@ buffer boundaries with possible narrowing." org-image-actual-width) (t nil)))) +(defun org-image--align (link) + "Determine the alignment of the image link. + +In decreasing order of priority, this is controlled: +- Per image by the value of `:center' or ``:align' in the +affiliated keyword `#+attr_org'. +- By the `#+attr_html' or `#+attr_latex` keywords with valid + `:center' or `:align' values. +- Globally by the user option `org-image-align'. + +The result is either nil or one of the strings \"left\", +\"center\" or \"right\". + +\"center\" will cause the image preview to be centered, \"right\" +will cause it to be right-aligned. A value of \"left\" or nil +implies no special alignment." + (let ((par (org-element-lineage link 'paragraph))) + ;; Only align when image is not surrounded by paragraph text: + (when (and (= (org-element-begin link) + (save-excursion + (goto-char (org-element-contents-begin par)) + (skip-chars-forward "\t ") + (point))) ;account for leading space + ;before link + (<= (- (org-element-contents-end par) + (org-element-end link)) + 1)) ;account for trailing newline + ;at end of paragraph + (save-match-data + ;; Look for a valid ":center t" or ":align left|center|right" + ;; attribute. + ;; + ;; An attr_org keyword has the highest priority, with + ;; any attr.* next. Choosing between these is + ;; unspecified. + (let ((center-re ":\\(center\\)[[:space:]]+t\\b") + (align-re ":align[[:space:]]+\\(left\\|center\\|right\\)\\b") + attr-align) + (catch 'exit + (org-element-properties-mapc + (lambda (propname propval) + (when (and propval + (string-match-p ":attr.*" (symbol-name propname))) + (setq propval (car-safe propval)) + (when (or (string-match center-re propval) + (string-match align-re propval)) + (setq attr-align (match-string 1 propval)) + (when (eq propname :attr_org) + (throw 'exit t))))) + par)) + (if attr-align + (when (member attr-align '("center" "right")) attr-align) + ;; No image-specific keyword, check global alignment property + (when (memq org-image-align '(center right)) + (symbol-name org-image-align)))))))) + + (defun org-display-inline-remove-overlay (ov after _beg _end &optional _len) "Remove inline-display overlay if a corresponding region is modified." (when (and ov after) -- 2.40.1