1 2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 3;; 4;; MODULE : tmimage.scm 5;; DESCRIPTION : convert texmacs fragment (selection) to image formats. 6;; Try embedding source code in image 7;; COPYRIGHT : (C) 2012 Philippe Joyez 8;; 9;; This software falls under the GNU general public license version 3 or later. 10;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE 11;; in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>. 12;; 13;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 14 15(texmacs-module (convert images tmimage) 16 (:use (convert tmml tmmlout) 17 (convert tmml tmtmml))) 18 19;; (display "Texmacs] Loading module tmimage\n") 20 21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22;; convert active selection to various graphics format 23;; try embedding texmacs code of the selection in metadata of the image for 24;; re-edition. 25;; the svg produced by this method can be pasted in inkscape via the clipboard 26;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 27 28(define-preferences 29 ("texmacs->graphics:format" "svg" noop)) 30 31;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 32;; private functions 33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 34 35;; temporary files with extensions 36 37(define (url-temp-ext ext) 38 (url-glue (url-temp) (string-append "." ext))) 39 40;; new system function call that check that output is produced 41;; and give minimum information if not 42 43(define no-error-yet #t) 44 45(define (system-2-check cmd urlin urlout) 46;; this fails for convert on windows XP, but why?? (system-2 cmd urlin urlout) 47;; very uggly workaround: 48(if (and (or (os-win32?) (os-mingw?)) (string=? (string-take cmd 7) "convert")) 49 (system (string-append cmd " \""(url-concretize urlin)"\" \"" 50 (url-concretize urlout) "\"" )) 51 (system-2 cmd urlin urlout)) 52(if (and (not (url-exists? urlout)) no-error-yet) 53 (begin 54 (set-message (string-append cmd " failed") "Check converters") 55 (display (string-append "image conversion problem: " cmd " failed\n" )) 56 (display "check converter setup, existence in path...\n" ) 57 (set! no-error-yet #f)) 58)) 59;; since we chain converters 60;; the first error will trigger a cascade of failures 61;; so we only report the first error in export-selection-as-graphics. 62;; We display error both in console and in status bar for console-less 63 64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 65;; external converters 66;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 67 68;; In windows, passing correctly arguments to 69;; the gs tools and pdf2svg is a serious issue in some cases. 70;; In particular, in XP filenames may have spaces and/or accents 71;; (eg in french localized TEXMACS_HOME_PATH : "Données d'applications") 72;; that cause them to fail. I deliver the only workaround I could find: 73;; We provide customized versions of gs tools which 74;; work by converting pathes to old MSDOS-style ascii-only shortened version. 75;; Note that these short names may be deactived in some NT-based systems, 76;; which would break our workaround. 77;; The custom .bat gs tools go in /bin with texmacs.exe and gsw32c.exe 78;; We also need to provide pdf2svg.exe and the needed dlls. 79;; The standard install of imagemagick on windows puts it in the path 80;; so not much to do 81 82(cond ((or (os-win32?) (os-mingw?)) 83 (define win-tm-path (system->url "$TEXMACS_PATH")) 84 (define ps2eps 85 (string-append 86 "\"" 87 (url-concretize 88 (url-append win-tm-path 89 (string->url "bin/tm-ps2epsi.bat"))) "\"")) 90 (define ps2pdf 91 (string-append 92 "\"" 93 (url-concretize 94 (url-append win-tm-path 95 (string->url "bin/tm-ps2pdf.bat")))"\"")) 96 (define pdf2svg 97 (string-append 98 "\"" 99 (url-concretize 100 (url-append win-tm-path 101 (string->url "bin/tm-pdf2svg.bat")))"\"")) 102 ) 103 104 (else ;; MacOS and Linux 105 (define ps2eps "ps2epsi") 106 (define ps2pdf "ps2pdf -dEPSCrop") 107 (define pdf2svg "pdf2svg") 108 109 (if (not (url-exists-in-path? "pdf2svg")) 110 (begin 111 (set-message "warning: pdf2svg not in path" 112 "svg export not available") 113 (display 114 "Texmacs] Warning: pdf2svg not in path; svg export not available\n" ) 115 ))) 116 117;;we just assume gs (including ps2epsi, ps2pdf) is available in *nix ans MacOS 118 ) 119 120;; on all OSes check for "convert" 121;; also check for "conjure" because windows systems may have an homonym 122(if (not (and (url-exists-in-path? "convert") (url-exists-in-path? "conjure"))) 123 (begin 124 (set-message "warning: ImageMagick not in path" 125 "bitmap export not available") 126 (display 127 "Texmacs] Warning: ImageMagick not in path; bitmap export not available\n" ) 128 )) 129 130 131;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 132;; commodity functions for tree manipulations 133;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 134 135(define (remove-node! node) 136 ;; removes node (with children if any) 137 (tree-remove! (tree-ref node :up) (tree-index node) 1)) 138 139(define (copy-node! node parent-dest pos) 140 ;; insert an existing node (with children if any) as new child of parent-dest 141 ;; FIXME: No sanity check! parent should not be in node's subtree! 142 (tree-insert! parent-dest pos `(,node))) 143 144(define (move-node! node parent-dest pos) 145 ;; moves an existing node (with children if any) and 146 ;; insert it as new child of parent-dest 147 ;; FIXME: No sanity check! parent should not be in node's subtree! 148 (copy-node! node parent-dest pos) 149 (remove-node! node)) 150 151(define (remove-node-raise-children! node . firstlast) 152 ;; similar to "remove tag" operation in the editor 153 (let* ((parent (tree-ref node :up)) 154 (pos (+ (tree-index node) 1)) 155 (lastindex (if (< (length firstlast) 2) (- (tree-arity node) 1) (cadr firstlast) )) 156 (firstindex (- (if (null? firstlast) 0 (car firstlast)) 1))) 157 (do ((i lastindex (- i 1))) ((= i firstindex)) 158 (copy-node! (tree-ref node i) parent pos)) 159 (remove-node! node) 160 )) 161 162(define (replace-leaftext! leaf newtext) 163 ;; replace a node's content by a new string. 164 ;; Makes the node a leaf if it wasn't one 165 (tree-assign! leaf (string->tree newtext))) 166 167;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 168;; 2 functions for remapping cross-referenced items (glyphs) 169;; in the svg using unique ids. This is needed to avoid collisions between 170;; definitions belonging to differents formulas in inkscape 171;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 172 173(define (newids! lablist tm-fragment-string) 174 ;; replaces all ids in the svg 175 ;; plus returns an associationlist for mapping old id to new ones 176 ;; we will use it to replace hyperlinks to the former ids to the new ones 177 (let* ((unique (number->string (string-hash tm-fragment-string 1000000))) 178 ;; generate a reproducible 6-digit number that depends 179 ;; on the tm code of the selection 180 (basename (string-append "tm" unique "-")) 181 (newalist '()) 182 (n (length lablist))) 183 (do ((i 0 (+ i 1))) ((= i n)) 184 (let* ((newlabel (string-append basename (number->string i))) 185 (labelnode (list-ref lablist i)) 186 (oldlabel (tree->string labelnode))) 187 (set! newalist (assoc-set! newalist (string-append "#" oldlabel) 188 (string-append "#" newlabel) )) 189 (replace-leaftext! labelnode newlabel))) 190 newalist)) 191 192(define (replace-hlinks! hreflist alist) 193 ;; use the above association list to actualy replace 194 ;; the xlink:href items with updated targets 195 (map (lambda (leaf) 196 (let ((newtarget (assoc-ref alist (tree->string leaf)))) 197 (replace-leaftext! leaf newtarget))) 198 hreflist)) 199 200;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 201;; define latex and texmacs string representation of selection 202;; we escape them to ascii so that they do not interfere with xml 203;; < -> < > -> > \ -> \\, all characters above #127->\xXX ... 204;; see TeXmacs/langs/encodings/cork-escaped-to-ascii.scm 205;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 206 207(define (latex-encode tm-fragment-tree) 208 ;; for the latex representation we mimick what is done when 209 ;; "copy to latex" is performed 210 (let* ((latex-tree (latex-expand tm-fragment-tree)) 211 ;; expand or not macros according to preferences 212 (latex-code (texmacs->generic latex-tree "latex-snippet"))) 213 ;; actual conversion 214 (escape-to-ascii latex-code))) 215 216(define (tm-encode tm-fragment-tree) 217 (escape-to-ascii (serialize-texmacs tm-fragment-tree))) 218 219(define (remove-clip! clipg) 220 (let* ((parent (tree-ref clipg :up)) 221 (pos (+ (tree-index clipg) 1)) 222 (lastindex (- (tree-arity clipg) 1))) 223 (do ((i lastindex (- i 1))) ((= i 0)) 224 (let* ((node (tree-ref clipg i))) 225 (move-node! node parent pos))) 226 (remove-node! clipg) 227 )) 228 229(tm-define (cr2? s) (or (equal? (tree->stree s) "\n") (equal? (tree->stree s) "\n "))) 230 231(define (refactor-svg dest tm-fragment) 232 ;; reorganize svg file and inject attributes containing tm code of 233 ;; equation. dest is the url of the svg file to be edited 234 ;; A latex fragment is also added for compatibility with 235 ;; 'textext' inkscape extension 236 ;; FIXME : no error checking, no return value... 237 ;; for improvements (we could pass the style that was used when 238 ;; the equation was created, the fonts,...) 239 240 241 (let* 242 (;; first: load svg and transform to an active tree in 243 ;; temporary buffer so that we can manipulate it 244 ;; using texmacs primitives for trees 245 (svg-in (string-load dest)) ;; load svg file as string 246 (s-svg-in (parse-xml svg-in)) ;; parse to stree 247 (mybuf (buffer-new)) 248 ;; create temporary buffer for subsequent manipulations of svg tree 249 (void (buffer-set-body mybuf (tree-assign-node! (stree->tree s-svg-in) 250 'concat))) 251 ;; populate buffer with tree 252 ;; replace *TOP* node by concat otherwise displaying 253 ;; that buffer crashes texmacs 254 255 ;; second: define a bunch of locations in the tree 256 (buftree (buffer-get-body mybuf)) ;; the whole tree 257 (svgroot (car (select buftree '(:* svg)))) ;; the <svg > node 258 (maingroup (car (select svgroot '(g)))) 259 ;; the main group in the svg, containing the drawing layout 260 (maingroup-attrib (car (select maingroup '(@)))) 261 ;; attributes of the main group 262 (defs (car (select svgroot '(defs)))) 263 ;; the defs, containing the glyph vector outlines, 264 ;; hyperlinked from the drawing (a.k.a cloned) 265 (idlist (select svgroot '(:* id :%1))) 266 ;; list of all ids in the drawing, used to label glyph outlines 267 (hreflist (select maingroup '(:* @ xlink:href :%1))) 268 ;; list of hyperlinks to the glyphs labels 269 270 ;; third: the new data we want to insert in the tree 271 (latex-code (latex-encode tm-fragment)) 272 (tm-code (tm-encode tm-fragment)) 273 (tm-style (tm-encode (get-all-inits))) 274 ;; define new attributes containing latex and texmacs code: 275 (extra-latex-attrib 276 `((xmlns:ns0 "http://www.iki.fi/pav/software/textext/") 277 (ns0:text ,latex-code) (ns0:preamble "texmacs_latex.sty"))) 278 (extra-tm-attrib `((xmlns:ns1 "http://www.texmacs.org/") 279 (ns1:texmacscode ,tm-code) (ns1:texmacstyle ,tm-style))) 280 ;; OK, the texmacs namespace maybe not correctly described at that url 281 (old->new-labels (newids! idlist tm-code)) 282 ;; rename all ids, create an association list of old to new ids 283 ) 284 285 ;; fourth: modify tree 286 (replace-hlinks! hreflist old->new-labels) 287 ;; replace hlinks with new pointers 288 (map remove-node! (select svgroot '(:* (:match :cr2?)))) 289 (map remove-clip! (reverse (select maingroup '(:* g @ clip-path :up :up)))) 290 (map remove-node! (select defs '(:* clipPath) )) 291 ;; cleanup & simplify svg tree removing unecessary clips 292 (tree-insert! maingroup-attrib 1 extra-latex-attrib) 293 ;; for textext compatibility 294 (tree-insert! maingroup-attrib 2 extra-tm-attrib) 295 (move-node! defs maingroup 2) 296 ;; move defs containing the glyph outlines inside main group 297 ;; so that they remain together in inkscape 298 299 ;; Fifth : finally create output 300 (let* (;; convert back to stree, recreate the *TOP* node, 301 ;; and restore *PI* xml 302 ;; (instead of *PI* "xml" given by tree->stree - 303 ;; otherwise serialize-html fails) 304 (s-svg-out 305 (append '(*TOP* (*PI* xml "version=\"1.0\" encoding=\"UTF-8\"")) 306 ;; actually we use only ascii 307 (cddr (tree->stree buftree)))) 308 (xml-svg-out (begin (output-flush) ;; necessary?? 309 (serialize-tmml s-svg-out)))) 310 ;; close temporary buffer 311 (buffer-pretend-saved mybuf) 312 (buffer-close mybuf) 313 (string-save xml-svg-out dest)))) 314 315;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 316;; public interface 317;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 318 319(tm-define (clipboard-copy-image void) 320 (:synopsis "Places an image of the current selection on the clipboard") 321 (:argument void "not used") 322 (:returns "nothing") 323 ;;the format of the graphics is set in the preferences 324 (if (not (qt-gui?)) 325 (set-message "Qt GUI only, sorry. Use \"Export selection...\"" "") 326 (if (not (selection-active-any?)) 327 (set-message "no selection!" "") 328 (let* ((format (get-preference "texmacs->graphics:format")) 329 (tmpurl (url-temp-ext format))) 330 (export-selection-as-graphics tmpurl) 331 ;; first generate an image file 332 (graphics-file-to-clipboard tmpurl) 333 ;; place that image on the clipboard 334 (system-remove tmpurl) 335 )))) 336 337(tm-define (export-selection-as-graphics myurl) 338 (:synopsis "Generates graphics format of the current selection") 339 (:argument myurl "A full file url with extension") 340 (:returns "nothing") 341 ;; global document parameters such as style, fonts, etc. are respected 342 ;; in the typesetting. However they are presently not passed to 343 ;; the svg and therefore lost when re-editing the svg 344 345 (if (not (selection-active-any?)) 346 (set-message "no selection!" "") 347 (let* (;; step 1 prepare and typeset selection 348 ;;if selection is part of math need to re-encapsulate 349 ;; it with math to obtain proper typesetting : 350 (tm-fragment 351 (if (tree-multi-paragraph? (selection-tree)) 352 (selection-tree) 353 (if (in-math?) 354 (stree->tree `(equation* (document ,(selection-tree)))) 355 (selection-tree)))) 356 ;; also if selection spans several lines of text, 357 ;; need to encapsulate it in a fixed-width table 358 ;;to enforce pagewidth : 359 (tm-fragment-enforce-pagewidth 360 (stree->tree 361 `(tabular 362 (tformat (twith "table-width" "1par") 363 (twith "table-hmode" "exact") 364 (cwith "1" "1" "1" "1" "cell-hyphen" "t") 365 (table (row (cell (document ,tm-fragment)))))))) 366 (temp0 (url-temp-ext "ps")) 367 (temp1 (url-temp-ext "eps")) 368 (dpi-pref (get-preference "printer dpi")) 369 (suffix (url-suffix myurl))) 370 371 (set! no-error-yet #t) 372 (set-printer-dpi "236") ; 472 is ~ exact size 373 ;;set to a fixed value so our graphics does 374 ;;not depend on the printer dpi 375 ;;We need to set this weird dpi value so that the size of the svg 376 ;;produced is about twice that of direct pdf or ps output. Why?? 377 (print-snippet temp0 tm-fragment-enforce-pagewidth) 378 ;;typeset fragment to ps as starting point 379 (set-printer-dpi dpi-pref) 380 ;; revert to preference dpi 381 (system-2-check ps2eps temp0 temp1) 382 ;;make eps to get optimized bounding box. We could generate 383 ;; directly the eps, but then the bounding box width 384 ;; is a full pagewidth 385 (system-remove temp0) 386 ;; step 2 generate output according to desired output format 387 388 (cond ((== suffix "eps") 389 (system-copy temp1 myurl)) 390 ((== suffix "pdf") 391 (system-2-check ps2pdf temp1 myurl)) 392 ((== suffix "svg") 393 ;; assume target is inkscape with texmacs.ink plugin 394 ;; allowing to re-edit the original tm selection 395 ;; (presumably an equation) 396 (let* ((temp2 (url-temp-ext "pdf"))) 397 ;; still need pdf as intermediate format 398 (system-2-check ps2pdf temp1 temp2) 399 (system-2-check pdf2svg temp2 myurl) 400 ;; chaining these 2 specific converters is crucial 401 ;; for svg inport in inkscape: 402 ;; fonts are properly passed as vector outlines 403 (refactor-svg myurl tm-fragment) 404 ;; modify svg, embedding texmacs code 405 (system-remove temp2) 406 )) 407 (else 408 ;; other formats : use imagemagick generic converter 409 ;; this is where png, jpg, etc is generated 410 ;; we ask imagemagick to insert texmacs source 411 ;; in image metadata (comment) 412 (system-2-check 413 (string-append "convert -density 300 -comment \"" 414 (tm-encode tm-fragment) "\"") 415 temp1 myurl))) 416 417 (system-remove temp1) ;; temp eps file not needed anymore 418 ))) 419