1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SVG project.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Crocodile Clips Ltd..
19 : * Portions created by the Initial Developer are Copyright (C) 2002
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsSVGPathGeometryFrame.h"
40 : #include "nsGkAtoms.h"
41 : #include "nsSVGMarkerFrame.h"
42 : #include "nsSVGUtils.h"
43 : #include "nsSVGEffects.h"
44 : #include "nsSVGGraphicElement.h"
45 : #include "nsSVGOuterSVGFrame.h"
46 : #include "nsSVGRect.h"
47 : #include "nsSVGPathGeometryElement.h"
48 : #include "gfxContext.h"
49 : #include "gfxPlatform.h"
50 :
51 : //----------------------------------------------------------------------
52 : // Implementation
53 :
54 : nsIFrame*
55 0 : NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell,
56 : nsStyleContext* aContext)
57 : {
58 0 : return new (aPresShell) nsSVGPathGeometryFrame(aContext);
59 : }
60 :
61 0 : NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame)
62 :
63 : //----------------------------------------------------------------------
64 : // nsQueryFrame methods
65 :
66 0 : NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame)
67 0 : NS_QUERYFRAME_ENTRY(nsISVGChildFrame)
68 0 : NS_QUERYFRAME_TAIL_INHERITING(nsSVGPathGeometryFrameBase)
69 :
70 : //----------------------------------------------------------------------
71 : // nsIFrame methods
72 :
73 : NS_IMETHODIMP
74 0 : nsSVGPathGeometryFrame::AttributeChanged(PRInt32 aNameSpaceID,
75 : nsIAtom* aAttribute,
76 : PRInt32 aModType)
77 : {
78 0 : if (aNameSpaceID == kNameSpaceID_None &&
79 : (static_cast<nsSVGPathGeometryElement*>
80 0 : (mContent)->AttributeDefinesGeometry(aAttribute) ||
81 : aAttribute == nsGkAtoms::transform))
82 0 : nsSVGUtils::UpdateGraphic(this);
83 :
84 0 : return NS_OK;
85 : }
86 :
87 : /* virtual */ void
88 0 : nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
89 : {
90 0 : nsSVGPathGeometryFrameBase::DidSetStyleContext(aOldStyleContext);
91 :
92 : // XXX: we'd like to use the style_hint mechanism and the
93 : // ContentStateChanged/AttributeChanged functions for style changes
94 : // to get slightly finer granularity, but unfortunately the
95 : // style_hints don't map very well onto svg. Here seems to be the
96 : // best place to deal with style changes:
97 :
98 0 : nsSVGUtils::UpdateGraphic(this);
99 0 : }
100 :
101 : nsIAtom *
102 0 : nsSVGPathGeometryFrame::GetType() const
103 : {
104 0 : return nsGkAtoms::svgPathGeometryFrame;
105 : }
106 :
107 : //----------------------------------------------------------------------
108 : // nsISVGChildFrame methods
109 :
110 : NS_IMETHODIMP
111 0 : nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext,
112 : const nsIntRect *aDirtyRect)
113 : {
114 0 : if (!GetStyleVisibility()->IsVisible())
115 0 : return NS_OK;
116 :
117 : /* render */
118 0 : Render(aContext);
119 :
120 0 : if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
121 0 : MarkerProperties properties = GetMarkerProperties(this);
122 :
123 0 : if (properties.MarkersExist()) {
124 0 : float strokeWidth = GetStrokeWidth();
125 :
126 0 : nsTArray<nsSVGMark> marks;
127 : static_cast<nsSVGPathGeometryElement*>
128 0 : (mContent)->GetMarkPoints(&marks);
129 :
130 0 : PRUint32 num = marks.Length();
131 :
132 0 : if (num) {
133 0 : nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame();
134 0 : if (frame)
135 0 : frame->PaintMark(aContext, this, &marks[0], strokeWidth);
136 :
137 0 : frame = properties.GetMarkerMidFrame();
138 0 : if (frame) {
139 0 : for (PRUint32 i = 1; i < num - 1; i++)
140 0 : frame->PaintMark(aContext, this, &marks[i], strokeWidth);
141 : }
142 :
143 0 : frame = properties.GetMarkerEndFrame();
144 0 : if (frame)
145 0 : frame->PaintMark(aContext, this, &marks[num-1], strokeWidth);
146 : }
147 : }
148 : }
149 :
150 0 : return NS_OK;
151 : }
152 :
153 : NS_IMETHODIMP_(nsIFrame*)
154 0 : nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint)
155 : {
156 : PRUint16 fillRule, hitTestFlags;
157 0 : if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
158 0 : hitTestFlags = SVG_HIT_TEST_FILL;
159 0 : fillRule = GetClipRule();
160 : } else {
161 0 : hitTestFlags = GetHitTestFlags();
162 : // XXX once bug 614732 is fixed, aPoint won't need any conversion in order
163 : // to compare it with mRect.
164 0 : gfxMatrix canvasTM = GetCanvasTM();
165 0 : if (canvasTM.IsSingular()) {
166 0 : return nsnull;
167 : }
168 : nsPoint point =
169 0 : nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, canvasTM, PresContext());
170 0 : if (!hitTestFlags || ((hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) &&
171 0 : !mRect.Contains(point)))
172 0 : return nsnull;
173 0 : fillRule = GetStyleSVG()->mFillRule;
174 : }
175 :
176 0 : bool isHit = false;
177 :
178 : nsRefPtr<gfxContext> context =
179 0 : new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
180 :
181 0 : GeneratePath(context);
182 : gfxPoint userSpacePoint =
183 : context->DeviceToUser(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x),
184 0 : PresContext()->AppUnitsToGfxUnits(aPoint.y)));
185 :
186 0 : if (fillRule == NS_STYLE_FILL_RULE_EVENODD)
187 0 : context->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
188 : else
189 0 : context->SetFillRule(gfxContext::FILL_RULE_WINDING);
190 :
191 0 : if (hitTestFlags & SVG_HIT_TEST_FILL)
192 0 : isHit = context->PointInFill(userSpacePoint);
193 0 : if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
194 0 : SetupCairoStrokeHitGeometry(context);
195 0 : isHit = context->PointInStroke(userSpacePoint);
196 : }
197 :
198 0 : if (isHit && nsSVGUtils::HitTestClip(this, aPoint))
199 0 : return this;
200 :
201 0 : return nsnull;
202 : }
203 :
204 : NS_IMETHODIMP_(nsRect)
205 0 : nsSVGPathGeometryFrame::GetCoveredRegion()
206 : {
207 : // See bug 614732 comment 32:
208 : //return nsSVGUtils::TransformFrameRectToOuterSVG(mRect, GetCanvasTM(), PresContext());
209 0 : return mCoveredRegion;
210 : }
211 :
212 : NS_IMETHODIMP
213 0 : nsSVGPathGeometryFrame::UpdateCoveredRegion()
214 : {
215 : gfxRect extent = GetBBoxContribution(gfxMatrix(),
216 : nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIgnoreFillIfNone |
217 : nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIgnoreStrokeIfNone |
218 0 : nsSVGUtils::eBBoxIncludeMarkers);
219 : mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent,
220 0 : PresContext()->AppUnitsPerCSSPixel());
221 :
222 : // See bug 614732 comment 32.
223 : mCoveredRegion = nsSVGUtils::TransformFrameRectToOuterSVG(
224 0 : mRect, GetCanvasTM(), PresContext());
225 :
226 0 : return NS_OK;
227 : }
228 :
229 : NS_IMETHODIMP
230 0 : nsSVGPathGeometryFrame::InitialUpdate()
231 : {
232 0 : NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW,
233 : "Yikes! We've been called already! Hopefully we weren't called "
234 : "before our nsSVGOuterSVGFrame's initial Reflow()!!!");
235 :
236 0 : nsSVGUtils::UpdateGraphic(this);
237 :
238 0 : NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW),
239 : "We don't actually participate in reflow");
240 :
241 : // Do unset the various reflow bits, though.
242 : mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
243 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
244 0 : return NS_OK;
245 : }
246 :
247 : void
248 0 : nsSVGPathGeometryFrame::NotifySVGChanged(PRUint32 aFlags)
249 : {
250 0 : NS_ABORT_IF_FALSE(!(aFlags & DO_NOT_NOTIFY_RENDERING_OBSERVERS) ||
251 : (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
252 : "Must be NS_STATE_SVG_NONDISPLAY_CHILD!");
253 :
254 0 : NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
255 : "Invalidation logic may need adjusting");
256 :
257 0 : if (!(aFlags & DO_NOT_NOTIFY_RENDERING_OBSERVERS)) {
258 0 : nsSVGUtils::UpdateGraphic(this);
259 : }
260 0 : }
261 :
262 : void
263 0 : nsSVGPathGeometryFrame::NotifyRedrawSuspended()
264 : {
265 0 : AddStateBits(NS_STATE_SVG_REDRAW_SUSPENDED);
266 0 : }
267 :
268 : void
269 0 : nsSVGPathGeometryFrame::NotifyRedrawUnsuspended()
270 : {
271 0 : RemoveStateBits(NS_STATE_SVG_REDRAW_SUSPENDED);
272 :
273 0 : if (GetStateBits() & NS_STATE_SVG_DIRTY)
274 0 : nsSVGUtils::UpdateGraphic(this);
275 0 : }
276 :
277 : gfxRect
278 0 : nsSVGPathGeometryFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
279 : PRUint32 aFlags)
280 : {
281 0 : if (aToBBoxUserspace.IsSingular()) {
282 : // XXX ReportToConsole
283 0 : return gfxRect(0.0, 0.0, 0.0, 0.0);
284 : }
285 :
286 : nsRefPtr<gfxContext> context =
287 0 : new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface());
288 :
289 0 : GeneratePath(context, &aToBBoxUserspace);
290 0 : context->IdentityMatrix();
291 :
292 0 : gfxRect bbox;
293 :
294 : // Be careful when replacing the following logic to get the fill and stroke
295 : // extents independently (instead of computing the stroke extents from the
296 : // path extents). You may think that you can just use the stroke extents if
297 : // there is both a fill and a stroke. In reality it's necessary to calculate
298 : // both the fill and stroke extents, and take the union of the two. There are
299 : // two reasons for this:
300 : //
301 : // # Due to stroke dashing, in certain cases the fill extents could actually
302 : // extend outside the stroke extents.
303 : // # If the stroke is very thin, cairo won't paint any stroke, and so the
304 : // stroke bounds that it will return will be empty.
305 :
306 0 : gfxRect pathExtents = context->GetUserPathExtent();
307 :
308 : // Account for fill:
309 0 : if ((aFlags & nsSVGUtils::eBBoxIncludeFill) != 0 &&
310 : ((aFlags & nsSVGUtils::eBBoxIgnoreFillIfNone) == 0 ||
311 0 : GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
312 0 : bbox = pathExtents;
313 : }
314 :
315 : // Account for stroke:
316 0 : if ((aFlags & nsSVGUtils::eBBoxIncludeStroke) != 0 &&
317 0 : ((aFlags & nsSVGUtils::eBBoxIgnoreStrokeIfNone) == 0 || HasStroke())) {
318 : // We can't use context->GetUserStrokeExtent() since it doesn't work for
319 : // device space extents. Instead we approximate the stroke extents from
320 : // pathExtents using PathExtentsToMaxStrokeExtents.
321 0 : if (pathExtents.Width() <= 0 && pathExtents.Height() <= 0) {
322 : // We have a zero length path, but it may still have non-empty stroke
323 : // bounds depending on the value of stroke-linecap. We need to fix up
324 : // pathExtents before it can be used with PathExtentsToMaxStrokeExtents
325 : // though, because if pathExtents is empty, its position will not have
326 : // been set. Happily we can use context->GetUserStrokeExtent() to find
327 : // the center point of the extents even though it gets the extents wrong.
328 0 : SetupCairoStrokeGeometry(context);
329 0 : pathExtents.MoveTo(context->GetUserStrokeExtent().Center());
330 0 : pathExtents.SizeTo(0, 0);
331 : }
332 : bbox =
333 : bbox.Union(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents,
334 : this,
335 0 : aToBBoxUserspace));
336 : }
337 :
338 : // Account for markers:
339 0 : if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
340 0 : static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
341 :
342 0 : float strokeWidth = GetStrokeWidth();
343 0 : MarkerProperties properties = GetMarkerProperties(this);
344 :
345 0 : if (properties.MarkersExist()) {
346 0 : nsTArray<nsSVGMark> marks;
347 0 : static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks);
348 0 : PRUint32 num = marks.Length();
349 :
350 0 : if (num) {
351 0 : nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame();
352 0 : if (frame) {
353 : gfxRect mbbox =
354 : frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
355 0 : &marks[0], strokeWidth);
356 0 : bbox.UnionRect(bbox, mbbox);
357 : }
358 :
359 0 : frame = properties.GetMarkerMidFrame();
360 0 : if (frame) {
361 0 : for (PRUint32 i = 1; i < num - 1; i++) {
362 : gfxRect mbbox =
363 : frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
364 0 : &marks[i], strokeWidth);
365 0 : bbox.UnionRect(bbox, mbbox);
366 : }
367 : }
368 :
369 0 : frame = properties.GetMarkerEndFrame();
370 0 : if (frame) {
371 : gfxRect mbbox =
372 : frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
373 0 : &marks[num-1], strokeWidth);
374 0 : bbox.UnionRect(bbox, mbbox);
375 : }
376 : }
377 : }
378 : }
379 :
380 0 : return bbox;
381 : }
382 :
383 : //----------------------------------------------------------------------
384 : // nsSVGGeometryFrame methods:
385 :
386 : gfxMatrix
387 0 : nsSVGPathGeometryFrame::GetCanvasTM()
388 : {
389 0 : NS_ASSERTION(mParent, "null parent");
390 :
391 0 : nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
392 0 : nsSVGGraphicElement *content = static_cast<nsSVGGraphicElement*>(mContent);
393 :
394 0 : return content->PrependLocalTransformsTo(parent->GetCanvasTM());
395 : }
396 :
397 : //----------------------------------------------------------------------
398 : // nsSVGPathGeometryFrame methods:
399 :
400 : nsSVGPathGeometryFrame::MarkerProperties
401 0 : nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame)
402 : {
403 0 : NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
404 :
405 : MarkerProperties result;
406 0 : const nsStyleSVG *style = aFrame->GetStyleSVG();
407 : result.mMarkerStart =
408 : nsSVGEffects::GetMarkerProperty(style->mMarkerStart, aFrame,
409 0 : nsSVGEffects::MarkerBeginProperty());
410 : result.mMarkerMid =
411 : nsSVGEffects::GetMarkerProperty(style->mMarkerMid, aFrame,
412 0 : nsSVGEffects::MarkerMiddleProperty());
413 : result.mMarkerEnd =
414 : nsSVGEffects::GetMarkerProperty(style->mMarkerEnd, aFrame,
415 0 : nsSVGEffects::MarkerEndProperty());
416 : return result;
417 : }
418 :
419 : nsSVGMarkerFrame *
420 0 : nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame()
421 : {
422 0 : if (!mMarkerStart)
423 0 : return nsnull;
424 : return static_cast<nsSVGMarkerFrame *>
425 0 : (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nsnull));
426 : }
427 :
428 : nsSVGMarkerFrame *
429 0 : nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame()
430 : {
431 0 : if (!mMarkerMid)
432 0 : return nsnull;
433 : return static_cast<nsSVGMarkerFrame *>
434 0 : (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nsnull));
435 : }
436 :
437 : nsSVGMarkerFrame *
438 0 : nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame()
439 : {
440 0 : if (!mMarkerEnd)
441 0 : return nsnull;
442 : return static_cast<nsSVGMarkerFrame *>
443 0 : (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nsnull));
444 : }
445 :
446 : void
447 0 : nsSVGPathGeometryFrame::Render(nsRenderingContext *aContext)
448 : {
449 0 : gfxContext *gfx = aContext->ThebesContext();
450 :
451 0 : PRUint16 renderMode = SVGAutoRenderState::GetRenderMode(aContext);
452 :
453 0 : switch (GetStyleSVG()->mShapeRendering) {
454 : case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED:
455 : case NS_STYLE_SHAPE_RENDERING_CRISPEDGES:
456 0 : gfx->SetAntialiasMode(gfxContext::MODE_ALIASED);
457 0 : break;
458 : default:
459 0 : gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE);
460 0 : break;
461 : }
462 :
463 : /* save/restore the state so we don't screw up the xform */
464 0 : gfx->Save();
465 :
466 0 : GeneratePath(gfx);
467 :
468 0 : if (renderMode != SVGAutoRenderState::NORMAL) {
469 0 : gfx->Restore();
470 :
471 0 : if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD)
472 0 : gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
473 : else
474 0 : gfx->SetFillRule(gfxContext::FILL_RULE_WINDING);
475 :
476 0 : if (renderMode == SVGAutoRenderState::CLIP_MASK) {
477 0 : gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f));
478 0 : gfx->Fill();
479 0 : gfx->NewPath();
480 : }
481 :
482 0 : return;
483 : }
484 :
485 0 : if (SetupCairoFill(gfx)) {
486 0 : gfx->Fill();
487 : }
488 :
489 0 : if (SetupCairoStroke(gfx)) {
490 0 : gfx->Stroke();
491 : }
492 :
493 0 : gfx->NewPath();
494 :
495 0 : gfx->Restore();
496 : }
497 :
498 : void
499 0 : nsSVGPathGeometryFrame::GeneratePath(gfxContext* aContext,
500 : const gfxMatrix *aOverrideTransform)
501 : {
502 0 : gfxMatrix matrix;
503 0 : if (aOverrideTransform) {
504 0 : matrix = *aOverrideTransform;
505 : } else {
506 0 : matrix = GetCanvasTM();
507 : }
508 :
509 0 : if (matrix.IsSingular()) {
510 0 : aContext->IdentityMatrix();
511 0 : aContext->NewPath();
512 0 : return;
513 : }
514 :
515 0 : aContext->Multiply(matrix);
516 :
517 : // Hack to let SVGPathData::ConstructPath know if we have square caps:
518 0 : const nsStyleSVG* style = GetStyleSVG();
519 0 : if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) {
520 0 : aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE);
521 : }
522 :
523 0 : aContext->NewPath();
524 0 : static_cast<nsSVGPathGeometryElement*>(mContent)->ConstructPath(aContext);
525 : }
|