~valavanisalex/ubuntu/precise/inkscape/fix-943984

« back to all changes in this revision

Viewing changes to inkscape-0.47pre1/src/libnrtype/Layout-TNG-Compute.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2009-07-02 17:09:45 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20090702170945-nn6d6zswovbwju1t
Tags: 0.47~pre1-0ubuntu1
* New upstream release.
  - Don't constrain maximization on small resolution devices (pre0)
    (LP: #348842)
  - Fixes segfault on startup (pre0)
    (LP: #391149)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Inkscape::Text::Layout::Calculator - text layout engine meaty bits
 
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
 */
 
11
#include "Layout-TNG.h"
 
12
#include "style.h"
 
13
#include "font-instance.h"
 
14
#include "svg/svg-length.h"
 
15
#include "sp-object.h"
 
16
#include "Layout-TNG-Scanline-Maker.h"
 
17
 
 
18
namespace Inkscape {
 
19
namespace Text {
 
20
 
 
21
//#define IFTRACE(_code) _code
 
22
#define IFTRACE(_code)
 
23
 
 
24
#define TRACE(_args) IFTRACE(g_print _args)
 
25
 
 
26
// ******* enum conversion tables
 
27
static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_pango_direction[] = {
 
28
    {SP_CSS_WRITING_MODE_LR_TB, PANGO_DIRECTION_LTR},
 
29
    {SP_CSS_WRITING_MODE_RL_TB, PANGO_DIRECTION_RTL},
 
30
    {SP_CSS_WRITING_MODE_TB_LR, PANGO_DIRECTION_LTR}};   // this is correct
 
31
 
 
32
static Layout::EnumConversionItem const enum_convert_spstyle_direction_to_my_direction[] = {
 
33
    {SP_CSS_WRITING_MODE_LR_TB, Layout::LEFT_TO_RIGHT},
 
34
    {SP_CSS_WRITING_MODE_RL_TB, Layout::RIGHT_TO_LEFT},
 
35
    {SP_CSS_WRITING_MODE_TB_LR, Layout::LEFT_TO_RIGHT}};   // this is correct
 
36
 
 
37
/** \brief private to Layout. Does the real work of text flowing.
 
38
 
 
39
This class does a standard greedy paragraph wrapping algorithm.
 
40
 
 
41
Very high-level overview:
 
42
 
 
43
<pre>
 
44
foreach(paragraph) {
 
45
  call pango_itemize() (_buildPangoItemizationForPara())
 
46
  break into spans, without dealing with wrapping (_buildSpansForPara())
 
47
  foreach(line in flow shape) {
 
48
    foreach(chunk in flow shape) {   (in _buildChunksInScanRun())
 
49
      // this inner loop in _measureUnbrokenSpan()
 
50
      if the line height changed discard the line and start again
 
51
      keep adding characters until we run out of space in the chunk, then back up to the last word boundary
 
52
      (do sensible things if there is no previous word break)
 
53
    }
 
54
    push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
 
55
  }
 
56
  push the paragraph (in calculate())
 
57
}
 
58
</pre>
 
59
 
 
60
...and all of that needs to work vertically too, and with all the little details that make life annoying
 
61
*/
 
62
class Layout::Calculator
 
63
{
 
64
    class SpanPosition;
 
65
    friend class SpanPosition;
 
66
 
 
67
    Layout &_flow;
 
68
 
 
69
    ScanlineMaker *_scanline_maker;
 
70
 
 
71
    unsigned _current_shape_index;     /// index into Layout::_input_wrap_shapes
 
72
 
 
73
    PangoContext *_pango_context;
 
74
 
 
75
    Direction _block_progression;
 
76
 
 
77
    /** for y= attributes in tspan elements et al, we do the adjustment by moving each
 
78
    glyph individually by this number. The spec means that this is maintained across
 
79
    paragraphs. */
 
80
    double _y_offset;
 
81
 
 
82
    /** to stop pango from hinting its output, the font factory creates all fonts very large.
 
83
    All numbers returned from pango have to be divided by this number \em and divided by
 
84
    PANGO_SCALE. See font_factory::font_factory(). */
 
85
    double _font_factory_size_multiplier;
 
86
 
 
87
    /** Temporary storage associated with each item in Layout::_input_stream. */
 
88
    struct InputItemInfo {
 
89
        bool in_sub_flow;
 
90
        Layout *sub_flow;    // this is only set for the first input item in a sub-flow
 
91
 
 
92
        InputItemInfo() : in_sub_flow(false), sub_flow(NULL) {}
 
93
        void free();
 
94
    };
 
95
 
 
96
    /** Temporary storage associated with each item returned by the call to
 
97
        pango_itemize(). */
 
98
    struct PangoItemInfo {
 
99
        PangoItem *item;
 
100
        font_instance *font;
 
101
 
 
102
        PangoItemInfo() : item(NULL), font(NULL) {}
 
103
        void free();
 
104
    };
 
105
 
 
106
    /** These spans have approximately the same definition as that used for
 
107
    Layout::Span (constant font, direction, etc), except that they are from
 
108
    before we have located the line breaks, so bear no relation to chunks.
 
109
    They are guaranteed to be in at most one PangoItem (spans with no text in
 
110
    them will not have an associated PangoItem), exactly one input source and
 
111
    will only have one change of x, y, dx, dy or rotate attribute, which will
 
112
    be at the beginning. An UnbrokenSpan can cross a chunk boundary, c.f.
 
113
    BrokenSpan. */
 
114
    struct UnbrokenSpan {
 
115
        PangoGlyphString *glyph_string;
 
116
        int pango_item_index;    /// index into _para.pango_items, or -1 if this is style only
 
117
        unsigned input_index;         /// index into Layout::_input_stream
 
118
        Glib::ustring::const_iterator input_stream_first_character;
 
119
        double font_size;
 
120
        LineHeight line_height;
 
121
        double line_height_multiplier;  /// calculated from the font-height css property
 
122
        unsigned text_bytes;
 
123
        unsigned char_index_in_para;    /// the index of the first character in this span in the paragraph, for looking up char_attributes
 
124
        SVGLength x, y, dx, dy, rotate;  // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
 
125
 
 
126
        UnbrokenSpan() : glyph_string(NULL) {}
 
127
        void free() {if (glyph_string) pango_glyph_string_free(glyph_string); glyph_string = NULL;}
 
128
    };
 
129
 
 
130
    /** a useful little iterator for moving char-by-char across spans. */
 
131
    struct UnbrokenSpanPosition {
 
132
        std::vector<UnbrokenSpan>::iterator iter_span;
 
133
        unsigned char_byte;
 
134
        unsigned char_index;
 
135
 
 
136
        void increment();   ///< Step forward by one character.
 
137
 
 
138
        inline bool operator== (UnbrokenSpanPosition const &other) const
 
139
            {return char_byte == other.char_byte && iter_span == other.iter_span;}
 
140
        inline bool operator!= (UnbrokenSpanPosition const &other) const
 
141
            {return char_byte != other.char_byte || iter_span != other.iter_span;}
 
142
    };
 
143
 
 
144
    /** The line breaking algorithm will convert each UnbrokenSpan into one
 
145
    or more of these. A BrokenSpan will never cross a chunk boundary, c.f.
 
146
    UnbrokenSpan. */
 
147
    struct BrokenSpan {
 
148
        UnbrokenSpanPosition start;
 
149
        UnbrokenSpanPosition end;    // the end of this will always be the same as the start of the next
 
150
        unsigned start_glyph_index;
 
151
        unsigned end_glyph_index;
 
152
        double width;
 
153
        unsigned whitespace_count;
 
154
        bool ends_with_whitespace;
 
155
        double each_whitespace_width;
 
156
        void setZero();
 
157
    };
 
158
 
 
159
    /** The definition of a chunk used here is the same as that used in Layout. */
 
160
    struct ChunkInfo {
 
161
        std::vector<BrokenSpan> broken_spans;
 
162
        double scanrun_width;
 
163
        double text_width;       ///< Total width used by the text (excluding justification).
 
164
        double x;
 
165
        int whitespace_count;
 
166
    };
 
167
 
 
168
    /** Used to provide storage for anything that applies to the current
 
169
    paragraph only. Since we're only processing one paragraph at a time,
 
170
    there's only one instantiation of this struct, on the stack of
 
171
    calculate(). */
 
172
    struct ParagraphInfo {
 
173
        unsigned first_input_index;      ///< Index into Layout::_input_stream.
 
174
        Direction direction;
 
175
        Alignment alignment;
 
176
        std::vector<InputItemInfo> input_items;
 
177
        std::vector<PangoItemInfo> pango_items;
 
178
        std::vector<PangoLogAttr> char_attributes;    ///< For every character in the paragraph.
 
179
        std::vector<UnbrokenSpan> unbroken_spans;
 
180
 
 
181
        template<typename T> static void free_sequence(T &seq);
 
182
        void free();
 
183
    };
 
184
 
 
185
/* *********************************************************************************************************/
 
186
//                       Initialisation of ParagraphInfo structure
 
187
 
 
188
 
 
189
#if 0 /* unused */
 
190
    void _initialiseInputItems(ParagraphInfo *para) const;
 
191
#endif
 
192
 
 
193
    void _buildPangoItemizationForPara(ParagraphInfo *para) const;
 
194
 
 
195
    static void _computeFontLineHeight(font_instance *font, double font_size,
 
196
                                       SPStyle const *style, LineHeight *line_height,
 
197
                                       double *line_height_multiplier);
 
198
 
 
199
    unsigned _buildSpansForPara(ParagraphInfo *para) const;
 
200
 
 
201
/* *********************************************************************************************************/
 
202
//                             Per-line functions
 
203
 
 
204
 
 
205
    bool _goToNextWrapShape();
 
206
 
 
207
    bool _findChunksForLine(ParagraphInfo const &para, UnbrokenSpanPosition *start_span_pos,
 
208
                            std::vector<ChunkInfo> *chunk_info, LineHeight *line_height);
 
209
 
 
210
    static inline PangoLogAttr const &_charAttributes(ParagraphInfo const &para,
 
211
                                                      UnbrokenSpanPosition const &span_pos)
 
212
    {
 
213
        return para.char_attributes[span_pos.iter_span->char_index_in_para + span_pos.char_index];
 
214
    }
 
215
 
 
216
    bool _buildChunksInScanRun(ParagraphInfo const &para,
 
217
                               UnbrokenSpanPosition const &start_span_pos,
 
218
                               ScanlineMaker::ScanRun const &scan_run,
 
219
                               std::vector<ChunkInfo> *chunk_info,
 
220
                               LineHeight *line_height) const;
 
221
 
 
222
    /** computes the width of a single UnbrokenSpan (pointed to by span->start.iter_span)
 
223
    and outputs its vital statistics into the other fields of \a span.
 
224
    Measuring will stop if maximum_width is reached and in that case the
 
225
    function will return false. In other cases where a line break must be
 
226
    done immediately the function will also return false. On return
 
227
    \a last_break_span will contain the vital statistics for the span only
 
228
    up to the last line breaking change. If there are no line breaking
 
229
    characters in the span then \a last_break_span will not be altered.
 
230
    Similarly, \a last_emergency_break_span will contain the vital
 
231
    statistics for the span up to the last inter-character boundary,
 
232
    or will be unaltered if there is none. */
 
233
    bool _measureUnbrokenSpan(ParagraphInfo const &para, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const
 
234
    {
 
235
        span->setZero();
 
236
 
 
237
        if (span->start.iter_span->dx._set && span->start.char_byte == 0)
 
238
            span->width += span->start.iter_span->dx.computed;
 
239
 
 
240
        if (span->start.iter_span->pango_item_index == -1) {
 
241
            // if this is a style-only span there's no text in it
 
242
            // so we don't need to do very much at all
 
243
            span->end.iter_span++;
 
244
            return true;
 
245
        }
 
246
 
 
247
        if (_flow._input_stream[span->start.iter_span->input_index]->Type() == CONTROL_CODE) {
 
248
            InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
 
249
            if (control_code->code == SHAPE_BREAK || control_code->code == PARAGRAPH_BREAK) {
 
250
                *last_emergency_break_span = *last_break_span = *span;
 
251
                return false;
 
252
            }
 
253
            if (control_code->code == ARBITRARY_GAP) {
 
254
                if (span->width + control_code->width > maximum_width)
 
255
                    return false;
 
256
                TRACE(("fitted control code, width = %f\n", control_code->width));
 
257
                span->width += control_code->width;
 
258
                span->end.increment();
 
259
            }
 
260
            return true;
 
261
 
 
262
        }
 
263
 
 
264
        if (_flow._input_stream[span->start.iter_span->input_index]->Type() != TEXT_SOURCE)
 
265
            return true;  // never happens
 
266
 
 
267
        InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
 
268
 
 
269
        if (_directions_are_orthogonal(_block_progression, text_source->styleGetBlockProgression())) {
 
270
            // TODO: block-progression altered in the middle
 
271
            // Measure the precomputed flow from para.input_items
 
272
            span->end.iter_span++;  // for now, skip to the next span
 
273
            return true;
 
274
        }
 
275
 
 
276
        // a normal span going with a normal block-progression
 
277
        double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
 
278
        double soft_hyphen_glyph_width = 0.0;
 
279
        bool soft_hyphen_in_word = false;
 
280
        bool is_soft_hyphen = false;
 
281
        IFTRACE(int char_count = 0);
 
282
 
 
283
        // if we're not at the start of the span we need to pre-init glyph_index
 
284
        span->start_glyph_index = 0;
 
285
        while (span->start_glyph_index < (unsigned)span->start.iter_span->glyph_string->num_glyphs
 
286
               && span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
 
287
            span->start_glyph_index++;
 
288
        span->end_glyph_index = span->start_glyph_index;
 
289
 
 
290
        // go char-by-char summing the width, while keeping track of the previous break point
 
291
        do {
 
292
            PangoLogAttr const &char_attributes = _charAttributes(para, span->end);
 
293
 
 
294
            if (char_attributes.is_mandatory_break && span->end != span->start) {
 
295
                *last_emergency_break_span = *last_break_span = *span;
 
296
                TRACE(("span %d end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
 
297
                return false;
 
298
            }
 
299
 
 
300
            if (char_attributes.is_line_break) {
 
301
                // a suitable position to break at, record where we are
 
302
                *last_emergency_break_span = *last_break_span = *span;
 
303
                if (soft_hyphen_in_word) {
 
304
                    // if there was a previous soft hyphen we're not going to need it any more so we can remove it
 
305
                    span->width -= soft_hyphen_glyph_width;
 
306
                    if (!is_soft_hyphen)
 
307
                        soft_hyphen_in_word = false;
 
308
                }
 
309
            } else if (char_attributes.is_char_break) {
 
310
                *last_emergency_break_span = *span;
 
311
            }
 
312
            // todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
 
313
 
 
314
            // sum the glyph widths, letter spacing and word spacing to get the character width
 
315
            double char_width = 0.0;
 
316
            while (span->end_glyph_index < (unsigned)span->end.iter_span->glyph_string->num_glyphs
 
317
                   && span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
 
318
                if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
 
319
                    char_width += span->start.iter_span->font_size * para.pango_items[span->end.iter_span->pango_item_index].font->Advance(span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].glyph, true);
 
320
                else
 
321
                    char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width;
 
322
                span->end_glyph_index++;
 
323
            }
 
324
            if (char_attributes.is_cursor_position)
 
325
                char_width += text_source->style->letter_spacing.computed;
 
326
            if (char_attributes.is_white)
 
327
                char_width += text_source->style->word_spacing.computed;
 
328
            span->width += char_width;
 
329
            IFTRACE(char_count++);
 
330
 
 
331
            if (char_attributes.is_white) {
 
332
                span->whitespace_count++;
 
333
                span->each_whitespace_width = char_width;
 
334
            }
 
335
            span->ends_with_whitespace = char_attributes.is_white;
 
336
 
 
337
            is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
 
338
            if (is_soft_hyphen)
 
339
                soft_hyphen_glyph_width = char_width;
 
340
 
 
341
            span->end.increment();
 
342
 
 
343
            if (span->width > maximum_width && !char_attributes.is_white) {       // whitespaces don't matter, we can put as many as we want at eol
 
344
                TRACE(("span %d exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
 
345
                return false;
 
346
            }
 
347
 
 
348
        } while (span->end.char_byte != 0);  // while we haven't wrapped to the next span
 
349
        TRACE(("fitted span %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
 
350
        return true;
 
351
    }
 
352
 
 
353
/* *********************************************************************************************************/
 
354
//                             Per-line functions (output)
 
355
 
 
356
    /** Uses the paragraph alignment and the chunk information to work out
 
357
    where the actual left of the final chunk must be. Also sets
 
358
    \a add_to_each_whitespace to be the amount of x to add at each
 
359
    whitespace character to make full justification work. */
 
360
    double _getChunkLeftWithAlignment(ParagraphInfo const &para, std::vector<ChunkInfo>::const_iterator it_chunk, double *add_to_each_whitespace) const
 
361
    {
 
362
        *add_to_each_whitespace = 0.0;
 
363
        if (_flow._input_wrap_shapes.empty()) {
 
364
            switch (para.alignment) {
 
365
                case FULL:
 
366
                case LEFT:
 
367
                default:
 
368
                    return it_chunk->x;
 
369
                case RIGHT:
 
370
                    return it_chunk->x - it_chunk->text_width;
 
371
                case CENTER:
 
372
                    return it_chunk->x - it_chunk->text_width / 2;
 
373
            }
 
374
        }
 
375
 
 
376
        switch (para.alignment) {
 
377
            case FULL:
 
378
                if (!it_chunk->broken_spans.empty()
 
379
                    && it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) {   // don't justify the last chunk in the para
 
380
                    if (it_chunk->whitespace_count)
 
381
                        *add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
 
382
                    //else
 
383
                        //add_to_each_charspace = something
 
384
                }
 
385
                return it_chunk->x;
 
386
            case LEFT:
 
387
            default:
 
388
                return it_chunk->x;
 
389
            case RIGHT:
 
390
                return it_chunk->x + it_chunk->scanrun_width - it_chunk->text_width;
 
391
            case CENTER:
 
392
                return it_chunk->x + (it_chunk->scanrun_width - it_chunk->text_width) / 2;
 
393
        }
 
394
    }
 
395
 
 
396
    /** Once we've got here we have finished making changes to the line and
 
397
    are ready to output the final result to #_flow. This method takes its
 
398
    input parameters and does that.
 
399
    */
 
400
    void _outputLine(ParagraphInfo const &para, LineHeight const &line_height, std::vector<ChunkInfo> const &chunk_info)
 
401
    {
 
402
        if (chunk_info.empty()) {
 
403
            TRACE(("line too short to fit anything on it, go to next\n"));
 
404
            return;
 
405
        }
 
406
 
 
407
        // we've finished fiddling about with ascents and descents: create the output
 
408
        TRACE(("found line fit; creating output\n"));
 
409
        Layout::Line new_line;
 
410
        new_line.in_paragraph = _flow._paragraphs.size() - 1;
 
411
        new_line.baseline_y = _scanline_maker->yCoordinate() + line_height.ascent;
 
412
        new_line.in_shape = _current_shape_index;
 
413
        _flow._lines.push_back(new_line);
 
414
 
 
415
        for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
 
416
 
 
417
            double add_to_each_whitespace;
 
418
            // add the chunk to the list
 
419
            Layout::Chunk new_chunk;
 
420
            new_chunk.in_line = _flow._lines.size() - 1;
 
421
            new_chunk.left_x = _getChunkLeftWithAlignment(para, it_chunk, &add_to_each_whitespace);
 
422
            // we may also have y move orders to deal with here (dx, dy and rotate are done per span)
 
423
            if (!it_chunk->broken_spans.empty()    // this one only happens for empty paragraphs
 
424
                && it_chunk->broken_spans.front().start.char_byte == 0
 
425
                && it_chunk->broken_spans.front().start.iter_span->y._set) {
 
426
                // if this is the start of a line, we should change the baseline rather than each glyph individually
 
427
                if (_flow._characters.empty() || _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
 
428
                    new_line.baseline_y = it_chunk->broken_spans.front().start.iter_span->y.computed;
 
429
                    _flow._lines.back().baseline_y = new_line.baseline_y;
 
430
                    _y_offset = 0.0;
 
431
                    _scanline_maker->setNewYCoordinate(new_line.baseline_y - line_height.ascent);
 
432
                } else
 
433
                    _y_offset = it_chunk->broken_spans.front().start.iter_span->y.computed - new_line.baseline_y;
 
434
            }
 
435
            _flow._chunks.push_back(new_chunk);
 
436
 
 
437
            double x;
 
438
            double direction_sign;
 
439
            Direction previous_direction = para.direction;
 
440
            double counter_directional_width_remaining = 0.0;
 
441
            float glyph_rotate = 0.0;
 
442
            if (para.direction == LEFT_TO_RIGHT) {
 
443
                direction_sign = +1.0;
 
444
                x = 0.0;
 
445
            } else {
 
446
                direction_sign = -1.0;
 
447
                if (para.alignment == FULL && !_flow._input_wrap_shapes.empty())
 
448
                    x = it_chunk->scanrun_width;
 
449
                else
 
450
                    x = it_chunk->text_width;
 
451
            }
 
452
 
 
453
            for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
 
454
                // begin adding spans to the list
 
455
                UnbrokenSpan const &unbroken_span = *it_span->start.iter_span;
 
456
 
 
457
                if (it_span->start.char_byte == 0) {
 
458
                    // start of an unbroken span, we might have dx, dy or rotate still to process (x and y are done per chunk)
 
459
                    if (unbroken_span.dx._set) x += unbroken_span.dx.computed;
 
460
                    if (unbroken_span.dy._set) _y_offset += unbroken_span.dy.computed;
 
461
                    if (unbroken_span.rotate._set) glyph_rotate = unbroken_span.rotate.computed * (M_PI/180);
 
462
                }
 
463
 
 
464
                if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE
 
465
                    && unbroken_span.pango_item_index == -1) {
 
466
                    // style only, nothing to output
 
467
                    continue;
 
468
                }
 
469
 
 
470
                Layout::Span new_span;
 
471
                double x_in_span = 0.0;
 
472
 
 
473
                new_span.in_chunk = _flow._chunks.size() - 1;
 
474
                new_span.line_height = unbroken_span.line_height;
 
475
                new_span.in_input_stream_item = unbroken_span.input_index;
 
476
                new_span.baseline_shift = _y_offset;
 
477
                new_span.block_progression = _block_progression;
 
478
                if ((_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) && (new_span.font = para.pango_items[unbroken_span.pango_item_index].font))
 
479
                    {
 
480
                    new_span.font->Ref();
 
481
                    new_span.font_size = unbroken_span.font_size;
 
482
                    new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
 
483
                    new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
 
484
                } else {  // a control code
 
485
                    new_span.font = NULL;
 
486
                    new_span.font_size = new_span.line_height.ascent + new_span.line_height.descent;
 
487
                    new_span.direction = para.direction;
 
488
                }
 
489
 
 
490
                if (new_span.direction == para.direction) {
 
491
                    x -= counter_directional_width_remaining;
 
492
                    counter_directional_width_remaining = 0.0;
 
493
                } else if (new_span.direction != previous_direction) {
 
494
                    // measure width of spans we need to switch round
 
495
                    counter_directional_width_remaining = 0.0;
 
496
                    std::vector<BrokenSpan>::const_iterator it_following_span;
 
497
                    for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
 
498
                        Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
 
499
                        if (!Layout::_directions_are_orthogonal(following_span_progression, _block_progression)) {
 
500
                            if (it_following_span->start.iter_span->pango_item_index == -1) {   // when the span came from a control code
 
501
                                if (new_span.direction != para.direction) break;
 
502
                            } else
 
503
                                if (new_span.direction != (para.pango_items[it_following_span->start.iter_span->pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT)) break;
 
504
                        }
 
505
                        counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
 
506
                    }
 
507
                    x += counter_directional_width_remaining;
 
508
                    counter_directional_width_remaining = 0.0;    // we want to go increasingly negative
 
509
                }
 
510
                new_span.x_start = x;
 
511
 
 
512
                if (_flow._input_stream[unbroken_span.input_index]->Type() == TEXT_SOURCE) {
 
513
                    // the span is set up, push the glyphs and chars
 
514
                    InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
 
515
                    Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
 
516
                    unsigned char_index_in_unbroken_span = it_span->start.char_index;
 
517
                    unsigned cluster_start_char_index = _flow._characters.size();
 
518
                    double font_size_multiplier = new_span.font_size / (PANGO_SCALE * _font_factory_size_multiplier);
 
519
 
 
520
                    for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
 
521
                        unsigned char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
 
522
                        if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start)
 
523
                            cluster_start_char_index = _flow._characters.size();
 
524
 
 
525
                        if (unbroken_span.glyph_string->log_clusters[glyph_index] < (int)unbroken_span.text_bytes
 
526
                            && *iter_source_text == UNICODE_SOFT_HYPHEN
 
527
                            && glyph_index + 1 != it_span->end_glyph_index) {
 
528
                            // if we're looking at a soft hyphen and it's not the last glyph in the
 
529
                            // chunk we don't draw the glyph but we still need to add to _characters
 
530
                            Layout::Character new_character;
 
531
                            new_character.in_span = _flow._spans.size();     // the span hasn't been added yet, so no -1
 
532
                            new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
 
533
                            new_character.in_glyph = -1;
 
534
                            _flow._characters.push_back(new_character);
 
535
                            iter_source_text++;
 
536
                            char_index_in_unbroken_span++;
 
537
                            while (glyph_index < (unsigned)unbroken_span.glyph_string->num_glyphs
 
538
                                   && unbroken_span.glyph_string->log_clusters[glyph_index] == (int)char_byte)
 
539
                                glyph_index++;
 
540
                            glyph_index--;
 
541
                            continue;
 
542
                        }
 
543
 
 
544
                        // create the Layout::Glyph
 
545
                        Layout::Glyph new_glyph;
 
546
                        new_glyph.glyph = unbroken_span.glyph_string->glyphs[glyph_index].glyph;
 
547
                        new_glyph.in_character = cluster_start_char_index;
 
548
                        new_glyph.rotation = glyph_rotate;
 
549
 
 
550
                        /* put something like this back in when we do glyph-rotation-horizontal/vertical
 
551
                        if (new_span.block_progression == LEFT_TO_RIGHT || new_span.block_progression == RIGHT_TO_LEFT) {
 
552
                            new_glyph.x += new_span.line_height.ascent;
 
553
                            new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
 
554
                            new_glyph.width = new_span.line_height.ascent + new_span.line_height.descent;
 
555
                        } else */
 
556
 
 
557
                        if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT) {
 
558
                            new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
 
559
                            new_glyph.y = _y_offset + (unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset - unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * 0.5) * font_size_multiplier;
 
560
                            new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, true);
 
561
                        } else {
 
562
                            new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
 
563
                            new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
 
564
                            new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
 
565
                            if ((new_glyph.width == 0) && (para.pango_items[unbroken_span.pango_item_index].font))
 
566
                                new_glyph.width = new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[glyph_index].glyph, false);
 
567
                                // for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
 
568
                        }
 
569
                        if (new_span.direction == RIGHT_TO_LEFT) {
 
570
                            // pango wanted to give us glyphs in visual order but we refused, so we need to work
 
571
                            // out where the cluster start is ourselves
 
572
                            double cluster_width = 0.0;
 
573
                            for (unsigned rtl_index = glyph_index; rtl_index < it_span->end_glyph_index ; rtl_index++) {
 
574
                                if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
 
575
                                    break;
 
576
                                if (_block_progression == LEFT_TO_RIGHT || _block_progression == RIGHT_TO_LEFT)
 
577
                                    cluster_width += new_span.font_size * para.pango_items[unbroken_span.pango_item_index].font->Advance(unbroken_span.glyph_string->glyphs[rtl_index].glyph, true);
 
578
                                else
 
579
                                    cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
 
580
                            }
 
581
                            new_glyph.x -= cluster_width;
 
582
                        }
 
583
                        _flow._glyphs.push_back(new_glyph);
 
584
 
 
585
                        // create the Layout::Character(s)
 
586
                        double advance_width = new_glyph.width;
 
587
                        unsigned end_byte;
 
588
                        if (glyph_index == (unsigned)unbroken_span.glyph_string->num_glyphs - 1)
 
589
                            end_byte = it_span->start.iter_span->text_bytes;
 
590
                        else {
 
591
                            // output chars for the whole cluster that is commenced by this glyph
 
592
                            if (unbroken_span.glyph_string->glyphs[glyph_index].attr.is_cluster_start) {
 
593
                                int next_cluster_glyph_index = glyph_index + 1;
 
594
                                while (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs
 
595
                                       && !unbroken_span.glyph_string->glyphs[next_cluster_glyph_index].attr.is_cluster_start)
 
596
                                    next_cluster_glyph_index++;
 
597
                                if (next_cluster_glyph_index < unbroken_span.glyph_string->num_glyphs)
 
598
                                    end_byte = unbroken_span.glyph_string->log_clusters[next_cluster_glyph_index];
 
599
                                else
 
600
                                    end_byte = it_span->start.iter_span->text_bytes;
 
601
                            } else
 
602
                                end_byte = char_byte;    // don't output any chars if we're not at the start of a cluster
 
603
                        }
 
604
                        while (char_byte < end_byte) {
 
605
                            Layout::Character new_character;
 
606
                            new_character.in_span = _flow._spans.size();
 
607
                            new_character.x = x_in_span;
 
608
                            new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
 
609
                            new_character.in_glyph = _flow._glyphs.size() - 1;
 
610
                            _flow._characters.push_back(new_character);
 
611
                            if (new_character.char_attributes.is_white)
 
612
                                advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace;    // justification
 
613
                            if (new_character.char_attributes.is_cursor_position)
 
614
                                advance_width += text_source->style->letter_spacing.computed;
 
615
                            iter_source_text++;
 
616
                            char_index_in_unbroken_span++;
 
617
                            char_byte = iter_source_text.base() - unbroken_span.input_stream_first_character.base();
 
618
                        }
 
619
 
 
620
                        advance_width *= direction_sign;
 
621
                        if (new_span.direction != para.direction) {
 
622
                            counter_directional_width_remaining -= advance_width;
 
623
                            x -= advance_width;
 
624
                            x_in_span -= advance_width;
 
625
                        } else {
 
626
                            x += advance_width;
 
627
                            x_in_span += advance_width;
 
628
                        }
 
629
                    }
 
630
                } else if (_flow._input_stream[unbroken_span.input_index]->Type() == CONTROL_CODE) {
 
631
                    x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
 
632
                }
 
633
 
 
634
                new_span.x_end = new_span.x_start + x_in_span;
 
635
                _flow._spans.push_back(new_span);
 
636
                previous_direction = new_span.direction;
 
637
            }
 
638
            // end adding spans to the list, on to the next chunk...
 
639
        }
 
640
        TRACE(("output done\n"));
 
641
    }
 
642
 
 
643
/* *********************************************************************************************************/
 
644
//                             Setup and top-level functions
 
645
 
 
646
    /** initialises the ScanlineMaker for the first shape in the flow, or
 
647
    the infinite version if we're not doing wrapping. */
 
648
    void _createFirstScanlineMaker()
 
649
    {
 
650
        _current_shape_index = 0;
 
651
        if (_flow._input_wrap_shapes.empty()) {
 
652
            // create the special no-wrapping infinite scanline maker
 
653
            double initial_x = 0, initial_y = 0;
 
654
            InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
 
655
            if (!text_source->x.empty())
 
656
                initial_x = text_source->x.front().computed;
 
657
            if (!text_source->y.empty())
 
658
                initial_y = text_source->y.front().computed;
 
659
            _scanline_maker = new InfiniteScanlineMaker(initial_x, initial_y, _block_progression);
 
660
            TRACE(("  wrapping disabled\n"));
 
661
        }
 
662
        else {
 
663
            _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
 
664
            TRACE(("  begin wrap shape 0\n"));
 
665
        }
 
666
    }
 
667
 
 
668
public:
 
669
    Calculator(Layout *text_flow)
 
670
        : _flow(*text_flow) {}
 
671
 
 
672
    bool calculate();
 
673
};
 
674
 
 
675
/* fixme: I don't like the fact that InputItemInfo etc. use the default copy constructor and
 
676
 * operator= (and thus don't involve incrementing reference counts), yet they provide a free method
 
677
 * that does delete or Unref.
 
678
 *
 
679
 * I suggest using the garbage collector to manage deletion.
 
680
 */
 
681
void Layout::Calculator::InputItemInfo::free()
 
682
{
 
683
    if (sub_flow) {
 
684
        delete sub_flow;
 
685
        sub_flow = NULL;
 
686
    }
 
687
}
 
688
 
 
689
void Layout::Calculator::PangoItemInfo::free()
 
690
{
 
691
    if (item) {
 
692
        pango_item_free(item);
 
693
        item = NULL;
 
694
    }
 
695
    if (font) {
 
696
        font->Unref();
 
697
        font = NULL;
 
698
    }
 
699
}
 
700
 
 
701
void Layout::Calculator::UnbrokenSpanPosition::increment()
 
702
{
 
703
    gchar const *text_base = &*iter_span->input_stream_first_character.base();
 
704
    char_byte = g_utf8_next_char(text_base + char_byte) - text_base;
 
705
    char_index++;
 
706
    if (char_byte == iter_span->text_bytes) {
 
707
        iter_span++;
 
708
        char_index = char_byte = 0;
 
709
    }
 
710
}
 
711
 
 
712
void Layout::Calculator::BrokenSpan::setZero()
 
713
{
 
714
    end = start;
 
715
    width = 0.0;
 
716
    whitespace_count = 0;
 
717
    end_glyph_index = start_glyph_index = 0;
 
718
    ends_with_whitespace = false;
 
719
    each_whitespace_width = 0.0;
 
720
}
 
721
 
 
722
template<typename T> void Layout::Calculator::ParagraphInfo::free_sequence(T &seq)
 
723
{
 
724
    for (typename T::iterator it(seq.begin()); it != seq.end(); ++it) {
 
725
        it->free();
 
726
    }
 
727
    seq.clear();
 
728
}
 
729
 
 
730
void Layout::Calculator::ParagraphInfo::free()
 
731
{
 
732
    free_sequence(input_items);
 
733
    free_sequence(pango_items);
 
734
    free_sequence(unbroken_spans);
 
735
}
 
736
 
 
737
///**
 
738
// * For sections of text with a block-progression different to the rest
 
739
// * of the flow, the best thing to do is to detect them in advance and
 
740
// * create child TextFlow objects with just the rotated text. In the
 
741
// * parent we then effectively use ARBITRARY_GAP fields during the
 
742
// * flowing (because we don't allow wrapping when the block-progression
 
743
// * changes) and copy the actual text in during the output phase.
 
744
// *
 
745
// * NB: this code not enabled yet.
 
746
// */
 
747
//void Layout::Calculator::_initialiseInputItems(ParagraphInfo *para) const
 
748
//{
 
749
//    Direction prev_block_progression = _block_progression;
 
750
//    int run_start_input_index = para->first_input_index;
 
751
//
 
752
//    para->free_sequence(para->input_items);
 
753
//    for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
 
754
//        InputItemInfo input_item;
 
755
//
 
756
//        input_item.in_sub_flow = false;
 
757
//        input_item.sub_flow = NULL;
 
758
//        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
 
759
//            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
 
760
//            if (   control_code->code == SHAPE_BREAK
 
761
//                   || control_code->code == PARAGRAPH_BREAK)
 
762
//                break;                                    // stop at the end of the paragraph
 
763
//            // all other control codes we'll pick up later
 
764
//
 
765
//        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
 
766
//            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
 
767
//            Direction this_block_progression = text_source->styleGetBlockProgression();
 
768
//            if (this_block_progression != prev_block_progression) {
 
769
//                if (prev_block_progression != _block_progression) {
 
770
//                    // need to back up so that control codes belong outside the block-progression change
 
771
//                    int run_end_input_index = input_index - 1;
 
772
//                    while (run_end_input_index > run_start_input_index
 
773
//                           && _flow._input_stream[run_end_input_index]->Type() != TEXT_SOURCE)
 
774
//                        run_end_input_index--;
 
775
//                    // now create the sub-flow
 
776
//                    input_item.sub_flow = new Layout;
 
777
//                    for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
 
778
//                        input_item.in_sub_flow = true;
 
779
//                        if (_flow._input_stream[sub_input_index]->Type() == CONTROL_CODE) {
 
780
//                            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
 
781
//                            input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
 
782
//                        } else if (_flow._input_stream[sub_input_index]->Type() == TEXT_SOURCE) {
 
783
//                            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
 
784
//                            input_item.sub_flow->appendText(*text_source->text, text_source->style, text_source->source_cookie, NULL, 0, text_source->text_begin, text_source->text_end);
 
785
//                            Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
 
786
//                            sub_flow_text_source->x = text_source->x;    // this is easier than going via optionalattrs for the appendText() call
 
787
//                            sub_flow_text_source->y = text_source->y;    // should these actually be allowed anyway? You'll almost never get the results you expect
 
788
//                            sub_flow_text_source->dx = text_source->dx;  // (not that it's very clear what you should expect, anyway)
 
789
//                            sub_flow_text_source->dy = text_source->dy;
 
790
//                            sub_flow_text_source->rotate = text_source->rotate;
 
791
//                        }
 
792
//                    }
 
793
//                    input_item.sub_flow->calculateFlow();
 
794
//                }
 
795
//                run_start_input_index = input_index;
 
796
//            }
 
797
//            prev_block_progression = this_block_progression;
 
798
//        }
 
799
//        para->input_items.push_back(input_item);
 
800
//    }
 
801
//}
 
802
 
 
803
/**
 
804
 * Take all the text from \a _para.first_input_index to the end of the
 
805
 * paragraph and stitch it together so that pango_itemize() can be called on
 
806
 * the whole thing.
 
807
 *
 
808
 * Input: para.first_input_index.
 
809
 * Output: para.direction, para.pango_items, para.char_attributes.
 
810
 */
 
811
void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const
 
812
{
 
813
    Glib::ustring para_text;
 
814
    PangoAttrList *attributes_list;
 
815
    unsigned input_index;
 
816
 
 
817
    para->free_sequence(para->pango_items);
 
818
    para->char_attributes.clear();
 
819
 
 
820
    TRACE(("itemizing para, first input %d\n", para->first_input_index));
 
821
 
 
822
    attributes_list = pango_attr_list_new();
 
823
    for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
 
824
        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
 
825
            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
 
826
            if (   control_code->code == SHAPE_BREAK
 
827
                   || control_code->code == PARAGRAPH_BREAK)
 
828
                break;                                    // stop at the end of the paragraph
 
829
            // all other control codes we'll pick up later
 
830
 
 
831
        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE) {
 
832
            Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
 
833
 
 
834
                        // create the font_instance
 
835
                        font_instance *font = text_source->styleGetFontInstance();
 
836
                        if (font == NULL)
 
837
                                continue;  // bad news: we'll have to ignore all this text because we know of no font to render it
 
838
 
 
839
            PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr);
 
840
            attribute_font_description->start_index = para_text.bytes();
 
841
            para_text.append(&*text_source->text_begin.base(), text_source->text_length);     // build the combined text
 
842
            attribute_font_description->end_index = para_text.bytes();
 
843
            pango_attr_list_insert(attributes_list, attribute_font_description);
 
844
            // ownership of attribute is assumed by the list
 
845
        }
 
846
    }
 
847
 
 
848
    TRACE(("whole para: \"%s\"\n", para_text.data()));
 
849
    TRACE(("%d input sources used\n", input_index - para->first_input_index));
 
850
 
 
851
    // do the pango_itemize()
 
852
    GList *pango_items_glist = NULL;
 
853
    if (_flow._input_stream[para->first_input_index]->Type() == TEXT_SOURCE) {
 
854
        Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
 
855
        if (text_source->style->direction.set) {
 
856
            PangoDirection pango_direction = (PangoDirection)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_pango_direction, sizeof(enum_convert_spstyle_direction_to_pango_direction)/sizeof(enum_convert_spstyle_direction_to_pango_direction[0]));
 
857
            pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
 
858
            para->direction = (Layout::Direction)_enum_converter(text_source->style->direction.computed, enum_convert_spstyle_direction_to_my_direction, sizeof(enum_convert_spstyle_direction_to_my_direction)/sizeof(enum_convert_spstyle_direction_to_my_direction[0]));
 
859
        }
 
860
    }
 
861
    if (pango_items_glist == NULL) {  // no direction specified, guess it
 
862
        pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
 
863
 
 
864
        // I think according to the css spec this is wrong and we're never allowed to guess the directionality
 
865
        // of a paragraph. Need to talk to an rtl speaker.
 
866
        if (pango_items_glist == NULL || pango_items_glist->data == NULL) para->direction = LEFT_TO_RIGHT;
 
867
        else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
 
868
    }
 
869
    pango_attr_list_unref(attributes_list);
 
870
 
 
871
    // convert the GList to our vector<> and make the font_instance for each PangoItem at the same time
 
872
    para->pango_items.reserve(g_list_length(pango_items_glist));
 
873
    TRACE(("para itemizes to %d sections\n", g_list_length(pango_items_glist)));
 
874
    for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
 
875
        PangoItemInfo new_item;
 
876
        new_item.item = (PangoItem*)current_pango_item->data;
 
877
        PangoFontDescription *font_description = pango_font_describe(new_item.item->analysis.font);
 
878
        new_item.font = (font_factory::Default())->Face(font_description);
 
879
        pango_font_description_free(font_description);   // Face() makes a copy
 
880
        para->pango_items.push_back(new_item);
 
881
    }
 
882
    g_list_free(pango_items_glist);
 
883
 
 
884
    // and get the character attributes on everything
 
885
    para->char_attributes.resize(para_text.length() + 1);
 
886
    pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
 
887
 
 
888
    TRACE(("end para itemize, direction = %d\n", para->direction));
 
889
}
 
890
 
 
891
/**
 
892
 * Gets the ascent, descent and leading for a font and the alteration that has to be performed
 
893
 * according to the value specified by the line-height css property. The result of multiplying
 
894
 * \a line_height by \a line_height_multiplier is the inline box height as specified in css2
 
895
 * section 10.8.
 
896
 */
 
897
void Layout::Calculator::_computeFontLineHeight(font_instance *font, double font_size,
 
898
                                                SPStyle const *style, LineHeight *line_height,
 
899
                                                double *line_height_multiplier)
 
900
{
 
901
    if (font == NULL) {
 
902
        line_height->setZero();
 
903
        *line_height_multiplier = 1.0;
 
904
    }
 
905
    else
 
906
        font->FontMetrics(line_height->ascent, line_height->descent, line_height->leading);
 
907
    *line_height *= font_size;
 
908
 
 
909
    // yet another borked SPStyle member that we're going to have to fix ourselves
 
910
    for ( ; ; ) {
 
911
        if (style->line_height.set && !style->line_height.inherit) {
 
912
            if (style->line_height.normal)
 
913
                break;
 
914
            switch (style->line_height.unit) {
 
915
                case SP_CSS_UNIT_NONE:
 
916
                    *line_height_multiplier = style->line_height.computed * font_size / line_height->total();
 
917
                    return;
 
918
                case SP_CSS_UNIT_EX:
 
919
                    *line_height_multiplier = style->line_height.value * 0.5 * font_size / line_height->total();
 
920
                    // 0.5 is an approximation of the x-height. Fixme.
 
921
                    return;
 
922
                case SP_CSS_UNIT_EM:
 
923
                case SP_CSS_UNIT_PERCENT:
 
924
                    *line_height_multiplier = style->line_height.value * font_size / line_height->total();
 
925
                    return;
 
926
                default:  // absolute values
 
927
                    *line_height_multiplier = style->line_height.computed / line_height->total();
 
928
                    return;
 
929
            }
 
930
            break;
 
931
        }
 
932
        if (style->object == NULL || style->object->parent == NULL) break;
 
933
        style = style->object->parent->style;
 
934
        if (style == NULL) break;
 
935
    }
 
936
    *line_height_multiplier = LINE_HEIGHT_NORMAL * font_size / line_height->total();
 
937
}
 
938
 
 
939
/**
 
940
 * Split the paragraph into spans. Also call pango_shape() on them.
 
941
 *
 
942
 * Input: para->first_input_index, para->pango_items
 
943
 * Output: para->spans
 
944
 * Returns: the index of the beginning of the following paragraph in _flow._input_stream
 
945
 */
 
946
unsigned Layout::Calculator::_buildSpansForPara(ParagraphInfo *para) const
 
947
{
 
948
    unsigned pango_item_index = 0;
 
949
    unsigned char_index_in_para = 0;
 
950
    unsigned byte_index_in_para = 0;
 
951
    unsigned input_index;
 
952
 
 
953
    TRACE(("build spans\n"));
 
954
    para->free_sequence(para->unbroken_spans);
 
955
 
 
956
    for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
 
957
        if (_flow._input_stream[input_index]->Type() == CONTROL_CODE) {
 
958
            Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
 
959
            if (   control_code->code == SHAPE_BREAK
 
960
                   || control_code->code == PARAGRAPH_BREAK)
 
961
                break;                                    // stop at the end of the paragraph
 
962
            else if (control_code->code == ARBITRARY_GAP) {
 
963
                UnbrokenSpan new_span;
 
964
                new_span.pango_item_index = -1;
 
965
                new_span.input_index = input_index;
 
966
                new_span.line_height.ascent = control_code->ascent;
 
967
                new_span.line_height.descent = control_code->descent;
 
968
                new_span.line_height.leading = 0.0;
 
969
                new_span.text_bytes = 0;
 
970
                new_span.char_index_in_para = char_index_in_para;
 
971
                para->unbroken_spans.push_back(new_span);
 
972
                TRACE(("add gap span %d\n", para->unbroken_spans.size() - 1));
 
973
            }
 
974
        } else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
 
975
            Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
 
976
            unsigned char_index_in_source = 0;
 
977
 
 
978
            unsigned span_start_byte_in_source = 0;
 
979
            // we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
 
980
            for ( ; ; ) {
 
981
                /* we need to change spans at every change of PangoItem, source stream change,
 
982
                   or change in one of the attributes altering position/rotation. */
 
983
 
 
984
                unsigned const pango_item_bytes = ( pango_item_index >= para->pango_items.size()
 
985
                                                    ? 0
 
986
                                                    : ( para->pango_items[pango_item_index].item->offset
 
987
                                                        + para->pango_items[pango_item_index].item->length
 
988
                                                        - byte_index_in_para ) );
 
989
                unsigned const text_source_bytes = ( text_source->text_end.base()
 
990
                                                     - text_source->text_begin.base()
 
991
                                                     - span_start_byte_in_source );
 
992
                UnbrokenSpan new_span;
 
993
                new_span.text_bytes = std::min(text_source_bytes, pango_item_bytes);
 
994
                new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
 
995
                new_span.char_index_in_para = char_index_in_para + char_index_in_source;
 
996
                new_span.input_index = input_index;
 
997
 
 
998
                // cut at <tspan> attribute changes as well
 
999
                new_span.x._set = false;
 
1000
                new_span.y._set = false;
 
1001
                new_span.dx._set = false;
 
1002
                new_span.dy._set = false;
 
1003
                new_span.rotate._set = false;
 
1004
                if (_block_progression == TOP_TO_BOTTOM || _block_progression == BOTTOM_TO_TOP) {
 
1005
                    if (text_source->x.size()  > char_index_in_source) new_span.x  = text_source->x[char_index_in_source];
 
1006
                    if (text_source->y.size()  > char_index_in_source) new_span.y  = text_source->y[char_index_in_source];
 
1007
                    if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
 
1008
                    if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
 
1009
                } else {
 
1010
                    if (text_source->x.size()  > char_index_in_source) new_span.y  = text_source->x[char_index_in_source];
 
1011
                    if (text_source->y.size()  > char_index_in_source) new_span.x  = text_source->y[char_index_in_source];
 
1012
                    if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
 
1013
                    if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
 
1014
                }
 
1015
                if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
 
1016
                else if (char_index_in_source == 0) new_span.rotate = 0.f;
 
1017
                if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
 
1018
                    // if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
 
1019
                    // so that the top of the letters is at zero, not the baseline
 
1020
                    new_span.y = 0.0;
 
1021
                }
 
1022
                Glib::ustring::const_iterator iter_text = new_span.input_stream_first_character;
 
1023
                iter_text++;
 
1024
                for (unsigned i = char_index_in_source + 1 ; ; i++, iter_text++) {
 
1025
                    if (iter_text >= text_source->text_end) break;
 
1026
                    if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
 
1027
                    if (   i >= text_source->x.size() && i >= text_source->y.size()
 
1028
                        && i >= text_source->dx.size() && i >= text_source->dy.size()
 
1029
                        && i >= text_source->rotate.size()) break;
 
1030
                    if (   (text_source->x.size()  > i && text_source->x[i]._set)
 
1031
                        || (text_source->y.size()  > i && text_source->y[i]._set)
 
1032
                        || (text_source->dx.size() > i && text_source->dx[i]._set && text_source->dx[i].computed != 0.0)
 
1033
                        || (text_source->dy.size() > i && text_source->dy[i]._set && text_source->dy[i].computed != 0.0)
 
1034
                        || (text_source->rotate.size() > i && text_source->rotate[i]._set
 
1035
                            && (i == 0 || text_source->rotate[i].computed != text_source->rotate[i - 1].computed))) {
 
1036
                        new_span.text_bytes = iter_text.base() - new_span.input_stream_first_character.base();
 
1037
                        break;
 
1038
                    }
 
1039
                }
 
1040
 
 
1041
                // now we know the length, do some final calculations and add the UnbrokenSpan to the list
 
1042
                new_span.font_size = text_source->styleComputeFontSize();
 
1043
                if (new_span.text_bytes) {
 
1044
                    new_span.glyph_string = pango_glyph_string_new();
 
1045
                    /* Some assertions intended to help diagnose bug #1277746. */
 
1046
                    g_assert( 0 < new_span.text_bytes );
 
1047
                    g_assert( span_start_byte_in_source < text_source->text->bytes() );
 
1048
                    g_assert( span_start_byte_in_source + new_span.text_bytes <= text_source->text->bytes() );
 
1049
                    g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
 
1050
                              == NULL );
 
1051
                    pango_shape(text_source->text->data() + span_start_byte_in_source,
 
1052
                                new_span.text_bytes,
 
1053
                                &para->pango_items[pango_item_index].item->analysis,
 
1054
                                new_span.glyph_string);
 
1055
 
 
1056
                    if (para->pango_items[pango_item_index].item->analysis.level & 1) {
 
1057
                        // pango_shape() will reorder glyphs in rtl sections into visual order which messes
 
1058
                        // us up because the svg spec requires us to draw glyphs in logical order
 
1059
                        // let's reverse the glyphstring on a cluster-by-cluster basis
 
1060
                        const unsigned nglyphs = new_span.glyph_string->num_glyphs;
 
1061
                        std::vector<PangoGlyphInfo> infos(nglyphs);
 
1062
                        std::vector<gint> clusters(nglyphs);
 
1063
                        unsigned i, cluster_start = 0;
 
1064
 
 
1065
                        for (i = 0 ; i < nglyphs ; ++i) {
 
1066
                            if (new_span.glyph_string->glyphs[i].attr.is_cluster_start) {
 
1067
                                if (i != cluster_start) {
 
1068
                                    std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
 
1069
                                    std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
 
1070
                                }
 
1071
                                cluster_start = i;
 
1072
                            }
 
1073
                        }
 
1074
                        if (i != cluster_start) {
 
1075
                            std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
 
1076
                            std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
 
1077
                        }
 
1078
                        std::copy(infos.begin(), infos.end(), new_span.glyph_string->glyphs);
 
1079
                        std::copy(clusters.begin(), clusters.end(), new_span.glyph_string->log_clusters);
 
1080
                    }
 
1081
                    new_span.pango_item_index = pango_item_index;
 
1082
                    _computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
 
1083
                    // TODO: metrics for vertical text
 
1084
                    TRACE(("add text span %d \"%s\"\n", para->unbroken_spans.size(), text_source->text->raw().substr(span_start_byte_in_source, new_span.text_bytes).c_str()));
 
1085
                    TRACE(("  %d glyphs\n", new_span.glyph_string->num_glyphs));
 
1086
                } else {
 
1087
                    // if there's no text we still need to initialise the styles
 
1088
                    new_span.pango_item_index = -1;
 
1089
                    font_instance *font = text_source->styleGetFontInstance();
 
1090
                    if (font) {
 
1091
                        _computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
 
1092
                        font->Unref();
 
1093
                    } else {
 
1094
                        new_span.line_height.setZero();
 
1095
                        new_span.line_height_multiplier = 1.0;
 
1096
                    }
 
1097
                    TRACE(("add style init span %d\n", para->unbroken_spans.size()));
 
1098
                }
 
1099
                para->unbroken_spans.push_back(new_span);
 
1100
 
 
1101
                // calculations for moving to the next UnbrokenSpan
 
1102
                byte_index_in_para += new_span.text_bytes;
 
1103
                char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
 
1104
 
 
1105
                if (new_span.text_bytes >= pango_item_bytes) {   // end of pango item
 
1106
                    pango_item_index++;
 
1107
                    if (pango_item_index == para->pango_items.size()) break;  // end of paragraph
 
1108
                }
 
1109
                if (new_span.text_bytes == text_source_bytes)
 
1110
                    break;    // end of source
 
1111
                // else <tspan> attribute changed
 
1112
                span_start_byte_in_source += new_span.text_bytes;
 
1113
            }
 
1114
            char_index_in_para += char_index_in_source;
 
1115
        }
 
1116
    }
 
1117
    TRACE(("end build spans\n"));
 
1118
    return input_index;
 
1119
}
 
1120
 
 
1121
/**
 
1122
 * Reinitialises the variables required on completion of one shape and
 
1123
 * moving on to the next. Returns false if there are no more shapes to wrap
 
1124
 * in to.
 
1125
 */
 
1126
bool Layout::Calculator::_goToNextWrapShape()
 
1127
{
 
1128
    delete _scanline_maker;
 
1129
    _scanline_maker = NULL;
 
1130
    _current_shape_index++;
 
1131
    if (_current_shape_index == _flow._input_wrap_shapes.size()) return false;
 
1132
    _scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
 
1133
    TRACE(("begin wrap shape %d\n", _current_shape_index));
 
1134
    return true;
 
1135
}
 
1136
 
 
1137
/**
 
1138
 * Given \a para filled in and \a start_span_pos set, keeps trying to
 
1139
 * find somewhere it can fit the next line of text. The process of finding
 
1140
 * the text that fits will involve creating one or more entries in
 
1141
 * \a chunk_info describing the bounds of the fitted text and several
 
1142
 * bits of information that will prove useful when we come to output the
 
1143
 * line to #_flow. Returns with \a start_span_pos set to the end of the
 
1144
 * text that was fitted, \a chunk_info completely filled out and
 
1145
 * \a line_height set to the largest line box on the line. The return
 
1146
 * value is false only if we've run out of shapes to wrap inside (and
 
1147
 * hence couldn't create any chunks).
 
1148
 */
 
1149
bool Layout::Calculator::_findChunksForLine(ParagraphInfo const &para,
 
1150
                                            UnbrokenSpanPosition *start_span_pos,
 
1151
                                            std::vector<ChunkInfo> *chunk_info,
 
1152
                                            LineHeight *line_height)
 
1153
{
 
1154
    // init the initial line_height
 
1155
    if (start_span_pos->iter_span == para.unbroken_spans.end()) {
 
1156
        if (_flow._spans.empty()) {
 
1157
            // empty first para: create a font for the sole purpose of measuring it
 
1158
            InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
 
1159
            font_instance *font = text_source->styleGetFontInstance();
 
1160
            if (font) {
 
1161
                double font_size = text_source->styleComputeFontSize();
 
1162
                double multiplier;
 
1163
                _computeFontLineHeight(font, font_size, text_source->style, line_height, &multiplier);
 
1164
                font->Unref();
 
1165
                *line_height *= multiplier;
 
1166
                _scanline_maker->setNewYCoordinate(_scanline_maker->yCoordinate() - line_height->ascent);
 
1167
            }
 
1168
        }
 
1169
        // else empty subsequent para: keep the old line height
 
1170
    } else {
 
1171
        if (_flow._input_wrap_shapes.empty()) {
 
1172
            // if we're not wrapping set the line_height big and negative so we can use negative line height
 
1173
            line_height->ascent = -1.0e10;
 
1174
            line_height->descent = -1.0e10;
 
1175
            line_height->leading = -1.0e10;
 
1176
        }
 
1177
        else
 
1178
            line_height->setZero();
 
1179
    }
 
1180
 
 
1181
    UnbrokenSpanPosition span_pos;
 
1182
    for( ; ; ) {
 
1183
        std::vector<ScanlineMaker::ScanRun> scan_runs;
 
1184
        scan_runs = _scanline_maker->makeScanline(*line_height);
 
1185
        while (scan_runs.empty()) {
 
1186
            if (!_goToNextWrapShape()) return false;  // no more shapes to wrap in to
 
1187
            scan_runs = _scanline_maker->makeScanline(*line_height);
 
1188
        }
 
1189
 
 
1190
        TRACE(("finding line fit y=%f, %d scan runs\n", scan_runs.front().y, scan_runs.size()));
 
1191
        chunk_info->clear();
 
1192
        chunk_info->reserve(scan_runs.size());
 
1193
        if (para.direction == RIGHT_TO_LEFT) std::reverse(scan_runs.begin(), scan_runs.end());
 
1194
        unsigned scan_run_index;
 
1195
        span_pos = *start_span_pos;
 
1196
        for (scan_run_index = 0 ; scan_run_index < scan_runs.size() ; scan_run_index++) {
 
1197
            if (!_buildChunksInScanRun(para, span_pos, scan_runs[scan_run_index], chunk_info, line_height))
 
1198
                break;
 
1199
            if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty())
 
1200
                span_pos = chunk_info->back().broken_spans.back().end;
 
1201
        }
 
1202
        if (scan_run_index == scan_runs.size()) break;  // ie when buildChunksInScanRun() succeeded
 
1203
    }
 
1204
    *start_span_pos = span_pos;
 
1205
    return true;
 
1206
}
 
1207
 
 
1208
/**
 
1209
 * Given a scan run and a first character, append one or more chunks to
 
1210
 * the \a chunk_info vector that describe all the spans and other detail
 
1211
 * necessary to output the greatest amount of text that will fit on this scan
 
1212
 * line (greedy line breaking algorithm). Each chunk contains one or more
 
1213
 * BrokenSpan structures that link back to UnbrokenSpan structures that link
 
1214
 * to the text itself. Normally there will be either one or zero (if the
 
1215
 * scanrun is too short to fit any text) chunk added to \a chunk_info by
 
1216
 * each call to this method, but we will add more than one if an x or y
 
1217
 * attribute has been set on a tspan. \a line_height must be set on input,
 
1218
 * and if it needs to be made larger and the #_scanline_maker can't do
 
1219
 * an in-situ resize then it will be set to the required value and the
 
1220
 * method will return false.
 
1221
 */
 
1222
bool Layout::Calculator::_buildChunksInScanRun(ParagraphInfo const &para,
 
1223
                                               UnbrokenSpanPosition const &start_span_pos,
 
1224
                                               ScanlineMaker::ScanRun const &scan_run,
 
1225
                                               std::vector<ChunkInfo> *chunk_info,
 
1226
                                               LineHeight *line_height) const
 
1227
{
 
1228
    ChunkInfo new_chunk;
 
1229
    new_chunk.text_width = 0.0;
 
1230
    new_chunk.whitespace_count = 0;
 
1231
    new_chunk.scanrun_width = scan_run.width();
 
1232
    new_chunk.x = scan_run.x_start;
 
1233
 
 
1234
    // we haven't done anything yet so the last valid break position is the beginning
 
1235
    BrokenSpan last_span_at_break, last_span_at_emergency_break;
 
1236
    last_span_at_break.start = start_span_pos;
 
1237
    last_span_at_break.setZero();
 
1238
    last_span_at_emergency_break.start = start_span_pos;
 
1239
    last_span_at_emergency_break.setZero();
 
1240
 
 
1241
    TRACE(("trying chunk from %f to %g\n", scan_run.x_start, scan_run.x_end));
 
1242
    BrokenSpan new_span;
 
1243
    new_span.end = start_span_pos;
 
1244
    while (new_span.end.iter_span != para.unbroken_spans.end()) {    // this loops once for each UnbrokenSpan
 
1245
 
 
1246
        new_span.start = new_span.end;
 
1247
 
 
1248
        // force a chunk change at x or y attribute change
 
1249
        if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
 
1250
 
 
1251
            if (new_span.start.iter_span != start_span_pos.iter_span)
 
1252
                chunk_info->push_back(new_chunk);
 
1253
 
 
1254
            new_chunk.x += new_chunk.text_width;
 
1255
            new_chunk.text_width = 0.0;
 
1256
            new_chunk.whitespace_count = 0;
 
1257
            new_chunk.broken_spans.clear();
 
1258
            if (new_span.start.iter_span->x._set) new_chunk.x = new_span.start.iter_span->x.computed;
 
1259
            // y doesn't need to be done until output time
 
1260
        }
 
1261
 
 
1262
        // see if this span is too tall to fit on the current line
 
1263
        LineHeight total_height = new_span.start.iter_span->line_height;
 
1264
        total_height *= new_span.start.iter_span->line_height_multiplier;
 
1265
        /* floating point 80-bit/64-bit rounding problems require epsilon. See
 
1266
           discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
 
1267
        if (   total_height.ascent  > line_height->ascent  + FLT_EPSILON
 
1268
               || total_height.descent > line_height->descent + FLT_EPSILON
 
1269
               || total_height.leading > line_height->leading + FLT_EPSILON) {
 
1270
            line_height->max(total_height);
 
1271
            if (!_scanline_maker->canExtendCurrentScanline(*line_height))
 
1272
                return false;
 
1273
        }
 
1274
 
 
1275
        bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
 
1276
 
 
1277
        new_chunk.text_width += new_span.width;
 
1278
        new_chunk.whitespace_count += new_span.whitespace_count;
 
1279
        new_chunk.broken_spans.push_back(new_span);   // if !span_fitted we'll correct ourselves below
 
1280
 
 
1281
        if (!span_fitted) break;
 
1282
 
 
1283
        if (new_span.end.iter_span == para.unbroken_spans.end()) {
 
1284
            last_span_at_break = new_span;
 
1285
            break;
 
1286
        }
 
1287
    }
 
1288
 
 
1289
    TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
 
1290
    chunk_info->push_back(new_chunk);
 
1291
 
 
1292
    if (scan_run.width() >= 4.0 * line_height->total() && last_span_at_break.end == start_span_pos) {
 
1293
        /* **non-SVG spec bit**: See bug #1191102
 
1294
           If the user types a very long line with no spaces, the way the spec
 
1295
           is written at the moment means that when the length of the text
 
1296
           exceeds the available width of all remaining areas, the text is
 
1297
           completely hidden. This condition alters that behaviour so that if
 
1298
           the length of the line is greater than four times the line-height
 
1299
           and there are no spaces, it'll be emergency-wrapped at the last
 
1300
           character. One could read the SVG Tiny 1.2 draft as permitting this
 
1301
           sort of behaviour, but it's still a bit dodgy. The hard-coding of
 
1302
           4x is not nice, either. */
 
1303
        last_span_at_break = last_span_at_emergency_break;
 
1304
    }
 
1305
 
 
1306
    if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
 
1307
        // need to back out spans until we come to the one with the last break in it
 
1308
        while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
 
1309
            chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
 
1310
            chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
 
1311
            chunk_info->back().broken_spans.pop_back();
 
1312
            if (chunk_info->back().broken_spans.empty())
 
1313
                chunk_info->pop_back();
 
1314
        }
 
1315
        if (!chunk_info->empty()) {
 
1316
            chunk_info->back().text_width -= chunk_info->back().broken_spans.back().width;
 
1317
            chunk_info->back().whitespace_count -= chunk_info->back().broken_spans.back().whitespace_count;
 
1318
            if (last_span_at_break.start == last_span_at_break.end) {
 
1319
                chunk_info->back().broken_spans.pop_back();   // last break was at an existing boundary
 
1320
                if (chunk_info->back().broken_spans.empty())
 
1321
                    chunk_info->pop_back();
 
1322
            } else {
 
1323
                chunk_info->back().broken_spans.back() = last_span_at_break;
 
1324
                chunk_info->back().text_width += last_span_at_break.width;
 
1325
                chunk_info->back().whitespace_count += last_span_at_break.whitespace_count;
 
1326
            }
 
1327
            TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
 
1328
        }
 
1329
    }
 
1330
 
 
1331
    if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
 
1332
        // for justification we need to discard space occupied by the single whitespace at the end of the chunk
 
1333
        chunk_info->back().broken_spans.back().ends_with_whitespace = false;
 
1334
        chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
 
1335
        chunk_info->back().broken_spans.back().whitespace_count--;
 
1336
        chunk_info->back().text_width -= chunk_info->back().broken_spans.back().each_whitespace_width;
 
1337
        chunk_info->back().whitespace_count--;
 
1338
    }
 
1339
 
 
1340
    return true;
 
1341
}
 
1342
 
 
1343
/** The management function to start the whole thing off. */
 
1344
bool Layout::Calculator::calculate()
 
1345
{
 
1346
    if (_flow._input_stream.empty())
 
1347
        return false;
 
1348
    /**
 
1349
    * hm, why do we want assert (crash) the application, now do simply return false
 
1350
    * \todo check if this is the correct behaviour
 
1351
    * g_assert(_flow._input_stream.front()->Type() == TEXT_SOURCE);
 
1352
    */
 
1353
    if (_flow._input_stream.front()->Type() != TEXT_SOURCE)
 
1354
    {
 
1355
        g_warning("flow text is not of type TEXT_SOURCE. Abort.");
 
1356
        return false;
 
1357
    }
 
1358
    TRACE(("begin calculateFlow()\n"));
 
1359
 
 
1360
    _flow._clearOutputObjects();
 
1361
 
 
1362
    _pango_context = (font_factory::Default())->fontContext;
 
1363
    _font_factory_size_multiplier = (font_factory::Default())->fontSize;
 
1364
 
 
1365
    _block_progression = _flow._blockProgression();
 
1366
    _y_offset = 0.0;
 
1367
    _createFirstScanlineMaker();
 
1368
 
 
1369
    ParagraphInfo para;
 
1370
    LineHeight line_height;     // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
 
1371
    for(para.first_input_index = 0 ; para.first_input_index < _flow._input_stream.size() ; ) {
 
1372
        // jump to the next wrap shape if this is a SHAPE_BREAK control code
 
1373
        if (_flow._input_stream[para.first_input_index]->Type() == CONTROL_CODE) {
 
1374
            InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
 
1375
            if (control_code->code == SHAPE_BREAK) {
 
1376
                TRACE(("shape break control code\n"));
 
1377
                if (!_goToNextWrapShape()) break;
 
1378
                continue;
 
1379
            }
 
1380
        }
 
1381
        if (_scanline_maker == NULL)
 
1382
            break;       // we're trying to flow past the last wrap shape
 
1383
 
 
1384
        _buildPangoItemizationForPara(&para);
 
1385
        unsigned para_end_input_index = _buildSpansForPara(&para);
 
1386
 
 
1387
        if (_flow._input_stream[para.first_input_index]->Type() == TEXT_SOURCE)
 
1388
            para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
 
1389
        else
 
1390
            para.alignment = para.direction == LEFT_TO_RIGHT ? LEFT : RIGHT;
 
1391
 
 
1392
        TRACE(("para prepared, adding as #%d\n", _flow._paragraphs.size()));
 
1393
        Layout::Paragraph new_paragraph;
 
1394
        new_paragraph.base_direction = para.direction;
 
1395
        new_paragraph.alignment = para.alignment;
 
1396
        _flow._paragraphs.push_back(new_paragraph);
 
1397
 
 
1398
        // start scanning lines
 
1399
        UnbrokenSpanPosition span_pos;
 
1400
        span_pos.iter_span = para.unbroken_spans.begin();
 
1401
        span_pos.char_byte = 0;
 
1402
        span_pos.char_index = 0;
 
1403
 
 
1404
        do {   // for each line in the paragraph
 
1405
            TRACE(("begin line\n"));
 
1406
            std::vector<ChunkInfo> line_chunk_info;
 
1407
            if (!_findChunksForLine(para, &span_pos, &line_chunk_info, &line_height))
 
1408
                break;   // out of shapes to wrap in to
 
1409
 
 
1410
            _outputLine(para, line_height, line_chunk_info);
 
1411
            _scanline_maker->completeLine();
 
1412
        } while (span_pos.iter_span != para.unbroken_spans.end());
 
1413
 
 
1414
        TRACE(("para %d end\n\n", _flow._paragraphs.size() - 1));
 
1415
        if (_scanline_maker != NULL) {
 
1416
            bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
 
1417
            if ((is_empty_para && para_end_input_index + 1 >= _flow._input_stream.size())
 
1418
                || para_end_input_index + 1 < _flow._input_stream.size()) {
 
1419
                // we need a span just for the para if it's either an empty last para or a break in the middle
 
1420
                Layout::Span new_span;
 
1421
                if (_flow._spans.empty()) {
 
1422
                    new_span.font = NULL;
 
1423
                    new_span.font_size = line_height.ascent + line_height.descent;
 
1424
                    new_span.line_height = line_height;
 
1425
                    new_span.x_end = 0.0;
 
1426
                } else {
 
1427
                    new_span = _flow._spans.back();
 
1428
                    if (_flow._chunks[new_span.in_chunk].in_line != _flow._lines.size() - 1)
 
1429
                        new_span.x_end = 0.0;
 
1430
                }
 
1431
                new_span.in_chunk = _flow._chunks.size() - 1;
 
1432
                if (new_span.font)
 
1433
                    new_span.font->Ref();
 
1434
                new_span.x_start = new_span.x_end;
 
1435
                new_span.baseline_shift = 0.0;
 
1436
                new_span.direction = para.direction;
 
1437
                new_span.block_progression = _block_progression;
 
1438
                if (para_end_input_index == _flow._input_stream.size())
 
1439
                    new_span.in_input_stream_item = _flow._input_stream.size() - 1;
 
1440
                else
 
1441
                    new_span.in_input_stream_item = para_end_input_index;
 
1442
                _flow._spans.push_back(new_span);
 
1443
            }
 
1444
            if (para_end_input_index + 1 < _flow._input_stream.size()) {
 
1445
                // we've got to add an invisible character between paragraphs so that we can position iterators
 
1446
                // (and hence cursors) both before and after the paragraph break
 
1447
                Layout::Character new_character;
 
1448
                new_character.in_span = _flow._spans.size() - 1;
 
1449
                new_character.char_attributes.is_line_break = 1;
 
1450
                new_character.char_attributes.is_mandatory_break = 1;
 
1451
                new_character.char_attributes.is_char_break = 1;
 
1452
                new_character.char_attributes.is_white = 1;
 
1453
                new_character.char_attributes.is_cursor_position = 1;
 
1454
                new_character.char_attributes.is_word_start = 0;
 
1455
                new_character.char_attributes.is_word_end = 1;
 
1456
                new_character.char_attributes.is_sentence_start = 0;
 
1457
                new_character.char_attributes.is_sentence_end = 1;
 
1458
                new_character.char_attributes.is_sentence_boundary = 1;
 
1459
                new_character.char_attributes.backspace_deletes_character = 1;
 
1460
                new_character.x = _flow._spans.back().x_end - _flow._spans.back().x_start;
 
1461
                new_character.in_glyph = -1;
 
1462
                _flow._characters.push_back(new_character);
 
1463
            }
 
1464
        }
 
1465
        para.free();
 
1466
        para.first_input_index = para_end_input_index + 1;
 
1467
    }
 
1468
 
 
1469
    para.free();
 
1470
    if (_scanline_maker)
 
1471
        delete _scanline_maker;
 
1472
 
 
1473
    return true;
 
1474
}
 
1475
 
 
1476
void Layout::_calculateCursorShapeForEmpty()
 
1477
{
 
1478
    _empty_cursor_shape.position = Geom::Point(0, 0);
 
1479
    _empty_cursor_shape.height = 0.0;
 
1480
    _empty_cursor_shape.rotation = 0.0;
 
1481
    if (_input_stream.empty() || _input_stream.front()->Type() != TEXT_SOURCE)
 
1482
        return;
 
1483
 
 
1484
    InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
 
1485
 
 
1486
    font_instance *font = text_source->styleGetFontInstance();
 
1487
    double font_size = text_source->styleComputeFontSize();
 
1488
    double caret_slope_run = 0.0, caret_slope_rise = 1.0;
 
1489
    LineHeight line_height;
 
1490
    if (font) {
 
1491
        const_cast<font_instance*>(font)->FontSlope(caret_slope_run, caret_slope_rise);
 
1492
        font->FontMetrics(line_height.ascent, line_height.descent, line_height.leading);
 
1493
        line_height *= font_size;
 
1494
        font->Unref();
 
1495
    } else {
 
1496
        line_height.ascent = font_size * 0.85;      // random guesses
 
1497
        line_height.descent = font_size * 0.15;
 
1498
        line_height.leading = 0.0;
 
1499
    }
 
1500
    double caret_slope = atan2(caret_slope_run, caret_slope_rise);
 
1501
    _empty_cursor_shape.height = font_size / cos(caret_slope);
 
1502
    _empty_cursor_shape.rotation = caret_slope;
 
1503
 
 
1504
    if (_input_wrap_shapes.empty()) {
 
1505
        _empty_cursor_shape.position = Geom::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
 
1506
                                                 text_source->y.empty() || !text_source->y.front()._set ? 0.0 : text_source->y.front().computed);
 
1507
    } else {
 
1508
        Direction block_progression = text_source->styleGetBlockProgression();
 
1509
        ShapeScanlineMaker scanline_maker(_input_wrap_shapes.front().shape, block_progression);
 
1510
        std::vector<ScanlineMaker::ScanRun> scan_runs = scanline_maker.makeScanline(line_height);
 
1511
        if (!scan_runs.empty()) {
 
1512
            if (block_progression == LEFT_TO_RIGHT || block_progression == RIGHT_TO_LEFT)
 
1513
                _empty_cursor_shape.position = Geom::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
 
1514
            else
 
1515
                _empty_cursor_shape.position = Geom::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
 
1516
        }
 
1517
    }
 
1518
}
 
1519
 
 
1520
bool Layout::calculateFlow()
 
1521
{
 
1522
    bool result = Calculator(this).calculate();
 
1523
    if (_characters.empty())
 
1524
        _calculateCursorShapeForEmpty();
 
1525
    return result;
 
1526
}
 
1527
 
 
1528
}//namespace Text
 
1529
}//namespace Inkscape
 
1530
 
 
1531
 
 
1532
/*
 
1533
  Local Variables:
 
1534
  mode:c++
 
1535
  c-file-style:"stroustrup"
 
1536
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
 
1537
  indent-tabs-mode:nil
 
1538
  fill-column:99
 
1539
  End:
 
1540
*/
 
1541
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :