1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Inkscape::Text::Layout - text layout engine input functions
4  *
5  * Authors:
6  *   Richard Hughes <cyreve@users.sf.net>
7  *
8  * Copyright (C) 2005 Richard Hughes
9  *
10  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11  */
12 
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"  // only include where actually required!
15 #endif
16 
17 #ifndef PANGO_ENABLE_ENGINE
18 #define PANGO_ENABLE_ENGINE
19 #endif
20 
21 #include "Layout-TNG.h"
22 #include "style.h"
23 #include "svg/svg-length.h"
24 #include "FontFactory.h"
25 
26 
27 namespace Inkscape {
28 namespace Text {
29 
_clearInputObjects()30 void Layout::_clearInputObjects()
31 {
32     for(auto & it : _input_stream) {
33         delete it;
34     }
35 
36     _input_stream.clear();
37     _input_wrap_shapes.clear();
38 }
39 
40 // this function does nothing more than store all its parameters for future reference
appendText(Glib::ustring const & text,SPStyle * style,SPObject * source,OptionalTextTagAttrs const * optional_attributes,unsigned optional_attributes_offset,Glib::ustring::const_iterator text_begin,Glib::ustring::const_iterator text_end)41 void Layout::appendText(Glib::ustring const &text,
42                         SPStyle *style,
43                         SPObject *source,
44                         OptionalTextTagAttrs const *optional_attributes,
45                         unsigned optional_attributes_offset,
46                         Glib::ustring::const_iterator text_begin,
47                         Glib::ustring::const_iterator text_end)
48 {
49     if (style == nullptr) return;
50 
51     InputStreamTextSource *new_source = new InputStreamTextSource;
52 
53     new_source->source = source;
54     new_source->text = &text;
55     new_source->text_begin = text_begin;
56     new_source->text_end = text_end;
57     new_source->style = style;
58     sp_style_ref(style);
59 
60     new_source->text_length = 0;
61     for ( ; text_begin != text_end && text_begin != text.end() ; ++text_begin)
62         new_source->text_length++;        // save this because calculating the length of a UTF-8 string is expensive
63 
64     if (optional_attributes) {
65         // we need to fill in x and y even if the text is empty so that empty paragraphs can be positioned correctly
66         _copyInputVector(optional_attributes->x, optional_attributes_offset, &new_source->x, std::max(1, new_source->text_length));
67         _copyInputVector(optional_attributes->y, optional_attributes_offset, &new_source->y, std::max(1, new_source->text_length));
68         _copyInputVector(optional_attributes->dx, optional_attributes_offset, &new_source->dx, new_source->text_length);
69         _copyInputVector(optional_attributes->dy, optional_attributes_offset, &new_source->dy, new_source->text_length);
70         _copyInputVector(optional_attributes->rotate, optional_attributes_offset, &new_source->rotate, new_source->text_length);
71         if (!optional_attributes->rotate.empty() && optional_attributes_offset >= optional_attributes->rotate.size()) {
72             SVGLength last_rotate;
73             last_rotate = 0.f;
74             for (auto it : optional_attributes->rotate)
75                 if (it._set)
76                     last_rotate = it;
77             new_source->rotate.resize(1, last_rotate);
78         }
79         new_source->textLength._set = optional_attributes->textLength._set;
80         new_source->textLength.value = optional_attributes->textLength.value;
81         new_source->textLength.computed = optional_attributes->textLength.computed;
82         new_source->textLength.unit = optional_attributes->textLength.unit;
83         new_source->lengthAdjust = optional_attributes->lengthAdjust;
84     }
85 
86     _input_stream.push_back(new_source);
87 }
88 
_copyInputVector(std::vector<SVGLength> const & input_vector,unsigned input_offset,std::vector<SVGLength> * output_vector,size_t max_length)89 void Layout::_copyInputVector(std::vector<SVGLength> const &input_vector, unsigned input_offset, std::vector<SVGLength> *output_vector, size_t max_length)
90 {
91     output_vector->clear();
92     if (input_offset >= input_vector.size()) return;
93     output_vector->reserve(std::min(max_length, input_vector.size() - input_offset));
94     while (input_offset < input_vector.size() && max_length != 0) {
95         if (!input_vector[input_offset]._set)
96             break;
97         output_vector->push_back(input_vector[input_offset]);
98         input_offset++;
99         max_length--;
100     }
101 }
102 
103 // just save what we've been given, really
appendControlCode(TextControlCode code,SPObject * source,double width,double ascent,double descent)104 void Layout::appendControlCode(TextControlCode code, SPObject *source, double width, double ascent, double descent)
105 {
106     InputStreamControlCode *new_code = new InputStreamControlCode;
107 
108     new_code->source = source;
109     new_code->code = code;
110     new_code->width = width;
111     new_code->ascent = ascent;
112     new_code->descent = descent;
113 
114     _input_stream.push_back(new_code);
115 }
116 
117 // more saving of the parameters
appendWrapShape(Shape const * shape,DisplayAlign display_align)118 void Layout::appendWrapShape(Shape const *shape, DisplayAlign display_align)
119 {
120     _input_wrap_shapes.emplace_back();
121     _input_wrap_shapes.back().shape = shape;
122     _input_wrap_shapes.back().display_align = display_align;
123 }
124 
styleGetBlockProgression() const125 Layout::Direction Layout::InputStreamTextSource::styleGetBlockProgression() const
126 {
127     switch( style->writing_mode.computed ) {
128         case SP_CSS_WRITING_MODE_LR_TB:
129         case SP_CSS_WRITING_MODE_RL_TB:
130             return TOP_TO_BOTTOM;
131 
132         case SP_CSS_WRITING_MODE_TB_RL:
133             return RIGHT_TO_LEFT;
134 
135         case SP_CSS_WRITING_MODE_TB_LR:
136             return LEFT_TO_RIGHT;
137 
138         default:
139             std::cerr << "Layout::InputTextStream::styleGetBlockProgression: invalid writing mode." << std::endl;
140     }
141     return TOP_TO_BOTTOM;
142 }
143 
styleGetTextOrientation() const144 SPCSSTextOrientation Layout::InputStreamTextSource::styleGetTextOrientation() const
145 {
146     return ((SPCSSTextOrientation)style->text_orientation.computed);
147 }
148 
styleGetDominantBaseline() const149 SPCSSBaseline Layout::InputStreamTextSource::styleGetDominantBaseline() const
150 {
151     return ((SPCSSBaseline)style->dominant_baseline.computed);
152 }
153 
text_anchor_to_alignment(unsigned anchor,Layout::Direction para_direction)154 static Layout::Alignment text_anchor_to_alignment(unsigned anchor, Layout::Direction para_direction)
155 {
156     switch (anchor) {
157         default:
158         case SP_CSS_TEXT_ANCHOR_START:  return para_direction == Layout::LEFT_TO_RIGHT ? Layout::LEFT : Layout::RIGHT;
159         case SP_CSS_TEXT_ANCHOR_MIDDLE: return Layout::CENTER;
160         case SP_CSS_TEXT_ANCHOR_END:    return para_direction == Layout::LEFT_TO_RIGHT ? Layout::RIGHT : Layout::LEFT;
161     }
162 }
163 
styleGetAlignment(Layout::Direction para_direction,bool try_text_align) const164 Layout::Alignment Layout::InputStreamTextSource::styleGetAlignment(Layout::Direction para_direction, bool try_text_align) const
165 {
166     if (!try_text_align)
167         return text_anchor_to_alignment(style->text_anchor.computed, para_direction);
168 
169     // there's no way to tell the difference between text-anchor set higher up the cascade to the default and
170     // text-anchor never set anywhere in the cascade, so in order to detect which of text-anchor or text-align
171     // to use we'll have to run up the style tree ourselves.
172     SPStyle const *this_style = style;
173 
174     for ( ; ; ) {
175         // If both text-align and text-anchor are set at the same level, text-align takes
176         // precedence because it is the most expressive.
177         if (this_style->text_align.set) {
178             switch (style->text_align.computed) {
179                 default:
180                 case SP_CSS_TEXT_ALIGN_START:   return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
181                 case SP_CSS_TEXT_ALIGN_END:     return para_direction == LEFT_TO_RIGHT ? RIGHT : LEFT;
182                 case SP_CSS_TEXT_ALIGN_LEFT:    return LEFT;
183                 case SP_CSS_TEXT_ALIGN_RIGHT:   return RIGHT;
184                 case SP_CSS_TEXT_ALIGN_CENTER:  return CENTER;
185                 case SP_CSS_TEXT_ALIGN_JUSTIFY: return FULL;
186             }
187         }
188         if (this_style->text_anchor.set)
189             return text_anchor_to_alignment(this_style->text_anchor.computed, para_direction);
190         if (this_style->object == nullptr || this_style->object->parent == nullptr) break;
191         this_style = this_style->object->parent->style;
192         if (this_style == nullptr) break;
193     }
194     return para_direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
195 }
196 
styleGetFontInstance() const197 font_instance *Layout::InputStreamTextSource::styleGetFontInstance() const
198 {
199     PangoFontDescription *descr = styleGetFontDescription();
200     if (descr == nullptr) return nullptr;
201     font_instance *res = (font_factory::Default())->Face(descr);
202     pango_font_description_free(descr);
203     return res;
204 }
205 
styleGetFontDescription() const206 PangoFontDescription *Layout::InputStreamTextSource::styleGetFontDescription() const
207 {
208     // This use to be done by code here but it duplicated more complete code in FontFactory.cpp.
209     PangoFontDescription *descr = ink_font_description_from_style( style );
210 
211     // Font size not yet set
212 #ifdef USE_PANGO_WIN32
213 
214     // Damn Pango fudges the size, so we need to unfudge. See source of pango_win32_font_map_init()
215     pango_font_description_set_size(descr,
216         (int) ((font_factory::Default())->fontSize*PANGO_SCALE*72 / GetDeviceCaps(pango_win32_get_dc(),LOGPIXELSY))
217     );
218 
219     // We unset stretch on Win32, because pango-win32 has no concept of it
220     // (Windows doesn't really provide any useful field it could use).
221     // If we did set stretch, then any text with a font-stretch attribute would
222     // end up falling back to a default.
223     pango_font_description_unset_fields(descr, PANGO_FONT_MASK_STRETCH);
224 
225 #else
226 
227     // mandatory huge size (hinting workaround)
228     pango_font_description_set_size(descr, (int) ((font_factory::Default())->fontSize*PANGO_SCALE));
229 
230 #endif
231 
232     return descr;
233 }
234 
~InputStreamTextSource()235 Layout::InputStreamTextSource::~InputStreamTextSource()
236 {
237     sp_style_unref(style);
238 }
239 
240 }//namespace Text
241 }//namespace Inkscape
242