;;; draw.el --- Fun & pretty -*- lexical-binding: t -*- ;; ;; Author: Emanuel Berg ;; Created: 2024-08-04 ;; Keywords: convenience ;; License: GPL3+ ;; Package-Requires: ((emacs "25.1")) ;; URL: https://dataswamp.org/~incal/emacs-init/draw.el ;; Version: 3.0.9 ;; ;;; Commentary: ;; ;; _________________________________________________________ ;; \\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\ ;; // _- _- / ;; \\ _- draw.el -- fun & pretty 2024 _- \ ;; // _- _- / ;; \\_______________________________________________________\ ;; `^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^` ;; ;; NOTE: Also check out the much bigger bad.el: ;; ----- [source] https://dataswamp.org/~incal/bad ;; [git] git clone https://dataswamp.org/~incal/bad.git ;; [web pile] https://dataswamp.org/~incal/bad-www ;; ;; Draw fun and pretty things with these functions: ;; ;; - `draw-banner' (try `draw-banner-example') ;; - `draw-box' ;; - `draw-center-string' ;; - `draw-line' ;; - `draw-scale' ;; ;;; Code: (require 'cl-lib) (defvar draw--end-col 74 "How long should `draw' draw things? Default is 74 as in 25x74. For smaller, one can use the value of `fill-column'.") (defun draw--use-region () (if (use-region-p) (list (region-beginning) (region-end)) (list nil nil))) (defun draw--max-len () (max 0 (- draw--end-col (current-column)))) (defun draw--find-reg () (list (point) (if (looking-at "[[:blank:]]*$") (+ (point) (draw--max-len)) (if (looking-at "[[:blank:]]+\\|[[:alnum:][:punct:]]+") (match-end 0) (pos-eol))))) (defun draw-center-string (str &optional beg end) "Insert STR centered between BEG and END. On a blank line STR is inserted halfway to `draw--end-col'. On a line with blanks the next non-blank char is endpoint. If instead upon some char the next whitespace is endpoint. Or use region for BEG and END (max one line). ----------------------------------------------------------------------- Center string, `draw-center-string' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ odd ...............................>even<.................................. odd P ~~~~~~~odd~~~~~~~ |- odd -| even P ~~~~~~even~~~~~~~ |- even -| odd P ~~~~~~~~odd~~~~~~~ |- odd -| even P ~~~~~~~even~~~~~~~ |- even -| -----------------------------------------------------------------------" (interactive `(,(read-string "text [last]: " nil nil "odd") ,@(draw--use-region))) (unless (= (line-number-at-pos beg) (line-number-at-pos end)) (error "One line at a time")) (unless (and beg end) (pcase-let ((`(,a ,b) (draw--find-reg))) (setq beg a) (setq end b))) (let* ((len (length str)) (dst (- end beg)) (span-beg (+ beg (/ dst 2) (- (/ len 2)))) (span-end (+ span-beg len)) (span-len (- span-end span-beg))) (when (and (<= beg span-beg) (<= span-end end)) (if (<= (pos-eol) span-beg) (insert (make-string (- span-beg (point)) ?\s) str) (goto-char span-beg) (insert str) (delete-char span-len))))) (defun draw-box (xc &optional yc) "Draw a box with XC columns and YC rows. If YC isn't set, make a square by XC. +-------------------+ | | | Box, `draw-box' | | | +-------------------+" (interactive (list (read-number "x cols: " 74) (read-number "y rows: " 25))) (or yc (setq yc xc)) (when (and (<= 3 xc) (<= 3 yc)) (let* ((yb ?\|) (xb ?\-) (cb ?\+) (bb ?\s) (xci (- xc 2)) (yci (- yc 2)) (tl (format "%c%s%c" cb (make-string xci xb) cb)) (xl (format "%c%s%c" yb (make-string xci bb) yb)) (all `(,tl ,@(make-list yci xl) ,tl)) (str (string-join all "\n"))) (insert "\n" str)))) (defun draw--string-repeat (s &optional len newline) (or len (setq len (draw--max-len))) (let* ((s-len (length s)) (times (/ len s-len)) (extra (% len s-len)) (str "")) (dotimes (_ times) (setq str (format "%s%s" str s))) (unless (zerop extra) (setq str (format "%s%s" str (substring s 0 extra)))) (when newline (setq str (format "%s\n" str))) str)) (defun draw--string-range (beg mid &optional end len newline) (or end (setq end (reverse beg))) (or len (setq len (draw--max-len))) (let*((beg-len (length beg)) (end-len (length end)) (mid-len (- len beg-len end-len))) (format "%s%s%s%s" beg (draw--string-repeat mid mid-len) end (if newline "\n" "")))) (defun draw-banner (up &optional down sides side len) "Output a banner. UP is the string or list of strings used for the top part. DOWN is the down part. SIDES are the number of sides, the side length in. SIDE is a string how the sides should be drawn. LEN is the banner length. See `draw-banner-example'. _______________________________________________________________________ `-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-` Banner, `draw-banner' _______________________________________________________________________ `-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`-`" (interactive) (when (stringp up) (setq up (list up))) (when (stringp down) (setq down (list down))) (or down (setq down up)) (or sides (setq sides (if (< 1 (length up)) 3 1))) (or len (setq len draw--end-col)) (let ((side-str (if side (draw--string-range side " " side len t) "\n"))) (dolist (s up) (insert (draw--string-repeat s len t))) (dotimes (_ sides) (insert side-str)) (dolist (s down) (insert (draw--string-repeat s len t))))) (defun draw-banner-example () "Draw banner examples." (interactive) (let ((buf (get-buffer-create "*banners*"))) (with-current-buffer buf (erase-buffer) (draw-banner '("_" ".~^`" "`^")) (insert "\n\n") (draw-banner '("_" "`-")) (insert "\n\n") (draw-banner '("_" "`") '("_" "`^")) (insert "\n\n") (draw-banner '("_" "`^")) (insert "\n\n") (draw-banner '("~" ".~^`") '(".~^`" "~"))) (pop-to-buffer buf))) (defun draw-line (&optional c len) "Draw a line with char C of length LEN. ----------------------------------------------------------------------- Horizontal line, `draw-line' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \(draw-line) ----------------------------------- \(draw-line 42) *********************************** \(draw-line ?+) +++++++++++++++++++++++++++++++++++ \\[draw-line] RET ----------------------------------- \\[universal-argument] \\[draw-line] RET ^ 12 ^^^^^^^^^^^^ \\[universal-argument] 1 \\[draw-line] RET RET RET - \\[universal-argument] \\[draw-line] RET RET RET ---- \\[universal-argument] 20 \\[draw-line] RET RET RET -------------------- -----------------------------------------------------------------------" (interactive (when current-prefix-arg (list (read-char "char [-]: ") (read-number "cols: " current-prefix-arg)))) (or (and c (<= ?\! c)) (setq c ?\-)) (or len (setq len (draw--max-len))) (insert (format "%s" (make-string len c)))) (defun draw-scale (&optional end step i) "Draw a ruler (scale) according to a certain pattern. It goes from point to END and for every STEP it inserts the column number. The first column is I = 0 by default. There are seven ways to call this function. ----------------------------------------------------------------------- Scale, `draw-scale' ~~~~~~~~~~~~~~~~~~~ \(draw-scale) 0......7......14.....21.....28.....35.... \(draw-scale 10 3) 0..3..6..9. \(draw-scale 16 4 1) 1...5...9...13.. \\[draw-scale] RET 0......7......14.....21.....28.....35.... \\[universal-argument] \\[draw-scale] RET 0.2.4 \\[universal-argument] 1 \\[draw-scale] RET 0. \\[universal-argument] 300 \\[draw-scale] RET 0.........10........20........30......... -----------------------------------------------------------------------" (interactive (if (numberp current-prefix-arg) (list current-prefix-arg) current-prefix-arg)) (or end (setq end 73)) (or step (setq step (min 10 (max 2 (/ end 10))))) (or i (setq i 0)) (unless (and (numberp i) (<= 0 i) (numberp end) (< 0 end) (numberp step) (< 0 step)) (error "Bogus indata")) (cl-loop with cur = i for dig = (format "%s" (if (zerop (% (- cur i) step)) cur ".")) while (<= cur end) do (insert dig) (cl-incf cur (length dig)))) ;; ___________________________________________ ;; | _____ _____ _ _ _ | ;; | | __ \ | __ \ / \ | | _ | | | ;; | | | | | | |__) | / _ \ | |/ \| | | ;; | | |__| | | __ < / ___ \ | _ | | ;; | |_____/ |_| \_\ /_/ \_\ \_/ \_/ | ;; | | ;; | dorks run away in the face of a warrior | ;; |___________________________________________| (provide 'draw) ;;; draw.el ends here