OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "ui/gfx/render_text_linux.h" | |
6 | |
7 #include <pango/pangocairo.h> | |
8 #include <algorithm> | |
9 #include <string> | |
10 #include <vector> | |
11 | |
12 #include "base/i18n/break_iterator.h" | |
13 #include "base/logging.h" | |
14 #include "third_party/skia/include/core/SkTypeface.h" | |
15 #include "ui/gfx/canvas.h" | |
16 #include "ui/gfx/font.h" | |
17 #include "ui/gfx/font_render_params_linux.h" | |
18 #include "ui/gfx/pango_util.h" | |
19 #include "ui/gfx/utf16_indexing.h" | |
20 | |
21 namespace gfx { | |
22 | |
23 namespace { | |
24 | |
25 // Returns the preceding element in a GSList (O(n)). | |
26 GSList* GSListPrevious(GSList* head, GSList* item) { | |
27 GSList* prev = NULL; | |
28 for (GSList* cur = head; cur != item; cur = cur->next) { | |
29 DCHECK(cur); | |
30 prev = cur; | |
31 } | |
32 return prev; | |
33 } | |
34 | |
35 // Returns true if the given visual cursor |direction| is logically forward | |
36 // motion in the given Pango |item|. | |
37 bool IsForwardMotion(VisualCursorDirection direction, const PangoItem* item) { | |
38 bool rtl = item->analysis.level & 1; | |
39 return rtl == (direction == CURSOR_LEFT); | |
40 } | |
41 | |
42 // Checks whether |range| contains |index|. This is not the same as calling | |
43 // |range.Contains(gfx::Range(index))| - as that would return true when | |
44 // |index| == |range.end()|. | |
45 bool IndexInRange(const Range& range, size_t index) { | |
46 return index >= range.start() && index < range.end(); | |
47 } | |
48 | |
49 // Sets underline metrics on |renderer| according to Pango font |desc|. | |
50 void SetPangoUnderlineMetrics(PangoFontDescription *desc, | |
51 internal::SkiaTextRenderer* renderer) { | |
52 PangoFontMetrics* metrics = GetPangoFontMetrics(desc); | |
53 int thickness = pango_font_metrics_get_underline_thickness(metrics); | |
54 // Pango returns the position "above the baseline". Change its sign to convert | |
55 // it to a vertical offset from the baseline. | |
56 int position = -pango_font_metrics_get_underline_position(metrics); | |
57 pango_quantize_line_geometry(&thickness, &position); | |
58 // Note: pango_quantize_line_geometry() guarantees pixel boundaries, so | |
59 // PANGO_PIXELS() is safe to use. | |
60 renderer->SetUnderlineMetrics(PANGO_PIXELS(thickness), | |
61 PANGO_PIXELS(position)); | |
62 } | |
63 | |
64 } // namespace | |
65 | |
66 // TODO(xji): index saved in upper layer is utf16 index. Pango uses utf8 index. | |
67 // Since caret_pos is used internally, we could save utf8 index for caret_pos | |
68 // to avoid conversion. | |
69 | |
70 RenderTextLinux::RenderTextLinux() | |
71 : layout_(NULL), | |
72 current_line_(NULL), | |
73 log_attrs_(NULL), | |
74 num_log_attrs_(0), | |
75 layout_text_(NULL) { | |
76 } | |
77 | |
78 RenderTextLinux::~RenderTextLinux() { | |
79 ResetLayout(); | |
80 } | |
81 | |
82 Size RenderTextLinux::GetStringSize() { | |
83 EnsureLayout(); | |
84 int width = 0, height = 0; | |
85 pango_layout_get_pixel_size(layout_, &width, &height); | |
86 // Keep a consistent height between this particular string's PangoLayout and | |
87 // potentially larger text supported by the FontList. | |
88 // For example, if a text field contains a Japanese character, which is | |
89 // smaller than Latin ones, and then later a Latin one is inserted, this | |
90 // ensures that the text baseline does not shift. | |
91 return Size(width, std::max(height, font_list().GetHeight())); | |
92 } | |
93 | |
94 SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) { | |
95 EnsureLayout(); | |
96 | |
97 if (text().empty()) | |
98 return SelectionModel(0, CURSOR_FORWARD); | |
99 | |
100 Point p(ToTextPoint(point)); | |
101 | |
102 // When the point is outside of text, return HOME/END position. | |
103 if (p.x() < 0) | |
104 return EdgeSelectionModel(CURSOR_LEFT); | |
105 if (p.x() > GetStringSize().width()) | |
106 return EdgeSelectionModel(CURSOR_RIGHT); | |
107 | |
108 int caret_pos = 0, trailing = 0; | |
109 pango_layout_xy_to_index(layout_, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE, | |
110 &caret_pos, &trailing); | |
111 | |
112 DCHECK_GE(trailing, 0); | |
113 if (trailing > 0) { | |
114 caret_pos = g_utf8_offset_to_pointer(layout_text_ + caret_pos, | |
115 trailing) - layout_text_; | |
116 DCHECK_LE(static_cast<size_t>(caret_pos), strlen(layout_text_)); | |
117 } | |
118 | |
119 return SelectionModel(LayoutIndexToTextIndex(caret_pos), | |
120 (trailing > 0) ? CURSOR_BACKWARD : CURSOR_FORWARD); | |
121 } | |
122 | |
123 std::vector<RenderText::FontSpan> RenderTextLinux::GetFontSpansForTesting() { | |
124 EnsureLayout(); | |
125 | |
126 std::vector<RenderText::FontSpan> spans; | |
127 for (GSList* it = current_line_->runs; it; it = it->next) { | |
128 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(it->data)->item; | |
129 const int start = LayoutIndexToTextIndex(item->offset); | |
130 const int end = LayoutIndexToTextIndex(item->offset + item->length); | |
131 const Range range(start, end); | |
132 | |
133 ScopedPangoFontDescription desc(pango_font_describe(item->analysis.font)); | |
134 spans.push_back(RenderText::FontSpan(Font(desc.get()), range)); | |
135 } | |
136 | |
137 return spans; | |
138 } | |
139 | |
140 int RenderTextLinux::GetLayoutTextBaseline() { | |
141 EnsureLayout(); | |
142 return PANGO_PIXELS(pango_layout_get_baseline(layout_)); | |
143 } | |
144 | |
145 SelectionModel RenderTextLinux::AdjacentCharSelectionModel( | |
146 const SelectionModel& selection, | |
147 VisualCursorDirection direction) { | |
148 GSList* run = GetRunContainingCaret(selection); | |
149 if (!run) { | |
150 // The cursor is not in any run: we're at the visual and logical edge. | |
151 SelectionModel edge = EdgeSelectionModel(direction); | |
152 if (edge.caret_pos() == selection.caret_pos()) | |
153 return edge; | |
154 else | |
155 run = (direction == CURSOR_RIGHT) ? | |
156 current_line_->runs : g_slist_last(current_line_->runs); | |
157 } else { | |
158 // If the cursor is moving within the current run, just move it by one | |
159 // grapheme in the appropriate direction. | |
160 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
161 size_t caret = selection.caret_pos(); | |
162 if (IsForwardMotion(direction, item)) { | |
163 if (caret < LayoutIndexToTextIndex(item->offset + item->length)) { | |
164 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); | |
165 return SelectionModel(caret, CURSOR_BACKWARD); | |
166 } | |
167 } else { | |
168 if (caret > LayoutIndexToTextIndex(item->offset)) { | |
169 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); | |
170 return SelectionModel(caret, CURSOR_FORWARD); | |
171 } | |
172 } | |
173 // The cursor is at the edge of a run; move to the visually adjacent run. | |
174 // TODO(xji): Keep a vector of runs to avoid using a singly-linked list. | |
175 run = (direction == CURSOR_RIGHT) ? | |
176 run->next : GSListPrevious(current_line_->runs, run); | |
177 if (!run) | |
178 return EdgeSelectionModel(direction); | |
179 } | |
180 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
181 return IsForwardMotion(direction, item) ? | |
182 FirstSelectionModelInsideRun(item) : LastSelectionModelInsideRun(item); | |
183 } | |
184 | |
185 SelectionModel RenderTextLinux::AdjacentWordSelectionModel( | |
186 const SelectionModel& selection, | |
187 VisualCursorDirection direction) { | |
188 if (obscured()) | |
189 return EdgeSelectionModel(direction); | |
190 | |
191 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); | |
192 bool success = iter.Init(); | |
193 DCHECK(success); | |
194 if (!success) | |
195 return selection; | |
196 | |
197 SelectionModel cur(selection); | |
198 for (;;) { | |
199 cur = AdjacentCharSelectionModel(cur, direction); | |
200 GSList* run = GetRunContainingCaret(cur); | |
201 if (!run) | |
202 break; | |
203 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
204 size_t cursor = cur.caret_pos(); | |
205 if (IsForwardMotion(direction, item) ? | |
206 iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) | |
207 break; | |
208 } | |
209 | |
210 return cur; | |
211 } | |
212 | |
213 Range RenderTextLinux::GetGlyphBounds(size_t index) { | |
214 PangoRectangle pos; | |
215 pango_layout_index_to_pos(layout_, TextIndexToLayoutIndex(index), &pos); | |
216 // TODO(derat): Support fractional ranges for subpixel positioning? | |
217 return Range(PANGO_PIXELS(pos.x), PANGO_PIXELS(pos.x + pos.width)); | |
218 } | |
219 | |
220 std::vector<Rect> RenderTextLinux::GetSubstringBounds(const Range& range) { | |
221 DCHECK_LE(range.GetMax(), text().length()); | |
222 if (range.is_empty()) | |
223 return std::vector<Rect>(); | |
224 | |
225 EnsureLayout(); | |
226 int* ranges = NULL; | |
227 int n_ranges = 0; | |
228 pango_layout_line_get_x_ranges(current_line_, | |
229 TextIndexToLayoutIndex(range.GetMin()), | |
230 TextIndexToLayoutIndex(range.GetMax()), | |
231 &ranges, | |
232 &n_ranges); | |
233 | |
234 const int height = GetStringSize().height(); | |
235 | |
236 std::vector<Rect> bounds; | |
237 for (int i = 0; i < n_ranges; ++i) { | |
238 // TODO(derat): Support fractional bounds for subpixel positioning? | |
239 int x = PANGO_PIXELS(ranges[2 * i]); | |
240 int width = PANGO_PIXELS(ranges[2 * i + 1]) - x; | |
241 Rect rect(x, 0, width, height); | |
242 rect.set_origin(ToViewPoint(rect.origin())); | |
243 bounds.push_back(rect); | |
244 } | |
245 g_free(ranges); | |
246 return bounds; | |
247 } | |
248 | |
249 size_t RenderTextLinux::TextIndexToLayoutIndex(size_t index) const { | |
250 DCHECK(layout_); | |
251 ptrdiff_t offset = gfx::UTF16IndexToOffset(text(), 0, index); | |
252 // Clamp layout indices to the length of the text actually used for layout. | |
253 offset = std::min<size_t>(offset, g_utf8_strlen(layout_text_, -1)); | |
254 const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset); | |
255 return (layout_pointer - layout_text_); | |
256 } | |
257 | |
258 size_t RenderTextLinux::LayoutIndexToTextIndex(size_t index) const { | |
259 DCHECK(layout_); | |
260 const char* layout_pointer = layout_text_ + index; | |
261 const long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer); | |
262 return gfx::UTF16OffsetToIndex(text(), 0, offset); | |
263 } | |
264 | |
265 bool RenderTextLinux::IsCursorablePosition(size_t position) { | |
266 if (position == 0 && text().empty()) | |
267 return true; | |
268 if (position >= text().length()) | |
269 return position == text().length(); | |
270 if (!gfx::IsValidCodePointIndex(text(), position)) | |
271 return false; | |
272 | |
273 EnsureLayout(); | |
274 ptrdiff_t offset = gfx::UTF16IndexToOffset(text(), 0, position); | |
275 // Check that the index corresponds with a valid text code point, that it is | |
276 // marked as a legitimate cursor position by Pango, and that it is not | |
277 // truncated from layout text (its glyph is shown on screen). | |
278 return (offset < num_log_attrs_ && log_attrs_[offset].is_cursor_position && | |
279 offset < g_utf8_strlen(layout_text_, -1)); | |
280 } | |
281 | |
282 void RenderTextLinux::ResetLayout() { | |
283 // set_cached_bounds_and_offset_valid(false) is done in RenderText for every | |
284 // operation that triggers ResetLayout(). | |
285 if (layout_) { | |
286 // TODO(msw): Keep |layout_| across text changes, etc.; it can be re-used. | |
287 g_object_unref(layout_); | |
288 layout_ = NULL; | |
289 } | |
290 if (current_line_) { | |
291 pango_layout_line_unref(current_line_); | |
292 current_line_ = NULL; | |
293 } | |
294 if (log_attrs_) { | |
295 g_free(log_attrs_); | |
296 log_attrs_ = NULL; | |
297 num_log_attrs_ = 0; | |
298 } | |
299 layout_text_ = NULL; | |
300 } | |
301 | |
302 void RenderTextLinux::EnsureLayout() { | |
303 if (layout_ == NULL) { | |
304 cairo_surface_t* surface = | |
305 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); | |
306 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_surface_status(surface)); | |
307 cairo_t* cr = cairo_create(surface); | |
308 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_status(cr)); | |
309 | |
310 layout_ = pango_cairo_create_layout(cr); | |
311 CHECK_NE(static_cast<PangoLayout*>(NULL), layout_); | |
312 cairo_destroy(cr); | |
313 cairo_surface_destroy(surface); | |
314 | |
315 SetupPangoLayoutWithFontDescription(layout_, | |
316 GetLayoutText(), | |
317 font_list().GetFontDescriptionString(), | |
318 0, | |
319 GetTextDirection(), | |
320 Canvas::DefaultCanvasTextAlignment()); | |
321 | |
322 // No width set so that the x-axis position is relative to the start of the | |
323 // text. ToViewPoint and ToTextPoint take care of the position conversion | |
324 // between text space and view spaces. | |
325 pango_layout_set_width(layout_, -1); | |
326 // TODO(xji): If RenderText will be used for displaying purpose, such as | |
327 // label, we will need to remove the single-line-mode setting. | |
328 pango_layout_set_single_paragraph_mode(layout_, true); | |
329 | |
330 layout_text_ = pango_layout_get_text(layout_); | |
331 SetupPangoAttributes(layout_); | |
332 | |
333 current_line_ = pango_layout_get_line_readonly(layout_, 0); | |
334 CHECK_NE(static_cast<PangoLayoutLine*>(NULL), current_line_); | |
335 pango_layout_line_ref(current_line_); | |
336 | |
337 pango_layout_get_log_attrs(layout_, &log_attrs_, &num_log_attrs_); | |
338 } | |
339 } | |
340 | |
341 void RenderTextLinux::SetupPangoAttributes(PangoLayout* layout) { | |
342 PangoAttrList* attrs = pango_attr_list_new(); | |
343 | |
344 // Splitting text runs to accommodate styling can break Arabic glyph shaping. | |
345 // Only split text runs as needed for bold and italic font styles changes. | |
346 BreakList<bool>::const_iterator bold = styles()[BOLD].breaks().begin(); | |
347 BreakList<bool>::const_iterator italic = styles()[ITALIC].breaks().begin(); | |
348 while (bold != styles()[BOLD].breaks().end() && | |
349 italic != styles()[ITALIC].breaks().end()) { | |
350 const int style = (bold->second ? Font::BOLD : 0) | | |
351 (italic->second ? Font::ITALIC : 0); | |
352 const size_t bold_end = styles()[BOLD].GetRange(bold).end(); | |
353 const size_t italic_end = styles()[ITALIC].GetRange(italic).end(); | |
354 const size_t style_end = std::min(bold_end, italic_end); | |
355 if (style != font_list().GetFontStyle()) { | |
356 FontList derived_font_list = font_list().DeriveFontList(style); | |
357 ScopedPangoFontDescription desc(pango_font_description_from_string( | |
358 derived_font_list.GetFontDescriptionString().c_str())); | |
359 | |
360 PangoAttribute* pango_attr = pango_attr_font_desc_new(desc.get()); | |
361 pango_attr->start_index = | |
362 TextIndexToLayoutIndex(std::max(bold->first, italic->first)); | |
363 pango_attr->end_index = TextIndexToLayoutIndex(style_end); | |
364 pango_attr_list_insert(attrs, pango_attr); | |
365 } | |
366 bold += bold_end == style_end ? 1 : 0; | |
367 italic += italic_end == style_end ? 1 : 0; | |
368 } | |
369 DCHECK(bold == styles()[BOLD].breaks().end()); | |
370 DCHECK(italic == styles()[ITALIC].breaks().end()); | |
371 | |
372 pango_layout_set_attributes(layout, attrs); | |
373 pango_attr_list_unref(attrs); | |
374 } | |
375 | |
376 void RenderTextLinux::DrawVisualText(Canvas* canvas) { | |
377 DCHECK(layout_); | |
378 | |
379 // Skia will draw glyphs with respect to the baseline. | |
380 Vector2d offset(GetLineOffset(0) + Vector2d(0, GetLayoutTextBaseline())); | |
381 | |
382 SkScalar x = SkIntToScalar(offset.x()); | |
383 SkScalar y = SkIntToScalar(offset.y()); | |
384 | |
385 std::vector<SkPoint> pos; | |
386 std::vector<uint16> glyphs; | |
387 | |
388 internal::SkiaTextRenderer renderer(canvas); | |
389 ApplyFadeEffects(&renderer); | |
390 ApplyTextShadows(&renderer); | |
391 | |
392 // TODO(derat): Use font-specific params: http://crbug.com/125235 | |
393 const gfx::FontRenderParams& render_params = | |
394 gfx::GetDefaultFontRenderParams(); | |
395 const bool use_subpixel_rendering = | |
396 render_params.subpixel_rendering != | |
397 gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE; | |
398 renderer.SetFontSmoothingSettings( | |
399 render_params.antialiasing, | |
400 use_subpixel_rendering && !background_is_transparent()); | |
401 | |
402 // Temporarily apply composition underlines and selection colors. | |
403 ApplyCompositionAndSelectionStyles(); | |
404 | |
405 internal::StyleIterator style(colors(), styles()); | |
406 for (GSList* it = current_line_->runs; it; it = it->next) { | |
407 PangoLayoutRun* run = reinterpret_cast<PangoLayoutRun*>(it->data); | |
408 int glyph_count = run->glyphs->num_glyphs; | |
409 // TODO(msw): Skip painting runs outside the display rect area, like Win. | |
410 if (glyph_count == 0) | |
411 continue; | |
412 | |
413 ScopedPangoFontDescription desc( | |
414 pango_font_describe(run->item->analysis.font)); | |
415 | |
416 const std::string family_name = | |
417 pango_font_description_get_family(desc.get()); | |
418 renderer.SetTextSize(GetPangoFontSizeInPixels(desc.get())); | |
419 | |
420 glyphs.resize(glyph_count); | |
421 pos.resize(glyph_count); | |
422 | |
423 // Track the current glyph and the glyph at the start of its styled range. | |
424 int glyph_index = 0; | |
425 int style_start_glyph_index = glyph_index; | |
426 | |
427 // Track the x-coordinates for each styled range (|x| marks the current). | |
428 SkScalar style_start_x = x; | |
429 | |
430 // Track the current style and its text (not layout) index range. | |
431 style.UpdatePosition(GetGlyphTextIndex(run, style_start_glyph_index)); | |
432 Range style_range = style.GetRange(); | |
433 | |
434 do { | |
435 const PangoGlyphInfo& glyph = run->glyphs->glyphs[glyph_index]; | |
436 glyphs[glyph_index] = static_cast<uint16>(glyph.glyph); | |
437 // Use pango_units_to_double() rather than PANGO_PIXELS() here, so units | |
438 // are not rounded to the pixel grid if subpixel positioning is enabled. | |
439 pos[glyph_index].set(x + pango_units_to_double(glyph.geometry.x_offset), | |
440 y + pango_units_to_double(glyph.geometry.y_offset)); | |
441 x += pango_units_to_double(glyph.geometry.width); | |
442 | |
443 ++glyph_index; | |
444 const size_t glyph_text_index = (glyph_index == glyph_count) ? | |
445 style_range.end() : GetGlyphTextIndex(run, glyph_index); | |
446 if (!IndexInRange(style_range, glyph_text_index)) { | |
447 // TODO(asvitkine): For cases like "fi", where "fi" is a single glyph | |
448 // but can span multiple styles, Pango splits the | |
449 // styles evenly over the glyph. We can do this too by | |
450 // clipping and drawing the glyph several times. | |
451 renderer.SetForegroundColor(style.color()); | |
452 const int font_style = (style.style(BOLD) ? Font::BOLD : 0) | | |
453 (style.style(ITALIC) ? Font::ITALIC : 0); | |
454 renderer.SetFontFamilyWithStyle(family_name, font_style); | |
455 renderer.DrawPosText(&pos[style_start_glyph_index], | |
456 &glyphs[style_start_glyph_index], | |
457 glyph_index - style_start_glyph_index); | |
458 if (style.style(UNDERLINE)) | |
459 SetPangoUnderlineMetrics(desc.get(), &renderer); | |
460 renderer.DrawDecorations(style_start_x, y, x - style_start_x, | |
461 style.style(UNDERLINE), style.style(STRIKE), | |
462 style.style(DIAGONAL_STRIKE)); | |
463 style.UpdatePosition(glyph_text_index); | |
464 style_range = style.GetRange(); | |
465 style_start_glyph_index = glyph_index; | |
466 style_start_x = x; | |
467 } | |
468 } while (glyph_index < glyph_count); | |
469 } | |
470 | |
471 // Undo the temporarily applied composition underlines and selection colors. | |
472 UndoCompositionAndSelectionStyles(); | |
473 } | |
474 | |
475 GSList* RenderTextLinux::GetRunContainingCaret( | |
476 const SelectionModel& caret) const { | |
477 size_t position = TextIndexToLayoutIndex(caret.caret_pos()); | |
478 LogicalCursorDirection affinity = caret.caret_affinity(); | |
479 GSList* run = current_line_->runs; | |
480 while (run) { | |
481 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
482 Range item_range(item->offset, item->offset + item->length); | |
483 if (RangeContainsCaret(item_range, position, affinity)) | |
484 return run; | |
485 run = run->next; | |
486 } | |
487 return NULL; | |
488 } | |
489 | |
490 SelectionModel RenderTextLinux::FirstSelectionModelInsideRun( | |
491 const PangoItem* item) { | |
492 size_t caret = IndexOfAdjacentGrapheme( | |
493 LayoutIndexToTextIndex(item->offset), CURSOR_FORWARD); | |
494 return SelectionModel(caret, CURSOR_BACKWARD); | |
495 } | |
496 | |
497 SelectionModel RenderTextLinux::LastSelectionModelInsideRun( | |
498 const PangoItem* item) { | |
499 size_t caret = IndexOfAdjacentGrapheme( | |
500 LayoutIndexToTextIndex(item->offset + item->length), CURSOR_BACKWARD); | |
501 return SelectionModel(caret, CURSOR_FORWARD); | |
502 } | |
503 | |
504 size_t RenderTextLinux::GetGlyphTextIndex(PangoLayoutRun* run, | |
505 int glyph_index) const { | |
506 return LayoutIndexToTextIndex(run->item->offset + | |
507 run->glyphs->log_clusters[glyph_index]); | |
508 } | |
509 | |
510 RenderText* RenderText::CreateInstance() { | |
511 return new RenderTextLinux; | |
512 } | |
513 | |
514 } // namespace gfx | |
OLD | NEW |