Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/gfx/render_text_linux.h" | 5 #include "ui/gfx/render_text_linux.h" |
| 6 | 6 |
| 7 #include <pango/pangocairo.h> | |
| 8 #include <algorithm> | |
| 9 | |
| 10 #include "base/logging.h" | |
| 11 #include "ui/gfx/canvas_skia.h" | |
| 12 #include "ui/gfx/gtk_util.h" | |
| 13 #include "unicode/uchar.h" | |
| 14 | |
| 15 #define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) | |
|
msw
2011/08/19 23:16:59
Make these functions instead of pre-processor dire
xji
2011/08/22 23:57:28
removed. already defined in include in uchar.h
| |
| 16 #define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) | |
| 17 // TODO(xji): massage ARGB to RGB. | |
| 18 #define COLOR_16_TO_32_BIT(c) ((c)/0xff * 0xffff) | |
| 19 | |
| 20 // TODO(xji): index saved in upper layer are utf16 index. Pango uses utf8 index. | |
|
msw
2011/08/19 23:16:59
That's unfortunate, I believe Uniscribe is UTF16,
xji
2011/08/22 23:57:28
I looked at those Uniscibe functions you used.
In
| |
| 21 // Since caret_pos is used internally, we could save utf8 index for caret_pos | |
| 22 // to avoid conversion. | |
| 23 | |
| 7 namespace gfx { | 24 namespace gfx { |
| 8 | 25 |
| 9 RenderTextLinux::RenderTextLinux() | 26 RenderTextLinux::RenderTextLinux() |
| 10 : RenderText() { | 27 : layout_(NULL), |
| 28 layout_line_(NULL) { | |
| 11 } | 29 } |
| 12 | 30 |
| 13 RenderTextLinux::~RenderTextLinux() { | 31 RenderTextLinux::~RenderTextLinux() { |
| 32 ResetLayout(); | |
| 14 } | 33 } |
| 15 | 34 |
| 16 RenderText* RenderText::CreateRenderText() { | 35 RenderText* RenderText::CreateRenderText() { |
| 17 return new RenderTextLinux; | 36 return new RenderTextLinux; |
| 18 } | 37 } |
| 19 | 38 |
| 39 int RenderTextLinux::GetStringWidth() { | |
| 40 PangoLayout* layout = EnsureLayout(); | |
| 41 int width, height; | |
| 42 pango_layout_get_pixel_size(layout, &width, &height); | |
| 43 return width; | |
| 44 } | |
| 45 | |
| 46 void RenderTextLinux::Draw(Canvas* canvas) { | |
| 47 PangoLayout* layout = EnsureLayout(); | |
| 48 Rect bounds(display_rect()); | |
| 49 | |
| 50 // Clip the canvas to the text display area. | |
| 51 CanvasSkia* canvas_skia = static_cast<CanvasSkia*>(canvas); | |
| 52 canvas_skia->ClipRectInt( | |
| 53 bounds.x(), bounds.y(), bounds.width(), bounds.height()); | |
| 54 | |
| 55 // TODO(xji): is this Begin/EndPlatformPaint correct? How about performance? | |
| 56 cairo_t* cr = skia::BeginPlatformPaint(canvas_skia); | |
| 57 cairo_save(cr); | |
| 58 cairo_rectangle(cr, bounds.x(), bounds.y(), bounds.width(), bounds.height()); | |
| 59 cairo_clip(cr); | |
| 60 | |
| 61 int text_width, text_height; | |
| 62 pango_layout_get_pixel_size(layout, &text_width, &text_height); | |
| 63 // Vertically centered. | |
| 64 int text_y = bounds.y() + ((bounds.height() - text_height) / 2); | |
| 65 int text_x = bounds.x() + GetUpdatedDisplayOffset().x(); | |
| 66 cairo_move_to(cr, text_x, text_y); | |
| 67 pango_cairo_show_layout(cr, layout); | |
| 68 | |
| 69 // Destructor. | |
| 70 cairo_restore(cr); | |
| 71 skia::EndPlatformPaint(canvas_skia); | |
| 72 | |
| 73 // Paint cursor. | |
| 74 bounds = GetUpdatedCursorBounds(); | |
| 75 if (cursor_visible() && focused() && !bounds.IsEmpty()) { | |
| 76 if (!bounds.IsEmpty()) | |
| 77 canvas->DrawRectInt(kCursorColor, | |
| 78 bounds.x(), | |
| 79 bounds.y(), | |
| 80 bounds.width(), | |
| 81 bounds.height()); | |
| 82 } | |
| 83 } | |
| 84 | |
| 85 SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) { | |
| 86 // TODO(xji): when points outside of text, return HOME/END position. | |
| 87 PangoLayout* layout = EnsureLayout(); | |
| 88 | |
| 89 if (text().empty()) | |
| 90 return SelectionModel(0, 0, SelectionModel::LEADING); | |
| 91 | |
| 92 int adjusted_x = | |
| 93 point.x() - display_rect().x() - GetUpdatedDisplayOffset().x(); | |
| 94 int caret_pos, trailing; | |
| 95 pango_layout_xy_to_index(layout, | |
| 96 adjusted_x * PANGO_SCALE, | |
| 97 point.y() * PANGO_SCALE, | |
| 98 &caret_pos, | |
| 99 &trailing); | |
| 100 | |
| 101 size_t selection_end = caret_pos; | |
| 102 if (trailing > 0) { | |
| 103 const char* layout_text = pango_layout_get_text(layout); | |
| 104 selection_end = g_utf8_offset_to_pointer(layout_text + caret_pos, trailing) | |
|
msw
2011/08/19 23:16:59
Yuck! GTK? We're trying to remove all GTK; is ther
xji
2011/08/22 23:57:28
those from glib or glib-object are fine from oshim
| |
| 105 - layout_text; | |
| 106 } | |
| 107 | |
| 108 return SelectionModel( | |
| 109 Utf8IndexToUtf16Index(text(), selection_end), | |
| 110 Utf8IndexToUtf16Index(text(), caret_pos), | |
| 111 trailing > 0 ? SelectionModel::TRAILING : SelectionModel::LEADING); | |
| 112 } | |
| 113 | |
| 114 Rect RenderTextLinux::GetCursorBounds(const SelectionModel& selection, | |
| 115 bool insert_mode) { | |
| 116 PangoLayout* layout = EnsureLayout(); | |
| 117 | |
| 118 PangoRectangle pos; | |
| 119 pango_layout_index_to_pos( | |
|
msw
2011/08/19 23:16:59
why not just call pango_layout_index_to_pos on sel
xji
2011/08/22 23:57:28
Done.
| |
| 120 layout, Utf16IndexToUtf8Index(text(), selection.caret_pos()), &pos); | |
| 121 | |
| 122 int x; | |
| 123 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
| 124 if (caret_placement == SelectionModel::TRAILING) | |
| 125 x = (pos.x + pos.width); | |
| 126 else | |
| 127 x = pos.x; | |
| 128 | |
| 129 if (pango_layout_get_alignment(layout_) == PANGO_ALIGN_RIGHT) | |
|
msw
2011/08/19 23:16:59
Can you comment on why this is offsetting by one?
xji
2011/08/22 23:57:28
Done.
| |
| 130 x -= 1; | |
| 131 x = x / PANGO_SCALE; | |
| 132 x += display_rect().x() + GetUpdatedDisplayOffset().x(); | |
| 133 | |
| 134 const Font& font = default_style().font; | |
| 135 int h = std::min(display_rect().height(), font.GetHeight()); | |
|
msw
2011/08/19 23:16:59
Is the PangoRectangle's height inaccurate? Otherwi
xji
2011/08/22 23:57:28
Ah, yes, you are right. I can use pos.height.
I di
msw
2011/08/23 08:01:01
Good. FYI someone could set a different font/size
| |
| 136 Rect bounds(x, (display_rect().height() - h) / 2, 1, h); | |
|
msw
2011/08/19 23:16:59
It looks like a width of 0 is okay for rendering a
xji
2011/08/22 23:57:28
Done.
| |
| 137 | |
| 138 size_t position = selection.selection_end(); | |
| 139 if (!insert_mode && text().length() != position) | |
| 140 AdjustBoundsForNonInsertMode(selection, pos, &bounds); | |
| 141 | |
| 142 return bounds; | |
| 143 } | |
| 144 | |
| 145 SelectionModel RenderTextLinux::GetLeftSelectionModel( | |
| 146 const SelectionModel& current, BreakType break_type) { | |
|
msw
2011/08/19 23:16:59
Put each argument on its own line if the whole sig
xji
2011/08/22 23:57:28
Done.
| |
| 147 EnsureLayout(); | |
| 148 | |
| 149 if (break_type == LINE_BREAK) | |
| 150 return GetSelectionModelForVisualLeftmost(); | |
| 151 if (break_type == CHARACTER_BREAK) | |
| 152 return GetLeftSelectionModelByGrapheme(current); | |
| 153 // TODO(xji): implementation. | |
| 154 return RenderText::GetLeftSelectionModel(current, break_type); | |
|
msw
2011/08/19 23:16:59
Sigh, word break is breaking our backs.
| |
| 155 } | |
| 156 | |
| 157 SelectionModel RenderTextLinux::GetRightSelectionModel( | |
| 158 const SelectionModel& current, BreakType break_type) { | |
|
msw
2011/08/19 23:16:59
Ditto for arguments on separate lines.
xji
2011/08/22 23:57:28
Done.
| |
| 159 EnsureLayout(); | |
| 160 | |
| 161 if (break_type == LINE_BREAK) { | |
| 162 const char* layout_text = pango_layout_get_text(layout_); | |
|
msw
2011/08/19 23:16:59
FYI, You might find it easier to implement somethi
xji
2011/08/22 23:57:28
Done.
| |
| 163 PangoDirection base_dir = | |
| 164 pango_find_base_dir(layout_text, strlen(layout_text)); | |
|
msw
2011/08/19 23:16:59
Maybe add a TODO like my // TODO(msw): Implement R
xji
2011/08/22 23:57:28
I've changed it to use GetTextDirection(). it retu
| |
| 165 if (base_dir == PANGO_DIRECTION_RTL || | |
| 166 base_dir == PANGO_DIRECTION_WEAK_RTL) | |
| 167 return SelectionModel(0, 0, SelectionModel::LEADING); | |
| 168 return SelectionModel( | |
| 169 text().length(), text().length(), SelectionModel::LEADING); | |
|
msw
2011/08/19 23:16:59
Perhaps we should discuss the merits of using text
xji
2011/08/22 23:57:28
I've changed to your implementation.
| |
| 170 } | |
| 171 if (break_type == CHARACTER_BREAK) | |
| 172 return GetRightSelectionModelByGrapheme(current); | |
| 173 // TODO(xji): implementation. | |
| 174 return RenderText::GetRightSelectionModel(current, break_type); | |
| 175 } | |
| 176 | |
| 177 size_t RenderTextLinux::GetIndexOfPreviousGrapheme(size_t position) { | |
| 178 EnsureLayout(); | |
| 179 return Utf16IndexOfAdjacentGrapheme(position, PREVIOUS); | |
| 180 } | |
| 181 | |
| 182 SelectionModel RenderTextLinux::GetSelectionModelForVisualLeftmost() const { | |
| 183 const char* layout_text = pango_layout_get_text(layout_); | |
|
msw
2011/08/19 23:16:59
Do you need to EnsureLayout();?
xji
2011/08/22 23:57:28
this one is a private function, and it is always c
msw
2011/08/23 08:01:01
I think you're okay.
| |
| 184 PangoDirection base_dir = | |
| 185 pango_find_base_dir(layout_text, strlen(layout_text)); | |
| 186 | |
| 187 if (base_dir == PANGO_DIRECTION_RTL || base_dir == PANGO_DIRECTION_WEAK_RTL) { | |
| 188 GSList* first_visual_run = layout_line_->runs; | |
| 189 if (first_visual_run) { | |
| 190 PangoItem* item = | |
| 191 reinterpret_cast<PangoLayoutRun*>(first_visual_run->data)->item; | |
|
msw
2011/08/19 23:16:59
As far as I can tell, PangoLayoutRun != PangoItem:
xji
2011/08/22 23:57:28
|item| is assigned to PangoLayoutRun->item.
msw
2011/08/23 08:01:01
Doh, thanks for clarifying!
| |
| 192 if (item->analysis.level % 2 == 0) { // LTR. | |
| 193 size_t utf16_index = Utf8IndexToUtf16Index(text(), item->offset); | |
| 194 return SelectionModel( | |
| 195 text().length(), utf16_index, SelectionModel::LEADING); | |
| 196 } else { // RTL. | |
| 197 size_t utf16_index = Utf8IndexToUtf16Index( | |
| 198 text(), item->offset + item->length); | |
|
msw
2011/08/19 23:16:59
Should this be item->length - 1? Or I guess gettin
xji
2011/08/22 23:57:28
Yes.
msw
2011/08/23 08:01:01
Function definition/declaration is all on one line
| |
| 199 utf16_index = Utf16IndexOfAdjacentGrapheme(utf16_index, PREVIOUS); | |
| 200 return SelectionModel( | |
| 201 text().length(), utf16_index, SelectionModel::TRAILING); | |
|
msw
2011/08/19 23:16:59
Ditto for moving "text().length()," to the line ab
xji
2011/08/22 23:57:28
Done.
| |
| 202 } | |
| 203 } | |
| 204 } | |
| 205 return SelectionModel(0, 0, SelectionModel::LEADING); | |
| 206 } | |
| 207 | |
| 208 GSList* RenderTextLinux::GetRunContainsCaretPos(const SelectionModel& current, | |
| 209 CursorMovementDirection dir, | |
| 210 bool* at_boundary) const { | |
| 211 GSList* run_contains_caret_pos = NULL; | |
| 212 *at_boundary = false; | |
| 213 | |
| 214 GSList* run = layout_line_->runs; | |
| 215 while (run) { | |
| 216 PangoItem* pango_item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
xji
2011/08/22 23:57:28
ditto
| |
| 217 size_t caret_pos = current.caret_pos(); | |
| 218 size_t run_end_utf16_index = | |
| 219 Utf8IndexToUtf16Index(text(), pango_item->offset + pango_item->length); | |
| 220 size_t run_start_utf16_index = | |
| 221 Utf8IndexToUtf16Index(text(), pango_item->offset); | |
| 222 | |
| 223 if (caret_pos >= run_start_utf16_index && caret_pos < run_end_utf16_index) { | |
| 224 run_contains_caret_pos = run; | |
| 225 | |
| 226 size_t selection_end = current.selection_end(); | |
| 227 SelectionModel::CaretPlacement caret_placement = | |
| 228 current.caret_placement(); | |
| 229 | |
| 230 if (caret_placement == SelectionModel::LEADING && | |
| 231 caret_pos == run_start_utf16_index) { | |
| 232 *at_boundary = (dir == RIGHT && pango_item->analysis.level % 2) || | |
| 233 (dir == LEFT && pango_item->analysis.level % 2 == 0); | |
| 234 } else if (caret_placement == SelectionModel::TRAILING && | |
| 235 selection_end == run_end_utf16_index) { | |
|
msw
2011/08/19 23:16:59
Why are we checking selection_end instead of caret
xji
2011/08/22 23:57:28
I am checking selecion_end == run_end, you are che
| |
| 236 *at_boundary = (dir == RIGHT && pango_item->analysis.level % 2 == 0) || | |
| 237 (dir == LEFT && pango_item->analysis.level % 2); | |
| 238 } | |
| 239 break; | |
| 240 } | |
| 241 run = run->next; | |
| 242 } | |
| 243 return run_contains_caret_pos; | |
| 244 } | |
| 245 | |
| 246 size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme( | |
| 247 size_t utf16_index_of_current_grapheme, RelativeLogicalPosition pos) const { | |
|
msw
2011/08/19 23:16:59
Ditto for argument lines.
xji
2011/08/22 23:57:28
Done.
| |
| 248 PangoLogAttr* log_attrs; | |
| 249 gint n_attrs; | |
| 250 pango_layout_get_log_attrs(layout_, &log_attrs, &n_attrs); | |
| 251 | |
| 252 const char* layout_text = pango_layout_get_text(layout_); | |
| 253 const char* ch = layout_text + | |
| 254 Utf16IndexToUtf8Index(text(), utf16_index_of_current_grapheme); | |
| 255 int char_offset = static_cast<int>(g_utf8_pointer_to_offset(layout_text, ch)); | |
|
msw
2011/08/19 23:16:59
Yuck, more GTK!
| |
| 256 | |
| 257 if (pos == PREVIOUS) { | |
| 258 if (ch > layout_text) { | |
| 259 do { | |
| 260 ch = g_utf8_find_prev_char(layout_text, ch); | |
|
msw
2011/08/19 23:16:59
Yuck, more GTK!
| |
| 261 --char_offset; | |
| 262 } while (ch && *ch && !log_attrs[char_offset].is_cursor_position); | |
| 263 if (!ch) | |
| 264 ch = layout_text; | |
| 265 } | |
| 266 } else { | |
| 267 if (ch < layout_text + strlen(layout_text)) { | |
| 268 do { | |
| 269 ch = g_utf8_find_next_char(ch, NULL); | |
|
msw
2011/08/19 23:16:59
Yuck, more GTK!
| |
| 270 ++char_offset; | |
| 271 } while (ch && *ch && !log_attrs[char_offset].is_cursor_position); | |
| 272 if (ch >= layout_text + strlen(layout_text)) | |
|
msw
2011/08/19 23:16:59
Could |ch| be null here (noting your check above).
xji
2011/08/22 23:57:28
pango text should be null terminated. I do not thi
| |
| 273 ch = layout_text + strlen(layout_text); | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 size_t utf8_index = static_cast<size_t>(ch - layout_text); | |
| 278 // TODO(xji): how costly those g_free is? | |
| 279 g_free(log_attrs); | |
|
msw
2011/08/19 23:16:59
Yuck, more GTK!
| |
| 280 return utf8_index; | |
| 281 } | |
| 282 | |
| 283 size_t RenderTextLinux::Utf16IndexOfAdjacentGrapheme( | |
| 284 size_t utf16_index_of_current_grapheme, RelativeLogicalPosition pos) const { | |
|
msw
2011/08/19 23:16:59
Ditto for argument lines.
xji
2011/08/22 23:57:28
Done.
| |
| 285 size_t utf8_index = Utf8IndexOfAdjacentGrapheme( | |
| 286 utf16_index_of_current_grapheme, pos); | |
| 287 return Utf8IndexToUtf16Index(text(), utf8_index); | |
| 288 } | |
| 289 | |
| 290 SelectionModel RenderTextLinux::FirstSelectionModelInsideRun( | |
| 291 const PangoItem* run) const { | |
| 292 size_t utf16_index = Utf8IndexToUtf16Index(text(), run->offset); | |
|
msw
2011/08/19 23:16:59
Out-dent the function contents two spaces.
xji
2011/08/22 23:57:28
Done.
| |
| 293 return SelectionModel(Utf16IndexOfAdjacentGrapheme(utf16_index, NEXT), | |
|
msw
2011/08/19 23:16:59
Can you split this up onto two lines like you did
xji
2011/08/22 23:57:28
Done.
| |
| 294 utf16_index, | |
| 295 SelectionModel::TRAILING); | |
| 296 } | |
| 297 | |
| 298 SelectionModel RenderTextLinux::LastSelectionModelInsideRun( | |
| 299 const PangoItem* run) const { | |
| 300 size_t utf16_index = Utf8IndexToUtf16Index( | |
|
msw
2011/08/19 23:16:59
Out-dent the function contents two spaces.
xji
2011/08/22 23:57:28
Done.
| |
| 301 text(), run->offset + run->length); | |
| 302 utf16_index = Utf16IndexOfAdjacentGrapheme(utf16_index, PREVIOUS); | |
| 303 return SelectionModel(utf16_index, utf16_index, SelectionModel::LEADING); | |
| 304 } | |
| 305 | |
| 306 SelectionModel RenderTextLinux::LeftmostSelectionModelInsideRun( | |
| 307 const PangoItem* run) const { | |
| 308 if (run->analysis.level % 2 == 0) | |
| 309 return FirstSelectionModelInsideRun(run); | |
| 310 return LastSelectionModelInsideRun(run); | |
| 311 } | |
| 312 | |
| 313 SelectionModel RenderTextLinux::RightmostSelectionModelInsideRun( | |
| 314 const PangoItem* run) const { | |
| 315 if (run->analysis.level % 2) | |
| 316 return FirstSelectionModelInsideRun(run); | |
| 317 return LastSelectionModelInsideRun(run); | |
| 318 } | |
| 319 | |
| 320 bool RenderTextLinux::GetVisuallyAdjacentCursorPositionForEnd( | |
| 321 const SelectionModel& current, | |
|
msw
2011/08/19 23:16:59
Out-dent these argument lines two spaces.
xji
2011/08/22 23:57:28
Done.
| |
| 322 CursorMovementDirection dir, | |
| 323 SelectionModel* adjacent) const { | |
| 324 size_t text_length = text().length(); | |
| 325 if (current.selection_end() == text_length && | |
| 326 current.caret_pos() == text_length && | |
| 327 current.caret_placement() == SelectionModel::LEADING) { | |
| 328 // Visually rightmost END position. | |
| 329 if (dir == LEFT) { | |
| 330 GSList* last_run = GetLastRun(); | |
| 331 if (last_run) { | |
| 332 *adjacent = RightmostSelectionModelInsideRun( | |
|
msw
2011/08/19 23:16:59
Does this work in a RTL context with plain RTL tex
xji
2011/08/22 23:57:28
it works. but this function is no longer needed.
| |
| 333 reinterpret_cast<PangoLayoutRun*>(last_run->data)->item); | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 334 } | |
| 335 } | |
| 336 adjacent->set_selection_start(adjacent->selection_end()); | |
| 337 return true; | |
| 338 } | |
| 339 return false; | |
| 340 } | |
| 341 | |
| 342 // Assume caret_pos in |current| is n, 'l' represents leading in | |
| 343 // caret_placement and 't' represents trailing in caret_placement. Following | |
| 344 // is the calculation from (caret_pos, caret_placement) in |current| to | |
| 345 // (caret_pos, caret_placement, selection_end) when moving cursor left by | |
|
msw
2011/08/19 23:16:59
While I can't thank you enough to writing the psue
xji
2011/08/22 23:57:28
Done.
| |
| 346 // one grapheme (for simplilcity, assume each grapheme is one character). | |
| 347 // If n is in LTR run, | |
| 348 // (n, t) ---> (n, l, n) | |
| 349 // (n, l) ---> (n-1, l, n-1) if n is inside run (not at boundary). | |
| 350 // (n, l) ---> goto across run case if n is at run boundary; | |
| 351 // If n is in RTL run, | |
| 352 // (n, l) --> (n, t, n+1); | |
| 353 // (n, t) --> (n+1, t n+2) if n is inside run; | |
| 354 // (n, t) --> goto across run case if n is at run boundar; | |
|
msw
2011/08/19 23:16:59
boundar*y*
xji
2011/08/22 23:57:28
Done.
| |
| 355 // If n is at run boudary, get its visually left run, | |
|
msw
2011/08/19 23:16:59
bou*n*dary
xji
2011/08/22 23:57:28
Done.
| |
| 356 // if left run is LTR run, | |
| 357 // (n, t) --> (left run's end, l, left run's end); | |
| 358 // If rght run is RTL run, | |
| 359 // (n, t) --> (left run's begin, t, left run's begin + 1); | |
| 360 // | |
| 361 // TODO(xji): duplication with GetRightSelectionModelByGrapheme. | |
| 362 SelectionModel RenderTextLinux::GetLeftSelectionModelByGrapheme( | |
| 363 const SelectionModel& current) const { | |
| 364 SelectionModel left(current); | |
| 365 if (GetVisuallyAdjacentCursorPositionForEnd(current, LEFT, &left)) | |
| 366 return left; | |
| 367 | |
| 368 bool at_run_boundary = false; | |
| 369 GSList* run = GetRunContainsCaretPos(current, LEFT, &at_run_boundary); | |
| 370 if (run == NULL) { | |
| 371 left.set_selection_start(left.selection_end()); | |
| 372 return left; | |
| 373 } | |
| 374 | |
| 375 PangoItem* box = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 376 if (box->analysis.level % 2 == 0) { // LTR box. | |
| 377 if (current.caret_placement() == SelectionModel::TRAILING) { | |
| 378 left.set_caret_placement(SelectionModel::LEADING); | |
| 379 left.set_selection_end(left.caret_pos()); | |
| 380 } else { // caret_placement == TRAILING. | |
| 381 if (at_run_boundary) { | |
| 382 GSList* prev_run = GetPreviousRun(run); | |
| 383 if (prev_run) { | |
| 384 return RightmostSelectionModelInsideRun( | |
| 385 reinterpret_cast<PangoLayoutRun*>(prev_run->data)->item); | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 386 } | |
| 387 } else { // !at_run_boundary. | |
| 388 size_t prev = Utf16IndexOfAdjacentGrapheme(left.caret_pos(), PREVIOUS); | |
| 389 left.set_caret_pos(prev); | |
| 390 left.set_selection_end(prev); | |
| 391 } | |
| 392 } | |
| 393 } else { // RTL box. | |
| 394 if (current.caret_placement() == SelectionModel::LEADING) { | |
| 395 left.set_caret_placement(SelectionModel::TRAILING); | |
| 396 left.set_selection_end( | |
| 397 Utf16IndexOfAdjacentGrapheme(current.caret_pos(), NEXT)); | |
| 398 } else { // caret_placement == LEADING | |
| 399 if (at_run_boundary) { | |
| 400 GSList* prev_run = GetPreviousRun(run); | |
| 401 if (prev_run) { | |
| 402 return RightmostSelectionModelInsideRun( | |
| 403 reinterpret_cast<PangoLayoutRun*>(prev_run->data)->item); | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 404 } | |
| 405 } else { // !at_run_boundary | |
| 406 // current.seletion_end - current.caret_pos | |
| 407 // == NumOfCharsInGrapheme(caret_pos); | |
| 408 left.set_caret_pos(current.selection_end()); | |
| 409 left.set_selection_end( | |
| 410 Utf16IndexOfAdjacentGrapheme(left.caret_pos(), NEXT)); | |
| 411 } | |
| 412 } | |
| 413 } | |
| 414 | |
| 415 left.set_selection_start(left.selection_end()); | |
| 416 return left; | |
| 417 } | |
| 418 | |
| 419 // Assume caret_pos in |current| is n, 'l' represents leading in | |
|
msw
2011/08/19 23:16:59
Ditto on triplet order and line endings.
xji
2011/08/22 23:57:28
Done.
| |
| 420 // caret_placement and 't' represents trailing in caret_placement. Following | |
| 421 // is the calculation from (caret_pos, caret_placement) in |current| to | |
| 422 // (caret_pos, caret_placement, selection_end) when moving cursor right by | |
| 423 // one grapheme (for simplilcity, assume each grapheme is one character). | |
| 424 // If n is in LTR run, | |
| 425 // (n, l) ---> (n, t, n+1) | |
| 426 // (n, t) ---> (n+1, t, n+2) if n is inside run (not at boundary). | |
| 427 // (n, t) ---> goto across run case if n is at run boundary; | |
| 428 // If n is in RTL run, | |
| 429 // (n, t) --> (n, l, n); | |
| 430 // (n, l) --> (n-1, l, n-1) if n is inside run; | |
| 431 // (n, l) --> goto across run case if n is at run boundar; | |
|
msw
2011/08/19 23:16:59
boundar*y*
xji
2011/08/22 23:57:28
Done.
| |
| 432 // If n is at run boudary, get its visually right run, | |
|
msw
2011/08/19 23:16:59
bou*n*dary
xji
2011/08/22 23:57:28
Done.
| |
| 433 // if right run is LTR run, | |
| 434 // (n, t) --> (right run's begin, t, right run's begin + 1); | |
| 435 // If rght run is RTL run, | |
| 436 // (n, t) --> (right run's end, l, right run's end); | |
| 437 SelectionModel RenderTextLinux::GetRightSelectionModelByGrapheme( | |
| 438 const SelectionModel& current) const { | |
| 439 SelectionModel right(current); | |
| 440 if (GetVisuallyAdjacentCursorPositionForEnd(current, RIGHT, &right)) | |
| 441 return right; | |
| 442 | |
| 443 bool at_run_boundary = false; | |
| 444 GSList* run = GetRunContainsCaretPos(current, RIGHT, &at_run_boundary); | |
| 445 if (run == NULL) { | |
| 446 right.set_selection_start(right.selection_end()); | |
| 447 return right; | |
| 448 } | |
| 449 | |
| 450 PangoItem* box = reinterpret_cast<PangoLayoutRun*>(run->data)->item; | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 451 if (box->analysis.level % 2 == 0) { // LTR box. | |
| 452 if (current.caret_placement() == SelectionModel::LEADING) { | |
| 453 right.set_caret_placement(SelectionModel::TRAILING); | |
| 454 right.set_selection_end( | |
| 455 Utf16IndexOfAdjacentGrapheme(current.caret_pos(), NEXT)); | |
| 456 } else { // caret_placement == TRAILING. | |
| 457 if (at_run_boundary) { | |
| 458 if (run->next) { | |
| 459 return LeftmostSelectionModelInsideRun( | |
| 460 reinterpret_cast<PangoLayoutRun*>(run->next->data)->item); | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 461 } | |
| 462 } else { // !at_run_boundary. | |
| 463 // current.seletion_end - current.caret_pos | |
| 464 // == NumOfCharsInGrapheme(caret_pos); | |
| 465 right.set_caret_pos(current.selection_end()); | |
| 466 right.set_selection_end( | |
| 467 Utf16IndexOfAdjacentGrapheme(right.caret_pos(), NEXT)); | |
| 468 } | |
| 469 } | |
| 470 } else { // RTL box. | |
| 471 if (current.caret_placement() == SelectionModel::TRAILING) { | |
| 472 right.set_caret_placement(SelectionModel::LEADING); | |
| 473 right.set_selection_end(right.caret_pos()); | |
| 474 } else { // caret_placement == LEADING | |
| 475 if (at_run_boundary) { | |
| 476 if (run->next) { | |
| 477 return LeftmostSelectionModelInsideRun( | |
| 478 reinterpret_cast<PangoLayoutRun*>(run->next->data)->item); | |
|
msw
2011/08/19 23:16:59
Same question about PangoLayoutRun and PangoItem c
| |
| 479 } | |
| 480 } else { // !at_run_boundary | |
| 481 // current.seletion_end - current.caret_pos | |
| 482 // == NumOfCharsInGrapheme(caret_pos); | |
| 483 size_t prev = Utf16IndexOfAdjacentGrapheme(right.caret_pos(), PREVIOUS); | |
| 484 right.set_caret_pos(prev); | |
| 485 right.set_selection_end(prev); | |
| 486 } | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 right.set_selection_start(right.selection_end()); | |
| 491 return right; | |
| 492 } | |
| 493 | |
| 494 PangoLayout* RenderTextLinux::EnsureLayout() { | |
| 495 if (layout_ == NULL) { | |
| 496 CanvasSkia canvas(display_rect().width(), display_rect().height(), false); | |
| 497 // TODO(xji): how costly is the Begin/EndPlatformPaint? Can I get cairo | |
| 498 // context in another way? | |
| 499 cairo_t* cr = skia::BeginPlatformPaint(&canvas); | |
| 500 | |
| 501 layout_ = pango_cairo_create_layout(cr); | |
| 502 SetupPangoLayout(layout_, text(), default_style().font, | |
| 503 display_rect().width(), GetTextDirection(), | |
| 504 CanvasSkia::DefaultCanvasTextAlignment()); | |
| 505 pango_layout_set_height(layout_, display_rect().height() * PANGO_SCALE); | |
| 506 SetupPangoAttributes(layout_); | |
| 507 | |
| 508 skia::EndPlatformPaint(&canvas); | |
| 509 | |
| 510 layout_line_ = pango_layout_get_line_readonly(layout_, 0); | |
| 511 } | |
| 512 return layout_; | |
| 513 } | |
| 514 | |
| 515 void RenderTextLinux::ResetLayout() { | |
| 516 // set_cached_bounds_and_offset_valid(false) is done in RenderText for every | |
| 517 // operation that trigger ResetLayout(). | |
|
msw
2011/08/19 23:16:59
Yeah, we might want to make a more general shared
| |
| 518 if (layout_) { | |
| 519 g_object_unref(layout_); | |
|
msw
2011/08/19 23:16:59
Yuck, more GTK!
| |
| 520 layout_ = NULL; | |
| 521 } | |
| 522 if (layout_line_) { | |
| 523 // TODO(xji): do I need ref/unref? | |
| 524 // pango_layout_line_unref(layout_line_); | |
|
msw
2011/08/19 23:16:59
Sounds like you should ref and unref it to retain
xji
2011/08/22 23:57:28
Done.
| |
| 525 layout_line_ = NULL; | |
| 526 } | |
| 527 } | |
| 528 | |
| 529 void RenderTextLinux::SetupPangoAttributes(PangoLayout* layout) { | |
| 530 PangoAttrList* attrs = pango_attr_list_new(); | |
| 531 // Set selection background color. | |
| 532 SkColor selection_color = | |
| 533 focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; | |
| 534 PangoAttribute* pango_attr = pango_attr_background_new( | |
| 535 COLOR_16_TO_32_BIT(SkColorGetR(selection_color)), | |
|
msw
2011/08/19 23:16:59
Can you use SkColorGetR/G/B or similar instead her
xji
2011/08/22 23:57:28
I am using SKColorGetR/G/B, but I need to convert
| |
| 536 COLOR_16_TO_32_BIT(SkColorGetG(selection_color)), | |
| 537 COLOR_16_TO_32_BIT(SkColorGetB(selection_color))); | |
| 538 AppendPangoAttribute(MinOfSelection(), MaxOfSelection(), | |
| 539 pango_attr, attrs); | |
| 540 | |
| 541 StyleRanges ranges_of_style(style_ranges()); | |
| 542 ApplyCompositionAndSelectionStyles(&ranges_of_style); | |
| 543 | |
| 544 for (StyleRanges::const_iterator i = ranges_of_style.begin(); | |
| 545 i < ranges_of_style.end(); ++i) { | |
| 546 const Font& font = !i->underline ? i->font : | |
| 547 i->font.DeriveFont(0, i->font.GetStyle() | Font::UNDERLINED); | |
| 548 pango_attr = pango_attr_font_desc_new( | |
| 549 reinterpret_cast<PangoFontDescription*>(font.GetNativeFont())); | |
| 550 AppendPangoAttribute(i->range.start(), i->range.end(), pango_attr, attrs); | |
| 551 | |
| 552 SkColor foreground = i->foreground; | |
| 553 pango_attr = pango_attr_foreground_new( | |
| 554 COLOR_16_TO_32_BIT(SkColorGetR(foreground)), | |
| 555 COLOR_16_TO_32_BIT(SkColorGetG(foreground)), | |
| 556 COLOR_16_TO_32_BIT(SkColorGetB(foreground))); | |
| 557 AppendPangoAttribute(i->range.start(), i->range.end(), pango_attr, attrs); | |
| 558 | |
| 559 if (i->strike) { | |
| 560 pango_attr = pango_attr_strikethrough_new(true); | |
| 561 AppendPangoAttribute(i->range.start(), i->range.end(), pango_attr, attrs); | |
| 562 } | |
| 563 } | |
| 564 | |
| 565 pango_layout_set_attributes(layout, attrs); | |
| 566 pango_attr_list_unref(attrs); | |
| 567 } | |
| 568 | |
| 569 void RenderTextLinux::AppendPangoAttribute(size_t start, | |
| 570 size_t end, | |
| 571 PangoAttribute* pango_attr, | |
| 572 PangoAttrList* attrs) { | |
| 573 pango_attr->start_index = Utf16IndexToUtf8Index(text(), start); | |
| 574 pango_attr->end_index = Utf16IndexToUtf8Index(text(), end); | |
| 575 pango_attr_list_insert(attrs, pango_attr); | |
| 576 } | |
| 577 | |
| 578 GSList* RenderTextLinux::GetPreviousRun(GSList* run) const { | |
|
msw
2011/08/19 23:16:59
lol at singly linked lists :)
| |
| 579 GSList* current = layout_line_->runs; | |
| 580 GSList* prev = NULL; | |
| 581 while (current) { | |
| 582 if (current == run) | |
| 583 return prev; | |
| 584 prev = current; | |
| 585 current = current->next; | |
| 586 } | |
| 587 return NULL; | |
| 588 } | |
| 589 | |
| 590 GSList* RenderTextLinux::GetLastRun() const { | |
| 591 GSList* current = layout_line_->runs; | |
|
msw
2011/08/19 23:16:59
i'm done loling, now i'm sighing :(
| |
| 592 while (current && current->next) { | |
| 593 current = current->next; | |
| 594 } | |
| 595 return current; | |
| 596 } | |
| 597 | |
| 598 size_t RenderTextLinux::Utf16IndexToUtf8Index(const string16& text, | |
| 599 size_t index) const { | |
| 600 int utf8_index = 0; | |
| 601 for (size_t i = 0; i < index; ++i) { | |
| 602 if (text[i] < 0x80) { | |
| 603 ++utf8_index; | |
| 604 } else if (text[i] < 0x800) { | |
| 605 utf8_index += 2; | |
| 606 } else { | |
| 607 if ((U16_IS_LEAD(text[i]))) { | |
| 608 if (i + 1 < index) { | |
| 609 if (U16_IS_TRAIL(text[i + 1])) { | |
| 610 // A surrogate pair. | |
| 611 utf8_index += 4; | |
| 612 ++i; | |
| 613 continue; | |
| 614 } | |
| 615 } else if (i + 1 < text.length() && U16_IS_TRAIL(text[i + 1])) { | |
| 616 // A split surrogate pair, stop at the leading. | |
| 617 break; | |
| 618 } | |
| 619 } | |
| 620 utf8_index += 3; | |
| 621 } | |
| 622 } | |
| 623 return utf8_index; | |
| 624 } | |
| 625 | |
| 626 | |
| 627 size_t RenderTextLinux::Utf8IndexToUtf16Index(const string16& text, | |
| 628 size_t index) const { | |
| 629 // TODO(xji): DUP with utf16IndexToUtf8Index. | |
| 630 size_t utf8_index = 0; | |
| 631 size_t utf16_index = 0; | |
| 632 for (; utf16_index < text.length(); ++utf16_index) { | |
| 633 if (text[utf16_index] < 0x80) { | |
| 634 ++utf8_index; | |
| 635 } else if (text[utf16_index] < 0x800) { | |
| 636 utf8_index += 2; | |
| 637 } else { | |
| 638 if ((U16_IS_LEAD(text[utf16_index]))) { | |
| 639 if (utf16_index + 1 < text.length()) { | |
| 640 if (U16_IS_TRAIL(text[utf16_index + 1])) { | |
| 641 // A surrogate pair. | |
| 642 utf8_index += 4; | |
| 643 if (utf8_index >= index) | |
| 644 break; | |
| 645 ++utf16_index; | |
| 646 continue; | |
| 647 } | |
| 648 } | |
| 649 } | |
| 650 utf8_index += 3; | |
| 651 } | |
| 652 if (utf8_index > index) | |
| 653 break; | |
| 654 } | |
| 655 return utf16_index; | |
| 656 } | |
| 657 | |
| 658 void RenderTextLinux::AdjustBoundsForNonInsertMode( | |
|
msw
2011/08/19 23:16:59
I think this function should be in-lined in GetCur
xji
2011/08/22 23:57:28
Done.
| |
| 659 const SelectionModel& selection, | |
| 660 const PangoRectangle& pos, | |
| 661 Rect* bounds) const { | |
| 662 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
| 663 if (caret_placement == SelectionModel::LEADING) { | |
| 664 if (pos.width > 0) { | |
| 665 bounds->set_width(pos.width); | |
| 666 } else { | |
| 667 bounds->set_x(pos.x + pos.width); | |
| 668 bounds->set_width(-pos.width); | |
| 669 } | |
| 670 } else { | |
| 671 // TODO(xji): what is the expected behavior? For example, "abcFED", | |
| 672 // when |selection| == SelectionModel(3, 2, TRAILING), in insert-mode, | |
| 673 // cursor is at right of 'c'. In non-insert-mode, is the bounds around 'D'? | |
|
msw
2011/08/19 23:16:59
I'd say yes, but I'm not sure if you're asking me
xji
2011/08/22 23:57:28
I implement as this. we can always change it if it
| |
| 674 } | |
| 675 } | |
| 676 | |
| 20 } // namespace gfx | 677 } // namespace gfx |
| OLD | NEW |