1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #ifndef INCLUDED_HWPFILTER_SOURCE_DRAWING_H
21 #define INCLUDED_HWPFILTER_SOURCE_DRAWING_H
22
23 #include "precompile.h"
24
25 #include <list>
26 #include <math.h>
27
28 #include <osl/diagnose.h>
29
30 #include <comphelper/newarray.hxx>
31
32 #include "hwplib.h"
33 #include "hwpfile.h"
34 #include "hiodev.h"
35 #include "hbox.h"
36 #include "drawdef.h"
37
38 enum
39 {
40 OBJFUNC_LOAD,
41 OBJFUNC_FREE,
42 OBJFUNC_DISPLAY,
43 OBJFUNC_NITEM
44 };
45
46 enum
47 {
48 BEGIN_GRADATION = 0, LINEAR, RADIAL, CONICAL, SQUARE,
49 END_GRADATION, BITMAP_PATTERN
50 };
51
52 #define OBJRET_FILE_OK 0
53 #define OBJRET_FILE_ERROR (-1)
54 #define OBJRET_FILE_NO_PRIVATE_BLOCK (-2)
55 #define OBJRET_FILE_NO_PRIVATE_BLOCK_2 (-3)
56
57 typedef int (*HWPDOFuncType) (int, HWPDrawingObject *, int, void *, int);
58
59 #define HWPDOFunc(hdo, cmd, argp, argv) \
60 (HWPDOFuncTbl[(hdo)->type]((hdo)->type, (hdo), (cmd), (argp), (argv)))
61
62 static int HWPDOLineFunc(int, HWPDrawingObject *, int, void *, int);
63 static int HWPDORectFunc(int, HWPDrawingObject *, int, void *, int);
64 static int HWPDOEllipseFunc(int, HWPDrawingObject *, int, void *, int);
65 static int HWPDOArcFunc(int, HWPDrawingObject *, int, void *, int);
66 static int HWPDOFreeFormFunc(int, HWPDrawingObject *, int, void *, int);
67 static int HWPDOTextBoxFunc(int, HWPDrawingObject *, int, void *, int);
68 static int HWPDOEllipse2Func(int, HWPDrawingObject *, int, void *, int);
69 static int HWPDOArc2Func(int, HWPDrawingObject *, int, void *, int);
70 static int HWPDOContainerFunc(int, HWPDrawingObject *, int, void *, int);
71 static HWPPara *LoadParaList();
72
73 HWPDOFuncType HWPDOFuncTbl[] =
74 {
75 HWPDOContainerFunc,
76 HWPDOLineFunc,
77 HWPDORectFunc,
78 HWPDOEllipseFunc,
79 HWPDOArcFunc,
80 HWPDOFreeFormFunc,
81 HWPDOTextBoxFunc,
82 HWPDOFreeFormFunc,
83 HWPDOEllipse2Func,
84 HWPDOArc2Func,
85 HWPDOFreeFormFunc,
86 };
87
88 static HIODev *hmem = nullptr;
89
90 static int count = 0;
91
SetHdoParallRgn(HWPDrawingObject * hdo,int width,int height)92 static void SetHdoParallRgn(HWPDrawingObject * hdo, int width, int height)
93 {
94 hdo->property.parall.pt[0].x = 0;
95 hdo->property.parall.pt[0].y = 0;
96 hdo->property.parall.pt[1].x = width;
97 hdo->property.parall.pt[1].y = 0;
98 hdo->property.parall.pt[2].x = width;
99 hdo->property.parall.pt[2].y = height;
100 }
101
SkipPrivateBlock(int type)102 static bool SkipPrivateBlock(int type)
103 {
104 int n;
105
106 if (type == OBJRET_FILE_NO_PRIVATE_BLOCK)
107 {
108 if (!hmem->read4b(n))
109 return false;
110 if (hmem->state() || hmem->skipBlock(n) != static_cast<size_t>(n))
111 return false;
112 }
113 if (!hmem->read4b(n))
114 return false;
115 if (hmem->state())
116 return false;
117 return hmem->skipBlock(n) == static_cast<size_t>(n);
118 }
119
120 static int SizeExpected;
121 static int SizeRead;
122
ReadSizeField(int size)123 static int ReadSizeField(int size)
124 {
125 SizeExpected = size;
126 if (!hmem->read4b(SizeRead))
127 return -1;
128 if (hmem->state())
129 return -1;
130 return SizeRead;
131 }
132
SkipUnusedField(void)133 static bool SkipUnusedField(void)
134 {
135 return (SizeExpected >= SizeRead) &&
136 hmem->skipBlock(SizeRead - SizeExpected) != 0;
137 }
138
139
140 #define HDOFILE_HEADER_SIZE (2*4+16) // 16=sizeof(ZZRect)
141 #define HDOFILE_COMMON_SIZE (7*4+16+44)
142
143 #define HDOFILE_HAS_NEXT 0x01
144 #define HDOFILE_HAS_CHILD 0x02
145
LoadCommonHeader(HWPDrawingObject * hdo,unsigned short * link_info)146 static bool LoadCommonHeader(HWPDrawingObject * hdo, unsigned short * link_info)
147 {
148 uint size, common_size;
149
150 if (!hmem)
151 return false;
152 if (!hmem->read4b(size))
153 return false;
154 if (hmem->state())
155 return false;
156 if (size < HDOFILE_COMMON_SIZE)
157 return false;
158
159 common_size = HDOFILE_COMMON_SIZE;
160 unsigned short tmp16;
161 if (!hmem->read2b(tmp16))
162 return false;
163 hdo->type = tmp16;
164 if (!hmem->read2b(tmp16))
165 return false;
166 *link_info = tmp16;
167 if (!hmem->read4b(hdo->offset.x))
168 return false;
169 if (!hmem->read4b(hdo->offset.y))
170 return false;
171 if (!hmem->read4b(hdo->extent.w))
172 return false;
173 if (!hmem->read4b(hdo->extent.h))
174 return false;
175 if (!hmem->read4b(hdo->offset2.x))
176 return false;
177 if (!hmem->read4b(hdo->offset2.y))
178 return false;
179
180 if (hmem->state())
181 return false;
182
183 if (!hmem->read4b(hdo->vrect.x))
184 return false;
185 if (!hmem->read4b(hdo->vrect.y))
186 return false;
187 if (!hmem->read4b(hdo->vrect.w))
188 return false;
189 if (!hmem->read4b(hdo->vrect.h))
190 return false;
191
192 // read bare property 44 bytes
193 if (!hmem->read4b(hdo->property.line_pstyle))
194 return false;
195 if (!hmem->read4b(hdo->property.line_hstyle))
196 return false;
197 if (!hmem->read4b(hdo->property.line_tstyle))
198 return false;
199 if (!hmem->read4b(hdo->property.line_color))
200 return false;
201 unsigned int tmp32;
202 if (!hmem->read4b(tmp32))
203 return false;
204 hdo->property.line_width = static_cast<hunit>(tmp32);
205 if (!hmem->read4b(hdo->property.fill_color))
206 return false;
207 if (!hmem->read4b(hdo->property.pattern_type))
208 return false;
209 if (!hmem->read4b(hdo->property.pattern_color))
210 return false;
211 if (!hmem->read4b(tmp32))
212 return false;
213 hdo->property.hmargin = static_cast<hunit>(tmp32);
214 if (!hmem->read4b(tmp32))
215 return false;
216 hdo->property.vmargin = static_cast<hunit>(tmp32);
217 if (!hmem->read4b(hdo->property.flag))
218 return false;
219 // read rotation property 32 bytes
220 if ((size >= common_size + 32)
221 && (hdo->property.flag & HWPDO_FLAG_ROTATION))
222 {
223 if (!hmem->read4b(hdo->property.rot_originx))
224 return false;
225 if (!hmem->read4b(hdo->property.rot_originy))
226 return false;
227 for (int ii = 0; ii < 3; ++ii)
228 {
229 if (!hmem->read4b(hdo->property.parall.pt[ii].x))
230 return false;
231 if (!hmem->read4b(hdo->property.parall.pt[ii].y))
232 return false;
233 }
234 common_size += 32;
235 }
236 else
237 SetHdoParallRgn(hdo, hdo->extent.w, hdo->extent.h);
238
239 // read gradient property 28 bytes
240 if ((size >= common_size + 28) &&
241 (hdo->property.flag & HWPDO_FLAG_GRADATION))
242 {
243 if (!hmem->read4b(hdo->property.fromcolor))
244 return false;
245 if (!hmem->read4b(hdo->property.tocolor))
246 return false;
247 if (!hmem->read4b(hdo->property.gstyle))
248 return false;
249 if (!hmem->read4b(hdo->property.angle))
250 return false;
251 if (!hmem->read4b(hdo->property.center_x))
252 return false;
253 if (!hmem->read4b(hdo->property.center_y))
254 return false;
255 if (!hmem->read4b(hdo->property.nstep))
256 return false;
257 common_size += 28;
258 }
259
260 // read bitmap property 278 bytes
261 if ((size >= common_size + 278) && \
262 (hdo->property.flag & HWPDO_FLAG_BITMAP))
263 {
264 if (!hmem->read4b(hdo->property.offset1.x))
265 return false;
266 if (!hmem->read4b(hdo->property.offset1.y))
267 return false;
268 if (!hmem->read4b(hdo->property.offset2.x))
269 return false;
270 if (!hmem->read4b(hdo->property.offset2.y))
271 return false;
272 if (!hmem->readBlock(hdo->property.szPatternFile, 261))
273 return false;
274 if (!hmem->read1b(hdo->property.pictype))
275 return false;
276 common_size += 278;
277 }
278 if( ( size >= common_size + 3 ) && ( hdo->property.flag & HWPDO_FLAG_WATERMARK ) )
279 //if( ( size >= common_size ) && ( hdo->property.flag >> 20 & 0x01 ) )
280 {
281 if (size - common_size >= 5)
282 hmem->skipBlock(2);
283 unsigned char tmp8;
284 if (!hmem->read1b(tmp8))
285 return false;
286 hdo->property.luminance = tmp8;
287 if (!hmem->read1b(tmp8))
288 return false;
289 hdo->property.contrast = tmp8;
290 if (!hmem->read1b(tmp8))
291 return false;
292 hdo->property.greyscale = tmp8;
293
294 common_size += 5;
295 }
296 else
297 {
298 hdo->property.luminance = 0;
299 hdo->property.contrast = 0;
300 hdo->property.greyscale = 0;
301 }
302 hdo->property.pPara = nullptr;
303
304 if( ( size > common_size ) && (hdo->property.flag & HWPDO_FLAG_AS_TEXTBOX) )
305 {
306 hmem->skipBlock(8);
307 hdo->property.pPara = LoadParaList();
308 if( hdo->property.pPara )
309 return true;
310 else
311 return false;
312 }
313
314 if (size <= common_size)
315 return true;
316 return hmem->skipBlock(size - common_size ) != 0;
317 }
318
LoadDrawingObject(void)319 static std::unique_ptr<HWPDrawingObject> LoadDrawingObject(void)
320 {
321 HWPDrawingObject *prev = nullptr;
322 std::unique_ptr<HWPDrawingObject> hdo, head;
323
324 unsigned short link_info;
325
326 do
327 {
328 hdo.reset(new HWPDrawingObject);
329 if (!LoadCommonHeader(hdo.get(), &link_info))
330 {
331 goto error;
332 }
333 if (hdo->type < 0 || hdo->type >= HWPDO_NITEMS)
334 {
335 hdo->type = HWPDO_RECT;
336 if (!SkipPrivateBlock(OBJRET_FILE_NO_PRIVATE_BLOCK))
337 {
338 goto error;
339 }
340 }
341 else
342 {
343 switch (int res = HWPDOFunc(hdo.get(), OBJFUNC_LOAD, nullptr, 0))
344 {
345 case OBJRET_FILE_ERROR:
346 goto error;
347 case OBJRET_FILE_OK:
348 break;
349 case OBJRET_FILE_NO_PRIVATE_BLOCK:
350 case OBJRET_FILE_NO_PRIVATE_BLOCK_2:
351 if (!SkipPrivateBlock(res))
352 goto error;
353 break;
354 }
355 }
356 if (link_info & HDOFILE_HAS_CHILD)
357 {
358 hdo->child = LoadDrawingObject();
359 if (hdo->child == nullptr)
360 {
361 goto error;
362 }
363 }
364 if (prev == nullptr)
365 {
366 head = std::move(hdo);
367 prev = head.get();
368 }
369 else
370 {
371 prev->next = std::move(hdo);
372 prev = prev->next.get();
373 }
374 }
375 while (link_info & HDOFILE_HAS_NEXT);
376
377 return head;
378
379 error:
380 // drawing object can be list.
381 // hdo = current item, head = list;
382
383 if (hdo->type < 0 || hdo->type >= HWPDO_NITEMS)
384 {
385 hdo->type = HWPDO_RECT;
386 }
387 HWPDOFunc(hdo.get(), OBJFUNC_FREE, nullptr, 0);
388 hdo.reset();
389
390 if( prev )
391 {
392 prev->next = nullptr;
393 return head;
394 }
395 else
396 return nullptr;
397 }
398
399
LoadDrawingObjectBlock(Picture * pic)400 static bool LoadDrawingObjectBlock(Picture * pic)
401 {
402 int size;
403 if (!hmem->read4b(size))
404 return false;
405
406 if (hmem->state() || size < HDOFILE_HEADER_SIZE)
407 return false;
408
409 if (!hmem->read4b(pic->picinfo.picdraw.zorder))
410 return false;
411 if (!hmem->read4b(pic->picinfo.picdraw.mbrcnt))
412 return false;
413 if (!hmem->read4b(pic->picinfo.picdraw.vrect.x))
414 return false;
415 if (!hmem->read4b(pic->picinfo.picdraw.vrect.y))
416 return false;
417 if (!hmem->read4b(pic->picinfo.picdraw.vrect.w))
418 return false;
419 if (!hmem->read4b(pic->picinfo.picdraw.vrect.h))
420 return false;
421
422 if (size > HDOFILE_HEADER_SIZE &&
423 !hmem->skipBlock(size - HDOFILE_HEADER_SIZE))
424 return false;
425
426 pic->picinfo.picdraw.hdo = LoadDrawingObject().release();
427 if (pic->picinfo.picdraw.hdo == nullptr)
428 return false;
429 return true;
430 }
431
432 // object manipulation function
433 static int
HWPDODefaultFunc(int cmd)434 HWPDODefaultFunc(int cmd)
435 {
436 if (cmd == OBJFUNC_LOAD)
437 return OBJRET_FILE_NO_PRIVATE_BLOCK;
438 return OBJRET_FILE_OK;
439 }
440
441 static int
HWPDOLineFunc(int,HWPDrawingObject * hdo,int cmd,void *,int)442 HWPDOLineFunc(int /*type*/, HWPDrawingObject * hdo, int cmd, void * /*argp*/, int /*argv*/)
443 {
444 int ret = OBJRET_FILE_OK;
445 switch (cmd)
446 {
447 case OBJFUNC_LOAD:
448 if (ReadSizeField(4) < 4)
449 return OBJRET_FILE_ERROR;
450 if (!hmem->read4b(hdo->u.line_arc.flip))
451 return OBJRET_FILE_ERROR;
452 if (hmem->state())
453 return OBJRET_FILE_ERROR;
454 if (!SkipUnusedField())
455 return OBJRET_FILE_ERROR;
456 ret = OBJRET_FILE_NO_PRIVATE_BLOCK_2;
457 break;
458 default:
459 ret = HWPDODefaultFunc(cmd);
460 break;
461 }
462 return ret;
463 }
464
465
466 // rectangle
467
468 static int
HWPDORectFunc(int,HWPDrawingObject *,int cmd,void *,int)469 HWPDORectFunc(int /*type*/, HWPDrawingObject * /*hdo*/, int cmd, void * /*argp*/, int /*argv*/)
470 {
471 return HWPDODefaultFunc(cmd);
472 }
473
474
475 // ellipse
476
477 static int
HWPDOEllipseFunc(int,HWPDrawingObject *,int cmd,void *,int)478 HWPDOEllipseFunc(int /*type*/, HWPDrawingObject * /*hdo*/,
479 int cmd, void * /*argp*/, int /*argv*/)
480 {
481 return HWPDODefaultFunc(cmd);
482 }
483
484 #define WTMM(x) ((double)(x) / 1800. * 25.4)
485 static int
HWPDOEllipse2Func(int,HWPDrawingObject * hdo,int cmd,void *,int)486 HWPDOEllipse2Func(int /*type*/, HWPDrawingObject * hdo,
487 int cmd, void * /*argp*/, int /*argv*/)
488 {
489 switch (cmd)
490 {
491 case OBJFUNC_LOAD:
492 if (ReadSizeField(16) < 16)
493 return OBJRET_FILE_ERROR;
494 if (!hmem->read4b(hdo->u.arc.radial[0].x))
495 return OBJRET_FILE_ERROR;
496 if (!hmem->read4b(hdo->u.arc.radial[0].y))
497 return OBJRET_FILE_ERROR;
498 if (!hmem->read4b(hdo->u.arc.radial[1].x))
499 return OBJRET_FILE_ERROR;
500 if (!hmem->read4b(hdo->u.arc.radial[1].y))
501 return OBJRET_FILE_ERROR;
502 if (ReadSizeField(0) < 0)
503 return OBJRET_FILE_ERROR;
504 break;
505 default:
506 return HWPDODefaultFunc(cmd);
507 }
508 return OBJRET_FILE_OK;
509 }
510
511
512 // arc
513
514 static int
HWPDOArcFunc(int,HWPDrawingObject * hdo,int cmd,void *,int)515 HWPDOArcFunc(int /*type*/, HWPDrawingObject * hdo, int cmd, void * /*argp*/, int /*argv*/)
516 {
517 switch (cmd)
518 {
519 case OBJFUNC_LOAD:
520 if (ReadSizeField(4) < 4)
521 return OBJRET_FILE_ERROR;
522 if (!hmem->read4b(hdo->u.line_arc.flip))
523 return OBJRET_FILE_ERROR;
524 if (hmem->state())
525 return OBJRET_FILE_ERROR;
526 if (!SkipUnusedField())
527 return OBJRET_FILE_ERROR;
528 break;
529 default:
530 return HWPDODefaultFunc(cmd);
531 }
532 return OBJRET_FILE_OK;
533 }
534
535
536 static int
HWPDOArc2Func(int,HWPDrawingObject *,int cmd,void *,int)537 HWPDOArc2Func(int /*type*/, HWPDrawingObject * /*hdo*/, int cmd, void * /*argp*/, int /*argv*/)
538 {
539 int ret = OBJRET_FILE_OK;
540 switch (cmd)
541 {
542 case OBJFUNC_LOAD:
543 ret = OBJRET_FILE_NO_PRIVATE_BLOCK;
544 break;
545 default:
546 ret = HWPDODefaultFunc(cmd);
547 break;
548 }
549 return ret;
550 }
551
552
553 static int
HWPDOFreeFormFunc(int,HWPDrawingObject * hdo,int cmd,void *,int)554 HWPDOFreeFormFunc(int /*type*/, HWPDrawingObject * hdo,
555 int cmd, void * /*argp*/, int /*argv*/)
556 {
557 switch (cmd)
558 {
559 case OBJFUNC_LOAD:
560 {
561 hdo->u.freeform.pt = nullptr;
562 if (ReadSizeField(4) < 4)
563 return OBJRET_FILE_ERROR;
564 if (!hmem->read4b(hdo->u.freeform.npt))
565 return OBJRET_FILE_ERROR;
566 if (hmem->state())
567 return OBJRET_FILE_ERROR;
568 if (!SkipUnusedField())
569 return OBJRET_FILE_ERROR;
570
571 int size = hdo->u.freeform.npt * sizeof(ZZPoint);
572
573 if (ReadSizeField(size) < size)
574 return OBJRET_FILE_ERROR;
575 if (hdo->u.freeform.npt)
576 {
577 hdo->u.freeform.pt =
578 ::comphelper::newArray_null<ZZPoint>(hdo->u.freeform.npt);
579 if (hdo->u.freeform.pt == nullptr)
580 {
581 hdo->u.freeform.npt = 0;
582 return OBJRET_FILE_ERROR;
583 }
584 for (int ii = 0; ii < hdo->u.freeform.npt; ++ii)
585 {
586 bool bFailure = false;
587 if (!hmem->read4b(hdo->u.freeform.pt[ii].x))
588 bFailure = true;
589 if (!hmem->read4b(hdo->u.freeform.pt[ii].y))
590 bFailure = true;
591 if (hmem->state())
592 bFailure = true;
593 if (bFailure)
594 {
595 delete[]hdo->u.freeform.pt;
596 hdo->u.freeform.npt = 0;
597 return OBJRET_FILE_ERROR;
598 }
599 }
600 }
601 if (!SkipUnusedField())
602 return OBJRET_FILE_ERROR;
603 return OBJRET_FILE_OK;
604 }
605 case OBJFUNC_FREE:
606 if (hdo->u.freeform.pt)
607 delete[]hdo->u.freeform.pt;
608 break;
609 default:
610 return HWPDODefaultFunc(cmd);
611 }
612 return OBJRET_FILE_OK;
613 }
614
615
616 // text box
617
FreeParaList(HWPPara * para)618 static void FreeParaList(HWPPara * para)
619 {
620 if (para->Next())
621 FreeParaList(para->Next());
622 delete para;
623 }
624
625
LoadParaList()626 static HWPPara *LoadParaList()
627 {
628 if (!hmem)
629 return nullptr;
630
631 HWPFile *hwpf = GetCurrentDoc();
632 std::unique_ptr<HIODev> hio = hwpf->SetIODevice(std::unique_ptr<HIODev>(hmem));
633
634 std::vector< HWPPara* > plist;
635
636 hwpf->ReadParaList(plist);
637 std::unique_ptr<HIODev> orighmem = hwpf->SetIODevice(std::move(hio));
638 hmem = orighmem.release();
639
640 return plist.size()? plist.front() : nullptr;
641 }
642
643
644 static int
HWPDOTextBoxFunc(int,HWPDrawingObject * hdo,int cmd,void *,int)645 HWPDOTextBoxFunc(int /*type*/, HWPDrawingObject * hdo,
646 int cmd, void * /*argp*/, int /*argv*/)
647 {
648 switch (cmd)
649 {
650 case OBJFUNC_LOAD:
651 if (ReadSizeField(0) < 0 || !SkipUnusedField())
652 return OBJRET_FILE_ERROR;
653 if (ReadSizeField(0) < 0)
654 return OBJRET_FILE_ERROR;
655 hdo->u.textbox.h = LoadParaList();
656 return hdo->u.textbox.h ? OBJRET_FILE_OK : OBJRET_FILE_ERROR;
657 case OBJFUNC_FREE:
658 if (hdo->u.textbox.h)
659 {
660 FreeParaList(hdo->u.textbox.h);
661 hdo->u.textbox.h = nullptr;
662 }
663 break;
664 default:
665 return HWPDODefaultFunc(cmd);
666 }
667 return OBJRET_FILE_OK;
668 }
669
670
671
672 static int
HWPDOContainerFunc(int,HWPDrawingObject *,int cmd,void *,int)673 HWPDOContainerFunc(int /*type*/, HWPDrawingObject * /*hdo*/,
674 int cmd, void * /*argp*/, int /*argv*/)
675 {
676 return HWPDODefaultFunc(cmd);
677 }
678
679
HWPDrawingObject()680 HWPDrawingObject::HWPDrawingObject():
681 type(0), offset{0, 0}, offset2{0, 0}, extent{0, 0}, vrect{0, 0, 0, 0}
682 {
683 memset(&property, 0, sizeof property);
684 memset(&u, 0, sizeof u);
685 index = ++count;
686 }
687
688
~HWPDrawingObject()689 HWPDrawingObject::~HWPDrawingObject()
690 {
691 if (property.pPara)
692 FreeParaList(property.pPara);
693
694 HWPDOFunc(this, OBJFUNC_FREE, nullptr, 0);
695 }
696 #endif
697
698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
699