Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: f7540fec391495e710e54994a5f61c5686dbf270 (plain) (blame)
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
386
387
388
389
390
391
392
393
394
/*******************************************************************************
 * Copyright (c) 2004, 2008 John Krasnay and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     John Krasnay - initial API and implementation
 *     Igor Jacy Lino Campista - Java 5 warnings fixed (bug 311325)
 *******************************************************************************/
package org.eclipse.vex.core.internal.layout;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.vex.core.internal.core.Drawable;
import org.eclipse.vex.core.internal.core.FontMetrics;
import org.eclipse.vex.core.internal.core.FontResource;
import org.eclipse.vex.core.internal.core.Graphics;
import org.eclipse.vex.core.internal.core.Rectangle;
import org.eclipse.vex.core.internal.css.CSS;
import org.eclipse.vex.core.internal.css.Styles;
import org.eclipse.vex.core.internal.dom.CommentElement;
import org.eclipse.vex.core.internal.dom.Element;
import org.eclipse.vex.core.internal.dom.Node;
import org.eclipse.vex.core.internal.dom.Text;

/**
 * An inline box that represents an inline element. This box is responsible for creating and laying out its child boxes.
 */
public class InlineElementBox extends CompositeInlineBox {

	private static final String COMMENT_AFTER_TEXT = "-->";
	private static final String COMMENT_BEFORE_TEXT = "<!--";
	private final Element element;
	private final InlineBox[] children;
	private InlineBox firstContentChild = null;
	private InlineBox lastContentChild = null;
	private int baseline;
	private int halfLeading;

	/**
	 * Class constructor, called by the createInlineBoxes static factory method.
	 * 
	 * @param context
	 *            LayoutContext to use.
	 * @param element
	 *            Element that generated this box
	 * @param startOffset
	 *            Start offset of the range being rendered, which may be arbitrarily before or inside the element.
	 * @param endOffset
	 *            End offset of the range being rendered, which may be arbitrarily after or inside the element.
	 */
	private InlineElementBox(final LayoutContext context, final Element element, final int startOffset, final int endOffset) {

		this.element = element;

		final List<InlineBox> childList = new ArrayList<InlineBox>();

		final Styles styles = context.getStyleSheet().getStyles(element);

		if (startOffset <= element.getStartOffset()) {

			// space for the left margin/border/padding
			final int space = styles.getMarginLeft().get(0) + styles.getBorderLeftWidth() + styles.getPaddingLeft().get(0);

			if (space > 0) {
				childList.add(new SpaceBox(space, 1));
			}

			if (element instanceof CommentElement) {
				childList.add(new StaticTextBox(context, element, COMMENT_BEFORE_TEXT));
			} else {
				// :before content
				final Element beforeElement;
				beforeElement = context.getStyleSheet().getBeforeElement(element);
				if (beforeElement != null) {
					childList.addAll(LayoutUtils.createGeneratedInlines(context, beforeElement));
				}
				// left marker
				childList.add(createLeftMarker(element, styles));
			}

		}

		// background image
		if (styles.hasBackgroundImage() && !styles.getDisplay().equalsIgnoreCase(CSS.NONE)) {
			final ImageBox imageBox = ImageBox.createWithHeight(getElement(), context, styles.getLineHeight());
			if (imageBox != null) {
				childList.add(imageBox);
			}
		}

		final InlineBoxes inlines = createInlineBoxes(context, element, startOffset, endOffset);
		childList.addAll(inlines.boxes);
		firstContentChild = inlines.firstContentBox;
		lastContentChild = inlines.lastContentBox;

		if (endOffset > element.getEndOffset()) {

			childList.add(new PlaceholderBox(context, element, element.getEndOffset() - element.getStartOffset()));

			if (element instanceof CommentElement) {
				childList.add(new StaticTextBox(context, element, COMMENT_AFTER_TEXT));
			} else {
				// trailing marker
				childList.add(createRightMarker(element, styles));

				// :after content
				final Element afterElement = context.getStyleSheet().getAfterElement(element);
				if (afterElement != null) {
					childList.addAll(LayoutUtils.createGeneratedInlines(context, afterElement));
				}
			}

			// space for the right margin/border/padding
			final int space = styles.getMarginRight().get(0) + styles.getBorderRightWidth() + styles.getPaddingRight().get(0);

			if (space > 0) {
				childList.add(new SpaceBox(space, 1));
			}
		}

		children = childList.toArray(new InlineBox[childList.size()]);
		layout(context);
	}

	/**
	 * Class constructor. This constructor is called by the split method.
	 * 
	 * @param context
	 *            LayoutContext used for the layout.
	 * @param element
	 *            Element to which this box applies.
	 * @param children
	 *            Child boxes.
	 */
	private InlineElementBox(final LayoutContext context, final Element element, final InlineBox[] children) {
		this.element = element;
		this.children = children;
		layout(context);
		for (final InlineBox child : children) {
			if (child.hasContent()) {
				if (firstContentChild == null) {
					firstContentChild = child;
				}
				lastContentChild = child;
			}
		}
	}

	/**
	 * @see org.eclipse.vex.core.internal.layout.InlineBox#getBaseline()
	 */
	public int getBaseline() {
		return baseline;
	}

	/**
	 * @see org.eclipse.vex.core.internal.layout.Box#getChildren()
	 */
	@Override
	public Box[] getChildren() {
		return children;
	}

	/**
	 * Returns the element associated with this box.
	 */
	@Override
	public Element getElement() {
		return element;
	}

	/**
	 * @see org.eclipse.vex.core.internal.layout.Box#getEndOffset()
	 */
	@Override
	public int getEndOffset() {
		if (lastContentChild == null) {
			return getElement().getEndOffset();
		} else {
			return lastContentChild.getEndOffset();
		}
	}

	/**
	 * @see org.eclipse.vex.core.internal.layout.Box#getStartOffset()
	 */
	@Override
	public int getStartOffset() {
		if (firstContentChild == null) {
			return getElement().getStartOffset();
		} else {
			return firstContentChild.getStartOffset();
		}
	}

	/**
	 * Override to paint background and borders.
	 * 
	 * @see org.eclipse.vex.core.internal.layout.AbstractBox#paint(org.eclipse.vex.core.internal.layout.LayoutContext,
	 *      int, int)
	 */
	@Override
	public void paint(final LayoutContext context, final int x, final int y) {
		this.drawBox(context, x, y, 0, true); // TODO CSS violation
		super.paint(context, x, y);
	}

	@Override
	public Pair split(final LayoutContext context, final InlineBox[] lefts, final InlineBox[] rights) {

		InlineElementBox left = null;
		InlineElementBox right = null;

		if (lefts.length > 0 || rights.length == 0) {
			left = new InlineElementBox(context, getElement(), lefts);
		}

		if (rights.length > 0) {
			right = new InlineElementBox(context, getElement(), rights);
		}

		return new Pair(left, right);
	}

	@Override
	public String toString() {
		final StringBuffer sb = new StringBuffer();
		if (getStartOffset() == getElement().getStartOffset() + 1) {
			sb.append("<");
			sb.append(getElement().getPrefixedName());
			sb.append(">");
		}
		final Box[] children = getChildren();
		for (final Box element2 : children) {
			sb.append(element2);
		}
		if (getEndOffset() == getElement().getEndOffset()) {
			sb.append("</");
			sb.append(getElement().getPrefixedName());
			sb.append(">");
		}
		return sb.toString();
	}

	/**
	 * Holds the results of the createInlineBoxes method.
	 */
	static class InlineBoxes {

		/** List of generated boxes */
		public List<InlineBox> boxes = new ArrayList<InlineBox>();

		/** First generated box that has content */
		public InlineBox firstContentBox;

		/** Last generated box that has content */
		public InlineBox lastContentBox;
	}

	/**
	 * Creates a list of inline boxes given a range of offsets. This method is used when creating both ParagraphBoxes
	 * and InlineElementBoxes.
	 * 
	 * @param context
	 *            LayoutContext to be used.
	 * @param element2
	 *            Element containing both offsets
	 * @param startOffset
	 *            The start of the range to convert to inline boxes.
	 * @param endOffset
	 *            The end of the range to convert to inline boxes.
	 * @return
	 */
	static InlineBoxes createInlineBoxes(final LayoutContext context, final Element element2, final int startOffset, final int endOffset) {

		final InlineBoxes result = new InlineBoxes();

		final List<Node> nodes = element2.getChildNodes();
		for (int i = 0; i < nodes.size(); i++) {

			final Node node = nodes.get(i);
			InlineBox child;

			if (node.getStartOffset() >= endOffset) {
				break;
			} else if (node instanceof Text) {

				// This check is different for Text and Element, so we have to
				// do it here and below, too.
				if (node.getEndOffset() <= startOffset) {
					continue;
				}

				final int start = Math.max(startOffset, node.getStartOffset());
				final int end = Math.min(endOffset, node.getEndOffset());
				child = new DocumentTextBox(context, element2, start, end);

			} else {

				if (node.getEndOffset() < startOffset) {
					continue;
				}

				final Element childElement = (Element) node;
				final InlineBox placeholder = new PlaceholderBox(context, element2, childElement.getStartOffset() - element2.getStartOffset());
				result.boxes.add(placeholder);
				if (result.firstContentBox == null) {
					result.firstContentBox = placeholder;
				}
				child = new InlineElementBox(context, childElement, startOffset, endOffset);
			}

			if (result.firstContentBox == null) {
				result.firstContentBox = child;
			}

			result.lastContentBox = child;

			result.boxes.add(child);
		}

		return result;
	}

	// ========================================================== PRIVATE

	private static InlineBox createLeftMarker(final Element element, final Styles styles) {
		final int size = Math.round(0.5f * styles.getFontSize());
		final int lift = Math.round(0.1f * styles.getFontSize());
		final Drawable drawable = new Drawable() {
			public void draw(final Graphics g, final int x, int y) {
				g.setLineStyle(Graphics.LINE_SOLID);
				g.setLineWidth(1);
				y -= lift;
				g.drawLine(x, y - size, x, y);
				g.drawLine(x, y, x + size - 1, y - size / 2);
				g.drawLine(x + size - 1, y - size / 2, x, y - size);
			}

			public Rectangle getBounds() {
				return new Rectangle(0, -size, size, size);
			}
		};
		return new DrawableBox(drawable, element, DrawableBox.START_MARKER);
	}

	private static InlineBox createRightMarker(final Element element, final Styles styles) {
		final int size = Math.round(0.5f * styles.getFontSize());
		final int lift = Math.round(0.1f * styles.getFontSize());
		final Drawable drawable = new Drawable() {
			public void draw(final Graphics g, final int x, int y) {
				g.setLineStyle(Graphics.LINE_SOLID);
				g.setLineWidth(1);
				y -= lift;
				g.drawLine(x + size - 1, y - size, x + size - 1, y);
				g.drawLine(x + size - 1, y, x, y - size / 2);
				g.drawLine(x, y - size / 2, x + size - 1, y - size);
			}

			public Rectangle getBounds() {
				return new Rectangle(0, -size, size, size);
			}
		};
		return new DrawableBox(drawable, element, DrawableBox.END_MARKER);
	}

	private void layout(final LayoutContext context) {
		final Graphics g = context.getGraphics();
		final Styles styles = context.getStyleSheet().getStyles(element);
		final FontResource font = g.createFont(styles.getFont());
		final FontResource oldFont = g.setFont(font);
		final FontMetrics fm = g.getFontMetrics();
		setHeight(styles.getLineHeight());
		halfLeading = (styles.getLineHeight() - fm.getAscent() - fm.getDescent()) / 2;
		baseline = halfLeading + fm.getAscent();
		g.setFont(oldFont);
		font.dispose();

		int x = 0;
		for (final InlineBox child : children) {
			// TODO: honour the child's vertical-align property
			child.setX(x);
			child.alignOnBaseline(baseline);
			x += child.getWidth();
		}

		setWidth(x);
	}

}

Back to the top