File: LayoutLineBox.cpp

package info (click to toggle)
freespace2 24.2.0%2Brepack-3
  • links: PTS, VCS
  • area: non-free
  • in suites: forky, sid
  • size: 43,740 kB
  • sloc: cpp: 595,005; ansic: 21,741; python: 1,174; sh: 457; makefile: 243; xml: 181
file content (385 lines) | stat: -rw-r--r-- 13,252 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
 * This source file is part of libRocket, the HTML/CSS Interface Middleware
 *
 * For the latest information, see http://www.librocket.com
 *
 * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include "precompiled.h"
#include "LayoutLineBox.h"
#include "LayoutBlockBox.h"
#include "LayoutEngine.h"
#include "LayoutInlineBoxText.h"
#include "FontFaceHandle.h"
#include "../../Include/Rocket/Core/Property.h"
#include "../../Include/Rocket/Core/ElementUtilities.h"
#include "../../Include/Rocket/Core/ElementText.h"
#include "../../Include/Rocket/Core/StyleSheetKeywords.h"
#include <stack>

namespace Rocket {
namespace Core {

static float GetSpacing(const Box& box, Box::Edge edge)
{
	return box.GetEdge(Box::PADDING, edge) +
		   box.GetEdge(Box::BORDER, edge) +
		   box.GetEdge(Box::MARGIN, edge);
}

LayoutLineBox::LayoutLineBox(LayoutBlockBox* _parent) : position(-1, -1), dimensions(-1, -1)
{
	parent = _parent;

	box_cursor = 0;
	open_inline_box = NULL;

	position_set = false;
	wrap_content = false;
}

LayoutLineBox::~LayoutLineBox()
{
	for (size_t i = 0; i < inline_boxes.size(); i++)
		delete inline_boxes[i];
}

// Closes the line box, positioning all inline elements within it.
LayoutInlineBox* LayoutLineBox::Close(LayoutInlineBox* overflow)
{
	// If we haven't positioned this line yet, and it has elements in it, then this is a great opportunity to do so.
	if (!position_set &&
		!inline_boxes.empty())
	{
		parent->PositionLineBox(position, dimensions.x, wrap_content, Vector2f(0, 0));
		dimensions.y = 0;

		position_set = true;
	}
	// If the line has been positioned and our content is greater than our original size (for example, if we aren't
	// wrapping or had to render a very long word), then we push our dimensions out to compensate.
	else
		dimensions.x = Math::Max(dimensions.x, box_cursor);

	// Now we calculate the baselines of each of our inline boxes relative to their parent box's baseline; either us,
	// or another of our inline boxes. The maximum distance each element is above and below our baseline is calculated
	// from that, and therefore our height.
	float ascender = 0;
	float descender = 0;
	float minimum_height = 0;

	for (size_t i = 0; i < inline_boxes.size(); ++i)
	{
		LayoutInlineBox* inline_box = inline_boxes[i];

		// Check if we've got an element aligned to the line box rather than a baseline.
		if (inline_box->GetVerticalAlignProperty() == VERTICAL_ALIGN_TOP ||
			inline_box->GetVerticalAlignProperty() == VERTICAL_ALIGN_BOTTOM)
		{
			// Get this element to calculate the baseline offsets of its children; it can't calculate its own baseline
			// because we don't know the height of the line box yet. We don't actually care about its ascender or
			// descender either, just its height.
			float box_ascender, box_descender;
			inline_box->CalculateBaseline(box_ascender, box_descender);

			minimum_height = Math::Max(minimum_height, inline_box->GetHeight());
		}
		// Otherwise, we have an element anchored to a baseline, so we can fetch its ascender and descender relative
		// to our baseline.
		else if (inline_box->GetParent() == NULL)
		{
			float box_ascender, box_descender;
			inline_box->CalculateBaseline(box_ascender, box_descender);

			ascender = Math::Max(ascender, box_ascender - inline_box->GetPosition().y);
			descender = Math::Max(descender, box_descender + inline_box->GetPosition().y);
		}
	}

	// We've now got the maximum ascender and descender, we can calculate the dimensions of the line box.
	dimensions.y = Math::Max(minimum_height, ascender + descender);
	// And from that, we can now set the final baseline of each box.
	for (size_t i = 0; i < inline_boxes.size(); ++i)
	{
		LayoutInlineBox* inline_box = inline_boxes[i];

		// Check again if this element is aligned to the line box. We don't need to worry about offsetting an element
		// tied to the top of the line box, as its position will always stay at exactly 0.
		if (inline_box->GetVerticalAlignProperty() == VERTICAL_ALIGN_TOP ||
			inline_box->GetVerticalAlignProperty() == VERTICAL_ALIGN_BOTTOM)
		{
			if (inline_box->GetVerticalAlignProperty() == VERTICAL_ALIGN_TOP)
				inline_box->OffsetBaseline(inline_box->GetHeight() - inline_box->GetBaseline());
			else
				inline_box->OffsetBaseline(dimensions.y - inline_box->GetBaseline());
		}
		// Otherwise, this element is tied to a baseline.
		else if (inline_box->GetParent() == NULL)
			inline_box->OffsetBaseline(ascender);
	}

	// Position all the boxes horizontally in the line. We only need to reposition the elements if they're set to
	// centre or right; the element are already placed left-aligned, and justification occurs at the text level.
	int text_align_property = parent->GetParent()->GetElement()->GetTextAlign();
	if (text_align_property == TEXT_ALIGN_CENTER ||
		text_align_property == TEXT_ALIGN_RIGHT)
	{
		float element_offset = 0;
		switch (text_align_property)
		{
			case TEXT_ALIGN_CENTER:		element_offset = (dimensions.x - box_cursor) * 0.5f; break;
			case TEXT_ALIGN_RIGHT:		element_offset = (dimensions.x - box_cursor); break;
		}

		if (element_offset != 0)
		{
			element_offset = LayoutEngine::Round(element_offset);

			for (size_t i = 0; i < inline_boxes.size(); i++)
				inline_boxes[i]->SetHorizontalPosition(inline_boxes[i]->GetPosition().x + element_offset);
		}
	}

	// Get each line box to set the position of their element, relative to their parents.
	for (int i = (int) inline_boxes.size() - 1; i >= 0; --i)
	{
		inline_boxes[i]->PositionElement();

		// Check if this inline box is part of the open box chain.
		bool inline_box_open = false;
		LayoutInlineBox* open_box = open_inline_box;
		while (open_box != NULL &&
			   !inline_box_open)
		{
			if (inline_boxes[i] == open_box)
				inline_box_open = true;

			open_box = open_box->GetParent();
		}

		inline_boxes[i]->SizeElement(inline_box_open);
	}

	return parent->CloseLineBox(this, overflow, open_inline_box);
}

// Closes one of the line box's inline boxes.
void LayoutLineBox::CloseInlineBox(LayoutInlineBox* inline_box)
{
	ROCKET_ASSERT(open_inline_box == inline_box);

	open_inline_box = inline_box->GetParent();
	box_cursor += GetSpacing(inline_box->GetBox(), Box::RIGHT);
}

// Attempts to add a new element to this line box.
LayoutInlineBox* LayoutLineBox::AddElement(Element* element, const Box& box)
{
	if (dynamic_cast< ElementText* >(element) != NULL)
		return AddBox(new LayoutInlineBoxText(element));
	else
		return AddBox(new LayoutInlineBox(element, box));
}

// Attempts to add a new inline box to this line.
LayoutInlineBox* LayoutLineBox::AddBox(LayoutInlineBox* box)
{
	// Set to true if we're flowing the first box (with content) on the line.
	bool first_box = false;
	// The spacing this element must leave on the right of the line, to account not only for its margins and padding,
	// but also for its parents which will close immediately after it.
	float right_spacing;

	// If this line is unplaced, then this is the first inline box; if it is sized, then we can place and size this
	// line.
	if (!position_set)
	{
		// Add the new box to the list of boxes in the line box. As this line box has not been placed, we don't have to
		// check if it can fit yet.
		AppendBox(box);

		// If the new box has a physical prescence, then we must place this line once we've figured out how wide it has to
		// be.
		if (box->GetBox().GetSize().x >= 0)
		{
			// Calculate the dimensions for the box we need to fit.
			Vector2f minimum_dimensions = box->GetBox().GetSize();

			// Add the width of any empty, already closed tags, or still opened spaced tags.
			minimum_dimensions.x += box_cursor;

			// Calculate the right spacing for the element.
			right_spacing = GetSpacing(box->GetBox(), Box::RIGHT);
			// Add the right spacing for any ancestor elements that must close immediately after it.
			LayoutInlineBox* closing_box = box;
			while (closing_box != NULL &&
				   closing_box->IsLastChild())
			{
				closing_box = closing_box->GetParent();
				if (closing_box != NULL)
					right_spacing += GetSpacing(closing_box->GetBox(), Box::RIGHT);
			}

			if (!box->CanOverflow())
				minimum_dimensions.x += right_spacing;

			parent->PositionLineBox(position, dimensions.x, wrap_content, minimum_dimensions);
			dimensions.y = minimum_dimensions.y;

			first_box = true;
			position_set = true;
		}
		else
			return box;
	}

	// This line has already been placed and sized, so we'll check if we can fit this new inline box on the line.
	else
	{
		// Build up the spacing required on the right side of this element. This consists of the right spacing on the
		// new element, and the right spacing on all parent element that will close next.
		right_spacing = GetSpacing(box->GetBox(), Box::RIGHT);
		if (open_inline_box != NULL &&
			box->IsLastChild())
		{
			LayoutInlineBox* closing_box = open_inline_box;
			while (closing_box != NULL &&
				   closing_box->IsLastChild())
			{
				closing_box = closing_box->GetParent();
				if (closing_box != NULL)
					right_spacing += GetSpacing(closing_box->GetBox(), Box::RIGHT);
			}
		}

		// Determine the inline box's spacing requirements (before we get onto it's actual content width).
		float element_width = box->GetBox().GetPosition(Box::CONTENT).x;
		if (!box->CanOverflow())
			element_width += right_spacing;

		// Add on the box's content area (if it has content).
		if (box->GetBox().GetSize().x >= 0)
			element_width += box->GetBox().GetSize().x;

		if (wrap_content &&
			box_cursor + element_width > dimensions.x)
		{
			// We can't fit the new inline element into this box! So we'll close this line box, and send the inline box
			// onto the next line.
			return Close(box);
		}
		else
		{
			// We can fit the new inline element into this box.
			AppendBox(box);
		}
	}

	// Flow the box's content into the line.
	LayoutInlineBox* overflow_box = open_inline_box->FlowContent(first_box, wrap_content ? dimensions.x - (open_inline_box->GetPosition().x + open_inline_box->GetBox().GetPosition(Box::CONTENT).x) : -1, right_spacing);
	box_cursor += open_inline_box->GetBox().GetSize().x;

	// If our box overflowed, then we'll close this line (as no more content will fit onto it) and tell our block box
	// to make a new line.
	if (overflow_box != NULL)
	{
		open_inline_box = open_inline_box->GetParent();
		return Close(overflow_box);
	}

	return open_inline_box;
}

// Adds an inline box as a chained hierarchy overflowing to this line.
void LayoutLineBox::AddChainedBox(LayoutInlineBox* chained_box)
{
	std::stack< LayoutInlineBox* > hierarchy;
	LayoutInlineBox* chain = chained_box;
	while (chain != NULL)
	{
		hierarchy.push(chain);
		chain = chain->GetParent();
	}

	while (!hierarchy.empty())
	{
		AddBox(new LayoutInlineBox(hierarchy.top()));
		hierarchy.pop();
	}
}

// Returns the position of the line box, relative to its parent's block box's content area.
const Vector2f& LayoutLineBox::GetPosition() const
{
	return position;
}

// Returns the position of the line box, relative to its parent's block box's offset parent.
Vector2f LayoutLineBox::GetRelativePosition() const
{
	return position - (parent->GetOffsetParent()->GetPosition() - parent->GetOffsetRoot()->GetPosition());
}

// Returns the dimensions of the line box.
const Vector2f& LayoutLineBox::GetDimensions() const
{
	return dimensions;
}

// Returns the line box's open inline box.
LayoutInlineBox* LayoutLineBox::GetOpenInlineBox()
{
	return open_inline_box;
}

// Returns the line's containing block box.
LayoutBlockBox* LayoutLineBox::GetBlockBox()
{
	return parent;
}

void* LayoutLineBox::operator new(size_t size)
{
	return LayoutEngine::AllocateLayoutChunk(size);
}

void LayoutLineBox::operator delete(void* chunk)
{
	LayoutEngine::DeallocateLayoutChunk(chunk);
}

// Appends an inline box to the end of the line box's list of inline boxes.
void LayoutLineBox::AppendBox(LayoutInlineBox* box)
{
	inline_boxes.push_back(box);

	box->SetParent(open_inline_box);
	box->SetLine(this);
	box->SetHorizontalPosition(LayoutEngine::Round(box_cursor + box->GetBox().GetEdge(Box::MARGIN, Box::LEFT)));
	box_cursor += GetSpacing(box->GetBox(), Box::LEFT);

	open_inline_box = box;
}

}
}