From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: miha--- via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#50806: 27.2; [PATCH] Optimize ansi-color.el Date: Sun, 03 Oct 2021 18:31:55 +0200 Message-ID: <87v92eax38.fsf@miha-pc> References: <87tui8wakb.fsf@miha-pc> Reply-To: Mime-Version: 1.0 Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="32981"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Jim Porter To: 50806@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sun Oct 03 18:29:12 2021 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mX4MQ-0008Jb-Sd for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 03 Oct 2021 18:29:11 +0200 Original-Received: from localhost ([::1]:45192 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mX4MO-0002Za-P9 for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 03 Oct 2021 12:29:08 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:43314) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mX4MI-0002ZQ-9p for bug-gnu-emacs@gnu.org; Sun, 03 Oct 2021 12:29:02 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]:51490) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mX4MI-0004qo-2r for bug-gnu-emacs@gnu.org; Sun, 03 Oct 2021 12:29:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1mX4MH-00045p-Ov for bug-gnu-emacs@gnu.org; Sun, 03 Oct 2021 12:29:01 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 03 Oct 2021 16:29:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 50806 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 50806-submit@debbugs.gnu.org id=B50806.163327851115689 (code B ref 50806); Sun, 03 Oct 2021 16:29:01 +0000 Original-Received: (at 50806) by debbugs.gnu.org; 3 Oct 2021 16:28:31 +0000 Original-Received: from localhost ([127.0.0.1]:34802 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mX4Lm-00044y-EG for submit@debbugs.gnu.org; Sun, 03 Oct 2021 12:28:31 -0400 Original-Received: from kamnitnik.top ([209.250.245.214]:38050) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mX4Lh-00044l-CD for 50806@debbugs.gnu.org; Sun, 03 Oct 2021 12:28:29 -0400 Original-Received: from localhost (BSN-77-156-43.static.siol.net [193.77.156.43]) by kamnitnik.top (Postfix) with ESMTPSA id A62B29C707; Sun, 3 Oct 2021 16:28:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kamnitnik.top; s=mail; t=1633278503; bh=feMmCQ+V8m3sDvEuGuVmsHNLsj5HE16ZpzcjwXEpZ7c=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=tQ/T8NOycnSQ8W+3oobBfusJR+GB256OcV3tpIDyLRsLRx6Gq55zv3cfB8S1daNqO L+d/OGzUqS4UVNrvQylzJuBNB2F6CupcUxugcJvggO0tes05Qs5zVwYkd3XFviGB/s rWpUkieUzV1cbKMsK5uTrEmI91w5H5p2t6AMWk+TsV1U0Ft2aPdTaBQtHg6cItbZ+N GLPv7KKJiop5uuPNpYbtJdJrqgPglw6iF+Xjvi4uVUwvr771JJq+uDG3jFma0oyFnw pcVOwimQ+dTQQDNejRPMuFkv8WXg5MvfG+So7R/8y+WvvBgGH/Tu4qVQjMULeKj5al hqhmGnucgeAuQ== In-Reply-To: <87tui8wakb.fsf@miha-pc> X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:216275 Archived-At: --==-=-= Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain miha--- via "Bug reports for GNU Emacs, the Swiss army knife of text editors" writes: > Attached patch speeds up ansi-color. It tries to eliminate as many > allocations (cons and list) as possible. > Sending patch, adjusted for Emacs 29. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Optimize-ansi-color.el.patch Content-Transfer-Encoding: quoted-printable From=2051ea3f59898fec91a9760447c9c5f4bbd51f85f2 Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Miha=3D20Rihtar=3DC5=3DA1i=3DC4=3D8D?=3D Date: Sat, 2 Oct 2021 21:36:10 +0200 Subject: [PATCH] Optimize ansi-color.el (ansi-color-context-region): (ansi-color-context): Adjust doc string to the new format of ansi-color context. (ansi-color--find-face): Rename to ansi-color--face-vec-face (ansi-color--face-vec-face): Adjust to the new format ansi-color context. (ansi-color-filter-apply): (ansi-color-apply): (ansi-color-filter-region): (ansi-color-apply-on-region): Adjust to the new format of ansi-color context in order to speed these functions up. (ansi-color-apply-sequence): Make it obsolete. (ansi-color--update-face-vec): New function to handle the new format of ansi-color context. (ansi-color-get-face-1): Make obsolete as this function isn't used any more. =2D-- lisp/ansi-color.el | 309 +++++++++++++++++++++++++++++++-------------- 1 file changed, 217 insertions(+), 92 deletions(-) diff --git a/lisp/ansi-color.el b/lisp/ansi-color.el index b1c9cdaeca..7b46754d83 100644 =2D-- a/lisp/ansi-color.el +++ b/lisp/ansi-color.el @@ -458,11 +458,18 @@ 'ansi-color-unfontify-region ;; Working with strings (defvar-local ansi-color-context nil "Context saved between two calls to `ansi-color-apply'. =2DThis is a list of the form (CODES FRAGMENT) or nil. CODES +This is a list of the form (FACE-VEC FRAGMENT) or nil. FACE-VEC represents the state the last call to `ansi-color-apply' ended =2Dwith, currently a list of ansi codes, and FRAGMENT is a string =2Dstarting with an escape sequence, possibly the start of a new =2Descape sequence.") +with, currently a list of the form + +(BASIC-FACES FG BG). + +BASIC-FACES is a bool-vector that specifies which basic faces +from `ansi-color-basic-faces-vector' to apply. FG and BG are +ANSI color codes for the foreground and background color. + +FRAGMENT is a string starting with an escape sequence, possibly +the start of a new escape sequence.") =20 (defun ansi-color-filter-apply (string) "Filter out all ANSI control sequences from STRING. @@ -473,17 +480,17 @@ ansi-color-filter-apply `ansi-color-context' to nil if you don't want this. =20 This function can be added to `comint-preoutput-filter-functions'." =2D (let ((start 0) end result) + (let ((context (ansi-color--ensure-context 'ansi-color-context nil)) + (start 0) end result) ;; if context was saved and is a string, prepend it =2D (if (cadr ansi-color-context) =2D (setq string (concat (cadr ansi-color-context) string) =2D ansi-color-context nil)) + (setq string (concat (cadr context) string)) + (setcar (cdr context) "") ;; find the next escape sequence (while (setq end (string-match ansi-color-control-seq-regexp string st= art)) (push (substring string start end) result) (setq start (match-end 0))) ;; save context, add the remainder of the string to the result =2D (let (fragment) + (let ((fragment "")) (push (substring string start (if (string-match "\033" string start) (let ((pos (match-beginning 0))) @@ -491,25 +498,9 @@ ansi-color-filter-apply pos) nil)) result) =2D (setq ansi-color-context (if fragment (list nil fragment)))) + (setcar (cdr context) fragment)) (apply #'concat (nreverse result)))) =20 =2D(defun ansi-color--find-face (codes) =2D "Return the face corresponding to CODES." =2D ;; Sort the codes in ascending order to guarantee that "bold" comes be= fore =2D ;; any of the colors. This ensures that `ansi-color-bold-is-bright' is =2D ;; applied correctly. =2D (let (faces bright (codes (sort (copy-sequence codes) #'<))) =2D (while codes =2D (when-let ((face (ansi-color-get-face-1 (pop codes) bright))) =2D (when (and ansi-color-bold-is-bright (eq face 'ansi-color-bold)) =2D (setq bright t)) =2D (push face faces))) =2D ;; Avoid some long-lived conses in the common case. =2D (if (cdr faces) =2D (nreverse faces) =2D (car faces)))) =2D (defun ansi-color-apply (string) "Translates SGR control sequences into text properties. Delete all other control sequences without processing them. @@ -524,49 +515,129 @@ ansi-color-apply Set `ansi-color-context' to nil if you don't want this. =20 This function can be added to `comint-preoutput-filter-functions'." =2D (let ((codes (car ansi-color-context)) =2D (start 0) end result) + (let* ((context + (ansi-color--ensure-context 'ansi-color-context nil)) + (face-vec (car context)) + (start 0) + end result) ;; If context was saved and is a string, prepend it. =2D (if (cadr ansi-color-context) =2D (setq string (concat (cadr ansi-color-context) string) =2D ansi-color-context nil)) + (setq string (concat (cadr context) string)) + (setcar (cdr context) "") ;; Find the next escape sequence. (while (setq end (string-match ansi-color-control-seq-regexp string st= art)) (let ((esc-end (match-end 0))) ;; Colorize the old block from start to end using old face. =2D (when codes + (when-let ((face (ansi-color--face-vec-face face-vec))) (put-text-property start end 'font-lock-face =2D (ansi-color--find-face codes) string)) + face string)) (push (substring string start end) result) (setq start (match-end 0)) ;; If this is a color escape sequence, (when (eq (aref string (1- esc-end)) ?m) ;; create a new face from it. =2D (setq codes (ansi-color-apply-sequence =2D (substring string end esc-end) codes))))) + (let ((cur-pos end)) + (ansi-color--update-face-vec + face-vec + (lambda () + (when (string-match ansi-color-parameter-regexp + string cur-pos) + (setq cur-pos (match-end 0)) + (when (<=3D cur-pos esc-end) + (string-to-number (match-string 1 string)))))))))) ;; if the rest of the string should have a face, put it there =2D (when codes + (when-let ((face (ansi-color--face-vec-face face-vec))) (put-text-property start (length string) =2D 'font-lock-face (ansi-color--find-face codes) s= tring)) + 'font-lock-face face string)) ;; save context, add the remainder of the string to the result =2D (let (fragment) =2D (if (string-match "\033" string start) =2D (let ((pos (match-beginning 0))) =2D (setq fragment (substring string pos)) =2D (push (substring string start pos) result)) =2D (push (substring string start) result)) =2D (setq ansi-color-context (if (or codes fragment) (list codes fragm= ent)))) + (if (string-match "\033" string start) + (let ((pos (match-beginning 0))) + (setcar (cdr context) (substring string pos)) + (push (substring string start pos) result)) + (push (substring string start) result)) (apply 'concat (nreverse result)))) =20 +(defun ansi-color--ensure-context (context-sym position) + "Return CONTEXT-SYM's value as a valid context. +If it is nil, set CONTEXT-SYM's value to a new context and return +it. Context is a list of the form as described in +`ansi-color-context' if POSITION is nil, or +`ansi-color-context-region' if POSITION is non-nil. + +If CONTEXT-SYM's value is already non-nil, return it. If its +marker doesn't point anywhere yet, position it before character +number POSITION, if non-nil." + (let ((context (symbol-value context-sym))) + (if context + (if position + (let ((marker (cadr context))) + (unless (marker-position marker) + (set-marker marker position)) + context) + context) + (set context-sym + (list (list (make-bool-vector 8 nil) + nil nil) + (if position + (copy-marker position) + "")))))) + +(defun ansi-color--face-vec-face (face-vec) + "Return the face corresponding to FACE-VEC. +FACE-VEC is a list containing information about the ANSI sequence +code. It is usually stored as the car of the variable +`ansi-color-context-region'." + (let* ((basic-faces (car face-vec)) + (colors (cdr face-vec)) + (bright (and ansi-color-bold-is-bright (aref basic-faces 1))) + (faces nil)) + + (when-let ((fg (car colors))) + (push + `(:foreground + ,(face-foreground + (aref (if (or bright (>=3D fg 8)) + ansi-color-bright-colors-vector + ansi-color-normal-colors-vector) + (mod fg 8)) + nil 'default)) + faces)) + (when-let ((bg (cadr colors))) + (push + `(:background + ,(face-background + (aref (if (or bright (>=3D bg 8)) + ansi-color-bright-colors-vector + ansi-color-normal-colors-vector) + (mod bg 8)) + nil 'default)) + faces)) + + (let ((i 8)) + (while (> i 0) + (setq i (1- i)) + (when (aref basic-faces i) + (push (aref ansi-color-basic-faces-vector i) faces)))) + ;; Avoid some long-lived conses in the common case. + (if (cdr faces) + faces + (car faces)))) + ;; Working with regions =20 (defvar-local ansi-color-context-region nil "Context saved between two calls to `ansi-color-apply-on-region'. =2DThis is a list of the form (CODES MARKER) or nil. CODES +This is a list of the form (FACE-VEC MARKER) or nil. FACE-VEC represents the state the last call to `ansi-color-apply-on-region' =2Dended with, currently a list of ansi codes, and MARKER is a =2Dbuffer position within an escape sequence or the last position =2Dprocessed.") +ended with, currently a list of the form + +(BASIC-FACES FG BG). + +BASIC-FACES is a bool-vector that specifies which basic faces +from `ansi-color-basic-faces-vector' to apply. FG and BG are +ANSI color codes for the foreground and background color. + +MARKER is a buffer position within an escape sequence or the last +position processed.") =20 (defun ansi-color-filter-region (begin end) "Filter out all ANSI control sequences from region BEGIN to END. @@ -576,8 +647,10 @@ ansi-color-filter-region used for the next call to `ansi-color-apply-on-region'. Specifically, it will override BEGIN, the start of the region. Set `ansi-color-context-region' to nil if you don't want this." =2D (let ((end-marker (copy-marker end)) =2D (start (or (cadr ansi-color-context-region) begin))) + (let* ((end-marker (copy-marker end)) + (context (ansi-color--ensure-context + 'ansi-color-context-region begin)) + (start (cadr context))) (save-excursion (goto-char start) ;; Delete escape sequences. @@ -585,8 +658,8 @@ ansi-color-filter-region (delete-region (match-beginning 0) (match-end 0))) ;; save context, add the remainder of the string to the result (if (re-search-forward "\033" end-marker t) =2D (setq ansi-color-context-region (list nil (match-beginning 0))) =2D (setq ansi-color-context-region nil))))) + (set-marker start (match-beginning 0)) + (set-marker start nil))))) =20 (defun ansi-color-apply-on-region (begin end &optional preserve-sequences) "Translates SGR control sequences into overlays or extents. @@ -608,58 +681,58 @@ ansi-color-apply-on-region =20 If PRESERVE-SEQUENCES is t, the sequences are hidden instead of being deleted." =2D (let ((codes (car ansi-color-context-region)) =2D (start-marker (or (cadr ansi-color-context-region) =2D (copy-marker begin))) =2D (end-marker (copy-marker end))) + (let* ((context (ansi-color--ensure-context + 'ansi-color-context-region begin)) + (face-vec (car context)) + (start-marker (cadr context)) + (end-marker (copy-marker end))) (save-excursion (goto-char start-marker) ;; Find the next escape sequence. (while (re-search-forward ansi-color-control-seq-regexp end-marker t) ;; Extract escape sequence. =2D (let ((esc-seq (buffer-substring =2D (match-beginning 0) (point)))) =2D (if preserve-sequences =2D ;; Make the escape sequence transparent. =2D (overlay-put (make-overlay (match-beginning 0) (point)) =2D 'invisible t) =2D ;; Otherwise, strip. =2D (delete-region (match-beginning 0) (point))) =2D + (let ((esc-beg (match-beginning 0)) + (esc-end (point))) ;; Colorize the old block from start to end using old face. (funcall ansi-color-apply-face-function (prog1 (marker-position start-marker) ;; Store new start position. =2D (set-marker start-marker (point))) =2D (match-beginning 0) (ansi-color--find-face codes)) + (set-marker start-marker esc-end)) + esc-beg (ansi-color--face-vec-face face-vec)) ;; If this is a color sequence, =2D (when (eq (aref esc-seq (1- (length esc-seq))) ?m) =2D ;; update the list of ansi codes. =2D (setq codes (ansi-color-apply-sequence esc-seq codes))))) + (when (eq (char-before esc-end) ?m) + (goto-char esc-beg) + (ansi-color--update-face-vec + face-vec (lambda () + (when (re-search-forward ansi-color-parameter-rege= xp + esc-end t) + (string-to-number (match-string 1)))))) + + (if preserve-sequences + ;; Make the escape sequence transparent. + (overlay-put (make-overlay esc-beg esc-end) 'invisible t) + ;; Otherwise, strip. + (delete-region esc-beg esc-end)))) ;; search for the possible start of a new escape sequence (if (re-search-forward "\033" end-marker t) =2D (progn =2D ;; if the rest of the region should have a face, put it there =2D (funcall ansi-color-apply-face-function =2D start-marker (point) (ansi-color--find-face codes)) =2D ;; save codes and point =2D (setq ansi-color-context-region =2D (list codes (copy-marker (match-beginning 0))))) =2D ;; if the rest of the region should have a face, put it there =2D (funcall ansi-color-apply-face-function =2D start-marker end-marker (ansi-color--find-face codes)) =2D ;; Save a restart position when there are codes active. It's =2D ;; convenient for man.el's process filter to pass `begin' =2D ;; positions that overlap regions previously colored; these =2D ;; `codes' should not be applied to that overlap, so we need =2D ;; to know where they should really start. =2D (setq ansi-color-context-region =2D (if codes (list codes (copy-marker (point))))))) =2D ;; Clean up our temporary markers. =2D (unless (eq start-marker (cadr ansi-color-context-region)) =2D (set-marker start-marker nil)) =2D (unless (eq end-marker (cadr ansi-color-context-region)) =2D (set-marker end-marker nil)))) + (progn + (while (re-search-forward "\033" end-marker t)) + (backward-char) + (funcall ansi-color-apply-face-function + start-marker (point) + (ansi-color--face-vec-face face-vec)) + (set-marker start-marker (point))) + (let ((faces (ansi-color--face-vec-face face-vec))) + (funcall ansi-color-apply-face-function + start-marker end-marker faces) + ;; Save a restart position when there are codes active. It's + ;; convenient for man.el's process filter to pass `begin' + ;; positions that overlap regions previously colored; these + ;; `codes' should not be applied to that overlap, so we need + ;; to know where they should really start. + (set-marker start-marker (when faces end-marker))))) + ;; Clean up our temporary marker. + (set-marker end-marker nil))) =20 (defun ansi-color-apply-overlay-face (beg end face) "Make an overlay from BEG to END, and apply face FACE. @@ -767,6 +840,7 @@ ansi-color-apply-sequence is 40-47 (or 100-107) resp. 49, the background color code is replaced or added resp. deleted; any other code is discarded together with the old codes. Finally, the so changed list of codes is returned." + (declare (obsolete ansi-color--update-face-vec "29.1")) (let ((new-codes (ansi-color-parse-sequence escape-sequence))) (while new-codes (let* ((new (pop new-codes)) @@ -795,6 +869,56 @@ ansi-color-apply-sequence (_ nil))))) codes)) =20 +(defun ansi-color--update-face-vec (face-vec iterator) + "Apply escape sequences to FACE-VEC. + +Destructively modify FACE-VEC, which should be a list containing +face information. It is described in +`ansi-color-context-region'. ITERATOR is a function which is +called repeatedly with zero arguments and should return either +the next ANSI code in the current sequence as a number or nil if +there are no more ANSI codes left + +For each new code, the following happens: if it is 1-7, set the +corresponding properties; if it is 21-25 or 27, unset appropriate +properties; if it is 30-37 (or 90-97) or resp. 39, set the +foreground color or resp. unset it; if it is 40-47 (or 100-107) +resp. 49, set the background color or resp. unset it; if it is 38 +or 48, the following codes are used to set the foreground or +background color and the correct color mode; any other code will +unset all properties and colors." + (let ((basic-faces (car face-vec)) + (colors (cdr face-vec)) + new q do-clear) + (while (setq new (funcall iterator)) + (setq q (/ new 10)) + (pcase q + (0 (if (memq new '(0 8 9)) + (setq do-clear t) + (aset basic-faces new t))) + (2 (if (memq new '(20 26 28 29)) + (setq do-clear t) + ;; The standard says `21 doubly underlined' while + ;; https://en.wikipedia.org/wiki/ANSI_escape_code claims + ;; `21 Bright/Bold: off or Underline: Double'. + (aset basic-faces (- new 20) nil) + (aset basic-faces (pcase new (22 1) (25 6) (_ 0)) nil))) + ((or 3 4 9 10) + (let ((r (mod new 10)) + (cell (if (memq q '(3 9)) colors (cdr colors)))) + (pcase r + (8 (setq do-clear t)) + (9 (setcar cell nil)) + (_ (setcar cell (+ (if (memq q '(3 4)) 0 8) r)))))) + (_ (setq do-clear t))) + + (when do-clear + (setq do-clear nil) + ;; Zero out our bool vector without any allocation + (bool-vector-intersection basic-faces #&8"\0" basic-faces) + (setcar colors nil) + (setcar (cdr colors) nil))))) + (defun ansi-color-make-color-map () "Create a vector of face definitions and return it. =20 @@ -859,6 +983,7 @@ ansi-color-get-face-1 "Get face definition for ANSI-CODE. BRIGHT, if non-nil, requests \"bright\" ANSI colors, even if ANSI-CODE is a normal-intensity color." + (declare (obsolete ansi-color--face-vec-face "28.1")) (when (and bright (<=3D 30 ansi-code 49)) (setq ansi-code (+ ansi-code 60))) (cond ((<=3D 0 ansi-code 7) =2D-=20 2.33.0 --=-=-=-- --==-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQJHBAEBCAAxFiEEmxVnesoT5rQXvVXnswkaGpIVmT8FAmFZ2vsTHG1paGFAa2Ft bml0bmlrLnRvcAAKCRCzCRoakhWZP4QcD/96olJLR+4qlj5aeRSTEfqu2RaoiT30 VDm6hcK902DZ6GEB5ty8pPwzoL3cSJuGKBhgETfayHGE+OEGQfZ08i9XSnPdC8ZW m+gUqnEiYz006W5g628mI9K2WFjzeh1TugZ6ARQHxMR+9eFzpySQ22Gx6aeLfih1 oHQqZz1RTrlAQsYSK/cACJzxAJtJyMija2H2/SmZQUw5WffLoLEomKsHNTnU8Wcy FPIztbjDVPiPiFj16/LYphaqyOX1f3WUmnuAOF58FR0mO+Gjp5FvWzkcpsm8DTV+ WwYIQBZgT8uS3w0ChnkEkI5Wxg0qmdBfFWDeA53y/7yY3vHVJ5AOKyzhC2d6V6L1 AbNJgHKwNA1Dj0Z7QCtfsPVbMmg1JShC1CnwlO7Fu8fjCvnvqpWfxf/x6kR+PBvt oiOWM6BLcyhymjCur4tr6ON4FXSvDHAWO+8c3wDt08JT+lgoV4tKRROUWyOmUu49 Es9rZzS1aMH8Qom5eIhPCwkh8CcmTVOo806WD/8TYWhBKDJTSzPKxaGNUZVkN1Ak oTib1mT4o3QBOKAGJl28qPnjrSVv1tH3dNQ0tgF4c8RhUnXMAxDRrAQ1wgkVOqs+ wmvlYplneNKe7jKwgiaiZ2D4yVe0QpmEzFDd7ftXagpwXwWslsaKY7dsZmV1P+79 eGfOlg4OnUV6CQ== =c7YM -----END PGP SIGNATURE----- --==-=-=--