Files
libreoffice/svgio/source/svgreader/svgimagenode.cxx
Armin Le Grand 080e5f2f24 SVGIO: tdf#97383: Keep ratio in embedded images...
... when height and width are different

SVG specifies embedded graphics to keep their aspect ratio (thanks to Regina for pointing out).
To implement this for Office SVG import, I had to correctly set the graphic data unit dimensions to make the existing SvgAspectRatio mapping work.
All together this now simplifies this method and packs all in clean linear transformations in the created stack of drwainglayer primitives.

Change-Id: Id80c2f74ffb148085085b2c9627bc31bc15bdee5
Reviewed-on: https://gerrit.libreoffice.org/21830
Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
Tested-by: Armin Le Grand <Armin.Le.Grand@cib.de>
2016-02-05 09:24:44 +00:00

365 lines
14 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <svgio/svgreader/svgimagenode.hxx>
#include <svgio/svgreader/svgdocument.hxx>
#include <sax/tools/converter.hxx>
#include <tools/stream.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/graphicfilter.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
#include <drawinglayer/primitive2d/groupprimitive2d.hxx>
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <rtl/uri.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
namespace svgio
{
namespace svgreader
{
SvgImageNode::SvgImageNode(
SvgDocument& rDocument,
SvgNode* pParent)
: SvgNode(SVGTokenRect, rDocument, pParent),
maSvgStyleAttributes(*this),
maSvgAspectRatio(),
mpaTransform(nullptr),
maX(0),
maY(0),
maWidth(0),
maHeight(0),
maXLink(),
maUrl(),
maMimeType(),
maData()
{
}
SvgImageNode::~SvgImageNode()
{
delete mpaTransform;
}
const SvgStyleAttributes* SvgImageNode::getSvgStyleAttributes() const
{
return checkForCssStyle("image", maSvgStyleAttributes);
}
void SvgImageNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent)
{
// call parent
SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
// read style attributes
maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent, false);
// parse own
switch(aSVGToken)
{
case SVGTokenStyle:
{
readLocalCssStyle(aContent);
break;
}
case SVGTokenPreserveAspectRatio:
{
setSvgAspectRatio(readSvgAspectRatio(aContent));
break;
}
case SVGTokenTransform:
{
const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
if(!aMatrix.isIdentity())
{
setTransform(&aMatrix);
}
break;
}
case SVGTokenX:
{
SvgNumber aNum;
if(readSingleNumber(aContent, aNum))
{
setX(aNum);
}
break;
}
case SVGTokenY:
{
SvgNumber aNum;
if(readSingleNumber(aContent, aNum))
{
setY(aNum);
}
break;
}
case SVGTokenWidth:
{
SvgNumber aNum;
if(readSingleNumber(aContent, aNum))
{
if(aNum.isPositive())
{
setWidth(aNum);
}
}
break;
}
case SVGTokenHeight:
{
SvgNumber aNum;
if(readSingleNumber(aContent, aNum))
{
if(aNum.isPositive())
{
setHeight(aNum);
}
}
break;
}
case SVGTokenXlinkHref:
{
const sal_Int32 nLen(aContent.getLength());
if(nLen)
{
readImageLink(aContent, maXLink, maUrl, maMimeType, maData);
}
break;
}
default:
{
break;
}
}
}
void extractFromGraphic(
const Graphic& rGraphic,
drawinglayer::primitive2d::Primitive2DContainer& rEmbedded,
basegfx::B2DRange& rViewBox,
BitmapEx& rBitmapEx)
{
if(GRAPHIC_BITMAP == rGraphic.GetType())
{
if(rGraphic.getSvgData().get())
{
// embedded Svg
rEmbedded = rGraphic.getSvgData()->getPrimitive2DSequence();
// fill aViewBox
rViewBox = rGraphic.getSvgData()->getRange();
}
else
{
// get bitmap
rBitmapEx = rGraphic.GetBitmapEx();
}
}
else
{
// evtl. convert to bitmap
rBitmapEx = rGraphic.GetBitmapEx();
}
}
void SvgImageNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool /*bReferenced*/) const
{
// get size range and create path
const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
if(pStyle && getWidth().isSet() && getHeight().isSet())
{
const double fWidth(getWidth().solve(*this, xcoordinate));
const double fHeight(getHeight().solve(*this, ycoordinate));
if(fWidth > 0.0 && fHeight > 0.0)
{
BitmapEx aBitmapEx;
drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
// prepare Target and ViewBox for evtl. AspectRatio mappings
const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
const basegfx::B2DRange aTarget(fX, fY, fX + fWidth, fY + fHeight);
basegfx::B2DRange aViewBox(aTarget);
if(!maMimeType.isEmpty() && !maData.isEmpty())
{
// use embedded base64 encoded data
css::uno::Sequence< sal_Int8 > aPass;
::sax::Converter::decodeBase64(aPass, maData);
if(aPass.hasElements())
{
SvMemoryStream aStream(aPass.getArray(), aPass.getLength(), StreamMode::READ);
Graphic aGraphic;
if(GRFILTER_OK == GraphicFilter::GetGraphicFilter().ImportGraphic(
aGraphic,
OUString(),
aStream))
{
extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
}
}
}
else if(!maUrl.isEmpty())
{
const OUString& rPath = getDocument().getAbsolutePath();
OUString aAbsUrl;
try {
aAbsUrl = rtl::Uri::convertRelToAbs(rPath, maUrl);
} catch (rtl::MalformedUriException & e) {
SAL_WARN(
"svg",
"caught rtl::MalformedUriException \""
<< e.getMessage() << "\"");
}
if (!aAbsUrl.isEmpty())
{
SvFileStream aStream(aAbsUrl, STREAM_STD_READ);
Graphic aGraphic;
if(GRFILTER_OK == GraphicFilter::GetGraphicFilter().ImportGraphic(
aGraphic,
aAbsUrl,
aStream))
{
extractFromGraphic(aGraphic, aNewTarget, aViewBox, aBitmapEx);
}
}
}
else if(!maXLink.isEmpty())
{
const SvgNode* mpXLink = getDocument().findSvgNodeById(maXLink);
if(mpXLink && Display_none != mpXLink->getDisplay())
{
mpXLink->decomposeSvgNode(aNewTarget, true);
if(!aNewTarget.empty())
{
aViewBox = aNewTarget.getB2DRange(drawinglayer::geometry::ViewInformation2D());
}
}
}
if(!aBitmapEx.IsEmpty() && 0 != aBitmapEx.GetSizePixel().Width() && 0 != aBitmapEx.GetSizePixel().Height())
{
// calculate centered unit size
const double fAspectRatio = (double)aBitmapEx.GetSizePixel().Width() / (double)aBitmapEx.GetSizePixel().Height();
if(basegfx::fTools::equal(fAspectRatio, 0.0))
{
// use unit range
aViewBox = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0);
}
else if(basegfx::fTools::more(fAspectRatio, 0.0))
{
// width bigger height
const double fHalfHeight((1.0 / fAspectRatio) * 0.5);
aViewBox = basegfx::B2DRange(
0.0,
0.5 - fHalfHeight,
1.0,
0.5 + fHalfHeight);
}
else
{
// height bigger width
const double fHalfWidth(fAspectRatio * 0.5);
aViewBox = basegfx::B2DRange(
0.5 - fHalfWidth,
0.0,
0.5 + fHalfWidth,
1.0);
}
// create content from created bitmap, use calculated unit range size
// as transformation to map the picture data correctly
aNewTarget.resize(1);
aNewTarget[0] = new drawinglayer::primitive2d::BitmapPrimitive2D(
aBitmapEx,
basegfx::tools::createScaleTranslateB2DHomMatrix(
aViewBox.getRange(),
aViewBox.getMinimum()));
}
if(!aNewTarget.empty())
{
if(aTarget.equal(aViewBox))
{
// just add to rTarget
rTarget.append(aNewTarget);
}
else
{
// create mapping
const SvgAspectRatio& rRatio = getSvgAspectRatio();
// even when ratio is not set, use the defaults
// let mapping be created from SvgAspectRatio
const basegfx::B2DHomMatrix aEmbeddingTransform(rRatio.createMapping(aTarget, aViewBox));
if(!aEmbeddingTransform.isIdentity())
{
const drawinglayer::primitive2d::Primitive2DReference xRef(
new drawinglayer::primitive2d::TransformPrimitive2D(
aEmbeddingTransform,
aNewTarget));
aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xRef };
}
if(!rRatio.isMeetOrSlice())
{
// need to embed in MaskPrimitive2D to ensure clipping
const drawinglayer::primitive2d::Primitive2DReference xMask(
new drawinglayer::primitive2d::MaskPrimitive2D(
basegfx::B2DPolyPolygon(
basegfx::tools::createPolygonFromRect(aTarget)),
aNewTarget));
aNewTarget = drawinglayer::primitive2d::Primitive2DContainer { xMask };
}
// embed and add to rTarget, take local extra-transform into account
pStyle->add_postProcess(rTarget, aNewTarget, getTransform());
}
}
}
}
}
} // end of namespace svgreader
} // end of namespace svgio
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */