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;; <  -> &lt;  > -> &gt; \ -> \\, 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