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 #if !PANGO_VERSION_CHECK(1,24,0)
23 #define PANGO_WEIGHT_THIN static_cast<PangoWeight>(100)
24 #define PANGO_WEIGHT_BOOK static_cast<PangoWeight>(380)
25 #define PANGO_WEIGHT_MEDIUM static_cast<PangoWeight>(500)
26 #define PANGO_WEIGHT_ULTRAHEAVY static_cast<PangoWeight>(1000)
27 #endif
29 namespace Inkscape {
30 namespace Text {
32 void Layout::_clearInputObjects()
33 {
34 for(std::vector<InputStreamItem*>::iterator it = _input_stream.begin() ; it != _input_stream.end() ; it++)
35 delete *it;
36 _input_stream.clear();
37 _input_wrap_shapes.clear();
38 }
40 // this function does nothing more than store all its parameters for future reference
41 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)
42 {
43 if (style == NULL) return;
45 InputStreamTextSource *new_source = new InputStreamTextSource;
47 new_source->source_cookie = source_cookie;
48 new_source->text = &text;
49 new_source->text_begin = text_begin;
50 new_source->text_end = text_end;
51 new_source->style = style;
52 sp_style_ref(style);
54 new_source->text_length = 0;
55 for ( ; text_begin != text_end && text_begin != text.end() ; text_begin++)
56 new_source->text_length++; // save this because calculating the length of a UTF-8 string is expensive
58 if (optional_attributes) {
59 // we need to fill in x and y even if the text is empty so that empty paragraphs can be positioned correctly
60 _copyInputVector(optional_attributes->x, optional_attributes_offset, &new_source->x, std::max(1, new_source->text_length));
61 _copyInputVector(optional_attributes->y, optional_attributes_offset, &new_source->y, std::max(1, new_source->text_length));
62 _copyInputVector(optional_attributes->dx, optional_attributes_offset, &new_source->dx, new_source->text_length);
63 _copyInputVector(optional_attributes->dy, optional_attributes_offset, &new_source->dy, new_source->text_length);
64 _copyInputVector(optional_attributes->rotate, optional_attributes_offset, &new_source->rotate, new_source->text_length);
65 if (!optional_attributes->rotate.empty() && optional_attributes_offset >= optional_attributes->rotate.size()) {
66 SVGLength last_rotate;
67 last_rotate = 0.f;
68 for (std::vector<SVGLength>::const_iterator it = optional_attributes->rotate.begin() ; it != optional_attributes->rotate.end() ; ++it)
69 if (it->_set)
70 last_rotate = *it;
71 new_source->rotate.resize(1, last_rotate);
72 }
73 }
75 _input_stream.push_back(new_source);
76 }
78 void Layout::_copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length)
79 {
80 output_vector->clear();
81 if (input_offset >= input_vector.size()) return;
82 output_vector->reserve(std::min(max_length, input_vector.size() - input_offset));
83 while (input_offset < input_vector.size() && max_length != 0) {
84 if (!input_vector[input_offset]._set)
85 break;
86 output_vector->push_back(input_vector[input_offset]);
87 input_offset++;
88 max_length--;
89 }
90 }
92 // just save what we've been given, really
93 void Layout::appendControlCode(TextControlCode code, void *source_cookie, double width, double ascent, double descent)
94 {
95 InputStreamControlCode *new_code = new InputStreamControlCode;
97 new_code->source_cookie = source_cookie;
98 new_code->code = code;
99 new_code->width = width;
100 new_code->ascent = ascent;
101 new_code->descent = descent;
103 _input_stream.push_back(new_code);
104 }
106 // more saving of the parameters
107 void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align)
108 {
109 _input_wrap_shapes.push_back(InputWrapShape());
110 _input_wrap_shapes.back().shape = shape;
111 _input_wrap_shapes.back().display_align = display_align;
112 }
114 int Layout::_enum_converter(int input, EnumConversionItem const *conversion_table, unsigned conversion_table_size)
115 {
116 for (unsigned i = 0 ; i < conversion_table_size ; i++)
117 if (conversion_table[i].input == input)
118 return conversion_table[i].output;
119 return conversion_table[0].output;
120 }
122 // ***** the style format interface
123 // this doesn't include all accesses to SPStyle, only the ones that are non-trivial
125 static const float medium_font_size = 12.0; // more of a default if all else fails than anything else
126 float Layout::InputStreamTextSource::styleComputeFontSize() const
127 {
128 return style->font_size.computed;
130 // in case the computed value's not good enough, here's some manual code held in reserve:
131 SPStyle const *this_style = style;
132 float inherit_multiplier = 1.0;
134 for ( ; ; ) {
135 if (this_style->font_size.set && !this_style->font_size.inherit) {
136 switch (this_style->font_size.type) {
137 case SP_FONT_SIZE_LITERAL: {
138 switch(this_style->font_size.value) { // these multipliers are straight out of the CSS spec
139 case SP_CSS_FONT_SIZE_XX_SMALL: return medium_font_size * inherit_multiplier * (3.0/5.0);
140 case SP_CSS_FONT_SIZE_X_SMALL: return medium_font_size * inherit_multiplier * (3.0/4.0);
141 case SP_CSS_FONT_SIZE_SMALL: return medium_font_size * inherit_multiplier * (8.0/9.0);
142 default:
143 case SP_CSS_FONT_SIZE_MEDIUM: return medium_font_size * inherit_multiplier;
144 case SP_CSS_FONT_SIZE_LARGE: return medium_font_size * inherit_multiplier * (6.0/5.0);
145 case SP_CSS_FONT_SIZE_X_LARGE: return medium_font_size * inherit_multiplier * (3.0/2.0);
146 case SP_CSS_FONT_SIZE_XX_LARGE: return medium_font_size * inherit_multiplier * 2.0;
147 case SP_CSS_FONT_SIZE_SMALLER: inherit_multiplier *= 0.84; break; //not exactly according to spec
148 case SP_CSS_FONT_SIZE_LARGER: inherit_multiplier *= 1.26; break; //not exactly according to spec
149 }
150 break;
151 }
152 case SP_FONT_SIZE_PERCENTAGE: { // 'em' units should be in here, but aren't. Fix in style.cpp.
153 inherit_multiplier *= this_style->font_size.value;
154 break;
155 }
156 case SP_FONT_SIZE_LENGTH: {
157 return this_style->font_size.value * inherit_multiplier;
158 }
159 }
160 }
161 if (this_style->object == NULL || this_style->object->parent == NULL) break;
162 this_style = this_style->object->parent->style;
163 if (this_style == NULL) break;
164 }
165 return medium_font_size * inherit_multiplier;
166 }
168 static const Layout::EnumConversionItem enum_convert_spstyle_block_progression_to_direction[] = {
169 {SP_CSS_BLOCK_PROGRESSION_TB, Layout::TOP_TO_BOTTOM},
170 {SP_CSS_BLOCK_PROGRESSION_LR, Layout::LEFT_TO_RIGHT},
171 {SP_CSS_BLOCK_PROGRESSION_RL, Layout::RIGHT_TO_LEFT}};
173 static const Layout::EnumConversionItem enum_convert_spstyle_writing_mode_to_direction[] = {
174 {SP_CSS_WRITING_MODE_LR_TB, Layout::TOP_TO_BOTTOM},
175 {SP_CSS_WRITING_MODE_RL_TB, Layout::TOP_TO_BOTTOM},
176 {SP_CSS_WRITING_MODE_TB_RL, Layout::RIGHT_TO_LEFT},
177 {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}};
179 Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const
180 {
181 // this function shouldn't be necessary, but since style.cpp doesn't support
182 // shorthand properties yet, it is.
183 SPStyle const *this_style = style;
185 for ( ; ; ) {
186 if (this_style->block_progression.set)
187 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]));
188 if (this_style->writing_mode.set)
189 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]));
190 if (this_style->object == NULL || this_style->object->parent == NULL) break;
191 this_style = this_style->object->parent->style;
192 if (this_style == NULL) break;
193 }
194 return TOP_TO_BOTTOM;
196 }
198 static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction /*para_direction*/)
199 {
200 switch (anchor) {
201 default:
202 case SP_CSS_TEXT_ANCHOR_START: return Layout::LEFT;
203 case SP_CSS_TEXT_ANCHOR_MIDDLE: return Layout::CENTER;
204 case SP_CSS_TEXT_ANCHOR_END: return Layout::RIGHT;
205 }
206 }
208 Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direction para_direction, bool try_text_align) const
209 {
210 if (!try_text_align)
211 return text_anchor_to_alignment(style->text_anchor.computed, para_direction);
213 // there's no way to tell the difference between text-anchor set higher up the cascade to the default and
214 // text-anchor never set anywhere in the cascade, so in order to detect which of text-anchor or text-align
215 // to use we'll have to run up the style tree ourselves.
216 SPStyle const *this_style = style;
218 for ( ; ; ) {
219 // If both text-align and text-anchor are set at the same level, text-align takes
220 // precedence because it is the most expressive.
221 if (this_style->text_align.set) {
222 switch (style->text_align.computed) {
223 default:
224 case SP_CSS_TEXT_ALIGN_START: return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
225 case SP_CSS_TEXT_ALIGN_END: return para_direction == LEFT_TO_RIGHT ? RIGHT : LEFT;
226 case SP_CSS_TEXT_ALIGN_LEFT: return LEFT;
227 case SP_CSS_TEXT_ALIGN_RIGHT: return RIGHT;
228 case SP_CSS_TEXT_ALIGN_CENTER: return CENTER;
229 case SP_CSS_TEXT_ALIGN_JUSTIFY: return FULL;
230 }
231 }
232 if (this_style->text_anchor.set)
233 return text_anchor_to_alignment(this_style->text_anchor.computed, para_direction);
234 if (this_style->object == NULL || this_style->object->parent == NULL) break;
235 this_style = this_style->object->parent->style;
236 if (this_style == NULL) break;
237 }
238 return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
239 }
241 static const Layout::EnumConversionItem enum_convert_spstyle_style_to_pango_style[] = {
242 {SP_CSS_FONT_STYLE_NORMAL, PANGO_STYLE_NORMAL},
243 {SP_CSS_FONT_STYLE_ITALIC, PANGO_STYLE_ITALIC},
244 {SP_CSS_FONT_STYLE_OBLIQUE, PANGO_STYLE_OBLIQUE}};
246 static const Layout::EnumConversionItem enum_convert_spstyle_weight_to_pango_weight[] = {
247 // NB: The Pango web page calls 500 "the normal font" but both CSS2 and the Pango
248 // enumeration define 400 as normal.
249 {SP_CSS_FONT_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL},
250 {SP_CSS_FONT_WEIGHT_BOLD,PANGO_WEIGHT_BOLD},
251 {SP_CSS_FONT_WEIGHT_100, PANGO_WEIGHT_THIN},
252 {SP_CSS_FONT_WEIGHT_200, PANGO_WEIGHT_ULTRALIGHT},
253 {SP_CSS_FONT_WEIGHT_300, PANGO_WEIGHT_LIGHT},
254 {SP_CSS_FONT_WEIGHT_400, PANGO_WEIGHT_NORMAL},
255 {SP_CSS_FONT_WEIGHT_500, PANGO_WEIGHT_MEDIUM},
256 {SP_CSS_FONT_WEIGHT_600, PANGO_WEIGHT_SEMIBOLD},
257 {SP_CSS_FONT_WEIGHT_700, PANGO_WEIGHT_BOLD},
258 {SP_CSS_FONT_WEIGHT_800, PANGO_WEIGHT_ULTRABOLD},
259 {SP_CSS_FONT_WEIGHT_900, PANGO_WEIGHT_HEAVY}};
261 static const Layout::EnumConversionItem enum_convert_spstyle_stretch_to_pango_stretch[] = {
262 {SP_CSS_FONT_STRETCH_NORMAL, PANGO_STRETCH_NORMAL},
263 {SP_CSS_FONT_STRETCH_ULTRA_CONDENSED, PANGO_STRETCH_ULTRA_CONDENSED},
264 {SP_CSS_FONT_STRETCH_EXTRA_CONDENSED, PANGO_STRETCH_EXTRA_CONDENSED},
265 {SP_CSS_FONT_STRETCH_CONDENSED, PANGO_STRETCH_CONDENSED},
266 {SP_CSS_FONT_STRETCH_SEMI_CONDENSED, PANGO_STRETCH_SEMI_CONDENSED},
267 {SP_CSS_FONT_STRETCH_SEMI_EXPANDED, PANGO_STRETCH_SEMI_EXPANDED},
268 {SP_CSS_FONT_STRETCH_EXPANDED, PANGO_STRETCH_EXPANDED},
269 {SP_CSS_FONT_STRETCH_EXTRA_EXPANDED, PANGO_STRETCH_EXTRA_EXPANDED},
270 {SP_CSS_FONT_STRETCH_ULTRA_EXPANDED, PANGO_STRETCH_ULTRA_EXPANDED}};
272 static const Layout::EnumConversionItem enum_convert_spstyle_variant_to_pango_variant[] = {
273 {SP_CSS_FONT_VARIANT_NORMAL, PANGO_VARIANT_NORMAL},
274 {SP_CSS_FONT_VARIANT_SMALL_CAPS, PANGO_VARIANT_SMALL_CAPS}};
276 font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const
277 {
278 PangoFontDescription *descr = styleGetFontDescription();
279 if (descr == NULL) return NULL;
280 font_instance *res = (font_factory::Default())->Face(descr);
281 pango_font_description_free(descr);
282 return res;
283 }
285 PangoFontDescription *Layout::InputStreamTextSource::styleGetFontDescription() const
286 {
287 if (style->text == NULL) return NULL;
288 PangoFontDescription *descr = pango_font_description_new();
289 // Pango can't cope with spaces before or after the commas - let's remove them.
290 // this code is not exactly unicode-safe, but it's similar to what's done in
291 // pango, so it's not the limiting factor
292 Glib::ustring family;
293 if (style->text->font_family.value == NULL) {
294 family = "Sans";
295 } else {
296 gchar **families = g_strsplit(style->text->font_family.value, ",", -1);
297 if (families) {
298 for (gchar **f = families ; *f ; ++f) {
299 g_strstrip(*f);
300 if (!family.empty()) family += ',';
301 family += *f;
302 }
303 }
304 g_strfreev(families);
305 }
307 pango_font_description_set_family(descr,family.c_str());
308 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])));
309 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])));
310 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])));
311 #ifdef USE_PANGO_WIN32
312 // damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
313 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)
314 // we don't set stretch on Win32, because pango-win32 has no concept of it
315 // (Windows doesn't really provide any useful field it could use).
316 // If we did set stretch, then any text with a font-stretch attribute would
317 // end up falling back to Arial.
318 #else
319 pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE)); // mandatory huge size (hinting workaround)
320 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])));
321 #endif
322 return descr;
323 }
325 Layout::InputStreamTextSource::~InputStreamTextSource()
326 {
327 sp_style_unref(style);
328 }
330 }//namespace Text
331 }//namespace Inkscape