Skip to content

Commit

Permalink
FIXED: further subtleties with the handling of viewports and viewboxe…
Browse files Browse the repository at this point in the history
…s (this commit is MUCH MORE ROBUST than previous one, it handles more edge-cases, better)
  • Loading branch information
adamgit authored and adamgit committed Apr 14, 2013
1 parent 2c8cbb3 commit c74a10d
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 39 deletions.
54 changes: 40 additions & 14 deletions Source/DOM classes/SVG-DOM/SVGHelperUtilities.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,55 @@ +(CGAffineTransform) transformRelativeIncludingViewportForTransformableOrViewpor
|| transformableOrSVGSVGElement.viewportElement == transformableOrSVGSVGElement // ?? I don't understand: ?? if it's something other than itself, then: we simply don't need to worry about it ??
)
{
SVGElement<SVGFitToViewBox>* svgSVGElement = (SVGElement<SVGFitToViewBox>*) transformableOrSVGSVGElement;
SVGSVGElement<SVGFitToViewBox>* svgSVGElement = (SVGSVGElement<SVGFitToViewBox>*) transformableOrSVGSVGElement;

/**
Calculate the "implicit" viewport transform (caused by the <SVG> tag's possible "viewBox" attribute)
Calculate the "implicit" viewport->viewbox transform (caused by the <SVG> tag's possible "viewBox" attribute)
Also calculate the "implicit" realViewport -> svgDefaultViewport transform (caused by the user changing the external
size of the rendered SVG)
*/
SVGRect frameViewBox = svgSVGElement.viewBox;
if( SVGRectIsInitialized( frameViewBox ) )
SVGRect frameViewBox = svgSVGElement.viewBox; // the ACTUAL viewbox (may be Uninitalized if none specified in SVG file)
SVGRect frameActualViewport = svgSVGElement.viewport; // the ACTUAL viewport (dictated by the graphics engine; may be Uninitialized if the renderer has too little info to decide on a viewport at all!)
SVGRect frameRequestedViewport = svgSVGElement.requestedViewport; // the default viewport requested in the SVG source file (may be Uninitialized if no svg width or height params in original source file)

if( ! SVGRectIsInitialized(frameActualViewport))
{
/* (NB: the viewport will ALWAYS have a value: UNLESS the SVG is so crappy it has NEITHER a viewbox NOR an explicit width
and height in the root <SVG> tag.
/** We have NO VIEWPORT (renderer was presented too little info)
...but since we know we already have a viewbox, we can rely upon there being a viewport too.
Net effect: we MUST render everything at 1:1, and apply NO FURTHER TRANSFORMS
*/
SVGRect frameViewport = ((SVGSVGElement*)svgSVGElement).viewport;

CGAffineTransform translateToViewBox = CGAffineTransformMakeTranslation( -frameViewBox.x, -frameViewBox.y );
CGAffineTransform scaleToViewBox = CGAffineTransformMakeScale( frameViewport.width / frameViewBox.width, frameViewport.height / frameViewBox.height);
optionalViewportTransform = CGAffineTransformConcat( translateToViewBox, scaleToViewBox );
optionalViewportTransform = CGAffineTransformIdentity;
}
else
optionalViewportTransform = CGAffineTransformIdentity;
{
CGAffineTransform transformRealViewportToSVGViewport;
CGAffineTransform transformSVGViewportToSVGViewBox;

/** Transform part 1: from REAL viewport to EXPECTED viewport */
SVGRect viewportForViewBoxToRelateTo;
if( SVGRectIsInitialized( frameRequestedViewport ))
{
viewportForViewBoxToRelateTo = frameRequestedViewport;
transformRealViewportToSVGViewport = CGAffineTransformMakeScale( frameActualViewport.width / frameRequestedViewport.width, frameActualViewport.height / frameRequestedViewport.height);
}
else
{
viewportForViewBoxToRelateTo = frameActualViewport;
transformRealViewportToSVGViewport = CGAffineTransformIdentity;
}

/** Transform part 2: from EXPECTED viewport to internal viewBox */
if( SVGRectIsInitialized( frameViewBox ) )
{
CGAffineTransform translateToViewBox = CGAffineTransformMakeTranslation( -frameViewBox.x, -frameViewBox.y );
CGAffineTransform scaleToViewBox = CGAffineTransformMakeScale( viewportForViewBoxToRelateTo.width / frameViewBox.width, viewportForViewBoxToRelateTo.height / frameViewBox.height);
transformSVGViewportToSVGViewBox = CGAffineTransformConcat( translateToViewBox, scaleToViewBox );
}
else
transformSVGViewportToSVGViewBox = CGAffineTransformIdentity;

optionalViewportTransform = CGAffineTransformConcat( transformRealViewportToSVGViewport, transformSVGViewportToSVGViewBox );
}
}
else
{
Expand Down
8 changes: 8 additions & 0 deletions Source/DOM classes/Unported or Partial DOM/SVGSVGElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
@property (nonatomic, retain, readonly) /*FIXME: should be SVGAnimatedLength instead*/ SVGLength* height;
@property (nonatomic, retain, readonly) NSString* contentScriptType;
@property (nonatomic, retain, readonly) NSString* contentStyleType;

/**
"The position and size of the viewport (implicit or explicit) that corresponds to this ‘svg’ element. When the user agent is actually rendering the content, then the position and size values represent the actual values when rendering. The position and size values are unitless values in the coordinate system of the parent element. If no parent element exists (i.e., ‘svg’ element represents the root of the document tree), if this SVG document is embedded as part of another document (e.g., via the HTML ‘object’ element), then the position and size are unitless values in the coordinate system of the parent document. (If the parent uses CSS or XSL layout, then unitless values represent pixel units for the current CSS or XSL viewport, as described in the CSS2 specification.) If the parent element does not have a coordinate system, then the user agent should provide reasonable default values for this attribute."
*/
@property (nonatomic, readonly) SVGRect viewport;
@property (nonatomic, readonly) float pixelUnitToMillimeterX;
@property (nonatomic, readonly) float pixelUnitToMillimeterY;
Expand Down Expand Up @@ -114,4 +118,8 @@

- (SVGElement *)findFirstElementOfClass:(Class)class; /*< temporary convenience method until SVGDocument support is complete */

#pragma mark - elements REQUIRED to implement the spec but not included in SVG Spec due to bugs in the spec writing!

@property(nonatomic,readonly) SVGRect requestedViewport;

@end
42 changes: 23 additions & 19 deletions Source/DOM classes/Unported or Partial DOM/SVGSVGElement.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
#import <UIKit/UIKit.h>
#endif

@interface SVGSVGElement()
#pragma mark - elements REQUIRED to implement the spec but not included in SVG Spec due to bugs in the spec writing!
@property(nonatomic,readwrite) SVGRect requestedViewport;
@end

@implementation SVGSVGElement

@synthesize x;
Expand Down Expand Up @@ -134,16 +139,25 @@ We would need to put extra (NON STANDARD) properties on SVGDocument, for the "vi
else
self.height = [SVGLength svgLengthFromNSString:[self getAttribute:@"height"]];

/** spec has complex rules for us defining height if width is missing
TODO: implement the rules for "if height is missing"
*/
/* set the frameRequestedViewport appropriately (NB: spec doesn't allow for this but it REQUIRES it to be done and saved!) */
if( self.width != nil && self.height != nil )
{
SVGRect initialViewport = { 0, 0, [self.width pixelsValue], [self.height pixelsValue] };
self.viewport = initialViewport;
}
self.requestedViewport = SVGRectMake( 0, 0, [self.width pixelsValue], [self.height pixelsValue] );
else
self.viewport = SVGRectUninitialized();
self.requestedViewport = SVGRectUninitialized();


/**
NB: this is VERY CONFUSING due to badly written SVG Spec, but: the viewport MUST NOT be set by the parser,
it MUST ONLY be set by the "renderer" -- and the renderer MAY have decided to use a different viewport from
the one that the SVG file *implies* (e.g. if the user scales the SVG, the viewport WILL BE DIFFERENT,
by definition!
...However: the renderer will ALWAYS start with the default viewport values (that are calcualted by the parsing process)
and it makes it much cleaner and safer to implement if we have the PARSER set the viewport initially
(and the renderer will IMMEDIATELY overwrite them once the parsing is finished IFF IT NEEDS TO)
*/
self.viewport = self.requestedViewport; // renderer can/will change the .viewport, but .requestedViewport can only be set by the PARSER

if( [[self getAttribute:@"viewBox"] length] > 0 )
{
Expand All @@ -153,17 +167,7 @@ We would need to put extra (NON STANDARD) properties on SVGDocument, for the "vi
}
else
{
self.viewBox = SVGRectUninitialized(); // VERY IMPORTANT: we MUST make it clear this was never initialized, instead of saying its 0,0,0,0 !

/**
According to spec, if we have no viewBox in the source SVG, we must NOT scale-to-fit the available space.
By NOT SETTING a viewBox here, we disable all the scaling that happens later on.
If you want to scale an SVG Image, you MUST do it either by:
1. EITHER: set a viewBox *and* a viewport on the image itself
2. OR:
*/
self.viewBox = SVGRectUninitialized(); // VERY IMPORTANT: we MUST make it clear this was never initialized, instead of saying its 0,0,0,0 !
}
NSLog(@"[%@] WARNING: SVG spec says we should calculate the 'intrinsic aspect ratio'. Some badly-made SVG files work better if you do this and then post-multiply onto the specified viewBox attribute ... BUT they ALSO require that you 're-center' them inside the newly-created viewBox; and the SVG Spec DOES NOT SAY you should do that. All examples so far were authored in Inkscape, I think, so ... I think it's a serious bug in Inkscape that has tricked people into making incorrect SVG files. For example, c.f. http://en.wikipedia.org/wiki/File:BlankMap-World6-Equirectangular.svg", [self class]);
//osx logging
Expand Down
9 changes: 3 additions & 6 deletions Source/SVGKImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ -(void)setSize:(CGSize)newSize
{
self.internalSizeThatWasSetExplicitlyByUser = newSize;

if( ! SVGRectIsInitialized(self.DOMTree.viewBox) )
if( ! SVGRectIsInitialized(self.DOMTree.viewBox) && !SVGRectIsInitialized( self.DOMTree.viewport ) )
{
NSLog(@"[%@] WARNING: you have set an explicit image size, but your SVG file has no viewBox. This means the image will NOT BE SCALED - either add a viewBox to your SVG source file -- or: use the .scale method on this class (SVGKImage) instead to scale by desired amount", [self class]);
NSLog(@"[%@] WARNING: you have set an explicit image size, but your SVG file has no explicit width or height AND no viewBox. This means the image will NOT BE SCALED - either add a viewBox to your SVG source file, or add an explicit svg width and height -- or: use the .scale method on this class (SVGKImage) instead to scale by desired amount", [self class]);
}

/** "size" is part of SVGKImage, not the SVG spec; we need to update the SVG spec size too (aka the ViewPort)
Expand All @@ -358,10 +358,7 @@ -(void)setSize:(CGSize)newSize
You can always re-calculate the "original" viewport by looking at self.DOMTree.width and self.DOMTree.height
*/
SVGRect newViewport = self.DOMTree.viewport;
newViewport.width = newSize.width;
newViewport.height = newSize.height;
self.DOMTree.viewport = newViewport; // implicitly resizes all the internal rendering of the SVG
self.DOMTree.viewport = SVGRectMake(0,0,newSize.width,newSize.height); // implicitly resizes all the internal rendering of the SVG

/** invalidate all cached data that's dependent upon SVG's size */
self.CALayerTree = nil; // invalidate the cached copy
Expand Down

0 comments on commit c74a10d

Please sign in to comment.