Layout-TNG-Compute.cpp revision da8aaf69891c33fe2263d877a65503cd1ce5c9c2
606N/A#include "Layout-TNG.h"
606N/A#include "font-instance.h"
606N/A#include "svg/svg-length.h"
606N/A#include "sp-object.h"
606N/A#include "Layout-TNG-Scanline-Maker.h"
235N/Astatic Layout::EnumConversionItem const enum_convert_spstyle_direction_to_pango_direction[] = {
406N/A keep adding characters until we run out of space in the chunk, then back up to the last word boundary
606N/A push all the glyphs, chars, spans, chunks and line to output (not completely trivial because we must draw rtl in character order) (in _outputLine())
...and all of that needs to work vertically too, and with all the little details that make life annoying
class SpanPosition;
friend class SpanPosition;
double _y_offset;
double _font_factory_size_multiplier;
struct InputItemInfo {
bool in_sub_flow;
void free();
struct PangoItemInfo {
void free();
struct UnbrokenSpan {
double font_size;
unsigned text_bytes;
unsigned char_index_in_para; /// the index of the first character in this span in the paragraph, for looking up char_attributes
SVGLength x, y, dx, dy, rotate; // these are reoriented copies of the <tspan> attributes. We change span when we encounter one.
struct UnbrokenSpanPosition {
unsigned char_byte;
unsigned char_index;
struct BrokenSpan {
unsigned start_glyph_index;
unsigned end_glyph_index;
double width;
unsigned whitespace_count;
bool ends_with_whitespace;
double each_whitespace_width;
void setZero();
struct ChunkInfo {
double scanrun_width;
int whitespace_count;
struct ParagraphInfo {
void free();
/* *********************************************************************************************************/
double *line_height_multiplier);
/* *********************************************************************************************************/
bool _goToNextWrapShape();
bool _measureUnbrokenSpan(ParagraphInfo const ¶, BrokenSpan *span, BrokenSpan *last_break_span, BrokenSpan *last_emergency_break_span, double maximum_width) const
InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[span->start.iter_span->input_index]);
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[span->start.iter_span->input_index]);
double font_size_multiplier = span->start.iter_span->font_size / (PANGO_SCALE * _font_factory_size_multiplier);
bool soft_hyphen_in_word = false;
bool is_soft_hyphen = false;
&& span->start.iter_span->glyph_string->log_clusters[span->start_glyph_index] < (int)span->start.char_byte)
TRACE(("span %d end of para; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
if (soft_hyphen_in_word) {
if (!is_soft_hyphen)
soft_hyphen_in_word = false;
// todo: break between chars if necessary (ie no word breaks present) when doing rectangular flowing
&& span->end.iter_span->glyph_string->log_clusters[span->end_glyph_index] <= (int)span->end.char_byte) {
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);
char_width += font_size_multiplier * span->end.iter_span->glyph_string->glyphs[span->end_glyph_index].geometry.width;
is_soft_hyphen = (UNICODE_SOFT_HYPHEN == *Glib::ustring::const_iterator(span->end.iter_span->input_stream_first_character.base() + span->end.char_byte));
if (is_soft_hyphen)
if (span->width > maximum_width && !char_attributes.is_white) { // whitespaces don't matter, we can put as many as we want at eol
TRACE(("span %d exceeded scanrun; width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
TRACE(("fitted span %d width = %f chars = %d\n", span->start.iter_span - para.unbroken_spans.begin(), span->width, char_count));
/* *********************************************************************************************************/
double _getChunkLeftWithAlignment(ParagraphInfo const ¶, std::vector<ChunkInfo>::const_iterator it_chunk, double *add_to_each_whitespace) const
case FULL:
case LEFT:
return it_chunk->x;
case RIGHT:
case CENTER:
case FULL:
&& it_chunk->broken_spans.back().end.iter_span != para.unbroken_spans.end()) { // don't justify the last chunk in the para
*add_to_each_whitespace = (it_chunk->scanrun_width - it_chunk->text_width) / it_chunk->whitespace_count;
return it_chunk->x;
case LEFT:
return it_chunk->x;
case RIGHT:
case CENTER:
void _outputLine(ParagraphInfo const ¶, LineHeight const &line_height, std::vector<ChunkInfo> const &chunk_info)
for (std::vector<ChunkInfo>::const_iterator it_chunk = chunk_info.begin() ; it_chunk != chunk_info.end() ; it_chunk++) {
double add_to_each_whitespace;
// if this is the start of a line, we should change the baseline rather than each glyph individually
if (_flow._characters.empty() || _flow._characters.back().chunk(&_flow).in_line != _flow._lines.size() - 1) {
double direction_sign;
for (std::vector<BrokenSpan>::const_iterator it_span = it_chunk->broken_spans.begin() ; it_span != it_chunk->broken_spans.end() ; it_span++) {
// start of an unbroken span, we might have dx, dy or rotate still to process (x and y are done per chunk)
new_span.direction = para.pango_items[unbroken_span.pango_item_index].item->analysis.level & 1 ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
new_span.input_stream_first_character = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte);
for (it_following_span = it_span ; it_following_span != it_chunk->broken_spans.end() ; it_following_span++) {
Layout::Direction following_span_progression = static_cast<InputStreamTextSource const *>(_flow._input_stream[it_following_span->start.iter_span->input_index])->styleGetBlockProgression();
if (it_following_span->start.iter_span->pango_item_index == -1) { // when the span came from a control code
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;
counter_directional_width_remaining += direction_sign * (it_following_span->width + it_following_span->whitespace_count * add_to_each_whitespace);
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream[unbroken_span.input_index]);
Glib::ustring::const_iterator iter_source_text = Glib::ustring::const_iterator(unbroken_span.input_stream_first_character.base() + it_span->start.char_byte) ;
for (unsigned glyph_index = it_span->start_glyph_index ; glyph_index < it_span->end_glyph_index ; glyph_index++) {
new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
glyph_index++;
glyph_index--;
/* put something like this back in when we do glyph-rotation-horizontal/vertical
new_glyph.y -= unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier * 0.5;
new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier + new_span.line_height.ascent;
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;
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);
new_glyph.x = x + unbroken_span.glyph_string->glyphs[glyph_index].geometry.x_offset * font_size_multiplier;
new_glyph.y = _y_offset + unbroken_span.glyph_string->glyphs[glyph_index].geometry.y_offset * font_size_multiplier;
new_glyph.width = unbroken_span.glyph_string->glyphs[glyph_index].geometry.width * font_size_multiplier;
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);
// for some reason pango returns zero width for invalid glyph characters (those empty boxes), so go to freetype for the info
if (unbroken_span.glyph_string->glyphs[rtl_index].attr.is_cluster_start && rtl_index != glyph_index)
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);
cluster_width += font_size_multiplier * unbroken_span.glyph_string->glyphs[rtl_index].geometry.width;
unsigned end_byte;
new_character.char_attributes = para.char_attributes[unbroken_span.char_index_in_para + char_index_in_unbroken_span];
advance_width += text_source->style->word_spacing.computed + add_to_each_whitespace; // justification
x -= advance_width;
x += advance_width;
x += static_cast<InputStreamControlCode const *>(_flow._input_stream[unbroken_span.input_index])->width;
/* *********************************************************************************************************/
void _createFirstScanlineMaker()
_current_shape_index = 0;
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
_scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
bool calculate();
if (sub_flow) {
delete sub_flow;
if (item) {
if (font) {
char_index++;
iter_span++;
whitespace_count = 0;
ends_with_whitespace = false;
// for(int input_index = para->first_input_index ; input_index < (int)_flow._input_stream.size() ; input_index++) {
// input_item.sub_flow = NULL;
// Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
// Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
// input_item.sub_flow = new Layout;
// for (int sub_input_index = run_start_input_index ; sub_input_index <= run_end_input_index ; sub_input_index++) {
// Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[sub_input_index]);
// input_item.sub_flow->appendControlCode(control_code->code, control_code->source_cookie, control_code->width, control_code->ascent, control_code->descent);
// Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[sub_input_index]);
// 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);
// Layout::InputStreamTextSource *sub_flow_text_source = static_cast<Layout::InputStreamTextSource *>(input_item.sub_flow->_input_stream.back());
// sub_flow_text_source->x = text_source->x; // this is easier than going via optionalattrs for the appendText() call
// sub_flow_text_source->y = text_source->y; // should these actually be allowed anyway? You'll almost never get the results you expect
// sub_flow_text_source->dx = text_source->dx; // (not that it's very clear what you should expect, anyway)
// input_item.sub_flow->calculateFlow();
* Output: para.direction, para.pango_items, para.char_attributes.
unsigned input_index;
for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
Layout::InputStreamTextSource *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[input_index]);
para_text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text
Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource *>(_flow._input_stream[para->first_input_index]);
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]));
pango_items_glist = pango_itemize_with_base_dir(_pango_context, pango_direction, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
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]));
pango_items_glist = pango_itemize(_pango_context, para_text.data(), 0, para_text.bytes(), attributes_list, NULL);
// I think according to the css spec this is wrong and we're never allowed to guess the directionality
else para->direction = (((PangoItem*)pango_items_glist->data)->analysis.level & 1) ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
for (GList *current_pango_item = pango_items_glist ; current_pango_item != NULL ; current_pango_item = current_pango_item->next) {
pango_get_log_attrs(para_text.data(), para_text.bytes(), -1, NULL, &*para->char_attributes.begin(), para->char_attributes.size());
double *line_height_multiplier)
case SP_CSS_UNIT_NONE:
case SP_CSS_UNIT_EX:
case SP_CSS_UNIT_EM:
case SP_CSS_UNIT_PERCENT:
unsigned pango_item_index = 0;
unsigned char_index_in_para = 0;
unsigned byte_index_in_para = 0;
unsigned input_index;
for(input_index = para->first_input_index ; input_index < _flow._input_stream.size() ; input_index++) {
Layout::InputStreamControlCode const *control_code = static_cast<Layout::InputStreamControlCode const *>(_flow._input_stream[input_index]);
} else if (_flow._input_stream[input_index]->Type() == TEXT_SOURCE && pango_item_index < para->pango_items.size()) {
Layout::InputStreamTextSource const *text_source = static_cast<Layout::InputStreamTextSource const *>(_flow._input_stream[input_index]);
unsigned char_index_in_source = 0;
unsigned span_start_byte_in_source = 0;
// we'll need to make several spans from each text source, based on the rules described about the UnbrokenSpan definition
- byte_index_in_para ) );
new_span.input_stream_first_character = Glib::ustring::const_iterator(text_source->text_begin.base() + span_start_byte_in_source);
if (text_source->x.size() > char_index_in_source) new_span.x = text_source->x[char_index_in_source];
if (text_source->y.size() > char_index_in_source) new_span.y = text_source->y[char_index_in_source];
if (text_source->dx.size() > char_index_in_source) new_span.dx = text_source->dx[char_index_in_source];
if (text_source->dy.size() > char_index_in_source) new_span.dy = text_source->dy[char_index_in_source];
if (text_source->x.size() > char_index_in_source) new_span.y = text_source->x[char_index_in_source];
if (text_source->y.size() > char_index_in_source) new_span.x = text_source->y[char_index_in_source];
if (text_source->dx.size() > char_index_in_source) new_span.dy = text_source->dx[char_index_in_source];
if (text_source->dy.size() > char_index_in_source) new_span.dx = text_source->dy[char_index_in_source];
if (text_source->rotate.size() > char_index_in_source) new_span.rotate = text_source->rotate[char_index_in_source];
if (input_index == 0 && para->unbroken_spans.empty() && !new_span.y._set && _flow._input_wrap_shapes.empty()) {
// if we don't set an explicit y some of the automatic wrapping code takes over and moves the text vertically
iter_text++;
if (iter_text.base() - new_span.input_stream_first_character.base() >= (int)new_span.text_bytes) break;
g_assert( memchr(text_source->text->data() + span_start_byte_in_source, '\0', static_cast<size_t>(new_span.text_bytes))
== NULL );
unsigned i, cluster_start = 0;
for (i = 0 ; i < nglyphs ; ++i) {
if (i != cluster_start) {
std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
cluster_start = i;
if (i != cluster_start) {
std::copy(&new_span.glyph_string->glyphs[cluster_start], &new_span.glyph_string->glyphs[i], infos.end() - i);
std::copy(&new_span.glyph_string->log_clusters[cluster_start], &new_span.glyph_string->log_clusters[i], clusters.end() - i);
_computeFontLineHeight(para->pango_items[pango_item_index].font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
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()));
if (font) {
_computeFontLineHeight(font, new_span.font_size, text_source->style, &new_span.line_height, &new_span.line_height_multiplier);
char_index_in_source += g_utf8_strlen(&*new_span.input_stream_first_character.base(), new_span.text_bytes);
return input_index;
delete _scanline_maker;
_scanline_maker = new ShapeScanlineMaker(_flow._input_wrap_shapes[_current_shape_index].shape, _block_progression);
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_flow._input_stream.front());
if (font) {
double multiplier;
unsigned scan_run_index;
while (new_span.end.iter_span != para.unbroken_spans.end()) { // this loops once for each UnbrokenSpan
if ((new_span.start.iter_span->x._set || new_span.start.iter_span->y._set) && new_span.start.char_byte == 0) {
discussion http://inkscape.gristle.org/2005-03-16.txt around 22:00 */
bool span_fitted = _measureUnbrokenSpan(para, &new_span, &last_span_at_break, &last_span_at_emergency_break, new_chunk.scanrun_width - new_chunk.text_width);
if (!span_fitted) break;
TRACE(("chunk complete, used %f width (%d whitespaces, %d brokenspans)\n", new_chunk.text_width, new_chunk.whitespace_count, new_chunk.broken_spans.size()));
if (!chunk_info->back().broken_spans.empty() && last_span_at_break.end != chunk_info->back().broken_spans.back().end) {
while (!chunk_info->empty() && last_span_at_break.start.iter_span != chunk_info->back().broken_spans.back().start.iter_span) {
TRACE(("correction: fitted span %d width = %f\n", last_span_at_break.start.iter_span - para.unbroken_spans.begin(), last_span_at_break.width));
if (!chunk_info->empty() && !chunk_info->back().broken_spans.empty() && chunk_info->back().broken_spans.back().ends_with_whitespace) {
// for justification we need to discard space occupied by the single whitespace at the end of the chunk
chunk_info->back().broken_spans.back().width -= chunk_info->back().broken_spans.back().each_whitespace_width;
LineHeight line_height; // needs to be maintained across paragraphs to be able to deal with blank paras (this is wrong)
InputStreamControlCode const *control_code = static_cast<InputStreamControlCode const *>(_flow._input_stream[para.first_input_index]);
if (!_goToNextWrapShape()) break;
para.alignment = static_cast<InputStreamTextSource*>(_flow._input_stream[para.first_input_index])->styleGetAlignment(para.direction, !_flow._input_wrap_shapes.empty());
bool is_empty_para = _flow._characters.empty() || _flow._characters.back().line(&_flow).in_paragraph != _flow._paragraphs.size() - 1;
if (_scanline_maker)
delete _scanline_maker;
InputStreamTextSource const *text_source = static_cast<InputStreamTextSource const *>(_input_stream.front());
if (font) {
_empty_cursor_shape.position = NR::Point(text_source->x.empty() || !text_source->x.front()._set ? 0.0 : text_source->x.front().computed,
_empty_cursor_shape.position = NR::Point(scan_runs.front().y + font_size, scan_runs.front().x_start);
_empty_cursor_shape.position = NR::Point(scan_runs.front().x_start, scan_runs.front().y + font_size);
return result;