canvas-text.cpp revision 4d9e17d0548ca7858aa258ae757c2d3a278f8ae4
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi/*
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Canvas text
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi *
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Authors:
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Lauris Kaplinski <lauris@kaplinski.com>
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Maximilian Albert <maximilian.albert@gmail.com>
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Jon A. Cruz <jon@joncruz.org>
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi *
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Copyright (C) 2000-2002 Lauris Kaplinski
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Copyright (C) 2008 Maximilian Albert
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi *
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi * Released under GNU GPL, read the file 'COPYING' for more information
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi */
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#ifdef HAVE_CONFIG_H
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi# include "config.h"
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#endif
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#include <sstream>
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#include <string.h>
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#include "sp-canvas-util.h"
44ed9dbbfa620821ecf59a131462082f628dd0f3Stephen Hanson#include "canvas-text.h"
aed5247ff899ec457005d93dfbdb4ffd74574695Joshua M. Clulow#include "display/cairo-utils.h"
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#include "desktop.h"
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#include "color.h"
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi#include "display/sp-canvas.h"
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void sp_canvastext_class_init (SPCanvasTextClass *klass);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void sp_canvastext_init (SPCanvasText *canvastext);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void sp_canvastext_destroy (GtkObject *object);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void sp_canvastext_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void sp_canvastext_render (SPCanvasItem *item, SPCanvasBuf *buf);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic SPCanvasItemClass *parent_class_ct;
908f1e1388f616898b4e515d343c0414f2a6472esd
184cd04c26b064536977dfbb913a1240eaf6f708cthGType
908f1e1388f616898b4e515d343c0414f2a6472esdsp_canvastext_get_type (void)
14ea4bb737263733ad80a36b4f73f681c30a6b45sd{
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi static GType type = 0;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi if (!type) {
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi GTypeInfo info = {
908f1e1388f616898b4e515d343c0414f2a6472esd sizeof (SPCanvasTextClass),
e3d60c9bd991a9826cbfa63b10595d44e123b9c4Adrian Frost NULL, NULL,
e3d60c9bd991a9826cbfa63b10595d44e123b9c4Adrian Frost (GClassInitFunc) sp_canvastext_class_init,
940d71d237794874e18a0eb72f6564821a823517eschrock NULL, NULL,
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi sizeof (SPCanvasText),
13faa91230bde46da937bf33010b9accc5bdeb59sd 0,
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi (GInstanceInitFunc) sp_canvastext_init,
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi NULL
908f1e1388f616898b4e515d343c0414f2a6472esd };
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi type = g_type_register_static (SP_TYPE_CANVAS_ITEM, "SPCanvasText", &info, (GTypeFlags)0);
2eeaed14a5e2ed9bd811643ad5bffc3510ca0310robj }
ded9341448cd6e2573619c7f6fe98909bdd35ec6Hyon Kim return type;
2eeaed14a5e2ed9bd811643ad5bffc3510ca0310robj}
fc33347812f84907261f6fd501e2409da108b8d8Tom Pothier
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void
0eb822a1c0c2bea495647510b75f77f0e57633ebcindisp_canvastext_class_init (SPCanvasTextClass *klass)
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi{
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim GtkObjectClass *object_class = (GtkObjectClass *) klass;
13faa91230bde46da937bf33010b9accc5bdeb59sd SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
908f1e1388f616898b4e515d343c0414f2a6472esd
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi parent_class_ct = (SPCanvasItemClass*)g_type_class_peek_parent (klass);
908f1e1388f616898b4e515d343c0414f2a6472esd
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi object_class->destroy = sp_canvastext_destroy;
14ea4bb737263733ad80a36b4f73f681c30a6b45sd
14ea4bb737263733ad80a36b4f73f681c30a6b45sd item_class->update = sp_canvastext_update;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi item_class->render = sp_canvastext_render;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi}
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void
0eb822a1c0c2bea495647510b75f77f0e57633ebcindisp_canvastext_init (SPCanvasText *canvastext)
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi{
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->anchor_position = TEXT_ANCHOR_CENTER;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->anchor_pos_x_manual = 0;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->anchor_pos_y_manual = 0;
ded9341448cd6e2573619c7f6fe98909bdd35ec6Hyon Kim canvastext->anchor_offset_x = 0;
2eeaed14a5e2ed9bd811643ad5bffc3510ca0310robj canvastext->anchor_offset_y = 0;
2eeaed14a5e2ed9bd811643ad5bffc3510ca0310robj canvastext->rgba = 0x33337fff;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->rgba_stroke = 0xffffffff;
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim canvastext->rgba_background = 0x0000007f;
908f1e1388f616898b4e515d343c0414f2a6472esd canvastext->background = false;
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim canvastext->s[Geom::X] = canvastext->s[Geom::Y] = 0.0;
908f1e1388f616898b4e515d343c0414f2a6472esd canvastext->affine = Geom::identity();
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim canvastext->fontsize = 10.0;
940d71d237794874e18a0eb72f6564821a823517eschrock canvastext->item = NULL;
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim canvastext->desktop = NULL;
4df55fde49134f9735f84011f23a767c75e393c7Janie Lu canvastext->text = NULL;
53dbcc5939527e6d5d52d814e51e364b5e8bb532Sundeep Panicker canvastext->outline = false;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->background = false;
14ea4bb737263733ad80a36b4f73f681c30a6b45sd canvastext->border = 3; // must be a constant, and not proportional to any width, height, or fontsize to allow alignment with other text boxes
14ea4bb737263733ad80a36b4f73f681c30a6b45sd}
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void
0eb822a1c0c2bea495647510b75f77f0e57633ebcindisp_canvastext_destroy (GtkObject *object)
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi{
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi g_return_if_fail (object != NULL);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi g_return_if_fail (SP_IS_CANVASTEXT (object));
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
9dd0f810214fdc8e1af881a9a5c4b6927629ff9ecindi SPCanvasText *canvastext = SP_CANVASTEXT (object);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
602ca9ea8f9ce0933f0944601cc5d230e91a950dcth g_free(canvastext->text);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->text = NULL;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi canvastext->item = NULL;
602ca9ea8f9ce0933f0944601cc5d230e91a950dcth
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi if (GTK_OBJECT_CLASS (parent_class_ct)->destroy)
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi (* GTK_OBJECT_CLASS (parent_class_ct)->destroy) (object);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi}
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindistatic void
0eb822a1c0c2bea495647510b75f77f0e57633ebcindisp_canvastext_render (SPCanvasItem *item, SPCanvasBuf *buf)
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi{
eae2e508a8e70b1ec407b10bd068c080651bbe5ckrishnae SPCanvasText *cl = SP_CANVASTEXT (item);
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim if (!buf->ct)
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim return;
aed5247ff899ec457005d93dfbdb4ffd74574695Joshua M. Clulow
aed5247ff899ec457005d93dfbdb4ffd74574695Joshua M. Clulow cairo_set_font_size(buf->ct, cl->fontsize);
aed5247ff899ec457005d93dfbdb4ffd74574695Joshua M. Clulow
aed5247ff899ec457005d93dfbdb4ffd74574695Joshua M. Clulow if (cl->background){
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim cairo_text_extents_t extents;
44ed9dbbfa620821ecf59a131462082f628dd0f3Stephen Hanson cairo_text_extents(buf->ct, cl->text, &extents);
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim cairo_rectangle(buf->ct, item->x1 - buf->rect.left(),
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim item->y1 - buf->rect.top(),
44ed9dbbfa620821ecf59a131462082f628dd0f3Stephen Hanson item->x2 - item->x1,
44ed9dbbfa620821ecf59a131462082f628dd0f3Stephen Hanson item->y2 - item->y1);
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim ink_cairo_set_source_rgba32(buf->ct, cl->rgba_background);
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim cairo_fill(buf->ct);
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim }
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim Geom::Point s = cl->s * cl->affine;
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim double offsetx = s[Geom::X] - cl->anchor_offset_x - buf->rect.left();
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim double offsety = s[Geom::Y] - cl->anchor_offset_y - buf->rect.top();
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim cairo_move_to(buf->ct, round(offsetx), round(offsety));
ac88567a7a5bb7f01cf22cf366bc9d6203e24d7aHyon Kim cairo_text_path(buf->ct, cl->text);
03f9f63d24f0494b7d47b927090ad9045e396402Tom Pothier
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson if (cl->outline){
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson ink_cairo_set_source_rgba32(buf->ct, cl->rgba_stroke);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson cairo_set_line_width (buf->ct, 2.0);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson cairo_stroke_preserve(buf->ct);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson }
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson ink_cairo_set_source_rgba32(buf->ct, cl->rgba);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson cairo_fill(buf->ct);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson}
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hansonstatic void
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hansonsp_canvastext_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson{
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson SPCanvasText *cl = SP_CANVASTEXT (item);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson if (parent_class_ct->update)
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson (* parent_class_ct->update) (item, affine, flags);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson sp_canvas_item_reset_bounds (item);
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson
e58a33b62cd4c9a6815fd752ce58b5f389289da1Stephen Hanson cl->affine = affine;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi Geom::Point s = cl->s * affine;
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi // Point s specifies the position of the anchor, which is at the bounding box of the text itself (i.e. not at the border of the filled background rectangle)
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi // The relative position of the anchor can be set using e.g. anchor_position = TEXT_ANCHOR_LEFT
0eb822a1c0c2bea495647510b75f77f0e57633ebcindi
// Set up a temporary cairo_t to measure the text extents; it would be better to compute this in the render()
// method but update() seems to be called before so we don't have the information available when we need it
cairo_surface_t *tmp_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
cairo_t* tmp_buf = cairo_create(tmp_surface);
cairo_set_font_size(tmp_buf, cl->fontsize);
cairo_text_extents_t extents;
cairo_text_extents(tmp_buf, cl->text, &extents);
double border = cl->border;
item->x1 = s[Geom::X] + extents.x_bearing - border;
item->y1 = s[Geom::Y] + extents.y_bearing - border;
item->x2 = item->x1 + extents.width + 2*border;
item->y2 = item->y1 + extents.height + 2*border;
/* FROM: http://lists.cairographics.org/archives/cairo-bugs/2009-March/003014.html
- Glyph surfaces: In most font rendering systems, glyph surfaces
have an origin at (0,0) and a bounding box that is typically
represented as (x_bearing,y_bearing,width,height). Depending on
which way y progresses in the system, y_bearing may typically be
negative (for systems similar to cairo, with origin at top left),
or be positive (in systems like PDF with origin at bottom left).
No matter which is the case, it is important to note that
(x_bearing,y_bearing) is the coordinates of top-left of the glyph
relative to the glyph origin. That is, for example:
Scaled-glyph space:
(x_bearing,y_bearing) <-- negative numbers
+----------------+
| . |
| . |
|......(0,0) <---|-- glyph origin
| |
| |
+----------------+
(width+x_bearing,height+y_bearing)
Note the similarity of the origin to the device space. That is
exactly how we use the device_offset to represent scaled glyphs:
to use the device-space origin as the glyph origin.
*/
// adjust update region according to anchor shift
switch (cl->anchor_position){
case TEXT_ANCHOR_LEFT:
cl->anchor_offset_x = 0;
cl->anchor_offset_y = -extents.height/2;
break;
case TEXT_ANCHOR_RIGHT:
cl->anchor_offset_x = extents.width;
cl->anchor_offset_y = -extents.height/2;
break;
case TEXT_ANCHOR_BOTTOM:
cl->anchor_offset_x = extents.width/2;
cl->anchor_offset_y = 0;
break;
case TEXT_ANCHOR_TOP:
cl->anchor_offset_x = extents.width/2;
cl->anchor_offset_y = -extents.height;
break;
case TEXT_ANCHOR_ZERO:
cl->anchor_offset_x = 0;
cl->anchor_offset_y = 0;
break;
case TEXT_ANCHOR_MANUAL:
cl->anchor_offset_x = (1 + cl->anchor_pos_x_manual) * extents.width/2;
cl->anchor_offset_y = -(1 + cl->anchor_pos_y_manual) * extents.height/2;
break;
case TEXT_ANCHOR_CENTER:
default:
cl->anchor_offset_x = extents.width/2;
cl->anchor_offset_y = -extents.height/2;
break;
}
cl->anchor_offset_x += extents.x_bearing;
cl->anchor_offset_y += extents.height + extents.y_bearing;
item->x1 -= cl->anchor_offset_x;
item->x2 -= cl->anchor_offset_x;
item->y1 -= cl->anchor_offset_y;
item->y2 -= cl->anchor_offset_y;
item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)item->x2, (int)item->y2);
}
SPCanvasText *sp_canvastext_new(SPCanvasGroup *parent, SPDesktop *desktop, Geom::Point pos, gchar const *new_text)
{
// Pos specifies the position of the anchor, which is at the bounding box of the text itself (i.e. not at the border of the filled background rectangle)
// The relative position of the anchor can be set using e.g. anchor_position = TEXT_ANCHOR_LEFT
SPCanvasItem *item = sp_canvas_item_new(parent, SP_TYPE_CANVASTEXT, NULL);
SPCanvasText *ct = SP_CANVASTEXT(item);
ct->desktop = desktop;
ct->s = pos;
g_free(ct->text);
ct->text = g_strdup(new_text);
return ct;
}
void
sp_canvastext_set_rgba32 (SPCanvasText *ct, guint32 rgba, guint32 rgba_stroke)
{
g_return_if_fail (ct != NULL);
g_return_if_fail (SP_IS_CANVASTEXT (ct));
if (rgba != ct->rgba || rgba_stroke != ct->rgba_stroke) {
ct->rgba = rgba;
ct->rgba_stroke = rgba_stroke;
SPCanvasItem *item = SP_CANVAS_ITEM (ct);
sp_canvas_item_request_update( item );
}
}
#define EPSILON 1e-6
#define DIFFER(a,b) (fabs ((a) - (b)) > EPSILON)
void
sp_canvastext_set_coords (SPCanvasText *ct, gdouble x0, gdouble y0)
{
sp_canvastext_set_coords(ct, Geom::Point(x0, y0));
}
void
sp_canvastext_set_coords (SPCanvasText *ct, const Geom::Point start)
{
Geom::Point pos = ct->desktop->doc2dt(start);
g_return_if_fail (ct != NULL);
g_return_if_fail (SP_IS_CANVASTEXT (ct));
if (DIFFER (pos[0], ct->s[Geom::X]) || DIFFER (pos[1], ct->s[Geom::Y])) {
ct->s[Geom::X] = pos[0];
ct->s[Geom::Y] = pos[1];
sp_canvas_item_request_update (SP_CANVAS_ITEM (ct));
}
}
void
sp_canvastext_set_text (SPCanvasText *ct, gchar const * new_text)
{
g_free (ct->text);
ct->text = g_strdup(new_text);
sp_canvas_item_request_update (SP_CANVAS_ITEM (ct));
}
void
sp_canvastext_set_number_as_text (SPCanvasText *ct, int num)
{
std::ostringstream number;
number << num;
sp_canvastext_set_text(ct, number.str().c_str());
}
void
sp_canvastext_set_fontsize (SPCanvasText *ct, double size)
{
ct->fontsize = size;
}
void
sp_canvastext_set_anchor_manually (SPCanvasText *ct, double anchor_x, double anchor_y)
{
ct->anchor_pos_x_manual = anchor_x;
ct->anchor_pos_y_manual = anchor_y;
ct->anchor_position = TEXT_ANCHOR_MANUAL;
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :