Code

fix transforms
[inkscape.git] / src / extension / internal / pdflatex-renderer.cpp
1 #define EXTENSION_INTERNAL_PDF_LATEX_RENDERER_CPP
3 /** \file
4  * Rendering LaTeX file (pdf+latex output)
5  */
6 /*
7  * Authors:
8  *   Johan Engelen <goejendaagh@zonnet.nl>
9  *   Miklos Erdelyi <erdelyim@gmail.com>
10  *
11  * Copyright (C) 2006-2010 Authors
12  *
13  * Most of the pre- and postamble is copied from GNUPlot's epslatex terminal output :-)
14  *
15  * Licensed under GNU GPL
16  */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #ifndef PANGO_ENABLE_BACKEND
23 #define PANGO_ENABLE_BACKEND
24 #endif
26 #ifndef PANGO_ENABLE_ENGINE
27 #define PANGO_ENABLE_ENGINE
28 #endif
31 #include <signal.h>
32 #include <errno.h>
34 #include "libnr/nr-rect.h"
35 #include "libnrtype/Layout-TNG.h"
36 #include <2geom/transforms.h>
37 #include <2geom/pathvector.h>
39 #include <glib/gmem.h>
41 #include <glibmm/i18n.h>
42 #include "display/nr-arena.h"
43 #include "display/nr-arena-item.h"
44 #include "display/nr-arena-group.h"
45 #include "display/curve.h"
46 #include "display/canvas-bpath.h"
47 #include "sp-item.h"
48 #include "sp-item-group.h"
49 #include "style.h"
50 #include "marker.h"
51 #include "sp-linear-gradient.h"
52 #include "sp-radial-gradient.h"
53 #include "sp-root.h"
54 #include "sp-use.h"
55 #include "sp-text.h"
56 #include "sp-flowtext.h"
57 #include "sp-mask.h"
58 #include "sp-clippath.h"
59 #include "text-editing.h"
61 #include <unit-constants.h>
62 #include "helper/png-write.h"
63 #include "helper/pixbuf-ops.h"
65 #include "pdflatex-renderer.h"
66 #include "extension/system.h"
68 #include "io/sys.h"
70 #include <cairo.h>
72 // include support for only the compiled-in surface types
73 #ifdef CAIRO_HAS_PDF_SURFACE
74 #include <cairo-pdf.h>
75 #endif
76 #ifdef CAIRO_HAS_PS_SURFACE
77 #include <cairo-ps.h>
78 #endif
80 //#define TRACE(_args) g_message(_args)
81 #define TRACE(_args)
82 //#define TEST(_args) _args
83 #define TEST(_args)
85 // FIXME: expose these from sp-clippath/mask.cpp
86 struct SPClipPathView {
87     SPClipPathView *next;
88     unsigned int key;
89     NRArenaItem *arenaitem;
90     NRRect bbox;
91 };
93 struct SPMaskView {
94     SPMaskView *next;
95     unsigned int key;
96     NRArenaItem *arenaitem;
97     NRRect bbox;
98 };
100 namespace Inkscape {
101 namespace Extension {
102 namespace Internal {
105 PDFLaTeXRenderer::PDFLaTeXRenderer(void)
106   : _stream(NULL),
107     _filename(NULL),
108     _width(0),
109     _height(0)
111     push_transform(Geom::identity());
114 PDFLaTeXRenderer::~PDFLaTeXRenderer(void)
116     if (_stream) {
117         writePostamble();
119         fclose(_stream);
120     }
122     /* restore default signal handling for SIGPIPE */
123 #if !defined(_WIN32) && !defined(__WIN32__)
124     (void) signal(SIGPIPE, SIG_DFL);
125 #endif
127     if (_filename) {
128         g_free(_filename);
129     }
131     return;
134 /** This should create the output LaTeX file, and assign it to _stream.
135  * @return Returns true when succesfull
136  */
137 bool
138 PDFLaTeXRenderer::setTargetFile(gchar const *filename) {
139     if (filename != NULL) {
140         while (isspace(*filename)) filename += 1;
141         
142         _filename = g_strdup(filename);
144         gchar *filename_ext = g_strdup_printf("%s.tex", filename);
145         Inkscape::IO::dump_fopen_call(filename_ext, "K");
146         FILE *osf = Inkscape::IO::fopen_utf8name(filename_ext, "w+");
147         if (!osf) {
148             fprintf(stderr, "inkscape: fopen(%s): %s\n",
149                     filename_ext, strerror(errno));
150             return false;
151         }
152         _stream = osf;
153         g_free(filename_ext);
154     }
156     if (_stream) {
157         /* fixme: this is kinda icky */
158 #if !defined(_WIN32) && !defined(__WIN32__)
159         (void) signal(SIGPIPE, SIG_IGN);
160 #endif
161     }
163     fprintf(_stream, "%%%% Creator: Inkscape %s, www.inkscape.org\n", PACKAGE_STRING);
164     fprintf(_stream, "%%%% PDF + LaTeX output extension by Johan Engelen, 2010\n");
165     fprintf(_stream, "%%%% Accompanies %s.pdf\n", _filename);
166     /* flush this to test output stream as early as possible */
167     if (fflush(_stream)) {
168         if (ferror(_stream)) {
169             g_print("Error %d on LaTeX file output stream: %s\n", errno,
170                     g_strerror(errno));
171         }
172         g_print("Output to LaTeX file failed\n");
173         /* fixme: should use pclose() for pipes */
174         fclose(_stream);
175         _stream = NULL;
176         fflush(stdout);
177         return false;
178     }
180     writePreamble();
182     return true;
185 /* Most of this preamble is copied from GNUPlot's epslatex terminal output :-) */
186 static char const preamble[] =
187 "\\begingroup                                                                              \n"
188 "  \\makeatletter                                                                          \n"
189 "  \\providecommand\\color[2][]{%%                                                         \n"
190 "    \\GenericError{(gnuplot) \\space\\space\\space\\@spaces}{%%                           \n"
191 "      Package color not loaded in conjunction with                                        \n"
192 "      terminal option `colourtext'%%                                                      \n"
193 "    }{See the gnuplot documentation for explanation.%%                                    \n"
194 "    }{Either use 'blacktext' in gnuplot or load the package                               \n"
195 "      color.sty in LaTeX.}%%                                                              \n"
196 "    \\renewcommand\\color[2][]{}%%                                                        \n"
197 "  }%%                                                                                     \n"
198 "  \\providecommand\\includegraphics[2][]{%%                                               \n"
199 "    \\GenericError{(gnuplot) \\space\\space\\space\\@spaces}{%%                           \n"
200 "      Package graphicx or graphics not loaded%%                                           \n"
201 "    }{See the gnuplot documentation for explanation.%%                                    \n"
202 "    }{The gnuplot epslatex terminal needs graphicx.sty or graphics.sty.}%%                \n"
203 "    \\renewcommand\\includegraphics[2][]{}%%                                              \n"
204 "  }%%                                                                                     \n"
205 "  \\providecommand\\rotatebox[2]{#2}%%                                                    \n"
206 "  \\@ifundefined{ifGPcolor}{%%                                                            \n"
207 "    \\newif\\ifGPcolor                                                                    \n"
208 "    \\GPcolorfalse                                                                        \n"
209 "  }{}%%                                                                                   \n"
210 "  \\@ifundefined{ifGPblacktext}{%%                                                        \n"
211 "    \\newif\\ifGPblacktext                                                                \n"
212 "    \\GPblacktexttrue                                                                     \n"
213 "  }{}%%                                                                                   \n"
214 "  %% define a \\g@addto@macro without @ in the name:                                      \n"
215 "  \\let\\gplgaddtomacro\\g@addto@macro                                                    \n"
216 "  %% define empty templates for all commands taking text:                                 \n"
217 "  \\gdef\\gplbacktext{}%%                                                                 \n"
218 "  \\gdef\\gplfronttext{}%%                                                                \n"
219 "  \\makeatother                                                                           \n"
220 "  \\ifGPblacktext                                                                         \n"
221 "    %% no textcolor at all                                                                \n"
222 "    \\def\\colorrgb#1{}%%                                                                 \n"
223 "    \\def\\colorgray#1{}%%                                                                \n"
224 "  \\else                                                                                  \n"
225 "    %% gray or color?                                                                     \n"
226 "    \\ifGPcolor                                                                           \n"
227 "      \\def\\colorrgb#1{\\color[rgb]{#1}}%%                                               \n"
228 "      \\def\\colorgray#1{\\color[gray]{#1}}%%                                             \n"
229 "      \\expandafter\\def\\csname LTw\\endcsname{\\color{white}}%%                         \n"
230 "      \\expandafter\\def\\csname LTb\\endcsname{\\color{black}}%%                         \n"
231 "      \\expandafter\\def\\csname LTa\\endcsname{\\color{black}}%%                         \n"
232 "      \\expandafter\\def\\csname LT0\\endcsname{\\color[rgb]{1,0,0}}%%                    \n"
233 "      \\expandafter\\def\\csname LT1\\endcsname{\\color[rgb]{0,1,0}}%%                    \n"
234 "      \\expandafter\\def\\csname LT2\\endcsname{\\color[rgb]{0,0,1}}%%                    \n"
235 "      \\expandafter\\def\\csname LT3\\endcsname{\\color[rgb]{1,0,1}}%%                    \n"
236 "      \\expandafter\\def\\csname LT4\\endcsname{\\color[rgb]{0,1,1}}%%                    \n"
237 "      \\expandafter\\def\\csname LT5\\endcsname{\\color[rgb]{1,1,0}}%%                    \n"
238 "      \\expandafter\\def\\csname LT6\\endcsname{\\color[rgb]{0,0,0}}%%                    \n"
239 "      \\expandafter\\def\\csname LT7\\endcsname{\\color[rgb]{1,0.3,0}}%%                  \n"
240 "      \\expandafter\\def\\csname LT8\\endcsname{\\color[rgb]{0.5,0.5,0.5}}%%              \n"
241 "    \\else                                                                                \n"
242 "      %% gray                                                                             \n"
243 "      \\def\\colorrgb#1{\\color{black}}%%                                                 \n"
244 "      \\def\\colorgray#1{\\color[gray]{#1}}%%                                             \n"
245 "      \\expandafter\\def\\csname LTw\\endcsname{\\color{white}}%                          \n"
246 "      \\expandafter\\def\\csname LTb\\endcsname{\\color{black}}%                          \n"
247 "      \\expandafter\\def\\csname LTa\\endcsname{\\color{black}}%                          \n"
248 "      \\expandafter\\def\\csname LT0\\endcsname{\\color{black}}%                          \n"
249 "      \\expandafter\\def\\csname LT1\\endcsname{\\color{black}}%                          \n"
250 "      \\expandafter\\def\\csname LT2\\endcsname{\\color{black}}%                          \n"
251 "      \\expandafter\\def\\csname LT3\\endcsname{\\color{black}}%                          \n"
252 "      \\expandafter\\def\\csname LT4\\endcsname{\\color{black}}%                          \n"
253 "      \\expandafter\\def\\csname LT5\\endcsname{\\color{black}}%                          \n"
254 "      \\expandafter\\def\\csname LT6\\endcsname{\\color{black}}%                          \n"
255 "      \\expandafter\\def\\csname LT7\\endcsname{\\color{black}}%                          \n"
256 "      \\expandafter\\def\\csname LT8\\endcsname{\\color{black}}%                          \n"
257 "    \\fi                                                                                  \n"
258 "  \\fi                                                                                    \n"
259 "  \\setlength{\\unitlength}{1pt}%                                                         \n";
261 static char const postamble1[] =
262 "    }%%                                                                                    \n"
263 "    \\gplgaddtomacro\\gplfronttext{%                                                       \n"
264 "    }%%                                                                                    \n"
265 "    \\gplbacktext                                                                          \n";
267 static char const postamble2[] =
268 "    \\gplfronttext                                                                         \n"
269 "  \\end{picture}%                                                                          \n"
270 "\\endgroup                                                                                 \n";
272 void
273 PDFLaTeXRenderer::writePreamble()
275     fprintf(_stream, "%s", preamble);
277 void
278 PDFLaTeXRenderer::writePostamble()
280     fprintf(_stream, "%s", postamble1);
282     // strip pathname on windows, as it is probably desired. It is not possible to work without paths on windows yet. (bug)
283 #ifdef WIN32
284     gchar *figurefile = g_path_get_basename(_filename);
285 #else
286     gchar *figurefile = g_strdup(_filename);
287 #endif
288     fprintf(_stream, "      \\put(0,0){\\includegraphics{%s.pdf}}%%\n", figurefile);
289     g_free(figurefile);
291     fprintf(_stream, "%s", postamble2);
294 void
295 PDFLaTeXRenderer::sp_group_render(SPItem *item)
297     SPGroup *group = SP_GROUP(item);
299     GSList *l = g_slist_reverse(group->childList(false));
300     while (l) {
301         SPObject *o = SP_OBJECT (l->data);
302         if (SP_IS_ITEM(o)) {
303             renderItem (SP_ITEM (o));
304         }
305         l = g_slist_remove (l, o);
306     }
309 void
310 PDFLaTeXRenderer::sp_use_render(SPItem *item)
312 /*
313     bool translated = false;
314     SPUse *use = SP_USE(item);
316     if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
317         Geom::Matrix tp(Geom::Translate(use->x.computed, use->y.computed));
318         ctx->pushState();
319         ctx->transform(&tp);
320         translated = true;
321     }
323     if (use->child && SP_IS_ITEM(use->child)) {
324         renderItem(SP_ITEM(use->child));
325     }
327     if (translated) {
328         ctx->popState();
329     }
330 */
333 void
334 PDFLaTeXRenderer::sp_text_render(SPItem *item)
336     SPText *textobj = SP_TEXT (item);
338     push_transform(sp_item_i2doc_affine(item));
340     gchar *str = sp_te_get_string_multiline(item);
341     Geom::Point pos = textobj->attributes.firstXY() * transform();
342     gchar *alignment = "lb";
344     pop_transform();
346     // write to LaTeX
347     Inkscape::SVGOStringStream os;
349     os << "\\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){\\makebox(0,0)[" << alignment << "]{\\strut{}" << str << "}}%%\n";
351     fprintf(_stream, "%s", os.str().c_str());
354 void
355 PDFLaTeXRenderer::sp_flowtext_render(SPItem *item)
357 /*    SPFlowtext *group = SP_FLOWTEXT(item);
359     // write to LaTeX
360     Inkscape::SVGOStringStream os;
362     os << "  \\begin{picture}(" << _width << "," << _height << ")%%\n";
363     os << "    \\gplgaddtomacro\\gplbacktext{%%\n";
364     os << "      \\csname LTb\\endcsname%%\n";
365     os << "\\put(0,0){\\makebox(0,0)[lb]{\\strut{}Position}}%%\n";
367     fprintf(_stream, "%s", os.str().c_str());
368 */
371 void
372 PDFLaTeXRenderer::sp_root_render(SPItem *item)
374     SPRoot *root = SP_ROOT(item);
376 //    ctx->pushState();
377 //    setStateForItem(ctx, item);
378     Geom::Matrix tempmat (root->c2p);
379     push_transform(tempmat);
380     sp_group_render(item);
381     pop_transform();
384 void
385 PDFLaTeXRenderer::sp_item_invoke_render(SPItem *item)
387     // Check item's visibility
388     if (item->isHidden()) {
389         return;
390     }
392     g_message("hier?");
393     if (SP_IS_ROOT(item)) {
394         TRACE(("root\n"));
395         return sp_root_render(item);
396     } else if (SP_IS_GROUP(item)) {
397         TRACE(("group\n"));
398         return sp_group_render(item);
399     } else if (SP_IS_USE(item)) {
400         TRACE(("use begin---\n"));
401         sp_use_render(item);
402         TRACE(("---use end\n"));
403     } else if (SP_IS_TEXT(item)) {
404         TRACE(("text\n"));
405         return sp_text_render(item);
406     } else if (SP_IS_FLOWTEXT(item)) {
407         TRACE(("flowtext\n"));
408         return sp_flowtext_render(item);
409     }
410     // We are not interested in writing the other SPItem types to LaTeX
413 void
414 PDFLaTeXRenderer::setStateForItem(SPItem const *item)
416 /*
417     SPStyle const *style = SP_OBJECT_STYLE(item);
418     ctx->setStateForStyle(style);
420     CairoRenderState *state = ctx->getCurrentState();
421     state->clip_path = item->clip_ref->getObject();
422     state->mask = item->mask_ref->getObject();
423     state->item_transform = Geom::Matrix (item->transform);
425     // If parent_has_userspace is true the parent state's transform
426     // has to be used for the mask's/clippath's context.
427     // This is so because we use the image's/(flow)text's transform for positioning
428     // instead of explicitly specifying it and letting the renderer do the
429     // transformation before rendering the item.
430     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item) || SP_IS_IMAGE(item))
431         state->parent_has_userspace = TRUE;
432     TRACE(("setStateForItem opacity: %f\n", state->opacity));
433 */
436 void
437 PDFLaTeXRenderer::renderItem(SPItem *item)
439 //    ctx->pushState();
440 //    setStateForItem(ctx, item);
442 //    CairoRenderState *state = ctx->getCurrentState();
443 //    state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 );
445     // Draw item on a temporary surface so a mask, clip path, or opacity can be applied to it.
446 //    if (state->need_layer) {
447 //        state->merge_opacity = FALSE;
448 //        ctx->pushLayer();
449 //    }
450     Geom::Matrix tempmat (item->transform);
451 //    ctx->transform(&tempmat);
452     sp_item_invoke_render(item);
454 //    if (state->need_layer)
455 //        ctx->popLayer();
457 //    ctx->popState();
460 bool
461 PDFLaTeXRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem *base)
463 // The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument !
465     if (!base)
466         base = SP_ITEM(sp_document_root(doc));
468     NRRect d;
469     if (pageBoundingBox) {
470         d.x0 = d.y0 = 0;
471         d.x1 = ceil(sp_document_width(doc));
472         d.y1 = ceil(sp_document_height(doc));
473     } else {
474         sp_item_invoke_bbox(base, &d, sp_item_i2d_affine(base), TRUE, SPItem::RENDERING_BBOX);
475     }
477     // convert from px to pt
478     push_transform( Geom::Scale(PT_PER_PX, PT_PER_PX) );
480     if (!pageBoundingBox)
481     {
482         double high = sp_document_height(doc);
484         push_transform( Geom::Translate( -d.x0,
485                                          -d.y0 ) );
486     }
488     // flip y-axis
489     push_transform( Geom::Scale(1,-1) * Geom::Translate(0, sp_document_height(doc)) );
491     _width = (d.x1-d.x0) * PT_PER_PX;
492     _height = (d.y1-d.y0) * PT_PER_PX;
494     // write the info to LaTeX
495     Inkscape::SVGOStringStream os;
497     os << "  \\begin{picture}(" << _width << "," << _height << ")%%\n";
498     os << "    \\gplgaddtomacro\\gplbacktext{%%\n";
499     os << "      \\csname LTb\\endcsname%%\n";
500     os << "\\put(0,0){\\makebox(0,0)[lb]{\\strut{}0,0}}%%\n";
502     fprintf(_stream, "%s", os.str().c_str());
504     return true;
507 Geom::Matrix const &
508 PDFLaTeXRenderer::transform()
510     return _transform_stack.top();
513 void
514 PDFLaTeXRenderer::push_transform(Geom::Matrix const &tr)
516     if(_transform_stack.size()){
517         Geom::Matrix tr_top = _transform_stack.top();
518         _transform_stack.push(tr * tr_top);
519     } else {
520         _transform_stack.push(tr);
521     }
524 void
525 PDFLaTeXRenderer::pop_transform()
527     _transform_stack.pop();
530 /*
531 #include "macros.h" // SP_PRINT_*
533 // Apply an SVG clip path
534 void
535 PDFLaTeXRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
537     g_assert( ctx != NULL && ctx->_is_valid );
539     if (cp == NULL)
540         return;
542     CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode();
543     ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP);
545     Geom::Matrix saved_ctm;
546     if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
547         //SP_PRINT_DRECT("clipd", cp->display->bbox);
548         NRRect clip_bbox(cp->display->bbox);
549         Geom::Matrix t(Geom::Scale(clip_bbox.x1 - clip_bbox.x0, clip_bbox.y1 - clip_bbox.y0));
550         t[4] = clip_bbox.x0;
551         t[5] = clip_bbox.y0;
552         t *= ctx->getCurrentState()->transform;
553         ctx->getTransform(&saved_ctm);
554         ctx->setTransform(&t);
555     }
557     TRACE(("BEGIN clip\n"));
558     SPObject *co = SP_OBJECT(cp);
559     for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
560         if (SP_IS_ITEM(child)) {
561             SPItem *item = SP_ITEM(child);
563             // combine transform of the item in clippath and the item using clippath:
564             Geom::Matrix tempmat (item->transform);
565             tempmat = tempmat * (ctx->getCurrentState()->item_transform);
567             // render this item in clippath
568             ctx->pushState();
569             ctx->transform(&tempmat);
570             setStateForItem(ctx, item);
571             sp_item_invoke_render(item, ctx);
572             ctx->popState();
573         }
574     }
575     TRACE(("END clip\n"));
577     // do clipping only if this was the first call to applyClipPath
578     if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH
579         && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL)
580         cairo_clip(ctx->_cr);
582     if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX)
583         ctx->setTransform(&saved_ctm);
585     ctx->setRenderMode(saved_mode);
588 // Apply an SVG mask
589 void
590 PDFLaTeXRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask)
592     g_assert( ctx != NULL && ctx->_is_valid );
594     if (mask == NULL)
595         return;
597     //SP_PRINT_DRECT("maskd", &mask->display->bbox);
598     NRRect mask_bbox(mask->display->bbox);
599     // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
600     if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) {
601         Geom::Matrix t(Geom::Scale(mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0));
602         t[4] = mask_bbox.x0;
603         t[5] = mask_bbox.y0;
604         t *= ctx->getCurrentState()->transform;
605         ctx->setTransform(&t);
606     }
608     // Clip mask contents... but...
609     // The mask's bounding box is the "geometric bounding box" which doesn't allow for
610     // filters which extend outside the bounding box. So don't clip.
611     // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
613     ctx->pushState();
615     TRACE(("BEGIN mask\n"));
616     SPObject *co = SP_OBJECT(mask);
617     for (SPObject *child = sp_object_first_child(co) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
618         if (SP_IS_ITEM(child)) {
619             SPItem *item = SP_ITEM(child);
620             renderItem(ctx, item);
621         }
622     }
623     TRACE(("END mask\n"));
625     ctx->popState();
627 */
629 }  /* namespace Internal */
630 }  /* namespace Extension */
631 }  /* namespace Inkscape */
633 #undef TRACE
635 /* End of GNU GPL code */
637 /*
638   Local Variables:
639   mode:c++
640   c-file-style:"stroustrup"
641   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
642   indent-tabs-mode:nil
643   fill-column:99
644   End:
645 */
646 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :