Code

Updating to current trunk
[inkscape.git] / src / libnrtype / Layout-TNG-Input.cpp
1 /*
2  * Inkscape::Text::Layout - text layout engine input functions
3  *
4  * Authors:
5  *   Richard Hughes <cyreve@users.sf.net>
6  *
7  * Copyright (C) 2005 Richard Hughes
8  *
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
12 #define PANGO_ENABLE_ENGINE
14 #include <gtk/gtkversion.h>
15 #include "Layout-TNG.h"
16 #include "style.h"
17 #include "svg/svg-length.h"
18 #include "sp-object.h"
19 #include "sp-string.h"
20 #include "FontFactory.h"
22 #include "text-editing.h" // for inputTruncated()
24 namespace Inkscape {
25 namespace Text {
27 void Layout::_clearInputObjects()
28 {
29     for(std::vector<InputStreamItem*>::iterator it = _input_stream.begin() ; it != _input_stream.end() ; it++)
30         delete *it;
31     _input_stream.clear();
32     _input_wrap_shapes.clear();
33 }
35 // this function does nothing more than store all its parameters for future reference
36 void Layout::appendText(Glib::ustring const &text, SPStyle *style, void *source_cookie, OptionalTextTagAttrs const *optional_attributes, unsigned optional_attributes_offset, Glib::ustring::const_iterator text_begin, Glib::ustring::const_iterator text_end)
37 {
38     if (style == NULL) return;
40     InputStreamTextSource *new_source = new InputStreamTextSource;
42     new_source->source_cookie = source_cookie;
43     new_source->text = &text;
44     new_source->text_begin = text_begin;
45     new_source->text_end = text_end;
46     new_source->style = style;
47     sp_style_ref(style);
49     new_source->text_length = 0;
50     for ( ; text_begin != text_end && text_begin != text.end() ; text_begin++)
51         new_source->text_length++;        // save this because calculating the length of a UTF-8 string is expensive
53     if (optional_attributes) {
54         // we need to fill in x and y even if the text is empty so that empty paragraphs can be positioned correctly
55         _copyInputVector(optional_attributes->x, optional_attributes_offset, &new_source->x, std::max(1, new_source->text_length));
56         _copyInputVector(optional_attributes->y, optional_attributes_offset, &new_source->y, std::max(1, new_source->text_length));
57         _copyInputVector(optional_attributes->dx, optional_attributes_offset, &new_source->dx, new_source->text_length);
58         _copyInputVector(optional_attributes->dy, optional_attributes_offset, &new_source->dy, new_source->text_length);
59         _copyInputVector(optional_attributes->rotate, optional_attributes_offset, &new_source->rotate, new_source->text_length);
60         if (!optional_attributes->rotate.empty() && optional_attributes_offset >= optional_attributes->rotate.size()) {
61             SVGLength last_rotate;
62             last_rotate = 0.f;
63             for (std::vector<SVGLength>::const_iterator it = optional_attributes->rotate.begin() ; it != optional_attributes->rotate.end() ; ++it)
64                 if (it->_set)
65                     last_rotate = *it;
66             new_source->rotate.resize(1, last_rotate);
67         }
68     }
69     
70     _input_stream.push_back(new_source);
71 }
73 void Layout::_copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length)
74 {
75     output_vector->clear();
76     if (input_offset >= input_vector.size()) return;
77     output_vector->reserve(std::min(max_length, input_vector.size() - input_offset));
78     while (input_offset < input_vector.size() && max_length != 0) {
79         if (!input_vector[input_offset]._set)
80             break;
81         output_vector->push_back(input_vector[input_offset]);
82         input_offset++;
83         max_length--;
84     }
85 }
87 // just save what we've been given, really
88 void Layout::appendControlCode(TextControlCode code, void *source_cookie, double width, double ascent, double descent)
89 {
90     InputStreamControlCode *new_code = new InputStreamControlCode;
92     new_code->source_cookie = source_cookie;
93     new_code->code = code;
94     new_code->width = width;
95     new_code->ascent = ascent;
96     new_code->descent = descent;
97     
98     _input_stream.push_back(new_code);
99 }
101 // more saving of the parameters
102 void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align)
104     _input_wrap_shapes.push_back(InputWrapShape());
105     _input_wrap_shapes.back().shape = shape;
106     _input_wrap_shapes.back().display_align = display_align;
109 int Layout::_enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size)
111     for (unsigned i = 0 ; i < conversion_table_size ; i++)
112         if (conversion_table[i].input == input)
113             return conversion_table[i].output;
114     return conversion_table[0].output;
117 // ***** the style format interface
118 // this doesn't include all accesses to SPStyle, only the ones that are non-trivial
120 static const float medium_font_size = 12.0;     // more of a default if all else fails than anything else
121 float Layout::InputStreamTextSource::styleComputeFontSize() const
123     return style->font_size.computed;
125     // in case the computed value's not good enough, here's some manual code held in reserve:
126     SPStyle const *this_style = style;
127     float inherit_multiplier = 1.0;
129     for ( ; ; ) {
130         if (this_style->font_size.set && !this_style->font_size.inherit) {
131             switch (this_style->font_size.type) {
132                 case SP_FONT_SIZE_LITERAL: {
133                     switch(this_style->font_size.value) {   // these multipliers are straight out of the CSS spec
134                             case SP_CSS_FONT_SIZE_XX_SMALL: return medium_font_size * inherit_multiplier * (3.0/5.0);
135                             case SP_CSS_FONT_SIZE_X_SMALL:  return medium_font_size * inherit_multiplier * (3.0/4.0);
136                             case SP_CSS_FONT_SIZE_SMALL:    return medium_font_size * inherit_multiplier * (8.0/9.0);
137                         default:
138                             case SP_CSS_FONT_SIZE_MEDIUM:   return medium_font_size * inherit_multiplier;
139                             case SP_CSS_FONT_SIZE_LARGE:    return medium_font_size * inherit_multiplier * (6.0/5.0);
140                             case SP_CSS_FONT_SIZE_X_LARGE:  return medium_font_size * inherit_multiplier * (3.0/2.0);
141                             case SP_CSS_FONT_SIZE_XX_LARGE: return medium_font_size * inherit_multiplier * 2.0;
142                             case SP_CSS_FONT_SIZE_SMALLER: inherit_multiplier *= 0.84; break;   //not exactly according to spec
143                             case SP_CSS_FONT_SIZE_LARGER:  inherit_multiplier *= 1.26; break;   //not exactly according to spec
144                     }
145                     break;
146                 }
147                 case SP_FONT_SIZE_PERCENTAGE: {    // 'em' units should be in here, but aren't. Fix in style.cpp.
148                     inherit_multiplier *= this_style->font_size.value;
149                     break;
150                 }
151                 case SP_FONT_SIZE_LENGTH: {
152                     return this_style->font_size.value * inherit_multiplier;
153                 }
154             }
155         }
156         if (this_style->object == NULL || this_style->object->parent == NULL) break;
157         this_style = this_style->object->parent->style;
158         if (this_style == NULL) break;
159     }
160     return medium_font_size * inherit_multiplier;
163 static const Layout::EnumConversionItem enum_convert_spstyle_block_progression_to_direction[] = {
164     {SP_CSS_BLOCK_PROGRESSION_TB, Layout::TOP_TO_BOTTOM},
165     {SP_CSS_BLOCK_PROGRESSION_LR, Layout::LEFT_TO_RIGHT},
166     {SP_CSS_BLOCK_PROGRESSION_RL, Layout::RIGHT_TO_LEFT}};
168 static const Layout::EnumConversionItem enum_convert_spstyle_writing_mode_to_direction[] = {
169     {SP_CSS_WRITING_MODE_LR_TB, Layout::TOP_TO_BOTTOM},
170     {SP_CSS_WRITING_MODE_RL_TB, Layout::TOP_TO_BOTTOM},
171     {SP_CSS_WRITING_MODE_TB_RL, Layout::RIGHT_TO_LEFT},
172     {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}};
174 Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const
176     // this function shouldn't be necessary, but since style.cpp doesn't support
177     // shorthand properties yet, it is.
178     SPStyle const *this_style = style;
180     for ( ; ; ) {
181         if (this_style->block_progression.set)
182             return (Layout::Direction)_enum_converter(this_style->block_progression.computed, enum_convert_spstyle_block_progression_to_direction, sizeof(enum_convert_spstyle_block_progression_to_direction)/sizeof(enum_convert_spstyle_block_progression_to_direction[0]));
183         if (this_style->writing_mode.set)
184             return (Layout::Direction)_enum_converter(this_style->writing_mode.computed, enum_convert_spstyle_writing_mode_to_direction, sizeof(enum_convert_spstyle_writing_mode_to_direction)/sizeof(enum_convert_spstyle_writing_mode_to_direction[0]));
185         if (this_style->object == NULL || this_style->object->parent == NULL) break;
186         this_style = this_style->object->parent->style;
187         if (this_style == NULL) break;
188     }
189     return TOP_TO_BOTTOM;
193 static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction /*para_direction*/)
195     switch (anchor) {
196         default:
197         case SP_CSS_TEXT_ANCHOR_START:  return Layout::LEFT;
198         case SP_CSS_TEXT_ANCHOR_MIDDLE: return Layout::CENTER;
199         case SP_CSS_TEXT_ANCHOR_END:    return Layout::RIGHT;
200     }
203 Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direction para_direction, bool try_text_align) const
205     if (!try_text_align)
206         return text_anchor_to_alignment(style->text_anchor.computed, para_direction);
208     // there's no way to tell the difference between text-anchor set higher up the cascade to the default and
209     // text-anchor never set anywhere in the cascade, so in order to detect which of text-anchor or text-align
210     // to use we'll have to run up the style tree ourselves.
211     SPStyle const *this_style = style;
213     for ( ; ; ) {
214         // If both text-align and text-anchor are set at the same level, text-align takes
215         // precedence because it is the most expressive.
216         if (this_style->text_align.set) {
217             switch (style->text_align.computed) {
218                 default:
219                 case SP_CSS_TEXT_ALIGN_START:   return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
220                 case SP_CSS_TEXT_ALIGN_END:     return para_direction == LEFT_TO_RIGHT ? RIGHT : LEFT;
221                 case SP_CSS_TEXT_ALIGN_LEFT:    return LEFT;
222                 case SP_CSS_TEXT_ALIGN_RIGHT:   return RIGHT;
223                 case SP_CSS_TEXT_ALIGN_CENTER:  return CENTER;
224                 case SP_CSS_TEXT_ALIGN_JUSTIFY: return FULL;
225             }
226         }
227         if (this_style->text_anchor.set)
228             return text_anchor_to_alignment(this_style->text_anchor.computed, para_direction);
229         if (this_style->object == NULL || this_style->object->parent == NULL) break;
230         this_style = this_style->object->parent->style;
231         if (this_style == NULL) break;
232     }
233     return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
236 static const Layout::EnumConversionItem enum_convert_spstyle_style_to_pango_style[] = {
237     {SP_CSS_FONT_STYLE_NORMAL,  PANGO_STYLE_NORMAL},
238     {SP_CSS_FONT_STYLE_ITALIC,  PANGO_STYLE_ITALIC},
239     {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE}};
241 static const Layout::EnumConversionItem enum_convert_spstyle_weight_to_pango_weight[] = {
242     {SP_CSS_FONT_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL},
243     {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_ULTRALIGHT},
244     {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT},
245     {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT},
246     {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL},
247 #if GTK_CHECK_VERSION(2,6,0)
248     {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_SEMIBOLD},
249 #else 
250     {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_NORMAL},
251 #endif
252     {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_BOLD},
253     {SP_CSS_FONT_WEIGHT_BOLD,PANGO_WEIGHT_BOLD},
254     {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD},
255     {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD},
256     {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY}};
258 static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_stretch[] = {
259     {SP_CSS_FONT_STRETCH_NORMAL,          PANGO_STRETCH_NORMAL},
260     {SP_CSS_FONT_STRETCH_ULTRA_CONDENSED, PANGO_STRETCH_ULTRA_CONDENSED},
261     {SP_CSS_FONT_STRETCH_EXTRA_CONDENSED, PANGO_STRETCH_EXTRA_CONDENSED},
262     {SP_CSS_FONT_STRETCH_CONDENSED,       PANGO_STRETCH_CONDENSED},
263     {SP_CSS_FONT_STRETCH_SEMI_CONDENSED,  PANGO_STRETCH_SEMI_CONDENSED},
264     {SP_CSS_FONT_STRETCH_SEMI_EXPANDED,   PANGO_STRETCH_SEMI_EXPANDED},
265     {SP_CSS_FONT_STRETCH_EXPANDED,        PANGO_STRETCH_EXPANDED},
266     {SP_CSS_FONT_STRETCH_EXTRA_EXPANDED,  PANGO_STRETCH_EXTRA_EXPANDED},
267     {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED,  PANGO_STRETCH_ULTRA_EXPANDED}};
269 static const Layout::EnumConversionItem enum_convert_spstyle_variant_to_pango_variant[] = {
270     {SP_CSS_FONT_VARIANT_NORMAL,     PANGO_VARIANT_NORMAL},
271     {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS}};
273 font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const
275     PangoFontDescription *descr = styleGetFontDescription();
276     if (descr == NULL) return NULL;
277     font_instance *res = (font_factory::Default())->Face(descr);
278     pango_font_description_free(descr);
279     return res;
282 PangoFontDescription *Layout::InputStreamTextSource::styleGetFontDescription() const
284     if (style->text == NULL) return NULL;
285     PangoFontDescription *descr = pango_font_description_new();
286     // Pango can't cope with spaces before or after the commas - let's remove them.
287     // this code is not exactly unicode-safe, but it's similar to what's done in
288     // pango, so it's not the limiting factor
289     Glib::ustring family;
290     if (style->text->font_family.value == NULL) {
291         family = "Sans";
292     } else {
293         gchar **families = g_strsplit(style->text->font_family.value, ",", -1);
294         if (families) {
295             for (gchar **f = families ; *f ; ++f) {
296                 g_strstrip(*f);
297                 if (!family.empty()) family += ',';
298                 family += *f;
299             }
300         }
301         g_strfreev(families);
302     }
304     pango_font_description_set_family(descr,family.c_str());
305     pango_font_description_set_weight(descr,(PangoWeight)_enum_converter(style->font_weight.computed,  enum_convert_spstyle_weight_to_pango_weight,   sizeof(enum_convert_spstyle_weight_to_pango_weight)/sizeof(enum_convert_spstyle_weight_to_pango_weight[0])));
306     pango_font_description_set_style(descr,(PangoStyle)_enum_converter(style->font_style.computed,   enum_convert_spstyle_style_to_pango_style,     sizeof(enum_convert_spstyle_style_to_pango_style)/sizeof(enum_convert_spstyle_style_to_pango_style[0])));
307     pango_font_description_set_variant(descr,(PangoVariant)_enum_converter(style->font_variant.computed, enum_convert_spstyle_variant_to_pango_variant, sizeof(enum_convert_spstyle_variant_to_pango_variant)/sizeof(enum_convert_spstyle_variant_to_pango_variant[0])));
308 #ifdef USE_PANGO_WIN32
309     // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
310     pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE*72/GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))); // mandatory huge size (hinting workaround)
311     // we don't set stretch on Win32, because pango-win32 has no concept of it
312     // (Windows doesn't really provide any useful field it could use).
313     // If we did set stretch, then any text with a font-stretch attribute would
314     // end up falling back to Arial.
315 #else
316     pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround)
317     pango_font_description_set_stretch(descr,(PangoStretch)_enum_converter(style->font_stretch.computed, enum_convert_spstyle_stretch_to_pango_stretch, sizeof(enum_convert_spstyle_stretch_to_pango_stretch)/sizeof(enum_convert_spstyle_stretch_to_pango_stretch[0])));
318 #endif
319     return descr;
322 Layout::InputStreamTextSource::~InputStreamTextSource()
324     sp_style_unref(style);
327 bool
328 Layout::inputTruncated() const
330         if (!inputExists())
331                 return false;
333         // Find out the SPObject to which the last visible character corresponds:
334         Layout::iterator last = end();
335         if (last == begin()) {
336            // FIXME: this returns a wrong "not truncated" when a flowtext is entirely
337            // truncated, so there are no visible characters. But how can I find out the
338            // originator SPObject without having anything to do getSourceOfCharacter
339            // from?
340                 return false; 
341         }
342         last.prevCharacter();
343         void *source;
344         Glib::ustring::iterator offset;
345         getSourceOfCharacter(last, &source, &offset);
346         SPObject *obj = SP_OBJECT(source);
348         // if that is SPString, see if it has further characters beyond the last visible
349         if (obj && SP_IS_STRING(obj)) {
350                 Glib::ustring::iterator offset_next = offset;
351                 offset_next ++;
352                 if (offset_next != SP_STRING(obj)->string.end()) {
353                         // truncated: source SPString has next char
354                         return true;
355                 }
356         }
358         // otherwise, see if the SPObject at end() or any of its text-tree ancestors
359         // (excluding top-level SPText or SPFlowText) have a text-tree next sibling with
360         // visible text
361         if (obj) {
362                 for (SPObject *ascend = obj; 
363                                  ascend && (is_part_of_text_subtree (ascend) && !is_top_level_text_object(ascend));
364                                  ascend = SP_OBJECT_PARENT(ascend)) {
365                         if (SP_OBJECT_NEXT(ascend)) {
366                                 SPObject *next = SP_OBJECT_NEXT(ascend);
367                                 if (next && is_part_of_text_subtree(next) && has_visible_text(next)) {
368                                         // truncated: source text object has next text sibling
369                                         return true;
370                                 }
371                         }
372                 }
373         }
375         // the above works for flowed text, but not for text on path.
376         // so now, we also check if the last of the _characters, if coming from a TEXT_SOURCE,
377         // has in_glyph different from -1
378         unsigned last_char = _characters.size() - 1;
379         unsigned span_index = _characters[last_char].in_span;
380         Glib::ustring::const_iterator iter_char = _spans[span_index].input_stream_first_character;
382         if (_input_stream[_spans[span_index].in_input_stream_item]->Type() == TEXT_SOURCE) {
383                 if (_characters[last_char].in_glyph == -1) {
384                         //truncated: last char has no glyph
385                         return true;
386                 }
387         }
389         // not truncated
390         return false;
393 }//namespace Text
394 }//namespace Inkscape