Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(316)

Side by Side Diff: Source/core/paint/InlineFlowBoxPainter.cpp

Issue 752373003: Remove duplicated code for image-strip computation in InlineFlowBoxPainter (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 "config.h" 5 #include "config.h"
6 #include "core/paint/InlineFlowBoxPainter.h" 6 #include "core/paint/InlineFlowBoxPainter.h"
7 7
8 #include "core/paint/BoxPainter.h" 8 #include "core/paint/BoxPainter.h"
9 #include "core/paint/DrawingRecorder.h" 9 #include "core/paint/DrawingRecorder.h"
10 #include "core/rendering/InlineFlowBox.h" 10 #include "core/rendering/InlineFlowBox.h"
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
99 StyleImage* img = fillLayer.image(); 99 StyleImage* img = fillLayer.image();
100 bool hasFillImage = img && img->canRender(m_inlineFlowBox.renderer(), m_inli neFlowBox.renderer().style()->effectiveZoom()); 100 bool hasFillImage = img && img->canRender(m_inlineFlowBox.renderer(), m_inli neFlowBox.renderer().style()->effectiveZoom());
101 if ((!hasFillImage && !m_inlineFlowBox.renderer().style()->hasBorderRadius() ) || (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) || !m_in lineFlowBox.parent()) { 101 if ((!hasFillImage && !m_inlineFlowBox.renderer().style()->hasBorderRadius() ) || (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) || !m_in lineFlowBox.parent()) {
102 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), pa intInfo, c, fillLayer, rect, BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op); 102 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), pa intInfo, c, fillLayer, rect, BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op);
103 } else if (m_inlineFlowBox.renderer().style()->boxDecorationBreak() == DCLON E) { 103 } else if (m_inlineFlowBox.renderer().style()->boxDecorationBreak() == DCLON E) {
104 GraphicsContextStateSaver stateSaver(*paintInfo.context); 104 GraphicsContextStateSaver stateSaver(*paintInfo.context);
105 paintInfo.context->clip(LayoutRect(rect.x(), rect.y(), m_inlineFlowBox.w idth(), m_inlineFlowBox.height())); 105 paintInfo.context->clip(LayoutRect(rect.x(), rect.y(), m_inlineFlowBox.w idth(), m_inlineFlowBox.height()));
106 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), pa intInfo, c, fillLayer, rect, BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op); 106 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), pa intInfo, c, fillLayer, rect, BackgroundBleedNone, &m_inlineFlowBox, rect.size(), op);
107 } else { 107 } else {
108 // We have a fill image that spans multiple lines. 108 // We have a fill image that spans multiple lines.
109 // We need to adjust tx and ty by the width of all previous lines. 109 // FIXME: frameSize ought to be the same as rect.size().
110 // Think of background painting on inlines as though you had one long li ne, a single continuous 110 LayoutSize frameSize(static_cast<LayoutUnit>(m_inlineFlowBox.width()), s tatic_cast<LayoutUnit>(m_inlineFlowBox.height()));
111 // strip. Even though that strip has been broken up across multiple line s, you still paint it 111 LayoutRect imageStripPaintRect = paintRectForImageStrip(rect.location(), frameSize, m_inlineFlowBox.renderer().style()->isLeftToRightDirection());
112 // as though you had one single line. This means each line has to pick u p the background where
113 // the previous line left off.
114 LayoutUnit logicalOffsetOnLine = 0;
115 LayoutUnit totalLogicalWidth;
116 if (m_inlineFlowBox.renderer().style()->direction() == LTR) {
117 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = curr->prevLineBox())
118 logicalOffsetOnLine += curr->logicalWidth();
119 totalLogicalWidth = logicalOffsetOnLine;
120 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->next LineBox())
121 totalLogicalWidth += curr->logicalWidth();
122 } else {
123 for (InlineFlowBox* curr = m_inlineFlowBox.nextLineBox(); curr; curr = curr->nextLineBox())
124 logicalOffsetOnLine += curr->logicalWidth();
125 totalLogicalWidth = logicalOffsetOnLine;
126 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->prev LineBox())
127 totalLogicalWidth += curr->logicalWidth();
128 }
129 LayoutUnit stripX = rect.x() - (m_inlineFlowBox.isHorizontal() ? logical OffsetOnLine : LayoutUnit());
130 LayoutUnit stripY = rect.y() - (m_inlineFlowBox.isHorizontal() ? LayoutU nit() : logicalOffsetOnLine);
131 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogicalWid th : static_cast<LayoutUnit>(m_inlineFlowBox.width());
132 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? static_cast<La youtUnit>(m_inlineFlowBox.height()) : totalLogicalWidth;
133
134 GraphicsContextStateSaver stateSaver(*paintInfo.context); 112 GraphicsContextStateSaver stateSaver(*paintInfo.context);
135 paintInfo.context->clip(LayoutRect(rect.x(), rect.y(), m_inlineFlowBox.w idth(), m_inlineFlowBox.height())); 113 paintInfo.context->clip(LayoutRect(rect.x(), rect.y(), m_inlineFlowBox.w idth(), m_inlineFlowBox.height()));
136 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), pa intInfo, c, fillLayer, LayoutRect(stripX, stripY, stripWidth, stripHeight), Back groundBleedNone, &m_inlineFlowBox, rect.size(), op); 114 BoxPainter::paintFillLayerExtended(*m_inlineFlowBox.boxModelObject(), pa intInfo, c, fillLayer, imageStripPaintRect, BackgroundBleedNone, &m_inlineFlowBo x, rect.size(), op);
137 } 115 }
138 } 116 }
139 117
140 void InlineFlowBoxPainter::paintBoxShadow(const PaintInfo& info, RenderStyle* s, ShadowStyle shadowStyle, const LayoutRect& paintRect) 118 void InlineFlowBoxPainter::paintBoxShadow(const PaintInfo& info, RenderStyle* s, ShadowStyle shadowStyle, const LayoutRect& paintRect)
141 { 119 {
142 if ((!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) || !m _inlineFlowBox.parent()) { 120 if ((!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) || !m _inlineFlowBox.parent()) {
143 BoxPainter::paintBoxShadow(info, paintRect, s, shadowStyle); 121 BoxPainter::paintBoxShadow(info, paintRect, s, shadowStyle);
144 } else { 122 } else {
145 // FIXME: We can do better here in the multi-line case. We want to push a clip so that the shadow doesn't 123 // FIXME: We can do better here in the multi-line case. We want to push a clip so that the shadow doesn't
146 // protrude incorrectly at the edges, and we want to possibly include sh adows cast from the previous/following lines 124 // protrude incorrectly at the edges, and we want to possibly include sh adows cast from the previous/following lines
(...skipping 22 matching lines...) Expand all
169 if (box->includeLogicalLeftEdge()) { 147 if (box->includeLogicalLeftEdge()) {
170 clipRect.setY(paintRect.y() - outsets.top()); 148 clipRect.setY(paintRect.y() - outsets.top());
171 clipRect.setHeight(paintRect.height() + outsets.top()); 149 clipRect.setHeight(paintRect.height() + outsets.top());
172 } 150 }
173 if (box->includeLogicalRightEdge()) 151 if (box->includeLogicalRightEdge())
174 clipRect.setHeight(clipRect.height() + outsets.bottom()); 152 clipRect.setHeight(clipRect.height() + outsets.bottom());
175 } 153 }
176 return clipRect; 154 return clipRect;
177 } 155 }
178 156
157 LayoutRect InlineFlowBoxPainter::paintRectForImageStrip(const LayoutPoint& paint Offset, const LayoutSize& frameSize, bool isLeftToRightDirection) const
158 {
159 // We have a fill/border/mask image that spans multiple lines.
160 // We need to adjust the offset by the width of all previous lines.
161 // Think of background painting on inlines as though you had one long line, a single continuous
162 // strip. Even though that strip has been broken up across multiple lines, y ou still paint it
163 // as though you had one single line. This means each line has to pick up th e background where
164 // the previous line left off.
165 LayoutUnit logicalOffsetOnLine = 0;
166 LayoutUnit totalLogicalWidth;
167 if (isLeftToRightDirection) {
168 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = c urr->prevLineBox())
169 logicalOffsetOnLine += curr->logicalWidth();
170 totalLogicalWidth = logicalOffsetOnLine;
171 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->nextLine Box())
172 totalLogicalWidth += curr->logicalWidth();
173 } else {
174 for (InlineFlowBox* curr = m_inlineFlowBox.nextLineBox(); curr; curr = c urr->nextLineBox())
175 logicalOffsetOnLine += curr->logicalWidth();
176 totalLogicalWidth = logicalOffsetOnLine;
177 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->prevLine Box())
178 totalLogicalWidth += curr->logicalWidth();
179 }
180 LayoutUnit stripX = paintOffset.x() - (m_inlineFlowBox.isHorizontal() ? logi calOffsetOnLine : LayoutUnit());
181 LayoutUnit stripY = paintOffset.y() - (m_inlineFlowBox.isHorizontal() ? Layo utUnit() : logicalOffsetOnLine);
182 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogicalWidth : frameSize.width();
183 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? frameSize.height() : totalLogicalWidth;
184 return LayoutRect(stripX, stripY, stripWidth, stripHeight);
185 }
186
179 void InlineFlowBoxPainter::paintBoxDecorationBackground(const PaintInfo& paintIn fo, const LayoutPoint& paintOffset) 187 void InlineFlowBoxPainter::paintBoxDecorationBackground(const PaintInfo& paintIn fo, const LayoutPoint& paintOffset)
180 { 188 {
181 ASSERT(paintInfo.phase == PaintPhaseForeground); 189 ASSERT(paintInfo.phase == PaintPhaseForeground);
182 if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.renderer()) || m_inlin eFlowBox.renderer().style()->visibility() != VISIBLE) 190 if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.renderer()) || m_inlin eFlowBox.renderer().style()->visibility() != VISIBLE)
183 return; 191 return;
184 192
185 // You can use p::first-line to specify a background. If so, the root line b oxes for 193 // You can use p::first-line to specify a background. If so, the root line b oxes for
186 // a line may actually have to paint a background. 194 // a line may actually have to paint a background.
187 RenderStyle* styleToUse = m_inlineFlowBox.renderer().style(m_inlineFlowBox.i sFirstLineStyle()); 195 RenderStyle* styleToUse = m_inlineFlowBox.renderer().style(m_inlineFlowBox.i sFirstLineStyle());
188 bool shouldPaintBoxDecorationBackground; 196 bool shouldPaintBoxDecorationBackground;
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 bool hasBorderImage = borderImageSource && borderImageSource->canRender( m_inlineFlowBox.renderer(), styleToUse->effectiveZoom()); 229 bool hasBorderImage = borderImageSource && borderImageSource->canRender( m_inlineFlowBox.renderer(), styleToUse->effectiveZoom());
222 if (hasBorderImage && !borderImageSource->isLoaded()) 230 if (hasBorderImage && !borderImageSource->isLoaded())
223 return; // Don't paint anything while we wait for the image to load. 231 return; // Don't paint anything while we wait for the image to load.
224 232
225 // The simple case is where we either have no border image or we are the only box for this object. 233 // The simple case is where we either have no border image or we are the only box for this object.
226 // In those cases only a single call to draw is required. 234 // In those cases only a single call to draw is required.
227 if (!hasBorderImage || (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowB ox.nextLineBox())) { 235 if (!hasBorderImage || (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowB ox.nextLineBox())) {
228 BoxPainter::paintBorder(*m_inlineFlowBox.boxModelObject(), paintInfo , paintRect, m_inlineFlowBox.renderer().style(m_inlineFlowBox.isFirstLineStyle() ), BackgroundBleedNone, m_inlineFlowBox.includeLogicalLeftEdge(), m_inlineFlowBo x.includeLogicalRightEdge()); 236 BoxPainter::paintBorder(*m_inlineFlowBox.boxModelObject(), paintInfo , paintRect, m_inlineFlowBox.renderer().style(m_inlineFlowBox.isFirstLineStyle() ), BackgroundBleedNone, m_inlineFlowBox.includeLogicalLeftEdge(), m_inlineFlowBo x.includeLogicalRightEdge());
229 } else { 237 } else {
230 // We have a border image that spans multiple lines. 238 // We have a border image that spans multiple lines.
231 // We need to adjust tx and ty by the width of all previous lines. 239 LayoutRect imageStripPaintRect = paintRectForImageStrip(adjustedPain tOffset, frameRect.size());
232 // Think of border image painting on inlines as though you had one l ong line, a single continuous
233 // strip. Even though that strip has been broken up across multiple lines, you still paint it
234 // as though you had one single line. This means each line has to pi ck up the image where
235 // the previous line left off.
236 // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right,
chrishtr 2014/12/04 17:25:03 Are you sure it's correct to address RTL directly
fs 2014/12/04 17:42:02 I've not done anything to address this FIXME (so m
chrishtr 2014/12/04 17:47:57 Ah I missed that subtlety.
fs 2014/12/04 18:15:45 Restored FIXME (and one like it to the mask case.)
237 // but it isn't even clear how this should work at all.
238 LayoutUnit logicalOffsetOnLine = 0;
239 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = curr->prevLineBox())
240 logicalOffsetOnLine += curr->logicalWidth();
241 LayoutUnit totalLogicalWidth = logicalOffsetOnLine;
242 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->next LineBox())
243 totalLogicalWidth += curr->logicalWidth();
244 LayoutUnit stripX = adjustedPaintOffset.x() - (m_inlineFlowBox.isHor izontal() ? logicalOffsetOnLine : LayoutUnit());
245 LayoutUnit stripY = adjustedPaintOffset.y() - (m_inlineFlowBox.isHor izontal() ? LayoutUnit() : logicalOffsetOnLine);
246 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogica lWidth : frameRect.width();
247 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? frameRect. height() : totalLogicalWidth;
248
249 LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBo x, borderImage, paintRect); 240 LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBo x, borderImage, paintRect);
250 GraphicsContextStateSaver stateSaver(*paintInfo.context); 241 GraphicsContextStateSaver stateSaver(*paintInfo.context);
251 paintInfo.context->clip(clipRect); 242 paintInfo.context->clip(clipRect);
252 BoxPainter::paintBorder(*m_inlineFlowBox.boxModelObject(), paintInfo , LayoutRect(stripX, stripY, stripWidth, stripHeight), m_inlineFlowBox.renderer( ).style(m_inlineFlowBox.isFirstLineStyle())); 243 BoxPainter::paintBorder(*m_inlineFlowBox.boxModelObject(), paintInfo , imageStripPaintRect, m_inlineFlowBox.renderer().style(m_inlineFlowBox.isFirstL ineStyle()));
253 } 244 }
254 } 245 }
255 } 246 }
256 247
257 void InlineFlowBoxPainter::paintMask(const PaintInfo& paintInfo, const LayoutPoi nt& paintOffset) 248 void InlineFlowBoxPainter::paintMask(const PaintInfo& paintInfo, const LayoutPoi nt& paintOffset)
258 { 249 {
259 if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.renderer()) || m_inlin eFlowBox.renderer().style()->visibility() != VISIBLE || paintInfo.phase != Paint PhaseMask) 250 if (!paintInfo.shouldPaintWithinRoot(&m_inlineFlowBox.renderer()) || m_inlin eFlowBox.renderer().style()->visibility() != VISIBLE || paintInfo.phase != Paint PhaseMask)
260 return; 251 return;
261 252
262 LayoutRect frameRect = roundedFrameRectClampedToLineTopAndBottomIfNeeded(); 253 LayoutRect frameRect = roundedFrameRectClampedToLineTopAndBottomIfNeeded();
(...skipping 29 matching lines...) Expand all
292 bool hasBoxImage = maskBoxImage && maskBoxImage->canRender(m_inlineFlowBox.r enderer(), m_inlineFlowBox.renderer().style()->effectiveZoom()); 283 bool hasBoxImage = maskBoxImage && maskBoxImage->canRender(m_inlineFlowBox.r enderer(), m_inlineFlowBox.renderer().style()->effectiveZoom());
293 if (!hasBoxImage || !maskBoxImage->isLoaded()) { 284 if (!hasBoxImage || !maskBoxImage->isLoaded()) {
294 if (pushTransparencyLayer) 285 if (pushTransparencyLayer)
295 paintInfo.context->endLayer(); 286 paintInfo.context->endLayer();
296 return; // Don't paint anything while we wait for the image to load. 287 return; // Don't paint anything while we wait for the image to load.
297 } 288 }
298 289
299 // The simple case is where we are the only box for this object. In those 290 // The simple case is where we are the only box for this object. In those
300 // cases only a single call to draw is required. 291 // cases only a single call to draw is required.
301 if (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) { 292 if (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) {
302 BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paint Info.context, LayoutRect(adjustedPaintOffset, frameRect.size()), m_inlineFlowBox .renderer().style(), maskNinePieceImage, compositeOp); 293 BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paint Info.context, paintRect, m_inlineFlowBox.renderer().style(), maskNinePieceImage, compositeOp);
303 } else { 294 } else {
304 // We have a mask image that spans multiple lines. 295 // We have a mask image that spans multiple lines.
305 // We need to adjust _tx and _ty by the width of all previous lines. 296 LayoutRect imageStripPaintRect = paintRectForImageStrip(adjustedPaintOff set, frameRect.size());
chrishtr 2014/12/04 17:25:03 Same Q here about RTL. Are we missing RTL test cov
fs 2014/12/04 17:42:02 See above. I'm not sure about the test-coverage fo
306 LayoutUnit logicalOffsetOnLine = 0;
307 for (InlineFlowBox* curr = m_inlineFlowBox.prevLineBox(); curr; curr = c urr->prevLineBox())
308 logicalOffsetOnLine += curr->logicalWidth();
309 LayoutUnit totalLogicalWidth = logicalOffsetOnLine;
310 for (InlineFlowBox* curr = &m_inlineFlowBox; curr; curr = curr->nextLine Box())
311 totalLogicalWidth += curr->logicalWidth();
312 LayoutUnit stripX = adjustedPaintOffset.x() - (m_inlineFlowBox.isHorizon tal() ? logicalOffsetOnLine : LayoutUnit());
313 LayoutUnit stripY = adjustedPaintOffset.y() - (m_inlineFlowBox.isHorizon tal() ? LayoutUnit() : logicalOffsetOnLine);
314 LayoutUnit stripWidth = m_inlineFlowBox.isHorizontal() ? totalLogicalWid th : frameRect.width();
315 LayoutUnit stripHeight = m_inlineFlowBox.isHorizontal() ? frameRect.heig ht() : totalLogicalWidth;
316
317 LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBox, m askNinePieceImage, paintRect); 297 LayoutRect clipRect = clipRectForNinePieceImageStrip(&m_inlineFlowBox, m askNinePieceImage, paintRect);
318 GraphicsContextStateSaver stateSaver(*paintInfo.context); 298 GraphicsContextStateSaver stateSaver(*paintInfo.context);
319 paintInfo.context->clip(clipRect); 299 paintInfo.context->clip(clipRect);
320 BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paint Info.context, LayoutRect(stripX, stripY, stripWidth, stripHeight), m_inlineFlowB ox.renderer().style(), maskNinePieceImage, compositeOp); 300 BoxPainter::paintNinePieceImage(*m_inlineFlowBox.boxModelObject(), paint Info.context, imageStripPaintRect, m_inlineFlowBox.renderer().style(), maskNineP ieceImage, compositeOp);
321 } 301 }
322 302
323 if (pushTransparencyLayer) 303 if (pushTransparencyLayer)
324 paintInfo.context->endLayer(); 304 paintInfo.context->endLayer();
325 } 305 }
326 306
327 LayoutRect InlineFlowBoxPainter::roundedFrameRectClampedToLineTopAndBottomIfNeed ed() const 307 LayoutRect InlineFlowBoxPainter::roundedFrameRectClampedToLineTopAndBottomIfNeed ed() const
328 { 308 {
329 // Pixel snap rect painting. 309 // Pixel snap rect painting.
330 LayoutRect rect = m_inlineFlowBox.roundedFrameRect(); 310 LayoutRect rect = m_inlineFlowBox.roundedFrameRect();
(...skipping 11 matching lines...) Expand all
342 rect.setHeight(logicalHeight); 322 rect.setHeight(logicalHeight);
343 } else { 323 } else {
344 rect.setX(logicalTop); 324 rect.setX(logicalTop);
345 rect.setWidth(logicalHeight); 325 rect.setWidth(logicalHeight);
346 } 326 }
347 } 327 }
348 return rect; 328 return rect;
349 } 329 }
350 330
351 } // namespace blink 331 } // namespace blink
OLDNEW
« Source/core/paint/InlineFlowBoxPainter.h ('K') | « Source/core/paint/InlineFlowBoxPainter.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698